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.
This commit is contained in:
2026-03-06 17:56:44 +02:00
parent e2aee57bd9
commit 5352b67887

View File

@@ -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::<Option<Vec<u8>>>();
std::thread::spawn(move || {
let result = (|| -> Option<Vec<u8>> {
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();
});
}
{