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:
@@ -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();
|
||||
});
|
||||
}
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user