From 51e7044b0e108c5184880c9eae3bb713d40a0dab Mon Sep 17 00:00:00 2001 From: bflatastic Date: Tue, 13 Oct 2026 11:43:44 +0700 Subject: [PATCH] Fix #1597: the Transform panel shows and sets a selected ruler guide's X or Y With only ruler guides selected, X is the position of the vertical ones and Y that of the horizontal ones; typing a value moves them (guide.move), together when several are selected. --- crates/ui-egui/src/panels/transform.rs | 62 +++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/panels/transform.rs b/crates/ui-egui/src/panels/transform.rs index 22013ec3..a57faabb 100644 --- a/crates/ui-egui/src/panels/transform.rs +++ b/crates/ui-egui/src/panels/transform.rs @@ -1,6 +1,7 @@ //! Transform panel: reference point, X/Y/W/H with the constrain link, rotate and shear, flips, //! live-shape properties and the Scale Corners / Scale Strokes & Effects options. While the -//! Artboard tool is in use it shows and edits the active artboard's X/Y/W/H instead. +//! Artboard tool is in use it shows and edits the active artboard's X/Y/W/H instead, and with only +//! ruler guides selected their X (vertical guides) and Y (horizontal ones). use egui::Ui; use serde_json::json; @@ -94,6 +95,39 @@ fn artboard_fields(app: &mut VectorcraftApp, ui: &mut Ui, index: usize, r: Rect) }); } +/// With ruler guides selected and no art: the X of the vertical ones and the Y of the horizontal +/// ones (the least of each; none where no such guide is selected). +fn selected_guides(app: &VectorcraftApp) -> Option<(Option, Option)> { + let st = app.session.active()?; + if !st.selection.objects.is_empty() || st.selection.guides.is_empty() { + return None; + } + let least = |vertical: bool| { + st.selection.guides.iter().filter_map(|i| st.doc.guides.get(*i)).filter(|g| g.vertical == vertical).map(|g| g.pos).min_by(f64::total_cmp) + }; + Some((least(true), least(false))) +} + +/// The selected ruler guides' view of the panel (#1597): X moves the vertical ones and Y the +/// horizontal ones, together. +fn guide_fields(app: &mut VectorcraftApp, ui: &mut Ui, (x, y): (Option, Option)) { + let units = app.session.general_unit(); + let origin = app.ruler_origin(); + egui::Grid::new("xfp-guide-grid").num_columns(2).spacing([4.0, 6.0]).min_col_width(0.0).show(ui, |ui| { + for (label, id, pos, origin, by) in [("X:", "xfp-guide-x", x, origin.x, "dx"), ("Y:", "xfp-guide-y", y, origin.y, "dy")] { + widgets::dim_label(ui, label); + ui.add_enabled_ui(pos.is_some(), |ui| { + if let (Some(v), Some(pos)) = (widgets::num_field(ui, id, pos.map(|p| p - origin), units, 80.0), pos) + && let Err(e) = app.run("guide.move", json!({ by: v + origin - pos })) + { + app.status(e); + } + }); + ui.end_row(); + } + }); +} + pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { let t = Tokens::get(ui.ctx()); if app.session.active().is_none() { @@ -104,6 +138,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { artboard_fields(app, ui, i, r); return; } + if let Some(guides) = selected_guides(app) { + guide_fields(app, ui, guides); + return; + } let units = app.session.general_unit(); let origin = app.ruler_origin(); // The bounding box, rotated with rotated objects: W/H are its own sides, X/Y its reference @@ -426,6 +464,28 @@ mod tests { assert!(!shown.iter().any(|t| t == "40 pt" || t == "0°"), "{shown:?}"); } + /// #1597: with only ruler guides selected the panel shows their X or Y, and nothing of the art. + #[test] + fn selected_guides_show_their_position() { + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default()); + app.run("file.new", json!({"width": 300, "height": 200})).unwrap(); + app.run("guide.add", json!({"vertical": true, "pos": 120})).unwrap(); + app.run("guide.add", json!({"vertical": false, "pos": 45})).unwrap(); + app.run("guide.select", json!({"indexes": [0]})).unwrap(); + assert_eq!(selected_guides(&app), Some((Some(120.0), None))); + let shown = texts(&mut app, show); + assert!(shown.iter().any(|t| t == "120 pt") && !shown.iter().any(|t| t == "W:" || t == "45 pt"), "{shown:?}"); + app.run("guide.select", json!({"indexes": [0, 1]})).unwrap(); + let shown = texts(&mut app, show); + assert!(shown.iter().any(|t| t == "120 pt") && shown.iter().any(|t| t == "45 pt"), "{shown:?}"); + // The field's edit: the vertical guide moves, the horizontal one stays. + app.run("guide.move", json!({"dx": 30})).unwrap(); + assert_eq!(selected_guides(&app), Some((Some(150.0), Some(45.0)))); + // Art selected with them: the art's box, as before. + app.run("shape.rectangle", json!({"x": 0, "y": 0, "width": 40, "height": 30})).unwrap(); + assert_eq!(selected_guides(&app), None); + } + #[test] fn artboard_fields_keep_the_reference_point() { let r = Rect::new(0.0, 0.0, 300.0, 200.0);