use core::{ops::RangeInclusive, time::Duration};
use crate::{Axis, ButtonInput, ButtonState};
use alloc::string::String;
#[cfg(feature = "bevy_reflect")]
use bevy_ecs::prelude::ReflectComponent;
use bevy_ecs::{
change_detection::DetectChangesMut,
component::Component,
entity::Entity,
event::{BufferedEvent, EventReader, EventWriter},
name::Name,
system::{Commands, Query},
};
use bevy_math::ops;
use bevy_math::Vec2;
use bevy_platform::collections::HashMap;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
use derive_more::derive::From;
use log::{info, warn};
use thiserror::Error;
#[derive(BufferedEvent, Debug, Clone, PartialEq, From)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum GamepadEvent {
Connection(GamepadConnectionEvent),
Button(GamepadButtonChangedEvent),
Axis(GamepadAxisChangedEvent),
}
#[derive(BufferedEvent, Debug, Clone, PartialEq, From)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum RawGamepadEvent {
Connection(GamepadConnectionEvent),
Button(RawGamepadButtonChangedEvent),
Axis(RawGamepadAxisChangedEvent),
}
#[derive(BufferedEvent, Debug, Copy, Clone, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct RawGamepadButtonChangedEvent {
pub gamepad: Entity,
pub button: GamepadButton,
pub value: f32,
}
impl RawGamepadButtonChangedEvent {
pub fn new(gamepad: Entity, button_type: GamepadButton, value: f32) -> Self {
Self {
gamepad,
button: button_type,
value,
}
}
}
#[derive(BufferedEvent, Debug, Copy, Clone, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct RawGamepadAxisChangedEvent {
pub gamepad: Entity,
pub axis: GamepadAxis,
pub value: f32,
}
impl RawGamepadAxisChangedEvent {
pub fn new(gamepad: Entity, axis_type: GamepadAxis, value: f32) -> Self {
Self {
gamepad,
axis: axis_type,
value,
}
}
}
#[derive(BufferedEvent, Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct GamepadConnectionEvent {
pub gamepad: Entity,
pub connection: GamepadConnection,
}
impl GamepadConnectionEvent {
pub fn new(gamepad: Entity, connection: GamepadConnection) -> Self {
Self {
gamepad,
connection,
}
}
pub fn connected(&self) -> bool {
matches!(self.connection, GamepadConnection::Connected { .. })
}
pub fn disconnected(&self) -> bool {
!self.connected()
}
}
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct GamepadButtonStateChangedEvent {
pub entity: Entity,
pub button: GamepadButton,
pub state: ButtonState,
}
impl GamepadButtonStateChangedEvent {
pub fn new(entity: Entity, button: GamepadButton, state: ButtonState) -> Self {
Self {
entity,
button,
state,
}
}
}
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct GamepadButtonChangedEvent {
pub entity: Entity,
pub button: GamepadButton,
pub state: ButtonState,
pub value: f32,
}
impl GamepadButtonChangedEvent {
pub fn new(entity: Entity, button: GamepadButton, state: ButtonState, value: f32) -> Self {
Self {
entity,
button,
state,
value,
}
}
}
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
all(feature = "bevy_reflect", feature = "serialize"),
reflect(Serialize, Deserialize)
)]
pub struct GamepadAxisChangedEvent {
pub entity: Entity,
pub axis: GamepadAxis,
pub value: f32,
}
impl GamepadAxisChangedEvent {
pub fn new(entity: Entity, axis: GamepadAxis, value: f32) -> Self {
Self {
entity,
axis,
value,
}
}
}
#[derive(Error, Debug, PartialEq)]
pub enum AxisSettingsError {
#[error("invalid livezone_lowerbound {0}, expected value [-1.0..=0.0]")]
LiveZoneLowerBoundOutOfRange(f32),
#[error("invalid deadzone_lowerbound {0}, expected value [-1.0..=0.0]")]
DeadZoneLowerBoundOutOfRange(f32),
#[error("invalid deadzone_upperbound {0}, expected value [0.0..=1.0]")]
DeadZoneUpperBoundOutOfRange(f32),
#[error("invalid livezone_upperbound {0}, expected value [0.0..=1.0]")]
LiveZoneUpperBoundOutOfRange(f32),
#[error("invalid parameter values livezone_lowerbound {} deadzone_lowerbound {}, expected livezone_lowerbound <= deadzone_lowerbound", livezone_lowerbound, deadzone_lowerbound)]
LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
livezone_lowerbound: f32,
deadzone_lowerbound: f32,
},
#[error("invalid parameter values livezone_upperbound {} deadzone_upperbound {}, expected deadzone_upperbound <= livezone_upperbound", livezone_upperbound, deadzone_upperbound)]
DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
livezone_upperbound: f32,
deadzone_upperbound: f32,
},
#[error("invalid threshold {0}, expected 0.0 <= threshold <= 2.0")]
Threshold(f32),
}
#[derive(Error, Debug, PartialEq)]
pub enum ButtonSettingsError {
#[error("invalid release_threshold {0}, expected value [0.0..=1.0]")]
ReleaseThresholdOutOfRange(f32),
#[error("invalid press_threshold {0}, expected [0.0..=1.0]")]
PressThresholdOutOfRange(f32),
#[error("invalid parameter values release_threshold {} press_threshold {}, expected release_threshold <= press_threshold", release_threshold, press_threshold)]
ReleaseThresholdGreaterThanPressThreshold {
press_threshold: f32,
release_threshold: f32,
},
}
#[derive(Component, Debug)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, Component, Default)
)]
#[require(GamepadSettings)]
pub struct Gamepad {
pub(crate) vendor_id: Option<u16>,
pub(crate) product_id: Option<u16>,
pub(crate) digital: ButtonInput<GamepadButton>,
pub(crate) analog: Axis<GamepadInput>,
}
impl Gamepad {
pub fn vendor_id(&self) -> Option<u16> {
self.vendor_id
}
pub fn product_id(&self) -> Option<u16> {
self.product_id
}
pub fn get(&self, input: impl Into<GamepadInput>) -> Option<f32> {
self.analog.get(input.into())
}
pub fn get_unclamped(&self, input: impl Into<GamepadInput>) -> Option<f32> {
self.analog.get_unclamped(input.into())
}
pub fn left_stick(&self) -> Vec2 {
Vec2 {
x: self.get(GamepadAxis::LeftStickX).unwrap_or(0.0),
y: self.get(GamepadAxis::LeftStickY).unwrap_or(0.0),
}
}
pub fn right_stick(&self) -> Vec2 {
Vec2 {
x: self.get(GamepadAxis::RightStickX).unwrap_or(0.0),
y: self.get(GamepadAxis::RightStickY).unwrap_or(0.0),
}
}
pub fn dpad(&self) -> Vec2 {
Vec2 {
x: self.get(GamepadButton::DPadRight).unwrap_or(0.0)
- self.get(GamepadButton::DPadLeft).unwrap_or(0.0),
y: self.get(GamepadButton::DPadUp).unwrap_or(0.0)
- self.get(GamepadButton::DPadDown).unwrap_or(0.0),
}
}
pub fn pressed(&self, button_type: GamepadButton) -> bool {
self.digital.pressed(button_type)
}
pub fn any_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
self.digital.any_pressed(button_inputs)
}
pub fn all_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
self.digital.all_pressed(button_inputs)
}
pub fn just_pressed(&self, button_type: GamepadButton) -> bool {
self.digital.just_pressed(button_type)
}
pub fn any_just_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
self.digital.any_just_pressed(button_inputs)
}
pub fn all_just_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
self.digital.all_just_pressed(button_inputs)
}
pub fn just_released(&self, button_type: GamepadButton) -> bool {
self.digital.just_released(button_type)
}
pub fn any_just_released(
&self,
button_inputs: impl IntoIterator<Item = GamepadButton>,
) -> bool {
self.digital.any_just_released(button_inputs)
}
pub fn all_just_released(
&self,
button_inputs: impl IntoIterator<Item = GamepadButton>,
) -> bool {
self.digital.all_just_released(button_inputs)
}
pub fn get_pressed(&self) -> impl Iterator<Item = &GamepadButton> {
self.digital.get_pressed()
}
pub fn get_just_pressed(&self) -> impl Iterator<Item = &GamepadButton> {
self.digital.get_just_pressed()
}
pub fn get_just_released(&self) -> impl Iterator<Item = &GamepadButton> {
self.digital.get_just_released()
}
pub fn get_analog_axes(&self) -> impl Iterator<Item = &GamepadInput> {
self.analog.all_axes()
}
pub fn digital(&self) -> &ButtonInput<GamepadButton> {
&self.digital
}
pub fn digital_mut(&mut self) -> &mut ButtonInput<GamepadButton> {
&mut self.digital
}
pub fn analog(&self) -> &Axis<GamepadInput> {
&self.analog
}
pub fn analog_mut(&mut self) -> &mut Axis<GamepadInput> {
&mut self.analog
}
}
impl Default for Gamepad {
fn default() -> Self {
let mut analog = Axis::default();
for button in GamepadButton::all().iter().copied() {
analog.set(button, 0.0);
}
for axis_type in GamepadAxis::all().iter().copied() {
analog.set(axis_type, 0.0);
}
Self {
vendor_id: None,
product_id: None,
digital: Default::default(),
analog,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, Hash, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum GamepadButton {
South,
East,
North,
West,
C,
Z,
LeftTrigger,
LeftTrigger2,
RightTrigger,
RightTrigger2,
Select,
Start,
Mode,
LeftThumb,
RightThumb,
DPadUp,
DPadDown,
DPadLeft,
DPadRight,
Other(u8),
}
impl GamepadButton {
pub const fn all() -> [GamepadButton; 19] {
[
GamepadButton::South,
GamepadButton::East,
GamepadButton::North,
GamepadButton::West,
GamepadButton::C,
GamepadButton::Z,
GamepadButton::LeftTrigger,
GamepadButton::LeftTrigger2,
GamepadButton::RightTrigger,
GamepadButton::RightTrigger2,
GamepadButton::Select,
GamepadButton::Start,
GamepadButton::Mode,
GamepadButton::LeftThumb,
GamepadButton::RightThumb,
GamepadButton::DPadUp,
GamepadButton::DPadDown,
GamepadButton::DPadLeft,
GamepadButton::DPadRight,
]
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum GamepadAxis {
LeftStickX,
LeftStickY,
LeftZ,
RightStickX,
RightStickY,
RightZ,
Other(u8),
}
impl GamepadAxis {
pub const fn all() -> [GamepadAxis; 6] {
[
GamepadAxis::LeftStickX,
GamepadAxis::LeftStickY,
GamepadAxis::LeftZ,
GamepadAxis::RightStickX,
GamepadAxis::RightStickY,
GamepadAxis::RightZ,
]
}
}
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, From)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, Hash, PartialEq, Clone)
)]
pub enum GamepadInput {
Axis(GamepadAxis),
Button(GamepadButton),
}
#[derive(Component, Clone, Default, Debug)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, Default, Component, Clone)
)]
pub struct GamepadSettings {
pub default_button_settings: ButtonSettings,
pub default_axis_settings: AxisSettings,
pub default_button_axis_settings: ButtonAxisSettings,
pub button_settings: HashMap<GamepadButton, ButtonSettings>,
pub axis_settings: HashMap<GamepadAxis, AxisSettings>,
pub button_axis_settings: HashMap<GamepadButton, ButtonAxisSettings>,
}
impl GamepadSettings {
pub fn get_button_settings(&self, button: GamepadButton) -> &ButtonSettings {
self.button_settings
.get(&button)
.unwrap_or(&self.default_button_settings)
}
pub fn get_axis_settings(&self, axis: GamepadAxis) -> &AxisSettings {
self.axis_settings
.get(&axis)
.unwrap_or(&self.default_axis_settings)
}
pub fn get_button_axis_settings(&self, button: GamepadButton) -> &ButtonAxisSettings {
self.button_axis_settings
.get(&button)
.unwrap_or(&self.default_button_axis_settings)
}
}
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, Default, Clone)
)]
pub struct ButtonSettings {
press_threshold: f32,
release_threshold: f32,
}
impl Default for ButtonSettings {
fn default() -> Self {
ButtonSettings {
press_threshold: 0.75,
release_threshold: 0.65,
}
}
}
impl ButtonSettings {
pub fn new(
press_threshold: f32,
release_threshold: f32,
) -> Result<ButtonSettings, ButtonSettingsError> {
if !(0.0..=1.0).contains(&release_threshold) {
Err(ButtonSettingsError::ReleaseThresholdOutOfRange(
release_threshold,
))
} else if !(0.0..=1.0).contains(&press_threshold) {
Err(ButtonSettingsError::PressThresholdOutOfRange(
press_threshold,
))
} else if release_threshold > press_threshold {
Err(
ButtonSettingsError::ReleaseThresholdGreaterThanPressThreshold {
press_threshold,
release_threshold,
},
)
} else {
Ok(ButtonSettings {
press_threshold,
release_threshold,
})
}
}
pub fn is_pressed(&self, value: f32) -> bool {
value >= self.press_threshold
}
pub fn is_released(&self, value: f32) -> bool {
value <= self.release_threshold
}
pub fn press_threshold(&self) -> f32 {
self.press_threshold
}
pub fn try_set_press_threshold(&mut self, value: f32) -> Result<(), ButtonSettingsError> {
if (self.release_threshold..=1.0).contains(&value) {
self.press_threshold = value;
Ok(())
} else if !(0.0..1.0).contains(&value) {
Err(ButtonSettingsError::PressThresholdOutOfRange(value))
} else {
Err(
ButtonSettingsError::ReleaseThresholdGreaterThanPressThreshold {
press_threshold: value,
release_threshold: self.release_threshold,
},
)
}
}
pub fn set_press_threshold(&mut self, value: f32) -> f32 {
self.try_set_press_threshold(value).ok();
self.press_threshold
}
pub fn release_threshold(&self) -> f32 {
self.release_threshold
}
pub fn try_set_release_threshold(&mut self, value: f32) -> Result<(), ButtonSettingsError> {
if (0.0..=self.press_threshold).contains(&value) {
self.release_threshold = value;
Ok(())
} else if !(0.0..1.0).contains(&value) {
Err(ButtonSettingsError::ReleaseThresholdOutOfRange(value))
} else {
Err(
ButtonSettingsError::ReleaseThresholdGreaterThanPressThreshold {
press_threshold: self.press_threshold,
release_threshold: value,
},
)
}
}
pub fn set_release_threshold(&mut self, value: f32) -> f32 {
self.try_set_release_threshold(value).ok();
self.release_threshold
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
pub struct AxisSettings {
livezone_upperbound: f32,
deadzone_upperbound: f32,
deadzone_lowerbound: f32,
livezone_lowerbound: f32,
threshold: f32,
}
impl Default for AxisSettings {
fn default() -> Self {
AxisSettings {
livezone_upperbound: 1.0,
deadzone_upperbound: 0.05,
deadzone_lowerbound: -0.05,
livezone_lowerbound: -1.0,
threshold: 0.01,
}
}
}
impl AxisSettings {
pub fn new(
livezone_lowerbound: f32,
deadzone_lowerbound: f32,
deadzone_upperbound: f32,
livezone_upperbound: f32,
threshold: f32,
) -> Result<AxisSettings, AxisSettingsError> {
if !(-1.0..=0.0).contains(&livezone_lowerbound) {
Err(AxisSettingsError::LiveZoneLowerBoundOutOfRange(
livezone_lowerbound,
))
} else if !(-1.0..=0.0).contains(&deadzone_lowerbound) {
Err(AxisSettingsError::DeadZoneLowerBoundOutOfRange(
deadzone_lowerbound,
))
} else if !(0.0..=1.0).contains(&deadzone_upperbound) {
Err(AxisSettingsError::DeadZoneUpperBoundOutOfRange(
deadzone_upperbound,
))
} else if !(0.0..=1.0).contains(&livezone_upperbound) {
Err(AxisSettingsError::LiveZoneUpperBoundOutOfRange(
livezone_upperbound,
))
} else if livezone_lowerbound > deadzone_lowerbound {
Err(
AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
livezone_lowerbound,
deadzone_lowerbound,
},
)
} else if deadzone_upperbound > livezone_upperbound {
Err(
AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
livezone_upperbound,
deadzone_upperbound,
},
)
} else if !(0.0..=2.0).contains(&threshold) {
Err(AxisSettingsError::Threshold(threshold))
} else {
Ok(Self {
livezone_lowerbound,
deadzone_lowerbound,
deadzone_upperbound,
livezone_upperbound,
threshold,
})
}
}
pub fn livezone_upperbound(&self) -> f32 {
self.livezone_upperbound
}
pub fn try_set_livezone_upperbound(&mut self, value: f32) -> Result<(), AxisSettingsError> {
if !(0.0..=1.0).contains(&value) {
Err(AxisSettingsError::LiveZoneUpperBoundOutOfRange(value))
} else if value < self.deadzone_upperbound {
Err(
AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
livezone_upperbound: value,
deadzone_upperbound: self.deadzone_upperbound,
},
)
} else {
self.livezone_upperbound = value;
Ok(())
}
}
pub fn set_livezone_upperbound(&mut self, value: f32) -> f32 {
self.try_set_livezone_upperbound(value).ok();
self.livezone_upperbound
}
pub fn deadzone_upperbound(&self) -> f32 {
self.deadzone_upperbound
}
pub fn try_set_deadzone_upperbound(&mut self, value: f32) -> Result<(), AxisSettingsError> {
if !(0.0..=1.0).contains(&value) {
Err(AxisSettingsError::DeadZoneUpperBoundOutOfRange(value))
} else if self.livezone_upperbound < value {
Err(
AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
livezone_upperbound: self.livezone_upperbound,
deadzone_upperbound: value,
},
)
} else {
self.deadzone_upperbound = value;
Ok(())
}
}
pub fn set_deadzone_upperbound(&mut self, value: f32) -> f32 {
self.try_set_deadzone_upperbound(value).ok();
self.deadzone_upperbound
}
pub fn livezone_lowerbound(&self) -> f32 {
self.livezone_lowerbound
}
pub fn try_set_livezone_lowerbound(&mut self, value: f32) -> Result<(), AxisSettingsError> {
if !(-1.0..=0.0).contains(&value) {
Err(AxisSettingsError::LiveZoneLowerBoundOutOfRange(value))
} else if value > self.deadzone_lowerbound {
Err(
AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
livezone_lowerbound: value,
deadzone_lowerbound: self.deadzone_lowerbound,
},
)
} else {
self.livezone_lowerbound = value;
Ok(())
}
}
pub fn set_livezone_lowerbound(&mut self, value: f32) -> f32 {
self.try_set_livezone_lowerbound(value).ok();
self.livezone_lowerbound
}
pub fn deadzone_lowerbound(&self) -> f32 {
self.deadzone_lowerbound
}
pub fn try_set_deadzone_lowerbound(&mut self, value: f32) -> Result<(), AxisSettingsError> {
if !(-1.0..=0.0).contains(&value) {
Err(AxisSettingsError::DeadZoneLowerBoundOutOfRange(value))
} else if self.livezone_lowerbound > value {
Err(
AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
livezone_lowerbound: self.livezone_lowerbound,
deadzone_lowerbound: value,
},
)
} else {
self.deadzone_lowerbound = value;
Ok(())
}
}
pub fn set_deadzone_lowerbound(&mut self, value: f32) -> f32 {
self.try_set_deadzone_lowerbound(value).ok();
self.deadzone_lowerbound
}
pub fn threshold(&self) -> f32 {
self.threshold
}
pub fn try_set_threshold(&mut self, value: f32) -> Result<(), AxisSettingsError> {
if !(0.0..=2.0).contains(&value) {
Err(AxisSettingsError::Threshold(value))
} else {
self.threshold = value;
Ok(())
}
}
pub fn set_threshold(&mut self, value: f32) -> f32 {
self.try_set_threshold(value).ok();
self.threshold
}
pub fn clamp(&self, raw_value: f32) -> f32 {
if self.deadzone_lowerbound <= raw_value && raw_value <= self.deadzone_upperbound {
0.0
} else if raw_value >= self.livezone_upperbound {
1.0
} else if raw_value <= self.livezone_lowerbound {
-1.0
} else {
raw_value
}
}
fn should_register_change(&self, new_raw_value: f32, old_raw_value: Option<f32>) -> bool {
match old_raw_value {
None => true,
Some(old_raw_value) => ops::abs(new_raw_value - old_raw_value) >= self.threshold,
}
}
fn filter(
&self,
new_raw_value: f32,
old_raw_value: Option<f32>,
) -> Option<FilteredAxisPosition> {
let clamped_unscaled = self.clamp(new_raw_value);
match self.should_register_change(clamped_unscaled, old_raw_value) {
true => Some(FilteredAxisPosition {
scaled: self.get_axis_position_from_value(clamped_unscaled),
raw: new_raw_value,
}),
false => None,
}
}
#[inline(always)]
fn get_axis_position_from_value(&self, value: f32) -> ScaledAxisWithDeadZonePosition {
if value < self.deadzone_upperbound && value > self.deadzone_lowerbound {
ScaledAxisWithDeadZonePosition::Dead
} else if value > self.livezone_upperbound {
ScaledAxisWithDeadZonePosition::AboveHigh
} else if value < self.livezone_lowerbound {
ScaledAxisWithDeadZonePosition::BelowLow
} else if value >= self.deadzone_upperbound {
ScaledAxisWithDeadZonePosition::High(linear_remapping(
value,
self.deadzone_upperbound..=self.livezone_upperbound,
0.0..=1.0,
))
} else if value <= self.deadzone_lowerbound {
ScaledAxisWithDeadZonePosition::Low(linear_remapping(
value,
self.livezone_lowerbound..=self.deadzone_lowerbound,
-1.0..=0.0,
))
} else {
unreachable!();
}
}
}
fn linear_remapping(value: f32, old: RangeInclusive<f32>, new: RangeInclusive<f32>) -> f32 {
((value - old.start()) / (old.end() - old.start())) * (new.end() - new.start()) + new.start()
}
#[derive(Debug, Clone, Copy)]
enum ScaledAxisWithDeadZonePosition {
BelowLow,
Low(f32),
Dead,
High(f32),
AboveHigh,
}
struct FilteredAxisPosition {
scaled: ScaledAxisWithDeadZonePosition,
raw: f32,
}
impl ScaledAxisWithDeadZonePosition {
fn to_f32(self) -> f32 {
match self {
ScaledAxisWithDeadZonePosition::BelowLow => -1.,
ScaledAxisWithDeadZonePosition::Low(scaled)
| ScaledAxisWithDeadZonePosition::High(scaled) => scaled,
ScaledAxisWithDeadZonePosition::Dead => 0.,
ScaledAxisWithDeadZonePosition::AboveHigh => 1.,
}
}
}
#[derive(Debug, Clone, Copy)]
enum ScaledAxisPosition {
ClampedLow,
Scaled(f32),
ClampedHigh,
}
struct FilteredButtonAxisPosition {
scaled: ScaledAxisPosition,
raw: f32,
}
impl ScaledAxisPosition {
fn to_f32(self) -> f32 {
match self {
ScaledAxisPosition::ClampedLow => 0.,
ScaledAxisPosition::Scaled(scaled) => scaled,
ScaledAxisPosition::ClampedHigh => 1.,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, Default, Clone)
)]
pub struct ButtonAxisSettings {
pub high: f32,
pub low: f32,
pub threshold: f32,
}
impl Default for ButtonAxisSettings {
fn default() -> Self {
ButtonAxisSettings {
high: 0.95,
low: 0.05,
threshold: 0.01,
}
}
}
impl ButtonAxisSettings {
fn clamp(&self, raw_value: f32) -> f32 {
if raw_value <= self.low {
return 0.0;
}
if raw_value >= self.high {
return 1.0;
}
raw_value
}
fn should_register_change(&self, new_raw_value: f32, old_raw_value: Option<f32>) -> bool {
match old_raw_value {
None => true,
Some(old_raw_value) => ops::abs(new_raw_value - old_raw_value) >= self.threshold,
}
}
fn filter(
&self,
new_raw_value: f32,
old_raw_value: Option<f32>,
) -> Option<FilteredButtonAxisPosition> {
let clamped_unscaled = self.clamp(new_raw_value);
match self.should_register_change(clamped_unscaled, old_raw_value) {
true => Some(FilteredButtonAxisPosition {
scaled: self.get_axis_position_from_value(clamped_unscaled),
raw: new_raw_value,
}),
false => None,
}
}
fn get_axis_position_from_value(&self, value: f32) -> ScaledAxisPosition {
if value <= self.low {
ScaledAxisPosition::ClampedLow
} else if value >= self.high {
ScaledAxisPosition::ClampedHigh
} else {
ScaledAxisPosition::Scaled(linear_remapping(value, self.low..=self.high, 0.0..=1.0))
}
}
}
pub fn gamepad_connection_system(
mut commands: Commands,
mut connection_events: EventReader<GamepadConnectionEvent>,
) {
for connection_event in connection_events.read() {
let id = connection_event.gamepad;
match &connection_event.connection {
GamepadConnection::Connected {
name,
vendor_id,
product_id,
} => {
let Ok(mut gamepad) = commands.get_entity(id) else {
warn!("Gamepad {id} removed before handling connection event.");
continue;
};
gamepad.insert((
Name::new(name.clone()),
Gamepad {
vendor_id: *vendor_id,
product_id: *product_id,
..Default::default()
},
));
info!("Gamepad {id} connected.");
}
GamepadConnection::Disconnected => {
let Ok(mut gamepad) = commands.get_entity(id) else {
warn!("Gamepad {id} removed before handling disconnection event. You can ignore this if you manually removed it.");
continue;
};
gamepad.remove::<Gamepad>();
info!("Gamepad {id} disconnected.");
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum GamepadConnection {
Connected {
name: String,
vendor_id: Option<u16>,
product_id: Option<u16>,
},
Disconnected,
}
pub fn gamepad_event_processing_system(
mut gamepads: Query<(&mut Gamepad, &GamepadSettings)>,
mut raw_events: EventReader<RawGamepadEvent>,
mut processed_events: EventWriter<GamepadEvent>,
mut processed_axis_events: EventWriter<GamepadAxisChangedEvent>,
mut processed_digital_events: EventWriter<GamepadButtonStateChangedEvent>,
mut processed_analog_events: EventWriter<GamepadButtonChangedEvent>,
) {
for (mut gamepad, _) in gamepads.iter_mut() {
gamepad.bypass_change_detection().digital.clear();
}
for event in raw_events.read() {
match event {
RawGamepadEvent::Connection(send_event) => {
processed_events.write(GamepadEvent::from(send_event.clone()));
}
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
gamepad,
axis,
value,
}) => {
let (gamepad, axis, value) = (*gamepad, *axis, *value);
let Ok((mut gamepad_axis, gamepad_settings)) = gamepads.get_mut(gamepad) else {
continue;
};
let Some(filtered_value) = gamepad_settings
.get_axis_settings(axis)
.filter(value, gamepad_axis.get(axis))
else {
continue;
};
gamepad_axis.analog.set(axis, filtered_value.raw);
let send_event =
GamepadAxisChangedEvent::new(gamepad, axis, filtered_value.scaled.to_f32());
processed_axis_events.write(send_event);
processed_events.write(GamepadEvent::from(send_event));
}
RawGamepadEvent::Button(RawGamepadButtonChangedEvent {
gamepad,
button,
value,
}) => {
let (gamepad, button, value) = (*gamepad, *button, *value);
let Ok((mut gamepad_buttons, settings)) = gamepads.get_mut(gamepad) else {
continue;
};
let Some(filtered_value) = settings
.get_button_axis_settings(button)
.filter(value, gamepad_buttons.get(button))
else {
continue;
};
let button_settings = settings.get_button_settings(button);
gamepad_buttons.analog.set(button, filtered_value.raw);
if button_settings.is_released(filtered_value.raw) {
if gamepad_buttons.pressed(button) {
processed_digital_events.write(GamepadButtonStateChangedEvent::new(
gamepad,
button,
ButtonState::Released,
));
}
gamepad_buttons.digital.release(button);
} else if button_settings.is_pressed(filtered_value.raw) {
if !gamepad_buttons.pressed(button) {
processed_digital_events.write(GamepadButtonStateChangedEvent::new(
gamepad,
button,
ButtonState::Pressed,
));
}
gamepad_buttons.digital.press(button);
};
let button_state = if gamepad_buttons.digital.pressed(button) {
ButtonState::Pressed
} else {
ButtonState::Released
};
let send_event = GamepadButtonChangedEvent::new(
gamepad,
button,
button_state,
filtered_value.scaled.to_f32(),
);
processed_analog_events.write(send_event);
processed_events.write(GamepadEvent::from(send_event));
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
pub struct GamepadRumbleIntensity {
pub strong_motor: f32,
pub weak_motor: f32,
}
impl GamepadRumbleIntensity {
pub const MAX: Self = GamepadRumbleIntensity {
strong_motor: 1.0,
weak_motor: 1.0,
};
pub const WEAK_MAX: Self = GamepadRumbleIntensity {
strong_motor: 0.0,
weak_motor: 1.0,
};
pub const STRONG_MAX: Self = GamepadRumbleIntensity {
strong_motor: 1.0,
weak_motor: 0.0,
};
pub const fn weak_motor(intensity: f32) -> Self {
Self {
weak_motor: intensity,
strong_motor: 0.0,
}
}
pub const fn strong_motor(intensity: f32) -> Self {
Self {
strong_motor: intensity,
weak_motor: 0.0,
}
}
}
#[doc(alias = "haptic feedback")]
#[doc(alias = "force feedback")]
#[doc(alias = "vibration")]
#[doc(alias = "vibrate")]
#[derive(BufferedEvent, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Clone))]
pub enum GamepadRumbleRequest {
Add {
duration: Duration,
intensity: GamepadRumbleIntensity,
gamepad: Entity,
},
Stop {
gamepad: Entity,
},
}
impl GamepadRumbleRequest {
pub fn gamepad(&self) -> Entity {
match self {
Self::Add { gamepad, .. } | Self::Stop { gamepad } => *gamepad,
}
}
}
#[cfg(test)]
mod tests {
use super::{
gamepad_connection_system, gamepad_event_processing_system, AxisSettings,
AxisSettingsError, ButtonAxisSettings, ButtonSettings, ButtonSettingsError, Gamepad,
GamepadAxis, GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent,
GamepadButtonStateChangedEvent,
GamepadConnection::{Connected, Disconnected},
GamepadConnectionEvent, GamepadEvent, GamepadSettings, RawGamepadAxisChangedEvent,
RawGamepadButtonChangedEvent, RawGamepadEvent,
};
use crate::ButtonState;
use alloc::string::ToString;
use bevy_app::{App, PreUpdate};
use bevy_ecs::entity::Entity;
use bevy_ecs::event::Events;
use bevy_ecs::schedule::IntoScheduleConfigs;
fn test_button_axis_settings_filter(
settings: ButtonAxisSettings,
new_raw_value: f32,
old_raw_value: Option<f32>,
expected: Option<f32>,
) {
let actual = settings
.filter(new_raw_value, old_raw_value)
.map(|f| f.scaled.to_f32());
assert_eq!(
expected, actual,
"Testing filtering for {settings:?} with new_raw_value = {new_raw_value:?}, old_raw_value = {old_raw_value:?}",
);
}
#[test]
fn test_button_axis_settings_default_filter() {
let cases = [
(1.0, None, Some(1.0)),
(0.99, None, Some(1.0)),
(0.96, None, Some(1.0)),
(0.95, None, Some(1.0)),
(0.9499, None, Some(0.9998889)),
(0.84, None, Some(0.87777776)),
(0.43, None, Some(0.42222223)),
(0.05001, None, Some(0.000011109644)),
(0.05, None, Some(0.0)),
(0.04, None, Some(0.0)),
(0.01, None, Some(0.0)),
(0.0, None, Some(0.0)),
];
for (new_raw_value, old_raw_value, expected) in cases {
let settings = ButtonAxisSettings::default();
test_button_axis_settings_filter(settings, new_raw_value, old_raw_value, expected);
}
}
#[test]
fn test_button_axis_settings_default_filter_with_old_raw_value() {
let cases = [
(0.43, Some(0.44001), Some(0.42222223)),
(0.43, Some(0.44), None),
(0.43, Some(0.43), None),
(0.43, Some(0.41999), Some(0.42222223)),
(0.43, Some(0.17), Some(0.42222223)),
(0.43, Some(0.84), Some(0.42222223)),
(0.05, Some(0.055), Some(0.0)),
(0.95, Some(0.945), Some(1.0)),
];
for (new_raw_value, old_raw_value, expected) in cases {
let settings = ButtonAxisSettings::default();
test_button_axis_settings_filter(settings, new_raw_value, old_raw_value, expected);
}
}
fn test_axis_settings_filter(
settings: AxisSettings,
new_raw_value: f32,
old_raw_value: Option<f32>,
expected: Option<f32>,
) {
let actual = settings.filter(new_raw_value, old_raw_value);
assert_eq!(
expected, actual.map(|f| f.scaled.to_f32()),
"Testing filtering for {settings:?} with new_raw_value = {new_raw_value:?}, old_raw_value = {old_raw_value:?}",
);
}
#[test]
fn test_axis_settings_default_filter() {
let cases = [
(1.0, Some(1.0)),
(0.99, Some(1.0)),
(0.96, Some(1.0)),
(0.95, Some(1.0)),
(0.9499, Some(0.9998889)),
(0.84, Some(0.87777776)),
(0.43, Some(0.42222223)),
(0.05001, Some(0.000011109644)),
(0.05, Some(0.0)),
(0.04, Some(0.0)),
(0.01, Some(0.0)),
(0.0, Some(0.0)),
(-1.0, Some(-1.0)),
(-0.99, Some(-1.0)),
(-0.96, Some(-1.0)),
(-0.95, Some(-1.0)),
(-0.9499, Some(-0.9998889)),
(-0.84, Some(-0.87777776)),
(-0.43, Some(-0.42222226)),
(-0.05001, Some(-0.000011146069)),
(-0.05, Some(0.0)),
(-0.04, Some(0.0)),
(-0.01, Some(0.0)),
];
for (new_raw_value, expected) in cases {
let settings = AxisSettings::new(-0.95, -0.05, 0.05, 0.95, 0.01).unwrap();
test_axis_settings_filter(settings, new_raw_value, None, expected);
}
}
#[test]
fn test_axis_settings_default_filter_with_old_raw_values() {
let threshold = 0.01;
let cases = [
(0.43, Some(0.43 + threshold * 1.1), Some(0.42222223)),
(0.43, Some(0.43 - threshold * 1.1), Some(0.42222223)),
(0.43, Some(0.43 + threshold * 0.9), None),
(0.43, Some(0.43 - threshold * 0.9), None),
(-0.43, Some(-0.43 + threshold * 1.1), Some(-0.42222226)),
(-0.43, Some(-0.43 - threshold * 1.1), Some(-0.42222226)),
(-0.43, Some(-0.43 + threshold * 0.9), None),
(-0.43, Some(-0.43 - threshold * 0.9), None),
(0.05, Some(0.0), None),
(0.06, Some(0.0), Some(0.0111111095)),
(-0.05, Some(0.0), None),
(-0.06, Some(0.0), Some(-0.011111081)),
(0.95, Some(1.0), None),
(0.94, Some(1.0), Some(0.9888889)),
(-0.95, Some(-1.0), None),
(-0.94, Some(-1.0), Some(-0.9888889)),
];
for (new_raw_value, old_raw_value, expected) in cases {
let settings = AxisSettings::new(-0.95, -0.05, 0.05, 0.95, threshold).unwrap();
test_axis_settings_filter(settings, new_raw_value, old_raw_value, expected);
}
}
#[test]
fn test_button_settings_default_is_pressed() {
let cases = [
(1.0, true),
(0.95, true),
(0.9, true),
(0.8, true),
(0.75, true),
(0.7, false),
(0.65, false),
(0.5, false),
(0.0, false),
];
for (value, expected) in cases {
let settings = ButtonSettings::default();
let actual = settings.is_pressed(value);
assert_eq!(
expected, actual,
"testing ButtonSettings::is_pressed() for value: {value}",
);
}
}
#[test]
fn test_button_settings_default_is_released() {
let cases = [
(1.0, false),
(0.95, false),
(0.9, false),
(0.8, false),
(0.75, false),
(0.7, false),
(0.65, true),
(0.5, true),
(0.0, true),
];
for (value, expected) in cases {
let settings = ButtonSettings::default();
let actual = settings.is_released(value);
assert_eq!(
expected, actual,
"testing ButtonSettings::is_released() for value: {value}",
);
}
}
#[test]
fn test_new_button_settings_given_valid_parameters() {
let cases = [
(1.0, 0.0),
(1.0, 1.0),
(1.0, 0.9),
(0.9, 0.9),
(0.9, 0.0),
(0.0, 0.0),
];
for (press_threshold, release_threshold) in cases {
let bs = ButtonSettings::new(press_threshold, release_threshold);
match bs {
Ok(button_settings) => {
assert_eq!(button_settings.press_threshold, press_threshold);
assert_eq!(button_settings.release_threshold, release_threshold);
}
Err(_) => {
panic!(
"ButtonSettings::new({press_threshold}, {release_threshold}) should be valid"
);
}
}
}
}
#[test]
fn test_new_button_settings_given_invalid_parameters() {
let cases = [
(1.1, 0.0),
(1.1, 1.0),
(1.0, 1.1),
(-1.0, 0.9),
(-1.0, 0.0),
(-1.0, -0.4),
(0.9, 1.0),
(0.0, 0.1),
];
for (press_threshold, release_threshold) in cases {
let bs = ButtonSettings::new(press_threshold, release_threshold);
match bs {
Ok(_) => {
panic!(
"ButtonSettings::new({press_threshold}, {release_threshold}) should be invalid"
);
}
Err(err_code) => match err_code {
ButtonSettingsError::PressThresholdOutOfRange(_press_threshold) => {}
ButtonSettingsError::ReleaseThresholdGreaterThanPressThreshold {
press_threshold: _press_threshold,
release_threshold: _release_threshold,
} => {}
ButtonSettingsError::ReleaseThresholdOutOfRange(_release_threshold) => {}
},
}
}
}
#[test]
fn test_try_out_of_range_axis_settings() {
let mut axis_settings = AxisSettings::default();
assert_eq!(
AxisSettings::new(-0.95, -0.05, 0.05, 0.95, 0.001),
Ok(AxisSettings {
livezone_lowerbound: -0.95,
deadzone_lowerbound: -0.05,
deadzone_upperbound: 0.05,
livezone_upperbound: 0.95,
threshold: 0.001,
})
);
assert_eq!(
Err(AxisSettingsError::LiveZoneLowerBoundOutOfRange(-2.0)),
axis_settings.try_set_livezone_lowerbound(-2.0)
);
assert_eq!(
Err(AxisSettingsError::LiveZoneLowerBoundOutOfRange(0.1)),
axis_settings.try_set_livezone_lowerbound(0.1)
);
assert_eq!(
Err(AxisSettingsError::DeadZoneLowerBoundOutOfRange(-2.0)),
axis_settings.try_set_deadzone_lowerbound(-2.0)
);
assert_eq!(
Err(AxisSettingsError::DeadZoneLowerBoundOutOfRange(0.1)),
axis_settings.try_set_deadzone_lowerbound(0.1)
);
assert_eq!(
Err(AxisSettingsError::DeadZoneUpperBoundOutOfRange(-0.1)),
axis_settings.try_set_deadzone_upperbound(-0.1)
);
assert_eq!(
Err(AxisSettingsError::DeadZoneUpperBoundOutOfRange(1.1)),
axis_settings.try_set_deadzone_upperbound(1.1)
);
assert_eq!(
Err(AxisSettingsError::LiveZoneUpperBoundOutOfRange(-0.1)),
axis_settings.try_set_livezone_upperbound(-0.1)
);
assert_eq!(
Err(AxisSettingsError::LiveZoneUpperBoundOutOfRange(1.1)),
axis_settings.try_set_livezone_upperbound(1.1)
);
axis_settings.set_livezone_lowerbound(-0.7);
axis_settings.set_deadzone_lowerbound(-0.3);
assert_eq!(
Err(
AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
livezone_lowerbound: -0.1,
deadzone_lowerbound: -0.3,
}
),
axis_settings.try_set_livezone_lowerbound(-0.1)
);
assert_eq!(
Err(
AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
livezone_lowerbound: -0.7,
deadzone_lowerbound: -0.9
}
),
axis_settings.try_set_deadzone_lowerbound(-0.9)
);
axis_settings.set_deadzone_upperbound(0.3);
axis_settings.set_livezone_upperbound(0.7);
assert_eq!(
Err(
AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
deadzone_upperbound: 0.8,
livezone_upperbound: 0.7
}
),
axis_settings.try_set_deadzone_upperbound(0.8)
);
assert_eq!(
Err(
AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
deadzone_upperbound: 0.3,
livezone_upperbound: 0.1
}
),
axis_settings.try_set_livezone_upperbound(0.1)
);
}
struct TestContext {
pub app: App,
}
impl TestContext {
pub fn new() -> Self {
let mut app = App::new();
app.add_systems(
PreUpdate,
(
gamepad_connection_system,
gamepad_event_processing_system.after(gamepad_connection_system),
),
)
.add_event::<GamepadEvent>()
.add_event::<GamepadConnectionEvent>()
.add_event::<RawGamepadButtonChangedEvent>()
.add_event::<GamepadButtonChangedEvent>()
.add_event::<GamepadButtonStateChangedEvent>()
.add_event::<GamepadAxisChangedEvent>()
.add_event::<RawGamepadAxisChangedEvent>()
.add_event::<RawGamepadEvent>();
Self { app }
}
pub fn update(&mut self) {
self.app.update();
}
pub fn send_gamepad_connection_event(&mut self, gamepad: Option<Entity>) -> Entity {
let gamepad = gamepad.unwrap_or_else(|| self.app.world_mut().spawn_empty().id());
self.app
.world_mut()
.resource_mut::<Events<GamepadConnectionEvent>>()
.write(GamepadConnectionEvent::new(
gamepad,
Connected {
name: "Test gamepad".to_string(),
vendor_id: None,
product_id: None,
},
));
gamepad
}
pub fn send_gamepad_disconnection_event(&mut self, gamepad: Entity) {
self.app
.world_mut()
.resource_mut::<Events<GamepadConnectionEvent>>()
.write(GamepadConnectionEvent::new(gamepad, Disconnected));
}
pub fn send_raw_gamepad_event(&mut self, event: RawGamepadEvent) {
self.app
.world_mut()
.resource_mut::<Events<RawGamepadEvent>>()
.write(event);
}
pub fn send_raw_gamepad_event_batch(
&mut self,
events: impl IntoIterator<Item = RawGamepadEvent>,
) {
self.app
.world_mut()
.resource_mut::<Events<RawGamepadEvent>>()
.write_batch(events);
}
}
#[test]
fn connection_event() {
let mut ctx = TestContext::new();
assert_eq!(
ctx.app
.world_mut()
.query::<&Gamepad>()
.iter(ctx.app.world())
.len(),
0
);
ctx.send_gamepad_connection_event(None);
ctx.update();
assert_eq!(
ctx.app
.world_mut()
.query::<(&Gamepad, &GamepadSettings)>()
.iter(ctx.app.world())
.len(),
1
);
}
#[test]
fn disconnection_event() {
let mut ctx = TestContext::new();
assert_eq!(
ctx.app
.world_mut()
.query::<&Gamepad>()
.iter(ctx.app.world())
.len(),
0
);
let entity = ctx.send_gamepad_connection_event(None);
ctx.update();
assert_eq!(
ctx.app
.world_mut()
.query::<(&Gamepad, &GamepadSettings)>()
.iter(ctx.app.world())
.len(),
1
);
ctx.send_gamepad_disconnection_event(entity);
ctx.update();
assert!(ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.is_err());
assert!(ctx
.app
.world_mut()
.query::<&GamepadSettings>()
.get(ctx.app.world(), entity)
.is_ok());
ctx.send_gamepad_disconnection_event(entity);
ctx.update();
assert!(ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.is_err());
assert!(ctx
.app
.world_mut()
.query::<&GamepadSettings>()
.get(ctx.app.world(), entity)
.is_ok());
}
#[test]
fn connection_disconnection_frame_event() {
let mut ctx = TestContext::new();
assert_eq!(
ctx.app
.world_mut()
.query::<&Gamepad>()
.iter(ctx.app.world())
.len(),
0
);
let entity = ctx.send_gamepad_connection_event(None);
ctx.send_gamepad_disconnection_event(entity);
ctx.update();
assert!(ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.is_err());
assert!(ctx
.app
.world_mut()
.query::<&GamepadSettings>()
.get(ctx.app.world(), entity)
.is_ok());
}
#[test]
fn reconnection_event() {
let button_settings = ButtonSettings::new(0.7, 0.2).expect("correct parameters");
let mut ctx = TestContext::new();
assert_eq!(
ctx.app
.world_mut()
.query::<&Gamepad>()
.iter(ctx.app.world())
.len(),
0
);
let entity = ctx.send_gamepad_connection_event(None);
ctx.update();
let mut settings = ctx
.app
.world_mut()
.query::<&mut GamepadSettings>()
.get_mut(ctx.app.world_mut(), entity)
.expect("be alive");
assert_ne!(settings.default_button_settings, button_settings);
settings.default_button_settings = button_settings.clone();
ctx.send_gamepad_disconnection_event(entity);
ctx.update();
assert_eq!(
ctx.app
.world_mut()
.query::<&Gamepad>()
.iter(ctx.app.world())
.len(),
0
);
ctx.send_gamepad_connection_event(Some(entity));
ctx.update();
let settings = ctx
.app
.world_mut()
.query::<&GamepadSettings>()
.get(ctx.app.world(), entity)
.expect("be alive");
assert_eq!(settings.default_button_settings, button_settings);
}
#[test]
fn reconnection_same_frame_event() {
let mut ctx = TestContext::new();
assert_eq!(
ctx.app
.world_mut()
.query::<&Gamepad>()
.iter(ctx.app.world())
.len(),
0
);
let entity = ctx.send_gamepad_connection_event(None);
ctx.send_gamepad_disconnection_event(entity);
ctx.update();
assert_eq!(
ctx.app
.world_mut()
.query::<&Gamepad>()
.iter(ctx.app.world())
.len(),
0
);
assert!(ctx
.app
.world_mut()
.query::<(Entity, &GamepadSettings)>()
.get(ctx.app.world(), entity)
.is_ok());
}
#[test]
fn gamepad_axis_valid() {
let mut ctx = TestContext::new();
let entity = ctx.send_gamepad_connection_event(None);
ctx.app
.world_mut()
.resource_mut::<Events<RawGamepadEvent>>()
.write_batch([
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::LeftStickY,
0.5,
)),
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::RightStickX,
0.6,
)),
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::RightZ,
-0.4,
)),
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::RightStickY,
-0.8,
)),
]);
ctx.update();
assert_eq!(
ctx.app
.world()
.resource::<Events<GamepadAxisChangedEvent>>()
.len(),
4
);
}
#[test]
fn gamepad_axis_threshold_filter() {
let mut ctx = TestContext::new();
let entity = ctx.send_gamepad_connection_event(None);
let settings = GamepadSettings::default().default_axis_settings;
let base_value = 0.5;
let events = [
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::LeftStickX,
base_value,
)),
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::LeftStickX,
base_value + settings.threshold - 0.01,
)),
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::LeftStickX,
base_value + settings.threshold + 0.01,
)),
];
ctx.app
.world_mut()
.resource_mut::<Events<RawGamepadEvent>>()
.write_batch(events);
ctx.update();
assert_eq!(
ctx.app
.world()
.resource::<Events<GamepadAxisChangedEvent>>()
.len(),
2
);
}
#[test]
fn gamepad_axis_deadzone_filter() {
let mut ctx = TestContext::new();
let entity = ctx.send_gamepad_connection_event(None);
let settings = GamepadSettings::default().default_axis_settings;
let events = [
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::LeftStickX,
settings.deadzone_upperbound - 0.01,
)),
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::LeftStickX,
settings.deadzone_lowerbound + 0.01,
)),
];
ctx.app
.world_mut()
.resource_mut::<Events<RawGamepadEvent>>()
.write_batch(events);
ctx.update();
assert_eq!(
ctx.app
.world()
.resource::<Events<GamepadAxisChangedEvent>>()
.len(),
0
);
}
#[test]
fn gamepad_axis_deadzone_rounded() {
let mut ctx = TestContext::new();
let entity = ctx.send_gamepad_connection_event(None);
let settings = GamepadSettings::default().default_axis_settings;
let events = [
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::LeftStickX,
1.0,
)),
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::LeftStickX,
settings.deadzone_upperbound - 0.01,
)),
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::LeftStickX,
1.0,
)),
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::LeftStickX,
settings.deadzone_lowerbound + 0.01,
)),
];
let results = [1.0, 0.0, 1.0, 0.0];
ctx.app
.world_mut()
.resource_mut::<Events<RawGamepadEvent>>()
.write_batch(events);
ctx.update();
let events = ctx
.app
.world()
.resource::<Events<GamepadAxisChangedEvent>>();
let mut event_reader = events.get_cursor();
for (event, result) in event_reader.read(events).zip(results) {
assert_eq!(event.value, result);
}
assert_eq!(
ctx.app
.world()
.resource::<Events<GamepadAxisChangedEvent>>()
.len(),
4
);
}
#[test]
fn gamepad_axis_livezone_filter() {
let mut ctx = TestContext::new();
let entity = ctx.send_gamepad_connection_event(None);
let settings = GamepadSettings::default().default_axis_settings;
let events = [
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::LeftStickX,
1.0,
)),
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::LeftStickX,
settings.livezone_upperbound + 0.01,
)),
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::LeftStickX,
-1.0,
)),
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::LeftStickX,
settings.livezone_lowerbound - 0.01,
)),
];
ctx.app
.world_mut()
.resource_mut::<Events<RawGamepadEvent>>()
.write_batch(events);
ctx.update();
assert_eq!(
ctx.app
.world()
.resource::<Events<GamepadAxisChangedEvent>>()
.len(),
2
);
}
#[test]
fn gamepad_axis_livezone_rounded() {
let mut ctx = TestContext::new();
let entity = ctx.send_gamepad_connection_event(None);
let settings = GamepadSettings::default().default_axis_settings;
let events = [
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::LeftStickX,
settings.livezone_upperbound + 0.01,
)),
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity,
GamepadAxis::LeftStickX,
settings.livezone_lowerbound - 0.01,
)),
];
let results = [1.0, -1.0];
ctx.app
.world_mut()
.resource_mut::<Events<RawGamepadEvent>>()
.write_batch(events);
ctx.update();
let events = ctx
.app
.world()
.resource::<Events<GamepadAxisChangedEvent>>();
let mut event_reader = events.get_cursor();
for (event, result) in event_reader.read(events).zip(results) {
assert_eq!(event.value, result);
}
assert_eq!(
ctx.app
.world()
.resource::<Events<GamepadAxisChangedEvent>>()
.len(),
2
);
}
#[test]
fn gamepad_buttons_pressed() {
let mut ctx = TestContext::new();
let entity = ctx.send_gamepad_connection_event(None);
let digital_settings = GamepadSettings::default().default_button_settings;
let events = [RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
entity,
GamepadButton::DPadDown,
digital_settings.press_threshold,
))];
ctx.app
.world_mut()
.resource_mut::<Events<RawGamepadEvent>>()
.write_batch(events);
ctx.update();
assert_eq!(
ctx.app
.world()
.resource::<Events<GamepadButtonStateChangedEvent>>()
.len(),
1
);
let events = ctx
.app
.world()
.resource::<Events<GamepadButtonStateChangedEvent>>();
let mut event_reader = events.get_cursor();
for event in event_reader.read(events) {
assert_eq!(event.button, GamepadButton::DPadDown);
assert_eq!(event.state, ButtonState::Pressed);
}
assert!(ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.pressed(GamepadButton::DPadDown));
ctx.app
.world_mut()
.resource_mut::<Events<GamepadButtonStateChangedEvent>>()
.clear();
ctx.update();
assert_eq!(
ctx.app
.world()
.resource::<Events<GamepadButtonStateChangedEvent>>()
.len(),
0
);
assert!(ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.pressed(GamepadButton::DPadDown));
}
#[test]
fn gamepad_buttons_just_pressed() {
let mut ctx = TestContext::new();
let entity = ctx.send_gamepad_connection_event(None);
let digital_settings = GamepadSettings::default().default_button_settings;
ctx.send_raw_gamepad_event(RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
entity,
GamepadButton::DPadDown,
digital_settings.press_threshold,
)));
ctx.update();
assert!(ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.just_pressed(GamepadButton::DPadDown));
ctx.update();
assert!(!ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.just_pressed(GamepadButton::DPadDown));
}
#[test]
fn gamepad_buttons_released() {
let mut ctx = TestContext::new();
let entity = ctx.send_gamepad_connection_event(None);
let digital_settings = GamepadSettings::default().default_button_settings;
ctx.send_raw_gamepad_event(RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
entity,
GamepadButton::DPadDown,
digital_settings.press_threshold,
)));
ctx.update();
ctx.app
.world_mut()
.resource_mut::<Events<GamepadButtonStateChangedEvent>>()
.clear();
ctx.send_raw_gamepad_event(RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
entity,
GamepadButton::DPadDown,
digital_settings.release_threshold - 0.01,
)));
ctx.update();
assert_eq!(
ctx.app
.world()
.resource::<Events<GamepadButtonStateChangedEvent>>()
.len(),
1
);
let events = ctx
.app
.world()
.resource::<Events<GamepadButtonStateChangedEvent>>();
let mut event_reader = events.get_cursor();
for event in event_reader.read(events) {
assert_eq!(event.button, GamepadButton::DPadDown);
assert_eq!(event.state, ButtonState::Released);
}
assert!(!ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.pressed(GamepadButton::DPadDown));
ctx.app
.world_mut()
.resource_mut::<Events<GamepadButtonStateChangedEvent>>()
.clear();
ctx.update();
assert_eq!(
ctx.app
.world()
.resource::<Events<GamepadButtonStateChangedEvent>>()
.len(),
0
);
}
#[test]
fn gamepad_buttons_just_released() {
let mut ctx = TestContext::new();
let entity = ctx.send_gamepad_connection_event(None);
let digital_settings = GamepadSettings::default().default_button_settings;
ctx.send_raw_gamepad_event_batch([
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
entity,
GamepadButton::DPadDown,
digital_settings.press_threshold,
)),
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
entity,
GamepadButton::DPadDown,
digital_settings.release_threshold - 0.01,
)),
]);
ctx.update();
assert!(ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.just_released(GamepadButton::DPadDown));
ctx.update();
assert!(!ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.just_released(GamepadButton::DPadDown));
}
#[test]
fn gamepad_buttons_axis() {
let mut ctx = TestContext::new();
let entity = ctx.send_gamepad_connection_event(None);
let digital_settings = GamepadSettings::default().default_button_settings;
let analog_settings = GamepadSettings::default().default_button_axis_settings;
let events = [
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
entity,
GamepadButton::DPadDown,
digital_settings.press_threshold,
)),
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
entity,
GamepadButton::DPadDown,
digital_settings.release_threshold,
)),
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
entity,
GamepadButton::DPadDown,
digital_settings.release_threshold - analog_settings.threshold * 1.01,
)),
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
entity,
GamepadButton::DPadDown,
digital_settings.release_threshold - (analog_settings.threshold * 1.5),
)),
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
entity,
GamepadButton::DPadDown,
digital_settings.release_threshold - (analog_settings.threshold * 2.02),
)),
];
ctx.send_raw_gamepad_event_batch(events);
ctx.update();
assert_eq!(
ctx.app
.world()
.resource::<Events<GamepadButtonStateChangedEvent>>()
.len(),
2
);
assert_eq!(
ctx.app
.world()
.resource::<Events<GamepadButtonChangedEvent>>()
.len(),
4
);
}
}