Direct Selection: a Shift-click on a segment pick's endpoint selects that anchor (#1278)

The endpoints of a selected segment are hollow, so select.anchors and select.anchorsMany now
start from no anchors of that path instead of toggling the endpoint off.
This commit is contained in:
bflatastic 2026-10-12 06:54:09 +07:00
parent 1ac797685f
commit 4149a09d61
2 changed files with 26 additions and 2 deletions

View File

@ -377,7 +377,10 @@ fn anchors(s: &mut Session, p: &Value) -> Result<Value> {
// 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);
sel.segments.remove(&id);
// A segment pick's endpoints aren't selected anchors: start from none of them.
if sel.segments.remove(&id).is_some() {
sel.anchors.remove(&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)));
@ -416,7 +419,8 @@ fn anchors_many(s: &mut Session, p: &Value) -> Result<Value> {
sel.clear();
}
for (id, refs) in items {
sel.segments.remove(&id);
// A segment pick's endpoints aren't selected anchors: start from none of them.
let segment_pick = sel.segments.remove(&id).is_some();
let all: BTreeSet<(usize, usize)> =
d.node(id).and_then(|n| n.path_data()).map(|p| p.anchors().map(|(s, i, _)| (s, i)).collect()).unwrap_or_default();
// Anything but a path is selected, toggled or deselected as a whole.
@ -430,6 +434,7 @@ fn anchors_many(s: &mut Session, p: &Value) -> Result<Value> {
}
// From the anchors selected now (all of them for a path selected as a whole).
let now = match sel.partial(id) {
_ if segment_pick => BTreeSet::new(),
Some(a) => a.clone(),
None if sel.contains(id) => all.clone(),
None => BTreeSet::new(),

View File

@ -724,6 +724,25 @@ fn direct_segment_delete_keeps_the_other_rectangle_edges_and_one_undo() {
}
}
/// A segment pick's endpoints are hollow: Shift-clicking or Shift-marqueeing one selects that
/// anchor, rather than toggling it off and leaving the other endpoint selected.
#[test]
fn selecting_an_anchor_after_a_segment_pick_starts_from_no_anchors() {
let mut s = session();
let id = rect(&mut s, 100.0, 100.0, 200.0, 150.0);
let segment = json!({"id": id.0, "segments": [[0, 0]]});
s.execute("select.segments", &segment).unwrap();
s.execute("select.anchors", &json!({"id": id.0, "anchors": [[0, 0]], "mode": "toggle"})).unwrap();
let sel = &s.doc().unwrap().selection;
assert_eq!(sel.partial(id), Some(&[(0, 0)].into()));
assert!(sel.segments.is_empty());
s.execute("select.segments", &segment).unwrap();
s.execute("select.anchorsMany", &json!({"items": [{"id": id.0, "anchors": [[0, 1]]}], "mode": "add"})).unwrap();
let sel = &s.doc().unwrap().selection;
assert_eq!(sel.partial(id), Some(&[(0, 1)].into()));
assert!(sel.segments.is_empty());
}
#[test]
fn segment_delete_preserves_curves_appearance_and_unrelated_contours() {
let data = "M100 100C110 70 150 130 170 100C180 75 230 125 250 100C270 140 280 80 310 100C320 120 140 140 100 100Z M400 100L500 100L500 200Z";