Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add getters and setters for InputAxis and ButtonSettings #3446

Closed
wants to merge 11 commits into from
1 change: 1 addition & 0 deletions crates/bevy_input/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ bevy_utils = { path = "../bevy_utils", version = "0.5.0" }

# other
serde = { version = "1", features = ["derive"], optional = true }
thiserror = "1.0.30"
312 changes: 285 additions & 27 deletions crates/bevy_input/src/gamepad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@ use crate::{Axis, Input};
use bevy_app::{EventReader, EventWriter};
use bevy_ecs::system::{Res, ResMut};
use bevy_utils::{tracing::info, HashMap, HashSet};
use thiserror::Error;

/// Errors that occur when setting settings for gamepad input
Copy link
Contributor

@IceSentry IceSentry Feb 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe "configuring settings" or "setting up gamepad input". "setting settings" feels weird

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Errors that occur when setting settings for gamepad input
/// Errors that occur when attempting to incorrectly configure gamepad input

#[derive(Error, Debug)]
pub enum GamepadSettingsError {
#[error("{0}")]
InvalidAxisSetting(String),
#[error("{0}")]
InvalidButtonSetting(String),
}

type Result<T, E = GamepadSettingsError> = std::result::Result<T, E>;

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
Expand Down Expand Up @@ -131,70 +143,316 @@ impl GamepadSettings {

#[derive(Debug, Clone)]
pub struct ButtonSettings {
pub press: f32,
pub release: f32,
press_threshold: f32,
release_threshold: f32,
}

impl Default for ButtonSettings {
fn default() -> Self {
ButtonSettings {
press: 0.75,
release: 0.65,
press_threshold: 0.75,
release_threshold: 0.65,
}
}
}

impl ButtonSettings {
/// Creates a new ButtonSettings instance
///
/// # Arguments
///
/// + `press_threshold` - the value above which the button is considered pressed.
/// + `release_threshold` - the value below which the button is considered released.
///
/// Restrictions:
/// + `0.0 <= release_threshold <= press_threshold <= 1.0`
///
/// # Errors
///
/// If the restrictions are not met, InvalidButtonSetting will be returned.
pub fn new(press_threshold: f32, release_threshold: f32) -> Result<ButtonSettings> {
if 0.0 <= release_threshold
&& release_threshold <= press_threshold
&& press_threshold <= 1.0
{
Ok(ButtonSettings {
press_threshold,
release_threshold,
})
} else {
Err(GamepadSettingsError::InvalidButtonSetting(
"The condition 0.0 <= release_threshold <= press_threshold <= 1.0 must hold true"
.to_owned(),
))
}
}

/// Get the button input threshold above which the button is considered pressed
pub fn press_threshold(&self) -> f32 {
self.press_threshold
}

/// Try to set the button input threshold above which the button is considered pressed
///
/// # Errors
///
/// If the value passed is outside the range [release_threshold, 1.0], InvalidButtonSetting is
/// returned.
pub fn try_set_press_threshold(&mut self, value: f32) -> Result<()> {
if (self.release_threshold..=1.0).contains(&value) {
self.press_threshold = value;
Ok(())
} else {
Err(GamepadSettingsError::InvalidButtonSetting(
"press_threshold must be in the range [release_threshold, 1.0]".to_owned(),
))
}
}

/// Try to set the button input threshold above which the button is considered pressed. If the
/// value passed is outside the range [release_threshold, 1.0], the value will not be changed.
///
/// Returns the new value of press_threshold.
pub fn set_press_threshold(&mut self, value: f32) -> f32 {
self.try_set_press_threshold(value).ok();
self.press_threshold
}

/// Get the button input threshold below which the button is considered released
pub fn release_threshold(&self) -> f32 {
self.release_threshold
}

/// Try to set the button input threshold below which the button is considered released
///
/// # Errors
///
/// If the value passed is outside the range [0.0, press_threshold], InvalidButtonSetting is
/// returned.
pub fn try_set_release_threshold(&mut self, value: f32) -> Result<()> {
if (0.0..=self.press_threshold).contains(&value) {
self.release_threshold = value;
Ok(())
} else {
Err(GamepadSettingsError::InvalidButtonSetting(
"release_threshold must be in the range [0.0, press_threshold]".to_owned(),
))
}
}

/// Try to set the button input threshold below which the button is considered released. If the
/// value passed is outside the range [0.0, press_threshold], the value will not be changed.
///
/// Returns the new value of release_threshold.
pub fn set_release_threshold(&mut self, value: f32) -> f32 {
self.try_set_release_threshold(value).ok();
self.release_threshold
}

fn is_pressed(&self, value: f32) -> bool {
value >= self.press
value >= self.press_threshold
}

fn is_released(&self, value: f32) -> bool {
value <= self.release
value <= self.release_threshold
}
}

/// Defines the sensitivity range and threshold for an axis.
///
/// Values that are lower than `negative_high` will be rounded to -1.0.
/// Values that are higher than `positive_high` will be rounded to 1.0.
/// Values that are in-between `negative_low` and `positive_low` will be rounded to 0.0.
/// Values that are lower than `livezone_lowerbound` will be rounded to -1.0.
/// Values that are higher than `livezone_upperbound` will be rounded to 1.0.
/// Values that are in-between `deadzone_lowerbound` and `deadzone_upperbound` will be rounded to 0.0.
/// Otherwise, values will not be rounded.
///
/// The valid range is from -1.0 to 1.0, inclusive.
#[derive(Debug, Clone)]
pub struct AxisSettings {
pub positive_high: f32,
pub positive_low: f32,
pub negative_high: f32,
pub negative_low: f32,
livezone_upperbound: f32,
deadzone_upperbound: f32,
deadzone_lowerbound: f32,
livezone_lowerbound: f32,
///`threshold` defines the minimum difference between old and new values to apply the changes.
pub threshold: f32,
threshold: f32,
}

impl Default for AxisSettings {
fn default() -> Self {
AxisSettings {
positive_high: 0.95,
positive_low: 0.05,
negative_high: -0.95,
negative_low: -0.05,
livezone_upperbound: 0.95,
deadzone_upperbound: 0.05,
livezone_lowerbound: -0.95,
deadzone_lowerbound: -0.05,
threshold: 0.01,
}
}
}

impl AxisSettings {
fn filter(&self, new_value: f32, old_value: Option<f32>) -> Option<f32> {
let new_value = if new_value <= self.positive_low && new_value >= self.negative_low {
0.0
} else if new_value >= self.positive_high {
1.0
} else if new_value <= self.negative_high {
-1.0
/// Get the value above which inputs will be rounded up to 1.0
pub fn livezone_upperbound(&self) -> f32 {
self.livezone_upperbound
}

/// Try to set the value above which inputs will be rounded up to 1.0
///
/// # Errors
///
/// If the value passed is less than deadzone_upperbound or greater than 1.0
pub fn try_set_livezone_upperbound(&mut self, value: f32) -> Result<()> {
if value < self.deadzone_upperbound || value > 1.0 {
Err(GamepadSettingsError::InvalidAxisSetting(
"livezone_upperbound must be greater than deadzone_upperbound and less than 1.0"
.to_owned(),
))
} else {
new_value
};
self.livezone_upperbound = value;
Ok(())
}
}

/// Try to set the value above which inputs will be rounded up to 1.0. If the value is less than
/// deadzone_upperbound or greater than 1.0, the value will not be changed.
///
/// Returns the new value of livezone_upperbound.
pub fn set_livezone_upperbound(&mut self, value: f32) -> f32 {
self.try_set_livezone_upperbound(value).ok();
self.livezone_upperbound
}

/// Get the value below which positive inputs will be rounded down to 0.0
pub fn deadzone_upperbound(&mut self) -> f32 {
self.deadzone_upperbound
}

/// Try to set the value below which positive inputs will be rounded down to 0.0
///
/// # Errors
///
/// If the value passed is negative or greater than livezone_upperbound
pub fn try_set_deadzone_upperbound(&mut self, value: f32) -> Result<()> {
if value < 0.0 || value > self.livezone_upperbound {
Err(GamepadSettingsError::InvalidAxisSetting(
"deadzone_upperbound must be positive and less than livezone_upperbound".to_owned(),
))
} else {
self.deadzone_upperbound = value;
Ok(())
}
}

/// Try to set the value below which positive inputs will be rounded down to 0.0. If the value
/// passed is negative or greater than livezone_upperbound, the value will not be changed.
///
/// Returns the new value of deadzone_upperbound.
pub fn set_deadzone_upperbound(&mut self, value: f32) -> f32 {
self.try_set_deadzone_upperbound(value).ok();
self.deadzone_upperbound
}

/// Get the value above which negative inputs will be rounded up to 0.0
pub fn deadzone_lowerbound(&self) -> f32 {
self.deadzone_lowerbound
}

/// Try to set the value above which negative inputs will be rounded up to 0.0
///
/// # Errors
///
/// If the value passed is positive or less than livezone_lowerbound
pub fn try_set_deadzone_lowerbound(&mut self, value: f32) -> Result<()> {
if value < self.livezone_lowerbound || value > 0.0 {
Err(GamepadSettingsError::InvalidAxisSetting(
"deadzone_lowerbound must be negative and greater than livezone_lowerbound"
.to_owned(),
))
} else {
self.deadzone_lowerbound = value;
Ok(())
}
}

/// Try to set the value above which negative inputs will be rounded up to 0.0. If the value
/// passed is positive or less than livezone_lowerbound, the value will not be changed.
///
/// Returns the new value of deadzone_lowerbound.
pub fn set_deadzone_lowerbound(&mut self, value: f32) -> f32 {
self.try_set_deadzone_lowerbound(value).ok();
self.deadzone_lowerbound
}

/// Get the value below which inputs will be rounded down to -1.0
pub fn livezone_lowerbound(&self) -> f32 {
self.livezone_lowerbound
}

/// Try to get the value below which inputs will be rounded down to -1.0
///
/// # Errors
///
/// If the value passed is less than -1.0 or greater than deadzone_lowerbound
pub fn try_set_livezone_lowerbound(&mut self, value: f32) -> Result<()> {
if value < -1.0 || value > self.deadzone_lowerbound {
Err(GamepadSettingsError::InvalidAxisSetting(
"livezone_lowerbound must be greater than -1.0 and less than deadzone_lowerbound"
.to_owned(),
))
} else {
self.livezone_lowerbound = value;
Ok(())
}
}

/// Try to set the value below which inputs will be rounded down to -1.0. If the value passed is
/// less than -1.0 or greater than deadzone_lowerbound, the value will not be changed.
///
/// Returns the new value of livezone_lowerbound.
pub fn set_livezone_lowerbound(&mut self, value: f32) -> f32 {
self.try_set_livezone_lowerbound(value).ok();
self.livezone_lowerbound
}

/// Get the minimum value by which input must change before the changes will be applied
pub fn threshold(&self) -> f32 {
self.threshold
}

/// Try to set the minimum value by which input must change before the changes will be applied
///
/// # Errors
///
/// If the value passed is not within [0.0, 2.0]
pub fn try_set_threshold(&mut self, value: f32) -> Result<()> {
if !(0.0..=2.0).contains(&value) {
Err(GamepadSettingsError::InvalidAxisSetting(
"threshold must be between 0.0 and 2.0, inclusive".to_owned(),
))
} else {
self.threshold = value;
Ok(())
}
}

/// Try to set the minimum value by which input must change before the changes will be applied.
/// If the value passed is not within [0.0, 2.0], the value will not be changed.
///
/// Returns the new value of threshold.
pub fn set_threshold(&mut self, value: f32) -> f32 {
self.try_set_threshold(value).ok();
self.threshold
}

fn filter(&self, new_value: f32, old_value: Option<f32>) -> Option<f32> {
let new_value =
if new_value <= self.deadzone_upperbound && new_value >= self.deadzone_lowerbound {
0.0
} else if new_value >= self.livezone_upperbound {
1.0
} else if new_value <= self.livezone_lowerbound {
-1.0
} else {
new_value
};

if let Some(old_value) = old_value {
if (new_value - old_value).abs() <= self.threshold {
Expand Down