Skip to main content

flywheel/layout/
rect.rs

1//! Rect: A rectangle primitive for layout calculations.
2
3/// A rectangle defined by position and size.
4#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
5pub struct Rect {
6    /// X coordinate (column) of the top-left corner.
7    pub x: u16,
8    /// Y coordinate (row) of the top-left corner.
9    pub y: u16,
10    /// Width in columns.
11    pub width: u16,
12    /// Height in rows.
13    pub height: u16,
14}
15
16impl Rect {
17    /// Create a new rectangle.
18    #[inline]
19    pub const fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
20        Self { x, y, width, height }
21    }
22
23    /// Create a rectangle from a terminal size (full screen).
24    #[inline]
25    pub const fn from_size(width: u16, height: u16) -> Self {
26        Self::new(0, 0, width, height)
27    }
28
29    /// Zero-sized rectangle.
30    pub const ZERO: Self = Self::new(0, 0, 0, 0);
31
32    /// Get the area (number of cells).
33    #[inline]
34    pub const fn area(&self) -> u32 {
35        (self.width as u32) * (self.height as u32)
36    }
37
38    /// Check if the rectangle is empty.
39    #[inline]
40    pub const fn is_empty(&self) -> bool {
41        self.width == 0 || self.height == 0
42    }
43
44    /// Get the right edge (exclusive).
45    #[inline]
46    pub const fn right(&self) -> u16 {
47        self.x.saturating_add(self.width)
48    }
49
50    /// Get the bottom edge (exclusive).
51    #[inline]
52    pub const fn bottom(&self) -> u16 {
53        self.y.saturating_add(self.height)
54    }
55
56    /// Check if a point is inside the rectangle.
57    #[inline]
58    pub const fn contains(&self, x: u16, y: u16) -> bool {
59        x >= self.x && x < self.right() && y >= self.y && y < self.bottom()
60    }
61
62    /// Check if this rectangle intersects with another.
63    #[inline]
64    pub const fn intersects(&self, other: &Self) -> bool {
65        self.x < other.right()
66            && self.right() > other.x
67            && self.y < other.bottom()
68            && self.bottom() > other.y
69    }
70
71    /// Shrink the rectangle by a margin on all sides.
72    #[inline]
73    #[must_use]
74    pub const fn shrink(&self, margin: u16) -> Self {
75        let m2 = margin * 2;
76        if self.width <= m2 || self.height <= m2 {
77            return Self::ZERO;
78        }
79        Self::new(self.x + margin, self.y + margin, self.width - m2, self.height - m2)
80    }
81
82    /// Split horizontally at a given column offset.
83    pub fn split_horizontal(&self, at: u16) -> (Self, Self) {
84        let at = at.min(self.width);
85        (
86            Self::new(self.x, self.y, at, self.height),
87            Self::new(self.x + at, self.y, self.width - at, self.height),
88        )
89    }
90
91    /// Split vertically at a given row offset.
92    pub fn split_vertical(&self, at: u16) -> (Self, Self) {
93        let at = at.min(self.height);
94        (
95            Self::new(self.x, self.y, self.width, at),
96            Self::new(self.x, self.y + at, self.width, self.height - at),
97        )
98    }
99}
100
101impl std::fmt::Debug for Rect {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        write!(f, "Rect({}, {} {}x{})", self.x, self.y, self.width, self.height)
104    }
105}