diff --git a/crates/engine/src/cmd/object.rs b/crates/engine/src/cmd/object.rs index 8812da20b..c559b4bff 100644 --- a/crates/engine/src/cmd/object.rs +++ b/crates/engine/src/cmd/object.rs @@ -380,6 +380,21 @@ pub(crate) fn transform_node(n: &mut Node, xf: Affine, sc: Scaling, objects: boo /// `anchors` (`ids` being the selection): with anchors selected with Direct Selection and no /// copy, only those anchors transform, their handles with them, and the objects selected whole. pub(crate) fn apply_transform(s: &mut Session, label: &str, ids: Vec, xf: Affine, p: &Value, anchors: bool) -> Result { + apply_transform_moving_guides(s, label, ids, xf, p, anchors, None) +} + +/// [`apply_transform`], with the selected ruler guides moved by `guides` instead of by the +/// translation `xf` when given (a nudge along a turned Constrain Angle moves the art along it and +/// the guides along the page's axes). +fn apply_transform_moving_guides( + s: &mut Session, + label: &str, + ids: Vec, + xf: Affine, + p: &Value, + anchors: bool, + guides: Option, +) -> Result { let copy = bool_or(p, "copy", false); let areas = bool_or(p, "typeAreas", false); let objects = transforms_objects(p); @@ -387,7 +402,7 @@ pub(crate) fn apply_transform(s: &mut Session, label: &str, ids: Vec, xf sc.patterns = transform_patterns(s, p, &ids)?; let c = xf.as_coeffs(); let is_translation = (c[0] - 1.0).abs() < 1e-9 && c[1].abs() < 1e-9 && c[2].abs() < 1e-9 && (c[3] - 1.0).abs() < 1e-9; - let delta = Vec2::new(c[4], c[5]); + let delta = guides.unwrap_or(Vec2::new(c[4], c[5])); let with_guides = objects && ids_param(p, "ids").is_none() && is_translation; let has_guides = with_guides && !s.doc()?.selection.guides.is_empty(); // A transform of ids that name no object, or an identity transform without a copy, moves @@ -564,7 +579,7 @@ fn nudge(s: &mut Session, p: &Value) -> Result { return super::docmenu::guide_move(s, &json!({ "dx": gx, "dy": gy, "copy": copy })); } let ids = selected_roots_to_transform(s)?; - apply_transform(s, "Move", ids, Affine::translate((dx, dy)), p, true) + apply_transform_moving_guides(s, "Move", ids, Affine::translate((dx, dy)), p, true, Some(Vec2::new(gx, gy))) } fn about(o: Point, a: Affine) -> Affine { diff --git a/crates/engine/src/tests_constrain_tools.rs b/crates/engine/src/tests_constrain_tools.rs index 5a9c2ddbd..ca6999c79 100644 --- a/crates/engine/src/tests_constrain_tools.rs +++ b/crates/engine/src/tests_constrain_tools.rs @@ -152,3 +152,28 @@ fn constrain_angle_whole_turns_nudge_as_zero() { set_pref(&mut s, "constrainAngle", json!(90)); assert_eq!(moved(&mut s, 1, 0), Vec2::new(0.0, -10.0)); } + +/// General › Constrain Angle turns only the art's arrow-key axes: ruler guides selected with the +/// art keep to the page's axes. At 90°, → moves the art straight up, a vertical guide 10 right and +/// a horizontal guide not at all; ↓ moves the horizontal guide 10 down. Copies alike. +#[test] +fn constrain_angle_keeps_guides_selected_with_art_on_the_page_axes() { + let mut s = new_doc(); + let id = rect(&mut s, 100.0, 100.0); + set_pref(&mut s, "keyboardIncrement", json!(10)); + set_pref(&mut s, "constrainAngle", json!(90)); + let v = s.execute("guide.add", &json!({"vertical": true, "pos": 50})).unwrap()["index"].as_u64().unwrap() as usize; + let h = s.execute("guide.add", &json!({"vertical": false, "pos": 50})).unwrap()["index"].as_u64().unwrap() as usize; + s.execute("select.set", &json!({"ids": [id.0]})).unwrap(); + s.execute("guide.select", &json!({"indexes": [v, h], "toggle": true})).unwrap(); + let pos = |s: &Session| s.doc().unwrap().doc.guides.iter().map(|g| g.pos).collect::>(); + let at = centre(&s, id); + s.execute("object.nudge", &json!({"dx": 1, "dy": 0})).unwrap(); + assert_eq!(centre(&s, id) - at, Vec2::new(0.0, -10.0), "the art moves along the turned axis"); + assert_eq!(pos(&s), [60.0, 50.0], "→: the vertical guide moves right, the horizontal one stays"); + s.execute("object.nudge", &json!({"dx": 0, "dy": 1})).unwrap(); + assert_eq!(pos(&s), [60.0, 60.0], "↓: the horizontal guide moves down"); + // With a copy, the guides' copies keep to the page too. + s.execute("object.nudge", &json!({"dx": 1, "dy": 0, "copy": true})).unwrap(); + assert_eq!(pos(&s), [60.0, 60.0, 70.0, 60.0]); +}