diff --git a/crates/engine/src/cmd/path.rs b/crates/engine/src/cmd/path.rs index 9d7b42cca2..b497a9f096 100644 --- a/crates/engine/src/cmd/path.rs +++ b/crates/engine/src/cmd/path.rs @@ -315,13 +315,13 @@ fn set_handle(s: &mut Session, p: &Value) -> Result { let pos = Point::new(f64_req(p, "x", "path.setHandle")?, f64_req(p, "y", "path.setHandle")?); let independent = switch_or(p, "independent", false, "path.setHandle")?; // A handle already there, on an anchor already of the kind it would get, changes nothing and - // records no undo step. - if let Some(a) = anchor_of(s, id, si, ai)? { - let mut b = a; - b.set_handle(out, pos, independent); - if b == a { - return ok(); - } + // records no undo step. The handle is compared directly: `set_handle` would recompute the + // other handle of a smooth anchor, which need not give back the same value. + if let Some(a) = anchor_of(s, id, si, ai)? + && (if out { a.h_out } else { a.h_in }) == pos + && !(independent && a.kind != AnchorKind::Corner) + { + return ok(); } s.edit("Reshape", |d, _| { let path = path_mut(d, id)?; diff --git a/crates/engine/src/tests/anchor_edit_work.rs b/crates/engine/src/tests/anchor_edit_work.rs index 7ad50a871d..1055156167 100644 --- a/crates/engine/src/tests/anchor_edit_work.rs +++ b/crates/engine/src/tests/anchor_edit_work.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use serde_json::{Value, json}; use vectorcraft_doc::{Appearance, Document, Node, NodeId}; use vectorcraft_geom::path::measured_segments; -use vectorcraft_geom::{Anchor, PathData, Point, SubPath}; +use vectorcraft_geom::{Anchor, AnchorKind, PathData, Point, SubPath, Vec2}; use crate::*; @@ -109,3 +109,29 @@ fn an_anchor_edit_that_changes_nothing_records_no_undo_step() { s.execute("path.moveAnchor", &json!({"id": id.0, "anchor": 5, "x": a.p.x + 1.0, "y": a.p.y})).unwrap(); assert_eq!(undo_steps(&s), 1); } + +/// `path.setHandle` to where the handle already is, on a smooth anchor whose other handle is not in +/// line with it, leaves both handles as they are and records no undo step. With `independent` it +/// makes the anchor a corner, which records one. +#[test] +fn a_handle_already_in_place_on_a_smooth_anchor_records_no_undo_step() { + let (mut s, id) = long_path(100); + let p = anchor(&s, id, 5).p; + let smooth = Anchor { p, h_in: p + Vec2::new(-3.0, 0.0), h_out: p + Vec2::new(0.0, 4.0), kind: AnchorKind::Smooth }; + let path = Arc::make_mut(&mut s.doc_mut().unwrap().doc).node_mut(id).unwrap().path_data_mut().unwrap(); + path.subpaths[0].anchors[5] = smooth; + for which in ["out", "in"] { + let h = if which == "out" { smooth.h_out } else { smooth.h_in }; + s.execute("path.setHandle", &json!({"id": id.0, "anchor": 5, "which": which, "x": h.x, "y": h.y})).unwrap(); + assert_eq!(undo_steps(&s), 0, "path.setHandle on the {which} handle recorded an undo step"); + assert_eq!(anchor(&s, id, 5), smooth, "path.setHandle on the {which} handle changed the anchor"); + } + s.execute("path.setHandle", &json!({"id": id.0, "anchor": 5, "which": "out", "x": smooth.h_out.x, "y": smooth.h_out.y, "independent": true})) + .unwrap(); + assert_eq!(undo_steps(&s), 1); + assert_eq!(anchor(&s, id, 5).kind, AnchorKind::Corner); + // On a corner, the same independent request changes nothing. + s.execute("path.setHandle", &json!({"id": id.0, "anchor": 5, "which": "out", "x": smooth.h_out.x, "y": smooth.h_out.y, "independent": true})) + .unwrap(); + assert_eq!(undo_steps(&s), 1); +}