History (Undo/Redo)
Relevant source files
The following files were used as context for generating this wiki page:
The History system in Dotorixel provides robust undo and redo capabilities by managing snapshots of the canvas state. It is implemented primarily in the Rust core to ensure high performance and cross-platform consistency across web (WASM) and Apple native (UniFFI) environments. The system allows users to revert changes to pixel data and canvas dimensions, maintaining a linear history of operations.
Sources: tasks/records/003-history-undo-redo.md, tasks/records/058-resize-undo-redo.md, crates/core/src/history.rs
Core Architecture
The history logic is centered around a HistoryManager that maintains two double-ended queues (VecDeque) for undo and redo stacks. Each entry in these stacks is a Snapshot containing the complete pixel buffer and the dimensions of the canvas at that point in time.
Data Structures
| Structure | Description | Key Fields |
|---|---|---|
Snapshot | Represents a single state of the canvas. | width: u32, height: u32, pixels: Vec<u8> |
HistoryManager | Manages the undo/redo stacks and stack eviction. | undo_stack, redo_stack |
Sources: crates/core/src/history.rs:7-12, crates/core/src/history.rs:18-22
Component Relationships
The following diagram illustrates how the HistoryManager interacts with the PixelCanvas and the shell layers.
The system uses a "snapshot-restore" approach where the entire canvas state is captured. To optimize memory, the HistoryManager uses VecDeque to allow O(1) eviction of the oldest snapshots when the history limit is reached.
Sources: tasks/records/003-history-undo-redo.md, tasks/records/020-line-tool.md
State Management and Operations
The HistoryManager provides several public methods to manipulate the history stacks. Every time a drawing operation is completed or the canvas is resized, a new snapshot is pushed onto the undo stack.
Key Methods
push_snapshot(snapshot: Snapshot): Adds a new state to the undo stack. Critically, this operation clears the redo stack to maintain a linear history from the new point.undo(current_state: Snapshot) -> Option<Snapshot>: Moves thecurrent_stateto the redo stack and returns the most recent snapshot from the undo stack.redo(current_state: Snapshot) -> Option<Snapshot>: Moves thecurrent_stateto the undo stack and returns the most recent snapshot from the redo stack.clear(): Empties both stacks, typically used when a document is closed or a major non-undoable operation occurs.
Sources: crates/core/src/history.rs:32-60, wasm/src/lib.rs:180-210
Transaction Flow
The sequence of an undo operation involves exchanging the active canvas state with the top of the history stack.
Dimension-Aware Snapshots
Initially, the history system only stored raw pixel buffers. This caused history to be cleared during canvas resizing because the buffer sizes would no longer match. The system was upgraded to include width and height metadata within the Snapshot struct.
This allows the undo/redo chain to seamlessly handle canvas resizes. When a resize is undone, the PixelCanvas is reconstructed using a from_pixels constructor that handles the change in dimensions.
Sources: tasks/records/058-resize-undo-redo.md, crates/core/src/history.rs:7-12
Platform Integration
The history system is exposed to different platforms via specific bindings.
Web (WASM)
The WasmHistoryManager wraps the Rust core manager and provides methods like push_snapshot, undo, and redo. It uses a WasmSnapshot object to transfer data between TypeScript and Rust. In the TypeScript EditorState, a #applySnapshot helper is used to shared the logic between undo and redo operations.
Sources: wasm/src/lib.rs:165-220, src/lib/canvas/editor-state.svelte.ts:250-265
Apple Native (SwiftUI)
On Apple platforms, the EditorState uses a historyVersion counter to notify SwiftUI when UniFFI-managed history state changes. Because the internal stacks are not directly observable by SwiftUI, this version increment forces the UI to re-evaluate the canUndo and canRedo properties for toolbar button states.
Sources: apple/Dotorixel/State/EditorState.swift:20-25, tasks/records/017-undo-redo-apple-native.md
Summary
Dotorixel's History system is a snapshot-based architecture implemented in Rust for efficiency and shared across platforms. By encapsulating dimensions within the snapshot data, it supports complex operations like canvas resizing within the undo/redo chain. The system maintains linear history by clearing the redo stack upon new actions and uses memory-efficient data structures to manage state eviction.
Sources: tasks/records/003-history-undo-redo.md, tasks/records/058-resize-undo-redo.md