Add `Style::number_formatter` as the default used by `DragValue` (#4740)
This allows users to customize how numbers are converted into strings in a `DragValue`, as a global default. This can be used (for instance) to show thousands separators, or use `,` as a decimal separator.
This commit is contained in:
parent
e297a1d107
commit
dc1f032846
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#![allow(clippy::if_same_then_else)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
|
||||
|
||||
use epaint::{Rounding, Shadow, Stroke};
|
||||
|
||||
|
|
@ -11,6 +11,51 @@ use crate::{
|
|||
RichText, WidgetText,
|
||||
};
|
||||
|
||||
/// How to format numbers in e.g. a [`crate::DragValue`].
|
||||
#[derive(Clone)]
|
||||
pub struct NumberFormatter(
|
||||
Arc<dyn 'static + Sync + Send + Fn(f64, RangeInclusive<usize>) -> String>,
|
||||
);
|
||||
|
||||
impl NumberFormatter {
|
||||
/// The first argument is the number to be formatted.
|
||||
/// The second argument is the range of the number of decimals to show.
|
||||
///
|
||||
/// See [`Self::format`] for the meaning of the `decimals` argument.
|
||||
#[inline]
|
||||
pub fn new(
|
||||
formatter: impl 'static + Sync + Send + Fn(f64, RangeInclusive<usize>) -> String,
|
||||
) -> Self {
|
||||
Self(Arc::new(formatter))
|
||||
}
|
||||
|
||||
/// Format the given number with the given number of decimals.
|
||||
///
|
||||
/// Decimals are counted after the decimal point.
|
||||
///
|
||||
/// The minimum number of decimals is usually automatically calculated
|
||||
/// from the sensitivity of the [`crate::DragValue`] and will usually be respected (e.g. include trailing zeroes),
|
||||
/// but if the given value requires more decimals to represent accurately,
|
||||
/// more decimals will be shown, up to the given max.
|
||||
#[inline]
|
||||
pub fn format(&self, value: f64, decimals: RangeInclusive<usize>) -> String {
|
||||
(self.0)(value, decimals)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for NumberFormatter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("NumberFormatter")
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for NumberFormatter {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Alias for a [`FontId`] (font of a certain size).
|
||||
|
|
@ -182,6 +227,12 @@ pub struct Style {
|
|||
/// The style to use for [`DragValue`] text.
|
||||
pub drag_value_text_style: TextStyle,
|
||||
|
||||
/// How to format numbers as strings, e.g. in a [`crate::DragValue`].
|
||||
///
|
||||
/// You can override this to e.g. add thousands separators.
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pub number_formatter: NumberFormatter,
|
||||
|
||||
/// If set, labels, buttons, etc. will use this to determine whether to wrap the text at the
|
||||
/// right edge of the [`Ui`] they are in. By default, this is `None`.
|
||||
///
|
||||
|
|
@ -231,6 +282,12 @@ pub struct Style {
|
|||
pub always_scroll_the_only_direction: bool,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_impl_send_sync() {
|
||||
fn assert_send_sync<T: Send + Sync>() {}
|
||||
assert_send_sync::<Style>();
|
||||
}
|
||||
|
||||
impl Style {
|
||||
// TODO(emilk): rename style.interact() to maybe… `style.interactive` ?
|
||||
/// Use this style for interactive things.
|
||||
|
|
@ -1060,6 +1117,7 @@ impl Default for Style {
|
|||
override_text_style: None,
|
||||
text_styles: default_text_styles(),
|
||||
drag_value_text_style: TextStyle::Button,
|
||||
number_formatter: NumberFormatter(Arc::new(emath::format_with_decimals_in_range)),
|
||||
wrap: None,
|
||||
wrap_mode: None,
|
||||
spacing: Spacing::default(),
|
||||
|
|
@ -1355,6 +1413,7 @@ impl Style {
|
|||
override_text_style,
|
||||
text_styles,
|
||||
drag_value_text_style,
|
||||
number_formatter: _, // can't change callbacks in the UI
|
||||
wrap: _,
|
||||
wrap_mode: _,
|
||||
spacing,
|
||||
|
|
|
|||
|
|
@ -176,6 +176,8 @@ impl<'a> DragValue<'a> {
|
|||
/// A custom formatter takes a `f64` for the numeric value and a `RangeInclusive<usize>` representing
|
||||
/// the decimal range i.e. minimum and maximum number of decimal places shown.
|
||||
///
|
||||
/// The default formatter is [`Style::number_formatter`].
|
||||
///
|
||||
/// See also: [`DragValue::custom_parser`]
|
||||
///
|
||||
/// ```
|
||||
|
|
@ -481,7 +483,10 @@ impl<'a> Widget for DragValue<'a> {
|
|||
|
||||
let value_text = match custom_formatter {
|
||||
Some(custom_formatter) => custom_formatter(value, auto_decimals..=max_decimals),
|
||||
None => emath::format_with_decimals_in_range(value, auto_decimals..=max_decimals),
|
||||
None => ui
|
||||
.style()
|
||||
.number_formatter
|
||||
.format(value, auto_decimals..=max_decimals),
|
||||
};
|
||||
|
||||
let text_style = ui.style().drag_value_text_style.clone();
|
||||
|
|
@ -676,8 +681,11 @@ fn parse(custom_parser: &Option<NumParser<'_>>, value_text: &str) -> Option<f64>
|
|||
}
|
||||
}
|
||||
|
||||
fn default_parser(value_text: &str) -> Option<f64> {
|
||||
let value_text: String = value_text
|
||||
/// The default egui parser of numbers.
|
||||
///
|
||||
/// It ignored whitespaces anywhere in the input, and treats the special minus character (U+2212) as a normal minus.
|
||||
fn default_parser(text: &str) -> Option<f64> {
|
||||
let text: String = text
|
||||
.chars()
|
||||
// Ignore whitespace (trailing, leading, and thousands separators):
|
||||
.filter(|c| !c.is_whitespace())
|
||||
|
|
@ -685,7 +693,7 @@ fn default_parser(value_text: &str) -> Option<f64> {
|
|||
.map(|c| if c == '−' { '-' } else { c })
|
||||
.collect();
|
||||
|
||||
value_text.parse().ok()
|
||||
text.parse().ok()
|
||||
}
|
||||
|
||||
fn clamp_value_to_range(x: f64, range: RangeInclusive<f64>) -> f64 {
|
||||
|
|
|
|||
|
|
@ -318,7 +318,9 @@ impl<'a> Slider<'a> {
|
|||
/// A custom formatter takes a `f64` for the numeric value and a `RangeInclusive<usize>` representing
|
||||
/// the decimal range i.e. minimum and maximum number of decimal places shown.
|
||||
///
|
||||
/// See also: [`DragValue::custom_parser`]
|
||||
/// The default formatter is [`Style::number_formatter`].
|
||||
///
|
||||
/// See also: [`Slider::custom_parser`]
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
|
|
@ -361,7 +363,7 @@ impl<'a> Slider<'a> {
|
|||
/// A custom parser takes an `&str` to parse into a number and returns `Some` if it was successfully parsed
|
||||
/// or `None` otherwise.
|
||||
///
|
||||
/// See also: [`DragValue::custom_formatter`]
|
||||
/// See also: [`Slider::custom_formatter`]
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
|
|
|
|||
Loading…
Reference in New Issue