mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-31 13:17:19 -06:00
Direct Selection: a marquee over part of a segment selects it (#4654)
This commit is contained in:
@@ -530,6 +530,28 @@ fn anchors_json(v: &[AnchorRef]) -> Value {
|
||||
Value::Array(v.iter().map(|(s, a)| json!([s, a])).collect())
|
||||
}
|
||||
|
||||
/// The segments of `path` that marquee `r` crosses with neither of their anchors inside it, as
|
||||
/// (subpath, segment) pairs: the reference app selects a segment that a marquee is dragged "over
|
||||
/// part of" (<http://web.archive.org/web/20211230015528/https://helpx.adobe.com/illustrator/using/selecting-objects.html>).
|
||||
fn crossed_segments(path: &PathData, r: Rect) -> Vec<AnchorRef> {
|
||||
let mut out = vec![];
|
||||
for (si, sp) in path.subpaths.iter().enumerate() {
|
||||
let n = sp.anchors.len();
|
||||
let count = if sp.closed { n } else { n.saturating_sub(1) };
|
||||
for seg in 0..count {
|
||||
let (Some(a), Some(b)) = (sp.anchors.get(seg), sp.anchors.get((seg + 1) % n)) else { continue };
|
||||
if r.contains(a.p) || r.contains(b.p) {
|
||||
continue;
|
||||
}
|
||||
let piece = PathData::single(vectorcraft_geom::SubPath::new(vec![a.clone(), b.clone()], false));
|
||||
if vectorcraft_geom::hit::intersects_rect(&piece, r) {
|
||||
out.push((si, seg));
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// A segment of a path under the pointer.
|
||||
struct SegmentHit {
|
||||
si: usize,
|
||||
@@ -1412,10 +1434,29 @@ impl DirectSelectionTool {
|
||||
}
|
||||
}
|
||||
sel.retain(|(id, _)| editable(*id));
|
||||
// Direct Selection: a segment the marquee crosses with neither of its anchors inside it is
|
||||
// selected, as a click on it selects it (#4654).
|
||||
let mut crossed: Vec<(NodeId, Vec<AnchorRef>)> = vec![];
|
||||
if !self.group {
|
||||
walk_pickable(cx.doc, cx.isolation, |n| {
|
||||
if let NodeKind::Path { path, .. } = &n.kind
|
||||
&& editable(n.id)
|
||||
{
|
||||
let segs = crossed_segments(path, r);
|
||||
if !segs.is_empty() {
|
||||
crossed.push((n.id, segs));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
self.frame = marked;
|
||||
let items: Vec<Value> = sel.iter().map(|(id, v)| json!({"id": id.0, "anchors": anchors_json(v)})).collect();
|
||||
let art = Action::Exec("select.anchorsMany".into(), json!({"items": items, "mode": if toggle { "toggle" } else { "set" }}));
|
||||
let mut out: Vec<Action> = std::iter::once(art).chain(crate::rulerguide::marquee_select(cx, r, false)).collect();
|
||||
let mode = if toggle { "toggle" } else { "add" };
|
||||
let segments = crossed
|
||||
.iter()
|
||||
.map(|(id, segs)| Action::Exec("select.segments".into(), json!({"id": id.0, "segments": anchors_json(segs), "mode": mode})));
|
||||
let mut out: Vec<Action> = std::iter::once(art).chain(segments).chain(crate::rulerguide::marquee_select(cx, r, false)).collect();
|
||||
if !self.frame.is_empty() {
|
||||
out.push(Action::Notify(FRAME_PICKED.into()));
|
||||
}
|
||||
|
||||
@@ -565,6 +565,28 @@ fn marquee_selects_anchors() {
|
||||
assert_eq!(a, vec![Action::Exec("select.anchorsMany".into(), json!({"items": [{"id": id.0, "anchors": [[0, 1]]}], "mode": "set"}))]);
|
||||
}
|
||||
|
||||
/// #4654: a marquee over part of a segment, with neither of its anchors inside, selects that
|
||||
/// segment as a click on it does; with Shift it toggles it. Group Selection selects objects.
|
||||
#[test]
|
||||
fn a_marquee_over_part_of_a_segment_selects_it() {
|
||||
let (d, id) = doc_with_rect();
|
||||
let (s, p) = (Selection::default(), paint());
|
||||
let cx = cx(&d, &s, &p);
|
||||
// Across the middle of the square's top edge, (100, 100)–(200, 100).
|
||||
let drag = |t: &mut DirectSelectionTool, m: Mods| {
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 140.0, 80.0).with_mods(m));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 160.0, 120.0).with_mods(m));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 160.0, 120.0).with_mods(m))
|
||||
};
|
||||
let segment = |mode: &str| Action::Exec("select.segments".into(), json!({"id": id.0, "segments": [[0, 0]], "mode": mode}));
|
||||
let mut t = DirectSelectionTool::new(false);
|
||||
assert_eq!(drag(&mut t, Mods::default()), vec![Action::Exec("select.anchorsMany".into(), json!({"items": [], "mode": "set"})), segment("add")]);
|
||||
let shift = Mods { shift: true, ..Mods::default() };
|
||||
assert_eq!(drag(&mut t, shift).get(1), Some(&segment("toggle")));
|
||||
let mut g = DirectSelectionTool::new(true);
|
||||
assert!(!drag(&mut g, Mods::default()).iter().any(|a| matches!(a, Action::Exec(c, _) if c == "select.segments")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn marquee_selects_ruler_guides() {
|
||||
let (mut d, id) = doc_with_rect();
|
||||
|
||||
Reference in New Issue
Block a user