Merge fix/657

This commit is contained in:
bflatastic
2026-10-17 15:20:37 +07:00
2 changed files with 72 additions and 1 deletions
+42 -1
View File
@@ -996,8 +996,13 @@ impl Session {
self.tool.overlay.guides.clear();
let Gesture::Vector(g) = g else { return Ok(()) };
match g {
VGesture::Points { moved, split, click_clears, .. } => {
VGesture::Points { verts, others, moved, split, click_clears, .. } => {
if moved || split.is_some() {
if let ([v], None) = (verts.as_slice(), split)
&& others.iter().all(|(_, o)| o.is_empty())
{
self.merge_dropped_point(ve.id, *v, 6.0 / ev.zoom.max(1e-6))?;
}
self.commit_interaction()?;
} else {
self.cancel_interaction()?;
@@ -1057,4 +1062,40 @@ impl Session {
}
Ok(())
}
/// Point `v` of vector `id`, dragged and dropped within `tol` (page units) of another of its
/// points, merges into that point (in the same undo step as the move), which stays selected.
fn merge_dropped_point(&mut self, id: NodeId, v: usize, tol: f64) -> Result<()> {
let moved = self.ds()?.doc.get(id)?.clone();
let w = self.ds()?.doc.world_transform(id);
let NodeKind::Vector { network: net } = &moved.kind else { return Ok(()) };
let Some(p) = net.vertices.get(v).map(|x| w * x.point()) else { return Ok(()) };
let Some(keep) = net
.vertices
.iter()
.enumerate()
.filter(|(i, _)| *i != v)
.map(|(i, x)| (i, (w * x.point() - p).hypot()))
.filter(|(_, d)| *d <= tol)
.min_by(|a, b| a.1.total_cmp(&b.1))
.map(|(i, _)| i)
else {
return Ok(());
};
let mut merged = net.clone();
ops::merge_vertices(&mut merged, keep, v);
// A merge that would leave no segment (a 2-point line's ends dropped together) only moves.
if merged.segments.is_empty() {
return Ok(());
}
// The preview starts again from the document before the drag: bring the moved layer.
self.preview(|doc, _| {
*doc.get_mut(id)? = moved;
edit(doc, id, |net| *net = merged)
})?;
set_state(self, |e| {
e.vertices = vec![if keep > v { keep - 1 } else { keep }];
e.segments.clear();
})
}
}
+30
View File
@@ -280,6 +280,36 @@ fn mirroring_position_and_join_commands() {
assert!(s.execute("vector.join", &json!({"ids": [id]})).is_ok());
}
#[test]
fn a_point_dropped_on_another_merges_into_it() {
// #401: an open path's end dragged onto its start closes the path at that point.
let mut s = Session::new();
let r = ex(&mut s, "vector.create", json!({"points": [[0, 0], [100, 0], [100, 100], [0, 100]]}));
let id = crate::params::id_of(&r["id"]).unwrap();
ex(&mut s, "vector.edit.begin", json!({"id": id}));
drag(&mut s, (0.0, 100.0), (2.0, 3.0));
let n = net(&s, id);
assert_eq!(n.vertices.len(), 3, "the dropped point merged");
assert!((0..3).all(|v| n.incident(v).len() == 2), "a closed path: {n:?}");
assert_eq!(ve(&s).vertices, vec![vertex_at(&n, 0.0, 0.0)], "the point it merged into stays selected");
s.undo().unwrap();
let n = net(&s, id);
assert_eq!(n.vertices.len(), 4, "one undo step takes back the move and the merge");
vertex_at(&n, 0.0, 100.0);
// A drop away from every point only moves it.
ex(&mut s, "vector.edit.select", json!({"vertices": []}));
drag(&mut s, (0.0, 100.0), (0.0, 50.0));
assert_eq!(net(&s, id).vertices.len(), 4);
// A 2-point line's end dropped on its other end only moves: no segment would be left.
let mut s = Session::new();
let r = ex(&mut s, "vector.create", json!({"points": [[0, 0], [100, 0]]}));
let id = crate::params::id_of(&r["id"]).unwrap();
ex(&mut s, "vector.edit.begin", json!({"id": id}));
drag(&mut s, (100.0, 0.0), (2.0, 0.0));
let n = net(&s, id);
assert_eq!((n.vertices.len(), n.segments.len()), (2, 1), "still a line: {n:?}");
}
#[test]
fn undo_back_past_the_conversion_leaves_edit_mode() {
let mut s = Session::new();