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 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(
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);
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);
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    /// Visit the 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                        todo!()
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.
992pub struct CommandHandle {
993    command: Option<Command>,
994    local_enabled: Var<bool>,
995    // event subscription handle in `subscribe` or event handle in `on_(pre_)event`
996    _handles: VarHandles,
997}
998/// Clone the handle.
999///
1000/// Note that the cloned handle will have its own enabled state, disconnected from this handle.
1001impl Clone for CommandHandle {
1002    fn clone(&self) -> Self {
1003        match self.command {
1004            Some(c) => c.subscribe(self.local_enabled.get()),
1005            None => Self::dummy(),
1006        }
1007    }
1008}
1009impl CommandHandle {
1010    /// The command.
1011    pub fn command(&self) -> Option<Command> {
1012        self.command
1013    }
1014
1015    /// Variable that gets and sets if this specific handle is enabled.
1016    ///
1017    /// The [`Command::is_enabled`] is `true` if any handle is enabled.
1018    pub fn enabled(&self) -> &Var<bool> {
1019        &self.local_enabled
1020    }
1021
1022    /// New handle not connected to any command.
1023    pub fn dummy() -> Self {
1024        CommandHandle {
1025            command: None,
1026            local_enabled: const_var(false),
1027            _handles: VarHandles::dummy(),
1028        }
1029    }
1030
1031    fn new(cmd: Command, event_handle: VarHandle, enabled: bool) -> Self {
1032        let mut r = Self {
1033            command: Some(cmd),
1034            local_enabled: var(enabled),
1035            _handles: VarHandles::dummy(),
1036        };
1037
1038        // var explicit update can call hook without changing value
1039        let mut last_applied = enabled;
1040        r._handles.push(r.local_enabled.hook(move |args| {
1041            let _hold = &event_handle;
1042            let enabled = *args.value();
1043            if last_applied != enabled {
1044                Self::update_enabled(cmd, enabled);
1045                last_applied = enabled;
1046            }
1047            true
1048        }));
1049
1050        r
1051    }
1052
1053    fn update_enabled(command: Command, enabled: bool) {
1054        let mut write = command.local.write();
1055        match command.scope {
1056            CommandScope::App => {
1057                if enabled {
1058                    write.enabled_count += 1;
1059                    if write.enabled_count == 1 {
1060                        write.is_enabled.set(true);
1061                    }
1062                    tracing::trace!(
1063                        "command handle {:?} enabled, count: {:?}",
1064                        CommandDbg::new(write.static_name, command.scope),
1065                        write.enabled_count
1066                    );
1067                } else {
1068                    write.enabled_count = match write.enabled_count.checked_sub(1) {
1069                        Some(c) => c,
1070                        None => {
1071                            #[cfg(debug_assertions)]
1072                            panic!("handle for {} was disabled when enabled_count was already zero", write.static_name);
1073                            #[cfg(not(debug_assertions))]
1074                            0
1075                        }
1076                    };
1077                    if write.enabled_count == 0 {
1078                        write.is_enabled.set(false);
1079                    }
1080                    tracing::trace!(
1081                        "command handle {:?} disabled, count: {:?}",
1082                        CommandDbg::new(write.static_name, command.scope),
1083                        write.enabled_count
1084                    );
1085                }
1086            }
1087            scope => {
1088                let write = &mut *write;
1089                if let Some(data) = write.scopes.get_mut(&scope) {
1090                    if enabled {
1091                        data.enabled_count += 1;
1092                        if data.enabled_count == 1 {
1093                            data.is_enabled.set(true);
1094                        }
1095                        tracing::trace!(
1096                            "command handle {:?} enabled, count: {:?}",
1097                            CommandDbg::new(write.static_name, command.scope),
1098                            data.enabled_count
1099                        );
1100                    } else {
1101                        data.enabled_count = match data.enabled_count.checked_sub(1) {
1102                            Some(c) => c,
1103                            None => {
1104                                #[cfg(debug_assertions)]
1105                                panic!(
1106                                    "handle for {:?} was disabled when enabled_count was already zero",
1107                                    CommandDbg::new(write.static_name, scope)
1108                                );
1109                                #[cfg(not(debug_assertions))]
1110                                0
1111                            }
1112                        };
1113                        if data.enabled_count == 0 {
1114                            data.is_enabled.set(false);
1115                        }
1116                        tracing::trace!(
1117                            "command handle {:?} enabled, count: {:?}",
1118                            CommandDbg::new(write.static_name, command.scope),
1119                            data.enabled_count
1120                        );
1121                    }
1122                }
1123            }
1124        }
1125    }
1126
1127    /// If the handle is not connected to any command.
1128    pub fn is_dummy(&self) -> bool {
1129        self.command.is_none()
1130    }
1131
1132    /// Drop the handle removing its effect.
1133    ///
1134    /// The command will be stuck indicating that it has handlers and if this handle is enabled, it will
1135    /// continue indicating it is enabled until the app exit.
1136    pub fn perm(mut self) {
1137        // perm the handle
1138        mem::replace(&mut self._handles, VarHandles::dummy()).perm();
1139        // pretend we are dummy to avoid Drop code
1140        self.command = None;
1141    }
1142}
1143impl fmt::Debug for CommandHandle {
1144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1145        f.debug_struct("CommandHandle")
1146            .field("command", &self.command)
1147            .field("enabled", &self.local_enabled.get())
1148            .finish_non_exhaustive()
1149    }
1150}
1151impl Drop for CommandHandle {
1152    fn drop(&mut self) {
1153        if let Some(command) = self.command.take()
1154            && APP.is_started()
1155        {
1156            let mut write = command.local.write();
1157            match command.scope {
1158                CommandScope::App => {
1159                    write.handle_count = match write.handle_count.checked_sub(1) {
1160                        Some(c) => c,
1161                        None => {
1162                            #[cfg(debug_assertions)]
1163                            panic!("handle for {} was dropped when handle_count was already zero", write.static_name);
1164                            #[cfg(not(debug_assertions))]
1165                            0
1166                        }
1167                    };
1168                    if write.handle_count == 0 {
1169                        write.has_handlers.set(false);
1170                    }
1171
1172                    if self.local_enabled.get() {
1173                        write.enabled_count = match write.enabled_count.checked_sub(1) {
1174                            Some(c) => c,
1175                            None => {
1176                                #[cfg(debug_assertions)]
1177                                panic!(
1178                                    "handle for enabled {} was dropped when enabled_count was already zero",
1179                                    write.static_name
1180                                );
1181                                #[cfg(not(debug_assertions))]
1182                                0
1183                            }
1184                        };
1185
1186                        if write.enabled_count == 0 {
1187                            write.is_enabled.set(false);
1188                        }
1189                    }
1190
1191                    tracing::trace!(
1192                        "unsubscribe to {:?}, handle_count: {:?}, enabled_count: {:?}",
1193                        CommandDbg::new(write.static_name, command.scope),
1194                        write.handle_count,
1195                        write.enabled_count
1196                    );
1197                }
1198                scope => {
1199                    let write = &mut *write;
1200                    if let hash_map::Entry::Occupied(mut entry) = write.scopes.entry(scope) {
1201                        let data = entry.get_mut();
1202
1203                        data.handle_count = match data.handle_count.checked_sub(1) {
1204                            Some(c) => c,
1205                            None => {
1206                                #[cfg(debug_assertions)]
1207                                panic!(
1208                                    "handle for {:?} was dropped when handle_count was already zero",
1209                                    CommandDbg::new(write.static_name, scope)
1210                                );
1211                                #[cfg(not(debug_assertions))]
1212                                0
1213                            }
1214                        };
1215
1216                        if self.local_enabled.get() {
1217                            data.enabled_count = match data.enabled_count.checked_sub(1) {
1218                                Some(c) => c,
1219                                None => {
1220                                    #[cfg(debug_assertions)]
1221                                    panic!(
1222                                        "handle for enabled {:?} was dropped when enabled_count was already zero",
1223                                        CommandDbg::new(write.static_name, scope)
1224                                    );
1225                                    #[cfg(not(debug_assertions))]
1226                                    0
1227                                }
1228                            };
1229
1230                            if data.enabled_count == 0 {
1231                                data.is_enabled.set(false);
1232                            }
1233                        }
1234
1235                        tracing::trace!(
1236                            "unsubscribe to {:?}, handle_count: {:?}, enabled_count: {:?}",
1237                            CommandDbg::new(write.static_name, command.scope),
1238                            data.handle_count,
1239                            data.enabled_count
1240                        );
1241
1242                        if data.handle_count == 0 {
1243                            data.has_handlers.set(false);
1244                            if data.observer_count == 0 {
1245                                entry.remove();
1246                                EVENTS.unregister_command(command);
1247                            }
1248                        }
1249                    }
1250                }
1251            }
1252        }
1253    }
1254}
1255impl Default for CommandHandle {
1256    fn default() -> Self {
1257        Self::dummy()
1258    }
1259}
1260
1261/// Represents a reference counted `dyn Any` object parameter for a command request.
1262#[derive(Clone)]
1263#[non_exhaustive]
1264pub struct CommandParam(pub Arc<dyn Any + Send + Sync>);
1265impl PartialEq for CommandParam {
1266    fn eq(&self, other: &Self) -> bool {
1267        Arc::ptr_eq(&self.0, &other.0)
1268    }
1269}
1270impl Eq for CommandParam {}
1271impl CommandParam {
1272    /// New param.
1273    ///
1274    /// If `param` is already a [`CommandParam`] or `Arc<dyn Any + Send + Sync>` returns a clone.
1275    pub fn new(param: impl Any + Send + Sync + 'static) -> Self {
1276        let p: &dyn Any = &param;
1277        if let Some(p) = p.downcast_ref::<Self>() {
1278            p.clone()
1279        } else if let Some(p) = p.downcast_ref::<Arc<dyn Any + Send + Sync>>() {
1280            CommandParam(p.clone())
1281        } else {
1282            CommandParam(Arc::new(param))
1283        }
1284    }
1285
1286    /// Gets the [`TypeId`] of the parameter.
1287    pub fn type_id(&self) -> TypeId {
1288        self.0.type_id()
1289    }
1290
1291    /// Gets a typed reference to the parameter if it is of type `T`.
1292    pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
1293        self.0.downcast_ref()
1294    }
1295
1296    /// Returns `true` if the parameter type is `T`.
1297    pub fn is<T: Any>(&self) -> bool {
1298        self.0.is::<T>()
1299    }
1300}
1301impl fmt::Debug for CommandParam {
1302    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1303        f.debug_tuple("CommandParam").field(&self.0.type_id()).finish()
1304    }
1305}
1306zng_var::impl_from_and_into_var! {
1307    fn from(param: CommandParam) -> Option<CommandParam>;
1308}
1309
1310#[rustfmt::skip] // for zng fmt
1311unique_id_64! {
1312    /// Unique identifier of a command metadata state variable.
1313    ///
1314    /// This type is very similar to [`StateId`], but `T` is the value type of the metadata variable.
1315    ///
1316    /// [`StateId`]: zng_state_map::StateId
1317    pub struct CommandMetaVarId<T: (StateValue + VarValue)>: StateId;
1318}
1319zng_unique_id::impl_unique_id_bytemuck!(CommandMetaVarId<T: (StateValue + VarValue)>);
1320impl<T: StateValue + VarValue> CommandMetaVarId<T> {
1321    fn app(self) -> StateId<Var<T>> {
1322        let id = self.get();
1323        StateId::from_raw(id)
1324    }
1325
1326    fn scope(self) -> StateId<Var<T>> {
1327        let id = self.get();
1328        StateId::from_raw(id)
1329    }
1330}
1331
1332impl<T: StateValue + VarValue> fmt::Debug for CommandMetaVarId<T> {
1333    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1334        #[cfg(debug_assertions)]
1335        let t = pretty_type_name::pretty_type_name::<T>();
1336        #[cfg(not(debug_assertions))]
1337        let t = "$T";
1338
1339        if f.alternate() {
1340            writeln!(f, "CommandMetaVarId<{t} {{")?;
1341            writeln!(f, "   id: {},", self.get())?;
1342            writeln!(f, "   sequential: {}", self.sequential())?;
1343            writeln!(f, "}}")
1344        } else {
1345            write!(f, "CommandMetaVarId<{t}>({})", self.sequential())
1346        }
1347    }
1348}
1349
1350/// Access to metadata of a command.
1351///
1352/// The metadata storage can be accessed using the [`Command::with_meta`]
1353/// method, implementers must declare and extension trait that adds methods that return [`CommandMetaVar`] or
1354/// [`ReadOnlyCommandMetaVar`] that are stored in the [`CommandMeta`]. An initialization builder method for
1355/// each value also must be provided to integrate with the [`command!`] macro.
1356///
1357/// # Examples
1358///
1359/// The [`command!`] initialization transforms `foo: true,` to `command.init_foo(true);`, to support that, the command extension trait
1360/// must have a `foo` and `init_foo` methods.
1361///
1362/// ```
1363/// use zng_app::{event::*, static_id, var::*};
1364///
1365/// static_id! {
1366///     static ref COMMAND_FOO_ID: CommandMetaVarId<bool>;
1367///     static ref COMMAND_BAR_ID: CommandMetaVarId<bool>;
1368/// }
1369///
1370/// /// FooBar command values.
1371/// pub trait CommandFooBarExt {
1372///     /// Gets read/write *foo*.
1373///     fn foo(self) -> CommandMetaVar<bool>;
1374///
1375///     /// Gets read-only *bar*.
1376///     fn bar(self) -> ReadOnlyCommandMetaVar<bool>;
1377///
1378///     /// Gets a read-only var derived from other metadata.
1379///     fn foo_and_bar(self) -> Var<bool>;
1380///
1381///     /// Init *foo*.
1382///     fn init_foo(self, foo: bool) -> Self;
1383///
1384///     /// Init *bar*.
1385///     fn init_bar(self, bar: bool) -> Self;
1386/// }
1387///
1388/// impl CommandFooBarExt for Command {
1389///     fn foo(self) -> CommandMetaVar<bool> {
1390///         self.with_meta(|m| m.get_var_or_default(*COMMAND_FOO_ID))
1391///     }
1392///
1393///     fn bar(self) -> ReadOnlyCommandMetaVar<bool> {
1394///         self.with_meta(|m| m.get_var_or_insert(*COMMAND_BAR_ID, || true)).read_only()
1395///     }
1396///
1397///     fn foo_and_bar(self) -> Var<bool> {
1398///         merge_var!(self.foo(), self.bar(), |f, b| *f && *b)
1399///     }
1400///
1401///     fn init_foo(self, foo: bool) -> Self {
1402///         self.with_meta(|m| m.init_var(*COMMAND_FOO_ID, foo));
1403///         self
1404///     }
1405///
1406///     fn init_bar(self, bar: bool) -> Self {
1407///         self.with_meta(|m| m.init_var(*COMMAND_BAR_ID, bar));
1408///         self
1409///     }
1410/// }
1411/// ```
1412///
1413/// [`command!`]: macro@crate::event::command
1414pub struct CommandMeta<'a> {
1415    meta: StateMapMut<'a, CommandMetaState>,
1416    scope: Option<StateMapMut<'a, CommandMetaState>>,
1417}
1418impl CommandMeta<'_> {
1419    /// Clone a meta value identified by a [`StateId`].
1420    ///
1421    /// If the key is not set in the app, insert it using `init` to produce a value.
1422    ///
1423    /// [`StateId`]: zng_state_map::StateId
1424    pub fn get_or_insert<T, F>(&mut self, id: impl Into<StateId<T>>, init: F) -> T
1425    where
1426        T: StateValue + Clone,
1427        F: FnOnce() -> T,
1428    {
1429        let id = id.into();
1430        if let Some(scope) = &mut self.scope {
1431            if let Some(value) = scope.get(id) {
1432                value.clone()
1433            } else if let Some(value) = self.meta.get(id) {
1434                value.clone()
1435            } else {
1436                let value = init();
1437                let r = value.clone();
1438                scope.set(id, value);
1439                r
1440            }
1441        } else {
1442            self.meta.entry(id).or_insert_with(init).clone()
1443        }
1444    }
1445
1446    /// Clone a meta value identified by a [`StateId`].
1447    ///
1448    /// If the key is not set, insert the default value and returns a clone of it.
1449    ///
1450    /// [`StateId`]: zng_state_map::StateId
1451    pub fn get_or_default<T>(&mut self, id: impl Into<StateId<T>>) -> T
1452    where
1453        T: StateValue + Clone + Default,
1454    {
1455        self.get_or_insert(id, Default::default)
1456    }
1457
1458    /// Clone a meta value identified by a [`StateId`] if it is set.
1459    ///
1460    /// [`StateId`]: zng_state_map::StateId
1461    pub fn get<T>(&self, id: impl Into<StateId<T>>) -> Option<T>
1462    where
1463        T: StateValue + Clone,
1464    {
1465        let id = id.into();
1466        if let Some(scope) = &self.scope {
1467            scope.get(id).or_else(|| self.meta.get(id))
1468        } else {
1469            self.meta.get(id)
1470        }
1471        .cloned()
1472    }
1473
1474    /// Set the meta value associated with the [`StateId`].
1475    ///
1476    /// [`StateId`]: zng_state_map::StateId
1477    pub fn set<T>(&mut self, id: impl Into<StateId<T>>, value: impl Into<T>)
1478    where
1479        T: StateValue + Clone,
1480    {
1481        if let Some(scope) = &mut self.scope {
1482            scope.set(id, value);
1483        } else {
1484            self.meta.set(id, value);
1485        }
1486    }
1487
1488    /// Set the metadata value only if it is not set.
1489    ///
1490    /// This does not set the scoped override, only the command type metadata.
1491    pub fn init<T>(&mut self, id: impl Into<StateId<T>>, value: impl Into<T>)
1492    where
1493        T: StateValue + Clone,
1494    {
1495        self.meta.entry(id).or_insert(value);
1496    }
1497
1498    /// Clone a meta variable identified by a [`CommandMetaVarId`].
1499    ///
1500    /// The variable is read-write and is clone-on-write if the command is scoped.
1501    ///
1502    /// [`read_only`]: Var::read_only
1503    pub fn get_var_or_insert<T, F>(&mut self, id: impl Into<CommandMetaVarId<T>>, init: F) -> CommandMetaVar<T>
1504    where
1505        T: StateValue + VarValue,
1506        F: FnOnce() -> T,
1507    {
1508        let id = id.into();
1509        if let Some(scope) = &mut self.scope {
1510            let meta = &mut self.meta;
1511            scope
1512                .entry(id.scope())
1513                .or_insert_with(|| {
1514                    let var = meta.entry(id.app()).or_insert_with(|| var(init())).clone();
1515                    var.cow()
1516                })
1517                .clone()
1518        } else {
1519            self.meta.entry(id.app()).or_insert_with(|| var(init())).clone()
1520        }
1521    }
1522
1523    /// Clone a meta variable identified by a [`CommandMetaVarId`], if it is set.
1524    pub fn get_var<T>(&self, id: impl Into<CommandMetaVarId<T>>) -> Option<CommandMetaVar<T>>
1525    where
1526        T: StateValue + VarValue,
1527    {
1528        let id = id.into();
1529        if let Some(scope) = &self.scope {
1530            let meta = &self.meta;
1531            scope.get(id.scope()).cloned().or_else(|| meta.get(id.app()).cloned())
1532        } else {
1533            self.meta.get(id.app()).cloned()
1534        }
1535    }
1536
1537    /// Clone a meta variable identified by a [`CommandMetaVarId`].
1538    ///
1539    /// Inserts a variable with the default value if no variable is in the metadata.
1540    pub fn get_var_or_default<T>(&mut self, id: impl Into<CommandMetaVarId<T>>) -> CommandMetaVar<T>
1541    where
1542        T: StateValue + VarValue + Default,
1543    {
1544        self.get_var_or_insert(id, Default::default)
1545    }
1546
1547    /// Set the metadata variable if it was not set.
1548    ///
1549    /// This does not set the scoped override, only the command type metadata.
1550    pub fn init_var<T>(&mut self, id: impl Into<CommandMetaVarId<T>>, value: impl Into<T>)
1551    where
1552        T: StateValue + VarValue,
1553    {
1554        self.meta.entry(id.into().app()).or_insert_with(|| var(value.into()));
1555    }
1556}
1557
1558/// Read-write command metadata variable.
1559///
1560/// This is a simple [`var`] for *app* scope, or a [`Var::cow`] for scoped commands.
1561/// If you get this variable from an app scoped command it sets
1562/// the value for all scopes. If you get this variable using a scoped command,
1563/// it is a clone-on-write variable that overrides only the value for the scope.
1564pub type CommandMetaVar<T> = Var<T>;
1565
1566/// Read-only command metadata variable.
1567///
1568/// To convert a [`CommandMetaVar<T>`] into this var call [`read_only`].
1569///
1570/// [`read_only`]: Var::read_only
1571pub type ReadOnlyCommandMetaVar<T> = Var<T>;
1572
1573/// Adds the [`name`](CommandNameExt) command metadata.
1574pub trait CommandNameExt {
1575    /// Gets a read-write variable that is the display name for the command.
1576    fn name(self) -> CommandMetaVar<Txt>;
1577
1578    /// Sets the initial name if it is not set.
1579    fn init_name(self, name: impl Into<Txt>) -> Self;
1580
1581    /// Gets a read-only variable that formats the name and first shortcut formatted as `"name (first_shortcut)"`
1582    ///
1583    /// Note that if no shortcut is set for the command this method returns the same as [`name`](Self::name).
1584    ///
1585    /// Note that the shortcut keys are not localized, consider using `ShortcutText!` instead.
1586    fn name_with_shortcut(self) -> Var<Txt>
1587    where
1588        Self: crate::shortcut::CommandShortcutExt;
1589}
1590static_id! {
1591    static ref COMMAND_NAME_ID: CommandMetaVarId<Txt>;
1592}
1593impl CommandNameExt for Command {
1594    fn name(self) -> CommandMetaVar<Txt> {
1595        self.with_meta(|m| {
1596            m.get_var_or_insert(*COMMAND_NAME_ID, || {
1597                let name = self.static_name();
1598                let name = name.strip_suffix("_CMD").unwrap_or(name);
1599                let mut title = String::with_capacity(name.len());
1600                let mut lower = false;
1601                for c in name.chars() {
1602                    if c == '_' {
1603                        if !title.ends_with(' ') {
1604                            title.push(' ');
1605                        }
1606                        lower = false;
1607                    } else if lower {
1608                        for l in c.to_lowercase() {
1609                            title.push(l);
1610                        }
1611                    } else {
1612                        title.push(c);
1613                        lower = true;
1614                    }
1615                }
1616                Txt::from(title)
1617            })
1618        })
1619    }
1620
1621    fn init_name(self, name: impl Into<Txt>) -> Self {
1622        self.with_meta(|m| m.init_var(*COMMAND_NAME_ID, name.into()));
1623        self
1624    }
1625
1626    fn name_with_shortcut(self) -> Var<Txt>
1627    where
1628        Self: crate::shortcut::CommandShortcutExt,
1629    {
1630        crate::var::merge_var!(self.name(), self.shortcut(), |name, shortcut| {
1631            if shortcut.is_empty() {
1632                name.clone()
1633            } else {
1634                zng_txt::formatx!("{name} ({})", shortcut[0])
1635            }
1636        })
1637    }
1638}
1639
1640// support `init: |cmd| { }` in `command!`
1641impl Command {
1642    #[doc(hidden)]
1643    pub fn init_init(self, init: impl FnOnce(Self)) {
1644        init(self)
1645    }
1646    #[doc(hidden)]
1647    pub fn init(self) {}
1648}
1649
1650/// Adds the [`info`](CommandInfoExt) command metadata.
1651pub trait CommandInfoExt {
1652    /// Gets a read-write variable that is a short informational string about the command.
1653    fn info(self) -> CommandMetaVar<Txt>;
1654
1655    /// Sets the initial info if it is not set.
1656    fn init_info(self, info: impl Into<Txt>) -> Self;
1657}
1658static_id! {
1659    static ref COMMAND_INFO_ID: CommandMetaVarId<Txt>;
1660}
1661impl CommandInfoExt for Command {
1662    fn info(self) -> CommandMetaVar<Txt> {
1663        self.with_meta(|m| m.get_var_or_insert(*COMMAND_INFO_ID, Txt::default))
1664    }
1665
1666    fn init_info(self, info: impl Into<Txt>) -> Self {
1667        self.with_meta(|m| m.init_var(*COMMAND_INFO_ID, info.into()));
1668        self
1669    }
1670}
1671
1672enum CommandMetaState {}
1673
1674#[derive(Clone)]
1675enum MetaInit {
1676    Init(fn(Command)),
1677    /// Initing in a thread, lock is for other threads.
1678    Initing(Arc<(ThreadId, Mutex<()>)>),
1679    Inited,
1680}
1681
1682#[doc(hidden)]
1683pub struct CommandData {
1684    static_name: &'static str,
1685
1686    meta_init: MetaInit,
1687    meta: Mutex<OwnedStateMap<CommandMetaState>>,
1688
1689    handle_count: usize,
1690    enabled_count: usize,
1691    registered: bool,
1692
1693    has_handlers: Var<bool>,
1694    is_enabled: Var<bool>,
1695
1696    scopes: HashMap<CommandScope, ScopedValue>,
1697}
1698impl CommandData {
1699    pub fn new(meta_init: fn(Command), static_name: &'static str) -> Self {
1700        CommandData {
1701            static_name,
1702            meta_init: MetaInit::Init(meta_init),
1703            meta: Mutex::new(OwnedStateMap::new()),
1704
1705            handle_count: 0,
1706            enabled_count: 0,
1707            registered: false,
1708
1709            has_handlers: var(false),
1710            is_enabled: var(false),
1711
1712            scopes: HashMap::default(),
1713        }
1714    }
1715
1716    fn subscribe(&mut self, command: Command, enabled: bool, mut target: Option<WidgetId>) -> CommandHandle {
1717        match command.scope {
1718            CommandScope::App => {
1719                if !mem::replace(&mut self.registered, true) {
1720                    EVENTS.register_command(command);
1721                }
1722
1723                self.handle_count += 1;
1724                if enabled {
1725                    self.enabled_count += 1;
1726                }
1727
1728                if self.handle_count == 1 {
1729                    self.has_handlers.set(true);
1730                }
1731                if self.enabled_count == 1 {
1732                    self.is_enabled.set(true);
1733                }
1734
1735                tracing::trace!(
1736                    "subscribe to {:?}, handle_count: {:?}, enabled_count: {:?}",
1737                    CommandDbg::new(self.static_name, command.scope),
1738                    self.handle_count,
1739                    self.enabled_count
1740                );
1741            }
1742            scope => {
1743                let data = self.scopes.entry(scope).or_default();
1744
1745                if !mem::replace(&mut data.registered, true) {
1746                    EVENTS.register_command(command);
1747                }
1748
1749                data.handle_count += 1;
1750                if enabled {
1751                    data.enabled_count += 1;
1752                }
1753
1754                if data.handle_count == 1 {
1755                    data.has_handlers.set(true);
1756                }
1757                if data.enabled_count == 1 {
1758                    data.is_enabled.set(true);
1759                }
1760
1761                tracing::trace!(
1762                    "subscribe to {:?}, handle_count: {:?}, enabled_count: {:?}",
1763                    CommandDbg::new(self.static_name, command.scope),
1764                    data.handle_count,
1765                    data.enabled_count
1766                );
1767
1768                if let CommandScope::Widget(id) = scope {
1769                    target = Some(id);
1770                }
1771            }
1772        };
1773
1774        CommandHandle::new(
1775            command,
1776            target
1777                .map(|t| command.event.subscribe(UpdateOp::Update, t))
1778                .unwrap_or_else(VarHandle::dummy),
1779            enabled,
1780        )
1781    }
1782}
1783
1784struct ScopedValue {
1785    observer_count: usize,
1786    handle_count: usize,
1787    enabled_count: usize,
1788    is_enabled: Var<bool>,
1789    has_handlers: Var<bool>,
1790    meta: Mutex<OwnedStateMap<CommandMetaState>>,
1791    registered: bool,
1792}
1793impl Default for ScopedValue {
1794    fn default() -> Self {
1795        ScopedValue {
1796            observer_count: 0,
1797            is_enabled: var(false),
1798            has_handlers: var(false),
1799            handle_count: 0,
1800            enabled_count: 0,
1801            meta: Mutex::new(OwnedStateMap::default()),
1802            registered: false,
1803        }
1804    }
1805}
1806
1807#[cfg(test)]
1808mod tests {
1809    use crate::APP;
1810
1811    use super::*;
1812
1813    command! {
1814        static FOO_CMD;
1815    }
1816
1817    #[test]
1818    fn parameter_none() {
1819        let _ = CommandArgs::now(None, CommandScope::App, None, true);
1820    }
1821
1822    #[test]
1823    fn enabled_not_scoped() {
1824        let mut app = APP.minimal().run_headless(false);
1825
1826        assert!(!FOO_CMD.has_handlers().get());
1827
1828        let handle = FOO_CMD.subscribe(true);
1829        app.update(false).assert_wait();
1830        assert!(FOO_CMD.is_enabled().get());
1831
1832        handle.enabled().set(false);
1833        app.update(false).assert_wait();
1834
1835        assert!(FOO_CMD.has_handlers().get());
1836        assert!(!FOO_CMD.is_enabled().get());
1837
1838        handle.enabled().set(true);
1839        app.update(false).assert_wait();
1840
1841        assert!(FOO_CMD.is_enabled().get());
1842
1843        drop(handle);
1844        app.update(false).assert_wait();
1845
1846        assert!(!FOO_CMD.has_handlers().get());
1847        assert!(!FOO_CMD.is_enabled().get());
1848    }
1849
1850    #[test]
1851    fn enabled_scoped() {
1852        let mut app = APP.minimal().run_headless(false);
1853
1854        let cmd = FOO_CMD;
1855        let cmd_scoped = FOO_CMD.scoped(WindowId::named("enabled_scoped"));
1856        app.update(false).assert_wait();
1857        assert!(!cmd.has_handlers().get());
1858        assert!(!cmd_scoped.has_handlers().get());
1859
1860        let handle_scoped = cmd_scoped.subscribe(true);
1861        app.update(false).assert_wait();
1862
1863        assert!(!cmd.has_handlers().get());
1864        assert!(cmd_scoped.is_enabled().get());
1865
1866        handle_scoped.enabled().set(false);
1867        app.update(false).assert_wait();
1868
1869        assert!(!cmd.has_handlers().get());
1870        assert!(!cmd_scoped.is_enabled().get());
1871        assert!(cmd_scoped.has_handlers().get());
1872
1873        handle_scoped.enabled().set(true);
1874        app.update(false).assert_wait();
1875
1876        assert!(!cmd.has_handlers().get());
1877        assert!(cmd_scoped.is_enabled().get());
1878
1879        drop(handle_scoped);
1880        app.update(false).assert_wait();
1881
1882        assert!(!cmd.has_handlers().get());
1883        assert!(!cmd_scoped.has_handlers().get());
1884    }
1885
1886    #[test]
1887    fn has_handlers_not_scoped() {
1888        let mut app = APP.minimal().run_headless(false);
1889
1890        assert!(!FOO_CMD.has_handlers().get());
1891
1892        let handle = FOO_CMD.subscribe(false);
1893        app.update(false).assert_wait();
1894
1895        assert!(FOO_CMD.has_handlers().get());
1896
1897        drop(handle);
1898        app.update(false).assert_wait();
1899
1900        assert!(!FOO_CMD.has_handlers().get());
1901    }
1902
1903    #[test]
1904    fn has_handlers_scoped() {
1905        let mut app = APP.minimal().run_headless(false);
1906
1907        let cmd = FOO_CMD;
1908        let cmd_scoped = FOO_CMD.scoped(WindowId::named("has_handlers_scoped"));
1909        app.update(false).assert_wait();
1910
1911        assert!(!cmd.has_handlers().get());
1912        assert!(!cmd_scoped.has_handlers().get());
1913
1914        let handle = cmd_scoped.subscribe(false);
1915        app.update(false).assert_wait();
1916
1917        assert!(!cmd.has_handlers().get());
1918        assert!(cmd_scoped.has_handlers().get());
1919
1920        drop(handle);
1921        app.update(false).assert_wait();
1922
1923        assert!(!cmd.has_handlers().get());
1924        assert!(!cmd_scoped.has_handlers().get());
1925    }
1926
1927    // there are also integration tests in tests/command.rs
1928}