buffer_graphics_lib/
clipping.rs

1use crate::clipping::ClipMode::*;
2use graphics_shapes::coord::Coord;
3use graphics_shapes::prelude::{Circle, Rect};
4use graphics_shapes::Shape;
5use log::error;
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8use std::mem::swap;
9
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11#[derive(Clone, Debug, Eq, PartialEq)]
12enum ClipShape {
13    Box(Rect),
14    Round(Circle),
15}
16
17impl ClipShape {
18    pub fn contains(&self, xy: (isize, isize)) -> bool {
19        match self {
20            ClipShape::Box(rect) => rect.contains(Coord::from(xy)),
21            ClipShape::Round(circle) => circle.contains(Coord::from(xy)),
22        }
23    }
24}
25
26#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27#[derive(Clone, Debug, Eq, PartialEq)]
28enum ClipElement {
29    Add(ClipShape),
30    Remove(ClipShape),
31}
32
33#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
34#[derive(Clone, Debug, Eq, PartialEq)]
35enum ClipMode {
36    Nothing,
37    Simple(ClipShape),
38    Complex(Vec<ClipElement>),
39    Custom(Vec<bool>),
40}
41
42/// Clip has four modes:
43/// * Nothing - All pixels are valid
44/// * Simple - Only pixels in the shape (rect or circle) are valid
45/// * Custom - User provides a list of which pixels are valid
46/// * Complex - A series of shapes adding and removing clip area
47///
48/// Complex starts with all pixels being valid
49/// * use `add_*` to decrease the valid area
50/// * use `remove_*` to increase the valid area
51///
52/// the last shape to touch a pixel determines it's validity
53///
54/// With complex mode a list of valid pixels is stored internally and each time the complex clip is updated the valid list is updated as well, if you're making a bulk edit call `set_auto_build_map(false)` first
55#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
56#[derive(Debug, Clone, Eq, PartialEq)]
57pub struct Clip {
58    width: usize,
59    height: usize,
60    mode: ClipMode,
61    old_mode: ClipMode,
62    valid_pixel_map: Option<Vec<bool>>,
63    old_valid_pixel_map: Option<Vec<bool>>,
64    auto_build_map: bool,
65}
66
67impl Clip {
68    pub fn new(width: usize, height: usize) -> Self {
69        Self {
70            width,
71            height,
72            mode: Nothing,
73            old_mode: Nothing,
74            valid_pixel_map: None,
75            old_valid_pixel_map: None,
76            auto_build_map: true,
77        }
78    }
79}
80
81impl Clip {
82    pub fn is_nothing(&self) -> bool {
83        matches!(&self.mode, Nothing)
84    }
85
86    pub fn is_simple(&self) -> bool {
87        matches!(&self.mode, Simple(_))
88    }
89
90    pub fn is_complex(&self) -> bool {
91        matches!(&self.mode, Complex(_))
92    }
93
94    pub fn is_custom(&self) -> bool {
95        matches!(&self.mode, Custom(_))
96    }
97
98    pub fn set_auto_build_map(&mut self, auto_build_map: bool) {
99        self.auto_build_map = auto_build_map;
100    }
101}
102
103impl Clip {
104    pub fn is_valid(&self, xy: (isize, isize)) -> bool {
105        let i = xy.0 + xy.1 * (self.width as isize);
106        let u = i.max(0) as usize;
107        match &self.mode {
108            Nothing => true,
109            Simple(shape) => shape.contains(xy),
110            Complex(_) => {
111                if let Some(map) = &self.valid_pixel_map {
112                    map[u]
113                } else {
114                    error!("Using complex clip but pixel map hasn't been built");
115                    true
116                }
117            }
118            Custom(map) => map[u],
119        }
120    }
121}
122
123impl Clip {
124    fn store_current_mode(&mut self) {
125        swap(&mut self.old_mode, &mut self.mode);
126        swap(&mut self.old_valid_pixel_map, &mut self.valid_pixel_map);
127        self.valid_pixel_map = None;
128    }
129}
130
131impl Clip {
132    /// Clears the clip so all pixels can be drawn to
133    pub fn set_all_valid(&mut self) {
134        self.store_current_mode();
135        self.mode = Nothing;
136    }
137
138    /// Set the valid pixels to `rect`
139    pub fn set_valid_rect(&mut self, rect: Rect) {
140        self.store_current_mode();
141        self.mode = Simple(ClipShape::Box(rect));
142    }
143
144    /// Set the valid pixels to `circle`
145    pub fn set_valid_circle(&mut self, circle: Circle) {
146        self.store_current_mode();
147        self.mode = Simple(ClipShape::Round(circle));
148    }
149
150    /// Set the valid pixels to `pixel_map`
151    pub fn custom(&mut self, pixel_map: Vec<bool>) {
152        self.store_current_mode();
153        self.mode = Custom(pixel_map);
154    }
155
156    pub fn get_pixel_map(&mut self) -> Vec<bool> {
157        match &self.mode {
158            Simple(_) | Nothing => self.build_pixel_map(),
159            Complex(_) => {
160                if let Some(map) = &self.valid_pixel_map {
161                    map.clone()
162                } else {
163                    self.update_pixel_map();
164                    self.valid_pixel_map.as_ref().unwrap().clone()
165                }
166            }
167            Custom(map) => map.clone(),
168        }
169    }
170}
171
172impl Clip {
173    fn swap_to_complex(&mut self) {
174        if !self.is_complex() {
175            self.store_current_mode();
176            self.mode = Complex(vec![]);
177        }
178    }
179
180    fn add(&mut self, element: ClipElement) {
181        self.swap_to_complex();
182        if let Complex(list) = &mut self.mode {
183            list.push(element);
184        }
185        if self.auto_build_map {
186            self.update_pixel_map();
187        }
188    }
189
190    /// Set the mode to `complex` (clearing any other mode)
191    /// Set any pixels in `rect` to valid
192    pub fn add_rect(&mut self, rect: Rect) {
193        self.add(ClipElement::Add(ClipShape::Box(rect)));
194    }
195
196    /// Set the mode to `complex` (clearing any other mode)
197    /// Set any pixels in `rect` to invalid
198    pub fn remove_rect(&mut self, rect: Rect) {
199        self.add(ClipElement::Remove(ClipShape::Box(rect)));
200    }
201
202    /// Set the mode to `complex` (clearing any other mode)
203    /// Set any pixels in `circle` to valid
204    pub fn add_circle(&mut self, circle: Circle) {
205        self.add(ClipElement::Add(ClipShape::Round(circle)));
206    }
207
208    /// Set the mode to `complex` (clearing any other mode)
209    /// Set any pixels in `circle` to invalid
210    pub fn remove_circle(&mut self, circle: Circle) {
211        self.add(ClipElement::Remove(ClipShape::Round(circle)));
212    }
213}
214
215impl Clip {
216    pub fn update_pixel_map(&mut self) {
217        if self.is_complex() {
218            self.valid_pixel_map = Some(self.build_pixel_map())
219        } else {
220            self.valid_pixel_map = None;
221        }
222    }
223
224    fn build_pixel_map(&self) -> Vec<bool> {
225        match &self.mode {
226            Nothing => vec![true; self.width * self.height],
227            Simple(shape) => self.build_simple_map(shape),
228            Complex(elements) => self.build_complex_map(elements),
229            Custom(map) => map.clone(),
230        }
231    }
232
233    fn build_simple_map(&self, shape: &ClipShape) -> Vec<bool> {
234        let mut output = vec![false; self.width * self.height];
235        for x in 0..self.width {
236            for y in 0..self.height {
237                let i = x + y * self.width;
238                let contains = shape.contains((x as isize, y as isize));
239                if contains {
240                    output[i] = true;
241                }
242            }
243        }
244        output
245    }
246
247    fn build_complex_map(&self, elements: &[ClipElement]) -> Vec<bool> {
248        let mut output = vec![true; self.width * self.height];
249        for x in 0..self.width {
250            for y in 0..self.height {
251                let mut valid = true;
252                for element in elements {
253                    match element {
254                        ClipElement::Add(shape) => {
255                            if shape.contains((x as isize, y as isize)) {
256                                valid = true;
257                            }
258                        }
259                        ClipElement::Remove(shape) => {
260                            if shape.contains((x as isize, y as isize)) {
261                                valid = false;
262                            }
263                        }
264                    }
265                }
266                let i = x + y * self.width;
267                if i >= output.len() {
268                    break;
269                }
270                output[x + y * self.width] = valid;
271            }
272        }
273        output
274    }
275}
276
277#[cfg(test)]
278mod test {
279    use crate::clipping::Clip;
280    use graphics_shapes::rect::Rect;
281
282    #[test]
283    fn check_all_pixels_valid_for_none() {
284        let mut clip = Clip::new(4, 4);
285        assert_eq!(clip.get_pixel_map(), vec![true; 16]);
286    }
287
288    #[test]
289    fn check_pixels_valid_for_square() {
290        let mut clip = Clip::new(4, 4);
291        clip.set_valid_rect(Rect::new((1, 1), (2, 2)));
292        let expected = vec![
293            false, false, false, false, false, true, true, false, false, true, true, false, false,
294            false, false, false,
295        ];
296        assert_eq!(clip.get_pixel_map(), expected);
297    }
298
299    #[test]
300    fn check_pixels_split_horz() {
301        let mut clip = Clip::new(4, 4);
302        clip.set_valid_rect(Rect::new((2, 0), (3, 3)));
303        let expected = vec![
304            false, false, true, true, false, false, true, true, false, false, true, true, false,
305            false, true, true,
306        ];
307        assert_eq!(clip.get_pixel_map(), expected);
308    }
309
310    #[test]
311    fn complex() {
312        let mut clip = Clip::new(4, 4);
313
314        clip.add_rect(Rect::new((0, 0), (3, 3))); //set all pixels to valid
315        let expected = vec![true; 16];
316        assert_eq!(clip.get_pixel_map(), expected);
317
318        clip.remove_rect(Rect::new((2, 0), (3, 3))); //set right hand side to invalid
319        let expected = vec![
320            true, true, false, false, true, true, false, false, true, true, false, false, true,
321            true, false, false,
322        ];
323        assert_eq!(clip.get_pixel_map(), expected);
324
325        clip.add_rect(Rect::new((3, 0), (3, 3))); //set last column to valid
326        let expected = vec![
327            true, true, false, true, true, true, false, true, true, true, false, true, true, true,
328            false, true,
329        ];
330        assert_eq!(clip.get_pixel_map(), expected);
331    }
332}