use crate::canvas::undo::UndoStack;
use egui::Response;
#[derive(Debug)]
pub(crate) struct Edit<'a, T> {
pub(crate) value: &'a mut T,
undo: &'a mut UndoStack<T>,
changed: bool,
}
impl<'a, T: Clone + PartialEq> Edit<'a, T> {
pub(crate) fn new(value: &'a mut T, undo: &'a mut UndoStack<T>) -> Self {
Self {
value,
undo,
changed: false,
}
}
pub(crate) fn apply<R>(&mut self, change: impl FnOnce(&mut T) -> R) -> R {
let before = self.value.clone();
let result = change(self.value);
if *self.value != before {
self.undo.record(&before);
self.changed = true;
}
result
}
pub(crate) fn begin(&mut self) {
self.undo.record(self.value);
}
pub(crate) fn continue_change(&mut self, change: impl FnOnce(&mut T)) {
change(self.value);
self.changed = true;
}
pub(crate) fn end(&mut self) {
self.undo.discard_if_unchanged(self.value);
}
pub(crate) fn tracked(&mut self, response: &Response, change: impl FnOnce(&mut T)) {
if response.gained_focus() || response.drag_started() {
self.begin();
}
if response.changed() {
let ongoing = response.has_focus() || response.dragged() || response.drag_stopped();
if !ongoing {
self.begin();
}
self.continue_change(change);
}
if response.lost_focus() || response.drag_stopped() {
self.end();
}
}
pub(crate) fn undo(&mut self) -> bool {
let done = self.undo.undo(self.value);
self.changed |= done;
done
}
pub(crate) fn redo(&mut self) -> bool {
let done = self.undo.redo(self.value);
self.changed |= done;
done
}
pub(crate) fn can_undo(&self) -> bool {
self.undo.can_undo()
}
pub(crate) fn can_redo(&self) -> bool {
self.undo.can_redo()
}
pub(crate) fn changed(&self) -> bool {
self.changed
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_change_is_one_undo_step() {
let mut value = 1;
let mut undo = UndoStack::default();
let mut edit = Edit::new(&mut value, &mut undo);
edit.apply(|value| *value = 2);
assert!(edit.changed());
assert!(edit.undo());
assert_eq!(*edit.value, 1);
assert!(edit.redo());
assert_eq!(*edit.value, 2);
}
#[test]
fn a_change_that_changes_nothing_leaves_no_step() {
let mut value = 1;
let mut undo = UndoStack::default();
let mut edit = Edit::new(&mut value, &mut undo);
edit.apply(|value| *value = 1);
assert!(!edit.changed());
assert!(!edit.can_undo());
}
#[test]
fn a_drag_over_many_frames_is_one_step() {
let mut value = 0;
let mut undo = UndoStack::default();
let mut edit = Edit::new(&mut value, &mut undo);
edit.begin();
for frame in 1..=10 {
edit.continue_change(|value| *value = frame);
}
edit.end();
assert!(edit.undo());
assert_eq!(*edit.value, 0);
assert!(!edit.can_undo());
}
#[test]
fn a_drag_that_moved_nothing_leaves_no_step() {
let mut value = 0;
let mut undo = UndoStack::default();
let mut edit = Edit::new(&mut value, &mut undo);
edit.begin();
edit.end();
assert!(!edit.can_undo());
}
#[test]
fn an_edit_that_changed_nothing_keeps_redo() {
let mut value = 0;
let mut undo = UndoStack::default();
let mut edit = Edit::new(&mut value, &mut undo);
edit.apply(|value| *value = 1);
edit.undo();
edit.begin();
edit.end();
assert!(edit.redo());
assert_eq!(*edit.value, 1);
}
}