1use std::{
2 any::TypeId,
3 collections::{HashMap, hash_map},
4 mem, ops,
5 thread::ThreadId,
6};
7
8use crate::{APP, shortcut::CommandShortcutExt, update::UpdatesTrace, widget::info::WidgetInfo, window::WindowId};
9
10use super::*;
11
12#[macro_export]
143macro_rules! command {
144 ($(
145 $(#[$attr:meta])*
146 $vis:vis static $COMMAND:ident $(=> |$cmd:ident|$custom_meta_init:expr ;)? $(= { $($meta_ident:ident $(!)? : $meta_init:expr),* $(,)? };)? $(;)?
147 )+) => {
148 $(
149 $crate::__command! {
150 $(#[$attr])*
151 $vis static $COMMAND $(=> |$cmd|$custom_meta_init)? $(= {
152 $($meta_ident: $meta_init,)+
153 })? ;
154 }
155 )+
156 }
157}
158#[doc(inline)]
159pub use command;
160
161use zng_app_context::AppId;
162use zng_state_map::{OwnedStateMap, StateId, StateMapMut, StateValue};
163use zng_txt::Txt;
164use zng_unique_id::{static_id, unique_id_64};
165use zng_var::{Var, VarValue, impl_from_and_into_var, var};
166
167#[doc(hidden)]
168pub use zng_app_context::app_local;
169
170#[doc(hidden)]
171pub use pastey::paste;
172
173#[doc(hidden)]
174#[macro_export]
175macro_rules! __command {
176 (
177 $(#[$attr:meta])*
178 $vis:vis static $COMMAND:ident => |$cmd:ident| $meta_init:expr;
179 ) => {
180 $(#[$attr])*
181 $vis static $COMMAND: $crate::event::Command = {
182 fn __meta_init__($cmd: $crate::event::Command) {
183 $meta_init
184 }
185 $crate::event::app_local! {
186 static EVENT: $crate::event::EventData = const { $crate::event::EventData::new(std::stringify!($COMMAND)) };
187 static DATA: $crate::event::CommandData = $crate::event::CommandData::new(__meta_init__);
188 }
189 $crate::event::Command::new(&EVENT, &DATA)
190 };
191 };
192 (
193 $(#[$attr:meta])*
194 $vis:vis static $COMMAND:ident = { l10n: $l10n_arg:expr, $($meta_ident:ident : $meta_init:expr),* $(,)? };
195 ) => {
196 $crate::event::paste! {
197 $crate::__command! {
198 $(#[$attr])*
199 $(#[doc = concat!("<tr> <td>", stringify!($meta_ident), "</td> <td>", stringify!($meta_init), "</td> </tr>")])+
208 $vis static $COMMAND => |cmd| {
214 let __l10n_arg = $l10n_arg;
215 $(
216 cmd.[<init_ $meta_ident>]($meta_init);
217 $crate::event::init_meta_l10n(std::env!("CARGO_PKG_NAME"), std::env!("CARGO_PKG_VERSION"), &__l10n_arg, cmd, stringify!($meta_ident), &cmd.$meta_ident());
218 )*
219 };
220 }
221 }
222 };
223 (
224 $(#[$attr:meta])*
225 $vis:vis static $COMMAND:ident = { $($meta_ident:ident : $meta_init:expr),* $(,)? };
226 ) => {
227 $crate::event::paste! {
228 $crate::__command! {
229 $(#[$attr])*
230 $(#[doc = concat!("<tr> <td>", stringify!($meta_ident), "</td> <td>", stringify!($meta_init), "</td> </tr>")])+
239 $vis static $COMMAND => |cmd| {
243 $(
244 cmd.[<init_ $meta_ident>]($meta_init);
245 )*
246 };
247 }
248 }
249 };
250 (
251 $(#[$attr:meta])*
252 $vis:vis static $COMMAND:ident;
253 ) => {
254 $crate::__command! {
255 $(#[$attr])*
256 $vis static $COMMAND => |_cmd|{};
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().as_any().name());
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#[derive(Clone, Copy)]
327pub struct Command {
328 event: Event<CommandArgs>,
329 local: &'static AppLocal<CommandData>,
330 scope: CommandScope,
331}
332impl fmt::Debug for Command {
333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334 if f.alternate() {
335 f.debug_struct("Command")
336 .field("event", &self.event)
337 .field("scope", &self.scope)
338 .finish_non_exhaustive()
339 } else {
340 write!(f, "{}", self.event.name())?;
341 match self.scope {
342 CommandScope::App => Ok(()),
343 CommandScope::Window(id) => write!(f, "({id})"),
344 CommandScope::Widget(id) => write!(f, "({id})"),
345 }
346 }
347 }
348}
349impl Command {
350 #[doc(hidden)]
351 pub const fn new(event_local: &'static AppLocal<EventData>, command_local: &'static AppLocal<CommandData>) -> Self {
352 Command {
353 event: Event::new(event_local),
354 local: command_local,
355 scope: CommandScope::App,
356 }
357 }
358
359 pub fn subscribe(&self, enabled: bool) -> CommandHandle {
366 let mut evs = EVENTS_SV.write();
367 self.local.write().subscribe(&mut evs, *self, enabled, None)
368 }
369
370 pub fn subscribe_wgt(&self, enabled: bool, target: WidgetId) -> CommandHandle {
378 let mut evs = EVENTS_SV.write();
379 self.local.write().subscribe(&mut evs, *self, enabled, Some(target))
380 }
381
382 pub fn event(&self) -> Event<CommandArgs> {
384 self.event
385 }
386
387 pub fn scope(&self) -> CommandScope {
389 self.scope
390 }
391
392 pub fn scoped(mut self, scope: impl Into<CommandScope>) -> Command {
394 self.scope = scope.into();
395 self
396 }
397
398 pub fn with_meta<R>(&self, visit: impl FnOnce(&mut CommandMeta) -> R) -> R {
407 fn init_meta(self_: &Command) -> parking_lot::MappedRwLockReadGuard<'static, CommandData> {
409 {
410 let mut write = self_.local.write();
411 match write.meta_init.clone() {
412 MetaInit::Init(init) => {
413 let lock = Arc::new((std::thread::current().id(), Mutex::new(())));
414 write.meta_init = MetaInit::Initing(lock.clone());
415 let _init_guard = lock.1.lock();
416 drop(write);
417 init(*self_);
418 self_.local.write().meta_init = MetaInit::Inited;
419 }
420 MetaInit::Initing(l) => {
421 drop(write);
422 if l.0 != std::thread::current().id() {
423 let _wait = l.1.lock();
424 }
425 }
426 MetaInit::Inited => {}
427 }
428 }
429
430 if !matches!(self_.scope, CommandScope::App) {
431 let mut write = self_.local.write();
432 write.scopes.entry(self_.scope).or_default();
433 }
434 self_.local.read()
435 }
436 let local_read = init_meta(self);
437 let mut meta_lock = local_read.meta.lock();
438
439 match self.scope {
440 CommandScope::App => visit(&mut CommandMeta {
441 meta: meta_lock.borrow_mut(),
442 scope: None,
443 }),
444 scope => {
445 let scope = local_read.scopes.get(&scope).unwrap();
446 visit(&mut CommandMeta {
447 meta: meta_lock.borrow_mut(),
448 scope: Some(scope.meta.lock().borrow_mut()),
449 })
450 }
451 }
452 }
453
454 pub fn has(&self, update: &EventUpdate) -> bool {
456 self.on(update).is_some()
457 }
458
459 pub fn on<'a>(&self, update: &'a EventUpdate) -> Option<&'a CommandArgs> {
461 self.event.on(update).filter(|a| a.scope == self.scope)
462 }
463
464 pub fn on_unhandled<'a>(&self, update: &'a EventUpdate) -> Option<&'a CommandArgs> {
466 self.event
467 .on(update)
468 .filter(|a| a.scope == self.scope && !a.propagation().is_stopped())
469 }
470
471 pub fn handle<R>(&self, update: &EventUpdate, handler: impl FnOnce(&CommandArgs) -> R) -> Option<R> {
474 if let Some(args) = self.on(update) {
475 args.handle(handler)
476 } else {
477 None
478 }
479 }
480
481 pub fn has_handlers(&self) -> Var<bool> {
483 let mut write = self.local.write();
484 match self.scope {
485 CommandScope::App => write.has_handlers.read_only(),
486 scope => write.scopes.entry(scope).or_default().has_handlers.read_only(),
487 }
488 }
489
490 pub fn is_enabled(&self) -> Var<bool> {
492 let mut write = self.local.write();
493 match self.scope {
494 CommandScope::App => write.is_enabled.read_only(),
495 scope => write.scopes.entry(scope).or_default().is_enabled.read_only(),
496 }
497 }
498
499 pub fn has_handlers_value(&self) -> bool {
501 let read = self.local.read();
502 match self.scope {
503 CommandScope::App => read.handle_count > 0,
504 scope => read.scopes.get(&scope).map(|l| l.handle_count > 0).unwrap_or(false),
505 }
506 }
507
508 pub fn is_enabled_value(&self) -> bool {
510 let read = self.local.read();
511 match self.scope {
512 CommandScope::App => read.enabled_count > 0,
513 scope => read.scopes.get(&scope).map(|l| l.enabled_count > 0).unwrap_or(false),
514 }
515 }
516
517 pub fn visit_scopes<T>(&self, mut visitor: impl FnMut(Command) -> ControlFlow<T>) -> Option<T> {
521 let read = self.local.read();
522 for &scope in read.scopes.keys() {
523 match visitor(self.scoped(scope)) {
524 ControlFlow::Continue(_) => continue,
525 ControlFlow::Break(r) => return Some(r),
526 }
527 }
528 None
529 }
530
531 pub fn notify(&self) {
533 self.event.notify(CommandArgs::now(None, self.scope, self.is_enabled_value()))
534 }
535
536 pub fn notify_descendants(&self, parent: &WidgetInfo) {
538 self.visit_scopes::<()>(|parse_cmd| {
539 if let CommandScope::Widget(id) = parse_cmd.scope()
540 && let Some(scope) = parent.tree().get(id)
541 && scope.is_descendant(parent)
542 {
543 parse_cmd.notify();
544 }
545 ControlFlow::Continue(())
546 });
547 }
548
549 pub fn notify_param(&self, param: impl Any + Send + Sync) {
551 self.event
552 .notify(CommandArgs::now(CommandParam::new(param), self.scope, self.is_enabled_value()));
553 }
554
555 pub fn notify_linked(&self, propagation: EventPropagationHandle, param: Option<CommandParam>) {
557 self.event.notify(CommandArgs::new(
558 crate::INSTANT.now(),
559 propagation,
560 param,
561 self.scope,
562 self.is_enabled_value(),
563 ))
564 }
565
566 pub fn new_update(&self) -> EventUpdate {
568 self.event.new_update(CommandArgs::now(None, self.scope, self.is_enabled_value()))
569 }
570
571 pub fn new_update_param(&self, param: impl Any + Send + Sync) -> EventUpdate {
573 self.event
574 .new_update(CommandArgs::now(CommandParam::new(param), self.scope, self.is_enabled_value()))
575 }
576
577 pub fn on_pre_event(&self, enabled: bool, handler: Handler<AppCommandArgs>) -> EventHandle {
585 self.event().on_pre_event(self.handler(enabled, handler))
586 }
587
588 pub fn on_event(&self, enabled: bool, handler: Handler<AppCommandArgs>) -> EventHandle {
596 self.event().on_event(self.handler(enabled, handler))
597 }
598
599 fn handler(&self, enabled: bool, mut handler: Handler<AppCommandArgs>) -> Handler<CommandArgs> {
600 let handle = Arc::new(self.subscribe(enabled));
601 Box::new(move |args| {
602 handler(&AppCommandArgs {
603 args: args.clone(),
604 handle: handle.clone(),
605 })
606 })
607 }
608
609 #[must_use]
611 pub(crate) fn update_state(&self) -> bool {
612 let mut write = self.local.write();
613 if let CommandScope::App = self.scope {
614 let has_handlers = write.handle_count > 0;
615 if has_handlers != write.has_handlers.get() {
616 write.has_handlers.set(has_handlers);
617 }
618 let is_enabled = has_handlers && write.enabled_count > 0;
619 if is_enabled != write.is_enabled.get() {
620 write.is_enabled.set(is_enabled);
621 }
622 true
623 } else if let hash_map::Entry::Occupied(entry) = write.scopes.entry(self.scope) {
624 let scope = entry.get();
625
626 if scope.handle_count == 0 && scope.has_handlers.strong_count() == 1 && scope.is_enabled.strong_count() == 1 {
627 entry.remove();
628 return false;
629 }
630
631 let has_handlers = scope.handle_count > 0;
632 if has_handlers != scope.has_handlers.get() {
633 scope.has_handlers.set(has_handlers);
634 }
635 let is_enabled = has_handlers && scope.enabled_count > 0;
636 if is_enabled != scope.is_enabled.get() {
637 scope.is_enabled.set(is_enabled);
638 }
639 true
640 } else {
641 false
642 }
643 }
644}
645impl Deref for Command {
646 type Target = Event<CommandArgs>;
647
648 fn deref(&self) -> &Self::Target {
649 &self.event
650 }
651}
652impl PartialEq for Command {
653 fn eq(&self, other: &Self) -> bool {
654 self.event == other.event && self.scope == other.scope
655 }
656}
657impl Eq for Command {}
658impl std::hash::Hash for Command {
659 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
660 std::hash::Hash::hash(&self.event.as_any(), state);
661 std::hash::Hash::hash(&self.scope, state);
662 }
663}
664
665#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
669pub enum CommandScope {
670 App,
672 Window(WindowId),
677 Widget(WidgetId),
679}
680impl_from_and_into_var! {
681 fn from(id: WidgetId) -> CommandScope {
682 CommandScope::Widget(id)
683 }
684 fn from(id: WindowId) -> CommandScope {
685 CommandScope::Window(id)
686 }
687 fn from(widget_name: &'static str) -> CommandScope {
689 WidgetId::named(widget_name).into()
690 }
691 fn from(widget_name: Txt) -> CommandScope {
693 WidgetId::named(widget_name).into()
694 }
695}
696
697event_args! {
698 pub struct CommandArgs {
700 pub param: Option<CommandParam>,
702
703 pub scope: CommandScope,
705
706 pub enabled: bool,
711
712 ..
713
714 fn delivery_list(&self, list: &mut UpdateDeliveryList) {
717 match self.scope {
718 CommandScope::Widget(id) => list.search_widget(id),
719 CommandScope::Window(id) => list.insert_window(id),
720 CommandScope::App => list.search_all(),
721 }
722 }
723 }
724}
725impl CommandArgs {
726 pub fn param<T: Any>(&self) -> Option<&T> {
728 self.param.as_ref().and_then(|p| p.downcast_ref::<T>())
729 }
730
731 pub fn enabled_param<T: Any>(&self) -> Option<&T> {
736 if self.enabled { self.param::<T>() } else { None }
737 }
738
739 pub fn disabled_param<T: Any>(&self) -> Option<&T> {
744 if !self.enabled { self.param::<T>() } else { None }
745 }
746
747 pub fn handle_enabled<F, R>(&self, local_handle: &CommandHandle, handler: F) -> Option<R>
755 where
756 F: FnOnce(&Self) -> R,
757 {
758 if self.propagation().is_stopped() || !self.enabled || !local_handle.is_enabled() {
759 None
760 } else {
761 let r = handler(self);
762 self.propagation().stop();
763 Some(r)
764 }
765 }
766}
767
768#[non_exhaustive]
770#[derive(Debug, Clone)]
771pub struct AppCommandArgs {
772 pub args: CommandArgs,
774 pub handle: Arc<CommandHandle>,
776}
777impl ops::Deref for AppCommandArgs {
778 type Target = CommandArgs;
779
780 fn deref(&self) -> &Self::Target {
781 &self.args
782 }
783}
784impl AnyEventArgs for AppCommandArgs {
785 fn clone_any(&self) -> Box<dyn AnyEventArgs> {
786 Box::new(self.clone())
787 }
788
789 fn as_any(&self) -> &dyn Any {
790 self
791 }
792
793 fn timestamp(&self) -> crate::DInstant {
794 self.args.timestamp()
795 }
796
797 fn delivery_list(&self, list: &mut UpdateDeliveryList) {
798 self.args.delivery_list(list)
799 }
800
801 fn propagation(&self) -> &EventPropagationHandle {
802 self.args.propagation()
803 }
804}
805impl EventArgs for AppCommandArgs {}
806
807pub struct CommandHandle {
814 command: Option<Command>,
815 local_enabled: AtomicBool,
816 app_id: Option<AppId>,
817 _event_handle: EventHandle,
818}
819impl CommandHandle {
820 pub fn command(&self) -> Option<Command> {
822 self.command
823 }
824
825 pub fn set_enabled(&self, enabled: bool) {
829 if let Some(command) = self.command
830 && self.local_enabled.swap(enabled, Ordering::Relaxed) != enabled
831 {
832 if self.app_id != APP.id() {
833 return;
834 }
835
836 UpdatesTrace::log_var(std::any::type_name::<bool>());
837
838 let mut write = command.local.write();
839 match command.scope {
840 CommandScope::App => {
841 if enabled {
842 write.enabled_count += 1;
843 } else {
844 write.enabled_count -= 1;
845 }
846 }
847 scope => {
848 if let Some(data) = write.scopes.get_mut(&scope) {
849 if enabled {
850 data.enabled_count += 1;
851 } else {
852 data.enabled_count -= 1;
853 }
854 }
855 }
856 }
857 }
858 }
859
860 pub fn is_enabled(&self) -> bool {
862 self.local_enabled.load(Ordering::Relaxed)
863 }
864
865 pub fn dummy() -> Self {
867 CommandHandle {
868 command: None,
869 app_id: None,
870 local_enabled: AtomicBool::new(false),
871 _event_handle: EventHandle::dummy(),
872 }
873 }
874
875 pub fn is_dummy(&self) -> bool {
877 self.command.is_none()
878 }
879}
880impl fmt::Debug for CommandHandle {
881 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
882 f.debug_struct("CommandHandle")
883 .field("command", &self.command)
884 .field("local_enabled", &self.local_enabled.load(Ordering::Relaxed))
885 .finish()
886 }
887}
888impl Drop for CommandHandle {
889 fn drop(&mut self) {
890 if let Some(command) = self.command {
891 if self.app_id != APP.id() {
892 return;
893 }
894
895 let mut write = command.local.write();
896 match command.scope {
897 CommandScope::App => {
898 write.handle_count -= 1;
899 if self.local_enabled.load(Ordering::Relaxed) {
900 write.enabled_count -= 1;
901 }
902 }
903 scope => {
904 if let Some(data) = write.scopes.get_mut(&scope) {
905 data.handle_count -= 1;
906 if self.local_enabled.load(Ordering::Relaxed) {
907 data.enabled_count -= 1;
908 }
909 }
910 }
911 }
912 }
913 }
914}
915impl Default for CommandHandle {
916 fn default() -> Self {
917 Self::dummy()
918 }
919}
920
921#[derive(Clone)]
923#[non_exhaustive]
924pub struct CommandParam(pub Arc<dyn Any + Send + Sync>);
925impl PartialEq for CommandParam {
926 fn eq(&self, other: &Self) -> bool {
927 Arc::ptr_eq(&self.0, &other.0)
928 }
929}
930impl Eq for CommandParam {}
931impl CommandParam {
932 pub fn new(param: impl Any + Send + Sync + 'static) -> Self {
936 let p: &dyn Any = ¶m;
937 if let Some(p) = p.downcast_ref::<Self>() {
938 p.clone()
939 } else if let Some(p) = p.downcast_ref::<Arc<dyn Any + Send + Sync>>() {
940 CommandParam(p.clone())
941 } else {
942 CommandParam(Arc::new(param))
943 }
944 }
945
946 pub fn type_id(&self) -> TypeId {
948 self.0.type_id()
949 }
950
951 pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
953 self.0.downcast_ref()
954 }
955
956 pub fn is<T: Any>(&self) -> bool {
958 self.0.is::<T>()
959 }
960}
961impl fmt::Debug for CommandParam {
962 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
963 f.debug_tuple("CommandParam").field(&self.0.type_id()).finish()
964 }
965}
966zng_var::impl_from_and_into_var! {
967 fn from(param: CommandParam) -> Option<CommandParam>;
968}
969
970#[rustfmt::skip] unique_id_64! {
972 pub struct CommandMetaVarId<T: (StateValue + VarValue)>: StateId;
978}
979zng_unique_id::impl_unique_id_bytemuck!(CommandMetaVarId<T: (StateValue + VarValue)>);
980impl<T: StateValue + VarValue> CommandMetaVarId<T> {
981 fn app(self) -> StateId<Var<T>> {
982 let id = self.get();
983 StateId::from_raw(id)
984 }
985
986 fn scope(self) -> StateId<Var<T>> {
987 let id = self.get();
988 StateId::from_raw(id)
989 }
990}
991
992impl<T: StateValue + VarValue> fmt::Debug for CommandMetaVarId<T> {
993 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
994 #[cfg(debug_assertions)]
995 let t = pretty_type_name::pretty_type_name::<T>();
996 #[cfg(not(debug_assertions))]
997 let t = "$T";
998
999 if f.alternate() {
1000 writeln!(f, "CommandMetaVarId<{t} {{")?;
1001 writeln!(f, " id: {},", self.get())?;
1002 writeln!(f, " sequential: {}", self.sequential())?;
1003 writeln!(f, "}}")
1004 } else {
1005 write!(f, "CommandMetaVarId<{t}>({})", self.sequential())
1006 }
1007 }
1008}
1009
1010pub struct CommandMeta<'a> {
1075 meta: StateMapMut<'a, CommandMetaState>,
1076 scope: Option<StateMapMut<'a, CommandMetaState>>,
1077}
1078impl CommandMeta<'_> {
1079 pub fn get_or_insert<T, F>(&mut self, id: impl Into<StateId<T>>, init: F) -> T
1085 where
1086 T: StateValue + Clone,
1087 F: FnOnce() -> T,
1088 {
1089 let id = id.into();
1090 if let Some(scope) = &mut self.scope {
1091 if let Some(value) = scope.get(id) {
1092 value.clone()
1093 } else if let Some(value) = self.meta.get(id) {
1094 value.clone()
1095 } else {
1096 let value = init();
1097 let r = value.clone();
1098 scope.set(id, value);
1099 r
1100 }
1101 } else {
1102 self.meta.entry(id).or_insert_with(init).clone()
1103 }
1104 }
1105
1106 pub fn get_or_default<T>(&mut self, id: impl Into<StateId<T>>) -> T
1112 where
1113 T: StateValue + Clone + Default,
1114 {
1115 self.get_or_insert(id, Default::default)
1116 }
1117
1118 pub fn get<T>(&self, id: impl Into<StateId<T>>) -> Option<T>
1122 where
1123 T: StateValue + Clone,
1124 {
1125 let id = id.into();
1126 if let Some(scope) = &self.scope {
1127 scope.get(id).or_else(|| self.meta.get(id))
1128 } else {
1129 self.meta.get(id)
1130 }
1131 .cloned()
1132 }
1133
1134 pub fn set<T>(&mut self, id: impl Into<StateId<T>>, value: impl Into<T>)
1138 where
1139 T: StateValue + Clone,
1140 {
1141 if let Some(scope) = &mut self.scope {
1142 scope.set(id, value);
1143 } else {
1144 self.meta.set(id, value);
1145 }
1146 }
1147
1148 pub fn init<T>(&mut self, id: impl Into<StateId<T>>, value: impl Into<T>)
1152 where
1153 T: StateValue + Clone,
1154 {
1155 self.meta.entry(id).or_insert(value);
1156 }
1157
1158 pub fn get_var_or_insert<T, F>(&mut self, id: impl Into<CommandMetaVarId<T>>, init: F) -> CommandMetaVar<T>
1164 where
1165 T: StateValue + VarValue,
1166 F: FnOnce() -> T,
1167 {
1168 let id = id.into();
1169 if let Some(scope) = &mut self.scope {
1170 let meta = &mut self.meta;
1171 scope
1172 .entry(id.scope())
1173 .or_insert_with(|| {
1174 let var = meta.entry(id.app()).or_insert_with(|| var(init())).clone();
1175 var.cow()
1176 })
1177 .clone()
1178 } else {
1179 self.meta.entry(id.app()).or_insert_with(|| var(init())).clone()
1180 }
1181 }
1182
1183 pub fn get_var<T>(&self, id: impl Into<CommandMetaVarId<T>>) -> Option<CommandMetaVar<T>>
1185 where
1186 T: StateValue + VarValue,
1187 {
1188 let id = id.into();
1189 if let Some(scope) = &self.scope {
1190 let meta = &self.meta;
1191 scope.get(id.scope()).cloned().or_else(|| meta.get(id.app()).cloned())
1192 } else {
1193 self.meta.get(id.app()).cloned()
1194 }
1195 }
1196
1197 pub fn get_var_or_default<T>(&mut self, id: impl Into<CommandMetaVarId<T>>) -> CommandMetaVar<T>
1201 where
1202 T: StateValue + VarValue + Default,
1203 {
1204 self.get_var_or_insert(id, Default::default)
1205 }
1206
1207 pub fn init_var<T>(&mut self, id: impl Into<CommandMetaVarId<T>>, value: impl Into<T>)
1211 where
1212 T: StateValue + VarValue,
1213 {
1214 self.meta.entry(id.into().app()).or_insert_with(|| var(value.into()));
1215 }
1216}
1217
1218pub type CommandMetaVar<T> = Var<T>;
1225
1226pub type ReadOnlyCommandMetaVar<T> = Var<T>;
1232
1233pub trait CommandNameExt {
1235 fn name(self) -> CommandMetaVar<Txt>;
1237
1238 fn init_name(self, name: impl Into<Txt>) -> Self;
1240
1241 fn name_with_shortcut(self) -> Var<Txt>
1247 where
1248 Self: crate::shortcut::CommandShortcutExt;
1249}
1250static_id! {
1251 static ref COMMAND_NAME_ID: CommandMetaVarId<Txt>;
1252}
1253impl CommandNameExt for Command {
1254 fn name(self) -> CommandMetaVar<Txt> {
1255 self.with_meta(|m| {
1256 m.get_var_or_insert(*COMMAND_NAME_ID, || {
1257 let name = self.event.name();
1258 let name = name.strip_suffix("_CMD").unwrap_or(name);
1259 let mut title = String::with_capacity(name.len());
1260 let mut lower = false;
1261 for c in name.chars() {
1262 if c == '_' {
1263 if !title.ends_with(' ') {
1264 title.push(' ');
1265 }
1266 lower = false;
1267 } else if lower {
1268 for l in c.to_lowercase() {
1269 title.push(l);
1270 }
1271 } else {
1272 title.push(c);
1273 lower = true;
1274 }
1275 }
1276 Txt::from(title)
1277 })
1278 })
1279 }
1280
1281 fn init_name(self, name: impl Into<Txt>) -> Self {
1282 self.with_meta(|m| m.init_var(*COMMAND_NAME_ID, name.into()));
1283 self
1284 }
1285
1286 fn name_with_shortcut(self) -> Var<Txt>
1287 where
1288 Self: crate::shortcut::CommandShortcutExt,
1289 {
1290 crate::var::merge_var!(self.name(), self.shortcut(), |name, shortcut| {
1291 if shortcut.is_empty() {
1292 name.clone()
1293 } else {
1294 zng_txt::formatx!("{name} ({})", shortcut[0])
1295 }
1296 })
1297 }
1298}
1299
1300pub trait CommandInfoExt {
1302 fn info(self) -> CommandMetaVar<Txt>;
1304
1305 fn init_info(self, info: impl Into<Txt>) -> Self;
1307}
1308static_id! {
1309 static ref COMMAND_INFO_ID: CommandMetaVarId<Txt>;
1310}
1311impl CommandInfoExt for Command {
1312 fn info(self) -> CommandMetaVar<Txt> {
1313 self.with_meta(|m| m.get_var_or_insert(*COMMAND_INFO_ID, Txt::default))
1314 }
1315
1316 fn init_info(self, info: impl Into<Txt>) -> Self {
1317 self.with_meta(|m| m.init_var(*COMMAND_INFO_ID, info.into()));
1318 self
1319 }
1320}
1321
1322enum CommandMetaState {}
1323
1324#[derive(Clone)]
1325enum MetaInit {
1326 Init(fn(Command)),
1327 Initing(Arc<(ThreadId, Mutex<()>)>),
1329 Inited,
1330}
1331
1332#[doc(hidden)]
1333pub struct CommandData {
1334 meta_init: MetaInit,
1335 meta: Mutex<OwnedStateMap<CommandMetaState>>,
1336
1337 handle_count: usize,
1338 enabled_count: usize,
1339 registered: bool,
1340
1341 has_handlers: Var<bool>,
1342 is_enabled: Var<bool>,
1343
1344 scopes: HashMap<CommandScope, ScopedValue>,
1345}
1346impl CommandData {
1347 pub fn new(meta_init: fn(Command)) -> Self {
1348 CommandData {
1349 meta_init: MetaInit::Init(meta_init),
1350 meta: Mutex::new(OwnedStateMap::new()),
1351
1352 handle_count: 0,
1353 enabled_count: 0,
1354 registered: false,
1355
1356 has_handlers: var(false),
1357 is_enabled: var(false),
1358
1359 scopes: HashMap::default(),
1360 }
1361 }
1362
1363 fn subscribe(&mut self, events: &mut EventsService, command: Command, enabled: bool, mut target: Option<WidgetId>) -> CommandHandle {
1364 match command.scope {
1365 CommandScope::App => {
1366 if !mem::replace(&mut self.registered, true) {
1367 events.register_command(command);
1368 }
1369
1370 self.handle_count += 1;
1371 if enabled {
1372 self.enabled_count += 1;
1373 }
1374 }
1375 scope => {
1376 let data = self.scopes.entry(scope).or_default();
1377
1378 if !mem::replace(&mut data.registered, true) {
1379 events.register_command(command);
1380 }
1381
1382 data.handle_count += 1;
1383 if enabled {
1384 data.enabled_count += 1;
1385 }
1386
1387 if let CommandScope::Widget(id) = scope {
1388 target = Some(id);
1389 }
1390 }
1391 };
1392
1393 CommandHandle {
1394 command: Some(command),
1395 app_id: APP.id(),
1396 local_enabled: AtomicBool::new(enabled),
1397 _event_handle: target.map(|t| command.event.subscribe(t)).unwrap_or_else(EventHandle::dummy),
1398 }
1399 }
1400}
1401
1402struct ScopedValue {
1403 handle_count: usize,
1404 enabled_count: usize,
1405 is_enabled: Var<bool>,
1406 has_handlers: Var<bool>,
1407 meta: Mutex<OwnedStateMap<CommandMetaState>>,
1408 registered: bool,
1409}
1410impl Default for ScopedValue {
1411 fn default() -> Self {
1412 ScopedValue {
1413 is_enabled: var(false),
1414 has_handlers: var(false),
1415 handle_count: 0,
1416 enabled_count: 0,
1417 meta: Mutex::new(OwnedStateMap::default()),
1418 registered: false,
1419 }
1420 }
1421}
1422
1423#[cfg(test)]
1424mod tests {
1425 use super::*;
1426
1427 command! {
1428 static FOO_CMD;
1429 }
1430
1431 #[test]
1432 fn parameter_none() {
1433 let _ = CommandArgs::now(None, CommandScope::App, true);
1434 }
1435
1436 #[test]
1437 fn enabled() {
1438 let _app = APP.minimal().run_headless(false);
1439
1440 assert!(!FOO_CMD.has_handlers_value());
1441
1442 let handle = FOO_CMD.subscribe(true);
1443 assert!(FOO_CMD.is_enabled_value());
1444
1445 handle.set_enabled(false);
1446 assert!(FOO_CMD.has_handlers_value());
1447 assert!(!FOO_CMD.is_enabled_value());
1448
1449 handle.set_enabled(true);
1450 assert!(FOO_CMD.is_enabled_value());
1451
1452 drop(handle);
1453 assert!(!FOO_CMD.has_handlers_value());
1454 }
1455
1456 #[test]
1457 fn enabled_scoped() {
1458 let _app = APP.minimal().run_headless(false);
1459
1460 let cmd = FOO_CMD;
1461 let cmd_scoped = FOO_CMD.scoped(WindowId::named("enabled_scoped"));
1462 assert!(!cmd.has_handlers_value());
1463 assert!(!cmd_scoped.has_handlers_value());
1464
1465 let handle_scoped = cmd_scoped.subscribe(true);
1466 assert!(!cmd.has_handlers_value());
1467 assert!(cmd_scoped.is_enabled_value());
1468
1469 handle_scoped.set_enabled(false);
1470 assert!(!cmd.has_handlers_value());
1471 assert!(!cmd_scoped.is_enabled_value());
1472 assert!(cmd_scoped.has_handlers_value());
1473
1474 handle_scoped.set_enabled(true);
1475 assert!(!cmd.has_handlers_value());
1476 assert!(cmd_scoped.is_enabled_value());
1477
1478 drop(handle_scoped);
1479 assert!(!cmd.has_handlers_value());
1480 assert!(!cmd_scoped.has_handlers_value());
1481 }
1482
1483 #[test]
1484 fn has_handlers() {
1485 let _app = APP.minimal().run_headless(false);
1486
1487 assert!(!FOO_CMD.has_handlers_value());
1488
1489 let handle = FOO_CMD.subscribe(false);
1490 assert!(FOO_CMD.has_handlers_value());
1491
1492 drop(handle);
1493 assert!(!FOO_CMD.has_handlers_value());
1494 }
1495
1496 #[test]
1497 fn has_handlers_scoped() {
1498 let _app = APP.minimal().run_headless(false);
1499
1500 let cmd = FOO_CMD;
1501 let cmd_scoped = FOO_CMD.scoped(WindowId::named("has_handlers_scoped"));
1502
1503 assert!(!cmd.has_handlers_value());
1504 assert!(!cmd_scoped.has_handlers_value());
1505
1506 let handle = cmd_scoped.subscribe(false);
1507
1508 assert!(!cmd.has_handlers_value());
1509 assert!(cmd_scoped.has_handlers_value());
1510
1511 drop(handle);
1512
1513 assert!(!cmd.has_handlers_value());
1514 assert!(!cmd_scoped.has_handlers_value());
1515 }
1516
1517 }