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 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::DeviceId,
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::{ArcVar, 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<DeviceId>,
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<DeviceId>,
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_enabled(&self, widget_id: WidgetId) -> bool {
228 self.target.interactivity_of(widget_id).map(|i| i.is_enabled()).unwrap_or(false)
229 }
230
231 pub fn is_disabled(&self, widget_id: WidgetId) -> bool {
235 self.target.interactivity_of(widget_id).map(|i| i.is_disabled()).unwrap_or(false)
236 }
237
238 pub fn is_primary(&self) -> bool {
245 match &self.source {
246 ClickArgsSource::Mouse { button, .. } => *button == MouseButton::Left,
247 ClickArgsSource::Touch { is_tap, .. } => *is_tap,
248 ClickArgsSource::Shortcut { kind, .. } => *kind == ShortcutClick::Primary,
249 ClickArgsSource::Access { is_primary } => *is_primary,
250 }
251 }
252
253 pub fn is_context(&self) -> bool {
259 self.click_count.get() == 1
260 && match &self.source {
261 ClickArgsSource::Mouse { button, .. } => *button == MouseButton::Right,
262 ClickArgsSource::Touch { is_tap, .. } => !*is_tap,
263 ClickArgsSource::Shortcut { kind, .. } => *kind == ShortcutClick::Context,
264 ClickArgsSource::Access { is_primary } => !*is_primary,
265 }
266 }
267
268 pub fn is_mouse_btn(&self, mouse_button: MouseButton) -> bool {
270 match &self.source {
271 ClickArgsSource::Mouse { button, .. } => *button == mouse_button,
272 _ => false,
273 }
274 }
275
276 pub fn shortcut(&self) -> Option<Shortcut> {
278 match &self.source {
279 ClickArgsSource::Shortcut { shortcut, .. } => Some(shortcut.clone()),
280 _ => None,
281 }
282 }
283
284 pub fn is_single(&self) -> bool {
286 self.click_count.get() == 1
287 }
288
289 pub fn is_double(&self) -> bool {
291 self.click_count.get() == 2
292 }
293
294 pub fn is_triple(&self) -> bool {
296 self.click_count.get() == 3
297 }
298
299 pub fn is_from_mouse(&self) -> bool {
301 matches!(&self.source, ClickArgsSource::Mouse { .. })
302 }
303
304 pub fn is_from_touch(&self) -> bool {
306 matches!(&self.source, ClickArgsSource::Touch { .. })
307 }
308
309 pub fn is_from_keyboard(&self) -> bool {
311 matches!(&self.source, ClickArgsSource::Shortcut { .. })
312 }
313
314 pub fn is_from_access(&self) -> bool {
319 matches!(&self.source, ClickArgsSource::Access { .. })
320 }
321
322 pub fn position(&self) -> Option<DipPoint> {
326 match &self.source {
327 ClickArgsSource::Mouse { position, .. } => Some(*position),
328 ClickArgsSource::Touch { position, .. } => Some(*position),
329 ClickArgsSource::Shortcut { .. } | ClickArgsSource::Access { .. } => None,
330 }
331 }
332}
333
334event! {
335 pub static CLICK_EVENT: ClickArgs;
339
340 pub static SHORTCUT_EVENT: ShortcutArgs;
348}
349
350#[derive(Default)]
361pub struct GestureManager {}
362impl AppExtension for GestureManager {
363 fn init(&mut self) {
364 TOUCH_TAP_EVENT.as_any().hook(|_| true).perm();
366 TOUCH_LONG_PRESS_EVENT.as_any().hook(|_| true).perm();
367 }
368
369 fn event(&mut self, update: &mut EventUpdate) {
370 if let Some(args) = MOUSE_CLICK_EVENT.on_unhandled(update) {
371 CLICK_EVENT.notify(args.clone().into());
373 } else if let Some(args) = KEY_INPUT_EVENT.on_unhandled(update) {
374 GESTURES_SV.write().on_key_input(args);
376 } else if let Some(args) = TOUCH_TAP_EVENT.on_unhandled(update) {
377 CLICK_EVENT.notify(args.clone().into());
379 } else if let Some(args) = TOUCH_LONG_PRESS_EVENT.on_unhandled(update) {
380 if !args.propagation().is_stopped() {
382 CLICK_EVENT.notify(args.clone().into());
383 }
384 } else if let Some(args) = SHORTCUT_EVENT.on_unhandled(update) {
385 GESTURES_SV.write().on_shortcut(args);
387 } else if let Some(args) = ACCESS_CLICK_EVENT.on_unhandled(update) {
388 GESTURES_SV.write().on_access(args);
390 }
391 }
392}
393
394app_local! {
395 static GESTURES_SV: GesturesService = GesturesService::new();
396}
397
398struct GesturesService {
399 click_focused: ArcVar<Shortcuts>,
400 context_click_focused: ArcVar<Shortcuts>,
401 shortcut_pressed_duration: ArcVar<Duration>,
402
403 pressed_modifier: Option<(WindowId, ModifierGesture)>,
404 primed_starter: Option<KeyGesture>,
405 chords: HashMap<KeyGesture, HashSet<KeyGesture>>,
406
407 primary_clicks: Vec<(Shortcut, Arc<ShortcutTarget>)>,
408 context_clicks: Vec<(Shortcut, Arc<ShortcutTarget>)>,
409 focus: Vec<(Shortcut, Arc<ShortcutTarget>)>,
410}
411impl GesturesService {
412 fn new() -> Self {
413 Self {
414 click_focused: var([shortcut!(Enter), shortcut!(Space)].into()),
415 context_click_focused: var([shortcut!(ContextMenu)].into()),
416 shortcut_pressed_duration: var(Duration::from_millis(50)),
417
418 pressed_modifier: None,
419 primed_starter: None,
420 chords: HashMap::default(),
421
422 primary_clicks: vec![],
423 context_clicks: vec![],
424 focus: vec![],
425 }
426 }
427
428 fn register_target(&mut self, shortcuts: Shortcuts, kind: Option<ShortcutClick>, target: WidgetId) -> ShortcutsHandle {
429 if shortcuts.is_empty() {
430 return ShortcutsHandle::dummy();
431 }
432
433 let (owner, handle) = ShortcutsHandle::new();
434 let target = Arc::new(ShortcutTarget {
435 widget_id: target,
436 last_found: Mutex::new(None),
437 handle: owner,
438 });
439
440 let collection = match kind {
441 Some(ShortcutClick::Primary) => &mut self.primary_clicks,
442 Some(ShortcutClick::Context) => &mut self.context_clicks,
443 None => &mut self.focus,
444 };
445
446 if collection.len() > 500 {
447 collection.retain(|(_, e)| !e.handle.is_dropped());
448 }
449
450 for s in shortcuts.0 {
451 if let Shortcut::Chord(c) = &s {
452 self.chords.entry(c.starter.clone()).or_default().insert(c.complement.clone());
453 }
454
455 collection.push((s, target.clone()));
456 }
457
458 handle
459 }
460
461 fn on_key_input(&mut self, args: &KeyInputArgs) {
462 let key = args.shortcut_key();
463 if !args.propagation().is_stopped() && !matches!(key, Key::Unidentified) {
464 match args.state {
465 KeyState::Pressed => {
466 if let Ok(gesture_key) = GestureKey::try_from(key.clone()) {
467 self.on_shortcut_pressed(Shortcut::Gesture(KeyGesture::new(args.modifiers, gesture_key)), args);
468 self.pressed_modifier = None;
469 } else if let Ok(mod_gesture) = ModifierGesture::try_from(key) {
470 if args.repeat_count == 0 {
471 self.pressed_modifier = Some((args.target.window_id(), mod_gesture));
472 }
473 } else {
474 self.pressed_modifier = None;
475 self.primed_starter = None;
476 }
477 }
478 KeyState::Released => {
479 if let Ok(mod_gesture) = ModifierGesture::try_from(key) {
480 if let (Some((window_id, gesture)), true) = (self.pressed_modifier.take(), args.modifiers.is_empty()) {
481 if window_id == args.target.window_id() && mod_gesture == gesture {
482 self.on_shortcut_pressed(Shortcut::Modifier(mod_gesture), args);
483 }
484 }
485 }
486 }
487 }
488 } else {
489 self.primed_starter = None;
491 self.pressed_modifier = None;
492 }
493 }
494 fn on_shortcut_pressed(&mut self, mut shortcut: Shortcut, key_args: &KeyInputArgs) {
495 if let Some(starter) = self.primed_starter.take() {
496 if let Shortcut::Gesture(g) = &shortcut {
497 if let Some(complements) = self.chords.get(&starter) {
498 if complements.contains(g) {
499 shortcut = Shortcut::Chord(KeyChord {
500 starter,
501 complement: g.clone(),
502 });
503 }
504 }
505 }
506 }
507
508 let actions = ShortcutActions::new(self, shortcut.clone());
509
510 SHORTCUT_EVENT.notify(ShortcutArgs::new(
511 key_args.timestamp,
512 key_args.propagation().clone(),
513 key_args.window_id,
514 key_args.device_id,
515 shortcut,
516 key_args.repeat_count,
517 actions,
518 ));
519 }
520
521 fn on_shortcut(&mut self, args: &ShortcutArgs) {
522 if args.actions.has_actions() {
523 args.actions
524 .run(args.timestamp, args.propagation(), args.device_id, args.repeat_count);
525 } else if let Shortcut::Gesture(k) = &args.shortcut {
526 if self.chords.contains_key(k) {
527 self.primed_starter = Some(k.clone());
528 }
529 }
530 }
531
532 fn on_access(&mut self, args: &AccessClickArgs) {
533 if let Ok(tree) = WINDOWS.widget_tree(args.window_id) {
534 if let Some(wgt) = tree.get(args.widget_id) {
535 let path = wgt.interaction_path();
536 if !path.interactivity().is_blocked() {
537 let args = ClickArgs::now(
538 args.window_id,
539 None,
540 ClickArgsSource::Access {
541 is_primary: args.is_primary,
542 },
543 NonZeroU32::new(1).unwrap(),
544 false,
545 ModifiersState::empty(),
546 path,
547 );
548 CLICK_EVENT.notify(args);
549 }
550 }
551 }
552 }
553
554 fn cleanup(&mut self) {
555 self.primary_clicks.retain(|(_, e)| !e.handle.is_dropped());
556 self.context_clicks.retain(|(_, e)| !e.handle.is_dropped());
557 self.focus.retain(|(_, e)| !e.handle.is_dropped());
558 }
559}
560
561pub struct GESTURES;
628struct ShortcutTarget {
629 widget_id: WidgetId,
630 last_found: Mutex<Option<WidgetPath>>,
631 handle: HandleOwner<()>,
632}
633impl ShortcutTarget {
634 fn resolve_path(&self) -> Option<InteractionPath> {
635 let mut found = self.last_found.lock();
636 if let Some(found) = &mut *found {
637 if let Ok(tree) = WINDOWS.widget_tree(found.window_id()) {
638 if let Some(w) = tree.get(found.widget_id()) {
639 let path = w.interaction_path();
640 *found = path.as_path().clone();
641
642 return path.unblocked();
643 }
644 }
645 }
646
647 if let Some(w) = WINDOWS.widget_info(self.widget_id) {
648 let path = w.interaction_path();
649 *found = Some(path.as_path().clone());
650
651 return path.unblocked();
652 }
653
654 None
655 }
656}
657impl GESTURES {
658 pub fn click_focused(&self) -> ArcVar<Shortcuts> {
665 GESTURES_SV.read().click_focused.clone()
666 }
667
668 pub fn context_click_focused(&self) -> ArcVar<Shortcuts> {
675 GESTURES_SV.read().context_click_focused.clone()
676 }
677
678 pub fn shortcut_pressed_duration(&self) -> ArcVar<Duration> {
683 GESTURES_SV.read().shortcut_pressed_duration.clone()
684 }
685
686 pub fn click_shortcut(&self, shortcuts: impl Into<Shortcuts>, kind: ShortcutClick, target: WidgetId) -> ShortcutsHandle {
688 GESTURES_SV.write().register_target(shortcuts.into(), Some(kind), target)
689 }
690
691 pub fn focus_shortcut(&self, shortcuts: impl Into<Shortcuts>, target: WidgetId) -> ShortcutsHandle {
695 GESTURES_SV.write().register_target(shortcuts.into(), None, target)
696 }
697
698 pub fn shortcut_actions(&self, shortcut: Shortcut) -> ShortcutActions {
704 ShortcutActions::new(&mut GESTURES_SV.write(), shortcut)
705 }
706}
707
708#[derive(Debug, Clone)]
714pub struct ShortcutActions {
715 shortcut: Shortcut,
716
717 focus: Option<WidgetId>,
718 click: Option<(InteractionPath, ShortcutClick)>,
719 commands: Vec<Command>,
720}
721impl ShortcutActions {
722 fn new(gestures: &mut GesturesService, shortcut: Shortcut) -> ShortcutActions {
723 let focused = FOCUS.focused().get();
744
745 enum Kind {
746 Click(InteractionPath, ShortcutClick),
747 Command(InteractionPath, Command),
748 Focus(InteractionPath),
749 }
750 impl Kind {
751 fn kind_key(&self) -> u8 {
752 match self {
753 Kind::Click(p, s) => {
754 if p.interactivity().is_enabled() {
755 match s {
756 ShortcutClick::Primary => 0,
757 ShortcutClick::Context => 2,
758 }
759 } else {
760 match s {
761 ShortcutClick::Primary => 10,
762 ShortcutClick::Context => 12,
763 }
764 }
765 }
766 Kind::Command(p, _) => {
767 if p.interactivity().is_enabled() {
768 1
769 } else {
770 11
771 }
772 }
773 Kind::Focus(p) => {
774 if p.interactivity().is_enabled() {
775 4
776 } else {
777 14
778 }
779 }
780 }
781 }
782 }
783
784 fn distance_key(focused: &Option<InteractionPath>, p: &InteractionPath) -> u32 {
785 let mut key = u32::MAX - 1;
786 if let Some(focused) = focused {
787 if p.window_id() == focused.window_id() {
788 key -= 1;
789 if let Some(i) = p.widgets_path().iter().position(|&id| id == focused.widget_id()) {
790 key = (p.widgets_path().len() - i) as u32;
792 } else if let Some(i) = focused.widgets_path().iter().position(|&id| id == p.widget_id()) {
793 key = key / 2 + (focused.widgets_path().len() - i) as u32;
795 }
796 }
797 }
798 key
799 }
800
801 let mut some_primary_dropped = false;
802 let primary_click_matches = gestures.primary_clicks.iter().filter_map(|(s, entry)| {
803 if entry.handle.is_dropped() {
804 some_primary_dropped = true;
805 return None;
806 }
807 if *s != shortcut {
808 return None;
809 }
810
811 let p = entry.resolve_path()?;
812 Some((distance_key(&focused, &p), Kind::Click(p, ShortcutClick::Primary)))
813 });
814
815 let mut some_ctx_dropped = false;
816 let context_click_matches = gestures.context_clicks.iter().filter_map(|(s, entry)| {
817 if entry.handle.is_dropped() {
818 some_ctx_dropped = true;
819 return None;
820 }
821 if *s != shortcut {
822 return None;
823 }
824
825 let p = entry.resolve_path()?;
826 Some((distance_key(&focused, &p), Kind::Click(p, ShortcutClick::Context)))
827 });
828
829 let mut some_focus_dropped = false;
830 let focus_matches = gestures.focus.iter().filter_map(|(s, entry)| {
831 if entry.handle.is_dropped() {
832 some_focus_dropped = true;
833 return None;
834 }
835 if *s != shortcut {
836 return None;
837 }
838
839 let p = entry.resolve_path()?;
840 Some((distance_key(&focused, &p), Kind::Focus(p)))
841 });
842
843 let mut cmd_window = vec![];
844 let mut cmd_app = vec![];
845 let cmd_matches = EVENTS.commands().into_iter().filter_map(|cmd| {
846 if !cmd.shortcut_matches(&shortcut) {
847 return None;
848 }
849
850 match cmd.scope() {
851 CommandScope::Window(w) => {
852 if let Some(f) = &focused {
853 if f.window_id() == w {
854 cmd_window.push(cmd);
855 }
856 }
857 }
858 CommandScope::Widget(id) => {
859 if let Some(info) = WINDOWS.widget_info(id) {
860 let p = info.interaction_path();
861 return Some((distance_key(&focused, &p), Kind::Command(p, cmd)));
862 }
863 }
864 CommandScope::App => cmd_app.push(cmd),
865 }
866
867 None
868 });
869
870 let mut best_kind = u8::MAX;
871 let mut best_distance = u32::MAX;
872 let mut best = None;
873
874 for (distance_key, choice) in primary_click_matches
875 .chain(cmd_matches)
876 .chain(context_click_matches)
877 .chain(focus_matches)
878 {
879 let kind_key = choice.kind_key();
880 match kind_key.cmp(&best_kind) {
881 std::cmp::Ordering::Less => {
882 best_kind = kind_key;
883 best_distance = distance_key;
884 best = Some(choice);
885 }
886 std::cmp::Ordering::Equal => {
887 if distance_key < best_distance {
888 best_distance = distance_key;
889 best = Some(choice);
890 }
891 }
892 std::cmp::Ordering::Greater => {}
893 }
894 }
895
896 let mut click = None;
897 let mut focus = None;
898 let mut commands = vec![];
899
900 match best {
901 Some(k) => match k {
902 Kind::Click(p, s) => click = Some((p, s)),
903 Kind::Command(_, cmd) => commands.push(cmd),
904 Kind::Focus(p) => focus = Some(p.widget_id()),
905 },
906 None => {
907 if let Some(p) = focused {
908 click = if gestures.click_focused.with(|c| c.contains(&shortcut)) {
909 Some((p, ShortcutClick::Primary))
910 } else if gestures.context_click_focused.with(|c| c.contains(&shortcut)) {
911 Some((p, ShortcutClick::Context))
912 } else {
913 None
914 };
915 }
916 }
917 }
918
919 commands.append(&mut cmd_window);
920 commands.append(&mut cmd_app);
921
922 if some_primary_dropped || some_ctx_dropped || some_focus_dropped {
923 gestures.cleanup();
924 }
925
926 Self {
927 shortcut,
928 focus,
929 click,
930 commands,
931 }
932 }
933
934 pub fn shortcut(&self) -> &Shortcut {
936 &self.shortcut
937 }
938
939 pub fn focus(&self) -> Option<FocusTarget> {
944 if let Some((p, _)) = &self.click {
945 return Some(FocusTarget::Direct { target: p.widget_id() });
946 } else if let Some(c) = self.commands.first() {
947 if let CommandScope::Widget(w) = c.scope() {
948 if FOCUS.focused().with(|f| f.as_ref().map(|p| !p.contains(w)).unwrap_or(true)) {
949 return Some(FocusTarget::Direct { target: w });
950 }
951 }
952 }
953 self.focus.map(|target| FocusTarget::DirectOrRelated {
954 target,
955 navigation_origin: true,
956 })
957 }
958
959 pub fn click(&self) -> Option<(&InteractionPath, ShortcutClick)> {
961 self.click.as_ref().map(|(p, k)| (p, *k))
962 }
963
964 pub fn commands(&self) -> &[Command] {
968 &self.commands
969 }
970
971 pub fn has_actions(&self) -> bool {
973 self.click.is_some() || self.focus.is_some() || !self.commands.is_empty()
974 }
975
976 fn run(&self, timestamp: DInstant, propagation: &EventPropagationHandle, device_id: Option<DeviceId>, repeat_count: u32) {
978 if let Some(target) = self.focus() {
979 FOCUS.focus(FocusRequest::new(target, true));
980 }
981
982 if let Some((target, kind)) = &self.click {
983 let args = ClickArgs::new(
984 timestamp,
985 propagation.clone(),
986 target.window_id(),
987 device_id,
988 ClickArgsSource::Shortcut {
989 shortcut: self.shortcut.clone(),
990 kind: *kind,
991 },
992 NonZeroU32::new(repeat_count.saturating_add(1)).unwrap(),
993 repeat_count > 0,
994 self.shortcut.modifiers_state(),
995 target.clone(),
996 );
997 CLICK_EVENT.notify(args);
998 }
999 for command in &self.commands {
1000 command.notify_linked(propagation.clone(), None);
1001 }
1002 }
1003}
1004
1005#[derive(Clone, PartialEq, Eq, Hash, Debug)]
1010#[repr(transparent)]
1011#[must_use = "the shortcuts claim is removed if the handle is dropped"]
1012pub struct ShortcutsHandle(Handle<()>);
1013impl ShortcutsHandle {
1014 pub(super) fn new() -> (HandleOwner<()>, Self) {
1015 let (owner, handle) = Handle::new(());
1016 (owner, ShortcutsHandle(handle))
1017 }
1018
1019 pub fn dummy() -> Self {
1023 ShortcutsHandle(Handle::dummy(()))
1024 }
1025
1026 pub fn perm(self) {
1033 self.0.perm();
1034 }
1035
1036 pub fn is_permanent(&self) -> bool {
1040 self.0.is_permanent()
1041 }
1042
1043 pub fn release(self) {
1045 self.0.force_drop();
1046 }
1047
1048 pub fn is_released(&self) -> bool {
1052 self.0.is_dropped()
1053 }
1054
1055 pub fn downgrade(&self) -> WeakShortcutsHandle {
1057 WeakShortcutsHandle(self.0.downgrade())
1058 }
1059}
1060
1061#[derive(Clone, PartialEq, Eq, Hash, Default, Debug)]
1063pub struct WeakShortcutsHandle(pub(super) WeakHandle<()>);
1064impl WeakShortcutsHandle {
1065 pub fn new() -> Self {
1067 Self(WeakHandle::new())
1068 }
1069
1070 pub fn upgrade(&self) -> Option<ShortcutsHandle> {
1072 self.0.upgrade().map(ShortcutsHandle)
1073 }
1074}
1075
1076pub trait HeadlessAppGestureExt {
1080 fn press_shortcut(&mut self, window_id: WindowId, shortcut: impl Into<Shortcut>);
1082}
1083impl HeadlessAppGestureExt for HeadlessApp {
1084 fn press_shortcut(&mut self, window_id: WindowId, shortcut: impl Into<Shortcut>) {
1085 let shortcut = shortcut.into();
1086 match shortcut {
1087 Shortcut::Modifier(m) => {
1088 let (code, key) = m.left_key();
1089 self.press_key(window_id, code, KeyLocation::Standard, key);
1090 }
1091 Shortcut::Gesture(g) => match g.key {
1092 GestureKey::Key(k) => self.press_modified_key(
1093 window_id,
1094 g.modifiers,
1095 KeyCode::Unidentified(NativeKeyCode::Unidentified),
1096 KeyLocation::Standard,
1097 k,
1098 ),
1099 GestureKey::Code(c) => self.press_modified_key(window_id, g.modifiers, c, KeyLocation::Standard, Key::Unidentified),
1100 },
1101 Shortcut::Chord(c) => {
1102 self.press_shortcut(window_id, c.starter);
1103 self.press_shortcut(window_id, c.complement);
1104 }
1105 }
1106 }
1107}
1108
1109pub trait CommandShortcutMatchesExt: CommandShortcutExt {
1111 fn shortcut_matches(self, shortcut: &Shortcut) -> bool;
1113}
1114impl CommandShortcutMatchesExt for Command {
1115 fn shortcut_matches(self, shortcut: &Shortcut) -> bool {
1116 if !self.has_handlers().get() {
1117 return false;
1118 }
1119
1120 let s = self.shortcut();
1121 if s.with(|s| !s.contains(shortcut)) {
1122 return false;
1123 }
1124
1125 let filter = self.shortcut_filter().get();
1126 if filter.is_empty() {
1127 return true;
1128 }
1129 if filter.contains(ShortcutFilter::CMD_ENABLED) && !self.is_enabled_value() {
1130 return false;
1131 }
1132
1133 match self.scope() {
1134 CommandScope::App => filter == ShortcutFilter::CMD_ENABLED,
1135 CommandScope::Window(id) => {
1136 if filter.contains(ShortcutFilter::FOCUSED) {
1137 FOCUS.focused().with(|p| {
1138 let p = match p {
1139 Some(p) => p,
1140 None => return false,
1141 };
1142 if p.window_id() != id {
1143 return false;
1144 }
1145 !filter.contains(ShortcutFilter::ENABLED) || p.interaction_path().next().map(|i| i.is_enabled()).unwrap_or(false)
1146 })
1147 } else if filter.contains(ShortcutFilter::ENABLED) {
1148 let tree = match WINDOWS.widget_tree(id) {
1149 Ok(t) => t,
1150 Err(_) => return false,
1151 };
1152
1153 tree.root().interactivity().is_enabled()
1154 } else {
1155 true
1156 }
1157 }
1158 CommandScope::Widget(id) => {
1159 if filter.contains(ShortcutFilter::FOCUSED) {
1160 FOCUS.focused().with(|p| {
1161 let p = match p {
1162 Some(p) => p,
1163 None => return false,
1164 };
1165 if !p.contains(id) {
1166 return false;
1167 }
1168 !filter.contains(ShortcutFilter::ENABLED) || p.interactivity_of(id).map(|i| i.is_enabled()).unwrap_or(false)
1169 })
1170 } else if filter.contains(ShortcutFilter::ENABLED) {
1171 if let Some(w) = WINDOWS.widget_info(id) {
1172 return w.interactivity().is_enabled();
1173 }
1174
1175 false
1176 } else {
1177 true
1178 }
1179 }
1180 }
1181 }
1182}