keyboard_types/
shortcuts.rs

1use crate::{Key, KeyState, KeyboardEvent, Modifiers};
2
3/// Match keyboard shortcuts and execute actions.
4///
5/// Every shortcut consists of a list of modifier keys pressed and a
6/// single non-modifier key pressed.
7///
8/// The Control + C shortcut requires the user to hold down the Control
9/// modifier key. When the C key is pressed the action (usually copy)
10/// is triggered. The event is consumed so other matchers don't also
11/// act on the shortcut. This is also true for the release of the
12/// C key as else only key release events would be forwarded.
13///
14/// ASCII letters are compared ignoring case. Only takes
15/// the shift, control, alt and meta modifiers into account.
16/// If other modifiers beside those expected are found
17/// the shortcut is not matched.
18pub struct ShortcutMatcher<T> {
19    state: KeyState,
20    key: Key,
21    modifiers: Modifiers,
22    matched: bool,
23    value: Option<T>,
24}
25
26impl<T> ShortcutMatcher<T> {
27    /// Create a new shortcut matcher.
28    pub fn new(state: KeyState, key: Key, mut modifiers: Modifiers) -> ShortcutMatcher<T> {
29        modifiers &= Modifiers::SHIFT | Modifiers::CONTROL | Modifiers::ALT | Modifiers::META;
30        ShortcutMatcher {
31            state,
32            key,
33            modifiers,
34            matched: false,
35            value: None,
36        }
37    }
38
39    /// Create a new matcher from an event.
40    ///
41    /// Only state, key and modifiers are stored. The other attributes are discarded.
42    pub fn from_event(key_event: KeyboardEvent) -> ShortcutMatcher<T> {
43        ShortcutMatcher::new(key_event.state, key_event.key, key_event.modifiers)
44    }
45
46    /// Test a keyboard shortcut.
47    ///
48    /// If the modifiers are active and the key is pressed,
49    /// execute the provided function.
50    ///
51    /// ```rust
52    /// # use keyboard_types::{Key, KeyboardEvent, Modifiers, NamedKey, ShortcutMatcher};
53    /// # fn do_something() {}
54    /// # fn forward_event() {}
55    /// # let event = KeyboardEvent {
56    /// #     state: keyboard_types::KeyState::Down,
57    /// #     key: Key::Named(NamedKey::Enter),
58    /// #     code: keyboard_types::Code::Enter,
59    /// #     location: keyboard_types::Location::Standard,
60    /// #     modifiers: Modifiers::empty(),
61    /// #     repeat: false,
62    /// #     is_composing: false,
63    /// # };
64    /// // Create a matcher from a keyboard event.
65    /// // Shortcuts are tested in-order.
66    /// ShortcutMatcher::from_event(event)
67    /// // Do something if the Tab key is pressed.
68    /// .shortcut(Modifiers::empty(), Key::Named(NamedKey::Tab), do_something)
69    /// // If Shift + Tab are pressed do something.
70    /// // This is executed because the previous shortcut requires modifiers to be empty.
71    /// .shortcut(Modifiers::SHIFT, Key::Named(NamedKey::Tab), do_something)
72    /// // Instead of named keys letters and other characters can be used.
73    /// .shortcut(Modifiers::CONTROL, 'L', do_something)
74    /// // Multiple modifiers are combined with bitwise OR (`|`) to form a new mask.
75    /// .shortcut(Modifiers::CONTROL | Modifiers::SHIFT, 'X', do_something)
76    /// // If none of the previous shortcuts matched forward the event.
77    /// .otherwise(forward_event);
78    /// ```
79    pub fn shortcut<K, F>(mut self, modifiers: Modifiers, key: K, f: F) -> ShortcutMatcher<T>
80    where
81        K: MatchKey,
82        F: (FnOnce() -> T),
83    {
84        if self.matched {
85            return self;
86        }
87        if modifiers == self.modifiers && key.match_key(&self.key) {
88            if self.state.is_down() {
89                self.value = Some(f());
90            }
91            self.matched = true;
92        }
93        self
94    }
95
96    /// Only test a shortcut if the enabled flag is set.
97    ///
98    /// If the `enabled` flag is true behaves the same as
99    /// `shortcut` otherwise does nothing.
100    ///
101    /// This is especially useful for platform specific shortcuts.
102    ///
103    /// ```rust
104    /// # use keyboard_types::{Key, KeyboardEvent, Modifiers, NamedKey, ShortcutMatcher};
105    /// # fn copy() {}
106    /// # fn quit() {}
107    /// # let event = KeyboardEvent {
108    /// #     state: keyboard_types::KeyState::Down,
109    /// #     key: Key::Named(NamedKey::Enter),
110    /// #     code: keyboard_types::Code::Enter,
111    /// #     location: keyboard_types::Location::Standard,
112    /// #     modifiers: Modifiers::empty(),
113    /// #     repeat: false,
114    /// #     is_composing: false,
115    /// # };
116    /// ShortcutMatcher::from_event(event)
117    /// .shortcut(Modifiers::CONTROL, 'c', copy)
118    /// .optional_shortcut(cfg!(target_os="macos"), Modifiers::META, 'q', quit)
119    /// .shortcut(Modifiers::CONTROL, 'w', quit);
120    /// ```
121    ///
122    /// In the example the app supports the copy action on all platforms
123    /// and can be closed with Control&nbsp;+&nbsp;W everywhere but additionally
124    /// with Command&nbsp;+&nbsp;Q on Mac OS.
125    pub fn optional_shortcut<K, F>(
126        self,
127        enabled: bool,
128        modifiers: Modifiers,
129        key: K,
130        f: F,
131    ) -> ShortcutMatcher<T>
132    where
133        K: MatchKey,
134        F: (FnOnce() -> T),
135    {
136        if !enabled {
137            return self;
138        }
139        self.shortcut(modifiers, key, f)
140    }
141
142    /// Execute the function is no keyboard shortcut matched.
143    ///
144    /// Note that the passed function is executed on both
145    /// keydown and keyup unlike the shortcuts which only
146    /// run on keydown.
147    pub fn otherwise<F>(self, f: F) -> Option<T>
148    where
149        F: (FnOnce() -> T),
150    {
151        if !self.matched {
152            Some(f())
153        } else {
154            self.value
155        }
156    }
157}
158
159pub trait MatchKey {
160    fn match_key(&self, key: &Key) -> bool;
161}
162
163impl MatchKey for Key {
164    fn match_key(&self, key: &Key) -> bool {
165        self == key
166    }
167}
168
169impl MatchKey for char {
170    fn match_key(&self, key: &Key) -> bool {
171        match key {
172            Key::Character(text) => {
173                let mut buf = [0; 4];
174                text.eq_ignore_ascii_case(self.encode_utf8(&mut buf))
175            }
176            _ => false,
177        }
178    }
179}