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