import '../models/ink_stroke.dart'; /// Manages undo/redo state for ink strokes. /// /// Each action records a stroke that was added or removed. /// [undo] returns the inverse of the last action (remove if add, add if remove). /// [redo] re-applies the undone action. class UndoManager { final List<_UndoAction> _undoStack = []; final List<_UndoAction> _redoStack = []; final List _strokes = []; /// The current list of strokes (read-only view). List get currentStrokes => List.unmodifiable(_strokes); bool get canUndo => _undoStack.isNotEmpty; bool get canRedo => _redoStack.isNotEmpty; /// Records that a new stroke was added to the canvas. void addStroke(InkStroke stroke) { _strokes.add(stroke); _undoStack.add(_UndoAction(type: _ActionType.add, stroke: stroke)); _redoStack.clear(); } /// Records that a stroke was removed from the canvas. /// Also handles partial-eraser replacements: removes [stroke] and adds /// [replacements] (which may be empty if fully erased, or 1-2 sub-strokes). void removeStroke( InkStroke stroke, { List replacements = const [], }) { _strokes.removeWhere((s) => s.id == stroke.id); _strokes.addAll(replacements); _undoStack.add( _UndoAction( type: _ActionType.remove, stroke: stroke, replacements: replacements, ), ); _redoStack.clear(); } /// Undoes the last action. Returns the stroke that was affected and needs /// to be reversed on the canvas, or `null` if nothing to undo. /// /// For add actions: the stroke should be removed from the canvas. /// For remove actions: the stroke (and its replacements) should be restored. InkStroke? undo() { if (_undoStack.isEmpty) return null; final action = _undoStack.removeLast(); _redoStack.add(action); switch (action.type) { case _ActionType.add: _strokes.removeWhere((s) => s.id == action.stroke.id); return action.stroke; case _ActionType.remove: // Remove the replacements that were added during the original remove for (final r in action.replacements) { _strokes.removeWhere((s) => s.id == r.id); } _strokes.add(action.stroke); return action.stroke; } } /// Redoes the last undone action. Returns the stroke that was affected, or /// `null` if nothing to redo. InkStroke? redo() { if (_redoStack.isEmpty) return null; final action = _redoStack.removeLast(); _undoStack.add(action); switch (action.type) { case _ActionType.add: _strokes.add(action.stroke); return action.stroke; case _ActionType.remove: _strokes.removeWhere((s) => s.id == action.stroke.id); _strokes.addAll(action.replacements); return action.stroke; } } } enum _ActionType { add, remove } class _UndoAction { final _ActionType type; final InkStroke stroke; final List replacements; _UndoAction({ required this.type, required this.stroke, this.replacements = const [], }); }