1#![forbid(unsafe_code)]
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
9pub struct Size {
10 pub width: u16,
12 pub height: u16,
14}
15
16impl Size {
17 pub const ZERO: Self = Self {
19 width: 0,
20 height: 0,
21 };
22
23 pub const MAX: Self = Self {
27 width: u16::MAX,
28 height: u16::MAX,
29 };
30
31 #[inline]
33 pub const fn new(width: u16, height: u16) -> Self {
34 Self { width, height }
35 }
36
37 #[inline]
39 pub const fn is_empty(&self) -> bool {
40 self.width == 0 || self.height == 0
41 }
42
43 #[inline]
45 pub const fn area(&self) -> u32 {
46 self.width as u32 * self.height as u32
47 }
48
49 #[inline]
51 pub const fn clamp_max(&self, max: Size) -> Size {
52 Size {
53 width: if self.width > max.width {
54 max.width
55 } else {
56 self.width
57 },
58 height: if self.height > max.height {
59 max.height
60 } else {
61 self.height
62 },
63 }
64 }
65
66 #[inline]
68 pub const fn clamp_min(&self, min: Size) -> Size {
69 Size {
70 width: if self.width < min.width {
71 min.width
72 } else {
73 self.width
74 },
75 height: if self.height < min.height {
76 min.height
77 } else {
78 self.height
79 },
80 }
81 }
82}
83
84impl From<(u16, u16)> for Size {
85 fn from((width, height): (u16, u16)) -> Self {
86 Self { width, height }
87 }
88}
89
90impl From<Rect> for Size {
91 fn from(rect: Rect) -> Self {
92 Self {
93 width: rect.width,
94 height: rect.height,
95 }
96 }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
103pub struct Rect {
104 pub x: u16,
106 pub y: u16,
108 pub width: u16,
110 pub height: u16,
112}
113
114impl Rect {
115 #[inline]
117 pub const fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
118 Self {
119 x,
120 y,
121 width,
122 height,
123 }
124 }
125
126 #[inline]
128 pub const fn from_size(width: u16, height: u16) -> Self {
129 Self::new(0, 0, width, height)
130 }
131
132 #[inline]
134 pub const fn left(&self) -> u16 {
135 self.x
136 }
137
138 #[inline]
140 pub const fn top(&self) -> u16 {
141 self.y
142 }
143
144 #[inline]
146 pub const fn right(&self) -> u16 {
147 self.x.saturating_add(self.width)
148 }
149
150 #[inline]
152 pub const fn bottom(&self) -> u16 {
153 self.y.saturating_add(self.height)
154 }
155
156 #[inline]
158 pub const fn area(&self) -> u32 {
159 self.width as u32 * self.height as u32
160 }
161
162 #[inline]
164 pub const fn is_empty(&self) -> bool {
165 self.width == 0 || self.height == 0
166 }
167
168 #[inline]
170 pub const fn contains(&self, x: u16, y: u16) -> bool {
171 x >= self.x && x < self.right() && y >= self.y && y < self.bottom()
172 }
173
174 #[inline]
178 pub fn intersection(&self, other: &Rect) -> Rect {
179 self.intersection_opt(other).unwrap_or_default()
180 }
181
182 #[inline]
184 pub fn inner(&self, margin: Sides) -> Rect {
185 let x = self.x.saturating_add(margin.left);
186 let y = self.y.saturating_add(margin.top);
187 let width = self
188 .width
189 .saturating_sub(margin.left)
190 .saturating_sub(margin.right);
191 let height = self
192 .height
193 .saturating_sub(margin.top)
194 .saturating_sub(margin.bottom);
195
196 Rect {
197 x,
198 y,
199 width,
200 height,
201 }
202 }
203
204 #[inline]
214 pub fn union(&self, other: &Rect) -> Rect {
215 let x = self.x.min(other.x);
216 let y = self.y.min(other.y);
217 let right = self.right().max(other.right());
218 let bottom = self.bottom().max(other.bottom());
219
220 Rect {
221 x,
222 y,
223 width: right.saturating_sub(x),
224 height: bottom.saturating_sub(y),
225 }
226 }
227
228 #[inline]
230 pub fn intersection_opt(&self, other: &Rect) -> Option<Rect> {
231 let x = self.x.max(other.x);
232 let y = self.y.max(other.y);
233 let right = self.right().min(other.right());
234 let bottom = self.bottom().min(other.bottom());
235
236 if x < right && y < bottom {
237 Some(Rect::new(
238 x,
239 y,
240 right.saturating_sub(x),
241 bottom.saturating_sub(y),
242 ))
243 } else {
244 None
245 }
246 }
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
251pub struct Sides {
252 pub top: u16,
254 pub right: u16,
256 pub bottom: u16,
258 pub left: u16,
260}
261
262impl Sides {
263 pub const fn all(val: u16) -> Self {
265 Self {
266 top: val,
267 right: val,
268 bottom: val,
269 left: val,
270 }
271 }
272
273 pub const fn horizontal(val: u16) -> Self {
275 Self {
276 top: 0,
277 right: val,
278 bottom: 0,
279 left: val,
280 }
281 }
282
283 pub const fn vertical(val: u16) -> Self {
285 Self {
286 top: val,
287 right: 0,
288 bottom: val,
289 left: 0,
290 }
291 }
292
293 pub const fn new(top: u16, right: u16, bottom: u16, left: u16) -> Self {
295 Self {
296 top,
297 right,
298 bottom,
299 left,
300 }
301 }
302
303 #[inline]
305 pub const fn horizontal_sum(&self) -> u16 {
306 self.left.saturating_add(self.right)
307 }
308
309 #[inline]
311 pub const fn vertical_sum(&self) -> u16 {
312 self.top.saturating_add(self.bottom)
313 }
314}
315
316impl From<u16> for Sides {
317 fn from(val: u16) -> Self {
318 Self::all(val)
319 }
320}
321
322impl From<(u16, u16)> for Sides {
323 fn from((vertical, horizontal): (u16, u16)) -> Self {
324 Self {
325 top: vertical,
326 right: horizontal,
327 bottom: vertical,
328 left: horizontal,
329 }
330 }
331}
332
333impl From<(u16, u16, u16, u16)> for Sides {
334 fn from((top, right, bottom, left): (u16, u16, u16, u16)) -> Self {
335 Self {
336 top,
337 right,
338 bottom,
339 left,
340 }
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::{Rect, Sides, Size};
347
348 #[test]
349 fn rect_contains_edges() {
350 let rect = Rect::new(2, 3, 4, 5);
351 assert!(rect.contains(2, 3));
352 assert!(rect.contains(5, 7));
353 assert!(!rect.contains(6, 3));
354 assert!(!rect.contains(2, 8));
355 }
356
357 #[test]
358 fn rect_intersection_overlaps() {
359 let a = Rect::new(0, 0, 4, 4);
360 let b = Rect::new(2, 2, 4, 4);
361 assert_eq!(a.intersection(&b), Rect::new(2, 2, 2, 2));
362 }
363
364 #[test]
365 fn rect_intersection_no_overlap_is_empty() {
366 let a = Rect::new(0, 0, 2, 2);
367 let b = Rect::new(3, 3, 2, 2);
368 assert_eq!(a.intersection(&b), Rect::default());
369 }
370
371 #[test]
372 fn rect_inner_reduces() {
373 let rect = Rect::new(0, 0, 10, 10);
374 let inner = rect.inner(Sides {
375 top: 1,
376 right: 2,
377 bottom: 3,
378 left: 4,
379 });
380 assert_eq!(inner, Rect::new(4, 1, 4, 6));
381 }
382
383 #[test]
384 fn sides_constructors_and_conversions() {
385 assert_eq!(Sides::all(3), Sides::from(3));
386 assert_eq!(
387 Sides::horizontal(2),
388 Sides {
389 top: 0,
390 right: 2,
391 bottom: 0,
392 left: 2,
393 }
394 );
395 assert_eq!(
396 Sides::vertical(4),
397 Sides {
398 top: 4,
399 right: 0,
400 bottom: 4,
401 left: 0,
402 }
403 );
404 assert_eq!(
405 Sides::from((1, 2)),
406 Sides {
407 top: 1,
408 right: 2,
409 bottom: 1,
410 left: 2,
411 }
412 );
413 assert_eq!(
414 Sides::from((1, 2, 3, 4)),
415 Sides {
416 top: 1,
417 right: 2,
418 bottom: 3,
419 left: 4,
420 }
421 );
422 }
423
424 #[test]
425 fn sides_sums() {
426 let sides = Sides {
427 top: 1,
428 right: 2,
429 bottom: 3,
430 left: 4,
431 };
432 assert_eq!(sides.horizontal_sum(), 6);
433 assert_eq!(sides.vertical_sum(), 4);
434 }
435
436 #[test]
439 fn rect_new_and_default() {
440 let r = Rect::new(5, 10, 20, 15);
441 assert_eq!(r.x, 5);
442 assert_eq!(r.y, 10);
443 assert_eq!(r.width, 20);
444 assert_eq!(r.height, 15);
445
446 let d = Rect::default();
447 assert_eq!(d, Rect::new(0, 0, 0, 0));
448 }
449
450 #[test]
451 fn rect_from_size() {
452 let r = Rect::from_size(80, 24);
453 assert_eq!(r.x, 0);
454 assert_eq!(r.y, 0);
455 assert_eq!(r.width, 80);
456 assert_eq!(r.height, 24);
457 }
458
459 #[test]
462 fn rect_left_top_right_bottom() {
463 let r = Rect::new(10, 20, 30, 40);
464 assert_eq!(r.left(), 10);
465 assert_eq!(r.top(), 20);
466 assert_eq!(r.right(), 40);
467 assert_eq!(r.bottom(), 60);
468 }
469
470 #[test]
471 fn rect_right_bottom_saturating() {
472 let r = Rect::new(u16::MAX - 5, u16::MAX - 3, 100, 100);
474 assert_eq!(r.right(), u16::MAX);
475 assert_eq!(r.bottom(), u16::MAX);
476 }
477
478 #[test]
481 fn rect_area() {
482 assert_eq!(Rect::new(0, 0, 10, 20).area(), 200);
483 assert_eq!(Rect::new(5, 5, 0, 10).area(), 0);
484 assert_eq!(Rect::new(0, 0, 1, 1).area(), 1);
485 }
486
487 #[test]
488 fn rect_is_empty() {
489 assert!(Rect::new(0, 0, 0, 0).is_empty());
490 assert!(Rect::new(5, 5, 0, 10).is_empty());
491 assert!(Rect::new(5, 5, 10, 0).is_empty());
492 assert!(!Rect::new(0, 0, 1, 1).is_empty());
493 }
494
495 #[test]
498 fn rect_contains_boundary_conditions() {
499 let r = Rect::new(0, 0, 5, 5);
500 assert!(r.contains(0, 0));
502 assert!(r.contains(4, 4));
504 assert!(!r.contains(5, 0));
506 assert!(!r.contains(0, 5));
508 }
509
510 #[test]
511 fn rect_contains_empty_rect() {
512 let r = Rect::new(5, 5, 0, 0);
513 assert!(!r.contains(5, 5));
515 }
516
517 #[test]
520 fn rect_union_basic() {
521 let a = Rect::new(0, 0, 5, 5);
522 let b = Rect::new(3, 3, 5, 5);
523 let u = a.union(&b);
524 assert_eq!(u, Rect::new(0, 0, 8, 8));
525 }
526
527 #[test]
528 fn rect_union_disjoint() {
529 let a = Rect::new(0, 0, 2, 2);
530 let b = Rect::new(10, 10, 3, 3);
531 let u = a.union(&b);
532 assert_eq!(u, Rect::new(0, 0, 13, 13));
533 }
534
535 #[test]
536 fn rect_union_contained() {
537 let outer = Rect::new(0, 0, 10, 10);
538 let inner = Rect::new(2, 2, 3, 3);
539 assert_eq!(outer.union(&inner), outer);
540 assert_eq!(inner.union(&outer), outer);
541 }
542
543 #[test]
544 fn rect_union_self() {
545 let r = Rect::new(5, 10, 20, 15);
546 assert_eq!(r.union(&r), r);
547 }
548
549 #[test]
552 fn rect_intersection_self() {
553 let r = Rect::new(5, 5, 10, 10);
554 assert_eq!(r.intersection(&r), r);
555 }
556
557 #[test]
558 fn rect_intersection_contained() {
559 let outer = Rect::new(0, 0, 20, 20);
560 let inner = Rect::new(5, 5, 5, 5);
561 assert_eq!(outer.intersection(&inner), inner);
562 assert_eq!(inner.intersection(&outer), inner);
563 }
564
565 #[test]
566 fn rect_intersection_adjacent_no_overlap() {
567 let a = Rect::new(0, 0, 5, 5);
569 let b = Rect::new(5, 0, 5, 5);
570 assert!(a.intersection(&b).is_empty());
571 }
572
573 #[test]
574 fn rect_intersection_opt_returns_none_for_no_overlap() {
575 let a = Rect::new(0, 0, 2, 2);
576 let b = Rect::new(5, 5, 2, 2);
577 assert_eq!(a.intersection_opt(&b), None);
578 }
579
580 #[test]
581 fn rect_intersection_opt_returns_some_for_overlap() {
582 let a = Rect::new(0, 0, 5, 5);
583 let b = Rect::new(3, 3, 5, 5);
584 assert_eq!(a.intersection_opt(&b), Some(Rect::new(3, 3, 2, 2)));
585 }
586
587 #[test]
590 fn rect_inner_large_margin_clamps_to_zero() {
591 let r = Rect::new(0, 0, 10, 10);
592 let inner = r.inner(Sides::all(20));
593 assert_eq!(inner.width, 0);
595 assert_eq!(inner.height, 0);
596 }
597
598 #[test]
599 fn rect_inner_zero_margin() {
600 let r = Rect::new(5, 10, 20, 30);
601 let inner = r.inner(Sides::all(0));
602 assert_eq!(inner, r);
603 }
604
605 #[test]
606 fn rect_inner_asymmetric_margin() {
607 let r = Rect::new(0, 0, 20, 20);
608 let inner = r.inner(Sides::new(2, 3, 4, 5));
609 assert_eq!(inner.x, 5);
610 assert_eq!(inner.y, 2);
611 assert_eq!(inner.width, 12); assert_eq!(inner.height, 14); }
614
615 #[test]
618 fn sides_new_explicit() {
619 let s = Sides::new(1, 2, 3, 4);
620 assert_eq!(s.top, 1);
621 assert_eq!(s.right, 2);
622 assert_eq!(s.bottom, 3);
623 assert_eq!(s.left, 4);
624 }
625
626 #[test]
627 fn sides_default_is_zero() {
628 let s = Sides::default();
629 assert_eq!(s, Sides::new(0, 0, 0, 0));
630 }
631
632 #[test]
633 fn sides_sums_saturating() {
634 let s = Sides::new(u16::MAX, 0, u16::MAX, 0);
635 assert_eq!(s.vertical_sum(), u16::MAX);
636 }
637
638 #[test]
641 fn size_new_and_constants() {
642 let s = Size::new(80, 24);
643 assert_eq!(s.width, 80);
644 assert_eq!(s.height, 24);
645
646 assert_eq!(Size::ZERO, Size::new(0, 0));
647 assert_eq!(Size::MAX, Size::new(u16::MAX, u16::MAX));
648 }
649
650 #[test]
651 fn size_default_is_zero() {
652 assert_eq!(Size::default(), Size::ZERO);
653 }
654
655 #[test]
656 fn size_is_empty() {
657 assert!(Size::ZERO.is_empty());
658 assert!(Size::new(0, 10).is_empty());
659 assert!(Size::new(10, 0).is_empty());
660 assert!(!Size::new(1, 1).is_empty());
661 }
662
663 #[test]
664 fn size_area() {
665 assert_eq!(Size::new(10, 20).area(), 200);
666 assert_eq!(Size::ZERO.area(), 0);
667 assert_eq!(Size::new(1, 1).area(), 1);
668 }
669
670 #[test]
671 fn size_clamp_max() {
672 let s = Size::new(100, 50);
673 assert_eq!(s.clamp_max(Size::new(80, 40)), Size::new(80, 40));
674 assert_eq!(s.clamp_max(Size::new(200, 200)), s);
675 assert_eq!(s.clamp_max(Size::new(80, 100)), Size::new(80, 50));
676 }
677
678 #[test]
679 fn size_clamp_min() {
680 let s = Size::new(10, 5);
681 assert_eq!(s.clamp_min(Size::new(20, 10)), Size::new(20, 10));
682 assert_eq!(s.clamp_min(Size::new(5, 3)), s);
683 assert_eq!(s.clamp_min(Size::new(15, 3)), Size::new(15, 5));
684 }
685
686 #[test]
687 fn size_from_tuple() {
688 let s: Size = (80, 24).into();
689 assert_eq!(s, Size::new(80, 24));
690 }
691
692 #[test]
693 fn size_from_rect() {
694 let r = Rect::new(5, 10, 80, 24);
695 let s: Size = r.into();
696 assert_eq!(s, Size::new(80, 24));
697 }
698}