Skip to main content

embedded_gui/
geometry.rs

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}