Stop locked guide objects from blocking marquee selection

Fixes #3036

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-19 22:19:42 +07:00
committed by bflatastic
co-authored by Claude Haiku 5.5
parent 85af04a83a
commit 3eed3be8e6
5 changed files with 42 additions and 2 deletions
+7 -1
View File
@@ -62,11 +62,14 @@ pub struct HitOptions {
/// (point type's baseline, area type's frame, type on a path's path), not anywhere in its
/// bounds.
pub type_path_only: bool,
/// Guide objects (paths made into guides) are not hittable: View › Guides › Lock Guides is
/// on or the guides are hidden.
pub skip_guides: bool,
}
impl Default for HitOptions {
fn default() -> Self {
Self { tol: 3.0, outline: false, path_only: false, type_path_only: false, scope: None }
Self { tol: 3.0, outline: false, path_only: false, type_path_only: false, skip_guides: false, scope: None }
}
}
@@ -318,6 +321,9 @@ fn may_hit(parent: &Node, i: usize, c: &Node, p: Point, opt: HitOptions, symbols
if !c.visible || editable_only && (c.locked || c.is_template()) {
return false;
}
if opt.skip_guides && matches!(c.kind, NodeKind::Path { guide: true, .. }) {
return false;
}
// (An envelope's content sits where it was, not where the envelope draws it.)
edits_contents(c)
|| c.extras().shaper.is_some() && source_of(c, opt.scope)
+1 -1
View File
@@ -1334,7 +1334,7 @@ pub fn snap_pick(cx: &ToolContext, p: Point) -> (Point, Vec<Overlay>) {
}
let tol = if cx.smart_guides { cx.snap_tol() } else { cx.tol(cx.snap_tolerance) };
let mut roots = cx.selection.objects.clone();
roots.extend(hit_test(cx.doc, p, HitOptions { tol, ..cx.hit_options() }).map(|h| h.leaf));
roots.extend(hit_test(cx.doc, p, HitOptions { tol, skip_guides: false, ..cx.hit_options() }).map(|h| h.leaf));
let near = Rect::new(p.x - tol, p.y - tol, p.x + tol, p.y + tol);
Targets::points_near(cx.doc, &roots, near).styled(cx).snap_point(p, tol)
}
+1
View File
@@ -403,6 +403,7 @@ impl ToolContext<'_> {
path_only: self.path_only,
type_path_only: self.type_path_only,
scope: self.isolation,
skip_guides: !self.guides,
}
}
}
+32
View File
@@ -1679,6 +1679,38 @@ mod tests {
assert_eq!(a, vec![Action::Exec("select.set".into(), json!({"ids": [id.0]}))]);
}
/// #3036: with Lock Guides on, a guide object neither takes the press nor stops a marquee
/// from starting over it; with guides unlocked, the press still picks it.
#[test]
fn marquee_passes_through_locked_guide_objects() {
let (mut d, id) = doc_with_rect();
let l = d.layers[0].id;
let g = d.alloc_id();
let pts = [Point::new(20.0, 20.0), Point::new(400.0, 20.0), Point::new(400.0, 400.0), Point::new(20.0, 400.0)];
let path = vectorcraft_geom::PathData::single(vectorcraft_geom::SubPath::polyline(&pts, true));
let mut guide = vectorcraft_doc::Node::path(g, path, vectorcraft_doc::Appearance::default_art());
let vectorcraft_doc::NodeKind::Path { guide: flag, .. } = &mut guide.kind else { panic!("a path") };
*flag = true;
d.insert(Some(l), 1, guide).unwrap();
let s = Selection::default();
let p = paint();
let locked = ToolContext { guides: false, ..cx(&d, &s, &p) };
for (x, y) in [(20.0, 60.0), (50.0, 50.0)] {
let mut t = SelectionTool::default();
assert!(t.pointer(&locked, &ev(PointerKind::Down, x, y)).is_empty(), "press at ({x}, {y})");
t.pointer(&locked, &ev(PointerKind::Drag, 250.0, 250.0));
assert!(matches!(t.state, State::Marquee { .. }), "press at ({x}, {y})");
// The rectangle is rubber-band selected (the engine leaves locked guides out).
let a = t.pointer(&locked, &ev(PointerKind::Up, 250.0, 250.0));
let Some(Action::Exec(c, v)) = a.first() else { panic!("press at ({x}, {y}): {a:?}") };
assert_eq!(c, "select.set", "press at ({x}, {y})");
assert!(v["ids"].as_array().unwrap().contains(&json!(id.0)), "press at ({x}, {y}): {a:?}");
}
let unlocked = ToolContext { guides: true, ..cx(&d, &s, &p) };
let mut t = SelectionTool::default();
assert_eq!(t.pointer(&unlocked, &ev(PointerKind::Down, 20.0, 60.0)), vec![Action::Exec("select.set".into(), json!({"ids": [g.0]}))]);
}
#[test]
fn handle_drag_scales() {
let (d, id) = doc_with_rect();
+1
View File
@@ -1881,6 +1881,7 @@ fn hit_with<T>(
path_only: prefs.object_selection_by_path_only,
type_path_only: prefs.type_selection_by_path_only,
scope: app.session.active().and_then(|st| st.isolation),
..Default::default()
};
test(&app.session.active()?.doc, p, opt)
}