mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 08:29:14 +00:00
Mesh: adding a line at a mesh point records no undo step
object.mesh.addLine at a point that is already a mesh point (a corner, a point on an edge or inside) adds neither a row nor a column, yet it recorded an undo step. The lines are now added to a copy of the object first: when the grid stays the same, including a mesh envelope stored without handles, the command returns the point's index and records nothing. A new color at the point is still one undo step.
This commit is contained in:
@@ -290,7 +290,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Add Mesh Line",
|
||||
[],
|
||||
None,
|
||||
"{id, x, y, color? (#rrggbbaa and #rgba are refused), keepModel?: bool (as paint.setFill), tolerance?: pt (0)} add a row and a column through (x, y), inside or on the edge, to a gradient mesh (new point in `color`) or a mesh envelope; a point outside within `tolerance` of the edge adds them through the nearest point of the edge → {index}",
|
||||
"{id, x, y, color? (#rrggbbaa and #rgba are refused), keepModel?: bool (as paint.setFill), tolerance?: pt (0)} add a row and a column through (x, y), inside or on the edge, to a gradient mesh (new point in `color`) or a mesh envelope; a point outside within `tolerance` of the edge adds them through the nearest point of the edge; at a point that is already a mesh point it adds no line, and records no undo step unless `color` changes it → {index}",
|
||||
has_doc,
|
||||
mesh_add_line
|
||||
),
|
||||
|
||||
@@ -122,9 +122,14 @@ fn with_mesh<T>(s: &mut Session, p: &Value, cmd: &str, label: &str, envelopes: b
|
||||
/// Run `f` on the gradient mesh `id` of `d`; with `envelopes`, a mesh envelope's grid too (its
|
||||
/// points and handles are written back: editing fills in the handles).
|
||||
fn edit_grid<T>(d: &mut Document, id: NodeId, cmd: &str, envelopes: bool, f: impl FnOnce(&mut GradientMesh) -> Result<T>) -> Result<T> {
|
||||
match d.node_mut(id).map(|n| &mut n.kind) {
|
||||
Some(NodeKind::Mesh(m)) => f(m),
|
||||
Some(NodeKind::Envelope { kind, .. }) if envelopes && matches!(**kind, EnvelopeKind::Mesh { .. }) => {
|
||||
edit_kind(&mut d.node_mut(id).ok_or(EngineError::NoNode(id))?.kind, cmd, envelopes, f)
|
||||
}
|
||||
|
||||
/// [`edit_grid`] on a node's kind.
|
||||
fn edit_kind<T>(k: &mut NodeKind, cmd: &str, envelopes: bool, f: impl FnOnce(&mut GradientMesh) -> Result<T>) -> Result<T> {
|
||||
match k {
|
||||
NodeKind::Mesh(m) => f(m),
|
||||
NodeKind::Envelope { kind, .. } if envelopes && matches!(**kind, EnvelopeKind::Mesh { .. }) => {
|
||||
let EnvelopeKind::Mesh { rows, cols, points, handles } = &mut **kind else {
|
||||
return Err(bad(cmd, "not a gradient mesh or mesh envelope"));
|
||||
};
|
||||
@@ -135,8 +140,7 @@ fn edit_grid<T>(d: &mut Document, id: NodeId, cmd: &str, envelopes: bool, f: imp
|
||||
*handles = m.points.iter().map(|q| q.handles).collect();
|
||||
Ok(r)
|
||||
}
|
||||
Some(_) => Err(bad(cmd, if envelopes { "not a gradient mesh or mesh envelope" } else { "not a gradient mesh" })),
|
||||
None => Err(EngineError::NoNode(id)),
|
||||
_ => Err(bad(cmd, if envelopes { "not a gradient mesh or mesh envelope" } else { "not a gradient mesh" })),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,12 +285,38 @@ pub(super) fn mesh_add_line(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
check_numbers(p, &["tolerance"], C)?;
|
||||
let tol = f64_or(p, "tolerance", 0.0).max(0.0);
|
||||
let color = color_param(p, "color", C)?.map(|c| crate::cmd::paint::new_color(s, p, c));
|
||||
let index = with_mesh(s, p, C, "Add Mesh Line", true, |m| {
|
||||
let add = move |m: &mut GradientMesh| {
|
||||
let i = m.add_lines_at(q, tol).ok_or_else(|| bad(C, "point is outside the mesh"))?;
|
||||
if let Some(c) = color {
|
||||
m.points[i].color = c;
|
||||
}
|
||||
Ok(i)
|
||||
};
|
||||
let id = object_param(p, "id", C)?.ok_or_else(|| bad(C, "missing `id`"))?;
|
||||
if s.script_running() {
|
||||
return Err(EngineError::Busy);
|
||||
}
|
||||
// The lines are added to a copy of the object. A point that is already a mesh point adds none:
|
||||
// when its color stays too, nothing changes and no undo step is recorded. The grid is compared,
|
||||
// not the node: a mesh envelope stored without handles takes them in any edit.
|
||||
let orig = s.doc()?.doc.node(id).ok_or(EngineError::NoNode(id))?.kind.clone();
|
||||
let mut kind = orig.clone();
|
||||
let (i, same) = edit_kind(&mut kind, C, true, |m| {
|
||||
let before = m.clone();
|
||||
let i = add(m)?;
|
||||
Ok((i, *m == before))
|
||||
})?;
|
||||
if same {
|
||||
return Ok(json!({ "index": i }));
|
||||
}
|
||||
let index = s.edit("Add Mesh Line", move |d, _| {
|
||||
let n = d.node_mut(id).ok_or(EngineError::NoNode(id))?;
|
||||
// Settling typed values before the edit may have changed the object: add the lines again.
|
||||
if n.kind != orig {
|
||||
return edit_kind(&mut n.kind, C, true, add);
|
||||
}
|
||||
n.kind = kind;
|
||||
Ok(i)
|
||||
})?;
|
||||
Ok(json!({ "index": index }))
|
||||
}
|
||||
|
||||
@@ -123,6 +123,25 @@ fn reset_with_warp_and_mesh_switch_the_kind_and_keep_the_content() {
|
||||
assert!(s.execute("object.envelope.resetWithMesh", &json!({})).is_err());
|
||||
}
|
||||
|
||||
/// `object.mesh.addLine` at a point a mesh envelope already has (one stored without handles) adds
|
||||
/// no line and records no undo step; a point between its lines adds them as one step.
|
||||
#[test]
|
||||
fn mesh_envelope_add_line_at_a_mesh_point_records_no_undo_step() {
|
||||
let mut s = session();
|
||||
let (_, e) = enveloped(&mut s, "object.envelope.makeWithMesh", json!({"rows": 2, "cols": 2}));
|
||||
let before = kind(&s, e);
|
||||
assert!(matches!(&before, EnvelopeKind::Mesh { handles, .. } if handles.is_empty()), "{before:?}");
|
||||
let undo_len = |s: &Session| s.doc().unwrap().history.undo.len();
|
||||
let n = undo_len(&s);
|
||||
for (x, y) in [(100.0, 100.0), (300.0, 200.0), (200.0, 150.0), (300.0, 150.0)] {
|
||||
s.execute("object.mesh.addLine", &json!({"id": e.0, "x": x, "y": y})).unwrap();
|
||||
assert_eq!((kind(&s, e), undo_len(&s)), (before.clone(), n), "({x}, {y}): nothing changed");
|
||||
}
|
||||
s.execute("object.mesh.addLine", &json!({"id": e.0, "x": 150, "y": 125})).unwrap();
|
||||
assert!(matches!(kind(&s, e), EnvelopeKind::Mesh { rows: 3, cols: 3, .. }), "{:?}", kind(&s, e));
|
||||
assert_eq!(undo_len(&s), n + 1);
|
||||
}
|
||||
|
||||
/// A new warp envelope bends with its style's Bézier net (`version` [`live::WARP_NET`]) and keeps
|
||||
/// it through Reset with Warp. One saved before the version existed keeps the earlier map when its
|
||||
/// warp or its options change.
|
||||
|
||||
@@ -1206,6 +1206,25 @@ fn mesh_add_line_exactly_on_the_edge_needs_no_tolerance() {
|
||||
assert_eq!(undo_len(&s), n, "nothing changed");
|
||||
}
|
||||
|
||||
/// `object.mesh.addLine` at a point that is already a mesh point (a corner, a point on an edge or
|
||||
/// inside) adds no line and records no undo step; with a new `color` there it is one step.
|
||||
#[test]
|
||||
fn mesh_add_line_at_a_mesh_point_records_no_undo_step() {
|
||||
let mut s = session();
|
||||
let a = rect(&mut s, 0.0, 0.0, 100.0, 100.0);
|
||||
sel(&mut s, &[a]);
|
||||
s.execute("object.mesh.create", &json!({"rows": 2, "cols": 2})).unwrap();
|
||||
let (before, n) = (mesh_of(&s, a), undo_len(&s));
|
||||
for (x, y) in [(0.0, 0.0), (100.0, 100.0), (50.0, 100.0), (100.0, 50.0), (50.0, 50.0)] {
|
||||
let r = s.execute("object.mesh.addLine", &json!({"id": a.0, "x": x, "y": y})).unwrap();
|
||||
let p = mesh_of(&s, a).points[r["index"].as_u64().unwrap() as usize].p;
|
||||
assert!(p.distance(Point::new(x, y)) < 0.5, "({x}, {y}): {p:?}");
|
||||
assert_eq!((mesh_of(&s, a), undo_len(&s)), (before.clone(), n), "({x}, {y}): nothing changed");
|
||||
}
|
||||
s.execute("object.mesh.addLine", &json!({"id": a.0, "x": 100, "y": 100, "color": "#00ff00"})).unwrap();
|
||||
assert_eq!(undo_len(&s), n + 1, "a new color is a step");
|
||||
}
|
||||
|
||||
/// A mesh point `index`, and `rows`, `cols` or `columns` for Create Gradient Mesh, that is not a
|
||||
/// whole number is an error that changes nothing; 4.0 names point 4.
|
||||
#[test]
|
||||
|
||||
+2
-1
@@ -9627,7 +9627,8 @@ right/left/down/up; none stored means the smooth mesh through the points, as old
|
||||
`object.mesh.deletePoint` edit them like a gradient mesh's (one undo step each). On the canvas a selected envelope
|
||||
shows its mesh; the Mesh tool and Direct Selection drag its points and the clicked point's handles, and the Mesh tool
|
||||
adds a row and a column where it clicks inside. `object.mesh.addLine`, and `object.mesh.create` with `at`, take a point exactly on
|
||||
the mesh's outline (within a millionth of a point) on any side without `tolerance`; a point outside needs `tolerance`. `press_key` Escape during a drag of a point or handle of a mesh
|
||||
the mesh's outline (within a millionth of a point) on any side without `tolerance`; a point outside needs `tolerance`. `object.mesh.addLine` at a point that is already a mesh
|
||||
point adds no line and records no undo step unless its `color` changes the point. `press_key` Escape during a drag of a point or handle of a mesh
|
||||
envelope or a gradient mesh cancels it: the point goes back and no undo step is added. The Control bar shows Edit Envelope / Edit Contents, the warp's
|
||||
style, orientation, bend and distortions (or the mesh's rows and columns), Reset (an unbent warp, a flat mesh) and
|
||||
Envelope Options.
|
||||
|
||||
Reference in New Issue
Block a user