1use core::fmt;
4
5use super::Position;
6
7#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
14pub struct Rect {
15 pub x: u16,
17 pub y: u16,
19 pub width: u16,
21 pub height: u16,
23}
24
25impl Rect {
26 pub const ZERO: Self = Self {
28 x: 0,
29 y: 0,
30 width: 0,
31 height: 0,
32 };
33
34 pub const fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
39 let width = x.saturating_add(width) - x;
40 let height = y.saturating_add(height) - y;
41 Self {
42 x,
43 y,
44 width,
45 height,
46 }
47 }
48
49 pub const fn area(self) -> u32 {
51 (self.width as u32) * (self.height as u32)
52 }
53
54 pub const fn is_empty(self) -> bool {
56 self.width == 0 || self.height == 0
57 }
58
59 pub const fn left(self) -> u16 {
61 self.x
62 }
63
64 pub const fn right(self) -> u16 {
66 self.x.saturating_add(self.width)
67 }
68
69 pub const fn top(self) -> u16 {
71 self.y
72 }
73
74 pub const fn bottom(self) -> u16 {
76 self.y.saturating_add(self.height)
77 }
78
79 pub const fn position(self) -> Position {
81 Position {
82 x: self.x,
83 y: self.y,
84 }
85 }
86
87 pub fn contains(self, pos: impl Into<Position>) -> bool {
89 let p = pos.into();
90 p.x >= self.left() && p.x < self.right() && p.y >= self.top() && p.y < self.bottom()
91 }
92
93 pub fn intersection(self, other: Self) -> Self {
95 let x1 = self.x.max(other.x);
96 let y1 = self.y.max(other.y);
97 let x2 = self.right().min(other.right());
98 let y2 = self.bottom().min(other.bottom());
99 if x2 <= x1 || y2 <= y1 {
100 Self::ZERO
101 } else {
102 Self {
103 x: x1,
104 y: y1,
105 width: x2 - x1,
106 height: y2 - y1,
107 }
108 }
109 }
110
111 pub fn union(self, other: Self) -> Self {
113 if self.is_empty() {
114 return other;
115 }
116 if other.is_empty() {
117 return self;
118 }
119 let x1 = self.x.min(other.x);
120 let y1 = self.y.min(other.y);
121 let x2 = self.right().max(other.right());
122 let y2 = self.bottom().max(other.bottom());
123 Self {
124 x: x1,
125 y: y1,
126 width: x2 - x1,
127 height: y2 - y1,
128 }
129 }
130}
131
132impl From<(u16, u16, u16, u16)> for Rect {
133 fn from((x, y, width, height): (u16, u16, u16, u16)) -> Self {
134 Self::new(x, y, width, height)
135 }
136}
137
138impl From<Rect> for (u16, u16, u16, u16) {
139 fn from(r: Rect) -> Self {
140 (r.x, r.y, r.width, r.height)
141 }
142}
143
144impl fmt::Display for Rect {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 write!(f, "{}x{}+{}+{}", self.width, self.height, self.x, self.y)
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn rect_basics() {
156 let r = Rect::new(2, 3, 10, 4);
157 assert_eq!(r.left(), 2);
158 assert_eq!(r.top(), 3);
159 assert_eq!(r.right(), 12);
160 assert_eq!(r.bottom(), 7);
161 assert_eq!(r.area(), 40);
162 assert!(!r.is_empty());
163 assert!(r.contains((2, 3)));
164 assert!(r.contains((11, 6)));
165 assert!(!r.contains((12, 6)));
166 assert!(!r.contains((11, 7)));
167 }
168
169 #[test]
170 fn rect_new_saturates() {
171 let r = Rect::new(u16::MAX - 2, 0, 100, 1);
172 assert_eq!(r.right(), u16::MAX);
173 assert_eq!(r.width, 2);
174 }
175
176 #[test]
177 fn rect_intersection() {
178 let a = Rect::new(0, 0, 10, 10);
179 let b = Rect::new(5, 5, 10, 10);
180 assert_eq!(a.intersection(b), Rect::new(5, 5, 5, 5));
181
182 let disjoint = Rect::new(20, 20, 5, 5);
183 assert!(a.intersection(disjoint).is_empty());
184 }
185
186 #[test]
187 fn rect_union() {
188 let a = Rect::new(0, 0, 5, 5);
189 let b = Rect::new(10, 10, 5, 5);
190 assert_eq!(a.union(b), Rect::new(0, 0, 15, 15));
191 assert_eq!(a.union(Rect::ZERO), a);
192 assert_eq!(Rect::ZERO.union(b), b);
193 }
194}