Files
BadNote/lib/editor/engine/undo_stack.dart

76 lines
2.4 KiB
Dart
Raw Normal View History

// lib/editor/engine/undo_stack.dart
//
// Generic undo/redo stack with a fixed capacity.
//
// RECORD DISCIPLINE: call `record(currentState)` BEFORE applying a mutation.
// The stack saves the pre-mutation snapshot so that `undo` can restore it.
//
// Example:
// final stack = UndoStack<List<PenStroke>>(cap: 50);
// // User draws a stroke:
// stack.record(List.unmodifiable(strokes)); // snapshot before mutation
// strokes = [...strokes, newStroke]; // apply mutation
//
// Snapshots are treated as opaque, immutable values; the caller is responsible
// for passing copies/immutable lists rather than mutable references.
/// A capped undo/redo stack for arbitrary snapshot types.
///
/// Capacity defaults to 50 entries. When the cap is reached the oldest
/// undo snapshot is silently dropped to make room.
class UndoStack<T> {
UndoStack({int cap = 50}) : _cap = cap;
final int _cap;
// Index 0 = oldest, last = most-recent snapshot available for undo.
final List<T> _undoStack = [];
final List<T> _redoStack = [];
/// True when there is at least one snapshot that can be undone.
bool get canUndo => _undoStack.isNotEmpty;
/// True when there is at least one snapshot that can be redone.
bool get canRedo => _redoStack.isNotEmpty;
/// Save [snapshot] (the state BEFORE a mutation) onto the undo stack and
/// clear the redo stack (any branched future is discarded).
///
/// If the stack is at capacity the oldest snapshot is dropped.
void record(T snapshot) {
if (_undoStack.length >= _cap) {
_undoStack.removeAt(0);
}
_undoStack.add(snapshot);
_redoStack.clear();
}
/// Undo the last recorded mutation.
///
/// Returns the snapshot to restore, pushing [current] (the live state at
/// the moment of calling) onto the redo stack. Returns `null` if [canUndo]
/// is false.
T? undo(T current) {
if (!canUndo) return null;
_redoStack.add(current);
return _undoStack.removeLast();
}
/// Redo the last undone mutation.
///
/// Returns the snapshot to restore and pushes it back onto the undo stack
/// so it can be undone again. Returns `null` if [canRedo] is false.
T? redo() {
if (!canRedo) return null;
final snapshot = _redoStack.removeLast();
_undoStack.add(snapshot);
return snapshot;
}
/// Clear both stacks.
void clear() {
_undoStack.clear();
_redoStack.clear();
}
}