Skip to main content

agg_gui/
undo.rs

1//! Shared undo / redo infrastructure.
2//!
3//! Mirrors the C# agg-sharp `IUndoRedoCommand` / `UndoBuffer` pattern so that
4//! any subsystem — text editing, layout, graph editing — can participate in a
5//! common, extensible undo stack.
6//!
7//! # Usage
8//!
9//! ```rust,ignore
10//! use agg_gui::undo::{DoUndoActions, UndoBuffer};
11//!
12//! let mut buf = UndoBuffer::new();
13//!
14//! // Execute an action and make it undoable:
15//! let v = std::rc::Rc::new(std::cell::Cell::new(0i32));
16//! let v2 = v.clone();
17//! buf.add_and_do(Box::new(DoUndoActions::new(
18//!     "set value",
19//!     move || v.set(42),
20//!     move || v2.set(0),
21//! )));
22//! ```
23
24// ---------------------------------------------------------------------------
25// IUndoRedoCommand — the core trait
26// ---------------------------------------------------------------------------
27
28/// A named, reversible operation.
29///
30/// Implement this trait to participate in the shared undo/redo stack.
31/// The `do_it` / `undo_it` methods are called by [`UndoBuffer`] on redo and
32/// undo respectively.
33pub trait UndoRedoCommand {
34    /// Short human-readable description, e.g. `"insert text"`.
35    fn name(&self) -> &str;
36    /// Re-apply the operation (called on Redo).
37    fn do_it(&mut self);
38    /// Reverse the operation (called on Undo).
39    fn undo_it(&mut self);
40}
41
42// ---------------------------------------------------------------------------
43// UndoBuffer
44// ---------------------------------------------------------------------------
45
46/// Two-stack undo/redo history buffer.
47///
48/// Mirrors the C# `UndoBuffer` class: when a new action is added the redo
49/// stack is cleared (so a new branch cannot be redone).  The undo stack is
50/// size-limited; the oldest entries are dropped when the limit is exceeded.
51pub struct UndoBuffer {
52    undo_stack: Vec<Box<dyn UndoRedoCommand>>,
53    redo_stack: Vec<Box<dyn UndoRedoCommand>>,
54    max_undos: usize,
55}
56
57impl UndoBuffer {
58    /// Create a new buffer with a default history limit of 200 entries.
59    pub fn new() -> Self {
60        Self {
61            undo_stack: Vec::new(),
62            redo_stack: Vec::new(),
63            max_undos: 200,
64        }
65    }
66
67    /// Set the maximum number of undo steps retained.
68    pub fn with_max_undos(mut self, n: usize) -> Self {
69        self.max_undos = n;
70        self
71    }
72
73    /// Push `cmd` without executing it.
74    ///
75    /// Use this when the action has **already** been applied to the state;
76    /// the command only needs to know how to undo (and redo) it.
77    /// Clears the redo stack.
78    pub fn add(&mut self, cmd: Box<dyn UndoRedoCommand>) {
79        self.redo_stack.clear();
80        self.undo_stack.push(cmd);
81        if self.undo_stack.len() > self.max_undos {
82            self.undo_stack.remove(0);
83        }
84    }
85
86    /// Execute `cmd.do_it()` and push it onto the undo stack.
87    ///
88    /// Use this when the action has **not** yet been applied.
89    pub fn add_and_do(&mut self, mut cmd: Box<dyn UndoRedoCommand>) {
90        cmd.do_it();
91        self.add(cmd);
92    }
93
94    /// Undo the most recent operation.  No-op if the stack is empty.
95    pub fn undo(&mut self) {
96        if let Some(mut cmd) = self.undo_stack.pop() {
97            cmd.undo_it();
98            self.redo_stack.push(cmd);
99        }
100    }
101
102    /// Redo the most recently undone operation.  No-op if the redo stack is empty.
103    pub fn redo(&mut self) {
104        if let Some(mut cmd) = self.redo_stack.pop() {
105            cmd.do_it();
106            self.undo_stack.push(cmd);
107        }
108    }
109
110    /// Returns `true` if there is at least one operation that can be undone.
111    pub fn can_undo(&self) -> bool {
112        !self.undo_stack.is_empty()
113    }
114
115    /// Returns `true` if there is at least one operation that can be redone.
116    pub fn can_redo(&self) -> bool {
117        !self.redo_stack.is_empty()
118    }
119
120    /// Name of the operation that `undo()` would reverse, if any.
121    pub fn undo_name(&self) -> Option<&str> {
122        self.undo_stack.last().map(|c| c.name())
123    }
124
125    /// Name of the operation that `redo()` would re-apply, if any.
126    pub fn redo_name(&self) -> Option<&str> {
127        self.redo_stack.last().map(|c| c.name())
128    }
129
130    /// Discard all undo and redo history.
131    pub fn clear_history(&mut self) {
132        self.undo_stack.clear();
133        self.redo_stack.clear();
134    }
135}
136
137impl Default for UndoBuffer {
138    fn default() -> Self {
139        Self::new()
140    }
141}
142
143// ---------------------------------------------------------------------------
144// DoUndoActions — closure-based command
145// ---------------------------------------------------------------------------
146
147/// A command backed by two closures: one for `do_it` and one for `undo_it`.
148///
149/// This is the Rust equivalent of the C# `DoUndoActions` class.  Use it for
150/// simple operations where capturing state in closures is natural.
151///
152/// For operations that share state with an owning object (e.g. text editing),
153/// consider using `std::rc::Rc<std::cell::RefCell<T>>` to share mutable state
154/// between the owning widget and the undo command closures.
155pub struct DoUndoActions {
156    name: String,
157    do_fn: Box<dyn FnMut()>,
158    undo_fn: Box<dyn FnMut()>,
159}
160
161impl DoUndoActions {
162    /// Create a command with the given `name`, `do_action`, and `undo_action`.
163    pub fn new(
164        name: impl Into<String>,
165        do_action: impl FnMut() + 'static,
166        undo_action: impl FnMut() + 'static,
167    ) -> Self {
168        Self {
169            name: name.into(),
170            do_fn: Box::new(do_action),
171            undo_fn: Box::new(undo_action),
172        }
173    }
174}
175
176impl UndoRedoCommand for DoUndoActions {
177    fn name(&self) -> &str {
178        &self.name
179    }
180    fn do_it(&mut self) {
181        (self.do_fn)()
182    }
183    fn undo_it(&mut self) {
184        (self.undo_fn)()
185    }
186}