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(&self, enabled: bool, handler: Handler<AppCommandArgs>) -> EventHandle {
585        self.event().on_pre_event(self.handler(enabled, handler))
586    }
587
588    /// Creates an event handler for the command.
589    ///
590    /// This is similar to [`Event::on_event`], but `handler` is only called if the command
591    /// scope matches.
592    ///
593    /// The `enabled` parameter defines the initial state of the command subscription, the subscription
594    /// handle is available in the handler args.
595    pub fn on_event(&self, enabled: bool, handler: Handler<AppCommandArgs>) -> EventHandle {
596        self.event().on_event(self.handler(enabled, handler))
597    }
598
599    fn handler(&self, enabled: bool, mut handler: Handler<AppCommandArgs>) -> Handler<CommandArgs> {
600        let handle = Arc::new(self.subscribe(enabled));
601        Box::new(move |args| {
602            handler(&AppCommandArgs {
603                args: args.clone(),
604                handle: handle.clone(),
605            })
606        })
607    }
608
609    /// Update state vars, returns if the command must be retained.
610    #[must_use]
611    pub(crate) fn update_state(&self) -> bool {
612        let mut write = self.local.write();
613        if let CommandScope::App = self.scope {
614            let has_handlers = write.handle_count > 0;
615            if has_handlers != write.has_handlers.get() {
616                write.has_handlers.set(has_handlers);
617            }
618            let is_enabled = has_handlers && write.enabled_count > 0;
619            if is_enabled != write.is_enabled.get() {
620                write.is_enabled.set(is_enabled);
621            }
622            true
623        } else if let hash_map::Entry::Occupied(entry) = write.scopes.entry(self.scope) {
624            let scope = entry.get();
625
626            if scope.handle_count == 0 && scope.has_handlers.strong_count() == 1 && scope.is_enabled.strong_count() == 1 {
627                entry.remove();
628                return false;
629            }
630
631            let has_handlers = scope.handle_count > 0;
632            if has_handlers != scope.has_handlers.get() {
633                scope.has_handlers.set(has_handlers);
634            }
635            let is_enabled = has_handlers && scope.enabled_count > 0;
636            if is_enabled != scope.is_enabled.get() {
637                scope.is_enabled.set(is_enabled);
638            }
639            true
640        } else {
641            false
642        }
643    }
644}
645impl Deref for Command {
646    type Target = Event<CommandArgs>;
647
648    fn deref(&self) -> &Self::Target {
649        &self.event
650    }
651}
652impl PartialEq for Command {
653    fn eq(&self, other: &Self) -> bool {
654        self.event == other.event && self.scope == other.scope
655    }
656}
657impl Eq for Command {}
658impl std::hash::Hash for Command {
659    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
660        std::hash::Hash::hash(&self.event.as_any(), state);
661        std::hash::Hash::hash(&self.scope, state);
662    }
663}
664
665/// Represents the scope of a [`Command`].
666///
667/// The command scope defines the targets of its event and the context of its metadata.
668#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
669pub enum CommandScope {
670    /// Default scope, this is the scope of command types declared using [`command!`].
671    App,
672    /// Scope of a window.
673    ///
674    /// Note that the window scope is different from the window root widget scope, the metadata store and command
675    /// handles are different, but subscribers set on the window root should probably also subscribe to the window scope.
676    Window(WindowId),
677    /// Scope of a widget.
678    Widget(WidgetId),
679}
680impl_from_and_into_var! {
681    fn from(id: WidgetId) -> CommandScope {
682        CommandScope::Widget(id)
683    }
684    fn from(id: WindowId) -> CommandScope {
685        CommandScope::Window(id)
686    }
687    /// Widget scope.
688    fn from(widget_name: &'static str) -> CommandScope {
689        WidgetId::named(widget_name).into()
690    }
691    /// Widget scope.
692    fn from(widget_name: Txt) -> CommandScope {
693        WidgetId::named(widget_name).into()
694    }
695}
696
697event_args! {
698    /// Event args for command events.
699    pub struct CommandArgs {
700        /// Optional parameter for the command handler.
701        pub param: Option<CommandParam>,
702
703        /// Scope of command that notified.
704        pub scope: CommandScope,
705
706        /// If the command handle was enabled when the command notified.
707        ///
708        /// If `false` the command primary action must not run, but a secondary "disabled interaction"
709        /// that indicates what conditions enable the command is recommended.
710        pub enabled: bool,
711
712        ..
713
714        /// Broadcast to all widget subscribers for [`CommandScope::App`]. Targets the window root for
715        /// [`CommandScope::Window`] if found. Target ancestors and widget for [`CommandScope::Widget`], if it is found.
716        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
717            match self.scope {
718                CommandScope::Widget(id) => list.search_widget(id),
719                CommandScope::Window(id) => list.insert_window(id),
720                CommandScope::App => list.search_all(),
721            }
722        }
723    }
724}
725impl CommandArgs {
726    /// Returns a reference to a parameter of `T` if [`parameter`](#structfield.parameter) is set to a value of `T`.
727    pub fn param<T: Any>(&self) -> Option<&T> {
728        self.param.as_ref().and_then(|p| p.downcast_ref::<T>())
729    }
730
731    /// Returns [`param`] if is [`enabled`].
732    ///
733    /// [`param`]: Self::param()
734    /// [`enabled`]: Self::enabled
735    pub fn enabled_param<T: Any>(&self) -> Option<&T> {
736        if self.enabled { self.param::<T>() } else { None }
737    }
738
739    /// Returns [`param`] if is not [`enabled`].
740    ///
741    /// [`param`]: Self::param()
742    /// [`enabled`]: Self::enabled
743    pub fn disabled_param<T: Any>(&self) -> Option<&T> {
744        if !self.enabled { self.param::<T>() } else { None }
745    }
746
747    /// Call `handler` if propagation is not stopped and the command and local handler are enabled. Stops propagation
748    /// after `handler` is called.
749    ///
750    /// This is the default behavior of commands, when a command has a handler it is *relevant* in the context, and overwrites
751    /// lower priority handlers, but if the handler is disabled the command primary action is not run.
752    ///
753    /// Returns the `handler` result if it was called.
754    pub fn handle_enabled<F, R>(&self, local_handle: &CommandHandle, handler: F) -> Option<R>
755    where
756        F: FnOnce(&Self) -> R,
757    {
758        if self.propagation().is_stopped() || !self.enabled || !local_handle.is_enabled() {
759            None
760        } else {
761            let r = handler(self);
762            self.propagation().stop();
763            Some(r)
764        }
765    }
766}
767
768/// Arguments for [`Command::on_event`] handler closure.
769#[non_exhaustive]
770#[derive(Debug, Clone)]
771pub struct AppCommandArgs {
772    /// The command args.
773    pub args: CommandArgs,
774    /// The command handle held by the event handler.
775    pub handle: Arc<CommandHandle>,
776}
777impl ops::Deref for AppCommandArgs {
778    type Target = CommandArgs;
779
780    fn deref(&self) -> &Self::Target {
781        &self.args
782    }
783}
784impl AnyEventArgs for AppCommandArgs {
785    fn clone_any(&self) -> Box<dyn AnyEventArgs> {
786        Box::new(self.clone())
787    }
788
789    fn as_any(&self) -> &dyn Any {
790        self
791    }
792
793    fn timestamp(&self) -> crate::DInstant {
794        self.args.timestamp()
795    }
796
797    fn delivery_list(&self, list: &mut UpdateDeliveryList) {
798        self.args.delivery_list(list)
799    }
800
801    fn propagation(&self) -> &EventPropagationHandle {
802        self.args.propagation()
803    }
804}
805impl EventArgs for AppCommandArgs {}
806
807/// A handle to a [`Command`] subscription.
808///
809/// Holding the command handle indicates that the command is relevant in the current app state.
810/// The handle needs to be enabled to indicate that the command primary action can be executed.
811///
812/// You can use the [`Command::subscribe`] method in a command type to create a handle.
813pub struct CommandHandle {
814    command: Option<Command>,
815    local_enabled: AtomicBool,
816    app_id: Option<AppId>,
817    _event_handle: EventHandle,
818}
819impl CommandHandle {
820    /// The command.
821    pub fn command(&self) -> Option<Command> {
822        self.command
823    }
824
825    /// Sets if the command event handler is active.
826    ///
827    /// When at least one [`CommandHandle`] is enabled the command is [`is_enabled`](Command::is_enabled).
828    pub fn set_enabled(&self, enabled: bool) {
829        if let Some(command) = self.command
830            && self.local_enabled.swap(enabled, Ordering::Relaxed) != enabled
831        {
832            if self.app_id != APP.id() {
833                return;
834            }
835
836            UpdatesTrace::log_var(std::any::type_name::<bool>());
837
838            let mut write = command.local.write();
839            match command.scope {
840                CommandScope::App => {
841                    if enabled {
842                        write.enabled_count += 1;
843                    } else {
844                        write.enabled_count -= 1;
845                    }
846                }
847                scope => {
848                    if let Some(data) = write.scopes.get_mut(&scope) {
849                        if enabled {
850                            data.enabled_count += 1;
851                        } else {
852                            data.enabled_count -= 1;
853                        }
854                    }
855                }
856            }
857        }
858    }
859
860    /// Returns if this handle has enabled the command.
861    pub fn is_enabled(&self) -> bool {
862        self.local_enabled.load(Ordering::Relaxed)
863    }
864
865    /// New handle not connected to any command.
866    pub fn dummy() -> Self {
867        CommandHandle {
868            command: None,
869            app_id: None,
870            local_enabled: AtomicBool::new(false),
871            _event_handle: EventHandle::dummy(),
872        }
873    }
874
875    /// If the handle is not connected to any command.
876    pub fn is_dummy(&self) -> bool {
877        self.command.is_none()
878    }
879}
880impl fmt::Debug for CommandHandle {
881    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
882        f.debug_struct("CommandHandle")
883            .field("command", &self.command)
884            .field("local_enabled", &self.local_enabled.load(Ordering::Relaxed))
885            .finish()
886    }
887}
888impl Drop for CommandHandle {
889    fn drop(&mut self) {
890        if let Some(command) = self.command {
891            if self.app_id != APP.id() {
892                return;
893            }
894
895            let mut write = command.local.write();
896            match command.scope {
897                CommandScope::App => {
898                    write.handle_count -= 1;
899                    if self.local_enabled.load(Ordering::Relaxed) {
900                        write.enabled_count -= 1;
901                    }
902                }
903                scope => {
904                    if let Some(data) = write.scopes.get_mut(&scope) {
905                        data.handle_count -= 1;
906                        if self.local_enabled.load(Ordering::Relaxed) {
907                            data.enabled_count -= 1;
908                        }
909                    }
910                }
911            }
912        }
913    }
914}
915impl Default for CommandHandle {
916    fn default() -> Self {
917        Self::dummy()
918    }
919}
920
921/// Represents a reference counted `dyn Any` object parameter for a command request.
922#[derive(Clone)]
923#[non_exhaustive]
924pub struct CommandParam(pub Arc<dyn Any + Send + Sync>);
925impl PartialEq for CommandParam {
926    fn eq(&self, other: &Self) -> bool {
927        Arc::ptr_eq(&self.0, &other.0)
928    }
929}
930impl Eq for CommandParam {}
931impl CommandParam {
932    /// New param.
933    ///
934    /// If `param` is already a [`CommandParam`] or `Arc<dyn Any + Send + Sync>` returns a clone.
935    pub fn new(param: impl Any + Send + Sync + 'static) -> Self {
936        let p: &dyn Any = &param;
937        if let Some(p) = p.downcast_ref::<Self>() {
938            p.clone()
939        } else if let Some(p) = p.downcast_ref::<Arc<dyn Any + Send + Sync>>() {
940            CommandParam(p.clone())
941        } else {
942            CommandParam(Arc::new(param))
943        }
944    }
945
946    /// Gets the [`TypeId`] of the parameter.
947    pub fn type_id(&self) -> TypeId {
948        self.0.type_id()
949    }
950
951    /// Gets a typed reference to the parameter if it is of type `T`.
952    pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
953        self.0.downcast_ref()
954    }
955
956    /// Returns `true` if the parameter type is `T`.
957    pub fn is<T: Any>(&self) -> bool {
958        self.0.is::<T>()
959    }
960}
961impl fmt::Debug for CommandParam {
962    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
963        f.debug_tuple("CommandParam").field(&self.0.type_id()).finish()
964    }
965}
966zng_var::impl_from_and_into_var! {
967    fn from(param: CommandParam) -> Option<CommandParam>;
968}
969
970#[rustfmt::skip] // for zng fmt
971unique_id_64! {
972    /// Unique identifier of a command metadata state variable.
973    ///
974    /// This type is very similar to [`StateId`], but `T` is the value type of the metadata variable.
975    ///
976    /// [`StateId`]: zng_state_map::StateId
977    pub struct CommandMetaVarId<T: (StateValue + VarValue)>: StateId;
978}
979zng_unique_id::impl_unique_id_bytemuck!(CommandMetaVarId<T: (StateValue + VarValue)>);
980impl<T: StateValue + VarValue> CommandMetaVarId<T> {
981    fn app(self) -> StateId<Var<T>> {
982        let id = self.get();
983        StateId::from_raw(id)
984    }
985
986    fn scope(self) -> StateId<Var<T>> {
987        let id = self.get();
988        StateId::from_raw(id)
989    }
990}
991
992impl<T: StateValue + VarValue> fmt::Debug for CommandMetaVarId<T> {
993    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
994        #[cfg(debug_assertions)]
995        let t = pretty_type_name::pretty_type_name::<T>();
996        #[cfg(not(debug_assertions))]
997        let t = "$T";
998
999        if f.alternate() {
1000            writeln!(f, "CommandMetaVarId<{t} {{")?;
1001            writeln!(f, "   id: {},", self.get())?;
1002            writeln!(f, "   sequential: {}", self.sequential())?;
1003            writeln!(f, "}}")
1004        } else {
1005            write!(f, "CommandMetaVarId<{t}>({})", self.sequential())
1006        }
1007    }
1008}
1009
1010/// Access to metadata of a command.
1011///
1012/// The metadata storage can be accessed using the [`Command::with_meta`]
1013/// method, implementers must declare and extension trait that adds methods that return [`CommandMetaVar`] or
1014/// [`ReadOnlyCommandMetaVar`] that are stored in the [`CommandMeta`]. An initialization builder method for
1015/// each value also must be provided to integrate with the [`command!`] macro.
1016///
1017/// # Examples
1018///
1019/// The [`command!`] initialization transforms `foo: true,` to `command.init_foo(true);`, to support that, the command extension trait
1020/// must have a `foo` and `init_foo` methods.
1021///
1022/// ```
1023/// use zng_app::{event::*, static_id, var::*};
1024///
1025/// static_id! {
1026///     static ref COMMAND_FOO_ID: CommandMetaVarId<bool>;
1027///     static ref COMMAND_BAR_ID: CommandMetaVarId<bool>;
1028/// }
1029///
1030/// /// FooBar command values.
1031/// pub trait CommandFooBarExt {
1032///     /// Gets read/write *foo*.
1033///     fn foo(self) -> CommandMetaVar<bool>;
1034///
1035///     /// Gets read-only *bar*.
1036///     fn bar(self) -> ReadOnlyCommandMetaVar<bool>;
1037///
1038///     /// Gets a read-only var derived from other metadata.
1039///     fn foo_and_bar(self) -> Var<bool>;
1040///
1041///     /// Init *foo*.
1042///     fn init_foo(self, foo: bool) -> Self;
1043///
1044///     /// Init *bar*.
1045///     fn init_bar(self, bar: bool) -> Self;
1046/// }
1047///
1048/// impl CommandFooBarExt for Command {
1049///     fn foo(self) -> CommandMetaVar<bool> {
1050///         self.with_meta(|m| m.get_var_or_default(*COMMAND_FOO_ID))
1051///     }
1052///
1053///     fn bar(self) -> ReadOnlyCommandMetaVar<bool> {
1054///         self.with_meta(|m| m.get_var_or_insert(*COMMAND_BAR_ID, || true)).read_only()
1055///     }
1056///
1057///     fn foo_and_bar(self) -> Var<bool> {
1058///         merge_var!(self.foo(), self.bar(), |f, b| *f && *b)
1059///     }
1060///
1061///     fn init_foo(self, foo: bool) -> Self {
1062///         self.with_meta(|m| m.init_var(*COMMAND_FOO_ID, foo));
1063///         self
1064///     }
1065///
1066///     fn init_bar(self, bar: bool) -> Self {
1067///         self.with_meta(|m| m.init_var(*COMMAND_BAR_ID, bar));
1068///         self
1069///     }
1070/// }
1071/// ```
1072///
1073/// [`command!`]: macro@crate::event::command
1074pub struct CommandMeta<'a> {
1075    meta: StateMapMut<'a, CommandMetaState>,
1076    scope: Option<StateMapMut<'a, CommandMetaState>>,
1077}
1078impl CommandMeta<'_> {
1079    /// Clone a meta value identified by a [`StateId`].
1080    ///
1081    /// If the key is not set in the app, insert it using `init` to produce a value.
1082    ///
1083    /// [`StateId`]: zng_state_map::StateId
1084    pub fn get_or_insert<T, F>(&mut self, id: impl Into<StateId<T>>, init: F) -> T
1085    where
1086        T: StateValue + Clone,
1087        F: FnOnce() -> T,
1088    {
1089        let id = id.into();
1090        if let Some(scope) = &mut self.scope {
1091            if let Some(value) = scope.get(id) {
1092                value.clone()
1093            } else if let Some(value) = self.meta.get(id) {
1094                value.clone()
1095            } else {
1096                let value = init();
1097                let r = value.clone();
1098                scope.set(id, value);
1099                r
1100            }
1101        } else {
1102            self.meta.entry(id).or_insert_with(init).clone()
1103        }
1104    }
1105
1106    /// Clone a meta value identified by a [`StateId`].
1107    ///
1108    /// If the key is not set, insert the default value and returns a clone of it.
1109    ///
1110    /// [`StateId`]: zng_state_map::StateId
1111    pub fn get_or_default<T>(&mut self, id: impl Into<StateId<T>>) -> T
1112    where
1113        T: StateValue + Clone + Default,
1114    {
1115        self.get_or_insert(id, Default::default)
1116    }
1117
1118    /// Clone a meta value identified by a [`StateId`] if it is set.
1119    ///
1120    /// [`StateId`]: zng_state_map::StateId
1121    pub fn get<T>(&self, id: impl Into<StateId<T>>) -> Option<T>
1122    where
1123        T: StateValue + Clone,
1124    {
1125        let id = id.into();
1126        if let Some(scope) = &self.scope {
1127            scope.get(id).or_else(|| self.meta.get(id))
1128        } else {
1129            self.meta.get(id)
1130        }
1131        .cloned()
1132    }
1133
1134    /// Set the meta value associated with the [`StateId`].
1135    ///
1136    /// [`StateId`]: zng_state_map::StateId
1137    pub fn set<T>(&mut self, id: impl Into<StateId<T>>, value: impl Into<T>)
1138    where
1139        T: StateValue + Clone,
1140    {
1141        if let Some(scope) = &mut self.scope {
1142            scope.set(id, value);
1143        } else {
1144            self.meta.set(id, value);
1145        }
1146    }
1147
1148    /// Set the metadata value only if it is not set.
1149    ///
1150    /// This does not set the scoped override, only the command type metadata.
1151    pub fn init<T>(&mut self, id: impl Into<StateId<T>>, value: impl Into<T>)
1152    where
1153        T: StateValue + Clone,
1154    {
1155        self.meta.entry(id).or_insert(value);
1156    }
1157
1158    /// Clone a meta variable identified by a [`CommandMetaVarId`].
1159    ///
1160    /// The variable is read-write and is clone-on-write if the command is scoped.
1161    ///
1162    /// [`read_only`]: Var::read_only
1163    pub fn get_var_or_insert<T, F>(&mut self, id: impl Into<CommandMetaVarId<T>>, init: F) -> CommandMetaVar<T>
1164    where
1165        T: StateValue + VarValue,
1166        F: FnOnce() -> T,
1167    {
1168        let id = id.into();
1169        if let Some(scope) = &mut self.scope {
1170            let meta = &mut self.meta;
1171            scope
1172                .entry(id.scope())
1173                .or_insert_with(|| {
1174                    let var = meta.entry(id.app()).or_insert_with(|| var(init())).clone();
1175                    var.cow()
1176                })
1177                .clone()
1178        } else {
1179            self.meta.entry(id.app()).or_insert_with(|| var(init())).clone()
1180        }
1181    }
1182
1183    /// Clone a meta variable identified by a [`CommandMetaVarId`], if it is set.
1184    pub fn get_var<T>(&self, id: impl Into<CommandMetaVarId<T>>) -> Option<CommandMetaVar<T>>
1185    where
1186        T: StateValue + VarValue,
1187    {
1188        let id = id.into();
1189        if let Some(scope) = &self.scope {
1190            let meta = &self.meta;
1191            scope.get(id.scope()).cloned().or_else(|| meta.get(id.app()).cloned())
1192        } else {
1193            self.meta.get(id.app()).cloned()
1194        }
1195    }
1196
1197    /// Clone a meta variable identified by a [`CommandMetaVarId`].
1198    ///
1199    /// Inserts a variable with the default value if no variable is in the metadata.
1200    pub fn get_var_or_default<T>(&mut self, id: impl Into<CommandMetaVarId<T>>) -> CommandMetaVar<T>
1201    where
1202        T: StateValue + VarValue + Default,
1203    {
1204        self.get_var_or_insert(id, Default::default)
1205    }
1206
1207    /// Set the metadata variable if it was not set.
1208    ///
1209    /// This does not set the scoped override, only the command type metadata.
1210    pub fn init_var<T>(&mut self, id: impl Into<CommandMetaVarId<T>>, value: impl Into<T>)
1211    where
1212        T: StateValue + VarValue,
1213    {
1214        self.meta.entry(id.into().app()).or_insert_with(|| var(value.into()));
1215    }
1216}
1217
1218/// Read-write command metadata variable.
1219///
1220/// This is a simple [`var`] for *app* scope, or a [`Var::cow`] for scoped commands.
1221/// If you get this variable from an app scoped command it sets
1222/// the value for all scopes. If you get this variable using a scoped command,
1223/// it is a clone-on-write variable that overrides only the value for the scope.
1224pub type CommandMetaVar<T> = Var<T>;
1225
1226/// Read-only command metadata variable.
1227///
1228/// To convert a [`CommandMetaVar<T>`] into this var call [`read_only`].
1229///
1230/// [`read_only`]: Var::read_only
1231pub type ReadOnlyCommandMetaVar<T> = Var<T>;
1232
1233/// Adds the [`name`](CommandNameExt) command metadata.
1234pub trait CommandNameExt {
1235    /// Gets a read-write variable that is the display name for the command.
1236    fn name(self) -> CommandMetaVar<Txt>;
1237
1238    /// Sets the initial name if it is not set.
1239    fn init_name(self, name: impl Into<Txt>) -> Self;
1240
1241    /// Gets a read-only variable that formats the name and first shortcut formatted as `"name (first_shortcut)"`
1242    ///
1243    /// Note that if no shortcut is set for the command this method returns the same as [`name`](Self::name).
1244    ///
1245    /// Note that the shortcut keys are not localized, consider using `ShortcutText!` instead.
1246    fn name_with_shortcut(self) -> Var<Txt>
1247    where
1248        Self: crate::shortcut::CommandShortcutExt;
1249}
1250static_id! {
1251    static ref COMMAND_NAME_ID: CommandMetaVarId<Txt>;
1252}
1253impl CommandNameExt for Command {
1254    fn name(self) -> CommandMetaVar<Txt> {
1255        self.with_meta(|m| {
1256            m.get_var_or_insert(*COMMAND_NAME_ID, || {
1257                let name = self.event.name();
1258                let name = name.strip_suffix("_CMD").unwrap_or(name);
1259                let mut title = String::with_capacity(name.len());
1260                let mut lower = false;
1261                for c in name.chars() {
1262                    if c == '_' {
1263                        if !title.ends_with(' ') {
1264                            title.push(' ');
1265                        }
1266                        lower = false;
1267                    } else if lower {
1268                        for l in c.to_lowercase() {
1269                            title.push(l);
1270                        }
1271                    } else {
1272                        title.push(c);
1273                        lower = true;
1274                    }
1275                }
1276                Txt::from(title)
1277            })
1278        })
1279    }
1280
1281    fn init_name(self, name: impl Into<Txt>) -> Self {
1282        self.with_meta(|m| m.init_var(*COMMAND_NAME_ID, name.into()));
1283        self
1284    }
1285
1286    fn name_with_shortcut(self) -> Var<Txt>
1287    where
1288        Self: crate::shortcut::CommandShortcutExt,
1289    {
1290        crate::var::merge_var!(self.name(), self.shortcut(), |name, shortcut| {
1291            if shortcut.is_empty() {
1292                name.clone()
1293            } else {
1294                zng_txt::formatx!("{name} ({})", shortcut[0])
1295            }
1296        })
1297    }
1298}
1299
1300/// Adds the [`info`](CommandInfoExt) command metadata.
1301pub trait CommandInfoExt {
1302    /// Gets a read-write variable that is a short informational string about the command.
1303    fn info(self) -> CommandMetaVar<Txt>;
1304
1305    /// Sets the initial info if it is not set.
1306    fn init_info(self, info: impl Into<Txt>) -> Self;
1307}
1308static_id! {
1309    static ref COMMAND_INFO_ID: CommandMetaVarId<Txt>;
1310}
1311impl CommandInfoExt for Command {
1312    fn info(self) -> CommandMetaVar<Txt> {
1313        self.with_meta(|m| m.get_var_or_insert(*COMMAND_INFO_ID, Txt::default))
1314    }
1315
1316    fn init_info(self, info: impl Into<Txt>) -> Self {
1317        self.with_meta(|m| m.init_var(*COMMAND_INFO_ID, info.into()));
1318        self
1319    }
1320}
1321
1322enum CommandMetaState {}
1323
1324#[derive(Clone)]
1325enum MetaInit {
1326    Init(fn(Command)),
1327    /// Initing in a thread, lock is for other threads.
1328    Initing(Arc<(ThreadId, Mutex<()>)>),
1329    Inited,
1330}
1331
1332#[doc(hidden)]
1333pub struct CommandData {
1334    meta_init: MetaInit,
1335    meta: Mutex<OwnedStateMap<CommandMetaState>>,
1336
1337    handle_count: usize,
1338    enabled_count: usize,
1339    registered: bool,
1340
1341    has_handlers: Var<bool>,
1342    is_enabled: Var<bool>,
1343
1344    scopes: HashMap<CommandScope, ScopedValue>,
1345}
1346impl CommandData {
1347    pub fn new(meta_init: fn(Command)) -> Self {
1348        CommandData {
1349            meta_init: MetaInit::Init(meta_init),
1350            meta: Mutex::new(OwnedStateMap::new()),
1351
1352            handle_count: 0,
1353            enabled_count: 0,
1354            registered: false,
1355
1356            has_handlers: var(false),
1357            is_enabled: var(false),
1358
1359            scopes: HashMap::default(),
1360        }
1361    }
1362
1363    fn subscribe(&mut self, events: &mut EventsService, command: Command, enabled: bool, mut target: Option<WidgetId>) -> CommandHandle {
1364        match command.scope {
1365            CommandScope::App => {
1366                if !mem::replace(&mut self.registered, true) {
1367                    events.register_command(command);
1368                }
1369
1370                self.handle_count += 1;
1371                if enabled {
1372                    self.enabled_count += 1;
1373                }
1374            }
1375            scope => {
1376                let data = self.scopes.entry(scope).or_default();
1377
1378                if !mem::replace(&mut data.registered, true) {
1379                    events.register_command(command);
1380                }
1381
1382                data.handle_count += 1;
1383                if enabled {
1384                    data.enabled_count += 1;
1385                }
1386
1387                if let CommandScope::Widget(id) = scope {
1388                    target = Some(id);
1389                }
1390            }
1391        };
1392
1393        CommandHandle {
1394            command: Some(command),
1395            app_id: APP.id(),
1396            local_enabled: AtomicBool::new(enabled),
1397            _event_handle: target.map(|t| command.event.subscribe(t)).unwrap_or_else(EventHandle::dummy),
1398        }
1399    }
1400}
1401
1402struct ScopedValue {
1403    handle_count: usize,
1404    enabled_count: usize,
1405    is_enabled: Var<bool>,
1406    has_handlers: Var<bool>,
1407    meta: Mutex<OwnedStateMap<CommandMetaState>>,
1408    registered: bool,
1409}
1410impl Default for ScopedValue {
1411    fn default() -> Self {
1412        ScopedValue {
1413            is_enabled: var(false),
1414            has_handlers: var(false),
1415            handle_count: 0,
1416            enabled_count: 0,
1417            meta: Mutex::new(OwnedStateMap::default()),
1418            registered: false,
1419        }
1420    }
1421}
1422
1423#[cfg(test)]
1424mod tests {
1425    use super::*;
1426
1427    command! {
1428        static FOO_CMD;
1429    }
1430
1431    #[test]
1432    fn parameter_none() {
1433        let _ = CommandArgs::now(None, CommandScope::App, true);
1434    }
1435
1436    #[test]
1437    fn enabled() {
1438        let _app = APP.minimal().run_headless(false);
1439
1440        assert!(!FOO_CMD.has_handlers_value());
1441
1442        let handle = FOO_CMD.subscribe(true);
1443        assert!(FOO_CMD.is_enabled_value());
1444
1445        handle.set_enabled(false);
1446        assert!(FOO_CMD.has_handlers_value());
1447        assert!(!FOO_CMD.is_enabled_value());
1448
1449        handle.set_enabled(true);
1450        assert!(FOO_CMD.is_enabled_value());
1451
1452        drop(handle);
1453        assert!(!FOO_CMD.has_handlers_value());
1454    }
1455
1456    #[test]
1457    fn enabled_scoped() {
1458        let _app = APP.minimal().run_headless(false);
1459
1460        let cmd = FOO_CMD;
1461        let cmd_scoped = FOO_CMD.scoped(WindowId::named("enabled_scoped"));
1462        assert!(!cmd.has_handlers_value());
1463        assert!(!cmd_scoped.has_handlers_value());
1464
1465        let handle_scoped = cmd_scoped.subscribe(true);
1466        assert!(!cmd.has_handlers_value());
1467        assert!(cmd_scoped.is_enabled_value());
1468
1469        handle_scoped.set_enabled(false);
1470        assert!(!cmd.has_handlers_value());
1471        assert!(!cmd_scoped.is_enabled_value());
1472        assert!(cmd_scoped.has_handlers_value());
1473
1474        handle_scoped.set_enabled(true);
1475        assert!(!cmd.has_handlers_value());
1476        assert!(cmd_scoped.is_enabled_value());
1477
1478        drop(handle_scoped);
1479        assert!(!cmd.has_handlers_value());
1480        assert!(!cmd_scoped.has_handlers_value());
1481    }
1482
1483    #[test]
1484    fn has_handlers() {
1485        let _app = APP.minimal().run_headless(false);
1486
1487        assert!(!FOO_CMD.has_handlers_value());
1488
1489        let handle = FOO_CMD.subscribe(false);
1490        assert!(FOO_CMD.has_handlers_value());
1491
1492        drop(handle);
1493        assert!(!FOO_CMD.has_handlers_value());
1494    }
1495
1496    #[test]
1497    fn has_handlers_scoped() {
1498        let _app = APP.minimal().run_headless(false);
1499
1500        let cmd = FOO_CMD;
1501        let cmd_scoped = FOO_CMD.scoped(WindowId::named("has_handlers_scoped"));
1502
1503        assert!(!cmd.has_handlers_value());
1504        assert!(!cmd_scoped.has_handlers_value());
1505
1506        let handle = cmd_scoped.subscribe(false);
1507
1508        assert!(!cmd.has_handlers_value());
1509        assert!(cmd_scoped.has_handlers_value());
1510
1511        drop(handle);
1512
1513        assert!(!cmd.has_handlers_value());
1514        assert!(!cmd_scoped.has_handlers_value());
1515    }
1516
1517    // there are also integration tests in tests/command.rs
1518}