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)]
|
#![allow(clippy::if_same_then_else)]
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
|
||||||
|
|
||||||
use epaint::{Rounding, Shadow, Stroke};
|
use epaint::{Rounding, Shadow, Stroke};
|
||||||
|
|
||||||
|
|
@ -11,6 +11,51 @@ use crate::{
|
||||||
RichText, WidgetText,
|
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).
|
/// Alias for a [`FontId`] (font of a certain size).
|
||||||
|
|
@ -182,6 +227,12 @@ pub struct Style {
|
||||||
/// The style to use for [`DragValue`] text.
|
/// The style to use for [`DragValue`] text.
|
||||||
pub drag_value_text_style: TextStyle,
|
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
|
/// 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`.
|
/// 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,
|
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 {
|
impl Style {
|
||||||
// TODO(emilk): rename style.interact() to maybe… `style.interactive` ?
|
// TODO(emilk): rename style.interact() to maybe… `style.interactive` ?
|
||||||
/// Use this style for interactive things.
|
/// Use this style for interactive things.
|
||||||
|
|
@ -1060,6 +1117,7 @@ impl Default for Style {
|
||||||
override_text_style: None,
|
override_text_style: None,
|
||||||
text_styles: default_text_styles(),
|
text_styles: default_text_styles(),
|
||||||
drag_value_text_style: TextStyle::Button,
|
drag_value_text_style: TextStyle::Button,
|
||||||
|
number_formatter: NumberFormatter(Arc::new(emath::format_with_decimals_in_range)),
|
||||||
wrap: None,
|
wrap: None,
|
||||||
wrap_mode: None,
|
wrap_mode: None,
|
||||||
spacing: Spacing::default(),
|
spacing: Spacing::default(),
|
||||||
|
|
@ -1355,6 +1413,7 @@ impl Style {
|
||||||
override_text_style,
|
override_text_style,
|
||||||
text_styles,
|
text_styles,
|
||||||
drag_value_text_style,
|
drag_value_text_style,
|
||||||
|
number_formatter: _, // can't change callbacks in the UI
|
||||||
wrap: _,
|
wrap: _,
|
||||||
wrap_mode: _,
|
wrap_mode: _,
|
||||||
spacing,
|
spacing,
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,8 @@ impl<'a> DragValue<'a> {
|
||||||
/// A custom formatter takes a `f64` for the numeric value and a `RangeInclusive<usize>` representing
|
/// 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 decimal range i.e. minimum and maximum number of decimal places shown.
|
||||||
///
|
///
|
||||||
|
/// The default formatter is [`Style::number_formatter`].
|
||||||
|
///
|
||||||
/// See also: [`DragValue::custom_parser`]
|
/// See also: [`DragValue::custom_parser`]
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -481,7 +483,10 @@ impl<'a> Widget for DragValue<'a> {
|
||||||
|
|
||||||
let value_text = match custom_formatter {
|
let value_text = match custom_formatter {
|
||||||
Some(custom_formatter) => custom_formatter(value, auto_decimals..=max_decimals),
|
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();
|
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> {
|
/// The default egui parser of numbers.
|
||||||
let value_text: String = value_text
|
///
|
||||||
|
/// 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()
|
.chars()
|
||||||
// Ignore whitespace (trailing, leading, and thousands separators):
|
// Ignore whitespace (trailing, leading, and thousands separators):
|
||||||
.filter(|c| !c.is_whitespace())
|
.filter(|c| !c.is_whitespace())
|
||||||
|
|
@ -685,7 +693,7 @@ fn default_parser(value_text: &str) -> Option<f64> {
|
||||||
.map(|c| if c == '−' { '-' } else { c })
|
.map(|c| if c == '−' { '-' } else { c })
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
value_text.parse().ok()
|
text.parse().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clamp_value_to_range(x: f64, range: RangeInclusive<f64>) -> f64 {
|
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
|
/// 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 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| {
|
/// # 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
|
/// A custom parser takes an `&str` to parse into a number and returns `Some` if it was successfully parsed
|
||||||
/// or `None` otherwise.
|
/// or `None` otherwise.
|
||||||
///
|
///
|
||||||
/// See also: [`DragValue::custom_formatter`]
|
/// See also: [`Slider::custom_formatter`]
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # egui::__run_test_ui(|ui| {
|
/// # egui::__run_test_ui(|ui| {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue