feat(editor): undo/redo, thumbnails, pen settings
All checks were successful
CI / Windows build (push) Successful in 8m39s
All checks were successful
CI / Windows build (push) Successful in 8m39s
Per the full-refactor plan (P0/P1/P2 modules, all pressure-independent): - engine/undo_stack: generic snapshot undo/redo (per page in the editor) - ui/thumbnail_grid: Drawboard-style lazy thumbnail nav sheet (pdfrx) - input/pen_config + ui/pen_settings_page: configurable side-button / eraser-end action mapping, pressure curve, palm sensitivity, finger drawing, widths (shared_preferences). Button-action mappings persist but consume in the input arbiter later; widths/finger consumed now. Wired into the live editor (undo/redo + grid + settings buttons). 19 new tests.
This commit is contained in:
75
lib/editor/engine/undo_stack.dart
Normal file
75
lib/editor/engine/undo_stack.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user