use parking_lot::Mutex;
use std::{
collections::{HashMap, HashSet},
convert::TryFrom,
num::NonZeroU32,
sync::Arc,
time::Duration,
};
use zng_app::{
APP, DInstant, HeadlessApp,
access::{ACCESS_CLICK_EVENT, AccessClickArgs},
event::{AnyEventArgs as _, Command, CommandScope, EVENTS, EventPropagationHandle, event, event_args},
hn,
shortcut::{
CommandShortcutExt, GestureKey, KeyChord, KeyGesture, ModifierGesture, ModifiersState, Shortcut, ShortcutFilter, Shortcuts,
shortcut,
},
view_process::raw_device_events::InputDeviceId,
widget::{
WidgetId,
info::{HitTestInfo, InteractionPath, WidgetPath},
},
window::WindowId,
};
use zng_app_context::app_local;
use zng_env::on_process_start;
use zng_ext_window::WINDOWS;
use zng_handle::{Handle, HandleOwner, WeakHandle};
use zng_layout::unit::DipPoint;
use zng_var::{Var, var};
use zng_view_api::{
keyboard::{Key, KeyCode, KeyLocation, KeyState, NativeKeyCode},
mouse::MouseButton,
};
use crate::{
focus::{FOCUS, FocusRequest, FocusTarget},
keyboard::{HeadlessAppKeyboardExt, KEY_INPUT_EVENT, KeyInputArgs},
mouse::{MOUSE_CLICK_EVENT, MouseClickArgs},
touch::{TOUCH_LONG_PRESS_EVENT, TOUCH_TAP_EVENT, TouchLongPressArgs, TouchTapArgs},
};
#[derive(Debug, Clone, PartialEq)]
pub enum ClickArgsSource {
Mouse {
button: MouseButton,
position: DipPoint,
hits: HitTestInfo,
},
Touch {
position: DipPoint,
hits: HitTestInfo,
is_tap: bool,
},
Shortcut {
shortcut: Shortcut,
kind: ShortcutClick,
},
Access {
is_primary: bool,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShortcutClick {
Primary,
Context,
}
event_args! {
pub struct ClickArgs {
pub window_id: WindowId,
pub device_id: Option<InputDeviceId>,
pub source: ClickArgsSource,
pub click_count: NonZeroU32,
pub is_repeat: bool,
pub modifiers: ModifiersState,
pub target: InteractionPath,
..
fn is_in_target(&self, id: WidgetId) -> bool {
self.target.contains(id)
}
}
pub struct ShortcutArgs {
pub window_id: WindowId,
pub device_id: Option<InputDeviceId>,
pub shortcut: Shortcut,
pub repeat_count: u32,
pub actions: ShortcutActions,
..
fn is_in_target(&self, _id: WidgetId) -> bool {
false
}
}
}
impl From<MouseClickArgs> for ClickArgs {
fn from(args: MouseClickArgs) -> Self {
ClickArgs::new(
args.timestamp,
args.propagation.clone(),
args.window_id,
args.device_id,
ClickArgsSource::Mouse {
button: args.button,
position: args.position,
hits: args.hits,
},
args.click_count,
args.is_repeat,
args.modifiers,
args.target,
)
}
}
impl From<TouchTapArgs> for ClickArgs {
fn from(args: TouchTapArgs) -> Self {
ClickArgs::new(
args.timestamp,
args.propagation.clone(),
args.window_id,
args.device_id,
ClickArgsSource::Touch {
position: args.position,
hits: args.hits,
is_tap: true,
},
args.tap_count,
false,
args.modifiers,
args.target,
)
}
}
impl From<TouchLongPressArgs> for ClickArgs {
fn from(args: TouchLongPressArgs) -> Self {
ClickArgs::new(
args.timestamp,
args.propagation.clone(),
args.window_id,
args.device_id,
ClickArgsSource::Touch {
position: args.position,
hits: args.hits,
is_tap: false,
},
NonZeroU32::new(1).unwrap(),
false,
args.modifiers,
args.target,
)
}
}
impl ClickArgs {
pub fn is_primary(&self) -> bool {
match &self.source {
ClickArgsSource::Mouse { button, .. } => *button == MouseButton::Left,
ClickArgsSource::Touch { is_tap, .. } => *is_tap,
ClickArgsSource::Shortcut { kind, .. } => *kind == ShortcutClick::Primary,
ClickArgsSource::Access { is_primary } => *is_primary,
}
}
pub fn is_context(&self) -> bool {
self.click_count.get() == 1
&& match &self.source {
ClickArgsSource::Mouse { button, .. } => *button == MouseButton::Right,
ClickArgsSource::Touch { is_tap, .. } => !*is_tap,
ClickArgsSource::Shortcut { kind, .. } => *kind == ShortcutClick::Context,
ClickArgsSource::Access { is_primary } => !*is_primary,
}
}
pub fn is_mouse_btn(&self, mouse_button: MouseButton) -> bool {
match &self.source {
ClickArgsSource::Mouse { button, .. } => *button == mouse_button,
_ => false,
}
}
pub fn shortcut(&self) -> Option<Shortcut> {
match &self.source {
ClickArgsSource::Shortcut { shortcut, .. } => Some(shortcut.clone()),
_ => None,
}
}
pub fn is_single(&self) -> bool {
self.click_count.get() == 1
}
pub fn is_double(&self) -> bool {
self.click_count.get() == 2
}
pub fn is_triple(&self) -> bool {
self.click_count.get() == 3
}
pub fn is_from_mouse(&self) -> bool {
matches!(&self.source, ClickArgsSource::Mouse { .. })
}
pub fn is_from_touch(&self) -> bool {
matches!(&self.source, ClickArgsSource::Touch { .. })
}
pub fn is_from_keyboard(&self) -> bool {
matches!(&self.source, ClickArgsSource::Shortcut { .. })
}
pub fn is_from_access(&self) -> bool {
matches!(&self.source, ClickArgsSource::Access { .. })
}
pub fn position(&self) -> Option<DipPoint> {
match &self.source {
ClickArgsSource::Mouse { position, .. } => Some(*position),
ClickArgsSource::Touch { position, .. } => Some(*position),
ClickArgsSource::Shortcut { .. } | ClickArgsSource::Access { .. } => None,
}
}
}
event! {
pub static CLICK_EVENT: ClickArgs {
let _ = GESTURES_SV.read();
};
pub static SHORTCUT_EVENT: ShortcutArgs {
let _ = GESTURES_SV.read();
};
}
fn hooks() {
MOUSE_CLICK_EVENT
.hook(|args| {
CLICK_EVENT.notify(args.clone().into());
true
})
.perm();
TOUCH_TAP_EVENT
.hook(|args| {
CLICK_EVENT.notify(args.clone().into());
true
})
.perm();
KEY_INPUT_EVENT
.hook(|args| {
GESTURES_SV.write().on_key_input(args);
true
})
.perm();
TOUCH_LONG_PRESS_EVENT
.hook(|args| {
CLICK_EVENT.notify(args.clone().into());
true
})
.perm();
ACCESS_CLICK_EVENT
.hook(|args| {
GESTURES_SV.write().on_access(args);
true
})
.perm();
SHORTCUT_EVENT
.hook(|args| {
GESTURES_SV.write().on_shortcut(args);
true
})
.perm();
}
app_local! {
static GESTURES_SV: GesturesService = {
hooks();
GesturesService::new()
};
}
struct GesturesService {
click_focused: Var<Shortcuts>,
context_click_focused: Var<Shortcuts>,
shortcut_pressed_duration: Var<Duration>,
pressed_modifier: Option<(WindowId, ModifierGesture)>,
primed_starter: Option<KeyGesture>,
chords: HashMap<KeyGesture, HashSet<KeyGesture>>,
primary_clicks: Vec<(Shortcut, Arc<ShortcutTarget>)>,
context_clicks: Vec<(Shortcut, Arc<ShortcutTarget>)>,
focus: Vec<(Shortcut, Arc<ShortcutTarget>)>,
}
impl GesturesService {
fn new() -> Self {
Self {
click_focused: var([shortcut!(Enter), shortcut!(Space)].into()),
context_click_focused: var([shortcut!(ContextMenu)].into()),
shortcut_pressed_duration: var(Duration::from_millis(50)),
pressed_modifier: None,
primed_starter: None,
chords: HashMap::default(),
primary_clicks: vec![],
context_clicks: vec![],
focus: vec![],
}
}
fn register_target(&mut self, shortcuts: Shortcuts, kind: Option<ShortcutClick>, target: WidgetId) -> ShortcutsHandle {
if shortcuts.is_empty() {
return ShortcutsHandle::dummy();
}
let (owner, handle) = ShortcutsHandle::new();
let target = Arc::new(ShortcutTarget {
widget_id: target,
last_found: Mutex::new(None),
handle: owner,
});
let collection = match kind {
Some(ShortcutClick::Primary) => &mut self.primary_clicks,
Some(ShortcutClick::Context) => &mut self.context_clicks,
None => &mut self.focus,
};
if collection.len() > 500 {
collection.retain(|(_, e)| !e.handle.is_dropped());
}
for s in shortcuts.0 {
if let Shortcut::Chord(c) = &s {
self.chords.entry(c.starter.clone()).or_default().insert(c.complement.clone());
}
collection.push((s, target.clone()));
}
handle
}
fn on_key_input(&mut self, args: &KeyInputArgs) {
let key = args.shortcut_key();
if !args.propagation.is_stopped() && !matches!(key, Key::Unidentified) {
match args.state {
KeyState::Pressed => {
if let Ok(gesture_key) = GestureKey::try_from(key.clone()) {
self.on_shortcut_pressed(Shortcut::Gesture(KeyGesture::new(args.modifiers, gesture_key)), args);
self.pressed_modifier = None;
} else if let Ok(mod_gesture) = ModifierGesture::try_from(key) {
if args.repeat_count == 0 {
self.pressed_modifier = Some((args.target.window_id(), mod_gesture));
}
} else {
self.pressed_modifier = None;
self.primed_starter = None;
}
}
KeyState::Released => {
if let Ok(mod_gesture) = ModifierGesture::try_from(key)
&& let Some((window_id, gesture)) = self.pressed_modifier.take()
&& args.modifiers.is_empty()
&& window_id == args.target.window_id()
&& mod_gesture == gesture
{
self.on_shortcut_pressed(Shortcut::Modifier(mod_gesture), args);
}
}
}
} else {
self.primed_starter = None;
self.pressed_modifier = None;
}
}
fn on_shortcut_pressed(&mut self, mut shortcut: Shortcut, key_args: &KeyInputArgs) {
if let Some(starter) = self.primed_starter.take()
&& let Shortcut::Gesture(g) = &shortcut
&& let Some(complements) = self.chords.get(&starter)
&& complements.contains(g)
{
shortcut = Shortcut::Chord(KeyChord {
starter,
complement: g.clone(),
});
}
let actions = ShortcutActions::new(self, shortcut.clone());
SHORTCUT_EVENT.notify(ShortcutArgs::new(
key_args.timestamp,
key_args.propagation.clone(),
key_args.window_id,
key_args.device_id,
shortcut,
key_args.repeat_count,
actions,
));
}
fn on_shortcut(&mut self, args: &ShortcutArgs) {
if args.actions.has_actions() {
tracing::trace!("shortcut pressed {:?}", &args.shortcut);
args.actions
.run(args.timestamp, args.propagation(), args.device_id, args.repeat_count);
} else if let Shortcut::Gesture(k) = &args.shortcut
&& self.chords.contains_key(k)
{
tracing::trace!("shortcut primed chord {k:?}");
self.primed_starter = Some(k.clone());
}
}
fn on_access(&mut self, args: &AccessClickArgs) {
if let Some(tree) = WINDOWS.widget_tree(args.target.window_id())
&& let Some(wgt) = tree.get(args.target.widget_id())
{
let path = wgt.interaction_path();
if !path.interactivity().is_blocked() {
let args = ClickArgs::now(
args.target.window_id(),
None,
ClickArgsSource::Access {
is_primary: args.is_primary,
},
NonZeroU32::new(1).unwrap(),
false,
ModifiersState::empty(),
path,
);
CLICK_EVENT.notify(args);
}
}
}
fn cleanup(&mut self) {
self.primary_clicks.retain(|(_, e)| !e.handle.is_dropped());
self.context_clicks.retain(|(_, e)| !e.handle.is_dropped());
self.focus.retain(|(_, e)| !e.handle.is_dropped());
}
}
pub struct GESTURES;
struct ShortcutTarget {
widget_id: WidgetId,
last_found: Mutex<Option<WidgetPath>>,
handle: HandleOwner<()>,
}
impl ShortcutTarget {
fn resolve_path(&self) -> Option<InteractionPath> {
let mut found = self.last_found.lock();
if let Some(found) = &mut *found
&& let Some(tree) = WINDOWS.widget_tree(found.window_id())
&& let Some(w) = tree.get(found.widget_id())
{
let path = w.interaction_path();
*found = path.as_path().clone();
return path.unblocked();
}
if let Some(w) = WINDOWS.widget_info(self.widget_id) {
let path = w.interaction_path();
*found = Some(path.as_path().clone());
return path.unblocked();
}
None
}
}
impl GESTURES {
pub fn click_focused(&self) -> Var<Shortcuts> {
GESTURES_SV.read().click_focused.clone()
}
pub fn context_click_focused(&self) -> Var<Shortcuts> {
GESTURES_SV.read().context_click_focused.clone()
}
pub fn shortcut_pressed_duration(&self) -> Var<Duration> {
GESTURES_SV.read().shortcut_pressed_duration.clone()
}
pub fn click_shortcut(&self, shortcuts: impl Into<Shortcuts>, kind: ShortcutClick, target: WidgetId) -> ShortcutsHandle {
GESTURES_SV.write().register_target(shortcuts.into(), Some(kind), target)
}
pub fn focus_shortcut(&self, shortcuts: impl Into<Shortcuts>, target: WidgetId) -> ShortcutsHandle {
GESTURES_SV.write().register_target(shortcuts.into(), None, target)
}
pub fn shortcut_actions(&self, shortcut: Shortcut) -> ShortcutActions {
ShortcutActions::new(&mut GESTURES_SV.write(), shortcut)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ShortcutActions {
shortcut: Shortcut,
focus: Option<WidgetId>,
click: Option<(InteractionPath, ShortcutClick)>,
commands: Vec<Command>,
}
impl ShortcutActions {
fn new(gestures: &mut GesturesService, shortcut: Shortcut) -> ShortcutActions {
let focused = FOCUS.focused().get();
enum Kind {
Click(InteractionPath, ShortcutClick),
Command(InteractionPath, Command),
Focus(InteractionPath),
}
impl Kind {
fn kind_key(&self) -> u8 {
match self {
Kind::Click(p, s) => {
if p.interactivity().is_enabled() {
match s {
ShortcutClick::Primary => 0,
ShortcutClick::Context => 2,
}
} else {
match s {
ShortcutClick::Primary => 10,
ShortcutClick::Context => 12,
}
}
}
Kind::Command(p, _) => {
if p.interactivity().is_enabled() {
1
} else {
11
}
}
Kind::Focus(p) => {
if p.interactivity().is_enabled() {
4
} else {
14
}
}
}
}
}
fn distance_key(focused: &Option<InteractionPath>, p: &InteractionPath) -> u32 {
let mut key = u32::MAX - 1;
if let Some(focused) = focused
&& p.window_id() == focused.window_id()
{
key -= 1;
if let Some(i) = p.widgets_path().iter().position(|&id| id == focused.widget_id()) {
key = (p.widgets_path().len() - i) as u32;
} else if let Some(i) = focused.widgets_path().iter().position(|&id| id == p.widget_id()) {
key = key / 2 + (focused.widgets_path().len() - i) as u32;
}
}
key
}
let mut some_primary_dropped = false;
let primary_click_matches = gestures.primary_clicks.iter().filter_map(|(s, entry)| {
if entry.handle.is_dropped() {
some_primary_dropped = true;
return None;
}
if *s != shortcut {
return None;
}
let p = entry.resolve_path()?;
Some((distance_key(&focused, &p), Kind::Click(p, ShortcutClick::Primary)))
});
let mut some_ctx_dropped = false;
let context_click_matches = gestures.context_clicks.iter().filter_map(|(s, entry)| {
if entry.handle.is_dropped() {
some_ctx_dropped = true;
return None;
}
if *s != shortcut {
return None;
}
let p = entry.resolve_path()?;
Some((distance_key(&focused, &p), Kind::Click(p, ShortcutClick::Context)))
});
let mut some_focus_dropped = false;
let focus_matches = gestures.focus.iter().filter_map(|(s, entry)| {
if entry.handle.is_dropped() {
some_focus_dropped = true;
return None;
}
if *s != shortcut {
return None;
}
let p = entry.resolve_path()?;
Some((distance_key(&focused, &p), Kind::Focus(p)))
});
let mut cmd_window = vec![];
let mut cmd_app = vec![];
let cmd_matches = EVENTS.commands().into_iter().filter_map(|cmd| {
if !cmd.shortcut_matches(&shortcut) {
return None;
}
match cmd.scope() {
CommandScope::Window(w) => {
if let Some(f) = &focused
&& f.window_id() == w
{
cmd_window.push(cmd);
}
}
CommandScope::Widget(id) => {
if let Some(info) = WINDOWS.widget_info(id) {
let p = info.interaction_path();
return Some((distance_key(&focused, &p), Kind::Command(p, cmd)));
}
}
CommandScope::App => cmd_app.push(cmd),
}
None
});
let mut best_kind = u8::MAX;
let mut best_distance = u32::MAX;
let mut best = None;
for (distance_key, choice) in primary_click_matches
.chain(cmd_matches)
.chain(context_click_matches)
.chain(focus_matches)
{
let kind_key = choice.kind_key();
match kind_key.cmp(&best_kind) {
std::cmp::Ordering::Less => {
best_kind = kind_key;
best_distance = distance_key;
best = Some(choice);
}
std::cmp::Ordering::Equal => {
if distance_key < best_distance {
best_distance = distance_key;
best = Some(choice);
}
}
std::cmp::Ordering::Greater => {}
}
}
let mut click = None;
let mut focus = None;
let mut commands = vec![];
match best {
Some(k) => match k {
Kind::Click(p, s) => click = Some((p, s)),
Kind::Command(_, cmd) => commands.push(cmd),
Kind::Focus(p) => focus = Some(p.widget_id()),
},
None => {
if let Some(p) = focused {
click = if gestures.click_focused.with(|c| c.contains(&shortcut)) {
Some((p, ShortcutClick::Primary))
} else if gestures.context_click_focused.with(|c| c.contains(&shortcut)) {
Some((p, ShortcutClick::Context))
} else {
None
};
}
}
}
commands.append(&mut cmd_window);
commands.append(&mut cmd_app);
if some_primary_dropped || some_ctx_dropped || some_focus_dropped {
gestures.cleanup();
}
Self {
shortcut,
focus,
click,
commands,
}
}
pub fn shortcut(&self) -> &Shortcut {
&self.shortcut
}
pub fn focus(&self) -> Option<FocusTarget> {
if let Some((p, _)) = &self.click {
return Some(FocusTarget::Direct { target: p.widget_id() });
} else if let Some(c) = self.commands.first()
&& let CommandScope::Widget(w) = c.scope()
&& FOCUS.focused().with(|f| f.as_ref().map(|p| !p.contains(w)).unwrap_or(true))
{
return Some(FocusTarget::Direct { target: w });
}
self.focus.map(|target| FocusTarget::DirectOrRelated {
target,
navigation_origin: true,
})
}
pub fn click(&self) -> Option<(&InteractionPath, ShortcutClick)> {
self.click.as_ref().map(|(p, k)| (p, *k))
}
pub fn commands(&self) -> &[Command] {
&self.commands
}
pub fn has_actions(&self) -> bool {
self.click.is_some() || self.focus.is_some() || !self.commands.is_empty()
}
fn run(&self, timestamp: DInstant, propagation: &EventPropagationHandle, device_id: Option<InputDeviceId>, repeat_count: u32) {
if let Some(target) = self.focus() {
tracing::trace!("shortcut focus {target:?}");
FOCUS.focus(FocusRequest::new(target, true));
}
if let Some((target, kind)) = &self.click {
tracing::trace!("shortcut click {target:?} {kind:?}");
let args = ClickArgs::new(
timestamp,
propagation.clone(),
target.window_id(),
device_id,
ClickArgsSource::Shortcut {
shortcut: self.shortcut.clone(),
kind: *kind,
},
NonZeroU32::new(repeat_count.saturating_add(1)).unwrap(),
repeat_count > 0,
self.shortcut.modifiers_state(),
target.clone(),
);
CLICK_EVENT.notify(args);
}
for command in &self.commands {
tracing::trace!("shortcut cmd {:?}", command);
command.notify_linked(propagation.clone(), None);
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[repr(transparent)]
#[must_use = "the shortcuts claim is removed if the handle is dropped"]
pub struct ShortcutsHandle(Handle<()>);
impl ShortcutsHandle {
pub(super) fn new() -> (HandleOwner<()>, Self) {
let (owner, handle) = Handle::new(());
(owner, ShortcutsHandle(handle))
}
pub fn dummy() -> Self {
ShortcutsHandle(Handle::dummy(()))
}
pub fn perm(self) {
self.0.perm();
}
pub fn is_permanent(&self) -> bool {
self.0.is_permanent()
}
pub fn release(self) {
self.0.force_drop();
}
pub fn is_released(&self) -> bool {
self.0.is_dropped()
}
pub fn downgrade(&self) -> WeakShortcutsHandle {
WeakShortcutsHandle(self.0.downgrade())
}
}
#[derive(Clone, PartialEq, Eq, Hash, Default, Debug)]
pub struct WeakShortcutsHandle(pub(super) WeakHandle<()>);
impl WeakShortcutsHandle {
pub fn new() -> Self {
Self(WeakHandle::new())
}
pub fn upgrade(&self) -> Option<ShortcutsHandle> {
self.0.upgrade().map(ShortcutsHandle)
}
}
pub trait HeadlessAppGestureExt {
fn press_shortcut(&mut self, window_id: WindowId, shortcut: impl Into<Shortcut>);
}
impl HeadlessAppGestureExt for HeadlessApp {
fn press_shortcut(&mut self, window_id: WindowId, shortcut: impl Into<Shortcut>) {
let shortcut = shortcut.into();
match shortcut {
Shortcut::Modifier(m) => {
let (code, key) = m.left_key();
self.press_key(window_id, code, KeyLocation::Standard, key);
}
Shortcut::Gesture(g) => match g.key {
GestureKey::Key(k) => self.press_modified_key(
window_id,
g.modifiers,
KeyCode::Unidentified(NativeKeyCode::Unidentified),
KeyLocation::Standard,
k,
),
GestureKey::Code(c) => self.press_modified_key(window_id, g.modifiers, c, KeyLocation::Standard, Key::Unidentified),
},
Shortcut::Chord(c) => {
self.press_shortcut(window_id, c.starter);
self.press_shortcut(window_id, c.complement);
}
}
}
}
pub trait CommandShortcutMatchesExt: CommandShortcutExt {
fn shortcut_matches(self, shortcut: &Shortcut) -> bool;
}
impl CommandShortcutMatchesExt for Command {
fn shortcut_matches(self, shortcut: &Shortcut) -> bool {
if !self.has_handlers().get() {
return false;
}
let s = self.shortcut();
if s.with(|s| !s.contains(shortcut)) {
return false;
}
let filter = self.shortcut_filter().get();
if filter.is_empty() {
return true;
}
if filter.contains(ShortcutFilter::CMD_ENABLED) && !self.is_enabled().get() {
return false;
}
match self.scope() {
CommandScope::App => filter == ShortcutFilter::CMD_ENABLED,
CommandScope::Window(id) => {
if filter.contains(ShortcutFilter::FOCUSED) {
FOCUS.focused().with(|p| {
let p = match p {
Some(p) => p,
None => return false,
};
if p.window_id() != id {
return false;
}
!filter.contains(ShortcutFilter::ENABLED) || p.interaction_path().next().map(|i| i.is_enabled()).unwrap_or(false)
})
} else if filter.contains(ShortcutFilter::ENABLED) {
let tree = match WINDOWS.widget_tree(id) {
Some(t) => t,
None => return false,
};
tree.root().interactivity().is_enabled()
} else {
true
}
}
CommandScope::Widget(id) => {
if filter.contains(ShortcutFilter::FOCUSED) {
FOCUS.focused().with(|p| {
let p = match p {
Some(p) => p,
None => return false,
};
if !p.contains(id) {
return false;
}
!filter.contains(ShortcutFilter::ENABLED) || p.contains_enabled(id)
})
} else if filter.contains(ShortcutFilter::ENABLED) {
if let Some(w) = WINDOWS.widget_info(id) {
return w.interactivity().is_enabled();
}
false
} else {
true
}
}
}
}
}
on_process_start!(|args| {
if args.yield_until_app() {
return;
}
APP.on_init(hn!(|args| {
if !args.is_minimal {
let _ = GESTURES_SV.read();
}
}));
});