use crate::action_state::ActionData;
use crate::buttonlike::ButtonState;
use crate::clashing_inputs::ClashStrategy;
use crate::input_streams::InputStreams;
use crate::user_input::{InputKind, Modifier, UserInput};
use crate::Actionlike;
#[cfg(feature = "asset")]
use bevy::asset::Asset;
use bevy::ecs::component::Component;
use bevy::ecs::system::Resource;
use bevy::input::gamepad::Gamepad;
use bevy::reflect::Reflect;
use bevy::utils::HashMap;
use serde::{Deserialize, Serialize};
use core::fmt::Debug;
#[derive(Resource, Component, Debug, Clone, PartialEq, Eq, Reflect, Serialize, Deserialize)]
#[cfg_attr(feature = "asset", derive(Asset))]
pub struct InputMap<A: Actionlike> {
map: HashMap<A, Vec<UserInput>>,
associated_gamepad: Option<Gamepad>,
}
impl<A: Actionlike> Default for InputMap<A> {
fn default() -> Self {
InputMap {
map: HashMap::default(),
associated_gamepad: None,
}
}
}
impl<A: Actionlike> InputMap<A> {
#[must_use]
pub fn new(bindings: impl IntoIterator<Item = (A, impl Into<UserInput>)>) -> Self {
let mut input_map = InputMap::default();
input_map.insert_multiple(bindings);
input_map
}
#[inline]
#[must_use]
pub fn build(&mut self) -> Self {
self.clone()
}
}
impl<A: Actionlike> InputMap<A> {
pub fn insert(&mut self, action: A, input: impl Into<UserInput>) -> &mut Self {
let input = input.into();
if !matches!(self.map.get(&action), Some(vec) if vec.contains(&input)) {
self.map.entry(action).or_default().push(input);
}
self
}
#[inline(always)]
pub fn insert_one_to_many(
&mut self,
action: A,
input: impl IntoIterator<Item = impl Into<UserInput>>,
) -> &mut Self {
for input in input {
self.insert(action.clone(), input);
}
self
}
pub fn insert_multiple(
&mut self,
input_action_pairs: impl IntoIterator<Item = (A, impl Into<UserInput>)>,
) -> &mut Self {
for (action, input) in input_action_pairs {
self.insert(action, input);
}
self
}
pub fn insert_chord(
&mut self,
action: A,
buttons: impl IntoIterator<Item = impl Into<InputKind>>,
) -> &mut Self {
self.insert(action, UserInput::chord(buttons));
self
}
pub fn insert_modified(
&mut self,
action: A,
modifier: Modifier,
input: impl Into<InputKind>,
) -> &mut Self {
self.insert(action, UserInput::modified(modifier, input));
self
}
pub fn merge(&mut self, other: &InputMap<A>) -> &mut Self {
if self.associated_gamepad != other.associated_gamepad {
self.associated_gamepad = None;
}
for other_action in other.map.iter() {
for input in other_action.1.iter() {
self.insert(other_action.0.clone(), input.clone());
}
}
self
}
}
impl<A: Actionlike> InputMap<A> {
#[must_use]
pub fn gamepad(&self) -> Option<Gamepad> {
self.associated_gamepad
}
pub fn set_gamepad(&mut self, gamepad: Gamepad) -> &mut Self {
self.associated_gamepad = Some(gamepad);
self
}
pub fn clear_gamepad(&mut self) -> &mut Self {
self.associated_gamepad = None;
self
}
}
impl<A: Actionlike> InputMap<A> {
#[must_use]
pub fn pressed(
&self,
action: &A,
input_streams: &InputStreams,
clash_strategy: ClashStrategy,
) -> bool {
self.which_pressed(input_streams, clash_strategy)
.get(action)
.map(|datum| datum.state.pressed())
.unwrap_or_default()
}
#[must_use]
pub fn which_pressed(
&self,
input_streams: &InputStreams,
clash_strategy: ClashStrategy,
) -> HashMap<A, ActionData> {
let mut action_data = HashMap::new();
for (action, input_vec) in self.iter() {
let mut action_datum = ActionData::default();
for input in input_vec {
if let Some(axis_pair) = input_streams.input_axis_pair(input) {
action_datum.axis_pair = action_datum
.axis_pair
.map_or(Some(axis_pair), |current_axis_pair| {
Some(current_axis_pair.merged_with(axis_pair))
});
}
if input_streams.input_pressed(input) {
action_datum.state = ButtonState::JustPressed;
action_datum.value += input_streams.input_value(input, true);
}
}
action_data.insert(action.clone(), action_datum);
}
self.handle_clashes(&mut action_data, input_streams, clash_strategy);
action_data
}
}
impl<A: Actionlike> InputMap<A> {
pub fn iter(&self) -> impl Iterator<Item = (&A, &Vec<UserInput>)> {
self.map.iter()
}
pub(crate) fn actions(&self) -> impl Iterator<Item = &A> {
self.map.keys()
}
#[must_use]
pub fn get(&self, action: &A) -> Option<&Vec<UserInput>> {
self.map.get(action)
}
#[must_use]
pub fn get_mut(&mut self, action: &A) -> Option<&mut Vec<UserInput>> {
self.map.get_mut(action)
}
#[must_use]
pub fn len(&self) -> usize {
self.map.values().map(|inputs| inputs.len()).sum()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn clear(&mut self) {
self.map.clear();
}
}
impl<A: Actionlike> InputMap<A> {
pub fn clear_action(&mut self, action: &A) {
self.map.remove(action);
}
pub fn remove_at(&mut self, action: &A, index: usize) -> Option<UserInput> {
let input_vec = self.map.get_mut(action)?;
(input_vec.len() > index).then(|| input_vec.remove(index))
}
pub fn remove(&mut self, action: &A, input: impl Into<UserInput>) -> Option<usize> {
let input_vec = self.map.get_mut(action)?;
let user_input = input.into();
let index = input_vec.iter().position(|i| i == &user_input)?;
input_vec.remove(index);
Some(index)
}
}
impl<A: Actionlike> From<HashMap<A, Vec<UserInput>>> for InputMap<A> {
fn from(map: HashMap<A, Vec<UserInput>>) -> Self {
map.iter()
.flat_map(|(action, inputs)| inputs.iter().map(|input| (action.clone(), input.clone())))
.collect()
}
}
impl<A: Actionlike> FromIterator<(A, UserInput)> for InputMap<A> {
fn from_iter<T: IntoIterator<Item = (A, UserInput)>>(iter: T) -> Self {
InputMap::new(iter)
}
}
mod tests {
use bevy::prelude::Reflect;
use serde::{Deserialize, Serialize};
use crate as leafwing_input_manager;
use crate::prelude::*;
#[derive(
Actionlike,
Serialize,
Deserialize,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Debug,
Reflect,
)]
enum Action {
Run,
Jump,
Hide,
}
#[test]
fn insertion_idempotency() {
use bevy::input::keyboard::KeyCode;
let mut input_map = InputMap::<Action>::default();
input_map.insert(Action::Run, KeyCode::Space);
assert_eq!(
input_map.get(&Action::Run),
Some(&vec![KeyCode::Space.into()])
);
input_map.insert(Action::Run, KeyCode::Space);
assert_eq!(
input_map.get(&Action::Run),
Some(&vec![KeyCode::Space.into()])
);
}
#[test]
fn multiple_insertion() {
use bevy::input::keyboard::KeyCode;
let mut input_map_1 = InputMap::<Action>::default();
input_map_1.insert(Action::Run, KeyCode::Space);
input_map_1.insert(Action::Run, KeyCode::Enter);
assert_eq!(
input_map_1.get(&Action::Run),
Some(&vec![KeyCode::Space.into(), KeyCode::Enter.into()])
);
let input_map_2 =
InputMap::<Action>::new([(Action::Run, KeyCode::Space), (Action::Run, KeyCode::Enter)]);
assert_eq!(input_map_1, input_map_2);
}
#[test]
fn chord_singleton_coercion() {
use crate::input_map::UserInput;
use bevy::input::keyboard::KeyCode;
let mut input_map_1 = InputMap::<Action>::default();
input_map_1.insert(Action::Run, KeyCode::Space);
let mut input_map_2 = InputMap::<Action>::default();
input_map_2.insert(Action::Run, UserInput::chord([KeyCode::Space]));
assert_eq!(input_map_1, input_map_2);
}
#[test]
fn input_clearing() {
use bevy::input::keyboard::KeyCode;
let mut input_map = InputMap::<Action>::default();
input_map.insert(Action::Run, KeyCode::Space);
input_map.clear_action(&Action::Run);
assert_eq!(input_map, InputMap::default());
input_map.insert(Action::Run, KeyCode::Space);
input_map.insert(Action::Run, KeyCode::ShiftLeft);
assert!(input_map.remove_at(&Action::Run, 1).is_some());
assert!(
input_map.remove_at(&Action::Run, 1).is_none(),
"Should return None on second removal at the same index"
);
assert!(input_map.remove_at(&Action::Run, 0).is_some());
assert!(
input_map.remove_at(&Action::Run, 0).is_none(),
"Should return None on second removal at the same index"
);
}
#[test]
fn merging() {
use bevy::input::{gamepad::GamepadButtonType, keyboard::KeyCode};
let mut input_map = InputMap::default();
let mut default_keyboard_map = InputMap::default();
default_keyboard_map.insert(Action::Run, KeyCode::ShiftLeft);
default_keyboard_map.insert_chord(Action::Hide, [KeyCode::ControlLeft, KeyCode::KeyH]);
let mut default_gamepad_map = InputMap::default();
default_gamepad_map.insert(Action::Run, GamepadButtonType::South);
default_gamepad_map.insert(Action::Hide, GamepadButtonType::East);
input_map.merge(&default_keyboard_map);
assert_eq!(input_map, default_keyboard_map);
input_map.merge(&default_keyboard_map);
assert_eq!(input_map, default_keyboard_map);
}
#[test]
fn gamepad_swapping() {
use bevy::input::gamepad::Gamepad;
let mut input_map = InputMap::<Action>::default();
assert_eq!(input_map.gamepad(), None);
input_map.set_gamepad(Gamepad { id: 0 });
assert_eq!(input_map.gamepad(), Some(Gamepad { id: 0 }));
input_map.clear_gamepad();
assert_eq!(input_map.gamepad(), None);
}
}