use inset_foundation::{App, Handle, HandleId};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum UndoDirection {
Undo,
Redo,
}
pub trait UndoManagerClient: Sized + 'static {
fn handle_platform_undo(self: Handle<Self>, app: &mut App, direction: UndoDirection);
fn undo(self: Handle<Self>, app: &mut App);
fn redo(self: Handle<Self>, app: &mut App);
fn can_undo(self: Handle<Self>, app: &App) -> bool;
fn can_redo(self: Handle<Self>, app: &App) -> bool;
fn as_undo_manager_client(self: Handle<Self>) -> AnyUndoManagerClient {
AnyUndoManagerClient {
id: self.id(),
vtable: const { &UndoManagerClientVTable::of::<Self>() },
}
}
}
struct UndoManagerClientVTable {
handle_platform_undo: fn(&mut App, HandleId, UndoDirection),
undo: fn(&mut App, HandleId),
redo: fn(&mut App, HandleId),
can_undo: fn(&App, HandleId) -> bool,
can_redo: fn(&App, HandleId) -> bool,
}
fn resolve<T: 'static>(id: HandleId) -> Handle<T> {
Handle::from_id(id)
}
impl UndoManagerClientVTable {
const fn of<C: UndoManagerClient>() -> UndoManagerClientVTable {
UndoManagerClientVTable {
handle_platform_undo: |app, id, direction| {
C::handle_platform_undo(resolve(id), app, direction)
},
undo: |app, id| C::undo(resolve(id), app),
redo: |app, id| C::redo(resolve(id), app),
can_undo: |app, id| C::can_undo(resolve(id), app),
can_redo: |app, id| C::can_redo(resolve(id), app),
}
}
}
#[derive(Clone, Copy)]
pub struct AnyUndoManagerClient {
id: HandleId,
vtable: &'static UndoManagerClientVTable,
}
impl AnyUndoManagerClient {
pub fn handle_platform_undo(self, app: &mut App, direction: UndoDirection) {
(self.vtable.handle_platform_undo)(app, self.id, direction);
}
pub fn undo(self, app: &mut App) {
(self.vtable.undo)(app, self.id);
}
pub fn redo(self, app: &mut App) {
(self.vtable.redo)(app, self.id);
}
pub fn can_undo(self, app: &App) -> bool {
(self.vtable.can_undo)(app, self.id)
}
pub fn can_redo(self, app: &App) -> bool {
(self.vtable.can_redo)(app, self.id)
}
}
impl PartialEq for AnyUndoManagerClient {
fn eq(&self, other: &AnyUndoManagerClient) -> bool {
self.id == other.id
}
}
impl Eq for AnyUndoManagerClient {}
impl std::fmt::Debug for AnyUndoManagerClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AnyUndoManagerClient")
.field("id", &self.id)
.finish()
}
}
#[derive(Default)]
pub struct UndoManager {
current_client: Option<AnyUndoManagerClient>,
}
impl UndoManager {
pub fn instance(app: &mut App) -> Handle<UndoManager> {
app.singleton::<UndoManager>()
}
pub fn set_client(app: &mut App, client: Option<AnyUndoManagerClient>) {
let this = Self::instance(app);
app.get_mut(this).current_client = client;
}
pub fn client(app: &mut App) -> Option<AnyUndoManagerClient> {
let this = Self::instance(app);
app.get(this).current_client
}
pub fn set_undo_state(_app: &mut App, _can_undo: bool, _can_redo: bool) {}
pub fn handle_platform_undo(app: &mut App, direction: UndoDirection) {
let this = Self::instance(app);
let client = app.get(this).current_client;
debug_assert!(
client.is_some(),
"There must be a current UndoManagerClient."
);
client
.expect("There must be a current UndoManagerClient.")
.handle_platform_undo(app, direction);
}
}
#[cfg(test)]
mod tests {
use super::*;
use inset_foundation::AppCell;
#[derive(Default)]
struct RecordingClient {
directions: Vec<UndoDirection>,
undos: usize,
redos: usize,
}
impl UndoManagerClient for RecordingClient {
fn handle_platform_undo(self: Handle<Self>, app: &mut App, direction: UndoDirection) {
app.get_mut(self).directions.push(direction);
match direction {
UndoDirection::Undo => self.undo(app),
UndoDirection::Redo => self.redo(app),
}
}
fn undo(self: Handle<Self>, app: &mut App) {
app.get_mut(self).undos += 1;
}
fn redo(self: Handle<Self>, app: &mut App) {
app.get_mut(self).redos += 1;
}
fn can_undo(self: Handle<Self>, _app: &App) -> bool {
false
}
fn can_redo(self: Handle<Self>, _app: &App) -> bool {
false
}
}
#[test]
fn set_client_receives_handle_platform_undo() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let client = app.create(RecordingClient::default());
UndoManager::set_client(&mut app, Some(client.as_undo_manager_client()));
assert_eq!(
UndoManager::client(&mut app),
Some(client.as_undo_manager_client())
);
UndoManager::handle_platform_undo(&mut app, UndoDirection::Undo);
UndoManager::handle_platform_undo(&mut app, UndoDirection::Redo);
assert_eq!(
app.get(client).directions,
[UndoDirection::Undo, UndoDirection::Redo]
);
assert_eq!(app.get(client).undos, 1);
assert_eq!(app.get(client).redos, 1);
UndoManager::set_client(&mut app, None);
assert!(UndoManager::client(&mut app).is_none());
UndoManager::set_undo_state(&mut app, true, true);
}
}