From b7b107ce24b4b7502313cee67579d44240cb098a Mon Sep 17 00:00:00 2001 From: nrq6993-a11y Date: Mon, 26 Oct 2026 22:07:58 +0700 Subject: [PATCH] G11: Join tool connects endpoints near scrub line ends Squashed from #4272 onto main. --- crates/engine/src/cmd/draw2.rs | 16 ++++++++++++++-- crates/engine/src/tests_draw2.rs | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/cmd/draw2.rs b/crates/engine/src/cmd/draw2.rs index 8a8e7b8b1..0ef78a06a 100644 --- a/crates/engine/src/cmd/draw2.rs +++ b/crates/engine/src/cmd/draw2.rs @@ -1497,6 +1497,7 @@ fn join_scrub(s: &mut Session, p: &Value) -> Result { .min_by(|a, b| a.1.total_cmp(&b.1)) .map_or(0.0, |x| x.0) }; + let (first_pt, last_pt) = (pts.first().copied(), pts.last().copied()); let mut ends: Vec<(f64, NodeId, usize, bool, Point)> = vec![]; for id in target_paths(&st.doc, &st.selection, poly_bounds(&pts, tol)) { let (pd, _) = path_of(&st.doc, id)?; @@ -1505,9 +1506,20 @@ fn join_scrub(s: &mut Session, p: &Value) -> Result { continue; } for (start, a) in [(true, sp.anchors[0].p), (false, sp.anchors[sp.anchors.len() - 1].p)] { - if dist_to_polyline(&pts, a) <= tol { - ends.push((order(a), id, si, start, a)); + let on_scrub = dist_to_polyline(&pts, a) <= tol; + let near_start = first_pt.is_some_and(|p| a.distance(p) <= tol); + let near_end = last_pt.is_some_and(|p| a.distance(p) <= tol); + if !on_scrub && !near_start && !near_end { + continue; } + let ord = if on_scrub { + order(a) + } else if near_start && (!near_end || first_pt.is_some_and(|p| a.distance(p) <= a.distance(last_pt.unwrap_or(p)))) { + -1.0 + } else { + 1e30 + }; + ends.push((ord, id, si, start, a)); } } } diff --git a/crates/engine/src/tests_draw2.rs b/crates/engine/src/tests_draw2.rs index ffb72a346..b14e29979 100644 --- a/crates/engine/src/tests_draw2.rs +++ b/crates/engine/src/tests_draw2.rs @@ -944,6 +944,21 @@ fn join_tool_joins_two_lines() { assert_eq!(p.subpaths[0].anchors[2].p, Point::new(300.0, 150.0)); } +/// #2899: a scrub from one open end toward the other joins them when the line passes near the +/// endpoints, not only when it crosses them. +#[test] +fn join_tool_joins_endpoints_near_the_scrub_ends() { + let mut s = session(); + let a = line(&mut s, 100.0, 100.0, 200.0, 100.0); + line(&mut s, 205.0, 100.0, 300.0, 150.0); + s.execute("select.none", &json!({})).unwrap(); + drag(&mut s, "join", &[(198.0, 92.0), (207.0, 92.0)], Mods::default()); + assert_eq!(paths(&s).len(), 1); + let p = path(&s, a); + assert_eq!(p.anchor_count(), 3); + assert_eq!(p.subpaths[0].anchors[1].p, Point::new(202.5, 100.0)); +} + #[test] fn line_family_tools_draw_and_click_asks_dialog() { let mut s = session();