1use bincode::{Decode, Encode}; 2use jgenesis_common::frontend::{DisplayInfo, FiniteF64, MappableInputs, Modal}; 3use jgenesis_common::input::Player; 4use snes_config::{SnesButton, SnesJoypadState, SuperScopeButton}; 5 6pub(crate) trait SnesJoypadStateExt: Sized + Copy { 7 #[must_use] 8 fn to_register_word(self) -> u16; 9} 10 11impl SnesJoypadStateExt for SnesJoypadState { 12 fn to_register_word(self) -> u16 { 13 u16::from(self.b) 14 | (u16::from(self.y) << 1) 15 | (u16::from(self.select) << 2) 16 | (u16::from(self.start) << 3) 17 | (u16::from(self.up) << 4) 18 | (u16::from(self.down) << 5) 19 | (u16::from(self.left) << 6) 20 | (u16::from(self.right) << 7) 21 | (u16::from(self.a) << 8) 22 | (u16::from(self.x) << 9) 23 | (u16::from(self.l) << 10) 24 | (u16::from(self.r) << 11) 25 } 26} 27 28#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 29pub struct SnesMouseState { 30 // Absolute position values are meaningless, only differences between positions are meaningful 31 pub x_position: FiniteF64, 32 pub y_position: FiniteF64, 33 pub left: bool, 34 pub right: bool, 35} 36 37impl SnesMouseState { 38 #[inline] 39 pub fn set_button(&mut self, button: SnesButton, pressed: bool) { 40 match button { 41 SnesButton::MouseLeft => self.left = pressed, 42 SnesButton::MouseRight => self.right = pressed, 43 _ => {} 44 } 45 } 46} 47 48#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 49pub struct SuperScopeState { 50 pub fire: bool, 51 pub cursor: bool, 52 pub pause: bool, 53 pub turbo: bool, 54 // X/Y position in SNES pixels starting from the top-left corner, or None if position is offscreen 55 // X should be in the range 0..=255 and Y should be in the range 0..=223 (or 238 if in 239-line mode); other values 56 // will be treated as offscreen 57 pub position: Option<(u16, u16)>, 58} 59 60impl Default for SuperScopeState { 61 fn default() -> Self { 62 Self { fire: false, cursor: false, pause: false, turbo: true, position: None } 63 } 64} 65 66impl SuperScopeState { 67 #[inline] 68 pub fn set_button(&mut self, button: SuperScopeButton, pressed: bool) { 69 match button { 70 SuperScopeButton::Fire => self.fire = pressed, 71 SuperScopeButton::Cursor => self.cursor = pressed, 72 SuperScopeButton::Pause => self.pause = pressed, 73 SuperScopeButton::TurboToggle => { 74 if pressed { 75 self.turbo = !self.turbo; 76 } 77 } 78 } 79 } 80 81 #[inline] 82 #[must_use] 83 pub fn with_button(mut self, button: SuperScopeButton, pressed: bool) -> Self { 84 self.set_button(button, pressed); 85 self 86 } 87} 88 89#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 90pub enum SnesController { 91 Gamepad(SnesJoypadState), 92 Mouse(SnesMouseState), 93 SuperScope(SuperScopeState), 94 None, 95} 96 97impl SnesController { 98 pub fn set_field(&mut self, button: SnesButton, pressed: bool) { 99 match self { 100 Self::Gamepad(state) => state.set_button(button, pressed), 101 Self::Mouse(state) => state.set_button(button, pressed), 102 Self::SuperScope(state) => { 103 if let Some(super_scope_button) = button.to_super_scope() { 104 state.set_button(super_scope_button, pressed); 105 } 106 } 107 Self::None => {} 108 } 109 } 110 111 #[must_use] 112 pub fn with_allow_opposing_directions(self, allow_opposing_directions: bool) -> Self { 113 match self { 114 Self::Gamepad(joypad) => { 115 Self::Gamepad(joypad.with_allow_opposing_directions(allow_opposing_directions)) 116 } 117 Self::Mouse(_) | Self::SuperScope(_) | Self::None => self, 118 } 119 } 120} 121 122#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 123pub struct SnesInputs { 124 pub p1: SnesController, 125 pub p2: SnesController, 126} 127 128impl Default for SnesInputs { 129 fn default() -> Self { 130 Self { p1: SnesController::Gamepad(SnesJoypadState::default()), p2: SnesController::None } 131 } 132} 133 134impl MappableInputs<SnesButton> for SnesInputs { 135 #[inline] 136 fn set_field(&mut self, button: SnesButton, player: Player, pressed: bool) { 137 match player { 138 Player::One => self.p1.set_field(button, pressed), 139 Player::Two => self.p2.set_field(button, pressed), 140 _ => {} 141 } 142 } 143 144 #[inline] 145 fn handle_mouse_motion( 146 &mut self, 147 (x, y): (f32, f32), 148 (dx, dy): (f32, f32), 149 display_info: DisplayInfo, 150 ) { 151 for controller in [&mut self.p1, &mut self.p2] { 152 match controller { 153 SnesController::Mouse(mouse_state) => { 154 if let Ok(dx) = FiniteF64::try_from(f64::from(dx)) { 155 mouse_state.x_position += dx; 156 } 157 if let Ok(dy) = FiniteF64::try_from(f64::from(dy)) { 158 mouse_state.y_position += dy; 159 } 160 } 161 SnesController::SuperScope(super_scope_state) => { 162 super_scope_state.position = 163 jgenesis_common::input::viewport_position_to_frame_position( 164 x, 165 y, 166 display_info, 167 ); 168 log::debug!("Set Super Scope position to {:?}", super_scope_state.position); 169 } 170 _ => {} 171 } 172 } 173 } 174 175 #[inline] 176 fn handle_mouse_leave(&mut self) { 177 for controller in [&mut self.p1, &mut self.p2] { 178 if let SnesController::SuperScope(super_scope_state) = controller { 179 super_scope_state.position = None; 180 } 181 } 182 } 183 184 #[inline] 185 fn needs_relative_mouse_mode(&self) -> bool { 186 [self.p1, self.p2] 187 .into_iter() 188 .any(|controller| matches!(controller, SnesController::Mouse(_))) 189 } 190 191 fn modal_for_input(&self, button: SnesButton, player: Player, pressed: bool) -> Option<Modal> { 192 if button != SnesButton::SuperScopeTurboToggle || !pressed { 193 return None; 194 } 195 196 let controller = match player { 197 Player::One => &self.p1, 198 Player::Two => &self.p2, 199 _ => return None, 200 }; 201 let SnesController::SuperScope(super_scope_state) = controller else { return None }; 202 203 let text = 204 format!("Super Scope Turbo: {}", if super_scope_state.turbo { "On" } else { "Off" }); 205 Some(Modal { id: Some("super_scope_turbo".into()), text }) 206 } 207}