From ddf039917a40d87ef26a0c8c1971db5331bd0efe Mon Sep 17 00:00:00 2001 From: rmsff <17902130+rmsff@users.noreply.github.com> Date: Wed, 11 Nov 2026 15:08:25 +0300 Subject: [PATCH] Clone Stamp: a press without a source point shows the reference's alert The Clone Stamp set a status-bar line when no source point was defined. It now opens the reference application's alert at the press ("Could not use the clone stamp tool because the area to clone has not been defined (Alt-click to define a source point)."), once while it is up, and starts no drag. The Healing Brush keeps its status line. --- crates/ui-egui/src/canvas/tool_input.rs | 7 +++++- crates/ui-egui/src/tools/retouch_ui.rs | 25 +++++++++++++++++--- crates/ui-egui/src/tools/retouch_ui/tests.rs | 18 +++++++------- 3 files changed, 37 insertions(+), 13 deletions(-) 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]