Skip to content

Commit

Permalink
egui: add redo support to Undoer (#3478)
Browse files Browse the repository at this point in the history
* Closes #3447
* Closes #3448

Better implementation than #3448. (by accident since I did not see that
PR)
  • Loading branch information
LoganDark authored Nov 10, 2023
1 parent c0b14f4 commit 5201c04
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 6 deletions.
34 changes: 30 additions & 4 deletions crates/egui/src/util/undoer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,22 @@ pub struct Undoer<State> {
/// The latest undo point may (often) be the current state.
undos: VecDeque<State>,

/// Stores redos immediately after a sequence of undos.
/// Gets cleared every time the state changes.
/// Does not need to be a deque, because there can only be up to undos.len() redos,
/// which is already limited to settings.max_undos.
redos: Vec<State>,

#[cfg_attr(feature = "serde", serde(skip))]
flux: Option<Flux<State>>,
}

impl<State> std::fmt::Debug for Undoer<State> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { undos, .. } = self;
let Self { undos, redos, .. } = self;
f.debug_struct("Undoer")
.field("undo count", &undos.len())
.field("redo count", &redos.len())
.finish()
}
}
Expand All @@ -91,6 +98,10 @@ where
}
}

pub fn has_redo(&self, current_state: &State) -> bool {
!self.redos.is_empty() && self.undos.back() == Some(current_state)
}

/// Return true if the state is currently changing
pub fn is_in_flux(&self) -> bool {
self.flux.is_some()
Expand All @@ -101,7 +112,9 @@ where
self.flux = None;

if self.undos.back() == Some(current_state) {
self.undos.pop_back();
self.redos.push(self.undos.pop_back().unwrap());
} else {
self.redos.push(current_state.clone());
}

// Note: we keep the undo point intact.
Expand All @@ -111,9 +124,20 @@ where
}
}

pub fn redo(&mut self, current_state: &State) -> Option<&State> {
if !self.undos.is_empty() && self.undos.back() != Some(current_state) {
// state changed since the last undo, redos should be cleared.
self.redos.clear();
None
} else if let Some(state) = self.redos.pop() {
self.undos.push_back(state);
self.undos.back()
} else {
None
}
}

/// Add an undo point if, and only if, there has been a change since the latest undo point.
///
/// * `time`: current time in seconds.
pub fn add_undo(&mut self, current_state: &State) {
if self.undos.back() != Some(current_state) {
self.undos.push_back(current_state.clone());
Expand All @@ -139,6 +163,8 @@ where
if latest_undo == current_state {
self.flux = None;
} else {
self.redos.clear();

match self.flux.as_mut() {
None => {
self.flux = Some(Flux {
Expand Down
22 changes: 20 additions & 2 deletions crates/egui/src/widgets/text_edit/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -986,8 +986,7 @@ fn events(
pressed: true,
modifiers,
..
} if modifiers.command && !modifiers.shift => {
// TODO(emilk): redo
} if modifiers.matches(Modifiers::COMMAND) => {
if let Some((undo_ccursor_range, undo_txt)) = state
.undoer
.lock()
Expand All @@ -999,6 +998,25 @@ fn events(
None
}
}
Event::Key {
key,
pressed: true,
modifiers,
..
} if (modifiers.matches(Modifiers::COMMAND) && *key == Key::Y)
|| (modifiers.matches(Modifiers::SHIFT | Modifiers::COMMAND) && *key == Key::Z) =>
{
if let Some((redo_ccursor_range, redo_txt)) = state
.undoer
.lock()
.redo(&(cursor_range.as_ccursor_range(), text.as_str().to_owned()))
{
text.replace(redo_txt);
Some(*redo_ccursor_range)
} else {
None
}
}

Event::Key {
key,
Expand Down

0 comments on commit 5201c04

Please sign in to comment.