diff --git a/crates/engine/src/canvas/vectoredit.rs b/crates/engine/src/canvas/vectoredit.rs index 5dcd8c0ba..47597ea92 100644 --- a/crates/engine/src/canvas/vectoredit.rs +++ b/crates/engine/src/canvas/vectoredit.rs @@ -1017,9 +1017,18 @@ pub fn specs() -> Vec { if moves.is_empty() { return Err(bad("vector.edit.position", "select points")); } + // Points with the same offset move together, so moving every point by one offset + // still carries the regions' paint frames, as a drag of all of them does. + let mut groups: Vec<(Vec2, Vec)> = Vec::new(); + for (i, d) in moves { + match groups.iter_mut().find(|(g, _)| (*g - d).hypot() < 1e-9) { + Some((_, v)) => v.push(i), + None => groups.push((d, vec![i])), + } + } edit(s.doc_mut()?, ve.id, |net| { - for (i, d) in &moves { - ops::move_vertices(net, &[*i], *d); + for (d, v) in &groups { + ops::move_vertices(net, v, *d); } })?; Ok(json!({"ok": true})) diff --git a/crates/engine/src/canvas/vectoredit/position_tests.rs b/crates/engine/src/canvas/vectoredit/position_tests.rs index f64da34d5..fb6af9105 100644 --- a/crates/engine/src/canvas/vectoredit/position_tests.rs +++ b/crates/engine/src/canvas/vectoredit/position_tests.rs @@ -100,6 +100,32 @@ fn the_values_are_in_the_parents_coordinates() { assert!(near(after[0].1, before[0].1)); } +#[test] +fn moving_every_point_by_one_offset_carries_the_regions_paint_frame() { + // `{add}` on every point is a plain translation: the region's paint frame goes with the + // points, as when they are dragged together. + let mut s = Session::new(); + let id = rect(&mut s); + { + let node = s.doc_mut().unwrap().get_mut(id).unwrap(); + let NodeKind::Vector { network } = &mut node.kind else { panic!("expected a vector") }; + assert!(!network.regions.is_empty(), "the rectangle has a region"); + for region in &mut network.regions { + region.paint_space = Some(figcraft_doc::RegionPaintSpace { width: 100.0, height: 100.0, transform: Affine::IDENTITY }); + } + } + let frame = |s: &Session| { + let world = s.doc().unwrap().world_transform(id); + world * net(s, id).regions[0].paint_space.as_ref().unwrap().transform + }; + let before = frame(&s); + let all: Vec = (0..net(&s, id).vertices.len()).collect(); + ex(&mut s, "vector.edit.select", json!({"vertices": all})); + ex(&mut s, "vector.edit.position", json!({"x": {"add": 10}})); + let (want, got) = ((Affine::translate((10.0, 0.0)) * before).as_coeffs(), frame(&s).as_coeffs()); + assert!(want.iter().zip(got).all(|(a, b)| near(*a, b)), "{got:?} vs {want:?}"); +} + #[test] fn junk_is_refused_and_changes_nothing() { let mut s = Session::new();