Skip to main content

ferroui_base/
thickness.rs

1//! Describes the thickness of a frame around a rectangle.
2
3use std::fmt;
4use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
5use std::str::FromStr;
6
7use crate::utilities::math_utilities;
8use crate::utilities::span_helpers::InvariantF64;
9use crate::utilities::{FormatError, SpanStringTokenizer};
10use crate::Size;
11
12/// Describes the thickness of a frame around a rectangle.
13#[derive(Clone, Copy, Debug, PartialEq, Default)]
14pub struct Thickness {
15    /// The thickness on the left.
16    pub left: f64,
17    /// The thickness on the top.
18    pub top: f64,
19    /// The thickness on the right.
20    pub right: f64,
21    /// The thickness on the bottom.
22    pub bottom: f64,
23}
24
25impl Thickness {
26    /// Initializes a new instance of the [`Thickness`] structure from the four sides.
27    #[inline]
28    pub const fn new(left: f64, top: f64, right: f64, bottom: f64) -> Self {
29        Self {
30            left,
31            top,
32            right,
33            bottom,
34        }
35    }
36
37    /// Initializes a [`Thickness`] with the same length on every side.
38    #[inline]
39    pub const fn uniform(uniform_length: f64) -> Self {
40        Self::new(
41            uniform_length,
42            uniform_length,
43            uniform_length,
44            uniform_length,
45        )
46    }
47
48    /// Initializes a [`Thickness`] from the thickness on the left and right, and
49    /// the thickness on the top and bottom.
50    #[inline]
51    pub const fn symmetric(horizontal: f64, vertical: f64) -> Self {
52        Self::new(horizontal, vertical, horizontal, vertical)
53    }
54
55    /// Gets a value indicating whether all sides are equal (NaN compares equal to NaN here).
56    #[inline]
57    pub fn is_uniform(&self) -> bool {
58        math_utilities::equals(self.left, self.right)
59            && math_utilities::equals(self.top, self.bottom)
60            && math_utilities::equals(self.right, self.bottom)
61    }
62
63    /// Parses a [`Thickness`] string: one, two or four numbers.
64    pub fn parse(s: &str) -> Result<Self, FormatError> {
65        const EXCEPTION_MESSAGE: &str = "Invalid Thickness.";
66
67        SpanStringTokenizer::with_message(s, EXCEPTION_MESSAGE).scope(|t| {
68            if let Some(a) = t.try_read_double()? {
69                if let Some(b) = t.try_read_double()? {
70                    if let Some(c) = t.try_read_double()? {
71                        return Ok(Thickness::new(a, b, c, t.read_double()?));
72                    }
73
74                    return Ok(Thickness::symmetric(a, b));
75                }
76
77                return Ok(Thickness::uniform(a));
78            }
79
80            Err(FormatError::new(EXCEPTION_MESSAGE))
81        })
82    }
83
84    /// Returns a boolean indicating whether the thickness is equal to the other
85    /// given thickness (exact comparison of the sides).
86    #[inline]
87    pub fn equals(&self, other: Thickness) -> bool {
88        self.left == other.left
89            && self.top == other.top
90            && self.right == other.right
91            && self.bottom == other.bottom
92    }
93
94    /// Deconstructs the thickness into its left, top, right and bottom values.
95    #[inline]
96    pub const fn deconstruct(&self) -> (f64, f64, f64, f64) {
97        (self.left, self.top, self.right, self.bottom)
98    }
99}
100
101impl From<Thickness> for (f64, f64, f64, f64) {
102    #[inline]
103    fn from(t: Thickness) -> Self {
104        (t.left, t.top, t.right, t.bottom)
105    }
106}
107
108impl Add for Thickness {
109    type Output = Thickness;
110    #[inline]
111    fn add(self, b: Thickness) -> Thickness {
112        Thickness::new(
113            self.left + b.left,
114            self.top + b.top,
115            self.right + b.right,
116            self.bottom + b.bottom,
117        )
118    }
119}
120
121impl Sub for Thickness {
122    type Output = Thickness;
123    #[inline]
124    fn sub(self, b: Thickness) -> Thickness {
125        Thickness::new(
126            self.left - b.left,
127            self.top - b.top,
128            self.right - b.right,
129            self.bottom - b.bottom,
130        )
131    }
132}
133
134impl Mul<f64> for Thickness {
135    type Output = Thickness;
136    #[inline]
137    fn mul(self, b: f64) -> Thickness {
138        Thickness::new(self.left * b, self.top * b, self.right * b, self.bottom * b)
139    }
140}
141
142/// Adds a thickness to a size.
143impl Add<Thickness> for Size {
144    type Output = Size;
145    #[inline]
146    fn add(self, thickness: Thickness) -> Size {
147        Size::new(
148            self.width + thickness.left + thickness.right,
149            self.height + thickness.top + thickness.bottom,
150        )
151    }
152}
153
154/// Subtracts a thickness from a size.
155impl Sub<Thickness> for Size {
156    type Output = Size;
157    #[inline]
158    fn sub(self, thickness: Thickness) -> Size {
159        Size::new(
160            self.width - (thickness.left + thickness.right),
161            self.height - (thickness.top + thickness.bottom),
162        )
163    }
164}
165
166impl AddAssign for Thickness {
167    #[inline]
168    fn add_assign(&mut self, b: Thickness) {
169        *self = *self + b;
170    }
171}
172
173impl SubAssign for Thickness {
174    #[inline]
175    fn sub_assign(&mut self, b: Thickness) {
176        *self = *self - b;
177    }
178}
179
180impl MulAssign<f64> for Thickness {
181    #[inline]
182    fn mul_assign(&mut self, b: f64) {
183        *self = *self * b;
184    }
185}
186
187impl AddAssign<Thickness> for Size {
188    #[inline]
189    fn add_assign(&mut self, thickness: Thickness) {
190        *self = *self + thickness;
191    }
192}
193
194impl SubAssign<Thickness> for Size {
195    #[inline]
196    fn sub_assign(&mut self, thickness: Thickness) {
197        *self = *self - thickness;
198    }
199}
200
201impl FromStr for Thickness {
202    type Err = FormatError;
203    #[inline]
204    fn from_str(s: &str) -> Result<Self, FormatError> {
205        Thickness::parse(s)
206    }
207}
208
209impl fmt::Display for Thickness {
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        write!(
212            f,
213            "{},{},{},{}",
214            InvariantF64(self.left),
215            InvariantF64(self.top),
216            InvariantF64(self.right),
217            InvariantF64(self.bottom)
218        )
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn parse_parses_single_uniform_size() {
228        let result = Thickness::parse("1.2").unwrap();
229
230        assert_eq!(Thickness::uniform(1.2), result);
231    }
232
233    #[test]
234    fn parse_parses_horizontal_vertical() {
235        let result = Thickness::parse("1.2,3.4").unwrap();
236
237        assert_eq!(Thickness::symmetric(1.2, 3.4), result);
238    }
239
240    #[test]
241    fn parse_parses_left_top_right_bottom() {
242        let result = Thickness::parse("1.2, 3.4, 5, 6").unwrap();
243
244        assert_eq!(Thickness::new(1.2, 3.4, 5.0, 6.0), result);
245    }
246
247    #[test]
248    fn parse_accepts_spaces() {
249        let result = Thickness::parse("1.2 3.4 5 6").unwrap();
250
251        assert_eq!(Thickness::new(1.2, 3.4, 5.0, 6.0), result);
252    }
253
254    // Additional coverage (not part of the reference suite).
255
256    #[test]
257    fn parse_errors_display_and_operators() {
258        assert_eq!(
259            Thickness::parse("").unwrap_err().message(),
260            "Invalid Thickness."
261        );
262        assert!(Thickness::parse("1,2,3").is_err());
263        assert!(Thickness::parse("1,2,3,4,5").is_err());
264        assert_eq!(Thickness::new(1.0, 2.5, 3.0, 4.0).to_string(), "1,2.5,3,4");
265        assert!(Thickness::uniform(2.0).is_uniform());
266        assert!(Thickness::uniform(f64::NAN).is_uniform());
267        assert!(!Thickness::symmetric(1.0, 2.0).is_uniform());
268        let t = Thickness::new(1.0, 2.0, 3.0, 4.0);
269        assert_eq!(t + t, t * 2.0);
270        assert_eq!(t - t, Thickness::default());
271        assert_eq!(Size::new(10.0, 10.0) + t, Size::new(14.0, 16.0));
272        assert_eq!(Size::new(10.0, 10.0) - t, Size::new(6.0, 4.0));
273    }
274}