1use super::platform::KeyGestureFormatInfo;
2use super::{Key, KeyEventArgs, KeyModifiers, ParseKeyModifiersError};
3use crate::utilities::FormatError;
4use std::fmt;
5use std::rc::Rc;
6use std::str::FromStr;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub struct KeyGesture {
11 key: Key,
12 key_modifiers: KeyModifiers,
13}
14
15impl KeyGesture {
16 pub const fn new(key: Key, modifiers: KeyModifiers) -> Self {
18 Self { key, key_modifiers: modifiers }
19 }
20
21 pub const fn from_key(key: Key) -> Self {
23 Self { key, key_modifiers: KeyModifiers::NONE }
24 }
25
26 #[inline]
28 pub const fn key(&self) -> Key {
29 self.key
30 }
31
32 #[inline]
34 pub const fn key_modifiers(&self) -> KeyModifiers {
35 self.key_modifiers
36 }
37
38 pub fn parse(gesture: &str) -> Result<KeyGesture, ParseKeyModifiersError> {
45 let mut key = Key::None;
48 let mut key_modifiers = KeyModifiers::NONE;
49 let mut cstart = 0;
50
51 for (c, ch) in gesture.char_indices().chain(std::iter::once((gesture.len(), '\0'))) {
52 let is_last = c == gesture.len();
53
54 if is_last || (ch == '+' && cstart != c) {
55 let part = gesture[cstart..c].trim();
56
57 match Self::try_parse_key(part) {
58 Some(parsed) => key = parsed,
59 None => {
60 key = Key::None;
63 key_modifiers |= Self::parse_modifier(part)?;
64 }
65 }
66
67 cstart = c + 1;
68 }
69 }
70
71 Ok(KeyGesture::new(key, key_modifiers))
72 }
73
74 pub fn matches(&self, key_event: Option<&KeyEventArgs>) -> bool {
78 key_event.is_some_and(|key_event| {
79 key_event.key_modifiers == self.key_modifiers
80 && Self::resolve_num_pad_operation_key(key_event.key) == Self::resolve_num_pad_operation_key(self.key)
81 })
82 }
83
84 fn try_parse_key(key_str: &str) -> Option<Key> {
85 match key_str {
86 "+" => return Some(Key::OemPlus),
87 "-" => return Some(Key::OemMinus),
88 "." => return Some(Key::OemPeriod),
89 "," => return Some(Key::OemComma),
90 _ => {}
91 }
92
93 Key::parse(key_str).ok()
94 }
95
96 fn parse_modifier(modifier: &str) -> Result<KeyModifiers, ParseKeyModifiersError> {
97 if modifier.eq_ignore_ascii_case("ctrl") {
98 return Ok(KeyModifiers::CONTROL);
99 }
100
101 if modifier.eq_ignore_ascii_case("cmd") || modifier.eq_ignore_ascii_case("win") || modifier == "⌘" {
102 return Ok(KeyModifiers::META);
103 }
104
105 KeyModifiers::parse(modifier)
106 }
107
108 fn resolve_num_pad_operation_key(key: Key) -> Key {
109 match key {
110 Key::Add => Key::OemPlus,
111 Key::Subtract => Key::OemMinus,
112 Key::Decimal => Key::OemPeriod,
113 _ => key,
114 }
115 }
116}
117
118impl FromStr for KeyGesture {
119 type Err = ParseKeyModifiersError;
120
121 fn from_str(s: &str) -> Result<Self, Self::Err> {
122 Self::parse(s)
123 }
124}
125
126impl KeyGesture {
127 pub fn to_platform_string(&self, format_provider: Option<Rc<KeyGestureFormatInfo>>) -> String {
131 self.format_with(&KeyGestureFormatInfo::get_instance(format_provider))
132 }
133
134 pub fn to_string_format(
143 &self,
144 format: Option<&str>,
145 format_provider: Option<Rc<KeyGestureFormatInfo>>,
146 ) -> Result<String, FormatError> {
147 let format_info = match format {
148 None | Some("") | Some("g") => KeyGestureFormatInfo::invariant(),
149 Some("p") => KeyGestureFormatInfo::get_instance(format_provider),
150 _ => return Err(FormatError::new("Unknown format specifier")),
151 };
152
153 Ok(self.format_with(&format_info))
154 }
155
156 pub fn format_with(&self, format_info: &KeyGestureFormatInfo) -> String {
158 let mut s = String::new();
159 let mut part = |text: &str| {
160 if !s.is_empty() {
161 s.push('+');
162 }
163 s.push_str(text);
164 };
165
166 if self.key_modifiers.contains(KeyModifiers::CONTROL) {
167 part(format_info.ctrl());
168 }
169
170 if self.key_modifiers.contains(KeyModifiers::SHIFT) {
171 part(format_info.shift());
172 }
173
174 if self.key_modifiers.contains(KeyModifiers::ALT) {
175 part(format_info.alt());
176 }
177
178 if self.key_modifiers.contains(KeyModifiers::META) {
179 part(format_info.meta());
180 }
181
182 if self.key != Key::None || self.key_modifiers == KeyModifiers::NONE {
183 part(&format_info.format_key(self.key));
184 }
185
186 s
187 }
188}
189
190impl fmt::Display for KeyGesture {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 let mut any = false;
194 let mut part = |f: &mut fmt::Formatter<'_>, s: &str| -> fmt::Result {
195 if any {
196 f.write_str("+")?;
197 }
198 any = true;
199 f.write_str(s)
200 };
201
202 if self.key_modifiers.contains(KeyModifiers::CONTROL) {
203 part(f, "Ctrl")?;
204 }
205
206 if self.key_modifiers.contains(KeyModifiers::SHIFT) {
207 part(f, "Shift")?;
208 }
209
210 if self.key_modifiers.contains(KeyModifiers::ALT) {
211 part(f, "Alt")?;
212 }
213
214 if self.key_modifiers.contains(KeyModifiers::META) {
215 part(f, "Cmd")?;
216 }
217
218 if self.key != Key::None || self.key_modifiers == KeyModifiers::NONE {
219 part(f, self.key.name())?;
220 }
221
222 Ok(())
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn key_gesture_is_able_to_parse_sample_data() {
232 let data = [
233 ("Ctrl+A", KeyGesture::new(Key::A, KeyModifiers::CONTROL)),
234 (" \tShift\t+Alt +B", KeyGesture::new(Key::B, KeyModifiers::SHIFT | KeyModifiers::ALT)),
235 ("Control++", KeyGesture::new(Key::OemPlus, KeyModifiers::CONTROL)),
236 ("Shift+⌘+A", KeyGesture::new(Key::A, KeyModifiers::META | KeyModifiers::SHIFT)),
237 ("Shift+Cmd+A", KeyGesture::new(Key::A, KeyModifiers::META | KeyModifiers::SHIFT)),
238 ("None", KeyGesture::from_key(Key::None)),
239 ("Alt+Shift", KeyGesture::new(Key::None, KeyModifiers::ALT | KeyModifiers::SHIFT)),
240 ];
241
242 for (text, gesture) in data {
243 assert_eq!(KeyGesture::parse(text), Ok(gesture), "{text}");
244 }
245 }
246
247 #[test]
248 fn key_gesture_parse_rejects_unknown_parts() {
249 assert!(KeyGesture::parse("Ctrl+NotAKey").is_err());
250 assert!("Bogus+A".parse::<KeyGesture>().is_err());
251 }
252
253 #[test]
254 fn key_gesture_matches_num_pad_to_regular_digit() {
255 let data = [(Key::OemMinus, Key::Subtract), (Key::OemPlus, Key::Add), (Key::OemPeriod, Key::Decimal)];
256
257 for (gesture_key, pressed_key) in data {
258 let key_gesture = KeyGesture::from_key(gesture_key);
259 let mut args = KeyEventArgs::new();
260 args.key = pressed_key;
261
262 assert!(key_gesture.matches(Some(&args)));
263 }
264
265 assert!(!KeyGesture::from_key(Key::A).matches(None));
266 }
267
268 #[test]
269 fn platform_format_uses_overrides() {
270 let mut overrides = std::collections::HashMap::new();
271 overrides.insert(Key::Return, "\u{21A9}".to_string());
272 let info = KeyGestureFormatInfo::new(Some(overrides), "\u{2318}", "\u{2303}", "\u{2325}", "\u{21E7}");
273
274 assert_eq!(KeyGesture::new(Key::Return, KeyModifiers::META).format_with(&info), "\u{2318}+\u{21A9}");
275 assert_eq!(KeyGesture::new(Key::OemPlus, KeyModifiers::CONTROL).format_with(&info), "\u{2303}++");
276 assert_eq!(KeyGesture::new(Key::Left, KeyModifiers::SHIFT).format_with(&info), "\u{21E7}+Left Arrow");
277 assert_eq!(KeyGesture::from_key(Key::A).format_with(&info), "A");
278
279 let gesture = KeyGesture::new(Key::OemPlus, KeyModifiers::CONTROL | KeyModifiers::META);
282 assert_eq!(gesture.to_platform_string(None), gesture.to_string());
283 assert_eq!(gesture.to_string(), "Ctrl+Cmd+OemPlus");
284 }
285
286 #[test]
288 fn to_string_format_selects_the_format_info() {
289 let gesture = KeyGesture::new(Key::A, KeyModifiers::CONTROL | KeyModifiers::SHIFT);
290
291 assert_eq!(gesture.to_string_format(None, None).unwrap(), "Ctrl+Shift+A");
292 assert_eq!(gesture.to_string_format(Some(""), None).unwrap(), "Ctrl+Shift+A");
293 assert_eq!(gesture.to_string_format(Some("g"), None).unwrap(), "Ctrl+Shift+A");
294
295 let info = Rc::new(KeyGestureFormatInfo::new(None, "\u{2318}", "\u{2303}", "\u{2325}", "\u{21E7}"));
296
297 assert_eq!(gesture.to_string_format(Some("g"), Some(info.clone())).unwrap(), "Ctrl+Shift+A");
299 assert_eq!(gesture.to_string_format(Some("p"), Some(info)).unwrap(), "\u{2303}+\u{21E7}+A");
300
301 assert_eq!(gesture.to_string_format(Some("x"), None), Err(FormatError::new("Unknown format specifier")));
302 }
303
304 #[test]
305 fn to_string_produces_correct_results() {
306 let data = [
307 (KeyGesture::from_key(Key::A), "A"),
308 (KeyGesture::new(Key::A, KeyModifiers::CONTROL), "Ctrl+A"),
309 (KeyGesture::new(Key::A, KeyModifiers::CONTROL | KeyModifiers::SHIFT), "Ctrl+Shift+A"),
310 (KeyGesture::new(Key::A, KeyModifiers::ALT | KeyModifiers::SHIFT), "Shift+Alt+A"),
311 (
312 KeyGesture::new(Key::A, KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT),
313 "Ctrl+Shift+Alt+A",
314 ),
315 (KeyGesture::new(Key::A, KeyModifiers::META | KeyModifiers::SHIFT), "Shift+Cmd+A"),
316 (KeyGesture::from_key(Key::None), "None"),
317 (KeyGesture::new(Key::None, KeyModifiers::ALT | KeyModifiers::SHIFT), "Shift+Alt"),
318 ];
319
320 for (gesture, expected) in data {
321 assert_eq!(gesture.to_string(), expected);
322 }
323 }
324}