zng_app/event/
command.rs

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