figures/
rect.rs

1use std::ops::{Add, AddAssign, Sub, SubAssign};
2
3use crate::traits::{IntoSigned, IntoUnsigned, Ranged, StdNumOps};
4use crate::{FloatConversion, IntoComponents, Point, Round, Size, Zero};
5
6/// A 2d area expressed as an origin ([`Point`]) and a [`Size`].
7#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct Rect<Unit> {
10    /// The origin of the rectangle
11    pub origin: Point<Unit>,
12    /// The size of the rectangle.
13    pub size: Size<Unit>,
14}
15
16impl<Unit> Rect<Unit> {
17    /// Returns a new rectangle.
18    pub const fn new(origin: Point<Unit>, size: Size<Unit>) -> Self {
19        Self { origin, size }
20    }
21
22    /// Returns a new rectangle using the given points to form the top-left and
23    /// bottom-right of the rectangle.
24    ///
25    /// The order of the parameters does not matter. The minimum values will
26    /// form the top-left and the maximum values will form the bottom-right.
27    pub fn from_extents(p1: Point<Unit>, p2: Point<Unit>) -> Self
28    where
29        Unit: crate::Unit,
30    {
31        let min_x = p1.x.min(p2.x);
32        let min_y = p1.y.min(p2.y);
33        let max_x = p1.x.max(p2.x);
34        let max_y = p1.y.max(p2.y);
35        Self {
36            origin: Point { x: min_x, y: min_y },
37            size: Size {
38                width: max_x - min_x,
39                height: max_y - min_y,
40            },
41        }
42    }
43
44    /// Expands this rect to the nearest whole number.
45    ///
46    /// This function will never return a smaller rectangle.
47    #[must_use]
48    pub fn expand_rounded(self) -> Self
49    where
50        Unit: Round + crate::Unit,
51    {
52        let (tl, br) = self.extents();
53
54        Self::from_extents(tl.floor(), br.ceil())
55    }
56
57    /// Maps each component to `map` and returns a new value with the mapped
58    /// components.
59    #[must_use]
60    pub fn map<NewUnit>(self, mut map: impl FnMut(Unit) -> NewUnit) -> Rect<NewUnit> {
61        Rect {
62            origin: self.origin.map(&mut map),
63            size: self.size.map(map),
64        }
65    }
66
67    /// Returns a rectangle that has been inset by `amount` on all sides.
68    #[must_use]
69    pub fn inset(mut self, amount: impl Into<Unit>) -> Self
70    where
71        Unit: Add<Unit, Output = Unit> + AddAssign<Unit> + SubAssign<Unit> + Copy,
72    {
73        let amount = amount.into();
74        let double_amount = amount + amount;
75        self.origin.x += amount;
76        self.origin.y += amount;
77        self.size.width -= double_amount;
78        self.size.height -= double_amount;
79        self
80    }
81
82    /// Converts the contents of this point to `NewUnit` using [`From`].
83    pub fn cast<NewUnit>(self) -> Rect<NewUnit>
84    where
85        NewUnit: From<Unit>,
86    {
87        Rect {
88            origin: self.origin.cast(),
89            size: self.size.cast(),
90        }
91    }
92
93    /// Converts the contents of this rect to `NewUnit` using [`TryFrom`].
94    ///
95    /// # Errors
96    ///
97    /// Returns `<NewUnit as TryFrom>::Error` when the inner type cannot be
98    /// converted. For this crate's types, this genenerally will be
99    pub fn try_cast<NewUnit>(self) -> Result<Rect<NewUnit>, NewUnit::Error>
100    where
101        NewUnit: TryFrom<Unit>,
102    {
103        Ok(Rect {
104            origin: self.origin.try_cast()?,
105            size: self.size.try_cast()?,
106        })
107    }
108
109    /// Returns true if this rect contains `point`.
110    pub fn contains(&self, point: Point<Unit>) -> bool
111    where
112        Unit: crate::Unit,
113    {
114        let (p1, p2) = self.extents();
115        p1.x <= point.x && p1.y <= point.y && p2.x > point.x && p2.y > point.y
116    }
117
118    /// Returns true if the areas of `self` and `other` overlap.
119    ///
120    /// This function does not return true if the edges touch but do not overlap.
121    ///
122    /// ```rust
123    /// use figures::{Point, Rect, Size};
124    ///
125    /// let a: Rect<i32> = Rect::new(Point::new(1, 1), Size::new(2, 2));
126    /// let b = Rect::new(Point::new(2, 2), Size::new(1, 1));
127    /// assert!(a.intersects(&b));
128    /// let c = Rect::new(Point::new(3, 1), Size::new(1, 1));
129    /// assert!(!a.intersects(&c));
130    /// ```
131    pub fn intersects(&self, other: &Self) -> bool
132    where
133        Unit: Add<Output = Unit> + Ord + Copy,
134    {
135        let (
136            Point {
137                x: r1_left,
138                y: r1_top,
139            },
140            Point {
141                x: r1_right,
142                y: r1_bottom,
143            },
144        ) = self.extents();
145        let (
146            Point {
147                x: r2_left,
148                y: r2_top,
149            },
150            Point {
151                x: r2_right,
152                y: r2_bottom,
153            },
154        ) = other.extents();
155        !(r1_right <= r2_left || r2_right <= r1_left || r1_bottom <= r2_top || r1_top >= r2_bottom)
156    }
157
158    /// Returns the overlapping rectangle of `self` and `other`. If the
159    /// rectangles do not overlap, None will be returned.
160    ///
161    /// ```rust
162    /// use figures::{Point, Rect, Size};
163    ///
164    /// let a: Rect<i32> = Rect::new(Point::new(1, 1), Size::new(3, 3));
165    /// let b = Rect::new(Point::new(2, 2), Size::new(3, 3));
166    /// assert_eq!(
167    ///     a.intersection(&b),
168    ///     Some(Rect::new(Point::new(2, 2), Size::new(2, 2)))
169    /// );
170    /// let c = Rect::new(Point::new(4, 1), Size::new(1, 1));
171    /// assert_eq!(a.intersection(&c), None);
172    /// ```
173    pub fn intersection(&self, other: &Self) -> Option<Rect<Unit>>
174    where
175        Unit: crate::Unit,
176    {
177        let (a1, a2) = self.extents();
178        let (b1, b2) = other.extents();
179        let x1 = a1.x.max(b1.x);
180        let x2 = a2.x.min(b2.x);
181        if x2 > x1 {
182            let y1 = a1.y.max(b1.y);
183            let y2 = a2.y.min(b2.y);
184            if y2 > y1 {
185                return Some(Rect::from_extents(Point::new(x1, y1), Point::new(x2, y2)));
186            }
187        }
188        None
189    }
190
191    /// Returns the non-origin point.
192    pub fn extent(&self) -> Point<Unit>
193    where
194        Unit: crate::Unit,
195    {
196        self.origin + self.size
197    }
198}
199
200impl<Unit> Rect<Unit>
201where
202    Unit: Add<Output = Unit> + Ord + Copy,
203{
204    /// Returns the top-left and bottom-right points of this rectangle.
205    ///
206    /// The first point returned will always be the top-right point, even if the size of the rectangle is negative.
207    pub fn extents(&self) -> (Point<Unit>, Point<Unit>) {
208        let extent = self.origin + self.size;
209        (
210            Point::new(self.origin.x.min(extent.x), self.origin.y.min(extent.y)),
211            Point::new(self.origin.x.max(extent.x), self.origin.y.max(extent.y)),
212        )
213    }
214}
215
216impl<Unit> Rect<Unit>
217where
218    Unit: StdNumOps + Ord + Copy,
219{
220    /// Returns the top-left and bottom-right points of this rectangle.
221    ///
222    /// The first point returned will always be the top-right point, even if the
223    /// size of the rectangle is negative.
224    ///
225    /// The returned extent point will be saturated instead of wrapping.
226    pub fn saturating_extents(&self) -> (Point<Unit>, Point<Unit>) {
227        let extent = self.origin.saturating_add(self.size.to_vec());
228        (
229            Point::new(self.origin.x.min(extent.x), self.origin.y.min(extent.y)),
230            Point::new(self.origin.x.max(extent.x), self.origin.y.max(extent.y)),
231        )
232    }
233}
234
235impl<Unit> Default for Rect<Unit>
236where
237    Unit: Default,
238{
239    fn default() -> Self {
240        Self {
241            origin: Point::default(),
242            size: Size::default(),
243        }
244    }
245}
246
247impl<Unit> IntoUnsigned for Rect<Unit>
248where
249    Unit: IntoUnsigned,
250{
251    type Unsigned = Rect<Unit::Unsigned>;
252
253    fn into_unsigned(self) -> Self::Unsigned {
254        Rect {
255            origin: self.origin.into_unsigned(),
256            size: self.size.into_unsigned(),
257        }
258    }
259}
260
261impl<Unit> IntoSigned for Rect<Unit>
262where
263    Unit: IntoSigned,
264{
265    type Signed = Rect<Unit::Signed>;
266
267    fn into_signed(self) -> Self::Signed {
268        Rect {
269            origin: self.origin.into_signed(),
270            size: self.size.into_signed(),
271        }
272    }
273}
274
275impl<Unit> From<Size<Unit>> for Rect<Unit>
276where
277    Unit: Default,
278{
279    fn from(size: Size<Unit>) -> Self {
280        Self::new(Point::default(), size)
281    }
282}
283
284impl<Unit> Add<Point<Unit>> for Rect<Unit>
285where
286    Unit: Add<Output = Unit>,
287{
288    type Output = Self;
289
290    fn add(self, rhs: Point<Unit>) -> Self::Output {
291        Self::new(self.origin + rhs, self.size)
292    }
293}
294
295impl<Unit> Sub<Point<Unit>> for Rect<Unit>
296where
297    Unit: Sub<Output = Unit>,
298{
299    type Output = Self;
300
301    fn sub(self, rhs: Point<Unit>) -> Self::Output {
302        Self::new(self.origin - rhs, self.size)
303    }
304}
305
306impl<Unit> Ranged for Rect<Unit>
307where
308    Unit: Ranged,
309{
310    const MAX: Self = Self::new(Point::MAX, Size::MAX);
311    const MIN: Self = Self::new(Point::MIN, Size::MIN);
312}
313
314impl<Unit> Zero for Rect<Unit>
315where
316    Unit: Zero,
317{
318    const ZERO: Self = Self {
319        origin: Point::ZERO,
320        size: Size::ZERO,
321    };
322
323    fn is_zero(&self) -> bool {
324        self.origin.is_zero() && self.size.is_zero()
325    }
326}
327
328impl<Unit> FloatConversion for Rect<Unit>
329where
330    Unit: FloatConversion,
331{
332    type Float = Rect<Unit::Float>;
333
334    fn into_float(self) -> Self::Float {
335        self.map(FloatConversion::into_float)
336    }
337
338    fn from_float(float: Self::Float) -> Self {
339        float.map(FloatConversion::from_float)
340    }
341}
342
343#[test]
344fn intersection() {
345    assert_eq!(
346        Rect::<i32>::new(Point::new(1, 1,), Size::new(3, 3))
347            .intersection(&Rect::new(Point::new(2, 2,), Size::new(3, 3))),
348        Some(Rect::new(Point::new(2, 2,), Size::new(2, 2)))
349    );
350}