use bevy::math::{Vec2, Vec3};
use bevy::prelude::{Entity, Reflect, World};
use leafwing_input_manager_macros::serde_typetag;
use serde::{Deserialize, Serialize};
use crate as leafwing_input_manager;
use crate::InputControlKind;
use crate::clashing_inputs::BasicInputs;
use crate::user_input::{Buttonlike, TripleAxislike, UserInput};
use super::updating::CentralInputStore;
use super::{Axislike, DualAxislike};
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct ButtonlikeChord(
pub(crate) Vec<Box<dyn Buttonlike>>,
);
impl ButtonlikeChord {
#[inline]
pub fn new<U: Buttonlike>(inputs: impl IntoIterator<Item = U>) -> Self {
Self::default().with_multiple(inputs)
}
#[inline]
pub fn from_single(input: impl Buttonlike) -> Self {
Self::default().with(input)
}
#[cfg(feature = "keyboard")]
pub fn modified(modifier: super::keyboard::ModifierKey, input: impl Buttonlike) -> Self {
Self::default().with(modifier).with(input)
}
#[inline]
pub fn with(mut self, input: impl Buttonlike) -> Self {
self.push_boxed_unique(Box::new(input));
self
}
#[inline]
pub fn with_multiple<U: Buttonlike>(mut self, inputs: impl IntoIterator<Item = U>) -> Self {
for input in inputs.into_iter() {
self.push_boxed_unique(Box::new(input));
}
self
}
#[inline]
fn push_boxed_unique(&mut self, input: Box<dyn Buttonlike>) {
if !self.0.contains(&input) {
self.0.push(input);
}
}
}
impl UserInput for ButtonlikeChord {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::Button
}
#[inline]
fn decompose(&self) -> BasicInputs {
let inputs = self
.0
.iter()
.flat_map(|input| input.decompose().inputs())
.collect();
BasicInputs::Chord(inputs)
}
}
#[serde_typetag]
impl Buttonlike for ButtonlikeChord {
#[inline]
fn get_pressed(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<bool> {
for input in &self.0 {
if !(input.get_pressed(input_store, gamepad)?) {
return Some(false);
}
}
Some(true)
}
fn press(&self, world: &mut World) {
for input in &self.0 {
input.press(world);
}
}
fn release(&self, world: &mut World) {
for input in &self.0 {
input.release(world);
}
}
fn press_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
for input in &self.0 {
input.press_as_gamepad(world, gamepad);
}
}
fn release_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
for input in &self.0 {
input.release_as_gamepad(world, gamepad);
}
}
}
impl<U: Buttonlike> FromIterator<U> for ButtonlikeChord {
#[inline]
fn from_iter<T: IntoIterator<Item = U>>(iter: T) -> Self {
Self::default().with_multiple(iter)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct AxislikeChord {
pub button: Box<dyn Buttonlike>,
pub axis: Box<dyn Axislike>,
}
impl AxislikeChord {
#[inline]
pub fn new(button: impl Buttonlike, axis: impl Axislike) -> Self {
Self {
button: Box::new(button),
axis: Box::new(axis),
}
}
}
impl UserInput for AxislikeChord {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::Axis
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::compose(self.button.decompose(), self.axis.decompose())
}
}
#[serde_typetag]
impl Axislike for AxislikeChord {
fn get_value(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<f32> {
if self.button.pressed(input_store, gamepad) {
self.axis.get_value(input_store, gamepad)
} else {
Some(0.0)
}
}
fn set_value(&self, world: &mut World, value: f32) {
self.axis.set_value(world, value);
}
fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Entity>) {
self.axis.set_value_as_gamepad(world, value, gamepad);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct DualAxislikeChord {
pub button: Box<dyn Buttonlike>,
pub dual_axis: Box<dyn DualAxislike>,
}
impl DualAxislikeChord {
#[inline]
pub fn new(button: impl Buttonlike, dual_axis: impl DualAxislike) -> Self {
Self {
button: Box::new(button),
dual_axis: Box::new(dual_axis),
}
}
}
impl UserInput for DualAxislikeChord {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::DualAxis
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::compose(self.button.decompose(), self.dual_axis.decompose())
}
}
#[serde_typetag]
impl DualAxislike for DualAxislikeChord {
fn get_axis_pair(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<Vec2> {
let pressed = self.button.get_pressed(input_store, gamepad)?;
if pressed {
self.dual_axis.get_axis_pair(input_store, gamepad)
} else {
Some(Vec2::ZERO)
}
}
fn set_axis_pair(&self, world: &mut World, axis_pair: Vec2) {
self.dual_axis.set_axis_pair(world, axis_pair);
}
fn set_axis_pair_as_gamepad(
&self,
world: &mut World,
axis_pair: Vec2,
gamepad: Option<Entity>,
) {
self.dual_axis
.set_axis_pair_as_gamepad(world, axis_pair, gamepad);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct TripleAxislikeChord {
pub button: Box<dyn Buttonlike>,
pub triple_axis: Box<dyn TripleAxislike>,
}
impl TripleAxislikeChord {
#[inline]
pub fn new(button: impl Buttonlike, triple_axis: impl TripleAxislike) -> Self {
Self {
button: Box::new(button),
triple_axis: Box::new(triple_axis),
}
}
}
impl UserInput for TripleAxislikeChord {
#[inline]
fn kind(&self) -> InputControlKind {
InputControlKind::TripleAxis
}
#[inline]
fn decompose(&self) -> BasicInputs {
BasicInputs::compose(self.button.decompose(), self.triple_axis.decompose())
}
}
#[serde_typetag]
impl TripleAxislike for TripleAxislikeChord {
fn get_axis_triple(&self, input_store: &CentralInputStore, gamepad: Entity) -> Option<Vec3> {
let pressed = self.button.get_pressed(input_store, gamepad)?;
if pressed {
self.triple_axis.get_axis_triple(input_store, gamepad)
} else {
Some(Vec3::ZERO)
}
}
fn set_axis_triple(&self, world: &mut World, axis_triple: Vec3) {
self.triple_axis.set_axis_triple(world, axis_triple);
}
fn set_axis_triple_as_gamepad(
&self,
world: &mut World,
axis_triple: Vec3,
gamepad: Option<Entity>,
) {
self.triple_axis
.set_axis_triple_as_gamepad(world, axis_triple, gamepad);
}
}
#[cfg(feature = "keyboard")]
#[cfg(test)]
mod tests {
use super::ButtonlikeChord;
use crate::plugin::CentralInputStorePlugin;
use crate::user_input::Buttonlike;
use crate::user_input::updating::CentralInputStore;
use bevy::input::InputPlugin;
use bevy::input::gamepad::{GamepadConnection, GamepadConnectionEvent, GamepadEvent};
use bevy::prelude::*;
fn test_app() -> App {
let mut app = App::new();
app.add_plugins(MinimalPlugins)
.add_plugins(InputPlugin)
.add_plugins(CentralInputStorePlugin);
let gamepad = app.world_mut().spawn(()).id();
let mut gamepad_messages = app.world_mut().resource_mut::<Messages<GamepadEvent>>();
gamepad_messages.write(GamepadEvent::Connection(GamepadConnectionEvent {
gamepad,
connection: GamepadConnection::Connected {
name: "TestController".into(),
vendor_id: None,
product_id: None,
},
}));
app.update();
app.update();
app
}
#[test]
fn test_chord_with_buttons_only() {
let chord = ButtonlikeChord::new([KeyCode::KeyC, KeyCode::KeyH])
.with(KeyCode::KeyO)
.with_multiple([KeyCode::KeyR, KeyCode::KeyD]);
let required_keys = [
KeyCode::KeyC,
KeyCode::KeyH,
KeyCode::KeyO,
KeyCode::KeyR,
KeyCode::KeyD,
];
let expected_inners = required_keys
.iter()
.map(|key| Box::new(*key) as Box<dyn Buttonlike>)
.collect::<Vec<_>>();
assert_eq!(chord.0, expected_inners);
let mut app = test_app();
app.update();
let gamepad = app.world_mut().spawn(()).id();
let inputs = app.world().resource::<CentralInputStore>();
assert!(!chord.pressed(inputs, gamepad));
let mut app = test_app();
for key in required_keys {
key.press(app.world_mut());
}
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(chord.pressed(inputs, gamepad));
for i in 1..=4 {
let mut app = test_app();
for key in required_keys.iter().take(i) {
key.press(app.world_mut());
}
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(!chord.pressed(inputs, gamepad));
}
let mut app = test_app();
for key in required_keys.iter().take(4) {
key.press(app.world_mut());
}
KeyCode::KeyB.press(app.world_mut());
app.update();
let inputs = app.world().resource::<CentralInputStore>();
assert!(!chord.pressed(inputs, gamepad));
}
}