1use crate::kurbo::Rect;
7use bytemuck::{Pod, Zeroable};
8use core::ops::Add;
9
10#[repr(C)]
12#[derive(Copy, Clone, Debug, Pod, Zeroable, PartialEq, Eq)]
13pub struct SizeU16(pub [u16; 2]);
14
15impl SizeU16 {
16 pub const ZERO: Self = Self::new(0);
18
19 pub const fn new(size: u16) -> Self {
21 Self([size; 2])
22 }
23
24 pub const fn from_wh(width: u16, height: u16) -> Self {
26 Self([width, height])
27 }
28
29 pub const fn width(self) -> u16 {
31 self.0[0]
32 }
33
34 pub const fn height(self) -> u16 {
36 self.0[1]
37 }
38
39 pub fn max(self, other: Self) -> Self {
41 Self::from_wh(
42 self.width().max(other.width()),
43 self.height().max(other.height()),
44 )
45 }
46
47 pub fn min(self, other: Self) -> Self {
49 Self::from_wh(
50 self.width().min(other.width()),
51 self.height().min(other.height()),
52 )
53 }
54
55 pub fn clamp(self, min: u16, max: u16) -> Self {
57 Self::from_wh(self.width().clamp(min, max), self.height().clamp(min, max))
58 }
59
60 pub fn checked_add(self, value: u16) -> Option<Self> {
62 Some(Self::from_wh(
63 self.width().checked_add(value)?,
64 self.height().checked_add(value)?,
65 ))
66 }
67}
68
69impl From<[u16; 2]> for SizeU16 {
70 fn from(value: [u16; 2]) -> Self {
71 Self(value)
72 }
73}
74
75impl From<(u16, u16)> for SizeU16 {
76 fn from((width, height): (u16, u16)) -> Self {
77 Self::from_wh(width, height)
78 }
79}
80
81impl From<SizeU16> for (u16, u16) {
82 fn from(size: SizeU16) -> Self {
83 (size.width(), size.height())
84 }
85}
86
87impl Add for SizeU16 {
88 type Output = Self;
89
90 fn add(self, rhs: Self) -> Self::Output {
91 Self::from_wh(
93 self.width().checked_add(rhs.width()).unwrap(),
94 self.height().checked_add(rhs.height()).unwrap(),
95 )
96 }
97}
98
99impl Add<u16> for SizeU16 {
100 type Output = Self;
101
102 fn add(self, rhs: u16) -> Self::Output {
103 self + Self::new(rhs)
104 }
105}
106
107#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
109pub struct PaddingU16 {
110 pub left: u16,
112 pub top: u16,
114 pub right: u16,
116 pub bottom: u16,
118}
119
120impl PaddingU16 {
121 pub const ZERO: Self = Self::new(0, 0, 0, 0);
123
124 pub const fn new(left: u16, top: u16, right: u16, bottom: u16) -> Self {
126 Self {
127 left,
128 top,
129 right,
130 bottom,
131 }
132 }
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
141pub struct RectU16 {
142 pub x0: u16,
144 pub y0: u16,
146 pub x1: u16,
148 pub y1: u16,
150}
151
152impl RectU16 {
153 pub const ZERO: Self = Self {
155 x0: 0,
156 y0: 0,
157 x1: 0,
158 y1: 0,
159 };
160
161 pub const INVERTED: Self = Self {
166 x0: u16::MAX,
167 y0: u16::MAX,
168 x1: 0,
169 y1: 0,
170 };
171
172 #[inline(always)]
174 pub const fn new(x0: u16, y0: u16, x1: u16, y1: u16) -> Self {
175 Self { x0, y0, x1, y1 }
176 }
177
178 #[inline(always)]
180 pub const fn width(self) -> u16 {
181 self.x1.saturating_sub(self.x0)
182 }
183
184 #[inline(always)]
186 pub const fn height(self) -> u16 {
187 self.y1.saturating_sub(self.y0)
188 }
189
190 #[inline(always)]
192 pub const fn is_empty(self) -> bool {
193 self.x0 >= self.x1 || self.y0 >= self.y1
194 }
195
196 #[inline(always)]
200 pub const fn contains(self, x: u16, y: u16) -> bool {
201 (x >= self.x0) & (x < self.x1) & (y >= self.y0) & (y < self.y1)
202 }
203
204 #[inline(always)]
208 pub const fn intersect(self, other: Self) -> Self {
209 let x0 = const_max(self.x0, other.x0);
210 let y0 = const_max(self.y0, other.y0);
211 let x1 = const_min(self.x1, other.x1);
212 let y1 = const_min(self.y1, other.y1);
213
214 Self::new(x0, y0, const_max(x1, x0), const_max(y1, y0))
215 }
216
217 #[inline(always)]
219 pub const fn expand(self, padding: PaddingU16) -> Self {
220 Self {
221 x0: self.x0.saturating_sub(padding.left),
222 y0: self.y0.saturating_sub(padding.top),
223 x1: self.x1.saturating_add(padding.right),
224 y1: self.y1.saturating_add(padding.bottom),
225 }
226 }
227
228 #[inline(always)]
230 pub fn relative_to_origin(self, origin: (u16, u16)) -> Self {
231 self.shift((-(origin.0 as i32), -(origin.1 as i32)))
232 }
233
234 #[inline]
236 pub fn shift(self, shift: (i32, i32)) -> Self {
237 Self {
238 x0: (self.x0 as i32)
239 .saturating_add(shift.0)
240 .clamp(0, u16::MAX as i32) as u16,
241 y0: (self.y0 as i32)
242 .saturating_add(shift.1)
243 .clamp(0, u16::MAX as i32) as u16,
244 x1: (self.x1 as i32)
245 .saturating_add(shift.0)
246 .clamp(0, u16::MAX as i32) as u16,
247 y1: (self.y1 as i32)
248 .saturating_add(shift.1)
249 .clamp(0, u16::MAX as i32) as u16,
250 }
251 }
252
253 #[inline(always)]
257 pub const fn union(&mut self, other: Self) {
258 self.x0 = const_min(self.x0, other.x0);
259 self.y0 = const_min(self.y0, other.y0);
260 self.x1 = const_max(self.x1, other.x1);
261 self.y1 = const_max(self.y1, other.y1);
262 }
263
264 pub fn as_rect(self) -> Rect {
266 Rect::new(
267 self.x0 as f64,
268 self.y0 as f64,
269 self.x1 as f64,
270 self.y1 as f64,
271 )
272 }
273}
274
275impl From<RectU16> for SizeU16 {
276 fn from(rect: RectU16) -> Self {
277 Self::from_wh(rect.width(), rect.height())
278 }
279}
280
281#[inline(always)]
282const fn const_max(a: u16, b: u16) -> u16 {
283 if a > b { a } else { b }
284}
285
286#[inline(always)]
287const fn const_min(a: u16, b: u16) -> u16 {
288 if a < b { a } else { b }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::RectU16;
294
295 #[test]
296 fn rect_u16_relative_to_origin() {
297 let rect = RectU16::new(10, 20, 30, 40);
298
299 assert_eq!(rect.relative_to_origin((5, 12)), RectU16::new(5, 8, 25, 28));
300 }
301
302 #[test]
303 fn rect_u16_relative_to_origin_clamps_to_zero() {
304 let rect = RectU16::new(10, 20, 30, 40);
305
306 assert_eq!(rect.relative_to_origin((20, 35)), RectU16::new(0, 0, 10, 5));
307 }
308
309 #[test]
310 fn disjoint_intersection_is_empty_but_not_inverted() {
311 let intersection = RectU16::new(0, 0, 4, 4).intersect(RectU16::new(8, 1, 12, 3));
312
313 assert_eq!(intersection, RectU16::new(8, 1, 8, 3));
314 assert!(intersection.is_empty());
315 assert!(intersection.x0 <= intersection.x1);
316 assert!(intersection.y0 <= intersection.y1);
317 }
318}