From 5201c045128cbb3a229a185ce6f9ccad04ff3713 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Fri, 10 Nov 2023 12:36:21 -0800 Subject: [PATCH] egui: add redo support to Undoer (#3478) * Closes #3447 * Closes #3448 Better implementation than #3448. (by accident since I did not see that PR) --- crates/egui/src/util/undoer.rs | 34 +++++++++++++++++--- crates/egui/src/widgets/text_edit/builder.rs | 22 +++++++++++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/crates/egui/src/util/undoer.rs b/crates/egui/src/util/undoer.rs index b5e272aff..de6d27161 100644 --- a/crates/egui/src/util/undoer.rs +++ b/crates/egui/src/util/undoer.rs @@ -57,15 +57,22 @@ pub struct Undoer { /// The latest undo point may (often) be the current state. undos: VecDeque, + /// 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, + #[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() } } @@ -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() @@ -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. @@ -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()); @@ -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 { diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 612f04cea..7898c2256 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -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() @@ -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,