use std::collections::VecDeque;
#[derive(Debug, Clone)]
pub struct UndoableAction {
pub description: String,
pub undo_msg: String,
pub redo_msg: String,
}
pub struct UndoStack {
undo_stack: VecDeque<UndoableAction>,
redo_stack: VecDeque<UndoableAction>,
max_size: usize,
}
impl UndoStack {
pub fn new(max_size: usize) -> Self {
Self {
undo_stack: VecDeque::with_capacity(max_size),
redo_stack: VecDeque::with_capacity(max_size),
max_size,
}
}
pub fn push(&mut self, action: UndoableAction) {
self.redo_stack.clear();
if self.undo_stack.len() >= self.max_size {
self.undo_stack.pop_front();
}
self.undo_stack.push_back(action);
}
pub fn undo(&mut self) -> Option<String> {
if let Some(action) = self.undo_stack.pop_back() {
let msg = action.undo_msg.clone();
self.redo_stack.push_back(action);
Some(msg)
} else {
None
}
}
pub fn redo(&mut self) -> Option<String> {
if let Some(action) = self.redo_stack.pop_back() {
let msg = action.redo_msg.clone();
self.undo_stack.push_back(action);
Some(msg)
} else {
None
}
}
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_count(&self) -> usize {
self.undo_stack.len()
}
pub fn redo_count(&self) -> usize {
self.redo_stack.len()
}
pub fn last_action(&self) -> Option<&str> {
self.undo_stack.back().map(|a| a.description.as_str())
}
pub fn clear(&mut self) {
self.undo_stack.clear();
self.redo_stack.clear();
}
}
impl Default for UndoStack {
fn default() -> Self {
Self::new(50) }
}
#[cfg(test)]
mod tests {
use super::*;
fn make_action(name: &str) -> UndoableAction {
UndoableAction {
description: name.to_string(),
undo_msg: format!("undo_{}", name),
redo_msg: format!("redo_{}", name),
}
}
#[test]
fn test_undo_redo() {
let mut stack = UndoStack::new(10);
stack.push(make_action("stage"));
assert!(stack.can_undo());
assert!(!stack.can_redo());
let undo = stack.undo();
assert_eq!(undo, Some("undo_stage".to_string()));
assert!(!stack.can_undo());
assert!(stack.can_redo());
let redo = stack.redo();
assert_eq!(redo, Some("redo_stage".to_string()));
}
#[test]
fn test_new_action_clears_redo() {
let mut stack = UndoStack::new(10);
stack.push(make_action("a"));
stack.push(make_action("b"));
stack.undo();
assert!(stack.can_redo());
stack.push(make_action("c")); assert!(!stack.can_redo());
}
}