Add interactive Mirror Repeat axis controls

This commit is contained in:
AJ Richardson
2026-10-18 19:02:44 +07:00
committed by bflatastic
parent 5906f53ca5
commit 63cebd392a
9 changed files with 436 additions and 8 deletions
+86
View File
@@ -763,3 +763,89 @@ fn inner_glow_on_a_pattern_fill_stays_inside_the_object_on_the_threaded_renderer
let [red, ..] = out.pixel(123, 200);
assert!(red > 0x40, "no glow along the edge: red {red}");
}
fn mirror_axis(s: &Session) -> vectorcraft_tools::mirror::MirrorAxis {
let st = s.active().unwrap();
vectorcraft_tools::mirror::MirrorAxis::of(&st.doc, &st.selection, 1.0).unwrap()
}
#[test]
fn mirror_axis_drag_previews_once_and_undo_redo_preserve_source() {
use vectorcraft_tools::{PointerEvent, PointerKind};
let mut s = session();
rect(&mut s, 100.0, 100.0, 60.0, 80.0);
let id = id_of(&s.execute("object.repeat.mirror", &json!({})).unwrap());
let before = node(&s, id);
let axis = mirror_axis(&s);
let v = ViewInfo::default();
s.select_tool("selection", v).unwrap();
let h = super::tests::history(&s);
for (kind, dx) in [(PointerKind::Down, 0.0), (PointerKind::Drag, 20.0), (PointerKind::Drag, 30.0), (PointerKind::Up, 40.0)] {
s.pointer(&PointerEvent::new(kind, axis.center.x + dx, axis.center.y), v).unwrap();
assert!(close(mirror_axis(&s).center, axis.center + vectorcraft_geom::Vec2::new(dx, 0.0)));
}
assert_eq!(super::tests::history(&s).0, h.0 + 1);
let after = node(&s, id);
let (NodeKind::Repeat(a), NodeKind::Repeat(b)) = (&before.kind, &after.kind) else { panic!() };
assert_eq!(a.source, b.source, "moving the mirror axis leaves the source artwork in place");
assert_ne!(a.transforms(), b.transforms(), "the reflected copy updates live");
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(node(&s, id), before);
s.execute("edit.redo", &json!({})).unwrap();
assert_eq!(node(&s, id), after);
}
#[test]
fn mirror_axis_rotation_snaps_and_escape_restores_document_and_history() {
use vectorcraft_tools::{Mods, PointerEvent, PointerKind, ToolKey};
let mut s = session();
rect(&mut s, 100.0, 100.0, 60.0, 80.0);
let id = id_of(&s.execute("object.repeat.mirror", &json!({})).unwrap());
let v = ViewInfo::default();
s.select_tool("selection", v).unwrap();
let before = node(&s, id);
let h = super::tests::history(&s);
let axis = mirror_axis(&s);
let end = axis.ends[1];
let destination = axis.center + vectorcraft_geom::Vec2::new(70.0, 60.0);
s.pointer(&PointerEvent::new(PointerKind::Down, end.x, end.y), v).unwrap();
let shift = Mods { shift: true, ..Default::default() };
s.pointer(&PointerEvent::new(PointerKind::Drag, destination.x, destination.y).with_mods(shift), v).unwrap();
assert!((mirror_axis(&s).angle - 45.0).abs() < 1e-6);
assert_eq!(mirror_axis(&s).center, axis.center);
s.tool_key(ToolKey::Escape, Mods::default(), v).unwrap();
assert!(!s.tool_busy());
s.pointer(&PointerEvent::new(PointerKind::Up, destination.x, destination.y), v).unwrap();
assert_eq!(node(&s, id), before);
assert_eq!(super::tests::history(&s), h);
// A click is not an edit, and a subsequent real drag still works after cancellation.
s.pointer(&PointerEvent::new(PointerKind::Down, end.x, end.y), v).unwrap();
s.pointer(&PointerEvent::new(PointerKind::Up, end.x, end.y), v).unwrap();
assert_eq!(super::tests::history(&s), h);
s.pointer(&PointerEvent::new(PointerKind::Down, end.x, end.y), v).unwrap();
s.pointer(&PointerEvent::new(PointerKind::Up, destination.x, destination.y).with_mods(shift), v).unwrap();
assert!((mirror_axis(&s).angle - 45.0).abs() < 1e-6);
assert_eq!(super::tests::history(&s).0, h.0 + 1);
}
#[test]
fn mirror_axis_hidden_controls_and_other_repeat_kinds_do_not_capture_drags() {
use vectorcraft_tools::{PointerEvent, PointerKind};
for command in ["object.repeat.mirror", "object.repeat.radial", "object.repeat.grid"] {
let mut s = session();
rect(&mut s, 100.0, 100.0, 60.0, 80.0);
let id = id_of(&s.execute(command, &json!({})).unwrap());
let before = node(&s, id);
let st = s.active().unwrap();
if command != "object.repeat.mirror" {
assert!(vectorcraft_tools::mirror::MirrorAxis::of(&st.doc, &st.selection, 1.0).is_none());
}
let v = ViewInfo { show_bbox: false, ..Default::default() };
s.select_tool("selection", v).unwrap();
// Away from every instance: the hidden axis's top handle cannot edit the repeat.
s.pointer(&PointerEvent::new(PointerKind::Down, 170.0, 76.0), v).unwrap();
s.pointer(&PointerEvent::new(PointerKind::Drag, 200.0, 80.0), v).unwrap();
s.pointer(&PointerEvent::new(PointerKind::Up, 200.0, 80.0), v).unwrap();
assert_eq!(node(&s, id), before);
}
}