From cec19d6af6fde42343bd5f9a7a458baa70509556 Mon Sep 17 00:00:00 2001 From: prxnvt <53838599+prxnvt@users.noreply.github.com> Date: Thu, 22 Oct 2026 02:16:01 -0400 Subject: [PATCH] UI: a reference box's pick ends when the box leaves the screen Choosing another step or inspector tab hid the picking box but left the pick in place, so a grid selection made later for another reason landed in the box when its step was shown again, and the draft was dirtied with a reference nobody chose. A pick now ends in any frame that does not draw its box. --- crates/ui-egui/src/macro_editor.rs | 10 +++++++ crates/ui-egui/tests/it/macro_editor.rs | 36 +++++++++++++++++++------ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/crates/ui-egui/src/macro_editor.rs b/crates/ui-egui/src/macro_editor.rs index 955cb45f6..12e9c1772 100644 --- a/crates/ui-egui/src/macro_editor.rs +++ b/crates/ui-egui/src/macro_editor.rs @@ -99,6 +99,9 @@ pub struct RefPick { pub home: Option<(usize, String, String)>, /// The reference box the window is collapsed to (only that box shows). pub collapsed: Option, + /// Whether the picking box was drawn this frame: a pick ends with a box that left the screen + /// (another step or tab was chosen), or a later selection would land in it unseen. + pub drawn: bool, } // ---------------------------------------------------------------- step paths @@ -752,6 +755,7 @@ pub fn window(app: &mut SheetApp, ctx: &egui::Context, d: &mut Dialog, open: &mu let dirty_mark = if ed.dirty { " •" } else { "" }; let screen = ctx.content_rect(); let mut win_open = true; + ed.refs.drawn = false; egui::Window::new(format!("Macro: {}{dirty_mark}", ed.draft.name)) .id(Id::new("gridcraft.macro_editor")) .collapsible(false) @@ -769,6 +773,9 @@ pub fn window(app: &mut SheetApp, ctx: &egui::Context, d: &mut Dialog, open: &mu } body(app, ui, d, &mut ed); }); + if ed.refs.pick.is_some() && !ed.refs.drawn { + ed.refs.pick = None; + } if !win_open { if app.session.macro_debug.is_some() { ed.ask_stop = true; @@ -840,6 +847,9 @@ fn ref_edit(app: &SheetApp, ui: &mut Ui, d: &mut Dialog, refs: &mut RefPick, key let mut s = d.values.get(key).and_then(Json::as_str).unwrap_or("").to_string(); let sel = selection_ref(app, refs.home.as_ref().map(|h| h.0)); let picking = refs.pick.as_deref() == Some(key); + if picking { + refs.drawn = true; + } ui.horizontal(|ui| { let r = ui.add(egui::TextEdit::singleline(&mut s).desired_width(width - 30.0).id_salt(("refedit", key))); if r.gained_focus() || (r.clicked() && !picking) { diff --git a/crates/ui-egui/tests/it/macro_editor.rs b/crates/ui-egui/tests/it/macro_editor.rs index 52e116f89..5dbf60847 100644 --- a/crates/ui-egui/tests/it/macro_editor.rs +++ b/crates/ui-egui/tests/it/macro_editor.rs @@ -162,6 +162,32 @@ fn run_to_here_then_stop_asks_whether_to_keep_the_changes() { } } +/// Step 3's Cell box is picking from the grid, with the selection it last saw (A1). +fn pick_step_3_cell(h: &mut Harness) { + let refs = &mut h.state_mut().macros.editor.as_mut().unwrap().refs; + refs.pick = Some("ref:3:cell".into()); + refs.seen = "$A$1".into(); +} + +#[test] +fn a_pick_ends_when_its_box_leaves_the_screen() { + // Choosing another step hides the picking box; a selection made meanwhile must not land in + // it when the step is shown again. + let mut h = harness(); + three(&mut h); + click(&mut h, "Step 3: Set A3 to A3"); + pick_step_3_cell(&mut h); + h.run_steps(2); + click(&mut h, "Step 1: Set A1 to A1"); + assert!(h.state().macros.editor.as_ref().unwrap().refs.pick.is_none(), "the pick ends with its box"); + h.state_mut().run("selection.set", json!({"range": "F9"})).unwrap(); + h.run_steps(2); + click(&mut h, "Step 3: Set A3 to A3"); + let ed = h.state().macros.editor.as_ref().unwrap(); + assert!(!ed.dirty, "the draft stays clean"); + assert_eq!(draft_cells(&h), ["A1", "A2", "A3"]); +} + #[test] fn debugging_never_writes_the_selection_into_a_reference_box() { // A reference box that was picking from the grid stays out of it once an action starts, and @@ -169,13 +195,7 @@ fn debugging_never_writes_the_selection_into_a_reference_box() { let mut h = harness(); three(&mut h); click(&mut h, "Step 3: Set A3 to A3"); - // The step's Cell box is picking, with the selection it last saw (A1). - let pick = |h: &mut Harness| { - let refs = &mut h.state_mut().macros.editor.as_mut().unwrap().refs; - refs.pick = Some("ref:3:cell".into()); - refs.seen = "$A$1".into(); - }; - pick(&mut h); + pick_step_3_cell(&mut h); h.run_steps(2); assert_eq!(draft_cells(&h), ["A1", "A2", "A3"]); click(&mut h, "Run to Here"); @@ -184,7 +204,7 @@ fn debugging_never_writes_the_selection_into_a_reference_box() { assert!(ed.refs.pick.is_none(), "the action ends the pick"); assert!(!ed.dirty); // A box focused while debugging does not take the selection either. - pick(&mut h); + pick_step_3_cell(&mut h); h.state_mut().run("selection.set", json!({"range": "F9"})).unwrap(); h.run_steps(3); let ed = h.state().macros.editor.as_ref().unwrap();