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
85#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
86pub struct EdgeInsets {
87 pub left: i16,
88 pub right: i16,
89 pub top: i16,
90 pub bottom: i16,
91}
92
93impl EdgeInsets {
94 pub const fn all(v: i16) -> Self {
95 Self {
96 left: v,
97 right: v,
98 top: v,
99 bottom: v,
100 }
101 }
102
103 pub const fn symmetric(horizontal: i16, vertical: i16) -> Self {
104 Self {
105 left: horizontal,
106 right: horizontal,
107 top: vertical,
108 bottom: vertical,
109 }
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum DirtyError {
115 Full,
116}
117
118pub struct DirtyTracker<const N: usize> {
119 regions: Vec<Rect, N>,
120}
121
122impl<const N: usize> DirtyTracker<N> {
123 pub const fn new() -> Self {
124 Self {
125 regions: Vec::new(),
126 }
127 }
128
129 pub fn clear(&mut self) {
130 self.regions.clear();
131 }
132
133 pub fn add(&mut self, rect: Rect) -> Result<(), DirtyError> {
134 if rect.is_empty() {
135 return Ok(());
136 }
137
138 if self.regions.iter().any(|r| r.intersects(rect)) {
139 let mut merged = rect;
140 let mut i = 0;
141 while i < self.regions.len() {
142 if self.regions[i].intersects(merged) {
143 merged = merged.union(self.regions.swap_remove(i));
144 } else {
145 i += 1;
146 }
147 }
148 return self.regions.push(merged).map_err(|_| DirtyError::Full);
149 }
150
151 self.regions.push(rect).map_err(|_| DirtyError::Full)
152 }
153
154 pub fn mark_all(&mut self, rect: Rect) -> Result<(), DirtyError> {
155 self.regions.clear();
156 self.add(rect)
157 }
158
159 pub fn as_slice(&self) -> &[Rect] {
160 self.regions.as_slice()
161 }
162
163 pub fn bounding_rect(&self) -> Option<Rect> {
164 let mut iter = self.regions.iter().copied();
165 let first = iter.next()?;
166 Some(iter.fold(first, Rect::union))
167 }
168
169 pub fn is_empty(&self) -> bool {
170 self.regions.is_empty()
171 }
172}
173
174impl<const N: usize> Default for DirtyTracker<N> {
175 fn default() -> Self {
176 Self::new()
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn test_rect_empty_and_contains() {
186 let r = Rect::new(10, 20, 30, 40);
187 assert!(!r.is_empty());
188 assert_eq!(r.right(), 40);
189 assert_eq!(r.bottom(), 60);
190
191 assert!(r.contains(10, 20));
192 assert!(r.contains(39, 59));
193 assert!(!r.contains(40, 60));
194 assert!(!r.contains(9, 20));
195
196 let empty = Rect::empty();
197 assert!(empty.is_empty());
198 assert!(!empty.contains(0, 0));
199 }
200
201 #[test]
202 fn test_rect_intersection_and_union() {
203 let r1 = Rect::new(0, 0, 20, 20);
204 let r2 = Rect::new(10, 10, 20, 20);
205
206 assert!(r1.intersects(r2));
207 assert_eq!(r1.intersection(r2), Rect::new(10, 10, 10, 10));
208 assert_eq!(r1.union(r2), Rect::new(0, 0, 30, 30));
209
210 let r3 = Rect::new(50, 50, 10, 10);
211 assert!(!r1.intersects(r3));
212 assert!(r1.intersection(r3).is_empty());
213 }
214
215 #[test]
216 fn test_rect_inset() {
217 let r = Rect::new(10, 10, 40, 40);
218 let inset = r.inset(EdgeInsets::all(5));
219 assert_eq!(inset, Rect::new(15, 15, 30, 30));
220
221 let over_inset = r.inset(EdgeInsets::all(30));
223 assert_eq!(over_inset.w, 0);
224 assert_eq!(over_inset.h, 0);
225 }
226
227 #[test]
228 fn test_dirty_tracker() {
229 let mut dt: DirtyTracker<4> = DirtyTracker::new();
230 assert!(dt.is_empty());
231
232 dt.add(Rect::new(0, 0, 10, 10)).unwrap();
233 assert_eq!(dt.as_slice().len(), 1);
234
235 dt.add(Rect::new(20, 20, 10, 10)).unwrap();
237 assert_eq!(dt.as_slice().len(), 2);
238
239 dt.add(Rect::new(5, 5, 10, 10)).unwrap();
241 assert_eq!(dt.as_slice().len(), 2);
243
244 assert_eq!(dt.bounding_rect(), Some(Rect::new(0, 0, 30, 30)));
245
246 dt.clear();
247 assert!(dt.is_empty());
248 assert_eq!(dt.bounding_rect(), None);
249 }
250}