use crate::data_structure::{Document, Entity};
#[derive(Debug, Clone)]
pub enum HistoryOp {
Added(Entity),
Removed(Entity),
Modified(Entity, Entity),
}
#[derive(Debug)]
pub struct OpHistory {
undo_stack: Vec<HistoryOp>,
redo_stack: Vec<HistoryOp>,
limit: usize,
}
impl Default for OpHistory {
fn default() -> Self {
Self::new()
}
}
impl OpHistory {
pub const DEFAULT_LIMIT: usize = 200;
pub fn new() -> Self {
Self {
undo_stack: Vec::new(),
redo_stack: Vec::new(),
limit: Self::DEFAULT_LIMIT,
}
}
pub fn with_limit(limit: usize) -> Self {
Self {
undo_stack: Vec::new(),
redo_stack: Vec::new(),
limit,
}
}
pub fn limit(&self) -> usize {
self.limit
}
pub fn push(&mut self, op: HistoryOp) {
self.redo_stack.clear();
self.undo_stack.push(op);
while self.undo_stack.len() > self.limit {
self.undo_stack.remove(0);
}
}
pub fn undo(&mut self, doc: &mut Document) -> bool {
let Some(op) = self.undo_stack.pop() else {
return false;
};
let ok = match &op {
HistoryOp::Added(e) => doc.remove_entity(e.id()),
HistoryOp::Removed(e) => {
doc.add_entity(e.clone());
true
}
HistoryOp::Modified(old, _) => {
doc.entities_mut().insert(old.id().clone(), old.clone()).is_some()
}
};
if ok {
self.redo_stack.push(op);
}
ok
}
pub fn redo(&mut self, doc: &mut Document) -> bool {
let Some(op) = self.redo_stack.pop() else {
return false;
};
let ok = match &op {
HistoryOp::Added(e) => {
doc.add_entity(e.clone());
true
}
HistoryOp::Removed(e) => doc.remove_entity(e.id()),
HistoryOp::Modified(_, new) => {
doc.entities_mut().insert(new.id().clone(), new.clone()).is_some()
}
};
if ok {
self.undo_stack.push(op);
}
ok
}
pub fn can_undo(&self) -> bool {
!self.undo_stack.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.redo_stack.is_empty()
}
pub fn is_empty(&self) -> bool {
self.undo_stack.is_empty()
}
pub fn len(&self) -> usize {
self.undo_stack.len()
}
pub fn clear(&mut self) {
self.undo_stack.clear();
self.redo_stack.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::data_structure::{make_line, EntityGeometry};
use crate::geometry::Point;
#[test]
fn test_op_history_undo_redo() {
let mut doc = Document::new("test".to_string());
let mut history = OpHistory::new();
assert!(history.is_empty());
assert!(!history.can_undo());
let line = make_line(Point::new2d(0.0, 0.0), Point::new2d(10.0, 0.0));
let id = line.id().clone();
doc.add_entity(line.clone());
history.push(HistoryOp::Added(line));
assert!(history.can_undo());
assert!(history.undo(&mut doc));
assert!(!doc.entity_exists(&id));
assert!(history.can_redo());
assert!(history.redo(&mut doc));
assert!(doc.entity_exists(&id));
}
#[test]
fn test_op_history_modified() {
let mut doc = Document::new("test".to_string());
let mut history = OpHistory::new();
let line = make_line(Point::new2d(0.0, 0.0), Point::new2d(10.0, 0.0));
let id = line.id().clone();
doc.add_entity(line.clone());
let mut moved = line.clone();
if let EntityGeometry::Line(l) = moved.geometry_mut() {
l.start = Point::new2d(5.0, 5.0);
}
doc.entities_mut().insert(id.clone(), moved.clone());
history.push(HistoryOp::Modified(line, moved));
assert!(history.undo(&mut doc));
match doc.get_entity(&id).unwrap().geometry() {
EntityGeometry::Line(l) => assert_eq!(l.start.x, 0.0),
_ => panic!("expected line"),
}
assert!(history.redo(&mut doc));
match doc.get_entity(&id).unwrap().geometry() {
EntityGeometry::Line(l) => assert_eq!(l.start.x, 5.0),
_ => panic!("expected line"),
}
}
#[test]
fn test_op_history_limit() {
let mut history = OpHistory::with_limit(3);
for i in 0..5 {
let line = make_line(
Point::new2d(i as f64, 0.0),
Point::new2d(i as f64 + 1.0, 0.0),
);
history.push(HistoryOp::Added(line));
}
assert_eq!(history.len(), 3);
}
}