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