mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 13:47:21 -06:00
Gradient Mesh: select several mesh points; move, color and opacity reach them all (#4207, #4208, #4170)
This commit is contained in:
@@ -233,6 +233,81 @@ fn a_clicked_mesh_point_takes_the_opacity() {
|
||||
assert!(mesh_opacities(&s, m).iter().all(|a| *a == 1.0));
|
||||
}
|
||||
|
||||
/// Mesh points picked together (Shift-clicks with the Mesh tool, a Direct Selection marquee) all
|
||||
/// take a solid fill colour and the Transparency panel's opacity, and only they do; the panel
|
||||
/// shows their opacity, blank when it differs; a drag of one moves them all as one undo step
|
||||
/// (#4207, #4208, #4170).
|
||||
#[test]
|
||||
fn picked_mesh_points_take_the_fill_opacity_and_drag_together() {
|
||||
let mut s = session();
|
||||
let m = rect(&mut s, json!("#ff0000"));
|
||||
s.execute("object.mesh.create", &json!({"rows": 2, "cols": 2})).unwrap();
|
||||
s.execute("select.set", &json!({"ids": [m.0]})).unwrap();
|
||||
let v = ViewInfo::default();
|
||||
let point = |s: &Session, i: usize| {
|
||||
let NodeKind::Mesh(g) = &s.doc().unwrap().doc.node(m).unwrap().kind else { panic!("not a mesh") };
|
||||
g.points[i].p
|
||||
};
|
||||
let pointer = |s: &mut Session, kind: PointerKind, q: vectorcraft_geom::Point, shift: bool| {
|
||||
let mut ev = PointerEvent::new(kind, q.x, q.y);
|
||||
ev.mods.shift = shift;
|
||||
s.pointer(&ev, v).unwrap();
|
||||
};
|
||||
let click = |s: &mut Session, i: usize, shift: bool| {
|
||||
let q = point(s, i);
|
||||
pointer(s, PointerKind::Down, q, shift);
|
||||
pointer(s, PointerKind::Up, q, shift);
|
||||
};
|
||||
let check = |s: &Session, picked: &[usize], hex: &str, opacity: f32, what: &str| {
|
||||
let (c, o) = (mesh_colors(s, m), mesh_opacities(s, m));
|
||||
for i in 0..c.len() {
|
||||
let on = picked.contains(&i);
|
||||
assert_eq!(c[i].to_hex() == hex, on, "{what}: point {i} colour {c:?}");
|
||||
assert_eq!((o[i] - opacity).abs() < 1e-6, on, "{what}: point {i} opacity {o:?}");
|
||||
}
|
||||
assert_eq!(s.doc().unwrap().doc.node(m).unwrap().opacity, 1.0, "{what}: the object stays opaque");
|
||||
};
|
||||
// The Mesh tool: a click, then Shift-clicks add points (and a second one takes one away).
|
||||
s.select_tool("mesh", v).unwrap();
|
||||
click(&mut s, 1, false);
|
||||
for i in [4, 8, 8] {
|
||||
click(&mut s, i, true);
|
||||
}
|
||||
s.execute("paint.setFill", &json!({"color": "#0000ff"})).unwrap();
|
||||
let undo = s.doc().unwrap().history.undo.len();
|
||||
s.execute("transparency.set", &json!({"opacity": 40})).unwrap();
|
||||
check(&s, &[1, 4], "#0000ff", 0.4, "mesh tool");
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), undo + 1, "one undo step for both points");
|
||||
assert_eq!(s.transparency_info().opacity, Some(40.0), "the panel shows the points' opacity");
|
||||
s.execute("object.mesh.setPointColor", &json!({"id": m.0, "index": 4, "opacity": 0.9})).unwrap();
|
||||
assert_eq!(s.transparency_info().opacity, None, "mixed: blank");
|
||||
// Direct Selection: a marquee over the top-left patch picks its four points.
|
||||
s.select_tool("directSelection", v).unwrap();
|
||||
let corner = point(&s, 0);
|
||||
pointer(&mut s, PointerKind::Down, corner - vectorcraft_geom::Vec2::new(8.0, 8.0), false);
|
||||
let far = point(&s, 4) + vectorcraft_geom::Vec2::new(5.0, 5.0);
|
||||
pointer(&mut s, PointerKind::Drag, far, false);
|
||||
pointer(&mut s, PointerKind::Up, far, false);
|
||||
assert_eq!(s.doc().unwrap().selection.objects, [m], "the marquee selects the mesh");
|
||||
s.execute("paint.setFill", &json!({"color": "#00ff00"})).unwrap();
|
||||
s.execute("transparency.set", &json!({"opacity": 20})).unwrap();
|
||||
check(&s, &[0, 1, 3, 4], "#00ff00", 0.2, "marquee");
|
||||
// A drag of one picked point moves all four by the same offset, in one step.
|
||||
let before: Vec<_> = (0..9).map(|i| point(&s, i)).collect();
|
||||
let undo = s.doc().unwrap().history.undo.len();
|
||||
let d = vectorcraft_geom::Vec2::new(4.0, 6.0);
|
||||
pointer(&mut s, PointerKind::Down, before[4], false);
|
||||
pointer(&mut s, PointerKind::Drag, before[4] + d, false);
|
||||
pointer(&mut s, PointerKind::Up, before[4] + d, false);
|
||||
for (i, was) in before.iter().enumerate() {
|
||||
let want = if [0, 1, 3, 4].contains(&i) { *was + d } else { *was };
|
||||
assert!(point(&s, i).distance(want) < 1e-9, "point {i}: {:?} ≠ {want:?}", point(&s, i));
|
||||
}
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), undo + 1, "one undo step");
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!((0..9).map(|i| point(&s, i)).collect::<Vec<_>>(), before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blend_moves_a_mesh_to_the_blended_colour() {
|
||||
let mut s = session();
|
||||
|
||||
@@ -851,6 +851,63 @@ fn mesh_point_colour_move_and_undo() {
|
||||
assert!(s.execute("object.mesh.setPointColor", &json!({"id": a.0, "index": 99, "color": "#000000"})).is_err());
|
||||
}
|
||||
|
||||
/// `object.mesh.movePoints` moves just the points it names (their handles with them), over one or
|
||||
/// several meshes, as one undo step; `object.mesh.setPointColor` colours and fades several points
|
||||
/// alike. A bad index, a non-number or a move past the coordinate range fails and changes nothing
|
||||
/// (#4207, #4208).
|
||||
#[test]
|
||||
fn mesh_points_move_and_colour_together() {
|
||||
let mut s = session();
|
||||
let a = rect(&mut s, 0.0, 0.0, 100.0, 100.0);
|
||||
let b = rect(&mut s, 200.0, 0.0, 100.0, 100.0);
|
||||
sel(&mut s, &[a, b]);
|
||||
s.execute("object.mesh.create", &json!({"rows": 2, "cols": 2})).unwrap();
|
||||
let (ma, mb) = (mesh_of(&s, a), mesh_of(&s, b));
|
||||
let n = undo_len(&s);
|
||||
s.execute("object.mesh.movePoints", &json!({"id": a.0, "points": [0, 4, 4], "dx": 10, "dy": -5})).unwrap();
|
||||
let m = mesh_of(&s, a);
|
||||
for (i, (q, was)) in m.points.iter().zip(&ma.points).enumerate() {
|
||||
let want = if i == 0 || i == 4 { Point::new(was.p.x + 10.0, was.p.y - 5.0) } else { was.p };
|
||||
assert_eq!((q.p, q.handles), (want, was.handles), "point {i}: moved once if named, its handles with it");
|
||||
}
|
||||
assert_eq!(undo_len(&s), n + 1);
|
||||
s.execute("object.mesh.movePoints", &json!({"items": [{"id": a.0, "points": [1]}, {"id": b.0, "index": 2}], "dx": 1, "dy": 1})).unwrap();
|
||||
assert_eq!(mesh_of(&s, a).points[1].p, Point::new(ma.points[1].p.x + 1.0, ma.points[1].p.y + 1.0));
|
||||
assert_eq!(mesh_of(&s, b).points[2].p, Point::new(mb.points[2].p.x + 1.0, mb.points[2].p.y + 1.0));
|
||||
assert_eq!(undo_len(&s), n + 2, "both meshes in one step");
|
||||
// A move that would take a point past the coordinate range fails.
|
||||
assert!(s.execute("object.mesh.movePoints", &json!({"id": a.0, "points": [0], "dx": 1e308, "dy": 0})).is_err());
|
||||
assert_eq!(undo_len(&s), n + 2);
|
||||
for _ in 0..2 {
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
}
|
||||
assert_eq!((mesh_of(&s, a), mesh_of(&s, b)), (ma.clone(), mb.clone()));
|
||||
// Several points take one colour and opacity, in one step.
|
||||
s.execute("object.mesh.setPointColor", &json!({"id": a.0, "points": [2, 6], "color": "#0000ff", "opacity": 0.25})).unwrap();
|
||||
for (i, q) in mesh_of(&s, a).points.iter().enumerate() {
|
||||
let named = i == 2 || i == 6;
|
||||
assert_eq!((q.color.to_hex() == "#0000ff", q.opacity == 0.25), (named, named), "point {i}");
|
||||
}
|
||||
assert_eq!(undo_len(&s), n + 1);
|
||||
let n = undo_len(&s);
|
||||
let before = (mesh_of(&s, a), mesh_of(&s, b));
|
||||
for p in [
|
||||
json!({"id": a.0, "points": [0, 99], "dx": 1, "dy": 1}),
|
||||
json!({"items": [{"id": a.0, "points": [0]}, {"id": b.0, "points": [99]}], "dx": 1, "dy": 1}),
|
||||
json!({"id": a.0, "points": [-1], "dx": 1, "dy": 1}),
|
||||
json!({"id": a.0, "points": [1.5], "dx": 1, "dy": 1}),
|
||||
json!({"id": a.0, "points": ["1"], "dx": 1, "dy": 1}),
|
||||
json!({"id": a.0, "points": 1, "dx": 1, "dy": 1}),
|
||||
json!({"id": a.0, "points": [0], "dx": "1", "dy": 1}),
|
||||
json!({"items": 3, "dx": 1, "dy": 1}),
|
||||
json!({"points": [0], "dx": 1, "dy": 1}),
|
||||
] {
|
||||
assert!(s.execute("object.mesh.movePoints", &p).is_err(), "{p}");
|
||||
}
|
||||
assert!(s.execute("object.mesh.setPointColor", &json!({"id": a.0, "points": [0, 9], "color": "#000000"})).is_err());
|
||||
assert_eq!(((mesh_of(&s, a), mesh_of(&s, b)), undo_len(&s)), (before, n), "nothing changed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mesh_add_line_and_delete_point() {
|
||||
let mut s = session();
|
||||
|
||||
Reference in New Issue
Block a user