Skip to main content

maps_engine_rust/
draw.rs

1//! Drawing tools with undo/redo history. Port of `draw.ts` + `edit.ts`.
2
3use crate::geo::LonLat;
4
5/// A drawable shape.
6#[derive(Debug, Clone, PartialEq)]
7pub enum Shape {
8    Marker(LonLat),
9    Polyline(Vec<LonLat>),
10    Polygon(Vec<LonLat>),
11    Circle { center: LonLat, radius_m: f64 },
12    Rectangle { sw: LonLat, ne: LonLat },
13}
14
15impl Shape {
16    /// Number of vertices (markers/circles count as 1).
17    pub fn vertex_count(&self) -> usize {
18        match self {
19            Shape::Marker(_) => 1,
20            Shape::Polyline(v) | Shape::Polygon(v) => v.len(),
21            Shape::Circle { .. } => 1,
22            Shape::Rectangle { .. } => 2,
23        }
24    }
25}
26
27/// Drawing session holding shapes plus undo/redo stacks.
28#[derive(Debug, Default)]
29pub struct DrawSession {
30    shapes: Vec<Shape>,
31    undo_stack: Vec<DrawOp>,
32    redo_stack: Vec<DrawOp>,
33}
34
35#[derive(Debug, Clone)]
36enum DrawOp {
37    Add { index: usize },
38    Remove { index: usize, shape: Shape },
39    Clear { shapes: Vec<Shape> },
40}
41
42impl DrawSession {
43    pub fn new() -> Self {
44        DrawSession::default()
45    }
46
47    pub fn shapes(&self) -> &[Shape] {
48        &self.shapes
49    }
50
51    /// Add a shape; clears the redo stack.
52    pub fn add(&mut self, shape: Shape) {
53        let index = self.shapes.len();
54        self.shapes.push(shape);
55        self.undo_stack.push(DrawOp::Add { index });
56        self.redo_stack.clear();
57    }
58
59    /// Remove shape at `index`.
60    pub fn remove(&mut self, index: usize) -> bool {
61        if index >= self.shapes.len() {
62            return false;
63        }
64        let shape = self.shapes.remove(index);
65        self.undo_stack.push(DrawOp::Remove { index, shape });
66        self.redo_stack.clear();
67        true
68    }
69
70    /// Remove all shapes.
71    pub fn clear(&mut self) {
72        if self.shapes.is_empty() {
73            return;
74        }
75        let shapes = std::mem::take(&mut self.shapes);
76        self.undo_stack.push(DrawOp::Clear { shapes });
77        self.redo_stack.clear();
78    }
79
80    pub fn can_undo(&self) -> bool {
81        !self.undo_stack.is_empty()
82    }
83    pub fn can_redo(&self) -> bool {
84        !self.redo_stack.is_empty()
85    }
86
87    /// Undo the last operation. Returns false if nothing to undo.
88    pub fn undo(&mut self) -> bool {
89        let op = match self.undo_stack.pop() {
90            Some(op) => op,
91            None => return false,
92        };
93        match op {
94            DrawOp::Add { index } => {
95                if index < self.shapes.len() {
96                    let shape = self.shapes.remove(index);
97                    self.redo_stack.push(DrawOp::Remove { index, shape });
98                }
99            }
100            DrawOp::Remove { index, shape } => {
101                let idx = index.min(self.shapes.len());
102                self.shapes.insert(idx, shape);
103                self.redo_stack.push(DrawOp::Add { index: idx });
104            }
105            DrawOp::Clear { shapes } => {
106                let current = std::mem::replace(&mut self.shapes, shapes);
107                self.redo_stack.push(DrawOp::Clear { shapes: current });
108            }
109        }
110        true
111    }
112
113    /// Redo the last undone operation. Returns false if nothing to redo.
114    pub fn redo(&mut self) -> bool {
115        let op = match self.redo_stack.pop() {
116            Some(op) => op,
117            None => return false,
118        };
119        // Redo stack holds inverse ops recorded during undo():
120        //   Remove{index, shape}  -> re-insert the shape
121        //   Add{index}            -> remove the shape again
122        //   Clear{shapes}         -> clear again
123        match op {
124            DrawOp::Remove { index, shape } => {
125                let idx = index.min(self.shapes.len());
126                self.shapes.insert(idx, shape);
127                self.undo_stack.push(DrawOp::Add { index: idx });
128            }
129            DrawOp::Add { index } => {
130                if index < self.shapes.len() {
131                    let shape = self.shapes.remove(index);
132                    self.undo_stack.push(DrawOp::Remove { index, shape });
133                }
134            }
135            DrawOp::Clear { .. } => {
136                let shapes = std::mem::take(&mut self.shapes);
137                self.undo_stack.push(DrawOp::Clear { shapes });
138            }
139        }
140        true
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    fn marker() -> Shape {
149        Shape::Marker(LonLat::new(105.85, 21.02))
150    }
151
152    #[test]
153    fn add_undo_redo() {
154        let mut s = DrawSession::new();
155        s.add(marker());
156        assert_eq!(s.shapes().len(), 1);
157        assert!(s.undo());
158        assert_eq!(s.shapes().len(), 0);
159        assert!(s.redo());
160        assert_eq!(s.shapes().len(), 1);
161        assert_eq!(s.shapes()[0], marker());
162    }
163
164    #[test]
165    fn remove_undo_restores_position() {
166        let mut s = DrawSession::new();
167        s.add(marker());
168        s.add(Shape::Marker(LonLat::new(106.0, 21.0)));
169        assert!(s.remove(0));
170        assert_eq!(s.shapes().len(), 1);
171        assert!(s.undo());
172        assert_eq!(s.shapes().len(), 2);
173        assert_eq!(s.shapes()[0], marker());
174    }
175
176    #[test]
177    fn clear_undo_restores_all() {
178        let mut s = DrawSession::new();
179        s.add(marker());
180        s.add(marker());
181        s.clear();
182        assert!(s.shapes().is_empty());
183        assert!(s.undo());
184        assert_eq!(s.shapes().len(), 2);
185    }
186
187    #[test]
188    fn new_action_clears_redo() {
189        let mut s = DrawSession::new();
190        s.add(marker());
191        s.undo();
192        assert!(s.can_redo());
193        s.add(marker());
194        assert!(!s.can_redo());
195    }
196
197    #[test]
198    fn undo_empty_returns_false() {
199        let mut s = DrawSession::new();
200        assert!(!s.undo());
201        assert!(!s.redo());
202        assert!(!s.remove(5));
203    }
204}