Add thumbnail selection for compression and watermark previews

This commit is contained in:
2026-03-06 15:59:44 +02:00
parent 1dc3abf691
commit 84bf287851
2 changed files with 142 additions and 2 deletions

View File

@@ -276,6 +276,53 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
preview_group.add(&size_box);
preview_group.add(&preview_frame);
// Thumbnail strip for selecting preview image
let thumb_scrolled = gtk::ScrolledWindow::builder()
.hscrollbar_policy(gtk::PolicyType::Automatic)
.vscrollbar_policy(gtk::PolicyType::Never)
.max_content_height(60)
.build();
let thumb_box = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.spacing(4)
.margin_top(4)
.margin_bottom(4)
.halign(gtk::Align::Center)
.build();
thumb_scrolled.set_child(Some(&thumb_box));
let preview_index: Rc<RefCell<usize>> = Rc::new(RefCell::new(0));
// Populate thumbnails from loaded files
{
let files = state.loaded_files.borrow();
let max_thumbs = files.len().min(10); // Show at most 10 thumbnails
for i in 0..max_thumbs {
let pic = gtk::Picture::builder()
.content_fit(gtk::ContentFit::Cover)
.width_request(50)
.height_request(50)
.build();
pic.set_filename(Some(&files[i]));
let frame = gtk::Frame::builder()
.child(&pic)
.build();
if i == 0 {
frame.add_css_class("accent");
}
let btn = gtk::Button::builder()
.child(&frame)
.has_frame(false)
.tooltip_text(files[i].file_name().and_then(|n| n.to_str()).unwrap_or("image"))
.build();
thumb_box.append(&btn);
}
thumb_scrolled.set_visible(max_thumbs > 1);
}
// "No image loaded" placeholder
let no_image_label = gtk::Label::builder()
.label("Add images first to see compression preview")
@@ -284,6 +331,7 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
.margin_top(4)
.build();
preview_group.add(&no_image_label);
preview_group.add(&thumb_scrolled);
content.append(&preview_group);
@@ -371,6 +419,7 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
let comp_label = compressed_size_label.clone();
let no_img_label = no_image_label.clone();
let jc = state.job_config.clone();
let pidx = preview_index.clone();
Rc::new(move || {
let loaded = files.borrow();
@@ -380,8 +429,9 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
}
no_img_label.set_visible(false);
// Pick the first image as sample
let sample_path = loaded[0].clone();
// Pick the selected preview image
let idx = *pidx.borrow();
let sample_path = loaded.get(idx).cloned().unwrap_or_else(|| loaded[0].clone());
let cfg = jc.borrow();
let preset = cfg.quality_preset;
drop(cfg);
@@ -449,6 +499,42 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
})
};
// Wire thumbnail buttons to switch preview image
{
let mut child = thumb_box.first_child();
let mut idx = 0usize;
while let Some(widget) = child {
if let Some(btn) = widget.downcast_ref::<gtk::Button>() {
let pidx = preview_index.clone();
let up = update_preview.clone();
let tb = thumb_box.clone();
let current_idx = idx;
btn.connect_clicked(move |_| {
*pidx.borrow_mut() = current_idx;
up();
// Update highlight on thumbnails
let mut c = tb.first_child();
let mut j = 0usize;
while let Some(w) = c {
if let Some(b) = w.downcast_ref::<gtk::Button>() {
if let Some(f) = b.child().and_then(|c| c.downcast::<gtk::Frame>().ok()) {
if j == current_idx {
f.add_css_class("accent");
} else {
f.remove_css_class("accent");
}
}
}
c = w.next_sibling();
j += 1;
}
});
idx += 1;
}
child = widget.next_sibling();
}
}
// Trigger initial preview load
{
let up = update_preview.clone();

View File

@@ -237,6 +237,59 @@ pub fn build_watermark_page(state: &AppState) -> adw::NavigationPage {
preview_picture.set_visible(has_files);
}
// Thumbnail strip for selecting preview image
let wm_thumb_box = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.spacing(4)
.halign(gtk::Align::Center)
.margin_top(4)
.build();
{
let files = state.loaded_files.borrow();
let max_thumbs = files.len().min(10);
for i in 0..max_thumbs {
let pic = gtk::Picture::builder()
.content_fit(gtk::ContentFit::Cover)
.width_request(40)
.height_request(40)
.build();
pic.set_filename(Some(&files[i]));
let frame = gtk::Frame::builder()
.child(&pic)
.build();
if i == 0 { frame.add_css_class("accent"); }
let btn = gtk::Button::builder()
.child(&frame)
.has_frame(false)
.tooltip_text(files[i].file_name().and_then(|n| n.to_str()).unwrap_or("image"))
.build();
let pp = preview_picture.clone();
let path = files[i].clone();
let tb = wm_thumb_box.clone();
let current_idx = i;
btn.connect_clicked(move |_| {
pp.set_filename(Some(&path));
let mut c = tb.first_child();
let mut j = 0usize;
while let Some(w) = c {
if let Some(b) = w.downcast_ref::<gtk::Button>() {
if let Some(f) = b.child().and_then(|c| c.downcast::<gtk::Frame>().ok()) {
if j == current_idx { f.add_css_class("accent"); }
else { f.remove_css_class("accent"); }
}
}
c = w.next_sibling();
j += 1;
}
});
wm_thumb_box.append(&btn);
}
wm_thumb_box.set_visible(max_thumbs > 1);
}
let preview_stack = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(4)
@@ -244,6 +297,7 @@ pub fn build_watermark_page(state: &AppState) -> adw::NavigationPage {
.margin_bottom(8)
.build();
preview_stack.append(&preview_overlay);
preview_stack.append(&wm_thumb_box);
preview_stack.append(&no_preview_label);
preview_group.add(&preview_stack);