Skip to main content

ui/
history.rs

1// history -- undo / redo for an app's own state
2//
3//   History::new(limit)                  empty; at most `limit` steps are kept (the oldest go)
4//   h.apply(&mut state, edit)            run an Edit and record it (the redo steps go)
5//   h.undo(&mut state) / h.redo(&mut state) -> bool
6//   h.can_undo() / can_redo()            undo_label() / redo_label() -> the step's label;
7//                                        undo_value() / redo_value() -> the step as data
8//   h.group_begin(label) / group_end()   the edits between them are one step
9//   h.mark_saved() / dirty()             whether the state differs from the saved one
10//   h.mark_unsaved()                     the state counts as unsaved (a recovered copy)
11//   h.clear()
12//   Snapshot::new(label, before, after)  an Edit for small states: the whole value (Clone)
13//   ui.undo_keys() -> Option<bool>       Ctrl+Z (Some(true)) / Ctrl+Y or Ctrl+Shift+Z
14//                                        (Some(false)) when no text field has the keyboard;
15//                                        the key is consumed
16//
17// An Edit changes the state forward (apply) and back (revert); merge lets a step absorb the
18// next one of the same kind (typing in one field, dragging a slider) so one undo takes it
19// all back.  Text fields keep their own undo; the edits here are the app's.
20
21use alloc::boxed::Box;
22use alloc::string::String;
23use alloc::vec::Vec;
24
25use super::Ui;
26use super::consts::*;
27
28/// one undoable change of a state S
29pub trait Edit<S> {
30    /// do it (again)
31    fn apply(&mut self, s: &mut S);
32    /// take it back
33    fn revert(&mut self, s: &mut S);
34    /// shown in "Rückgängig: …"
35    fn label(&self) -> &str;
36    /// absorb the following edit into this one (true: it is part of this step now)
37    fn merge(&mut self, _next: &mut dyn Edit<S>) -> bool {
38        false
39    }
40    /// (merge) the edit as Any-like data: the merge partner's own value
41    fn value(&self) -> Option<&dyn core::any::Any> {
42        None
43    }
44}
45
46/// an Edit of a whole small value (Clone): before and after
47pub struct Snapshot<T: Clone + 'static> {
48    label: String,
49    before: T,
50    after: T,
51    /// merge key: snapshots with the same non-zero key merge (one step for a drag)
52    key: u32,
53}
54
55impl<T: Clone + 'static> Snapshot<T> {
56    pub fn new(label: &str, before: T, after: T) -> Self {
57        Snapshot { label: String::from(label), before, after, key: 0 }
58    }
59    /// edits with the same key merge into one step
60    pub fn merging(mut self, key: u32) -> Self {
61        self.key = key;
62        self
63    }
64}
65
66impl<T: Clone + 'static> Edit<T> for Snapshot<T> {
67    fn apply(&mut self, s: &mut T) {
68        *s = self.after.clone();
69    }
70    fn revert(&mut self, s: &mut T) {
71        *s = self.before.clone();
72    }
73    fn label(&self) -> &str {
74        &self.label
75    }
76    fn merge(&mut self, next: &mut dyn Edit<T>) -> bool {
77        if self.key == 0 {
78            return false;
79        }
80        match next.value().and_then(|v| v.downcast_ref::<Snapshot<T>>()) {
81            Some(n) if n.key == self.key => {
82                self.after = n.after.clone();
83                true
84            }
85            _ => false,
86        }
87    }
88    fn value(&self) -> Option<&dyn core::any::Any> {
89        Some(self)
90    }
91}
92
93// several edits as one step
94struct Group<S> {
95    label: String,
96    edits: Vec<Box<dyn Edit<S>>>,
97}
98
99impl<S: 'static> Edit<S> for Group<S> {
100    fn apply(&mut self, s: &mut S) {
101        for e in self.edits.iter_mut() {
102            e.apply(s);
103        }
104    }
105    fn revert(&mut self, s: &mut S) {
106        for e in self.edits.iter_mut().rev() {
107            e.revert(s);
108        }
109    }
110    fn label(&self) -> &str {
111        &self.label
112    }
113}
114
115/// the undo and redo steps of a state S
116pub struct History<S> {
117    done: Vec<Box<dyn Edit<S>>>,
118    undone: Vec<Box<dyn Edit<S>>>,
119    limit: usize,
120    /// open groups (label, edits so far)
121    groups: Vec<(String, Vec<Box<dyn Edit<S>>>)>,
122    /// done.len() at the last save (None: the saved state is gone)
123    saved: Option<usize>,
124}
125
126impl<S: 'static> History<S> {
127    pub fn new(limit: usize) -> Self {
128        History { done: Vec::new(), undone: Vec::new(), limit: limit.max(1), groups: Vec::new(), saved: Some(0) }
129    }
130
131    /// run the edit and record it as the newest step (redo steps are gone)
132    pub fn apply(&mut self, s: &mut S, mut edit: Box<dyn Edit<S>>) {
133        edit.apply(s);
134        self.record(edit);
135    }
136
137    /// record an edit that was already applied
138    pub fn record(&mut self, mut edit: Box<dyn Edit<S>>) {
139        if let Some(g) = self.groups.last_mut() {
140            g.1.push(edit);
141            return;
142        }
143        self.undone.clear();
144        if self.saved.is_some_and(|k| k > self.done.len()) {
145            self.saved = None; // the saved state was in the redo steps
146        }
147        // merge into the newest step (not across the saved point)
148        let at_save = self.saved == Some(self.done.len());
149        if let Some(last) = self.done.last_mut() {
150            if !at_save && last.merge(edit.as_mut()) {
151                return;
152            }
153        }
154        self.done.push(edit);
155        if self.done.len() > self.limit {
156            self.done.remove(0);
157            self.saved = match self.saved {
158                Some(0) => None,
159                Some(k) => Some(k - 1),
160                None => None,
161            };
162        }
163    }
164
165    /// take the newest step back -> false when there is none
166    pub fn undo(&mut self, s: &mut S) -> bool {
167        let Some(mut e) = self.done.pop() else { return false };
168        e.revert(s);
169        self.undone.push(e);
170        true
171    }
172
173    /// do the last undone step again -> false when there is none
174    pub fn redo(&mut self, s: &mut S) -> bool {
175        let Some(mut e) = self.undone.pop() else { return false };
176        e.apply(s);
177        self.done.push(e);
178        true
179    }
180
181    pub fn can_undo(&self) -> bool {
182        !self.done.is_empty()
183    }
184    pub fn can_redo(&self) -> bool {
185        !self.undone.is_empty()
186    }
187    /// the label of the step undo would take back
188    pub fn undo_label(&self) -> Option<&str> {
189        self.done.last().map(|e| e.label())
190    }
191    /// the label of the step redo would do again
192    pub fn redo_label(&self) -> Option<&str> {
193        self.undone.last().map(|e| e.label())
194    }
195    /// the step undo would take back as data (Edit::value; None for a group): the app can
196    /// see what kind of edit it is before it runs
197    pub fn undo_value(&self) -> Option<&dyn core::any::Any> {
198        self.done.last().and_then(|e| e.value())
199    }
200    /// the step redo would do again as data (Edit::value)
201    pub fn redo_value(&self) -> Option<&dyn core::any::Any> {
202        self.undone.last().and_then(|e| e.value())
203    }
204
205    /// the following edits are one step (nested groups join the outer one)
206    pub fn group_begin(&mut self, label: &str) {
207        self.groups.push((String::from(label), Vec::new()));
208    }
209
210    /// close the group of group_begin (an empty group records nothing)
211    pub fn group_end(&mut self) {
212        let Some((label, edits)) = self.groups.pop() else { return };
213        if !edits.is_empty() {
214            self.record(Box::new(Group { label, edits }));
215        }
216    }
217
218    /// the state is saved now (dirty() false until the next change)
219    pub fn mark_saved(&mut self) {
220        self.saved = Some(self.done.len());
221    }
222
223    /// the state counts as unsaved until the next mark_saved (a copy recovered after a crash)
224    pub fn mark_unsaved(&mut self) {
225        self.saved = None;
226    }
227
228    /// the state differs from the saved one
229    pub fn dirty(&self) -> bool {
230        self.saved != Some(self.done.len())
231    }
232
233    /// forget every step (the current state counts as saved when it was)
234    pub fn clear(&mut self) {
235        let dirty = self.dirty();
236        self.done.clear();
237        self.undone.clear();
238        self.groups.clear();
239        self.saved = if dirty { None } else { Some(0) };
240    }
241
242    /// the number of undo steps
243    pub fn len(&self) -> usize {
244        self.done.len()
245    }
246
247    pub fn is_empty(&self) -> bool {
248        self.done.is_empty()
249    }
250}
251
252impl Ui {
253    /// undo_keys() -> Some(true) for Ctrl+Z, Some(false) for Ctrl+Y / Ctrl+Shift+Z, when no
254    /// text field has the keyboard (they keep their own undo); the key is consumed
255    pub fn undo_keys(&mut self) -> Option<bool> {
256        // (called before the widgets of the pass: the last pass knows the focused one)
257        if self.focus_is_text() || self.focus_was_text() {
258            return None;
259        }
260        let k = self.key_first();
261        let (vk, m) = (k & 0xFFFF, k >> 16);
262        if m & (MOD_CTRL | MOD_ALT) != MOD_CTRL {
263            return None;
264        }
265        let shift = m & MOD_SHIFT != 0;
266        let r = match vk {
267            0x5A if !shift => Some(true), // Z
268            0x5A => Some(false),          // Shift+Z
269            0x59 if !shift => Some(false), // Y
270            _ => None,
271        };
272        if r.is_some() {
273            self.key(vk);
274        }
275        r
276    }
277}