Skip to main content

ferroui_base/
relative_scalar.rs

1//! Defines a scalar that may be defined relative to a containing element.
2
3use std::fmt;
4use std::str::FromStr;
5
6use crate::utilities::math_utilities;
7use crate::utilities::span_helpers::{parse_double, InvariantF64};
8use crate::utilities::FormatError;
9use crate::RelativeUnit;
10
11/// Defines a scalar that may be defined relative to a containing element.
12///
13/// Equality treats NaN scalars as equal to each other.
14#[derive(Clone, Copy, Debug, Default)]
15pub struct RelativeScalar {
16    /// The scalar.
17    pub scalar: f64,
18    /// The unit of the scalar.
19    pub unit: RelativeUnit,
20}
21
22impl RelativeScalar {
23    /// The beginning of the containing element (0%).
24    pub const BEGINNING: RelativeScalar = RelativeScalar::new(0.0, RelativeUnit::Relative);
25
26    /// The middle of the containing element (50%).
27    pub const MIDDLE: RelativeScalar = RelativeScalar::new(0.5, RelativeUnit::Relative);
28
29    /// The end of the containing element (100%).
30    pub const END: RelativeScalar = RelativeScalar::new(1.0, RelativeUnit::Relative);
31
32    #[inline]
33    pub const fn new(scalar: f64, unit: RelativeUnit) -> Self {
34        Self { scalar, unit }
35    }
36
37    #[inline]
38    pub fn equals(&self, other: RelativeScalar) -> bool {
39        math_utilities::equals(self.scalar, other.scalar) && self.unit == other.unit
40    }
41
42    /// Converts the scalar into a final value, using `size` as the size the scalar
43    /// is relative to.
44    #[inline]
45    pub fn to_value(&self, size: f64) -> f64 {
46        if self.unit == RelativeUnit::Absolute {
47            self.scalar
48        } else {
49            size * self.scalar
50        }
51    }
52
53    /// Parses a [`RelativeScalar`] string: `"n"` (absolute) or `"n%"` (relative).
54    pub fn parse(s: &str) -> Result<Self, FormatError> {
55        let trimmed = s.trim();
56        if trimmed.ends_with('%') {
57            let number = trimmed.trim_end_matches('%');
58            let value = parse_double(number).ok_or_else(|| FormatError::invalid_input(number))?;
59            return Ok(RelativeScalar::new(value * 0.01, RelativeUnit::Relative));
60        }
61
62        let value = parse_double(trimmed).ok_or_else(|| FormatError::invalid_input(trimmed))?;
63        Ok(RelativeScalar::new(value, RelativeUnit::Absolute))
64    }
65}
66
67impl PartialEq for RelativeScalar {
68    #[inline]
69    fn eq(&self, other: &RelativeScalar) -> bool {
70        self.equals(*other)
71    }
72}
73
74impl FromStr for RelativeScalar {
75    type Err = FormatError;
76    #[inline]
77    fn from_str(s: &str) -> Result<Self, FormatError> {
78        RelativeScalar::parse(s)
79    }
80}
81
82impl fmt::Display for RelativeScalar {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        if self.unit == RelativeUnit::Absolute {
85            write!(f, "{}", InvariantF64(self.scalar))
86        } else {
87            write!(f, "{}%", InvariantF64(self.scalar * 100.0))
88        }
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    // The reference test-suite has no tests for this type.
95    use super::*;
96
97    #[test]
98    fn parse_display_and_to_value() {
99        assert_eq!(
100            RelativeScalar::parse(" 50% ").unwrap(),
101            RelativeScalar::MIDDLE
102        );
103        assert_eq!(
104            RelativeScalar::parse("12.5").unwrap(),
105            RelativeScalar::new(12.5, RelativeUnit::Absolute)
106        );
107        assert_eq!(
108            RelativeScalar::parse("1,000").unwrap(),
109            RelativeScalar::new(1000.0, RelativeUnit::Absolute)
110        );
111        assert!(RelativeScalar::parse("").is_err());
112        assert!(RelativeScalar::parse("abc%").is_err());
113        assert_eq!(RelativeScalar::MIDDLE.to_string(), "50%");
114        assert_eq!(
115            RelativeScalar::new(12.5, RelativeUnit::Absolute).to_string(),
116            "12.5"
117        );
118        assert_eq!(RelativeScalar::MIDDLE.to_value(200.0), 100.0);
119        assert_eq!(
120            RelativeScalar::new(12.5, RelativeUnit::Absolute).to_value(200.0),
121            12.5
122        );
123        assert_eq!(
124            RelativeScalar::new(f64::NAN, RelativeUnit::Absolute),
125            RelativeScalar::new(f64::NAN, RelativeUnit::Absolute)
126        );
127    }
128}