input.rsannotatedinput.rssource382 lines · 11.3 KB · raw
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}