From 5352b67887f67920b424adf4064b0b3074e6e488 Mon Sep 17 00:00:00 2001 From: lashman Date: Fri, 6 Mar 2026 17:56:44 +0200 Subject: [PATCH] Add thumbnail re-render preview to resize step Shows an actual resized thumbnail alongside the dimension rectangle visualization. Updates live when width/height values change. --- pixstrip-gtk/src/steps/step_resize.rs | 107 ++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/pixstrip-gtk/src/steps/step_resize.rs b/pixstrip-gtk/src/steps/step_resize.rs index 108ab3e..477572a 100644 --- a/pixstrip-gtk/src/steps/step_resize.rs +++ b/pixstrip-gtk/src/steps/step_resize.rs @@ -1,4 +1,5 @@ use adw::prelude::*; +use gtk::glib; use crate::app::AppState; pub fn build_resize_page(state: &AppState) -> adw::NavigationPage { @@ -326,11 +327,113 @@ pub fn build_resize_page(state: &AppState) -> adw::NavigationPage { .margin_top(4) .margin_bottom(4) .build(); + // Thumbnail re-render preview + let thumb_picture = gtk::Picture::builder() + .content_fit(gtk::ContentFit::Contain) + .height_request(120) + .halign(gtk::Align::Center) + .build(); + thumb_picture.update_property(&[ + gtk::accessible::Property::Label("Thumbnail preview of resized image"), + ]); + + let thumb_label = gtk::Label::builder() + .label("Resized thumbnail preview") + .css_classes(["dim-label", "caption"]) + .halign(gtk::Align::Center) + .build(); + preview_box.append(&drawing); preview_box.append(&preview_label); + preview_box.append(&thumb_picture); + preview_box.append(&thumb_label); preview_group.add(&preview_box); content.append(&preview_group); + // Shared thumbnail re-render closure + let render_thumb = { + let lf = loaded_files.clone(); + let pw = preview_width.clone(); + let ph = preview_height.clone(); + let pic = thumb_picture.clone(); + let lbl = thumb_label.clone(); + std::rc::Rc::new(move || { + let files = lf.borrow(); + let Some(first) = files.first() else { + pic.set_paintable(gtk::gdk::Paintable::NONE); + lbl.set_label("Add images to see resize preview"); + return; + }; + let tw = *pw.borrow(); + let th = *ph.borrow(); + if tw == 0 && th == 0 { + pic.set_paintable(gtk::gdk::Paintable::NONE); + lbl.set_label("Set dimensions to preview"); + return; + } + let path = first.clone(); + let pic = pic.clone(); + let lbl = lbl.clone(); + let (tx, rx) = std::sync::mpsc::channel::>>(); + std::thread::spawn(move || { + let result = (|| -> Option> { + let img = image::open(&path).ok()?; + let target_w = if tw > 0 { tw } else { img.width() }; + let target_h = if th > 0 { + th + } else { + let scale = target_w as f64 / img.width() as f64; + (img.height() as f64 * scale).round() as u32 + }; + let resized = img.resize( + target_w.min(400), + target_h.min(400), + image::imageops::FilterType::Lanczos3, + ); + let mut buf = Vec::new(); + resized.write_to( + &mut std::io::Cursor::new(&mut buf), + image::ImageFormat::Png, + ).ok()?; + Some(buf) + })(); + let _ = tx.send(result); + }); + glib::timeout_add_local(std::time::Duration::from_millis(100), move || { + match rx.try_recv() { + Ok(Some(bytes)) => { + let gbytes = glib::Bytes::from(&bytes); + let stream = gtk::gio::MemoryInputStream::from_bytes(&gbytes); + if let Ok(pb) = gtk::gdk_pixbuf::Pixbuf::from_stream( + &stream, + gtk::gio::Cancellable::NONE, + ) { + let texture = gtk::gdk::Texture::for_pixbuf(&pb); + pic.set_paintable(Some(&texture)); + lbl.set_label(&format!( + "Preview at {}x{}", + pb.width(), + pb.height() + )); + } + glib::ControlFlow::Break + } + Ok(None) => { + lbl.set_label("Preview unavailable"); + glib::ControlFlow::Break + } + Err(std::sync::mpsc::TryRecvError::Empty) => glib::ControlFlow::Continue, + Err(_) => glib::ControlFlow::Break, + } + }); + }) + }; + // Trigger initial render + { + let rt = render_thumb.clone(); + glib::idle_add_local_once(move || rt()); + } + // Basic orientation adjustments (folded into resize step per design doc) let orientation_group = adw::PreferencesGroup::builder() .title("Orientation") @@ -425,22 +528,26 @@ pub fn build_resize_page(state: &AppState) -> adw::NavigationPage { let jc = state.job_config.clone(); let pw = preview_width.clone(); let draw = drawing.clone(); + let rt = render_thumb.clone(); width_row.connect_value_notify(move |row| { let val = row.value() as u32; jc.borrow_mut().resize_width = val; *pw.borrow_mut() = val; draw.queue_draw(); + rt(); }); } { let jc = state.job_config.clone(); let ph = preview_height.clone(); let draw = drawing.clone(); + let rt = render_thumb.clone(); height_row.connect_value_notify(move |row| { let val = row.value() as u32; jc.borrow_mut().resize_height = val; *ph.borrow_mut() = val; draw.queue_draw(); + rt(); }); } {