#[cfg(feature = "ui")]
use crate::action_driver::ActionStateDriver;
use crate::{
action_state::ActionState, clashing_inputs::ClashStrategy, input_map::InputMap,
input_streams::InputStreams, plugin::ToggleActions, Actionlike,
};
use bevy::ecs::prelude::*;
use bevy::{
input::{
gamepad::{GamepadAxis, GamepadButton, Gamepads},
keyboard::KeyCode,
mouse::{MouseButton, MouseMotion, MouseWheel},
Axis, ButtonInput,
},
log::warn,
math::Vec2,
time::{Real, Time},
utils::{HashMap, Instant},
};
use crate::action_diff::{ActionDiff, ActionDiffEvent};
#[cfg(feature = "ui")]
use bevy::ui::Interaction;
#[cfg(feature = "egui")]
use bevy_egui::EguiContext;
pub fn tick_action_state<A: Actionlike>(
mut query: Query<&mut ActionState<A>>,
action_state: Option<ResMut<ActionState<A>>>,
time: Res<Time<Real>>,
mut stored_previous_instant: Local<Option<Instant>>,
) {
let current_instant = time.last_update().unwrap_or_else(|| time.startup());
let previous_instant = stored_previous_instant.unwrap_or_else(|| time.startup());
if let Some(mut action_state) = action_state {
action_state.tick(current_instant, previous_instant);
}
for mut action_state in query.iter_mut() {
action_state.tick(current_instant, previous_instant);
}
*stored_previous_instant = time.last_update();
}
#[allow(clippy::too_many_arguments)]
pub fn update_action_state<A: Actionlike>(
gamepad_buttons: Res<ButtonInput<GamepadButton>>,
gamepad_button_axes: Res<Axis<GamepadButton>>,
gamepad_axes: Res<Axis<GamepadAxis>>,
gamepads: Res<Gamepads>,
keycodes: Option<Res<ButtonInput<KeyCode>>>,
mouse_buttons: Option<Res<ButtonInput<MouseButton>>>,
mut mouse_wheel: EventReader<MouseWheel>,
mut mouse_motion: EventReader<MouseMotion>,
clash_strategy: Res<ClashStrategy>,
#[cfg(all(feature = "ui", not(feature = "no_ui_priority")))] interactions: Query<&Interaction>,
#[cfg(feature = "egui")] mut maybe_egui: Query<(Entity, &'static mut EguiContext)>,
action_state: Option<ResMut<ActionState<A>>>,
input_map: Option<Res<InputMap<A>>>,
mut query: Query<(&mut ActionState<A>, &InputMap<A>)>,
) {
let gamepad_buttons = gamepad_buttons.into_inner();
let gamepad_button_axes = gamepad_button_axes.into_inner();
let gamepad_axes = gamepad_axes.into_inner();
let gamepads = gamepads.into_inner();
let keycodes = keycodes.map(|keycodes| keycodes.into_inner());
let mouse_buttons = mouse_buttons.map(|mouse_buttons| mouse_buttons.into_inner());
let mouse_wheel: Option<Vec<MouseWheel>> = Some(mouse_wheel.read().cloned().collect());
let mouse_motion: Vec<MouseMotion> = mouse_motion.read().cloned().collect();
#[cfg(all(feature = "ui", not(feature = "no_ui_priority")))]
let (mouse_buttons, mouse_wheel) = if interactions
.iter()
.any(|&interaction| interaction != Interaction::None)
{
(None, None)
} else {
(mouse_buttons, mouse_wheel)
};
#[cfg(feature = "egui")]
let keycodes = maybe_egui
.iter_mut()
.all(|(_, mut ctx)| !ctx.get_mut().wants_keyboard_input())
.then_some(keycodes)
.flatten();
#[cfg(feature = "egui")]
let (mouse_buttons, mouse_wheel) = if maybe_egui.iter_mut().any(|(_, mut ctx)| {
ctx.get_mut().is_pointer_over_area() || ctx.get_mut().wants_pointer_input()
}) {
(None, None)
} else {
(mouse_buttons, mouse_wheel)
};
let resources = input_map
.zip(action_state)
.map(|(input_map, action_state)| (Mut::from(action_state), input_map.into_inner()));
for (mut action_state, input_map) in query.iter_mut().chain(resources) {
let input_streams = InputStreams {
gamepad_buttons,
gamepad_button_axes,
gamepad_axes,
gamepads,
keycodes,
mouse_buttons,
mouse_wheel: mouse_wheel.clone(),
mouse_motion: mouse_motion.clone(),
associated_gamepad: input_map.gamepad(),
};
action_state.update(input_map.which_pressed(&input_streams, *clash_strategy));
}
}
#[cfg(feature = "ui")]
pub fn update_action_state_from_interaction<A: Actionlike>(
ui_query: Query<(&Interaction, &ActionStateDriver<A>)>,
mut action_state_query: Query<&mut ActionState<A>>,
) {
for (&interaction, action_state_driver) in ui_query.iter() {
if interaction == Interaction::Pressed {
for entity in action_state_driver.targets.iter() {
let mut action_state = action_state_query
.get_mut(*entity)
.expect("Entity does not exist, or does not have an `ActionState` component.");
action_state.press(&action_state_driver.action.clone());
}
}
}
}
pub fn generate_action_diffs<A: Actionlike>(
action_state: Option<ResMut<ActionState<A>>>,
action_state_query: Query<(Entity, &ActionState<A>)>,
mut action_diffs: EventWriter<ActionDiffEvent<A>>,
mut previous_values: Local<HashMap<A, HashMap<Option<Entity>, f32>>>,
mut previous_axis_pairs: Local<HashMap<A, HashMap<Option<Entity>, Vec2>>>,
) {
let action_state_iter = action_state_query
.iter()
.map(|(entity, action_state)| (Some(entity), action_state))
.chain(
action_state
.as_ref()
.map(|action_state| (None, action_state.as_ref())),
);
for (maybe_entity, action_state) in action_state_iter {
let mut diffs = vec![];
for action in action_state.get_just_pressed() {
let Some(action_data) = action_state.action_data(&action) else {
warn!("Action in ActionDiff has no data: was it generated correctly?");
continue;
};
if let Some(axis_pair) = action_data.axis_pair {
diffs.push(ActionDiff::AxisPairChanged {
action: action.clone(),
axis_pair: axis_pair.into(),
});
previous_axis_pairs
.entry(action)
.or_default()
.insert(maybe_entity, axis_pair.xy());
} else {
let value = action_data.value;
diffs.push(if value == 1. {
ActionDiff::Pressed {
action: action.clone(),
}
} else {
ActionDiff::ValueChanged {
action: action.clone(),
value,
}
});
previous_values
.entry(action)
.or_default()
.insert(maybe_entity, value);
}
}
for action in action_state.get_pressed() {
if action_state.just_pressed(&action) {
continue;
}
let Some(action_data) = action_state.action_data(&action) else {
warn!("Action in ActionState has no data: was it generated correctly?");
continue;
};
if let Some(axis_pair) = action_data.axis_pair {
let current_value = axis_pair.xy();
let values = previous_axis_pairs.get_mut(&action).unwrap();
let existing_value = values.get(&maybe_entity);
if !matches!(existing_value, Some(value) if *value == current_value) {
diffs.push(ActionDiff::AxisPairChanged {
action: action.clone(),
axis_pair: axis_pair.into(),
});
values.insert(maybe_entity, current_value);
}
} else {
let current_value = action_data.value;
let values = previous_values.get_mut(&action).unwrap();
if !matches!(values.get(&maybe_entity), Some(value) if *value == current_value) {
diffs.push(ActionDiff::ValueChanged {
action: action.clone(),
value: current_value,
});
values.insert(maybe_entity, current_value);
}
}
}
for action in action_state.get_just_released() {
diffs.push(ActionDiff::Released {
action: action.clone(),
});
if let Some(previous_axes) = previous_axis_pairs.get_mut(&action) {
previous_axes.remove(&maybe_entity);
}
if let Some(previous_values) = previous_values.get_mut(&action) {
previous_values.remove(&maybe_entity);
}
}
if !diffs.is_empty() {
action_diffs.send(ActionDiffEvent {
owner: maybe_entity,
action_diffs: diffs,
});
}
}
}
pub fn release_on_disable<A: Actionlike>(
mut query: Query<&mut ActionState<A>>,
resource: Option<ResMut<ActionState<A>>>,
toggle_actions: Res<ToggleActions<A>>,
) {
if toggle_actions.is_changed() && !toggle_actions.enabled {
for mut action_state in query.iter_mut() {
action_state.release_all();
}
if let Some(mut action_state) = resource {
action_state.release_all();
}
}
}
pub fn release_on_input_map_removed<A: Actionlike>(
mut removed_components: RemovedComponents<InputMap<A>>,
input_map_resource: Option<ResMut<InputMap<A>>>,
action_state_resource: Option<ResMut<ActionState<A>>>,
mut input_map_resource_existed: Local<bool>,
mut action_state_query: Query<&mut ActionState<A>>,
) {
let mut iter = action_state_query.iter_many_mut(removed_components.read());
while let Some(mut action_state) = iter.fetch_next() {
action_state.release_all();
}
if input_map_resource.is_some() {
*input_map_resource_existed = true;
} else if *input_map_resource_existed {
if let Some(mut action_state) = action_state_resource {
action_state.release_all();
}
*input_map_resource_existed = false;
}
}
pub fn run_if_enabled<A: Actionlike>(toggle_actions: Res<ToggleActions<A>>) -> bool {
toggle_actions.enabled
}