use std::{
any::{Any, TypeId},
collections::{HashMap, hash_map},
fmt, mem,
ops::{self, ControlFlow},
thread::ThreadId,
};
use crate::{
APP,
handler::{Handler, HandlerExt},
shortcut::CommandShortcutExt,
widget::info::{WidgetInfo, WidgetPath},
window::{WINDOWS_APP, WindowId},
};
use super::*;
#[macro_export]
macro_rules! command {
($(
$(#[$attr:meta])*
$vis:vis static $COMMAND:ident $({ $($meta_ident:ident $(!)? : $meta_init:expr),* $(,)? };)? $(;)?
)+) => {
$(
$crate::__command! {
$(#[$attr])*
$vis static $COMMAND $({
$($meta_ident: $meta_init,)+
})? ;
}
)+
}
}
#[doc(inline)]
pub use command;
use parking_lot::Mutex;
use zng_state_map::{OwnedStateMap, StateId, StateMapMut, StateValue};
use zng_txt::Txt;
use zng_unique_id::{static_id, unique_id_64};
use zng_var::{Var, VarHandles, VarValue, const_var, impl_from_and_into_var, var};
#[doc(hidden)]
pub use zng_app_context::app_local;
#[doc(hidden)]
pub use pastey::paste;
#[doc(hidden)]
#[macro_export]
macro_rules! __command {
(
$(#[$attr:meta])*
$vis:vis static $COMMAND:ident { l10n: $l10n_arg:expr, $($meta_ident:ident : $meta_init:expr),* $(,)? };
) => {
$(#[$attr])*
$(#[doc = concat!("* `", stringify!($meta_ident), "`")])+
$vis static $COMMAND: $crate::event::Command = {
fn __meta_init__(cmd: $crate::event::Command) {
let __l10n_arg = $l10n_arg;
$crate::event::paste! {$(
cmd.[<init_ $meta_ident>]($meta_init);
$crate::event::init_meta_l10n(std::env!("CARGO_PKG_NAME"), std::env!("CARGO_PKG_VERSION"), &__l10n_arg, cmd, stringify!($meta_ident), &cmd.$meta_ident());
)*}
}
$crate::event::app_local! {
static EVENT: $crate::event::EventData = $crate::event::EventData::new::<$crate::event::CommandArgs>();
static DATA: $crate::event::CommandData = $crate::event::CommandData::new(__meta_init__, stringify!($COMMAND));
}
$crate::event::Command::new(&EVENT, &DATA)
};
};
(
$(#[$attr:meta])*
$vis:vis static $COMMAND:ident { $($meta_ident:ident : $meta_init:expr),* $(,)? };
) => {
$(#[$attr])*
$(#[doc = concat!("* `", stringify!($meta_ident), "`")])+
$vis static $COMMAND: $crate::event::Command = {
fn __meta_init__(cmd: $crate::event::Command) {
$crate::event::paste! {$(
cmd.[<init_ $meta_ident>]($meta_init);
)*}
}
$crate::event::app_local! {
static EVENT: $crate::event::EventData = $crate::event::EventData::new::<$crate::event::CommandArgs>();
static DATA: $crate::event::CommandData = $crate::event::CommandData::new(__meta_init__, stringify!($COMMAND));
}
$crate::event::Command::new(&EVENT, &DATA)
};
};
(
$(#[$attr:meta])*
$vis:vis static $COMMAND:ident;
) => {
$(#[$attr])*
$vis static $COMMAND: $crate::event::Command = {
fn __meta_init__(_: $crate::event::Command) {
}
$crate::event::app_local! {
static EVENT: $crate::event::EventData = $crate::event::EventData::new::<$crate::event::CommandArgs>();
static DATA: $crate::event::CommandData = $crate::event::CommandData::new(__meta_init__, stringify!($COMMAND));
}
$crate::event::Command::new(&EVENT, &DATA)
};
};
}
#[doc(hidden)]
pub fn init_meta_l10n(
pkg_name: &'static str,
pkg_version: &'static str,
l10n_arg: &dyn Any,
cmd: Command,
meta_name: &'static str,
meta_value: &dyn Any,
) {
if let Some(txt) = meta_value.downcast_ref::<CommandMetaVar<Txt>>() {
let mut l10n_file = "";
if let Some(&enabled) = l10n_arg.downcast_ref::<bool>() {
if !enabled {
return;
}
} else if let Some(&file) = l10n_arg.downcast_ref::<&'static str>() {
l10n_file = file;
} else {
tracing::error!("unknown l10n value in {:?}", cmd.event());
return;
}
EVENTS_L10N.init_meta_l10n([pkg_name, pkg_version, l10n_file], cmd, meta_name, txt.clone());
}
}
#[derive(Clone, Copy)]
pub struct Command {
event: Event<CommandArgs>,
local: &'static AppLocal<CommandData>,
scope: CommandScope,
}
struct CommandDbg {
static_name: &'static str,
scope: CommandScope,
state: Option<[usize; 2]>,
}
impl CommandDbg {
fn new(static_name: &'static str, scope: CommandScope) -> Self {
Self {
static_name,
scope,
state: None,
}
}
}
impl fmt::Debug for CommandDbg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
let mut d = f.debug_struct("Command");
d.field("static_name", &self.static_name).field("scope", &self.scope);
if let Some([has, enabled]) = &self.state {
d.field("handle_count", has);
d.field("enabled_count", enabled);
}
d.finish_non_exhaustive()
} else {
write!(f, "{}", self.static_name)?;
match self.scope {
CommandScope::App => Ok(()),
CommandScope::Window(id) => write!(f, "({id})"),
CommandScope::Widget(id) => write!(f, "({id})"),
}
}
}
}
impl fmt::Debug for Command {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let dbg = if let Some(d) = self.local.try_read() {
let mut dbg = CommandDbg::new(d.static_name, self.scope);
dbg.state = Some([d.handle_count, d.enabled_count]);
dbg
} else {
CommandDbg::new("<locked>", self.scope)
};
fmt::Debug::fmt(&dbg, f)
}
}
impl Command {
#[doc(hidden)]
pub const fn new(event_local: &'static AppLocal<EventData>, command_local: &'static AppLocal<CommandData>) -> Self {
Command {
event: Event::new(event_local),
local: command_local,
scope: CommandScope::App,
}
}
pub fn subscribe(&self, enabled: bool) -> CommandHandle {
self.local.write().subscribe(*self, enabled, None)
}
pub fn subscribe_wgt(&self, enabled: bool, target: WidgetId) -> CommandHandle {
self.local.write().subscribe(*self, enabled, Some(target))
}
pub fn event(&self) -> Event<CommandArgs> {
self.event
}
pub fn scope(&self) -> CommandScope {
self.scope
}
pub fn scoped(mut self, scope: impl Into<CommandScope>) -> Command {
self.scope = scope.into();
self
}
pub fn with_meta<R>(&self, visit: impl FnOnce(&mut CommandMeta) -> R) -> R {
fn init_meta(self_: &Command) -> parking_lot::MappedRwLockReadGuard<'static, CommandData> {
{
let mut write = self_.local.write();
match write.meta_init.clone() {
MetaInit::Init(init) => {
let lock = Arc::new((std::thread::current().id(), Mutex::new(())));
write.meta_init = MetaInit::Initing(lock.clone());
let _init_guard = lock.1.lock();
drop(write);
init(*self_);
self_.local.write().meta_init = MetaInit::Inited;
}
MetaInit::Initing(l) => {
drop(write);
if l.0 != std::thread::current().id() {
let _wait = l.1.lock();
}
}
MetaInit::Inited => {}
}
}
if !matches!(self_.scope, CommandScope::App) {
let mut write = self_.local.write();
write.scopes.entry(self_.scope).or_default();
}
self_.local.read()
}
let local_read = init_meta(self);
let mut meta_lock = local_read.meta.lock();
match self.scope {
CommandScope::App => visit(&mut CommandMeta {
meta: meta_lock.borrow_mut(),
scope: None,
}),
scope => {
let scope = local_read.scopes.get(&scope).unwrap();
visit(&mut CommandMeta {
meta: meta_lock.borrow_mut(),
scope: Some(scope.meta.lock().borrow_mut()),
})
}
}
}
pub fn has_handlers(&self) -> Var<bool> {
let mut write = self.local.write();
match self.scope {
CommandScope::App => write.has_handlers.read_only(),
scope => write.scopes.entry(scope).or_default().has_handlers.read_only(),
}
}
pub fn is_enabled(&self) -> Var<bool> {
let mut write = self.local.write();
match self.scope {
CommandScope::App => write.is_enabled.read_only(),
scope => write.scopes.entry(scope).or_default().is_enabled.read_only(),
}
}
pub fn visit_scopes<T>(&self, mut visitor: impl FnMut(Command) -> ControlFlow<T>) -> Option<T> {
let read = self.local.read();
for &scope in read.scopes.keys() {
match visitor(self.scoped(scope)) {
ControlFlow::Continue(_) => continue,
ControlFlow::Break(r) => return Some(r),
}
}
None
}
pub fn notify(&self) {
self.event.notify(CommandArgs::now(
None,
self.scope,
self.scope.search_target(),
self.is_enabled().get(),
))
}
pub fn notify_descendants(&self, parent: &WidgetInfo) {
self.visit_scopes::<()>(|parse_cmd| {
if let CommandScope::Widget(id) = parse_cmd.scope()
&& let Some(scope) = parent.tree().get(id)
&& scope.is_descendant(parent)
{
parse_cmd.notify();
}
ControlFlow::Continue(())
});
}
pub fn notify_param(&self, param: impl Any + Send + Sync) {
self.event.notify(CommandArgs::now(
CommandParam::new(param),
self.scope,
self.scope.search_target(),
self.is_enabled().get(),
));
}
pub fn notify_linked(&self, propagation: EventPropagationHandle, param: Option<CommandParam>) {
self.event.notify(CommandArgs::new(
crate::INSTANT.now(),
propagation,
param,
self.scope,
self.scope.search_target(),
self.is_enabled().get(),
))
}
pub fn each_update(&self, direct_scope_only: bool, ignore_propagation: bool, mut handler: impl FnMut(&CommandArgs)) {
self.event.each_update(ignore_propagation, move |args| {
if args.scope_matches(direct_scope_only, self.scope) {
handler(args);
}
});
}
pub fn latest_update<O>(
&self,
direct_scope_only: bool,
ignore_propagation: bool,
handler: impl FnOnce(&CommandArgs) -> O,
) -> Option<O> {
let mut r = None;
self.event.latest_update(ignore_propagation, |args| {
if args.scope_matches(direct_scope_only, self.scope) {
r = Some(handler(args));
}
});
r
}
pub fn has_update(&self, direct_scope_only: bool, ignore_propagation: bool) -> bool {
self.latest_update(direct_scope_only, ignore_propagation, |_| true).unwrap_or(false)
}
pub fn on_pre_event(
&self,
init_enabled: bool,
direct_scope_only: bool,
ignore_propagation: bool,
handler: Handler<CommandArgs>,
) -> CommandHandle {
let (mut handle, handler) = self.event_handler(init_enabled, direct_scope_only, handler);
handle._handles.push(self.event().on_pre_event(ignore_propagation, handler));
handle
}
pub fn on_event(
&self,
init_enabled: bool,
direct_scope_only: bool,
ignore_propagation: bool,
handler: Handler<CommandArgs>,
) -> CommandHandle {
let (mut handle, handler) = self.event_handler(init_enabled, direct_scope_only, handler);
handle._handles.push(self.event().on_event(ignore_propagation, handler));
handle
}
fn event_handler(
&self,
init_enabled: bool,
direct_scope_only: bool,
handler: Handler<CommandArgs>,
) -> (CommandHandle, Handler<CommandArgs>) {
let handle = self.subscribe(init_enabled);
let local_enabled = handle.enabled().clone();
let handler = if direct_scope_only {
let scope = self.scope();
handler.filtered(move |a| a.scope == scope && local_enabled.get())
} else {
match self.scope() {
CommandScope::App => handler.filtered(move |_| local_enabled.get()),
CommandScope::Window(id) => {
handler.filtered(move |a| a.target.as_ref().map(|t| t.window_id() == id).unwrap_or(false) && local_enabled.get())
}
CommandScope::Widget(id) => {
handler.filtered(move |a| a.target.as_ref().map(|t| t.contains(id)).unwrap_or(false) && local_enabled.get())
}
}
};
(handle, handler)
}
pub fn on_pre_event_with_enabled(
&self,
init_enabled: bool,
direct_scope_only: bool,
ignore_propagation: bool,
handler: Handler<(CommandArgs, Var<bool>)>,
) -> CommandHandle {
let (mut handle, handler) = self.event_handler_with_enabled(init_enabled, direct_scope_only, handler);
handle._handles.push(self.event().on_pre_event(ignore_propagation, handler));
handle
}
pub fn on_event_with_enabled(
&self,
init_enabled: bool,
direct_scope_only: bool,
ignore_propagation: bool,
handler: Handler<(CommandArgs, Var<bool>)>,
) -> CommandHandle {
let (mut handle, handler) = self.event_handler_with_enabled(init_enabled, direct_scope_only, handler);
handle._handles.push(self.event().on_event(ignore_propagation, handler));
handle
}
fn event_handler_with_enabled(
&self,
init_enabled: bool,
direct_scope_only: bool,
mut handler: Handler<(CommandArgs, Var<bool>)>,
) -> (CommandHandle, Handler<CommandArgs>) {
let handle = self.subscribe(init_enabled);
let local_enabled = handle.enabled().clone();
let r: Handler<CommandArgs>;
if direct_scope_only {
let scope = self.scope();
r = Box::new(move |a: &CommandArgs| {
if a.scope == scope {
handler(&(a.clone(), local_enabled.clone()))
} else {
HandlerResult::Done
}
});
} else {
match self.scope() {
CommandScope::App => r = Box::new(move |a: &CommandArgs| handler(&(a.clone(), local_enabled.clone()))),
CommandScope::Window(id) => {
r = Box::new(move |a: &CommandArgs| {
if a.target.as_ref().map(|t| t.window_id() == id).unwrap_or(false) {
handler(&(a.clone(), local_enabled.clone()))
} else {
HandlerResult::Done
}
})
}
CommandScope::Widget(id) => {
r = Box::new(move |a: &CommandArgs| {
if a.target.as_ref().map(|t| t.contains(id)).unwrap_or(false) {
handler(&(a.clone(), local_enabled.clone()))
} else {
HandlerResult::Done
}
})
}
}
};
(handle, r)
}
pub fn static_name(&self) -> &'static str {
self.local.read().static_name
}
}
impl ops::Deref for Command {
type Target = Event<CommandArgs>;
fn deref(&self) -> &Self::Target {
&self.event
}
}
impl PartialEq for Command {
fn eq(&self, other: &Self) -> bool {
self.event == other.event && self.scope == other.scope
}
}
impl Eq for Command {}
impl std::hash::Hash for Command {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::hash::Hash::hash(&self.event.as_any(), state);
std::hash::Hash::hash(&self.scope, state);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum CommandScope {
App,
Window(WindowId),
Widget(WidgetId),
}
impl CommandScope {
pub fn search_target(self) -> Option<WidgetPath> {
match self {
CommandScope::App => None,
CommandScope::Window(id) => WINDOWS_APP.widget_tree(id).map(|t| t.root().path()),
CommandScope::Widget(id) => WINDOWS_APP.widget_info(id).map(|w| w.path()),
}
}
}
impl_from_and_into_var! {
fn from(id: WidgetId) -> CommandScope {
CommandScope::Widget(id)
}
fn from(id: WindowId) -> CommandScope {
CommandScope::Window(id)
}
fn from(widget_name: &'static str) -> CommandScope {
WidgetId::named(widget_name).into()
}
fn from(widget_name: Txt) -> CommandScope {
WidgetId::named(widget_name).into()
}
}
event_args! {
pub struct CommandArgs {
pub param: Option<CommandParam>,
pub scope: CommandScope,
pub target: Option<WidgetPath>,
pub enabled: bool,
..
fn is_in_target(&self, id: WidgetId) -> bool {
match self.scope {
CommandScope::App => true,
_ => match &self.target {
Some(t) => t.contains(id),
None => false,
},
}
}
fn validate(&self) -> Result<(), Txt> {
if let Some(t) = &self.target {
match self.scope {
CommandScope::App => return Err("args for app scope cannot have a `target`".into()),
CommandScope::Window(id) => {
if id != t.window_id() || t.widgets_path().len() > 1 {
return Err("args for window scope must only `target` that window root widget".into());
}
}
CommandScope::Widget(id) => {
if id != t.widget_id() {
return Err("args for widget scope must only `target` that widget".into());
}
}
}
}
Ok(())
}
}
}
impl CommandArgs {
pub fn param<T: Any>(&self) -> Option<&T> {
self.param.as_ref().and_then(|p| p.downcast_ref::<T>())
}
pub fn enabled_param<T: Any>(&self) -> Option<&T> {
if self.enabled { self.param::<T>() } else { None }
}
pub fn disabled_param<T: Any>(&self) -> Option<&T> {
if !self.enabled { self.param::<T>() } else { None }
}
pub fn scope_matches(&self, direct_only: bool, scope: CommandScope) -> bool {
if direct_only {
self.scope == scope
} else {
match (scope, self.scope) {
(CommandScope::App, _) => true,
(CommandScope::Window(scope_id), CommandScope::Window(args_id)) => scope_id == args_id,
(CommandScope::Window(scope_id), CommandScope::Widget(args_id)) => {
if let Some(t) = &self.target {
t.window_id() == scope_id && t.contains(args_id)
} else if let Some(info) = WINDOWS_APP.widget_tree(scope_id) {
info.contains(args_id)
} else {
false
}
}
(CommandScope::Widget(scope_id), CommandScope::Widget(args_id)) => {
if let Some(t) = &self.target {
t.widgets_path().iter().position(|i| *i == scope_id).unwrap_or(usize::MAX)
< t.widgets_path().iter().position(|i| *i == args_id).unwrap_or(usize::MAX)
} else {
todo!()
}
}
_ => false,
}
}
}
}
pub struct CommandHandle {
command: Option<Command>,
local_enabled: Var<bool>,
_handles: VarHandles,
}
impl Clone for CommandHandle {
fn clone(&self) -> Self {
match self.command {
Some(c) => c.subscribe(self.local_enabled.get()),
None => Self::dummy(),
}
}
}
impl CommandHandle {
pub fn command(&self) -> Option<Command> {
self.command
}
pub fn enabled(&self) -> &Var<bool> {
&self.local_enabled
}
pub fn dummy() -> Self {
CommandHandle {
command: None,
local_enabled: const_var(false),
_handles: VarHandles::dummy(),
}
}
fn new(cmd: Command, event_handle: VarHandle, enabled: bool) -> Self {
let mut r = Self {
command: Some(cmd),
local_enabled: var(enabled),
_handles: VarHandles::dummy(),
};
let mut last_applied = enabled;
r._handles.push(r.local_enabled.hook(move |args| {
let _hold = &event_handle;
let enabled = *args.value();
if last_applied != enabled {
Self::update_enabled(cmd, enabled);
last_applied = enabled;
}
true
}));
r
}
fn update_enabled(command: Command, enabled: bool) {
let mut write = command.local.write();
match command.scope {
CommandScope::App => {
if enabled {
write.enabled_count += 1;
if write.enabled_count == 1 {
write.is_enabled.set(true);
}
tracing::trace!(
"command handle {:?} enabled, count: {:?}",
CommandDbg::new(write.static_name, command.scope),
write.enabled_count
);
} else {
write.enabled_count = match write.enabled_count.checked_sub(1) {
Some(c) => c,
None => {
#[cfg(debug_assertions)]
panic!("handle for {} was disabled when enabled_count was already zero", write.static_name);
#[cfg(not(debug_assertions))]
0
}
};
if write.enabled_count == 0 {
write.is_enabled.set(false);
}
tracing::trace!(
"command handle {:?} disabled, count: {:?}",
CommandDbg::new(write.static_name, command.scope),
write.enabled_count
);
}
}
scope => {
let write = &mut *write;
if let Some(data) = write.scopes.get_mut(&scope) {
if enabled {
data.enabled_count += 1;
if data.enabled_count == 1 {
data.is_enabled.set(true);
}
tracing::trace!(
"command handle {:?} enabled, count: {:?}",
CommandDbg::new(write.static_name, command.scope),
data.enabled_count
);
} else {
data.enabled_count = match data.enabled_count.checked_sub(1) {
Some(c) => c,
None => {
#[cfg(debug_assertions)]
panic!(
"handle for {:?} was disabled when enabled_count was already zero",
CommandDbg::new(write.static_name, scope)
);
#[cfg(not(debug_assertions))]
0
}
};
if data.enabled_count == 0 {
data.is_enabled.set(false);
}
tracing::trace!(
"command handle {:?} enabled, count: {:?}",
CommandDbg::new(write.static_name, command.scope),
data.enabled_count
);
}
}
}
}
}
pub fn is_dummy(&self) -> bool {
self.command.is_none()
}
pub fn perm(mut self) {
mem::replace(&mut self._handles, VarHandles::dummy()).perm();
self.command = None;
}
}
impl fmt::Debug for CommandHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CommandHandle")
.field("command", &self.command)
.field("enabled", &self.local_enabled.get())
.finish_non_exhaustive()
}
}
impl Drop for CommandHandle {
fn drop(&mut self) {
if let Some(command) = self.command.take()
&& APP.is_started()
{
let mut write = command.local.write();
match command.scope {
CommandScope::App => {
write.handle_count = match write.handle_count.checked_sub(1) {
Some(c) => c,
None => {
#[cfg(debug_assertions)]
panic!("handle for {} was dropped when handle_count was already zero", write.static_name);
#[cfg(not(debug_assertions))]
0
}
};
if write.handle_count == 0 {
write.has_handlers.set(false);
}
if self.local_enabled.get() {
write.enabled_count = match write.enabled_count.checked_sub(1) {
Some(c) => c,
None => {
#[cfg(debug_assertions)]
panic!(
"handle for enabled {} was dropped when enabled_count was already zero",
write.static_name
);
#[cfg(not(debug_assertions))]
0
}
};
if write.enabled_count == 0 {
write.is_enabled.set(false);
}
}
tracing::trace!(
"unsubscribe to {:?}, handle_count: {:?}, enabled_count: {:?}",
CommandDbg::new(write.static_name, command.scope),
write.handle_count,
write.enabled_count
);
}
scope => {
let write = &mut *write;
if let hash_map::Entry::Occupied(mut entry) = write.scopes.entry(scope) {
let data = entry.get_mut();
data.handle_count = match data.handle_count.checked_sub(1) {
Some(c) => c,
None => {
#[cfg(debug_assertions)]
panic!(
"handle for {:?} was dropped when handle_count was already zero",
CommandDbg::new(write.static_name, scope)
);
#[cfg(not(debug_assertions))]
0
}
};
if self.local_enabled.get() {
data.enabled_count = match data.enabled_count.checked_sub(1) {
Some(c) => c,
None => {
#[cfg(debug_assertions)]
panic!(
"handle for enabled {:?} was dropped when enabled_count was already zero",
CommandDbg::new(write.static_name, scope)
);
#[cfg(not(debug_assertions))]
0
}
};
if data.enabled_count == 0 {
data.is_enabled.set(false);
}
}
tracing::trace!(
"unsubscribe to {:?}, handle_count: {:?}, enabled_count: {:?}",
CommandDbg::new(write.static_name, command.scope),
data.handle_count,
data.enabled_count
);
if data.handle_count == 0 {
data.has_handlers.set(false);
entry.remove();
EVENTS.unregister_command(command);
}
}
}
}
}
}
}
impl Default for CommandHandle {
fn default() -> Self {
Self::dummy()
}
}
#[derive(Clone)]
#[non_exhaustive]
pub struct CommandParam(pub Arc<dyn Any + Send + Sync>);
impl PartialEq for CommandParam {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for CommandParam {}
impl CommandParam {
pub fn new(param: impl Any + Send + Sync + 'static) -> Self {
let p: &dyn Any = ¶m;
if let Some(p) = p.downcast_ref::<Self>() {
p.clone()
} else if let Some(p) = p.downcast_ref::<Arc<dyn Any + Send + Sync>>() {
CommandParam(p.clone())
} else {
CommandParam(Arc::new(param))
}
}
pub fn type_id(&self) -> TypeId {
self.0.type_id()
}
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
self.0.downcast_ref()
}
pub fn is<T: Any>(&self) -> bool {
self.0.is::<T>()
}
}
impl fmt::Debug for CommandParam {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("CommandParam").field(&self.0.type_id()).finish()
}
}
zng_var::impl_from_and_into_var! {
fn from(param: CommandParam) -> Option<CommandParam>;
}
#[rustfmt::skip] unique_id_64! {
pub struct CommandMetaVarId<T: (StateValue + VarValue)>: StateId;
}
zng_unique_id::impl_unique_id_bytemuck!(CommandMetaVarId<T: (StateValue + VarValue)>);
impl<T: StateValue + VarValue> CommandMetaVarId<T> {
fn app(self) -> StateId<Var<T>> {
let id = self.get();
StateId::from_raw(id)
}
fn scope(self) -> StateId<Var<T>> {
let id = self.get();
StateId::from_raw(id)
}
}
impl<T: StateValue + VarValue> fmt::Debug for CommandMetaVarId<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(debug_assertions)]
let t = pretty_type_name::pretty_type_name::<T>();
#[cfg(not(debug_assertions))]
let t = "$T";
if f.alternate() {
writeln!(f, "CommandMetaVarId<{t} {{")?;
writeln!(f, " id: {},", self.get())?;
writeln!(f, " sequential: {}", self.sequential())?;
writeln!(f, "}}")
} else {
write!(f, "CommandMetaVarId<{t}>({})", self.sequential())
}
}
}
pub struct CommandMeta<'a> {
meta: StateMapMut<'a, CommandMetaState>,
scope: Option<StateMapMut<'a, CommandMetaState>>,
}
impl CommandMeta<'_> {
pub fn get_or_insert<T, F>(&mut self, id: impl Into<StateId<T>>, init: F) -> T
where
T: StateValue + Clone,
F: FnOnce() -> T,
{
let id = id.into();
if let Some(scope) = &mut self.scope {
if let Some(value) = scope.get(id) {
value.clone()
} else if let Some(value) = self.meta.get(id) {
value.clone()
} else {
let value = init();
let r = value.clone();
scope.set(id, value);
r
}
} else {
self.meta.entry(id).or_insert_with(init).clone()
}
}
pub fn get_or_default<T>(&mut self, id: impl Into<StateId<T>>) -> T
where
T: StateValue + Clone + Default,
{
self.get_or_insert(id, Default::default)
}
pub fn get<T>(&self, id: impl Into<StateId<T>>) -> Option<T>
where
T: StateValue + Clone,
{
let id = id.into();
if let Some(scope) = &self.scope {
scope.get(id).or_else(|| self.meta.get(id))
} else {
self.meta.get(id)
}
.cloned()
}
pub fn set<T>(&mut self, id: impl Into<StateId<T>>, value: impl Into<T>)
where
T: StateValue + Clone,
{
if let Some(scope) = &mut self.scope {
scope.set(id, value);
} else {
self.meta.set(id, value);
}
}
pub fn init<T>(&mut self, id: impl Into<StateId<T>>, value: impl Into<T>)
where
T: StateValue + Clone,
{
self.meta.entry(id).or_insert(value);
}
pub fn get_var_or_insert<T, F>(&mut self, id: impl Into<CommandMetaVarId<T>>, init: F) -> CommandMetaVar<T>
where
T: StateValue + VarValue,
F: FnOnce() -> T,
{
let id = id.into();
if let Some(scope) = &mut self.scope {
let meta = &mut self.meta;
scope
.entry(id.scope())
.or_insert_with(|| {
let var = meta.entry(id.app()).or_insert_with(|| var(init())).clone();
var.cow()
})
.clone()
} else {
self.meta.entry(id.app()).or_insert_with(|| var(init())).clone()
}
}
pub fn get_var<T>(&self, id: impl Into<CommandMetaVarId<T>>) -> Option<CommandMetaVar<T>>
where
T: StateValue + VarValue,
{
let id = id.into();
if let Some(scope) = &self.scope {
let meta = &self.meta;
scope.get(id.scope()).cloned().or_else(|| meta.get(id.app()).cloned())
} else {
self.meta.get(id.app()).cloned()
}
}
pub fn get_var_or_default<T>(&mut self, id: impl Into<CommandMetaVarId<T>>) -> CommandMetaVar<T>
where
T: StateValue + VarValue + Default,
{
self.get_var_or_insert(id, Default::default)
}
pub fn init_var<T>(&mut self, id: impl Into<CommandMetaVarId<T>>, value: impl Into<T>)
where
T: StateValue + VarValue,
{
self.meta.entry(id.into().app()).or_insert_with(|| var(value.into()));
}
}
pub type CommandMetaVar<T> = Var<T>;
pub type ReadOnlyCommandMetaVar<T> = Var<T>;
pub trait CommandNameExt {
fn name(self) -> CommandMetaVar<Txt>;
fn init_name(self, name: impl Into<Txt>) -> Self;
fn name_with_shortcut(self) -> Var<Txt>
where
Self: crate::shortcut::CommandShortcutExt;
}
static_id! {
static ref COMMAND_NAME_ID: CommandMetaVarId<Txt>;
}
impl CommandNameExt for Command {
fn name(self) -> CommandMetaVar<Txt> {
self.with_meta(|m| {
m.get_var_or_insert(*COMMAND_NAME_ID, || {
let name = self.static_name();
let name = name.strip_suffix("_CMD").unwrap_or(name);
let mut title = String::with_capacity(name.len());
let mut lower = false;
for c in name.chars() {
if c == '_' {
if !title.ends_with(' ') {
title.push(' ');
}
lower = false;
} else if lower {
for l in c.to_lowercase() {
title.push(l);
}
} else {
title.push(c);
lower = true;
}
}
Txt::from(title)
})
})
}
fn init_name(self, name: impl Into<Txt>) -> Self {
self.with_meta(|m| m.init_var(*COMMAND_NAME_ID, name.into()));
self
}
fn name_with_shortcut(self) -> Var<Txt>
where
Self: crate::shortcut::CommandShortcutExt,
{
crate::var::merge_var!(self.name(), self.shortcut(), |name, shortcut| {
if shortcut.is_empty() {
name.clone()
} else {
zng_txt::formatx!("{name} ({})", shortcut[0])
}
})
}
}
impl Command {
#[doc(hidden)]
pub fn init_init(self, init: impl FnOnce(Self)) {
init(self)
}
#[doc(hidden)]
pub fn init(self) {}
}
pub trait CommandInfoExt {
fn info(self) -> CommandMetaVar<Txt>;
fn init_info(self, info: impl Into<Txt>) -> Self;
}
static_id! {
static ref COMMAND_INFO_ID: CommandMetaVarId<Txt>;
}
impl CommandInfoExt for Command {
fn info(self) -> CommandMetaVar<Txt> {
self.with_meta(|m| m.get_var_or_insert(*COMMAND_INFO_ID, Txt::default))
}
fn init_info(self, info: impl Into<Txt>) -> Self {
self.with_meta(|m| m.init_var(*COMMAND_INFO_ID, info.into()));
self
}
}
enum CommandMetaState {}
#[derive(Clone)]
enum MetaInit {
Init(fn(Command)),
Initing(Arc<(ThreadId, Mutex<()>)>),
Inited,
}
#[doc(hidden)]
pub struct CommandData {
static_name: &'static str,
meta_init: MetaInit,
meta: Mutex<OwnedStateMap<CommandMetaState>>,
handle_count: usize,
enabled_count: usize,
registered: bool,
has_handlers: Var<bool>,
is_enabled: Var<bool>,
scopes: HashMap<CommandScope, ScopedValue>,
}
impl CommandData {
pub fn new(meta_init: fn(Command), static_name: &'static str) -> Self {
CommandData {
static_name,
meta_init: MetaInit::Init(meta_init),
meta: Mutex::new(OwnedStateMap::new()),
handle_count: 0,
enabled_count: 0,
registered: false,
has_handlers: var(false),
is_enabled: var(false),
scopes: HashMap::default(),
}
}
fn subscribe(&mut self, command: Command, enabled: bool, mut target: Option<WidgetId>) -> CommandHandle {
match command.scope {
CommandScope::App => {
if !mem::replace(&mut self.registered, true) {
EVENTS.register_command(command);
}
self.handle_count += 1;
if enabled {
self.enabled_count += 1;
}
if self.handle_count == 1 {
self.has_handlers.set(true);
}
if self.enabled_count == 1 {
self.is_enabled.set(true);
}
tracing::trace!(
"subscribe to {:?}, handle_count: {:?}, enabled_count: {:?}",
CommandDbg::new(self.static_name, command.scope),
self.handle_count,
self.enabled_count
);
}
scope => {
let data = self.scopes.entry(scope).or_default();
if !mem::replace(&mut data.registered, true) {
EVENTS.register_command(command);
}
data.handle_count += 1;
if enabled {
data.enabled_count += 1;
}
if data.handle_count == 1 {
data.has_handlers.set(true);
}
if data.enabled_count == 1 {
data.is_enabled.set(true);
}
tracing::trace!(
"subscribe to {:?}, handle_count: {:?}, enabled_count: {:?}",
CommandDbg::new(self.static_name, command.scope),
self.handle_count,
self.enabled_count
);
if let CommandScope::Widget(id) = scope {
target = Some(id);
}
}
};
CommandHandle::new(
command,
target
.map(|t| command.event.subscribe(UpdateOp::Update, t))
.unwrap_or_else(VarHandle::dummy),
enabled,
)
}
}
struct ScopedValue {
handle_count: usize,
enabled_count: usize,
is_enabled: Var<bool>,
has_handlers: Var<bool>,
meta: Mutex<OwnedStateMap<CommandMetaState>>,
registered: bool,
}
impl Default for ScopedValue {
fn default() -> Self {
ScopedValue {
is_enabled: var(false),
has_handlers: var(false),
handle_count: 0,
enabled_count: 0,
meta: Mutex::new(OwnedStateMap::default()),
registered: false,
}
}
}
#[cfg(test)]
mod tests {
use crate::APP;
use super::*;
command! {
static FOO_CMD;
}
#[test]
fn parameter_none() {
let _ = CommandArgs::now(None, CommandScope::App, None, true);
}
#[test]
fn enabled_not_scoped() {
let mut app = APP.minimal().run_headless(false);
assert!(!FOO_CMD.has_handlers().get());
let handle = FOO_CMD.subscribe(true);
app.update(false).assert_wait();
assert!(FOO_CMD.is_enabled().get());
handle.enabled().set(false);
app.update(false).assert_wait();
assert!(FOO_CMD.has_handlers().get());
assert!(!FOO_CMD.is_enabled().get());
handle.enabled().set(true);
app.update(false).assert_wait();
assert!(FOO_CMD.is_enabled().get());
drop(handle);
app.update(false).assert_wait();
assert!(!FOO_CMD.has_handlers().get());
assert!(!FOO_CMD.is_enabled().get());
}
#[test]
fn enabled_scoped() {
let mut app = APP.minimal().run_headless(false);
let cmd = FOO_CMD;
let cmd_scoped = FOO_CMD.scoped(WindowId::named("enabled_scoped"));
app.update(false).assert_wait();
assert!(!cmd.has_handlers().get());
assert!(!cmd_scoped.has_handlers().get());
let handle_scoped = cmd_scoped.subscribe(true);
app.update(false).assert_wait();
assert!(!cmd.has_handlers().get());
assert!(cmd_scoped.is_enabled().get());
handle_scoped.enabled().set(false);
app.update(false).assert_wait();
assert!(!cmd.has_handlers().get());
assert!(!cmd_scoped.is_enabled().get());
assert!(cmd_scoped.has_handlers().get());
handle_scoped.enabled().set(true);
app.update(false).assert_wait();
assert!(!cmd.has_handlers().get());
assert!(cmd_scoped.is_enabled().get());
drop(handle_scoped);
app.update(false).assert_wait();
assert!(!cmd.has_handlers().get());
assert!(!cmd_scoped.has_handlers().get());
}
#[test]
fn has_handlers() {
let mut app = APP.minimal().run_headless(false);
assert!(!FOO_CMD.has_handlers().get());
let handle = FOO_CMD.subscribe(false);
app.update(false).assert_wait();
assert!(FOO_CMD.has_handlers().get());
drop(handle);
app.update(false).assert_wait();
assert!(!FOO_CMD.has_handlers().get());
}
#[test]
fn has_handlers_scoped() {
let mut app = APP.minimal().run_headless(false);
let cmd = FOO_CMD;
let cmd_scoped = FOO_CMD.scoped(WindowId::named("has_handlers_scoped"));
app.update(false).assert_wait();
assert!(!cmd.has_handlers().get());
assert!(!cmd_scoped.has_handlers().get());
let handle = cmd_scoped.subscribe(false);
app.update(false).assert_wait();
assert!(!cmd.has_handlers().get());
assert!(cmd_scoped.has_handlers().get());
drop(handle);
app.update(false).assert_wait();
assert!(!cmd.has_handlers().get());
assert!(!cmd_scoped.has_handlers().get());
}
}