M2.1: Pen continuation in a compound path reverses only the path it continues

Clicking the first anchor of an open path inside a compound path made the Pen
select the compound and then reverse through the selection, which `paths_in`
expands to every member path: the clicked subpath of each sibling flipped too,
and under the non-zero fill rule a hole became a fill. Name the path in the
reverse instead.

The adjacent continuation of an already selected path is unaffected: it issues
no selection change, so its selection-addressed reverse still means that path.

Regressions: an engine test continues a compound member with two open subpaths
at its first anchor and holds the member's other subpath and the sibling member
byte-identical (anchors, handles, closed flags); a tool test pins the emitted
`path.reverse` parameters to the member's id.
This commit is contained in:
skylar taylor-barrick
2026-10-24 16:08:09 -06:00
parent ba27d1dcc1
commit 11f1d5c4d6
2 changed files with 68 additions and 3 deletions
+26
View File
@@ -2960,3 +2960,29 @@ fn join_two_refuses_an_empty_subpath() {
.unwrap();
assert!(s.execute("path.join", &json!({"ids": [a.0, b.0], "ends": ["last", "first"], "subpaths": [0, 0]})).is_err());
}
/// Continuing a compound member at its first anchor reverses that subpath only. The Pen puts the
/// compound back in the selection before it reverses, so the reverse has to name the member path:
/// addressed through the selection it would walk the compound and flip every member's subpath.
#[test]
fn pen_continue_at_a_first_anchor_in_a_compound_reverses_only_that_subpath() {
let (v, none) = (view(), Mods::default());
let mut s = session();
let a = NodeId(s.execute("path.create", &json!({"d": "M100 100 L200 100 M100 150 L200 150"})).unwrap()["id"].as_u64().unwrap());
let b = NodeId(s.execute("path.create", &json!({"d": "M300 100 L400 100 M300 150 L400 150"})).unwrap()["id"].as_u64().unwrap());
s.execute("select.set", &json!({"ids": [a.0, b.0]})).unwrap();
let cid = NodeId(s.execute("object.compoundPath.make", &json!({})).unwrap()["id"].as_u64().unwrap());
// The member path itself is the whole selection, as a direct selection inside the compound
// leaves it: continuing it selects the compound again, which must not widen the reverse.
s.execute("select.set", &json!({"ids": [a.0]})).unwrap();
let (before_a, before_b) = (path(&s, a), path(&s, b));
s.select_tool("pen", v).unwrap();
gesture(&mut s, &[(100.0, 100.0)], none);
let after_a = path(&s, a);
assert_eq!(after_a.subpaths[0].anchors.iter().map(|x| x.p).collect::<Vec<_>>(), vec![Point::new(200.0, 100.0), Point::new(100.0, 100.0)]);
assert_eq!(after_a.subpaths[1], before_a.subpaths[1], "the member's other subpath keeps its anchors and handles");
assert_eq!(path(&s, b), before_b, "the compound's other member keeps its anchors, handles and closed flags");
assert_eq!(s.doc().unwrap().selection.objects, vec![cid], "the compound is selected again");
}