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() {
55 let mut handled = false;
56 if let Some(callback) = &self.on_key_down {
57 callback(event.clone());
58 handled = true;
59 }
60 if event.repeat
61 && let Some(callback) = &self.on_key_repeat
62 {
63 callback(event);
64 handled = true;
65 }
66 handled
67 } else if let Some(callback) = &self.on_key_up {
68 callback(event);
69 true
70 } else {
71 false
72 }
73 }
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
78pub struct ShortcutKey {
79 pub code: Code,
80 pub modifiers: Modifiers,
81}
82
83#[derive(Clone, Debug, PartialEq, Eq, Hash)]
91pub struct Command(String);
92impl Command {
93 #[must_use]
94 pub fn new(name: impl Into<String>) -> Self {
95 Self(name.into())
96 }
97 #[must_use]
98 pub fn name(&self) -> &str {
99 &self.0
100 }
101}
102impl From<&str> for Command {
103 fn from(value: &str) -> Self {
104 Self::new(value)
105 }
106}
107
108pub trait CommandId: Clone + Eq + std::hash::Hash + 'static {}
114
115impl<T> CommandId for T where T: Clone + Eq + std::hash::Hash + 'static {}
116
117#[derive(Clone, Debug, PartialEq, Eq, Hash)]
119pub struct Intent<C: CommandId = Command> {
120 command: C,
121}
122
123impl<C: CommandId> Intent<C> {
124 #[must_use]
126 pub fn new(command: impl Into<C>) -> Self {
127 Self {
128 command: command.into(),
129 }
130 }
131
132 #[must_use]
134 pub fn command(&self) -> &C {
135 &self.command
136 }
137}
138
139impl Intent<Command> {
140 #[must_use]
142 pub fn name(&self) -> &str {
143 self.command.name()
144 }
145}
146
147impl<C: CommandId> From<C> for Intent<C> {
148 fn from(value: C) -> Self {
149 Self::new(value)
150 }
151}
152
153impl From<&str> for Intent {
154 fn from(value: &str) -> Self {
155 Self::new(value)
156 }
157}
158
159#[derive(Clone)]
163pub struct ErasedIntent {
164 command: Rc<dyn Any>,
165}
166
167impl ErasedIntent {
168 #[must_use]
169 pub fn from_intent<C: CommandId>(intent: &Intent<C>) -> Self {
170 Self {
171 command: Rc::new(intent.command().clone()),
172 }
173 }
174
175 #[must_use]
176 pub fn downcast_command<C: CommandId>(&self) -> Option<&C> {
177 self.command.as_ref().downcast_ref::<C>()
178 }
179}
180
181#[derive(Clone, Copy, Debug, PartialEq, Eq)]
183pub enum ActionResult {
184 Handled,
186 Ignored,
188}
189
190impl From<bool> for ActionResult {
191 fn from(handled: bool) -> Self {
192 if handled {
193 Self::Handled
194 } else {
195 Self::Ignored
196 }
197 }
198}
199
200struct ActionListenerState {
201 listeners: RefCell<Vec<Weak<ActionListenerEntry>>>,
202}
203
204struct ActionListenerEntry {
205 active: Cell<bool>,
206 callback: Rc<dyn Fn()>,
207}
208
209pub struct ActionListenerSubscription {
212 state: Weak<ActionListenerState>,
213 entry: Rc<ActionListenerEntry>,
214}
215
216#[derive(Clone, Copy, Debug, PartialEq, Eq)]
218pub enum ActionInvocationPhase {
219 Start,
220 End,
221}
222
223struct ActionInvocationListenerState {
224 listeners: RefCell<Vec<Weak<ActionInvocationListenerEntry>>>,
225}
226
227struct ActionInvocationListenerEntry {
228 active: Cell<bool>,
229 callback: Rc<dyn Fn(ActionInvocationPhase)>,
230}
231
232pub struct ActionInvocationSubscription {
234 state: Weak<ActionInvocationListenerState>,
235 entry: Rc<ActionInvocationListenerEntry>,
236}
237
238impl ActionInvocationSubscription {
239 fn remove(&self) -> bool {
240 let was_active = self.entry.active.replace(false);
241 if let Some(state) = self.state.upgrade() {
242 state.listeners.borrow_mut().retain(|listener| {
243 listener
244 .upgrade()
245 .is_some_and(|listener| !Rc::ptr_eq(&listener, &self.entry))
246 });
247 }
248 was_active
249 }
250}
251
252impl Drop for ActionInvocationSubscription {
253 fn drop(&mut self) {
254 let _ = self.remove();
255 }
256}
257
258impl ActionListenerSubscription {
259 fn remove(&self) -> bool {
260 let was_active = self.entry.active.replace(false);
261 if let Some(state) = self.state.upgrade() {
262 state.listeners.borrow_mut().retain(|listener| {
263 listener
264 .upgrade()
265 .is_some_and(|listener| !Rc::ptr_eq(&listener, &self.entry))
266 });
267 }
268 was_active
269 }
270}
271
272impl Drop for ActionListenerSubscription {
273 fn drop(&mut self) {
274 let _ = self.remove();
275 }
276}
277
278type ActionCallback<C> = Rc<dyn Fn(&Intent<C>) -> ActionResult>;
284
285#[derive(Clone)]
286pub struct Action<C: CommandId = Command> {
287 intent: Intent<C>,
288 enabled: Rc<Cell<bool>>,
289 callback: ActionCallback<C>,
290 listeners: Rc<ActionListenerState>,
291 invocation_listeners: Rc<ActionInvocationListenerState>,
292}
293
294impl<C: CommandId + std::fmt::Debug> std::fmt::Debug for Action<C> {
295 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296 formatter
297 .debug_struct("Action")
298 .field("intent", &self.intent)
299 .field("enabled", &self.is_enabled())
300 .finish_non_exhaustive()
301 }
302}
303
304impl<C: CommandId> Action<C> {
305 #[must_use]
307 pub fn new(command: impl Into<Intent<C>>, callback: impl Fn() + 'static) -> Self {
308 Self::with_handler(command, move |_| {
309 callback();
310 ActionResult::Handled
311 })
312 }
313
314 #[must_use]
316 pub fn with_handler<R>(
317 command: impl Into<Intent<C>>,
318 callback: impl Fn(&Intent<C>) -> R + 'static,
319 ) -> Self
320 where
321 R: Into<ActionResult> + 'static,
322 {
323 Self {
324 intent: command.into(),
325 enabled: Rc::new(Cell::new(true)),
326 callback: Rc::new(move |intent| callback(intent).into()),
327 listeners: Rc::new(ActionListenerState {
328 listeners: RefCell::new(Vec::new()),
329 }),
330 invocation_listeners: Rc::new(ActionInvocationListenerState {
331 listeners: RefCell::new(Vec::new()),
332 }),
333 }
334 }
335
336 #[must_use]
338 pub fn intent(&self) -> &Intent<C> {
339 &self.intent
340 }
341
342 #[must_use]
344 pub fn command(&self) -> &C {
345 self.intent.command()
346 }
347
348 pub fn set_enabled(&self, enabled: bool) {
350 if self.enabled.replace(enabled) != enabled {
351 self.notify_action_listeners();
352 }
353 }
354
355 #[must_use]
357 pub fn is_enabled(&self) -> bool {
358 self.enabled.get()
359 }
360
361 #[must_use]
363 pub fn is_action_enabled(&self, intent: &Intent<C>) -> bool {
364 self.is_enabled() && self.intent.command() == intent.command()
365 }
366
367 #[must_use]
370 pub fn add_action_listener(&self, listener: impl Fn() + 'static) -> ActionListenerSubscription {
371 let entry = Rc::new(ActionListenerEntry {
372 active: Cell::new(true),
373 callback: Rc::new(listener),
374 });
375 self.listeners
376 .listeners
377 .borrow_mut()
378 .push(Rc::downgrade(&entry));
379 ActionListenerSubscription {
380 state: Rc::downgrade(&self.listeners),
381 entry,
382 }
383 }
384
385 #[must_use]
387 pub fn add_listener(&self, listener: impl Fn() + 'static) -> ActionListenerSubscription {
388 self.add_action_listener(listener)
389 }
390
391 pub fn remove_action_listener(&self, subscription: &ActionListenerSubscription) -> bool {
393 subscription.remove()
394 }
395
396 pub fn notify_action_listeners(&self) {
400 let listeners = {
401 let mut listeners = self.listeners.listeners.borrow_mut();
402 listeners.retain(|listener| listener.strong_count() != 0);
403 listeners
404 .iter()
405 .filter_map(Weak::upgrade)
406 .collect::<Vec<_>>()
407 };
408 for listener in listeners {
409 if listener.active.get() {
410 (listener.callback)();
411 }
412 }
413 }
414
415 #[must_use]
418 pub fn add_invocation_listener(
419 &self,
420 listener: impl Fn(ActionInvocationPhase) + 'static,
421 ) -> ActionInvocationSubscription {
422 let entry = Rc::new(ActionInvocationListenerEntry {
423 active: Cell::new(true),
424 callback: Rc::new(listener),
425 });
426 self.invocation_listeners
427 .listeners
428 .borrow_mut()
429 .push(Rc::downgrade(&entry));
430 ActionInvocationSubscription {
431 state: Rc::downgrade(&self.invocation_listeners),
432 entry,
433 }
434 }
435
436 fn notify_invocation(&self, phase: ActionInvocationPhase) {
437 let listeners = {
438 let mut listeners = self.invocation_listeners.listeners.borrow_mut();
439 listeners.retain(|listener| listener.strong_count() != 0);
440 listeners
441 .iter()
442 .filter_map(Weak::upgrade)
443 .collect::<Vec<_>>()
444 };
445 for listener in listeners {
446 if listener.active.get() {
447 (listener.callback)(phase);
448 }
449 }
450 }
451
452 #[must_use]
453 pub fn listener_count(&self) -> usize {
454 let mut listeners = self.listeners.listeners.borrow_mut();
455 listeners.retain(|listener| listener.strong_count() != 0);
456 listeners
457 .iter()
458 .filter_map(Weak::upgrade)
459 .filter(|listener| listener.active.get())
460 .count()
461 }
462
463 #[must_use]
465 pub fn invoke(&self, intent: &Intent<C>) -> ActionResult {
466 if !self.is_action_enabled(intent) {
467 return ActionResult::Ignored;
468 }
469 self.notify_invocation(ActionInvocationPhase::Start);
470 let result = (self.callback)(intent);
471 self.notify_invocation(ActionInvocationPhase::End);
472 result
473 }
474}
475
476#[derive(Clone)]
478pub enum ShortcutMatch {
479 Callback(Rc<dyn Fn()>),
480 Intent(ErasedIntent),
481}
482
483pub struct ErasedShortcutScope {
486 finder: Rc<dyn Fn(KeyboardEvent) -> Option<ShortcutMatch>>,
487}
488
489impl ErasedShortcutScope {
490 #[must_use]
491 pub fn from_shortcuts<C: CommandId>(shortcuts: Rc<Shortcuts<C>>) -> Rc<Self> {
492 Rc::new(Self {
493 finder: Rc::new(move |event| shortcuts.find_match(&event)),
494 })
495 }
496
497 #[must_use]
498 pub fn find(&self, event: KeyboardEvent) -> Option<ShortcutMatch> {
499 (self.finder)(event)
500 }
501}
502
503type ErasedActionInvoker = dyn Fn(&ErasedIntent) -> Option<ActionResult>;
506
507pub struct ErasedActionScope {
508 invoker: Rc<ErasedActionInvoker>,
509}
510
511impl ErasedActionScope {
512 #[must_use]
513 pub fn from_actions<C: CommandId>(actions: Rc<Actions<C>>) -> Rc<Self> {
514 Rc::new(Self {
515 invoker: Rc::new(move |intent| {
516 let command = intent.downcast_command::<C>()?;
517 let intent = Intent::new(command.clone());
518 actions.invoke_intent_first(&intent)
519 }),
520 })
521 }
522
523 #[must_use]
524 pub fn invoke(&self, intent: &ErasedIntent) -> Option<ActionResult> {
525 (self.invoker)(intent)
526 }
527}
528
529struct ActionScope<C: CommandId> {
530 actions: HashMap<C, Action<C>>,
531}
532
533pub struct Actions<C: CommandId = Command> {
539 scopes: Vec<ActionScope<C>>,
540}
541
542impl<C: CommandId> Default for Actions<C> {
543 fn default() -> Self {
544 Self {
545 scopes: vec![ActionScope::<C> {
546 actions: HashMap::new(),
547 }],
548 }
549 }
550}
551
552impl<C: CommandId> Actions<C> {
553 #[must_use]
556 pub fn typed() -> Self {
557 Self::default()
558 }
559
560 pub fn register(&mut self, command: impl Into<C>, action: impl Fn() + 'static) {
562 let command = command.into();
563 self.register_action(Action::new(Intent::new(command), action));
564 }
565
566 pub fn register_action(&mut self, action: Action<C>) {
568 self.current_scope_mut()
569 .actions
570 .insert(action.command().clone(), action);
571 }
572
573 pub fn register_handler<R>(
575 &mut self,
576 command: impl Into<Intent<C>>,
577 action: impl Fn(&Intent<C>) -> R + 'static,
578 ) where
579 R: Into<ActionResult> + 'static,
580 {
581 self.register_action(Action::with_handler(command, action));
582 }
583
584 pub fn push_scope(&mut self) {
587 self.scopes.push(ActionScope::<C> {
588 actions: HashMap::new(),
589 });
590 }
591
592 #[must_use]
594 pub fn pop_scope(&mut self) -> bool {
595 (self.scopes.len() > 1).then(|| self.scopes.pop()).is_some()
596 }
597
598 pub fn with_scope<R>(&mut self, callback: impl FnOnce(&mut Self) -> R) -> R {
601 self.push_scope();
602 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(self)));
603 let _ = self.pop_scope();
604 match result {
605 Ok(result) => result,
606 Err(panic) => std::panic::resume_unwind(panic),
607 }
608 }
609
610 #[must_use]
612 pub fn scope_depth(&self) -> usize {
613 self.scopes.len()
614 }
615
616 #[must_use]
618 pub fn is_empty(&self) -> bool {
619 self.scopes.iter().all(|scope| scope.actions.is_empty())
620 }
621
622 pub fn unregister(&mut self, command: &C) -> bool {
624 self.current_scope_mut().actions.remove(command).is_some()
625 }
626
627 #[must_use]
628 pub fn invoke(&self, command: &C) -> bool {
629 self.invoke_intent(&Intent::new(command.clone()))
630 }
631
632 #[must_use]
634 pub fn invoke_intent(&self, intent: &Intent<C>) -> bool {
635 self.scopes.iter().rev().any(|scope| {
636 scope
637 .actions
638 .get(intent.command())
639 .is_some_and(|action| action.invoke(intent) == ActionResult::Handled)
640 })
641 }
642
643 #[must_use]
648 pub fn invoke_intent_first(&self, intent: &Intent<C>) -> Option<ActionResult> {
649 self.scopes.iter().rev().find_map(|scope| {
650 scope
651 .actions
652 .get(intent.command())
653 .map(|action| action.invoke(intent))
654 })
655 }
656
657 #[must_use]
659 pub fn action(&self, command: &C) -> Option<Action<C>> {
660 self.scopes
661 .iter()
662 .rev()
663 .find_map(|scope| scope.actions.get(command).cloned())
664 }
665
666 fn current_scope_mut(&mut self) -> &mut ActionScope<C> {
667 if self.scopes.is_empty() {
668 self.scopes.push(ActionScope::<C> {
669 actions: HashMap::new(),
670 });
671 }
672 self.scopes
673 .last_mut()
674 .expect("action registry has a root scope")
675 }
676}
677
678impl Actions<Command> {
679 #[must_use]
681 pub fn new() -> Self {
682 Self::default()
683 }
684}
685
686impl ShortcutKey {
687 #[must_use]
688 pub const fn new(code: Code, modifiers: Modifiers) -> Self {
689 Self { code, modifiers }
690 }
691
692 #[must_use]
695 pub fn logical(key: KeyboardKey, modifiers: Modifiers) -> LogicalShortcutKey {
696 LogicalShortcutKey { key, modifiers }
697 }
698}
699
700#[derive(Clone, Debug, PartialEq, Eq, Hash)]
705pub struct SingleActivator {
706 pub trigger: KeyboardKey,
707 pub modifiers: Modifiers,
708 pub include_repeats: bool,
709}
710
711impl SingleActivator {
712 #[must_use]
713 pub fn new(trigger: KeyboardKey, modifiers: Modifiers) -> Self {
714 Self {
715 trigger,
716 modifiers: shortcut_modifiers(modifiers),
717 include_repeats: true,
718 }
719 }
720
721 #[must_use]
722 pub fn with_repeats(mut self, include_repeats: bool) -> Self {
723 self.include_repeats = include_repeats;
724 self
725 }
726
727 #[must_use]
728 pub const fn trigger(&self) -> &KeyboardKey {
729 &self.trigger
730 }
731
732 #[must_use]
733 pub const fn modifiers(&self) -> Modifiers {
734 self.modifiers
735 }
736
737 #[must_use]
738 pub const fn includes_repeats(&self) -> bool {
739 self.include_repeats
740 }
741
742 #[must_use]
743 pub fn accepts(&self, event: &KeyboardEvent) -> bool {
744 event.state.is_down()
745 && (self.include_repeats || !event.repeat)
746 && event.key == self.trigger
747 && shortcut_modifiers(event.modifiers) == self.modifiers
748 }
749}
750
751#[derive(Clone, Debug, PartialEq, Eq, Hash)]
758pub enum ShortcutActivator {
759 Physical {
760 key: ShortcutKey,
761 trigger: ShortcutTrigger,
762 },
763 Logical {
764 key: LogicalShortcutKey,
765 trigger: ShortcutTrigger,
766 },
767 Single(SingleActivator),
768}
769
770impl ShortcutActivator {
771 #[must_use]
772 pub const fn physical(key: ShortcutKey) -> Self {
773 Self::Physical {
774 key,
775 trigger: ShortcutTrigger::Down,
776 }
777 }
778
779 #[must_use]
780 pub const fn physical_on(key: ShortcutKey, trigger: ShortcutTrigger) -> Self {
781 Self::Physical { key, trigger }
782 }
783
784 #[must_use]
785 pub fn logical(key: LogicalShortcutKey) -> Self {
786 Self::Logical {
787 key,
788 trigger: ShortcutTrigger::Down,
789 }
790 }
791
792 #[must_use]
793 pub fn logical_on(key: LogicalShortcutKey, trigger: ShortcutTrigger) -> Self {
794 Self::Logical { key, trigger }
795 }
796
797 #[must_use]
798 pub fn single(trigger: KeyboardKey, modifiers: Modifiers) -> Self {
799 Self::Single(SingleActivator::new(trigger, modifiers))
800 }
801
802 #[must_use]
803 pub fn accepts(&self, event: &KeyboardEvent) -> bool {
804 match self {
805 Self::Physical { key, trigger } => {
806 key.code == event.code && key.modifiers == event.modifiers && trigger.matches(event)
807 }
808 Self::Logical { key, trigger } => {
809 key.key == event.key && key.modifiers == event.modifiers && trigger.matches(event)
810 }
811 Self::Single(activator) => activator.accepts(event),
812 }
813 }
814}
815
816impl From<ShortcutKey> for ShortcutActivator {
817 fn from(value: ShortcutKey) -> Self {
818 Self::physical(value)
819 }
820}
821
822impl From<LogicalShortcutKey> for ShortcutActivator {
823 fn from(value: LogicalShortcutKey) -> Self {
824 Self::logical(value)
825 }
826}
827
828impl From<SingleActivator> for ShortcutActivator {
829 fn from(value: SingleActivator) -> Self {
830 Self::Single(value)
831 }
832}
833
834fn shortcut_modifiers(modifiers: Modifiers) -> Modifiers {
835 modifiers & (Modifiers::ALT | Modifiers::CONTROL | Modifiers::META | Modifiers::SHIFT)
836}
837
838#[derive(Clone, Debug, PartialEq, Eq, Hash)]
840pub struct LogicalShortcutKey {
841 pub key: KeyboardKey,
842 pub modifiers: Modifiers,
843}
844
845impl LogicalShortcutKey {
846 #[must_use]
847 pub fn new(key: KeyboardKey, modifiers: Modifiers) -> Self {
848 Self { key, modifiers }
849 }
850}
851
852#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
854pub enum ShortcutTrigger {
855 #[default]
857 Down,
858 Press,
860 Repeat,
862 Up,
864 Any,
866}
867
868impl ShortcutTrigger {
869 #[must_use]
870 pub const fn matches(self, event: &KeyboardEvent) -> bool {
871 match self {
872 Self::Down => event.state.is_down(),
873 Self::Press => event.state.is_down() && !event.repeat,
874 Self::Repeat => event.state.is_down() && event.repeat,
875 Self::Up => event.state.is_up(),
876 Self::Any => true,
877 }
878 }
879}
880
881enum ShortcutBinding<C: CommandId> {
882 Callback(Rc<dyn Fn()>),
883 Intent(Intent<C>),
884}
885
886struct ShortcutRegistration<C: CommandId> {
887 trigger: ShortcutTrigger,
888 binding: ShortcutBinding<C>,
889}
890
891struct ShortcutScope<C: CommandId> {
892 physical: HashMap<ShortcutKey, Vec<ShortcutRegistration<C>>>,
893 logical: HashMap<LogicalShortcutKey, Vec<ShortcutRegistration<C>>>,
894}
895
896pub struct Shortcuts<C: CommandId = Command> {
898 scopes: Vec<ShortcutScope<C>>,
899}
900
901impl<C: CommandId> Default for Shortcuts<C> {
902 fn default() -> Self {
903 Self {
904 scopes: vec![ShortcutScope {
905 physical: HashMap::new(),
906 logical: HashMap::new(),
907 }],
908 }
909 }
910}
911
912impl<C: CommandId> Shortcuts<C> {
913 #[must_use]
916 pub fn typed() -> Self {
917 Self::default()
918 }
919
920 pub fn register(&mut self, key: ShortcutKey, action: impl Fn() + 'static) {
921 self.register_on(key, ShortcutTrigger::Down, action);
922 }
923
924 pub fn register_on(
926 &mut self,
927 key: ShortcutKey,
928 trigger: ShortcutTrigger,
929 action: impl Fn() + 'static,
930 ) {
931 Self::replace_binding(
932 &mut self.current_scope_mut().physical,
933 key,
934 trigger,
935 ShortcutBinding::Callback(Rc::new(action)),
936 );
937 }
938
939 pub fn register_logical(&mut self, key: LogicalShortcutKey, action: impl Fn() + 'static) {
941 self.register_logical_on(key, ShortcutTrigger::Down, action);
942 }
943
944 pub fn register_logical_on(
946 &mut self,
947 key: LogicalShortcutKey,
948 trigger: ShortcutTrigger,
949 action: impl Fn() + 'static,
950 ) {
951 Self::replace_binding(
952 &mut self.current_scope_mut().logical,
953 key,
954 trigger,
955 ShortcutBinding::Callback(Rc::new(action)),
956 );
957 }
958
959 pub fn bind(&mut self, key: ShortcutKey, command: impl Into<C>) {
961 self.bind_on(key, ShortcutTrigger::Down, Intent::new(command.into()));
962 }
963
964 pub fn bind_on(
966 &mut self,
967 key: ShortcutKey,
968 trigger: ShortcutTrigger,
969 command: impl Into<Intent<C>>,
970 ) {
971 Self::replace_binding(
972 &mut self.current_scope_mut().physical,
973 key,
974 trigger,
975 ShortcutBinding::Intent(command.into()),
976 );
977 }
978
979 pub fn bind_logical(&mut self, key: LogicalShortcutKey, command: impl Into<C>) {
981 self.bind_logical_on(key, ShortcutTrigger::Down, Intent::new(command.into()));
982 }
983
984 pub fn bind_logical_on(
986 &mut self,
987 key: LogicalShortcutKey,
988 trigger: ShortcutTrigger,
989 command: impl Into<Intent<C>>,
990 ) {
991 Self::replace_binding(
992 &mut self.current_scope_mut().logical,
993 key,
994 trigger,
995 ShortcutBinding::Intent(command.into()),
996 );
997 }
998
999 pub fn clear(&mut self, key: &ShortcutKey) -> bool {
1001 self.current_scope_mut().physical.remove(key).is_some()
1002 }
1003
1004 pub fn clear_logical(&mut self, key: &LogicalShortcutKey) -> bool {
1006 self.current_scope_mut().logical.remove(key).is_some()
1007 }
1008
1009 pub fn push_scope(&mut self) {
1011 self.scopes.push(ShortcutScope {
1012 physical: HashMap::new(),
1013 logical: HashMap::new(),
1014 });
1015 }
1016
1017 #[must_use]
1019 pub fn pop_scope(&mut self) -> bool {
1020 (self.scopes.len() > 1).then(|| self.scopes.pop()).is_some()
1021 }
1022
1023 pub fn with_scope<R>(&mut self, callback: impl FnOnce(&mut Self) -> R) -> R {
1025 self.push_scope();
1026 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(self)));
1027 let _ = self.pop_scope();
1028 match result {
1029 Ok(result) => result,
1030 Err(panic) => std::panic::resume_unwind(panic),
1031 }
1032 }
1033
1034 #[must_use]
1036 pub fn scope_depth(&self) -> usize {
1037 self.scopes.len()
1038 }
1039
1040 #[must_use]
1042 pub fn is_empty(&self) -> bool {
1043 self.scopes.iter().all(|scope| {
1044 scope.physical.values().all(Vec::is_empty) && scope.logical.values().all(Vec::is_empty)
1045 })
1046 }
1047
1048 pub fn handle(&self, event: KeyboardEvent) -> bool {
1049 self.dispatch(event, None)
1050 }
1051
1052 #[must_use]
1055 pub fn handle_actions(&self, event: KeyboardEvent, actions: &Actions<C>) -> bool {
1056 self.dispatch(event, Some(actions))
1057 }
1058
1059 #[must_use]
1061 pub fn handle_event(&self, event: KeyboardEvent, actions: &Actions<C>) -> bool {
1062 self.handle_actions(event, actions)
1063 }
1064
1065 #[must_use]
1067 pub fn erased_scope(self: &Rc<Self>) -> Rc<ErasedShortcutScope> {
1068 ErasedShortcutScope::from_shortcuts(self.clone())
1069 }
1070
1071 fn replace_binding<K>(
1072 bindings: &mut HashMap<K, Vec<ShortcutRegistration<C>>>,
1073 key: K,
1074 trigger: ShortcutTrigger,
1075 binding: ShortcutBinding<C>,
1076 ) where
1077 K: Eq + std::hash::Hash,
1078 {
1079 let registrations = bindings.entry(key).or_default();
1080 registrations.retain(|registration| registration.trigger != trigger);
1081 registrations.push(ShortcutRegistration { trigger, binding });
1082 }
1083
1084 fn dispatch(&self, event: KeyboardEvent, actions: Option<&Actions<C>>) -> bool {
1085 let physical = ShortcutKey::new(event.code, event.modifiers);
1086 let logical = LogicalShortcutKey::new(event.key.clone(), event.modifiers);
1087 for scope in self.scopes.iter().rev() {
1088 if scope.physical.get(&physical).is_some_and(|registrations| {
1089 Self::dispatch_registrations(registrations, &event, actions)
1090 }) || scope.logical.get(&logical).is_some_and(|registrations| {
1091 Self::dispatch_registrations(registrations, &event, actions)
1092 }) {
1093 return true;
1094 }
1095 }
1096 false
1097 }
1098
1099 fn find_match(&self, event: &KeyboardEvent) -> Option<ShortcutMatch> {
1100 let physical = ShortcutKey::new(event.code, event.modifiers);
1101 let logical = LogicalShortcutKey::new(event.key.clone(), event.modifiers);
1102 for scope in self.scopes.iter().rev() {
1103 if let Some(registration) = scope.physical.get(&physical).and_then(|registrations| {
1104 registrations
1105 .iter()
1106 .find(|registration| registration.trigger.matches(event))
1107 }) {
1108 return Some(Self::erase_binding(®istration.binding));
1109 }
1110 if let Some(registration) = scope.logical.get(&logical).and_then(|registrations| {
1111 registrations
1112 .iter()
1113 .find(|registration| registration.trigger.matches(event))
1114 }) {
1115 return Some(Self::erase_binding(®istration.binding));
1116 }
1117 }
1118 None
1119 }
1120
1121 fn erase_binding(binding: &ShortcutBinding<C>) -> ShortcutMatch {
1122 match binding {
1123 ShortcutBinding::Callback(callback) => ShortcutMatch::Callback(callback.clone()),
1124 ShortcutBinding::Intent(intent) => {
1125 ShortcutMatch::Intent(ErasedIntent::from_intent(intent))
1126 }
1127 }
1128 }
1129
1130 fn dispatch_registrations(
1131 registrations: &[ShortcutRegistration<C>],
1132 event: &KeyboardEvent,
1133 actions: Option<&Actions<C>>,
1134 ) -> bool {
1135 registrations
1136 .iter()
1137 .filter(|registration| registration.trigger.matches(event))
1138 .any(|registration| match ®istration.binding {
1139 ShortcutBinding::Callback(callback) => {
1140 callback();
1141 true
1142 }
1143 ShortcutBinding::Intent(intent) => {
1144 actions.is_some_and(|actions| actions.invoke_intent(intent))
1145 }
1146 })
1147 }
1148
1149 fn current_scope_mut(&mut self) -> &mut ShortcutScope<C> {
1150 if self.scopes.is_empty() {
1151 self.scopes.push(ShortcutScope {
1152 physical: HashMap::new(),
1153 logical: HashMap::new(),
1154 });
1155 }
1156 self.scopes
1157 .last_mut()
1158 .expect("shortcut registry has a root scope")
1159 }
1160}
1161
1162impl Shortcuts<Command> {
1163 #[must_use]
1165 pub fn new() -> Self {
1166 Self::default()
1167 }
1168}