use bevy_ecs::prelude::{Component, Resource, World};
use serde::{Deserialize, Deserializer, Serialize};
pub use rusting_core::input::{ActionMap, InputBinding};
use super::{KeyCode, MouseButton, PadButton};
pub(super) fn install(world: &mut World) {
world.insert_resource(ActionMap::default());
}
#[derive(
Component, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize,
)]
#[serde(default)]
pub struct InputAction {
pub action: String,
#[serde(deserialize_with = "input_names")]
pub inputs: Vec<String>,
}
pub fn parse_input(name: &str) -> Result<InputBinding, String> {
let mouse = match name {
"MouseLeft" => Some(MouseButton::Left),
"MouseRight" => Some(MouseButton::Right),
"MouseMiddle" => Some(MouseButton::Middle),
_ => None,
};
if let Some(button) = mouse {
return Ok(InputBinding::Mouse(button));
}
if let Some(pad) = name.strip_prefix("Pad") {
return serde_json::from_value::<PadButton>(serde_json::Value::from(pad))
.map(InputBinding::Pad)
.map_err(|_| {
format!(
"unknown gamepad input `{name}`: use PadSouth, PadEast, PadWest, \
PadNorth, PadLeftBumper, PadRightBumper, PadLeftTrigger, \
PadRightTrigger, PadSelect, PadStart, PadLeftStick, PadRightStick, \
PadDpadUp/Down/Left/Right or PadLeftStickUp/Down/Left/Right \
(and RightStick)"
)
});
}
serde_json::from_value::<KeyCode>(serde_json::Value::from(name))
.map(InputBinding::Key)
.map_err(|_| {
format!(
"unknown input `{name}`: use a key name such as KeyF, Space \
or ArrowUp, MouseLeft, MouseRight or MouseMiddle, or a gamepad \
input such as PadSouth"
)
})
}
fn input_names<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Vec<String>, D::Error> {
let names = Vec::<String>::deserialize(deserializer)?;
for name in &names {
parse_input(name).map_err(serde::de::Error::custom)?;
}
Ok(names)
}
#[derive(Resource, Clone, Default)]
pub(super) struct SceneBindings {
declared: Vec<(String, String)>,
bound: Vec<(String, InputBinding)>,
}
pub fn bind_input_actions(world: &mut World) {
let mut scene =
world.remove_resource::<SceneBindings>().unwrap_or_default();
sync_bindings(world, &mut scene);
world.insert_resource(scene);
}
fn sync_bindings(world: &mut World, scene: &mut SceneBindings) {
let mut query = world.query::<&InputAction>();
let mut declared: Vec<(String, String)> = query
.iter(world)
.flat_map(|action| {
action
.inputs
.iter()
.map(|name| (action.action.clone(), name.clone()))
})
.collect();
declared.sort();
if scene.declared != declared {
let mut map = world.resource_mut::<ActionMap>();
for (action, binding) in scene.bound.drain(..) {
map.unbind(&action, binding);
}
for (action, name) in &declared {
let Ok(binding) = parse_input(name) else {
continue;
};
if !map.bindings(action).contains(&binding) {
map.bind(action.clone(), binding);
scene.bound.push((action.clone(), binding));
}
}
scene.declared = declared;
}
}