Direct Selection: Shift-click deselects one anchor of a path selected whole

select.anchors in toggle and add mode now starts from all of a path's
anchors when the path is selected as a whole, as select.anchorsMany does.
Before, Shift-clicking an anchor of a fully selected path left only that
anchor selected.

Follow-up to #1237 / #1254.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Fabio Lussi 2026-10-11 21:38:41 +02:00 committed by bflatastic
parent 8362223932
commit aa15cb52bd
2 changed files with 45 additions and 1 deletions

View File

@ -340,12 +340,17 @@ fn anchors(s: &mut Session, p: &Value) -> Result<Value> {
Some("toggle") => "toggle",
Some(m) => return Err(bad(C, format!("unknown mode {m:?}"))),
};
s.select(|_, sel| {
s.select(|d, sel| {
if mode == "set" {
sel.clear();
}
// A path selected as a whole has every anchor selected: toggle and add start from them all.
let whole = sel.contains(id) && sel.partial(id).is_none();
sel.add(id);
let e = sel.anchors.entry(id).or_default();
if whole && let Some(path) = d.node(id).and_then(|n| n.path_data()) {
e.extend(path.anchors().map(|(s, i, _)| (s, i)));
}
for r in refs {
if mode == "toggle" && e.contains(&r) {
e.remove(&r);

View File

@ -1423,6 +1423,45 @@ fn direct_selection_moves_one_anchor() {
assert_eq!(p.subpaths[0].anchors[1].p, vectorcraft_geom::Point::new(200.0, 100.0));
}
/// Follow-up to #1254: toggling or adding anchors of a path selected as a whole starts from all
/// its anchors, so toggling one deselects it alone and adding one keeps them all.
#[test]
fn select_anchors_toggle_and_add_start_from_a_whole_path() {
let mut s = session();
let a = rect(&mut s, 100.0, 100.0, 100.0, 100.0);
let picked = |s: &Session| s.doc().unwrap().selection.partial(a).map(|set| set.iter().copied().collect::<Vec<_>>());
s.execute("select.set", &json!({"ids": [a.0]})).unwrap();
s.execute("select.anchors", &json!({"id": a.0, "anchors": [[0, 0]], "mode": "toggle"})).unwrap();
assert_eq!(picked(&s), Some(vec![(0, 1), (0, 2), (0, 3)]));
s.execute("select.set", &json!({"ids": [a.0]})).unwrap();
s.execute("select.anchors", &json!({"id": a.0, "anchors": [[0, 0]], "mode": "add"})).unwrap();
assert!(picked(&s).is_none_or(|v| v.len() == 4), "{:?}", picked(&s));
assert!(s.doc().unwrap().selection.contains(a));
}
/// Follow-up to #1254: Shift-clicking an anchor of a path selected as a whole (with the Selection
/// tool) deselects that anchor and keeps the others, as in Illustrator.
#[test]
fn direct_selection_shift_click_deselects_one_anchor_of_a_whole_path() {
let mut s = session();
let a = rect(&mut s, 100.0, 100.0, 100.0, 100.0);
let v = ViewInfo::default();
s.execute("select.none", &json!({})).unwrap();
s.select_tool("selection", v).unwrap();
for kind in [PointerKind::Down, PointerKind::Up] {
s.pointer(&PointerEvent::new(kind, 150.0, 150.0), v).unwrap();
}
assert_eq!(s.doc().unwrap().selection.objects, vec![a]);
assert!(s.doc().unwrap().selection.partial(a).is_none(), "selected whole");
s.select_tool("directSelection", v).unwrap();
let shift = vectorcraft_tools::Mods { shift: true, ..Default::default() };
for kind in [PointerKind::Down, PointerKind::Up] {
s.pointer(&PointerEvent::new(kind, 100.0, 100.0).with_mods(shift), v).unwrap();
}
let sel = &s.doc().unwrap().selection;
assert_eq!(sel.partial(a).map(|set| set.iter().copied().collect::<Vec<_>>()), Some(vec![(0, 1), (0, 2), (0, 3)]));
}
/// Dragging a curved segment with Direct Selection bends it (its anchors stay), and dragging a
/// straight one moves its two anchors, not the whole path; each is one undo step.
#[test]