1pub mod mappings; 2mod serialize; 3 4use crate::input::mappings::PceInputConfig; 5use crate::input::mappings::{ 6 GameBoyInputConfig, GbaInputConfig, GenesisInputConfig, HotkeyConfig, NesInputConfig, 7 SmsGgInputConfig, SnesInputConfig, 8}; 9use jgenesis_proc_macros::{EnumAll, EnumDisplay, EnumFromStr, deserialize_default_on_error}; 10use sdl3::keyboard::{Keycode, Scancode}; 11use sdl3::mouse::MouseButton; 12use serde::{Deserialize, Serialize}; 13use std::borrow::Cow; 14use std::fmt::{Display, Formatter}; 15use std::str::FromStr; 16 17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 18pub enum KeyboardInput { 19 Keycode(Keycode), 20 Scancode(Scancode), 21} 22 23impl KeyboardInput { 24 fn display(self) -> Cow<'static, str> { 25 match self { 26 Self::Keycode(keycode) => keycode_to_str(keycode), 27 Self::Scancode(scancode) => scancode_to_str(scancode).into(), 28 } 29 } 30 31 fn serialize_to_str(self) -> String { 32 match self { 33 Self::Keycode(keycode) => keycode_to_str(keycode).to_string(), 34 Self::Scancode(scancode) => serialize_scancode(scancode), 35 } 36 } 37 38 fn deserialize_from_str(s: &str) -> Option<Self> { 39 keycode_from_str(s) 40 .map(Self::Keycode) 41 .or_else(|| deserialize_scancode(s).map(Self::Scancode)) 42 } 43} 44 45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 46pub enum AxisDirection { 47 Positive, 48 Negative, 49} 50 51impl AxisDirection { 52 #[inline] 53 #[must_use] 54 pub fn from_value(value: i16) -> Self { 55 if value >= 0 { Self::Positive } else { Self::Negative } 56 } 57 58 #[inline] 59 #[must_use] 60 pub fn inverse(self) -> Self { 61 match self { 62 Self::Positive => Self::Negative, 63 Self::Negative => Self::Positive, 64 } 65 } 66} 67 68impl Display for AxisDirection { 69 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 70 match self { 71 Self::Positive => write!(f, "+"), 72 Self::Negative => write!(f, "-"), 73 } 74 } 75} 76 77impl FromStr for AxisDirection { 78 type Err = String; 79 80 fn from_str(s: &str) -> Result<Self, Self::Err> { 81 match s { 82 "+" => Ok(Self::Positive), 83 "-" => Ok(Self::Negative), 84 _ => Err(format!("Invalid AxisDirection string: {s}")), 85 } 86 } 87} 88 89#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumDisplay, EnumFromStr, EnumAll)] 90pub enum HatDirection { 91 Up, 92 Left, 93 Right, 94 Down, 95} 96 97impl HatDirection {} 98 99#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 100pub enum GamepadAction { 101 Button(u8), 102 Axis(u8, AxisDirection), 103 Hat(u8, HatDirection), 104} 105 106impl Display for GamepadAction { 107 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 108 match self { 109 Self::Button(idx) => write!(f, "Button {idx}"), 110 Self::Axis(idx, direction) => write!(f, "Axis {idx} {direction}"), 111 Self::Hat(idx, direction) => write!(f, "Hat {idx} {direction}"), 112 } 113 } 114} 115 116impl FromStr for GamepadAction { 117 type Err = String; 118 119 fn from_str(s: &str) -> Result<Self, Self::Err> { 120 let err_fn = || format!("Invalid gamepad action string: {s}"); 121 122 let mut split = s.split_ascii_whitespace(); 123 let Some(input_type) = split.next() else { 124 return Err(err_fn()); 125 }; 126 127 let Some(idx) = split.next().and_then(|idx| idx.parse().ok()) else { 128 return Err(err_fn()); 129 }; 130 131 match input_type { 132 "Button" | "button" => Ok(Self::Button(idx)), 133 "Axis" | "axis" => { 134 let Some(direction) = split.next().and_then(|direction| direction.parse().ok()) 135 else { 136 return Err(err_fn()); 137 }; 138 139 Ok(Self::Axis(idx, direction)) 140 } 141 "Hat" | "hat" => { 142 let Some(direction) = split.next().and_then(|direction| direction.parse().ok()) 143 else { 144 return Err(err_fn()); 145 }; 146 147 Ok(Self::Hat(idx, direction)) 148 } 149 _ => Err(err_fn()), 150 } 151 } 152} 153 154#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 155pub enum GenericInput { 156 Keyboard(KeyboardInput), 157 Gamepad { gamepad_idx: u32, action: GamepadAction }, 158 Mouse(MouseButton), 159} 160 161impl Display for GenericInput { 162 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 163 match self { 164 &Self::Keyboard(key) => write!(f, "Key: {}", key.display()), 165 Self::Gamepad { gamepad_idx, action } => write!(f, "Gamepad {gamepad_idx}: {action}"), 166 Self::Mouse(mouse_button) => write!(f, "Mouse: {mouse_button:?}"), 167 } 168 } 169} 170 171fn keycode_to_str(keycode: Keycode) -> Cow<'static, str> { 172 match keycode { 173 Keycode::LShift | Keycode::RShift => "Shift".into(), 174 Keycode::LCtrl | Keycode::RCtrl => "Ctrl".into(), 175 Keycode::LAlt | Keycode::RAlt => "Alt".into(), 176 _ => keycode.name().into(), 177 } 178} 179 180fn keycode_from_str(s: &str) -> Option<Keycode> { 181 match s { 182 // SDL2 -> SDL3 keycode name changes 183 "Backquote" => Some(Keycode::Grave), 184 "Quote" => Some(Keycode::Apostrophe), 185 "Quotedbl" => Some(Keycode::DblApostrophe), 186 // Modifier keys 187 "Shift" => Some(Keycode::LShift), 188 "Ctrl" => Some(Keycode::LCtrl), 189 "Alt" => Some(Keycode::LAlt), 190 _ => { 191 if s == Keycode::RShift.name().as_str() { 192 Some(Keycode::LShift) 193 } else if s == Keycode::RCtrl.name().as_str() { 194 Some(Keycode::LCtrl) 195 } else if s == Keycode::RAlt.name().as_str() { 196 Some(Keycode::LAlt) 197 } else { 198 Keycode::from_name(s) 199 } 200 } 201 } 202} 203 204const SCANCODE_PREFIX: &str = "Scancode "; 205 206fn scancode_to_str(scancode: Scancode) -> &'static str { 207 match scancode { 208 Scancode::LCtrl | Scancode::RCtrl => "Ctrl", 209 Scancode::LAlt | Scancode::RAlt => "Alt", 210 Scancode::LShift | Scancode::RShift => "Shift", 211 _ => scancode.name(), 212 } 213} 214 215fn serialize_scancode(scancode: Scancode) -> String { 216 format!("{SCANCODE_PREFIX}{}", scancode_to_str(scancode)) 217} 218 219fn deserialize_scancode(mut s: &str) -> Option<Scancode> { 220 if !s.starts_with(SCANCODE_PREFIX) { 221 return None; 222 } 223 224 s = &s[SCANCODE_PREFIX.len()..]; 225 226 match s { 227 "Ctrl" => Some(Scancode::LCtrl), 228 "Alt" => Some(Scancode::LAlt), 229 "Shift" => Some(Scancode::LShift), 230 _ => { 231 if s == Scancode::RCtrl.name() { 232 Some(Scancode::LCtrl) 233 } else if s == Scancode::RAlt.name() { 234 Some(Scancode::LAlt) 235 } else if s == Scancode::RShift.name() { 236 Some(Scancode::RShift) 237 } else { 238 Scancode::from_name(s) 239 } 240 } 241 } 242} 243 244#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumDisplay, EnumAll)] 245pub enum Hotkey { 246 Exit, 247 ToggleFullscreen, 248 SoftReset, 249 HardReset, 250 PowerOff, 251 Pause, 252 StepFrame, 253 FastForward, 254 Rewind, 255 CancelMouseInput, 256 ToggleOverclocking, 257 OpenDebugger, 258 SaveState, 259 LoadState, 260 NextSaveStateSlot, 261 PrevSaveStateSlot, 262 SaveStateSlot0, 263 LoadStateSlot0, 264 SaveStateSlot1, 265 LoadStateSlot1, 266 SaveStateSlot2, 267 LoadStateSlot2, 268 SaveStateSlot3, 269 LoadStateSlot3, 270 SaveStateSlot4, 271 LoadStateSlot4, 272 SaveStateSlot5, 273 LoadStateSlot5, 274 SaveStateSlot6, 275 LoadStateSlot6, 276 SaveStateSlot7, 277 LoadStateSlot7, 278 SaveStateSlot8, 279 LoadStateSlot8, 280 SaveStateSlot9, 281 LoadStateSlot9, 282} 283 284#[derive(Debug, Clone, Copy, PartialEq, Eq)] 285pub enum CompactHotkey { 286 PowerOff, 287 Exit, 288 ToggleFullscreen, 289 SaveState, 290 LoadState, 291 SaveStateSlot(usize), 292 LoadStateSlot(usize), 293 NextSaveStateSlot, 294 PrevSaveStateSlot, 295 SoftReset, 296 HardReset, 297 Pause, 298 StepFrame, 299 FastForward, 300 Rewind, 301 CancelMouseInput, 302 ToggleOverclocking, 303 OpenDebugger, 304} 305 306impl Hotkey { 307 #[inline] 308 #[must_use] 309 pub fn to_compact(self) -> CompactHotkey { 310 match self { 311 Self::PowerOff => CompactHotkey::PowerOff, 312 Self::Exit => CompactHotkey::Exit, 313 Self::ToggleFullscreen => CompactHotkey::ToggleFullscreen, 314 Self::SaveState => CompactHotkey::SaveState, 315 Self::LoadState => CompactHotkey::LoadState, 316 Self::NextSaveStateSlot => CompactHotkey::NextSaveStateSlot, 317 Self::PrevSaveStateSlot => CompactHotkey::PrevSaveStateSlot, 318 Self::SoftReset => CompactHotkey::SoftReset, 319 Self::HardReset => CompactHotkey::HardReset, 320 Self::Pause => CompactHotkey::Pause, 321 Self::StepFrame => CompactHotkey::StepFrame, 322 Self::FastForward => CompactHotkey::FastForward, 323 Self::Rewind => CompactHotkey::Rewind, 324 Self::CancelMouseInput => CompactHotkey::CancelMouseInput, 325 Self::ToggleOverclocking => CompactHotkey::ToggleOverclocking, 326 Self::OpenDebugger => CompactHotkey::OpenDebugger, 327 Self::SaveStateSlot0 => CompactHotkey::SaveStateSlot(0), 328 Self::SaveStateSlot1 => CompactHotkey::SaveStateSlot(1), 329 Self::SaveStateSlot2 => CompactHotkey::SaveStateSlot(2), 330 Self::SaveStateSlot3 => CompactHotkey::SaveStateSlot(3), 331 Self::SaveStateSlot4 => CompactHotkey::SaveStateSlot(4), 332 Self::SaveStateSlot5 => CompactHotkey::SaveStateSlot(5), 333 Self::SaveStateSlot6 => CompactHotkey::SaveStateSlot(6), 334 Self::SaveStateSlot7 => CompactHotkey::SaveStateSlot(7), 335 Self::SaveStateSlot8 => CompactHotkey::SaveStateSlot(8), 336 Self::SaveStateSlot9 => CompactHotkey::SaveStateSlot(9), 337 Self::LoadStateSlot0 => CompactHotkey::LoadStateSlot(0), 338 Self::LoadStateSlot1 => CompactHotkey::LoadStateSlot(1), 339 Self::LoadStateSlot2 => CompactHotkey::LoadStateSlot(2), 340 Self::LoadStateSlot3 => CompactHotkey::LoadStateSlot(3), 341 Self::LoadStateSlot4 => CompactHotkey::LoadStateSlot(4), 342 Self::LoadStateSlot5 => CompactHotkey::LoadStateSlot(5), 343 Self::LoadStateSlot6 => CompactHotkey::LoadStateSlot(6), 344 Self::LoadStateSlot7 => CompactHotkey::LoadStateSlot(7), 345 Self::LoadStateSlot8 => CompactHotkey::LoadStateSlot(8), 346 Self::LoadStateSlot9 => CompactHotkey::LoadStateSlot(9), 347 } 348 } 349} 350 351#[deserialize_default_on_error] 352#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 353#[serde(default)] 354pub struct InputAppConfig { 355 pub smsgg: SmsGgInputConfig, 356 pub genesis: GenesisInputConfig, 357 pub nes: NesInputConfig, 358 pub snes: SnesInputConfig, 359 pub game_boy: GameBoyInputConfig, 360 pub game_boy_advance: GbaInputConfig, 361 pub pc_engine: PceInputConfig, 362 pub hotkeys: HotkeyConfig, 363 pub axis_deadzone: i16, 364} 365 366const DEFAULT_AXIS_DEADZONE: i16 = 8000; 367 368impl Default for InputAppConfig { 369 fn default() -> Self { 370 Self { 371 smsgg: SmsGgInputConfig::default(), 372 genesis: GenesisInputConfig::default(), 373 nes: NesInputConfig::default(), 374 snes: SnesInputConfig::default(), 375 game_boy: GameBoyInputConfig::default(), 376 game_boy_advance: GbaInputConfig::default(), 377 pc_engine: PceInputConfig::default(), 378 hotkeys: HotkeyConfig::default(), 379 axis_deadzone: DEFAULT_AXIS_DEADZONE, 380 } 381 } 382}