1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
8pub struct Size {
9 pub width: u16,
11 pub height: u16,
13}
14
15impl Size {
16 #[must_use]
18 pub const fn new(width: u16, height: u16) -> Self {
19 Self { width, height }
20 }
21
22 #[must_use]
24 pub fn min(self, other: Self) -> Self {
25 Self::new(self.width.min(other.width), self.height.min(other.height))
26 }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
31pub struct Padding {
32 pub top: u16,
34 pub right: u16,
36 pub bottom: u16,
38 pub left: u16,
40}
41
42impl Padding {
43 #[must_use]
45 pub const fn all(cells: u16) -> Self {
46 Self { top: cells, right: cells, bottom: cells, left: cells }
47 }
48
49 #[must_use]
51 pub const fn symmetric(vertical: u16, horizontal: u16) -> Self {
52 Self { top: vertical, right: horizontal, bottom: vertical, left: horizontal }
53 }
54
55 #[must_use]
57 pub fn horizontal(self) -> u16 {
58 self.left.saturating_add(self.right)
59 }
60
61 #[must_use]
63 pub fn vertical(self) -> u16 {
64 self.top.saturating_add(self.bottom)
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
70pub struct Rect {
71 pub x: i32,
73 pub y: i32,
75 pub width: u16,
77 pub height: u16,
79}
80
81impl Rect {
82 #[must_use]
84 pub const fn new(x: i32, y: i32, width: u16, height: u16) -> Self {
85 Self { x, y, width, height }
86 }
87
88 #[must_use]
90 pub fn right(self) -> i32 {
91 self.x.saturating_add(i32::from(self.width))
92 }
93
94 #[must_use]
96 pub fn bottom(self) -> i32 {
97 self.y.saturating_add(i32::from(self.height))
98 }
99
100 #[must_use]
102 pub fn size(self) -> Size {
103 Size::new(self.width, self.height)
104 }
105
106 #[must_use]
108 pub fn is_empty(self) -> bool {
109 self.width == 0 || self.height == 0
110 }
111
112 #[must_use]
114 pub fn contains(self, x: i32, y: i32) -> bool {
115 x >= self.x && x < self.right() && y >= self.y && y < self.bottom()
116 }
117
118 #[must_use]
120 pub fn intersect(self, other: Self) -> Self {
121 let x = self.x.max(other.x);
122 let y = self.y.max(other.y);
123 let right = self.right().min(other.right());
124 let bottom = self.bottom().min(other.bottom());
125 if right <= x || bottom <= y {
126 return Self::new(self.x, self.y, 0, 0);
127 }
128 Self::new(x, y, clamp_u16(right - x), clamp_u16(bottom - y))
129 }
130
131 #[must_use]
133 pub fn inset(self, padding: Padding) -> Self {
134 Self::new(
135 self.x.saturating_add(i32::from(padding.left)),
136 self.y.saturating_add(i32::from(padding.top)),
137 self.width.saturating_sub(padding.horizontal()),
138 self.height.saturating_sub(padding.vertical()),
139 )
140 }
141
142 #[must_use]
144 pub fn row(self, offset: u16) -> Self {
145 Self::new(self.x, self.y.saturating_add(i32::from(offset)), self.width, u16::from(offset < self.height))
146 }
147
148 #[must_use]
150 pub fn centered(self, size: Size) -> Self {
151 let size = size.min(self.size());
152 Self::new(
153 self.x.saturating_add(i32::from((self.width - size.width) / 2)),
154 self.y.saturating_add(i32::from((self.height - size.height) / 2)),
155 size.width,
156 size.height,
157 )
158 }
159}
160
161pub(crate) fn clamp_u16(value: i32) -> u16 {
163 u16::try_from(value.max(0)).unwrap_or(u16::MAX)
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn intersection_clips_and_handles_disjoint() {
172 let a = Rect::new(0, 0, 10, 5);
173 assert_eq!(a.intersect(Rect::new(5, -2, 10, 4)), Rect::new(5, 0, 5, 2));
174 assert!(a.intersect(Rect::new(20, 20, 3, 3)).is_empty());
175 }
176
177 #[test]
178 fn inset_never_underflows() {
179 let r = Rect::new(2, 3, 4, 2).inset(Padding::symmetric(1, 3));
180 assert_eq!(r, Rect::new(5, 4, 0, 0));
181 assert_eq!(Padding::all(2).horizontal(), 4);
182 }
183
184 #[test]
185 fn contains_uses_half_open_edges() {
186 let r = Rect::new(-1, -1, 2, 2);
187 assert!(r.contains(-1, -1) && r.contains(0, 0));
188 assert!(!r.contains(1, 0));
189 }
190
191 #[test]
192 fn edges_saturate_far_from_the_origin() {
193 let far = Rect::new(i32::MAX - 1, i32::MAX - 1, 10, 10);
194 assert_eq!((far.right(), far.bottom()), (i32::MAX, i32::MAX));
195 assert_eq!(far.inset(Padding::all(4)), Rect::new(i32::MAX, i32::MAX, 2, 2));
196 assert_eq!(far.row(3).y, i32::MAX);
197 assert!(far.contains(i32::MAX - 1, i32::MAX - 1));
198 assert_eq!(far.centered(Size::new(4, 4)).x, i32::MAX);
199 }
200
201 #[test]
202 fn centered_and_rows() {
203 let r = Rect::new(0, 0, 10, 6);
204 assert_eq!(r.centered(Size::new(4, 2)), Rect::new(3, 2, 4, 2));
205 assert_eq!(r.centered(Size::new(40, 2)), Rect::new(0, 2, 10, 2));
206 assert_eq!(r.row(2), Rect::new(0, 2, 10, 1));
207 assert_eq!(r.row(9).height, 0);
208 }
209}