From 9c4df0b8abd62929915ded783e377285a716ac0c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 30 Jun 2024 17:18:52 +0200 Subject: [PATCH] Format numbers in `DragValue`s using `re_format` --- crates/re_format/src/lib.rs | 14 ++++++++++++ crates/re_ui/src/lib.rs | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/crates/re_format/src/lib.rs b/crates/re_format/src/lib.rs index 56a57798d6ca4..2a86a475f2843 100644 --- a/crates/re_format/src/lib.rs +++ b/crates/re_format/src/lib.rs @@ -383,6 +383,20 @@ fn test_format_f64_custom() { } } +/// Parses a number, ignoring whitespace (e.g. thousand separators), +/// and treating the special minus character `MINUS` (−) as a minus sign. +pub fn parse_f64(text: &str) -> Option { + let text: String = text + .chars() + // Ignore whitespace (trailing, leading, and thousands separators): + .filter(|c| !c.is_whitespace()) + // Replace special minus character with normal minus (hyphen): + .map(|c| if c == '−' { '-' } else { c }) + .collect(); + + text.parse().ok() +} + /// Pretty format a large number by using SI notation (base 10), e.g. /// /// ``` diff --git a/crates/re_ui/src/lib.rs b/crates/re_ui/src/lib.rs index 3597e96a79562..12a3a9c3de270 100644 --- a/crates/re_ui/src/lib.rs +++ b/crates/re_ui/src/lib.rs @@ -89,6 +89,51 @@ pub fn apply_style_and_install_loaders(egui_ctx: &egui::Context) { ); design_tokens().apply(egui_ctx); + + egui_ctx.style_mut(|style| { + style.number_formatter = egui::style::NumberFormatter::new(format_with_decimals_in_range); + }); +} + +fn format_with_decimals_in_range( + value: f64, + decimal_range: std::ops::RangeInclusive, +) -> String { + fn format_with_decimals(value: f64, decimals: usize) -> String { + re_format::FloatFormatOptions::DEFAULT_f64 + .with_decimals(decimals) + .with_strip_trailing_zeros(false) + .format(value) + } + + let epsilon = 16.0 * f32::EPSILON; // margin large enough to handle most peoples round-tripping needs + + let min_decimals = *decimal_range.start(); + let max_decimals = *decimal_range.end(); + debug_assert!(min_decimals <= max_decimals); + debug_assert!(max_decimals < 100); + let max_decimals = max_decimals.min(16); + let min_decimals = min_decimals.min(max_decimals); + + if min_decimals < max_decimals { + // Try using a few decimals as possible, and then add more until we have enough precision + // to round-trip the number. + for decimals in min_decimals..max_decimals { + let text = format_with_decimals(value, decimals); + if let Some(parsed) = re_format::parse_f64(&text) { + if egui::emath::almost_equal(parsed as f32, value as f32, epsilon) { + // Enough precision to show the value accurately - good! + return text; + } + } + } + // The value has more precision than we expected. + // Probably the value was set not by the slider, but from outside. + // In any case: show the full value + } + + // Use max decimals + format_with_decimals(value, max_decimals) } /// Is this Ui in a resizable panel?