Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add redo to undoer #3448

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions crates/egui/src/util/undoer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,20 @@ pub struct Undoer<State> {
/// The latest undo point may (often) be the current state.
undos: VecDeque<State>,

/// 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<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 Down Expand Up @@ -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();
}
Expand All @@ -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();
Expand All @@ -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.
Expand Down Expand Up @@ -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()
}
}
25 changes: 24 additions & 1 deletion crates/egui/src/widgets/text_edit/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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()
Expand All @@ -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,
Expand Down
Loading