use super::state::{PlaylistItem, QueueItemId};
const MAX_UNDO_DEPTH: usize = 100;
#[derive(Debug, Clone)]
pub enum UndoEntry {
Added { ids: Vec<QueueItemId> },
Removed {
items: Vec<(Box<PlaylistItem>, Option<QueueItemId>)>,
},
Inserted { ids: Vec<QueueItemId> },
Moved {
id: QueueItemId,
was_after: Option<QueueItemId>,
},
MovedBatch {
entries: Vec<(QueueItemId, Option<QueueItemId>)>,
},
Replaced {
items: Vec<PlaylistItem>,
cursor: Option<QueueItemId>,
},
Batch(Vec<UndoEntry>),
}
#[derive(Debug, Default)]
pub struct UndoStack {
undo: Vec<UndoEntry>,
redo: Vec<UndoEntry>,
}
impl UndoStack {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, entry: UndoEntry) {
self.redo.clear();
self.undo.push(entry);
if self.undo.len() > MAX_UNDO_DEPTH {
self.undo.remove(0);
}
}
pub fn pop_undo(&mut self) -> Option<UndoEntry> {
self.undo.pop()
}
pub fn pop_redo(&mut self) -> Option<UndoEntry> {
self.redo.pop()
}
pub fn push_redo(&mut self, entry: UndoEntry) {
self.redo.push(entry);
}
pub fn push_undo_keep_redo(&mut self, entry: UndoEntry) {
self.undo.push(entry);
if self.undo.len() > MAX_UNDO_DEPTH {
self.undo.remove(0);
}
}
pub fn can_undo(&self) -> bool {
!self.undo.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.redo.is_empty()
}
pub fn undo_len(&self) -> usize {
self.undo.len()
}
pub fn redo_len(&self) -> usize {
self.redo.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn dummy_id() -> QueueItemId {
QueueItemId::new()
}
#[test]
fn push_and_pop_undo() {
let mut stack = UndoStack::new();
let id = dummy_id();
stack.push(UndoEntry::Added { ids: vec![id] });
assert!(stack.can_undo());
assert!(!stack.can_redo());
let entry = stack.pop_undo().unwrap();
assert!(matches!(entry, UndoEntry::Added { .. }));
assert!(!stack.can_undo());
}
#[test]
fn new_action_clears_redo() {
let mut stack = UndoStack::new();
let id = dummy_id();
stack.push(UndoEntry::Added { ids: vec![id] });
let entry = stack.pop_undo().unwrap();
stack.push_redo(entry);
assert!(stack.can_redo());
stack.push(UndoEntry::Added { ids: vec![id] });
assert!(!stack.can_redo());
}
#[test]
fn max_depth_enforced() {
let mut stack = UndoStack::new();
for _ in 0..150 {
stack.push(UndoEntry::Added {
ids: vec![dummy_id()],
});
}
assert_eq!(stack.undo_len(), MAX_UNDO_DEPTH);
}
#[test]
fn push_undo_keep_redo_preserves_redo() {
let mut stack = UndoStack::new();
let id = dummy_id();
stack.push_redo(UndoEntry::Added { ids: vec![id] });
assert!(stack.can_redo());
stack.push_undo_keep_redo(UndoEntry::Added { ids: vec![id] });
assert!(stack.can_undo());
assert!(stack.can_redo()); }
}