mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 02:19:56 -06:00
Merge pull request #5881 from dotCooCoo/fix/selected-hover-widgets
Selection: empty-canvas hover skips distant widgets in large selections
This commit is contained in:
@@ -1059,10 +1059,16 @@ impl Tool for SelectionTool {
|
||||
if self.guide.busy() {
|
||||
return self.guide.cursor(cx, p).unwrap_or_default();
|
||||
}
|
||||
if over_widget(cx, p, false) {
|
||||
// Corner widgets sit inside selected art, and pie widgets extend at most 17 screen
|
||||
// pixels beyond it. A distant pointer cannot take either one, so avoid building their
|
||||
// widgets for every selected object on each cursor move.
|
||||
let near_selected = cx.selection.objects.len() <= 128
|
||||
|| cx.preview_bounds
|
||||
|| selection_bounds(cx).is_none_or(|b| b.inflate(cx.tol(32.0), cx.tol(32.0)).contains(p));
|
||||
if near_selected && over_widget(cx, p, false) {
|
||||
return Cursor::CornerRadius;
|
||||
}
|
||||
if pie::over_widget(cx, p) {
|
||||
if near_selected && pie::over_widget(cx, p) {
|
||||
return Cursor::ArrowHollow;
|
||||
}
|
||||
if over_rotate_all(cx, p) {
|
||||
|
||||
@@ -545,6 +545,65 @@ fn the_rotate_cursor_wraps_the_corner_it_is_at() {
|
||||
assert_eq!(Cursor::rotate_about(Point::ORIGIN, Point::new(0.0, -5.0)), Cursor::RotateToward(0), "straight up");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_rotated_large_selection_keeps_handles_outside_its_axis_aligned_bounds() {
|
||||
use vectorcraft_doc::{Appearance, Node};
|
||||
use vectorcraft_geom::shapes;
|
||||
|
||||
let mut d = vectorcraft_doc::Document::new(4000.0, 500.0);
|
||||
let layer = d.layers[0].id;
|
||||
let mut ids = vec![];
|
||||
for i in 0..129 {
|
||||
let id = d.alloc_id();
|
||||
let x = 100.0 + i as f64 * 25.0;
|
||||
let mut node = Node::path(id, shapes::rectangle(Rect::new(x, 100.0, x + 20.0, 120.0)), Appearance::default_art());
|
||||
node.bbox_angle = 45.0;
|
||||
d.insert(Some(layer), usize::MAX, node).unwrap();
|
||||
ids.push(id);
|
||||
}
|
||||
let mut selection = Selection::default();
|
||||
selection.set(ids);
|
||||
let paint = paint();
|
||||
let cx = cx(&d, &selection, &paint);
|
||||
let bx = selection_box(&cx).unwrap();
|
||||
let bounds = selection_bounds(&cx).unwrap().inflate(cx.tol(32.0), cx.tol(32.0));
|
||||
let p = [Point::new(bx.rect.x0, bx.rect.y0), Point::new(bx.rect.x1, bx.rect.y0), Point::new(bx.rect.x1, bx.rect.y1)]
|
||||
.into_iter()
|
||||
.map(|q| bx.to_doc() * q)
|
||||
.find(|q| !bounds.contains(*q))
|
||||
.unwrap();
|
||||
assert!(matches!(box_hit(&cx, &bx, p), Some(BoxHit::Handle(_))));
|
||||
assert_ne!(SelectionTool::default().cursor(&cx, p, Mods::default()), Cursor::Arrow);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_large_selection_keeps_a_pie_widget_just_outside_the_art() {
|
||||
use vectorcraft_doc::{Appearance, LiveShape, Node, NodeKind};
|
||||
use vectorcraft_geom::{Affine, shapes};
|
||||
|
||||
let mut d = vectorcraft_doc::Document::new(500.0, 500.0);
|
||||
let layer = d.layers[0].id;
|
||||
let live = LiveShape::Ellipse { w: 200.0, h: 100.0, pie: (0.0, 360.0), xf: Affine::translate((100.0, 100.0)) };
|
||||
let id = d.alloc_id();
|
||||
let mut ellipse = Node::path(id, live.to_path(), Appearance::default_art());
|
||||
if let NodeKind::Path { live: slot, .. } = &mut ellipse.kind {
|
||||
*slot = Some(Box::new(live));
|
||||
}
|
||||
d.insert(Some(layer), 0, ellipse).unwrap();
|
||||
let mut ids = vec![id];
|
||||
for _ in 0..128 {
|
||||
let id = d.alloc_id();
|
||||
d.insert(Some(layer), usize::MAX, Node::path(id, shapes::rectangle(Rect::new(120.0, 120.0, 140.0, 140.0)), Appearance::default_art()))
|
||||
.unwrap();
|
||||
ids.push(id);
|
||||
}
|
||||
let mut selection = Selection::default();
|
||||
selection.set(ids);
|
||||
let paint = paint();
|
||||
let cx = cx(&d, &selection, &paint);
|
||||
assert_eq!(SelectionTool::default().cursor(&cx, Point::new(312.0, 150.0), Mods::default()), Cursor::ArrowHollow);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turned_cursors_step_with_the_view() {
|
||||
assert_eq!(Cursor::ResizeH.turned(90.0), Cursor::ResizeV);
|
||||
|
||||
+10
-3
@@ -36,7 +36,8 @@ 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: Selection skips building corner and pie widgets when the pointer is well outside a large selection; empty-canvas hover with 20,000 selected paths took 28.72 ms before and 16.41 ms after in local median runs. Other cursor targets remain active; estimate unchanged. |
|
||||
| 2026-11-05 | minor | G15: Direct Selection reuses one document hit result when choosing the cursor over a path; after the path hit-test improvement, a 200,000-anchor path's segment cursor took 3.72 ms before and 2.66 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. |
|
||||
@@ -652,9 +653,15 @@ 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):** when more than 128 objects are selected, Selection skips making
|
||||
corner and pie widgets if the pointer is farther than their reach from the selected art.
|
||||
Empty-canvas hover with 20,000 selected paths took 28.72 ms before and 16.41 ms after in
|
||||
local median runs. Pie widgets just beyond the art and rotated bounding-box handles still
|
||||
take the cursor. This is a directional measurement, not an idle performance-budget result.
|
||||
- **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.
|
||||
cursor over a path. With the path hit-test improvement below, the cursor over a segment of a
|
||||
200,000-anchor path took 3.72 ms before and 2.66 ms after in local median runs; a nearby point
|
||||
took 4.04 ms before and 2.98 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
|
||||
|
||||
Reference in New Issue
Block a user