Browse Source

egui: add redo support to Undoer (#3478)

* Closes #3447
* Closes #3448

Better implementation than #3448. (by accident since I did not see that
PR)
pull/3543/head
LoganDark 1 year ago
committed by GitHub
parent
commit
5201c04512
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 34
      crates/egui/src/util/undoer.rs
  2. 22
      crates/egui/src/widgets/text_edit/builder.rs

34
crates/egui/src/util/undoer.rs

@ -57,15 +57,22 @@ pub struct Undoer<State> {
/// The latest undo point may (often) be the current state. /// The latest undo point may (often) be the current state.
undos: VecDeque<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))] #[cfg_attr(feature = "serde", serde(skip))]
flux: Option<Flux<State>>, flux: Option<Flux<State>>,
} }
impl<State> std::fmt::Debug for Undoer<State> { impl<State> std::fmt::Debug for Undoer<State> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { undos, .. } = self; let Self { undos, redos, .. } = self;
f.debug_struct("Undoer") f.debug_struct("Undoer")
.field("undo count", &undos.len()) .field("undo count", &undos.len())
.field("redo count", &redos.len())
.finish() .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 /// Return true if the state is currently changing
pub fn is_in_flux(&self) -> bool { pub fn is_in_flux(&self) -> bool {
self.flux.is_some() self.flux.is_some()
@ -101,7 +112,9 @@ where
self.flux = None; self.flux = None;
if self.undos.back() == Some(current_state) { 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. // 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. /// 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) { pub fn add_undo(&mut self, current_state: &State) {
if self.undos.back() != Some(current_state) { if self.undos.back() != Some(current_state) {
self.undos.push_back(current_state.clone()); self.undos.push_back(current_state.clone());
@ -139,6 +163,8 @@ where
if latest_undo == current_state { if latest_undo == current_state {
self.flux = None; self.flux = None;
} else { } else {
self.redos.clear();
match self.flux.as_mut() { match self.flux.as_mut() {
None => { None => {
self.flux = Some(Flux { self.flux = Some(Flux {

22
crates/egui/src/widgets/text_edit/builder.rs

@ -986,8 +986,7 @@ fn events(
pressed: true, pressed: true,
modifiers, modifiers,
.. ..
} if modifiers.command && !modifiers.shift => { } if modifiers.matches(Modifiers::COMMAND) => {
// TODO(emilk): redo
if let Some((undo_ccursor_range, undo_txt)) = state if let Some((undo_ccursor_range, undo_txt)) = state
.undoer .undoer
.lock() .lock()
@ -999,6 +998,25 @@ fn events(
None 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 { Event::Key {
key, key,

Loading…
Cancel
Save