use std::ops::Neg; use crate::ui; const R_DPAD: usize = 0; const L_DPAD: usize = 1; const D_DPAD: usize = 2; const U_DPAD: usize = 3; const START_BUTTON: usize = 4; const Z_TRIG: usize = 5; const B_BUTTON: usize = 6; const A_BUTTON: usize = 7; const R_CBUTTON: usize = 8; const L_CBUTTON: usize = 9; const D_CBUTTON: usize = 10; const U_CBUTTON: usize = 11; const R_TRIG: usize = 12; const L_TRIG: usize = 13; const AXIS_LEFT: usize = 14; const AXIS_RIGHT: usize = 15; const AXIS_UP: usize = 16; const AXIS_DOWN: usize = 17; const HOTKEY: usize = 18; pub const PROFILE_SIZE: usize = 19; const X_AXIS_SHIFT: usize = 16; const Y_AXIS_SHIFT: usize = 24; const MAX_AXIS_VALUE: f64 = 85.0; pub const DEADZONE_DEFAULT: i32 = 5; pub const UNKNOWN_CONTROLLER_NAME: &str = "Unknown controller"; #[derive(Default)] pub struct Controllers { pub rumble: bool, pub game_controller: *mut sdl3_sys::gamepad::SDL_Gamepad, pub joystick: *mut sdl3_sys::joystick::SDL_Joystick, pub guid: sdl3_sys::guid::SDL_GUID, pub last_key_state: u32, } pub struct InputData { pub data: u32, pub pak_change_pressed: bool, } fn bound_axis(x: &mut f64, y: &mut f64) { let radius = f64::sqrt(70.0 * 70.0 + 70.0 * 70.0); // this is roughly the maximum diagonal distance of the controller // Calculate the distance from the origin (0, 0) let distance = f64::sqrt((*x) * (*x) + (*y) * (*y)); // If the distance is greater than the radius, scale the coordinates if distance > radius { let scale_factor = radius / distance; *x *= scale_factor; *y *= scale_factor; } } fn apply_deadzone(x: &mut f64, y: &mut f64, deadzone: i32) { let axis_deadzone = MAX_AXIS_VALUE * (deadzone as f64 / 100.0); // Calculate the distance from the origin (0, 0) let distance = f64::sqrt((*x) * (*x) + (*y) * (*y)); if distance <= axis_deadzone { *x = 0.0; *y = 0.0; return; } let new_distance = (distance - axis_deadzone) * MAX_AXIS_VALUE / (MAX_AXIS_VALUE - axis_deadzone); *x = *x / distance * new_distance; *y = *y / distance * new_distance; } fn normalize_axis_position(axis_position: i16) -> f64 { axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64 } fn set_axis_from_joystick( profile: &ui::config::InputProfile, joystick: *mut sdl3_sys::joystick::SDL_Joystick, ) -> (f64, f64) { let mut x = 0.0; let mut y = 0.0; if profile.joystick_axis[AXIS_LEFT].enabled { let axis_position = unsafe { sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_LEFT].id) }; if axis_position as isize * profile.joystick_axis[AXIS_LEFT].axis as isize > 0 { x = normalize_axis_position(axis_position); } } if profile.joystick_axis[AXIS_RIGHT].enabled { let axis_position = unsafe { sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_RIGHT].id) }; if axis_position as isize * profile.joystick_axis[AXIS_RIGHT].axis as isize > 0 { x = normalize_axis_position(axis_position); } } if profile.joystick_axis[AXIS_DOWN].enabled { let axis_position = unsafe { sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_DOWN].id) }; if axis_position as isize * profile.joystick_axis[AXIS_DOWN].axis as isize > 0 { y = normalize_axis_position(axis_position).neg(); } } if profile.joystick_axis[AXIS_UP].enabled { let axis_position = unsafe { sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_UP].id) }; if axis_position as isize * profile.joystick_axis[AXIS_UP].axis as isize > 0 { y = normalize_axis_position(axis_position).neg(); } } (x, y) } fn set_axis_from_controller( profile: &ui::config::InputProfile, controller: *mut sdl3_sys::gamepad::SDL_Gamepad, ) -> (f64, f64) { let mut x = 0.0; let mut y = 0.0; if profile.controller_axis[AXIS_LEFT].enabled { let axis_position = unsafe { sdl3_sys::gamepad::SDL_GetGamepadAxis( controller, sdl3_sys::gamepad::SDL_GamepadAxis(profile.controller_axis[AXIS_LEFT].id), ) }; if axis_position as isize * profile.controller_axis[AXIS_LEFT].axis as isize > 0 { x = normalize_axis_position(axis_position); } } if profile.controller_axis[AXIS_RIGHT].enabled { let axis_position = unsafe { sdl3_sys::gamepad::SDL_GetGamepadAxis( controller, sdl3_sys::gamepad::SDL_GamepadAxis(profile.controller_axis[AXIS_RIGHT].id), ) }; if axis_position as isize * profile.controller_axis[AXIS_RIGHT].axis as isize > 0 { x = normalize_axis_position(axis_position); } } if profile.controller_axis[AXIS_DOWN].enabled { let axis_position = unsafe { sdl3_sys::gamepad::SDL_GetGamepadAxis( controller, sdl3_sys::gamepad::SDL_GamepadAxis(profile.controller_axis[AXIS_DOWN].id), ) }; if axis_position as isize * profile.controller_axis[AXIS_DOWN].axis as isize > 0 { y = normalize_axis_position(axis_position).neg(); } } if profile.controller_axis[AXIS_UP].enabled { let axis_position = unsafe { sdl3_sys::gamepad::SDL_GetGamepadAxis( controller, sdl3_sys::gamepad::SDL_GamepadAxis(profile.controller_axis[AXIS_UP].id), ) }; if axis_position as isize * profile.controller_axis[AXIS_UP].axis as isize > 0 { y = normalize_axis_position(axis_position).neg(); } } (x, y) } fn set_axis_from_keys( profile: &ui::config::InputProfile, keyboard_state: *const bool, ) -> (f64, f64) { let mut x = 0.0; let mut y = 0.0; if profile.keys[AXIS_LEFT].enabled && unsafe { *keyboard_state.offset(profile.keys[AXIS_LEFT].id as isize) } { x = -MAX_AXIS_VALUE } if profile.keys[AXIS_RIGHT].enabled && unsafe { *keyboard_state.offset(profile.keys[AXIS_RIGHT].id as isize) } { x = MAX_AXIS_VALUE } if profile.keys[AXIS_DOWN].enabled && unsafe { *keyboard_state.offset(profile.keys[AXIS_DOWN].id as isize) } { y = -MAX_AXIS_VALUE } if profile.keys[AXIS_UP].enabled && unsafe { *keyboard_state.offset(profile.keys[AXIS_UP].id as isize) } { y = MAX_AXIS_VALUE } (x, y) } fn set_buttons_from_joystick( profile: &ui::config::InputProfile, i: usize, joystick: *mut sdl3_sys::joystick::SDL_Joystick, keys: &mut u32, ) { let profile_joystick_button = profile.joystick_buttons[i]; if profile_joystick_button.enabled { *keys |= (unsafe { sdl3_sys::joystick::SDL_GetJoystickButton(joystick, profile_joystick_button.id) } as u32) << i; } let profile_joystick_hat = profile.joystick_hat[i]; if profile_joystick_hat.enabled && (unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, profile_joystick_hat.id) } & profile_joystick_hat.direction) != 0 { *keys |= 1 << i; } let profile_joystick_axis = profile.joystick_axis[i]; if profile_joystick_axis.enabled { let axis_position = unsafe { sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile_joystick_axis.id) }; if (axis_position as isize * profile_joystick_axis.axis as isize > 0 || profile_joystick_axis.initial_state != 0) && axis_position.abs_diff(profile_joystick_axis.initial_state) > (u16::MAX / 4) { *keys |= 1 << i; } } } fn set_buttons_from_controller( profile: &ui::config::InputProfile, i: usize, controller: *mut sdl3_sys::gamepad::SDL_Gamepad, keys: &mut u32, ) { let profile_controller_button = profile.controller_buttons[i]; if profile_controller_button.enabled { *keys |= (unsafe { sdl3_sys::gamepad::SDL_GetGamepadButton( controller, sdl3_sys::gamepad::SDL_GamepadButton(profile_controller_button.id), ) } as u32) << i; } let profile_controller_axis = profile.controller_axis[i]; if profile_controller_axis.enabled { let axis_position = unsafe { sdl3_sys::gamepad::SDL_GetGamepadAxis( controller, sdl3_sys::gamepad::SDL_GamepadAxis(profile_controller_axis.id), ) }; if axis_position as isize * profile_controller_axis.axis as isize > 0 && axis_position.saturating_abs() > i16::MAX / 2 { *keys |= 1 << i; } } } pub fn set_rumble(ui: &ui::Ui, channel: usize, rumble: u8) { if !ui.input.controllers[channel].rumble { return; } let controller = ui.input.controllers[channel].game_controller; let joystick = ui.input.controllers[channel].joystick; if !controller.is_null() { unsafe { sdl3_sys::gamepad::SDL_RumbleGamepad( controller, (rumble & 1) as u16 * u16::MAX, (rumble & 1) as u16 * u16::MAX, (rumble & 1) as u32 * sdl3_sys::haptic::SDL_HAPTIC_INFINITY, ) }; } else if !joystick.is_null() { unsafe { sdl3_sys::joystick::SDL_RumbleJoystick( joystick, (rumble & 1) as u16 * u16::MAX, (rumble & 1) as u16 * u16::MAX, (rumble & 1) as u32 * sdl3_sys::haptic::SDL_HAPTIC_INFINITY, ) }; } } fn hotkey_pressed( profile: &ui::config::InputProfile, joystick: *mut sdl3_sys::joystick::SDL_Joystick, controller: *mut sdl3_sys::gamepad::SDL_Gamepad, ) -> bool { let controller_button = profile.controller_buttons[HOTKEY]; let controller_axis = profile.controller_axis[HOTKEY]; let joystick_button = profile.joystick_buttons[HOTKEY]; let joystick_hat = profile.joystick_hat[HOTKEY]; let joystick_axis = profile.joystick_axis[HOTKEY]; let mut pressed = false; if controller_button.enabled && !controller.is_null() { pressed = unsafe { sdl3_sys::gamepad::SDL_GetGamepadButton( controller, sdl3_sys::gamepad::SDL_GamepadButton(controller_button.id), ) }; } else if controller_axis.enabled && !controller.is_null() { let axis_position = unsafe { sdl3_sys::gamepad::SDL_GetGamepadAxis( controller, sdl3_sys::gamepad::SDL_GamepadAxis(controller_axis.id), ) }; pressed = axis_position as isize * controller_axis.axis as isize > 0 && axis_position.saturating_abs() > i16::MAX / 2 } else if joystick_button.enabled && !joystick.is_null() { pressed = unsafe { sdl3_sys::joystick::SDL_GetJoystickButton(joystick, joystick_button.id) }; } else if joystick_hat.enabled && !joystick.is_null() { pressed = (unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, joystick_hat.id) } & joystick_hat.direction) != 0; } else if joystick_axis.enabled && !joystick.is_null() { let axis_position = unsafe { sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, joystick_axis.id) }; pressed = (axis_position as isize * joystick_axis.axis as isize > 0 || joystick_axis.initial_state != 0) && axis_position.abs_diff(joystick_axis.initial_state) > (u16::MAX / 4) } pressed } pub fn get_controller_names() -> Vec { #[cfg(target_os = "android")] return ui::android::get_controller_names(); #[cfg(not(target_os = "android"))] { let mut controllers: Vec = vec![]; for joystick in get_joysticks().iter() { let name = unsafe { sdl3_sys::joystick::SDL_GetJoystickNameForID(*joystick) }; controllers.push(if name.is_null() { UNKNOWN_CONTROLLER_NAME.to_string() } else { unsafe { std::ffi::CStr::from_ptr(name).to_str().unwrap() }.to_string() }); } controllers.insert(0, "None".into()); controllers } } #[cfg(feature = "gui")] pub fn get_controller_paths() -> Vec { #[cfg(target_os = "android")] return ui::android::get_controller_paths(); #[cfg(not(target_os = "android"))] { let mut controller_paths: Vec = vec![]; for joystick in get_joysticks().iter() { let path = unsafe { sdl3_sys::joystick::SDL_GetJoystickPathForID(*joystick) }; controller_paths.push(if path.is_null() { String::new() } else { unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() }.to_string() }); } controller_paths.insert(0, String::new()); controller_paths } } fn handle_joystick_events(ui: &mut ui::Ui) { let joystick_event = unsafe { ui::video::get_joystick_event() }; if joystick_event.joystick_id != 0 { let joystick_id = sdl3_sys::joystick::SDL_JoystickID(joystick_event.joystick_id); for (i, controller) in ui.input.controllers.iter_mut().enumerate() { if joystick_event.connected { if let Some(profile) = ui .config .input .input_profiles .get(&ui.config.input.input_profile_binding[i]) { if profile.dinput { if controller.joystick.is_null() && controller.guid == unsafe { sdl3_sys::joystick::SDL_GetJoystickGUIDForID(joystick_id) } { controller.joystick = unsafe { sdl3_sys::joystick::SDL_OpenJoystick(joystick_id) }; } } else { if controller.game_controller.is_null() && controller.guid == unsafe { sdl3_sys::gamepad::SDL_GetGamepadGUIDForID(joystick_id) } { controller.game_controller = unsafe { sdl3_sys::gamepad::SDL_OpenGamepad(joystick_id) }; } } } } else { if !controller.joystick.is_null() && controller.joystick == unsafe { sdl3_sys::joystick::SDL_GetJoystickFromID(joystick_id) } { unsafe { sdl3_sys::joystick::SDL_CloseJoystick(controller.joystick) }; controller.joystick = std::ptr::null_mut(); } else if !controller.game_controller.is_null() && controller.game_controller == unsafe { sdl3_sys::gamepad::SDL_GetGamepadFromID(joystick_id) } { unsafe { sdl3_sys::gamepad::SDL_CloseGamepad(controller.game_controller) }; controller.game_controller = std::ptr::null_mut(); } } } } } fn handle_hotkeys(keys: u32, last_key_state: u32) { if keys & (1 << L_TRIG) != 0 && last_key_state & (1 << L_TRIG) == 0 { unsafe { sdl3_sys::events::SDL_PushEvent(&mut sdl3_sys::events::SDL_Event { user: sdl3_sys::events::SDL_UserEvent { r#type: u32::from(sdl3_sys::events::SDL_EVENT_USER), code: 1, //save state ..Default::default() }, }) }; } if keys & (1 << R_TRIG) != 0 && last_key_state & (1 << R_TRIG) == 0 { unsafe { sdl3_sys::events::SDL_PushEvent(&mut sdl3_sys::events::SDL_Event { user: sdl3_sys::events::SDL_UserEvent { r#type: u32::from(sdl3_sys::events::SDL_EVENT_USER), code: 2, //load state ..Default::default() }, }) }; } if keys & (1 << START_BUTTON) != 0 && last_key_state & (1 << START_BUTTON) == 0 { unsafe { sdl3_sys::events::SDL_PushEvent(&mut sdl3_sys::events::SDL_Event { user: sdl3_sys::events::SDL_UserEvent { r#type: u32::from(sdl3_sys::events::SDL_EVENT_USER), code: 3, //exit game ..Default::default() }, }) }; } if keys & (1 << Z_TRIG) != 0 && last_key_state & (1 << Z_TRIG) == 0 { unsafe { sdl3_sys::events::SDL_PushEvent(&mut sdl3_sys::events::SDL_Event { user: sdl3_sys::events::SDL_UserEvent { r#type: u32::from(sdl3_sys::events::SDL_EVENT_USER), code: 4, //fast forward ..Default::default() }, }) }; } if keys & (1 << L_CBUTTON) != 0 && last_key_state & (1 << L_CBUTTON) == 0 { unsafe { sdl3_sys::events::SDL_PushEvent(&mut sdl3_sys::events::SDL_Event { user: sdl3_sys::events::SDL_UserEvent { r#type: u32::from(sdl3_sys::events::SDL_EVENT_USER), code: 5, //load rewind ..Default::default() }, }) }; } } pub fn get(ui: &mut ui::Ui, channel: usize, vi_counter: u64) -> InputData { handle_joystick_events(ui); if ui.input.last_polled != vi_counter { ui.input.last_polled = vi_counter; unsafe { sdl3_sys::joystick::SDL_UpdateJoysticks() }; } let profile_name = &ui.config.input.input_profile_binding[channel]; let Some(profile) = ui.config.input.input_profiles.get(profile_name) else { eprintln!("Invalid profile name: {profile_name}"); return InputData { data: 0, pak_change_pressed: false, }; }; let mut keys = 0; let controller = ui.input.controllers[channel].game_controller; let joystick = ui.input.controllers[channel].joystick; let alt_pressed = unsafe { // ignore key presses if ALT is pressed *ui.input .keyboard_state .offset(i32::from(sdl3_sys::scancode::SDL_SCANCODE_LALT) as isize) || *ui .input .keyboard_state .offset(i32::from(sdl3_sys::scancode::SDL_SCANCODE_RALT) as isize) }; for i in 0..14 { if profile_name != "default" || channel == 0 { let profile_key = profile.keys[i]; if profile_key.enabled && !alt_pressed { keys |= (unsafe { *ui.input.keyboard_state.offset(profile_key.id as isize) } as u32) << i; } } if !controller.is_null() { set_buttons_from_controller(profile, i, controller, &mut keys); } else if !joystick.is_null() { set_buttons_from_joystick(profile, i, joystick, &mut keys); } } let mut x: f64 = 0.0; let mut y: f64 = 0.0; if profile_name != "default" || channel == 0 { (x, y) = set_axis_from_keys(profile, ui.input.keyboard_state); } if !controller.is_null() { (x, y) = set_axis_from_controller(profile, controller); apply_deadzone(&mut x, &mut y, profile.deadzone); } else if !joystick.is_null() { (x, y) = set_axis_from_joystick(profile, joystick); apply_deadzone(&mut x, &mut y, profile.deadzone); } bound_axis(&mut x, &mut y); keys |= (x.round() as i8 as u8 as u32) << X_AXIS_SHIFT; keys |= (y.round() as i8 as u8 as u32) << Y_AXIS_SHIFT; let last_key_state = ui.input.controllers[channel].last_key_state; ui.input.controllers[channel].last_key_state = keys; if hotkey_pressed(profile, joystick, controller) { handle_hotkeys(keys, last_key_state); InputData { data: 0, pak_change_pressed: keys & (1 << B_BUTTON) != 0, } } else { let key = profile.keys[HOTKEY]; InputData { data: keys, pak_change_pressed: key.enabled && unsafe { *ui.input.keyboard_state.offset(key.id as isize) }, } } } pub fn assign_controller(config: &mut ui::config::Config, controller: i32, port: usize) { let joysticks = get_joysticks(); if controller < 0 { config.input.controller_assignment[port - 1] = None; } else if controller < joysticks.len() as i32 { let path = unsafe { sdl3_sys::joystick::SDL_GetJoystickPathForID(joysticks[controller as usize]) }; if !path.is_null() { config.input.controller_assignment[port - 1] = Some(unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap().to_string() }); } else { eprintln!("Invalid controller path for controller {controller}"); } } else { eprintln!("Invalid controller number") } } pub fn bind_input_profile(config: &mut ui::config::Config, profile: String, port: usize) { if config.input.input_profiles.contains_key(&profile) { config.input.input_profile_binding[port - 1] = profile; } else { eprintln!("Invalid profile name") } } pub fn clear_bindings(config: &mut ui::config::Config) { for i in 0..4 { config.input.controller_assignment[i] = None; config.input.input_profile_binding[i] = "default".to_string(); } } fn close_input_profile_window( open_joysticks: Vec<*mut sdl3_sys::joystick::SDL_Joystick>, open_controllers: Vec<*mut sdl3_sys::gamepad::SDL_Gamepad>, renderer: *mut sdl3_sys::render::SDL_Renderer, text_engine: *mut sdl3_ttf_sys::ttf::TTF_TextEngine, font: *mut sdl3_ttf_sys::ttf::TTF_Font, texture: *mut sdl3_sys::render::SDL_Texture, window: *mut sdl3_sys::video::SDL_Window, ) { for joystick in open_joysticks { unsafe { sdl3_sys::joystick::SDL_CloseJoystick(joystick) } } for controller in open_controllers { unsafe { sdl3_sys::gamepad::SDL_CloseGamepad(controller) } } unsafe { sdl3_sys::render::SDL_DestroyTexture(texture); sdl3_ttf_sys::ttf::TTF_CloseFont(font); sdl3_ttf_sys::ttf::TTF_DestroyRendererTextEngine(text_engine); sdl3_sys::render::SDL_DestroyRenderer(renderer); sdl3_sys::video::SDL_DestroyWindow(window); } } pub fn configure_input_profile( config: &mut ui::config::Config, profile: String, dinput: bool, deadzone: i32, ) { ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO); ui::sdl_init(sdl3_sys::init::SDL_INIT_GAMEPAD); ui::ttf_init(); if profile == "default" { eprintln!("Profile name cannot be default"); return; } if profile.is_empty() { eprintln!("Profile name cannot be empty"); return; } let mut open_joysticks: Vec<*mut sdl3_sys::joystick::SDL_Joystick> = Vec::new(); let mut open_controllers: Vec<*mut sdl3_sys::gamepad::SDL_Gamepad> = Vec::new(); for joystick in get_joysticks().iter() { if !dinput { let controller = unsafe { sdl3_sys::gamepad::SDL_OpenGamepad(*joystick) }; if !controller.is_null() { open_controllers.push(controller); } } else { let joystick = unsafe { sdl3_sys::joystick::SDL_OpenJoystick(*joystick) }; if !joystick.is_null() { open_joysticks.push(joystick); } } } let title = std::ffi::CString::new("configure input profile").unwrap(); let mut window: *mut sdl3_sys::video::SDL_Window = std::ptr::null_mut(); let mut renderer: *mut sdl3_sys::render::SDL_Renderer = std::ptr::null_mut(); #[cfg(target_os = "android")] let window_flags = sdl3_sys::video::SDL_WINDOW_FULLSCREEN; #[cfg(not(target_os = "android"))] let window_flags = sdl3_sys::video::SDL_WindowFlags(0); if !unsafe { sdl3_sys::render::SDL_CreateWindowAndRenderer( title.as_ptr(), 852, 480, window_flags, &mut window, &mut renderer, ) } { panic!("Could not create window and renderer") } if !unsafe { sdl3_sys::video::SDL_ShowWindow(window) } { panic!("Could not show window") } let key_labels: [(&str, usize); PROFILE_SIZE] = [ ("A", A_BUTTON), ("B", B_BUTTON), ("Start", START_BUTTON), ("D Up", U_DPAD), ("D Down", D_DPAD), ("D Left", L_DPAD), ("D Right", R_DPAD), ("C Up", U_CBUTTON), ("C Down", D_CBUTTON), ("C Left", L_CBUTTON), ("C Right", R_CBUTTON), ("L", L_TRIG), ("R", R_TRIG), ("Z", Z_TRIG), ("Control Stick Up", AXIS_UP), ("Control Stick Down", AXIS_DOWN), ("Control Stick Left", AXIS_LEFT), ("Control Stick Right", AXIS_RIGHT), ("Hotkey Activator", HOTKEY), ]; let mut new_keys = [ui::config::InputKeyButton { enabled: false, id: 0, }; PROFILE_SIZE]; let mut new_joystick_buttons = [ui::config::InputKeyButton { enabled: false, id: 0, }; PROFILE_SIZE]; let mut new_joystick_hat = [ui::config::InputJoystickHat { enabled: false, id: 0, direction: 0, }; PROFILE_SIZE]; let mut new_joystick_axis = [ui::config::InputControllerAxis { enabled: false, id: 0, axis: 0, initial_state: 0, }; PROFILE_SIZE]; let mut new_controller_buttons = [ui::config::InputKeyButton { enabled: false, id: 0, }; PROFILE_SIZE]; let mut new_controller_axis = [ui::config::InputControllerAxis { enabled: false, id: 0, axis: 0, initial_state: 0, }; PROFILE_SIZE]; let mut last_joystick_axis_result = ui::config::InputControllerAxis { enabled: false, id: 0, axis: 0, initial_state: 0, }; let image_bytes = include_bytes!("../../data/ui/controller.png"); let image = unsafe { sdl3_image_sys::image::IMG_LoadPNG_IO(sdl3_sys::everything::SDL_IOFromConstMem( image_bytes.as_ptr() as *const std::ffi::c_void, image_bytes.len(), )) }; let image_texture = unsafe { sdl3_sys::render::SDL_CreateTextureFromSurface(renderer, image) }; unsafe { sdl3_sys::surface::SDL_DestroySurface(image) }; let text_engine = unsafe { sdl3_ttf_sys::ttf::TTF_CreateRendererTextEngine(renderer) }; let font = unsafe { let font_bytes = include_bytes!("../../data/ui/RobotoMono-Regular.ttf"); sdl3_ttf_sys::ttf::TTF_OpenFontIO( sdl3_sys::everything::SDL_IOFromConstMem( font_bytes.as_ptr() as *const std::ffi::c_void, font_bytes.len(), ), true, 35.0, ) }; for (key, value) in key_labels.iter() { unsafe { sdl3_sys::events::SDL_PumpEvents(); sdl3_sys::events::SDL_FlushEvents( u32::from(sdl3_sys::events::SDL_EVENT_FIRST), u32::from(sdl3_sys::events::SDL_EVENT_LAST), ); } let mut key_set = false; while !key_set { ui::video::draw_text( format!("Select binding for: {key}").as_str(), renderer, image_texture, text_engine, font, ); let mut event: sdl3_sys::events::SDL_Event = Default::default(); while !key_set && unsafe { sdl3_sys::events::SDL_WaitEventTimeout(&mut event, 100) } { if event.event_type() == sdl3_sys::events::SDL_EVENT_WINDOW_CLOSE_REQUESTED || (event.event_type() == sdl3_sys::events::SDL_EVENT_KEY_DOWN && unsafe { event.key.scancode } == sdl3_sys::scancode::SDL_SCANCODE_AC_BACK) { close_input_profile_window( open_joysticks, open_controllers, renderer, text_engine, font, image_texture, window, ); return; } else if event.event_type() == sdl3_sys::events::SDL_EVENT_KEY_DOWN { if unsafe { !event.key.repeat && event.key.scancode != sdl3_sys::scancode::SDL_SCANCODE_LALT && event.key.scancode != sdl3_sys::scancode::SDL_SCANCODE_RALT } { new_keys[*value] = ui::config::InputKeyButton { enabled: true, id: i32::from(unsafe { event.key.scancode }), }; key_set = true } } else if event.event_type() == sdl3_sys::events::SDL_EVENT_GAMEPAD_BUTTON_DOWN { if !open_controllers.is_empty() { new_controller_buttons[*value] = ui::config::InputKeyButton { enabled: true, id: i32::from(unsafe { event.gbutton.button }), }; key_set = true } } else if event.event_type() == sdl3_sys::events::SDL_EVENT_GAMEPAD_AXIS_MOTION { let axis_value = unsafe { event.gaxis.value }; let axis = unsafe { event.gaxis.axis }; if !open_controllers.is_empty() && axis_value.saturating_abs() > (i16::MAX as i32 * 3 / 4) as i16 { let result = ui::config::InputControllerAxis { enabled: true, id: axis as i32, axis: axis_value / axis_value.saturating_abs(), initial_state: 0, }; if result != last_joystick_axis_result { new_controller_axis[*value] = result; last_joystick_axis_result = result; key_set = true } } } else if event.event_type() == sdl3_sys::events::SDL_EVENT_JOYSTICK_BUTTON_DOWN { if !open_joysticks.is_empty() { new_joystick_buttons[*value] = ui::config::InputKeyButton { enabled: true, id: i32::from(unsafe { event.jbutton.button }), }; key_set = true } } else if event.event_type() == sdl3_sys::events::SDL_EVENT_JOYSTICK_HAT_MOTION { let state = unsafe { event.jhat.value }; let hat = unsafe { event.jhat.hat }; if !open_joysticks.is_empty() && state != sdl3_sys::joystick::SDL_HAT_CENTERED { new_joystick_hat[*value] = ui::config::InputJoystickHat { enabled: true, id: hat as i32, direction: state, }; key_set = true } } else if event.event_type() == sdl3_sys::events::SDL_EVENT_JOYSTICK_AXIS_MOTION { let axis_value = unsafe { event.jaxis.value }; let axis = unsafe { event.jaxis.axis }; let mut initial_state = 0; let has_initial_state = unsafe { sdl3_sys::joystick::SDL_GetJoystickAxisInitialState( sdl3_sys::joystick::SDL_GetJoystickFromID(event.jaxis.which), axis as i32, &mut initial_state, ) }; initial_state = if has_initial_state { if initial_state < i16::MIN / 2 { i16::MIN } else if initial_state > i16::MAX / 2 { i16::MAX } else { 0 } } else { 0 }; if !open_joysticks.is_empty() && axis_value.abs_diff(initial_state) > (u16::MAX / 4) { let result = ui::config::InputControllerAxis { enabled: true, id: axis as i32, axis: axis_value / axis_value.saturating_abs(), initial_state, }; let same_trigger = last_joystick_axis_result.id == result.id && last_joystick_axis_result.initial_state != 0; if result != last_joystick_axis_result && !same_trigger { new_joystick_axis[*value] = result; last_joystick_axis_result = result; key_set = true } } } } } ui::video::draw_text( "Binding set, please wait...", renderer, image_texture, text_engine, font, ); std::thread::sleep(std::time::Duration::from_millis(500)); } close_input_profile_window( open_joysticks, open_controllers, renderer, text_engine, font, image_texture, window, ); let new_profile = ui::config::InputProfile { keys: new_keys, controller_buttons: new_controller_buttons, controller_axis: new_controller_axis, joystick_buttons: new_joystick_buttons, joystick_hat: new_joystick_hat, joystick_axis: new_joystick_axis, dinput, deadzone, }; config.input.input_profiles.insert(profile, new_profile); } pub fn get_default_profile() -> ui::config::InputProfile { let mut default_controller_buttons = [ui::config::InputKeyButton { enabled: false, id: 0, }; PROFILE_SIZE]; let mut default_controller_axis = [ui::config::InputControllerAxis { enabled: false, id: 0, axis: 0, initial_state: 0, }; PROFILE_SIZE]; let mut default_keys = [ui::config::InputKeyButton { enabled: false, id: 0, }; PROFILE_SIZE]; default_keys[R_DPAD] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_D), }; default_keys[L_DPAD] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_A), }; default_keys[D_DPAD] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_S), }; default_keys[U_DPAD] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_W), }; default_keys[START_BUTTON] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_RETURN), }; default_keys[Z_TRIG] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_Z), }; default_keys[B_BUTTON] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_LCTRL), }; default_keys[A_BUTTON] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_LSHIFT), }; default_keys[R_CBUTTON] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_L), }; default_keys[L_CBUTTON] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_J), }; default_keys[D_CBUTTON] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_K), }; default_keys[U_CBUTTON] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_I), }; default_keys[R_TRIG] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_C), }; default_keys[L_TRIG] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_X), }; default_keys[AXIS_LEFT] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_LEFT), }; default_keys[AXIS_RIGHT] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_RIGHT), }; default_keys[AXIS_UP] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_UP), }; default_keys[AXIS_DOWN] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_DOWN), }; default_keys[HOTKEY] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_COMMA), }; default_controller_buttons[R_DPAD] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_RIGHT), }; default_controller_buttons[L_DPAD] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_LEFT), }; default_controller_buttons[D_DPAD] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_DOWN), }; default_controller_buttons[U_DPAD] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_UP), }; default_controller_buttons[START_BUTTON] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_START), }; default_controller_axis[Z_TRIG] = ui::config::InputControllerAxis { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFT_TRIGGER), axis: 1, initial_state: 0, }; default_controller_buttons[B_BUTTON] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_WEST), }; default_controller_buttons[A_BUTTON] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_SOUTH), }; default_controller_axis[R_CBUTTON] = ui::config::InputControllerAxis { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTX), axis: 1, initial_state: 0, }; default_controller_axis[L_CBUTTON] = ui::config::InputControllerAxis { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTX), axis: -1, initial_state: 0, }; default_controller_axis[D_CBUTTON] = ui::config::InputControllerAxis { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTY), axis: 1, initial_state: 0, }; default_controller_axis[U_CBUTTON] = ui::config::InputControllerAxis { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTY), axis: -1, initial_state: 0, }; default_controller_buttons[R_TRIG] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), }; default_controller_buttons[L_TRIG] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), }; default_controller_axis[AXIS_LEFT] = ui::config::InputControllerAxis { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTX), axis: -1, initial_state: 0, }; default_controller_axis[AXIS_RIGHT] = ui::config::InputControllerAxis { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTX), axis: 1, initial_state: 0, }; default_controller_axis[AXIS_UP] = ui::config::InputControllerAxis { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTY), axis: -1, initial_state: 0, }; default_controller_axis[AXIS_DOWN] = ui::config::InputControllerAxis { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTY), axis: 1, initial_state: 0, }; default_controller_buttons[HOTKEY] = ui::config::InputKeyButton { enabled: true, id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_BACK), }; ui::config::InputProfile { keys: default_keys, controller_buttons: default_controller_buttons, controller_axis: default_controller_axis, joystick_buttons: [ui::config::InputKeyButton { enabled: false, id: 0, }; PROFILE_SIZE], joystick_hat: [ui::config::InputJoystickHat { enabled: false, id: 0, direction: 0, }; PROFILE_SIZE], joystick_axis: [ui::config::InputControllerAxis { enabled: false, id: 0, axis: 0, initial_state: 0, }; PROFILE_SIZE], dinput: false, deadzone: DEADZONE_DEFAULT, } } fn get_joysticks() -> Vec { unsafe { sdl3_sys::events::SDL_PumpEvents() }; let mut num_joysticks = 0; let sdl_joysticks = unsafe { sdl3_sys::joystick::SDL_GetJoysticks(&mut num_joysticks) }; if !sdl_joysticks.is_null() { let parts = unsafe { std::slice::from_raw_parts(sdl_joysticks, num_joysticks as usize) }.to_vec(); unsafe { sdl3_sys::stdinc::SDL_free(sdl_joysticks as *mut std::ffi::c_void) }; parts } else { eprintln!("Could not get joysticks"); vec![] } } pub fn init(ui: &mut ui::Ui) { ui::sdl_init(sdl3_sys::init::SDL_INIT_GAMEPAD); ui.input.keyboard_state = unsafe { sdl3_sys::keyboard::SDL_GetKeyboardState(std::ptr::null_mut()) }; if ui.input.keyboard_state.is_null() { panic!("Could not get keyboard state"); } for i in 0..4 { if let Some(controller_assignment) = &ui.config.input.controller_assignment[i] && ui.config.input.controller_enabled[i] { let mut joystick_id = sdl3_sys::everything::SDL_JoystickID(0); for joystick in get_joysticks().iter() { let path = if cfg!(target_os = "android") { let name = if let name = unsafe { sdl3_sys::joystick::SDL_GetJoystickNameForID(*joystick) } && !name.is_null() { unsafe { std::ffi::CStr::from_ptr(name).to_str().unwrap() }.to_string() } else { UNKNOWN_CONTROLLER_NAME.to_string() }; let vendor_id = unsafe { sdl3_sys::joystick::SDL_GetJoystickVendorForID(*joystick) }; let product_id = unsafe { sdl3_sys::joystick::SDL_GetJoystickProductForID(*joystick) }; Some(format!("{}:{}:{}", name, vendor_id, product_id)) } else { let path = unsafe { sdl3_sys::joystick::SDL_GetJoystickPathForID(*joystick) }; if !path.is_null() { Some( unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() }.to_string(), ) } else { None } }; if let Some(path) = path && path == *controller_assignment && unsafe { sdl3_sys::joystick::SDL_GetJoystickFromID(*joystick) }.is_null() && unsafe { sdl3_sys::gamepad::SDL_GetGamepadFromID(*joystick) }.is_null() { joystick_id = *joystick; break; } } if joystick_id != 0 && let Some(profile) = ui .config .input .input_profiles .get(&ui.config.input.input_profile_binding[i]) { if !profile.dinput { let gamepad = unsafe { sdl3_sys::gamepad::SDL_OpenGamepad(joystick_id) }; if gamepad.is_null() { eprintln!("could not connect gamepad: {}", u32::from(joystick_id)) } else { ui.input.controllers[i].game_controller = gamepad; ui.input.controllers[i].guid = unsafe { sdl3_sys::gamepad::SDL_GetGamepadGUIDForID(joystick_id) }; let properties = unsafe { sdl3_sys::gamepad::SDL_GetGamepadProperties(gamepad) }; if properties == 0 { eprintln!("could not get gamepad properties"); } ui.input.controllers[i].rumble = unsafe { sdl3_sys::properties::SDL_GetBooleanProperty( properties, sdl3_sys::gamepad::SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN, false, ) }; } } else { let joystick = unsafe { sdl3_sys::joystick::SDL_OpenJoystick(joystick_id) }; if joystick.is_null() { eprintln!("could not connect joystick: {}", u32::from(joystick_id)) } else { ui.input.controllers[i].joystick = joystick; ui.input.controllers[i].guid = unsafe { sdl3_sys::joystick::SDL_GetJoystickGUIDForID(joystick_id) }; let properties = unsafe { sdl3_sys::joystick::SDL_GetJoystickProperties(joystick) }; if properties == 0 { eprintln!("could not get joystick properties"); } ui.input.controllers[i].rumble = unsafe { sdl3_sys::properties::SDL_GetBooleanProperty( properties, sdl3_sys::joystick::SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, false, ) }; } } } else { eprintln!("Could not bind assigned controller"); } } } } pub fn close(ui: &mut ui::Ui) { for controller in ui.input.controllers.iter_mut() { if !controller.joystick.is_null() { unsafe { sdl3_sys::joystick::SDL_CloseJoystick(controller.joystick) } controller.joystick = std::ptr::null_mut(); } if !controller.game_controller.is_null() { unsafe { sdl3_sys::gamepad::SDL_CloseGamepad(controller.game_controller) } controller.game_controller = std::ptr::null_mut(); } } }