1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
15#![warn(unused_extern_crates)]
16#![warn(missing_docs)]
17#![recursion_limit = "256"]
18#![expect(clippy::type_complexity)]
20
21use std::{
22 any::Any,
23 fmt, mem,
24 sync::{
25 Arc,
26 atomic::{AtomicBool, Ordering},
27 },
28 time::Duration,
29};
30
31use atomic::Atomic;
32use parking_lot::Mutex;
33use zng_app::{
34 DInstant, INSTANT,
35 event::{Command, CommandNameExt, CommandScope, command},
36 hn,
37 shortcut::{CommandShortcutExt, shortcut},
38 widget::{
39 WIDGET, WidgetId,
40 info::{WidgetInfo, WidgetInfoBuilder},
41 },
42};
43use zng_app_context::{RunOnDrop, app_local, context_local};
44use zng_clone_move::clmv;
45use zng_ext_input::{focus::cmd::CommandFocusExt, keyboard::KEYBOARD};
46use zng_state_map::{StateId, StateMapRef, static_id};
47use zng_txt::Txt;
48use zng_var::{Var, VarHandle, VarValue, context_var, var};
49use zng_wgt::{CommandIconExt as _, ICONS, wgt_fn};
50
51mod private {
52 pub trait Sealed {}
54}
55
56context_var! {
57 pub static UNDO_LIMIT_VAR: u32 = UNDO.undo_limit();
63
64 pub static UNDO_INTERVAL_VAR: Duration = UNDO.undo_interval();
70}
71
72pub struct UNDO;
74impl UNDO {
75 pub fn undo_limit(&self) -> Var<u32> {
82 UNDO_SV.read().undo_limit.clone()
83 }
84
85 pub fn undo_interval(&self) -> Var<Duration> {
99 UNDO_SV.read().undo_interval.clone()
100 }
101
102 pub fn is_enabled(&self) -> bool {
108 UNDO_SCOPE_CTX.get().enabled.load(Ordering::Relaxed) && UNDO_SV.read().undo_limit.get() > 0
109 }
110
111 pub fn undo_select(&self, selector: impl UndoSelector) {
121 UNDO_SCOPE_CTX.get().undo_select(selector);
122 }
123
124 pub fn redo_select(&self, selector: impl UndoSelector) {
126 UNDO_SCOPE_CTX.get().redo_select(selector);
127 }
128
129 pub fn undo(&self) {
133 self.undo_select(UNDO_INTERVAL_VAR.get());
134 }
135
136 pub fn redo(&self) {
140 self.redo_select(UNDO_INTERVAL_VAR.get());
141 }
142
143 pub fn scope(&self) -> Option<WidgetId> {
146 UNDO_SCOPE_CTX.get().id()
147 }
148
149 pub fn register(&self, action: impl UndoAction) {
151 UNDO_SCOPE_CTX.get().register(Box::new(action))
152 }
153
154 pub fn register_op(&self, info: impl UndoInfo, op: impl FnMut(UndoOp) + Send + 'static) {
158 self.register(UndoRedoOp {
159 info: info.into_dyn(),
160 op: Box::new(op),
161 })
162 }
163
164 pub fn register_full_op<D>(&self, data: D, mut op: impl FnMut(&mut D, UndoFullOp) + Send + 'static)
168 where
169 D: Any + Send + 'static,
170 {
171 self.register(UndoRedoFullOp {
172 data: Box::new(data),
173 op: Box::new(move |d, o| {
174 op(d.downcast_mut::<D>().unwrap(), o);
175 }),
176 })
177 }
178
179 pub fn run(&self, action: impl RedoAction) {
181 UNDO_SCOPE_CTX.get().register(Box::new(action).redo())
182 }
183
184 pub fn run_op(&self, info: impl UndoInfo, op: impl FnMut(UndoOp) + Send + 'static) {
186 self.run(UndoRedoOp {
187 info: info.into_dyn(),
188 op: Box::new(op),
189 })
190 }
191
192 pub fn run_full_op<D>(&self, mut data: D, mut op: impl FnMut(&mut D, UndoFullOp) + Send + 'static)
194 where
195 D: Any + Send + 'static,
196 {
197 let mut redo = true;
198 op(&mut data, UndoFullOp::Init { redo: &mut redo });
199
200 if redo {
201 self.run(UndoRedoFullOp {
202 data: Box::new(data),
203 op: Box::new(move |d, o| {
204 op(d.downcast_mut::<D>().unwrap(), o);
205 }),
206 })
207 }
208 }
209
210 pub fn group(&self, info: impl UndoInfo, actions: impl FnOnce()) -> bool {
214 let t = self.transaction(actions);
215 let any = !t.is_empty();
216 if any {
217 t.commit_group(info);
218 }
219 any
220 }
221
222 pub fn transaction(&self, actions: impl FnOnce()) -> UndoTransaction {
227 let mut scope = UndoScope::default();
228 let parent_scope = UNDO_SCOPE_CTX.get();
229 *scope.enabled.get_mut() = parent_scope.enabled.load(Ordering::Relaxed);
230 *scope.id.get_mut() = parent_scope.id.load(Ordering::Relaxed);
231
232 let t_scope = Arc::new(scope);
233 let _panic_undo = RunOnDrop::new(clmv!(t_scope, || {
234 for undo in mem::take(&mut *t_scope.undo.lock()).into_iter().rev() {
235 let _ = undo.action.undo();
236 }
237 }));
238
239 let mut scope = Some(t_scope);
240 UNDO_SCOPE_CTX.with_context(&mut scope, actions);
241
242 let scope = scope.unwrap();
243 let undo = mem::take(&mut *scope.undo.lock());
244
245 UndoTransaction { undo }
246 }
247
248 pub fn try_group<O, E>(&self, info: impl UndoInfo, actions: impl FnOnce() -> Result<O, E>) -> Result<O, E> {
253 let mut r = None;
254 let t = self.transaction(|| r = Some(actions()));
255 let r = r.unwrap();
256 if !t.is_empty() {
257 if r.is_ok() {
258 t.commit_group(info);
259 } else {
260 t.undo();
261 }
262 }
263 r
264 }
265
266 pub fn try_commit<O, E>(&self, actions: impl FnOnce() -> Result<O, E>) -> Result<O, E> {
270 let mut r = None;
271 let t = self.transaction(|| r = Some(actions()));
272 let r = r.unwrap();
273 if !t.is_empty() {
274 if r.is_ok() {
275 t.commit();
276 } else {
277 t.undo();
278 }
279 }
280 r
281 }
282
283 pub fn with_scope<R>(&self, scope: &mut WidgetUndoScope, f: impl FnOnce() -> R) -> R {
285 UNDO_SCOPE_CTX.with_context(&mut scope.0, f)
286 }
287
288 pub fn with_disabled<R>(&self, f: impl FnOnce() -> R) -> R {
290 let mut scope = UndoScope::default();
291 let parent_scope = UNDO_SCOPE_CTX.get();
292 *scope.enabled.get_mut() = false;
293 *scope.id.get_mut() = parent_scope.id.load(Ordering::Relaxed);
294
295 UNDO_SCOPE_CTX.with_context(&mut Some(Arc::new(scope)), f)
296 }
297
298 pub fn watch_var<T: VarValue>(&self, info: impl UndoInfo, var: Var<T>) -> VarHandle {
308 if var.capabilities().is_always_read_only() {
309 return VarHandle::dummy();
310 }
311 let var = var.current_context();
312 let wk_var = var.downgrade();
313
314 let mut prev_value = Some(var.get());
315 let info = info.into_dyn();
316
317 var.trace_value(move |args| {
318 if args.downcast_tags::<UndoVarModifyTag>().next().is_none() {
319 let prev = prev_value.take().unwrap();
320 let new = args.value();
321 if &prev == new {
322 prev_value = Some(prev);
324 return;
325 }
326 prev_value = Some(new.clone());
327 UNDO.register_op(
328 info.clone(),
329 clmv!(wk_var, new, |op| if let Some(var) = wk_var.upgrade() {
330 match op {
331 UndoOp::Undo => var.modify(clmv!(prev, |args| {
332 args.set(prev);
333 args.push_tag(UndoVarModifyTag);
334 })),
335 UndoOp::Redo => var.modify(clmv!(new, |args| {
336 args.set(new);
337 args.push_tag(UndoVarModifyTag);
338 })),
339 };
340 }),
341 );
342 }
343 })
344 }
345
346 pub fn clear_redo(&self) {
348 UNDO_SCOPE_CTX.get().redo.lock().clear();
349 }
350
351 pub fn clear(&self) {
353 let ctx = UNDO_SCOPE_CTX.get();
354 let mut u = ctx.undo.lock();
355 u.clear();
356 ctx.redo.lock().clear();
357 }
358
359 pub fn can_undo(&self) -> bool {
361 !UNDO_SCOPE_CTX.get().undo.lock().is_empty()
362 }
363
364 pub fn can_redo(&self) -> bool {
366 !UNDO_SCOPE_CTX.get().redo.lock().is_empty()
367 }
368
369 pub fn undo_stack(&self) -> UndoStackInfo {
373 UndoStackInfo::undo(&UNDO_SCOPE_CTX.get(), UNDO_INTERVAL_VAR.get())
374 }
375
376 pub fn redo_stack(&self) -> UndoStackInfo {
382 UndoStackInfo::redo(&UNDO_SCOPE_CTX.get(), UNDO_INTERVAL_VAR.get())
383 }
384}
385
386#[derive(Clone)]
388pub struct UndoStackInfo {
389 pub stack: Vec<(DInstant, Arc<dyn UndoInfo>)>,
397
398 pub undo_interval: Duration,
400}
401impl UndoStackInfo {
402 fn undo(ctx: &UndoScope, undo_interval: Duration) -> Self {
403 Self {
404 stack: ctx.undo.lock().iter_mut().map(|e| (e.timestamp, e.action.info())).collect(),
405 undo_interval,
406 }
407 }
408 fn redo(ctx: &UndoScope, undo_interval: Duration) -> Self {
409 Self {
410 stack: ctx.redo.lock().iter_mut().map(|e| (e.timestamp, e.action.info())).collect(),
411 undo_interval,
412 }
413 }
414
415 pub fn iter_groups(&self) -> impl DoubleEndedIterator<Item = &[(DInstant, Arc<dyn UndoInfo>)]> {
417 struct Iter<'a> {
418 stack: &'a [(DInstant, Arc<dyn UndoInfo>)],
419 interval: Duration,
420 ts_inverted: bool,
421 }
422 impl<'a> Iterator for Iter<'a> {
423 type Item = &'a [(DInstant, Arc<dyn UndoInfo>)];
424
425 fn next(&mut self) -> Option<Self::Item> {
426 if self.stack.is_empty() {
427 None
428 } else {
429 let mut older = self.stack[0].0;
430
431 let mut r = self.stack;
432
433 if let Some(i) = self.stack.iter().position(|(newer, _)| {
434 let (a, b) = if self.ts_inverted { (older, *newer) } else { (*newer, older) };
435 let break_ = a.saturating_duration_since(b) > self.interval;
436 older = *newer;
437 break_
438 }) {
439 r = &self.stack[..i];
440 self.stack = &self.stack[i..];
441 } else {
442 self.stack = &[];
443 }
444
445 Some(r)
446 }
447 }
448 }
449 impl DoubleEndedIterator for Iter<'_> {
450 fn next_back(&mut self) -> Option<Self::Item> {
451 if self.stack.is_empty() {
452 None
453 } else {
454 let mut newer = self.stack[self.stack.len() - 1].0;
455
456 let mut r = self.stack;
457
458 if let Some(i) = self.stack.iter().rposition(|(older, _)| {
459 let (a, b) = if self.ts_inverted { (*older, newer) } else { (newer, *older) };
460 let break_ = a.saturating_duration_since(b) > self.interval;
461 newer = *older;
462 break_
463 }) {
464 let i = i + 1;
465 r = &self.stack[i..];
466 self.stack = &self.stack[..i];
467 } else {
468 self.stack = &[];
469 }
470
471 Some(r)
472 }
473 }
474 }
475 Iter {
476 stack: &self.stack,
477 interval: self.undo_interval,
478 ts_inverted: self.stack.len() > 1 && self.stack[0].0 > self.stack[self.stack.len() - 1].0,
479 }
480 }
481}
482
483#[derive(Debug, Clone, Copy, PartialEq)]
489pub struct UndoVarModifyTag;
490
491pub trait UndoInfo: Send + Sync + Any {
493 fn description(&self) -> Txt;
495
496 fn meta(&self) -> StateMapRef<'_, UNDO> {
501 StateMapRef::empty()
502 }
503
504 fn into_dyn(self) -> Arc<dyn UndoInfo>
506 where
507 Self: Sized,
508 {
509 Arc::new(self)
510 }
511}
512impl UndoInfo for Txt {
513 fn description(&self) -> Txt {
514 self.clone()
515 }
516}
517impl UndoInfo for Var<Txt> {
518 fn description(&self) -> Txt {
519 self.get()
520 }
521}
522impl UndoInfo for &'static str {
523 fn description(&self) -> Txt {
524 Txt::from_static(self)
525 }
526}
527impl UndoInfo for Arc<dyn UndoInfo> {
528 fn description(&self) -> Txt {
529 self.as_ref().description()
530 }
531
532 fn meta(&self) -> StateMapRef<'_, UNDO> {
533 self.as_ref().meta()
534 }
535
536 fn into_dyn(self) -> Arc<dyn UndoInfo>
537 where
538 Self: Sized,
539 {
540 self
541 }
542}
543pub trait UndoAction: Send + Any {
545 fn info(&mut self) -> Arc<dyn UndoInfo>;
547
548 fn undo(self: Box<Self>) -> Box<dyn RedoAction>;
550
551 fn as_any(&mut self) -> &mut dyn Any;
553
554 fn merge(self: Box<Self>, args: UndoActionMergeArgs) -> Result<Box<dyn UndoAction>, (Box<dyn UndoAction>, Box<dyn UndoAction>)>;
566}
567
568pub struct UndoActionMergeArgs {
570 pub next: Box<dyn UndoAction>,
572
573 pub prev_timestamp: DInstant,
575
576 pub within_undo_interval: bool,
581}
582
583pub trait RedoAction: Send + Any {
585 fn info(&mut self) -> Arc<dyn UndoInfo>;
587
588 fn redo(self: Box<Self>) -> Box<dyn UndoAction>;
590}
591
592#[derive(Debug, Clone, Copy, PartialEq, Eq)]
600pub enum UndoOp {
601 Undo,
603 Redo,
605}
606impl UndoOp {
607 pub fn cmd(self) -> Command {
609 match self {
610 UndoOp::Undo => UNDO_CMD,
611 UndoOp::Redo => REDO_CMD,
612 }
613 }
614}
615
616pub enum UndoFullOp<'r> {
624 Init {
629 redo: &'r mut bool,
634 },
635
636 Op(UndoOp),
638 Info {
640 info: &'r mut Option<Arc<dyn UndoInfo>>,
644 },
645 Merge {
647 next_data: &'r mut dyn Any,
652
653 prev_timestamp: DInstant,
655
656 within_undo_interval: bool,
661
662 merged: &'r mut bool,
665 },
666}
667impl fmt::Debug for UndoFullOp<'_> {
668 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
669 match self {
670 Self::Init { .. } => f.debug_struct("Init").finish_non_exhaustive(),
671 Self::Op(arg0) => f.debug_tuple("Op").field(arg0).finish(),
672 Self::Info { .. } => f.debug_struct("Info").finish_non_exhaustive(),
673 Self::Merge { .. } => f.debug_struct("Merge").finish_non_exhaustive(),
674 }
675 }
676}
677
678#[must_use = "dropping the transaction undoes all captured actions"]
682pub struct UndoTransaction {
683 undo: Vec<UndoEntry>,
684}
685impl UndoTransaction {
686 pub fn is_empty(&self) -> bool {
688 self.undo.is_empty()
689 }
690
691 pub fn commit(mut self) {
693 let mut undo = mem::take(&mut self.undo);
694 let now = INSTANT.now();
695 for u in &mut undo {
696 u.timestamp = now;
697 }
698 let ctx = UNDO_SCOPE_CTX.get();
699 let mut ctx_undo = ctx.undo.lock();
700 if ctx_undo.is_empty() {
701 *ctx_undo = undo;
702 } else {
703 ctx_undo.extend(undo);
704 }
705 }
706
707 pub fn commit_group(mut self, info: impl UndoInfo) {
712 UNDO.register(UndoGroup {
713 info: info.into_dyn(),
714 undo: mem::take(&mut self.undo),
715 })
716 }
717
718 pub fn undo(self) {
722 let _ = self;
723 }
724}
725impl Drop for UndoTransaction {
726 fn drop(&mut self) {
727 for undo in self.undo.drain(..).rev() {
728 let _ = undo.action.undo();
729 }
730 }
731}
732
733command! {
734 pub static UNDO_CMD {
749 l10n!: true,
750 name: "Undo",
751 shortcut: [shortcut!(CTRL + 'Z')],
752 icon: wgt_fn!(|_| ICONS.get("undo")),
753 init: |_| {
754 let _ = UNDO_SV.read();
755 },
756 };
757
758 pub static REDO_CMD {
768 l10n!: true,
769 name: "Redo",
770 shortcut: [shortcut!(CTRL + 'Y')],
771 icon: wgt_fn!(|_| ICONS.get("redo")),
772 init: |_| {
773 let _ = UNDO_SV.read();
774 },
775 };
776
777 pub static CLEAR_HISTORY_CMD {
783 l10n!: true,
784 name: "Clear History",
785 init: |_| {
786 let _ = UNDO_SV.read();
787 },
788 };
789}
790
791pub struct WidgetUndoScope(Option<Arc<UndoScope>>);
797impl WidgetUndoScope {
798 pub const fn new() -> Self {
800 Self(None)
801 }
802
803 pub fn is_inited(&self) -> bool {
805 self.0.is_some()
806 }
807
808 pub fn init(&mut self) {
812 let mut scope = UndoScope::default();
813 let id = WIDGET.id();
814 *scope.id.get_mut() = Some(id);
815
816 let scope = Arc::new(scope);
817 let wk_scope = Arc::downgrade(&scope);
818 let interval = UNDO_INTERVAL_VAR.current_context();
819
820 UNDO_CMD
821 .scoped(id)
822 .with_meta(|m| m.set(*WEAK_UNDO_SCOPE_ID, (wk_scope.clone(), interval.clone())));
823 REDO_CMD.scoped(id).with_meta(|m| m.set(*WEAK_UNDO_SCOPE_ID, (wk_scope, interval)));
824
825 self.0 = Some(scope);
826 }
827
828 pub fn info(&mut self, info: &mut WidgetInfoBuilder) {
832 info.flag_meta(*FOCUS_SCOPE_ID);
833 }
834
835 pub fn deinit(&mut self) {
841 self.0 = None;
842 }
843
844 pub fn set_enabled(&mut self, enabled: bool) {
848 self.0.as_ref().unwrap().enabled.store(enabled, Ordering::Relaxed);
849 }
850
851 pub fn can_undo(&self) -> bool {
853 !self.0.as_ref().unwrap().undo.lock().is_empty()
854 }
855
856 pub fn can_redo(&self) -> bool {
858 !self.0.as_ref().unwrap().redo.lock().is_empty()
859 }
860}
861impl Default for WidgetUndoScope {
862 fn default() -> Self {
863 Self::new()
864 }
865}
866
867struct UndoScope {
868 id: Atomic<Option<WidgetId>>,
869 undo: Mutex<Vec<UndoEntry>>,
870 redo: Mutex<Vec<RedoEntry>>,
871 enabled: AtomicBool,
872}
873impl Default for UndoScope {
874 fn default() -> Self {
875 Self {
876 id: Default::default(),
877 undo: Default::default(),
878 redo: Default::default(),
879 enabled: AtomicBool::new(true),
880 }
881 }
882}
883impl UndoScope {
884 fn with_enabled_undo_redo(&self, f: impl FnOnce(&mut Vec<UndoEntry>, &mut Vec<RedoEntry>)) {
885 let mut undo = self.undo.lock();
886 let mut redo = self.redo.lock();
887
888 let max_undo = if self.enabled.load(Ordering::Relaxed) {
889 UNDO_LIMIT_VAR.get() as usize
890 } else {
891 tracing::debug!("not enabled, will cleanup");
892 0
893 };
894
895 if undo.len() > max_undo {
896 undo.reverse();
897 while undo.len() > max_undo {
898 undo.pop();
899 }
900 undo.reverse();
901 }
902
903 if redo.len() > max_undo {
904 redo.reverse();
905 while redo.len() > max_undo {
906 redo.pop();
907 }
908 redo.reverse();
909 }
910
911 if max_undo > 0 {
912 f(&mut undo, &mut redo);
913 }
914 }
915
916 fn register(&self, mut action: Box<dyn UndoAction>) {
917 tracing::trace!("register '{}'", action.info().description());
918 self.with_enabled_undo_redo(|undo, redo| {
919 let now = INSTANT.now();
920 if let Some(prev) = undo.pop() {
921 match prev.action.merge(UndoActionMergeArgs {
922 next: action,
923 prev_timestamp: prev.timestamp,
924 within_undo_interval: now.duration_since(prev.timestamp) <= UNDO_SV.read().undo_interval.get(),
925 }) {
926 Ok(merged) => undo.push(UndoEntry {
927 timestamp: now,
928 action: merged,
929 }),
930 Err((p, action)) => {
931 undo.push(UndoEntry {
932 timestamp: prev.timestamp,
933 action: p,
934 });
935 undo.push(UndoEntry { timestamp: now, action });
936 }
937 }
938 } else {
939 undo.push(UndoEntry { timestamp: now, action });
940 }
941 redo.clear();
942 });
943 }
944
945 fn undo_select(&self, selector: impl UndoSelector) {
946 let _s = tracing::trace_span!("undo").entered();
947
948 let mut actions = vec![];
949
950 self.with_enabled_undo_redo(|undo, _| {
951 let mut select = selector.select(UndoOp::Undo);
952 while let Some(entry) = undo.last() {
953 if select.include(entry.timestamp) {
954 actions.push(undo.pop().unwrap());
955 } else {
956 break;
957 }
958 }
959 });
960
961 for mut undo in actions {
962 tracing::trace!("undo '{}'", undo.action.info().description());
963 let redo = undo.action.undo();
964 self.redo.lock().push(RedoEntry {
965 timestamp: undo.timestamp,
966 action: redo,
967 });
968 }
969 }
970
971 fn redo_select(&self, selector: impl UndoSelector) {
972 let _s = tracing::trace_span!("redo").entered();
973
974 let mut actions = vec![];
975
976 self.with_enabled_undo_redo(|_, redo| {
977 let mut select = selector.select(UndoOp::Redo);
978 while let Some(entry) = redo.last() {
979 if select.include(entry.timestamp) {
980 actions.push(redo.pop().unwrap());
981 } else {
982 break;
983 }
984 }
985 });
986
987 for mut redo in actions {
988 tracing::trace!("redo '{}'", redo.action.info().description());
989 let undo = redo.action.redo();
990 self.undo.lock().push(UndoEntry {
991 timestamp: redo.timestamp,
992 action: undo,
993 });
994 }
995 }
996
997 fn id(&self) -> Option<WidgetId> {
998 self.id.load(Ordering::Relaxed)
999 }
1000}
1001
1002struct UndoEntry {
1003 timestamp: DInstant,
1004 action: Box<dyn UndoAction>,
1005}
1006
1007struct RedoEntry {
1008 pub timestamp: DInstant,
1009 pub action: Box<dyn RedoAction>,
1010}
1011
1012struct UndoGroup {
1013 info: Arc<dyn UndoInfo>,
1014 undo: Vec<UndoEntry>,
1015}
1016impl UndoAction for UndoGroup {
1017 fn undo(self: Box<Self>) -> Box<dyn RedoAction> {
1018 let mut redo = Vec::with_capacity(self.undo.len());
1019 for undo in self.undo.into_iter().rev() {
1020 redo.push(RedoEntry {
1021 timestamp: undo.timestamp,
1022 action: undo.action.undo(),
1023 });
1024 }
1025 Box::new(RedoGroup { info: self.info, redo })
1026 }
1027
1028 fn info(&mut self) -> Arc<dyn UndoInfo> {
1029 self.info.clone()
1030 }
1031
1032 fn as_any(&mut self) -> &mut dyn Any {
1033 self
1034 }
1035
1036 fn merge(self: Box<Self>, args: UndoActionMergeArgs) -> Result<Box<dyn UndoAction>, (Box<dyn UndoAction>, Box<dyn UndoAction>)> {
1037 Err((self, args.next))
1038 }
1039}
1040struct RedoGroup {
1041 info: Arc<dyn UndoInfo>,
1042 redo: Vec<RedoEntry>,
1043}
1044impl RedoAction for RedoGroup {
1045 fn redo(self: Box<Self>) -> Box<dyn UndoAction> {
1046 let mut undo = Vec::with_capacity(self.redo.len());
1047 for redo in self.redo.into_iter().rev() {
1048 undo.push(UndoEntry {
1049 timestamp: redo.timestamp,
1050 action: redo.action.redo(),
1051 });
1052 }
1053 Box::new(UndoGroup { info: self.info, undo })
1054 }
1055
1056 fn info(&mut self) -> Arc<dyn UndoInfo> {
1057 self.info.clone()
1058 }
1059}
1060
1061struct UndoRedoOp {
1062 info: Arc<dyn UndoInfo>,
1063 op: Box<dyn FnMut(UndoOp) + Send>,
1064}
1065impl UndoAction for UndoRedoOp {
1066 fn undo(mut self: Box<Self>) -> Box<dyn RedoAction> {
1067 (self.op)(UndoOp::Undo);
1068 self
1069 }
1070
1071 fn info(&mut self) -> Arc<dyn UndoInfo> {
1072 self.info.clone()
1073 }
1074
1075 fn as_any(&mut self) -> &mut dyn Any {
1076 self
1077 }
1078
1079 fn merge(self: Box<Self>, args: UndoActionMergeArgs) -> Result<Box<dyn UndoAction>, (Box<dyn UndoAction>, Box<dyn UndoAction>)> {
1080 Err((self, args.next))
1081 }
1082}
1083impl RedoAction for UndoRedoOp {
1084 fn redo(mut self: Box<Self>) -> Box<dyn UndoAction> {
1085 (self.op)(UndoOp::Redo);
1086 self
1087 }
1088
1089 fn info(&mut self) -> Arc<dyn UndoInfo> {
1090 self.info.clone()
1091 }
1092}
1093
1094struct UndoRedoFullOp {
1095 data: Box<dyn Any + Send>,
1096 op: Box<dyn FnMut(&mut dyn Any, UndoFullOp) + Send>,
1097}
1098impl UndoAction for UndoRedoFullOp {
1099 fn info(&mut self) -> Arc<dyn UndoInfo> {
1100 let mut info = None;
1101 (self.op)(&mut self.data, UndoFullOp::Info { info: &mut info });
1102 info.unwrap_or_else(|| Arc::new("action"))
1103 }
1104
1105 fn undo(mut self: Box<Self>) -> Box<dyn RedoAction> {
1106 (self.op)(&mut self.data, UndoFullOp::Op(UndoOp::Undo));
1107 self
1108 }
1109
1110 fn merge(mut self: Box<Self>, mut args: UndoActionMergeArgs) -> Result<Box<dyn UndoAction>, (Box<dyn UndoAction>, Box<dyn UndoAction>)>
1111 where
1112 Self: Sized,
1113 {
1114 if let Some(u) = args.next.as_any().downcast_mut::<Self>() {
1115 let mut merged = false;
1116 (self.op)(
1117 &mut self.data,
1118 UndoFullOp::Merge {
1119 next_data: &mut u.data,
1120 prev_timestamp: args.prev_timestamp,
1121 within_undo_interval: args.within_undo_interval,
1122 merged: &mut merged,
1123 },
1124 );
1125 if merged { Ok(self) } else { Err((self, args.next)) }
1126 } else {
1127 Err((self, args.next))
1128 }
1129 }
1130
1131 fn as_any(&mut self) -> &mut dyn Any {
1132 self
1133 }
1134}
1135impl RedoAction for UndoRedoFullOp {
1136 fn info(&mut self) -> Arc<dyn UndoInfo> {
1137 let mut info = None;
1138 (self.op)(&mut self.data, UndoFullOp::Info { info: &mut info });
1139 info.unwrap_or_else(|| Arc::new("action"))
1140 }
1141
1142 fn redo(mut self: Box<Self>) -> Box<dyn UndoAction> {
1143 (self.op)(&mut self.data, UndoFullOp::Op(UndoOp::Redo));
1144 self
1145 }
1146}
1147
1148struct UndoService {
1149 undo_limit: Var<u32>,
1150 undo_interval: Var<Duration>,
1151}
1152
1153impl Default for UndoService {
1154 fn default() -> Self {
1155 Self {
1156 undo_limit: var(u32::MAX),
1157 undo_interval: KEYBOARD.repeat_config().map(|c| c.start_delay + c.interval).cow(),
1158 }
1159 }
1160}
1161
1162context_local! {
1163 static UNDO_SCOPE_CTX: UndoScope = UndoScope::default();
1164}
1165app_local! {
1166 static UNDO_SV: UndoService = {
1167 hooks();
1168 UndoService::default()
1169 };
1170}
1171
1172fn hooks() {
1173 UNDO_CMD
1174 .on_event(
1175 true,
1176 true,
1177 false,
1178 hn!(|args| {
1179 args.propagation.stop();
1180 if let Some(c) = args.param::<u32>() {
1181 UNDO.undo_select(*c);
1182 } else if let Some(i) = args.param::<Duration>() {
1183 UNDO.undo_select(*i);
1184 } else if let Some(t) = args.param::<DInstant>() {
1185 UNDO.undo_select(*t);
1186 } else {
1187 UNDO.undo();
1188 }
1189 }),
1190 )
1191 .perm();
1192 REDO_CMD
1193 .on_event(
1194 true,
1195 true,
1196 false,
1197 hn!(|args| {
1198 args.propagation.stop();
1199 if let Some(c) = args.param::<u32>() {
1200 UNDO.redo_select(*c);
1201 } else if let Some(i) = args.param::<Duration>() {
1202 UNDO.redo_select(*i);
1203 } else if let Some(t) = args.param::<DInstant>() {
1204 UNDO.redo_select(*t);
1205 } else {
1206 UNDO.redo();
1207 }
1208 }),
1209 )
1210 .perm();
1211}
1212
1213pub trait WidgetInfoUndoExt {
1215 fn is_undo_scope(&self) -> bool;
1217
1218 fn undo_scope(&self) -> Option<WidgetInfo>;
1220}
1221impl WidgetInfoUndoExt for WidgetInfo {
1222 fn is_undo_scope(&self) -> bool {
1223 self.meta().flagged(*FOCUS_SCOPE_ID)
1224 }
1225
1226 fn undo_scope(&self) -> Option<WidgetInfo> {
1227 self.ancestors().find(WidgetInfoUndoExt::is_undo_scope)
1228 }
1229}
1230
1231static_id! {
1232 static ref FOCUS_SCOPE_ID: StateId<()>;
1233}
1234
1235pub trait CommandUndoExt {
1237 fn undo_scoped(self) -> Var<Command>;
1240
1241 fn undo_stack(self) -> UndoStackInfo;
1243 fn redo_stack(self) -> UndoStackInfo;
1245}
1246impl CommandUndoExt for Command {
1247 fn undo_scoped(self) -> Var<Command> {
1248 self.focus_scoped_with(|w| match w {
1249 Some(w) => {
1250 if w.is_undo_scope() {
1251 CommandScope::Widget(w.id())
1252 } else if let Some(scope) = w.undo_scope() {
1253 CommandScope::Widget(scope.id())
1254 } else {
1255 CommandScope::App
1256 }
1257 }
1258 None => CommandScope::App,
1259 })
1260 }
1261
1262 fn undo_stack(self) -> UndoStackInfo {
1263 let scope = self.with_meta(|m| m.get(*WEAK_UNDO_SCOPE_ID));
1264 if let Some(scope) = scope
1265 && let Some(s) = scope.0.upgrade()
1266 {
1267 return UndoStackInfo::undo(&s, scope.1.get());
1268 }
1269
1270 if let CommandScope::App = self.scope() {
1271 let mut r = UNDO_SCOPE_CTX.with_default(|| UNDO.undo_stack());
1272 r.undo_interval = UNDO.undo_interval().get();
1273 return r;
1274 }
1275
1276 UndoStackInfo {
1277 stack: vec![],
1278 undo_interval: Duration::ZERO,
1279 }
1280 }
1281
1282 fn redo_stack(self) -> UndoStackInfo {
1283 let scope = self.with_meta(|m| m.get(*WEAK_UNDO_SCOPE_ID));
1284 if let Some(scope) = scope
1285 && let Some(s) = scope.0.upgrade()
1286 {
1287 return UndoStackInfo::redo(&s, scope.1.get());
1288 }
1289
1290 if let CommandScope::App = self.scope() {
1291 let mut r = UNDO_SCOPE_CTX.with_default(|| UNDO.redo_stack());
1292 r.undo_interval = UNDO.undo_interval().get();
1293 return r;
1294 }
1295
1296 UndoStackInfo {
1297 stack: vec![],
1298 undo_interval: Duration::ZERO,
1299 }
1300 }
1301}
1302
1303static_id! {
1304 static ref WEAK_UNDO_SCOPE_ID: StateId<(std::sync::Weak<UndoScope>, Var<Duration>)>;
1305}
1306
1307pub trait UndoSelector: crate::private::Sealed {
1313 type Select: UndoSelect;
1315
1316 fn select(self, op: UndoOp) -> Self::Select;
1318}
1319
1320pub trait UndoSelect {
1322 fn include(&mut self, timestamp: DInstant) -> bool;
1327}
1328impl crate::private::Sealed for u32 {}
1329impl UndoSelector for u32 {
1330 type Select = u32;
1331
1332 fn select(self, op: UndoOp) -> Self::Select {
1333 let _ = op;
1334 self
1335 }
1336}
1337impl UndoSelect for u32 {
1338 fn include(&mut self, _: DInstant) -> bool {
1339 let i = *self > 0;
1340 if i {
1341 *self -= 1;
1342 }
1343 i
1344 }
1345}
1346impl crate::private::Sealed for Duration {}
1347impl UndoSelector for Duration {
1348 type Select = UndoSelectInterval;
1349
1350 fn select(self, op: UndoOp) -> Self::Select {
1351 UndoSelectInterval {
1352 prev: None,
1353 interval: self,
1354 op,
1355 }
1356 }
1357}
1358#[doc(hidden)]
1359pub struct UndoSelectInterval {
1360 prev: Option<DInstant>,
1361 interval: Duration,
1362 op: UndoOp,
1363}
1364impl UndoSelect for UndoSelectInterval {
1365 fn include(&mut self, timestamp: DInstant) -> bool {
1366 if let Some(prev) = &mut self.prev {
1367 let (older, newer) = match self.op {
1368 UndoOp::Undo => (timestamp, *prev),
1369 UndoOp::Redo => (*prev, timestamp),
1370 };
1371 if newer.saturating_duration_since(older) <= self.interval {
1372 *prev = timestamp;
1373 true
1374 } else {
1375 false
1376 }
1377 } else {
1378 self.prev = Some(timestamp);
1379 true
1380 }
1381 }
1382}
1383impl crate::private::Sealed for DInstant {}
1384impl UndoSelector for DInstant {
1385 type Select = UndoSelectLtEq;
1386
1387 fn select(self, op: UndoOp) -> Self::Select {
1388 UndoSelectLtEq { instant: self, op }
1389 }
1390}
1391#[doc(hidden)]
1392pub struct UndoSelectLtEq {
1393 instant: DInstant,
1394 op: UndoOp,
1395}
1396impl UndoSelect for UndoSelectLtEq {
1397 fn include(&mut self, timestamp: DInstant) -> bool {
1398 match self.op {
1399 UndoOp::Undo => timestamp >= self.instant,
1400 UndoOp::Redo => timestamp <= self.instant,
1401 }
1402 }
1403}
1404
1405#[cfg(test)]
1406mod tests {
1407 use zng_app::APP;
1408
1409 use super::*;
1410
1411 #[test]
1412 fn register() {
1413 let _a = APP.minimal().run_headless(false);
1414 let data = Arc::new(Mutex::new(vec![1, 2]));
1415
1416 UNDO.register(PushAction {
1417 data: data.clone(),
1418 item: 1,
1419 });
1420 UNDO.register(PushAction {
1421 data: data.clone(),
1422 item: 2,
1423 });
1424 assert_eq!(&[1, 2], &data.lock()[..]);
1425
1426 UNDO.undo_select(1);
1427 assert_eq!(&[1], &data.lock()[..]);
1428 UNDO.undo_select(1);
1429 assert_eq!(&[] as &[u8], &data.lock()[..]);
1430
1431 UNDO.redo_select(1);
1432 assert_eq!(&[1], &data.lock()[..]);
1433 UNDO.redo_select(1);
1434 assert_eq!(&[1, 2], &data.lock()[..]);
1435 }
1436
1437 fn push_1_2(data: &Arc<Mutex<Vec<u8>>>) {
1438 UNDO.run_op(
1439 "push 1",
1440 clmv!(data, |op| match op {
1441 UndoOp::Undo => assert_eq!(data.lock().pop(), Some(1)),
1442 UndoOp::Redo => data.lock().push(1),
1443 }),
1444 );
1445 UNDO.run_op(
1446 "push 2",
1447 clmv!(data, |op| match op {
1448 UndoOp::Undo => assert_eq!(data.lock().pop(), Some(2)),
1449 UndoOp::Redo => data.lock().push(2),
1450 }),
1451 );
1452 }
1453
1454 #[test]
1455 fn run_op() {
1456 let _a = APP.minimal().run_headless(false);
1457 let data = Arc::new(Mutex::new(vec![]));
1458
1459 push_1_2(&data);
1460 assert_eq!(&[1, 2], &data.lock()[..]);
1461
1462 UNDO.undo_select(1);
1463 assert_eq!(&[1], &data.lock()[..]);
1464 UNDO.undo_select(1);
1465 assert_eq!(&[] as &[u8], &data.lock()[..]);
1466
1467 UNDO.redo_select(1);
1468 assert_eq!(&[1], &data.lock()[..]);
1469 UNDO.redo_select(1);
1470 assert_eq!(&[1, 2], &data.lock()[..]);
1471 }
1472
1473 #[test]
1474 fn transaction_undo() {
1475 let _a = APP.minimal().run_headless(false);
1476 let data = Arc::new(Mutex::new(vec![]));
1477
1478 let t = UNDO.transaction(|| {
1479 push_1_2(&data);
1480 });
1481
1482 assert_eq!(&[1, 2], &data.lock()[..]);
1483 UNDO.undo_select(1);
1484 assert_eq!(&[1, 2], &data.lock()[..]);
1485
1486 t.undo();
1487 assert_eq!(&[] as &[u8], &data.lock()[..]);
1488 }
1489
1490 #[test]
1491 fn transaction_commit() {
1492 let _a = APP.minimal().run_headless(false);
1493 let data = Arc::new(Mutex::new(vec![]));
1494
1495 let t = UNDO.transaction(|| {
1496 push_1_2(&data);
1497 });
1498
1499 assert_eq!(&[1, 2], &data.lock()[..]);
1500 UNDO.undo_select(1);
1501 assert_eq!(&[1, 2], &data.lock()[..]);
1502
1503 t.commit();
1504
1505 UNDO.undo_select(1);
1506 assert_eq!(&[1], &data.lock()[..]);
1507 UNDO.undo_select(1);
1508 assert_eq!(&[] as &[u8], &data.lock()[..]);
1509
1510 UNDO.redo_select(1);
1511 assert_eq!(&[1], &data.lock()[..]);
1512 UNDO.redo_select(1);
1513 assert_eq!(&[1, 2], &data.lock()[..]);
1514 }
1515
1516 #[test]
1517 fn transaction_group() {
1518 let _a = APP.minimal().run_headless(false);
1519 let data = Arc::new(Mutex::new(vec![]));
1520
1521 let t = UNDO.transaction(|| {
1522 push_1_2(&data);
1523 });
1524
1525 assert_eq!(&[1, 2], &data.lock()[..]);
1526 UNDO.undo_select(1);
1527 assert_eq!(&[1, 2], &data.lock()[..]);
1528
1529 t.commit_group("push 1, 2");
1530
1531 UNDO.undo_select(1);
1532 assert_eq!(&[] as &[u8], &data.lock()[..]);
1533
1534 UNDO.redo_select(1);
1535 assert_eq!(&[1, 2], &data.lock()[..]);
1536 }
1537
1538 fn push_1_sleep_2(data: &Arc<Mutex<Vec<u8>>>) {
1539 UNDO.run_op(
1540 "push 1",
1541 clmv!(data, |op| match op {
1542 UndoOp::Undo => assert_eq!(data.lock().pop(), Some(1)),
1543 UndoOp::Redo => data.lock().push(1),
1544 }),
1545 );
1546 std::thread::sleep(Duration::from_millis(100));
1547 UNDO.run_op(
1548 "push 2",
1549 clmv!(data, |op| match op {
1550 UndoOp::Undo => assert_eq!(data.lock().pop(), Some(2)),
1551 UndoOp::Redo => data.lock().push(2),
1552 }),
1553 );
1554 }
1555
1556 #[test]
1557 fn undo_redo_t_zero() {
1558 let _a = APP.minimal().run_headless(false);
1559 let data = Arc::new(Mutex::new(vec![]));
1560
1561 push_1_sleep_2(&data);
1562 assert_eq!(&[1, 2], &data.lock()[..]);
1563
1564 UNDO.undo_select(Duration::ZERO);
1565 assert_eq!(&[1], &data.lock()[..]);
1566 UNDO.undo_select(Duration::ZERO);
1567 assert_eq!(&[] as &[u8], &data.lock()[..]);
1568
1569 UNDO.redo_select(Duration::ZERO);
1570 assert_eq!(&[1], &data.lock()[..]);
1571 UNDO.redo_select(Duration::ZERO);
1572 assert_eq!(&[1, 2], &data.lock()[..]);
1573 }
1574
1575 #[test]
1576 fn undo_redo_t_max() {
1577 undo_redo_t_large(Duration::MAX);
1578 }
1579
1580 #[test]
1581 fn undo_redo_t_10s() {
1582 undo_redo_t_large(Duration::from_secs(10));
1583 }
1584
1585 fn undo_redo_t_large(t: Duration) {
1586 let _a = APP.minimal().run_headless(false);
1587 let data = Arc::new(Mutex::new(vec![]));
1588
1589 push_1_sleep_2(&data);
1590 assert_eq!(&[1, 2], &data.lock()[..]);
1591
1592 UNDO.undo_select(t);
1593 assert_eq!(&[] as &[u8], &data.lock()[..]);
1594
1595 UNDO.redo_select(t);
1596 assert_eq!(&[1, 2], &data.lock()[..]);
1597 }
1598
1599 #[test]
1600 fn watch_var() {
1601 let mut app = APP.minimal().run_headless(false);
1602
1603 let test_var = var(0);
1604 UNDO.watch_var("set test var", test_var.clone()).perm();
1605
1606 test_var.set(10);
1607 app.update(false).assert_wait();
1608
1609 test_var.set(20);
1610 app.update(false).assert_wait();
1611
1612 assert_eq!(20, test_var.get());
1613
1614 UNDO.undo_select(1);
1615 app.update(false).assert_wait();
1616 assert_eq!(10, test_var.get());
1617
1618 UNDO.undo_select(1);
1619 app.update(false).assert_wait();
1620 assert_eq!(0, test_var.get());
1621
1622 UNDO.redo_select(1);
1623 app.update(false).assert_wait();
1624 assert_eq!(10, test_var.get());
1625
1626 UNDO.redo_select(1);
1627 app.update(false).assert_wait();
1628 assert_eq!(20, test_var.get());
1629 }
1630
1631 struct PushAction {
1632 data: Arc<Mutex<Vec<u8>>>,
1633 item: u8,
1634 }
1635 impl UndoAction for PushAction {
1636 fn undo(self: Box<Self>) -> Box<dyn RedoAction> {
1637 assert_eq!(self.data.lock().pop(), Some(self.item));
1638 self
1639 }
1640
1641 fn info(&mut self) -> Arc<dyn UndoInfo> {
1642 Arc::new("push")
1643 }
1644
1645 fn as_any(&mut self) -> &mut dyn Any {
1646 self
1647 }
1648
1649 fn merge(self: Box<Self>, args: UndoActionMergeArgs) -> Result<Box<dyn UndoAction>, (Box<dyn UndoAction>, Box<dyn UndoAction>)> {
1650 Err((self, args.next))
1651 }
1652 }
1653 impl RedoAction for PushAction {
1654 fn redo(self: Box<Self>) -> Box<dyn UndoAction> {
1655 self.data.lock().push(self.item);
1656 self
1657 }
1658
1659 fn info(&mut self) -> Arc<dyn UndoInfo> {
1660 Arc::new("push")
1661 }
1662 }
1663}