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#[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 $(#[doc = concat!("* `", stringify!($meta_ident), "`")])+
196 $vis static $COMMAND: $crate::event::Command = {
199 fn __meta_init__(cmd: $crate::event::Command) {
200 let __l10n_arg = $l10n_arg;
201 $crate::event::paste! {$(
202 cmd.[<init_ $meta_ident>]($meta_init);
203 $crate::event::init_meta_l10n(std::env!("CARGO_PKG_NAME"), std::env!("CARGO_PKG_VERSION"), &__l10n_arg, cmd, stringify!($meta_ident), &cmd.$meta_ident());
204 )*}
205 }
206 $crate::event::app_local! {
207 static EVENT: $crate::event::EventData = $crate::event::EventData::new::<$crate::event::CommandArgs>();
208 static DATA: $crate::event::CommandData = $crate::event::CommandData::new(__meta_init__, stringify!($COMMAND));
209 }
210 $crate::event::Command::new(&EVENT, &DATA)
211 };
212 };
213 (
214 $(#[$attr:meta])*
215 $vis:vis static $COMMAND:ident { $($meta_ident:ident : $meta_init:expr),* $(,)? };
216 ) => {
217 $(#[$attr])*
218 $(#[doc = concat!("* `", stringify!($meta_ident), "`")])+
224 $vis static $COMMAND: $crate::event::Command = {
225 fn __meta_init__(cmd: $crate::event::Command) {
226 $crate::event::paste! {$(
227 cmd.[<init_ $meta_ident>]($meta_init);
228 )*}
229 }
230 $crate::event::app_local! {
231 static EVENT: $crate::event::EventData = $crate::event::EventData::new::<$crate::event::CommandArgs>();
232 static DATA: $crate::event::CommandData = $crate::event::CommandData::new(__meta_init__, stringify!($COMMAND));
233 }
234 $crate::event::Command::new(&EVENT, &DATA)
235 };
236 };
237 (
238 $(#[$attr:meta])*
239 $vis:vis static $COMMAND:ident;
240 ) => {
241 $(#[$attr])*
242 $vis static $COMMAND: $crate::event::Command = {
243 fn __meta_init__(_: $crate::event::Command) {
244 }
245 $crate::event::app_local! {
246 static EVENT: $crate::event::EventData = $crate::event::EventData::new::<$crate::event::CommandArgs>();
247 static DATA: $crate::event::CommandData = $crate::event::CommandData::new(__meta_init__, stringify!($COMMAND));
248 }
249 $crate::event::Command::new(&EVENT, &DATA)
250 };
251 };
252}
253
254#[doc(hidden)]
255pub fn init_meta_l10n(
256 pkg_name: &'static str,
257 pkg_version: &'static str,
258 l10n_arg: &dyn Any,
259 cmd: Command,
260 meta_name: &'static str,
261 meta_value: &dyn Any,
262) {
263 if let Some(txt) = meta_value.downcast_ref::<CommandMetaVar<Txt>>() {
264 let mut l10n_file = "";
265
266 if let Some(&enabled) = l10n_arg.downcast_ref::<bool>() {
267 if !enabled {
268 return;
269 }
270 } else if let Some(&file) = l10n_arg.downcast_ref::<&'static str>() {
271 l10n_file = file;
272 } else {
273 tracing::error!("unknown l10n value in {:?}", cmd.event());
274 return;
275 }
276
277 EVENTS_L10N.init_meta_l10n([pkg_name, pkg_version, l10n_file], cmd, meta_name, txt.clone());
278 }
279}
280
281#[derive(Clone, Copy)]
320pub struct Command {
321 event: Event<CommandArgs>,
322 local: &'static AppLocal<CommandData>,
323 scope: CommandScope,
324}
325struct CommandDbg {
326 static_name: &'static str,
327 scope: CommandScope,
328 state: Option<[usize; 2]>,
329}
330impl CommandDbg {
331 fn new(static_name: &'static str, scope: CommandScope) -> Self {
332 Self {
333 static_name,
334 scope,
335 state: None,
336 }
337 }
338}
339impl fmt::Debug for CommandDbg {
340 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341 if f.alternate() {
342 let mut d = f.debug_struct("Command");
343 d.field("static_name", &self.static_name).field("scope", &self.scope);
344 if let Some([has, enabled]) = &self.state {
345 d.field("handle_count", has);
346 d.field("enabled_count", enabled);
347 }
348
349 d.finish_non_exhaustive()
350 } else {
351 write!(f, "{}", self.static_name)?;
352 match self.scope {
353 CommandScope::App => Ok(()),
354 CommandScope::Window(id) => write!(f, "({id})"),
355 CommandScope::Widget(id) => write!(f, "({id})"),
356 }
357 }
358 }
359}
360impl fmt::Debug for Command {
361 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362 let dbg = if let Some(d) = self.local.try_read() {
363 let mut dbg = CommandDbg::new(d.static_name, self.scope);
364 dbg.state = Some([d.handle_count, d.enabled_count]);
365 dbg
366 } else {
367 CommandDbg::new("<locked>", self.scope)
368 };
369 fmt::Debug::fmt(&dbg, f)
370 }
371}
372impl Command {
373 #[doc(hidden)]
374 pub const fn new(event_local: &'static AppLocal<EventData>, command_local: &'static AppLocal<CommandData>) -> Self {
375 Command {
376 event: Event::new(event_local),
377 local: command_local,
378 scope: CommandScope::App,
379 }
380 }
381
382 pub fn subscribe(&self, enabled: bool) -> CommandHandle {
389 self.local.write().subscribe(*self, enabled, None)
390 }
391
392 pub fn subscribe_wgt(&self, enabled: bool, target: WidgetId) -> CommandHandle {
400 self.local.write().subscribe(*self, enabled, Some(target))
401 }
402
403 pub fn event(&self) -> Event<CommandArgs> {
405 self.event
406 }
407
408 pub fn scope(&self) -> CommandScope {
410 self.scope
411 }
412
413 pub fn scoped(mut self, scope: impl Into<CommandScope>) -> Command {
415 self.scope = scope.into();
416 self
417 }
418
419 pub fn with_meta<R>(&self, visit: impl FnOnce(&mut CommandMeta) -> R) -> R {
428 fn init_meta(self_: &Command) -> parking_lot::MappedRwLockReadGuard<'static, CommandData> {
430 {
431 let mut write = self_.local.write();
432 match write.meta_init.clone() {
433 MetaInit::Init(init) => {
434 let lock = Arc::new((std::thread::current().id(), Mutex::new(())));
435 write.meta_init = MetaInit::Initing(lock.clone());
436 let _init_guard = lock.1.lock();
437 drop(write);
438 init(*self_);
439 self_.local.write().meta_init = MetaInit::Inited;
440 }
441 MetaInit::Initing(l) => {
442 drop(write);
443 if l.0 != std::thread::current().id() {
444 let _wait = l.1.lock();
445 }
446 }
447 MetaInit::Inited => {}
448 }
449 }
450
451 if !matches!(self_.scope, CommandScope::App) {
452 let mut write = self_.local.write();
453 write.scopes.entry(self_.scope).or_default();
454 }
455 self_.local.read()
456 }
457 let local_read = init_meta(self);
458 let mut meta_lock = local_read.meta.lock();
459
460 match self.scope {
461 CommandScope::App => visit(&mut CommandMeta {
462 meta: meta_lock.borrow_mut(),
463 scope: None,
464 }),
465 scope => {
466 let scope = local_read.scopes.get(&scope).unwrap();
467 visit(&mut CommandMeta {
468 meta: meta_lock.borrow_mut(),
469 scope: Some(scope.meta.lock().borrow_mut()),
470 })
471 }
472 }
473 }
474
475 pub fn has_handlers(&self) -> Var<bool> {
477 match self.scope {
478 CommandScope::App => self.local.read().has_handlers.read_only(),
479 scope => {
480 let mut write = self.local.write();
484 let entry = write.scopes.entry(scope).or_default();
485
486 entry.observer_count += 1;
488 let observer = var(entry.has_handlers.get());
489 entry.has_handlers.set_bind(&observer).perm();
490
491 let command = *self;
493 observer
494 .hook_drop(move || {
495 if !APP.is_started() {
496 return;
497 }
498 let mut write = command.local.write();
499 let mut entry = match write.scopes.entry(scope) {
500 hash_map::Entry::Occupied(e) => e,
501 hash_map::Entry::Vacant(_) => unreachable!(),
502 };
503 entry.get_mut().observer_count -= 1;
504 if entry.get().observer_count == 0 && entry.get().handle_count == 0 {
505 entry.remove();
506 EVENTS.unregister_command(command);
507 }
508 })
509 .perm();
510
511 observer.read_only()
512 }
513 }
514 }
515
516 pub fn is_enabled(&self) -> Var<bool> {
518 match self.scope {
519 CommandScope::App => self.local.read().is_enabled.read_only(),
520 scope => {
521 let mut write = self.local.write();
522 let entry = write.scopes.entry(scope).or_default();
523
524 entry.observer_count += 1;
525 let observer = var(entry.is_enabled.get());
526 entry.is_enabled.set_bind(&observer).perm();
527
528 let command = *self;
529 observer
530 .hook_drop(move || {
531 if !APP.is_started() {
532 return;
533 }
534
535 let mut write = command.local.write();
536 let mut entry = match write.scopes.entry(scope) {
537 hash_map::Entry::Occupied(e) => e,
538 hash_map::Entry::Vacant(_) => unreachable!(),
539 };
540 entry.get_mut().observer_count -= 1;
541 if entry.get().observer_count == 0 && entry.get().handle_count == 0 {
542 entry.remove();
543 EVENTS.unregister_command(command);
544 }
545 })
546 .perm();
547
548 observer.read_only()
549 }
550 }
551 }
552
553 pub fn visit_scopes<T>(&self, mut visitor: impl FnMut(Command) -> ControlFlow<T>) -> Option<T> {
557 let read = self.local.read();
558 for &scope in read.scopes.keys() {
559 match visitor(self.scoped(scope)) {
560 ControlFlow::Continue(_) => continue,
561 ControlFlow::Break(r) => return Some(r),
562 }
563 }
564 None
565 }
566
567 pub fn notify(&self) {
569 self.event.notify(CommandArgs::now(
570 None,
571 self.scope,
572 self.scope.search_target(),
573 self.is_enabled().get(),
574 ))
575 }
576
577 pub fn notify_descendants(&self, parent: &WidgetInfo) {
579 self.visit_scopes::<()>(|parse_cmd| {
580 if let CommandScope::Widget(id) = parse_cmd.scope()
581 && let Some(scope) = parent.tree().get(id)
582 && scope.is_descendant(parent)
583 {
584 parse_cmd.notify();
585 }
586 ControlFlow::Continue(())
587 });
588 }
589
590 pub fn notify_param(&self, param: impl Any + Send + Sync) {
592 self.event.notify(CommandArgs::now(
593 CommandParam::new(param),
594 self.scope,
595 self.scope.search_target(),
596 self.is_enabled().get(),
597 ));
598 }
599
600 pub fn notify_linked(&self, propagation: EventPropagationHandle, param: Option<CommandParam>) {
602 self.event.notify(CommandArgs::new(
603 crate::INSTANT.now(),
604 propagation,
605 param,
606 self.scope,
607 self.scope.search_target(),
608 self.is_enabled().get(),
609 ))
610 }
611
612 pub fn each_update(&self, direct_scope_only: bool, ignore_propagation: bool, mut handler: impl FnMut(&CommandArgs)) {
618 self.event.each_update(ignore_propagation, move |args| {
619 if args.scope_matches(direct_scope_only, self.scope) {
620 handler(args);
621 }
622 });
623 }
624
625 pub fn latest_update<O>(
629 &self,
630 direct_scope_only: bool,
631 ignore_propagation: bool,
632 handler: impl FnOnce(&CommandArgs) -> O,
633 ) -> Option<O> {
634 let mut r = None;
635 self.event.latest_update(ignore_propagation, |args| {
636 if args.scope_matches(direct_scope_only, self.scope) {
637 r = Some(handler(args));
638 }
639 });
640 r
641 }
642
643 pub fn has_update(&self, direct_scope_only: bool, ignore_propagation: bool) -> bool {
647 self.latest_update(direct_scope_only, ignore_propagation, |_| true).unwrap_or(false)
648 }
649
650 pub fn on_pre_event(
661 &self,
662 init_enabled: bool,
663 direct_scope_only: bool,
664 ignore_propagation: bool,
665 handler: Handler<CommandArgs>,
666 ) -> CommandHandle {
667 let (mut handle, handler) = self.event_handler(init_enabled, direct_scope_only, handler);
668 handle._handles.push(self.event().on_pre_event(ignore_propagation, handler));
669 handle
670 }
671
672 pub fn on_event(
683 &self,
684 init_enabled: bool,
685 direct_scope_only: bool,
686 ignore_propagation: bool,
687 handler: Handler<CommandArgs>,
688 ) -> CommandHandle {
689 let (mut handle, handler) = self.event_handler(init_enabled, direct_scope_only, handler);
690 handle._handles.push(self.event().on_event(ignore_propagation, handler));
691 handle
692 }
693
694 fn event_handler(
695 &self,
696 init_enabled: bool,
697 direct_scope_only: bool,
698 handler: Handler<CommandArgs>,
699 ) -> (CommandHandle, Handler<CommandArgs>) {
700 let handle = self.subscribe(init_enabled);
701 let local_enabled = handle.enabled().clone();
702 let handler = if direct_scope_only {
703 let scope = self.scope();
704 handler.filtered(move |a| a.scope == scope && local_enabled.get())
705 } else {
706 match self.scope() {
707 CommandScope::App => handler.filtered(move |_| local_enabled.get()),
708 CommandScope::Window(id) => {
709 handler.filtered(move |a| a.target.as_ref().map(|t| t.window_id() == id).unwrap_or(false) && local_enabled.get())
710 }
711 CommandScope::Widget(id) => {
712 handler.filtered(move |a| a.target.as_ref().map(|t| t.contains(id)).unwrap_or(false) && local_enabled.get())
713 }
714 }
715 };
716 (handle, handler)
717 }
718
719 pub fn on_pre_event_with_enabled(
724 &self,
725 init_enabled: bool,
726 direct_scope_only: bool,
727 ignore_propagation: bool,
728 handler: Handler<(CommandArgs, Var<bool>)>,
729 ) -> CommandHandle {
730 let (mut handle, handler) = self.event_handler_with_enabled(init_enabled, direct_scope_only, handler);
731 handle._handles.push(self.event().on_pre_event(ignore_propagation, handler));
732 handle
733 }
734
735 pub fn on_event_with_enabled(
740 &self,
741 init_enabled: bool,
742 direct_scope_only: bool,
743 ignore_propagation: bool,
744 handler: Handler<(CommandArgs, Var<bool>)>,
745 ) -> CommandHandle {
746 let (mut handle, handler) = self.event_handler_with_enabled(init_enabled, direct_scope_only, handler);
747 handle._handles.push(self.event().on_event(ignore_propagation, handler));
748 handle
749 }
750
751 fn event_handler_with_enabled(
752 &self,
753 init_enabled: bool,
754 direct_scope_only: bool,
755 mut handler: Handler<(CommandArgs, Var<bool>)>,
756 ) -> (CommandHandle, Handler<CommandArgs>) {
757 let handle = self.subscribe(init_enabled);
758 let local_enabled = handle.enabled().clone();
759
760 let r: Handler<CommandArgs>;
761 if direct_scope_only {
762 let scope = self.scope();
763 r = Box::new(move |a: &CommandArgs| {
764 if a.scope == scope {
765 handler(&(a.clone(), local_enabled.clone()))
766 } else {
767 HandlerResult::Done
768 }
769 });
770 } else {
771 match self.scope() {
772 CommandScope::App => r = Box::new(move |a: &CommandArgs| handler(&(a.clone(), local_enabled.clone()))),
773 CommandScope::Window(id) => {
774 r = Box::new(move |a: &CommandArgs| {
775 if a.target.as_ref().map(|t| t.window_id() == id).unwrap_or(false) {
776 handler(&(a.clone(), local_enabled.clone()))
777 } else {
778 HandlerResult::Done
779 }
780 })
781 }
782 CommandScope::Widget(id) => {
783 r = Box::new(move |a: &CommandArgs| {
784 if a.target.as_ref().map(|t| t.contains(id)).unwrap_or(false) {
785 handler(&(a.clone(), local_enabled.clone()))
786 } else {
787 HandlerResult::Done
788 }
789 })
790 }
791 }
792 };
793 (handle, r)
794 }
795
796 pub fn static_name(&self) -> &'static str {
798 self.local.read().static_name
799 }
800}
801impl ops::Deref for Command {
802 type Target = Event<CommandArgs>;
803
804 fn deref(&self) -> &Self::Target {
805 &self.event
806 }
807}
808impl PartialEq for Command {
809 fn eq(&self, other: &Self) -> bool {
810 self.event == other.event && self.scope == other.scope
811 }
812}
813impl Eq for Command {}
814impl std::hash::Hash for Command {
815 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
816 std::hash::Hash::hash(&self.event.as_any(), state);
817 std::hash::Hash::hash(&self.scope, state);
818 }
819}
820
821#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
825pub enum CommandScope {
826 App,
828 Window(WindowId),
833 Widget(WidgetId),
835}
836impl CommandScope {
837 pub fn search_target(self) -> Option<WidgetPath> {
839 match self {
840 CommandScope::App => None,
841 CommandScope::Window(id) => WINDOWS_APP.widget_tree(id).map(|t| t.root().path()),
842 CommandScope::Widget(id) => WINDOWS_APP.widget_info(id).map(|w| w.path()),
843 }
844 }
845}
846impl_from_and_into_var! {
847 fn from(id: WidgetId) -> CommandScope {
848 CommandScope::Widget(id)
849 }
850 fn from(id: WindowId) -> CommandScope {
851 CommandScope::Window(id)
852 }
853 fn from(widget_name: &'static str) -> CommandScope {
855 WidgetId::named(widget_name).into()
856 }
857 fn from(widget_name: Txt) -> CommandScope {
859 WidgetId::named(widget_name).into()
860 }
861}
862
863event_args! {
864 pub struct CommandArgs {
866 pub param: Option<CommandParam>,
868
869 pub scope: CommandScope,
871
872 pub target: Option<WidgetPath>,
878
879 pub enabled: bool,
887
888 ..
889
890 fn is_in_target(&self, id: WidgetId) -> bool {
892 match self.scope {
893 CommandScope::App => true,
894 _ => match &self.target {
895 Some(t) => t.contains(id),
896 None => false,
897 },
898 }
899 }
900
901 fn validate(&self) -> Result<(), Txt> {
903 if let Some(t) = &self.target {
904 match self.scope {
905 CommandScope::App => return Err("args for app scope cannot have a `target`".into()),
906 CommandScope::Window(id) => {
907 if id != t.window_id() || t.widgets_path().len() > 1 {
908 return Err("args for window scope must only `target` that window root widget".into());
909 }
910 }
911 CommandScope::Widget(id) => {
912 if id != t.widget_id() {
913 return Err("args for widget scope must only `target` that widget".into());
914 }
915 }
916 }
917 }
918 Ok(())
919 }
920 }
921}
922impl CommandArgs {
923 pub fn param<T: Any>(&self) -> Option<&T> {
925 self.param.as_ref().and_then(|p| p.downcast_ref::<T>())
926 }
927
928 pub fn enabled_param<T: Any>(&self) -> Option<&T> {
933 if self.enabled { self.param::<T>() } else { None }
934 }
935
936 pub fn disabled_param<T: Any>(&self) -> Option<&T> {
941 if !self.enabled { self.param::<T>() } else { None }
942 }
943
944 pub fn scope_matches(&self, direct_only: bool, scope: CommandScope) -> bool {
948 if direct_only {
949 self.scope == scope
950 } else {
951 match (scope, self.scope) {
952 (CommandScope::App, _) => true,
953 (CommandScope::Window(scope_id), CommandScope::Window(args_id)) => scope_id == args_id,
954 (CommandScope::Window(scope_id), CommandScope::Widget(args_id)) => {
955 if let Some(t) = &self.target {
957 t.window_id() == scope_id && t.contains(args_id)
958 } else if let Some(info) = WINDOWS_APP.widget_tree(scope_id) {
959 info.contains(args_id)
960 } else {
961 false
962 }
963 }
964 (CommandScope::Widget(scope_id), CommandScope::Widget(args_id)) => {
965 if let Some(t) = &self.target {
967 t.widgets_path().iter().position(|i| *i == scope_id).unwrap_or(usize::MAX)
968 < t.widgets_path().iter().position(|i| *i == args_id).unwrap_or(usize::MAX)
969 } else {
970 todo!()
971 }
972 }
973 _ => false,
974 }
975 }
976 }
977}
978
979pub struct CommandHandle {
986 command: Option<Command>,
987 local_enabled: Var<bool>,
988 _handles: VarHandles,
990}
991impl Clone for CommandHandle {
995 fn clone(&self) -> Self {
996 match self.command {
997 Some(c) => c.subscribe(self.local_enabled.get()),
998 None => Self::dummy(),
999 }
1000 }
1001}
1002impl CommandHandle {
1003 pub fn command(&self) -> Option<Command> {
1005 self.command
1006 }
1007
1008 pub fn enabled(&self) -> &Var<bool> {
1012 &self.local_enabled
1013 }
1014
1015 pub fn dummy() -> Self {
1017 CommandHandle {
1018 command: None,
1019 local_enabled: const_var(false),
1020 _handles: VarHandles::dummy(),
1021 }
1022 }
1023
1024 fn new(cmd: Command, event_handle: VarHandle, enabled: bool) -> Self {
1025 let mut r = Self {
1026 command: Some(cmd),
1027 local_enabled: var(enabled),
1028 _handles: VarHandles::dummy(),
1029 };
1030
1031 let mut last_applied = enabled;
1033 r._handles.push(r.local_enabled.hook(move |args| {
1034 let _hold = &event_handle;
1035 let enabled = *args.value();
1036 if last_applied != enabled {
1037 Self::update_enabled(cmd, enabled);
1038 last_applied = enabled;
1039 }
1040 true
1041 }));
1042
1043 r
1044 }
1045
1046 fn update_enabled(command: Command, enabled: bool) {
1047 let mut write = command.local.write();
1048 match command.scope {
1049 CommandScope::App => {
1050 if enabled {
1051 write.enabled_count += 1;
1052 if write.enabled_count == 1 {
1053 write.is_enabled.set(true);
1054 }
1055 tracing::trace!(
1056 "command handle {:?} enabled, count: {:?}",
1057 CommandDbg::new(write.static_name, command.scope),
1058 write.enabled_count
1059 );
1060 } else {
1061 write.enabled_count = match write.enabled_count.checked_sub(1) {
1062 Some(c) => c,
1063 None => {
1064 #[cfg(debug_assertions)]
1065 panic!("handle for {} was disabled when enabled_count was already zero", write.static_name);
1066 #[cfg(not(debug_assertions))]
1067 0
1068 }
1069 };
1070 if write.enabled_count == 0 {
1071 write.is_enabled.set(false);
1072 }
1073 tracing::trace!(
1074 "command handle {:?} disabled, count: {:?}",
1075 CommandDbg::new(write.static_name, command.scope),
1076 write.enabled_count
1077 );
1078 }
1079 }
1080 scope => {
1081 let write = &mut *write;
1082 if let Some(data) = write.scopes.get_mut(&scope) {
1083 if enabled {
1084 data.enabled_count += 1;
1085 if data.enabled_count == 1 {
1086 data.is_enabled.set(true);
1087 }
1088 tracing::trace!(
1089 "command handle {:?} enabled, count: {:?}",
1090 CommandDbg::new(write.static_name, command.scope),
1091 data.enabled_count
1092 );
1093 } else {
1094 data.enabled_count = match data.enabled_count.checked_sub(1) {
1095 Some(c) => c,
1096 None => {
1097 #[cfg(debug_assertions)]
1098 panic!(
1099 "handle for {:?} was disabled when enabled_count was already zero",
1100 CommandDbg::new(write.static_name, scope)
1101 );
1102 #[cfg(not(debug_assertions))]
1103 0
1104 }
1105 };
1106 if data.enabled_count == 0 {
1107 data.is_enabled.set(false);
1108 }
1109 tracing::trace!(
1110 "command handle {:?} enabled, count: {:?}",
1111 CommandDbg::new(write.static_name, command.scope),
1112 data.enabled_count
1113 );
1114 }
1115 }
1116 }
1117 }
1118 }
1119
1120 pub fn is_dummy(&self) -> bool {
1122 self.command.is_none()
1123 }
1124
1125 pub fn perm(mut self) {
1130 mem::replace(&mut self._handles, VarHandles::dummy()).perm();
1132 self.command = None;
1134 }
1135}
1136impl fmt::Debug for CommandHandle {
1137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1138 f.debug_struct("CommandHandle")
1139 .field("command", &self.command)
1140 .field("enabled", &self.local_enabled.get())
1141 .finish_non_exhaustive()
1142 }
1143}
1144impl Drop for CommandHandle {
1145 fn drop(&mut self) {
1146 if let Some(command) = self.command.take()
1147 && APP.is_started()
1148 {
1149 let mut write = command.local.write();
1150 match command.scope {
1151 CommandScope::App => {
1152 write.handle_count = match write.handle_count.checked_sub(1) {
1153 Some(c) => c,
1154 None => {
1155 #[cfg(debug_assertions)]
1156 panic!("handle for {} was dropped when handle_count was already zero", write.static_name);
1157 #[cfg(not(debug_assertions))]
1158 0
1159 }
1160 };
1161 if write.handle_count == 0 {
1162 write.has_handlers.set(false);
1163 }
1164
1165 if self.local_enabled.get() {
1166 write.enabled_count = match write.enabled_count.checked_sub(1) {
1167 Some(c) => c,
1168 None => {
1169 #[cfg(debug_assertions)]
1170 panic!(
1171 "handle for enabled {} was dropped when enabled_count was already zero",
1172 write.static_name
1173 );
1174 #[cfg(not(debug_assertions))]
1175 0
1176 }
1177 };
1178
1179 if write.enabled_count == 0 {
1180 write.is_enabled.set(false);
1181 }
1182 }
1183
1184 tracing::trace!(
1185 "unsubscribe to {:?}, handle_count: {:?}, enabled_count: {:?}",
1186 CommandDbg::new(write.static_name, command.scope),
1187 write.handle_count,
1188 write.enabled_count
1189 );
1190 }
1191 scope => {
1192 let write = &mut *write;
1193 if let hash_map::Entry::Occupied(mut entry) = write.scopes.entry(scope) {
1194 let data = entry.get_mut();
1195
1196 data.handle_count = match data.handle_count.checked_sub(1) {
1197 Some(c) => c,
1198 None => {
1199 #[cfg(debug_assertions)]
1200 panic!(
1201 "handle for {:?} was dropped when handle_count was already zero",
1202 CommandDbg::new(write.static_name, scope)
1203 );
1204 #[cfg(not(debug_assertions))]
1205 0
1206 }
1207 };
1208
1209 if self.local_enabled.get() {
1210 data.enabled_count = match data.enabled_count.checked_sub(1) {
1211 Some(c) => c,
1212 None => {
1213 #[cfg(debug_assertions)]
1214 panic!(
1215 "handle for enabled {:?} was dropped when enabled_count was already zero",
1216 CommandDbg::new(write.static_name, scope)
1217 );
1218 #[cfg(not(debug_assertions))]
1219 0
1220 }
1221 };
1222
1223 if data.enabled_count == 0 {
1224 data.is_enabled.set(false);
1225 }
1226 }
1227
1228 tracing::trace!(
1229 "unsubscribe to {:?}, handle_count: {:?}, enabled_count: {:?}",
1230 CommandDbg::new(write.static_name, command.scope),
1231 data.handle_count,
1232 data.enabled_count
1233 );
1234
1235 if data.handle_count == 0 {
1236 data.has_handlers.set(false);
1237 if data.observer_count == 0 {
1238 entry.remove();
1239 EVENTS.unregister_command(command);
1240 }
1241 }
1242 }
1243 }
1244 }
1245 }
1246 }
1247}
1248impl Default for CommandHandle {
1249 fn default() -> Self {
1250 Self::dummy()
1251 }
1252}
1253
1254#[derive(Clone)]
1256#[non_exhaustive]
1257pub struct CommandParam(pub Arc<dyn Any + Send + Sync>);
1258impl PartialEq for CommandParam {
1259 fn eq(&self, other: &Self) -> bool {
1260 Arc::ptr_eq(&self.0, &other.0)
1261 }
1262}
1263impl Eq for CommandParam {}
1264impl CommandParam {
1265 pub fn new(param: impl Any + Send + Sync + 'static) -> Self {
1269 let p: &dyn Any = ¶m;
1270 if let Some(p) = p.downcast_ref::<Self>() {
1271 p.clone()
1272 } else if let Some(p) = p.downcast_ref::<Arc<dyn Any + Send + Sync>>() {
1273 CommandParam(p.clone())
1274 } else {
1275 CommandParam(Arc::new(param))
1276 }
1277 }
1278
1279 pub fn type_id(&self) -> TypeId {
1281 self.0.type_id()
1282 }
1283
1284 pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
1286 self.0.downcast_ref()
1287 }
1288
1289 pub fn is<T: Any>(&self) -> bool {
1291 self.0.is::<T>()
1292 }
1293}
1294impl fmt::Debug for CommandParam {
1295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1296 f.debug_tuple("CommandParam").field(&self.0.type_id()).finish()
1297 }
1298}
1299zng_var::impl_from_and_into_var! {
1300 fn from(param: CommandParam) -> Option<CommandParam>;
1301}
1302
1303#[rustfmt::skip] unique_id_64! {
1305 pub struct CommandMetaVarId<T: (StateValue + VarValue)>: StateId;
1311}
1312zng_unique_id::impl_unique_id_bytemuck!(CommandMetaVarId<T: (StateValue + VarValue)>);
1313impl<T: StateValue + VarValue> CommandMetaVarId<T> {
1314 fn app(self) -> StateId<Var<T>> {
1315 let id = self.get();
1316 StateId::from_raw(id)
1317 }
1318
1319 fn scope(self) -> StateId<Var<T>> {
1320 let id = self.get();
1321 StateId::from_raw(id)
1322 }
1323}
1324
1325impl<T: StateValue + VarValue> fmt::Debug for CommandMetaVarId<T> {
1326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1327 #[cfg(debug_assertions)]
1328 let t = pretty_type_name::pretty_type_name::<T>();
1329 #[cfg(not(debug_assertions))]
1330 let t = "$T";
1331
1332 if f.alternate() {
1333 writeln!(f, "CommandMetaVarId<{t} {{")?;
1334 writeln!(f, " id: {},", self.get())?;
1335 writeln!(f, " sequential: {}", self.sequential())?;
1336 writeln!(f, "}}")
1337 } else {
1338 write!(f, "CommandMetaVarId<{t}>({})", self.sequential())
1339 }
1340 }
1341}
1342
1343pub struct CommandMeta<'a> {
1408 meta: StateMapMut<'a, CommandMetaState>,
1409 scope: Option<StateMapMut<'a, CommandMetaState>>,
1410}
1411impl CommandMeta<'_> {
1412 pub fn get_or_insert<T, F>(&mut self, id: impl Into<StateId<T>>, init: F) -> T
1418 where
1419 T: StateValue + Clone,
1420 F: FnOnce() -> T,
1421 {
1422 let id = id.into();
1423 if let Some(scope) = &mut self.scope {
1424 if let Some(value) = scope.get(id) {
1425 value.clone()
1426 } else if let Some(value) = self.meta.get(id) {
1427 value.clone()
1428 } else {
1429 let value = init();
1430 let r = value.clone();
1431 scope.set(id, value);
1432 r
1433 }
1434 } else {
1435 self.meta.entry(id).or_insert_with(init).clone()
1436 }
1437 }
1438
1439 pub fn get_or_default<T>(&mut self, id: impl Into<StateId<T>>) -> T
1445 where
1446 T: StateValue + Clone + Default,
1447 {
1448 self.get_or_insert(id, Default::default)
1449 }
1450
1451 pub fn get<T>(&self, id: impl Into<StateId<T>>) -> Option<T>
1455 where
1456 T: StateValue + Clone,
1457 {
1458 let id = id.into();
1459 if let Some(scope) = &self.scope {
1460 scope.get(id).or_else(|| self.meta.get(id))
1461 } else {
1462 self.meta.get(id)
1463 }
1464 .cloned()
1465 }
1466
1467 pub fn set<T>(&mut self, id: impl Into<StateId<T>>, value: impl Into<T>)
1471 where
1472 T: StateValue + Clone,
1473 {
1474 if let Some(scope) = &mut self.scope {
1475 scope.set(id, value);
1476 } else {
1477 self.meta.set(id, value);
1478 }
1479 }
1480
1481 pub fn init<T>(&mut self, id: impl Into<StateId<T>>, value: impl Into<T>)
1485 where
1486 T: StateValue + Clone,
1487 {
1488 self.meta.entry(id).or_insert(value);
1489 }
1490
1491 pub fn get_var_or_insert<T, F>(&mut self, id: impl Into<CommandMetaVarId<T>>, init: F) -> CommandMetaVar<T>
1497 where
1498 T: StateValue + VarValue,
1499 F: FnOnce() -> T,
1500 {
1501 let id = id.into();
1502 if let Some(scope) = &mut self.scope {
1503 let meta = &mut self.meta;
1504 scope
1505 .entry(id.scope())
1506 .or_insert_with(|| {
1507 let var = meta.entry(id.app()).or_insert_with(|| var(init())).clone();
1508 var.cow()
1509 })
1510 .clone()
1511 } else {
1512 self.meta.entry(id.app()).or_insert_with(|| var(init())).clone()
1513 }
1514 }
1515
1516 pub fn get_var<T>(&self, id: impl Into<CommandMetaVarId<T>>) -> Option<CommandMetaVar<T>>
1518 where
1519 T: StateValue + VarValue,
1520 {
1521 let id = id.into();
1522 if let Some(scope) = &self.scope {
1523 let meta = &self.meta;
1524 scope.get(id.scope()).cloned().or_else(|| meta.get(id.app()).cloned())
1525 } else {
1526 self.meta.get(id.app()).cloned()
1527 }
1528 }
1529
1530 pub fn get_var_or_default<T>(&mut self, id: impl Into<CommandMetaVarId<T>>) -> CommandMetaVar<T>
1534 where
1535 T: StateValue + VarValue + Default,
1536 {
1537 self.get_var_or_insert(id, Default::default)
1538 }
1539
1540 pub fn init_var<T>(&mut self, id: impl Into<CommandMetaVarId<T>>, value: impl Into<T>)
1544 where
1545 T: StateValue + VarValue,
1546 {
1547 self.meta.entry(id.into().app()).or_insert_with(|| var(value.into()));
1548 }
1549}
1550
1551pub type CommandMetaVar<T> = Var<T>;
1558
1559pub type ReadOnlyCommandMetaVar<T> = Var<T>;
1565
1566pub trait CommandNameExt {
1568 fn name(self) -> CommandMetaVar<Txt>;
1570
1571 fn init_name(self, name: impl Into<Txt>) -> Self;
1573
1574 fn name_with_shortcut(self) -> Var<Txt>
1580 where
1581 Self: crate::shortcut::CommandShortcutExt;
1582}
1583static_id! {
1584 static ref COMMAND_NAME_ID: CommandMetaVarId<Txt>;
1585}
1586impl CommandNameExt for Command {
1587 fn name(self) -> CommandMetaVar<Txt> {
1588 self.with_meta(|m| {
1589 m.get_var_or_insert(*COMMAND_NAME_ID, || {
1590 let name = self.static_name();
1591 let name = name.strip_suffix("_CMD").unwrap_or(name);
1592 let mut title = String::with_capacity(name.len());
1593 let mut lower = false;
1594 for c in name.chars() {
1595 if c == '_' {
1596 if !title.ends_with(' ') {
1597 title.push(' ');
1598 }
1599 lower = false;
1600 } else if lower {
1601 for l in c.to_lowercase() {
1602 title.push(l);
1603 }
1604 } else {
1605 title.push(c);
1606 lower = true;
1607 }
1608 }
1609 Txt::from(title)
1610 })
1611 })
1612 }
1613
1614 fn init_name(self, name: impl Into<Txt>) -> Self {
1615 self.with_meta(|m| m.init_var(*COMMAND_NAME_ID, name.into()));
1616 self
1617 }
1618
1619 fn name_with_shortcut(self) -> Var<Txt>
1620 where
1621 Self: crate::shortcut::CommandShortcutExt,
1622 {
1623 crate::var::merge_var!(self.name(), self.shortcut(), |name, shortcut| {
1624 if shortcut.is_empty() {
1625 name.clone()
1626 } else {
1627 zng_txt::formatx!("{name} ({})", shortcut[0])
1628 }
1629 })
1630 }
1631}
1632
1633impl Command {
1635 #[doc(hidden)]
1636 pub fn init_init(self, init: impl FnOnce(Self)) {
1637 init(self)
1638 }
1639 #[doc(hidden)]
1640 pub fn init(self) {}
1641}
1642
1643pub trait CommandInfoExt {
1645 fn info(self) -> CommandMetaVar<Txt>;
1647
1648 fn init_info(self, info: impl Into<Txt>) -> Self;
1650}
1651static_id! {
1652 static ref COMMAND_INFO_ID: CommandMetaVarId<Txt>;
1653}
1654impl CommandInfoExt for Command {
1655 fn info(self) -> CommandMetaVar<Txt> {
1656 self.with_meta(|m| m.get_var_or_insert(*COMMAND_INFO_ID, Txt::default))
1657 }
1658
1659 fn init_info(self, info: impl Into<Txt>) -> Self {
1660 self.with_meta(|m| m.init_var(*COMMAND_INFO_ID, info.into()));
1661 self
1662 }
1663}
1664
1665enum CommandMetaState {}
1666
1667#[derive(Clone)]
1668enum MetaInit {
1669 Init(fn(Command)),
1670 Initing(Arc<(ThreadId, Mutex<()>)>),
1672 Inited,
1673}
1674
1675#[doc(hidden)]
1676pub struct CommandData {
1677 static_name: &'static str,
1678
1679 meta_init: MetaInit,
1680 meta: Mutex<OwnedStateMap<CommandMetaState>>,
1681
1682 handle_count: usize,
1683 enabled_count: usize,
1684 registered: bool,
1685
1686 has_handlers: Var<bool>,
1687 is_enabled: Var<bool>,
1688
1689 scopes: HashMap<CommandScope, ScopedValue>,
1690}
1691impl CommandData {
1692 pub fn new(meta_init: fn(Command), static_name: &'static str) -> Self {
1693 CommandData {
1694 static_name,
1695 meta_init: MetaInit::Init(meta_init),
1696 meta: Mutex::new(OwnedStateMap::new()),
1697
1698 handle_count: 0,
1699 enabled_count: 0,
1700 registered: false,
1701
1702 has_handlers: var(false),
1703 is_enabled: var(false),
1704
1705 scopes: HashMap::default(),
1706 }
1707 }
1708
1709 fn subscribe(&mut self, command: Command, enabled: bool, mut target: Option<WidgetId>) -> CommandHandle {
1710 match command.scope {
1711 CommandScope::App => {
1712 if !mem::replace(&mut self.registered, true) {
1713 EVENTS.register_command(command);
1714 }
1715
1716 self.handle_count += 1;
1717 if enabled {
1718 self.enabled_count += 1;
1719 }
1720
1721 if self.handle_count == 1 {
1722 self.has_handlers.set(true);
1723 }
1724 if self.enabled_count == 1 {
1725 self.is_enabled.set(true);
1726 }
1727
1728 tracing::trace!(
1729 "subscribe to {:?}, handle_count: {:?}, enabled_count: {:?}",
1730 CommandDbg::new(self.static_name, command.scope),
1731 self.handle_count,
1732 self.enabled_count
1733 );
1734 }
1735 scope => {
1736 let data = self.scopes.entry(scope).or_default();
1737
1738 if !mem::replace(&mut data.registered, true) {
1739 EVENTS.register_command(command);
1740 }
1741
1742 data.handle_count += 1;
1743 if enabled {
1744 data.enabled_count += 1;
1745 }
1746
1747 if data.handle_count == 1 {
1748 data.has_handlers.set(true);
1749 }
1750 if data.enabled_count == 1 {
1751 data.is_enabled.set(true);
1752 }
1753
1754 tracing::trace!(
1755 "subscribe to {:?}, handle_count: {:?}, enabled_count: {:?}",
1756 CommandDbg::new(self.static_name, command.scope),
1757 data.handle_count,
1758 data.enabled_count
1759 );
1760
1761 if let CommandScope::Widget(id) = scope {
1762 target = Some(id);
1763 }
1764 }
1765 };
1766
1767 CommandHandle::new(
1768 command,
1769 target
1770 .map(|t| command.event.subscribe(UpdateOp::Update, t))
1771 .unwrap_or_else(VarHandle::dummy),
1772 enabled,
1773 )
1774 }
1775}
1776
1777struct ScopedValue {
1778 observer_count: usize,
1779 handle_count: usize,
1780 enabled_count: usize,
1781 is_enabled: Var<bool>,
1782 has_handlers: Var<bool>,
1783 meta: Mutex<OwnedStateMap<CommandMetaState>>,
1784 registered: bool,
1785}
1786impl Default for ScopedValue {
1787 fn default() -> Self {
1788 ScopedValue {
1789 observer_count: 0,
1790 is_enabled: var(false),
1791 has_handlers: var(false),
1792 handle_count: 0,
1793 enabled_count: 0,
1794 meta: Mutex::new(OwnedStateMap::default()),
1795 registered: false,
1796 }
1797 }
1798}
1799
1800#[cfg(test)]
1801mod tests {
1802 use crate::APP;
1803
1804 use super::*;
1805
1806 command! {
1807 static FOO_CMD;
1808 }
1809
1810 #[test]
1811 fn parameter_none() {
1812 let _ = CommandArgs::now(None, CommandScope::App, None, true);
1813 }
1814
1815 #[test]
1816 fn enabled_not_scoped() {
1817 let mut app = APP.minimal().run_headless(false);
1818
1819 assert!(!FOO_CMD.has_handlers().get());
1820
1821 let handle = FOO_CMD.subscribe(true);
1822 app.update(false).assert_wait();
1823 assert!(FOO_CMD.is_enabled().get());
1824
1825 handle.enabled().set(false);
1826 app.update(false).assert_wait();
1827
1828 assert!(FOO_CMD.has_handlers().get());
1829 assert!(!FOO_CMD.is_enabled().get());
1830
1831 handle.enabled().set(true);
1832 app.update(false).assert_wait();
1833
1834 assert!(FOO_CMD.is_enabled().get());
1835
1836 drop(handle);
1837 app.update(false).assert_wait();
1838
1839 assert!(!FOO_CMD.has_handlers().get());
1840 assert!(!FOO_CMD.is_enabled().get());
1841 }
1842
1843 #[test]
1844 fn enabled_scoped() {
1845 let mut app = APP.minimal().run_headless(false);
1846
1847 let cmd = FOO_CMD;
1848 let cmd_scoped = FOO_CMD.scoped(WindowId::named("enabled_scoped"));
1849 app.update(false).assert_wait();
1850 assert!(!cmd.has_handlers().get());
1851 assert!(!cmd_scoped.has_handlers().get());
1852
1853 let handle_scoped = cmd_scoped.subscribe(true);
1854 app.update(false).assert_wait();
1855
1856 assert!(!cmd.has_handlers().get());
1857 assert!(cmd_scoped.is_enabled().get());
1858
1859 handle_scoped.enabled().set(false);
1860 app.update(false).assert_wait();
1861
1862 assert!(!cmd.has_handlers().get());
1863 assert!(!cmd_scoped.is_enabled().get());
1864 assert!(cmd_scoped.has_handlers().get());
1865
1866 handle_scoped.enabled().set(true);
1867 app.update(false).assert_wait();
1868
1869 assert!(!cmd.has_handlers().get());
1870 assert!(cmd_scoped.is_enabled().get());
1871
1872 drop(handle_scoped);
1873 app.update(false).assert_wait();
1874
1875 assert!(!cmd.has_handlers().get());
1876 assert!(!cmd_scoped.has_handlers().get());
1877 }
1878
1879 #[test]
1880 fn has_handlers_not_scoped() {
1881 let mut app = APP.minimal().run_headless(false);
1882
1883 assert!(!FOO_CMD.has_handlers().get());
1884
1885 let handle = FOO_CMD.subscribe(false);
1886 app.update(false).assert_wait();
1887
1888 assert!(FOO_CMD.has_handlers().get());
1889
1890 drop(handle);
1891 app.update(false).assert_wait();
1892
1893 assert!(!FOO_CMD.has_handlers().get());
1894 }
1895
1896 #[test]
1897 fn has_handlers_scoped() {
1898 let mut app = APP.minimal().run_headless(false);
1899
1900 let cmd = FOO_CMD;
1901 let cmd_scoped = FOO_CMD.scoped(WindowId::named("has_handlers_scoped"));
1902 app.update(false).assert_wait();
1903
1904 assert!(!cmd.has_handlers().get());
1905 assert!(!cmd_scoped.has_handlers().get());
1906
1907 let handle = cmd_scoped.subscribe(false);
1908 app.update(false).assert_wait();
1909
1910 assert!(!cmd.has_handlers().get());
1911 assert!(cmd_scoped.has_handlers().get());
1912
1913 drop(handle);
1914 app.update(false).assert_wait();
1915
1916 assert!(!cmd.has_handlers().get());
1917 assert!(!cmd_scoped.has_handlers().get());
1918 }
1919
1920 }