Improve UX, add popover tour, metadata, and hicolor icons
- Redesign tutorial tour from modal dialogs to popovers pointing at actual UI elements - Add beginner-friendly improvements: help buttons, tooltips, welcome wizard enhancements - Add AppStream metainfo with screenshots, branding, categories, keywords, provides - Update desktop file with GTK category and SingleMainWindow - Add hicolor icon theme with all sizes (16-512px) - Fix debounce SourceId panic in rename step - Various step UI improvements and bug fixes
This commit is contained in:
@@ -38,6 +38,7 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
|
||||
.title("Enable Compression")
|
||||
.subtitle("Reduce file size with quality control")
|
||||
.active(cfg.compress_enabled)
|
||||
.tooltip_text("Toggle compression on or off")
|
||||
.build();
|
||||
|
||||
let enable_group = adw::PreferencesGroup::new();
|
||||
@@ -234,6 +235,7 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
|
||||
let compressed_pixbuf: Rc<RefCell<Option<gtk::gdk_pixbuf::Pixbuf>>> = Rc::new(RefCell::new(None));
|
||||
let divider_dragging = Rc::new(Cell::new(false));
|
||||
let image_dragging = Rc::new(Cell::new(false));
|
||||
let divider_hint_visible = Rc::new(Cell::new(true));
|
||||
|
||||
// Pan state for cover-fill preview
|
||||
let pan_x: Rc<Cell<f64>> = Rc::new(Cell::new(0.0));
|
||||
@@ -256,6 +258,17 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
|
||||
gtk::accessible::Property::Label("Compression quality comparison. Drag the vertical divider to compare original and compressed image. Drag elsewhere to pan."),
|
||||
]);
|
||||
|
||||
// Hint label shown over the preview until the user first interacts with the divider
|
||||
let divider_hint_label = gtk::Label::builder()
|
||||
.label("Drag the divider to compare before and after")
|
||||
.css_classes(["dim-label", "caption"])
|
||||
.halign(gtk::Align::Center)
|
||||
.valign(gtk::Align::Center)
|
||||
.build();
|
||||
divider_hint_label.update_property(&[
|
||||
gtk::accessible::Property::Label("Hint: drag the divider to compare before and after compression"),
|
||||
]);
|
||||
|
||||
// Draw function - cover fill with pan support
|
||||
{
|
||||
let dp = divider_pos.clone();
|
||||
@@ -376,12 +389,19 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
|
||||
let dspy = drag_start_pan_y.clone();
|
||||
let px = pan_x.clone();
|
||||
let py = pan_y.clone();
|
||||
let hint_vis = divider_hint_visible.clone();
|
||||
let hint_lbl = divider_hint_label.clone();
|
||||
drag_gesture.connect_drag_begin(move |_, x, _| {
|
||||
let w = drawing.width() as f64;
|
||||
let current = *dp.borrow() * w;
|
||||
if (x - current).abs() < 30.0 {
|
||||
dd.set(true);
|
||||
id.set(false);
|
||||
// Hide the hint on first divider interaction
|
||||
if hint_vis.get() {
|
||||
hint_vis.set(false);
|
||||
hint_lbl.set_visible(false);
|
||||
}
|
||||
} else {
|
||||
dd.set(false);
|
||||
id.set(true);
|
||||
@@ -437,10 +457,62 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
|
||||
}
|
||||
preview_drawing.add_controller(drag_gesture);
|
||||
|
||||
// Keyboard support for divider: Left/Right to move divider, Space to reset to center
|
||||
{
|
||||
let dp = divider_pos.clone();
|
||||
let drawing = preview_drawing.clone();
|
||||
let hint_vis = divider_hint_visible.clone();
|
||||
let hint_lbl = divider_hint_label.clone();
|
||||
let key = gtk::EventControllerKey::new();
|
||||
key.connect_key_pressed(move |_, keyval, _, _| {
|
||||
let step = 0.02;
|
||||
match keyval {
|
||||
gtk::gdk::Key::Left => {
|
||||
let new_pos = (*dp.borrow() - step).clamp(0.05, 0.95);
|
||||
*dp.borrow_mut() = new_pos;
|
||||
drawing.queue_draw();
|
||||
if hint_vis.get() {
|
||||
hint_vis.set(false);
|
||||
hint_lbl.set_visible(false);
|
||||
}
|
||||
return gtk::glib::Propagation::Stop;
|
||||
}
|
||||
gtk::gdk::Key::Right => {
|
||||
let new_pos = (*dp.borrow() + step).clamp(0.05, 0.95);
|
||||
*dp.borrow_mut() = new_pos;
|
||||
drawing.queue_draw();
|
||||
if hint_vis.get() {
|
||||
hint_vis.set(false);
|
||||
hint_lbl.set_visible(false);
|
||||
}
|
||||
return gtk::glib::Propagation::Stop;
|
||||
}
|
||||
gtk::gdk::Key::space => {
|
||||
*dp.borrow_mut() = 0.5;
|
||||
drawing.queue_draw();
|
||||
if hint_vis.get() {
|
||||
hint_vis.set(false);
|
||||
hint_lbl.set_visible(false);
|
||||
}
|
||||
return gtk::glib::Propagation::Stop;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
gtk::glib::Propagation::Proceed
|
||||
});
|
||||
preview_drawing.set_focusable(true);
|
||||
preview_drawing.add_controller(key);
|
||||
}
|
||||
|
||||
let preview_overlay = gtk::Overlay::builder()
|
||||
.child(&preview_drawing)
|
||||
.build();
|
||||
preview_overlay.add_overlay(÷r_hint_label);
|
||||
|
||||
let preview_frame = gtk::Frame::builder()
|
||||
.halign(gtk::Align::Fill)
|
||||
.build();
|
||||
preview_frame.set_child(Some(&preview_drawing));
|
||||
preview_frame.set_child(Some(&preview_overlay));
|
||||
|
||||
preview_group.add(&size_box);
|
||||
preview_group.add(&preview_frame);
|
||||
@@ -480,11 +552,22 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
|
||||
frame.add_css_class("accent");
|
||||
}
|
||||
|
||||
let file_name = files[i].file_name().and_then(|n| n.to_str()).unwrap_or("image");
|
||||
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"))
|
||||
.tooltip_text(file_name)
|
||||
.build();
|
||||
let selected_label = if i == 0 { "currently selected" } else { "" };
|
||||
btn.update_property(&[
|
||||
gtk::accessible::Property::Label(
|
||||
&if selected_label.is_empty() {
|
||||
format!("Preview thumbnail: {}", file_name)
|
||||
} else {
|
||||
format!("Preview thumbnail: {} ({})", file_name, selected_label)
|
||||
}
|
||||
),
|
||||
]);
|
||||
|
||||
thumb_box.append(&btn);
|
||||
}
|
||||
@@ -816,7 +899,7 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
|
||||
.child(&scrolled)
|
||||
.build();
|
||||
|
||||
// On page map: refresh thumbnail strip, preview, and show/hide per-format rows
|
||||
// On page map: sync enable toggle, refresh thumbnail strip, preview, and show/hide per-format rows
|
||||
{
|
||||
let up = update_preview.clone();
|
||||
let jc = state.job_config.clone();
|
||||
@@ -832,7 +915,10 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
|
||||
let ts = thumb_scrolled.clone();
|
||||
let pidx = preview_index.clone();
|
||||
let up2 = update_preview.clone();
|
||||
let er = enable_row.clone();
|
||||
page.connect_map(move |_| {
|
||||
let enabled = jc.borrow().compress_enabled;
|
||||
er.set_active(enabled);
|
||||
// Rebuild thumbnail strip from current file list
|
||||
while let Some(child) = tb.first_child() {
|
||||
tb.remove(&child);
|
||||
@@ -856,11 +942,22 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
|
||||
let up_c = up2.clone();
|
||||
let tb_c = tb.clone();
|
||||
let current_idx = i;
|
||||
let file_name = files[i].file_name().and_then(|n| n.to_str()).unwrap_or("image");
|
||||
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"))
|
||||
.tooltip_text(file_name)
|
||||
.build();
|
||||
let is_selected = i == *pidx.borrow();
|
||||
btn.update_property(&[
|
||||
gtk::accessible::Property::Label(
|
||||
&if is_selected {
|
||||
format!("Preview thumbnail: {} (currently selected)", file_name)
|
||||
} else {
|
||||
format!("Preview thumbnail: {}", file_name)
|
||||
}
|
||||
),
|
||||
]);
|
||||
btn.connect_clicked(move |_| {
|
||||
*pidx_c.borrow_mut() = current_idx;
|
||||
up_c(true);
|
||||
|
||||
Reference in New Issue
Block a user