use std::hash::{Hash, Hasher};
use bevy::ecs::message::Messages;
use bevy::ecs::system::lifetimeless::{Read, SQuery};
use bevy::ecs::system::{StaticSystemParam, SystemParam, SystemState};
use bevy::input::gamepad::{
GamepadInput, RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent,
};
use bevy::input::{Axis, ButtonInput};
use bevy::math::FloatOrd;
use bevy::prelude::{
Entity, Gamepad, GamepadAxis, GamepadButton, Query, Reflect, Res, ResMut, Vec2, With, World,
};
use leafwing_input_manager_macros::serde_typetag;
use serde::{Deserialize, Serialize};
use crate as leafwing_input_manager;
use crate::InputControlKind;
use crate::axislike::AxisDirection;
use crate::buttonlike::ButtonValue;
use crate::clashing_inputs::BasicInputs;
use crate::input_processing::{
AxisProcessor, DualAxisProcessor, WithAxisProcessingPipelineExt,
WithDualAxisProcessingPipelineExt,
};
use crate::user_input::UserInput;
use super::updating::{CentralInputStore, UpdatableInput};
use super::{Axislike, Buttonlike, DualAxislike};
#[must_use]
pub fn find_gamepad(gamepads: Option<Query<Entity, With<Gamepad>>>) -> Entity {
match gamepads {
None => Entity::PLACEHOLDER,
Some(gamepads) => gamepads.iter().next().unwrap_or(Entity::PLACEHOLDER),
}
}
#[must_use]
#[inline]
fn read_axis_value(
input_store: &CentralInputStore,
gamepad: Entity,
axis: GamepadAxis,
) -> Option<f32> {
let axis = SpecificGamepadAxis::new(gamepad, axis);
input_store.value(&axis)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub struct SpecificGamepadAxis {
pub gamepad: Entity,
pub axis: GamepadAxis,
}
impl SpecificGamepadAxis {
pub fn new(gamepad: Entity, axis: GamepadAxis) -> Self {
Self { gamepad, axis }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub struct SpecificGamepadButton {
pub gamepad: Entity,
pub button: GamepadButton,
}
impl SpecificGamepadButton {
pub fn new(gamepad: Entity, button: GamepadButton) -> Self {
Self { gamepad, button }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct GamepadControlDirection {
pub axis: GamepadAxis,
pub direction: AxisDirection,
pub threshold: f32,
}
impl GamepadControlDirection {
#[inline]
pub const fn negative(axis: GamepadAxis) -> Self {
Self {
axis,
direction: AxisDirection::Negative,
threshold: 0.0,
}
}
#[inline]
pub const fn positive(axis: GamepadAxis) -> Self {
Self {
axis,
direction: AxisDirection::Positive,
threshold: 0.0,
}
}
#[inline]
pub fn threshold(mut self, threshold: f32) -> Self {
assert!(threshold >= 0.0);
self.threshold = threshold;
self
}
pub const LEFT_UP: Self = Self::positive(GamepadAxis::LeftStickY);
pub const LEFT_DOWN: Self = Self::negative(GamepadAxis::LeftStickY);
pub const LEFT_LEFT: Self = Self::negative(GamepadAxis::LeftStickX);
pub const LEFT_RIGHT: Self = Self::positive(GamepadAxis::LeftStickX);
pub const RIGHT_UP: Self = Self::positive(GamepadAxis::RightStickY);
pub const RIGHT_DOWN: Self = Self::negative(GamepadAxis::RightStickY);
pub const RIGHT_LEFT: Self = Self::negative(GamepadAxis::RightStickX);
pub const RIGHT_RIGHT: Self = Self::positive(GamepadAxis::RightStickX);
}
impl UserInput for GamepadControlDirection {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::Button
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Simple(Box::new((*self).threshold(0.0)))
}
}
#[serde_typetag]
impl Buttonlike for GamepadControlDirection {
#[inline]
fn get_pressed(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<bool> {
read_axis_value(input_store, gamepad, self.axis)
.map(|value| self.direction.is_active(value, self.threshold))
}
fn press_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
let mut query_state = SystemState::<Query<Entity, With<Gamepad>>>::new(world);
let query = query_state.get(world);
let gamepad = gamepad.unwrap_or(find_gamepad(Some(query)));
let message = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
gamepad,
axis: self.axis,
value: self.direction.full_active_value(),
});
world
.resource_mut::<Messages<RawGamepadEvent>>()
.write(message);
}
fn release_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
let mut query_state = SystemState::<Query<Entity, With<Gamepad>>>::new(world);
let query = query_state.get(world);
let gamepad = gamepad.unwrap_or(find_gamepad(Some(query)));
let message = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
gamepad,
axis: self.axis,
value: 0.0,
});
world
.resource_mut::<Messages<RawGamepadEvent>>()
.write(message);
}
fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Entity>) {
if value > 0.0 {
self.press_as_gamepad(world, gamepad);
} else {
self.release_as_gamepad(world, gamepad);
}
}
}
impl Eq for GamepadControlDirection {}
impl Hash for GamepadControlDirection {
fn hash<H: Hasher>(&self, state: &mut H) {
self.axis.hash(state);
self.direction.hash(state);
FloatOrd(self.threshold).hash(state);
}
}
impl UpdatableInput for GamepadAxis {
type SourceData = SQuery<(Entity, Read<Gamepad>)>;
fn compute(
mut central_input_store: ResMut<CentralInputStore>,
source_data: StaticSystemParam<Self::SourceData>,
) {
for (gamepad_entity, gamepad) in source_data.iter() {
for input in gamepad.get_analog_axes() {
let GamepadInput::Axis(axis) = input else {
continue;
};
let value = gamepad.get(*axis).unwrap_or_default();
central_input_store.update_axislike(
SpecificGamepadAxis {
gamepad: gamepad_entity,
axis: *axis,
},
value,
);
central_input_store.update_axislike(*axis, value);
}
}
}
}
impl UserInput for GamepadAxis {
fn kind(&self) -> InputControlKind {
InputControlKind::Axis
}
fn decompose(&self) -> BasicInputs {
BasicInputs::Composite(vec![
Box::new(GamepadControlDirection::negative(*self)),
Box::new(GamepadControlDirection::positive(*self)),
])
}
}
#[serde_typetag]
impl Axislike for GamepadAxis {
fn get_value(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<f32> {
read_axis_value(input_store, gamepad, *self)
}
}
impl UserInput for SpecificGamepadAxis {
fn kind(&self) -> InputControlKind {
InputControlKind::Axis
}
fn decompose(&self) -> BasicInputs {
BasicInputs::Composite(vec![
Box::new(GamepadControlDirection::negative(self.axis)),
Box::new(GamepadControlDirection::positive(self.axis)),
])
}
}
#[serde_typetag]
impl Axislike for SpecificGamepadAxis {
fn get_value(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<f32> {
read_axis_value(input_store, gamepad, self.axis)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct GamepadControlAxis {
pub axis: GamepadAxis,
pub processors: Vec<AxisProcessor>,
}
impl GamepadControlAxis {
#[inline]
pub const fn new(axis: GamepadAxis) -> Self {
Self {
axis,
processors: Vec::new(),
}
}
pub const LEFT_X: Self = Self::new(GamepadAxis::LeftStickX);
pub const LEFT_Y: Self = Self::new(GamepadAxis::LeftStickY);
pub const LEFT_Z: Self = Self::new(GamepadAxis::LeftZ);
pub const RIGHT_X: Self = Self::new(GamepadAxis::RightStickX);
pub const RIGHT_Y: Self = Self::new(GamepadAxis::RightStickY);
pub const RIGHT_Z: Self = Self::new(GamepadAxis::RightZ);
}
impl UserInput for GamepadControlAxis {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::Axis
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Composite(vec![
Box::new(GamepadControlDirection::negative(self.axis)),
Box::new(GamepadControlDirection::positive(self.axis)),
])
}
}
#[serde_typetag]
impl Axislike for GamepadControlAxis {
#[inline]
fn get_value(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<f32> {
read_axis_value(input_store, gamepad, self.axis).map(|value| {
self.processors
.iter()
.fold(value, |value, processor| processor.process(value))
})
}
fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Entity>) {
let mut query_state = SystemState::<Query<Entity, With<Gamepad>>>::new(world);
let query = query_state.get(world);
let gamepad = gamepad.unwrap_or(find_gamepad(Some(query)));
let message = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
gamepad,
axis: self.axis,
value,
});
world
.resource_mut::<Messages<RawGamepadEvent>>()
.write(message);
}
}
impl WithAxisProcessingPipelineExt for GamepadControlAxis {
#[inline]
fn reset_processing_pipeline(mut self) -> Self {
self.processors.clear();
self
}
#[inline]
fn replace_processing_pipeline(
mut self,
processors: impl IntoIterator<Item = AxisProcessor>,
) -> Self {
self.processors = processors.into_iter().collect();
self
}
#[inline]
fn with_processor(mut self, processor: impl Into<AxisProcessor>) -> Self {
self.processors.push(processor.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct GamepadStick {
pub x: GamepadAxis,
pub y: GamepadAxis,
pub processors: Vec<DualAxisProcessor>,
}
impl GamepadStick {
pub const LEFT: Self = Self {
x: GamepadAxis::LeftStickX,
y: GamepadAxis::LeftStickY,
processors: Vec::new(),
};
pub const RIGHT: Self = Self {
x: GamepadAxis::RightStickX,
y: GamepadAxis::RightStickY,
processors: Vec::new(),
};
}
impl UserInput for GamepadStick {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::DualAxis
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Composite(vec![
Box::new(GamepadControlDirection::negative(self.x)),
Box::new(GamepadControlDirection::positive(self.x)),
Box::new(GamepadControlDirection::negative(self.y)),
Box::new(GamepadControlDirection::positive(self.y)),
])
}
}
#[serde_typetag]
impl DualAxislike for GamepadStick {
#[inline]
fn get_axis_pair(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<Vec2> {
let x = read_axis_value(input_store, gamepad, self.x)?;
let y = read_axis_value(input_store, gamepad, self.y)?;
Some(
self.processors
.iter()
.fold(Vec2::new(x, y), |value, processor| processor.process(value)),
)
}
fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, gamepad: Option<Entity>) {
let mut query_state = SystemState::<Query<Entity, With<Gamepad>>>::new(world);
let query = query_state.get(world);
let gamepad = gamepad.unwrap_or(find_gamepad(Some(query)));
let message = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
gamepad,
axis: self.x,
value: value.x,
});
world
.resource_mut::<Messages<RawGamepadEvent>>()
.write(message);
let message = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
gamepad,
axis: self.y,
value: value.y,
});
world
.resource_mut::<Messages<RawGamepadEvent>>()
.write(message);
}
}
impl WithDualAxisProcessingPipelineExt for GamepadStick {
#[inline]
fn reset_processing_pipeline(mut self) -> Self {
self.processors.clear();
self
}
#[inline]
fn replace_processing_pipeline(
mut self,
processor: impl IntoIterator<Item = DualAxisProcessor>,
) -> Self {
self.processors = processor.into_iter().collect();
self
}
#[inline]
fn with_processor(mut self, processor: impl Into<DualAxisProcessor>) -> Self {
self.processors.push(processor.into());
self
}
}
#[must_use]
#[inline]
fn button_pressed(
input_store: &CentralInputStore,
gamepad: Entity,
button: GamepadButton,
) -> Option<bool> {
let button = SpecificGamepadButton::new(gamepad, button);
input_store.pressed(&button)
}
#[must_use]
#[inline]
fn button_value(input_store: &CentralInputStore, gamepad: Entity, button: GamepadButton) -> f32 {
let button = SpecificGamepadButton::new(gamepad, button);
input_store.button_value(&button)
}
#[derive(SystemParam)]
pub struct GamepadButtonInput<'w> {
pub buttons: Res<'w, ButtonInput<GamepadButton>>,
pub axes: Res<'w, Axis<GamepadButton>>,
}
impl UpdatableInput for GamepadButton {
type SourceData = SQuery<(Entity, Read<Gamepad>)>;
fn compute(
mut central_input_store: ResMut<CentralInputStore>,
source_data: StaticSystemParam<Self::SourceData>,
) {
for (gamepad_entity, gamepad) in source_data.iter() {
for gamepad_button in GamepadButton::all() {
let specific_button = SpecificGamepadButton {
gamepad: gamepad_entity,
button: gamepad_button,
};
let pressed = gamepad.pressed(gamepad_button);
let value = gamepad.get(gamepad_button).unwrap_or(f32::from(pressed));
let button_value = ButtonValue::new(pressed, value);
central_input_store.update_buttonlike(specific_button, button_value);
central_input_store.update_buttonlike(gamepad_button, button_value);
}
}
}
}
impl UserInput for SpecificGamepadButton {
fn kind(&self) -> InputControlKind {
InputControlKind::Button
}
fn decompose(&self) -> BasicInputs {
BasicInputs::Simple(Box::new(*self))
}
}
#[serde_typetag]
impl Buttonlike for SpecificGamepadButton {
fn get_pressed(&self, input_store: &CentralInputStore, _gamepad: Entity) -> Option<bool> {
button_pressed(input_store, self.gamepad, self.button)
}
fn value(&self, input_store: &CentralInputStore, _gamepad: Entity) -> f32 {
button_value(input_store, self.gamepad, self.button)
}
fn press(&self, world: &mut World) {
self.set_value(world, 1.0);
}
fn release(&self, world: &mut World) {
self.set_value(world, 0.0);
}
fn set_value(&self, world: &mut World, value: f32) {
let message = RawGamepadEvent::Button(RawGamepadButtonChangedEvent {
gamepad: self.gamepad,
button: self.button,
value,
});
world
.resource_mut::<Messages<RawGamepadEvent>>()
.write(message);
}
}
impl UserInput for GamepadButton {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::Button
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Simple(Box::new(*self))
}
}
#[serde_typetag]
impl Buttonlike for GamepadButton {
#[inline]
fn get_pressed(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<bool> {
button_pressed(input_store, gamepad, *self)
}
#[inline]
fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 {
button_value(input_store, gamepad, *self)
}
fn press_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
self.set_value_as_gamepad(world, 1.0, gamepad);
}
fn release_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
self.set_value_as_gamepad(world, 0.0, gamepad);
}
#[inline]
fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Entity>) {
let mut query_state = SystemState::<Query<Entity, With<Gamepad>>>::new(world);
let query = query_state.get(world);
let gamepad = gamepad.unwrap_or(find_gamepad(Some(query)));
let message = RawGamepadEvent::Button(RawGamepadButtonChangedEvent {
gamepad,
button: *self,
value,
});
world
.resource_mut::<Messages<RawGamepadEvent>>()
.write(message);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::plugin::CentralInputStorePlugin;
use bevy::input::InputPlugin;
use bevy::input::gamepad::{GamepadConnection, GamepadConnectionEvent};
use bevy::prelude::*;
struct TestContext {
pub app: App,
}
impl TestContext {
pub fn new() -> Self {
let mut app = App::new();
app.add_plugins(MinimalPlugins);
app.add_plugins((InputPlugin, CentralInputStorePlugin));
Self { app }
}
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::<Messages<GamepadConnectionEvent>>()
.write(GamepadConnectionEvent::new(
gamepad,
GamepadConnection::Connected {
name: "TestController".to_string(),
vendor_id: None,
product_id: None,
},
));
gamepad
}
pub fn update(&mut self) {
self.app.update();
}
pub fn send_raw_gamepad_event(&mut self, event: RawGamepadEvent) {
self.app
.world_mut()
.resource_mut::<Messages<RawGamepadEvent>>()
.write(event);
}
}
#[test]
fn test_gamepad_axes() {
let left_up = GamepadControlDirection::LEFT_UP;
assert_eq!(left_up.kind(), InputControlKind::Button);
let left_down = GamepadControlDirection::LEFT_DOWN;
assert_eq!(left_down.kind(), InputControlKind::Button);
let left_x = GamepadControlAxis::LEFT_X;
assert_eq!(left_x.kind(), InputControlKind::Axis);
let left_y = GamepadControlAxis::LEFT_Y;
assert_eq!(left_y.kind(), InputControlKind::Axis);
let left = GamepadStick::LEFT;
assert_eq!(left.kind(), InputControlKind::DualAxis);
let right_up = GamepadControlDirection::RIGHT_DOWN;
assert_eq!(right_up.kind(), InputControlKind::Button);
let right_y = GamepadControlAxis::RIGHT_Y;
assert_eq!(right_y.kind(), InputControlKind::Axis);
let right = GamepadStick::RIGHT;
assert_eq!(right.kind(), InputControlKind::DualAxis);
let mut ctx = TestContext::new();
ctx.update();
let gamepad = ctx.send_gamepad_connection_event(None);
ctx.update();
let inputs = ctx.app.world().resource::<CentralInputStore>();
assert!(!left_up.pressed(inputs, gamepad));
assert!(!left_down.pressed(inputs, gamepad));
assert!(!right_up.pressed(inputs, gamepad));
assert_eq!(left_x.value(inputs, gamepad), 0.0);
assert_eq!(left_y.value(inputs, gamepad), 0.0);
assert_eq!(right_y.value(inputs, gamepad), 0.0);
assert_eq!(left.axis_pair(inputs, gamepad), Vec2::ZERO);
assert_eq!(right.axis_pair(inputs, gamepad), Vec2::ZERO);
let mut ctx = TestContext::new();
ctx.update();
let gamepad = ctx.send_gamepad_connection_event(None);
ctx.send_raw_gamepad_event(RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
gamepad,
axis: GamepadControlDirection::LEFT_UP.axis,
value: GamepadControlDirection::LEFT_UP
.direction
.full_active_value(),
}));
ctx.update();
let inputs: &CentralInputStore = ctx.app.world().resource::<CentralInputStore>();
assert!(left_up.pressed(inputs, gamepad));
assert!(!left_down.pressed(inputs, gamepad));
assert!(!right_up.pressed(inputs, gamepad));
assert_eq!(left_x.value(inputs, gamepad), 0.0);
assert_eq!(left_y.value(inputs, gamepad), 1.0);
assert_eq!(right_y.value(inputs, gamepad), 0.0);
assert_eq!(left.axis_pair(inputs, gamepad), Vec2::new(0.0, 1.0));
assert_eq!(right.axis_pair(inputs, gamepad), Vec2::ZERO);
let mut ctx = TestContext::new();
ctx.update();
let gamepad = ctx.send_gamepad_connection_event(None);
ctx.send_raw_gamepad_event(RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
gamepad,
axis: GamepadControlDirection::LEFT_UP.axis,
value: 0.6,
}));
ctx.update();
let inputs: &CentralInputStore = ctx.app.world().resource::<CentralInputStore>();
assert!(left_up.pressed(inputs, gamepad));
assert!(!left_down.pressed(inputs, gamepad));
assert!(!right_up.pressed(inputs, gamepad));
assert_eq!(left_x.value(inputs, gamepad), 0.0);
assert_eq!(left_y.value(inputs, gamepad), 0.6);
assert_eq!(right_y.value(inputs, gamepad), 0.0);
assert_eq!(left.axis_pair(inputs, gamepad), Vec2::new(0.0, 0.6));
assert_eq!(right.axis_pair(inputs, gamepad), Vec2::ZERO);
let mut ctx = TestContext::new();
ctx.update();
let gamepad = ctx.send_gamepad_connection_event(None);
ctx.send_raw_gamepad_event(RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
gamepad,
axis: GamepadStick::LEFT.x,
value: 0.6,
}));
ctx.send_raw_gamepad_event(RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
gamepad,
axis: GamepadAxis::LeftStickY,
value: 0.4,
}));
ctx.update();
let inputs: &CentralInputStore = ctx.app.world().resource::<CentralInputStore>();
assert!(left_up.pressed(inputs, gamepad));
assert!(!left_down.pressed(inputs, gamepad));
assert!(!right_up.pressed(inputs, gamepad));
assert_eq!(left_x.value(inputs, gamepad), 0.6);
assert_eq!(left_y.value(inputs, gamepad), 0.4);
assert_eq!(right_y.value(inputs, gamepad), 0.0);
assert_eq!(left.axis_pair(inputs, gamepad), Vec2::new(0.6, 0.4));
assert_eq!(right.axis_pair(inputs, gamepad), Vec2::ZERO);
}
#[test]
fn test_gamepad_buttons() {
let up = GamepadButton::DPadUp;
assert_eq!(up.kind(), InputControlKind::Button);
let left = GamepadButton::DPadLeft;
assert_eq!(left.kind(), InputControlKind::Button);
let down = GamepadButton::DPadDown;
assert_eq!(left.kind(), InputControlKind::Button);
let right = GamepadButton::DPadRight;
assert_eq!(left.kind(), InputControlKind::Button);
let mut ctx = TestContext::new();
ctx.update();
let gamepad = ctx.send_gamepad_connection_event(None);
ctx.update();
let inputs = ctx.app.world().resource::<CentralInputStore>();
assert!(!up.pressed(inputs, gamepad));
assert!(!left.pressed(inputs, gamepad));
assert!(!down.pressed(inputs, gamepad));
assert!(!right.pressed(inputs, gamepad));
let mut ctx = TestContext::new();
ctx.update();
let gamepad = ctx.send_gamepad_connection_event(None);
ctx.send_raw_gamepad_event(RawGamepadEvent::Button(RawGamepadButtonChangedEvent {
gamepad,
button: GamepadButton::DPadLeft,
value: 1.0,
}));
ctx.update();
let inputs = ctx.app.world().resource::<CentralInputStore>();
assert!(!up.pressed(inputs, gamepad));
assert!(left.pressed(inputs, gamepad));
assert!(!down.pressed(inputs, gamepad));
assert!(!right.pressed(inputs, gamepad));
}
#[test]
fn test_gamepad_button_values() {
let up = GamepadButton::DPadUp;
assert_eq!(up.kind(), InputControlKind::Button);
let left = GamepadButton::DPadLeft;
assert_eq!(left.kind(), InputControlKind::Button);
let down = GamepadButton::DPadDown;
assert_eq!(down.kind(), InputControlKind::Button);
let right = GamepadButton::DPadRight;
assert_eq!(right.kind(), InputControlKind::Button);
let mut ctx = TestContext::new();
ctx.update();
let gamepad = ctx.send_gamepad_connection_event(None);
ctx.update();
let inputs = ctx.app.world().resource::<CentralInputStore>();
assert_eq!(up.value(inputs, gamepad), 0.0);
assert_eq!(left.value(inputs, gamepad), 0.0);
assert_eq!(down.value(inputs, gamepad), 0.0);
assert_eq!(right.value(inputs, gamepad), 0.0);
let mut ctx = TestContext::new();
ctx.update();
let gamepad = ctx.send_gamepad_connection_event(None);
ctx.send_raw_gamepad_event(RawGamepadEvent::Button(RawGamepadButtonChangedEvent {
gamepad,
button: GamepadButton::DPadLeft,
value: 1.0,
}));
ctx.update();
let inputs = ctx.app.world().resource::<CentralInputStore>();
assert_eq!(up.value(inputs, gamepad), 0.0);
assert_eq!(left.value(inputs, gamepad), 1.0);
assert_eq!(down.value(inputs, gamepad), 0.0);
assert_eq!(right.value(inputs, gamepad), 0.0);
}
#[test]
fn test_gamepad_triggers() {
let trigger = GamepadButton::RightTrigger2;
assert_eq!(trigger.kind(), InputControlKind::Button);
let mut ctx = TestContext::new();
ctx.update();
let gamepad = ctx.send_gamepad_connection_event(None);
ctx.update();
let inputs: &CentralInputStore = ctx.app.world().resource::<CentralInputStore>();
assert_eq!(trigger.value(inputs, gamepad), 0.0);
let mut ctx = TestContext::new();
ctx.update();
let gamepad = ctx.send_gamepad_connection_event(None);
ctx.send_raw_gamepad_event(RawGamepadEvent::Button(RawGamepadButtonChangedEvent {
gamepad,
button: trigger,
value: 0.8,
}));
ctx.update();
let inputs: &CentralInputStore = ctx.app.world().resource::<CentralInputStore>();
assert!(trigger.pressed(inputs, gamepad));
assert_eq!(trigger.value(inputs, gamepad), 0.8);
let mut ctx = TestContext::new();
ctx.update();
let gamepad = ctx.send_gamepad_connection_event(None);
ctx.send_raw_gamepad_event(RawGamepadEvent::Button(RawGamepadButtonChangedEvent {
gamepad,
button: trigger,
value: 0.4,
}));
ctx.update();
let inputs: &CentralInputStore = ctx.app.world().resource::<CentralInputStore>();
assert!(trigger.released(inputs, gamepad));
assert_eq!(trigger.value(inputs, gamepad), 0.4);
}
#[test]
fn test_gamepad_control_direction() {
let dir = GamepadControlDirection::positive(GamepadAxis::LeftStickX);
assert_eq!(
dir,
GamepadControlDirection {
axis: GamepadAxis::LeftStickX,
direction: AxisDirection::Positive,
threshold: 0.0,
}
);
let dir = GamepadControlDirection::negative(GamepadAxis::LeftStickY);
assert_eq!(
dir,
GamepadControlDirection {
axis: GamepadAxis::LeftStickY,
direction: AxisDirection::Negative,
threshold: 0.0,
}
);
}
#[test]
fn test_gamepad_control_direction_threshold() {
let dir = GamepadControlDirection::positive(GamepadAxis::LeftStickX).threshold(0.1);
assert_eq!(
dir,
GamepadControlDirection {
axis: GamepadAxis::LeftStickX,
direction: AxisDirection::Positive,
threshold: 0.1,
}
);
}
#[test]
#[should_panic = "assertion failed: threshold >= 0.0"]
fn test_gamepad_control_direction_threshold_with_negative() {
let _ = GamepadControlDirection::positive(GamepadAxis::LeftStickX).threshold(-0.1);
}
#[test]
fn test_gamepad_control_direction_kind() {
let dir = GamepadControlDirection::positive(GamepadAxis::LeftStickX);
assert_eq!(dir.kind(), InputControlKind::Button);
}
}