1use std::any::Any;
2use std::cell::{Cell, RefCell};
3
4use std::collections::HashMap;
5
6use std::rc::{Rc, Weak};
7
8use incular_core::{Code, KeyboardEvent, KeyboardKey, Modifiers};
9
10use crate::details::GestureCallbacks;
11
12impl GestureCallbacks {
13 #[must_use]
15 pub fn has_keyboard_listener(&self) -> bool {
16 self.on_key.is_some()
17 || self.on_key_down.is_some()
18 || self.on_key_repeat.is_some()
19 || self.on_key_up.is_some()
20 || self.on_shortcut.is_some()
21 || self.focus_node.is_some()
22 }
23
24 #[must_use]
30 pub fn services_semantic_keys(&self) -> bool {
31 self.has_keyboard_listener() && self.include_semantics
32 }
33
34 #[must_use]
39 pub fn handle_keyboard(&self, event: KeyboardEvent) -> bool {
40 if self
41 .on_shortcut
42 .as_ref()
43 .is_some_and(|callback| callback(event.clone()))
44 {
45 return true;
46 }
47 if self
48 .on_key
49 .as_ref()
50 .is_some_and(|callback| callback(event.clone()))
51 {
52 return true;
53 }
54 if event.state.is_down() {
57 if let Some(callback) = &self.on_key_down {
58 callback(event.clone());
59 }
60 if event.repeat
61 && let Some(callback) = &self.on_key_repeat
62 {
63 callback(event);
64 }
65 } else if let Some(callback) = &self.on_key_up {
66 callback(event);
67 }
68 false
69 }
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
74pub struct ShortcutKey {
75 pub code: Code,
76 pub modifiers: Modifiers,
77}
78
79#[derive(Clone, Debug, PartialEq, Eq, Hash)]
87pub struct Command(String);
88impl Command {
89 #[must_use]
90 pub fn new(name: impl Into<String>) -> Self {
91 Self(name.into())
92 }
93 #[must_use]
94 pub fn name(&self) -> &str {
95 &self.0
96 }
97}
98impl From<&str> for Command {
99 fn from(value: &str) -> Self {
100 Self::new(value)
101 }
102}
103
104pub trait CommandId: Clone + Eq + std::hash::Hash + 'static {}
110
111impl<T> CommandId for T where T: Clone + Eq + std::hash::Hash + 'static {}
112
113#[derive(Clone, Debug, PartialEq, Eq, Hash)]
115pub struct Intent<C: CommandId = Command> {
116 command: C,
117}
118
119impl<C: CommandId> Intent<C> {
120 #[must_use]
122 pub fn new(command: impl Into<C>) -> Self {
123 Self {
124 command: command.into(),
125 }
126 }
127
128 #[must_use]
130 pub fn command(&self) -> &C {
131 &self.command
132 }
133}
134
135impl Intent<Command> {
136 #[must_use]
138 pub fn name(&self) -> &str {
139 self.command.name()
140 }
141}
142
143impl<C: CommandId> From<C> for Intent<C> {
144 fn from(value: C) -> Self {
145 Self::new(value)
146 }
147}
148
149impl From<&str> for Intent {
150 fn from(value: &str) -> Self {
151 Self::new(value)
152 }
153}
154
155#[derive(Clone)]
159pub struct ErasedIntent {
160 command: Rc<dyn Any>,
161}
162
163impl ErasedIntent {
164 #[must_use]
165 pub fn from_intent<C: CommandId>(intent: &Intent<C>) -> Self {
166 Self {
167 command: Rc::new(intent.command().clone()),
168 }
169 }
170
171 #[must_use]
172 pub fn downcast_command<C: CommandId>(&self) -> Option<&C> {
173 self.command.as_ref().downcast_ref::<C>()
174 }
175}
176
177#[derive(Clone, Copy, Debug, PartialEq, Eq)]
179pub enum ActionResult {
180 Handled,
182 Ignored,
184}
185
186impl From<bool> for ActionResult {
187 fn from(handled: bool) -> Self {
188 if handled {
189 Self::Handled
190 } else {
191 Self::Ignored
192 }
193 }
194}
195
196struct ActionListenerState {
197 listeners: RefCell<Vec<Weak<ActionListenerEntry>>>,
198}
199
200struct ActionListenerEntry {
201 active: Cell<bool>,
202 callback: Rc<dyn Fn()>,
203}
204
205pub struct ActionListenerSubscription {
208 state: Weak<ActionListenerState>,
209 entry: Rc<ActionListenerEntry>,
210}
211
212#[derive(Clone, Copy, Debug, PartialEq, Eq)]
214pub enum ActionInvocationPhase {
215 Start,
216 End,
217}
218
219struct ActionInvocationListenerState {
220 listeners: RefCell<Vec<Weak<ActionInvocationListenerEntry>>>,
221}
222
223struct ActionInvocationListenerEntry {
224 active: Cell<bool>,
225 callback: Rc<dyn Fn(ActionInvocationPhase)>,
226}
227
228pub struct ActionInvocationSubscription {
230 state: Weak<ActionInvocationListenerState>,
231 entry: Rc<ActionInvocationListenerEntry>,
232}
233
234impl ActionInvocationSubscription {
235 fn remove(&self) -> bool {
236 let was_active = self.entry.active.replace(false);
237 if let Some(state) = self.state.upgrade() {
238 state.listeners.borrow_mut().retain(|listener| {
239 listener
240 .upgrade()
241 .is_some_and(|listener| !Rc::ptr_eq(&listener, &self.entry))
242 });
243 }
244 was_active
245 }
246}
247
248impl Drop for ActionInvocationSubscription {
249 fn drop(&mut self) {
250 let _ = self.remove();
251 }
252}
253
254impl ActionListenerSubscription {
255 fn remove(&self) -> bool {
256 let was_active = self.entry.active.replace(false);
257 if let Some(state) = self.state.upgrade() {
258 state.listeners.borrow_mut().retain(|listener| {
259 listener
260 .upgrade()
261 .is_some_and(|listener| !Rc::ptr_eq(&listener, &self.entry))
262 });
263 }
264 was_active
265 }
266}
267
268impl Drop for ActionListenerSubscription {
269 fn drop(&mut self) {
270 let _ = self.remove();
271 }
272}
273
274type ActionCallback<C> = Rc<dyn Fn(&Intent<C>) -> ActionResult>;
280
281#[derive(Clone)]
282pub struct Action<C: CommandId = Command> {
283 intent: Intent<C>,
284 enabled: Rc<Cell<bool>>,
285 callback: ActionCallback<C>,
286 listeners: Rc<ActionListenerState>,
287 invocation_listeners: Rc<ActionInvocationListenerState>,
288}
289
290impl<C: CommandId + std::fmt::Debug> std::fmt::Debug for Action<C> {
291 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292 formatter
293 .debug_struct("Action")
294 .field("intent", &self.intent)
295 .field("enabled", &self.is_enabled())
296 .finish_non_exhaustive()
297 }
298}
299
300impl<C: CommandId> Action<C> {
301 #[must_use]
303 pub fn new(command: impl Into<Intent<C>>, callback: impl Fn() + 'static) -> Self {
304 Self::with_handler(command, move |_| {
305 callback();
306 ActionResult::Handled
307 })
308 }
309
310 #[must_use]
312 pub fn with_handler<R>(
313 command: impl Into<Intent<C>>,
314 callback: impl Fn(&Intent<C>) -> R + 'static,
315 ) -> Self
316 where
317 R: Into<ActionResult> + 'static,
318 {
319 Self {
320 intent: command.into(),
321 enabled: Rc::new(Cell::new(true)),
322 callback: Rc::new(move |intent| callback(intent).into()),
323 listeners: Rc::new(ActionListenerState {
324 listeners: RefCell::new(Vec::new()),
325 }),
326 invocation_listeners: Rc::new(ActionInvocationListenerState {
327 listeners: RefCell::new(Vec::new()),
328 }),
329 }
330 }
331
332 #[must_use]
334 pub fn intent(&self) -> &Intent<C> {
335 &self.intent
336 }
337
338 #[must_use]
340 pub fn command(&self) -> &C {
341 self.intent.command()
342 }
343
344 pub fn set_enabled(&self, enabled: bool) {
346 if self.enabled.replace(enabled) != enabled {
347 self.notify_action_listeners();
348 }
349 }
350
351 #[must_use]
353 pub fn is_enabled(&self) -> bool {
354 self.enabled.get()
355 }
356
357 #[must_use]
359 pub fn is_action_enabled(&self, intent: &Intent<C>) -> bool {
360 self.is_enabled() && self.intent.command() == intent.command()
361 }
362
363 #[must_use]
366 pub fn add_action_listener(&self, listener: impl Fn() + 'static) -> ActionListenerSubscription {
367 let entry = Rc::new(ActionListenerEntry {
368 active: Cell::new(true),
369 callback: Rc::new(listener),
370 });
371 self.listeners
372 .listeners
373 .borrow_mut()
374 .push(Rc::downgrade(&entry));
375 ActionListenerSubscription {
376 state: Rc::downgrade(&self.listeners),
377 entry,
378 }
379 }
380
381 #[must_use]
383 pub fn add_listener(&self, listener: impl Fn() + 'static) -> ActionListenerSubscription {
384 self.add_action_listener(listener)
385 }
386
387 pub fn remove_action_listener(&self, subscription: &ActionListenerSubscription) -> bool {
389 subscription.remove()
390 }
391
392 pub fn notify_action_listeners(&self) {
396 let listeners = {
397 let mut listeners = self.listeners.listeners.borrow_mut();
398 listeners.retain(|listener| listener.strong_count() != 0);
399 listeners
400 .iter()
401 .filter_map(Weak::upgrade)
402 .collect::<Vec<_>>()
403 };
404 for listener in listeners {
405 if listener.active.get() {
406 (listener.callback)();
407 }
408 }
409 }
410
411 #[must_use]
414 pub fn add_invocation_listener(
415 &self,
416 listener: impl Fn(ActionInvocationPhase) + 'static,
417 ) -> ActionInvocationSubscription {
418 let entry = Rc::new(ActionInvocationListenerEntry {
419 active: Cell::new(true),
420 callback: Rc::new(listener),
421 });
422 self.invocation_listeners
423 .listeners
424 .borrow_mut()
425 .push(Rc::downgrade(&entry));
426 ActionInvocationSubscription {
427 state: Rc::downgrade(&self.invocation_listeners),
428 entry,
429 }
430 }
431
432 fn notify_invocation(&self, phase: ActionInvocationPhase) {
433 let listeners = {
434 let mut listeners = self.invocation_listeners.listeners.borrow_mut();
435 listeners.retain(|listener| listener.strong_count() != 0);
436 listeners
437 .iter()
438 .filter_map(Weak::upgrade)
439 .collect::<Vec<_>>()
440 };
441 for listener in listeners {
442 if listener.active.get() {
443 (listener.callback)(phase);
444 }
445 }
446 }
447
448 #[must_use]
449 pub fn listener_count(&self) -> usize {
450 let mut listeners = self.listeners.listeners.borrow_mut();
451 listeners.retain(|listener| listener.strong_count() != 0);
452 listeners
453 .iter()
454 .filter_map(Weak::upgrade)
455 .filter(|listener| listener.active.get())
456 .count()
457 }
458
459 #[must_use]
461 pub fn invoke(&self, intent: &Intent<C>) -> ActionResult {
462 if !self.is_action_enabled(intent) {
463 return ActionResult::Ignored;
464 }
465 self.notify_invocation(ActionInvocationPhase::Start);
466 let result = (self.callback)(intent);
467 self.notify_invocation(ActionInvocationPhase::End);
468 result
469 }
470}
471
472#[derive(Clone)]
474pub enum ShortcutMatch {
475 Callback(Rc<dyn Fn()>),
476 Intent(ErasedIntent),
477}
478
479pub struct ErasedShortcutScope {
482 finder: Rc<dyn Fn(KeyboardEvent) -> Option<ShortcutMatch>>,
483}
484
485impl ErasedShortcutScope {
486 #[must_use]
487 pub fn from_shortcuts<C: CommandId>(shortcuts: Rc<Shortcuts<C>>) -> Rc<Self> {
488 Rc::new(Self {
489 finder: Rc::new(move |event| shortcuts.find_match(&event)),
490 })
491 }
492
493 #[must_use]
494 pub fn find(&self, event: KeyboardEvent) -> Option<ShortcutMatch> {
495 (self.finder)(event)
496 }
497}
498
499type ErasedActionInvoker = dyn Fn(&ErasedIntent) -> Option<ActionResult>;
502
503pub struct ErasedActionScope {
504 invoker: Rc<ErasedActionInvoker>,
505}
506
507impl ErasedActionScope {
508 #[must_use]
509 pub fn from_actions<C: CommandId>(actions: Rc<Actions<C>>) -> Rc<Self> {
510 Rc::new(Self {
511 invoker: Rc::new(move |intent| {
512 let command = intent.downcast_command::<C>()?;
513 let intent = Intent::new(command.clone());
514 actions.invoke_intent_first(&intent)
515 }),
516 })
517 }
518
519 #[must_use]
520 pub fn invoke(&self, intent: &ErasedIntent) -> Option<ActionResult> {
521 (self.invoker)(intent)
522 }
523}
524
525struct ActionScope<C: CommandId> {
526 actions: HashMap<C, Action<C>>,
527}
528
529pub struct Actions<C: CommandId = Command> {
535 scopes: Vec<ActionScope<C>>,
536}
537
538impl<C: CommandId> Default for Actions<C> {
539 fn default() -> Self {
540 Self {
541 scopes: vec![ActionScope::<C> {
542 actions: HashMap::new(),
543 }],
544 }
545 }
546}
547
548impl<C: CommandId> Actions<C> {
549 #[must_use]
552 pub fn typed() -> Self {
553 Self::default()
554 }
555
556 pub fn register(&mut self, command: impl Into<C>, action: impl Fn() + 'static) {
558 let command = command.into();
559 self.register_action(Action::new(Intent::new(command), action));
560 }
561
562 pub fn register_action(&mut self, action: Action<C>) {
564 self.current_scope_mut()
565 .actions
566 .insert(action.command().clone(), action);
567 }
568
569 pub fn register_handler<R>(
571 &mut self,
572 command: impl Into<Intent<C>>,
573 action: impl Fn(&Intent<C>) -> R + 'static,
574 ) where
575 R: Into<ActionResult> + 'static,
576 {
577 self.register_action(Action::with_handler(command, action));
578 }
579
580 pub fn push_scope(&mut self) {
583 self.scopes.push(ActionScope::<C> {
584 actions: HashMap::new(),
585 });
586 }
587
588 #[must_use]
590 pub fn pop_scope(&mut self) -> bool {
591 (self.scopes.len() > 1).then(|| self.scopes.pop()).is_some()
592 }
593
594 pub fn with_scope<R>(&mut self, callback: impl FnOnce(&mut Self) -> R) -> R {
597 self.push_scope();
598 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(self)));
599 let _ = self.pop_scope();
600 match result {
601 Ok(result) => result,
602 Err(panic) => std::panic::resume_unwind(panic),
603 }
604 }
605
606 #[must_use]
608 pub fn scope_depth(&self) -> usize {
609 self.scopes.len()
610 }
611
612 #[must_use]
614 pub fn is_empty(&self) -> bool {
615 self.scopes.iter().all(|scope| scope.actions.is_empty())
616 }
617
618 pub fn unregister(&mut self, command: &C) -> bool {
620 self.current_scope_mut().actions.remove(command).is_some()
621 }
622
623 #[must_use]
624 pub fn invoke(&self, command: &C) -> bool {
625 self.invoke_intent(&Intent::new(command.clone()))
626 }
627
628 #[must_use]
630 pub fn invoke_intent(&self, intent: &Intent<C>) -> bool {
631 self.scopes.iter().rev().any(|scope| {
632 scope
633 .actions
634 .get(intent.command())
635 .is_some_and(|action| action.invoke(intent) == ActionResult::Handled)
636 })
637 }
638
639 #[must_use]
644 pub fn invoke_intent_first(&self, intent: &Intent<C>) -> Option<ActionResult> {
645 self.scopes.iter().rev().find_map(|scope| {
646 scope
647 .actions
648 .get(intent.command())
649 .map(|action| action.invoke(intent))
650 })
651 }
652
653 #[must_use]
655 pub fn action(&self, command: &C) -> Option<Action<C>> {
656 self.scopes
657 .iter()
658 .rev()
659 .find_map(|scope| scope.actions.get(command).cloned())
660 }
661
662 fn current_scope_mut(&mut self) -> &mut ActionScope<C> {
663 if self.scopes.is_empty() {
664 self.scopes.push(ActionScope::<C> {
665 actions: HashMap::new(),
666 });
667 }
668 self.scopes
669 .last_mut()
670 .expect("action registry has a root scope")
671 }
672}
673
674impl Actions<Command> {
675 #[must_use]
677 pub fn new() -> Self {
678 Self::default()
679 }
680}
681
682impl ShortcutKey {
683 #[must_use]
684 pub const fn new(code: Code, modifiers: Modifiers) -> Self {
685 Self { code, modifiers }
686 }
687
688 #[must_use]
691 pub fn logical(key: KeyboardKey, modifiers: Modifiers) -> LogicalShortcutKey {
692 LogicalShortcutKey { key, modifiers }
693 }
694}
695
696#[derive(Clone, Debug, PartialEq, Eq, Hash)]
701pub struct SingleActivator {
702 pub trigger: KeyboardKey,
703 pub modifiers: Modifiers,
704 pub include_repeats: bool,
705}
706
707impl SingleActivator {
708 #[must_use]
709 pub fn new(trigger: KeyboardKey, modifiers: Modifiers) -> Self {
710 Self {
711 trigger,
712 modifiers: shortcut_modifiers(modifiers),
713 include_repeats: true,
714 }
715 }
716
717 #[must_use]
718 pub fn with_repeats(mut self, include_repeats: bool) -> Self {
719 self.include_repeats = include_repeats;
720 self
721 }
722
723 #[must_use]
724 pub const fn trigger(&self) -> &KeyboardKey {
725 &self.trigger
726 }
727
728 #[must_use]
729 pub const fn modifiers(&self) -> Modifiers {
730 self.modifiers
731 }
732
733 #[must_use]
734 pub const fn includes_repeats(&self) -> bool {
735 self.include_repeats
736 }
737
738 #[must_use]
739 pub fn accepts(&self, event: &KeyboardEvent) -> bool {
740 event.state.is_down()
741 && (self.include_repeats || !event.repeat)
742 && event.key == self.trigger
743 && shortcut_modifiers(event.modifiers) == self.modifiers
744 }
745}
746
747#[derive(Clone, Debug, PartialEq, Eq, Hash)]
754pub enum ShortcutActivator {
755 Physical {
756 key: ShortcutKey,
757 trigger: ShortcutTrigger,
758 },
759 Logical {
760 key: LogicalShortcutKey,
761 trigger: ShortcutTrigger,
762 },
763 Single(SingleActivator),
764}
765
766impl ShortcutActivator {
767 #[must_use]
768 pub const fn physical(key: ShortcutKey) -> Self {
769 Self::Physical {
770 key,
771 trigger: ShortcutTrigger::Down,
772 }
773 }
774
775 #[must_use]
776 pub const fn physical_on(key: ShortcutKey, trigger: ShortcutTrigger) -> Self {
777 Self::Physical { key, trigger }
778 }
779
780 #[must_use]
781 pub fn logical(key: LogicalShortcutKey) -> Self {
782 Self::Logical {
783 key,
784 trigger: ShortcutTrigger::Down,
785 }
786 }
787
788 #[must_use]
789 pub fn logical_on(key: LogicalShortcutKey, trigger: ShortcutTrigger) -> Self {
790 Self::Logical { key, trigger }
791 }
792
793 #[must_use]
794 pub fn single(trigger: KeyboardKey, modifiers: Modifiers) -> Self {
795 Self::Single(SingleActivator::new(trigger, modifiers))
796 }
797
798 #[must_use]
799 pub fn accepts(&self, event: &KeyboardEvent) -> bool {
800 match self {
801 Self::Physical { key, trigger } => {
802 key.code == event.code && key.modifiers == event.modifiers && trigger.matches(event)
803 }
804 Self::Logical { key, trigger } => {
805 key.key == event.key && key.modifiers == event.modifiers && trigger.matches(event)
806 }
807 Self::Single(activator) => activator.accepts(event),
808 }
809 }
810}
811
812impl From<ShortcutKey> for ShortcutActivator {
813 fn from(value: ShortcutKey) -> Self {
814 Self::physical(value)
815 }
816}
817
818impl From<LogicalShortcutKey> for ShortcutActivator {
819 fn from(value: LogicalShortcutKey) -> Self {
820 Self::logical(value)
821 }
822}
823
824impl From<SingleActivator> for ShortcutActivator {
825 fn from(value: SingleActivator) -> Self {
826 Self::Single(value)
827 }
828}
829
830#[derive(Clone, Debug, PartialEq, Eq, Hash)]
837pub struct LogicalKeySet {
838 keys: Vec<KeyboardKey>,
839}
840
841impl LogicalKeySet {
842 #[must_use]
845 pub fn new(keys: impl IntoIterator<Item = KeyboardKey>) -> Self {
846 let mut unique = Vec::new();
847 for key in keys {
848 if !unique.contains(&key) {
849 unique.push(key);
850 }
851 }
852 Self { keys: unique }
853 }
854
855 #[must_use]
856 pub fn keys(&self) -> &[KeyboardKey] {
857 &self.keys
858 }
859
860 fn modifier_of(key: &KeyboardKey) -> Option<Modifiers> {
861 use incular_core::NamedKey;
862 match key {
863 KeyboardKey::Named(NamedKey::Alt) => Some(Modifiers::ALT),
864 KeyboardKey::Named(NamedKey::Control) => Some(Modifiers::CONTROL),
865 KeyboardKey::Named(NamedKey::Meta | NamedKey::Super) => Some(Modifiers::META),
866 KeyboardKey::Named(NamedKey::Shift) => Some(Modifiers::SHIFT),
867 _ => None,
868 }
869 }
870
871 #[must_use]
875 pub fn activator(&self) -> Option<SingleActivator> {
876 let mut modifiers = Modifiers::empty();
877 let mut trigger = None;
878 for key in &self.keys {
879 match Self::modifier_of(key) {
880 Some(modifier) => modifiers |= modifier,
881 None if trigger.is_some() => return None,
882 None => trigger = Some(key.clone()),
883 }
884 }
885 match trigger {
886 Some(trigger) => Some(SingleActivator::new(trigger, modifiers)),
887 None => {
888 let last = self.keys.last()?.clone();
889 Some(SingleActivator::new(last, modifiers))
890 }
891 }
892 }
893
894 #[must_use]
897 pub fn accepts(&self, event: &KeyboardEvent) -> bool {
898 self.activator().is_some_and(|activator| {
899 if Self::modifier_of(&activator.trigger).is_some() {
900 event.state.is_down()
903 && self.keys.contains(&event.key)
904 && shortcut_modifiers(event.modifiers)
905 | Self::modifier_of(&event.key).unwrap_or_default()
906 == activator.modifiers
907 } else {
908 activator.accepts(event)
909 }
910 })
911 }
912}
913
914impl From<LogicalKeySet> for ShortcutActivator {
915 fn from(value: LogicalKeySet) -> Self {
917 match value.activator() {
918 Some(activator) => Self::Single(activator),
919 None => Self::logical(LogicalShortcutKey {
920 key: KeyboardKey::Named(incular_core::NamedKey::Unidentified),
921 modifiers: Modifiers::all(),
922 }),
923 }
924 }
925}
926
927fn shortcut_modifiers(modifiers: Modifiers) -> Modifiers {
928 modifiers & (Modifiers::ALT | Modifiers::CONTROL | Modifiers::META | Modifiers::SHIFT)
929}
930
931#[derive(Clone, Debug, PartialEq, Eq, Hash)]
933pub struct LogicalShortcutKey {
934 pub key: KeyboardKey,
935 pub modifiers: Modifiers,
936}
937
938impl LogicalShortcutKey {
939 #[must_use]
940 pub fn new(key: KeyboardKey, modifiers: Modifiers) -> Self {
941 Self { key, modifiers }
942 }
943}
944
945#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
947pub enum ShortcutTrigger {
948 #[default]
950 Down,
951 Press,
953 Repeat,
955 Up,
957 Any,
959}
960
961impl ShortcutTrigger {
962 #[must_use]
963 pub const fn matches(self, event: &KeyboardEvent) -> bool {
964 match self {
965 Self::Down => event.state.is_down(),
966 Self::Press => event.state.is_down() && !event.repeat,
967 Self::Repeat => event.state.is_down() && event.repeat,
968 Self::Up => event.state.is_up(),
969 Self::Any => true,
970 }
971 }
972}
973
974enum ShortcutBinding<C: CommandId> {
975 Callback(Rc<dyn Fn()>),
976 Intent(Intent<C>),
977}
978
979struct ShortcutRegistration<C: CommandId> {
980 trigger: ShortcutTrigger,
981 binding: ShortcutBinding<C>,
982}
983
984struct ShortcutScope<C: CommandId> {
985 physical: HashMap<ShortcutKey, Vec<ShortcutRegistration<C>>>,
986 logical: HashMap<LogicalShortcutKey, Vec<ShortcutRegistration<C>>>,
987}
988
989pub struct Shortcuts<C: CommandId = Command> {
991 scopes: Vec<ShortcutScope<C>>,
992}
993
994impl<C: CommandId> Default for Shortcuts<C> {
995 fn default() -> Self {
996 Self {
997 scopes: vec![ShortcutScope {
998 physical: HashMap::new(),
999 logical: HashMap::new(),
1000 }],
1001 }
1002 }
1003}
1004
1005impl<C: CommandId> Shortcuts<C> {
1006 #[must_use]
1009 pub fn typed() -> Self {
1010 Self::default()
1011 }
1012
1013 pub fn register(&mut self, key: ShortcutKey, action: impl Fn() + 'static) {
1014 self.register_on(key, ShortcutTrigger::Down, action);
1015 }
1016
1017 pub fn register_on(
1019 &mut self,
1020 key: ShortcutKey,
1021 trigger: ShortcutTrigger,
1022 action: impl Fn() + 'static,
1023 ) {
1024 Self::replace_binding(
1025 &mut self.current_scope_mut().physical,
1026 key,
1027 trigger,
1028 ShortcutBinding::Callback(Rc::new(action)),
1029 );
1030 }
1031
1032 pub fn register_logical(&mut self, key: LogicalShortcutKey, action: impl Fn() + 'static) {
1034 self.register_logical_on(key, ShortcutTrigger::Down, action);
1035 }
1036
1037 pub fn register_logical_on(
1039 &mut self,
1040 key: LogicalShortcutKey,
1041 trigger: ShortcutTrigger,
1042 action: impl Fn() + 'static,
1043 ) {
1044 Self::replace_binding(
1045 &mut self.current_scope_mut().logical,
1046 key,
1047 trigger,
1048 ShortcutBinding::Callback(Rc::new(action)),
1049 );
1050 }
1051
1052 pub fn bind(&mut self, key: ShortcutKey, command: impl Into<C>) {
1054 self.bind_on(key, ShortcutTrigger::Down, Intent::new(command.into()));
1055 }
1056
1057 pub fn bind_on(
1059 &mut self,
1060 key: ShortcutKey,
1061 trigger: ShortcutTrigger,
1062 command: impl Into<Intent<C>>,
1063 ) {
1064 Self::replace_binding(
1065 &mut self.current_scope_mut().physical,
1066 key,
1067 trigger,
1068 ShortcutBinding::Intent(command.into()),
1069 );
1070 }
1071
1072 pub fn bind_logical(&mut self, key: LogicalShortcutKey, command: impl Into<C>) {
1074 self.bind_logical_on(key, ShortcutTrigger::Down, Intent::new(command.into()));
1075 }
1076
1077 pub fn bind_logical_on(
1079 &mut self,
1080 key: LogicalShortcutKey,
1081 trigger: ShortcutTrigger,
1082 command: impl Into<Intent<C>>,
1083 ) {
1084 Self::replace_binding(
1085 &mut self.current_scope_mut().logical,
1086 key,
1087 trigger,
1088 ShortcutBinding::Intent(command.into()),
1089 );
1090 }
1091
1092 pub fn clear(&mut self, key: &ShortcutKey) -> bool {
1094 self.current_scope_mut().physical.remove(key).is_some()
1095 }
1096
1097 pub fn clear_logical(&mut self, key: &LogicalShortcutKey) -> bool {
1099 self.current_scope_mut().logical.remove(key).is_some()
1100 }
1101
1102 pub fn push_scope(&mut self) {
1104 self.scopes.push(ShortcutScope {
1105 physical: HashMap::new(),
1106 logical: HashMap::new(),
1107 });
1108 }
1109
1110 #[must_use]
1112 pub fn pop_scope(&mut self) -> bool {
1113 (self.scopes.len() > 1).then(|| self.scopes.pop()).is_some()
1114 }
1115
1116 pub fn with_scope<R>(&mut self, callback: impl FnOnce(&mut Self) -> R) -> R {
1118 self.push_scope();
1119 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(self)));
1120 let _ = self.pop_scope();
1121 match result {
1122 Ok(result) => result,
1123 Err(panic) => std::panic::resume_unwind(panic),
1124 }
1125 }
1126
1127 #[must_use]
1129 pub fn scope_depth(&self) -> usize {
1130 self.scopes.len()
1131 }
1132
1133 #[must_use]
1135 pub fn is_empty(&self) -> bool {
1136 self.scopes.iter().all(|scope| {
1137 scope.physical.values().all(Vec::is_empty) && scope.logical.values().all(Vec::is_empty)
1138 })
1139 }
1140
1141 pub fn handle(&self, event: KeyboardEvent) -> bool {
1142 self.dispatch(event, None)
1143 }
1144
1145 #[must_use]
1148 pub fn handle_actions(&self, event: KeyboardEvent, actions: &Actions<C>) -> bool {
1149 self.dispatch(event, Some(actions))
1150 }
1151
1152 #[must_use]
1154 pub fn handle_event(&self, event: KeyboardEvent, actions: &Actions<C>) -> bool {
1155 self.handle_actions(event, actions)
1156 }
1157
1158 #[must_use]
1160 pub fn erased_scope(self: &Rc<Self>) -> Rc<ErasedShortcutScope> {
1161 ErasedShortcutScope::from_shortcuts(self.clone())
1162 }
1163
1164 fn replace_binding<K>(
1165 bindings: &mut HashMap<K, Vec<ShortcutRegistration<C>>>,
1166 key: K,
1167 trigger: ShortcutTrigger,
1168 binding: ShortcutBinding<C>,
1169 ) where
1170 K: Eq + std::hash::Hash,
1171 {
1172 let registrations = bindings.entry(key).or_default();
1173 registrations.retain(|registration| registration.trigger != trigger);
1174 registrations.push(ShortcutRegistration { trigger, binding });
1175 }
1176
1177 fn dispatch(&self, event: KeyboardEvent, actions: Option<&Actions<C>>) -> bool {
1178 let physical = ShortcutKey::new(event.code, event.modifiers);
1179 let logical = LogicalShortcutKey::new(event.key.clone(), event.modifiers);
1180 for scope in self.scopes.iter().rev() {
1181 if scope.physical.get(&physical).is_some_and(|registrations| {
1182 Self::dispatch_registrations(registrations, &event, actions)
1183 }) || scope.logical.get(&logical).is_some_and(|registrations| {
1184 Self::dispatch_registrations(registrations, &event, actions)
1185 }) {
1186 return true;
1187 }
1188 }
1189 false
1190 }
1191
1192 fn find_match(&self, event: &KeyboardEvent) -> Option<ShortcutMatch> {
1193 let physical = ShortcutKey::new(event.code, event.modifiers);
1194 let logical = LogicalShortcutKey::new(event.key.clone(), event.modifiers);
1195 for scope in self.scopes.iter().rev() {
1196 if let Some(registration) = scope.physical.get(&physical).and_then(|registrations| {
1197 registrations
1198 .iter()
1199 .find(|registration| registration.trigger.matches(event))
1200 }) {
1201 return Some(Self::erase_binding(®istration.binding));
1202 }
1203 if let Some(registration) = scope.logical.get(&logical).and_then(|registrations| {
1204 registrations
1205 .iter()
1206 .find(|registration| registration.trigger.matches(event))
1207 }) {
1208 return Some(Self::erase_binding(®istration.binding));
1209 }
1210 }
1211 None
1212 }
1213
1214 fn erase_binding(binding: &ShortcutBinding<C>) -> ShortcutMatch {
1215 match binding {
1216 ShortcutBinding::Callback(callback) => ShortcutMatch::Callback(callback.clone()),
1217 ShortcutBinding::Intent(intent) => {
1218 ShortcutMatch::Intent(ErasedIntent::from_intent(intent))
1219 }
1220 }
1221 }
1222
1223 fn dispatch_registrations(
1224 registrations: &[ShortcutRegistration<C>],
1225 event: &KeyboardEvent,
1226 actions: Option<&Actions<C>>,
1227 ) -> bool {
1228 registrations
1229 .iter()
1230 .filter(|registration| registration.trigger.matches(event))
1231 .any(|registration| match ®istration.binding {
1232 ShortcutBinding::Callback(callback) => {
1233 callback();
1234 true
1235 }
1236 ShortcutBinding::Intent(intent) => {
1237 actions.is_some_and(|actions| actions.invoke_intent(intent))
1238 }
1239 })
1240 }
1241
1242 fn current_scope_mut(&mut self) -> &mut ShortcutScope<C> {
1243 if self.scopes.is_empty() {
1244 self.scopes.push(ShortcutScope {
1245 physical: HashMap::new(),
1246 logical: HashMap::new(),
1247 });
1248 }
1249 self.scopes
1250 .last_mut()
1251 .expect("shortcut registry has a root scope")
1252 }
1253}
1254
1255impl Shortcuts<Command> {
1256 #[must_use]
1258 pub fn new() -> Self {
1259 Self::default()
1260 }
1261}