1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
//!
//! Undo properties.
//!
//! # Crate
//!
#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
#![warn(unused_extern_crates)]
#![warn(missing_docs)]
use std::time::Duration;
use zng_ext_undo::*;
use zng_wgt::prelude::*;
/// Sets if the widget is an undo scope.
///
/// If `true` the widget will handle [`UNDO_CMD`] and [`REDO_CMD`] for all undo actions
/// that happen inside it.
///
/// [`UNDO_CMD`]: static@zng_ext_undo::UNDO_CMD
/// [`REDO_CMD`]: static@zng_ext_undo::REDO_CMD
#[property(CONTEXT - 10, default(false))]
pub fn undo_scope(child: impl UiNode, is_scope: impl IntoVar<bool>) -> impl UiNode {
let mut scope = WidgetUndoScope::new();
let mut undo_cmd = CommandHandle::dummy();
let mut redo_cmd = CommandHandle::dummy();
let mut clear_cmd = CommandHandle::dummy();
let is_scope = is_scope.into_var();
match_node(child, move |c, mut op| {
match &mut op {
UiNodeOp::Init => {
WIDGET.sub_var(&is_scope);
if !is_scope.get() {
return; // default handling without scope context.
}
scope.init();
let id = WIDGET.id();
undo_cmd = UNDO_CMD.scoped(id).subscribe(false);
redo_cmd = REDO_CMD.scoped(id).subscribe(false);
clear_cmd = CLEAR_HISTORY_CMD.scoped(id).subscribe(false);
}
UiNodeOp::Deinit => {
if !is_scope.get() {
return;
}
UNDO.with_scope(&mut scope, || c.deinit());
scope.deinit();
undo_cmd = CommandHandle::dummy();
redo_cmd = CommandHandle::dummy();
return;
}
UiNodeOp::Info { info } => {
if !is_scope.get() {
return;
}
scope.info(info);
}
UiNodeOp::Event { update } => {
if !is_scope.get() {
return;
}
let id = WIDGET.id();
if let Some(args) = UNDO_CMD.scoped(id).on_unhandled(update) {
args.propagation().stop();
UNDO.with_scope(&mut scope, || {
if let Some(&n) = args.param::<u32>() {
UNDO.undo_select(n);
} else if let Some(&i) = args.param::<Duration>() {
UNDO.undo_select(i);
} else if let Some(&t) = args.param::<DInstant>() {
UNDO.undo_select(t);
} else {
UNDO.undo();
}
});
} else if let Some(args) = REDO_CMD.scoped(id).on_unhandled(update) {
args.propagation().stop();
UNDO.with_scope(&mut scope, || {
if let Some(&n) = args.param::<u32>() {
UNDO.redo_select(n);
} else if let Some(&i) = args.param::<Duration>() {
UNDO.redo_select(i);
} else if let Some(&t) = args.param::<DInstant>() {
UNDO.redo_select(t);
} else {
UNDO.redo();
}
});
} else if let Some(args) = CLEAR_HISTORY_CMD.scoped(id).on_unhandled(update) {
args.propagation().stop();
UNDO.with_scope(&mut scope, || {
UNDO.clear();
});
}
}
UiNodeOp::Update { .. } => {
if let Some(is_scope) = is_scope.get_new() {
WIDGET.info();
if is_scope {
if !scope.is_inited() {
scope.init();
let id = WIDGET.id();
undo_cmd = UNDO_CMD.scoped(id).subscribe(false);
redo_cmd = REDO_CMD.scoped(id).subscribe(false);
}
} else if scope.is_inited() {
scope.deinit();
undo_cmd = CommandHandle::dummy();
redo_cmd = CommandHandle::dummy();
}
}
if !is_scope.get() {
return;
}
}
_ => {
if !is_scope.get() {
return;
}
}
}
UNDO.with_scope(&mut scope, || c.op(op));
let can_undo = scope.can_undo();
let can_redo = scope.can_redo();
undo_cmd.set_enabled(can_undo);
redo_cmd.set_enabled(can_redo);
clear_cmd.set_enabled(can_undo || can_redo);
})
}
/// Enable or disable undo inside the widget.
#[property(CONTEXT, default(true))]
pub fn undo_enabled(child: impl UiNode, enabled: impl IntoVar<bool>) -> impl UiNode {
let enabled = enabled.into_var();
match_node(child, move |c, op| {
if !enabled.get() {
UNDO.with_disabled(|| c.op(op))
}
})
}
/// Sets the maximum length for undo/redo stacks in the widget and descendants.
///
/// This property sets the [`UNDO_LIMIT_VAR`].
///
/// [`UNDO_LIMIT_VAR`]: zng_ext_undo::UNDO_LIMIT_VAR
#[property(CONTEXT - 11, default(UNDO_LIMIT_VAR))]
pub fn undo_limit(child: impl UiNode, max: impl IntoVar<u32>) -> impl UiNode {
with_context_var(child, UNDO_LIMIT_VAR, max)
}
/// Sets the time interval that undo and redo cover each call for undo handlers in the widget and descendants.
///
/// When undo is requested inside the context all actions after the latest that are within `interval` of the
/// previous are undone.
///
/// This property sets the [`UNDO_INTERVAL_VAR`].
///
/// [`UNDO_INTERVAL_VAR`]: zng_ext_undo::UNDO_INTERVAL_VAR
#[property(CONTEXT - 11, default(UNDO_INTERVAL_VAR))]
pub fn undo_interval(child: impl UiNode, interval: impl IntoVar<Duration>) -> impl UiNode {
with_context_var(child, UNDO_INTERVAL_VAR, interval)
}
/// Undo scope widget mixin.
///
/// Widget is an undo/redo scope, it tracks changes and handles undo/redo commands.
///
/// You can force the widget to use a parent undo scope by setting [`undo_scope`] to `false`, this will cause the widget
/// to start registering undo/redo actions in the parent, note that the widget will continue behaving as if it
/// owns the scope, so it may clear it.
///
/// [`undo_scope`]: fn@undo_scope
#[widget_mixin]
pub struct UndoMix<P>(P);
impl<P: WidgetImpl> UndoMix<P> {
fn widget_intrinsic(&mut self) {
widget_set! {
self;
crate::undo_scope = true;
}
}
widget_impl! {
/// If the widget can register undo actions.
///
/// Is `true` by default in this widget, if set to `false` disables undo in the widget.
pub undo_enabled(enabled: impl IntoVar<bool>);
/// Sets the maximum number of undo/redo actions that are retained in the widget.
pub undo_limit(limit: impl IntoVar<u32>);
/// Sets the time interval that undo and redo cover each call for undo handlers in the widget and descendants.
///
/// When undo is requested inside the context all actions after the latest that are within `interval` of the
/// previous are undone.
pub undo_interval(interval: impl IntoVar<Duration>);
}
}