1use parking_lot::Mutex;
13use std::{
14 collections::{HashMap, HashSet},
15 convert::TryFrom,
16 num::NonZeroU32,
17 sync::Arc,
18 time::Duration,
19};
20use zng_app::{
21 APP, DInstant, HeadlessApp,
22 access::{ACCESS_CLICK_EVENT, AccessClickArgs},
23 event::{AnyEventArgs as _, Command, CommandScope, EVENTS, EventPropagationHandle, event, event_args},
24 hn,
25 shortcut::{
26 CommandShortcutExt, GestureKey, KeyChord, KeyGesture, ModifierGesture, ModifiersState, Shortcut, ShortcutFilter, Shortcuts,
27 shortcut,
28 },
29 view_process::raw_device_events::InputDeviceId,
30 widget::{
31 WidgetId,
32 info::{HitTestInfo, InteractionPath, WidgetPath},
33 },
34 window::WindowId,
35};
36use zng_app_context::app_local;
37use zng_env::on_process_start;
38use zng_ext_window::WINDOWS;
39use zng_handle::{Handle, HandleOwner, WeakHandle};
40use zng_layout::unit::DipPoint;
41use zng_var::{Var, var};
42use zng_view_api::{
43 keyboard::{Key, KeyCode, KeyLocation, KeyState, NativeKeyCode},
44 mouse::MouseButton,
45};
46
47use crate::{
48 focus::{FOCUS, FocusRequest, FocusTarget},
49 keyboard::{HeadlessAppKeyboardExt, KEY_INPUT_EVENT, KeyInputArgs},
50 mouse::{MOUSE_CLICK_EVENT, MouseClickArgs},
51 touch::{TOUCH_LONG_PRESS_EVENT, TOUCH_TAP_EVENT, TouchLongPressArgs, TouchTapArgs},
52};
53
54#[derive(Debug, Clone, PartialEq)]
56pub enum ClickArgsSource {
57 Mouse {
59 button: MouseButton,
61
62 position: DipPoint,
64
65 hits: HitTestInfo,
68 },
69
70 Touch {
72 position: DipPoint,
74
75 hits: HitTestInfo,
78
79 is_tap: bool,
81 },
82
83 Shortcut {
85 shortcut: Shortcut,
87 kind: ShortcutClick,
89 },
90
91 Access {
95 is_primary: bool,
97 },
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum ShortcutClick {
103 Primary,
105 Context,
107}
108
109event_args! {
110 pub struct ClickArgs {
114 pub window_id: WindowId,
116
117 pub device_id: Option<InputDeviceId>,
121
122 pub source: ClickArgsSource,
124
125 pub click_count: NonZeroU32,
129
130 pub is_repeat: bool,
132
133 pub modifiers: ModifiersState,
135
136 pub target: InteractionPath,
138
139 ..
140
141 fn is_in_target(&self, id: WidgetId) -> bool {
145 self.target.contains(id)
146 }
147 }
148
149 pub struct ShortcutArgs {
151 pub window_id: WindowId,
153
154 pub device_id: Option<InputDeviceId>,
158
159 pub shortcut: Shortcut,
161
162 pub repeat_count: u32,
166
167 pub actions: ShortcutActions,
169
170 ..
171
172 fn is_in_target(&self, _id: WidgetId) -> bool {
174 false
175 }
176 }
177}
178impl From<MouseClickArgs> for ClickArgs {
179 fn from(args: MouseClickArgs) -> Self {
180 ClickArgs::new(
181 args.timestamp,
182 args.propagation.clone(),
183 args.window_id,
184 args.device_id,
185 ClickArgsSource::Mouse {
186 button: args.button,
187 position: args.position,
188 hits: args.hits,
189 },
190 args.click_count,
191 args.is_repeat,
192 args.modifiers,
193 args.target,
194 )
195 }
196}
197impl From<TouchTapArgs> for ClickArgs {
198 fn from(args: TouchTapArgs) -> Self {
199 ClickArgs::new(
200 args.timestamp,
201 args.propagation.clone(),
202 args.window_id,
203 args.device_id,
204 ClickArgsSource::Touch {
205 position: args.position,
206 hits: args.hits,
207 is_tap: true,
208 },
209 args.tap_count,
210 false,
211 args.modifiers,
212 args.target,
213 )
214 }
215}
216impl From<TouchLongPressArgs> for ClickArgs {
217 fn from(args: TouchLongPressArgs) -> Self {
218 ClickArgs::new(
219 args.timestamp,
220 args.propagation.clone(),
221 args.window_id,
222 args.device_id,
223 ClickArgsSource::Touch {
224 position: args.position,
225 hits: args.hits,
226 is_tap: false,
227 },
228 NonZeroU32::new(1).unwrap(),
229 false,
230 args.modifiers,
231 args.target,
232 )
233 }
234}
235impl ClickArgs {
236 pub fn is_primary(&self) -> bool {
243 match &self.source {
244 ClickArgsSource::Mouse { button, .. } => *button == MouseButton::Left,
245 ClickArgsSource::Touch { is_tap, .. } => *is_tap,
246 ClickArgsSource::Shortcut { kind, .. } => *kind == ShortcutClick::Primary,
247 ClickArgsSource::Access { is_primary } => *is_primary,
248 }
249 }
250
251 pub fn is_context(&self) -> bool {
257 self.click_count.get() == 1
258 && match &self.source {
259 ClickArgsSource::Mouse { button, .. } => *button == MouseButton::Right,
260 ClickArgsSource::Touch { is_tap, .. } => !*is_tap,
261 ClickArgsSource::Shortcut { kind, .. } => *kind == ShortcutClick::Context,
262 ClickArgsSource::Access { is_primary } => !*is_primary,
263 }
264 }
265
266 pub fn is_mouse_btn(&self, mouse_button: MouseButton) -> bool {
268 match &self.source {
269 ClickArgsSource::Mouse { button, .. } => *button == mouse_button,
270 _ => false,
271 }
272 }
273
274 pub fn shortcut(&self) -> Option<Shortcut> {
276 match &self.source {
277 ClickArgsSource::Shortcut { shortcut, .. } => Some(shortcut.clone()),
278 _ => None,
279 }
280 }
281
282 pub fn is_single(&self) -> bool {
284 self.click_count.get() == 1
285 }
286
287 pub fn is_double(&self) -> bool {
289 self.click_count.get() == 2
290 }
291
292 pub fn is_triple(&self) -> bool {
294 self.click_count.get() == 3
295 }
296
297 pub fn is_from_mouse(&self) -> bool {
299 matches!(&self.source, ClickArgsSource::Mouse { .. })
300 }
301
302 pub fn is_from_touch(&self) -> bool {
304 matches!(&self.source, ClickArgsSource::Touch { .. })
305 }
306
307 pub fn is_from_keyboard(&self) -> bool {
309 matches!(&self.source, ClickArgsSource::Shortcut { .. })
310 }
311
312 pub fn is_from_access(&self) -> bool {
317 matches!(&self.source, ClickArgsSource::Access { .. })
318 }
319
320 pub fn position(&self) -> Option<DipPoint> {
324 match &self.source {
325 ClickArgsSource::Mouse { position, .. } => Some(*position),
326 ClickArgsSource::Touch { position, .. } => Some(*position),
327 ClickArgsSource::Shortcut { .. } | ClickArgsSource::Access { .. } => None,
328 }
329 }
330}
331
332event! {
333 pub static CLICK_EVENT: ClickArgs {
337 let _ = GESTURES_SV.read();
338 };
339
340 pub static SHORTCUT_EVENT: ShortcutArgs {
348 let _ = GESTURES_SV.read();
349 };
350}
351
352fn hooks() {
353 MOUSE_CLICK_EVENT
355 .hook(|args| {
356 CLICK_EVENT.notify(args.clone().into());
357 true
358 })
359 .perm();
360 TOUCH_TAP_EVENT
362 .hook(|args| {
363 CLICK_EVENT.notify(args.clone().into());
364 true
365 })
366 .perm();
367
368 KEY_INPUT_EVENT
370 .hook(|args| {
371 GESTURES_SV.write().on_key_input(args);
372 true
373 })
374 .perm();
375
376 TOUCH_LONG_PRESS_EVENT
378 .hook(|args| {
379 CLICK_EVENT.notify(args.clone().into());
380 true
381 })
382 .perm();
383
384 ACCESS_CLICK_EVENT
386 .hook(|args| {
387 GESTURES_SV.write().on_access(args);
388 true
389 })
390 .perm();
391
392 SHORTCUT_EVENT
394 .hook(|args| {
395 GESTURES_SV.write().on_shortcut(args);
396 true
397 })
398 .perm();
399}
400
401app_local! {
402 static GESTURES_SV: GesturesService = {
403 hooks();
404 GesturesService::new()
405 };
406}
407
408struct GesturesService {
409 click_focused: Var<Shortcuts>,
410 context_click_focused: Var<Shortcuts>,
411 shortcut_pressed_duration: Var<Duration>,
412
413 pressed_modifier: Option<(WindowId, ModifierGesture)>,
414 primed_starter: Option<KeyGesture>,
415 chords: HashMap<KeyGesture, HashSet<KeyGesture>>,
416
417 primary_clicks: Vec<(Shortcut, Arc<ShortcutTarget>)>,
418 context_clicks: Vec<(Shortcut, Arc<ShortcutTarget>)>,
419 focus: Vec<(Shortcut, Arc<ShortcutTarget>)>,
420}
421impl GesturesService {
422 fn new() -> Self {
423 Self {
424 click_focused: var([shortcut!(Enter), shortcut!(Space)].into()),
425 context_click_focused: var([shortcut!(ContextMenu)].into()),
426 shortcut_pressed_duration: var(Duration::from_millis(50)),
427
428 pressed_modifier: None,
429 primed_starter: None,
430 chords: HashMap::default(),
431
432 primary_clicks: vec![],
433 context_clicks: vec![],
434 focus: vec![],
435 }
436 }
437
438 fn register_target(&mut self, shortcuts: Shortcuts, kind: Option<ShortcutClick>, target: WidgetId) -> ShortcutsHandle {
439 if shortcuts.is_empty() {
440 return ShortcutsHandle::dummy();
441 }
442
443 let (owner, handle) = ShortcutsHandle::new();
444 let target = Arc::new(ShortcutTarget {
445 widget_id: target,
446 last_found: Mutex::new(None),
447 handle: owner,
448 });
449
450 let collection = match kind {
451 Some(ShortcutClick::Primary) => &mut self.primary_clicks,
452 Some(ShortcutClick::Context) => &mut self.context_clicks,
453 None => &mut self.focus,
454 };
455
456 if collection.len() > 500 {
457 collection.retain(|(_, e)| !e.handle.is_dropped());
458 }
459
460 for s in shortcuts.0 {
461 if let Shortcut::Chord(c) = &s {
462 self.chords.entry(c.starter.clone()).or_default().insert(c.complement.clone());
463 }
464
465 collection.push((s, target.clone()));
466 }
467
468 handle
469 }
470
471 fn on_key_input(&mut self, args: &KeyInputArgs) {
472 let key = args.shortcut_key();
473 if !args.propagation.is_stopped() && !matches!(key, Key::Unidentified) {
474 match args.state {
475 KeyState::Pressed => {
476 if let Ok(gesture_key) = GestureKey::try_from(key.clone()) {
477 self.on_shortcut_pressed(Shortcut::Gesture(KeyGesture::new(args.modifiers, gesture_key)), args);
478 self.pressed_modifier = None;
479 } else if let Ok(mod_gesture) = ModifierGesture::try_from(key) {
480 if args.repeat_count == 0 {
481 self.pressed_modifier = Some((args.target.window_id(), mod_gesture));
482 }
483 } else {
484 self.pressed_modifier = None;
485 self.primed_starter = None;
486 }
487 }
488 KeyState::Released => {
489 if let Ok(mod_gesture) = ModifierGesture::try_from(key)
490 && let Some((window_id, gesture)) = self.pressed_modifier.take()
491 && args.modifiers.is_empty()
492 && window_id == args.target.window_id()
493 && mod_gesture == gesture
494 {
495 self.on_shortcut_pressed(Shortcut::Modifier(mod_gesture), args);
496 }
497 }
498 }
499 } else {
500 self.primed_starter = None;
502 self.pressed_modifier = None;
503 }
504 }
505 fn on_shortcut_pressed(&mut self, mut shortcut: Shortcut, key_args: &KeyInputArgs) {
506 if let Some(starter) = self.primed_starter.take()
507 && let Shortcut::Gesture(g) = &shortcut
508 && let Some(complements) = self.chords.get(&starter)
509 && complements.contains(g)
510 {
511 shortcut = Shortcut::Chord(KeyChord {
512 starter,
513 complement: g.clone(),
514 });
515 }
516
517 let actions = ShortcutActions::new(self, shortcut.clone());
518
519 SHORTCUT_EVENT.notify(ShortcutArgs::new(
520 key_args.timestamp,
521 key_args.propagation.clone(),
522 key_args.window_id,
523 key_args.device_id,
524 shortcut,
525 key_args.repeat_count,
526 actions,
527 ));
528 }
529
530 fn on_shortcut(&mut self, args: &ShortcutArgs) {
531 if args.actions.has_actions() {
532 tracing::trace!("shortcut pressed {:?}", &args.shortcut);
533 args.actions
534 .run(args.timestamp, args.propagation(), args.device_id, args.repeat_count);
535 } else if let Shortcut::Gesture(k) = &args.shortcut
536 && self.chords.contains_key(k)
537 {
538 tracing::trace!("shortcut primed chord {k:?}");
539 self.primed_starter = Some(k.clone());
540 }
541 }
542
543 fn on_access(&mut self, args: &AccessClickArgs) {
544 if let Some(tree) = WINDOWS.widget_tree(args.target.window_id())
545 && let Some(wgt) = tree.get(args.target.widget_id())
546 {
547 let path = wgt.interaction_path();
548 if !path.interactivity().is_blocked() {
549 let args = ClickArgs::now(
550 args.target.window_id(),
551 None,
552 ClickArgsSource::Access {
553 is_primary: args.is_primary,
554 },
555 NonZeroU32::new(1).unwrap(),
556 false,
557 ModifiersState::empty(),
558 path,
559 );
560 CLICK_EVENT.notify(args);
561 }
562 }
563 }
564
565 fn cleanup(&mut self) {
566 self.primary_clicks.retain(|(_, e)| !e.handle.is_dropped());
567 self.context_clicks.retain(|(_, e)| !e.handle.is_dropped());
568 self.focus.retain(|(_, e)| !e.handle.is_dropped());
569 }
570}
571
572pub struct GESTURES;
634struct ShortcutTarget {
635 widget_id: WidgetId,
636 last_found: Mutex<Option<WidgetPath>>,
637 handle: HandleOwner<()>,
638}
639impl ShortcutTarget {
640 fn resolve_path(&self) -> Option<InteractionPath> {
641 let mut found = self.last_found.lock();
642 if let Some(found) = &mut *found
643 && let Some(tree) = WINDOWS.widget_tree(found.window_id())
644 && let Some(w) = tree.get(found.widget_id())
645 {
646 let path = w.interaction_path();
647 *found = path.as_path().clone();
648
649 return path.unblocked();
650 }
651
652 if let Some(w) = WINDOWS.widget_info(self.widget_id) {
653 let path = w.interaction_path();
654 *found = Some(path.as_path().clone());
655
656 return path.unblocked();
657 }
658
659 None
660 }
661}
662impl GESTURES {
663 pub fn click_focused(&self) -> Var<Shortcuts> {
670 GESTURES_SV.read().click_focused.clone()
671 }
672
673 pub fn context_click_focused(&self) -> Var<Shortcuts> {
680 GESTURES_SV.read().context_click_focused.clone()
681 }
682
683 pub fn shortcut_pressed_duration(&self) -> Var<Duration> {
688 GESTURES_SV.read().shortcut_pressed_duration.clone()
689 }
690
691 pub fn click_shortcut(&self, shortcuts: impl Into<Shortcuts>, kind: ShortcutClick, target: WidgetId) -> ShortcutsHandle {
693 GESTURES_SV.write().register_target(shortcuts.into(), Some(kind), target)
694 }
695
696 pub fn focus_shortcut(&self, shortcuts: impl Into<Shortcuts>, target: WidgetId) -> ShortcutsHandle {
700 GESTURES_SV.write().register_target(shortcuts.into(), None, target)
701 }
702
703 pub fn shortcut_actions(&self, shortcut: Shortcut) -> ShortcutActions {
709 ShortcutActions::new(&mut GESTURES_SV.write(), shortcut)
710 }
711}
712
713#[derive(Debug, Clone, PartialEq)]
719pub struct ShortcutActions {
720 shortcut: Shortcut,
721
722 focus: Option<WidgetId>,
723 click: Option<(InteractionPath, ShortcutClick)>,
724 commands: Vec<Command>,
725}
726impl ShortcutActions {
727 fn new(gestures: &mut GesturesService, shortcut: Shortcut) -> ShortcutActions {
728 let focused = FOCUS.focused().get();
749
750 enum Kind {
751 Click(InteractionPath, ShortcutClick),
752 Command(InteractionPath, Command),
753 Focus(InteractionPath),
754 }
755 impl Kind {
756 fn kind_key(&self) -> u8 {
757 match self {
758 Kind::Click(p, s) => {
759 if p.interactivity().is_enabled() {
760 match s {
761 ShortcutClick::Primary => 0,
762 ShortcutClick::Context => 2,
763 }
764 } else {
765 match s {
766 ShortcutClick::Primary => 10,
767 ShortcutClick::Context => 12,
768 }
769 }
770 }
771 Kind::Command(p, _) => {
772 if p.interactivity().is_enabled() {
773 1
774 } else {
775 11
776 }
777 }
778 Kind::Focus(p) => {
779 if p.interactivity().is_enabled() {
780 4
781 } else {
782 14
783 }
784 }
785 }
786 }
787 }
788
789 fn distance_key(focused: &Option<InteractionPath>, p: &InteractionPath) -> u32 {
790 let mut key = u32::MAX - 1;
791 if let Some(focused) = focused
792 && p.window_id() == focused.window_id()
793 {
794 key -= 1;
795 if let Some(i) = p.widgets_path().iter().position(|&id| id == focused.widget_id()) {
796 key = (p.widgets_path().len() - i) as u32;
798 } else if let Some(i) = focused.widgets_path().iter().position(|&id| id == p.widget_id()) {
799 key = key / 2 + (focused.widgets_path().len() - i) as u32;
801 }
802 }
803 key
804 }
805
806 let mut some_primary_dropped = false;
807 let primary_click_matches = gestures.primary_clicks.iter().filter_map(|(s, entry)| {
808 if entry.handle.is_dropped() {
809 some_primary_dropped = true;
810 return None;
811 }
812 if *s != shortcut {
813 return None;
814 }
815
816 let p = entry.resolve_path()?;
817 Some((distance_key(&focused, &p), Kind::Click(p, ShortcutClick::Primary)))
818 });
819
820 let mut some_ctx_dropped = false;
821 let context_click_matches = gestures.context_clicks.iter().filter_map(|(s, entry)| {
822 if entry.handle.is_dropped() {
823 some_ctx_dropped = true;
824 return None;
825 }
826 if *s != shortcut {
827 return None;
828 }
829
830 let p = entry.resolve_path()?;
831 Some((distance_key(&focused, &p), Kind::Click(p, ShortcutClick::Context)))
832 });
833
834 let mut some_focus_dropped = false;
835 let focus_matches = gestures.focus.iter().filter_map(|(s, entry)| {
836 if entry.handle.is_dropped() {
837 some_focus_dropped = true;
838 return None;
839 }
840 if *s != shortcut {
841 return None;
842 }
843
844 let p = entry.resolve_path()?;
845 Some((distance_key(&focused, &p), Kind::Focus(p)))
846 });
847
848 let mut cmd_window = vec![];
849 let mut cmd_app = vec![];
850 let cmd_matches = EVENTS.commands().into_iter().filter_map(|cmd| {
851 if !cmd.shortcut_matches(&shortcut) {
852 return None;
853 }
854
855 match cmd.scope() {
856 CommandScope::Window(w) => {
857 if let Some(f) = &focused
858 && f.window_id() == w
859 {
860 cmd_window.push(cmd);
861 }
862 }
863 CommandScope::Widget(id) => {
864 if let Some(info) = WINDOWS.widget_info(id) {
865 let p = info.interaction_path();
866 return Some((distance_key(&focused, &p), Kind::Command(p, cmd)));
867 }
868 }
869 CommandScope::App => cmd_app.push(cmd),
870 }
871
872 None
873 });
874
875 let mut best_kind = u8::MAX;
876 let mut best_distance = u32::MAX;
877 let mut best = None;
878
879 for (distance_key, choice) in primary_click_matches
880 .chain(cmd_matches)
881 .chain(context_click_matches)
882 .chain(focus_matches)
883 {
884 let kind_key = choice.kind_key();
885 match kind_key.cmp(&best_kind) {
886 std::cmp::Ordering::Less => {
887 best_kind = kind_key;
888 best_distance = distance_key;
889 best = Some(choice);
890 }
891 std::cmp::Ordering::Equal => {
892 if distance_key < best_distance {
893 best_distance = distance_key;
894 best = Some(choice);
895 }
896 }
897 std::cmp::Ordering::Greater => {}
898 }
899 }
900
901 let mut click = None;
902 let mut focus = None;
903 let mut commands = vec![];
904
905 match best {
906 Some(k) => match k {
907 Kind::Click(p, s) => click = Some((p, s)),
908 Kind::Command(_, cmd) => commands.push(cmd),
909 Kind::Focus(p) => focus = Some(p.widget_id()),
910 },
911 None => {
912 if let Some(p) = focused {
913 click = if gestures.click_focused.with(|c| c.contains(&shortcut)) {
914 Some((p, ShortcutClick::Primary))
915 } else if gestures.context_click_focused.with(|c| c.contains(&shortcut)) {
916 Some((p, ShortcutClick::Context))
917 } else {
918 None
919 };
920 }
921 }
922 }
923
924 commands.append(&mut cmd_window);
925 commands.append(&mut cmd_app);
926
927 if some_primary_dropped || some_ctx_dropped || some_focus_dropped {
928 gestures.cleanup();
929 }
930
931 Self {
932 shortcut,
933 focus,
934 click,
935 commands,
936 }
937 }
938
939 pub fn shortcut(&self) -> &Shortcut {
941 &self.shortcut
942 }
943
944 pub fn focus(&self) -> Option<FocusTarget> {
949 if let Some((p, _)) = &self.click {
950 return Some(FocusTarget::Direct { target: p.widget_id() });
951 } else if let Some(c) = self.commands.first()
952 && let CommandScope::Widget(w) = c.scope()
953 && FOCUS.focused().with(|f| f.as_ref().map(|p| !p.contains(w)).unwrap_or(true))
954 {
955 return Some(FocusTarget::Direct { target: w });
956 }
957 self.focus.map(|target| FocusTarget::DirectOrRelated {
958 target,
959 navigation_origin: true,
960 })
961 }
962
963 pub fn click(&self) -> Option<(&InteractionPath, ShortcutClick)> {
965 self.click.as_ref().map(|(p, k)| (p, *k))
966 }
967
968 pub fn commands(&self) -> &[Command] {
972 &self.commands
973 }
974
975 pub fn has_actions(&self) -> bool {
977 self.click.is_some() || self.focus.is_some() || !self.commands.is_empty()
978 }
979
980 fn run(&self, timestamp: DInstant, propagation: &EventPropagationHandle, device_id: Option<InputDeviceId>, repeat_count: u32) {
982 if let Some(target) = self.focus() {
983 tracing::trace!("shortcut focus {target:?}");
984 FOCUS.focus(FocusRequest::new(target, true));
985 }
986
987 if let Some((target, kind)) = &self.click {
988 tracing::trace!("shortcut click {target:?} {kind:?}");
989 let args = ClickArgs::new(
990 timestamp,
991 propagation.clone(),
992 target.window_id(),
993 device_id,
994 ClickArgsSource::Shortcut {
995 shortcut: self.shortcut.clone(),
996 kind: *kind,
997 },
998 NonZeroU32::new(repeat_count.saturating_add(1)).unwrap(),
999 repeat_count > 0,
1000 self.shortcut.modifiers_state(),
1001 target.clone(),
1002 );
1003 CLICK_EVENT.notify(args);
1004 }
1005 for command in &self.commands {
1006 tracing::trace!("shortcut cmd {:?}", command);
1007 command.notify_linked(propagation.clone(), None);
1008 }
1009 }
1010}
1011
1012#[derive(Clone, PartialEq, Eq, Hash, Debug)]
1017#[repr(transparent)]
1018#[must_use = "the shortcuts claim is removed if the handle is dropped"]
1019pub struct ShortcutsHandle(Handle<()>);
1020impl ShortcutsHandle {
1021 pub(super) fn new() -> (HandleOwner<()>, Self) {
1022 let (owner, handle) = Handle::new(());
1023 (owner, ShortcutsHandle(handle))
1024 }
1025
1026 pub fn dummy() -> Self {
1030 ShortcutsHandle(Handle::dummy(()))
1031 }
1032
1033 pub fn perm(self) {
1040 self.0.perm();
1041 }
1042
1043 pub fn is_permanent(&self) -> bool {
1047 self.0.is_permanent()
1048 }
1049
1050 pub fn release(self) {
1052 self.0.force_drop();
1053 }
1054
1055 pub fn is_released(&self) -> bool {
1059 self.0.is_dropped()
1060 }
1061
1062 pub fn downgrade(&self) -> WeakShortcutsHandle {
1064 WeakShortcutsHandle(self.0.downgrade())
1065 }
1066}
1067
1068#[derive(Clone, PartialEq, Eq, Hash, Default, Debug)]
1070pub struct WeakShortcutsHandle(pub(super) WeakHandle<()>);
1071impl WeakShortcutsHandle {
1072 pub fn new() -> Self {
1074 Self(WeakHandle::new())
1075 }
1076
1077 pub fn upgrade(&self) -> Option<ShortcutsHandle> {
1079 self.0.upgrade().map(ShortcutsHandle)
1080 }
1081}
1082
1083pub trait HeadlessAppGestureExt {
1087 fn press_shortcut(&mut self, window_id: WindowId, shortcut: impl Into<Shortcut>);
1089}
1090impl HeadlessAppGestureExt for HeadlessApp {
1091 fn press_shortcut(&mut self, window_id: WindowId, shortcut: impl Into<Shortcut>) {
1092 let shortcut = shortcut.into();
1093 match shortcut {
1094 Shortcut::Modifier(m) => {
1095 let (code, key) = m.left_key();
1096 self.press_key(window_id, code, KeyLocation::Standard, key);
1097 }
1098 Shortcut::Gesture(g) => match g.key {
1099 GestureKey::Key(k) => self.press_modified_key(
1100 window_id,
1101 g.modifiers,
1102 KeyCode::Unidentified(NativeKeyCode::Unidentified),
1103 KeyLocation::Standard,
1104 k,
1105 ),
1106 GestureKey::Code(c) => self.press_modified_key(window_id, g.modifiers, c, KeyLocation::Standard, Key::Unidentified),
1107 },
1108 Shortcut::Chord(c) => {
1109 self.press_shortcut(window_id, c.starter);
1110 self.press_shortcut(window_id, c.complement);
1111 }
1112 }
1113 }
1114}
1115
1116pub trait CommandShortcutMatchesExt: CommandShortcutExt {
1118 fn shortcut_matches(self, shortcut: &Shortcut) -> bool;
1120}
1121impl CommandShortcutMatchesExt for Command {
1122 fn shortcut_matches(self, shortcut: &Shortcut) -> bool {
1123 if !self.has_handlers().get() {
1124 return false;
1125 }
1126
1127 let s = self.shortcut();
1128 if s.with(|s| !s.contains(shortcut)) {
1129 return false;
1130 }
1131
1132 let filter = self.shortcut_filter().get();
1133 if filter.is_empty() {
1134 return true;
1135 }
1136 if filter.contains(ShortcutFilter::CMD_ENABLED) && !self.is_enabled().get() {
1137 return false;
1138 }
1139
1140 match self.scope() {
1141 CommandScope::App => filter == ShortcutFilter::CMD_ENABLED,
1142 CommandScope::Window(id) => {
1143 if filter.contains(ShortcutFilter::FOCUSED) {
1144 FOCUS.focused().with(|p| {
1145 let p = match p {
1146 Some(p) => p,
1147 None => return false,
1148 };
1149 if p.window_id() != id {
1150 return false;
1151 }
1152 !filter.contains(ShortcutFilter::ENABLED) || p.interaction_path().next().map(|i| i.is_enabled()).unwrap_or(false)
1153 })
1154 } else if filter.contains(ShortcutFilter::ENABLED) {
1155 let tree = match WINDOWS.widget_tree(id) {
1156 Some(t) => t,
1157 None => return false,
1158 };
1159
1160 tree.root().interactivity().is_enabled()
1161 } else {
1162 true
1163 }
1164 }
1165 CommandScope::Widget(id) => {
1166 if filter.contains(ShortcutFilter::FOCUSED) {
1167 FOCUS.focused().with(|p| {
1168 let p = match p {
1169 Some(p) => p,
1170 None => return false,
1171 };
1172 if !p.contains(id) {
1173 return false;
1174 }
1175 !filter.contains(ShortcutFilter::ENABLED) || p.contains_enabled(id)
1176 })
1177 } else if filter.contains(ShortcutFilter::ENABLED) {
1178 if let Some(w) = WINDOWS.widget_info(id) {
1179 return w.interactivity().is_enabled();
1180 }
1181
1182 false
1183 } else {
1184 true
1185 }
1186 }
1187 }
1188 }
1189}
1190
1191on_process_start!(|args| {
1192 if args.yield_until_app() {
1193 return;
1194 }
1195 APP.on_init(hn!(|args| {
1196 if !args.is_minimal {
1197 let _ = GESTURES_SV.read();
1200 }
1201 }));
1202});