pub mod mod_keys;
pub mod relationship;
use core::fmt::{self, Display, Formatter};
use bevy::{
ecs::{lifecycle::HookContext, world::DeferredWorld},
prelude::*,
};
use log::{Level, error, log_enabled, warn};
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
use crate::prelude::*;
#[derive(Component, Reflect, Debug, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[component(on_insert = on_insert, immutable)]
#[require(FirstActivation)]
pub enum Binding {
Keyboard { key: KeyCode, mod_keys: ModKeys },
MouseButton {
button: MouseButton,
mod_keys: ModKeys,
},
MouseMotion { mod_keys: ModKeys },
MouseWheel { mod_keys: ModKeys },
GamepadButton(GamepadButton),
GamepadAxis(GamepadAxis),
AnyKey,
None,
}
impl Binding {
#[must_use]
pub const fn mouse_motion() -> Self {
Self::MouseMotion {
mod_keys: ModKeys::empty(),
}
}
#[must_use]
pub const fn mouse_wheel() -> Self {
Self::MouseWheel {
mod_keys: ModKeys::empty(),
}
}
#[must_use]
pub fn mod_keys_count(self) -> usize {
self.mod_keys().iter_names().count()
}
#[must_use]
pub const fn mod_keys(self) -> ModKeys {
match self {
Binding::Keyboard { mod_keys, .. }
| Binding::MouseButton { mod_keys, .. }
| Binding::MouseMotion { mod_keys }
| Binding::MouseWheel { mod_keys } => mod_keys,
Binding::GamepadButton(_)
| Binding::GamepadAxis(_)
| Binding::AnyKey
| Binding::None => ModKeys::empty(),
}
}
#[must_use]
pub fn without_mod_keys(self) -> Self {
self.with_mod_keys(ModKeys::empty())
}
}
impl Display for Binding {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mod_keys = self.mod_keys();
if !mod_keys.is_empty() {
write!(f, "{mod_keys} + ")?;
}
match self {
Binding::Keyboard { key, .. } => write!(f, "{key:?}"),
Binding::MouseButton { button, .. } => write!(f, "Mouse {button:?}"),
Binding::MouseMotion { .. } => write!(f, "Mouse Motion"),
Binding::MouseWheel { .. } => write!(f, "Scroll Wheel"),
Binding::GamepadButton(gamepad_button) => write!(f, "{gamepad_button:?}"),
Binding::GamepadAxis(gamepad_axis) => write!(f, "{gamepad_axis:?}"),
Binding::AnyKey => write!(f, "Any Key"),
Binding::None => write!(f, "None"),
}
}
}
impl From<KeyCode> for Binding {
fn from(key: KeyCode) -> Self {
Self::Keyboard {
key,
mod_keys: Default::default(),
}
}
}
impl From<MouseButton> for Binding {
fn from(button: MouseButton) -> Self {
Self::MouseButton {
button,
mod_keys: Default::default(),
}
}
}
impl From<GamepadButton> for Binding {
fn from(value: GamepadButton) -> Self {
Self::GamepadButton(value)
}
}
impl From<GamepadAxis> for Binding {
fn from(value: GamepadAxis) -> Self {
Self::GamepadAxis(value)
}
}
pub trait InputModKeys {
#[must_use]
fn with_mod_keys(self, mod_keys: ModKeys) -> Binding;
}
impl<I: Into<Binding>> InputModKeys for I {
fn with_mod_keys(self, mod_keys: ModKeys) -> Binding {
let binding = self.into();
match binding {
Binding::Keyboard { key, .. } => Binding::Keyboard { key, mod_keys },
Binding::MouseButton { button, .. } => Binding::MouseButton { button, mod_keys },
Binding::MouseMotion { .. } => Binding::MouseMotion { mod_keys },
Binding::MouseWheel { .. } => Binding::MouseWheel { mod_keys },
Binding::GamepadButton { .. }
| Binding::GamepadAxis { .. }
| Binding::None
| Binding::AnyKey => {
error!("can't add `{mod_keys:?}` to `{binding:?}`");
binding
}
}
}
}
fn on_insert(mut world: DeferredWorld, ctx: HookContext) {
let mut entity = world.entity_mut(ctx.entity);
let mut first_activation = entity.get_mut::<FirstActivation>().unwrap();
**first_activation = true;
if log_enabled!(Level::Warn) {
if let Some(action) = entity.get::<BindingOf>().map(|b| **b) {
if world.get::<ActionState>(action).is_none() {
let binding = world.get::<Binding>(ctx.entity).unwrap();
warn!(
"`{}` has binding `{binding:?}`, but the associated action `{action}` is invalid",
ctx.entity
);
}
} else {
let binding = world.get::<Binding>(ctx.entity).unwrap();
warn!(
"`{}` has binding `{binding:?}`, but it is not associated with any action",
ctx.entity
);
}
}
}
#[derive(Component, Deref, DerefMut, Default)]
pub(crate) struct FirstActivation(bool);
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
#[test]
fn input_display() {
assert_eq!(
Binding::Keyboard {
key: KeyCode::KeyA,
mod_keys: ModKeys::empty()
}
.to_string(),
"KeyA"
);
assert_eq!(
Binding::Keyboard {
key: KeyCode::KeyA,
mod_keys: ModKeys::CONTROL
}
.to_string(),
"Ctrl + KeyA"
);
assert_eq!(
Binding::MouseButton {
button: MouseButton::Left,
mod_keys: ModKeys::empty()
}
.to_string(),
"Mouse Left"
);
assert_eq!(
Binding::MouseMotion {
mod_keys: ModKeys::empty()
}
.to_string(),
"Mouse Motion"
);
assert_eq!(
Binding::MouseWheel {
mod_keys: ModKeys::empty()
}
.to_string(),
"Scroll Wheel"
);
assert_eq!(
Binding::GamepadAxis(GamepadAxis::LeftStickX).to_string(),
"LeftStickX"
);
assert_eq!(
Binding::GamepadButton(GamepadButton::North).to_string(),
"North"
);
}
}