use std::{fmt, str::FromStr}; use sdl2::keyboard::{Keycode, Mod}; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Keybind { keycode: Keycode, keymod: Mod, } impl Keybind { pub fn new(keycode: Keycode, keymod: Mod) -> Self { Self { keycode, keymod } } } // ctrl keys: C- len 3 // shift keys: S- len 3 // normal keys: len 1 #[derive(Debug, Copy, Clone, PartialEq)] pub enum KeybindError { Parse, InvalidMod, InvalidKey, } impl fmt::Display for KeybindError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Parse => write!(f, "error parsing keybind"), Self::InvalidKey => write!(f, "unsupported keybind, try `A-Z0-9`"), Self::InvalidMod => { write!(f, "unsupported keybind modifier, try `C- or S-`") } } } } impl std::error::Error for KeybindError {} impl FromStr for Keybind { type Err = KeybindError; fn from_str(s: &str) -> Result { parse_from_str(s) } } fn parse_from_str(s: &str) -> Result { let s = s.to_lowercase(); match s.len() { // either ctrl or shift key 3 => { let keymod = parse_modifier(&s[..2])?; let keybind = parse_keybind(&s[2..])?; Ok(Keybind::new(keybind, keymod)) } 1 => { let keybind = parse_keybind(&s)?; Ok(Keybind::new(keybind, Mod::NOMOD)) } _ => Err(KeybindError::Parse), } } fn parse_modifier(s: &str) -> Result { match s { "c-" => Ok(Mod::LCTRLMOD), "s-" => Ok(Mod::LSHIFTMOD), _ => Err(KeybindError::InvalidMod), } } fn parse_keybind(s: &str) -> Result { match s.to_uppercase().as_str() { "0" => Ok(Keycode::Num0), "1" => Ok(Keycode::Num1), "2" => Ok(Keycode::Num2), "3" => Ok(Keycode::Num3), "4" => Ok(Keycode::Num4), "5" => Ok(Keycode::Num5), "6" => Ok(Keycode::Num6), "7" => Ok(Keycode::Num7), "8" => Ok(Keycode::Num8), "9" => Ok(Keycode::Num9), "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" => { Ok(Keycode::from_name(s).unwrap()) } _ => Err(KeybindError::InvalidKey), } } #[cfg(test)] mod tests { use super::*; #[test] fn without_mod() { assert_eq!( Keybind::from_str("x").unwrap(), Keybind::new(Keycode::X, Mod::NOMOD) ); assert_eq!( Keybind::from_str("X").unwrap(), Keybind::new(Keycode::X, Mod::NOMOD) ); } #[test] fn with_ctrl_mod() { assert_eq!( Keybind::from_str("c-x").unwrap(), Keybind::new(Keycode::X, Mod::LCTRLMOD) ); assert_eq!( Keybind::from_str("C-x").unwrap(), Keybind::new(Keycode::X, Mod::LCTRLMOD) ); assert_eq!( Keybind::from_str("c-X").unwrap(), Keybind::new(Keycode::X, Mod::LCTRLMOD) ); assert_eq!( Keybind::from_str("C-X").unwrap(), Keybind::new(Keycode::X, Mod::LCTRLMOD) ); } #[test] fn with_shift_mod() { assert_eq!( Keybind::from_str("s-x").unwrap(), Keybind::new(Keycode::X, Mod::LSHIFTMOD) ); assert_eq!( Keybind::from_str("S-x").unwrap(), Keybind::new(Keycode::X, Mod::LSHIFTMOD) ); assert_eq!( Keybind::from_str("s-X").unwrap(), Keybind::new(Keycode::X, Mod::LSHIFTMOD) ); assert_eq!( Keybind::from_str("S-X").unwrap(), Keybind::new(Keycode::X, Mod::LSHIFTMOD) ); } }