Rectangle dialog: width and height in the ruler units, not always picas (#1964)

A click with a frame tool opened the Rectangle dialog in picas whatever
Preferences > Units & Increments or the rulers' unit menus said. It now
shows the width in the horizontal ruler unit and the height in the
vertical one, and reads bare numbers typed in each field the same way.
The properties panel and the control channel open it the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Zinadin ZidanandClaude Opus 5.5 committed 2026-10-30 21:36:32 +06:00
1 parent ea08e4c2ba
commit cdb2057aab
5 files changed
+63 -11

No files matched your search

+1
View File
@@ -83,6 +83,7 @@ impl DesignApp {
let was_find = self.ui.dialog.as_ref().is_some_and(|d| d.id == "findChange");
match r {
UiRequest::Dialog { id, params } if id == "colorTheme" => dialogs::open_color_theme(self, params),
UiRequest::Dialog { id, params } if id == "frameSize" => self.ui.dialog = Some(dialogs::frame_size_dialog(self, params)),
UiRequest::Dialog { id, params } => self.ui.dialog = Some(dialogs::Dialog::new(&id, params)),
UiRequest::View { params } => canvas::apply_view_request(self, &params),
UiRequest::Pick { purpose, params } => {
+2 -1
View File
@@ -389,7 +389,8 @@ fn handle_request(app: &mut DesignApp, ctx: &egui::Context, req: &ControlRequest
if id == "findChange" || app.ui.dialog.as_ref().is_some_and(|d| d.id == "findChange") {
app.session.reset_find_scope();
}
let d = crate::dialogs::Dialog::new(id, p.get("fields").cloned().unwrap_or(Value::Null));
let fields = p.get("fields").cloned().unwrap_or(Value::Null);
let d = if id == "frameSize" { crate::dialogs::frame_size_dialog(app, fields) } else { crate::dialogs::Dialog::new(id, fields) };
let v = serde_json::to_value(&d).unwrap_or_default();
app.ui.dialog = Some(d);
ok(v)
+25 -9
View File
@@ -79,13 +79,6 @@ impl Dialog {
if id == "ruby" {
fields.entry("section".to_string()).or_insert(json!("cjk.rubyPlacement"));
}
if id == "frameSize" {
for k in ["width", "height"] {
if let Some(v) = fields.get(k).and_then(Value::as_f64) {
fields.insert(k.into(), json!(format_measure(v, Unit::Picas)));
}
}
}
Dialog { id: id.into(), fields, unit: Unit::Picas }
}
/// Set a field and report a genuine Find domain/mode transition to the caller.
@@ -106,9 +99,13 @@ impl Dialog {
}
}
fn m(&self, k: &str) -> Option<f64> {
self.m_in(k, self.unit)
}
/// A measure where a bare number is in `unit`.
fn m_in(&self, k: &str, unit: Unit) -> Option<f64> {
match self.fields.get(k) {
Some(Value::Number(n)) => n.as_f64(),
Some(Value::String(s)) => parse_measure(s, self.unit).ok(),
Some(Value::String(s)) => parse_measure(s, unit).ok(),
_ => None,
}
}
@@ -934,6 +931,25 @@ fn doc_unit(app: &DesignApp) -> Unit {
app.session.active().map(|s| s.doc.settings.horizontal_units).unwrap_or(Unit::Picas)
}
/// The document's vertical ruler unit: what a bare number means in a height field.
fn doc_vertical_unit(app: &DesignApp) -> Unit {
app.session.active().map(|s| s.doc.settings.vertical_units).unwrap_or(Unit::Picas)
}
/// The Rectangle dialog a click with a frame tool opens: the width (points in `params`) shown in
/// the horizontal ruler unit, the height in the vertical one, as Preferences › Units &
/// Increments and the rulers' menus set them (#1964).
pub fn frame_size_dialog(app: &DesignApp, params: Value) -> Dialog {
let mut d = Dialog::new("frameSize", params);
for (k, unit) in [("width", doc_unit(app)), ("height", doc_vertical_unit(app))] {
if let Some(v) = d.fields.get(k).and_then(Value::as_f64) {
d.fields.insert(k.into(), json!(format_measure(v, unit)));
}
}
d.unit = doc_unit(app);
d
}
/// Layout › Create Guides…: guides go on the page in view (or the parent page being edited), not
/// on the first page, and the gutters are typed in the document's ruler unit.
pub fn open_create_guides(app: &mut DesignApp) {
@@ -3277,7 +3293,7 @@ fn confirm_part1(app: &mut DesignApp, d: Dialog) -> Result<Value, String> {
let x = d.fields.get("x").and_then(Value::as_f64).unwrap_or(0.0);
let y = d.fields.get("y").and_then(Value::as_f64).unwrap_or(0.0);
let w = d.m("width").unwrap_or(72.0);
let h = d.m("height").unwrap_or(72.0);
let h = d.m_in("height", doc_vertical_unit(app)).unwrap_or(72.0);
app.run(
"frame.create",
json!({"spread": d.fields.get("spread").cloned().unwrap_or(json!(0)), "shape": d.s("shape"), "content": d.s("content"), "rect": [x, y, x + w, y + h]}),
+34
View File
@@ -3521,3 +3521,37 @@ fn character_style_options_set_the_digits() {
assert_eq!(ctl(&mut app, "ui.dialog.confirm", json!({}))["ok"], true);
assert_eq!(char_style(&app, "Emphasis").unwrap().chars.digits, Some(designcraft_doc::Digits::Farsi));
}
/// The Rectangle dialog a frame-tool click opens shows the width in the horizontal ruler unit and
/// the height in the vertical one, not always picas, and reads bare numbers the same way (#1964).
#[test]
fn frame_size_dialog_follows_the_ruler_units() {
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
app.run("file.new", json!({})).unwrap();
app.run("document.preferences", json!({"horizontalUnits": "inches", "verticalUnits": "millimeters"})).unwrap();
// As the frame tool asks: a click at (36, 36), 72 pt by 72 pt.
app.session.ui_requests.push(designcraft_engine::UiRequest::Dialog {
id: "frameSize".into(),
params: json!({"spread": 0, "shape": "rectangle", "content": "graphic", "x": 36.0, "y": 36.0, "width": 72.0, "height": 72.0}),
});
app.after_engine();
let d = app.ui.dialog.as_ref().expect("the Rectangle dialog");
assert_eq!((d.id.as_str(), d.unit), ("frameSize", Unit::Inches));
assert_eq!(d.s("width"), format_measure(72.0, Unit::Inches));
assert_eq!(d.s("height"), format_measure(72.0, Unit::Millimeters));
assert!(!d.s("width").contains('p') && !d.s("height").contains('p'), "no picas: {} x {}", d.s("width"), d.s("height"));
// Bare numbers typed in each field are in that field's unit: 2 in by 50 mm.
let d = app.ui.dialog.as_mut().unwrap();
d.fields.insert("width".into(), json!("2"));
d.fields.insert("height".into(), json!("50"));
let before = app.session.doc().unwrap().doc.clone();
confirm(&mut app).unwrap();
let after = &app.session.doc().unwrap().doc;
let added: Vec<_> = after.spreads[0].items.iter().filter(|i| !before.spreads[0].items.iter().any(|b| b.id == i.id)).collect();
assert_eq!(added.len(), 1, "one frame");
let b = added[0].bounds();
assert!((b.width() - 144.0).abs() < 0.01 && (b.height() - 50.0 / 25.4 * 72.0).abs() < 0.01, "{b:?}");
// The properties panel's link opens it in the same units.
let d = frame_size_dialog(&app, json!({"width": 36.0, "height": 36.0}));
assert_eq!((d.s("width"), d.s("height")), (format_measure(36.0, Unit::Inches), format_measure(36.0, Unit::Millimeters)));
}
@@ -327,7 +327,7 @@ pub(super) fn appearance_section(app: &mut DesignApp, ui: &mut Ui, i: &SelInfo)
let b = block(ui, FIELD_H);
icons::paint(ui.painter(), sub(b, 3.0, 1.0, 19.0, 19.0), "corner", t.accent);
if place(ui, sub(b, 34.0, 0.0, 56.0, FIELD_H), |ui| link_label(ui, crate::i18n::tr(&app.ui.language, "Corner"), 56.0)).clicked() {
app.ui.dialog = Some(crate::dialogs::Dialog::new("frameSize", json!({})));
app.ui.dialog = Some(crate::dialogs::frame_size_dialog(app, json!({})));
}
let (shape, size) = i.corner;
if let Some(v) = place(ui, sub(b, 96.0, 0.0, 69.0, FIELD_H), |ui| {