1use crate::{Insets, Point, Size, Vec2};
2
3#[derive(Clone, Copy, Debug, Default, PartialEq)]
6pub struct Rect {
7 pub origin: Point,
8 pub size: Size,
9}
10
11impl Rect {
12 pub const ZERO: Self = Self {
13 origin: Point::ORIGIN,
14 size: Size::ZERO,
15 };
16
17 #[inline]
18 pub const fn new(origin: Point, size: Size) -> Self {
19 Self { origin, size }
20 }
21
22 #[inline]
24 pub const fn from_xywh(x: f64, y: f64, width: f64, height: f64) -> Self {
25 Self {
26 origin: Point::new(x, y),
27 size: Size::new(width, height),
28 }
29 }
30
31 #[inline]
33 pub fn from_points(a: Point, b: Point) -> Self {
34 let x = a.x.min(b.x);
35 let y = a.y.min(b.y);
36 Self::from_xywh(x, y, (a.x - b.x).abs(), (a.y - b.y).abs())
37 }
38
39 #[inline]
40 pub fn min_x(self) -> f64 {
41 self.origin.x
42 }
43 #[inline]
44 pub fn min_y(self) -> f64 {
45 self.origin.y
46 }
47 #[inline]
48 pub fn max_x(self) -> f64 {
49 self.origin.x + self.size.width
50 }
51 #[inline]
52 pub fn max_y(self) -> f64 {
53 self.origin.y + self.size.height
54 }
55
56 #[inline]
57 pub fn width(self) -> f64 {
58 self.size.width
59 }
60 #[inline]
61 pub fn height(self) -> f64 {
62 self.size.height
63 }
64
65 #[inline]
66 pub fn center(self) -> Point {
67 Point::new(
68 (self.min_x() + self.max_x()) * 0.5,
69 (self.min_y() + self.max_y()) * 0.5,
70 )
71 }
72
73 #[inline]
74 pub fn is_empty(self) -> bool {
75 self.size.is_empty()
76 }
77
78 #[inline]
82 pub fn contains(self, p: Point) -> bool {
83 p.x >= self.min_x() && p.x < self.max_x() && p.y >= self.min_y() && p.y < self.max_y()
84 }
85
86 #[inline]
88 pub fn translate(self, by: Vec2) -> Rect {
89 Rect::new(self.origin + by, self.size)
90 }
91
92 #[inline]
94 pub fn inset(self, insets: Insets) -> Rect {
95 Rect::new(
96 Point::new(self.min_x() + insets.left, self.min_y() + insets.top),
97 self.size.deflate(insets),
98 )
99 }
100
101 pub fn intersection(self, other: Rect) -> Option<Rect> {
103 let x0 = self.min_x().max(other.min_x());
104 let y0 = self.min_y().max(other.min_y());
105 let x1 = self.max_x().min(other.max_x());
106 let y1 = self.max_y().min(other.max_y());
107 if x1 > x0 && y1 > y0 {
108 Some(Rect::from_xywh(x0, y0, x1 - x0, y1 - y0))
109 } else {
110 None
111 }
112 }
113
114 pub fn union(self, other: Rect) -> Rect {
116 if self.is_empty() {
117 return other;
118 }
119 if other.is_empty() {
120 return self;
121 }
122 let x0 = self.min_x().min(other.min_x());
123 let y0 = self.min_y().min(other.min_y());
124 let x1 = self.max_x().max(other.max_x());
125 let y1 = self.max_y().max(other.max_y());
126 Rect::from_xywh(x0, y0, x1 - x0, y1 - y0)
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn contains_is_half_open() {
136 let r = Rect::from_xywh(0.0, 0.0, 10.0, 10.0);
137 assert!(r.contains(Point::new(0.0, 0.0)));
138 assert!(r.contains(Point::new(9.999, 9.999)));
139 assert!(!r.contains(Point::new(10.0, 5.0)));
140 }
141
142 #[test]
143 fn intersection_and_union() {
144 let a = Rect::from_xywh(0.0, 0.0, 10.0, 10.0);
145 let b = Rect::from_xywh(5.0, 5.0, 10.0, 10.0);
146 assert_eq!(a.intersection(b), Some(Rect::from_xywh(5.0, 5.0, 5.0, 5.0)));
147 assert_eq!(a.union(b), Rect::from_xywh(0.0, 0.0, 15.0, 15.0));
148
149 let c = Rect::from_xywh(100.0, 100.0, 1.0, 1.0);
150 assert_eq!(a.intersection(c), None);
151 }
152
153 #[test]
154 fn inset_shrinks() {
155 let r = Rect::from_xywh(0.0, 0.0, 20.0, 20.0).inset(Insets::uniform(5.0));
156 assert_eq!(r, Rect::from_xywh(5.0, 5.0, 10.0, 10.0));
157 }
158}