zng_app/event/
command.rs

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