From 07d47b6b3fc9be663a62e137f73e1bcc6822c828 Mon Sep 17 00:00:00 2001 From: lashman Date: Fri, 6 Mar 2026 13:38:12 +0200 Subject: [PATCH] Replace watermark position dropdown with visual 3x3 grid - 9-point position grid using ToggleButtons in a Grid layout - Visual feedback with radio-checked/radio-symbolic icons - Position label below grid shows current selection name - Much better UX than ComboRow for spatial position selection --- pixstrip-gtk/src/steps/step_watermark.rs | 84 +++++++++++++++++++++--- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/pixstrip-gtk/src/steps/step_watermark.rs b/pixstrip-gtk/src/steps/step_watermark.rs index 64c2bb3..e0e71d5 100644 --- a/pixstrip-gtk/src/steps/step_watermark.rs +++ b/pixstrip-gtk/src/steps/step_watermark.rs @@ -94,7 +94,7 @@ pub fn build_watermark_page(state: &AppState) -> adw::NavigationPage { image_group.add(&image_path_row); content.append(&image_group); - // Position grid (9-point) + // Visual 9-point position grid let position_group = adw::PreferencesGroup::builder() .title("Position") .description("Choose where the watermark appears on the image") @@ -106,14 +106,62 @@ pub fn build_watermark_page(state: &AppState) -> adw::NavigationPage { "Bottom Left", "Bottom Center", "Bottom Right", ]; - let position_row = adw::ComboRow::builder() - .title("Watermark Position") + // Build a 3x3 grid of toggle buttons + let grid = gtk::Grid::builder() + .row_spacing(4) + .column_spacing(4) + .halign(gtk::Align::Center) + .margin_top(8) + .margin_bottom(8) .build(); - let position_model = gtk::StringList::new(&position_names); - position_row.set_model(Some(&position_model)); - position_row.set_selected(cfg.watermark_position); - position_group.add(&position_row); + // Create a visual "image" area as background context + let grid_frame = gtk::Frame::builder() + .halign(gtk::Align::Center) + .build(); + grid_frame.set_child(Some(&grid)); + + let mut first_button: Option = None; + let buttons: Vec = position_names.iter().enumerate().map(|(i, name)| { + let btn = gtk::ToggleButton::builder() + .tooltip_text(*name) + .width_request(48) + .height_request(48) + .build(); + + // Use a dot icon for each position + let icon = if i == cfg.watermark_position as usize { + "radio-checked-symbolic" + } else { + "radio-symbolic" + }; + btn.set_child(Some(>k::Image::from_icon_name(icon))); + btn.set_active(i == cfg.watermark_position as usize); + + if let Some(ref first) = first_button { + btn.set_group(Some(first)); + } else { + first_button = Some(btn.clone()); + } + + let row = i / 3; + let col = i % 3; + grid.attach(&btn, col as i32, row as i32, 1, 1); + + btn + }).collect(); + + position_group.add(&grid_frame); + + // Position label showing current selection + let position_label = gtk::Label::builder() + .label(position_names[cfg.watermark_position as usize]) + .css_classes(["dim-label"]) + .halign(gtk::Align::Center) + .margin_bottom(4) + .build(); + position_group.add(&position_label); + content.append(&position_group); // Advanced options @@ -200,10 +248,26 @@ pub fn build_watermark_page(state: &AppState) -> adw::NavigationPage { jc.borrow_mut().watermark_font_size = row.value() as f32; }); } - { + // Wire position grid buttons + for (i, btn) in buttons.iter().enumerate() { let jc = state.job_config.clone(); - position_row.connect_selected_notify(move |row| { - jc.borrow_mut().watermark_position = row.selected(); + let label = position_label.clone(); + let names = position_names; + let all_buttons = buttons.clone(); + btn.connect_toggled(move |b| { + if b.is_active() { + jc.borrow_mut().watermark_position = i as u32; + label.set_label(names[i]); + // Update icons + for (j, other) in all_buttons.iter().enumerate() { + let icon_name = if j == i { + "radio-checked-symbolic" + } else { + "radio-symbolic" + }; + other.set_child(Some(>k::Image::from_icon_name(icon_name))); + } + } }); } {