1use heapless::Vec;
2
3#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
4pub struct Rect {
5 pub x: i32,
6 pub y: i32,
7 pub w: u32,
8 pub h: u32,
9}
10
11impl Rect {
12 pub const fn new(x: i32, y: i32, w: u32, h: u32) -> Self {
13 Self { x, y, w, h }
14 }
15
16 pub const fn empty() -> Self {
17 Self::new(0, 0, 0, 0)
18 }
19
20 pub const fn right(self) -> i32 {
21 self.x + self.w as i32
22 }
23
24 pub const fn bottom(self) -> i32 {
25 self.y + self.h as i32
26 }
27
28 pub const fn is_empty(self) -> bool {
29 self.w == 0 || self.h == 0
30 }
31
32 pub fn contains(self, x: i32, y: i32) -> bool {
33 x >= self.x && y >= self.y && x < self.right() && y < self.bottom()
34 }
35
36 pub fn intersects(self, other: Self) -> bool {
37 !self.intersection(other).is_empty()
38 }
39
40 pub fn intersection(self, other: Self) -> Self {
41 let x0 = self.x.max(other.x);
42 let y0 = self.y.max(other.y);
43 let x1 = self.right().min(other.right());
44 let y1 = self.bottom().min(other.bottom());
45
46 if x1 <= x0 || y1 <= y0 {
47 Self::empty()
48 } else {
49 Self::new(x0, y0, (x1 - x0) as u32, (y1 - y0) as u32)
50 }
51 }
52
53 pub fn union(self, other: Self) -> Self {
54 if self.is_empty() {
55 return other;
56 }
57 if other.is_empty() {
58 return self;
59 }
60
61 let x0 = self.x.min(other.x);
62 let y0 = self.y.min(other.y);
63 let x1 = self.right().max(other.right());
64 let y1 = self.bottom().max(other.bottom());
65 Self::new(x0, y0, (x1 - x0) as u32, (y1 - y0) as u32)
66 }
67
68 pub fn inset(self, edges: EdgeInsets) -> Self {
69 let left = edges.left.max(0) as u32;
70 let right = edges.right.max(0) as u32;
71 let top = edges.top.max(0) as u32;
72 let bottom = edges.bottom.max(0) as u32;
73 let shrink_w = left.saturating_add(right).min(self.w);
74 let shrink_h = top.saturating_add(bottom).min(self.h);
75
76 Self::new(
77 self.x + left as i32,
78 self.y + top as i32,
79 self.w - shrink_w,
80 self.h - shrink_h,
81 )
82 }
83
84 pub fn align_to(self, reference: Rect, h: HorizontalAlign, v: VerticalAlign) -> Rect {
86 let x = match h {
87 HorizontalAlign::Left => reference.x,
88 HorizontalAlign::Center => reference.x + (reference.w as i32 - self.w as i32) / 2,
89 HorizontalAlign::Right => reference.right() - self.w as i32,
90 HorizontalAlign::LeftToRight => reference.right(),
91 HorizontalAlign::RightToLeft => reference.x - self.w as i32,
92 };
93 let y = match v {
94 VerticalAlign::Top => reference.y,
95 VerticalAlign::Center => reference.y + (reference.h as i32 - self.h as i32) / 2,
96 VerticalAlign::Bottom => reference.bottom() - self.h as i32,
97 VerticalAlign::TopToBottom => reference.bottom(),
98 VerticalAlign::BottomToTop => reference.y - self.h as i32,
99 };
100 Rect::new(x, y, self.w, self.h)
101 }
102
103 pub fn anchor_to(self, reference: Rect, anchor: Anchor) -> Rect {
105 match anchor {
106 Anchor::TopLeft => self.align_to(reference, HorizontalAlign::Left, VerticalAlign::Top),
107 Anchor::TopCenter => {
108 self.align_to(reference, HorizontalAlign::Center, VerticalAlign::Top)
109 }
110 Anchor::TopRight => {
111 self.align_to(reference, HorizontalAlign::Right, VerticalAlign::Top)
112 }
113 Anchor::CenterLeft => {
114 self.align_to(reference, HorizontalAlign::Left, VerticalAlign::Center)
115 }
116 Anchor::Center => {
117 self.align_to(reference, HorizontalAlign::Center, VerticalAlign::Center)
118 }
119 Anchor::CenterRight => {
120 self.align_to(reference, HorizontalAlign::Right, VerticalAlign::Center)
121 }
122 Anchor::BottomLeft => {
123 self.align_to(reference, HorizontalAlign::Left, VerticalAlign::Bottom)
124 }
125 Anchor::BottomCenter => {
126 self.align_to(reference, HorizontalAlign::Center, VerticalAlign::Bottom)
127 }
128 Anchor::BottomRight => {
129 self.align_to(reference, HorizontalAlign::Right, VerticalAlign::Bottom)
130 }
131 Anchor::OutsideTop => self.align_to(
132 reference,
133 HorizontalAlign::Center,
134 VerticalAlign::BottomToTop,
135 ),
136 Anchor::OutsideBottom => self.align_to(
137 reference,
138 HorizontalAlign::Center,
139 VerticalAlign::TopToBottom,
140 ),
141 Anchor::OutsideLeft => self.align_to(
142 reference,
143 HorizontalAlign::RightToLeft,
144 VerticalAlign::Center,
145 ),
146 Anchor::OutsideRight => self.align_to(
147 reference,
148 HorizontalAlign::LeftToRight,
149 VerticalAlign::Center,
150 ),
151 }
152 }
153}
154
155#[derive(Clone, Copy, Debug, PartialEq, Eq)]
157pub enum HorizontalAlign {
158 Left,
159 Center,
160 Right,
161 LeftToRight,
163 RightToLeft,
165}
166
167#[derive(Clone, Copy, Debug, PartialEq, Eq)]
169pub enum VerticalAlign {
170 Top,
171 Center,
172 Bottom,
173 TopToBottom,
175 BottomToTop,
177}
178
179#[derive(Clone, Copy, Debug, PartialEq, Eq)]
181pub enum Anchor {
182 TopLeft,
183 TopCenter,
184 TopRight,
185 CenterLeft,
186 Center,
187 CenterRight,
188 BottomLeft,
189 BottomCenter,
190 BottomRight,
191 OutsideTop,
192 OutsideBottom,
193 OutsideLeft,
194 OutsideRight,
195}
196
197#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
198pub struct EdgeInsets {
199 pub left: i16,
200 pub right: i16,
201 pub top: i16,
202 pub bottom: i16,
203}
204
205impl EdgeInsets {
206 pub const fn all(v: i16) -> Self {
207 Self {
208 left: v,
209 right: v,
210 top: v,
211 bottom: v,
212 }
213 }
214
215 pub const fn symmetric(horizontal: i16, vertical: i16) -> Self {
216 Self {
217 left: horizontal,
218 right: horizontal,
219 top: vertical,
220 bottom: vertical,
221 }
222 }
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226pub enum DirtyError {
227 Full,
228}
229
230pub struct DirtyTracker<const N: usize> {
231 regions: Vec<Rect, N>,
232}
233
234impl<const N: usize> DirtyTracker<N> {
235 pub const fn new() -> Self {
236 Self {
237 regions: Vec::new(),
238 }
239 }
240
241 pub fn clear(&mut self) {
242 self.regions.clear();
243 }
244
245 pub fn add(&mut self, rect: Rect) -> Result<(), DirtyError> {
246 if rect.is_empty() {
247 return Ok(());
248 }
249
250 if self.regions.iter().any(|r| r.intersects(rect)) {
251 let mut merged = rect;
252 let mut i = 0;
253 while i < self.regions.len() {
254 if self.regions[i].intersects(merged) {
255 merged = merged.union(self.regions.swap_remove(i));
256 } else {
257 i += 1;
258 }
259 }
260 return self.regions.push(merged).map_err(|_| DirtyError::Full);
261 }
262
263 self.regions.push(rect).map_err(|_| DirtyError::Full)
264 }
265
266 pub fn mark_all(&mut self, rect: Rect) -> Result<(), DirtyError> {
267 self.regions.clear();
268 self.add(rect)
269 }
270
271 pub fn as_slice(&self) -> &[Rect] {
272 self.regions.as_slice()
273 }
274
275 pub fn bounding_rect(&self) -> Option<Rect> {
276 let mut iter = self.regions.iter().copied();
277 let first = iter.next()?;
278 Some(iter.fold(first, Rect::union))
279 }
280
281 pub fn is_empty(&self) -> bool {
282 self.regions.is_empty()
283 }
284}
285
286impl<const N: usize> Default for DirtyTracker<N> {
287 fn default() -> Self {
288 Self::new()
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295
296 #[test]
297 fn test_rect_empty_and_contains() {
298 let r = Rect::new(10, 20, 30, 40);
299 assert!(!r.is_empty());
300 assert_eq!(r.right(), 40);
301 assert_eq!(r.bottom(), 60);
302
303 assert!(r.contains(10, 20));
304 assert!(r.contains(39, 59));
305 assert!(!r.contains(40, 60));
306 assert!(!r.contains(9, 20));
307
308 let empty = Rect::empty();
309 assert!(empty.is_empty());
310 assert!(!empty.contains(0, 0));
311 }
312
313 #[test]
314 fn test_rect_intersection_and_union() {
315 let r1 = Rect::new(0, 0, 20, 20);
316 let r2 = Rect::new(10, 10, 20, 20);
317
318 assert!(r1.intersects(r2));
319 assert_eq!(r1.intersection(r2), Rect::new(10, 10, 10, 10));
320 assert_eq!(r1.union(r2), Rect::new(0, 0, 30, 30));
321
322 let r3 = Rect::new(50, 50, 10, 10);
323 assert!(!r1.intersects(r3));
324 assert!(r1.intersection(r3).is_empty());
325 }
326
327 #[test]
328 fn test_rect_inset() {
329 let r = Rect::new(10, 10, 40, 40);
330 let inset = r.inset(EdgeInsets::all(5));
331 assert_eq!(inset, Rect::new(15, 15, 30, 30));
332
333 let over_inset = r.inset(EdgeInsets::all(30));
335 assert_eq!(over_inset.w, 0);
336 assert_eq!(over_inset.h, 0);
337 }
338
339 #[test]
340 fn test_dirty_tracker() {
341 let mut dt: DirtyTracker<4> = DirtyTracker::new();
342 assert!(dt.is_empty());
343
344 dt.add(Rect::new(0, 0, 10, 10)).unwrap();
345 assert_eq!(dt.as_slice().len(), 1);
346
347 dt.add(Rect::new(20, 20, 10, 10)).unwrap();
349 assert_eq!(dt.as_slice().len(), 2);
350
351 dt.add(Rect::new(5, 5, 10, 10)).unwrap();
353 assert_eq!(dt.as_slice().len(), 2);
355
356 assert_eq!(dt.bounding_rect(), Some(Rect::new(0, 0, 30, 30)));
357
358 dt.clear();
359 assert!(dt.is_empty());
360 assert_eq!(dt.bounding_rect(), None);
361 }
362
363 #[test]
364 fn test_rect_align_to_and_anchor_to() {
365 let parent = Rect::new(0, 0, 100, 100);
366 let child = Rect::new(0, 0, 20, 20);
367
368 let centered = child.align_to(parent, HorizontalAlign::Center, VerticalAlign::Center);
370 assert_eq!(centered, Rect::new(40, 40, 20, 20));
371
372 let top_right = child.anchor_to(parent, Anchor::TopRight);
374 assert_eq!(top_right, Rect::new(80, 0, 20, 20));
375
376 let dropdown = child.anchor_to(parent, Anchor::OutsideBottom);
378 assert_eq!(dropdown, Rect::new(40, 100, 20, 20));
379
380 let badge = child.align_to(parent, HorizontalAlign::LeftToRight, VerticalAlign::Top);
382 assert_eq!(badge, Rect::new(100, 0, 20, 20));
383 }
384}