diff --git a/crates/ui-egui/src/panels/dialogs.rs b/crates/ui-egui/src/panels/dialogs.rs index a90b4bb4..32bc8141 100644 --- a/crates/ui-egui/src/panels/dialogs.rs +++ b/crates/ui-egui/src/panels/dialogs.rs @@ -521,7 +521,7 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context) { ui.label("Choose a PDF destination after confirming. Print the PDF from your viewer."); } Dialog::Export { opts, full_size, resize, preset_name, limit_kb, dir } => { - use lightcraft_engine::export::{Anchor as P, ExportFormat as F, MetadataPolicy as M, SharpenAmount as A, SharpenFor as S}; + use lightcraft_engine::export::{ExportFormat as F, MetadataPolicy as M, SharpenAmount as A, SharpenFor as S}; let n = app.session.selection.ids.len().max(1); ui.label(egui::RichText::new(crate::i18n::tr_format!("{n} photo{}", if n == 1 { "" } else { "s" }, n = n)).color(t.text_dim)); // Preset: load a built-in or saved set of options into the dialog @@ -691,7 +691,7 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context) { ui, "Position", "exportWmAnchor", - &[(P::TopLeft, "↖"), (P::TopRight, "↗"), (P::Center, "•"), (P::BottomLeft, "↙"), (P::BottomRight, "↘")], + &WM_POSITIONS, &mut wm.anchor, ); let mut size = wm.size as f64 * 100.0; @@ -702,6 +702,10 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context) { if num(ui, &WM_OPACITY, &mut op) { wm.opacity = (op / 100.0) as f32; } + let mut inset = wm.inset as f64 * 100.0; + if num(ui, &WM_INSET, &mut inset) { + wm.inset = (inset / 100.0) as f32; + } if !graphic { ui.checkbox(&mut wm.shadow, crate::i18n::tr("Shadow")); } @@ -1539,9 +1543,21 @@ fn export_size(ui: &mut egui::Ui, full: &mut bool, r: &mut lightcraft_engine::ex *ppi = p as u16; } } +/// All nine positions supported by both the text and graphic watermark renderer. +/// Keep them in grid order so bottom-centre and edge-centre anchors are available in Export. +const WM_POSITIONS: [(lightcraft_engine::export::Anchor, &str); 9] = { + use lightcraft_engine::export::Anchor::*; + [ + (TopLeft, "↖"), (Top, "↑"), (TopRight, "↗"), + (Left, "←"), (Center, "•"), (Right, "→"), + (BottomLeft, "↙"), (Bottom, "↓"), (BottomRight, "↘"), + ] +}; + const WM_IMAGE_WIDTH: ControlSpec = spec("export.watermarkImageWidth", "Width (% of photo)", 2.0, 100.0, 20.0, 1.0); const WM_SIZE: ControlSpec = spec("export.watermarkSize", "Size (% of short edge)", 1.0, 15.0, 3.5, 0.5); const WM_OPACITY: ControlSpec = spec("export.watermarkOpacity", "Opacity (%)", 5.0, 100.0, 70.0, 1.0); +const WM_INSET: ControlSpec = ControlSpec { decimals: 1, ..spec("export.watermarkInset", "Inset (% of short edge)", 0.0, 40.0, 2.5, 0.5) }; /// A themed slider row editing `v`; true when it changed. fn num(ui: &mut egui::Ui, spec: &ControlSpec, v: &mut f64) -> bool { @@ -1614,3 +1630,22 @@ fn just_opened(ui: &egui::Ui, key: &str) -> bool { ui.data_mut(|d| d.insert_temp(id, pass)); last.and_then(|l| l.checked_add(1)) != Some(pass) } + +#[cfg(test)] +mod watermark_position_tests { + use super::WM_POSITIONS; + use lightcraft_engine::export::Anchor; + + #[test] + fn export_dialog_offers_every_rendered_anchor_exactly_once() { + let anchors = [ + Anchor::TopLeft, Anchor::Top, Anchor::TopRight, + Anchor::Left, Anchor::Center, Anchor::Right, + Anchor::BottomLeft, Anchor::Bottom, Anchor::BottomRight, + ]; + assert_eq!(WM_POSITIONS.len(), anchors.len()); + for anchor in anchors { + assert_eq!(WM_POSITIONS.iter().filter(|(position, _)| *position == anchor).count(), 1); + } + } +}