zng_app/event/
command.rs

1use std::{
2    any::{Any, TypeId},
3    collections::{HashMap, hash_map},
4    fmt, mem,
5    ops::{self, ControlFlow},
6    thread::ThreadId,
7};
8
9use crate::{
10    APP,
11    handler::{Handler, HandlerExt},
12    shortcut::CommandShortcutExt,
13    widget::info::{WidgetInfo, WidgetPath},
14    window::{WINDOWS_APP, WindowId},
15};
16
17use super::*;
18
19/// <span data-del-macro-root></span> Declares new [`Command`] static items.
20///
21/// Command static items represent widget or service actions. Command items are also events, that is they dereference
22/// to [`Event<A>`] and *override* some event methods to enable communication from the command subscribers to the command
23/// notifier. Command static items also host metadata about the command.
24///
25/// [`Event<A>`]: crate::event::Event
26///
27/// # Conventions
28///
29/// Command events have the `_CMD` suffix, for example a command for the clipboard *copy* action is called `COPY_CMD`.
30/// Public and user facing commands also set the [`CommandNameExt`] and [`CommandInfoExt`] with localized display text.
31///
32/// # Shortcuts
33///
34/// You can give commands one or more shortcuts using the [`CommandShortcutExt`], the `GestureManager` notifies commands
35/// that match a pressed shortcut automatically.
36///
37/// # Properties
38///
39/// If the command implementation is not specific you can use `command_property!` to declare properties that setup command handlers
40/// for the command.
41///
42/// # Examples
43///
44/// Declare two commands:
45///
46/// ```
47/// use zng_app::event::command;
48///
49/// command! {
50///     static FOO_CMD;
51///
52///     /// Command docs.
53///     pub(crate) static BAR_CMD;
54/// }
55/// ```
56///
57/// You can also initialize metadata:
58///
59/// ```
60/// use zng_app::{
61///     event::{CommandInfoExt, CommandNameExt, command},
62///     shortcut::{CommandShortcutExt, shortcut},
63/// };
64///
65/// command! {
66///     /// Represents the **foo** action.
67///     pub static FOO_CMD {
68///         name: "Foo!",
69///         info: "Does the foo thing",
70///         shortcut: shortcut![CTRL + 'F'],
71///     };
72/// }
73/// ```
74///
75/// The initialization uses the [command extensions] pattern and runs once for each app.
76///
77/// Or you the special `init: |cmd| { }` to run an arbitrary closure on init:
78///
79/// ```
80/// use zng_app::{
81///     event::{CommandInfoExt, CommandNameExt, command},
82///     shortcut::{CommandShortcutExt, shortcut},
83/// };
84///
85/// command! {
86///     /// Represents the **foo** action.
87///     pub static FOO_CMD {
88///         init: |cmd| {
89///             cmd.init_name("Foo!");
90///             cmd.init_info("Does the foo thing.");
91///             cmd.init_shortcut(shortcut![CTRL + 'F']);
92///         },
93///     };
94/// }
95/// ```
96///
97/// A documentation section is also generated with a table of metadata for each inited metadata.
98///
99/// # Localization
100///
101/// If the first metadata is `l10n!:` the command init will attempt to localize the other string metadata. The `cargo zng l10n`
102/// command line tool scraps commands that set this special metadata.
103///
104/// ```
105/// # use zng_app::{event::{command, CommandNameExt, CommandInfoExt}, shortcut::{CommandShortcutExt, shortcut}};
106/// command! {
107///     pub static FOO_CMD {
108///         l10n!: true,
109///         name: "Foo!",
110///         info: "Does the foo thing",
111///     };
112/// }
113/// ```
114///
115/// The example above will be scrapped as:
116///
117/// ```ftl
118/// FOO_CMD =
119///     .name = Foo!
120///     .info = Does the foo thing.
121/// ```
122///
123/// The `l10n!:` meta can also be set to a localization file name:
124///
125/// ```
126/// # use zng_app::{event::{command, CommandNameExt, CommandInfoExt}, shortcut::{CommandShortcutExt, shortcut}};
127/// command! {
128///     pub static FOO_CMD {
129///         l10n!: "file",
130///         name: "Foo!",
131///     };
132/// }
133/// ```
134///
135/// The example above is scrapped to `{l10n-dir}/{lang}/file.ftl` files.
136///
137/// ## Limitations
138///
139/// Interpolation is not supported in command localization strings.
140///
141/// The `l10n!:` value must be a *textual* literal, that is, it can be only a string literal or a `bool` literal, and it cannot be
142/// inside a macro expansion.
143///
144/// [`Command`]: crate::event::Command
145/// [`CommandArgs`]: crate::event::CommandArgs
146/// [`CommandNameExt`]: crate::event::CommandNameExt
147/// [`CommandInfoExt`]: crate::event::CommandInfoExt
148/// [`Event`]: crate::event::Event
149/// [command extensions]: crate::event::Command#extensions
150/// [`CommandShortcutExt`]: crate::shortcut::CommandShortcutExt
151#[macro_export]
152macro_rules! command {
153    ($(
154        $(#[$attr:meta])*
155        $vis:vis static $COMMAND:ident $({ $($meta_ident:ident $(!)? : $meta_init:expr),* $(,)? };)? $(;)?
156    )+) => {
157        $(
158            $crate::__command! {
159                $(#[$attr])*
160                $vis static $COMMAND $({
161                    $($meta_ident: $meta_init,)+
162                })? ;
163            }
164        )+
165    }
166}
167#[doc(inline)]
168pub use command;
169
170use parking_lot::Mutex;
171use zng_state_map::{OwnedStateMap, StateId, StateMapMut, StateValue};
172use zng_txt::Txt;
173use zng_unique_id::{static_id, unique_id_64};
174use zng_var::{Var, VarHandles, VarValue, const_var, impl_from_and_into_var, var};
175
176#[doc(hidden)]
177pub use zng_app_context::app_local;
178
179#[doc(hidden)]
180pub use pastey::paste;
181
182#[doc(hidden)]
183#[macro_export]
184macro_rules! __command {
185    (
186        $(#[$attr:meta])*
187        $vis:vis static $COMMAND:ident { l10n: $l10n_arg:expr, $($meta_ident:ident : $meta_init:expr),* $(,)? };
188    ) => {
189        $(#[$attr])*
190        ///
191        /// # Metadata
192        ///
193        /// This command has the following default metadata:
194        ///
195        $(#[doc = concat!("* `", stringify!($meta_ident), "`")])+
196        ///
197        /// Text metadata is localized.
198        $vis static $COMMAND: $crate::event::Command = {
199            fn __meta_init__(cmd: $crate::event::Command) {
200                let __l10n_arg = $l10n_arg;
201                $crate::event::paste! {$(
202                    cmd.[<init_ $meta_ident>]($meta_init);
203                    $crate::event::init_meta_l10n(std::env!("CARGO_PKG_NAME"), std::env!("CARGO_PKG_VERSION"), &__l10n_arg, cmd, stringify!($meta_ident), &cmd.$meta_ident());
204                )*}
205            }
206            $crate::event::app_local! {
207                static EVENT: $crate::event::EventData = $crate::event::EventData::new::<$crate::event::CommandArgs>();
208                static DATA: $crate::event::CommandData = $crate::event::CommandData::new(__meta_init__, stringify!($COMMAND));
209            }
210            $crate::event::Command::new(&EVENT, &DATA)
211        };
212    };
213    (
214        $(#[$attr:meta])*
215        $vis:vis static $COMMAND:ident { $($meta_ident:ident : $meta_init:expr),* $(,)? };
216    ) => {
217        $(#[$attr])*
218        ///
219        /// # Metadata
220        ///
221        /// This command has the following default metadata:
222        ///
223        $(#[doc = concat!("* `", stringify!($meta_ident), "`")])+
224        $vis static $COMMAND: $crate::event::Command = {
225            fn __meta_init__(cmd: $crate::event::Command) {
226                $crate::event::paste! {$(
227                    cmd.[<init_ $meta_ident>]($meta_init);
228                )*}
229            }
230            $crate::event::app_local! {
231                static EVENT: $crate::event::EventData = $crate::event::EventData::new::<$crate::event::CommandArgs>();
232                static DATA: $crate::event::CommandData = $crate::event::CommandData::new(__meta_init__, stringify!($COMMAND));
233            }
234            $crate::event::Command::new(&EVENT, &DATA)
235        };
236    };
237    (
238        $(#[$attr:meta])*
239        $vis:vis static $COMMAND:ident;
240    ) => {
241        $(#[$attr])*
242        $vis static $COMMAND: $crate::event::Command = {
243            fn __meta_init__(_: $crate::event::Command) {
244            }
245            $crate::event::app_local! {
246                static EVENT: $crate::event::EventData = $crate::event::EventData::new::<$crate::event::CommandArgs>();
247                static DATA: $crate::event::CommandData = $crate::event::CommandData::new(__meta_init__, stringify!($COMMAND));
248            }
249            $crate::event::Command::new(&EVENT, &DATA)
250        };
251    };
252}
253
254#[doc(hidden)]
255pub fn init_meta_l10n(
256    pkg_name: &'static str,
257    pkg_version: &'static str,
258    l10n_arg: &dyn Any,
259    cmd: Command,
260    meta_name: &'static str,
261    meta_value: &dyn Any,
262) {
263    if let Some(txt) = meta_value.downcast_ref::<CommandMetaVar<Txt>>() {
264        let mut l10n_file = "";
265
266        if let Some(&enabled) = l10n_arg.downcast_ref::<bool>() {
267            if !enabled {
268                return;
269            }
270        } else if let Some(&file) = l10n_arg.downcast_ref::<&'static str>() {
271            l10n_file = file;
272        } else {
273            tracing::error!("unknown l10n value in {:?}", cmd.event());
274            return;
275        }
276
277        EVENTS_L10N.init_meta_l10n([pkg_name, pkg_version, l10n_file], cmd, meta_name, txt.clone());
278    }
279}
280
281/// Identifies a command event.
282///
283/// Use the [`command!`] to declare commands, it declares command static items with optional
284/// [metadata](#metadata) initialization.
285///
286/// ```
287/// # use zng_app::event::*;
288/// # pub trait CommandFooBarExt: Sized { fn init_foo(self, foo: bool) -> Self { self } fn init_bar(self, bar: bool) -> Self { self } }
289/// # impl CommandFooBarExt for Command { }
290/// command! {
291///     /// Foo-bar command.
292///     pub static FOO_BAR_CMD { foo: true, bar: false };
293/// }
294/// ```
295///
296/// # Metadata
297///
298/// Commands can have associated metadata, this metadata is extendable and can be used to enable
299/// command features such as command shortcuts. The metadata can be accessed using [`with_meta`], metadata
300/// extensions traits can use this metadata to store state. See [`CommandMeta`] for more details.
301///
302/// # Handles
303///
304/// Unlike other events, commands only notify if it has at least one handler, handlers
305/// must call [`subscribe`] to indicate that the command is relevant to the current app state and
306/// set the subscription handle [`enabled`] flag to indicate that the handler can fulfill command requests.
307///
308/// # Scopes
309///
310/// Commands are *global* by default, meaning an enabled handle anywhere in the app enables it everywhere.
311/// You can use [`scoped`] to declare *sub-commands* that are the same command event, but filtered to a scope, metadata
312/// of scoped commands inherit from the app scope metadata, but can be overridden just for the scope.
313///
314/// [`command!`]: macro@crate::event::command
315/// [`subscribe`]: Command::subscribe
316/// [`with_meta`]: Command::with_meta
317/// [`scoped`]: Command::scoped
318/// [`enabled`]: CommandHandle::enabled
319#[derive(Clone, Copy)]
320pub struct Command {
321    event: Event<CommandArgs>,
322    local: &'static AppLocal<CommandData>,
323    scope: CommandScope,
324}
325struct CommandDbg {
326    static_name: &'static str,
327    scope: CommandScope,
328    state: Option<[usize; 2]>,
329}
330impl CommandDbg {
331    fn new(static_name: &'static str, scope: CommandScope) -> Self {
332        Self {
333            static_name,
334            scope,
335            state: None,
336        }
337    }
338}
339impl fmt::Debug for CommandDbg {
340    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341        if f.alternate() {
342            let mut d = f.debug_struct("Command");
343            d.field("static_name", &self.static_name).field("scope", &self.scope);
344            if let Some([has, enabled]) = &self.state {
345                d.field("handle_count", has);
346                d.field("enabled_count", enabled);
347            }
348
349            d.finish_non_exhaustive()
350        } else {
351            write!(f, "{}", self.static_name)?;
352            match self.scope {
353                CommandScope::App => Ok(()),
354                CommandScope::Window(id) => write!(f, "({id})"),
355                CommandScope::Widget(id) => write!(f, "({id})"),
356            }
357        }
358    }
359}
360impl fmt::Debug for Command {
361    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362        let dbg = if let Some(d) = self.local.try_read() {
363            let mut dbg = CommandDbg::new(d.static_name, self.scope);
364            dbg.state = Some([d.handle_count, d.enabled_count]);
365            dbg
366        } else {
367            CommandDbg::new("<locked>", self.scope)
368        };
369        fmt::Debug::fmt(&dbg, f)
370    }
371}
372impl Command {
373    #[doc(hidden)]
374    pub const fn new(event_local: &'static AppLocal<EventData>, command_local: &'static AppLocal<CommandData>) -> Self {
375        Command {
376            event: Event::new(event_local),
377            local: command_local,
378            scope: CommandScope::App,
379        }
380    }
381
382    /// Create a new handle to this command.
383    ///
384    /// A handle indicates that command handlers are present in the current app, the `enabled` flag
385    /// indicates the handler is ready to fulfill command requests.
386    ///
387    /// If the command is scoped on a window or widget if it is added to the command event subscribers.
388    pub fn subscribe(&self, enabled: bool) -> CommandHandle {
389        self.local.write().subscribe(*self, enabled, None)
390    }
391
392    /// Create a new handle for this command for a handler in the `target` widget.
393    ///
394    /// The handle behaves like [`subscribe`], but include the `target` on the delivery list for app scoped commands.
395    /// Note this only works for global commands (app scope), window and widget scoped commands only notify the scope
396    /// so the `target` is ignored for scoped commands.
397    ///
398    /// [`subscribe`]: Command::subscribe
399    pub fn subscribe_wgt(&self, enabled: bool, target: WidgetId) -> CommandHandle {
400        self.local.write().subscribe(*self, enabled, Some(target))
401    }
402
403    /// Underlying event that represents this command in any scope.
404    pub fn event(&self) -> Event<CommandArgs> {
405        self.event
406    }
407
408    /// Command scope.
409    pub fn scope(&self) -> CommandScope {
410        self.scope
411    }
412
413    /// Gets the command in a new `scope`.
414    pub fn scoped(mut self, scope: impl Into<CommandScope>) -> Command {
415        self.scope = scope.into();
416        self
417    }
418
419    /// Visit the command custom metadata of the current scope.
420    ///
421    /// Metadata for [`CommandScope::App`] is retained for the duration of the app, metadata scoped
422    /// on window or widgets is dropped after an update cycle with no handler and no strong references
423    /// to [`has_handlers`] and [`is_enabled`].
424    ///
425    /// [`has_handlers`]: Self::has_handlers
426    /// [`is_enabled`]: Self::is_enabled
427    pub fn with_meta<R>(&self, visit: impl FnOnce(&mut CommandMeta) -> R) -> R {
428        // code that runs before  calling `visit`, removed from the generics function
429        fn init_meta(self_: &Command) -> parking_lot::MappedRwLockReadGuard<'static, CommandData> {
430            {
431                let mut write = self_.local.write();
432                match write.meta_init.clone() {
433                    MetaInit::Init(init) => {
434                        let lock = Arc::new((std::thread::current().id(), Mutex::new(())));
435                        write.meta_init = MetaInit::Initing(lock.clone());
436                        let _init_guard = lock.1.lock();
437                        drop(write);
438                        init(*self_);
439                        self_.local.write().meta_init = MetaInit::Inited;
440                    }
441                    MetaInit::Initing(l) => {
442                        drop(write);
443                        if l.0 != std::thread::current().id() {
444                            let _wait = l.1.lock();
445                        }
446                    }
447                    MetaInit::Inited => {}
448                }
449            }
450
451            if !matches!(self_.scope, CommandScope::App) {
452                let mut write = self_.local.write();
453                write.scopes.entry(self_.scope).or_default();
454            }
455            self_.local.read()
456        }
457        let local_read = init_meta(self);
458        let mut meta_lock = local_read.meta.lock();
459
460        match self.scope {
461            CommandScope::App => visit(&mut CommandMeta {
462                meta: meta_lock.borrow_mut(),
463                scope: None,
464            }),
465            scope => {
466                let scope = local_read.scopes.get(&scope).unwrap();
467                visit(&mut CommandMeta {
468                    meta: meta_lock.borrow_mut(),
469                    scope: Some(scope.meta.lock().borrow_mut()),
470                })
471            }
472        }
473    }
474
475    /// Gets a variable that tracks if this command has any handlers.
476    pub fn has_handlers(&self) -> Var<bool> {
477        let mut write = self.local.write();
478        match self.scope {
479            CommandScope::App => write.has_handlers.read_only(),
480            scope => write.scopes.entry(scope).or_default().has_handlers.read_only(),
481        }
482    }
483
484    /// Gets a variable that tracks if this command has any enabled handlers.
485    pub fn is_enabled(&self) -> Var<bool> {
486        let mut write = self.local.write();
487        match self.scope {
488            CommandScope::App => write.is_enabled.read_only(),
489            scope => write.scopes.entry(scope).or_default().is_enabled.read_only(),
490        }
491    }
492
493    /// Calls `visitor` for each scope of this command.
494    ///
495    /// Note that scoped commands are removed if unused, see [`with_meta`](Self::with_meta) for more details.
496    pub fn visit_scopes<T>(&self, mut visitor: impl FnMut(Command) -> ControlFlow<T>) -> Option<T> {
497        let read = self.local.read();
498        for &scope in read.scopes.keys() {
499            match visitor(self.scoped(scope)) {
500                ControlFlow::Continue(_) => continue,
501                ControlFlow::Break(r) => return Some(r),
502            }
503        }
504        None
505    }
506
507    /// Schedule a command update without param.
508    pub fn notify(&self) {
509        self.event.notify(CommandArgs::now(
510            None,
511            self.scope,
512            self.scope.search_target(),
513            self.is_enabled().get(),
514        ))
515    }
516
517    /// Schedule a command update without param for all scopes inside `parent`.
518    pub fn notify_descendants(&self, parent: &WidgetInfo) {
519        self.visit_scopes::<()>(|parse_cmd| {
520            if let CommandScope::Widget(id) = parse_cmd.scope()
521                && let Some(scope) = parent.tree().get(id)
522                && scope.is_descendant(parent)
523            {
524                parse_cmd.notify();
525            }
526            ControlFlow::Continue(())
527        });
528    }
529
530    /// Schedule a command update with custom `param`.
531    pub fn notify_param(&self, param: impl Any + Send + Sync) {
532        self.event.notify(CommandArgs::now(
533            CommandParam::new(param),
534            self.scope,
535            self.scope.search_target(),
536            self.is_enabled().get(),
537        ));
538    }
539
540    /// Schedule a command update linked with an external event `propagation`.
541    pub fn notify_linked(&self, propagation: EventPropagationHandle, param: Option<CommandParam>) {
542        self.event.notify(CommandArgs::new(
543            crate::INSTANT.now(),
544            propagation,
545            param,
546            self.scope,
547            self.scope.search_target(),
548            self.is_enabled().get(),
549        ))
550    }
551
552    /// Visit each new update, oldest first, that target the context widget.
553    ///
554    /// This is similar to [`Event::each_update`], but with extra filtering. If `direct_scope_only` is enabled
555    /// only handle exact command scope matches, otherwise the app scope matches all events, the window scope matches all events for the window
556    /// or widgets in the window and the widget scope matches the widget and all descendants.
557    pub fn each_update(&self, direct_scope_only: bool, ignore_propagation: bool, mut handler: impl FnMut(&CommandArgs)) {
558        self.event.each_update(ignore_propagation, move |args| {
559            if args.scope_matches(direct_scope_only, self.scope) {
560                handler(args);
561            }
562        });
563    }
564
565    /// Visit the latest update that targets the context widget.
566    ///
567    /// This is similar to [`Event::latest_update`], but with extra filtering.
568    pub fn latest_update<O>(
569        &self,
570        direct_scope_only: bool,
571        ignore_propagation: bool,
572        handler: impl FnOnce(&CommandArgs) -> O,
573    ) -> Option<O> {
574        let mut r = None;
575        self.event.latest_update(ignore_propagation, |args| {
576            if args.scope_matches(direct_scope_only, self.scope) {
577                r = Some(handler(args));
578            }
579        });
580        r
581    }
582
583    /// Visit the latest update that targets the context widget.
584    ///
585    /// This is similar to [`Event::has_update`], but with extra filtering.
586    pub fn has_update(&self, direct_scope_only: bool, ignore_propagation: bool) -> bool {
587        self.latest_update(direct_scope_only, ignore_propagation, |_| true).unwrap_or(false)
588    }
589
590    /// Creates a preview event handler for the command.
591    ///
592    /// This is similar to [`Event::on_pre_event`], but with extra filtering. The `handler` is only called if
593    /// handle is [`enabled`] and if the scope matches. if `direct_scope_only` is enabled only handles exact
594    /// matches, otherwise the app scope matches all events, the window scope matches all events for the window
595    /// or widgets in the window and the widget scope matches the widget and all descendants.
596    ///
597    /// The `init_enabled` value defines the handle initial state.
598    ///
599    /// [`enabled`]: CommandHandle::enabled
600    pub fn on_pre_event(
601        &self,
602        init_enabled: bool,
603        direct_scope_only: bool,
604        ignore_propagation: bool,
605        handler: Handler<CommandArgs>,
606    ) -> CommandHandle {
607        let (mut handle, handler) = self.event_handler(init_enabled, direct_scope_only, handler);
608        handle._handles.push(self.event().on_pre_event(ignore_propagation, handler));
609        handle
610    }
611
612    /// Creates an event handler for the command.
613    ///
614    /// This is similar to [`Event::on_event`], but with extra filtering. The `handler` is only called if
615    /// the command handle is [`enabled`] and if the scope matches. if `direct_scope_only` is enabled only handles exact
616    /// matches, otherwise the app scope matches all events, the window scope matches all events for the window
617    /// or widgets in the window and the widget scope matches the widget and all descendants.
618    ///
619    /// The `init_enabled` value defines the handle initial state.
620    ///
621    /// [`enabled`]: CommandHandle::enabled
622    pub fn on_event(
623        &self,
624        init_enabled: bool,
625        direct_scope_only: bool,
626        ignore_propagation: bool,
627        handler: Handler<CommandArgs>,
628    ) -> CommandHandle {
629        let (mut handle, handler) = self.event_handler(init_enabled, direct_scope_only, handler);
630        handle._handles.push(self.event().on_event(ignore_propagation, handler));
631        handle
632    }
633
634    fn event_handler(
635        &self,
636        init_enabled: bool,
637        direct_scope_only: bool,
638        handler: Handler<CommandArgs>,
639    ) -> (CommandHandle, Handler<CommandArgs>) {
640        let handle = self.subscribe(init_enabled);
641        let local_enabled = handle.enabled().clone();
642        let handler = if direct_scope_only {
643            let scope = self.scope();
644            handler.filtered(move |a| a.scope == scope && local_enabled.get())
645        } else {
646            match self.scope() {
647                CommandScope::App => handler.filtered(move |_| local_enabled.get()),
648                CommandScope::Window(id) => {
649                    handler.filtered(move |a| a.target.as_ref().map(|t| t.window_id() == id).unwrap_or(false) && local_enabled.get())
650                }
651                CommandScope::Widget(id) => {
652                    handler.filtered(move |a| a.target.as_ref().map(|t| t.contains(id)).unwrap_or(false) && local_enabled.get())
653                }
654            }
655        };
656        (handle, handler)
657    }
658
659    /// Sets a handler like [`Command::on_pre_event`], but the args include the handle [`enabled`] and the handler
660    /// is called when the handle is disabled as well.
661    ///
662    /// [`enabled`]: CommandHandle::enabled
663    pub fn on_pre_event_with_enabled(
664        &self,
665        init_enabled: bool,
666        direct_scope_only: bool,
667        ignore_propagation: bool,
668        handler: Handler<(CommandArgs, Var<bool>)>,
669    ) -> CommandHandle {
670        let (mut handle, handler) = self.event_handler_with_enabled(init_enabled, direct_scope_only, handler);
671        handle._handles.push(self.event().on_pre_event(ignore_propagation, handler));
672        handle
673    }
674
675    /// Sets a handler like [`Command::on_event`], but the args include the handle [`enabled`] and the handler
676    /// is called when the handle is disabled as well.
677    ///
678    /// [`enabled`]: CommandHandle::enabled
679    pub fn on_event_with_enabled(
680        &self,
681        init_enabled: bool,
682        direct_scope_only: bool,
683        ignore_propagation: bool,
684        handler: Handler<(CommandArgs, Var<bool>)>,
685    ) -> CommandHandle {
686        let (mut handle, handler) = self.event_handler_with_enabled(init_enabled, direct_scope_only, handler);
687        handle._handles.push(self.event().on_event(ignore_propagation, handler));
688        handle
689    }
690
691    fn event_handler_with_enabled(
692        &self,
693        init_enabled: bool,
694        direct_scope_only: bool,
695        mut handler: Handler<(CommandArgs, Var<bool>)>,
696    ) -> (CommandHandle, Handler<CommandArgs>) {
697        let handle = self.subscribe(init_enabled);
698        let local_enabled = handle.enabled().clone();
699
700        let r: Handler<CommandArgs>;
701        if direct_scope_only {
702            let scope = self.scope();
703            r = Box::new(move |a: &CommandArgs| {
704                if a.scope == scope {
705                    handler(&(a.clone(), local_enabled.clone()))
706                } else {
707                    HandlerResult::Done
708                }
709            });
710        } else {
711            match self.scope() {
712                CommandScope::App => r = Box::new(move |a: &CommandArgs| handler(&(a.clone(), local_enabled.clone()))),
713                CommandScope::Window(id) => {
714                    r = Box::new(move |a: &CommandArgs| {
715                        if a.target.as_ref().map(|t| t.window_id() == id).unwrap_or(false) {
716                            handler(&(a.clone(), local_enabled.clone()))
717                        } else {
718                            HandlerResult::Done
719                        }
720                    })
721                }
722                CommandScope::Widget(id) => {
723                    r = Box::new(move |a: &CommandArgs| {
724                        if a.target.as_ref().map(|t| t.contains(id)).unwrap_or(false) {
725                            handler(&(a.clone(), local_enabled.clone()))
726                        } else {
727                            HandlerResult::Done
728                        }
729                    })
730                }
731            }
732        };
733        (handle, r)
734    }
735
736    /// Name of the `static` item that defines this command.
737    pub fn static_name(&self) -> &'static str {
738        self.local.read().static_name
739    }
740}
741impl ops::Deref for Command {
742    type Target = Event<CommandArgs>;
743
744    fn deref(&self) -> &Self::Target {
745        &self.event
746    }
747}
748impl PartialEq for Command {
749    fn eq(&self, other: &Self) -> bool {
750        self.event == other.event && self.scope == other.scope
751    }
752}
753impl Eq for Command {}
754impl std::hash::Hash for Command {
755    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
756        std::hash::Hash::hash(&self.event.as_any(), state);
757        std::hash::Hash::hash(&self.scope, state);
758    }
759}
760
761/// Represents the scope of a [`Command`].
762///
763/// The command scope defines the targets of its event and the context of its metadata.
764#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
765pub enum CommandScope {
766    /// Default scope, this is the scope of command types declared using [`command!`].
767    App,
768    /// Scope of a window.
769    ///
770    /// Note that the window scope is different from the window root widget scope, the metadata store and command
771    /// handles are different, but subscribers set on the window root should probably also subscribe to the window scope.
772    Window(WindowId),
773    /// Scope of a widget.
774    Widget(WidgetId),
775}
776impl CommandScope {
777    /// Search for the widget scope, or the window root widget for window scope.
778    pub fn search_target(self) -> Option<WidgetPath> {
779        match self {
780            CommandScope::App => None,
781            CommandScope::Window(id) => WINDOWS_APP.widget_tree(id).map(|t| t.root().path()),
782            CommandScope::Widget(id) => WINDOWS_APP.widget_info(id).map(|w| w.path()),
783        }
784    }
785}
786impl_from_and_into_var! {
787    fn from(id: WidgetId) -> CommandScope {
788        CommandScope::Widget(id)
789    }
790    fn from(id: WindowId) -> CommandScope {
791        CommandScope::Window(id)
792    }
793    /// Widget scope.
794    fn from(widget_name: &'static str) -> CommandScope {
795        WidgetId::named(widget_name).into()
796    }
797    /// Widget scope.
798    fn from(widget_name: Txt) -> CommandScope {
799        WidgetId::named(widget_name).into()
800    }
801}
802
803event_args! {
804    /// Event args for command events.
805    pub struct CommandArgs {
806        /// Optional parameter for the command handler.
807        pub param: Option<CommandParam>,
808
809        /// Scope of command that notified.
810        pub scope: CommandScope,
811
812        /// Target widget.
813        ///
814        /// * If the scope is `App` this is `None`.
815        /// * If the scope is `Window` this is the window root widget, if the window was found.
816        /// * If the scope is `Widget` this is the widget, if it was found.
817        pub target: Option<WidgetPath>,
818
819        /// If the command was enabled when the command notified.
820        ///
821        /// If `false` the command primary action must not run, but a secondary "disabled interaction"
822        /// that indicates what conditions enable the command is recommended.
823        ///
824        /// Note that this is the [`Command::is_enabled`] value, it is `true` id any handle is enabled,
825        /// the local handler might still be disabled.
826        pub enabled: bool,
827
828        ..
829
830        /// Broadcast to all if the scope is `App`, otherwise if is in `target`.
831        fn is_in_target(&self, id: WidgetId) -> bool {
832            match self.scope {
833                CommandScope::App => true,
834                _ => match &self.target {
835                    Some(t) => t.contains(id),
836                    None => false,
837                },
838            }
839        }
840
841        /// Validates if the target matches the scope.
842        fn validate(&self) -> Result<(), Txt> {
843            if let Some(t) = &self.target {
844                match self.scope {
845                    CommandScope::App => return Err("args for app scope cannot have a `target`".into()),
846                    CommandScope::Window(id) => {
847                        if id != t.window_id() || t.widgets_path().len() > 1 {
848                            return Err("args for window scope must only `target` that window root widget".into());
849                        }
850                    }
851                    CommandScope::Widget(id) => {
852                        if id != t.widget_id() {
853                            return Err("args for widget scope must only `target` that widget".into());
854                        }
855                    }
856                }
857            }
858            Ok(())
859        }
860    }
861}
862impl CommandArgs {
863    /// Returns a reference to a parameter of `T` if [`parameter`](#structfield.parameter) is set to a value of `T`.
864    pub fn param<T: Any>(&self) -> Option<&T> {
865        self.param.as_ref().and_then(|p| p.downcast_ref::<T>())
866    }
867
868    /// Returns [`param`] if is [`enabled`].
869    ///
870    /// [`param`]: Self::param()
871    /// [`enabled`]: Self::enabled
872    pub fn enabled_param<T: Any>(&self) -> Option<&T> {
873        if self.enabled { self.param::<T>() } else { None }
874    }
875
876    /// Returns [`param`] if is not [`enabled`].
877    ///
878    /// [`param`]: Self::param()
879    /// [`enabled`]: Self::enabled
880    pub fn disabled_param<T: Any>(&self) -> Option<&T> {
881        if !self.enabled { self.param::<T>() } else { None }
882    }
883
884    /// If `direct_only` is enabled only matches exact command scope matches,
885    /// otherwise the app `scope` matches all args, the window `scope` matches all events for the window
886    /// or widgets in the window and the widget `scope` matches the widget and all descendants.
887    pub fn scope_matches(&self, direct_only: bool, scope: CommandScope) -> bool {
888        if direct_only {
889            self.scope == scope
890        } else {
891            match (scope, self.scope) {
892                (CommandScope::App, _) => true,
893                (CommandScope::Window(scope_id), CommandScope::Window(args_id)) => scope_id == args_id,
894                (CommandScope::Window(scope_id), CommandScope::Widget(args_id)) => {
895                    // if window contains widget
896                    if let Some(t) = &self.target {
897                        t.window_id() == scope_id && t.contains(args_id)
898                    } else if let Some(info) = WINDOWS_APP.widget_tree(scope_id) {
899                        info.contains(args_id)
900                    } else {
901                        false
902                    }
903                }
904                (CommandScope::Widget(scope_id), CommandScope::Widget(args_id)) => {
905                    // if scope widget contains args scope widget
906                    if let Some(t) = &self.target {
907                        t.widgets_path().iter().position(|i| *i == scope_id).unwrap_or(usize::MAX)
908                            < t.widgets_path().iter().position(|i| *i == args_id).unwrap_or(usize::MAX)
909                    } else {
910                        todo!()
911                    }
912                }
913                _ => false,
914            }
915        }
916    }
917}
918
919/// A handle to a [`Command`] subscription.
920///
921/// Holding the command handle indicates that the command is relevant in the current app state.
922/// The handle also needs to be enabled to indicate that the command primary action can be executed.
923///
924/// You can use the [`Command::subscribe`] method in a command type to create a handle.
925pub struct CommandHandle {
926    command: Option<Command>,
927    local_enabled: Var<bool>,
928    // event subscription handle in `subscribe` or event handle in `on_(pre_)event`
929    _handles: VarHandles,
930}
931/// Clone the handle.
932///
933/// Note that the cloned handle will have its own enabled state, disconnected from this handle.
934impl Clone for CommandHandle {
935    fn clone(&self) -> Self {
936        match self.command {
937            Some(c) => c.subscribe(self.local_enabled.get()),
938            None => Self::dummy(),
939        }
940    }
941}
942impl CommandHandle {
943    /// The command.
944    pub fn command(&self) -> Option<Command> {
945        self.command
946    }
947
948    /// Variable that gets and sets if this specific handle is enabled.
949    ///
950    /// The [`Command::is_enabled`] is `true` if any handle is enabled.
951    pub fn enabled(&self) -> &Var<bool> {
952        &self.local_enabled
953    }
954
955    /// New handle not connected to any command.
956    pub fn dummy() -> Self {
957        CommandHandle {
958            command: None,
959            local_enabled: const_var(false),
960            _handles: VarHandles::dummy(),
961        }
962    }
963
964    fn new(cmd: Command, event_handle: VarHandle, enabled: bool) -> Self {
965        let mut r = Self {
966            command: Some(cmd),
967            local_enabled: var(enabled),
968            _handles: VarHandles::dummy(),
969        };
970
971        // var explicit update can call hook without changing value
972        let mut last_applied = enabled;
973        r._handles.push(r.local_enabled.hook(move |args| {
974            let _hold = &event_handle;
975            let enabled = *args.value();
976            if last_applied != enabled {
977                Self::update_enabled(cmd, enabled);
978                last_applied = enabled;
979            }
980            true
981        }));
982
983        r
984    }
985
986    fn update_enabled(command: Command, enabled: bool) {
987        let mut write = command.local.write();
988        match command.scope {
989            CommandScope::App => {
990                if enabled {
991                    write.enabled_count += 1;
992                    if write.enabled_count == 1 {
993                        write.is_enabled.set(true);
994                    }
995                    tracing::trace!(
996                        "command handle {:?} enabled, count: {:?}",
997                        CommandDbg::new(write.static_name, command.scope),
998                        write.enabled_count
999                    );
1000                } else {
1001                    write.enabled_count = match write.enabled_count.checked_sub(1) {
1002                        Some(c) => c,
1003                        None => {
1004                            #[cfg(debug_assertions)]
1005                            panic!("handle for {} was disabled when enabled_count was already zero", write.static_name);
1006                            #[cfg(not(debug_assertions))]
1007                            0
1008                        }
1009                    };
1010                    if write.enabled_count == 0 {
1011                        write.is_enabled.set(false);
1012                    }
1013                    tracing::trace!(
1014                        "command handle {:?} disabled, count: {:?}",
1015                        CommandDbg::new(write.static_name, command.scope),
1016                        write.enabled_count
1017                    );
1018                }
1019            }
1020            scope => {
1021                let write = &mut *write;
1022                if let Some(data) = write.scopes.get_mut(&scope) {
1023                    if enabled {
1024                        data.enabled_count += 1;
1025                        if data.enabled_count == 1 {
1026                            data.is_enabled.set(true);
1027                        }
1028                        tracing::trace!(
1029                            "command handle {:?} enabled, count: {:?}",
1030                            CommandDbg::new(write.static_name, command.scope),
1031                            data.enabled_count
1032                        );
1033                    } else {
1034                        data.enabled_count = match data.enabled_count.checked_sub(1) {
1035                            Some(c) => c,
1036                            None => {
1037                                #[cfg(debug_assertions)]
1038                                panic!(
1039                                    "handle for {:?} was disabled when enabled_count was already zero",
1040                                    CommandDbg::new(write.static_name, scope)
1041                                );
1042                                #[cfg(not(debug_assertions))]
1043                                0
1044                            }
1045                        };
1046                        if data.enabled_count == 0 {
1047                            data.is_enabled.set(false);
1048                        }
1049                        tracing::trace!(
1050                            "command handle {:?} enabled, count: {:?}",
1051                            CommandDbg::new(write.static_name, command.scope),
1052                            data.enabled_count
1053                        );
1054                    }
1055                }
1056            }
1057        }
1058    }
1059
1060    /// If the handle is not connected to any command.
1061    pub fn is_dummy(&self) -> bool {
1062        self.command.is_none()
1063    }
1064
1065    /// Drop the handle removing its effect.
1066    ///
1067    /// The command will be stuck indicating that it has handlers and if this handle is enabled, it will
1068    /// continue indicating it is enabled until the app exit.
1069    pub fn perm(mut self) {
1070        // perm the handle
1071        mem::replace(&mut self._handles, VarHandles::dummy()).perm();
1072        // pretend we are dummy to avoid Drop code
1073        self.command = None;
1074    }
1075}
1076impl fmt::Debug for CommandHandle {
1077    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1078        f.debug_struct("CommandHandle")
1079            .field("command", &self.command)
1080            .field("enabled", &self.local_enabled.get())
1081            .finish_non_exhaustive()
1082    }
1083}
1084impl Drop for CommandHandle {
1085    fn drop(&mut self) {
1086        if let Some(command) = self.command.take()
1087            && APP.is_started()
1088        {
1089            let mut write = command.local.write();
1090            match command.scope {
1091                CommandScope::App => {
1092                    write.handle_count = match write.handle_count.checked_sub(1) {
1093                        Some(c) => c,
1094                        None => {
1095                            #[cfg(debug_assertions)]
1096                            panic!("handle for {} was dropped when handle_count was already zero", write.static_name);
1097                            #[cfg(not(debug_assertions))]
1098                            0
1099                        }
1100                    };
1101                    if write.handle_count == 0 {
1102                        write.has_handlers.set(false);
1103                    }
1104
1105                    if self.local_enabled.get() {
1106                        write.enabled_count = match write.enabled_count.checked_sub(1) {
1107                            Some(c) => c,
1108                            None => {
1109                                #[cfg(debug_assertions)]
1110                                panic!(
1111                                    "handle for enabled {} was dropped when enabled_count was already zero",
1112                                    write.static_name
1113                                );
1114                                #[cfg(not(debug_assertions))]
1115                                0
1116                            }
1117                        };
1118
1119                        if write.enabled_count == 0 {
1120                            write.is_enabled.set(false);
1121                        }
1122                    }
1123
1124                    tracing::trace!(
1125                        "unsubscribe to {:?}, handle_count: {:?}, enabled_count: {:?}",
1126                        CommandDbg::new(write.static_name, command.scope),
1127                        write.handle_count,
1128                        write.enabled_count
1129                    );
1130                }
1131                scope => {
1132                    let write = &mut *write;
1133                    if let hash_map::Entry::Occupied(mut entry) = write.scopes.entry(scope) {
1134                        let data = entry.get_mut();
1135
1136                        data.handle_count = match data.handle_count.checked_sub(1) {
1137                            Some(c) => c,
1138                            None => {
1139                                #[cfg(debug_assertions)]
1140                                panic!(
1141                                    "handle for {:?} was dropped when handle_count was already zero",
1142                                    CommandDbg::new(write.static_name, scope)
1143                                );
1144                                #[cfg(not(debug_assertions))]
1145                                0
1146                            }
1147                        };
1148
1149                        if self.local_enabled.get() {
1150                            data.enabled_count = match data.enabled_count.checked_sub(1) {
1151                                Some(c) => c,
1152                                None => {
1153                                    #[cfg(debug_assertions)]
1154                                    panic!(
1155                                        "handle for enabled {:?} was dropped when enabled_count was already zero",
1156                                        CommandDbg::new(write.static_name, scope)
1157                                    );
1158                                    #[cfg(not(debug_assertions))]
1159                                    0
1160                                }
1161                            };
1162
1163                            if data.enabled_count == 0 {
1164                                data.is_enabled.set(false);
1165                            }
1166                        }
1167
1168                        tracing::trace!(
1169                            "unsubscribe to {:?}, handle_count: {:?}, enabled_count: {:?}",
1170                            CommandDbg::new(write.static_name, command.scope),
1171                            data.handle_count,
1172                            data.enabled_count
1173                        );
1174
1175                        if data.handle_count == 0 {
1176                            data.has_handlers.set(false);
1177                            entry.remove();
1178                            EVENTS.unregister_command(command);
1179                        }
1180                    }
1181                }
1182            }
1183        }
1184    }
1185}
1186impl Default for CommandHandle {
1187    fn default() -> Self {
1188        Self::dummy()
1189    }
1190}
1191
1192/// Represents a reference counted `dyn Any` object parameter for a command request.
1193#[derive(Clone)]
1194#[non_exhaustive]
1195pub struct CommandParam(pub Arc<dyn Any + Send + Sync>);
1196impl PartialEq for CommandParam {
1197    fn eq(&self, other: &Self) -> bool {
1198        Arc::ptr_eq(&self.0, &other.0)
1199    }
1200}
1201impl Eq for CommandParam {}
1202impl CommandParam {
1203    /// New param.
1204    ///
1205    /// If `param` is already a [`CommandParam`] or `Arc<dyn Any + Send + Sync>` returns a clone.
1206    pub fn new(param: impl Any + Send + Sync + 'static) -> Self {
1207        let p: &dyn Any = &param;
1208        if let Some(p) = p.downcast_ref::<Self>() {
1209            p.clone()
1210        } else if let Some(p) = p.downcast_ref::<Arc<dyn Any + Send + Sync>>() {
1211            CommandParam(p.clone())
1212        } else {
1213            CommandParam(Arc::new(param))
1214        }
1215    }
1216
1217    /// Gets the [`TypeId`] of the parameter.
1218    pub fn type_id(&self) -> TypeId {
1219        self.0.type_id()
1220    }
1221
1222    /// Gets a typed reference to the parameter if it is of type `T`.
1223    pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
1224        self.0.downcast_ref()
1225    }
1226
1227    /// Returns `true` if the parameter type is `T`.
1228    pub fn is<T: Any>(&self) -> bool {
1229        self.0.is::<T>()
1230    }
1231}
1232impl fmt::Debug for CommandParam {
1233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1234        f.debug_tuple("CommandParam").field(&self.0.type_id()).finish()
1235    }
1236}
1237zng_var::impl_from_and_into_var! {
1238    fn from(param: CommandParam) -> Option<CommandParam>;
1239}
1240
1241#[rustfmt::skip] // for zng fmt
1242unique_id_64! {
1243    /// Unique identifier of a command metadata state variable.
1244    ///
1245    /// This type is very similar to [`StateId`], but `T` is the value type of the metadata variable.
1246    ///
1247    /// [`StateId`]: zng_state_map::StateId
1248    pub struct CommandMetaVarId<T: (StateValue + VarValue)>: StateId;
1249}
1250zng_unique_id::impl_unique_id_bytemuck!(CommandMetaVarId<T: (StateValue + VarValue)>);
1251impl<T: StateValue + VarValue> CommandMetaVarId<T> {
1252    fn app(self) -> StateId<Var<T>> {
1253        let id = self.get();
1254        StateId::from_raw(id)
1255    }
1256
1257    fn scope(self) -> StateId<Var<T>> {
1258        let id = self.get();
1259        StateId::from_raw(id)
1260    }
1261}
1262
1263impl<T: StateValue + VarValue> fmt::Debug for CommandMetaVarId<T> {
1264    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1265        #[cfg(debug_assertions)]
1266        let t = pretty_type_name::pretty_type_name::<T>();
1267        #[cfg(not(debug_assertions))]
1268        let t = "$T";
1269
1270        if f.alternate() {
1271            writeln!(f, "CommandMetaVarId<{t} {{")?;
1272            writeln!(f, "   id: {},", self.get())?;
1273            writeln!(f, "   sequential: {}", self.sequential())?;
1274            writeln!(f, "}}")
1275        } else {
1276            write!(f, "CommandMetaVarId<{t}>({})", self.sequential())
1277        }
1278    }
1279}
1280
1281/// Access to metadata of a command.
1282///
1283/// The metadata storage can be accessed using the [`Command::with_meta`]
1284/// method, implementers must declare and extension trait that adds methods that return [`CommandMetaVar`] or
1285/// [`ReadOnlyCommandMetaVar`] that are stored in the [`CommandMeta`]. An initialization builder method for
1286/// each value also must be provided to integrate with the [`command!`] macro.
1287///
1288/// # Examples
1289///
1290/// The [`command!`] initialization transforms `foo: true,` to `command.init_foo(true);`, to support that, the command extension trait
1291/// must have a `foo` and `init_foo` methods.
1292///
1293/// ```
1294/// use zng_app::{event::*, static_id, var::*};
1295///
1296/// static_id! {
1297///     static ref COMMAND_FOO_ID: CommandMetaVarId<bool>;
1298///     static ref COMMAND_BAR_ID: CommandMetaVarId<bool>;
1299/// }
1300///
1301/// /// FooBar command values.
1302/// pub trait CommandFooBarExt {
1303///     /// Gets read/write *foo*.
1304///     fn foo(self) -> CommandMetaVar<bool>;
1305///
1306///     /// Gets read-only *bar*.
1307///     fn bar(self) -> ReadOnlyCommandMetaVar<bool>;
1308///
1309///     /// Gets a read-only var derived from other metadata.
1310///     fn foo_and_bar(self) -> Var<bool>;
1311///
1312///     /// Init *foo*.
1313///     fn init_foo(self, foo: bool) -> Self;
1314///
1315///     /// Init *bar*.
1316///     fn init_bar(self, bar: bool) -> Self;
1317/// }
1318///
1319/// impl CommandFooBarExt for Command {
1320///     fn foo(self) -> CommandMetaVar<bool> {
1321///         self.with_meta(|m| m.get_var_or_default(*COMMAND_FOO_ID))
1322///     }
1323///
1324///     fn bar(self) -> ReadOnlyCommandMetaVar<bool> {
1325///         self.with_meta(|m| m.get_var_or_insert(*COMMAND_BAR_ID, || true)).read_only()
1326///     }
1327///
1328///     fn foo_and_bar(self) -> Var<bool> {
1329///         merge_var!(self.foo(), self.bar(), |f, b| *f && *b)
1330///     }
1331///
1332///     fn init_foo(self, foo: bool) -> Self {
1333///         self.with_meta(|m| m.init_var(*COMMAND_FOO_ID, foo));
1334///         self
1335///     }
1336///
1337///     fn init_bar(self, bar: bool) -> Self {
1338///         self.with_meta(|m| m.init_var(*COMMAND_BAR_ID, bar));
1339///         self
1340///     }
1341/// }
1342/// ```
1343///
1344/// [`command!`]: macro@crate::event::command
1345pub struct CommandMeta<'a> {
1346    meta: StateMapMut<'a, CommandMetaState>,
1347    scope: Option<StateMapMut<'a, CommandMetaState>>,
1348}
1349impl CommandMeta<'_> {
1350    /// Clone a meta value identified by a [`StateId`].
1351    ///
1352    /// If the key is not set in the app, insert it using `init` to produce a value.
1353    ///
1354    /// [`StateId`]: zng_state_map::StateId
1355    pub fn get_or_insert<T, F>(&mut self, id: impl Into<StateId<T>>, init: F) -> T
1356    where
1357        T: StateValue + Clone,
1358        F: FnOnce() -> T,
1359    {
1360        let id = id.into();
1361        if let Some(scope) = &mut self.scope {
1362            if let Some(value) = scope.get(id) {
1363                value.clone()
1364            } else if let Some(value) = self.meta.get(id) {
1365                value.clone()
1366            } else {
1367                let value = init();
1368                let r = value.clone();
1369                scope.set(id, value);
1370                r
1371            }
1372        } else {
1373            self.meta.entry(id).or_insert_with(init).clone()
1374        }
1375    }
1376
1377    /// Clone a meta value identified by a [`StateId`].
1378    ///
1379    /// If the key is not set, insert the default value and returns a clone of it.
1380    ///
1381    /// [`StateId`]: zng_state_map::StateId
1382    pub fn get_or_default<T>(&mut self, id: impl Into<StateId<T>>) -> T
1383    where
1384        T: StateValue + Clone + Default,
1385    {
1386        self.get_or_insert(id, Default::default)
1387    }
1388
1389    /// Clone a meta value identified by a [`StateId`] if it is set.
1390    ///
1391    /// [`StateId`]: zng_state_map::StateId
1392    pub fn get<T>(&self, id: impl Into<StateId<T>>) -> Option<T>
1393    where
1394        T: StateValue + Clone,
1395    {
1396        let id = id.into();
1397        if let Some(scope) = &self.scope {
1398            scope.get(id).or_else(|| self.meta.get(id))
1399        } else {
1400            self.meta.get(id)
1401        }
1402        .cloned()
1403    }
1404
1405    /// Set the meta value associated with the [`StateId`].
1406    ///
1407    /// [`StateId`]: zng_state_map::StateId
1408    pub fn set<T>(&mut self, id: impl Into<StateId<T>>, value: impl Into<T>)
1409    where
1410        T: StateValue + Clone,
1411    {
1412        if let Some(scope) = &mut self.scope {
1413            scope.set(id, value);
1414        } else {
1415            self.meta.set(id, value);
1416        }
1417    }
1418
1419    /// Set the metadata value only if it is not set.
1420    ///
1421    /// This does not set the scoped override, only the command type metadata.
1422    pub fn init<T>(&mut self, id: impl Into<StateId<T>>, value: impl Into<T>)
1423    where
1424        T: StateValue + Clone,
1425    {
1426        self.meta.entry(id).or_insert(value);
1427    }
1428
1429    /// Clone a meta variable identified by a [`CommandMetaVarId`].
1430    ///
1431    /// The variable is read-write and is clone-on-write if the command is scoped.
1432    ///
1433    /// [`read_only`]: Var::read_only
1434    pub fn get_var_or_insert<T, F>(&mut self, id: impl Into<CommandMetaVarId<T>>, init: F) -> CommandMetaVar<T>
1435    where
1436        T: StateValue + VarValue,
1437        F: FnOnce() -> T,
1438    {
1439        let id = id.into();
1440        if let Some(scope) = &mut self.scope {
1441            let meta = &mut self.meta;
1442            scope
1443                .entry(id.scope())
1444                .or_insert_with(|| {
1445                    let var = meta.entry(id.app()).or_insert_with(|| var(init())).clone();
1446                    var.cow()
1447                })
1448                .clone()
1449        } else {
1450            self.meta.entry(id.app()).or_insert_with(|| var(init())).clone()
1451        }
1452    }
1453
1454    /// Clone a meta variable identified by a [`CommandMetaVarId`], if it is set.
1455    pub fn get_var<T>(&self, id: impl Into<CommandMetaVarId<T>>) -> Option<CommandMetaVar<T>>
1456    where
1457        T: StateValue + VarValue,
1458    {
1459        let id = id.into();
1460        if let Some(scope) = &self.scope {
1461            let meta = &self.meta;
1462            scope.get(id.scope()).cloned().or_else(|| meta.get(id.app()).cloned())
1463        } else {
1464            self.meta.get(id.app()).cloned()
1465        }
1466    }
1467
1468    /// Clone a meta variable identified by a [`CommandMetaVarId`].
1469    ///
1470    /// Inserts a variable with the default value if no variable is in the metadata.
1471    pub fn get_var_or_default<T>(&mut self, id: impl Into<CommandMetaVarId<T>>) -> CommandMetaVar<T>
1472    where
1473        T: StateValue + VarValue + Default,
1474    {
1475        self.get_var_or_insert(id, Default::default)
1476    }
1477
1478    /// Set the metadata variable if it was not set.
1479    ///
1480    /// This does not set the scoped override, only the command type metadata.
1481    pub fn init_var<T>(&mut self, id: impl Into<CommandMetaVarId<T>>, value: impl Into<T>)
1482    where
1483        T: StateValue + VarValue,
1484    {
1485        self.meta.entry(id.into().app()).or_insert_with(|| var(value.into()));
1486    }
1487}
1488
1489/// Read-write command metadata variable.
1490///
1491/// This is a simple [`var`] for *app* scope, or a [`Var::cow`] for scoped commands.
1492/// If you get this variable from an app scoped command it sets
1493/// the value for all scopes. If you get this variable using a scoped command,
1494/// it is a clone-on-write variable that overrides only the value for the scope.
1495pub type CommandMetaVar<T> = Var<T>;
1496
1497/// Read-only command metadata variable.
1498///
1499/// To convert a [`CommandMetaVar<T>`] into this var call [`read_only`].
1500///
1501/// [`read_only`]: Var::read_only
1502pub type ReadOnlyCommandMetaVar<T> = Var<T>;
1503
1504/// Adds the [`name`](CommandNameExt) command metadata.
1505pub trait CommandNameExt {
1506    /// Gets a read-write variable that is the display name for the command.
1507    fn name(self) -> CommandMetaVar<Txt>;
1508
1509    /// Sets the initial name if it is not set.
1510    fn init_name(self, name: impl Into<Txt>) -> Self;
1511
1512    /// Gets a read-only variable that formats the name and first shortcut formatted as `"name (first_shortcut)"`
1513    ///
1514    /// Note that if no shortcut is set for the command this method returns the same as [`name`](Self::name).
1515    ///
1516    /// Note that the shortcut keys are not localized, consider using `ShortcutText!` instead.
1517    fn name_with_shortcut(self) -> Var<Txt>
1518    where
1519        Self: crate::shortcut::CommandShortcutExt;
1520}
1521static_id! {
1522    static ref COMMAND_NAME_ID: CommandMetaVarId<Txt>;
1523}
1524impl CommandNameExt for Command {
1525    fn name(self) -> CommandMetaVar<Txt> {
1526        self.with_meta(|m| {
1527            m.get_var_or_insert(*COMMAND_NAME_ID, || {
1528                let name = self.static_name();
1529                let name = name.strip_suffix("_CMD").unwrap_or(name);
1530                let mut title = String::with_capacity(name.len());
1531                let mut lower = false;
1532                for c in name.chars() {
1533                    if c == '_' {
1534                        if !title.ends_with(' ') {
1535                            title.push(' ');
1536                        }
1537                        lower = false;
1538                    } else if lower {
1539                        for l in c.to_lowercase() {
1540                            title.push(l);
1541                        }
1542                    } else {
1543                        title.push(c);
1544                        lower = true;
1545                    }
1546                }
1547                Txt::from(title)
1548            })
1549        })
1550    }
1551
1552    fn init_name(self, name: impl Into<Txt>) -> Self {
1553        self.with_meta(|m| m.init_var(*COMMAND_NAME_ID, name.into()));
1554        self
1555    }
1556
1557    fn name_with_shortcut(self) -> Var<Txt>
1558    where
1559        Self: crate::shortcut::CommandShortcutExt,
1560    {
1561        crate::var::merge_var!(self.name(), self.shortcut(), |name, shortcut| {
1562            if shortcut.is_empty() {
1563                name.clone()
1564            } else {
1565                zng_txt::formatx!("{name} ({})", shortcut[0])
1566            }
1567        })
1568    }
1569}
1570
1571// support `init: |cmd| { }` in `command!`
1572impl Command {
1573    #[doc(hidden)]
1574    pub fn init_init(self, init: impl FnOnce(Self)) {
1575        init(self)
1576    }
1577    #[doc(hidden)]
1578    pub fn init(self) {}
1579}
1580
1581/// Adds the [`info`](CommandInfoExt) command metadata.
1582pub trait CommandInfoExt {
1583    /// Gets a read-write variable that is a short informational string about the command.
1584    fn info(self) -> CommandMetaVar<Txt>;
1585
1586    /// Sets the initial info if it is not set.
1587    fn init_info(self, info: impl Into<Txt>) -> Self;
1588}
1589static_id! {
1590    static ref COMMAND_INFO_ID: CommandMetaVarId<Txt>;
1591}
1592impl CommandInfoExt for Command {
1593    fn info(self) -> CommandMetaVar<Txt> {
1594        self.with_meta(|m| m.get_var_or_insert(*COMMAND_INFO_ID, Txt::default))
1595    }
1596
1597    fn init_info(self, info: impl Into<Txt>) -> Self {
1598        self.with_meta(|m| m.init_var(*COMMAND_INFO_ID, info.into()));
1599        self
1600    }
1601}
1602
1603enum CommandMetaState {}
1604
1605#[derive(Clone)]
1606enum MetaInit {
1607    Init(fn(Command)),
1608    /// Initing in a thread, lock is for other threads.
1609    Initing(Arc<(ThreadId, Mutex<()>)>),
1610    Inited,
1611}
1612
1613#[doc(hidden)]
1614pub struct CommandData {
1615    static_name: &'static str,
1616
1617    meta_init: MetaInit,
1618    meta: Mutex<OwnedStateMap<CommandMetaState>>,
1619
1620    handle_count: usize,
1621    enabled_count: usize,
1622    registered: bool,
1623
1624    has_handlers: Var<bool>,
1625    is_enabled: Var<bool>,
1626
1627    scopes: HashMap<CommandScope, ScopedValue>,
1628}
1629impl CommandData {
1630    pub fn new(meta_init: fn(Command), static_name: &'static str) -> Self {
1631        CommandData {
1632            static_name,
1633            meta_init: MetaInit::Init(meta_init),
1634            meta: Mutex::new(OwnedStateMap::new()),
1635
1636            handle_count: 0,
1637            enabled_count: 0,
1638            registered: false,
1639
1640            has_handlers: var(false),
1641            is_enabled: var(false),
1642
1643            scopes: HashMap::default(),
1644        }
1645    }
1646
1647    fn subscribe(&mut self, command: Command, enabled: bool, mut target: Option<WidgetId>) -> CommandHandle {
1648        match command.scope {
1649            CommandScope::App => {
1650                if !mem::replace(&mut self.registered, true) {
1651                    EVENTS.register_command(command);
1652                }
1653
1654                self.handle_count += 1;
1655                if enabled {
1656                    self.enabled_count += 1;
1657                }
1658
1659                if self.handle_count == 1 {
1660                    self.has_handlers.set(true);
1661                }
1662                if self.enabled_count == 1 {
1663                    self.is_enabled.set(true);
1664                }
1665
1666                tracing::trace!(
1667                    "subscribe to {:?}, handle_count: {:?}, enabled_count: {:?}",
1668                    CommandDbg::new(self.static_name, command.scope),
1669                    self.handle_count,
1670                    self.enabled_count
1671                );
1672            }
1673            scope => {
1674                let data = self.scopes.entry(scope).or_default();
1675
1676                if !mem::replace(&mut data.registered, true) {
1677                    EVENTS.register_command(command);
1678                }
1679
1680                data.handle_count += 1;
1681                if enabled {
1682                    data.enabled_count += 1;
1683                }
1684
1685                if data.handle_count == 1 {
1686                    data.has_handlers.set(true);
1687                }
1688                if data.enabled_count == 1 {
1689                    data.is_enabled.set(true);
1690                }
1691
1692                tracing::trace!(
1693                    "subscribe to {:?}, handle_count: {:?}, enabled_count: {:?}",
1694                    CommandDbg::new(self.static_name, command.scope),
1695                    self.handle_count,
1696                    self.enabled_count
1697                );
1698
1699                if let CommandScope::Widget(id) = scope {
1700                    target = Some(id);
1701                }
1702            }
1703        };
1704
1705        CommandHandle::new(
1706            command,
1707            target
1708                .map(|t| command.event.subscribe(UpdateOp::Update, t))
1709                .unwrap_or_else(VarHandle::dummy),
1710            enabled,
1711        )
1712    }
1713}
1714
1715struct ScopedValue {
1716    handle_count: usize,
1717    enabled_count: usize,
1718    is_enabled: Var<bool>,
1719    has_handlers: Var<bool>,
1720    meta: Mutex<OwnedStateMap<CommandMetaState>>,
1721    registered: bool,
1722}
1723impl Default for ScopedValue {
1724    fn default() -> Self {
1725        ScopedValue {
1726            is_enabled: var(false),
1727            has_handlers: var(false),
1728            handle_count: 0,
1729            enabled_count: 0,
1730            meta: Mutex::new(OwnedStateMap::default()),
1731            registered: false,
1732        }
1733    }
1734}
1735
1736#[cfg(test)]
1737mod tests {
1738    use crate::APP;
1739
1740    use super::*;
1741
1742    command! {
1743        static FOO_CMD;
1744    }
1745
1746    #[test]
1747    fn parameter_none() {
1748        let _ = CommandArgs::now(None, CommandScope::App, None, true);
1749    }
1750
1751    #[test]
1752    fn enabled_not_scoped() {
1753        let mut app = APP.minimal().run_headless(false);
1754
1755        assert!(!FOO_CMD.has_handlers().get());
1756
1757        let handle = FOO_CMD.subscribe(true);
1758        app.update(false).assert_wait();
1759        assert!(FOO_CMD.is_enabled().get());
1760
1761        handle.enabled().set(false);
1762        app.update(false).assert_wait();
1763
1764        assert!(FOO_CMD.has_handlers().get());
1765        assert!(!FOO_CMD.is_enabled().get());
1766
1767        handle.enabled().set(true);
1768        app.update(false).assert_wait();
1769
1770        assert!(FOO_CMD.is_enabled().get());
1771
1772        drop(handle);
1773        app.update(false).assert_wait();
1774
1775        assert!(!FOO_CMD.has_handlers().get());
1776        assert!(!FOO_CMD.is_enabled().get());
1777    }
1778
1779    #[test]
1780    fn enabled_scoped() {
1781        let mut app = APP.minimal().run_headless(false);
1782
1783        let cmd = FOO_CMD;
1784        let cmd_scoped = FOO_CMD.scoped(WindowId::named("enabled_scoped"));
1785        app.update(false).assert_wait();
1786        assert!(!cmd.has_handlers().get());
1787        assert!(!cmd_scoped.has_handlers().get());
1788
1789        let handle_scoped = cmd_scoped.subscribe(true);
1790        app.update(false).assert_wait();
1791
1792        assert!(!cmd.has_handlers().get());
1793        assert!(cmd_scoped.is_enabled().get());
1794
1795        handle_scoped.enabled().set(false);
1796        app.update(false).assert_wait();
1797
1798        assert!(!cmd.has_handlers().get());
1799        assert!(!cmd_scoped.is_enabled().get());
1800        assert!(cmd_scoped.has_handlers().get());
1801
1802        handle_scoped.enabled().set(true);
1803        app.update(false).assert_wait();
1804
1805        assert!(!cmd.has_handlers().get());
1806        assert!(cmd_scoped.is_enabled().get());
1807
1808        drop(handle_scoped);
1809        app.update(false).assert_wait();
1810
1811        assert!(!cmd.has_handlers().get());
1812        assert!(!cmd_scoped.has_handlers().get());
1813    }
1814
1815    #[test]
1816    fn has_handlers() {
1817        let mut app = APP.minimal().run_headless(false);
1818
1819        assert!(!FOO_CMD.has_handlers().get());
1820
1821        let handle = FOO_CMD.subscribe(false);
1822        app.update(false).assert_wait();
1823
1824        assert!(FOO_CMD.has_handlers().get());
1825
1826        drop(handle);
1827        app.update(false).assert_wait();
1828
1829        assert!(!FOO_CMD.has_handlers().get());
1830    }
1831
1832    #[test]
1833    fn has_handlers_scoped() {
1834        let mut app = APP.minimal().run_headless(false);
1835
1836        let cmd = FOO_CMD;
1837        let cmd_scoped = FOO_CMD.scoped(WindowId::named("has_handlers_scoped"));
1838        app.update(false).assert_wait();
1839
1840        assert!(!cmd.has_handlers().get());
1841        assert!(!cmd_scoped.has_handlers().get());
1842
1843        let handle = cmd_scoped.subscribe(false);
1844        app.update(false).assert_wait();
1845
1846        assert!(!cmd.has_handlers().get());
1847        assert!(cmd_scoped.has_handlers().get());
1848
1849        drop(handle);
1850        app.update(false).assert_wait();
1851
1852        assert!(!cmd.has_handlers().get());
1853        assert!(!cmd_scoped.has_handlers().get());
1854    }
1855
1856    // there are also integration tests in tests/command.rs
1857}