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")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12use std::time::Duration;
13
14use zng_ext_undo::*;
15use zng_wgt::prelude::*;
16
17#[property(CONTEXT - 10, default(false))]
25pub fn undo_scope(child: impl IntoUiNode, is_scope: impl IntoVar<bool>) -> UiNode {
26 let mut scope = WidgetUndoScope::new();
27 let mut undo_cmd = CommandHandle::dummy();
28 let mut redo_cmd = CommandHandle::dummy();
29 let mut clear_cmd = CommandHandle::dummy();
30 let is_scope = is_scope.into_var();
31 let mut can_undo = false;
32 let mut can_redo = false;
33 match_node(child, move |c, mut op| {
34 match &mut op {
35 UiNodeOp::Init => {
36 WIDGET.sub_var(&is_scope);
37
38 if !is_scope.get() {
39 return; }
41
42 scope.init();
43
44 let id = WIDGET.id();
45 undo_cmd = UNDO_CMD.scoped(id).subscribe(false);
46 redo_cmd = REDO_CMD.scoped(id).subscribe(false);
47 clear_cmd = CLEAR_HISTORY_CMD.scoped(id).subscribe(false);
48 can_undo = false;
49 can_redo = false;
50 }
51 UiNodeOp::Deinit => {
52 if !is_scope.get() {
53 return;
54 }
55
56 UNDO.with_scope(&mut scope, || c.deinit());
57 scope.deinit();
58 undo_cmd = CommandHandle::dummy();
59 redo_cmd = CommandHandle::dummy();
60 return;
61 }
62 UiNodeOp::Info { info } => {
63 if !is_scope.get() {
64 return;
65 }
66 scope.info(info);
67 }
68 UiNodeOp::Update { .. } => {
69 if let Some(is_scope) = is_scope.get_new() {
70 WIDGET.update_info();
71
72 if is_scope {
73 if !scope.is_inited() {
74 scope.init();
75
76 let id = WIDGET.id();
77 undo_cmd = UNDO_CMD.scoped(id).subscribe(false);
78 redo_cmd = REDO_CMD.scoped(id).subscribe(false);
79 }
80 } else if scope.is_inited() {
81 scope.deinit();
82 undo_cmd = CommandHandle::dummy();
83 redo_cmd = CommandHandle::dummy();
84 }
85 }
86 if !is_scope.get() {
87 return;
88 }
89
90 let id = WIDGET.id();
91 UNDO_CMD.scoped(id).each_update(false, false, |args| {
92 args.propagation.stop();
93 UNDO.with_scope(&mut scope, || {
94 if let Some(&n) = args.param::<u32>() {
95 UNDO.undo_select(n);
96 } else if let Some(&i) = args.param::<Duration>() {
97 UNDO.undo_select(i);
98 } else if let Some(&t) = args.param::<DInstant>() {
99 UNDO.undo_select(t);
100 } else {
101 UNDO.undo();
102 }
103 });
104 });
105 REDO_CMD.scoped(id).each_update(false, false, |args| {
106 args.propagation.stop();
107 UNDO.with_scope(&mut scope, || {
108 if let Some(&n) = args.param::<u32>() {
109 UNDO.redo_select(n);
110 } else if let Some(&i) = args.param::<Duration>() {
111 UNDO.redo_select(i);
112 } else if let Some(&t) = args.param::<DInstant>() {
113 UNDO.redo_select(t);
114 } else {
115 UNDO.redo();
116 }
117 });
118 });
119 CLEAR_HISTORY_CMD.scoped(id).each_update(false, false, |args| {
120 args.propagation.stop();
121 UNDO.with_scope(&mut scope, || {
122 UNDO.clear();
123 });
124 });
125 }
126 _ => {
127 if !is_scope.get() {
128 return;
129 }
130 }
131 }
132
133 UNDO.with_scope(&mut scope, || c.op(op));
134
135 let new_can_undo = scope.can_undo();
136 let new_can_redo = scope.can_redo();
137
138 if can_undo != new_can_undo || can_redo != new_can_redo {
139 tracing::trace!("undo_scope={:?}, undo={new_can_undo:?}, redo={new_can_redo:?}", WIDGET.id());
140 can_undo = new_can_undo;
141 can_redo = new_can_redo;
142 undo_cmd.enabled().set(can_undo);
143 redo_cmd.enabled().set(can_redo);
144 clear_cmd.enabled().set(can_undo || can_redo);
145 }
146 })
147}
148
149#[property(CONTEXT, default(true))]
151pub fn undo_enabled(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
152 let enabled = enabled.into_var();
153 match_node(child, move |c, op| {
154 if !enabled.get() {
155 UNDO.with_disabled(|| c.op(op))
156 }
157 })
158}
159
160#[property(CONTEXT - 11, default(UNDO_LIMIT_VAR))]
166pub fn undo_limit(child: impl IntoUiNode, max: impl IntoVar<u32>) -> UiNode {
167 with_context_var(child, UNDO_LIMIT_VAR, max)
168}
169
170#[property(CONTEXT - 11, default(UNDO_INTERVAL_VAR))]
179pub fn undo_interval(child: impl IntoUiNode, interval: impl IntoVar<Duration>) -> UiNode {
180 with_context_var(child, UNDO_INTERVAL_VAR, interval)
181}
182
183#[widget_mixin]
193pub struct UndoMix<P>(P);
194
195impl<P: WidgetImpl> UndoMix<P> {
196 fn widget_intrinsic(&mut self) {
197 widget_set! {
198 self;
199 crate::undo_scope = true;
200 }
201 }
202
203 widget_impl! {
204 pub undo_enabled(enabled: impl IntoVar<bool>);
208
209 pub undo_limit(limit: impl IntoVar<u32>);
211
212 pub undo_interval(interval: impl IntoVar<Duration>);
217 }
218}