76 lines
2.4 KiB
Dart
76 lines
2.4 KiB
Dart
|
|
// 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();
|
||
|
|
}
|
||
|
|
}
|