1use inset_foundation::{App, Handle, HandleId};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub enum UndoDirection {
11 Undo,
13 Redo,
15}
16
17pub trait UndoManagerClient: Sized + 'static {
21 fn handle_platform_undo(self: Handle<Self>, app: &mut App, direction: UndoDirection);
27
28 fn undo(self: Handle<Self>, app: &mut App);
30
31 fn redo(self: Handle<Self>, app: &mut App);
33
34 fn can_undo(self: Handle<Self>, app: &App) -> bool;
36
37 fn can_redo(self: Handle<Self>, app: &App) -> bool;
39
40 fn as_undo_manager_client(self: Handle<Self>) -> AnyUndoManagerClient {
42 AnyUndoManagerClient {
43 id: self.id(),
44 vtable: const { &UndoManagerClientVTable::of::<Self>() },
45 }
46 }
47}
48
49struct UndoManagerClientVTable {
50 handle_platform_undo: fn(&mut App, HandleId, UndoDirection),
51 undo: fn(&mut App, HandleId),
52 redo: fn(&mut App, HandleId),
53 can_undo: fn(&App, HandleId) -> bool,
54 can_redo: fn(&App, HandleId) -> bool,
55}
56
57fn resolve<T: 'static>(id: HandleId) -> Handle<T> {
58 Handle::from_id(id)
59}
60
61impl UndoManagerClientVTable {
62 const fn of<C: UndoManagerClient>() -> UndoManagerClientVTable {
63 UndoManagerClientVTable {
64 handle_platform_undo: |app, id, direction| {
65 C::handle_platform_undo(resolve(id), app, direction)
66 },
67 undo: |app, id| C::undo(resolve(id), app),
68 redo: |app, id| C::redo(resolve(id), app),
69 can_undo: |app, id| C::can_undo(resolve(id), app),
70 can_redo: |app, id| C::can_redo(resolve(id), app),
71 }
72 }
73}
74
75#[derive(Clone, Copy)]
77pub struct AnyUndoManagerClient {
78 id: HandleId,
79 vtable: &'static UndoManagerClientVTable,
80}
81
82impl AnyUndoManagerClient {
83 pub fn handle_platform_undo(self, app: &mut App, direction: UndoDirection) {
85 (self.vtable.handle_platform_undo)(app, self.id, direction);
86 }
87
88 pub fn undo(self, app: &mut App) {
90 (self.vtable.undo)(app, self.id);
91 }
92
93 pub fn redo(self, app: &mut App) {
95 (self.vtable.redo)(app, self.id);
96 }
97
98 pub fn can_undo(self, app: &App) -> bool {
100 (self.vtable.can_undo)(app, self.id)
101 }
102
103 pub fn can_redo(self, app: &App) -> bool {
105 (self.vtable.can_redo)(app, self.id)
106 }
107}
108
109impl PartialEq for AnyUndoManagerClient {
110 fn eq(&self, other: &AnyUndoManagerClient) -> bool {
111 self.id == other.id
112 }
113}
114
115impl Eq for AnyUndoManagerClient {}
116
117impl std::fmt::Debug for AnyUndoManagerClient {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 f.debug_struct("AnyUndoManagerClient")
120 .field("id", &self.id)
121 .finish()
122 }
123}
124
125#[derive(Default)]
147pub struct UndoManager {
148 current_client: Option<AnyUndoManagerClient>,
149}
150
151impl UndoManager {
152 pub fn instance(app: &mut App) -> Handle<UndoManager> {
154 app.singleton::<UndoManager>()
155 }
156
157 pub fn set_client(app: &mut App, client: Option<AnyUndoManagerClient>) {
163 let this = Self::instance(app);
164 app.get_mut(this).current_client = client;
165 }
166
167 pub fn client(app: &mut App) -> Option<AnyUndoManagerClient> {
169 let this = Self::instance(app);
170 app.get(this).current_client
171 }
172
173 pub fn set_undo_state(_app: &mut App, _can_undo: bool, _can_redo: bool) {}
178
179 pub fn handle_platform_undo(app: &mut App, direction: UndoDirection) {
181 let this = Self::instance(app);
182 let client = app.get(this).current_client;
183 debug_assert!(
184 client.is_some(),
185 "There must be a current UndoManagerClient."
186 );
187 client
188 .expect("There must be a current UndoManagerClient.")
189 .handle_platform_undo(app, direction);
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use inset_foundation::AppCell;
197
198 #[derive(Default)]
199 struct RecordingClient {
200 directions: Vec<UndoDirection>,
201 undos: usize,
202 redos: usize,
203 }
204
205 impl UndoManagerClient for RecordingClient {
206 fn handle_platform_undo(self: Handle<Self>, app: &mut App, direction: UndoDirection) {
207 app.get_mut(self).directions.push(direction);
208 match direction {
209 UndoDirection::Undo => self.undo(app),
210 UndoDirection::Redo => self.redo(app),
211 }
212 }
213
214 fn undo(self: Handle<Self>, app: &mut App) {
215 app.get_mut(self).undos += 1;
216 }
217
218 fn redo(self: Handle<Self>, app: &mut App) {
219 app.get_mut(self).redos += 1;
220 }
221
222 fn can_undo(self: Handle<Self>, _app: &App) -> bool {
223 false
224 }
225
226 fn can_redo(self: Handle<Self>, _app: &App) -> bool {
227 false
228 }
229 }
230
231 #[test]
232 fn set_client_receives_handle_platform_undo() {
233 let cell = AppCell::new();
234 let mut app = cell.borrow_mut();
235 let client = app.create(RecordingClient::default());
236 UndoManager::set_client(&mut app, Some(client.as_undo_manager_client()));
237 assert_eq!(
238 UndoManager::client(&mut app),
239 Some(client.as_undo_manager_client())
240 );
241
242 UndoManager::handle_platform_undo(&mut app, UndoDirection::Undo);
243 UndoManager::handle_platform_undo(&mut app, UndoDirection::Redo);
244 assert_eq!(
245 app.get(client).directions,
246 [UndoDirection::Undo, UndoDirection::Redo]
247 );
248 assert_eq!(app.get(client).undos, 1);
249 assert_eq!(app.get(client).redos, 1);
250
251 UndoManager::set_client(&mut app, None);
252 assert!(UndoManager::client(&mut app).is_none());
253 UndoManager::set_undo_state(&mut app, true, true);
254 }
255}