From 752bbb050de4b8e4e0bf4d8bb0543c6c9b8d0a1b Mon Sep 17 00:00:00 2001 From: el-j Date: Thu, 29 Oct 2026 12:57:43 +0100 Subject: [PATCH] Fix #4567: vertically center numbers and text in input fields In single-line text and numeric inputs across panels and dialogs, egui 0.37 defaulted TextEdit alignment to Align2::LEFT_TOP, placing the text galley at the very top of the allocated input box (e.g. 1pt top margin, 11pt bottom margin in 26pt rows). Align single-line TextEdit instances to Align2::LEFT_CENTER across recessed_text, opt_field_named (dash/gap cells), hex fields, search fields, and dialog inputs, matching DesignCraft's vertical centering. Add regression assertions in look_tests verifying balanced vertical spacing. --- crates/ui-egui/src/dialogs/dimension.rs | 6 +- crates/ui-egui/src/dialogs/export_as.rs | 2 +- crates/ui-egui/src/dialogs/form.rs | 11 +++- crates/ui-egui/src/dialogs/graph_data.rs | 2 +- crates/ui-egui/src/dialogs/import_pdf.rs | 2 +- crates/ui-egui/src/dialogs/print.rs | 2 +- crates/ui-egui/src/dialogs/save_options.rs | 2 +- crates/ui-egui/src/dialogs/save_pdf.rs | 9 ++- crates/ui-egui/src/dialogs/shortcut_import.rs | 4 +- crates/ui-egui/src/dialogs/svg_options.rs | 2 +- crates/ui-egui/src/dialogs/swatch_options.rs | 9 ++- crates/ui-egui/src/dialogs/variables.rs | 2 +- crates/ui-egui/src/palette.rs | 7 ++- crates/ui-egui/src/panels/artboards.rs | 5 +- crates/ui-egui/src/panels/asset_export.rs | 9 ++- crates/ui-egui/src/panels/layers/rows.rs | 4 +- crates/ui-egui/src/panels/pattern_options.rs | 2 +- crates/ui-egui/src/prefs_dialog.rs | 2 +- crates/ui-egui/src/shortcut_editor.rs | 3 +- crates/ui-egui/src/widgets/color.rs | 1 + crates/ui-egui/src/widgets/look_tests.rs | 59 +++++++++++++++++++ crates/ui-egui/src/widgets/numeric.rs | 1 + crates/ui-egui/src/widgets/text.rs | 7 ++- crates/ui-egui/src/workspaces.rs | 4 +- 24 files changed, 130 insertions(+), 27 deletions(-) diff --git a/crates/ui-egui/src/dialogs/dimension.rs b/crates/ui-egui/src/dialogs/dimension.rs index 06f89ce28..9e84b39f8 100644 --- a/crates/ui-egui/src/dialogs/dimension.rs +++ b/crates/ui-egui/src/dialogs/dimension.rs @@ -131,7 +131,11 @@ fn body(app: &mut VectorcraftApp, ui: &mut Ui, d: &mut Dialog) -> bool { ui.horizontal(|ui| { widgets::label_row(ui, tl!("Scale:"), 96.0, |ui| { let mut scale = f.get("scale").and_then(Value::as_str).unwrap_or_default().to_owned(); - let changed = ui.name_inputs(tl!("Scale"), |ui| ui.add(egui::TextEdit::singleline(&mut scale).desired_width(72.0))).changed(); + let changed = ui + .name_inputs(tl!("Scale"), |ui| { + ui.add(egui::TextEdit::singleline(&mut scale).align(egui::Align2::LEFT_CENTER).desired_width(72.0)) + }) + .changed(); if changed { f.insert("scale".into(), json!(scale)); } diff --git a/crates/ui-egui/src/dialogs/export_as.rs b/crates/ui-egui/src/dialogs/export_as.rs index ee918ecf4..32ee696af 100644 --- a/crates/ui-egui/src/dialogs/export_as.rs +++ b/crates/ui-egui/src/dialogs/export_as.rs @@ -103,7 +103,7 @@ fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { d.fields.insert("all".into(), json!(all)); } let mut range = d.str("range"); - if ui.add_enabled(!all, egui::TextEdit::singleline(&mut range).desired_width(90.0)).changed() { + if ui.add_enabled(!all, egui::TextEdit::singleline(&mut range).align(egui::Align2::LEFT_CENTER).desired_width(90.0)).changed() { d.fields.insert("range".into(), json!(range)); } }); diff --git a/crates/ui-egui/src/dialogs/form.rs b/crates/ui-egui/src/dialogs/form.rs index f1c45cf18..b82fe54f8 100644 --- a/crates/ui-egui/src/dialogs/form.rs +++ b/crates/ui-egui/src/dialogs/form.rs @@ -181,7 +181,14 @@ fn text_edit_limited_name(ui: &mut egui::Ui, d: &mut Dialog, key: &str, width: f .inner_margin(egui::Margin::symmetric(6, 3)) .show(ui, |ui| { crate::theme::strong_selected_text(ui); - ui.add(egui::TextEdit::singleline(&mut s).id(id).frame(egui::Frame::NONE).desired_width(width).char_limit(max_chars)) + ui.add( + egui::TextEdit::singleline(&mut s) + .id(id) + .frame(egui::Frame::NONE) + .align(egui::Align2::LEFT_CENTER) + .desired_width(width) + .char_limit(max_chars), + ) }); if r.inner.changed() { d.fields.insert(key.into(), Value::String(s)); @@ -404,7 +411,7 @@ pub(super) fn param_fields( } } Value::String(mut s) => { - if ui.add(egui::TextEdit::singleline(&mut s).desired_width(140.0)).changed() { + if ui.add(egui::TextEdit::singleline(&mut s).align(egui::Align2::LEFT_CENTER).desired_width(140.0)).changed() { d.fields.insert(k.clone(), json!(s)); changed = true; } diff --git a/crates/ui-egui/src/dialogs/graph_data.rs b/crates/ui-egui/src/dialogs/graph_data.rs index a74735363..c5b7d7e58 100644 --- a/crates/ui-egui/src/dialogs/graph_data.rs +++ b/crates/ui-egui/src/dialogs/graph_data.rs @@ -379,7 +379,7 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { let mut text = d.str("entry"); // The entry box has the keys when the window opens, so Enter moves down instead of applying. widgets::take_dialog_focus(ui, entry_id, &text); - let r = ui.add(egui::TextEdit::singleline(&mut text).id(entry_id).desired_width(260.0)); + let r = ui.add(egui::TextEdit::singleline(&mut text).id(entry_id).align(egui::Align2::LEFT_CENTER).desired_width(260.0)); if r.changed() { d.fields.insert("entry".into(), json!(clean(&text))); } diff --git a/crates/ui-egui/src/dialogs/import_pdf.rs b/crates/ui-egui/src/dialogs/import_pdf.rs index 45ec6850f..eb30028b0 100644 --- a/crates/ui-egui/src/dialogs/import_pdf.rs +++ b/crates/ui-egui/src/dialogs/import_pdf.rs @@ -297,7 +297,7 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { fn password_row(ui: &mut egui::Ui, d: &mut Dialog) { widgets::label_row(ui, tl!("Password"), LABEL_WIDTH, |ui| { let mut s = d.str("password"); - let r = ui.add(egui::TextEdit::singleline(&mut s).password(true).desired_width(180.0)); + let r = ui.add(egui::TextEdit::singleline(&mut s).password(true).align(egui::Align2::LEFT_CENTER).desired_width(180.0)); if r.changed() { d.fields.insert("password".into(), json!(s)); } diff --git a/crates/ui-egui/src/dialogs/print.rs b/crates/ui-egui/src/dialogs/print.rs index b767f8dae..5355ef0f6 100644 --- a/crates/ui-egui/src/dialogs/print.rs +++ b/crates/ui-egui/src/dialogs/print.rs @@ -582,7 +582,7 @@ fn general(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) { } } let mut range = get(d, "range").as_str().unwrap_or_default().to_string(); - let edit = egui::TextEdit::singleline(&mut range).desired_width(140.0).hint_text("1-3, 5"); + let edit = egui::TextEdit::singleline(&mut range).align(egui::Align2::LEFT_CENTER).desired_width(140.0).hint_text("1-3, 5"); if ui.add_enabled(artboards == PrintArtboards::Range, edit).changed() { set(d, "range", json!(range)); } diff --git a/crates/ui-egui/src/dialogs/save_options.rs b/crates/ui-egui/src/dialogs/save_options.rs index c05ff231b..cbf450a6a 100644 --- a/crates/ui-egui/src/dialogs/save_options.rs +++ b/crates/ui-egui/src/dialogs/save_options.rs @@ -124,7 +124,7 @@ fn artboard_range(ui: &mut egui::Ui, d: &mut Dialog) { d.fields.insert("all".into(), json!(all)); } let mut range = d.str("range"); - let field = egui::TextEdit::singleline(&mut range).desired_width(90.0).hint_text("1-3, 5"); + let field = egui::TextEdit::singleline(&mut range).align(egui::Align2::LEFT_CENTER).desired_width(90.0).hint_text("1-3, 5"); if ui.add_enabled(!all, field).changed() { d.fields.insert("range".into(), json!(range)); } diff --git a/crates/ui-egui/src/dialogs/save_pdf.rs b/crates/ui-egui/src/dialogs/save_pdf.rs index 4b8fc33c6..72a8d64ac 100644 --- a/crates/ui-egui/src/dialogs/save_pdf.rs +++ b/crates/ui-egui/src/dialogs/save_pdf.rs @@ -321,7 +321,7 @@ pub(super) fn length(ui: &mut egui::Ui, d: &mut Dialog, path: &str, unit: Unit, pub(super) fn text(ui: &mut egui::Ui, d: &mut Dialog, path: &str, enabled: bool) { let mut s = get(d, path).as_str().unwrap_or_default().to_string(); - if ui.add_enabled(enabled, egui::TextEdit::singleline(&mut s).desired_width(240.0)).changed() { + if ui.add_enabled(enabled, egui::TextEdit::singleline(&mut s).align(egui::Align2::LEFT_CENTER).desired_width(240.0)).changed() { set(d, path, json!(s)); } } @@ -493,7 +493,10 @@ fn general(ui: &mut egui::Ui, d: &mut Dialog, editor: bool) { d.fields.insert("__allArtboards".into(), json!(all)); } let mut range = d.str("range"); - if ui.add_enabled(!all, egui::TextEdit::singleline(&mut range).desired_width(140.0).hint_text("1-3, 5")).changed() { + if ui + .add_enabled(!all, egui::TextEdit::singleline(&mut range).align(egui::Align2::LEFT_CENTER).desired_width(140.0).hint_text("1-3, 5")) + .changed() + { d.fields.insert("range".into(), json!(range)); } }); @@ -700,7 +703,7 @@ fn password_check(ui: &mut egui::Ui, d: &mut Dialog, path: &str, flag: &str, lab fn password_field(ui: &mut egui::Ui, d: &mut Dialog, path: &str, enabled: bool) { let mut s = get(d, path).as_str().unwrap_or_default().to_string(); - if ui.add_enabled(enabled, egui::TextEdit::singleline(&mut s).password(true).desired_width(200.0)).changed() { + if ui.add_enabled(enabled, egui::TextEdit::singleline(&mut s).password(true).align(egui::Align2::LEFT_CENTER).desired_width(200.0)).changed() { set(d, path, json!(s)); } } diff --git a/crates/ui-egui/src/dialogs/shortcut_import.rs b/crates/ui-egui/src/dialogs/shortcut_import.rs index ab32ada06..c65b4e602 100644 --- a/crates/ui-egui/src/dialogs/shortcut_import.rs +++ b/crates/ui-egui/src/dialogs/shortcut_import.rs @@ -667,7 +667,7 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) { ui.horizontal(|ui| { ui.label(egui::RichText::new(tl!("Name:")).color(t.text_dim)); let mut name = d.str("name"); - if ui.add(egui::TextEdit::singleline(&mut name).desired_width(260.0)).changed() { + if ui.add(egui::TextEdit::singleline(&mut name).align(egui::Align2::LEFT_CENTER).desired_width(260.0)).changed() { d.fields.insert("name".into(), json!(name)); } }); @@ -867,7 +867,7 @@ fn rows_section(ui: &mut egui::Ui, d: &mut Dialog, review: &mut Review, statuses } ui.add_space(8.0); let mut q = d.str("__query"); - if ui.add(egui::TextEdit::singleline(&mut q).hint_text(tl!("Search")).desired_width(180.0)).changed() { + if ui.add(egui::TextEdit::singleline(&mut q).align(egui::Align2::LEFT_CENTER).hint_text(tl!("Search")).desired_width(180.0)).changed() { d.fields.insert("__query".into(), json!(q)); } }); diff --git a/crates/ui-egui/src/dialogs/svg_options.rs b/crates/ui-egui/src/dialogs/svg_options.rs index 261d51b8a..86db8a6bc 100644 --- a/crates/ui-egui/src/dialogs/svg_options.rs +++ b/crates/ui-egui/src/dialogs/svg_options.rs @@ -245,7 +245,7 @@ fn artboards(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) { d.fields.insert("allArtboards".into(), json!(false)); } let mut range = d.str("range"); - let edit = egui::TextEdit::singleline(&mut range).hint_text(format!("1-{count}")).desired_width(90.0); + let edit = egui::TextEdit::singleline(&mut range).align(egui::Align2::LEFT_CENTER).hint_text(format!("1-{count}")).desired_width(90.0); if ui.add_enabled(!all, edit).changed() { d.fields.insert("range".into(), json!(range)); } diff --git a/crates/ui-egui/src/dialogs/swatch_options.rs b/crates/ui-egui/src/dialogs/swatch_options.rs index 87d15152c..cdeca225b 100644 --- a/crates/ui-egui/src/dialogs/swatch_options.rs +++ b/crates/ui-egui/src/dialogs/swatch_options.rs @@ -266,7 +266,14 @@ fn hex_field(ui: &mut egui::Ui, hex: &str) -> Option { .inner_margin(egui::Margin::symmetric(6, 4)) .show(ui, |ui| { crate::theme::strong_selected_text(ui); - ui.add(egui::TextEdit::singleline(&mut buf).id(id).frame(egui::Frame::NONE).desired_width(70.0).char_limit(7)) + ui.add( + egui::TextEdit::singleline(&mut buf) + .id(id) + .frame(egui::Frame::NONE) + .align(egui::Align2::LEFT_CENTER) + .desired_width(70.0) + .char_limit(7), + ) }) .inner; let focused = ui.memory(|m| m.has_focus(id)); diff --git a/crates/ui-egui/src/dialogs/variables.rs b/crates/ui-egui/src/dialogs/variables.rs index 12ee10020..d63ea5562 100644 --- a/crates/ui-egui/src/dialogs/variables.rs +++ b/crates/ui-egui/src/dialogs/variables.rs @@ -225,7 +225,7 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { /// A text field for `key`, written back as it is typed. fn text(ui: &mut egui::Ui, d: &mut Dialog, key: &str, width: f32) -> egui::Response { let mut s = d.str(key); - let r = ui.add(egui::TextEdit::singleline(&mut s).desired_width(width)); + let r = ui.add(egui::TextEdit::singleline(&mut s).align(egui::Align2::LEFT_CENTER).desired_width(width)); if r.changed() { d.fields.insert(key.into(), json!(s)); } diff --git a/crates/ui-egui/src/palette.rs b/crates/ui-egui/src/palette.rs index 5fd8bb62e..a6cc61e23 100644 --- a/crates/ui-egui/src/palette.rs +++ b/crates/ui-egui/src/palette.rs @@ -104,7 +104,12 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) { egui::Area::new(egui::Id::unique("palette")).order(egui::Order::Foreground).anchor(egui::Align2::CENTER_TOP, [0.0, 90.0]).show(ctx, |ui| { egui::Frame::popup(ui.style()).fill(t.panel).inner_margin(egui::Margin::same(10)).show(ui, |ui| { ui.set_width(520.0); - let r = ui.add(egui::TextEdit::singleline(&mut app.ui.palette_query).hint_text(tl!("Search commands and tools…")).desired_width(500.0)); + let r = ui.add( + egui::TextEdit::singleline(&mut app.ui.palette_query) + .align(egui::Align2::LEFT_CENTER) + .hint_text(tl!("Search commands and tools…")) + .desired_width(500.0), + ); r.request_focus(); ui.add_space(6.0); for (i, e) in matches.iter().enumerate() { diff --git a/crates/ui-egui/src/panels/artboards.rs b/crates/ui-egui/src/panels/artboards.rs index f929bc3a4..741365922 100644 --- a/crates/ui-egui/src/panels/artboards.rs +++ b/crates/ui-egui/src/panels/artboards.rs @@ -437,7 +437,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { let id = ui.scope_id().with(("ab-name", i)); let mut buf: String = ui.data_mut(|d| d.get_temp::(id)).unwrap_or_else(|| shown.to_string()); let kept = widgets::own_field_shift_enter(ui, id, &mut buf, shown); - let te = ui.put(name_rect, egui::TextEdit::singleline(&mut buf).id(id).font(egui::FontId::proportional(12.0))); + let te = ui.put( + name_rect, + egui::TextEdit::singleline(&mut buf).id(id).align(egui::Align2::LEFT_CENTER).font(egui::FontId::proportional(12.0)), + ); if !te.has_focus() && !te.lost_focus() { te.request_focus(); } diff --git a/crates/ui-egui/src/panels/asset_export.rs b/crates/ui-egui/src/panels/asset_export.rs index 737655540..3cc81fce7 100644 --- a/crates/ui-egui/src/panels/asset_export.rs +++ b/crates/ui-egui/src/panels/asset_export.rs @@ -130,8 +130,13 @@ fn tiles(ui: &mut Ui, st: &DocState, sel: &[u64], ev: &mut Events) { let field = rename_id.with("field"); let kept = widgets::own_field_shift_enter(ui, field, &mut buf, &a.name); let mut child = ui.new_child(egui::UiBuilder::new().max_rect(name_rect)); - let te = child - .add(egui::TextEdit::singleline(&mut buf).id(field).desired_width(name_rect.width()).font(egui::FontId::proportional(11.5))); + let te = child.add( + egui::TextEdit::singleline(&mut buf) + .id(field) + .align(egui::Align2::LEFT_CENTER) + .desired_width(name_rect.width()) + .font(egui::FontId::proportional(11.5)), + ); // Enter or a click elsewhere commits, Escape cancels; Shift+Enter commits and // keeps the field open. if te.lost_focus() { diff --git a/crates/ui-egui/src/panels/layers/rows.rs b/crates/ui-egui/src/panels/layers/rows.rs index 94a231c23..f9e05716d 100644 --- a/crates/ui-egui/src/panels/layers/rows.rs +++ b/crates/ui-egui/src/panels/layers/rows.rs @@ -250,7 +250,9 @@ pub(super) fn row(ui: &mut Ui, view: &View, item: &Row, out: &mut Out) { let field = rename_id().with("field"); let kept = crate::widgets::own_field_shift_enter(ui, field, &mut buf, &name); let mut child = ui.new_child(egui::UiBuilder::new().max_rect(name_rect)); - let te = child.add(egui::TextEdit::singleline(&mut buf).id(field).desired_width(name_rect.width()).font(font.clone())); + let te = child.add( + egui::TextEdit::singleline(&mut buf).id(field).align(egui::Align2::LEFT_CENTER).desired_width(name_rect.width()).font(font.clone()), + ); // Focus the field as it opens. Asking every frame took the focus back from Enter, // Escape or a click elsewhere, so the rename never ended. if !te.has_focus() && !te.lost_focus() { diff --git a/crates/ui-egui/src/panels/pattern_options.rs b/crates/ui-egui/src/panels/pattern_options.rs index 9e4575d01..a84f0042f 100644 --- a/crates/ui-egui/src/panels/pattern_options.rs +++ b/crates/ui-egui/src/panels/pattern_options.rs @@ -51,7 +51,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { buf = name.clone(); } let kept = widgets::own_field_shift_enter(ui, id, &mut buf, &name); - let r = ui.add(egui::TextEdit::singleline(&mut buf).id(id).desired_width(150.0)); + let r = ui.add(egui::TextEdit::singleline(&mut buf).id(id).align(egui::Align2::LEFT_CENTER).desired_width(150.0)); ui.data_mut(|d| d.insert_temp(id, buf.clone())); if (r.lost_focus() || kept) && !buf.trim().is_empty() && buf != name { set(app, &name, json!({"newName": buf.trim()})); diff --git a/crates/ui-egui/src/prefs_dialog.rs b/crates/ui-egui/src/prefs_dialog.rs index 79121f85d..d638e0b26 100644 --- a/crates/ui-egui/src/prefs_dialog.rs +++ b/crates/ui-egui/src/prefs_dialog.rs @@ -371,7 +371,7 @@ fn category_fields(ui: &mut egui::Ui, d: &mut Dialog, cat: &str) { PrefKind::Text => { labeled(ui, sp.label, |ui| { let mut s = v.as_str().unwrap_or("").to_string(); - if ui.add(egui::TextEdit::singleline(&mut s).desired_width(260.0)).changed() { + if ui.add(egui::TextEdit::singleline(&mut s).align(egui::Align2::LEFT_CENTER).desired_width(260.0)).changed() { d.fields.insert(sp.key.into(), json!(s)); } }); diff --git a/crates/ui-egui/src/shortcut_editor.rs b/crates/ui-egui/src/shortcut_editor.rs index ab3b31f13..695ace916 100644 --- a/crates/ui-egui/src/shortcut_editor.rs +++ b/crates/ui-egui/src/shortcut_editor.rs @@ -788,7 +788,8 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) { } ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { let mut q = d.str("query"); - if ui.add(egui::TextEdit::singleline(&mut q).hint_text(tl!("Search")).desired_width(200.0)).changed() { + if ui.add(egui::TextEdit::singleline(&mut q).align(egui::Align2::LEFT_CENTER).hint_text(tl!("Search")).desired_width(200.0)).changed() + { d.fields.insert("query".into(), json!(q)); } }); diff --git a/crates/ui-egui/src/widgets/color.rs b/crates/ui-egui/src/widgets/color.rs index 80c43e2e8..a73b6ae2b 100644 --- a/crates/ui-egui/src/widgets/color.rs +++ b/crates/ui-egui/src/widgets/color.rs @@ -417,6 +417,7 @@ pub fn hex_field_named(ui: &mut Ui, id: impl std::hash::Hash, name: Option<&str> egui::TextEdit::singleline(&mut buf) .id(id) .frame(egui::Frame::NONE) + .align(egui::Align2::LEFT_CENTER) .desired_width(62.0) .char_limit(7) .font(egui::FontId::proportional(12.5)) diff --git a/crates/ui-egui/src/widgets/look_tests.rs b/crates/ui-egui/src/widgets/look_tests.rs index a1ac9c3b8..6ec7112cb 100644 --- a/crates/ui-egui/src/widgets/look_tests.rs +++ b/crates/ui-egui/src/widgets/look_tests.rs @@ -99,6 +99,65 @@ fn a_spinner_field_reads_as_one_box_in_three_parts() { for pair in boxes.windows(2) { assert!((pair[1].min.x - pair[0].max.x + 1.0).abs() < 0.01, "neighbours share one border: {boxes:?}"); } + // The number text in the numeric field is vertically centered in the row box (#900, #4567). + let text_shape = shapes + .iter() + .find_map(|s| match s { + egui::Shape::Text(t) if t.galley.text() == "4 pt" => Some(t), + _ => None, + }) + .expect("field text shape present"); + let text_top = text_shape.pos.y - boxes[1].min.y; + let text_bottom = boxes[1].max.y - (text_shape.pos.y + text_shape.galley.rect.height()); + assert!((text_top - text_bottom).abs() <= 1.5, "text is vertically centered in its box: top gap {text_top}, bottom gap {text_bottom}"); +} + +// Regression test for #4567: numbers and text in input fields (numeric fields, dash/gap fields, +// etc.) are vertically centered in their boxes, not shoved against the top edge. +#[test] +fn input_elements_are_vertically_centered_in_their_boxes() { + let ctx = themed(Default::default()); + let mut add = |ui: &mut Ui| { + ui.horizontal(|ui| { + num_field(ui, "plain_num", Some(12.0), Unit::Points, 70.0); + opt_field(ui, "dash_cell", Some(12.0), Unit::Points, 40.0); + }); + }; + frame(&ctx, 0.0, vec![], &mut add); + let shapes = frame(&ctx, 0.1, vec![], &mut add); + let mut boxes: Vec = vec![]; + for s in &shapes { + if let egui::Shape::Rect(r) = s + && r.rect.height() > 20.0 + && r.fill != Color32::TRANSPARENT + && !boxes.contains(&r.rect) + { + boxes.push(r.rect); + } + } + boxes.sort_by(|a, b| a.min.x.total_cmp(&b.min.x)); + assert_eq!(boxes.len(), 2, "found both field boxes: {boxes:?}"); + + let mut text_shapes: Vec<&egui::epaint::TextShape> = vec![]; + for s in &shapes { + if let egui::Shape::Text(t) = s + && (t.galley.text() == "12 pt" || t.galley.text() == "12") + { + text_shapes.push(t); + } + } + text_shapes.sort_by(|a, b| a.pos.x.total_cmp(&b.pos.x)); + assert_eq!(text_shapes.len(), 2, "found both text shapes: {text_shapes:?}"); + + for (b, t) in boxes.iter().zip(text_shapes.iter()) { + let text_top = t.pos.y - b.min.y; + let text_bottom = b.max.y - (t.pos.y + t.galley.rect.height()); + assert!( + (text_top - text_bottom).abs() <= 1.5, + "text '{}' is vertically centered: top gap {text_top}, bottom gap {text_bottom} in box {b:?}", + t.galley.text() + ); + } } /// The colour of every glyph of the text in `shapes`: selected glyphs are drawn in the diff --git a/crates/ui-egui/src/widgets/numeric.rs b/crates/ui-egui/src/widgets/numeric.rs index c12144a99..234c76dbf 100644 --- a/crates/ui-egui/src/widgets/numeric.rs +++ b/crates/ui-egui/src/widgets/numeric.rs @@ -710,6 +710,7 @@ pub fn opt_field_named(ui: &mut Ui, id: impl std::hash::Hash, name: Option<&str> egui::TextEdit::singleline(&mut buf) .id(id) .frame(egui::Frame::NONE) + .align(egui::Align2::LEFT_CENTER) .desired_width(width - 10.0) .font(egui::FontId::proportional(12.5)) .text_color(if enabled { t.text_strong } else { t.text_disabled }), diff --git a/crates/ui-egui/src/widgets/text.rs b/crates/ui-egui/src/widgets/text.rs index b4e531a52..ea3bd0da9 100644 --- a/crates/ui-egui/src/widgets/text.rs +++ b/crates/ui-egui/src/widgets/text.rs @@ -206,7 +206,11 @@ pub(super) fn recessed_text( .inner_margin(margin) .show(ui, |ui| { crate::theme::strong_selected_text(ui); - let edit = if rows > 1 { egui::TextEdit::multiline(&mut buf).desired_rows(rows) } else { egui::TextEdit::singleline(&mut buf) }; + let edit = if rows > 1 { + egui::TextEdit::multiline(&mut buf).desired_rows(rows) + } else { + egui::TextEdit::singleline(&mut buf).align(egui::Align2::LEFT_CENTER) + }; ui.add( edit.id(id) .frame(egui::Frame::NONE) @@ -293,6 +297,7 @@ pub fn search_field(ui: &mut Ui, id: egui::Id, hint: &str) -> String { egui::TextEdit::singleline(&mut query) .id(id.with("edit")) .frame(egui::Frame::NONE) + .align(egui::Align2::LEFT_CENTER) .hint_text(egui::RichText::new(tl!(hint)).italics()) .desired_width(ui.available_width()), ); diff --git a/crates/ui-egui/src/workspaces.rs b/crates/ui-egui/src/workspaces.rs index 4e6648e26..a67bc690c 100644 --- a/crates/ui-egui/src/workspaces.rs +++ b/crates/ui-egui/src/workspaces.rs @@ -453,7 +453,7 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) { let sel = d.str("selected"); ui.horizontal(|ui| { let mut name = d.str("name"); - if ui.add(egui::TextEdit::singleline(&mut name).desired_width(180.0)).changed() { + if ui.add(egui::TextEdit::singleline(&mut name).align(egui::Align2::LEFT_CENTER).desired_width(180.0)).changed() { d.fields.insert("name".into(), json!(name)); } if ui.button(tl!("New")).on_hover_text(tl!("Save the current layout under this name")).clicked() { @@ -472,7 +472,7 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) { ui.horizontal(|ui| { ui.label(egui::RichText::new(tl!("Name:")).color(t.text_dim)); let mut name = d.str("name"); - let r = ui.add(egui::TextEdit::singleline(&mut name).desired_width(240.0)); + let r = ui.add(egui::TextEdit::singleline(&mut name).align(egui::Align2::LEFT_CENTER).desired_width(240.0)); if r.changed() { d.fields.insert("name".into(), json!(name)); }