euclid/
rect.rs

1// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10use super::UnknownUnit;
11use length::Length;
12use scale::TypedScale;
13use num::*;
14use box2d::TypedBox2D;
15use point::TypedPoint2D;
16use vector::TypedVector2D;
17use side_offsets::TypedSideOffsets2D;
18use size::TypedSize2D;
19use approxord::{min, max};
20
21use num_traits::NumCast;
22#[cfg(feature = "serde")]
23use serde::{Deserialize, Deserializer, Serialize, Serializer};
24
25use core::borrow::Borrow;
26use core::cmp::PartialOrd;
27use core::fmt;
28use core::hash::{Hash, Hasher};
29use core::ops::{Add, Div, Mul, Sub, Range};
30
31
32/// A 2d Rectangle optionally tagged with a unit.
33#[repr(C)]
34pub struct TypedRect<T, U = UnknownUnit> {
35    pub origin: TypedPoint2D<T, U>,
36    pub size: TypedSize2D<T, U>,
37}
38
39/// The default rectangle type with no unit.
40pub type Rect<T> = TypedRect<T, UnknownUnit>;
41
42#[cfg(feature = "serde")]
43impl<'de, T: Copy + Deserialize<'de>, U> Deserialize<'de> for TypedRect<T, U> {
44    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
45    where
46        D: Deserializer<'de>,
47    {
48        let (origin, size) = try!(Deserialize::deserialize(deserializer));
49        Ok(TypedRect::new(origin, size))
50    }
51}
52
53#[cfg(feature = "serde")]
54impl<T: Serialize, U> Serialize for TypedRect<T, U> {
55    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
56    where
57        S: Serializer,
58    {
59        (&self.origin, &self.size).serialize(serializer)
60    }
61}
62
63impl<T: Hash, U> Hash for TypedRect<T, U> {
64    fn hash<H: Hasher>(&self, h: &mut H) {
65        self.origin.hash(h);
66        self.size.hash(h);
67    }
68}
69
70impl<T: Copy, U> Copy for TypedRect<T, U> {}
71
72impl<T: Copy, U> Clone for TypedRect<T, U> {
73    fn clone(&self) -> Self {
74        *self
75    }
76}
77
78impl<T: PartialEq, U> PartialEq<TypedRect<T, U>> for TypedRect<T, U> {
79    fn eq(&self, other: &Self) -> bool {
80        self.origin.eq(&other.origin) && self.size.eq(&other.size)
81    }
82}
83
84impl<T: Eq, U> Eq for TypedRect<T, U> {}
85
86impl<T: fmt::Debug, U> fmt::Debug for TypedRect<T, U> {
87    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
88        write!(f, "TypedRect({:?} at {:?})", self.size, self.origin)
89    }
90}
91
92impl<T: fmt::Display, U> fmt::Display for TypedRect<T, U> {
93    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
94        write!(formatter, "Rect({} at {})", self.size, self.origin)
95    }
96}
97
98impl<T: Default, U> Default for TypedRect<T, U> {
99    fn default() -> Self {
100        TypedRect::new(Default::default(), Default::default())
101    }
102}
103
104impl<T, U> TypedRect<T, U> {
105    /// Constructor.
106    pub fn new(origin: TypedPoint2D<T, U>, size: TypedSize2D<T, U>) -> Self {
107        TypedRect {
108            origin,
109            size,
110        }
111    }
112}
113
114impl<T, U> TypedRect<T, U>
115where
116    T: Copy + Zero
117{
118    /// Creates a rect of the given size, at offset zero.
119    pub fn from_size(size: TypedSize2D<T, U>) -> Self {
120        TypedRect {
121            origin: TypedPoint2D::zero(),
122            size,
123        }
124    }
125}
126
127impl<T, U> TypedRect<T, U>
128where
129    T: Copy + Clone + Zero + PartialOrd + PartialEq + Add<T, Output = T> + Sub<T, Output = T>,
130{
131    #[inline]
132    pub fn intersects(&self, other: &Self) -> bool {
133        self.origin.x < other.origin.x + other.size.width
134            && other.origin.x < self.origin.x + self.size.width
135            && self.origin.y < other.origin.y + other.size.height
136            && other.origin.y < self.origin.y + self.size.height
137    }
138
139    #[inline]
140    pub fn max_x(&self) -> T {
141        self.origin.x + self.size.width
142    }
143
144    #[inline]
145    pub fn min_x(&self) -> T {
146        self.origin.x
147    }
148
149    #[inline]
150    pub fn max_y(&self) -> T {
151        self.origin.y + self.size.height
152    }
153
154    #[inline]
155    pub fn min_y(&self) -> T {
156        self.origin.y
157    }
158
159    #[inline]
160    pub fn max_x_typed(&self) -> Length<T, U> {
161        Length::new(self.max_x())
162    }
163
164    #[inline]
165    pub fn min_x_typed(&self) -> Length<T, U> {
166        Length::new(self.min_x())
167    }
168
169    #[inline]
170    pub fn max_y_typed(&self) -> Length<T, U> {
171        Length::new(self.max_y())
172    }
173
174    #[inline]
175    pub fn min_y_typed(&self) -> Length<T, U> {
176        Length::new(self.min_y())
177    }
178
179    #[inline]
180    pub fn x_range(&self) -> Range<T> {
181        self.min_x()..self.max_x()
182    }
183
184    #[inline]
185    pub fn y_range(&self) -> Range<T> {
186        self.min_y()..self.max_y()
187    }
188
189    #[inline]
190    pub fn intersection(&self, other: &Self) -> Option<Self> {
191        if !self.intersects(other) {
192            return None;
193        }
194
195        let upper_left = TypedPoint2D::new(
196            max(self.min_x(), other.min_x()),
197            max(self.min_y(), other.min_y()),
198        );
199        let lower_right_x = min(self.max_x(), other.max_x());
200        let lower_right_y = min(self.max_y(), other.max_y());
201
202        Some(TypedRect::new(
203            upper_left,
204            TypedSize2D::new(lower_right_x - upper_left.x, lower_right_y - upper_left.y),
205        ))
206    }
207
208    /// Returns the same rectangle, translated by a vector.
209    #[inline]
210    #[cfg_attr(feature = "unstable", must_use)]
211    pub fn translate(&self, by: &TypedVector2D<T, U>) -> Self {
212        Self::new(self.origin + *by, self.size)
213    }
214
215    /// Returns true if this rectangle contains the point. Points are considered
216    /// in the rectangle if they are on the left or top edge, but outside if they
217    /// are on the right or bottom edge.
218    #[inline]
219    pub fn contains(&self, other: &TypedPoint2D<T, U>) -> bool {
220        self.origin.x <= other.x && other.x < self.origin.x + self.size.width
221            && self.origin.y <= other.y && other.y < self.origin.y + self.size.height
222    }
223
224    /// Returns true if this rectangle contains the interior of rect. Always
225    /// returns true if rect is empty, and always returns false if rect is
226    /// nonempty but this rectangle is empty.
227    #[inline]
228    pub fn contains_rect(&self, rect: &Self) -> bool {
229        rect.is_empty()
230            || (self.min_x() <= rect.min_x() && rect.max_x() <= self.max_x()
231                && self.min_y() <= rect.min_y() && rect.max_y() <= self.max_y())
232    }
233
234    #[inline]
235    #[cfg_attr(feature = "unstable", must_use)]
236    pub fn inflate(&self, width: T, height: T) -> Self {
237        TypedRect::new(
238            TypedPoint2D::new(self.origin.x - width, self.origin.y - height),
239            TypedSize2D::new(
240                self.size.width + width + width,
241                self.size.height + height + height,
242            ),
243        )
244    }
245
246    #[inline]
247    #[cfg_attr(feature = "unstable", must_use)]
248    pub fn inflate_typed(&self, width: Length<T, U>, height: Length<T, U>) -> Self {
249        self.inflate(width.get(), height.get())
250    }
251
252    #[inline]
253    pub fn top_right(&self) -> TypedPoint2D<T, U> {
254        TypedPoint2D::new(self.max_x(), self.origin.y)
255    }
256
257    #[inline]
258    pub fn bottom_left(&self) -> TypedPoint2D<T, U> {
259        TypedPoint2D::new(self.origin.x, self.max_y())
260    }
261
262    #[inline]
263    pub fn bottom_right(&self) -> TypedPoint2D<T, U> {
264        TypedPoint2D::new(self.max_x(), self.max_y())
265    }
266
267    #[inline]
268    pub fn to_box2d(&self) -> TypedBox2D<T, U> {
269        TypedBox2D {
270            min: self.origin,
271            max: self.bottom_right(),
272        }
273    }
274
275    #[inline]
276    #[cfg_attr(feature = "unstable", must_use)]
277    pub fn translate_by_size(&self, size: &TypedSize2D<T, U>) -> Self {
278        self.translate(&size.to_vector())
279    }
280
281    /// Calculate the size and position of an inner rectangle.
282    ///
283    /// Subtracts the side offsets from all sides. The horizontal and vertical
284    /// offsets must not be larger than the original side length.
285    pub fn inner_rect(&self, offsets: TypedSideOffsets2D<T, U>) -> Self {
286        let rect = TypedRect::new(
287            TypedPoint2D::new(
288                self.origin.x + offsets.left,
289                self.origin.y + offsets.top
290            ),
291            TypedSize2D::new(
292                self.size.width - offsets.horizontal(),
293                self.size.height - offsets.vertical()
294            )
295        );
296        debug_assert!(rect.size.width >= Zero::zero());
297        debug_assert!(rect.size.height >= Zero::zero());
298        rect
299    }
300
301    /// Calculate the size and position of an outer rectangle.
302    ///
303    /// Add the offsets to all sides. The expanded rectangle is returned.
304    pub fn outer_rect(&self, offsets: TypedSideOffsets2D<T, U>) -> Self {
305        TypedRect::new(
306            TypedPoint2D::new(
307                self.origin.x - offsets.left,
308                self.origin.y - offsets.top
309            ),
310            TypedSize2D::new(
311                self.size.width + offsets.horizontal(),
312                self.size.height + offsets.vertical()
313            )
314        )
315    }
316
317    /// Returns the smallest rectangle defined by the top/bottom/left/right-most
318    /// points provided as parameter.
319    ///
320    /// Note: This function has a behavior that can be surprising because
321    /// the right-most and bottom-most points are exactly on the edge
322    /// of the rectangle while the `contains` function is has exclusive
323    /// semantic on these edges. This means that the right-most and bottom-most
324    /// points provided to `from_points` will count as not contained by the rect.
325    /// This behavior may change in the future.
326    pub fn from_points<I>(points: I) -> Self
327    where
328        I: IntoIterator,
329        I::Item: Borrow<TypedPoint2D<T, U>>,
330    {
331        let mut points = points.into_iter();
332
333        let (mut min_x, mut min_y) = match points.next() {
334            Some(first) => (first.borrow().x, first.borrow().y),
335            None => return TypedRect::zero(),
336        };
337
338        let (mut max_x, mut max_y) = (min_x, min_y);
339        for point in points {
340            let p = point.borrow();
341            if p.x < min_x {
342                min_x = p.x
343            }
344            if p.x > max_x {
345                max_x = p.x
346            }
347            if p.y < min_y {
348                min_y = p.y
349            }
350            if p.y > max_y {
351                max_y = p.y
352            }
353        }
354        TypedRect::new(
355            TypedPoint2D::new(min_x, min_y),
356            TypedSize2D::new(max_x - min_x, max_y - min_y),
357        )
358    }
359}
360
361impl<T, U> TypedRect<T, U>
362where
363    T: Copy + One + Add<Output = T> + Sub<Output = T> + Mul<Output = T>,
364{
365    /// Linearly interpolate between this rectangle and another rectangle.
366    ///
367    /// `t` is expected to be between zero and one.
368    #[inline]
369    pub fn lerp(&self, other: Self, t: T) -> Self {
370        Self::new(
371            self.origin.lerp(other.origin, t),
372            self.size.lerp(other.size, t),
373        )
374    }
375}
376
377impl<T, U> TypedRect<T, U>
378where
379    T: Copy + One + Add<Output = T> + Div<Output = T>,
380{
381    pub fn center(&self) -> TypedPoint2D<T, U> {
382        let two = T::one() + T::one();
383        self.origin + self.size.to_vector() / two
384    }
385}
386
387impl<T, U> TypedRect<T, U>
388where
389    T: Copy + Clone + PartialOrd + Add<T, Output = T> + Sub<T, Output = T> + Zero,
390{
391    #[inline]
392    pub fn union(&self, other: &Self) -> Self {
393        if self.size == Zero::zero() {
394            return *other;
395        }
396        if other.size == Zero::zero() {
397            return *self;
398        }
399
400        let upper_left = TypedPoint2D::new(
401            min(self.min_x(), other.min_x()),
402            min(self.min_y(), other.min_y()),
403        );
404
405        let lower_right_x = max(self.max_x(), other.max_x());
406        let lower_right_y = max(self.max_y(), other.max_y());
407
408        TypedRect::new(
409            upper_left,
410            TypedSize2D::new(lower_right_x - upper_left.x, lower_right_y - upper_left.y),
411        )
412    }
413}
414
415impl<T, U> TypedRect<T, U> {
416    #[inline]
417    pub fn scale<S: Copy>(&self, x: S, y: S) -> Self
418    where
419        T: Copy + Clone + Mul<S, Output = T>,
420    {
421        TypedRect::new(
422            TypedPoint2D::new(self.origin.x * x, self.origin.y * y),
423            TypedSize2D::new(self.size.width * x, self.size.height * y),
424        )
425    }
426}
427
428impl<T: Copy + Clone + Mul<T, Output = T>, U> TypedRect<T, U> {
429    #[inline]
430    pub fn area(&self) -> T {
431        self.size.area()
432    }
433}
434
435impl<T: Copy + PartialEq + Zero, U> TypedRect<T, U> {
436    /// Constructor, setting all sides to zero.
437    pub fn zero() -> Self {
438        TypedRect::new(TypedPoint2D::origin(), TypedSize2D::zero())
439    }
440
441    /// Returns true if the size is zero, regardless of the origin's value.
442    pub fn is_empty(&self) -> bool {
443        self.size.width == Zero::zero() || self.size.height == Zero::zero()
444    }
445}
446
447impl<T: Copy + Mul<T, Output = T>, U> Mul<T> for TypedRect<T, U> {
448    type Output = Self;
449    #[inline]
450    fn mul(self, scale: T) -> Self {
451        TypedRect::new(self.origin * scale, self.size * scale)
452    }
453}
454
455impl<T: Copy + Div<T, Output = T>, U> Div<T> for TypedRect<T, U> {
456    type Output = Self;
457    #[inline]
458    fn div(self, scale: T) -> Self {
459        TypedRect::new(self.origin / scale, self.size / scale)
460    }
461}
462
463impl<T: Copy + Mul<T, Output = T>, U1, U2> Mul<TypedScale<T, U1, U2>> for TypedRect<T, U1> {
464    type Output = TypedRect<T, U2>;
465    #[inline]
466    fn mul(self, scale: TypedScale<T, U1, U2>) -> TypedRect<T, U2> {
467        TypedRect::new(self.origin * scale, self.size * scale)
468    }
469}
470
471impl<T: Copy + Div<T, Output = T>, U1, U2> Div<TypedScale<T, U1, U2>> for TypedRect<T, U2> {
472    type Output = TypedRect<T, U1>;
473    #[inline]
474    fn div(self, scale: TypedScale<T, U1, U2>) -> TypedRect<T, U1> {
475        TypedRect::new(self.origin / scale, self.size / scale)
476    }
477}
478
479impl<T: Copy, Unit> TypedRect<T, Unit> {
480    /// Drop the units, preserving only the numeric value.
481    pub fn to_untyped(&self) -> Rect<T> {
482        TypedRect::new(self.origin.to_untyped(), self.size.to_untyped())
483    }
484
485    /// Tag a unitless value with units.
486    pub fn from_untyped(r: &Rect<T>) -> TypedRect<T, Unit> {
487        TypedRect::new(
488            TypedPoint2D::from_untyped(&r.origin),
489            TypedSize2D::from_untyped(&r.size),
490        )
491    }
492}
493
494impl<T0: NumCast + Copy, Unit> TypedRect<T0, Unit> {
495    /// Cast from one numeric representation to another, preserving the units.
496    ///
497    /// When casting from floating point to integer coordinates, the decimals are truncated
498    /// as one would expect from a simple cast, but this behavior does not always make sense
499    /// geometrically. Consider using round(), round_in or round_out() before casting.
500    pub fn cast<T1: NumCast + Copy>(&self) -> TypedRect<T1, Unit> {
501        TypedRect::new(
502            self.origin.cast(),
503            self.size.cast(),
504        )
505    }
506
507    /// Fallible cast from one numeric representation to another, preserving the units.
508    ///
509    /// When casting from floating point to integer coordinates, the decimals are truncated
510    /// as one would expect from a simple cast, but this behavior does not always make sense
511    /// geometrically. Consider using round(), round_in or round_out() before casting.
512    pub fn try_cast<T1: NumCast + Copy>(&self) -> Option<TypedRect<T1, Unit>> {
513        match (self.origin.try_cast(), self.size.try_cast()) {
514            (Some(origin), Some(size)) => Some(TypedRect::new(origin, size)),
515            _ => None,
516        }
517    }
518}
519
520impl<T: Floor + Ceil + Round + Add<T, Output = T> + Sub<T, Output = T>, U> TypedRect<T, U> {
521    /// Return a rectangle with edges rounded to integer coordinates, such that
522    /// the returned rectangle has the same set of pixel centers as the original
523    /// one.
524    /// Edges at offset 0.5 round up.
525    /// Suitable for most places where integral device coordinates
526    /// are needed, but note that any translation should be applied first to
527    /// avoid pixel rounding errors.
528    /// Note that this is *not* rounding to nearest integer if the values are negative.
529    /// They are always rounding as floor(n + 0.5).
530    #[cfg_attr(feature = "unstable", must_use)]
531    pub fn round(&self) -> Self {
532        let origin = self.origin.round();
533        let size = self.origin.add_size(&self.size).round() - origin;
534        TypedRect::new(origin, TypedSize2D::new(size.x, size.y))
535    }
536
537    /// Return a rectangle with edges rounded to integer coordinates, such that
538    /// the original rectangle contains the resulting rectangle.
539    #[cfg_attr(feature = "unstable", must_use)]
540    pub fn round_in(&self) -> Self {
541        let origin = self.origin.ceil();
542        let size = self.origin.add_size(&self.size).floor() - origin;
543        TypedRect::new(origin, TypedSize2D::new(size.x, size.y))
544    }
545
546    /// Return a rectangle with edges rounded to integer coordinates, such that
547    /// the original rectangle is contained in the resulting rectangle.
548    #[cfg_attr(feature = "unstable", must_use)]
549    pub fn round_out(&self) -> Self {
550        let origin = self.origin.floor();
551        let size = self.origin.add_size(&self.size).ceil() - origin;
552        TypedRect::new(origin, TypedSize2D::new(size.x, size.y))
553    }
554}
555
556// Convenience functions for common casts
557impl<T: NumCast + Copy, Unit> TypedRect<T, Unit> {
558    /// Cast into an `f32` rectangle.
559    pub fn to_f32(&self) -> TypedRect<f32, Unit> {
560        self.cast()
561    }
562
563    /// Cast into an `f64` rectangle.
564    pub fn to_f64(&self) -> TypedRect<f64, Unit> {
565        self.cast()
566    }
567
568    /// Cast into an `usize` rectangle, truncating decimals if any.
569    ///
570    /// When casting from floating point rectangles, it is worth considering whether
571    /// to `round()`, `round_in()` or `round_out()` before the cast in order to
572    /// obtain the desired conversion behavior.
573    pub fn to_usize(&self) -> TypedRect<usize, Unit> {
574        self.cast()
575    }
576
577    /// Cast into an `u32` rectangle, truncating decimals if any.
578    ///
579    /// When casting from floating point rectangles, it is worth considering whether
580    /// to `round()`, `round_in()` or `round_out()` before the cast in order to
581    /// obtain the desired conversion behavior.
582    pub fn to_u32(&self) -> TypedRect<u32, Unit> {
583        self.cast()
584    }
585
586    /// Cast into an `i32` rectangle, truncating decimals if any.
587    ///
588    /// When casting from floating point rectangles, it is worth considering whether
589    /// to `round()`, `round_in()` or `round_out()` before the cast in order to
590    /// obtain the desired conversion behavior.
591    pub fn to_i32(&self) -> TypedRect<i32, Unit> {
592        self.cast()
593    }
594
595    /// Cast into an `i64` rectangle, truncating decimals if any.
596    ///
597    /// When casting from floating point rectangles, it is worth considering whether
598    /// to `round()`, `round_in()` or `round_out()` before the cast in order to
599    /// obtain the desired conversion behavior.
600    pub fn to_i64(&self) -> TypedRect<i64, Unit> {
601        self.cast()
602    }
603}
604
605impl<T, U> From<TypedSize2D<T, U>> for TypedRect<T, U>
606where T: Copy + Zero
607{
608    fn from(size: TypedSize2D<T, U>) -> Self {
609        Self::from_size(size)
610    }
611}
612
613/// Shorthand for `TypedRect::new(TypedPoint2D::new(x, y), TypedSize2D::new(w, h))`.
614pub fn rect<T: Copy, U>(x: T, y: T, w: T, h: T) -> TypedRect<T, U> {
615    TypedRect::new(TypedPoint2D::new(x, y), TypedSize2D::new(w, h))
616}
617
618#[cfg(test)]
619mod tests {
620    use point::{Point2D, point2};
621    use vector::vec2;
622    use side_offsets::SideOffsets2D;
623    use size::Size2D;
624    use super::*;
625
626    #[test]
627    fn test_translate() {
628        let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32));
629        let pp = p.translate(&vec2(10, 15));
630
631        assert!(pp.size.width == 50);
632        assert!(pp.size.height == 40);
633        assert!(pp.origin.x == 10);
634        assert!(pp.origin.y == 15);
635
636        let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40));
637        let rr = r.translate(&vec2(0, -10));
638
639        assert!(rr.size.width == 50);
640        assert!(rr.size.height == 40);
641        assert!(rr.origin.x == -10);
642        assert!(rr.origin.y == -15);
643    }
644
645    #[test]
646    fn test_translate_by_size() {
647        let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32));
648        let pp = p.translate_by_size(&Size2D::new(10, 15));
649
650        assert!(pp.size.width == 50);
651        assert!(pp.size.height == 40);
652        assert!(pp.origin.x == 10);
653        assert!(pp.origin.y == 15);
654
655        let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40));
656        let rr = r.translate_by_size(&Size2D::new(0, -10));
657
658        assert!(rr.size.width == 50);
659        assert!(rr.size.height == 40);
660        assert!(rr.origin.x == -10);
661        assert!(rr.origin.y == -15);
662    }
663
664    #[test]
665    fn test_union() {
666        let p = Rect::new(Point2D::new(0, 0), Size2D::new(50, 40));
667        let q = Rect::new(Point2D::new(20, 20), Size2D::new(5, 5));
668        let r = Rect::new(Point2D::new(-15, -30), Size2D::new(200, 15));
669        let s = Rect::new(Point2D::new(20, -15), Size2D::new(250, 200));
670
671        let pq = p.union(&q);
672        assert!(pq.origin == Point2D::new(0, 0));
673        assert!(pq.size == Size2D::new(50, 40));
674
675        let pr = p.union(&r);
676        assert!(pr.origin == Point2D::new(-15, -30));
677        assert!(pr.size == Size2D::new(200, 70));
678
679        let ps = p.union(&s);
680        assert!(ps.origin == Point2D::new(0, -15));
681        assert!(ps.size == Size2D::new(270, 200));
682    }
683
684    #[test]
685    fn test_intersection() {
686        let p = Rect::new(Point2D::new(0, 0), Size2D::new(10, 20));
687        let q = Rect::new(Point2D::new(5, 15), Size2D::new(10, 10));
688        let r = Rect::new(Point2D::new(-5, -5), Size2D::new(8, 8));
689
690        let pq = p.intersection(&q);
691        assert!(pq.is_some());
692        let pq = pq.unwrap();
693        assert!(pq.origin == Point2D::new(5, 15));
694        assert!(pq.size == Size2D::new(5, 5));
695
696        let pr = p.intersection(&r);
697        assert!(pr.is_some());
698        let pr = pr.unwrap();
699        assert!(pr.origin == Point2D::new(0, 0));
700        assert!(pr.size == Size2D::new(3, 3));
701
702        let qr = q.intersection(&r);
703        assert!(qr.is_none());
704    }
705
706    #[test]
707    fn test_contains() {
708        let r = Rect::new(Point2D::new(-20, 15), Size2D::new(100, 200));
709
710        assert!(r.contains(&Point2D::new(0, 50)));
711        assert!(r.contains(&Point2D::new(-10, 200)));
712
713        // The `contains` method is inclusive of the top/left edges, but not the
714        // bottom/right edges.
715        assert!(r.contains(&Point2D::new(-20, 15)));
716        assert!(!r.contains(&Point2D::new(80, 15)));
717        assert!(!r.contains(&Point2D::new(80, 215)));
718        assert!(!r.contains(&Point2D::new(-20, 215)));
719
720        // Points beyond the top-left corner.
721        assert!(!r.contains(&Point2D::new(-25, 15)));
722        assert!(!r.contains(&Point2D::new(-15, 10)));
723
724        // Points beyond the top-right corner.
725        assert!(!r.contains(&Point2D::new(85, 20)));
726        assert!(!r.contains(&Point2D::new(75, 10)));
727
728        // Points beyond the bottom-right corner.
729        assert!(!r.contains(&Point2D::new(85, 210)));
730        assert!(!r.contains(&Point2D::new(75, 220)));
731
732        // Points beyond the bottom-left corner.
733        assert!(!r.contains(&Point2D::new(-25, 210)));
734        assert!(!r.contains(&Point2D::new(-15, 220)));
735
736        let r = Rect::new(Point2D::new(-20.0, 15.0), Size2D::new(100.0, 200.0));
737        assert!(r.contains_rect(&r));
738        assert!(!r.contains_rect(&r.translate(&vec2(0.1, 0.0))));
739        assert!(!r.contains_rect(&r.translate(&vec2(-0.1, 0.0))));
740        assert!(!r.contains_rect(&r.translate(&vec2(0.0, 0.1))));
741        assert!(!r.contains_rect(&r.translate(&vec2(0.0, -0.1))));
742        // Empty rectangles are always considered as contained in other rectangles,
743        // even if their origin is not.
744        let p = Point2D::new(1.0, 1.0);
745        assert!(!r.contains(&p));
746        assert!(r.contains_rect(&Rect::new(p, Size2D::zero())));
747    }
748
749    #[test]
750    fn test_scale() {
751        let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32));
752        let pp = p.scale(10, 15);
753
754        assert!(pp.size.width == 500);
755        assert!(pp.size.height == 600);
756        assert!(pp.origin.x == 0);
757        assert!(pp.origin.y == 0);
758
759        let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40));
760        let rr = r.scale(1, 20);
761
762        assert!(rr.size.width == 50);
763        assert!(rr.size.height == 800);
764        assert!(rr.origin.x == -10);
765        assert!(rr.origin.y == -100);
766    }
767
768    #[test]
769    fn test_inflate() {
770        let p = Rect::new(Point2D::new(0, 0), Size2D::new(10, 10));
771        let pp = p.inflate(10, 20);
772
773        assert!(pp.size.width == 30);
774        assert!(pp.size.height == 50);
775        assert!(pp.origin.x == -10);
776        assert!(pp.origin.y == -20);
777
778        let r = Rect::new(Point2D::new(0, 0), Size2D::new(10, 20));
779        let rr = r.inflate(-2, -5);
780
781        assert!(rr.size.width == 6);
782        assert!(rr.size.height == 10);
783        assert!(rr.origin.x == 2);
784        assert!(rr.origin.y == 5);
785    }
786
787    #[test]
788    fn test_inner_outer_rect() {
789        let inner_rect: Rect<i32> = Rect::new(Point2D::new(20, 40), Size2D::new(80, 100));
790        let offsets = SideOffsets2D::new(20, 10, 10, 10);
791        let outer_rect = inner_rect.outer_rect(offsets);
792        assert_eq!(outer_rect.origin.x, 10);
793        assert_eq!(outer_rect.origin.y, 20);
794        assert_eq!(outer_rect.size.width, 100);
795        assert_eq!(outer_rect.size.height, 130);
796        assert_eq!(outer_rect.inner_rect(offsets), inner_rect);
797    }
798
799    #[test]
800    fn test_min_max_x_y() {
801        let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32));
802        assert!(p.max_y() == 40);
803        assert!(p.min_y() == 0);
804        assert!(p.max_x() == 50);
805        assert!(p.min_x() == 0);
806
807        let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40));
808        assert!(r.max_y() == 35);
809        assert!(r.min_y() == -5);
810        assert!(r.max_x() == 40);
811        assert!(r.min_x() == -10);
812    }
813
814    #[test]
815    fn test_is_empty() {
816        assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(0u32, 0u32)).is_empty());
817        assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(10u32, 0u32)).is_empty());
818        assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(0u32, 10u32)).is_empty());
819        assert!(!Rect::new(Point2D::new(0u32, 0u32), Size2D::new(1u32, 1u32)).is_empty());
820        assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(0u32, 0u32)).is_empty());
821        assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(10u32, 0u32)).is_empty());
822        assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(0u32, 10u32)).is_empty());
823        assert!(!Rect::new(Point2D::new(10u32, 10u32), Size2D::new(1u32, 1u32)).is_empty());
824    }
825
826    #[test]
827    fn test_round() {
828        let mut x = -2.0;
829        let mut y = -2.0;
830        let mut w = -2.0;
831        let mut h = -2.0;
832        while x < 2.0 {
833            while y < 2.0 {
834                while w < 2.0 {
835                    while h < 2.0 {
836                        let rect = Rect::new(Point2D::new(x, y), Size2D::new(w, h));
837
838                        assert!(rect.contains_rect(&rect.round_in()));
839                        assert!(rect.round_in().inflate(1.0, 1.0).contains_rect(&rect));
840
841                        assert!(rect.round_out().contains_rect(&rect));
842                        assert!(rect.inflate(1.0, 1.0).contains_rect(&rect.round_out()));
843
844                        assert!(rect.inflate(1.0, 1.0).contains_rect(&rect.round()));
845                        assert!(rect.round().inflate(1.0, 1.0).contains_rect(&rect));
846
847                        h += 0.1;
848                    }
849                    w += 0.1;
850                }
851                y += 0.1;
852            }
853            x += 0.1
854        }
855    }
856
857    #[test]
858    fn test_center() {
859        let r: Rect<i32> = rect(-2, 5, 4, 10);
860        assert_eq!(r.center(), point2(0, 10));
861
862        let r: Rect<f32> = rect(1.0, 2.0, 3.0, 4.0);
863        assert_eq!(r.center(), point2(2.5, 4.0));
864    }
865}