Skip to main content

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