1#![allow(clippy::unwrap_used, clippy::disallowed_methods)]
2use crate::event::Key;
11use crate::widget::WidgetId;
12use std::collections::HashMap;
13
14#[derive(
16 Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
17)]
18pub struct Modifiers {
19 pub ctrl: bool,
21 pub alt: bool,
23 pub shift: bool,
25 pub meta: bool,
27}
28
29impl Modifiers {
30 pub const NONE: Self = Self {
32 ctrl: false,
33 alt: false,
34 shift: false,
35 meta: false,
36 };
37
38 pub const CTRL: Self = Self {
40 ctrl: true,
41 alt: false,
42 shift: false,
43 meta: false,
44 };
45
46 pub const ALT: Self = Self {
48 ctrl: false,
49 alt: true,
50 shift: false,
51 meta: false,
52 };
53
54 pub const SHIFT: Self = Self {
56 ctrl: false,
57 alt: false,
58 shift: true,
59 meta: false,
60 };
61
62 pub const META: Self = Self {
64 ctrl: false,
65 alt: false,
66 shift: false,
67 meta: true,
68 };
69
70 pub const CTRL_SHIFT: Self = Self {
72 ctrl: true,
73 alt: false,
74 shift: true,
75 meta: false,
76 };
77
78 pub const CTRL_ALT: Self = Self {
80 ctrl: true,
81 alt: true,
82 shift: false,
83 meta: false,
84 };
85
86 pub const fn new(ctrl: bool, alt: bool, shift: bool, meta: bool) -> Self {
88 Self {
89 ctrl,
90 alt,
91 shift,
92 meta,
93 }
94 }
95
96 pub const fn any(&self) -> bool {
98 self.ctrl || self.alt || self.shift || self.meta
99 }
100
101 pub const fn none(&self) -> bool {
103 !self.any()
104 }
105
106 pub fn display(&self) -> String {
108 let mut parts = Vec::new();
109 if self.ctrl {
110 parts.push("Ctrl");
111 }
112 if self.alt {
113 parts.push("Alt");
114 }
115 if self.shift {
116 parts.push("Shift");
117 }
118 if self.meta {
119 parts.push("Meta");
120 }
121 parts.join("+")
122 }
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
127pub struct Shortcut {
128 pub key: Key,
130 pub modifiers: Modifiers,
132}
133
134impl Shortcut {
135 pub const fn new(key: Key, modifiers: Modifiers) -> Self {
137 Self { key, modifiers }
138 }
139
140 pub const fn key(key: Key) -> Self {
142 Self::new(key, Modifiers::NONE)
143 }
144
145 pub const fn ctrl(key: Key) -> Self {
147 Self::new(key, Modifiers::CTRL)
148 }
149
150 pub const fn alt(key: Key) -> Self {
152 Self::new(key, Modifiers::ALT)
153 }
154
155 pub const fn shift(key: Key) -> Self {
157 Self::new(key, Modifiers::SHIFT)
158 }
159
160 pub const fn ctrl_shift(key: Key) -> Self {
162 Self::new(key, Modifiers::CTRL_SHIFT)
163 }
164
165 pub fn display(&self) -> String {
167 let key_name = format!("{:?}", self.key);
168 if self.modifiers.none() {
169 key_name
170 } else {
171 format!("{}+{}", self.modifiers.display(), key_name)
172 }
173 }
174
175 pub const COPY: Self = Self::ctrl(Key::C);
177 pub const CUT: Self = Self::ctrl(Key::X);
178 pub const PASTE: Self = Self::ctrl(Key::V);
179 pub const UNDO: Self = Self::ctrl(Key::Z);
180 pub const REDO: Self = Self::ctrl_shift(Key::Z);
181 pub const SAVE: Self = Self::ctrl(Key::S);
182 pub const SELECT_ALL: Self = Self::ctrl(Key::A);
183 pub const FIND: Self = Self::ctrl(Key::F);
184 pub const ESCAPE: Self = Self::key(Key::Escape);
185 pub const ENTER: Self = Self::key(Key::Enter);
186 pub const TAB: Self = Self::key(Key::Tab);
187 pub const DELETE: Self = Self::key(Key::Delete);
188 pub const BACKSPACE: Self = Self::key(Key::Backspace);
189}
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
193pub struct ShortcutId(pub u64);
194
195impl ShortcutId {
196 pub const fn new(id: u64) -> Self {
198 Self(id)
199 }
200}
201
202#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
204pub enum ShortcutContext {
205 #[default]
207 Global,
208 Widget(WidgetId),
210 WidgetType(String),
212 Custom(String),
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
218pub enum ShortcutPriority {
219 Low = 0,
221 #[default]
223 Normal = 1,
224 High = 2,
226}
227
228pub type ShortcutHandler = Box<dyn FnMut() -> bool + Send>;
230
231struct ShortcutBinding {
233 #[allow(dead_code)]
234 id: ShortcutId,
235 shortcut: Shortcut,
236 context: ShortcutContext,
237 priority: ShortcutPriority,
238 description: String,
239 enabled: bool,
240}
241
242pub struct ShortcutManager {
244 next_id: u64,
246 bindings: HashMap<ShortcutId, ShortcutBinding>,
248 handlers: HashMap<ShortcutId, ShortcutHandler>,
250 by_shortcut: HashMap<Shortcut, Vec<ShortcutId>>,
252 active_contexts: Vec<ShortcutContext>,
254 modifiers: Modifiers,
256}
257
258impl ShortcutManager {
259 pub fn new() -> Self {
261 Self {
262 next_id: 0,
263 bindings: HashMap::new(),
264 handlers: HashMap::new(),
265 by_shortcut: HashMap::new(),
266 active_contexts: vec![ShortcutContext::Global],
267 modifiers: Modifiers::NONE,
268 }
269 }
270
271 pub fn register(&mut self, shortcut: Shortcut, handler: ShortcutHandler) -> ShortcutId {
273 self.register_with_options(
274 shortcut,
275 handler,
276 ShortcutContext::Global,
277 ShortcutPriority::Normal,
278 "",
279 )
280 }
281
282 pub fn register_with_options(
284 &mut self,
285 shortcut: Shortcut,
286 handler: ShortcutHandler,
287 context: ShortcutContext,
288 priority: ShortcutPriority,
289 description: &str,
290 ) -> ShortcutId {
291 let id = ShortcutId::new(self.next_id);
292 self.next_id += 1;
293
294 let binding = ShortcutBinding {
295 id,
296 shortcut,
297 context,
298 priority,
299 description: description.to_string(),
300 enabled: true,
301 };
302
303 self.bindings.insert(id, binding);
304 self.handlers.insert(id, handler);
305
306 self.by_shortcut.entry(shortcut).or_default().push(id);
307
308 id
309 }
310
311 pub fn unregister(&mut self, id: ShortcutId) -> bool {
313 if let Some(binding) = self.bindings.remove(&id) {
314 self.handlers.remove(&id);
315
316 if let Some(ids) = self.by_shortcut.get_mut(&binding.shortcut) {
317 ids.retain(|&i| i != id);
318 }
319
320 true
321 } else {
322 false
323 }
324 }
325
326 pub fn set_enabled(&mut self, id: ShortcutId, enabled: bool) {
328 if let Some(binding) = self.bindings.get_mut(&id) {
329 binding.enabled = enabled;
330 }
331 }
332
333 pub fn is_enabled(&self, id: ShortcutId) -> bool {
335 self.bindings.get(&id).is_some_and(|b| b.enabled)
336 }
337
338 pub fn set_modifiers(&mut self, modifiers: Modifiers) {
340 self.modifiers = modifiers;
341 }
342
343 pub fn modifiers(&self) -> Modifiers {
345 self.modifiers
346 }
347
348 pub fn push_context(&mut self, context: ShortcutContext) {
350 self.active_contexts.push(context);
351 }
352
353 pub fn pop_context(&mut self) -> Option<ShortcutContext> {
355 if self.active_contexts.len() > 1 {
356 self.active_contexts.pop()
357 } else {
358 None
359 }
360 }
361
362 pub fn set_focused_widget(&mut self, widget_id: Option<WidgetId>) {
364 self.active_contexts
366 .retain(|c| !matches!(c, ShortcutContext::Widget(_)));
367
368 if let Some(id) = widget_id {
369 self.active_contexts.push(ShortcutContext::Widget(id));
370 }
371 }
372
373 pub fn handle_key(&mut self, key: Key) -> bool {
376 let shortcut = Shortcut::new(key, self.modifiers);
377 self.trigger(shortcut)
378 }
379
380 pub fn trigger(&mut self, shortcut: Shortcut) -> bool {
382 let binding_ids = match self.by_shortcut.get(&shortcut) {
383 Some(ids) => ids.clone(),
384 None => return false,
385 };
386
387 let mut matches: Vec<(ShortcutId, ShortcutPriority)> = binding_ids
389 .iter()
390 .filter_map(|&id| {
391 let binding = self.bindings.get(&id)?;
392 if !binding.enabled {
393 return None;
394 }
395 if self.is_context_active(&binding.context) {
396 Some((id, binding.priority))
397 } else {
398 None
399 }
400 })
401 .collect();
402
403 matches.sort_by(|a, b| b.1.cmp(&a.1));
405
406 for (id, _) in matches {
408 if let Some(handler) = self.handlers.get_mut(&id) {
409 if handler() {
410 return true;
411 }
412 }
413 }
414
415 false
416 }
417
418 fn is_context_active(&self, context: &ShortcutContext) -> bool {
420 match context {
421 ShortcutContext::Global => true,
422 other => self.active_contexts.contains(other),
423 }
424 }
425
426 pub fn shortcuts(&self) -> impl Iterator<Item = (&Shortcut, &str)> {
428 self.bindings
429 .values()
430 .map(|b| (&b.shortcut, b.description.as_str()))
431 }
432
433 pub fn binding_count(&self) -> usize {
435 self.bindings.len()
436 }
437
438 pub fn find_conflicts(&self) -> Vec<(Shortcut, Vec<ShortcutId>)> {
440 let mut conflicts = Vec::new();
441
442 for (shortcut, ids) in &self.by_shortcut {
443 if ids.len() < 2 {
444 continue;
445 }
446
447 let mut by_context: HashMap<&ShortcutContext, Vec<ShortcutId>> = HashMap::new();
449 for &id in ids {
450 if let Some(binding) = self.bindings.get(&id) {
451 by_context.entry(&binding.context).or_default().push(id);
452 }
453 }
454
455 for (_, context_ids) in by_context {
457 if context_ids.len() > 1 {
458 conflicts.push((*shortcut, context_ids));
459 }
460 }
461 }
462
463 conflicts
464 }
465
466 pub fn clear(&mut self) {
468 self.bindings.clear();
469 self.handlers.clear();
470 self.by_shortcut.clear();
471 }
472
473 pub fn description(&self, id: ShortcutId) -> Option<&str> {
475 self.bindings.get(&id).map(|b| b.description.as_str())
476 }
477}
478
479impl Default for ShortcutManager {
480 fn default() -> Self {
481 Self::new()
482 }
483}
484
485impl std::fmt::Debug for ShortcutManager {
486 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
487 f.debug_struct("ShortcutManager")
488 .field("binding_count", &self.bindings.len())
489 .field("active_contexts", &self.active_contexts)
490 .field("modifiers", &self.modifiers)
491 .finish()
492 }
493}
494
495#[derive(Debug, Clone)]
497pub struct ShortcutBuilder {
498 key: Key,
499 modifiers: Modifiers,
500 context: ShortcutContext,
501 priority: ShortcutPriority,
502 description: String,
503}
504
505impl ShortcutBuilder {
506 pub fn new(key: Key) -> Self {
508 Self {
509 key,
510 modifiers: Modifiers::NONE,
511 context: ShortcutContext::Global,
512 priority: ShortcutPriority::Normal,
513 description: String::new(),
514 }
515 }
516
517 pub fn ctrl(mut self) -> Self {
519 self.modifiers.ctrl = true;
520 self
521 }
522
523 pub fn alt(mut self) -> Self {
525 self.modifiers.alt = true;
526 self
527 }
528
529 pub fn shift(mut self) -> Self {
531 self.modifiers.shift = true;
532 self
533 }
534
535 pub fn meta(mut self) -> Self {
537 self.modifiers.meta = true;
538 self
539 }
540
541 pub fn context(mut self, context: ShortcutContext) -> Self {
543 self.context = context;
544 self
545 }
546
547 pub fn for_widget(mut self, widget_id: WidgetId) -> Self {
549 self.context = ShortcutContext::Widget(widget_id);
550 self
551 }
552
553 pub fn priority(mut self, priority: ShortcutPriority) -> Self {
555 self.priority = priority;
556 self
557 }
558
559 pub fn description(mut self, desc: &str) -> Self {
561 self.description = desc.to_string();
562 self
563 }
564
565 pub fn build(self) -> Shortcut {
567 Shortcut::new(self.key, self.modifiers)
568 }
569
570 pub fn register(self, manager: &mut ShortcutManager, handler: ShortcutHandler) -> ShortcutId {
572 let shortcut = Shortcut::new(self.key, self.modifiers);
573 manager.register_with_options(
574 shortcut,
575 handler,
576 self.context,
577 self.priority,
578 &self.description,
579 )
580 }
581}
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
587 use std::sync::Arc;
588
589 #[allow(clippy::assertions_on_constants)]
591 #[test]
592 fn test_modifiers_constants() {
593 assert!(!Modifiers::NONE.any());
594 assert!(Modifiers::NONE.none());
595
596 assert!(Modifiers::CTRL.ctrl);
597 assert!(!Modifiers::CTRL.alt);
598
599 assert!(Modifiers::CTRL_SHIFT.ctrl);
600 assert!(Modifiers::CTRL_SHIFT.shift);
601 }
602
603 #[test]
604 fn test_modifiers_new() {
605 let mods = Modifiers::new(true, true, false, false);
606 assert!(mods.ctrl);
607 assert!(mods.alt);
608 assert!(!mods.shift);
609 assert!(!mods.meta);
610 }
611
612 #[test]
613 fn test_modifiers_display() {
614 assert_eq!(Modifiers::NONE.display(), "");
615 assert_eq!(Modifiers::CTRL.display(), "Ctrl");
616 assert_eq!(Modifiers::CTRL_SHIFT.display(), "Ctrl+Shift");
617 }
618
619 #[test]
621 fn test_shortcut_new() {
622 let shortcut = Shortcut::new(Key::A, Modifiers::CTRL);
623 assert_eq!(shortcut.key, Key::A);
624 assert!(shortcut.modifiers.ctrl);
625 }
626
627 #[test]
628 fn test_shortcut_constructors() {
629 let key_only = Shortcut::key(Key::Escape);
630 assert!(key_only.modifiers.none());
631
632 let ctrl = Shortcut::ctrl(Key::S);
633 assert!(ctrl.modifiers.ctrl);
634
635 let alt = Shortcut::alt(Key::F4);
636 assert!(alt.modifiers.alt);
637
638 let shift = Shortcut::shift(Key::Tab);
639 assert!(shift.modifiers.shift);
640
641 let ctrl_shift = Shortcut::ctrl_shift(Key::Z);
642 assert!(ctrl_shift.modifiers.ctrl);
643 assert!(ctrl_shift.modifiers.shift);
644 }
645
646 #[test]
647 fn test_shortcut_display() {
648 assert_eq!(Shortcut::key(Key::A).display(), "A");
649 assert_eq!(Shortcut::ctrl(Key::S).display(), "Ctrl+S");
650 assert_eq!(Shortcut::ctrl_shift(Key::Z).display(), "Ctrl+Shift+Z");
651 }
652
653 #[test]
654 fn test_shortcut_constants() {
655 assert_eq!(Shortcut::COPY, Shortcut::ctrl(Key::C));
656 assert_eq!(Shortcut::UNDO, Shortcut::ctrl(Key::Z));
657 assert_eq!(Shortcut::REDO, Shortcut::ctrl_shift(Key::Z));
658 }
659
660 #[test]
661 fn test_shortcut_equality() {
662 let s1 = Shortcut::ctrl(Key::S);
663 let s2 = Shortcut::ctrl(Key::S);
664 let s3 = Shortcut::ctrl(Key::A);
665
666 assert_eq!(s1, s2);
667 assert_ne!(s1, s3);
668 }
669
670 #[test]
672 fn test_shortcut_id() {
673 let id1 = ShortcutId::new(1);
674 let id2 = ShortcutId::new(1);
675 let id3 = ShortcutId::new(2);
676
677 assert_eq!(id1, id2);
678 assert_ne!(id1, id3);
679 }
680
681 #[test]
683 fn test_shortcut_context_default() {
684 assert_eq!(ShortcutContext::default(), ShortcutContext::Global);
685 }
686
687 #[test]
689 fn test_shortcut_priority_ordering() {
690 assert!(ShortcutPriority::High > ShortcutPriority::Normal);
691 assert!(ShortcutPriority::Normal > ShortcutPriority::Low);
692 }
693
694 #[test]
696 fn test_manager_new() {
697 let manager = ShortcutManager::new();
698 assert_eq!(manager.binding_count(), 0);
699 }
700
701 #[test]
702 fn test_manager_register() {
703 let mut manager = ShortcutManager::new();
704
705 let id = manager.register(Shortcut::ctrl(Key::S), Box::new(|| true));
706 assert_eq!(manager.binding_count(), 1);
707 assert!(manager.is_enabled(id));
708 }
709
710 #[test]
711 fn test_manager_unregister() {
712 let mut manager = ShortcutManager::new();
713
714 let id = manager.register(Shortcut::ctrl(Key::S), Box::new(|| true));
715 assert_eq!(manager.binding_count(), 1);
716
717 let removed = manager.unregister(id);
718 assert!(removed);
719 assert_eq!(manager.binding_count(), 0);
720 }
721
722 #[test]
723 fn test_manager_set_enabled() {
724 let mut manager = ShortcutManager::new();
725
726 let id = manager.register(Shortcut::ctrl(Key::S), Box::new(|| true));
727 assert!(manager.is_enabled(id));
728
729 manager.set_enabled(id, false);
730 assert!(!manager.is_enabled(id));
731
732 manager.set_enabled(id, true);
733 assert!(manager.is_enabled(id));
734 }
735
736 #[test]
737 fn test_manager_handle_key() {
738 let triggered = Arc::new(AtomicBool::new(false));
739 let triggered_clone = triggered.clone();
740
741 let mut manager = ShortcutManager::new();
742 manager.register(
743 Shortcut::ctrl(Key::S),
744 Box::new(move || {
745 triggered_clone.store(true, Ordering::SeqCst);
746 true
747 }),
748 );
749
750 manager.set_modifiers(Modifiers::NONE);
752 let result = manager.handle_key(Key::S);
753 assert!(!result);
754 assert!(!triggered.load(Ordering::SeqCst));
755
756 manager.set_modifiers(Modifiers::CTRL);
758 let result = manager.handle_key(Key::S);
759 assert!(result);
760 assert!(triggered.load(Ordering::SeqCst));
761 }
762
763 #[test]
764 fn test_manager_trigger() {
765 let counter = Arc::new(AtomicUsize::new(0));
766 let counter_clone = counter.clone();
767
768 let mut manager = ShortcutManager::new();
769 manager.register(
770 Shortcut::ctrl(Key::C),
771 Box::new(move || {
772 counter_clone.fetch_add(1, Ordering::SeqCst);
773 true
774 }),
775 );
776
777 manager.trigger(Shortcut::ctrl(Key::C));
778 assert_eq!(counter.load(Ordering::SeqCst), 1);
779
780 manager.trigger(Shortcut::ctrl(Key::C));
781 assert_eq!(counter.load(Ordering::SeqCst), 2);
782 }
783
784 #[test]
785 fn test_manager_disabled_shortcut_not_triggered() {
786 let triggered = Arc::new(AtomicBool::new(false));
787 let triggered_clone = triggered.clone();
788
789 let mut manager = ShortcutManager::new();
790 let id = manager.register(
791 Shortcut::ctrl(Key::S),
792 Box::new(move || {
793 triggered_clone.store(true, Ordering::SeqCst);
794 true
795 }),
796 );
797
798 manager.set_enabled(id, false);
799 manager.set_modifiers(Modifiers::CTRL);
800
801 let result = manager.handle_key(Key::S);
802 assert!(!result);
803 assert!(!triggered.load(Ordering::SeqCst));
804 }
805
806 #[test]
807 fn test_manager_context() {
808 let triggered = Arc::new(AtomicBool::new(false));
809 let triggered_clone = triggered.clone();
810
811 let mut manager = ShortcutManager::new();
812 manager.register_with_options(
813 Shortcut::ctrl(Key::S),
814 Box::new(move || {
815 triggered_clone.store(true, Ordering::SeqCst);
816 true
817 }),
818 ShortcutContext::Widget(WidgetId::new(1)),
819 ShortcutPriority::Normal,
820 "",
821 );
822
823 manager.set_modifiers(Modifiers::CTRL);
825 let result = manager.handle_key(Key::S);
826 assert!(!result);
827
828 manager.set_focused_widget(Some(WidgetId::new(1)));
830 let result = manager.handle_key(Key::S);
831 assert!(result);
832 assert!(triggered.load(Ordering::SeqCst));
833 }
834
835 #[test]
836 fn test_manager_priority() {
837 let order = Arc::new(std::sync::Mutex::new(Vec::new()));
838 let order1 = order.clone();
839 let order2 = order.clone();
840
841 let mut manager = ShortcutManager::new();
842
843 manager.register_with_options(
844 Shortcut::ctrl(Key::S),
845 Box::new(move || {
846 order1.lock().unwrap().push("low");
847 false }),
849 ShortcutContext::Global,
850 ShortcutPriority::Low,
851 "",
852 );
853
854 manager.register_with_options(
855 Shortcut::ctrl(Key::S),
856 Box::new(move || {
857 order2.lock().unwrap().push("high");
858 false
859 }),
860 ShortcutContext::Global,
861 ShortcutPriority::High,
862 "",
863 );
864
865 manager.trigger(Shortcut::ctrl(Key::S));
866
867 let order_vec = order.lock().unwrap();
868 assert_eq!(*order_vec, vec!["high", "low"]);
869 }
870
871 #[test]
872 fn test_manager_handler_consumes() {
873 let counter = Arc::new(AtomicUsize::new(0));
874 let c1 = counter.clone();
875 let c2 = counter.clone();
876
877 let mut manager = ShortcutManager::new();
878
879 manager.register_with_options(
880 Shortcut::ctrl(Key::S),
881 Box::new(move || {
882 c1.fetch_add(1, Ordering::SeqCst);
883 true }),
885 ShortcutContext::Global,
886 ShortcutPriority::High,
887 "",
888 );
889
890 manager.register_with_options(
891 Shortcut::ctrl(Key::S),
892 Box::new(move || {
893 c2.fetch_add(1, Ordering::SeqCst);
894 true
895 }),
896 ShortcutContext::Global,
897 ShortcutPriority::Low,
898 "",
899 );
900
901 manager.trigger(Shortcut::ctrl(Key::S));
902
903 assert_eq!(counter.load(Ordering::SeqCst), 1);
905 }
906
907 #[test]
908 fn test_manager_push_pop_context() {
909 let mut manager = ShortcutManager::new();
910
911 manager.push_context(ShortcutContext::Custom("editor".to_string()));
912 manager.push_context(ShortcutContext::Custom("modal".to_string()));
913
914 let popped = manager.pop_context();
915 assert_eq!(popped, Some(ShortcutContext::Custom("modal".to_string())));
916
917 let popped = manager.pop_context();
918 assert_eq!(popped, Some(ShortcutContext::Custom("editor".to_string())));
919
920 let popped = manager.pop_context();
922 assert!(popped.is_none());
923 }
924
925 #[test]
926 fn test_manager_find_conflicts() {
927 let mut manager = ShortcutManager::new();
928
929 let id1 = manager.register(Shortcut::ctrl(Key::S), Box::new(|| true));
930 let id2 = manager.register(Shortcut::ctrl(Key::S), Box::new(|| true));
931 manager.register(Shortcut::ctrl(Key::A), Box::new(|| true)); let conflicts = manager.find_conflicts();
934 assert_eq!(conflicts.len(), 1);
935 assert!(conflicts[0].1.contains(&id1));
936 assert!(conflicts[0].1.contains(&id2));
937 }
938
939 #[test]
940 fn test_manager_shortcuts() {
941 let mut manager = ShortcutManager::new();
942
943 manager.register_with_options(
944 Shortcut::ctrl(Key::S),
945 Box::new(|| true),
946 ShortcutContext::Global,
947 ShortcutPriority::Normal,
948 "Save",
949 );
950
951 manager.register_with_options(
952 Shortcut::ctrl(Key::O),
953 Box::new(|| true),
954 ShortcutContext::Global,
955 ShortcutPriority::Normal,
956 "Open",
957 );
958
959 assert_eq!(manager.shortcuts().count(), 2);
960 }
961
962 #[test]
963 fn test_manager_clear() {
964 let mut manager = ShortcutManager::new();
965 manager.register(Shortcut::ctrl(Key::S), Box::new(|| true));
966 manager.register(Shortcut::ctrl(Key::O), Box::new(|| true));
967
968 manager.clear();
969 assert_eq!(manager.binding_count(), 0);
970 }
971
972 #[test]
973 fn test_manager_description() {
974 let mut manager = ShortcutManager::new();
975
976 let id = manager.register_with_options(
977 Shortcut::ctrl(Key::S),
978 Box::new(|| true),
979 ShortcutContext::Global,
980 ShortcutPriority::Normal,
981 "Save document",
982 );
983
984 assert_eq!(manager.description(id), Some("Save document"));
985 }
986
987 #[test]
989 fn test_builder() {
990 let shortcut = ShortcutBuilder::new(Key::S).ctrl().shift().build();
991
992 assert_eq!(shortcut.key, Key::S);
993 assert!(shortcut.modifiers.ctrl);
994 assert!(shortcut.modifiers.shift);
995 assert!(!shortcut.modifiers.alt);
996 }
997
998 #[test]
999 fn test_builder_register() {
1000 let mut manager = ShortcutManager::new();
1001
1002 let id = ShortcutBuilder::new(Key::S)
1003 .ctrl()
1004 .description("Save")
1005 .register(&mut manager, Box::new(|| true));
1006
1007 assert!(manager.is_enabled(id));
1008 assert_eq!(manager.description(id), Some("Save"));
1009 }
1010
1011 #[test]
1012 fn test_builder_for_widget() {
1013 let builder = ShortcutBuilder::new(Key::Enter).for_widget(WidgetId::new(42));
1014
1015 assert_eq!(builder.context, ShortcutContext::Widget(WidgetId::new(42)));
1016 }
1017}