ferroui_base/
relative_scalar.rs1use 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#[derive(Clone, Copy, Debug, Default)]
15pub struct RelativeScalar {
16 pub scalar: f64,
18 pub unit: RelativeUnit,
20}
21
22impl RelativeScalar {
23 pub const BEGINNING: RelativeScalar = RelativeScalar::new(0.0, RelativeUnit::Relative);
25
26 pub const MIDDLE: RelativeScalar = RelativeScalar::new(0.5, RelativeUnit::Relative);
28
29 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 #[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 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 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}