1use winit::keyboard::KeyCode;
7
8pub const DEFAULT_KEYMAP: &str = include_str!("default-keymap.txt");
9pub const BUTTONS: [&str; 12] = ["a", "b", "x", "y", "l", "r", "select", "start", "up", "down", "left", "right"];
10
11pub type Keymap = Vec<(KeyCode, usize, u32)>;
13
14pub fn parse(text: &str) -> Result<Keymap, String> {
15 let mut map = Vec::new();
16 let mut errors = Vec::new();
17 for (i, raw) in text.lines().enumerate() {
18 let line = raw.split('#').next().unwrap_or("").trim();
19 if line.is_empty() {
20 continue;
21 }
22 let mut parts = line.split_whitespace();
23 let (pad, button) = (parts.next(), parts.next());
24 let keys: Vec<&str> = parts.collect();
25 let pad = pad.and_then(|p| p.parse::<usize>().ok()).filter(|p| (1..=4).contains(p));
26 let bit = button.and_then(|b| BUTTONS.iter().position(|n| n.eq_ignore_ascii_case(b)));
27 let (Some(pad), Some(bit), false) = (pad, bit, keys.is_empty()) else {
28 errors.push(format!("line {}: expected \"<pad 1-4> <button> <key>...\", got {line:?}", i + 1));
29 continue;
30 };
31 for k in keys {
32 match key_from_code(normalize(k)) {
33 Some(KeyCode::Escape) => errors.push(format!("line {}: Escape is reserved (quit)", i + 1)),
34 Some(code) => map.push((code, pad - 1, 1u32 << bit)),
35 None => errors.push(format!("line {}: unknown key code {k:?}", i + 1)),
36 }
37 }
38 }
39 if errors.is_empty() { Ok(map) } else { Err(errors.join("\n")) }
40}
41
42pub fn pads(map: &Keymap, held: &std::collections::HashSet<KeyCode>, gamepads: usize) -> [u32; 4] {
44 let mut pads = [0u32; 4];
45 for (key, pad, bit) in map {
46 if held.contains(key) && (*pad == 0 || gamepads <= *pad) {
47 pads[*pad] |= bit;
48 }
49 }
50 pads
51}
52
53macro_rules! codes {
54 ($($name:ident),* $(,)?) => {
55 pub fn key_from_code(s: &str) -> Option<KeyCode> {
57 match s { $(stringify!($name) => Some(KeyCode::$name),)* _ => None }
58 }
59 };
60}
61
62codes!(
63 KeyA, KeyB, KeyC, KeyD, KeyE, KeyF, KeyG, KeyH, KeyI, KeyJ, KeyK, KeyL, KeyM,
64 KeyN, KeyO, KeyP, KeyQ, KeyR, KeyS, KeyT, KeyU, KeyV, KeyW, KeyX, KeyY, KeyZ,
65 Digit0, Digit1, Digit2, Digit3, Digit4, Digit5, Digit6, Digit7, Digit8, Digit9,
66 ArrowUp, ArrowDown, ArrowLeft, ArrowRight,
67 Numpad0, Numpad1, Numpad2, Numpad3, Numpad4, Numpad5, Numpad6, Numpad7, Numpad8, Numpad9,
68 NumpadAdd, NumpadSubtract, NumpadMultiply, NumpadDivide, NumpadDecimal, NumpadEnter, NumpadEqual, NumpadComma,
69 F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
70 Space, Enter, Tab, Backspace, Escape, CapsLock,
71 ShiftLeft, ShiftRight, ControlLeft, ControlRight, AltLeft, AltRight, SuperLeft, SuperRight,
72 Comma, Period, Slash, Semicolon, Quote, BracketLeft, BracketRight, Backslash, Minus, Equal, Backquote, IntlBackslash,
73 Insert, Delete, Home, End, PageUp, PageDown,
74);
75
76pub fn normalize(s: &str) -> &str {
78 match s { "MetaLeft" | "OSLeft" => "SuperLeft", "MetaRight" | "OSRight" => "SuperRight", other => other }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use std::collections::HashSet;
85
86 #[test]
87 fn default_keymap_parses_and_two_players_share_a_keyboard() {
88 let map = parse(DEFAULT_KEYMAP).unwrap();
89 let held: HashSet<_> = [KeyCode::KeyX, KeyCode::KeyL, KeyCode::ControlRight].into();
90 let p = pads(&map, &held, 0);
91 assert_eq!(p[0], 1 << 0); assert_eq!(p[1], (1 << 11) | (1 << 7)); assert_eq!(pads(&map, &held, 2)[1], 0); assert!(parse("1 jump KeyX").is_err());
95 assert!(parse("5 a KeyX").is_err());
96 assert!(parse("1 a Escape").is_err());
97 }
98}