1use parking_lot::Mutex;
4use std::{
5 collections::{HashMap, HashSet},
6 convert::TryFrom,
7 num::NonZeroU32,
8 sync::Arc,
9 time::Duration,
10};
11use zng_app::{
12 APP, AppExtension, DInstant, HeadlessApp,
13 access::{ACCESS_CLICK_EVENT, AccessClickArgs},
14 event::{AnyEventArgs, Command, CommandScope, EVENTS, EventPropagationHandle, event, event_args},
15 shortcut::{
16 CommandShortcutExt, GestureKey, KeyChord, KeyGesture, ModifierGesture, ModifiersState, Shortcut, ShortcutFilter, Shortcuts,
17 shortcut,
18 },
19 update::EventUpdate,
20 view_process::raw_device_events::InputDeviceId,
21 widget::{
22 WidgetId,
23 info::{HitTestInfo, InteractionPath, WidgetPath},
24 },
25 window::WindowId,
26};
27use zng_app_context::app_local;
28use zng_ext_window::WINDOWS;
29use zng_handle::{Handle, HandleOwner, WeakHandle};
30use zng_layout::unit::DipPoint;
31use zng_var::{Var, var};
32use zng_view_api::{
33 keyboard::{Key, KeyCode, KeyLocation, KeyState, NativeKeyCode},
34 mouse::MouseButton,
35};
36
37use crate::{
38 focus::{FOCUS, FocusRequest, FocusTarget},
39 keyboard::{HeadlessAppKeyboardExt, KEY_INPUT_EVENT, KeyInputArgs},
40 mouse::{MOUSE_CLICK_EVENT, MouseClickArgs},
41 touch::{TOUCH_LONG_PRESS_EVENT, TOUCH_TAP_EVENT, TouchLongPressArgs, TouchTapArgs},
42};
43
44#[derive(Debug, Clone)]
46pub enum ClickArgsSource {
47 Mouse {
49 button: MouseButton,
51
52 position: DipPoint,
54
55 hits: HitTestInfo,
58 },
59
60 Touch {
62 position: DipPoint,
64
65 hits: HitTestInfo,
68
69 is_tap: bool,
71 },
72
73 Shortcut {
75 shortcut: Shortcut,
77 kind: ShortcutClick,
79 },
80
81 Access {
85 is_primary: bool,
87 },
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum ShortcutClick {
93 Primary,
95 Context,
97}
98
99event_args! {
100 pub struct ClickArgs {
104 pub window_id: WindowId,
106
107 pub device_id: Option<InputDeviceId>,
111
112 pub source: ClickArgsSource,
114
115 pub click_count: NonZeroU32,
119
120 pub is_repeat: bool,
122
123 pub modifiers: ModifiersState,
125
126 pub target: InteractionPath,
128
129 ..
130
131 fn delivery_list(&self, list: &mut UpdateDeliveryList) {
135 list.insert_wgt(&self.target)
136 }
137 }
138
139 pub struct ShortcutArgs {
141 pub window_id: WindowId,
143
144 pub device_id: Option<InputDeviceId>,
148
149 pub shortcut: Shortcut,
151
152 pub repeat_count: u32,
156
157 pub actions: ShortcutActions,
159
160 ..
161
162 fn delivery_list(&self, _list: &mut UpdateDeliveryList) {}
164 }
165}
166impl From<MouseClickArgs> for ClickArgs {
167 fn from(args: MouseClickArgs) -> Self {
168 ClickArgs::new(
169 args.timestamp,
170 args.propagation().clone(),
171 args.window_id,
172 args.device_id,
173 ClickArgsSource::Mouse {
174 button: args.button,
175 position: args.position,
176 hits: args.hits,
177 },
178 args.click_count,
179 args.is_repeat,
180 args.modifiers,
181 args.target,
182 )
183 }
184}
185impl From<TouchTapArgs> for ClickArgs {
186 fn from(args: TouchTapArgs) -> Self {
187 ClickArgs::new(
188 args.timestamp,
189 args.propagation().clone(),
190 args.window_id,
191 args.device_id,
192 ClickArgsSource::Touch {
193 position: args.position,
194 hits: args.hits,
195 is_tap: true,
196 },
197 args.tap_count,
198 false,
199 args.modifiers,
200 args.target,
201 )
202 }
203}
204impl From<TouchLongPressArgs> for ClickArgs {
205 fn from(args: TouchLongPressArgs) -> Self {
206 ClickArgs::new(
207 args.timestamp,
208 args.propagation().clone(),
209 args.window_id,
210 args.device_id,
211 ClickArgsSource::Touch {
212 position: args.position,
213 hits: args.hits,
214 is_tap: false,
215 },
216 NonZeroU32::new(1).unwrap(),
217 false,
218 args.modifiers,
219 args.target,
220 )
221 }
222}
223impl ClickArgs {
224 pub fn is_primary(&self) -> bool {
231 match &self.source {
232 ClickArgsSource::Mouse { button, .. } => *button == MouseButton::Left,
233 ClickArgsSource::Touch { is_tap, .. } => *is_tap,
234 ClickArgsSource::Shortcut { kind, .. } => *kind == ShortcutClick::Primary,
235 ClickArgsSource::Access { is_primary } => *is_primary,
236 }
237 }
238
239 pub fn is_context(&self) -> bool {
245 self.click_count.get() == 1
246 && match &self.source {
247 ClickArgsSource::Mouse { button, .. } => *button == MouseButton::Right,
248 ClickArgsSource::Touch { is_tap, .. } => !*is_tap,
249 ClickArgsSource::Shortcut { kind, .. } => *kind == ShortcutClick::Context,
250 ClickArgsSource::Access { is_primary } => !*is_primary,
251 }
252 }
253
254 pub fn is_mouse_btn(&self, mouse_button: MouseButton) -> bool {
256 match &self.source {
257 ClickArgsSource::Mouse { button, .. } => *button == mouse_button,
258 _ => false,
259 }
260 }
261
262 pub fn shortcut(&self) -> Option<Shortcut> {
264 match &self.source {
265 ClickArgsSource::Shortcut { shortcut, .. } => Some(shortcut.clone()),
266 _ => None,
267 }
268 }
269
270 pub fn is_single(&self) -> bool {
272 self.click_count.get() == 1
273 }
274
275 pub fn is_double(&self) -> bool {
277 self.click_count.get() == 2
278 }
279
280 pub fn is_triple(&self) -> bool {
282 self.click_count.get() == 3
283 }
284
285 pub fn is_from_mouse(&self) -> bool {
287 matches!(&self.source, ClickArgsSource::Mouse { .. })
288 }
289
290 pub fn is_from_touch(&self) -> bool {
292 matches!(&self.source, ClickArgsSource::Touch { .. })
293 }
294
295 pub fn is_from_keyboard(&self) -> bool {
297 matches!(&self.source, ClickArgsSource::Shortcut { .. })
298 }
299
300 pub fn is_from_access(&self) -> bool {
305 matches!(&self.source, ClickArgsSource::Access { .. })
306 }
307
308 pub fn position(&self) -> Option<DipPoint> {
312 match &self.source {
313 ClickArgsSource::Mouse { position, .. } => Some(*position),
314 ClickArgsSource::Touch { position, .. } => Some(*position),
315 ClickArgsSource::Shortcut { .. } | ClickArgsSource::Access { .. } => None,
316 }
317 }
318}
319
320event! {
321 pub static CLICK_EVENT: ClickArgs;
325
326 pub static SHORTCUT_EVENT: ShortcutArgs;
334}
335
336#[derive(Default)]
347pub struct GestureManager {}
348impl AppExtension for GestureManager {
349 fn init(&mut self) {
350 TOUCH_TAP_EVENT.as_any().hook(|_| true).perm();
352 TOUCH_LONG_PRESS_EVENT.as_any().hook(|_| true).perm();
353 }
354
355 fn event(&mut self, update: &mut EventUpdate) {
356 if let Some(args) = MOUSE_CLICK_EVENT.on_unhandled(update) {
357 CLICK_EVENT.notify(args.clone().into());
359 } else if let Some(args) = KEY_INPUT_EVENT.on_unhandled(update) {
360 GESTURES_SV.write().on_key_input(args);
362 } else if let Some(args) = TOUCH_TAP_EVENT.on_unhandled(update) {
363 CLICK_EVENT.notify(args.clone().into());
365 } else if let Some(args) = TOUCH_LONG_PRESS_EVENT.on_unhandled(update) {
366 if !args.propagation().is_stopped() {
368 CLICK_EVENT.notify(args.clone().into());
369 }
370 } else if let Some(args) = SHORTCUT_EVENT.on_unhandled(update) {
371 GESTURES_SV.write().on_shortcut(args);
373 } else if let Some(args) = ACCESS_CLICK_EVENT.on_unhandled(update) {
374 GESTURES_SV.write().on_access(args);
376 }
377 }
378}
379
380app_local! {
381 static GESTURES_SV: GesturesService = {
382 APP.extensions().require::<GestureManager>();
383 GesturesService::new()
384 };
385}
386
387struct GesturesService {
388 click_focused: Var<Shortcuts>,
389 context_click_focused: Var<Shortcuts>,
390 shortcut_pressed_duration: Var<Duration>,
391
392 pressed_modifier: Option<(WindowId, ModifierGesture)>,
393 primed_starter: Option<KeyGesture>,
394 chords: HashMap<KeyGesture, HashSet<KeyGesture>>,
395
396 primary_clicks: Vec<(Shortcut, Arc<ShortcutTarget>)>,
397 context_clicks: Vec<(Shortcut, Arc<ShortcutTarget>)>,
398 focus: Vec<(Shortcut, Arc<ShortcutTarget>)>,
399}
400impl GesturesService {
401 fn new() -> Self {
402 Self {
403 click_focused: var([shortcut!(Enter), shortcut!(Space)].into()),
404 context_click_focused: var([shortcut!(ContextMenu)].into()),
405 shortcut_pressed_duration: var(Duration::from_millis(50)),
406
407 pressed_modifier: None,
408 primed_starter: None,
409 chords: HashMap::default(),
410
411 primary_clicks: vec![],
412 context_clicks: vec![],
413 focus: vec![],
414 }
415 }
416
417 fn register_target(&mut self, shortcuts: Shortcuts, kind: Option<ShortcutClick>, target: WidgetId) -> ShortcutsHandle {
418 if shortcuts.is_empty() {
419 return ShortcutsHandle::dummy();
420 }
421
422 let (owner, handle) = ShortcutsHandle::new();
423 let target = Arc::new(ShortcutTarget {
424 widget_id: target,
425 last_found: Mutex::new(None),
426 handle: owner,
427 });
428
429 let collection = match kind {
430 Some(ShortcutClick::Primary) => &mut self.primary_clicks,
431 Some(ShortcutClick::Context) => &mut self.context_clicks,
432 None => &mut self.focus,
433 };
434
435 if collection.len() > 500 {
436 collection.retain(|(_, e)| !e.handle.is_dropped());
437 }
438
439 for s in shortcuts.0 {
440 if let Shortcut::Chord(c) = &s {
441 self.chords.entry(c.starter.clone()).or_default().insert(c.complement.clone());
442 }
443
444 collection.push((s, target.clone()));
445 }
446
447 handle
448 }
449
450 fn on_key_input(&mut self, args: &KeyInputArgs) {
451 let key = args.shortcut_key();
452 if !args.propagation().is_stopped() && !matches!(key, Key::Unidentified) {
453 match args.state {
454 KeyState::Pressed => {
455 if let Ok(gesture_key) = GestureKey::try_from(key.clone()) {
456 self.on_shortcut_pressed(Shortcut::Gesture(KeyGesture::new(args.modifiers, gesture_key)), args);
457 self.pressed_modifier = None;
458 } else if let Ok(mod_gesture) = ModifierGesture::try_from(key) {
459 if args.repeat_count == 0 {
460 self.pressed_modifier = Some((args.target.window_id(), mod_gesture));
461 }
462 } else {
463 self.pressed_modifier = None;
464 self.primed_starter = None;
465 }
466 }
467 KeyState::Released => {
468 if let Ok(mod_gesture) = ModifierGesture::try_from(key)
469 && let Some((window_id, gesture)) = self.pressed_modifier.take()
470 && args.modifiers.is_empty()
471 && window_id == args.target.window_id()
472 && mod_gesture == gesture
473 {
474 self.on_shortcut_pressed(Shortcut::Modifier(mod_gesture), args);
475 }
476 }
477 }
478 } else {
479 self.primed_starter = None;
481 self.pressed_modifier = None;
482 }
483 }
484 fn on_shortcut_pressed(&mut self, mut shortcut: Shortcut, key_args: &KeyInputArgs) {
485 if let Some(starter) = self.primed_starter.take()
486 && let Shortcut::Gesture(g) = &shortcut
487 && let Some(complements) = self.chords.get(&starter)
488 && complements.contains(g)
489 {
490 shortcut = Shortcut::Chord(KeyChord {
491 starter,
492 complement: g.clone(),
493 });
494 }
495
496 let actions = ShortcutActions::new(self, shortcut.clone());
497
498 SHORTCUT_EVENT.notify(ShortcutArgs::new(
499 key_args.timestamp,
500 key_args.propagation().clone(),
501 key_args.window_id,
502 key_args.device_id,
503 shortcut,
504 key_args.repeat_count,
505 actions,
506 ));
507 }
508
509 fn on_shortcut(&mut self, args: &ShortcutArgs) {
510 if args.actions.has_actions() {
511 args.actions
512 .run(args.timestamp, args.propagation(), args.device_id, args.repeat_count);
513 } else if let Shortcut::Gesture(k) = &args.shortcut
514 && self.chords.contains_key(k)
515 {
516 self.primed_starter = Some(k.clone());
517 }
518 }
519
520 fn on_access(&mut self, args: &AccessClickArgs) {
521 if let Ok(tree) = WINDOWS.widget_tree(args.window_id)
522 && let Some(wgt) = tree.get(args.widget_id)
523 {
524 let path = wgt.interaction_path();
525 if !path.interactivity().is_blocked() {
526 let args = ClickArgs::now(
527 args.window_id,
528 None,
529 ClickArgsSource::Access {
530 is_primary: args.is_primary,
531 },
532 NonZeroU32::new(1).unwrap(),
533 false,
534 ModifiersState::empty(),
535 path,
536 );
537 CLICK_EVENT.notify(args);
538 }
539 }
540 }
541
542 fn cleanup(&mut self) {
543 self.primary_clicks.retain(|(_, e)| !e.handle.is_dropped());
544 self.context_clicks.retain(|(_, e)| !e.handle.is_dropped());
545 self.focus.retain(|(_, e)| !e.handle.is_dropped());
546 }
547}
548
549pub struct GESTURES;
620struct ShortcutTarget {
621 widget_id: WidgetId,
622 last_found: Mutex<Option<WidgetPath>>,
623 handle: HandleOwner<()>,
624}
625impl ShortcutTarget {
626 fn resolve_path(&self) -> Option<InteractionPath> {
627 let mut found = self.last_found.lock();
628 if let Some(found) = &mut *found
629 && let Ok(tree) = WINDOWS.widget_tree(found.window_id())
630 && let Some(w) = tree.get(found.widget_id())
631 {
632 let path = w.interaction_path();
633 *found = path.as_path().clone();
634
635 return path.unblocked();
636 }
637
638 if let Some(w) = WINDOWS.widget_info(self.widget_id) {
639 let path = w.interaction_path();
640 *found = Some(path.as_path().clone());
641
642 return path.unblocked();
643 }
644
645 None
646 }
647}
648impl GESTURES {
649 pub fn click_focused(&self) -> Var<Shortcuts> {
656 GESTURES_SV.read().click_focused.clone()
657 }
658
659 pub fn context_click_focused(&self) -> Var<Shortcuts> {
666 GESTURES_SV.read().context_click_focused.clone()
667 }
668
669 pub fn shortcut_pressed_duration(&self) -> Var<Duration> {
674 GESTURES_SV.read().shortcut_pressed_duration.clone()
675 }
676
677 pub fn click_shortcut(&self, shortcuts: impl Into<Shortcuts>, kind: ShortcutClick, target: WidgetId) -> ShortcutsHandle {
679 GESTURES_SV.write().register_target(shortcuts.into(), Some(kind), target)
680 }
681
682 pub fn focus_shortcut(&self, shortcuts: impl Into<Shortcuts>, target: WidgetId) -> ShortcutsHandle {
686 GESTURES_SV.write().register_target(shortcuts.into(), None, target)
687 }
688
689 pub fn shortcut_actions(&self, shortcut: Shortcut) -> ShortcutActions {
695 ShortcutActions::new(&mut GESTURES_SV.write(), shortcut)
696 }
697}
698
699#[derive(Debug, Clone)]
705pub struct ShortcutActions {
706 shortcut: Shortcut,
707
708 focus: Option<WidgetId>,
709 click: Option<(InteractionPath, ShortcutClick)>,
710 commands: Vec<Command>,
711}
712impl ShortcutActions {
713 fn new(gestures: &mut GesturesService, shortcut: Shortcut) -> ShortcutActions {
714 let focused = FOCUS.focused().get();
735
736 enum Kind {
737 Click(InteractionPath, ShortcutClick),
738 Command(InteractionPath, Command),
739 Focus(InteractionPath),
740 }
741 impl Kind {
742 fn kind_key(&self) -> u8 {
743 match self {
744 Kind::Click(p, s) => {
745 if p.interactivity().is_enabled() {
746 match s {
747 ShortcutClick::Primary => 0,
748 ShortcutClick::Context => 2,
749 }
750 } else {
751 match s {
752 ShortcutClick::Primary => 10,
753 ShortcutClick::Context => 12,
754 }
755 }
756 }
757 Kind::Command(p, _) => {
758 if p.interactivity().is_enabled() {
759 1
760 } else {
761 11
762 }
763 }
764 Kind::Focus(p) => {
765 if p.interactivity().is_enabled() {
766 4
767 } else {
768 14
769 }
770 }
771 }
772 }
773 }
774
775 fn distance_key(focused: &Option<InteractionPath>, p: &InteractionPath) -> u32 {
776 let mut key = u32::MAX - 1;
777 if let Some(focused) = focused
778 && p.window_id() == focused.window_id()
779 {
780 key -= 1;
781 if let Some(i) = p.widgets_path().iter().position(|&id| id == focused.widget_id()) {
782 key = (p.widgets_path().len() - i) as u32;
784 } else if let Some(i) = focused.widgets_path().iter().position(|&id| id == p.widget_id()) {
785 key = key / 2 + (focused.widgets_path().len() - i) as u32;
787 }
788 }
789 key
790 }
791
792 let mut some_primary_dropped = false;
793 let primary_click_matches = gestures.primary_clicks.iter().filter_map(|(s, entry)| {
794 if entry.handle.is_dropped() {
795 some_primary_dropped = true;
796 return None;
797 }
798 if *s != shortcut {
799 return None;
800 }
801
802 let p = entry.resolve_path()?;
803 Some((distance_key(&focused, &p), Kind::Click(p, ShortcutClick::Primary)))
804 });
805
806 let mut some_ctx_dropped = false;
807 let context_click_matches = gestures.context_clicks.iter().filter_map(|(s, entry)| {
808 if entry.handle.is_dropped() {
809 some_ctx_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::Context)))
818 });
819
820 let mut some_focus_dropped = false;
821 let focus_matches = gestures.focus.iter().filter_map(|(s, entry)| {
822 if entry.handle.is_dropped() {
823 some_focus_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::Focus(p)))
832 });
833
834 let mut cmd_window = vec![];
835 let mut cmd_app = vec![];
836 let cmd_matches = EVENTS.commands().into_iter().filter_map(|cmd| {
837 if !cmd.shortcut_matches(&shortcut) {
838 return None;
839 }
840
841 match cmd.scope() {
842 CommandScope::Window(w) => {
843 if let Some(f) = &focused
844 && f.window_id() == w
845 {
846 cmd_window.push(cmd);
847 }
848 }
849 CommandScope::Widget(id) => {
850 if let Some(info) = WINDOWS.widget_info(id) {
851 let p = info.interaction_path();
852 return Some((distance_key(&focused, &p), Kind::Command(p, cmd)));
853 }
854 }
855 CommandScope::App => cmd_app.push(cmd),
856 }
857
858 None
859 });
860
861 let mut best_kind = u8::MAX;
862 let mut best_distance = u32::MAX;
863 let mut best = None;
864
865 for (distance_key, choice) in primary_click_matches
866 .chain(cmd_matches)
867 .chain(context_click_matches)
868 .chain(focus_matches)
869 {
870 let kind_key = choice.kind_key();
871 match kind_key.cmp(&best_kind) {
872 std::cmp::Ordering::Less => {
873 best_kind = kind_key;
874 best_distance = distance_key;
875 best = Some(choice);
876 }
877 std::cmp::Ordering::Equal => {
878 if distance_key < best_distance {
879 best_distance = distance_key;
880 best = Some(choice);
881 }
882 }
883 std::cmp::Ordering::Greater => {}
884 }
885 }
886
887 let mut click = None;
888 let mut focus = None;
889 let mut commands = vec![];
890
891 match best {
892 Some(k) => match k {
893 Kind::Click(p, s) => click = Some((p, s)),
894 Kind::Command(_, cmd) => commands.push(cmd),
895 Kind::Focus(p) => focus = Some(p.widget_id()),
896 },
897 None => {
898 if let Some(p) = focused {
899 click = if gestures.click_focused.with(|c| c.contains(&shortcut)) {
900 Some((p, ShortcutClick::Primary))
901 } else if gestures.context_click_focused.with(|c| c.contains(&shortcut)) {
902 Some((p, ShortcutClick::Context))
903 } else {
904 None
905 };
906 }
907 }
908 }
909
910 commands.append(&mut cmd_window);
911 commands.append(&mut cmd_app);
912
913 if some_primary_dropped || some_ctx_dropped || some_focus_dropped {
914 gestures.cleanup();
915 }
916
917 Self {
918 shortcut,
919 focus,
920 click,
921 commands,
922 }
923 }
924
925 pub fn shortcut(&self) -> &Shortcut {
927 &self.shortcut
928 }
929
930 pub fn focus(&self) -> Option<FocusTarget> {
935 if let Some((p, _)) = &self.click {
936 return Some(FocusTarget::Direct { target: p.widget_id() });
937 } else if let Some(c) = self.commands.first()
938 && let CommandScope::Widget(w) = c.scope()
939 && FOCUS.focused().with(|f| f.as_ref().map(|p| !p.contains(w)).unwrap_or(true))
940 {
941 return Some(FocusTarget::Direct { target: w });
942 }
943 self.focus.map(|target| FocusTarget::DirectOrRelated {
944 target,
945 navigation_origin: true,
946 })
947 }
948
949 pub fn click(&self) -> Option<(&InteractionPath, ShortcutClick)> {
951 self.click.as_ref().map(|(p, k)| (p, *k))
952 }
953
954 pub fn commands(&self) -> &[Command] {
958 &self.commands
959 }
960
961 pub fn has_actions(&self) -> bool {
963 self.click.is_some() || self.focus.is_some() || !self.commands.is_empty()
964 }
965
966 fn run(&self, timestamp: DInstant, propagation: &EventPropagationHandle, device_id: Option<InputDeviceId>, repeat_count: u32) {
968 if let Some(target) = self.focus() {
969 FOCUS.focus(FocusRequest::new(target, true));
970 }
971
972 if let Some((target, kind)) = &self.click {
973 let args = ClickArgs::new(
974 timestamp,
975 propagation.clone(),
976 target.window_id(),
977 device_id,
978 ClickArgsSource::Shortcut {
979 shortcut: self.shortcut.clone(),
980 kind: *kind,
981 },
982 NonZeroU32::new(repeat_count.saturating_add(1)).unwrap(),
983 repeat_count > 0,
984 self.shortcut.modifiers_state(),
985 target.clone(),
986 );
987 CLICK_EVENT.notify(args);
988 }
989 for command in &self.commands {
990 command.notify_linked(propagation.clone(), None);
991 }
992 }
993}
994
995#[derive(Clone, PartialEq, Eq, Hash, Debug)]
1000#[repr(transparent)]
1001#[must_use = "the shortcuts claim is removed if the handle is dropped"]
1002pub struct ShortcutsHandle(Handle<()>);
1003impl ShortcutsHandle {
1004 pub(super) fn new() -> (HandleOwner<()>, Self) {
1005 let (owner, handle) = Handle::new(());
1006 (owner, ShortcutsHandle(handle))
1007 }
1008
1009 pub fn dummy() -> Self {
1013 ShortcutsHandle(Handle::dummy(()))
1014 }
1015
1016 pub fn perm(self) {
1023 self.0.perm();
1024 }
1025
1026 pub fn is_permanent(&self) -> bool {
1030 self.0.is_permanent()
1031 }
1032
1033 pub fn release(self) {
1035 self.0.force_drop();
1036 }
1037
1038 pub fn is_released(&self) -> bool {
1042 self.0.is_dropped()
1043 }
1044
1045 pub fn downgrade(&self) -> WeakShortcutsHandle {
1047 WeakShortcutsHandle(self.0.downgrade())
1048 }
1049}
1050
1051#[derive(Clone, PartialEq, Eq, Hash, Default, Debug)]
1053pub struct WeakShortcutsHandle(pub(super) WeakHandle<()>);
1054impl WeakShortcutsHandle {
1055 pub fn new() -> Self {
1057 Self(WeakHandle::new())
1058 }
1059
1060 pub fn upgrade(&self) -> Option<ShortcutsHandle> {
1062 self.0.upgrade().map(ShortcutsHandle)
1063 }
1064}
1065
1066pub trait HeadlessAppGestureExt {
1070 fn press_shortcut(&mut self, window_id: WindowId, shortcut: impl Into<Shortcut>);
1072}
1073impl HeadlessAppGestureExt for HeadlessApp {
1074 fn press_shortcut(&mut self, window_id: WindowId, shortcut: impl Into<Shortcut>) {
1075 let shortcut = shortcut.into();
1076 match shortcut {
1077 Shortcut::Modifier(m) => {
1078 let (code, key) = m.left_key();
1079 self.press_key(window_id, code, KeyLocation::Standard, key);
1080 }
1081 Shortcut::Gesture(g) => match g.key {
1082 GestureKey::Key(k) => self.press_modified_key(
1083 window_id,
1084 g.modifiers,
1085 KeyCode::Unidentified(NativeKeyCode::Unidentified),
1086 KeyLocation::Standard,
1087 k,
1088 ),
1089 GestureKey::Code(c) => self.press_modified_key(window_id, g.modifiers, c, KeyLocation::Standard, Key::Unidentified),
1090 },
1091 Shortcut::Chord(c) => {
1092 self.press_shortcut(window_id, c.starter);
1093 self.press_shortcut(window_id, c.complement);
1094 }
1095 }
1096 }
1097}
1098
1099pub trait CommandShortcutMatchesExt: CommandShortcutExt {
1101 fn shortcut_matches(self, shortcut: &Shortcut) -> bool;
1103}
1104impl CommandShortcutMatchesExt for Command {
1105 fn shortcut_matches(self, shortcut: &Shortcut) -> bool {
1106 if !self.has_handlers().get() {
1107 return false;
1108 }
1109
1110 let s = self.shortcut();
1111 if s.with(|s| !s.contains(shortcut)) {
1112 return false;
1113 }
1114
1115 let filter = self.shortcut_filter().get();
1116 if filter.is_empty() {
1117 return true;
1118 }
1119 if filter.contains(ShortcutFilter::CMD_ENABLED) && !self.is_enabled_value() {
1120 return false;
1121 }
1122
1123 match self.scope() {
1124 CommandScope::App => filter == ShortcutFilter::CMD_ENABLED,
1125 CommandScope::Window(id) => {
1126 if filter.contains(ShortcutFilter::FOCUSED) {
1127 FOCUS.focused().with(|p| {
1128 let p = match p {
1129 Some(p) => p,
1130 None => return false,
1131 };
1132 if p.window_id() != id {
1133 return false;
1134 }
1135 !filter.contains(ShortcutFilter::ENABLED) || p.interaction_path().next().map(|i| i.is_enabled()).unwrap_or(false)
1136 })
1137 } else if filter.contains(ShortcutFilter::ENABLED) {
1138 let tree = match WINDOWS.widget_tree(id) {
1139 Ok(t) => t,
1140 Err(_) => return false,
1141 };
1142
1143 tree.root().interactivity().is_enabled()
1144 } else {
1145 true
1146 }
1147 }
1148 CommandScope::Widget(id) => {
1149 if filter.contains(ShortcutFilter::FOCUSED) {
1150 FOCUS.focused().with(|p| {
1151 let p = match p {
1152 Some(p) => p,
1153 None => return false,
1154 };
1155 if !p.contains(id) {
1156 return false;
1157 }
1158 !filter.contains(ShortcutFilter::ENABLED) || p.contains_enabled(id)
1159 })
1160 } else if filter.contains(ShortcutFilter::ENABLED) {
1161 if let Some(w) = WINDOWS.widget_info(id) {
1162 return w.interactivity().is_enabled();
1163 }
1164
1165 false
1166 } else {
1167 true
1168 }
1169 }
1170 }
1171 }
1172}