use crate as leafwing_input_manager;
use crate::InputControlKind;
use crate::clashing_inputs::BasicInputs;
use crate::input_processing::{
AxisProcessor, DualAxisProcessor, WithAxisProcessingPipelineExt,
WithDualAxisProcessingPipelineExt,
};
use crate::prelude::updating::CentralInputStore;
use crate::prelude::{Axislike, DualAxislike, TripleAxislike, UserInput};
use crate::user_input::Buttonlike;
use bevy::math::{Vec2, Vec3};
#[cfg(feature = "gamepad")]
use bevy::prelude::GamepadButton;
#[cfg(feature = "keyboard")]
use bevy::prelude::KeyCode;
use bevy::prelude::{Entity, Reflect, World};
use leafwing_input_manager_macros::serde_typetag;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct VirtualAxis {
pub negative: Box<dyn Buttonlike>,
pub positive: Box<dyn Buttonlike>,
pub processors: Vec<AxisProcessor>,
}
impl VirtualAxis {
#[inline]
pub fn new(negative: impl Buttonlike, positive: impl Buttonlike) -> Self {
Self {
negative: Box::new(negative),
positive: Box::new(positive),
processors: Vec::new(),
}
}
#[cfg(feature = "keyboard")]
#[inline]
pub fn vertical_arrow_keys() -> Self {
Self::new(KeyCode::ArrowDown, KeyCode::ArrowUp)
}
#[cfg(feature = "keyboard")]
#[inline]
pub fn horizontal_arrow_keys() -> Self {
Self::new(KeyCode::ArrowLeft, KeyCode::ArrowRight)
}
#[cfg(feature = "keyboard")]
#[inline]
pub fn ws() -> Self {
Self::new(KeyCode::KeyS, KeyCode::KeyW)
}
#[cfg(feature = "keyboard")]
#[inline]
pub fn ad() -> Self {
Self::new(KeyCode::KeyA, KeyCode::KeyD)
}
#[cfg(feature = "keyboard")]
#[inline]
pub fn vertical_numpad() -> Self {
Self::new(KeyCode::Numpad2, KeyCode::Numpad8)
}
#[cfg(feature = "keyboard")]
#[inline]
pub fn horizontal_numpad() -> Self {
Self::new(KeyCode::Numpad4, KeyCode::Numpad6)
}
#[cfg(feature = "gamepad")]
#[inline]
pub fn dpad_x() -> Self {
Self::new(GamepadButton::DPadLeft, GamepadButton::DPadRight)
}
#[cfg(feature = "gamepad")]
#[inline]
pub fn dpad_y() -> Self {
Self::new(GamepadButton::DPadDown, GamepadButton::DPadUp)
}
#[cfg(feature = "gamepad")]
#[inline]
pub fn action_pad_x() -> Self {
Self::new(GamepadButton::West, GamepadButton::East)
}
#[cfg(feature = "gamepad")]
#[inline]
pub fn action_pad_y() -> Self {
Self::new(GamepadButton::South, GamepadButton::North)
}
}
impl UserInput for VirtualAxis {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::Axis
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Composite(vec![self.negative.clone(), self.positive.clone()])
}
}
#[serde_typetag]
impl Axislike for VirtualAxis {
#[inline]
fn get_value(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<f32> {
let negative = self.negative.value(input_store, gamepad);
let positive = self.positive.value(input_store, gamepad);
let value = positive - negative;
Some(
self.processors
.iter()
.fold(value, |value, processor| processor.process(value)),
)
}
fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Entity>) {
if value < 0.0 {
self.negative
.set_value_as_gamepad(world, value.abs(), gamepad);
} else if value > 0.0 {
self.positive.set_value_as_gamepad(world, value, gamepad);
}
}
}
impl WithAxisProcessingPipelineExt for VirtualAxis {
#[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 VirtualDPad {
pub up: Box<dyn Buttonlike>,
pub down: Box<dyn Buttonlike>,
pub left: Box<dyn Buttonlike>,
pub right: Box<dyn Buttonlike>,
pub processors: Vec<DualAxisProcessor>,
}
impl VirtualDPad {
#[inline]
pub fn new(
up: impl Buttonlike,
down: impl Buttonlike,
left: impl Buttonlike,
right: impl Buttonlike,
) -> Self {
Self {
up: Box::new(up),
down: Box::new(down),
left: Box::new(left),
right: Box::new(right),
processors: Vec::new(),
}
}
#[cfg(feature = "keyboard")]
#[inline]
pub fn arrow_keys() -> Self {
Self::new(
KeyCode::ArrowUp,
KeyCode::ArrowDown,
KeyCode::ArrowLeft,
KeyCode::ArrowRight,
)
}
#[cfg(feature = "keyboard")]
#[inline]
pub fn wasd() -> Self {
Self::new(KeyCode::KeyW, KeyCode::KeyS, KeyCode::KeyA, KeyCode::KeyD)
}
#[cfg(feature = "keyboard")]
#[inline]
pub fn numpad() -> Self {
Self::new(
KeyCode::Numpad8,
KeyCode::Numpad2,
KeyCode::Numpad4,
KeyCode::Numpad6,
)
}
#[cfg(feature = "gamepad")]
#[inline]
pub fn dpad() -> Self {
Self::new(
GamepadButton::DPadUp,
GamepadButton::DPadDown,
GamepadButton::DPadLeft,
GamepadButton::DPadRight,
)
}
#[cfg(feature = "gamepad")]
#[inline]
pub fn action_pad() -> Self {
Self::new(
GamepadButton::North,
GamepadButton::South,
GamepadButton::West,
GamepadButton::East,
)
}
}
impl UserInput for VirtualDPad {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::DualAxis
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Composite(vec![
self.up.clone(),
self.down.clone(),
self.left.clone(),
self.right.clone(),
])
}
}
#[serde_typetag]
impl DualAxislike for VirtualDPad {
#[inline]
fn get_axis_pair(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<Vec2> {
let up = self.up.get_value(input_store, gamepad);
let down = self.down.get_value(input_store, gamepad);
let left = self.left.get_value(input_store, gamepad);
let right = self.right.get_value(input_store, gamepad);
if up.is_none() && down.is_none() && left.is_none() && right.is_none() {
return None;
}
let up = up.unwrap_or(0.0);
let down = down.unwrap_or(0.0);
let left = left.unwrap_or(0.0);
let right = right.unwrap_or(0.0);
let value = Vec2::new(right - left, up - down);
Some(
self.processors
.iter()
.fold(value, |value, processor| processor.process(value)),
)
}
fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, gamepad: Option<Entity>) {
let Vec2 { x, y } = value;
if x < 0.0 {
self.left.set_value_as_gamepad(world, x.abs(), gamepad);
} else if x > 0.0 {
self.right.set_value_as_gamepad(world, x, gamepad);
}
if y < 0.0 {
self.down.set_value_as_gamepad(world, y.abs(), gamepad);
} else if y > 0.0 {
self.up.set_value_as_gamepad(world, y, gamepad);
}
}
}
impl WithDualAxisProcessingPipelineExt for VirtualDPad {
#[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
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct VirtualDPad3D {
pub up: Box<dyn Buttonlike>,
pub down: Box<dyn Buttonlike>,
pub left: Box<dyn Buttonlike>,
pub right: Box<dyn Buttonlike>,
pub forward: Box<dyn Buttonlike>,
pub backward: Box<dyn Buttonlike>,
}
impl VirtualDPad3D {
#[inline]
pub fn new(
up: impl Buttonlike,
down: impl Buttonlike,
left: impl Buttonlike,
right: impl Buttonlike,
forward: impl Buttonlike,
backward: impl Buttonlike,
) -> Self {
Self {
up: Box::new(up),
down: Box::new(down),
left: Box::new(left),
right: Box::new(right),
forward: Box::new(forward),
backward: Box::new(backward),
}
}
}
impl UserInput for VirtualDPad3D {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::TripleAxis
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::Composite(vec![
self.up.clone(),
self.down.clone(),
self.left.clone(),
self.right.clone(),
self.forward.clone(),
self.backward.clone(),
])
}
}
#[serde_typetag]
impl TripleAxislike for VirtualDPad3D {
#[inline]
fn get_axis_triple(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<Vec3> {
let up = self.up.get_value(input_store, gamepad);
let down = self.down.get_value(input_store, gamepad);
let left = self.left.get_value(input_store, gamepad);
let right = self.right.get_value(input_store, gamepad);
let forward = self.forward.get_value(input_store, gamepad);
let backward = self.backward.get_value(input_store, gamepad);
if up.is_none()
&& down.is_none()
&& left.is_none()
&& right.is_none()
&& forward.is_none()
&& backward.is_none()
{
return None;
}
let up = up.unwrap_or(0.0);
let down = down.unwrap_or(0.0);
let left = left.unwrap_or(0.0);
let right = right.unwrap_or(0.0);
let forward = forward.unwrap_or(0.0);
let backward = backward.unwrap_or(0.0);
Some(Vec3::new(right - left, up - down, backward - forward))
}
fn set_axis_triple_as_gamepad(&self, world: &mut World, value: Vec3, gamepad: Option<Entity>) {
let Vec3 { x, y, z } = value;
if x < 0.0 {
self.left.set_value_as_gamepad(world, x.abs(), gamepad);
} else if x > 0.0 {
self.right.set_value_as_gamepad(world, x, gamepad);
}
if y < 0.0 {
self.down.set_value_as_gamepad(world, y.abs(), gamepad);
} else if y > 0.0 {
self.up.set_value_as_gamepad(world, y, gamepad);
}
if z < 0.0 {
self.forward.set_value_as_gamepad(world, z.abs(), gamepad);
} else if z > 0.0 {
self.backward.set_value_as_gamepad(world, z, gamepad);
}
}
}
#[cfg(feature = "keyboard")]
#[cfg(test)]
mod tests {
use bevy::input::InputPlugin;
use bevy::prelude::*;
use crate::plugin::CentralInputStorePlugin;
use crate::prelude::updating::CentralInputStore;
use crate::prelude::*;
fn test_app() -> App {
let mut app = App::new();
app.add_plugins(InputPlugin)
.add_plugins(CentralInputStorePlugin);
app
}
#[test]
fn test_virtual() {
let x = VirtualAxis::horizontal_arrow_keys();
let xy = VirtualDPad::arrow_keys();
let xyz = VirtualDPad3D::new(
KeyCode::ArrowUp,
KeyCode::ArrowDown,
KeyCode::ArrowLeft,
KeyCode::ArrowRight,
KeyCode::KeyF,
KeyCode::KeyB,
);
let mut app = test_app();
app.update();
let inputs = app.world().resource::<CentralInputStore>();
let gamepad = Entity::PLACEHOLDER;
assert_eq!(x.value(inputs, gamepad), 0.0);
assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::ZERO);
assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::ZERO);
let mut app = test_app();
KeyCode::ArrowLeft.press(app.world_mut());
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert_eq!(x.value(inputs, gamepad), -1.0);
assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::new(-1.0, 0.0));
assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::new(-1.0, 0.0, 0.0));
let mut app = test_app();
KeyCode::ArrowUp.press(app.world_mut());
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert_eq!(x.value(inputs, gamepad), 0.0);
assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::new(0.0, 1.0));
assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::new(0.0, 1.0, 0.0));
let mut app = test_app();
KeyCode::ArrowRight.press(app.world_mut());
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert_eq!(x.value(inputs, gamepad), 1.0);
assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::new(1.0, 0.0));
assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::new(1.0, 0.0, 0.0));
let mut app = test_app();
KeyCode::KeyB.press(app.world_mut());
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert_eq!(x.value(inputs, gamepad), 0.0);
assert_eq!(xy.axis_pair(inputs, gamepad), Vec2::new(0.0, 0.0));
assert_eq!(xyz.axis_triple(inputs, gamepad), Vec3::new(0.0, 0.0, 1.0));
}
}