Direct Selection: a click on an already selected anchor selects it alone

Clicking an anchor of a path selected whole (or one of several selected
anchors) with the Direct Selection tool left the selection as it was,
so the clicked anchor never became the selected one and Live Corners
and the anchor controls kept acting on every corner. A plain click now
selects that anchor alone, as the public documentation describes (a
click selects the anchor point, Shift-click adds others):
https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/select-anchor-points-in-paths.html

The press keeps the selection so a drag still moves every selected
anchor with the one held; only the release without a drag narrows it.
Shift still toggles, Alt still picks the whole path, and an anchor that
wasn't selected is still selected on the press.

Test: direct::tests::a_click_on_a_selected_anchor_selects_it_alone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
zakariayacine 2026-10-11 16:23:01 +01:00 committed by bflatastic
parent f08c970588
commit db7efb7923
1 changed files with 44 additions and 1 deletions

View File

@ -136,6 +136,9 @@ pub struct DirectSelectionTool {
/// The anchor under the pointer (Highlight anchors on mouse over): its path, where it is.
hover: Option<(NodeId, Point)>,
guide: GuideEdit,
/// The already selected anchor the press landed on: a click (no drag) selects it alone, as a
/// drag moves every selected anchor with it.
click: Option<(NodeId, usize, usize)>,
}
impl DirectSelectionTool {
@ -152,6 +155,7 @@ impl DirectSelectionTool {
move_snap: None,
hover: None,
guide: GuideEdit::default(),
click: None,
}
}
}
@ -360,6 +364,7 @@ impl Tool for DirectSelectionTool {
vec![]
}
(PointerKind::Down, _) => {
self.click = None;
if let Some(c) = CornerDrag::hit(cx, ev, true) {
self.state = State::Corner(c);
return vec![];
@ -412,6 +417,9 @@ impl Tool for DirectSelectionTool {
if ev.mods.shift {
return vec![Action::Exec("select.anchors".into(), json!({"id": id.0, "anchors": [[si, ai]], "mode": "toggle"}))];
}
// Pressed among other selected anchors: a click selects this one alone (the
// drag, which moves them all, keeps them).
self.click = already.then_some((id, si, ai));
if !already {
return vec![Action::Exec("select.anchors".into(), json!({"id": id.0, "anchors": [[si, ai]], "mode": "set"}))];
}
@ -628,7 +636,13 @@ impl Tool for DirectSelectionTool {
self.state = State::Idle;
self.guides.clear();
(self.anchor_snap, self.copy_snap, self.move_snap) = (None, None, None);
if began { vec![Action::Commit] } else { vec![] }
match (began, self.click.take()) {
(true, _) => vec![Action::Commit],
(false, Some((id, si, ai))) => {
vec![Action::Exec("select.anchors".into(), json!({"id": id.0, "anchors": [[si, ai]], "mode": "set"}))]
}
(false, None) => vec![],
}
}
(PointerKind::Up, State::Handle { .. } | State::SpineHandle { .. }) => {
self.state = State::Idle;
@ -969,6 +983,35 @@ mod tests {
assert_eq!(a, vec![Action::Exec("select.anchors".into(), json!({"id": id.0, "anchors": [[0, 0]], "mode": "toggle"}))]);
}
/// A click (no drag) on an anchor that is selected with others, whether the whole path is or
/// some of its anchors, selects that anchor alone, as in Illustrator: a press keeps the
/// selection so a drag moves it all, the release without a drag narrows it. Shift still
/// toggles, and a click on a path's only selected anchor changes nothing visible.
#[test]
fn a_click_on_a_selected_anchor_selects_it_alone() {
let (d, id) = doc_with_rect();
let p = paint();
let alone = vec![Action::Exec("select.anchors".into(), json!({"id": id.0, "anchors": [[0, 0]], "mode": "set"}))];
let mut whole = Selection::default();
whole.set([id]);
for s in [whole, anchors_of(id, &[(0, 0), (0, 1)])] {
let cx = cx(&d, &s, &p);
let mut t = DirectSelectionTool::new(false);
assert!(t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 100.0, 100.0)).is_empty(), "{s:?}");
assert_eq!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 100.0, 100.0)), alone, "{s:?}");
// Dragged instead: moved, selection kept.
assert!(t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 100.0, 100.0)).is_empty());
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 120.0, 100.0));
assert_eq!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 120.0, 100.0)), vec![Action::Commit], "{s:?}");
}
// An anchor not yet selected is selected on the press, and the release adds nothing.
let none = Selection::default();
let cx = cx(&d, &none, &p);
let mut t = DirectSelectionTool::new(false);
assert_eq!(t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 100.0, 100.0)), alone);
assert!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 100.0, 100.0)).is_empty());
}
/// Alt-pressing an anchor picks the whole path, as Alt on a segment does, and the drag copies
/// it as the Selection tool's Alt-drag does.
#[test]