aboutsummaryrefslogtreecommitdiff
path: root/src/keybind.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/keybind.rs')
-rw-r--r--src/keybind.rs146
1 files changed, 146 insertions, 0 deletions
diff --git a/src/keybind.rs b/src/keybind.rs
new file mode 100644
index 0000000..8ab479a
--- /dev/null
+++ b/src/keybind.rs
@@ -0,0 +1,146 @@
1use std::{fmt, str::FromStr};
2
3use sdl2::keyboard::{Keycode, Mod};
4
5#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
6pub struct Keybind {
7 keycode: Keycode,
8 keymod: Mod,
9}
10
11impl Keybind {
12 pub fn new(keycode: Keycode, keymod: Mod) -> Self {
13 Self { keycode, keymod }
14 }
15}
16
17// ctrl keys: C-<letter> len 3
18// shift keys: S-<letter> len 3
19// normal keys: <letter> len 1
20
21#[derive(Debug, Copy, Clone, PartialEq)]
22pub enum KeybindError {
23 Parse,
24 InvalidMod,
25 InvalidKey,
26}
27
28impl fmt::Display for KeybindError {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 Self::Parse => write!(f, "error parsing keybind"),
32 Self::InvalidKey => write!(f, "unsupported keybind, try `A-Z0-9`"),
33 Self::InvalidMod => {
34 write!(f, "unsupported keybind modifier, try `C-<key> or S-<key>`")
35 }
36 }
37 }
38}
39
40impl std::error::Error for KeybindError {}
41
42impl FromStr for Keybind {
43 type Err = KeybindError;
44 fn from_str(s: &str) -> Result<Self, Self::Err> {
45 parse_from_str(s)
46 }
47}
48
49fn parse_from_str(s: &str) -> Result<Keybind, KeybindError> {
50 let s = s.to_lowercase();
51 match s.len() {
52 // either ctrl or shift key
53 3 => {
54 let keymod = parse_modifier(&s[..2])?;
55 let keybind = parse_keybind(&s[2..])?;
56 Ok(Keybind::new(keybind, keymod))
57 }
58 1 => {
59 let keybind = parse_keybind(&s)?;
60 Ok(Keybind::new(keybind, Mod::NOMOD))
61 }
62 _ => Err(KeybindError::Parse),
63 }
64}
65
66fn parse_modifier(s: &str) -> Result<Mod, KeybindError> {
67 match s {
68 "c-" => Ok(Mod::LCTRLMOD),
69 "s-" => Ok(Mod::LSHIFTMOD),
70 _ => Err(KeybindError::InvalidMod),
71 }
72}
73
74fn parse_keybind(s: &str) -> Result<Keycode, KeybindError> {
75 match s.to_uppercase().as_str() {
76 "0" => Ok(Keycode::Num0),
77 "1" => Ok(Keycode::Num1),
78 "2" => Ok(Keycode::Num2),
79 "3" => Ok(Keycode::Num3),
80 "4" => Ok(Keycode::Num4),
81 "5" => Ok(Keycode::Num5),
82 "6" => Ok(Keycode::Num6),
83 "7" => Ok(Keycode::Num7),
84 "8" => Ok(Keycode::Num8),
85 "9" => Ok(Keycode::Num9),
86 "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O"
87 | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" => {
88 Ok(Keycode::from_name(s).unwrap())
89 }
90 _ => Err(KeybindError::InvalidKey),
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 #[test]
98 fn without_mod() {
99 assert_eq!(
100 Keybind::from_str("x").unwrap(),
101 Keybind::new(Keycode::X, Mod::NOMOD)
102 );
103 assert_eq!(
104 Keybind::from_str("X").unwrap(),
105 Keybind::new(Keycode::X, Mod::NOMOD)
106 );
107 }
108 #[test]
109 fn with_ctrl_mod() {
110 assert_eq!(
111 Keybind::from_str("c-x").unwrap(),
112 Keybind::new(Keycode::X, Mod::LCTRLMOD)
113 );
114 assert_eq!(
115 Keybind::from_str("C-x").unwrap(),
116 Keybind::new(Keycode::X, Mod::LCTRLMOD)
117 );
118 assert_eq!(
119 Keybind::from_str("c-X").unwrap(),
120 Keybind::new(Keycode::X, Mod::LCTRLMOD)
121 );
122 assert_eq!(
123 Keybind::from_str("C-X").unwrap(),
124 Keybind::new(Keycode::X, Mod::LCTRLMOD)
125 );
126 }
127 #[test]
128 fn with_shift_mod() {
129 assert_eq!(
130 Keybind::from_str("s-x").unwrap(),
131 Keybind::new(Keycode::X, Mod::LSHIFTMOD)
132 );
133 assert_eq!(
134 Keybind::from_str("S-x").unwrap(),
135 Keybind::new(Keycode::X, Mod::LSHIFTMOD)
136 );
137 assert_eq!(
138 Keybind::from_str("s-X").unwrap(),
139 Keybind::new(Keycode::X, Mod::LSHIFTMOD)
140 );
141 assert_eq!(
142 Keybind::from_str("S-X").unwrap(),
143 Keybind::new(Keycode::X, Mod::LSHIFTMOD)
144 );
145 }
146}