Skip to content

Commit 0450cc0

Browse files
committed
egui: add redo support to Undoer
Fixes #3447
1 parent 2338a85 commit 0450cc0

File tree

2 files changed

+54
-7
lines changed

2 files changed

+54
-7
lines changed

crates/egui/src/util/undoer.rs

+30-4
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,22 @@ pub struct Undoer<State> {
5757
/// The latest undo point may (often) be the current state.
5858
undos: VecDeque<State>,
5959

60+
/// Stores redos immediately after a sequence of undos.
61+
/// Gets cleared every time the state changes.
62+
/// Does not need to be a deque, because there can only be up to undos.len() redos,
63+
/// which is already limited to settings.max_undos.
64+
redos: Vec<State>,
65+
6066
#[cfg_attr(feature = "serde", serde(skip))]
6167
flux: Option<Flux<State>>,
6268
}
6369

6470
impl<State> std::fmt::Debug for Undoer<State> {
6571
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66-
let Self { undos, .. } = self;
72+
let Self { undos, redos, .. } = self;
6773
f.debug_struct("Undoer")
6874
.field("undo count", &undos.len())
75+
.field("redo count", &redos.len())
6976
.finish()
7077
}
7178
}
@@ -91,6 +98,10 @@ where
9198
}
9299
}
93100

101+
pub fn has_redo(&self, current_state: &State) -> bool {
102+
!self.redos.is_empty() && self.undos.back() == Some(current_state)
103+
}
104+
94105
/// Return true if the state is currently changing
95106
pub fn is_in_flux(&self) -> bool {
96107
self.flux.is_some()
@@ -101,7 +112,9 @@ where
101112
self.flux = None;
102113

103114
if self.undos.back() == Some(current_state) {
104-
self.undos.pop_back();
115+
self.redos.push(self.undos.pop_back().unwrap());
116+
} else {
117+
self.redos.push(current_state.clone());
105118
}
106119

107120
// Note: we keep the undo point intact.
@@ -111,9 +124,20 @@ where
111124
}
112125
}
113126

127+
pub fn redo(&mut self, current_state: &State) -> Option<&State> {
128+
if !self.undos.is_empty() && self.undos.back() != Some(current_state) {
129+
// state changed since the last undo, redos should be cleared.
130+
self.redos.clear();
131+
None
132+
} else if let Some(state) = self.redos.pop() {
133+
self.undos.push_back(state);
134+
self.undos.back()
135+
} else {
136+
None
137+
}
138+
}
139+
114140
/// Add an undo point if, and only if, there has been a change since the latest undo point.
115-
///
116-
/// * `time`: current time in seconds.
117141
pub fn add_undo(&mut self, current_state: &State) {
118142
if self.undos.back() != Some(current_state) {
119143
self.undos.push_back(current_state.clone());
@@ -139,6 +163,8 @@ where
139163
if latest_undo == current_state {
140164
self.flux = None;
141165
} else {
166+
self.redos.clear();
167+
142168
match self.flux.as_mut() {
143169
None => {
144170
self.flux = Some(Flux {

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

+24-3
Original file line numberDiff line numberDiff line change
@@ -978,10 +978,9 @@ fn events(
978978
Event::Key {
979979
key: Key::Z,
980980
pressed: true,
981-
modifiers,
981+
modifiers: Modifiers { command: true, shift: false, .. },
982982
..
983-
} if modifiers.command && !modifiers.shift => {
984-
// TODO(emilk): redo
983+
} => {
985984
if let Some((undo_ccursor_range, undo_txt)) = state
986985
.undoer
987986
.lock()
@@ -993,6 +992,28 @@ fn events(
993992
None
994993
}
995994
}
995+
Event::Key {
996+
key: Key::Z,
997+
pressed: true,
998+
modifiers: Modifiers { command: true, shift: true, .. },
999+
..
1000+
} | Event::Key {
1001+
key: Key::Y,
1002+
pressed: true,
1003+
modifiers: Modifiers { command: true, shift: false, .. },
1004+
..
1005+
} => {
1006+
if let Some((redo_ccursor_range, redo_txt)) = state
1007+
.undoer
1008+
.lock()
1009+
.redo(&(cursor_range.as_ccursor_range(), text.as_str().to_owned()))
1010+
{
1011+
text.replace(redo_txt);
1012+
Some(*redo_ccursor_range)
1013+
} else {
1014+
None
1015+
}
1016+
}
9961017

9971018
Event::Key {
9981019
key,

0 commit comments

Comments
 (0)