1use std::{
2 cmp::{
3 max,
4 min,
5 },
6 fmt,
7};
8
9use super::{
10 Margin,
11 Offset,
12 Position,
13 Size,
14};
15
16#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
18pub struct Rect {
19 pub x: u16,
20 pub y: u16,
21 pub width: u16,
22 pub height: u16,
23}
24
25impl Rect {
26 pub const MAX: Self = Self::new(0, 0, u16::MAX, u16::MAX);
27 pub const MIN: Self = Self::ZERO;
28 pub const ZERO: Self = Self::new(0, 0, 0, 0);
29
30 pub const fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
31 let width = x.saturating_add(width).saturating_sub(x);
32 let height = y.saturating_add(height).saturating_sub(y);
33 Self {
34 x,
35 y,
36 width,
37 height,
38 }
39 }
40
41 pub const fn area(self) -> u32 {
42 self.width as u32 * self.height as u32
43 }
44
45 pub const fn is_empty(self) -> bool {
46 self.width == 0 || self.height == 0
47 }
48
49 pub const fn left(self) -> u16 {
50 self.x
51 }
52
53 pub const fn right(self) -> u16 {
54 self.x.saturating_add(self.width)
55 }
56
57 pub const fn top(self) -> u16 {
58 self.y
59 }
60
61 pub const fn bottom(self) -> u16 {
62 self.y.saturating_add(self.height)
63 }
64
65 pub const fn row(self) -> u16 {
66 self.y
67 }
68
69 pub const fn col(self) -> u16 {
70 self.x
71 }
72
73 pub const fn inner(self, margin: Margin) -> Self {
74 let doubled_h = margin.horizontal.saturating_mul(2);
75 let doubled_v = margin.vertical.saturating_mul(2);
76 if self.width < doubled_h || self.height < doubled_v {
77 Self::ZERO
78 } else {
79 Self {
80 x: self.x.saturating_add(margin.horizontal),
81 y: self.y.saturating_add(margin.vertical),
82 width: self.width.saturating_sub(doubled_h),
83 height: self.height.saturating_sub(doubled_v),
84 }
85 }
86 }
87
88 pub const fn outer(self, margin: Margin) -> Self {
89 let x = self.x.saturating_sub(margin.horizontal);
90 let y = self.y.saturating_sub(margin.vertical);
91 let width = self
92 .right()
93 .saturating_add(margin.horizontal)
94 .saturating_sub(x);
95 let height = self
96 .bottom()
97 .saturating_add(margin.vertical)
98 .saturating_sub(y);
99 Self {
100 x,
101 y,
102 width,
103 height,
104 }
105 }
106
107 pub fn offset(self, offset: Offset) -> Self {
108 self + offset
109 }
110
111 pub const fn resize(self, size: Size) -> Self {
112 Self {
113 width: self.x.saturating_add(size.width).saturating_sub(self.x),
114 height: self.y.saturating_add(size.height).saturating_sub(self.y),
115 ..self
116 }
117 }
118
119 pub fn union(self, other: Self) -> Self {
120 let x1 = min(self.x, other.x);
121 let y1 = min(self.y, other.y);
122 let x2 = max(self.right(), other.right());
123 let y2 = max(self.bottom(), other.bottom());
124 Self {
125 x: x1,
126 y: y1,
127 width: x2.saturating_sub(x1),
128 height: y2.saturating_sub(y1),
129 }
130 }
131
132 pub fn intersection(self, other: Self) -> Self {
133 let x1 = max(self.x, other.x);
134 let y1 = max(self.y, other.y);
135 let x2 = min(self.right(), other.right());
136 let y2 = min(self.bottom(), other.bottom());
137 Self {
138 x: x1,
139 y: y1,
140 width: x2.saturating_sub(x1),
141 height: y2.saturating_sub(y1),
142 }
143 }
144
145 pub const fn intersects(self, other: Self) -> bool {
146 self.x < other.right() &&
147 self.right() > other.x &&
148 self.y < other.bottom() &&
149 self.bottom() > other.y
150 }
151
152 pub const fn contains(self, position: Position) -> bool {
153 position.x >= self.x &&
154 position.x < self.right() &&
155 position.y >= self.y &&
156 position.y < self.bottom()
157 }
158
159 pub fn clamp(self, other: Self) -> Self {
160 let width = self.width.min(other.width);
161 let height = self.height.min(other.height);
162 let x = self.x.clamp(other.x, other.right().saturating_sub(width));
163 let y = self.y.clamp(other.y, other.bottom().saturating_sub(height));
164 Self::new(x, y, width, height)
165 }
166
167 pub const fn rows(self) -> Rows {
168 Rows::new(self)
169 }
170
171 pub const fn columns(self) -> Columns {
172 Columns::new(self)
173 }
174
175 pub const fn positions(self) -> Positions {
176 Positions::new(self)
177 }
178
179 pub const fn as_position(self) -> Position {
180 Position {
181 x: self.x,
182 y: self.y,
183 }
184 }
185
186 pub const fn as_size(self) -> Size {
187 Size {
188 width: self.width,
189 height: self.height,
190 }
191 }
192}
193
194impl fmt::Display for Rect {
195 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196 write!(f, "{}x{}+{}+{}", self.width, self.height, self.x, self.y)
197 }
198}
199
200impl From<(Position, Size)> for Rect {
201 fn from((position, size): (Position, Size)) -> Self {
202 Self {
203 x: position.x,
204 y: position.y,
205 width: size.width,
206 height: size.height,
207 }
208 }
209}
210
211impl From<Size> for Rect {
212 fn from(size: Size) -> Self {
213 Self {
214 x: 0,
215 y: 0,
216 width: size.width,
217 height: size.height,
218 }
219 }
220}
221
222impl std::ops::Add<Offset> for Rect {
223 type Output = Self;
224
225 fn add(self, offset: Offset) -> Self::Output {
226 let max = i32::from(u16::MAX);
227 let x = i32::from(self.x)
228 .saturating_add(i32::from(offset.x))
229 .clamp(0, max) as u16;
230 let y = i32::from(self.y)
231 .saturating_add(i32::from(offset.y))
232 .clamp(0, max) as u16;
233 Self { x, y, ..self }
234 }
235}
236
237impl std::ops::Sub<Offset> for Rect {
238 type Output = Self;
239
240 fn sub(self, offset: Offset) -> Self::Output {
241 let max = i32::from(u16::MAX);
242 let x = i32::from(self.x)
243 .saturating_sub(i32::from(offset.x))
244 .clamp(0, max) as u16;
245 let y = i32::from(self.y)
246 .saturating_sub(i32::from(offset.y))
247 .clamp(0, max) as u16;
248 Self { x, y, ..self }
249 }
250}
251
252#[derive(Debug, Clone)]
254pub struct Rows {
255 rect: Rect,
256 current: u16,
257}
258
259impl Rows {
260 pub const fn new(rect: Rect) -> Self {
261 Self { rect, current: 0 }
262 }
263}
264
265impl Iterator for Rows {
266 type Item = Rect;
267
268 fn next(&mut self) -> Option<Self::Item> {
269 if self.current >= self.rect.height {
270 return None;
271 }
272 let row = Rect {
273 x: self.rect.x,
274 y: self.rect.y + self.current,
275 width: self.rect.width,
276 height: 1,
277 };
278 self.current += 1;
279 Some(row)
280 }
281}
282
283#[derive(Debug, Clone)]
285pub struct Columns {
286 rect: Rect,
287 current: u16,
288}
289
290impl Columns {
291 pub const fn new(rect: Rect) -> Self {
292 Self { rect, current: 0 }
293 }
294}
295
296impl Iterator for Columns {
297 type Item = Rect;
298
299 fn next(&mut self) -> Option<Self::Item> {
300 if self.current >= self.rect.width {
301 return None;
302 }
303 let col = Rect {
304 x: self.rect.x + self.current,
305 y: self.rect.y,
306 width: 1,
307 height: self.rect.height,
308 };
309 self.current += 1;
310 Some(col)
311 }
312}
313
314#[derive(Debug, Clone)]
316pub struct Positions {
317 rect: Rect,
318 current: u16,
319}
320
321impl Positions {
322 pub const fn new(rect: Rect) -> Self {
323 Self { rect, current: 0 }
324 }
325}
326
327impl Iterator for Positions {
328 type Item = Position;
329
330 fn next(&mut self) -> Option<Self::Item> {
331 let area = self.rect.area();
332 if self.current as u32 >= area {
333 return None;
334 }
335 let x = self.rect.x + (self.current % self.rect.width);
336 let y = self.rect.y + (self.current / self.rect.width);
337 self.current += 1;
338 Some(Position { x, y })
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 #[test]
347 fn rect_new() {
348 let r = Rect::new(1, 2, 3, 4);
349 assert_eq!(r.x, 1);
350 assert_eq!(r.y, 2);
351 assert_eq!(r.width, 3);
352 assert_eq!(r.height, 4);
353 }
354
355 #[test]
356 fn rect_new_clamps_overflow() {
357 let r = Rect::new(u16::MAX - 5, u16::MAX - 3, 100, 100);
358 assert_eq!(r.width, 5);
359 assert_eq!(r.height, 3);
360 }
361
362 #[test]
363 fn rect_area() {
364 assert_eq!(Rect::new(0, 0, 3, 4).area(), 12);
365 assert_eq!(Rect::ZERO.area(), 0);
366 }
367
368 #[test]
369 fn rect_is_empty() {
370 assert!(Rect::new(0, 0, 0, 5).is_empty());
371 assert!(Rect::new(0, 0, 5, 0).is_empty());
372 assert!(!Rect::new(0, 0, 1, 1).is_empty());
373 }
374
375 #[test]
376 fn rect_edges() {
377 let r = Rect::new(1, 2, 3, 4);
378 assert_eq!(r.left(), 1);
379 assert_eq!(r.right(), 4);
380 assert_eq!(r.top(), 2);
381 assert_eq!(r.bottom(), 6);
382 }
383
384 #[test]
385 fn rect_row_col_compat() {
386 let r = Rect::new(5, 10, 1, 1);
387 assert_eq!(r.row(), 10);
388 assert_eq!(r.col(), 5);
389 }
390
391 #[test]
392 fn rect_inner() {
393 let r = Rect::new(0, 0, 10, 10).inner(Margin::new(2, 3));
394 assert_eq!(r, Rect::new(2, 3, 6, 4));
395 }
396
397 #[test]
398 fn rect_inner_zero_when_margin_too_large() {
399 let r = Rect::new(0, 0, 3, 3).inner(Margin::new(2, 2));
400 assert_eq!(r, Rect::ZERO);
401 }
402
403 #[test]
404 fn rect_outer() {
405 let r = Rect::new(10, 20, 5, 5).outer(Margin::new(2, 3));
406 assert_eq!(r, Rect::new(8, 17, 9, 11));
407 }
408
409 #[test]
410 fn rect_outer_saturates() {
411 let r = Rect::new(0, 0, 5, 5).outer(Margin::new(10, 10));
412 assert_eq!(r.x, 0);
413 assert_eq!(r.y, 0);
414 }
415
416 #[test]
417 fn rect_offset() {
418 let r = Rect::new(5, 5, 10, 10).offset(Offset::new(3, -2));
419 assert_eq!(r, Rect::new(8, 3, 10, 10));
420 }
421
422 #[test]
423 fn rect_offset_clamps() {
424 let r = Rect::new(0, 0, 1, 1).offset(Offset::new(-5, -5));
425 assert_eq!(r, Rect::new(0, 0, 1, 1));
426 }
427
428 #[test]
429 fn rect_resize() {
430 let r = Rect::new(1, 1, 5, 5).resize(Size::new(3, 3));
431 assert_eq!(r, Rect::new(1, 1, 3, 3));
432 }
433
434 #[test]
435 fn rect_resize_clamps() {
436 let r = Rect::new(u16::MAX - 2, u16::MAX - 1, 1, 1).resize(Size::new(10, 10));
437 assert_eq!(r.width, 2);
438 assert_eq!(r.height, 1);
439 }
440
441 #[test]
442 fn rect_union() {
443 let a = Rect::new(0, 0, 5, 5);
444 let b = Rect::new(3, 3, 5, 5);
445 assert_eq!(a.union(b), Rect::new(0, 0, 8, 8));
446 }
447
448 #[test]
449 fn rect_intersection() {
450 let a = Rect::new(0, 0, 5, 5);
451 let b = Rect::new(3, 3, 5, 5);
452 assert_eq!(a.intersection(b), Rect::new(3, 3, 2, 2));
453 }
454
455 #[test]
456 fn rect_intersection_no_overlap() {
457 let a = Rect::new(0, 0, 2, 2);
458 let b = Rect::new(5, 5, 2, 2);
459 assert_eq!(a.intersection(b), Rect::new(5, 5, 0, 0));
460 }
461
462 #[test]
463 fn rect_intersects() {
464 assert!(Rect::new(0, 0, 5, 5).intersects(Rect::new(3, 3, 5, 5)));
465 assert!(!Rect::new(0, 0, 2, 2).intersects(Rect::new(5, 5, 2, 2)));
466 }
467
468 #[test]
469 fn rect_contains() {
470 let r = Rect::new(1, 1, 3, 3);
471 assert!(r.contains(Position::new(1, 1)));
472 assert!(r.contains(Position::new(3, 3)));
473 assert!(!r.contains(Position::new(0, 1)));
474 assert!(!r.contains(Position::new(4, 4)));
475 }
476
477 #[test]
478 fn rect_clamp() {
479 let area = Rect::new(0, 0, 100, 100);
480 let r = Rect::new(80, 80, 30, 30).clamp(area);
481 assert_eq!(r, Rect::new(70, 70, 30, 30));
482 }
483
484 #[test]
485 fn rect_rows() {
486 let rows: Vec<_> = Rect::new(0, 0, 3, 2).rows().collect();
487 assert_eq!(rows, vec![Rect::new(0, 0, 3, 1), Rect::new(0, 1, 3, 1)]);
488 }
489
490 #[test]
491 fn rect_columns() {
492 let cols: Vec<_> = Rect::new(0, 0, 2, 3).columns().collect();
493 assert_eq!(cols, vec![Rect::new(0, 0, 1, 3), Rect::new(1, 0, 1, 3)]);
494 }
495
496 #[test]
497 fn rect_positions() {
498 let positions: Vec<_> = Rect::new(1, 1, 2, 2).positions().collect();
499 assert_eq!(
500 positions,
501 vec![
502 Position::new(1, 1),
503 Position::new(2, 1),
504 Position::new(1, 2),
505 Position::new(2, 2),
506 ]
507 );
508 }
509
510 #[test]
511 fn rect_as_position() {
512 assert_eq!(Rect::new(5, 10, 1, 1).as_position(), Position::new(5, 10));
513 }
514
515 #[test]
516 fn rect_as_size() {
517 assert_eq!(Rect::new(0, 0, 5, 7).as_size(), Size::new(5, 7));
518 }
519
520 #[test]
521 fn rect_display() {
522 assert_eq!(Rect::new(1, 2, 3, 4).to_string(), "3x4+1+2");
523 }
524
525 #[test]
526 fn rect_from_position_and_size() {
527 let p = Position::new(1, 2);
528 let s = Size::new(3, 4);
529 let r: Rect = (p, s).into();
530 assert_eq!(r, Rect::new(1, 2, 3, 4));
531 }
532
533 #[test]
534 fn rect_from_size() {
535 let r: Rect = Size::new(5, 7).into();
536 assert_eq!(r, Rect::new(0, 0, 5, 7));
537 }
538
539 #[test]
540 fn rect_add_offset() {
541 let r = Rect::new(1, 2, 3, 4) + Offset::new(5, -1);
542 assert_eq!(r, Rect::new(6, 1, 3, 4));
543 }
544
545 #[test]
546 fn rect_sub_offset() {
547 let r = Rect::new(5, 5, 3, 4) - Offset::new(2, 3);
548 assert_eq!(r, Rect::new(3, 2, 3, 4));
549 }
550}