use crate::geo::LonLat;
#[derive(Debug, Clone, PartialEq)]
pub enum Shape {
Marker(LonLat),
Polyline(Vec<LonLat>),
Polygon(Vec<LonLat>),
Circle { center: LonLat, radius_m: f64 },
Rectangle { sw: LonLat, ne: LonLat },
}
impl Shape {
pub fn vertex_count(&self) -> usize {
match self {
Shape::Marker(_) => 1,
Shape::Polyline(v) | Shape::Polygon(v) => v.len(),
Shape::Circle { .. } => 1,
Shape::Rectangle { .. } => 2,
}
}
}
#[derive(Debug, Default)]
pub struct DrawSession {
shapes: Vec<Shape>,
undo_stack: Vec<DrawOp>,
redo_stack: Vec<DrawOp>,
}
#[derive(Debug, Clone)]
enum DrawOp {
Add { index: usize },
Remove { index: usize, shape: Shape },
Clear { shapes: Vec<Shape> },
}
impl DrawSession {
pub fn new() -> Self {
DrawSession::default()
}
pub fn shapes(&self) -> &[Shape] {
&self.shapes
}
pub fn add(&mut self, shape: Shape) {
let index = self.shapes.len();
self.shapes.push(shape);
self.undo_stack.push(DrawOp::Add { index });
self.redo_stack.clear();
}
pub fn remove(&mut self, index: usize) -> bool {
if index >= self.shapes.len() {
return false;
}
let shape = self.shapes.remove(index);
self.undo_stack.push(DrawOp::Remove { index, shape });
self.redo_stack.clear();
true
}
pub fn clear(&mut self) {
if self.shapes.is_empty() {
return;
}
let shapes = std::mem::take(&mut self.shapes);
self.undo_stack.push(DrawOp::Clear { shapes });
self.redo_stack.clear();
}
pub fn can_undo(&self) -> bool {
!self.undo_stack.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.redo_stack.is_empty()
}
pub fn undo(&mut self) -> bool {
let op = match self.undo_stack.pop() {
Some(op) => op,
None => return false,
};
match op {
DrawOp::Add { index } => {
if index < self.shapes.len() {
let shape = self.shapes.remove(index);
self.redo_stack.push(DrawOp::Remove { index, shape });
}
}
DrawOp::Remove { index, shape } => {
let idx = index.min(self.shapes.len());
self.shapes.insert(idx, shape);
self.redo_stack.push(DrawOp::Add { index: idx });
}
DrawOp::Clear { shapes } => {
let current = std::mem::replace(&mut self.shapes, shapes);
self.redo_stack.push(DrawOp::Clear { shapes: current });
}
}
true
}
pub fn redo(&mut self) -> bool {
let op = match self.redo_stack.pop() {
Some(op) => op,
None => return false,
};
match op {
DrawOp::Remove { index, shape } => {
let idx = index.min(self.shapes.len());
self.shapes.insert(idx, shape);
self.undo_stack.push(DrawOp::Add { index: idx });
}
DrawOp::Add { index } => {
if index < self.shapes.len() {
let shape = self.shapes.remove(index);
self.undo_stack.push(DrawOp::Remove { index, shape });
}
}
DrawOp::Clear { .. } => {
let shapes = std::mem::take(&mut self.shapes);
self.undo_stack.push(DrawOp::Clear { shapes });
}
}
true
}
}
#[cfg(test)]
mod tests {
use super::*;
fn marker() -> Shape {
Shape::Marker(LonLat::new(105.85, 21.02))
}
#[test]
fn add_undo_redo() {
let mut s = DrawSession::new();
s.add(marker());
assert_eq!(s.shapes().len(), 1);
assert!(s.undo());
assert_eq!(s.shapes().len(), 0);
assert!(s.redo());
assert_eq!(s.shapes().len(), 1);
assert_eq!(s.shapes()[0], marker());
}
#[test]
fn remove_undo_restores_position() {
let mut s = DrawSession::new();
s.add(marker());
s.add(Shape::Marker(LonLat::new(106.0, 21.0)));
assert!(s.remove(0));
assert_eq!(s.shapes().len(), 1);
assert!(s.undo());
assert_eq!(s.shapes().len(), 2);
assert_eq!(s.shapes()[0], marker());
}
#[test]
fn clear_undo_restores_all() {
let mut s = DrawSession::new();
s.add(marker());
s.add(marker());
s.clear();
assert!(s.shapes().is_empty());
assert!(s.undo());
assert_eq!(s.shapes().len(), 2);
}
#[test]
fn new_action_clears_redo() {
let mut s = DrawSession::new();
s.add(marker());
s.undo();
assert!(s.can_redo());
s.add(marker());
assert!(!s.can_redo());
}
#[test]
fn undo_empty_returns_false() {
let mut s = DrawSession::new();
assert!(!s.undo());
assert!(!s.redo());
assert!(!s.remove(5));
}
}