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                let r = visit(&mut CommandMeta {
439                    meta: read.meta.lock().borrow_mut(),
440                    scope: Some(scope.meta.lock().borrow_mut()),
441                });
442
443                r
444            }
445        }
446    }
447
448    /// Returns `true` if the update is for this command and scope.
449    pub fn has(&self, update: &EventUpdate) -> bool {
450        self.on(update).is_some()
451    }
452
453    /// Get the command update args if the update is for this command and scope.
454    pub fn on<'a>(&self, update: &'a EventUpdate) -> Option<&'a CommandArgs> {
455        self.event.on(update).filter(|a| a.scope == self.scope)
456    }
457
458    /// Get the event update args if the update is for this event and propagation is not stopped.
459    pub fn on_unhandled<'a>(&self, update: &'a EventUpdate) -> Option<&'a CommandArgs> {
460        self.event
461            .on(update)
462            .filter(|a| a.scope == self.scope && !a.propagation().is_stopped())
463    }
464
465    /// Calls `handler` if the update is for this event and propagation is not stopped,
466    /// after the handler is called propagation is stopped.
467    pub fn handle<R>(&self, update: &EventUpdate, handler: impl FnOnce(&CommandArgs) -> R) -> Option<R> {
468        if let Some(args) = self.on(update) {
469            args.handle(handler)
470        } else {
471            None
472        }
473    }
474
475    /// Gets a variable that tracks if this command has any handlers.
476    pub fn has_handlers(&self) -> ReadOnlyArcVar<bool> {
477        let mut write = self.local.write();
478        match self.scope {
479            CommandScope::App => write.has_handlers.read_only(),
480            scope => write.scopes.entry(scope).or_default().has_handlers.read_only(),
481        }
482    }
483
484    /// Gets a variable that tracks if this command has any enabled handlers.
485    pub fn is_enabled(&self) -> ReadOnlyArcVar<bool> {
486        let mut write = self.local.write();
487        match self.scope {
488            CommandScope::App => write.is_enabled.read_only(),
489            scope => write.scopes.entry(scope).or_default().is_enabled.read_only(),
490        }
491    }
492
493    /// Gets if the command has handlers without creating a tracking variable for the state.
494    pub fn has_handlers_value(&self) -> bool {
495        let read = self.local.read();
496        match self.scope {
497            CommandScope::App => read.handle_count > 0,
498            scope => read.scopes.get(&scope).map(|l| l.handle_count > 0).unwrap_or(false),
499        }
500    }
501
502    /// Gets if the command is enabled without creating a tracking variable for the state.
503    pub fn is_enabled_value(&self) -> bool {
504        let read = self.local.read();
505        match self.scope {
506            CommandScope::App => read.enabled_count > 0,
507            scope => read.scopes.get(&scope).map(|l| l.enabled_count > 0).unwrap_or(false),
508        }
509    }
510
511    /// Calls `visitor` for each scope of this command.
512    ///
513    /// Note that scoped commands are removed if unused, see [`with_meta`](Self::with_meta) for more details.
514    pub fn visit_scopes<T>(&self, mut visitor: impl FnMut(Command) -> ControlFlow<T>) -> Option<T> {
515        let read = self.local.read();
516        for &scope in read.scopes.keys() {
517            match visitor(self.scoped(scope)) {
518                ControlFlow::Continue(_) => continue,
519                ControlFlow::Break(r) => return Some(r),
520            }
521        }
522        None
523    }
524
525    /// Schedule a command update without param.
526    pub fn notify(&self) {
527        self.event.notify(CommandArgs::now(None, self.scope, self.is_enabled_value()))
528    }
529
530    /// Schedule a command update without param for all scopes inside `parent`.
531    pub fn notify_descendants(&self, parent: &WidgetInfo) {
532        self.visit_scopes::<()>(|parse_cmd| {
533            if let CommandScope::Widget(id) = parse_cmd.scope() {
534                if let Some(scope) = parent.tree().get(id) {
535                    if scope.is_descendant(parent) {
536                        parse_cmd.notify();
537                    }
538                }
539            }
540            ControlFlow::Continue(())
541        });
542    }
543
544    /// Schedule a command update with custom `param`.
545    pub fn notify_param(&self, param: impl Any + Send + Sync) {
546        self.event
547            .notify(CommandArgs::now(CommandParam::new(param), self.scope, self.is_enabled_value()));
548    }
549
550    /// Schedule a command update linked with an external event `propagation`.
551    pub fn notify_linked(&self, propagation: EventPropagationHandle, param: Option<CommandParam>) {
552        self.event.notify(CommandArgs::new(
553            crate::INSTANT.now(),
554            propagation,
555            param,
556            self.scope,
557            self.is_enabled_value(),
558        ))
559    }
560
561    /// Creates a preview event handler for the command.
562    ///
563    /// This is similar to [`Event::on_pre_event`], but `handler` is only called if the command
564    /// scope matches.
565    ///
566    /// The `enabled` parameter defines the initial state of the command subscription, the subscription
567    /// handle is available in the handler args.
568    pub fn on_pre_event<H>(&self, enabled: bool, handler: H) -> EventHandle
569    where
570        H: AppHandler<AppCommandArgs>,
571    {
572        self.event().on_pre_event(CmdAppHandler {
573            handler,
574            handle: Arc::new(self.subscribe(enabled)),
575        })
576    }
577
578    /// Creates an event handler for the command.
579    ///
580    /// This is similar to [`Event::on_event`], but `handler` is only called if the command
581    /// scope matches.
582    ///
583    /// The `enabled` parameter defines the initial state of the command subscription, the subscription
584    /// handle is available in the handler args.
585    pub fn on_event<H>(&self, enabled: bool, handler: H) -> EventHandle
586    where
587        H: AppHandler<AppCommandArgs>,
588    {
589        self.event().on_event(CmdAppHandler {
590            handler,
591            handle: Arc::new(self.subscribe(enabled)),
592        })
593    }
594
595    /// Update state vars, returns if the command must be retained.
596    #[must_use]
597    pub(crate) fn update_state(&self) -> bool {
598        let mut write = self.local.write();
599        if let CommandScope::App = self.scope {
600            let has_handlers = write.handle_count > 0;
601            if has_handlers != write.has_handlers.get() {
602                write.has_handlers.set(has_handlers);
603            }
604            let is_enabled = has_handlers && write.enabled_count > 0;
605            if is_enabled != write.is_enabled.get() {
606                write.is_enabled.set(is_enabled);
607            }
608            true
609        } else if let hash_map::Entry::Occupied(entry) = write.scopes.entry(self.scope) {
610            let scope = entry.get();
611
612            if scope.handle_count == 0 && scope.has_handlers.strong_count() == 1 && scope.is_enabled.strong_count() == 1 {
613                entry.remove();
614                return false;
615            }
616
617            let has_handlers = scope.handle_count > 0;
618            if has_handlers != scope.has_handlers.get() {
619                scope.has_handlers.set(has_handlers);
620            }
621            let is_enabled = has_handlers && scope.enabled_count > 0;
622            if is_enabled != scope.is_enabled.get() {
623                scope.is_enabled.set(is_enabled);
624            }
625            true
626        } else {
627            false
628        }
629    }
630}
631impl Deref for Command {
632    type Target = Event<CommandArgs>;
633
634    fn deref(&self) -> &Self::Target {
635        &self.event
636    }
637}
638impl PartialEq for Command {
639    fn eq(&self, other: &Self) -> bool {
640        self.event == other.event && self.scope == other.scope
641    }
642}
643impl Eq for Command {}
644impl std::hash::Hash for Command {
645    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
646        std::hash::Hash::hash(&self.event.as_any(), state);
647        std::hash::Hash::hash(&self.scope, state);
648    }
649}
650
651struct CmdAppHandler<H> {
652    handler: H,
653    handle: Arc<CommandHandle>,
654}
655impl<H: AppHandler<AppCommandArgs>> AppHandler<CommandArgs> for CmdAppHandler<H> {
656    fn event(&mut self, args: &CommandArgs, handler_args: &AppHandlerArgs) {
657        let args = AppCommandArgs {
658            args: args.clone(),
659            handle: self.handle.clone(),
660        };
661        self.handler.event(&args, handler_args);
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`].
769#[derive(Debug, Clone)]
770pub struct AppCommandArgs {
771    /// The command args.
772    pub args: CommandArgs,
773    /// The command handle held by the event handler.
774    pub handle: Arc<CommandHandle>,
775}
776impl ops::Deref for AppCommandArgs {
777    type Target = CommandArgs;
778
779    fn deref(&self) -> &Self::Target {
780        &self.args
781    }
782}
783impl AnyEventArgs for AppCommandArgs {
784    fn clone_any(&self) -> Box<dyn AnyEventArgs> {
785        Box::new(self.clone())
786    }
787
788    fn as_any(&self) -> &dyn Any {
789        self
790    }
791
792    fn timestamp(&self) -> crate::DInstant {
793        self.args.timestamp()
794    }
795
796    fn delivery_list(&self, list: &mut UpdateDeliveryList) {
797        self.args.delivery_list(list)
798    }
799
800    fn propagation(&self) -> &EventPropagationHandle {
801        self.args.propagation()
802    }
803}
804impl EventArgs for AppCommandArgs {}
805
806/// A handle to a [`Command`] subscription.
807///
808/// Holding the command handle indicates that the command is relevant in the current app state.
809/// The handle needs to be enabled to indicate that the command primary action can be executed.
810///
811/// You can use the [`Command::subscribe`] method in a command type to create a handle.
812pub struct CommandHandle {
813    command: Option<Command>,
814    local_enabled: AtomicBool,
815    app_id: Option<AppId>,
816    _event_handle: EventHandle,
817}
818impl CommandHandle {
819    /// The command.
820    pub fn command(&self) -> Option<Command> {
821        self.command
822    }
823
824    /// Sets if the command event handler is active.
825    ///
826    /// When at least one [`CommandHandle`] is enabled the command is [`is_enabled`](Command::is_enabled).
827    pub fn set_enabled(&self, enabled: bool) {
828        if let Some(command) = self.command {
829            if self.local_enabled.swap(enabled, Ordering::Relaxed) != enabled {
830                if self.app_id != APP.id() {
831                    return;
832                }
833
834                UpdatesTrace::log_var(std::any::type_name::<bool>());
835
836                let mut write = command.local.write();
837                match command.scope {
838                    CommandScope::App => {
839                        if enabled {
840                            write.enabled_count += 1;
841                        } else {
842                            write.enabled_count -= 1;
843                        }
844                    }
845                    scope => {
846                        if let Some(data) = write.scopes.get_mut(&scope) {
847                            if enabled {
848                                data.enabled_count += 1;
849                            } else {
850                                data.enabled_count -= 1;
851                            }
852                        }
853                    }
854                }
855            }
856        }
857    }
858
859    /// Returns if this handle has enabled the command.
860    pub fn is_enabled(&self) -> bool {
861        self.local_enabled.load(Ordering::Relaxed)
862    }
863
864    /// New handle not connected to any command.
865    pub fn dummy() -> Self {
866        CommandHandle {
867            command: None,
868            app_id: None,
869            local_enabled: AtomicBool::new(false),
870            _event_handle: EventHandle::dummy(),
871        }
872    }
873
874    /// If the handle is not connected to any command.
875    pub fn is_dummy(&self) -> bool {
876        self.command.is_none()
877    }
878}
879impl fmt::Debug for CommandHandle {
880    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
881        f.debug_struct("CommandHandle")
882            .field("command", &self.command)
883            .field("local_enabled", &self.local_enabled.load(Ordering::Relaxed))
884            .finish()
885    }
886}
887impl Drop for CommandHandle {
888    fn drop(&mut self) {
889        if let Some(command) = self.command {
890            if self.app_id != APP.id() {
891                return;
892            }
893
894            let mut write = command.local.write();
895            match command.scope {
896                CommandScope::App => {
897                    write.handle_count -= 1;
898                    if self.local_enabled.load(Ordering::Relaxed) {
899                        write.enabled_count -= 1;
900                    }
901                }
902                scope => {
903                    if let Some(data) = write.scopes.get_mut(&scope) {
904                        data.handle_count -= 1;
905                        if self.local_enabled.load(Ordering::Relaxed) {
906                            data.enabled_count -= 1;
907                        }
908                    }
909                }
910            }
911        }
912    }
913}
914impl Default for CommandHandle {
915    fn default() -> Self {
916        Self::dummy()
917    }
918}
919
920/// Represents a reference counted `dyn Any` object parameter for a command request.
921#[derive(Clone)]
922pub struct CommandParam(pub Arc<dyn Any + Send + Sync>);
923impl PartialEq for CommandParam {
924    fn eq(&self, other: &Self) -> bool {
925        Arc::ptr_eq(&self.0, &other.0)
926    }
927}
928impl Eq for CommandParam {}
929impl CommandParam {
930    /// New param.
931    ///
932    /// If `param` is already a [`CommandParam`] returns a clone.
933    pub fn new(param: impl Any + Send + Sync + 'static) -> Self {
934        let p: &dyn Any = &param;
935        if let Some(p) = p.downcast_ref::<Self>() {
936            p.clone()
937        } else {
938            CommandParam(Arc::new(param))
939        }
940    }
941
942    /// Gets the [`TypeId`] of the parameter.
943    pub fn type_id(&self) -> TypeId {
944        self.0.type_id()
945    }
946
947    /// Gets a typed reference to the parameter if it is of type `T`.
948    pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
949        self.0.downcast_ref()
950    }
951
952    /// Returns `true` if the parameter type is `T`.
953    pub fn is<T: Any>(&self) -> bool {
954        self.0.is::<T>()
955    }
956}
957impl fmt::Debug for CommandParam {
958    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
959        f.debug_tuple("CommandParam").field(&self.0.type_id()).finish()
960    }
961}
962zng_var::impl_from_and_into_var! {
963    fn from(param: CommandParam) -> Option<CommandParam>;
964}
965
966unique_id_64! {
967    /// Unique identifier of a command metadata state variable.
968    ///
969    /// This type is very similar to [`StateId`], but `T` is the value type of the metadata variable.
970    ///
971    /// [`StateId`]: zng_state_map::StateId
972    pub struct CommandMetaVarId<T: (StateValue + VarValue)>: StateId;
973}
974zng_unique_id::impl_unique_id_bytemuck!(CommandMetaVarId<T: (StateValue + VarValue)>);
975impl<T: StateValue + VarValue> CommandMetaVarId<T> {
976    fn app(self) -> StateId<ArcVar<T>> {
977        let id = self.get();
978        StateId::from_raw(id)
979    }
980
981    fn scope(self) -> StateId<ArcCowVar<T, ArcVar<T>>> {
982        let id = self.get();
983        StateId::from_raw(id)
984    }
985}
986
987impl<T: StateValue + VarValue> fmt::Debug for CommandMetaVarId<T> {
988    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
989        #[cfg(debug_assertions)]
990        let t = pretty_type_name::pretty_type_name::<T>();
991        #[cfg(not(debug_assertions))]
992        let t = "$T";
993
994        if f.alternate() {
995            writeln!(f, "CommandMetaVarId<{t} {{")?;
996            writeln!(f, "   id: {},", self.get())?;
997            writeln!(f, "   sequential: {}", self.sequential())?;
998            writeln!(f, "}}")
999        } else {
1000            write!(f, "CommandMetaVarId<{t}>({})", self.sequential())
1001        }
1002    }
1003}
1004
1005/// Access to metadata of a command.
1006///
1007/// The metadata storage can be accessed using the [`Command::with_meta`]
1008/// method, implementers must declare and extension trait that adds methods that return [`CommandMetaVar`] or
1009/// [`ReadOnlyCommandMetaVar`] that are stored in the [`CommandMeta`]. An initialization builder method for
1010/// each value also must be provided to integrate with the [`command!`] macro.
1011///
1012/// # Examples
1013///
1014/// The [`command!`] initialization transforms `foo: true,` to `command.init_foo(true);`, to support that, the command extension trait
1015/// must have a `foo` and `init_foo` methods.
1016///
1017/// ```
1018/// use zng_app::{event::*, var::*, static_id};
1019///
1020/// static_id! {
1021///     static ref COMMAND_FOO_ID: CommandMetaVarId<bool>;
1022///     static ref COMMAND_BAR_ID: CommandMetaVarId<bool>;
1023/// }
1024///
1025/// /// FooBar command values.
1026/// pub trait CommandFooBarExt {
1027///     /// Gets read/write *foo*.
1028///     fn foo(self) -> CommandMetaVar<bool>;
1029///
1030///     /// Gets read-only *bar*.
1031///     fn bar(self) -> ReadOnlyCommandMetaVar<bool>;
1032///
1033///     /// Gets a read-only var derived from other metadata.
1034///     fn foo_and_bar(self) -> BoxedVar<bool>;
1035///
1036///     /// Init *foo*.
1037///     fn init_foo(self, foo: bool) -> Self;
1038///
1039///     /// Init *bar*.
1040///     fn init_bar(self, bar: bool) -> Self;
1041/// }
1042///
1043/// impl CommandFooBarExt for Command {
1044///     fn foo(self) -> CommandMetaVar<bool> {
1045///         self.with_meta(|m| m.get_var_or_default(*COMMAND_FOO_ID))
1046///     }
1047///
1048///     fn bar(self) -> ReadOnlyCommandMetaVar<bool> {
1049///         self.with_meta(|m| m.get_var_or_insert(*COMMAND_BAR_ID, ||true)).read_only()
1050///     }
1051///
1052///     fn foo_and_bar(self) -> BoxedVar<bool> {
1053///         merge_var!(self.foo(), self.bar(), |f, b| *f && *b).boxed()
1054///     }
1055///
1056///     fn init_foo(self, foo: bool) -> Self {
1057///         self.with_meta(|m| m.init_var(*COMMAND_FOO_ID, foo));
1058///         self
1059///     }
1060///
1061///     fn init_bar(self, bar: bool) -> Self {
1062///         self.with_meta(|m| m.init_var(*COMMAND_BAR_ID, bar));
1063///         self
1064///     }
1065/// }
1066/// ```
1067///
1068/// [`command!`]: macro@crate::event::command
1069pub struct CommandMeta<'a> {
1070    meta: StateMapMut<'a, CommandMetaState>,
1071    scope: Option<StateMapMut<'a, CommandMetaState>>,
1072}
1073impl CommandMeta<'_> {
1074    /// Clone a meta value identified by a [`StateId`].
1075    ///
1076    /// If the key is not set in the app, insert it using `init` to produce a value.
1077    ///
1078    /// [`StateId`]: zng_state_map::StateId
1079    pub fn get_or_insert<T, F>(&mut self, id: impl Into<StateId<T>>, init: F) -> T
1080    where
1081        T: StateValue + Clone,
1082        F: FnOnce() -> T,
1083    {
1084        let id = id.into();
1085        if let Some(scope) = &mut self.scope {
1086            if let Some(value) = scope.get(id) {
1087                value.clone()
1088            } else if let Some(value) = self.meta.get(id) {
1089                value.clone()
1090            } else {
1091                let value = init();
1092                let r = value.clone();
1093                scope.set(id, value);
1094                r
1095            }
1096        } else {
1097            self.meta.entry(id).or_insert_with(init).clone()
1098        }
1099    }
1100
1101    /// Clone a meta value identified by a [`StateId`].
1102    ///
1103    /// If the key is not set, insert the default value and returns a clone of it.
1104    ///
1105    /// [`StateId`]: zng_state_map::StateId
1106    pub fn get_or_default<T>(&mut self, id: impl Into<StateId<T>>) -> T
1107    where
1108        T: StateValue + Clone + Default,
1109    {
1110        self.get_or_insert(id, Default::default)
1111    }
1112
1113    /// Clone a meta value identified by a [`StateId`] if it is set.
1114    ///
1115    /// [`StateId`]: zng_state_map::StateId
1116    pub fn get<T>(&self, id: impl Into<StateId<T>>) -> Option<T>
1117    where
1118        T: StateValue + Clone,
1119    {
1120        let id = id.into();
1121        if let Some(scope) = &self.scope {
1122            scope.get(id).or_else(|| self.meta.get(id))
1123        } else {
1124            self.meta.get(id)
1125        }
1126        .cloned()
1127    }
1128
1129    /// Set the meta value associated with the [`StateId`].
1130    ///
1131    /// [`StateId`]: zng_state_map::StateId
1132    pub fn set<T>(&mut self, id: impl Into<StateId<T>>, value: impl Into<T>)
1133    where
1134        T: StateValue + Clone,
1135    {
1136        if let Some(scope) = &mut self.scope {
1137            scope.set(id, value);
1138        } else {
1139            self.meta.set(id, value);
1140        }
1141    }
1142
1143    /// Set the metadata value only if it is not set.
1144    ///
1145    /// This does not set the scoped override, only the command type metadata.
1146    pub fn init<T>(&mut self, id: impl Into<StateId<T>>, value: impl Into<T>)
1147    where
1148        T: StateValue + Clone,
1149    {
1150        self.meta.entry(id).or_insert(value);
1151    }
1152
1153    /// Clone a meta variable identified by a [`CommandMetaVarId`].
1154    ///
1155    /// The variable is read-write and is clone-on-write if the command is scoped.
1156    ///
1157    /// [`read_only`]: Var::read_only
1158    pub fn get_var_or_insert<T, F>(&mut self, id: impl Into<CommandMetaVarId<T>>, init: F) -> CommandMetaVar<T>
1159    where
1160        T: StateValue + VarValue,
1161        F: FnOnce() -> T,
1162    {
1163        let id = id.into();
1164        if let Some(scope) = &mut self.scope {
1165            let meta = &mut self.meta;
1166            scope
1167                .entry(id.scope())
1168                .or_insert_with(|| {
1169                    let var = meta.entry(id.app()).or_insert_with(|| var(init())).clone();
1170                    var.cow()
1171                })
1172                .clone()
1173                .boxed()
1174        } else {
1175            self.meta.entry(id.app()).or_insert_with(|| var(init())).clone().boxed()
1176        }
1177    }
1178
1179    /// Clone a meta variable identified by a [`CommandMetaVarId`], if it is set.
1180    pub fn get_var<T>(&self, id: impl Into<CommandMetaVarId<T>>) -> Option<CommandMetaVar<T>>
1181    where
1182        T: StateValue + VarValue,
1183    {
1184        let id = id.into();
1185        if let Some(scope) = &self.scope {
1186            let meta = &self.meta;
1187            scope
1188                .get(id.scope())
1189                .map(|c| c.clone().boxed())
1190                .or_else(|| meta.get(id.app()).map(|c| c.clone().boxed()))
1191        } else {
1192            self.meta.get(id.app()).map(|c| c.clone().boxed())
1193        }
1194    }
1195
1196    /// Clone a meta variable identified by a [`CommandMetaVarId`].
1197    ///
1198    /// Inserts a variable with the default value if no variable is in the metadata.
1199    pub fn get_var_or_default<T>(&mut self, id: impl Into<CommandMetaVarId<T>>) -> CommandMetaVar<T>
1200    where
1201        T: StateValue + VarValue + Default,
1202    {
1203        self.get_var_or_insert(id, Default::default)
1204    }
1205
1206    /// Set the metadata variable if it was not set.
1207    ///
1208    /// This does not set the scoped override, only the command type metadata.
1209    pub fn init_var<T>(&mut self, id: impl Into<CommandMetaVarId<T>>, value: impl Into<T>)
1210    where
1211        T: StateValue + VarValue,
1212    {
1213        self.meta.entry(id.into().app()).or_insert_with(|| var(value.into()));
1214    }
1215}
1216
1217/// Read-write command metadata variable.
1218///
1219/// The boxed var is an [`ArcVar<T>`] for *app* scope, or [`ArcCowVar<T, ArcVar<T>>`] for scoped commands.
1220/// If you get this variable from an app scoped command it sets
1221/// the value for all scopes. If you get this variable using a scoped command,
1222/// it is a clone-on-write variable that overrides only the value for the scope.
1223///
1224/// [`ArcVar<T>`]: zng_var::ArcVar
1225/// [`ArcCowVar<T, ArcVar<T>>`]: zng_var::types::ArcCowVar
1226pub type CommandMetaVar<T> = BoxedVar<T>;
1227
1228/// Read-only command metadata variable.
1229///
1230/// To convert a [`CommandMetaVar<T>`] into this var call [`read_only`].
1231///
1232/// [`read_only`]: Var::read_only
1233pub type ReadOnlyCommandMetaVar<T> = BoxedVar<T>;
1234
1235/// Adds the [`name`](CommandNameExt) command metadata.
1236pub trait CommandNameExt {
1237    /// Gets a read-write variable that is the display name for the command.
1238    fn name(self) -> CommandMetaVar<Txt>;
1239
1240    /// Sets the initial name if it is not set.
1241    fn init_name(self, name: impl Into<Txt>) -> Self;
1242
1243    /// Gets a read-only variable that formats the name and first shortcut in the following format: name (first_shortcut)
1244    /// Note: If no shortcuts are available this method returns the same as [`name`](Self::name)
1245    fn name_with_shortcut(self) -> BoxedVar<Txt>
1246    where
1247        Self: crate::shortcut::CommandShortcutExt;
1248}
1249static_id! {
1250    static ref COMMAND_NAME_ID: CommandMetaVarId<Txt>;
1251}
1252impl CommandNameExt for Command {
1253    fn name(self) -> CommandMetaVar<Txt> {
1254        self.with_meta(|m| {
1255            m.get_var_or_insert(*COMMAND_NAME_ID, || {
1256                let name = self.event.name();
1257                let name = name.strip_suffix("_CMD").unwrap_or(name);
1258                let mut title = String::with_capacity(name.len());
1259                let mut lower = false;
1260                for c in name.chars() {
1261                    if c == '_' {
1262                        if !title.ends_with(' ') {
1263                            title.push(' ');
1264                        }
1265                        lower = false;
1266                    } else if lower {
1267                        for l in c.to_lowercase() {
1268                            title.push(l);
1269                        }
1270                    } else {
1271                        title.push(c);
1272                        lower = true;
1273                    }
1274                }
1275                Txt::from(title)
1276            })
1277        })
1278    }
1279
1280    fn init_name(self, name: impl Into<Txt>) -> Self {
1281        self.with_meta(|m| m.init_var(*COMMAND_NAME_ID, name.into()));
1282        self
1283    }
1284
1285    fn name_with_shortcut(self) -> BoxedVar<Txt>
1286    where
1287        Self: crate::shortcut::CommandShortcutExt,
1288    {
1289        crate::var::merge_var!(self.name(), self.shortcut(), |name, shortcut| {
1290            if shortcut.is_empty() {
1291                name.clone()
1292            } else {
1293                zng_txt::formatx!("{name} ({})", shortcut[0])
1294            }
1295        })
1296        .boxed()
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: ArcVar<bool>,
1342    is_enabled: ArcVar<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: ArcVar<bool>,
1406    has_handlers: ArcVar<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}