1use std::{fmt::Display, hash::Hash};
2
3use serde::{Deserialize, Serialize};
4
5use super::key_strike::KeyStrike;
6
7#[derive(
8 Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord,
9)]
10#[serde(try_from = "String", into = "String")]
11pub struct KeyCombo {
12 pub modifiers: KeyModifiers,
13 pub key: KeyStrike,
14}
15
16impl Display for KeyCombo {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 let modif = self.modifiers.to_string();
19 let key = self.key.to_string();
20 if modif.is_empty() {
21 write!(f, "{}", key)
22 } else {
23 write!(f, "{}&{}", modif, key)
24 }
25 }
26}
27
28impl TryFrom<String> for KeyCombo {
29 type Error = String;
30
31 fn try_from(value: String) -> Result<Self, Self::Error> {
32 let splits = value.split("&").collect::<Vec<_>>();
33 match splits.len() {
34 0 => Err("No Keys found here".to_string()),
35 1 => match KeyStrike::try_from(splits.first().unwrap().trim().to_string()) {
36 Ok(ks) => Ok(KeyCombo {
37 modifiers: KeyModifiers::default(),
38 key: ks,
39 }),
40 Err(e) => Err(e),
41 },
42 2 => {
43 let m = splits.first().unwrap().trim().to_string();
44 let k = splits.last().unwrap().trim().to_string();
45
46 match (KeyModifiers::try_from(m), KeyStrike::try_from(k)) {
47 (Ok(modifiers), Ok(key)) => Ok(KeyCombo { modifiers, key }),
48 (Ok(_), Err(e)) => Err(e),
49 (Err(e), Ok(_)) => Err(e),
50 (Err(em), Err(ek)) => Err(format!("{} - {}", em, ek)),
51 }
52 }
53 _ => Err(format!(
54 "This is a non valid combination, only one key and a modifier combination is allowed: {}",
55 value
56 )),
57 }
58 }
59}
60
61impl From<KeyCombo> for String {
62 fn from(value: KeyCombo) -> Self {
63 value.to_string()
64 }
65}
66
67impl KeyCombo {
97 pub fn new(modifiers: KeyModifiers, key: KeyStrike) -> Self {
98 Self { modifiers, key }
99 }
100
101 pub fn is_valid_binding(&self) -> bool {
105 let is_letter_combo = (self.modifiers.is_ctrl() || self.modifiers.is_alt())
106 && (self.key >= KeyStrike::KeyA && self.key <= KeyStrike::KeyZ
107 || self.key >= KeyStrike::Digit0 && self.key <= KeyStrike::Digit9
108 || matches!(
109 self.key,
110 KeyStrike::Comma
111 | KeyStrike::Period
112 | KeyStrike::Slash
113 | KeyStrike::Semicolon
114 | KeyStrike::Quote
115 | KeyStrike::BracketLeft
116 | KeyStrike::BracketRight
117 | KeyStrike::Backslash
118 | KeyStrike::Backquote
119 | KeyStrike::Minus
120 | KeyStrike::Equal
121 ));
122 let is_fkey = matches!(
123 self.key,
124 KeyStrike::F1
125 | KeyStrike::F2
126 | KeyStrike::F3
127 | KeyStrike::F4
128 | KeyStrike::F5
129 | KeyStrike::F6
130 | KeyStrike::F7
131 | KeyStrike::F8
132 | KeyStrike::F9
133 | KeyStrike::F10
134 | KeyStrike::F11
135 | KeyStrike::F12
136 );
137 is_letter_combo || is_fkey
138 }
139}
140
141#[derive(
146 Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord,
147)]
148#[serde(try_from = "String", into = "String")]
149pub struct KeyModifiers {
150 alt: bool,
151 ctrl: bool,
152 cmd: bool,
153 shift: bool,
154}
155
156const META: &str = "meta";
158const CMD: &str = "cmd";
159
160const ALT: &str = "alt";
161const CONTROL: &str = "ctrl";
162const SHIFT: &str = "shift";
163
164#[cfg(target_os = "macos")]
166const META_CMD: &str = CMD;
167#[cfg(not(target_os = "macos"))]
168const META_CMD: &str = META;
169
170impl TryFrom<String> for KeyModifiers {
171 type Error = String;
172
173 fn try_from(value: String) -> Result<Self, Self::Error> {
174 let splits = value.split("+");
175 let mut modifiers = KeyModifiers::default();
176 for modif in splits {
177 match modif {
178 "" => {}
179 CONTROL => modifiers.with_ctrl(),
180 SHIFT => modifiers.with_shift(),
181 ALT => modifiers.with_alt(),
182 META => modifiers.with_meta_cmd(),
183 CMD => modifiers.with_meta_cmd(),
184 _ => return Err(format!("Non valid modifier value: {}", modif)),
185 }
186 }
187 Ok(modifiers)
188 }
189}
190
191impl From<KeyModifiers> for String {
192 fn from(value: KeyModifiers) -> Self {
193 value.to_string()
194 }
195}
196
197impl Display for KeyModifiers {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 let mut modifiers = vec![];
219 if self.is_ctrl() {
220 modifiers.push(CONTROL);
221 }
222 if self.is_alt() {
223 modifiers.push(ALT);
224 }
225 if self.is_meta_cmd() {
226 modifiers.push(META_CMD);
227 }
228 if self.is_shift() {
229 modifiers.push(SHIFT);
230 }
231 let string = modifiers.join("+");
232 write!(f, "{}", string)
233 }
234}
235
236impl KeyModifiers {
237 pub fn new() -> Self {
238 KeyModifiers::default()
239 }
240
241 pub fn is_empty(&self) -> bool {
242 !self.alt && !self.ctrl && !self.cmd && !self.shift
243 }
244
245 pub fn with_shift(&mut self) {
246 self.shift = true;
247 }
248 pub fn with_ctrl(&mut self) {
249 self.ctrl = true;
250 }
251 pub fn with_alt(&mut self) {
252 self.alt = true;
253 }
254 pub fn with_meta_cmd(&mut self) {
255 self.cmd = true;
256 }
257
258 pub fn and_shift(mut self) -> Self {
259 self.with_shift();
260 self
261 }
262 pub fn and_ctrl(mut self) -> Self {
263 self.with_ctrl();
264 self
265 }
266 pub fn and_alt(mut self) -> Self {
267 self.with_alt();
268 self
269 }
270 pub fn and_meta_cmd(mut self) -> Self {
271 self.with_meta_cmd();
272 self
273 }
274 pub fn is_shift(&self) -> bool {
276 self.shift
277 }
278
279 pub fn is_ctrl(&self) -> bool {
281 self.ctrl
282 }
283
284 pub fn is_alt(&self) -> bool {
286 self.alt
287 }
288
289 pub fn is_meta_cmd(&self) -> bool {
291 self.cmd
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use color_eyre::eyre;
298
299 use crate::keys::{key_combo::KeyCombo, key_strike::KeyStrike};
300
301 use super::KeyModifiers;
302
303 #[test]
304 fn serialize_keymodifier() -> eyre::Result<()> {
305 let mut km = KeyModifiers::default();
306 km.with_shift();
307
308 let km_ser = km.to_string();
309 assert_eq!("shift", km_ser);
310
311 km.with_ctrl();
312 let km_ser = km.to_string();
313 assert_eq!("ctrl+shift", km_ser);
314 Ok(())
315 }
316
317 #[test]
318 fn deserialize_keymodifier() -> eyre::Result<()> {
319 let text = "meta+shift";
320 let km = KeyModifiers::try_from(text.to_string());
321
322 assert!(km.is_ok());
323
324 let km = km.unwrap();
325 assert!(km.cmd);
326 assert!(km.shift);
327 assert!(!km.ctrl);
328 assert!(!km.alt);
329
330 Ok(())
331 }
332
333 #[test]
334 fn serialize_keycombo() {
335 let kc = KeyCombo::new(
336 KeyModifiers::new().and_meta_cmd().and_ctrl(),
337 crate::keys::key_strike::KeyStrike::KeyN,
338 );
339
340 let kc_ser = kc.to_string();
341
342 #[cfg(target_os = "macos")]
343 assert_eq!("ctrl+cmd&N", kc_ser);
344 #[cfg(not(target_os = "macos"))]
345 assert_eq!("ctrl+meta&N", kc_ser);
346 }
347
348 #[test]
349 fn deserialize_keycombo_meta() {
350 let string = "shift+meta & H".to_string();
351
352 let kc = KeyCombo::try_from(string).unwrap();
353
354 assert!(kc.modifiers.shift);
355 assert!(kc.modifiers.cmd);
356 assert!(!kc.modifiers.ctrl);
357 assert!(!kc.modifiers.alt);
358 assert_eq!(kc.key, KeyStrike::KeyH);
359 }
360
361 #[test]
362 fn deserialize_keycombo_cmd() {
363 let string = "shift+cmd & H".to_string();
364
365 let kc = KeyCombo::try_from(string).unwrap();
366
367 assert!(kc.modifiers.shift);
368 assert!(kc.modifiers.cmd);
369 assert!(!kc.modifiers.ctrl);
370 assert!(!kc.modifiers.alt);
371 assert_eq!(kc.key, KeyStrike::KeyH);
372 }
373
374 #[test]
375 fn deserialize_keycombo_no_mod() {
376 let string = "L".to_string();
377
378 let kc = KeyCombo::try_from(string).unwrap();
379
380 assert!(!kc.modifiers.shift);
381 assert!(!kc.modifiers.cmd);
382 assert!(!kc.modifiers.ctrl);
383 assert!(!kc.modifiers.alt);
384 assert_eq!(kc.key, KeyStrike::KeyL);
385 }
386
387 #[test]
388 fn roundtrip_keycombo_no_modifier() {
389 let kc = KeyCombo::new(KeyModifiers::default(), KeyStrike::Tab);
392 let serialized = kc.to_string();
393 assert_eq!(serialized, "<Tab>");
394
395 let parsed = KeyCombo::try_from(serialized).unwrap();
396 assert_eq!(parsed, kc);
397 }
398
399 #[test]
400 fn deserialize_legacy_no_modifier_with_ampersand() {
401 let kc = KeyCombo::try_from(" & <Tab>".to_string()).unwrap();
403 assert!(!kc.modifiers.is_ctrl());
404 assert!(!kc.modifiers.is_shift());
405 assert_eq!(kc.key, KeyStrike::Tab);
406 }
407}