From 3bba2324f75f7c01bc4d33a76f60a055ccb604cd Mon Sep 17 00:00:00 2001 From: Ygor Souza Date: Sat, 7 Oct 2023 20:42:01 +0200 Subject: [PATCH 1/2] Add redo to Undoer Closes #3447 --- crates/egui/src/util/undoer.rs | 43 ++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/util/undoer.rs b/crates/egui/src/util/undoer.rs index b5e272aff1e..294618fb28b 100644 --- a/crates/egui/src/util/undoer.rs +++ b/crates/egui/src/util/undoer.rs @@ -57,15 +57,20 @@ pub struct Undoer { /// The latest undo point may (often) be the current state. undos: VecDeque, + /// The list of redos is a simple LIFO stack. It is updated with the current state when undo is + /// called successfully, and cleared when a new undo point is created. + redos: Vec, + #[cfg_attr(feature = "serde", serde(skip))] flux: Option>, } impl std::fmt::Debug for Undoer { 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() } } @@ -96,10 +101,12 @@ where self.flux.is_some() } + /// Return the previous undo point, if any, and adds the current state to the redo stack. pub fn undo(&mut self, current_state: &State) -> Option<&State> { if self.has_undo(current_state) { self.flux = None; + self.redos.push(current_state.clone()); if self.undos.back() == Some(current_state) { self.undos.pop_back(); } @@ -113,10 +120,12 @@ where /// Add an undo point if, and only if, there has been a change since the latest undo point. /// - /// * `time`: current time in seconds. + /// If an undo point is added, this also clears the redo stack, meaning [`Self::has_redo`] will + /// return false and [`Self::redo`] will return `None`. pub fn add_undo(&mut self, current_state: &State) { if self.undos.back() != Some(current_state) { self.undos.push_back(current_state.clone()); + self.redos.clear(); } while self.undos.len() > self.settings.max_undos { self.undos.pop_front(); @@ -129,6 +138,9 @@ where /// /// * `current_time`: current time in seconds. pub fn feed_state(&mut self, current_time: f64, current_state: &State) { + if self.redos.last() == Some(current_state) { + return; + } match self.undos.back() { None => { // First time feed_state is called. @@ -169,4 +181,31 @@ where } } } + + /// Return true if there is a redo point available in the stack. + /// + /// A redo point is created for each call to [`Self::undo`] (if it doesn't return `None`), but + /// when a new undo point is created, all previous redo points are deleted. + pub fn has_redo(&self, current_state: &State) -> bool { + match self.redos.len() { + 0 => false, + 1 => self.redos.last() != Some(current_state), + _ => true, + } + } + + /// Return the last redo point, if any, and remove it from the stack. + /// + /// A redo point is created for each call to [`Self::undo`] (if it doesn't return `None`), but + /// when a new undo point is created, all previous redo points are deleted. + pub fn redo(&mut self, current_state: &State) -> Option<&State> { + if self.redos.last() == Some(current_state) { + if let Some(state) = self.redos.pop() { + if self.undos.back() != Some(&state) { + self.undos.push_back(state); + } + } + } + self.redos.last() + } } From af8ad6e7778555c4aff32dc251059c76e16ac1cf Mon Sep 17 00:00:00 2001 From: Ygor Souza Date: Sat, 7 Oct 2023 20:42:40 +0200 Subject: [PATCH 2/2] Implement redo for TextEdit Using shortcuts Ctrl/Cmd + Shift + Z or Ctrl/Cmd + Y, like most applications. --- crates/egui/src/widgets/text_edit/builder.rs | 25 +++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 7de4733a059..0d611a580a8 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -903,6 +903,18 @@ fn events( ui.ctx().copy_text(text); } }; + let redo = |cursor_range: CursorRange, text: &mut dyn TextBuffer, state: &mut TextEditState| { + 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.as_str()); + Some(*redo_ccursor_range) + } else { + None + } + }; let mut any_change = false; @@ -985,7 +997,6 @@ fn events( modifiers, .. } if modifiers.command && !modifiers.shift => { - // TODO(emilk): redo if let Some((undo_ccursor_range, undo_txt)) = state .undoer .lock() @@ -997,6 +1008,18 @@ fn events( None } } + Event::Key { + key: Key::Z, + pressed: true, + modifiers, + .. + } if modifiers.command && modifiers.shift => redo(cursor_range, text, state), + Event::Key { + key: Key::Y, + pressed: true, + modifiers, + .. + } if modifiers.command && !modifiers.shift => redo(cursor_range, text, state), Event::Key { key,