1#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
10pub struct Rect {
11 pub x: u16,
13 pub y: u16,
15 pub width: u16,
17 pub height: u16,
19}
20
21impl std::fmt::Display for Rect {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 write!(f, "{}x{}+{}+{}", self.width, self.height, self.x, self.y)
24 }
25}
26
27impl Rect {
28 pub const ZERO: Self = Self {
30 x: 0,
31 y: 0,
32 width: 0,
33 height: 0,
34 };
35
36 pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
39 let max_area = u16::MAX;
40 let (clipped_width, clipped_height) =
41 if u32::from(width) * u32::from(height) > u32::from(max_area) {
42 let aspect_ratio = f64::from(width) / f64::from(height);
43 let max_area_f = f64::from(max_area);
44 let height_f = (max_area_f / aspect_ratio).sqrt();
45 let width_f = height_f * aspect_ratio;
46 (width_f as u16, height_f as u16)
47 } else {
48 (width, height)
49 };
50 Self {
51 x,
52 y,
53 width: clipped_width,
54 height: clipped_height,
55 }
56 }
57
58 pub const fn area(self) -> u16 {
61 self.width.saturating_mul(self.height)
62 }
63
64 pub const fn is_empty(self) -> bool {
66 self.width == 0 || self.height == 0
67 }
68
69 pub const fn left(self) -> u16 {
71 self.x
72 }
73
74 pub const fn right(self) -> u16 {
80 self.x.saturating_add(self.width)
81 }
82
83 pub const fn top(self) -> u16 {
85 self.y
86 }
87
88 pub const fn bottom(self) -> u16 {
94 self.y.saturating_add(self.height)
95 }
96
97 #[must_use = "method returns the modified value"]
101 pub const fn inner(self, margin_x: u16, margin_y: u16) -> Self {
102 let doubled_margin_horizontal = margin_x.saturating_mul(2);
103 let doubled_margin_vertical = margin_y.saturating_mul(2);
104
105 if self.width < doubled_margin_horizontal || self.height < doubled_margin_vertical {
106 Self::ZERO
107 } else {
108 Self {
109 x: self.x.saturating_add(margin_x),
110 y: self.y.saturating_add(margin_y),
111 width: self.width.saturating_sub(doubled_margin_horizontal),
112 height: self.height.saturating_sub(doubled_margin_vertical),
113 }
114 }
115 }
116
117 #[must_use = "method returns the modified value"]
124 pub fn offset(self, x: i32, y: i32) -> Self {
125 Self {
126 x: i32::from(self.x)
127 .saturating_add(x)
128 .clamp(0, i32::from(u16::MAX - self.width)) as u16,
129 y: i32::from(self.y)
130 .saturating_add(y)
131 .clamp(0, i32::from(u16::MAX - self.height)) as u16,
132 ..self
133 }
134 }
135
136 #[must_use = "method returns the modified value"]
138 pub fn union(self, other: Self) -> Self {
139 let x1 = std::cmp::min(self.x, other.x);
140 let y1 = std::cmp::min(self.y, other.y);
141 let x2 = std::cmp::max(self.right(), other.right());
142 let y2 = std::cmp::max(self.bottom(), other.bottom());
143 Self {
144 x: x1,
145 y: y1,
146 width: x2.saturating_sub(x1),
147 height: y2.saturating_sub(y1),
148 }
149 }
150
151 #[must_use = "method returns the modified value"]
155 pub fn intersection(self, other: Self) -> Self {
156 let x1 = std::cmp::max(self.x, other.x);
157 let y1 = std::cmp::max(self.y, other.y);
158 let x2 = std::cmp::min(self.right(), other.right());
159 let y2 = std::cmp::min(self.bottom(), other.bottom());
160 Self {
161 x: x1,
162 y: y1,
163 width: x2.saturating_sub(x1),
164 height: y2.saturating_sub(y1),
165 }
166 }
167
168 pub const fn intersects(self, other: Self) -> bool {
170 self.x < other.right()
171 && self.right() > other.x
172 && self.y < other.bottom()
173 && self.bottom() > other.y
174 }
175
176 pub const fn contains(self, x: u16, y: u16) -> bool {
180 x >= self.x
181 && x < self.right()
182 && y >= self.y
183 && y < self.bottom()
184 }
185
186 #[must_use = "method returns the modified value"]
201 pub fn clamp(self, other: Self) -> Self {
202 let width = self.width.min(other.width);
203 let height = self.height.min(other.height);
204 let x = self.x.clamp(other.x, other.right().saturating_sub(width));
205 let y = self.y.clamp(other.y, other.bottom().saturating_sub(height));
206 Self::new(x, y, width, height)
207 }
208}
209
210impl Rect {
211 pub fn hsplit_portion(&self, portion: f32) -> (Self, Self) {
212 let width_a = (self.width as f32 * portion).floor() as u16;
213 let width_b = self.width - width_a;
214 (
215 Rect::new(self.x, self.y, width_a, self.height),
216 Rect::new(self.x + width_a, self.y, width_b, self.height),
217 )
218 }
219
220 pub fn vsplit_portion(&self, portion: f32) -> (Self, Self) {
221 let height_a = (self.height as f32 * portion).floor() as u16;
222 let height_b = self.height - height_a;
223 (
224 Rect::new(self.x, self.y, self.width, height_a),
225 Rect::new(self.x, self.y + height_a, self.width, height_b),
226 )
227 }
228
229 pub fn hsplit_len(&self, length: u16) -> (Self, Self) {
230 if length >= self.width {
231 return (*self, Rect::ZERO);
232 }
233 (
234 Rect::new(self.x, self.y, length, self.height),
235 Rect::new(self.x + length, self.y, self.width - length, self.height),
236 )
237 }
238
239 pub fn vsplit_len(&self, length: u16) -> (Self, Self) {
240 if length >= self.height {
241 return (*self, Rect::ZERO);
242 }
243 (
244 Rect::new(self.x, self.y, self.width, length),
245 Rect::new(self.x, self.y + length, self.width, self.height - length),
246 )
247 }
248
249 pub fn hsplit_inverse_portion(&self, portion: f32) -> (Self, Self) {
250 let width_a = (self.width as f32 * portion).floor() as u16;
251 let width_b = self.width - width_a;
252 (
253 Rect::new(self.x + width_b, self.y, width_a, self.height),
254 Rect::new(self.x, self.y, width_b, self.height),
255 )
256 }
257
258 pub fn vsplit_inverse_portion(&self, portion: f32) -> (Self, Self) {
259 let height_a = (self.height as f32 * portion).floor() as u16;
260 let height_b = self.height - height_a;
261 (
262 Rect::new(self.x, self.y + height_b, self.width, height_a),
263 Rect::new(self.x, self.y, self.width, height_b),
264 )
265 }
266
267 pub fn hsplit_inverse_len(&self, length: u16) -> (Self, Self) {
268 if length >= self.width {
269 return (Rect::ZERO, *self);
270 }
271 (
272 Rect::new(self.x + (self.width - length), self.y, length, self.height),
273 Rect::new(self.x, self.y, self.width - length, self.height),
274 )
275 }
276
277 pub fn vsplit_inverse_len(&self, length: u16) -> (Self, Self) {
278 if length >= self.height {
279 return (Rect::ZERO, *self);
280 }
281 (
282 Rect::new(self.x, self.y + (self.height - length), self.width, length),
283 Rect::new(self.x, self.y, self.width, self.height - length),
284 )
285 }
286
287 pub fn inner_centered(&self, width: u16, height: u16) -> Self {
288 let x = self.x + (self.width.saturating_sub(width) / 2);
289 let y = self.y + (self.height.saturating_sub(height) / 2);
290 Rect::new(x, y, width.min(self.width), height.min(self.height))
291 }
292
293 pub fn rows(&self) -> Vec<Self> {
294 (0..self.height)
295 .into_iter()
296 .map(|row_index| {
297 Rect::new(self.left(), self.top() + row_index, self.width, self.height)
298 })
299 .collect()
300 }
301}
302
303
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308
309 #[test]
310 fn rect_splitting() {
311 let test_rect = Rect::new(0, 0, 10, 20);
312
313 assert_eq!(test_rect.hsplit_len(3), (Rect::new(0, 0, 3, 20), Rect::new(3, 0, 7, 20)));
314 assert_eq!(test_rect.hsplit_len(11), (Rect::new(0, 0, 10, 20), Rect::new(0, 0, 0, 0)));
315 assert_eq!(test_rect.vsplit_len(7), (Rect::new(0, 0, 10, 7), Rect::new(0, 7, 10, 13)));
316 assert_eq!(test_rect.vsplit_len(22), (Rect::new(0, 0, 10, 20), Rect::new(0, 0, 0, 0)));
317
318 assert_eq!(
319 test_rect.hsplit_inverse_len(3),
320 (Rect::new(7, 0, 3, 20), Rect::new(0, 0, 7, 20)),
321 );
322 }
323
324 #[test]
325 fn rect_splitting_with_offset() {
326 let test_rect = Rect::new(1, 1, 10, 20);
327
328 assert_eq!(test_rect.hsplit_len(3), (Rect::new(1, 1, 3, 20), Rect::new(4, 1, 7, 20)));
329 assert_eq!(test_rect.hsplit_len(11), (Rect::new(1, 1, 10, 20), Rect::new(0, 0, 0, 0)));
330 assert_eq!(test_rect.vsplit_len(7), (Rect::new(1, 1, 10, 7), Rect::new(1, 8, 10, 13)));
331 assert_eq!(test_rect.vsplit_len(22), (Rect::new(1, 1, 10, 20), Rect::new(0, 0, 0, 0)));
332
333 assert_eq!(
334 test_rect.hsplit_inverse_len(3),
335 (Rect::new(8, 1, 3, 20), Rect::new(1, 1, 7, 20)),
336 );
337 }
338
339 #[test]
340 fn fixed_issue4() {
341 let rect = Rect::new(3, 5, 7, 11);
342
343 assert_eq!(
344 rect.hsplit_inverse_len(2),
345 (
346 Rect::new(8, 5, 2, 11),
347 Rect::new(3, 5, 5, 11),
348 )
349 )
350 }
351}