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 adw::prelude::*;
|
||||||
|
use gtk::glib;
|
||||||
use crate::app::AppState;
|
use crate::app::AppState;
|
||||||
|
|
||||||
pub fn build_resize_page(state: &AppState) -> adw::NavigationPage {
|
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_top(4)
|
||||||
.margin_bottom(4)
|
.margin_bottom(4)
|
||||||
.build();
|
.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(&drawing);
|
||||||
preview_box.append(&preview_label);
|
preview_box.append(&preview_label);
|
||||||
|
preview_box.append(&thumb_picture);
|
||||||
|
preview_box.append(&thumb_label);
|
||||||
preview_group.add(&preview_box);
|
preview_group.add(&preview_box);
|
||||||
content.append(&preview_group);
|
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)
|
// Basic orientation adjustments (folded into resize step per design doc)
|
||||||
let orientation_group = adw::PreferencesGroup::builder()
|
let orientation_group = adw::PreferencesGroup::builder()
|
||||||
.title("Orientation")
|
.title("Orientation")
|
||||||
@@ -425,22 +528,26 @@ pub fn build_resize_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
let jc = state.job_config.clone();
|
let jc = state.job_config.clone();
|
||||||
let pw = preview_width.clone();
|
let pw = preview_width.clone();
|
||||||
let draw = drawing.clone();
|
let draw = drawing.clone();
|
||||||
|
let rt = render_thumb.clone();
|
||||||
width_row.connect_value_notify(move |row| {
|
width_row.connect_value_notify(move |row| {
|
||||||
let val = row.value() as u32;
|
let val = row.value() as u32;
|
||||||
jc.borrow_mut().resize_width = val;
|
jc.borrow_mut().resize_width = val;
|
||||||
*pw.borrow_mut() = val;
|
*pw.borrow_mut() = val;
|
||||||
draw.queue_draw();
|
draw.queue_draw();
|
||||||
|
rt();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let jc = state.job_config.clone();
|
let jc = state.job_config.clone();
|
||||||
let ph = preview_height.clone();
|
let ph = preview_height.clone();
|
||||||
let draw = drawing.clone();
|
let draw = drawing.clone();
|
||||||
|
let rt = render_thumb.clone();
|
||||||
height_row.connect_value_notify(move |row| {
|
height_row.connect_value_notify(move |row| {
|
||||||
let val = row.value() as u32;
|
let val = row.value() as u32;
|
||||||
jc.borrow_mut().resize_height = val;
|
jc.borrow_mut().resize_height = val;
|
||||||
*ph.borrow_mut() = val;
|
*ph.borrow_mut() = val;
|
||||||
draw.queue_draw();
|
draw.queue_draw();
|
||||||
|
rt();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user