From 55606d29279ab0e3c2ea1ea7fb9acef6986bd077 Mon Sep 17 00:00:00 2001 From: moppeeer Date: Fri, 23 Oct 2026 14:37:19 +0200 Subject: [PATCH] Anchor Point(s): one point shows its frame's W and H, greyed A single selected point has no size, so Transform's W and H read 0p0. InDesign shows the object's width and height there, greyed out and not editable; with two or more points they measure (and set) the points' bounds as before. SelInfo keeps the selected objects' size from before the points replace the bounds, and the anchor Transform section shows it in disabled W/H fields when one point is selected. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/panels/mod.rs | 5 ++++ crates/ui-egui/src/panels/properties.rs | 33 ++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/crates/ui-egui/src/panels/mod.rs b/crates/ui-egui/src/panels/mod.rs index d1b532ff8..6cdb99f36 100644 --- a/crates/ui-egui/src/panels/mod.rs +++ b/crates/ui-egui/src/panels/mod.rs @@ -34,6 +34,9 @@ use crate::theme::Tokens; pub struct SelInfo { /// Bounds measured from the rulers' zero point (as the X/Y fields show them). pub page_rect: Rect, + /// Width and height of the selected objects themselves (with points selected, `page_rect` + /// measures the points). + pub object_size: (f64, f64), pub count: usize, pub fill: String, pub stroke: String, @@ -197,6 +200,7 @@ pub fn sel_info(app: &DesignApp) -> Option { }; b = Some(b.map_or(ib, |r| r.union(ib))); } + let object_size = b.map_or((0.0, 0.0), |r| (r.width(), r.height())); // Points selected with Direct Selection: Transform measures the points, not their paths. let points = selected_anchor_points(app); let b = match points.split_first() { @@ -220,6 +224,7 @@ pub fn sel_info(app: &DesignApp) -> Option { }; Some(SelInfo { page_rect: Rect::new(b.x0 - o.x, b.y0 - o.y, b.x1 - o.x, b.y1 - o.y), + object_size, count: st.selection.items.len(), fill: first.fill.swatch.clone(), stroke: first.stroke.swatch.clone(), diff --git a/crates/ui-egui/src/panels/properties.rs b/crates/ui-egui/src/panels/properties.rs index 2a1d29f13..b46131385 100644 --- a/crates/ui-egui/src/panels/properties.rs +++ b/crates/ui-egui/src/panels/properties.rs @@ -644,16 +644,19 @@ fn anchor_transform_section(app: &mut DesignApp, ui: &mut Ui, i: &SelInfo) { app.ui.ref_point = p; } let mut set = serde_json::Map::new(); - for (row, (lx, vx, kx, lw, vw, kw)) in - [("X:", x, "x", "W:", r.width(), "width"), ("Y:", y, "y", "H:", r.height(), "height")].into_iter().enumerate() - { + // A single point has no size: as InDesign, W and H show its object's, greyed out. + let single = super::selected_anchor_points(app).len() == 1; + let (w, h) = if single { i.object_size } else { (r.width(), r.height()) }; + for (row, (lx, vx, kx, lw, vw, kw)) in [("X:", x, "x", "W:", w, "width"), ("Y:", y, "y", "H:", h, "height")].into_iter().enumerate() { let yy = row as f32 * 25.0; place(ui, sub(b, 40.0, yy, 18.0, FIELD_H), |ui| widgets::caption_w(ui, lx, 18.0)); if let Some(v) = place(ui, sub(b, 59.0, yy, 54.0, FIELD_H), |ui| measure(ui, &format!("pt_{kx}"), Some(vx), u, 54.0)) { set.insert(kx.into(), json!(v)); } place(ui, sub(b, 120.0, yy, 19.0, FIELD_H), |ui| widgets::caption_w(ui, lw, 19.0)); - if let Some(v) = place(ui, sub(b, 140.0, yy, 54.0, FIELD_H), |ui| measure(ui, &format!("pt_{kw}"), Some(vw), u, 54.0)) { + let id = format!("pt_{kw}"); + let field = NumField::measure(&id, Some(vw), u).width(54.0).enabled(!single); + if let Some(v) = place(ui, sub(b, 140.0, yy, 54.0, FIELD_H), |ui| field.show(ui)) { set.insert(kw.into(), json!(v)); } } @@ -3609,6 +3612,28 @@ mod tests { assert!((corner(&app, right).x - (pr.x + w)).abs() < 1e-9, "spread to twice the width"); } + /// One point has no size: W and H show its frame's, greyed out, as InDesign; two points + /// measure (and set) their own. + #[test] + fn one_selected_point_shows_its_frames_size_greyed() { + let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); + app.run("file.new", json!({})).unwrap(); + let a = app.run("frame.create", json!({"rect": [72, 72, 172, 222]})).unwrap()["id"].as_u64().unwrap(); + app.run("selection.set", json!({"anchors": [[a, 0, 0]]})).unwrap(); + assert_eq!(super::super::sel_info(&app).unwrap().object_size, (100.0, 150.0)); + let mut h = panel(app); + h.run_steps(2); + let greyed = |h: &Harness<'_, DesignApp>| { + h.query_all_by(|n| n.role() == egui::accesskit::Role::TextInput && n.is_disabled()).filter_map(|n| n.value()).collect::>() + }; + let u = units(h.state()); + let f = |v| designcraft_geom::units::format_measure(v, u); + assert_eq!(greyed(&h), [f(100.0), f(150.0)], "the frame's W and H, greyed"); + h.state_mut().run("selection.set", json!({"anchors": [[a, 0, 0], [a, 0, 2]]})).unwrap(); + h.run_steps(2); + assert!(greyed(&h).is_empty(), "two points have a size of their own"); + } + #[test] fn transform_flips_and_rotates_from_properties() { let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());