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