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