use bevy::input::gamepad::{GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadEvent};
use bevy::input::{Axis, ButtonInput};
use bevy::prelude::{
Events, Gamepad, GamepadAxis, GamepadAxisType, GamepadButton, GamepadButtonType, Gamepads,
Reflect, Res, ResMut, Vec2, World,
};
use leafwing_input_manager_macros::serde_typetag;
use serde::{Deserialize, Serialize};
use crate as leafwing_input_manager;
use crate::axislike::AxisDirection;
use crate::clashing_inputs::BasicInputs;
use crate::input_processing::{
AxisProcessor, DualAxisProcessor, WithAxisProcessingPipelineExt,
WithDualAxisProcessingPipelineExt,
};
use crate::user_input::UserInput;
use crate::InputControlKind;
use super::updating::{CentralInputStore, UpdatableInput};
use super::{Axislike, Buttonlike, DualAxislike};
#[must_use]
pub fn find_gamepad(gamepads: &Gamepads) -> Gamepad {
gamepads.iter().next().unwrap_or(Gamepad { id: 0 })
}
#[must_use]
#[inline]
fn read_axis_value(
input_store: &CentralInputStore,
gamepad: Gamepad,
axis: GamepadAxisType,
) -> f32 {
let axis = GamepadAxis::new(gamepad, axis);
input_store.value(&axis)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct GamepadControlDirection {
pub axis: GamepadAxisType,
pub side: AxisDirection,
}
impl GamepadControlDirection {
#[inline]
pub const fn negative(axis: GamepadAxisType) -> Self {
let side = AxisDirection::Negative;
Self { axis, side }
}
#[inline]
pub const fn positive(axis: GamepadAxisType) -> Self {
let side = AxisDirection::Positive;
Self { axis, side }
}
pub const LEFT_UP: Self = Self::positive(GamepadAxisType::LeftStickY);
pub const LEFT_DOWN: Self = Self::negative(GamepadAxisType::LeftStickY);
pub const LEFT_LEFT: Self = Self::negative(GamepadAxisType::LeftStickX);
pub const LEFT_RIGHT: Self = Self::positive(GamepadAxisType::LeftStickX);
pub const RIGHT_UP: Self = Self::positive(GamepadAxisType::RightStickY);
pub const RIGHT_DOWN: Self = Self::negative(GamepadAxisType::RightStickY);
pub const RIGHT_LEFT: Self = Self::negative(GamepadAxisType::RightStickX);
pub const RIGHT_RIGHT: Self = Self::positive(GamepadAxisType::RightStickX);
}
impl UserInput for GamepadControlDirection {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::Button
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Simple(Box::new(*self))
}
}
#[serde_typetag]
impl Buttonlike for GamepadControlDirection {
#[must_use]
#[inline]
fn pressed(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> bool {
let value = read_axis_value(input_store, gamepad, self.axis);
self.side.is_active(value)
}
fn press_as_gamepad(&self, world: &mut World, gamepad: Option<Gamepad>) {
let gamepad = gamepad.unwrap_or(find_gamepad(world.resource::<Gamepads>()));
let event = GamepadEvent::Axis(GamepadAxisChangedEvent {
gamepad,
axis_type: self.axis,
value: self.side.full_active_value(),
});
world.resource_mut::<Events<GamepadEvent>>().send(event);
}
fn release_as_gamepad(&self, world: &mut World, gamepad: Option<Gamepad>) {
let gamepad = gamepad.unwrap_or(find_gamepad(world.resource::<Gamepads>()));
let event = GamepadEvent::Axis(GamepadAxisChangedEvent {
gamepad,
axis_type: self.axis,
value: 0.0,
});
world.resource_mut::<Events<GamepadEvent>>().send(event);
}
}
impl UpdatableInput for GamepadAxis {
type SourceData = Axis<GamepadAxis>;
fn compute(
mut central_input_store: ResMut<CentralInputStore>,
source_data: Res<Self::SourceData>,
) {
for axis in source_data.devices() {
let value = source_data.get(*axis).unwrap_or_default();
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.axis_type)),
Box::new(GamepadControlDirection::positive(self.axis_type)),
])
}
}
#[serde_typetag]
impl Axislike for GamepadAxis {
fn value(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> f32 {
read_axis_value(input_store, gamepad, self.axis_type)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct GamepadControlAxis {
pub(crate) axis: GamepadAxisType,
pub(crate) processors: Vec<AxisProcessor>,
}
impl GamepadControlAxis {
#[inline]
pub const fn new(axis: GamepadAxisType) -> Self {
Self {
axis,
processors: Vec::new(),
}
}
pub const LEFT_X: Self = Self::new(GamepadAxisType::LeftStickX);
pub const LEFT_Y: Self = Self::new(GamepadAxisType::LeftStickY);
pub const LEFT_Z: Self = Self::new(GamepadAxisType::LeftZ);
pub const RIGHT_X: Self = Self::new(GamepadAxisType::RightStickX);
pub const RIGHT_Y: Self = Self::new(GamepadAxisType::RightStickY);
pub const RIGHT_Z: Self = Self::new(GamepadAxisType::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 {
#[must_use]
#[inline]
fn value(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> f32 {
let value = read_axis_value(input_store, gamepad, self.axis);
self.processors
.iter()
.fold(value, |value, processor| processor.process(value))
}
fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Gamepad>) {
let gamepad = gamepad.unwrap_or(find_gamepad(world.resource::<Gamepads>()));
let event = GamepadEvent::Axis(GamepadAxisChangedEvent {
gamepad,
axis_type: self.axis,
value,
});
world.resource_mut::<Events<GamepadEvent>>().send(event);
}
}
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(crate) x: GamepadAxisType,
pub(crate) y: GamepadAxisType,
pub(crate) processors: Vec<DualAxisProcessor>,
}
impl GamepadStick {
pub const LEFT: Self = Self {
x: GamepadAxisType::LeftStickX,
y: GamepadAxisType::LeftStickY,
processors: Vec::new(),
};
pub const RIGHT: Self = Self {
x: GamepadAxisType::RightStickX,
y: GamepadAxisType::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 {
#[must_use]
#[inline]
fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> Vec2 {
let x = read_axis_value(input_store, gamepad, self.x);
let y = read_axis_value(input_store, gamepad, self.y);
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<Gamepad>) {
let gamepad = gamepad.unwrap_or(find_gamepad(world.resource::<Gamepads>()));
let event = GamepadEvent::Axis(GamepadAxisChangedEvent {
gamepad,
axis_type: self.x,
value: value.x,
});
world.resource_mut::<Events<GamepadEvent>>().send(event);
let event = GamepadEvent::Axis(GamepadAxisChangedEvent {
gamepad,
axis_type: self.y,
value: value.y,
});
world.resource_mut::<Events<GamepadEvent>>().send(event);
}
}
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: Gamepad,
button: GamepadButtonType,
) -> bool {
let button = GamepadButton::new(gamepad, button);
input_store.pressed(&button)
}
#[must_use]
#[inline]
fn button_value(
input_store: &CentralInputStore,
gamepad: Gamepad,
button: GamepadButtonType,
) -> f32 {
f32::from(button_pressed(input_store, gamepad, button))
}
impl UpdatableInput for GamepadButton {
type SourceData = ButtonInput<GamepadButton>;
fn compute(
mut central_input_store: ResMut<CentralInputStore>,
source_data: Res<Self::SourceData>,
) {
for key in source_data.get_pressed() {
central_input_store.update_buttonlike(*key, true);
}
for key in source_data.get_just_released() {
central_input_store.update_buttonlike(*key, false);
}
}
}
impl UserInput for GamepadButton {
fn kind(&self) -> InputControlKind {
InputControlKind::Button
}
fn decompose(&self) -> BasicInputs {
BasicInputs::Simple(Box::new(*self))
}
}
#[serde_typetag]
impl Buttonlike for GamepadButton {
fn pressed(&self, input_store: &CentralInputStore, _gamepad: Gamepad) -> bool {
button_pressed(input_store, self.gamepad, self.button_type)
}
fn press(&self, world: &mut World) {
let event = GamepadEvent::Button(GamepadButtonChangedEvent {
gamepad: self.gamepad,
button_type: self.button_type,
value: 1.0,
});
world.resource_mut::<Events<GamepadEvent>>().send(event);
}
fn release(&self, world: &mut World) {
let event = GamepadEvent::Button(GamepadButtonChangedEvent {
gamepad: self.gamepad,
button_type: self.button_type,
value: 0.0,
});
world.resource_mut::<Events<GamepadEvent>>().send(event);
}
}
impl UserInput for GamepadButtonType {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::Button
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Simple(Box::new(*self))
}
}
#[serde_typetag]
impl Buttonlike for GamepadButtonType {
#[must_use]
#[inline]
fn pressed(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> bool {
button_pressed(input_store, gamepad, *self)
}
fn press_as_gamepad(&self, world: &mut World, gamepad: Option<Gamepad>) {
let gamepad = gamepad.unwrap_or(find_gamepad(world.resource::<Gamepads>()));
let event = GamepadEvent::Button(GamepadButtonChangedEvent {
gamepad,
button_type: *self,
value: 1.0,
});
world.resource_mut::<Events<GamepadEvent>>().send(event);
}
fn release_as_gamepad(&self, world: &mut World, gamepad: Option<Gamepad>) {
let gamepad = gamepad.unwrap_or(find_gamepad(world.resource::<Gamepads>()));
let event = GamepadEvent::Button(GamepadButtonChangedEvent {
gamepad,
button_type: *self,
value: 0.0,
});
world.resource_mut::<Events<GamepadEvent>>().send(event);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct GamepadVirtualAxis {
pub(crate) negative: GamepadButtonType,
pub(crate) positive: GamepadButtonType,
pub(crate) processors: Vec<AxisProcessor>,
}
impl GamepadVirtualAxis {
#[inline]
pub const fn new(negative: GamepadButtonType, positive: GamepadButtonType) -> Self {
Self {
negative,
positive,
processors: Vec::new(),
}
}
pub const DPAD_X: Self = Self::new(GamepadButtonType::DPadLeft, GamepadButtonType::DPadRight);
pub const DPAD_Y: Self = Self::new(GamepadButtonType::DPadDown, GamepadButtonType::DPadUp);
pub const ACTION_PAD_X: Self = Self::new(GamepadButtonType::West, GamepadButtonType::East);
pub const ACTION_PAD_Y: Self = Self::new(GamepadButtonType::South, GamepadButtonType::North);
}
impl UserInput for GamepadVirtualAxis {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::Axis
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Composite(vec![Box::new(self.negative), Box::new(self.positive)])
}
}
#[serde_typetag]
impl Axislike for GamepadVirtualAxis {
#[must_use]
#[inline]
fn value(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> f32 {
let negative = button_value(input_store, gamepad, self.negative);
let positive = button_value(input_store, gamepad, self.positive);
let value = positive - negative;
self.processors
.iter()
.fold(value, |value, processor| processor.process(value))
}
fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Gamepad>) {
if value < 0.0 {
self.negative.press_as_gamepad(world, gamepad);
} else if value > 0.0 {
self.positive.press_as_gamepad(world, gamepad);
}
}
}
impl WithAxisProcessingPipelineExt for GamepadVirtualAxis {
#[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 GamepadVirtualDPad {
pub(crate) up: GamepadButtonType,
pub(crate) down: GamepadButtonType,
pub(crate) left: GamepadButtonType,
pub(crate) right: GamepadButtonType,
pub(crate) processors: Vec<DualAxisProcessor>,
}
impl GamepadVirtualDPad {
#[inline]
pub const fn new(
up: GamepadButtonType,
down: GamepadButtonType,
left: GamepadButtonType,
right: GamepadButtonType,
) -> Self {
Self {
up,
down,
left,
right,
processors: Vec::new(),
}
}
pub const DPAD: Self = Self::new(
GamepadButtonType::DPadUp,
GamepadButtonType::DPadDown,
GamepadButtonType::DPadLeft,
GamepadButtonType::DPadRight,
);
pub const ACTION_PAD: Self = Self::new(
GamepadButtonType::North,
GamepadButtonType::South,
GamepadButtonType::West,
GamepadButtonType::East,
);
}
impl UserInput for GamepadVirtualDPad {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::DualAxis
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Composite(vec![
Box::new(self.up),
Box::new(self.down),
Box::new(self.left),
Box::new(self.right),
])
}
}
#[serde_typetag]
impl DualAxislike for GamepadVirtualDPad {
#[must_use]
#[inline]
fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Gamepad) -> Vec2 {
let up = button_value(input_store, gamepad, self.up);
let down = button_value(input_store, gamepad, self.down);
let left = button_value(input_store, gamepad, self.left);
let right = button_value(input_store, gamepad, self.right);
let value = Vec2::new(right - left, up - down);
self.processors
.iter()
.fold(value, |value, processor| processor.process(value))
}
fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, gamepad: Option<Gamepad>) {
if value.x < 0.0 {
self.left.press_as_gamepad(world, gamepad);
} else if value.x > 0.0 {
self.right.press_as_gamepad(world, gamepad);
}
if value.y < 0.0 {
self.down.press_as_gamepad(world, gamepad);
} else if value.y > 0.0 {
self.up.press_as_gamepad(world, gamepad);
}
}
}
impl WithDualAxisProcessingPipelineExt for GamepadVirtualDPad {
#[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
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::plugin::{AccumulatorPlugin, CentralInputStorePlugin};
use bevy::input::gamepad::{
GamepadConnection, GamepadConnectionEvent, GamepadEvent, GamepadInfo,
};
use bevy::input::InputPlugin;
use bevy::prelude::*;
fn test_app() -> App {
let mut app = App::new();
app.add_plugins(MinimalPlugins);
app.add_plugins((InputPlugin, AccumulatorPlugin, CentralInputStorePlugin));
let mut gamepad_events = app.world_mut().resource_mut::<Events<GamepadEvent>>();
gamepad_events.send(GamepadEvent::Connection(GamepadConnectionEvent {
gamepad: Gamepad { id: 1 },
connection: GamepadConnection::Connected(GamepadInfo {
name: "TestController".into(),
}),
}));
app.update();
app.update();
app
}
#[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 app = test_app();
app.update();
let inputs = app.world().resource::<CentralInputStore>();
let gamepad = Gamepad::new(0);
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 data = Vec2::new(0.0, 1.0);
let mut app = test_app();
GamepadControlDirection::LEFT_UP.press_as_gamepad(app.world_mut(), Some(gamepad));
app.update();
let inputs = 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), data);
assert_eq!(right.axis_pair(inputs, gamepad), Vec2::ZERO);
let data = Vec2::new(0.0, 0.6);
let mut app = test_app();
GamepadControlAxis::LEFT_Y.set_value_as_gamepad(app.world_mut(), data.y, Some(gamepad));
app.update();
let inputs = 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), data);
assert_eq!(right.axis_pair(inputs, gamepad), Vec2::ZERO);
let data = Vec2::new(0.6, 0.4);
let mut app = test_app();
GamepadStick::LEFT.set_axis_pair_as_gamepad(app.world_mut(), data, Some(gamepad));
app.update();
let inputs = 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), data.x);
assert_eq!(left_y.value(inputs, gamepad), data.y);
assert_eq!(right_y.value(inputs, gamepad), 0.0);
assert_eq!(left.axis_pair(inputs, gamepad), data);
assert_eq!(right.axis_pair(inputs, gamepad), Vec2::ZERO);
}
#[test]
#[ignore = "Input mocking is subtly broken: https://github.com/Leafwing-Studios/leafwing-input-manager/issues/516"]
fn test_gamepad_buttons() {
let up = GamepadButtonType::DPadUp;
assert_eq!(up.kind(), InputControlKind::Button);
let left = GamepadButtonType::DPadLeft;
assert_eq!(left.kind(), InputControlKind::Button);
let down = GamepadButtonType::DPadDown;
assert_eq!(left.kind(), InputControlKind::Button);
let right = GamepadButtonType::DPadRight;
assert_eq!(left.kind(), InputControlKind::Button);
let x_axis = GamepadVirtualAxis::DPAD_X;
assert_eq!(x_axis.kind(), InputControlKind::Axis);
let y_axis = GamepadVirtualAxis::DPAD_Y;
assert_eq!(y_axis.kind(), InputControlKind::Axis);
let dpad = GamepadVirtualDPad::DPAD;
assert_eq!(dpad.kind(), InputControlKind::DualAxis);
let zeros = Vec2::new(0.0, 0.0);
let mut app = test_app();
app.update();
let inputs = app.world().resource::<CentralInputStore>();
let gamepad = Gamepad::new(0);
assert!(!up.pressed(inputs, gamepad));
assert!(!left.pressed(inputs, gamepad));
assert!(!down.pressed(inputs, gamepad));
assert!(!right.pressed(inputs, gamepad));
assert_eq!(x_axis.value(inputs, gamepad), 0.0);
assert_eq!(y_axis.value(inputs, gamepad), 0.0);
assert_eq!(dpad.axis_pair(inputs, gamepad), zeros);
let data = Vec2::new(1.0, 0.0);
let mut app = test_app();
GamepadButtonType::DPadLeft.press(app.world_mut());
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(!up.pressed(inputs, gamepad));
assert!(left.pressed(inputs, gamepad));
assert!(!down.pressed(inputs, gamepad));
assert!(!right.pressed(inputs, gamepad));
assert_eq!(x_axis.value(inputs, gamepad), 1.0);
assert_eq!(y_axis.value(inputs, gamepad), 0.0);
assert_eq!(dpad.axis_pair(inputs, gamepad), data);
let data = Vec2::new(0.6, 0.0);
let mut app = test_app();
GamepadVirtualAxis::DPAD_X.set_value(app.world_mut(), data.x);
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(!up.pressed(inputs, gamepad));
assert!(left.pressed(inputs, gamepad));
assert!(!down.pressed(inputs, gamepad));
assert!(!right.pressed(inputs, gamepad));
assert_eq!(x_axis.value(inputs, gamepad), 0.6);
assert_eq!(y_axis.value(inputs, gamepad), 0.0);
assert_eq!(dpad.axis_pair(inputs, gamepad), data);
let data = Vec2::new(0.6, 0.4);
let mut app = test_app();
GamepadVirtualDPad::DPAD.set_axis_pair(app.world_mut(), data);
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(!up.pressed(inputs, gamepad));
assert!(left.pressed(inputs, gamepad));
assert!(!down.pressed(inputs, gamepad));
assert!(!right.pressed(inputs, gamepad));
assert_eq!(x_axis.value(inputs, gamepad), data.x);
assert_eq!(y_axis.value(inputs, gamepad), data.y);
assert_eq!(dpad.axis_pair(inputs, gamepad), data);
}
}