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

egui: add redo support to Undoer #3478

Merged
merged 3 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
34 changes: 30 additions & 4 deletions crates/egui/src/util/undoer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,22 @@ pub struct Undoer<State> {
/// The latest undo point may (often) be the current 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))]
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 All @@ -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()
Expand All @@ -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.
Expand All @@ -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());
Expand All @@ -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 {
Expand Down
43 changes: 40 additions & 3 deletions crates/egui/src/widgets/text_edit/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
} => {
LoganDark marked this conversation as resolved.
Show resolved Hide resolved
if let Some((undo_ccursor_range, undo_txt)) = state
.undoer
.lock()
Expand All @@ -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,
Expand Down
Loading