mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
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:
committed by
bflatastic
co-authored by
Claude Haiku 5.5
parent
85af04a83a
commit
3eed3be8e6
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user