1use astrelis_winit::event::KeyCode;
7use bitflags::bitflags;
8
9bitflags! {
10 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
12 pub struct Modifiers: u8 {
13 const NONE = 0;
15 const SHIFT = 1 << 0;
17 const CTRL = 1 << 1;
19 const ALT = 1 << 2;
21 const SUPER = 1 << 3;
23 }
24}
25
26impl Modifiers {
27 pub fn from_keys(shift: bool, ctrl: bool, alt: bool, super_key: bool) -> Self {
29 let mut mods = Modifiers::NONE;
30 if shift {
31 mods |= Modifiers::SHIFT;
32 }
33 if ctrl {
34 mods |= Modifiers::CTRL;
35 }
36 if alt {
37 mods |= Modifiers::ALT;
38 }
39 if super_key {
40 mods |= Modifiers::SUPER;
41 }
42 mods
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Hash)]
48pub struct Keybind {
49 pub key: KeyCode,
51 pub modifiers: Modifiers,
53 pub description: &'static str,
55}
56
57impl Keybind {
58 pub fn new(key: KeyCode, modifiers: Modifiers, description: &'static str) -> Self {
60 Self {
61 key,
62 modifiers,
63 description,
64 }
65 }
66
67 pub fn key(key: KeyCode, description: &'static str) -> Self {
69 Self::new(key, Modifiers::NONE, description)
70 }
71
72 pub fn ctrl(key: KeyCode, description: &'static str) -> Self {
74 Self::new(key, Modifiers::CTRL, description)
75 }
76
77 pub fn shift(key: KeyCode, description: &'static str) -> Self {
79 Self::new(key, Modifiers::SHIFT, description)
80 }
81
82 pub fn ctrl_shift(key: KeyCode, description: &'static str) -> Self {
84 Self::new(key, Modifiers::CTRL | Modifiers::SHIFT, description)
85 }
86
87 pub fn matches(&self, key: KeyCode, modifiers: Modifiers) -> bool {
89 self.key == key && self.modifiers == modifiers
90 }
91
92 pub fn to_string_short(&self) -> String {
94 let mut parts = Vec::new();
95
96 if self.modifiers.contains(Modifiers::CTRL) {
97 #[cfg(target_os = "macos")]
98 parts.push("⌘");
99 #[cfg(not(target_os = "macos"))]
100 parts.push("Ctrl");
101 }
102 if self.modifiers.contains(Modifiers::ALT) {
103 #[cfg(target_os = "macos")]
104 parts.push("⌥");
105 #[cfg(not(target_os = "macos"))]
106 parts.push("Alt");
107 }
108 if self.modifiers.contains(Modifiers::SHIFT) {
109 #[cfg(target_os = "macos")]
110 parts.push("⇧");
111 #[cfg(not(target_os = "macos"))]
112 parts.push("Shift");
113 }
114 if self.modifiers.contains(Modifiers::SUPER) {
115 #[cfg(target_os = "macos")]
116 parts.push("⌘");
117 #[cfg(not(target_os = "macos"))]
118 parts.push("Win");
119 }
120
121 parts.push(key_code_name(self.key));
122
123 parts.join("+")
124 }
125}
126
127#[derive(Debug, Default)]
129pub struct KeybindRegistry {
130 keybinds: Vec<(&'static str, Keybind, i32)>,
132}
133
134impl KeybindRegistry {
135 pub fn new() -> Self {
137 Self::default()
138 }
139
140 pub fn register(&mut self, middleware: &'static str, keybind: Keybind, priority: i32) {
144 self.keybinds.push((middleware, keybind, priority));
145 self.keybinds.sort_by(|a, b| b.2.cmp(&a.2));
147 }
148
149 pub fn unregister(&mut self, middleware: &str) {
151 self.keybinds.retain(|(name, _, _)| *name != middleware);
152 }
153
154 pub fn find_matches(&self, key: KeyCode, modifiers: Modifiers) -> Vec<(&str, &Keybind)> {
158 self.keybinds
159 .iter()
160 .filter(|(_, keybind, _)| keybind.matches(key, modifiers))
161 .map(|(name, keybind, _)| (*name, keybind))
162 .collect()
163 }
164
165 pub fn get_keybinds(&self, middleware: &'static str) -> Vec<&Keybind> {
167 self.keybinds
168 .iter()
169 .filter(|(name, _, _)| *name == middleware)
170 .map(|(_, keybind, _)| keybind)
171 .collect()
172 }
173
174 pub fn all_keybinds(&self) -> impl Iterator<Item = (&'static str, &Keybind)> {
176 self.keybinds
177 .iter()
178 .map(|(name, keybind, _)| (*name, keybind))
179 }
180
181 pub fn clear(&mut self) {
183 self.keybinds.clear();
184 }
185}
186
187fn key_code_name(key: KeyCode) -> &'static str {
189 match key {
190 KeyCode::Escape => "Esc",
191 KeyCode::F1 => "F1",
192 KeyCode::F2 => "F2",
193 KeyCode::F3 => "F3",
194 KeyCode::F4 => "F4",
195 KeyCode::F5 => "F5",
196 KeyCode::F6 => "F6",
197 KeyCode::F7 => "F7",
198 KeyCode::F8 => "F8",
199 KeyCode::F9 => "F9",
200 KeyCode::F10 => "F10",
201 KeyCode::F11 => "F11",
202 KeyCode::F12 => "F12",
203 KeyCode::Backquote => "`",
204 KeyCode::Digit1 => "1",
205 KeyCode::Digit2 => "2",
206 KeyCode::Digit3 => "3",
207 KeyCode::Digit4 => "4",
208 KeyCode::Digit5 => "5",
209 KeyCode::Digit6 => "6",
210 KeyCode::Digit7 => "7",
211 KeyCode::Digit8 => "8",
212 KeyCode::Digit9 => "9",
213 KeyCode::Digit0 => "0",
214 KeyCode::Minus => "-",
215 KeyCode::Equal => "=",
216 KeyCode::Backspace => "Backspace",
217 KeyCode::Tab => "Tab",
218 KeyCode::KeyQ => "Q",
219 KeyCode::KeyW => "W",
220 KeyCode::KeyE => "E",
221 KeyCode::KeyR => "R",
222 KeyCode::KeyT => "T",
223 KeyCode::KeyY => "Y",
224 KeyCode::KeyU => "U",
225 KeyCode::KeyI => "I",
226 KeyCode::KeyO => "O",
227 KeyCode::KeyP => "P",
228 KeyCode::BracketLeft => "[",
229 KeyCode::BracketRight => "]",
230 KeyCode::Backslash => "\\",
231 KeyCode::KeyA => "A",
232 KeyCode::KeyS => "S",
233 KeyCode::KeyD => "D",
234 KeyCode::KeyF => "F",
235 KeyCode::KeyG => "G",
236 KeyCode::KeyH => "H",
237 KeyCode::KeyJ => "J",
238 KeyCode::KeyK => "K",
239 KeyCode::KeyL => "L",
240 KeyCode::Semicolon => ";",
241 KeyCode::Quote => "'",
242 KeyCode::Enter => "Enter",
243 KeyCode::KeyZ => "Z",
244 KeyCode::KeyX => "X",
245 KeyCode::KeyC => "C",
246 KeyCode::KeyV => "V",
247 KeyCode::KeyB => "B",
248 KeyCode::KeyN => "N",
249 KeyCode::KeyM => "M",
250 KeyCode::Comma => ",",
251 KeyCode::Period => ".",
252 KeyCode::Slash => "/",
253 KeyCode::Space => "Space",
254 KeyCode::ArrowUp => "↑",
255 KeyCode::ArrowDown => "↓",
256 KeyCode::ArrowLeft => "←",
257 KeyCode::ArrowRight => "→",
258 KeyCode::Home => "Home",
259 KeyCode::End => "End",
260 KeyCode::PageUp => "PgUp",
261 KeyCode::PageDown => "PgDn",
262 KeyCode::Insert => "Ins",
263 KeyCode::Delete => "Del",
264 _ => "?",
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 #[test]
273 fn test_keybind_creation() {
274 let kb = Keybind::key(KeyCode::F12, "Toggle inspector");
275 assert_eq!(kb.key, KeyCode::F12);
276 assert_eq!(kb.modifiers, Modifiers::NONE);
277 assert_eq!(kb.description, "Toggle inspector");
278 }
279
280 #[test]
281 fn test_keybind_with_modifiers() {
282 let kb = Keybind::ctrl_shift(KeyCode::KeyI, "Open inspector");
283 assert!(kb.modifiers.contains(Modifiers::CTRL));
284 assert!(kb.modifiers.contains(Modifiers::SHIFT));
285 assert!(!kb.modifiers.contains(Modifiers::ALT));
286 }
287
288 #[test]
289 fn test_keybind_matching() {
290 let kb = Keybind::ctrl(KeyCode::KeyS, "Save");
291
292 assert!(kb.matches(KeyCode::KeyS, Modifiers::CTRL));
293 assert!(!kb.matches(KeyCode::KeyS, Modifiers::NONE));
294 assert!(!kb.matches(KeyCode::KeyS, Modifiers::CTRL | Modifiers::SHIFT));
295 assert!(!kb.matches(KeyCode::KeyA, Modifiers::CTRL));
296 }
297
298 #[test]
299 fn test_registry_operations() {
300 let mut registry = KeybindRegistry::new();
301
302 registry.register("inspector", Keybind::key(KeyCode::F12, "Toggle"), 100);
303 registry.register("inspector", Keybind::key(KeyCode::F5, "Freeze"), 100);
304 registry.register("profiler", Keybind::key(KeyCode::F11, "Profile"), 50);
305
306 let matches = registry.find_matches(KeyCode::F12, Modifiers::NONE);
308 assert_eq!(matches.len(), 1);
309 assert_eq!(matches[0].0, "inspector");
310
311 let inspector_binds = registry.get_keybinds("inspector");
313 assert_eq!(inspector_binds.len(), 2);
314
315 registry.unregister("inspector");
317 assert!(registry.get_keybinds("inspector").is_empty());
318 assert_eq!(registry.get_keybinds("profiler").len(), 1);
319 }
320
321 #[test]
322 fn test_registry_priority_ordering() {
323 let mut registry = KeybindRegistry::new();
324
325 registry.register("low", Keybind::key(KeyCode::F1, "Low priority"), 10);
327 registry.register("high", Keybind::key(KeyCode::F1, "High priority"), 100);
328 registry.register("medium", Keybind::key(KeyCode::F1, "Medium priority"), 50);
329
330 let matches = registry.find_matches(KeyCode::F1, Modifiers::NONE);
331 assert_eq!(matches.len(), 3);
332 assert_eq!(matches[0].0, "high");
333 assert_eq!(matches[1].0, "medium");
334 assert_eq!(matches[2].0, "low");
335 }
336
337 #[test]
338 fn test_keybind_to_string() {
339 let simple = Keybind::key(KeyCode::F12, "Test");
340 assert_eq!(simple.to_string_short(), "F12");
341
342 let _with_ctrl = Keybind::ctrl(KeyCode::KeyS, "Save");
343 #[cfg(not(target_os = "macos"))]
344 assert_eq!(_with_ctrl.to_string_short(), "Ctrl+S");
345
346 let _with_shift = Keybind::ctrl_shift(KeyCode::KeyZ, "Redo");
347 #[cfg(not(target_os = "macos"))]
348 assert_eq!(_with_shift.to_string_short(), "Ctrl+Shift+Z");
349 }
350
351 #[test]
352 fn test_modifiers_bitflags() {
353 let mods = Modifiers::CTRL | Modifiers::SHIFT;
354 assert!(mods.contains(Modifiers::CTRL));
355 assert!(mods.contains(Modifiers::SHIFT));
356 assert!(!mods.contains(Modifiers::ALT));
357 }
358}