Skip to main content

ferroui_base/
size.rs

1//! Defines a size.
2
3use std::fmt;
4use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
5use std::str::FromStr;
6
7use crate::utilities::math_utilities::{self, MathUtilities};
8use crate::utilities::span_helpers::InvariantF64;
9use crate::utilities::{FormatError, SpanStringTokenizer};
10use crate::{Thickness, Vector};
11
12/// Defines a size.
13#[derive(Clone, Copy, Debug, PartialEq, Default)]
14pub struct Size {
15    /// The width.
16    pub width: f64,
17    /// The height.
18    pub height: f64,
19}
20
21impl Size {
22    /// A size representing infinity.
23    pub const INFINITY: Size = Size::new(f64::INFINITY, f64::INFINITY);
24
25    /// Initializes a new instance of the [`Size`] structure.
26    #[inline]
27    pub const fn new(width: f64, height: f64) -> Self {
28        Self { width, height }
29    }
30
31    /// Gets the aspect ratio of the size.
32    #[inline]
33    pub fn aspect_ratio(&self) -> f64 {
34        self.width / self.height
35    }
36
37    /// Parses a [`Size`] string (`"width, height"` or `"width height"`).
38    pub fn parse(s: &str) -> Result<Self, FormatError> {
39        SpanStringTokenizer::with_message(s, "Invalid Size.")
40            .scope(|t| Ok(Size::new(t.read_double()?, t.read_double()?)))
41    }
42
43    /// Constrains the size: each dimension becomes the smaller of this size's and
44    /// the constraint's.
45    #[inline]
46    pub fn constrain(&self, constraint: Size) -> Size {
47        Size::new(
48            math_utilities::min(self.width, constraint.width),
49            math_utilities::min(self.height, constraint.height),
50        )
51    }
52
53    /// Deflates the size by a [`Thickness`]. The result never has negative dimensions.
54    #[inline]
55    pub fn deflate(&self, thickness: Thickness) -> Size {
56        let mut width = self.width - thickness.left - thickness.right;
57        if width < 0.0 {
58            width = 0.0;
59        }
60
61        let mut height = self.height - thickness.top - thickness.bottom;
62        if height < 0.0 {
63            height = 0.0;
64        }
65
66        Size::new(width, height)
67    }
68
69    /// Returns a boolean indicating whether the size is equal to the other given
70    /// size (exact comparison of the dimensions).
71    #[inline]
72    pub fn equals(&self, other: Size) -> bool {
73        self.width == other.width && self.height == other.height
74    }
75
76    /// Returns a boolean indicating whether the size is equal to the other given
77    /// size (numerically, within a relative epsilon).
78    #[inline]
79    pub fn nearly_equals(&self, other: Size) -> bool {
80        MathUtilities::are_close(self.width, other.width)
81            && MathUtilities::are_close(self.height, other.height)
82    }
83
84    /// Inflates the size by a [`Thickness`].
85    #[inline]
86    pub fn inflate(&self, thickness: Thickness) -> Size {
87        Size::new(
88            self.width + thickness.left + thickness.right,
89            self.height + thickness.top + thickness.bottom,
90        )
91    }
92
93    /// Returns a new [`Size`] with the same height and the specified width.
94    #[inline]
95    pub const fn with_width(&self, width: f64) -> Size {
96        Size::new(width, self.height)
97    }
98
99    /// Returns a new [`Size`] with the same width and the specified height.
100    #[inline]
101    pub const fn with_height(&self, height: f64) -> Size {
102        Size::new(self.width, height)
103    }
104
105    /// Deconstructs the size into its width and height values.
106    #[inline]
107    pub const fn deconstruct(&self) -> (f64, f64) {
108        (self.width, self.height)
109    }
110}
111
112impl From<Size> for (f64, f64) {
113    #[inline]
114    fn from(s: Size) -> Self {
115        (s.width, s.height)
116    }
117}
118
119/// Scales a size.
120impl Mul<Vector> for Size {
121    type Output = Size;
122    #[inline]
123    fn mul(self, scale: Vector) -> Size {
124        Size::new(self.width * scale.x, self.height * scale.y)
125    }
126}
127
128/// Scales a size.
129impl Div<Vector> for Size {
130    type Output = Size;
131    #[inline]
132    fn div(self, scale: Vector) -> Size {
133        Size::new(self.width / scale.x, self.height / scale.y)
134    }
135}
136
137/// Divides a size by another size to produce a scaling factor.
138impl Div for Size {
139    type Output = Vector;
140    #[inline]
141    fn div(self, right: Size) -> Vector {
142        Vector::new(self.width / right.width, self.height / right.height)
143    }
144}
145
146/// Scales a size.
147impl Mul<f64> for Size {
148    type Output = Size;
149    #[inline]
150    fn mul(self, scale: f64) -> Size {
151        Size::new(self.width * scale, self.height * scale)
152    }
153}
154
155/// Scales a size.
156impl Div<f64> for Size {
157    type Output = Size;
158    #[inline]
159    fn div(self, scale: f64) -> Size {
160        Size::new(self.width / scale, self.height / scale)
161    }
162}
163
164impl Add for Size {
165    type Output = Size;
166    #[inline]
167    fn add(self, to_add: Size) -> Size {
168        Size::new(self.width + to_add.width, self.height + to_add.height)
169    }
170}
171
172impl Sub for Size {
173    type Output = Size;
174    #[inline]
175    fn sub(self, to_subtract: Size) -> Size {
176        Size::new(
177            self.width - to_subtract.width,
178            self.height - to_subtract.height,
179        )
180    }
181}
182
183impl AddAssign for Size {
184    #[inline]
185    fn add_assign(&mut self, rhs: Size) {
186        *self = *self + rhs;
187    }
188}
189
190impl SubAssign for Size {
191    #[inline]
192    fn sub_assign(&mut self, rhs: Size) {
193        *self = *self - rhs;
194    }
195}
196
197impl MulAssign<f64> for Size {
198    #[inline]
199    fn mul_assign(&mut self, scale: f64) {
200        *self = *self * scale;
201    }
202}
203
204impl DivAssign<f64> for Size {
205    #[inline]
206    fn div_assign(&mut self, scale: f64) {
207        *self = *self / scale;
208    }
209}
210
211impl MulAssign<Vector> for Size {
212    #[inline]
213    fn mul_assign(&mut self, scale: Vector) {
214        *self = *self * scale;
215    }
216}
217
218impl DivAssign<Vector> for Size {
219    #[inline]
220    fn div_assign(&mut self, scale: Vector) {
221        *self = *self / scale;
222    }
223}
224
225impl FromStr for Size {
226    type Err = FormatError;
227    #[inline]
228    fn from_str(s: &str) -> Result<Self, FormatError> {
229        Size::parse(s)
230    }
231}
232
233impl fmt::Display for Size {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        write!(
236            f,
237            "{}, {}",
238            InvariantF64(self.width),
239            InvariantF64(self.height)
240        )
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247
248    #[test]
249    fn should_produce_correct_aspect_ratio() {
250        let result = Size::new(3.0, 2.0).aspect_ratio();
251
252        assert_eq!(1.5, result);
253    }
254
255    #[test]
256    fn dividing_should_produce_scaling_factor() {
257        let result = Size::new(15.0, 10.0) / Size::new(5.0, 5.0);
258
259        assert_eq!(Vector::new(3.0, 2.0), result);
260    }
261
262    // Additional coverage (not part of the reference suite).
263
264    #[test]
265    fn parse_display_inflate_deflate_constrain() {
266        assert_eq!(Size::parse("10, 20").unwrap(), Size::new(10.0, 20.0));
267        assert_eq!(Size::parse("10").unwrap_err().message(), "Invalid Size.");
268        assert_eq!(Size::INFINITY.to_string(), "Infinity, Infinity");
269        let t = Thickness::new(1.0, 2.0, 3.0, 4.0);
270        assert_eq!(Size::new(10.0, 10.0).inflate(t), Size::new(14.0, 16.0));
271        assert_eq!(Size::new(10.0, 10.0).deflate(t), Size::new(6.0, 4.0));
272        assert_eq!(Size::new(1.0, 1.0).deflate(t), Size::new(0.0, 0.0));
273        assert_eq!(
274            Size::new(10.0, 30.0).constrain(Size::new(20.0, 20.0)),
275            Size::new(10.0, 20.0)
276        );
277        assert!(Size::new(f64::NAN, 1.0)
278            .constrain(Size::new(1.0, 1.0))
279            .width
280            .is_nan());
281    }
282}