use crate::action_diff::ActionDiff;
use crate::timing::Timing;
use crate::Actionlike;
use crate::{axislike::DualAxisData, buttonlike::ButtonState};
use bevy::ecs::component::Component;
use bevy::prelude::Resource;
use bevy::reflect::Reflect;
use bevy::utils::{Duration, HashMap, Instant};
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, Reflect)]
pub struct ActionData {
pub state: ButtonState,
pub value: f32,
pub axis_pair: Option<DualAxisData>,
pub timing: Timing,
pub consumed: bool,
}
#[derive(Resource, Component, Clone, Debug, PartialEq, Serialize, Deserialize, Reflect)]
pub struct ActionState<A: Actionlike> {
action_data: HashMap<A, ActionData>,
}
impl<A: Actionlike> Default for ActionState<A> {
fn default() -> Self {
Self {
action_data: HashMap::default(),
}
}
}
impl<A: Actionlike> ActionState<A> {
pub fn update(&mut self, action_data: HashMap<A, ActionData>) {
for (action, action_datum) in self.action_data.iter_mut() {
if !action_data.contains_key(action) {
action_datum.state.release();
}
}
for (action, action_datum) in action_data {
if self.action_data.contains_key(&action) {
match action_datum.state {
ButtonState::JustPressed => self.press(&action),
ButtonState::Pressed => self.press(&action),
ButtonState::JustReleased => self.release(&action),
ButtonState::Released => self.release(&action),
}
let current_data = self.action_data.get_mut(&action).unwrap();
current_data.axis_pair = action_datum.axis_pair;
current_data.value = action_datum.value;
} else {
self.action_data.insert(action, action_datum.clone());
}
}
}
pub fn tick(&mut self, current_instant: Instant, previous_instant: Instant) {
self.action_data.values_mut().for_each(|ad| ad.state.tick());
self.action_data.values_mut().for_each(|ad| {
if !ad.consumed {
ad.timing.tick(current_instant, previous_instant);
}
});
}
#[inline]
#[must_use]
pub fn action_data(&self, action: &A) -> Option<&ActionData> {
self.action_data.get(action)
}
#[inline]
#[must_use]
pub fn action_data_mut(&mut self, action: &A) -> Option<&mut ActionData> {
self.action_data.get_mut(action)
}
#[inline]
#[must_use]
pub fn action_data_mut_or_default(&mut self, action: &A) -> &mut ActionData {
self.action_data
.raw_entry_mut()
.from_key(action)
.or_insert_with(|| (action.clone(), ActionData::default()))
.1
}
pub fn value(&self, action: &A) -> f32 {
match self.action_data(action) {
Some(action_data) => action_data.value,
None => 0.0,
}
}
pub fn clamped_value(&self, action: &A) -> f32 {
self.value(action).clamp(-1., 1.)
}
pub fn axis_pair(&self, action: &A) -> Option<DualAxisData> {
let action_data = self.action_data(action)?;
action_data.axis_pair
}
pub fn clamped_axis_pair(&self, action: &A) -> Option<DualAxisData> {
self.axis_pair(action)
.map(|pair| DualAxisData::new(pair.x().clamp(-1.0, 1.0), pair.y().clamp(-1.0, 1.0)))
}
#[inline]
pub fn set_action_data(&mut self, action: A, data: ActionData) {
self.action_data.insert(action, data);
}
#[inline]
pub fn press(&mut self, action: &A) {
let action_data = self.action_data_mut_or_default(action);
if action_data.consumed {
return;
}
if action_data.state.released() {
action_data.timing.flip();
}
action_data.state.press();
}
#[inline]
pub fn release(&mut self, action: &A) {
let action_data = self.action_data_mut_or_default(action);
action_data.consumed = false;
if action_data.state.pressed() {
action_data.timing.flip();
}
action_data.state.release();
}
#[inline]
pub fn consume(&mut self, action: &A) {
let action_data = self.action_data_mut_or_default(action);
action_data.consumed = true;
action_data.state.release();
action_data.timing.flip();
}
#[inline]
pub fn consume_all(&mut self) {
for action in self.keys() {
self.consume(&action);
}
}
pub fn release_all(&mut self) {
for action in self.keys() {
self.release(&action);
}
}
#[inline]
#[must_use]
pub fn consumed(&self, action: &A) -> bool {
matches!(self.action_data(action), Some(action_data) if action_data.consumed)
}
#[inline]
#[must_use]
pub fn pressed(&self, action: &A) -> bool {
matches!(self.action_data(action), Some(action_data) if action_data.state.pressed())
}
#[inline]
#[must_use]
pub fn just_pressed(&self, action: &A) -> bool {
matches!(self.action_data(action), Some(action_data) if action_data.state.just_pressed())
}
#[inline]
#[must_use]
pub fn released(&self, action: &A) -> bool {
match self.action_data(action) {
Some(action_data) => action_data.state.released(),
None => true,
}
}
#[inline]
#[must_use]
pub fn just_released(&self, action: &A) -> bool {
matches!(self.action_data(action), Some(action_data) if action_data.state.just_released())
}
#[must_use]
pub fn get_pressed(&self) -> Vec<A> {
self.action_data
.iter()
.filter(|(_action, data)| data.state.pressed())
.map(|(action, _data)| action.clone())
.collect()
}
#[must_use]
pub fn get_just_pressed(&self) -> Vec<A> {
self.action_data
.iter()
.filter(|(_action, data)| data.state.just_pressed())
.map(|(action, _data)| action.clone())
.collect()
}
#[must_use]
pub fn get_released(&self) -> Vec<A> {
self.action_data
.iter()
.filter(|(_action, data)| data.state.released())
.map(|(action, _data)| action.clone())
.collect()
}
#[must_use]
pub fn get_just_released(&self) -> Vec<A> {
self.action_data
.iter()
.filter(|(_action, data)| data.state.just_released())
.map(|(action, _data)| action.clone())
.collect()
}
pub fn instant_started(&self, action: &A) -> Option<Instant> {
let action_data = self.action_data(action)?;
action_data.timing.instant_started
}
pub fn current_duration(&self, action: &A) -> Duration {
self.action_data(action)
.map(|data| data.timing.current_duration)
.unwrap_or_default()
}
pub fn previous_duration(&self, action: &A) -> Duration {
self.action_data(action)
.map(|data| data.timing.previous_duration)
.unwrap_or_default()
}
pub fn apply_diff(&mut self, action_diff: &ActionDiff<A>) {
match action_diff {
ActionDiff::Pressed { action } => {
self.press(action);
self.action_data_mut(action).unwrap().value = 1.;
}
ActionDiff::Released { action } => {
self.release(action);
let action_data = self.action_data_mut(action).unwrap();
action_data.value = 0.;
action_data.axis_pair = None;
}
ActionDiff::ValueChanged { action, value } => {
self.press(action);
self.action_data_mut(action).unwrap().value = *value;
}
ActionDiff::AxisPairChanged { action, axis_pair } => {
self.press(action);
let action_data = self.action_data_mut(action).unwrap();
action_data.axis_pair = Some(DualAxisData::from_xy(*axis_pair));
action_data.value = axis_pair.length();
}
};
}
#[inline]
#[must_use]
pub fn keys(&self) -> Vec<A> {
self.action_data.keys().cloned().collect()
}
}
#[cfg(test)]
mod tests {
use crate as leafwing_input_manager;
use crate::action_state::ActionState;
use crate::clashing_inputs::ClashStrategy;
use crate::input_map::InputMap;
use crate::input_mocking::MockInput;
use crate::input_streams::InputStreams;
use bevy::input::InputPlugin;
use bevy::prelude::*;
use bevy::utils::{Duration, Instant};
use leafwing_input_manager_macros::Actionlike;
#[test]
fn press_lifecycle() {
let mut app = App::new();
app.add_plugins(InputPlugin);
#[derive(Actionlike, Clone, Copy, PartialEq, Eq, Hash, Debug, bevy::prelude::Reflect)]
enum Action {
Run,
Jump,
Hide,
}
let mut action_state = ActionState::<Action>::default();
let mut input_map = InputMap::default();
input_map.insert(Action::Run, KeyCode::KeyR);
let input_streams = InputStreams::from_world(&app.world, None);
action_state.update(input_map.which_pressed(&input_streams, ClashStrategy::PressAll));
assert!(!action_state.pressed(&Action::Run));
assert!(!action_state.just_pressed(&Action::Run));
assert!(action_state.released(&Action::Run));
assert!(!action_state.just_released(&Action::Run));
app.send_input(KeyCode::KeyR);
app.update();
let input_streams = InputStreams::from_world(&app.world, None);
action_state.update(input_map.which_pressed(&input_streams, ClashStrategy::PressAll));
assert!(action_state.pressed(&Action::Run));
assert!(action_state.just_pressed(&Action::Run));
assert!(!action_state.released(&Action::Run));
assert!(!action_state.just_released(&Action::Run));
action_state.tick(Instant::now(), Instant::now() - Duration::from_micros(1));
action_state.update(input_map.which_pressed(&input_streams, ClashStrategy::PressAll));
assert!(action_state.pressed(&Action::Run));
assert!(!action_state.just_pressed(&Action::Run));
assert!(!action_state.released(&Action::Run));
assert!(!action_state.just_released(&Action::Run));
app.release_input(KeyCode::KeyR);
app.update();
let input_streams = InputStreams::from_world(&app.world, None);
action_state.update(input_map.which_pressed(&input_streams, ClashStrategy::PressAll));
assert!(!action_state.pressed(&Action::Run));
assert!(!action_state.just_pressed(&Action::Run));
assert!(action_state.released(&Action::Run));
assert!(action_state.just_released(&Action::Run));
action_state.tick(Instant::now(), Instant::now() - Duration::from_micros(1));
action_state.update(input_map.which_pressed(&input_streams, ClashStrategy::PressAll));
assert!(!action_state.pressed(&Action::Run));
assert!(!action_state.just_pressed(&Action::Run));
assert!(action_state.released(&Action::Run));
assert!(!action_state.just_released(&Action::Run));
}
#[test]
fn update_with_clashes_prioritizing_longest() {
#[derive(Actionlike, Clone, Copy, PartialEq, Eq, Hash, Debug, Reflect)]
enum Action {
One,
Two,
OneAndTwo,
}
use bevy::prelude::KeyCode::*;
let mut input_map = InputMap::default();
input_map.insert(Action::One, Digit1);
input_map.insert(Action::Two, Digit2);
input_map.insert_chord(Action::OneAndTwo, [Digit1, Digit2]);
let mut app = App::new();
app.add_plugins(InputPlugin);
let mut action_state = ActionState::<Action>::default();
let input_streams = InputStreams::from_world(&app.world, None);
action_state
.update(input_map.which_pressed(&input_streams, ClashStrategy::PrioritizeLongest));
assert!(action_state.released(&Action::One));
assert!(action_state.released(&Action::Two));
assert!(action_state.released(&Action::OneAndTwo));
app.send_input(Digit1);
app.update();
let input_streams = InputStreams::from_world(&app.world, None);
action_state
.update(input_map.which_pressed(&input_streams, ClashStrategy::PrioritizeLongest));
assert!(action_state.pressed(&Action::One));
assert!(action_state.released(&Action::Two));
assert!(action_state.released(&Action::OneAndTwo));
action_state.tick(Instant::now(), Instant::now() - Duration::from_micros(1));
action_state
.update(input_map.which_pressed(&input_streams, ClashStrategy::PrioritizeLongest));
assert!(action_state.pressed(&Action::One));
assert!(action_state.released(&Action::Two));
assert!(action_state.released(&Action::OneAndTwo));
app.send_input(Digit2);
app.update();
let input_streams = InputStreams::from_world(&app.world, None);
action_state
.update(input_map.which_pressed(&input_streams, ClashStrategy::PrioritizeLongest));
assert!(action_state.released(&Action::One));
assert!(action_state.released(&Action::Two));
assert!(action_state.pressed(&Action::OneAndTwo));
action_state.tick(Instant::now(), Instant::now() - Duration::from_micros(1));
action_state
.update(input_map.which_pressed(&input_streams, ClashStrategy::PrioritizeLongest));
assert!(action_state.released(&Action::One));
assert!(action_state.released(&Action::Two));
assert!(action_state.pressed(&Action::OneAndTwo));
}
}