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
107 && self.key <= KeyStrike::KeyZ;
108 let is_fkey = matches!(
109 self.key,
110 KeyStrike::F1
111 | KeyStrike::F2
112 | KeyStrike::F3
113 | KeyStrike::F4
114 | KeyStrike::F5
115 | KeyStrike::F6
116 | KeyStrike::F7
117 | KeyStrike::F8
118 | KeyStrike::F9
119 | KeyStrike::F10
120 | KeyStrike::F11
121 | KeyStrike::F12
122 );
123 is_letter_combo || is_fkey
124 }
125}
126
127#[derive(
132 Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord,
133)]
134#[serde(try_from = "String", into = "String")]
135pub struct KeyModifiers {
136 alt: bool,
137 ctrl: bool,
138 cmd: bool,
139 shift: bool,
140}
141
142const META: &str = "meta";
144const CMD: &str = "cmd";
145
146const ALT: &str = "alt";
147const CONTROL: &str = "ctrl";
148const SHIFT: &str = "shift";
149
150#[cfg(target_os = "macos")]
152const META_CMD: &str = CMD;
153#[cfg(not(target_os = "macos"))]
154const META_CMD: &str = META;
155
156impl TryFrom<String> for KeyModifiers {
157 type Error = String;
158
159 fn try_from(value: String) -> Result<Self, Self::Error> {
160 let splits = value.split("+");
161 let mut modifiers = KeyModifiers::default();
162 for modif in splits {
163 match modif {
164 "" => {}
165 CONTROL => modifiers.with_ctrl(),
166 SHIFT => modifiers.with_shift(),
167 ALT => modifiers.with_alt(),
168 META => modifiers.with_meta_cmd(),
169 CMD => modifiers.with_meta_cmd(),
170 _ => return Err(format!("Non valid modifier value: {}", modif)),
171 }
172 }
173 Ok(modifiers)
174 }
175}
176
177impl From<KeyModifiers> for String {
178 fn from(value: KeyModifiers) -> Self {
179 value.to_string()
180 }
181}
182
183impl Display for KeyModifiers {
203 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204 let mut modifiers = vec![];
205 if self.is_ctrl() {
206 modifiers.push(CONTROL);
207 }
208 if self.is_alt() {
209 modifiers.push(ALT);
210 }
211 if self.is_meta_cmd() {
212 modifiers.push(META_CMD);
213 }
214 if self.is_shift() {
215 modifiers.push(SHIFT);
216 }
217 let string = modifiers.join("+");
218 write!(f, "{}", string)
219 }
220}
221
222impl KeyModifiers {
223 pub fn new() -> Self {
224 KeyModifiers::default()
225 }
226
227 pub fn is_empty(&self) -> bool {
228 !self.alt && !self.ctrl && !self.cmd && !self.shift
229 }
230
231 pub fn with_shift(&mut self) {
232 self.shift = true;
233 }
234 pub fn with_ctrl(&mut self) {
235 self.ctrl = true;
236 }
237 pub fn with_alt(&mut self) {
238 self.alt = true;
239 }
240 pub fn with_meta_cmd(&mut self) {
241 self.cmd = true;
242 }
243
244 pub fn and_shift(mut self) -> Self {
245 self.with_shift();
246 self
247 }
248 pub fn and_ctrl(mut self) -> Self {
249 self.with_ctrl();
250 self
251 }
252 pub fn and_alt(mut self) -> Self {
253 self.with_alt();
254 self
255 }
256 pub fn and_meta_cmd(mut self) -> Self {
257 self.with_meta_cmd();
258 self
259 }
260 pub fn is_shift(&self) -> bool {
262 self.shift
263 }
264
265 pub fn is_ctrl(&self) -> bool {
267 self.ctrl
268 }
269
270 pub fn is_alt(&self) -> bool {
272 self.alt
273 }
274
275 pub fn is_meta_cmd(&self) -> bool {
277 self.cmd
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use color_eyre::eyre;
284
285 use crate::keys::{key_combo::KeyCombo, key_strike::KeyStrike};
286
287 use super::KeyModifiers;
288
289 #[test]
290 fn serialize_keymodifier() -> eyre::Result<()> {
291 let mut km = KeyModifiers::default();
292 km.with_shift();
293
294 let km_ser = km.to_string();
295 assert_eq!("shift", km_ser);
296
297 km.with_ctrl();
298 let km_ser = km.to_string();
299 assert_eq!("ctrl+shift", km_ser);
300 Ok(())
301 }
302
303 #[test]
304 fn deserialize_keymodifier() -> eyre::Result<()> {
305 let text = "meta+shift";
306 let km = KeyModifiers::try_from(text.to_string());
307
308 assert!(km.is_ok());
309
310 let km = km.unwrap();
311 assert!(km.cmd);
312 assert!(km.shift);
313 assert!(!km.ctrl);
314 assert!(!km.alt);
315
316 Ok(())
317 }
318
319 #[test]
320 fn serialize_keycombo() {
321 let kc = KeyCombo::new(
322 KeyModifiers::new().and_meta_cmd().and_ctrl(),
323 crate::keys::key_strike::KeyStrike::KeyN,
324 );
325
326 let kc_ser = kc.to_string();
327
328 #[cfg(target_os = "macos")]
329 assert_eq!("ctrl+cmd&N", kc_ser);
330 #[cfg(not(target_os = "macos"))]
331 assert_eq!("ctrl+meta&N", kc_ser);
332 }
333
334 #[test]
335 fn deserialize_keycombo_meta() {
336 let string = "shift+meta & H".to_string();
337
338 let kc = KeyCombo::try_from(string).unwrap();
339
340 assert!(kc.modifiers.shift);
341 assert!(kc.modifiers.cmd);
342 assert!(!kc.modifiers.ctrl);
343 assert!(!kc.modifiers.alt);
344 assert_eq!(kc.key, KeyStrike::KeyH);
345 }
346
347 #[test]
348 fn deserialize_keycombo_cmd() {
349 let string = "shift+cmd & H".to_string();
350
351 let kc = KeyCombo::try_from(string).unwrap();
352
353 assert!(kc.modifiers.shift);
354 assert!(kc.modifiers.cmd);
355 assert!(!kc.modifiers.ctrl);
356 assert!(!kc.modifiers.alt);
357 assert_eq!(kc.key, KeyStrike::KeyH);
358 }
359
360 #[test]
361 fn deserialize_keycombo_no_mod() {
362 let string = "L".to_string();
363
364 let kc = KeyCombo::try_from(string).unwrap();
365
366 assert!(!kc.modifiers.shift);
367 assert!(!kc.modifiers.cmd);
368 assert!(!kc.modifiers.ctrl);
369 assert!(!kc.modifiers.alt);
370 assert_eq!(kc.key, KeyStrike::KeyL);
371 }
372
373 #[test]
374 fn roundtrip_keycombo_no_modifier() {
375 let kc = KeyCombo::new(KeyModifiers::default(), KeyStrike::Tab);
378 let serialized = kc.to_string();
379 assert_eq!(serialized, "<Tab>");
380
381 let parsed = KeyCombo::try_from(serialized).unwrap();
382 assert_eq!(parsed, kc);
383 }
384
385 #[test]
386 fn deserialize_legacy_no_modifier_with_ampersand() {
387 let kc = KeyCombo::try_from(" & <Tab>".to_string()).unwrap();
389 assert!(!kc.modifiers.is_ctrl());
390 assert!(!kc.modifiers.is_shift());
391 assert_eq!(kc.key, KeyStrike::Tab);
392 }
393}