From 8c67ade8248f695c76496020df780ff0ab086389 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 24 Oct 2026 18:50:53 -0700 Subject: [PATCH] Dialogs: Move runs with a Distance and Angle set before its next frame Move opened without Distance and Angle, so its first frame replaced the values a control client set with ones computed from Horizontal and Vertical, and OK read Horizontal and Vertical without the Distance and Angle set since the last frame. Move now opens holding the Distance and Angle of its offset, and OK applies a Distance and Angle set since the last frame. --- crates/ui-egui/src/dialogs/transform.rs | 54 +++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/crates/ui-egui/src/dialogs/transform.rs b/crates/ui-egui/src/dialogs/transform.rs index 3da97c4a6..5bf48a739 100644 --- a/crates/ui-egui/src/dialogs/transform.rs +++ b/crates/ui-egui/src/dialogs/transform.rs @@ -86,12 +86,19 @@ fn clean(v: f64) -> f64 { (v * 1e6).round() / 1e6 + 0.0 } -/// The Move dialog's fields as it opens: Horizontal and Vertical are the offset moved last, and -/// Copy, Transform Objects and Preview are as it was last confirmed with (Transform Patterns is -/// General › Transform Pattern Tiles, which its OK sets). +/// The Move dialog's fields as it opens: Horizontal and Vertical are the offset moved last, Distance +/// and Angle are the same offset, and Copy, Transform Objects and Preview are as it was last +/// confirmed with (Transform Patterns is General › Transform Pattern Tiles, which its OK sets). pub(crate) fn move_fields(app: &VectorcraftApp) -> Value { let (dx, dy) = last_move(app).unwrap_or_default(); - let mut fields = json!({"dx": format!("{} pt", clean(dx)), "dy": format!("{} pt", clean(dy))}); + let [dx, dy, distance, angle] = [dx, dy, dx.hypot(dy), (-dy).atan2(dx).to_degrees()].map(clean); + let mut fields = json!({ + "dx": format!("{dx} pt"), + "dy": format!("{dy} pt"), + "distance": distance, + "angle": angle, + "__polar": [dx, dy, distance, angle], + }); let kept = &app.ui.move_options; let kept_bool = |k: &str| kept.get(k).and_then(Value::as_bool); for k in ["copy", "objects", "preview"] { @@ -217,6 +224,12 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { if app.session.in_interaction() { app.session.cancel_interaction().map_err(|e| e.to_string())?; } + // A Distance and Angle set since the last frame set the offset Move runs with. + let mut d = d.clone(); + if d.kind == "move" { + link_distance_and_angle(&mut d); + } + let d = &d; let (id, mut params) = command(app, d); if d.kind == "scale" { params = with_scale_options(app, d, params)?; @@ -395,6 +408,39 @@ mod tests { assert_eq!((b.x0, b.y0), (100.0, 90.0)); } + /// A Distance and Angle set before the dialog's first frame, or just before OK, move by that + /// offset (as `ui.dialog.set` then `ui.dialog.confirm` from a control client). + #[test] + fn move_takes_a_distance_and_an_angle_set_before_a_frame() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 300, "height": 300})).unwrap(); + app.run("shape.rectangle", json!({"x": 100, "y": 100, "width": 60, "height": 20})).unwrap(); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let corner = |app: &VectorcraftApp| { + let b = app.session.doc().unwrap().doc.layers[0].children().unwrap()[0].geometric_bounds().unwrap(); + (b.x0, b.y0) + }; + // Up 10 pt with no frame, then right 20 pt (Move opens on the last offset) with one. + for (with_frame, distance, angle, want, corner_after) in + [(false, 10.0, 90.0, (0.0, -10.0), (100.0, 90.0)), (true, 20.0, 0.0, (20.0, 0.0), (120.0, 90.0))] + { + crate::menus::invoke(&mut app, "object.move", json!({})); + let d = app.ui.dialog.as_mut().unwrap(); + d.fields.extend([("distance".to_string(), json!(distance)), ("angle".to_string(), json!(angle))]); + if with_frame { + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| crate::dialogs::show(&mut app, ui.ctx())); + out.textures_delta.clear(); + let d = app.ui.dialog.as_ref().unwrap(); + assert_eq!(["distance", "angle"].map(|k| d.f64(k, f64::NAN)), [distance, angle], "the first frame keeps them"); + } + crate::dialogs::confirm(&mut app).unwrap(); + let (cmd, p) = app.session.journal.last().unwrap(); + assert_eq!((cmd.as_str(), &p["dx"], &p["dy"]), ("object.move", &json!(want.0), &json!(want.1)), "with a frame: {with_frame}"); + assert_eq!(corner(&app), corner_after); + } + } + /// Move opens on the offset of the last drag or Move, and with the checkboxes it was last /// confirmed with: Enter, Enter makes copies at the same spacing. #[test]