From ad95227e7320105e955ac59e67a49dcf25088834 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Mon, 16 Oct 2023 17:29:21 -0700 Subject: [PATCH 1/3] egui: add redo support to Undoer Fixes #3447 --- crates/egui/src/util/undoer.rs | 34 ++++++++++++++-- crates/egui/src/widgets/text_edit/builder.rs | 43 ++++++++++++++++++-- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/crates/egui/src/util/undoer.rs b/crates/egui/src/util/undoer.rs index b5e272aff1e..de6d2716171 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 3019fa7fd92..3605a7e4d60 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -978,10 +978,14 @@ fn events( Event::Key { key: Key::Z, pressed: true, - modifiers, + modifiers: + Modifiers { + command: true, + shift: false, + .. + }, .. - } if modifiers.command && !modifiers.shift => { - // TODO(emilk): redo + } => { if let Some((undo_ccursor_range, undo_txt)) = state .undoer .lock() @@ -993,6 +997,39 @@ fn events( None } } + Event::Key { + key: Key::Z, + pressed: true, + modifiers: + Modifiers { + command: true, + shift: true, + .. + }, + .. + } + | Event::Key { + key: Key::Y, + pressed: true, + modifiers: + Modifiers { + command: true, + shift: false, + .. + }, + .. + } => { + 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, From 7b216109ce25ef8aec99724986cff62f772b1ca4 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Fri, 10 Nov 2023 06:34:58 -0800 Subject: [PATCH 2/3] Use `Modifiers::matches` for keyboard shortcuts in TextEdit --- crates/egui/src/widgets/text_edit/builder.rs | 31 ++++---------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 3605a7e4d60..adc72dcb239 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -978,14 +978,9 @@ fn events( Event::Key { key: Key::Z, pressed: true, - modifiers: - Modifiers { - command: true, - shift: false, - .. - }, + modifiers, .. - } => { + } if modifiers.matches(Modifiers::COMMAND) => { if let Some((undo_ccursor_range, undo_txt)) = state .undoer .lock() @@ -998,27 +993,11 @@ fn events( } } Event::Key { - key: Key::Z, - pressed: true, - modifiers: - Modifiers { - command: true, - shift: true, - .. - }, - .. - } - | Event::Key { - key: Key::Y, + key, pressed: true, - modifiers: - Modifiers { - command: true, - shift: false, - .. - }, + 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() From ae25aed2715afe53fcb241a0d17e0cea4caccce9 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Fri, 10 Nov 2023 06:40:03 -0800 Subject: [PATCH 3/3] cargo fmt --- crates/egui/src/widgets/text_edit/builder.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index adc72dcb239..e6e7041ad94 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -997,7 +997,9 @@ fn events( pressed: true, modifiers, .. - } if (modifiers.matches(Modifiers::COMMAND) && *key == Key::Y) || (modifiers.matches(Modifiers::SHIFT | Modifiers::COMMAND) && *key == Key::Z) => { + } 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()