From a7266dd10a97faef50c460a9b63c9a0ef77b8234 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Wed, 4 Nov 2026 03:29:44 -0800 Subject: [PATCH] Character: Kerning reads (10+5)*2 as nothing instead of 10+5*2 Numeric fields dropped every parenthesis before reading a value, so (10+5)*2 typed into a field committed 20. Only the Kerning field, which shows its automatic value as (12), now drops one pair of parentheses around the whole text. Parentheses anywhere else are refused, and fields without that display refuse them too. --- crates/ui-egui/src/widgets.rs | 26 +++++++++++++++++++++++++- crates/ui-egui/src/widgets/numeric.rs | 23 ++++++++++++++++------- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index 2e139d013..c74a78a0f 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -57,7 +57,7 @@ pub use numeric::{ spin_field_auto, spin_field_auto_named, spin_field_named, spin_field_stops_named, spin_kerning, spin_plain, spin_plain_named, }; #[cfg(test)] -use numeric::{decimal_point, share_of, spinner_step, step_through, step_to_multiple, within_a_millionth}; +use numeric::{decimal_point, field_number, share_of, spinner_step, step_through, step_to_multiple, within_a_millionth}; pub use panel_chrome::{bottom_bar, layout_button, list_box, orientation_button, tab_bar}; pub(crate) use text::{ arm_field_focus, dialog_focus_flag, dialog_reject_commit_flag, disarm_field_focus, enter_modifiers, focus_field, own_field_shift_enter, @@ -464,6 +464,30 @@ mod decimal_point_tests { } } +#[cfg(test)] +mod field_number_tests { + use super::*; + + /// Kerning drops one pair of parentheses around its whole text, so its automatic value `(12)` + /// reads as 12. Parentheses inside the text are refused rather than dropped, which would read + /// `(10+5)*2` as `10+5*2`, and fields without the Kerning flag refuse parentheses too. + #[test] + fn only_kerning_drops_one_pair_of_parentheses_around_the_text() { + assert_eq!(field_number("(12)", "", true), Some(12.0)); + assert_eq!(field_number(" (-25) ", "", true), Some(-25.0)); + assert_eq!(field_number("(10+5)", "", true), Some(15.0)); + assert_eq!(field_number("12", "", true), Some(12.0)); + for text in ["(10+5)*2", "2*(10+5)", "(10)+(5)", "((12))", "(12", "12)"] { + assert_eq!(field_number(text, "", true), None, "{text}"); + } + for text in ["(12)", "(10+5)*2"] { + assert_eq!(field_number(text, "", false), None, "{text} without the Kerning flag"); + } + assert_eq!(field_number("45*2°", "°", false), Some(90.0)); + assert_eq!(field_number("50% / 2", "%", false), Some(25.0)); + } +} + #[cfg(test)] mod menu_room_tests { use super::*; diff --git a/crates/ui-egui/src/widgets/numeric.rs b/crates/ui-egui/src/widgets/numeric.rs index 91cd18eeb..56ace75b8 100644 --- a/crates/ui-egui/src/widgets/numeric.rs +++ b/crates/ui-egui/src/widgets/numeric.rs @@ -454,13 +454,7 @@ fn bounded_field( let armed = take_armed_focus(ui, &id); let id = id_with(ui.scope_id(), id); let show = |value: f64| field_text(value, suffix, decimals); - // The suffix (and %, °) may follow any operand: `45*2°`, `50% / 2`. - let read = |buf: &str| { - let suffix = suffix.trim(); - let buf = decimal_point(buf); - let bare = if suffix.is_empty() { buf.to_string() } else { buf.replace(suffix, "") }; - vectorcraft_doc::parse_number(&bare.replace(['%', '°', '(', ')'], "")) - }; + let read = |buf: &str| field_number(buf, suffix, parentheses); let shown = value.map(|v| if parentheses { format!("({})", show(v)) } else { show(v) }).unwrap_or_default(); take_dialog_focus(ui, id, &shown); if armed { @@ -491,6 +485,21 @@ fn bounded_field( } } +/// The number text typed into a [`bounded_field`] with `suffix` reads as. The suffix, `%` and `°` +/// may follow any operand: `45*2°`, `50% / 2`. With `parentheses` (Kerning, which shows its +/// automatic value as `(12)`), one pair of parentheses around the whole text is dropped first, so +/// `(12)` reads as 12. Any other parentheses are refused: `(10+5)*2` reads as nothing, not as +/// `10+5*2`. +pub(super) fn field_number(buf: &str, suffix: &str, parentheses: bool) -> Option { + let suffix = suffix.trim(); + let buf = decimal_point(buf); + let bare = if suffix.is_empty() { buf.to_string() } else { buf.replace(suffix, "") }; + let bare = bare.replace(['%', '°'], ""); + let bare = bare.trim(); + let bare = if parentheses { bare.strip_prefix('(').and_then(|b| b.strip_suffix(')')).unwrap_or(bare) } else { bare }; + vectorcraft_doc::parse_number(bare) +} + /// The text [`plain_field`] shows for `value`: at most `decimals` places, without trailing zeros, /// then `suffix`. pub(super) fn field_text(value: f64, suffix: &str, decimals: usize) -> String {