From 00604b74f27a84ebcb0d8d0c1af37977de0bb95c Mon Sep 17 00:00:00 2001 From: tareq Date: Fri, 30 Oct 2026 14:31:53 +0100 Subject: [PATCH] Toolbar: accept slot releases that drift outside the button. egui still counts a click when the pointer leaves the tool rect; keep the release modifiers when the press started on the slot. The Alt-cycle test now uses 1 ms frames so an Alt-then-plain pair is a real double-click that must not open Polygon Settings. --- crates/ui-egui/src/toolbar.rs | 8 +++++--- crates/ui-egui/src/toolbar/tests.rs | 20 ++++++++++++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/crates/ui-egui/src/toolbar.rs b/crates/ui-egui/src/toolbar.rs index 1ba172004..de06cc26c 100644 --- a/crates/ui-egui/src/toolbar.rs +++ b/crates/ui-egui/src/toolbar.rs @@ -107,9 +107,11 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { // group (InDesign / Photoshop parity with the letter-key cycle). // Prefer the primary-release event's modifiers: Alt may already be up // by the time this frame reads i.modifiers. - // Only a release on this slot: the frame's event list is shared, - // so a later release elsewhere must not steer Alt-cycle / dialogs. + // Prefer a release on this slot; egui still counts a click when the + // pointer drifts a little outside, so also accept a release whose + // press started here. Ignore releases whose press was elsewhere. let click_mods = ui.input(|i| { + let press_here = i.pointer.press_origin().is_some_and(|o| resp.rect.contains(o)); i.events .iter() .rev() @@ -120,7 +122,7 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { pressed: false, modifiers, .. - } if resp.rect.contains(*pos) => Some(*modifiers), + } if resp.rect.contains(*pos) || press_here => Some(*modifiers), _ => None, }) .unwrap_or(i.modifiers) diff --git a/crates/ui-egui/src/toolbar/tests.rs b/crates/ui-egui/src/toolbar/tests.rs index 5551ba2dd..899f4855b 100644 --- a/crates/ui-egui/src/toolbar/tests.rs +++ b/crates/ui-egui/src/toolbar/tests.rs @@ -385,7 +385,9 @@ fn alt_click_cycles_the_tool_flyout_group() { use egui::{Event, Modifiers, PointerButton}; use egui_kittest::kittest::Queryable; let app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); - let mut h = crate::test_window::open(app, vec2(1400.0, 900.0)); + // 1 ms frames so an Alt-click and a following plain click can form a real double-click + // (default open() spaces frames by 0.25 s, outside egui's double-click window). + let mut h = crate::test_window::open_stepped(app, vec2(1400.0, 900.0), 1.0, 0.001); h.state_mut().app.select_tool("rectangleFrame"); h.run_steps(2); let at = h.get_by_label("Rectangle Frame Tool (F)").rect().center(); @@ -410,16 +412,30 @@ fn alt_click_cycles_the_tool_flyout_group() { }); h.run_steps(3); }; + // Break the double-click window between independent clicks (~0.4 s at 1 ms/step). + let settle = |h: &mut egui_kittest::Harness<'static, crate::test_window::Window>| h.run_steps(400); click(&mut h, Modifiers::NONE, Modifiers::NONE); assert_eq!(h.state().app.session.tool_id(), "rectangleFrame"); assert!(h.state().app.ui.flyout.is_none()); + settle(&mut h); click(&mut h, alt, Modifiers::NONE); assert_eq!(h.state().app.session.tool_id(), "ellipseFrame", "Alt-click advances the frame group"); assert!(h.state().app.ui.flyout.is_none(), "Alt-click must not open the flyout"); + settle(&mut h); click(&mut h, alt, Modifiers::NONE); assert_eq!(h.state().app.session.tool_id(), "polygonFrame"); - assert!(h.state().app.ui.dialog.is_none(), "modified double-click must not open a dialog"); + settle(&mut h); click(&mut h, alt, Modifiers::NONE); assert_eq!(h.state().app.session.tool_id(), "rectangleFrame", "Alt-click after the last group tool wraps to the first"); assert!(h.state().app.ui.flyout.is_none(), "wrap-around Alt-click must not open the flyout"); + settle(&mut h); + // Alt-click onto polygonFrame, then a plain click inside the double-click window: egui + // reports double_clicked, but prev_modified must keep Polygon Settings closed. + h.state_mut().app.select_tool("ellipseFrame"); + h.run_steps(2); + click(&mut h, alt, Modifiers::NONE); + assert_eq!(h.state().app.session.tool_id(), "polygonFrame"); + click(&mut h, Modifiers::NONE, Modifiers::NONE); + assert!(h.state().app.ui.dialog.is_none(), "plain click after Alt-click must not open Polygon Settings"); + assert_eq!(h.state().app.session.tool_id(), "polygonFrame"); }