From 64c38d29c71fc19f20bb6d2fe8bb2b35f5efa475 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 5 Nov 2026 20:21:59 -0800 Subject: [PATCH] Reuse hit result for Direct Selection cursor --- crates/tools/src/direct.rs | 15 ++++++++++----- docs/gaps.md | 5 +++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/tools/src/direct.rs b/crates/tools/src/direct.rs index 42cb495c8..cb75d5ea6 100644 --- a/crates/tools/src/direct.rs +++ b/crates/tools/src/direct.rs @@ -50,7 +50,7 @@ use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet}; use serde_json::{Value, json}; -use vectorcraft_doc::hit::{HitKind, compound_part_at, hit_test, selected_hidden_at}; +use vectorcraft_doc::hit::{Hit, HitKind, compound_part_at, hit_test, selected_hidden_at}; use vectorcraft_doc::{AnchorRef, Node, NodeId, NodeKind, Selection}; use vectorcraft_geom::{Anchor, PathData, PathSeg, Point, Rect, Vec2}; @@ -1061,7 +1061,8 @@ impl DirectSelectionTool { } // A selected path's anchors or the anchors of the path under the pointer, as Highlight // anchors on mouse over finds them: walking every path on each hover is too slow. - if let Some((id, _)) = hovered_anchor(cx, p) { + let hit = hit_test(cx.doc, p, cx.selection_hit_options()); + if let Some((id, _)) = hovered_anchor_with_hit(cx, p, hit.as_ref()) { return Some(if cx.selection.contains(id) { Cursor::DirectAnchorSelected } else { Cursor::DirectAnchor }); } if hit_frame_edge(cx, p, tol).is_some() { @@ -1073,7 +1074,7 @@ impl DirectSelectionTool { if m.shift || self.guide.cursor(cx, p).is_some() { return None; } - let h = hit_test(cx.doc, p, cx.selection_hit_options())?; + let h = hit?; let whole = cx.selection.contains(h.leaf) && cx.selection.partial(h.leaf).is_none(); let curved = matches!(h.kind, HitKind::Stroke | HitKind::Outline) && segment_at(cx, h.leaf, p).is_some_and(|s| s.curved); (!whole && curved).then_some(Cursor::ReshapeSegment) @@ -1083,9 +1084,13 @@ impl DirectSelectionTool { /// The anchor within a press's reach of `p` ([`ToolContext::point_tol`]), of a selected path or the /// path under `p`: what Highlight anchors on mouse over marks (a press there picks it). fn hovered_anchor(cx: &ToolContext, p: Point) -> Option<(NodeId, Point)> { + let hit = hit_test(cx.doc, p, cx.selection_hit_options()); + hovered_anchor_with_hit(cx, p, hit.as_ref()) +} + +fn hovered_anchor_with_hit(cx: &ToolContext, p: Point, hit: Option<&Hit>) -> Option<(NodeId, Point)> { let tol = cx.point_tol(); - let under = - hit_test(cx.doc, p, cx.selection_hit_options()).map(|h| cx.doc.node(h.leaf).and_then(|n| compound_part_at(n, p, tol)).unwrap_or(h.leaf)); + let under = hit.map(|h| cx.doc.node(h.leaf).and_then(|n| compound_part_at(n, p, tol)).unwrap_or(h.leaf)); cx.selection.objects.iter().copied().filter(|id| in_scope(cx.doc, cx.isolation, *id)).chain(under).find_map(|id| { let path = cx.doc.node(id).and_then(editable_path)?; nearest_anchor(&path, p, tol).map(|(.., a)| (id, a)) diff --git a/docs/gaps.md b/docs/gaps.md index 5bdf50322..c1575f79a 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -36,6 +36,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard | Date | Change | Summary | |---|---|---| +| 2026-11-05 | minor | G15: Direct Selection reuses one document hit result when choosing the cursor over a path; a 200,000-anchor path's segment cursor took 58.15 ms before and 29.33 ms after in local median runs. Cursor behavior is unchanged; estimate unchanged. | | 2026-11-05 | minor | G15: large-selection transforms reuse object locations found during command validation; a 20,000-object nudge took 58.07 ms before and 51.26 ms after in paired median runs. Estimate unchanged. | | 2026-11-05 | minor | G9, G14: `.ai` paths with a Calligraphic brush open brushed (the brush on the stroke, at its weight) where the object's settings are the brush's own, and the brush's Tilt, Bearing and Rotation codes come in as those channels; Windows reads the pen's tilt and barrel rotation from Windows Ink (#372). Checked against a user's file of 6D Art Pen strokes, used locally; not tried with a pen. Scores and estimates unchanged. | | 2026-11-04 | minor | Contour add-on (beyond Illustrator): drag away from a selected shape's edge to grow a live copy of it, or toward the inside to shrink it; the copy carries a live Offset Path effect, so it stays editable until Bake (Expand Appearance), a second drag changes the same Contour instead of nesting one, and a floating bar sets the distance and the Sharp, Round or Square corners. `addon.contour.set` is the command behind it. It is a built-in add-on: Object › Plug-ins › Contour, and its own heading in All Tools, with no default shortcut or toolbar slot. A VectorCraft design built on Offset Path; no other application was inspected. Scores and estimates unchanged. | @@ -651,6 +652,10 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall ## G15 Performance budgets and a GPU renderer +- **Improved (2026-11-05):** Direct Selection reuses its document hit result when choosing the + cursor over a path. The cursor over a segment of a 200,000-anchor path took 58.15 ms before + and 29.33 ms after in local median runs; a nearby point took 56.21 ms before and 29.81 ms after. + Anchor, segment and plain cursor choices remain the same. The idle-frame budget remains open. - **Improved (2026-11-05):** hovering over a nearby plain stroke or path outline checks the candidate segments directly. On a 200,000-anchor open path, a hit near its middle took about 11–13 ms before and 1.0–1.4 ms after in paired runs on a busy Windows machine. A miss between