diff --git a/crates/ui-egui/src/canvas/tool_input.rs b/crates/ui-egui/src/canvas/tool_input.rs index 7d4b17e166..ed69d3d909 100644 --- a/crates/ui-egui/src/canvas/tool_input.rs +++ b/crates/ui-egui/src/canvas/tool_input.rs @@ -525,7 +525,12 @@ pub(super) fn tool_event_with_view(app: &mut PhotocraftApp, ev: ToolEvent, mods: if !sets_source && press_refused(app, tool) { return; } - if !sets_source && (crate::rasterize_prompt::intercept(app, tool, x, y, pressure) || refuse_group(app, tool)) { + // The Clone Stamp's no-source alert opens at the press, after any rasterize prompt. + if !sets_source + && (crate::rasterize_prompt::intercept(app, tool, x, y, pressure) + || refuse_group(app, tool) + || crate::retouch_ui::clone_stamp_without_source(app, tool)) + { return; } // ⌘ with a selection tool is the Move tool for the drag: outside the selection (or diff --git a/crates/ui-egui/src/tools/retouch_ui.rs b/crates/ui-egui/src/tools/retouch_ui.rs index 58aac368db..7576c7dee3 100644 --- a/crates/ui-egui/src/tools/retouch_ui.rs +++ b/crates/ui-egui/src/tools/retouch_ui.rs @@ -198,6 +198,21 @@ pub(crate) fn stroke_seed(points: &[[f64; 3]]) -> u64 { photocraft_engine::paint::rng::seed_from_bytes(first.as_bytes()) } +/// A Clone Stamp press with no source point yet: the reference application's alert, opened at +/// the press (#12609) and once while it is up. Returns whether it refused the stroke. +pub(crate) fn clone_stamp_without_source(app: &mut PhotocraftApp, tool: Tool) -> bool { + if tool != Tool::CloneStamp || clone_params(app).is_some() { + return false; + } + let message = if cfg!(target_os = "macos") { + tl!("Could not use the clone stamp tool because the area to clone has not been defined (Option-click to define a source point).") + } else { + tl!("Could not use the clone stamp tool because the area to clone has not been defined (Alt-click to define a source point).") + }; + open_alert_once(app, message); + true +} + /// Finish a stroke with a retouching tool. Returns false if `tool` isn't one. pub fn finish_stroke(app: &mut PhotocraftApp, tool: Tool, points: &[[f64; 3]], mods: egui::Modifiers, line: bool) -> bool { finish_stroke_with(app, tool, points, mods, line, None) @@ -224,6 +239,9 @@ pub(crate) fn finish_stroke_with(app: &mut PhotocraftApp, tool: Tool, points: &[ return true; } let Some(p) = clone_params(app) else { + if clone_stamp_without_source(app, tool) { + return true; + } // Option-click on the Mac, Alt-click on Windows. app.ui.status = if cfg!(target_os = "macos") { tl!("Option-click to define a source point to clone from") @@ -783,7 +801,8 @@ fn cam_amount_slider(ui: &mut egui::Ui, name: &str, value: &mut f32, lo: f32, hi }); } -fn open_cam_range_alert(app: &mut PhotocraftApp, message: &str) { +/// Opens the alert with `message` (English, translated here) unless it is already up. +fn open_alert_once(app: &mut PhotocraftApp, message: &str) { let message = tl!(message).to_owned(); let shown = app.ui.dialogs.iter().any(|d| d.kind == crate::state::DialogKind::Error && d.fields.get("message").and_then(|m| m.as_str()) == Some(message.as_str())); @@ -807,11 +826,11 @@ fn content_aware_move_bar(app: &mut PhotocraftApp, ui: &mut egui::Ui) { } opt(ui, tl!("Structure:")); if cam_integer_popup(ui, "cam-structure", "Structure", &mut app.ui.tool_options.cam_structure, 1.0, 7.0, STRUCTURE_TIP) { - open_cam_range_alert(app, STRUCTURE_RANGE_ALERT); + open_alert_once(app, STRUCTURE_RANGE_ALERT); } opt(ui, tl!("Color:")); if cam_integer_popup(ui, "cam-color", "Color", &mut app.ui.tool_options.cam_color, 0.0, 10.0, COLOR_TIP) { - open_cam_range_alert(app, COLOR_RANGE_ALERT); + open_alert_once(app, COLOR_RANGE_ALERT); } crate::widgets::checkbox(ui, &mut app.ui.tool_options.sample_all.content_aware_move, tl!("Sample All Layers")) .on_hover_text(tl!("Enable to remix all layers")); diff --git a/crates/ui-egui/src/tools/retouch_ui/tests.rs b/crates/ui-egui/src/tools/retouch_ui/tests.rs index a6eba29bd2..545e475ae4 100644 --- a/crates/ui-egui/src/tools/retouch_ui/tests.rs +++ b/crates/ui-egui/src/tools/retouch_ui/tests.rs @@ -225,15 +225,15 @@ fn paint_bucket_mode_reaches_the_fill() { #[test] fn clone_without_a_source_names_the_platform_modifier() { - // Option-click on the Mac, Alt-click elsewhere (#251). - for tool in [Tool::CloneStamp, Tool::Healing] { - let mut app = app(); - app.ui.tool = tool; - assert!(finish_stroke(&mut app, tool, &[[10.0, 30.0, 1.0], [50.0, 30.0, 1.0]], egui::Modifiers::NONE, false)); - assert!(app.ui.status_error, "{tool:?}"); - let want = if cfg!(target_os = "macos") { "Option-click" } else { "Alt-click" }; - assert!(app.ui.status.starts_with(want), "{tool:?}: {}", app.ui.status); - } + // Option-click on the Mac, Alt-click elsewhere (#251). The Clone Stamp says it in an alert + // (#12609, clone_stamp_no_source_alert_tests.rs). + let tool = Tool::Healing; + let mut app = app(); + app.ui.tool = tool; + assert!(finish_stroke(&mut app, tool, &[[10.0, 30.0, 1.0], [50.0, 30.0, 1.0]], egui::Modifiers::NONE, false)); + assert!(app.ui.status_error); + let want = if cfg!(target_os = "macos") { "Option-click" } else { "Alt-click" }; + assert!(app.ui.status.starts_with(want), "{}", app.ui.status); } #[test]