// 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>(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 { UndoStack({int cap = 50}) : _cap = cap; final int _cap; // Index 0 = oldest, last = most-recent snapshot available for undo. final List _undoStack = []; final List _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(); } }