From 2db1ada6608b239a5ac78178c1cffe98f4b89de8 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 22 Oct 2026 23:55:51 -0700 Subject: [PATCH] Open and Place: a file read in the background waits for a busy tool only during a drag A file opened or placed off the UI thread waited while the active tool reported busy. The Dimension tool also reports busy between its clicks, so after one click a ready file stayed queued with its spinner until the dimension was finished or canceled. Files also waited between the clicks of the Blend tool. The session now records whether the pointer button is held. A file waits for a busy tool only while the button is held. A file that opens between a tool's clicks resets the tool, as a switch of documents does; a file placed in the active document leaves the tool's clicks in place. Files still wait while a dialog is open, a drag is under way or a preview is open, and a tool's preview between its clicks counts as one (the Flare tool after its center drag, the Dimension tool's line once the pointer moves after its second click). --- crates/engine/src/lib.rs | 4 ++ crates/engine/src/tests.rs | 20 +++++++ crates/engine/src/tooling.rs | 12 +++++ crates/ui-egui/src/opening.rs | 98 +++++++++++++++++++++++++++++++++-- crates/ui-egui/src/placing.rs | 42 ++++++++++++--- 5 files changed, 166 insertions(+), 10 deletions(-) diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 48fa2e344..c43a5c3f6 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -1425,6 +1425,9 @@ pub struct Session { pub(crate) liquify_stroke: Option>, /// A press on the Plane Switching Widget is under way: its drag and release are the widget's. pub(crate) plane_widget_press: bool, + /// The pointer button is held: the last pointer event was a press or a drag, not a release or + /// a move ([`Session::pointer_held`]). + pub(crate) pointer_held: bool, /// A guide being dragged out of a ruler ([`Session::ruler_guide`]). pub(crate) ruler_guide: Option, /// Which selected type overflows its frame, for the markers every tool but the Type tools shows. @@ -1520,6 +1523,7 @@ impl Session { envelope_defaults: None, liquify_stroke: None, plane_widget_press: false, + pointer_held: false, ruler_guide: None, overflow_marks: Default::default(), font_search: None, diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index ed7cee619..f0c081ef6 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -825,6 +825,26 @@ fn selection_tool_drag_is_one_undo_step() { assert_eq!(s.doc().unwrap().doc.node(a).unwrap().geometric_bounds().unwrap().x0, 100.0); } +/// The pointer button is held from a press or a drag until a release or a move. The Dimension +/// tool stays busy after its first click with the button released, waiting for the second one. +#[test] +fn the_pointer_is_held_from_a_press_until_a_release_or_a_move() { + let mut s = session(); + let v = ViewInfo::default(); + s.select_tool("dimension", v).unwrap(); + let mut held = vec![]; + for (kind, x) in [(PointerKind::Down, 100.0), (PointerKind::Drag, 100.0), (PointerKind::Up, 100.0)] { + s.pointer(&PointerEvent::new(kind, x, 100.0), v).unwrap(); + held.push(s.pointer_held()); + } + assert!(s.tool_busy(), "the first point waits for the second"); + for (kind, x) in [(PointerKind::Move, 200.0), (PointerKind::Down, 300.0), (PointerKind::Drag, 310.0), (PointerKind::Move, 320.0)] { + s.pointer(&PointerEvent::new(kind, x, 100.0), v).unwrap(); + held.push(s.pointer_held()); + } + assert_eq!(held, [true, true, false, false, true, true, false]); +} + /// How many steps there are to undo and to redo, and whether the document has unsaved changes. pub(crate) fn history(s: &Session) -> (usize, usize, bool) { let st = s.doc().unwrap(); diff --git a/crates/engine/src/tooling.rs b/crates/engine/src/tooling.rs index e56934c14..c426b99a5 100644 --- a/crates/engine/src/tooling.rs +++ b/crates/engine/src/tooling.rs @@ -313,6 +313,11 @@ impl Session { /// Feed a pointer event to the active tool. Returns requests for the UI. pub fn pointer(&mut self, ev: &PointerEvent, view: ViewInfo) -> Result> { self.last_view = view; + match ev.kind { + PointerKind::Down | PointerKind::Drag => self.pointer_held = true, + PointerKind::Up | PointerKind::Move => self.pointer_held = false, + PointerKind::DoubleClick => {} + } // The Plane Switching Widget takes its clicks whatever the tool. if let Some(r) = self.plane_widget_pointer(ev, view) { return r; @@ -437,6 +442,13 @@ impl Session { self.tool.busy() } + /// Is the pointer button held: was the last pointer event a press or a drag, not a release or a + /// move? A tool that is busy while the button is not held is waiting between its clicks (the + /// Dimension tool after its first point). + pub fn pointer_held(&self) -> bool { + self.pointer_held + } + /// Is the active tool moving, scaling or rotating the selection with a drag? pub fn tool_transforming(&self) -> bool { self.tool.transforming() diff --git a/crates/ui-egui/src/opening.rs b/crates/ui-egui/src/opening.rs index 65db2c75e..37d5d85b1 100644 --- a/crates/ui-egui/src/opening.rs +++ b/crates/ui-egui/src/opening.rs @@ -82,10 +82,14 @@ pub fn start(app: &mut VectorcraftApp, path: &str, bytes: Vec) -> Result>(), ["photo.tif"]); assert_eq!(placed_in(1), [("a.png".to_string(), Some(vectorcraft_geom::Rect::new(20.0, 20.0, 60.0, 60.0)))], "the drag placed its file"); } + + /// A file read while a tool waits between its clicks (the Dimension tool after its first point), + /// in the file's document or in another one, is placed without waiting for the next click. In + /// the same document the tool keeps its point; in another one it starts afresh, as when the user + /// switches documents. + #[test] + fn a_file_read_between_a_tools_clicks_is_placed() { + use vectorcraft_tools::{PointerEvent, PointerKind}; + let path = saved("clicks", "photo.tif", &picture(Tiff, 60, 40)); + for other_document in [false, true] { + let mut app = app(true); + start(&mut app, &json!({"path": path, "at": [300, 200]}), false).unwrap(); + if other_document { + app.run("file.new", json!({"width": 400, "height": 300})).unwrap(); + } + app.select_tool("dimension"); + let view = vectorcraft_engine::ViewInfo::default(); + for kind in [PointerKind::Down, PointerKind::Up] { + crate::canvas::dispatch(&mut app, &PointerEvent::new(kind, 20.0, 20.0), view); + } + assert!(app.session.tool_busy() && !app.session.in_interaction(), "other document: {other_document}"); + placed(&mut app); + assert_eq!(names(&app), ["photo.tif"], "other document: {other_document}"); + assert_eq!(app.session.tool_busy(), !other_document, "other document: {other_document}"); + } + } }