From 879965024530d7b1afd9e44ca67f7ef44405e86e Mon Sep 17 00:00:00 2001 From: Logan McNaughton <848146+loganmc10@users.noreply.github.com> Date: Sat, 23 Aug 2025 15:36:03 +0200 Subject: [PATCH] add deadzone option (#554) * add deadzone option * add deadzone calc * more --- src/main.rs | 12 +++++- src/ui/config.rs | 1 + src/ui/gui.rs | 9 +++- src/ui/gui/input_profile.slint | 25 ++++++++++- src/ui/input.rs | 77 +++++++++++++++++++++++++--------- 5 files changed, 101 insertions(+), 23 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3b6ea94..015659c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,15 +22,22 @@ struct Args { short, long, value_name = "PROFILE_NAME", - help = "Create a new input profile (keyboard/gamepad mappings)." + help = "Create a new input profile (keyboard/gamepad mappings)" )] configure_input_profile: Option, #[arg( short, long, - help = "Use DirectInput when configuring a new input profile." + help = "Use DirectInput when configuring a new input profile" )] use_dinput: bool, + #[arg( + short, + long, + value_name = "DEADZONE_PERCENTAGE", + help = "Used along with --configure-input-profile to set the deadzone for analog sticks" + )] + deadzone: Option, #[arg( short, long, @@ -112,6 +119,7 @@ async fn main() { &mut ui, args.configure_input_profile.unwrap(), args.use_dinput, + args.deadzone.unwrap_or(ui::input::DEADZONE_DEFAULT), ); return; } diff --git a/src/ui/config.rs b/src/ui/config.rs index d2a3022..133a7ba 100644 --- a/src/ui/config.rs +++ b/src/ui/config.rs @@ -29,6 +29,7 @@ pub struct InputProfile { pub joystick_hat: [InputJoystickHat; ui::input::PROFILE_SIZE], pub joystick_axis: [InputControllerAxis; ui::input::PROFILE_SIZE], pub dinput: bool, + pub deadzone: i32, } #[derive(serde::Serialize, serde::Deserialize)] diff --git a/src/ui/gui.rs b/src/ui/gui.rs index 8d9a312..93aaddb 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -211,6 +211,7 @@ fn controller_window( let weak_app = app.as_weak(); app.on_input_profile_button_clicked(move || { let dialog = InputProfileDialog::new().unwrap(); + dialog.set_deadzone(ui::input::DEADZONE_DEFAULT); let weak_dialog = dialog.as_weak(); let weak_app = weak_app.clone(); dialog.on_profile_creation_button_clicked(move || { @@ -220,10 +221,16 @@ fn controller_window( handle.hide().unwrap(); let profile_name = handle.get_profile_name().into(); let dinput = handle.get_dinput(); + let deadzone = handle.get_deadzone(); tokio::spawn(async move { let mut game_ui = ui::Ui::new(); - ui::input::configure_input_profile(&mut game_ui, profile_name, dinput); + ui::input::configure_input_profile( + &mut game_ui, + profile_name, + dinput, + deadzone, + ); update_input_profiles(&weak_app, &game_ui.config); }); }) diff --git a/src/ui/gui/input_profile.slint b/src/ui/gui/input_profile.slint index 17d6b1e..a5c43d6 100644 --- a/src/ui/gui/input_profile.slint +++ b/src/ui/gui/input_profile.slint @@ -1,8 +1,9 @@ -import { StandardButton, HorizontalBox, VerticalBox, Button, LineEdit, CheckBox } from "std-widgets.slint"; +import { StandardButton, HorizontalBox, VerticalBox, Button, LineEdit, CheckBox, Slider } from "std-widgets.slint"; export global InputProfileData { in-out property dinput; in-out property profile_name; + in-out property deadzone; } export component InputProfileDialog inherits Window { @@ -10,6 +11,7 @@ export component InputProfileDialog inherits Window { callback profile_creation_button_clicked; in-out property dinput <=> InputProfileData.dinput; in-out property profile_name <=> InputProfileData.profile_name; + in-out property deadzone <=> InputProfileData.deadzone; VerticalBox { HorizontalBox { LineEdit { @@ -21,6 +23,27 @@ export component InputProfileDialog inherits Window { } } + HorizontalBox { + alignment: center; + Text { + text: @tr("Deadzone Percentage"); + vertical-alignment: center; + } + + Slider { + min-width: 160px; + value: InputProfileData.deadzone; + changed(value) => { + InputProfileData.deadzone = value; + } + } + + Text { + text: InputProfileData.deadzone + "%"; + vertical-alignment: center; + } + } + HorizontalBox { alignment: center; CheckBox { diff --git a/src/ui/input.rs b/src/ui/input.rs index 6839562..cdeda53 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -28,6 +28,8 @@ const Y_AXIS_SHIFT: usize = 24; const MAX_AXIS_VALUE: f64 = 85.0; +pub const DEADZONE_DEFAULT: i32 = 5; + pub struct Controllers { pub rumble: bool, pub game_controller: *mut sdl3_sys::gamepad::SDL_Gamepad, @@ -56,6 +58,7 @@ fn bound_axis(x: &mut f64, y: &mut f64) { fn set_axis_from_joystick( profile: &ui::config::InputProfile, joystick: *mut sdl3_sys::joystick::SDL_Joystick, + deadzone: u16, ) -> (f64, f64) { let mut x = 0.0; let mut y = 0.0; @@ -63,32 +66,48 @@ fn set_axis_from_joystick( 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 = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64; + if axis_position.unsigned_abs() > deadzone + && axis_position as isize * profile.joystick_axis[AXIS_LEFT].axis as isize > 0 + { + x = (axis_position.unsigned_abs() - deadzone) as f64 * MAX_AXIS_VALUE + / (i16::MAX as u16 - deadzone) as f64; + x *= profile.joystick_axis[AXIS_LEFT].axis as f64; } } 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 = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64; + if axis_position.unsigned_abs() > deadzone + && axis_position as isize * profile.joystick_axis[AXIS_RIGHT].axis as isize > 0 + { + x = (axis_position.unsigned_abs() - deadzone) as f64 * MAX_AXIS_VALUE + / (i16::MAX as u16 - deadzone) as f64; + x *= profile.joystick_axis[AXIS_RIGHT].axis as f64; } } 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 = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg(); + if axis_position.unsigned_abs() > deadzone + && axis_position as isize * profile.joystick_axis[AXIS_DOWN].axis as isize > 0 + { + y = (axis_position.unsigned_abs() - deadzone) as f64 * MAX_AXIS_VALUE + / (i16::MAX as u16 - deadzone) as f64; + y *= (profile.joystick_axis[AXIS_DOWN].axis as f64).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 = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg(); + if axis_position.unsigned_abs() > deadzone + && axis_position as isize * profile.joystick_axis[AXIS_UP].axis as isize > 0 + { + y = (axis_position.unsigned_abs() - deadzone) as f64 * MAX_AXIS_VALUE + / (i16::MAX as u16 - deadzone) as f64; + y *= (profile.joystick_axis[AXIS_UP].axis as f64).neg(); } } (x, y) @@ -130,6 +149,7 @@ fn get_button_from_i32(button: i32) -> sdl3_sys::gamepad::SDL_GamepadButton { fn set_axis_from_controller( profile: &ui::config::InputProfile, controller: *mut sdl3_sys::gamepad::SDL_Gamepad, + deadzone: u16, ) -> (f64, f64) { let mut x = 0.0; let mut y = 0.0; @@ -140,8 +160,12 @@ fn set_axis_from_controller( get_axis_from_i32(profile.controller_axis[AXIS_LEFT].id), ) }; - if axis_position as isize * profile.controller_axis[AXIS_LEFT].axis as isize > 0 { - x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64; + if axis_position.unsigned_abs() > deadzone + && axis_position as isize * profile.controller_axis[AXIS_LEFT].axis as isize > 0 + { + x = (axis_position.unsigned_abs() - deadzone) as f64 * MAX_AXIS_VALUE + / (i16::MAX as u16 - deadzone) as f64; + x *= profile.controller_axis[AXIS_LEFT].axis as f64; } } if profile.controller_axis[AXIS_RIGHT].enabled { @@ -151,8 +175,12 @@ fn set_axis_from_controller( get_axis_from_i32(profile.controller_axis[AXIS_RIGHT].id), ) }; - if axis_position as isize * profile.controller_axis[AXIS_RIGHT].axis as isize > 0 { - x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64; + if axis_position.unsigned_abs() > deadzone + && axis_position as isize * profile.controller_axis[AXIS_RIGHT].axis as isize > 0 + { + x = (axis_position.unsigned_abs() - deadzone) as f64 * MAX_AXIS_VALUE + / (i16::MAX as u16 - deadzone) as f64; + x *= profile.controller_axis[AXIS_RIGHT].axis as f64; } } if profile.controller_axis[AXIS_DOWN].enabled { @@ -162,8 +190,12 @@ fn set_axis_from_controller( get_axis_from_i32(profile.controller_axis[AXIS_DOWN].id), ) }; - if axis_position as isize * profile.controller_axis[AXIS_DOWN].axis as isize > 0 { - y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg(); + if axis_position.unsigned_abs() > deadzone + && axis_position as isize * profile.controller_axis[AXIS_DOWN].axis as isize > 0 + { + y = (axis_position.unsigned_abs() - deadzone) as f64 * MAX_AXIS_VALUE + / (i16::MAX as u16 - deadzone) as f64; + y *= (profile.controller_axis[AXIS_DOWN].axis as f64).neg(); } } if profile.controller_axis[AXIS_UP].enabled { @@ -173,8 +205,12 @@ fn set_axis_from_controller( get_axis_from_i32(profile.controller_axis[AXIS_UP].id), ) }; - if axis_position as isize * profile.controller_axis[AXIS_UP].axis as isize > 0 { - y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg(); + if axis_position.unsigned_abs() > deadzone + && axis_position as isize * profile.controller_axis[AXIS_UP].axis as isize > 0 + { + y = (axis_position.unsigned_abs() - deadzone) as f64 * MAX_AXIS_VALUE + / (i16::MAX as u16 - deadzone) as f64; + y *= (profile.controller_axis[AXIS_UP].axis as f64).neg(); } } (x, y) @@ -408,10 +444,11 @@ pub fn get(ui: &ui::Ui, channel: usize) -> InputData { (x, y) = set_axis_from_keys(profile, ui.input.keyboard_state); } + let deadzone = (i16::MAX as i32 * profile.deadzone / 100) as u16; if !controller.is_null() { - (x, y) = set_axis_from_controller(profile, controller) + (x, y) = set_axis_from_controller(profile, controller, deadzone) } else if !joystick.is_null() { - (x, y) = set_axis_from_joystick(profile, joystick) + (x, y) = set_axis_from_joystick(profile, joystick, deadzone) } bound_axis(&mut x, &mut y); @@ -466,7 +503,7 @@ fn close_controllers( } } -pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) { +pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool, deadzone: i32) { ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO); ui::ttf_init(); @@ -699,6 +736,7 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) { joystick_hat: new_joystick_hat, joystick_axis: new_joystick_axis, dinput, + deadzone, }; ui.config.input.input_profiles.insert(profile, new_profile); } @@ -899,6 +937,7 @@ pub fn get_default_profile() -> ui::config::InputProfile { axis: 0, }; PROFILE_SIZE], dinput: false, + deadzone: DEADZONE_DEFAULT, } }