From f12ad3aa0ab579b2a0ada6fae7edf5ddaa9a9a95 Mon Sep 17 00:00:00 2001 From: nrq6993-a11y Date: Tue, 20 Oct 2026 01:55:07 +0300 Subject: [PATCH] G1: Pin grid dialog bounds to canvas click via reference locator (#1377) Store the click as __anchorX/__anchorY and recompute x/y when the locator opens or changes so the chosen point stays on the click like Illustrator. Co-authored-by: Cursor --- crates/ui-egui/src/dialogs/shapes.rs | 13 +++++++++++- .../src/dialogs/tests_rectangular_grid.rs | 11 ++++++++++ crates/ui-egui/src/dialogs/tools.rs | 21 ++++++++++--------- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/crates/ui-egui/src/dialogs/shapes.rs b/crates/ui-egui/src/dialogs/shapes.rs index ddc09a366..fe5de6464 100644 --- a/crates/ui-egui/src/dialogs/shapes.rs +++ b/crates/ui-egui/src/dialogs/shapes.rs @@ -2,7 +2,7 @@ //! Star, Line Segment Tool Options and the two grids (size, divider counts and skews, switches). use serde_json::{Value, json}; -use vectorcraft_geom::Rect; +use vectorcraft_geom::{Point, Rect}; use super::{DialogSpec, form, run_and_close}; use crate::panels::transform::artboard_rect; @@ -48,6 +48,16 @@ fn set_grid_placed(d: &mut Dialog, r: Rect) { d.fields.insert("height".into(), json!(r.height())); } +/// Top-left `x`/`y` so reference point `refi` sits on the canvas click (`__anchorX`/`__anchorY`). +pub(crate) fn grid_pin_to_anchor(d: &mut Dialog) { + let anchor = Point::new(d.f64("__anchorX", d.f64("x", 0.0)), d.f64("__anchorY", d.f64("y", 0.0))); + let refi = grid_reference(d); + let (w, h) = (d.f64("width", 100.0), d.f64("height", 100.0)); + let rp = vectorcraft_geom::reference_point(Rect::new(0.0, 0.0, w, h), refi); + d.fields.insert("x".into(), json!(anchor.x - rp.x)); + d.fields.insert("y".into(), json!(anchor.y - rp.y)); +} + /// The form's fields, then a grid's switches. fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { let unit = app.session.general_unit(); @@ -56,6 +66,7 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { let refi = grid_reference(d); if let Some(i) = widgets::reference_point(ui, refi) { d.fields.insert("reference".into(), json!(i)); + grid_pin_to_anchor(d); } }); ui.add_space(4.0); diff --git a/crates/ui-egui/src/dialogs/tests_rectangular_grid.rs b/crates/ui-egui/src/dialogs/tests_rectangular_grid.rs index 88a0c5ab8..8c6f6331e 100644 --- a/crates/ui-egui/src/dialogs/tests_rectangular_grid.rs +++ b/crates/ui-egui/src/dialogs/tests_rectangular_grid.rs @@ -159,6 +159,17 @@ fn key_frame(app: &mut VectorcraftApp, key: egui::Key) { out.textures_delta.clear(); } +#[test] +fn rectangular_grid_reference_locator_reanchors_to_the_click() { + let mut app = app(); + click(&mut app, Point::new(100.0, 100.0)); + set(&mut app, "reference", json!(4)); + super::shapes::grid_pin_to_anchor(app.ui.dialog.as_mut().unwrap()); + assert_eq!((field(&app, "x"), field(&app, "y")), (json!(50.0), json!(50.0))); + let id = confirm(&mut app); + assert_grid(&app, id, Rect::new(50.0, 50.0, 150.0, 150.0), 5, 5); +} + /// #1377: OK uses the bounding box after a reference-point resize (`artboard_rect` keeps the /// center pinned when width doubles). #[test] diff --git a/crates/ui-egui/src/dialogs/tools.rs b/crates/ui-egui/src/dialogs/tools.rs index 999dd9c2e..0b1cd94d0 100644 --- a/crates/ui-egui/src/dialogs/tools.rs +++ b/crates/ui-egui/src/dialogs/tools.rs @@ -3,6 +3,7 @@ use serde_json::{Value, json}; +use super::shapes::grid_pin_to_anchor; use crate::VectorcraftApp; use crate::state::Dialog; @@ -18,17 +19,17 @@ pub fn open_tool_dialog(app: &mut VectorcraftApp, kind: &str, p: Value) { "lineSegment" => Dialog::new(kind, json!({"x": x, "y": y, "length": "100 pt", "angle": 0})), // The grids: the tool's stored options (divider counts and skews, switches) at the default size. "rectangularGrid" | "polarGrid" => { - let mut fields = app.session.tool_options_of(kind); - for (key, value) in [ - ("x", json!(x)), - ("y", json!(y)), - ("width", json!("100 pt")), - ("height", json!("100 pt")), - ("reference", json!(0)), - ] { - fields[key] = value; + let mut obj = match app.session.tool_options_of(kind) { + Value::Object(o) => o, + _ => serde_json::Map::new(), + }; + for (key, value) in [("__anchorX", json!(x)), ("__anchorY", json!(y)), ("width", json!("100 pt")), ("height", json!("100 pt"))] { + obj.insert(key.into(), value); } - Dialog::new(kind, fields) + obj.insert("reference".into(), json!(0)); + let mut d = Dialog::new(kind, Value::Object(obj)); + grid_pin_to_anchor(&mut d); + d } // Graph tool click: the graph's size. "graph" => Dialog::new(