Join the selected end points of two paths with Ctrl+J

Fixes #4163

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-26 08:18:33 +07:00
committed by bflatastic
co-authored by Claude Haiku 5.5
parent 6259448c39
commit b089b7e948
2 changed files with 35 additions and 2 deletions
+21 -2
View File
@@ -467,6 +467,9 @@ fn join(s: &mut Session, p: &Value) -> Result<Value> {
if a == b {
return Err(bad(C, "join two different paths"));
}
let st = s.doc()?;
let picked_a = st.selection.anchors.get(&a).cloned().unwrap_or_default();
let picked_b = st.selection.anchors.get(&b).cloned().unwrap_or_default();
s.edit("Join", |d, sel| {
let pb = d.node(b).and_then(|n| n.path_data()).cloned().ok_or(EngineError::NoNode(b))?;
let pa = path_mut(d, a)?;
@@ -481,8 +484,24 @@ fn join(s: &mut Session, p: &Value) -> Result<Value> {
// The ends asked for, else the closest pair.
let cands =
[(xl.distance(yf), false, false), (xl.distance(yl), false, true), (xf.distance(yf), true, false), (xf.distance(yl), true, true)];
let (rx, ry) =
ends.unwrap_or_else(|| cands.into_iter().min_by(|p, q| p.0.total_cmp(&q.0)).map_or((false, false), |(_, rx, ry)| (rx, ry)));
// Direct-selected end points decide which ends meet, before the closest pair does.
let end_of = |picked: &std::collections::BTreeSet<(usize, usize)>, si: usize, len: usize| {
let last_ix = len.checked_sub(1)?;
let first = picked.contains(&(si, 0));
let last = picked.contains(&(si, last_ix));
match (first, last) {
(true, false) => Some(true),
(false, true) => Some(false),
_ => None,
}
};
let by_selection = match (end_of(&picked_a, sa, x.anchors.len()), end_of(&picked_b, sb, y.anchors.len())) {
(Some(fa), Some(fb)) => Some((fa, !fb)),
_ => None,
};
let (rx, ry) = ends
.or(by_selection)
.unwrap_or_else(|| cands.into_iter().min_by(|p, q| p.0.total_cmp(&q.0)).map_or((false, false), |(_, rx, ry)| (rx, ry)));
if rx {
x.reverse();
}
+14
View File
@@ -2188,6 +2188,20 @@ fn join_takes_the_paths_and_the_ends_to_join() {
}
}
/// Without `ends`, the end points picked with the direct selection are the ones joined.
#[test]
fn join_uses_the_selected_end_points() {
let mut s = session();
let a = line(&mut s, 0.0, 0.0, 100.0, 0.0);
let b = line(&mut s, 110.0, 0.0, 200.0, 0.0);
s.execute("select.all", &json!({})).unwrap();
s.execute("select.anchors", &json!({"id": a.0, "anchors": [[0, 0]], "mode": "add"})).unwrap();
s.execute("select.anchors", &json!({"id": b.0, "anchors": [[0, 1]], "mode": "add"})).unwrap();
s.execute("path.join", &json!({})).unwrap();
let pts: Vec<(f64, f64)> = path(&s, a).subpaths[0].anchors.iter().map(|x| (x.p.x, x.p.y)).collect();
assert_eq!(pts, [(100.0, 0.0), (0.0, 0.0), (200.0, 0.0), (110.0, 0.0)]);
}
/// `path.join {to}` makes the joined anchor a corner or smooth point.
#[test]
fn join_to_corner_or_smooth_sets_the_merged_anchor() {