From 0b3ce29eb333ee6c611649836f4d21606778fc8c Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sat, 24 Oct 2026 14:14:37 +0700 Subject: [PATCH] Canvas: hover distances go when the pointer leaves the canvas --- crates/engine/src/measure_tests.rs | 18 ++++++++++++++++++ crates/engine/src/tools.rs | 10 ++++++++++ crates/ui-egui/src/canvas.rs | 4 ++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/measure_tests.rs b/crates/engine/src/measure_tests.rs index 539386bd..9f793721 100644 --- a/crates/engine/src/measure_tests.rs +++ b/crates/engine/src/measure_tests.rs @@ -250,3 +250,21 @@ fn measure_distances_command() { assert!(s.execute("measure.distances", &json!({"ids": [f]})).is_err(), "top level: pass `to`"); assert!(s.execute("measure.distances", &json!({"ids": [r], "to": 99999})).is_err()); } + +#[test] +fn leaving_the_canvas_clears_the_hover_measurement() { + let mut s = Session::new(); + let a = rect(&mut s, 0.0, 0.0, 50.0, 50.0, None); + rect(&mut s, 100.0, 0.0, 50.0, 50.0, None); + ex(&mut s, "select.set", json!({"ids": [a]})); + assert!(!hover(&mut s, 125.0, 25.0, true, false, false).is_empty()); + assert!(s.tool.overlay.measure_box.is_some()); + s.pointer_left(); + assert!(s.tool.overlay.guides.is_empty(), "{:?}", s.tool.overlay.guides); + assert_eq!(s.tool.overlay.measure_box, None); + // Dev Mode measures without Alt; leaving clears those too. + s.tool.dev_mode = true; + assert!(!hover(&mut s, 125.0, 25.0, false, false, false).is_empty()); + s.pointer_left(); + assert!(s.tool.overlay.guides.is_empty()); +} diff --git a/crates/engine/src/tools.rs b/crates/engine/src/tools.rs index 86feed4f..15a244f7 100644 --- a/crates/engine/src/tools.rs +++ b/crates/engine/src/tools.rs @@ -941,6 +941,16 @@ pub(crate) fn scale_factor(handle: Handle, q: Point, w0: f64, h0: f64, anchor: ( } impl Session { + /// The pointer left the canvas: what hovering there showed goes (the distances Alt or Dev Mode + /// measured and the box measured to, a vector region's highlight), unless a gesture holds it. + pub fn pointer_left(&mut self) { + if !self.tool.busy() { + self.tool.overlay.guides.clear(); + self.tool.overlay.measure_box = None; + } + self.vector_pointer_left(); + } + /// What a marquee starting at page point `p` selects inside: the top-level frame it starts in /// (its children), or the focus view's frame. pub(crate) fn marquee_within(&self, p: Point) -> Result> { diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 5ced1edc..8942bbac 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -542,7 +542,7 @@ fn input(app: &mut FigApp, ui: &mut Ui, resp: &egui::Response) -> bool { } // Leaving the canvas: nothing is hovered there any more. if !app.canvas.dragging && std::mem::take(&mut app.canvas.hovering) { - app.session.vector_pointer_left(); + app.session.pointer_left(); } // A pan whose button was released off the canvas is over. if !ctx.input(|i| i.pointer.button_down(egui::PointerButton::Middle) || i.pointer.primary_down()) { @@ -640,7 +640,7 @@ fn input(app: &mut FigApp, ui: &mut Ui, resp: &egui::Response) -> bool { } else { // Something over the canvas took the pointer. if std::mem::take(&mut app.canvas.hovering) { - app.session.vector_pointer_left(); + app.session.pointer_left(); } None };