use crate::components::InputKey;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Bindable {
Forward,
Backward,
Left,
Right,
Sprint,
Jump,
Interact,
}
impl Bindable {
pub const ALL: [Bindable; 7] = [
Bindable::Forward,
Bindable::Backward,
Bindable::Left,
Bindable::Right,
Bindable::Sprint,
Bindable::Jump,
Bindable::Interact,
];
pub fn setting_key(self) -> &'static str {
match self {
Bindable::Forward => "key_forward",
Bindable::Backward => "key_backward",
Bindable::Left => "key_left",
Bindable::Right => "key_right",
Bindable::Sprint => "key_sprint",
Bindable::Jump => "key_jump",
Bindable::Interact => "key_interact",
}
}
pub fn from_setting_key(key: &str) -> Option<Bindable> {
Bindable::ALL.into_iter().find(|b| b.setting_key() == key)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct KeyMap {
#[serde(default = "def_forward")]
pub forward: InputKey,
#[serde(default = "def_backward")]
pub backward: InputKey,
#[serde(default = "def_left")]
pub left: InputKey,
#[serde(default = "def_right")]
pub right: InputKey,
#[serde(default = "def_sprint")]
pub sprint: InputKey,
#[serde(default = "def_jump")]
pub jump: InputKey,
#[serde(default = "def_interact")]
pub interact: InputKey,
}
impl KeyMap {
pub const DEFAULT: KeyMap = KeyMap {
forward: InputKey::W,
backward: InputKey::S,
left: InputKey::A,
right: InputKey::D,
sprint: InputKey::Shift,
jump: InputKey::Space,
interact: InputKey::E,
};
pub fn get(self, action: Bindable) -> InputKey {
match action {
Bindable::Forward => self.forward,
Bindable::Backward => self.backward,
Bindable::Left => self.left,
Bindable::Right => self.right,
Bindable::Sprint => self.sprint,
Bindable::Jump => self.jump,
Bindable::Interact => self.interact,
}
}
pub fn set(&mut self, action: Bindable, key: InputKey) {
match action {
Bindable::Forward => self.forward = key,
Bindable::Backward => self.backward = key,
Bindable::Left => self.left = key,
Bindable::Right => self.right = key,
Bindable::Sprint => self.sprint = key,
Bindable::Jump => self.jump = key,
Bindable::Interact => self.interact = key,
}
}
pub fn action_for_key(self, key: InputKey) -> Option<Bindable> {
Bindable::ALL.into_iter().find(|&b| self.get(b) == key)
}
pub fn rebind(&mut self, action: Bindable, new_key: InputKey) {
let old_key = self.get(action);
if old_key == new_key {
return;
}
if let Some(other) = self.action_for_key(new_key)
&& other != action
{
self.set(other, old_key);
}
self.set(action, new_key);
}
}
impl Default for KeyMap {
fn default() -> Self {
Self::DEFAULT
}
}
fn def_forward() -> InputKey {
KeyMap::DEFAULT.forward
}
fn def_backward() -> InputKey {
KeyMap::DEFAULT.backward
}
fn def_left() -> InputKey {
KeyMap::DEFAULT.left
}
fn def_right() -> InputKey {
KeyMap::DEFAULT.right
}
fn def_sprint() -> InputKey {
KeyMap::DEFAULT.sprint
}
fn def_jump() -> InputKey {
KeyMap::DEFAULT.jump
}
fn def_interact() -> InputKey {
KeyMap::DEFAULT.interact
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::String;
use alloc::vec::Vec;
#[test]
fn default_is_wasd_shift_space_e() {
let m = KeyMap::default();
assert_eq!(m.forward, InputKey::W);
assert_eq!(m.backward, InputKey::S);
assert_eq!(m.left, InputKey::A);
assert_eq!(m.right, InputKey::D);
assert_eq!(m.sprint, InputKey::Shift);
assert_eq!(m.jump, InputKey::Space);
assert_eq!(m.interact, InputKey::E);
}
#[test]
fn setting_key_round_trips() {
for b in Bindable::ALL {
assert_eq!(Bindable::from_setting_key(b.setting_key()), Some(b));
}
assert_eq!(Bindable::from_setting_key("vsync"), None);
assert_eq!(Bindable::from_setting_key("key_nope"), None);
}
#[test]
fn get_set_round_trip() {
let mut m = KeyMap::default();
m.set(Bindable::Forward, InputKey::Up);
assert_eq!(m.get(Bindable::Forward), InputKey::Up);
}
#[test]
fn set_get_cover_every_action_arm() {
let keys = [
InputKey::Up,
InputKey::Down,
InputKey::Left,
InputKey::Right,
InputKey::Q,
InputKey::R,
InputKey::T,
];
let mut m = KeyMap::default();
for (b, k) in Bindable::ALL.into_iter().zip(keys) {
m.set(b, k);
}
for (b, k) in Bindable::ALL.into_iter().zip(keys) {
assert_eq!(m.get(b), k);
}
}
#[test]
fn empty_cbor_map_uses_all_defaults() {
let empty: alloc::collections::BTreeMap<String, InputKey> =
alloc::collections::BTreeMap::new();
let mut bytes = Vec::new();
ciborium::into_writer(&empty, &mut bytes).unwrap();
let loaded: KeyMap = ciborium::from_reader(&bytes[..]).unwrap();
assert_eq!(loaded, KeyMap::DEFAULT);
}
#[test]
fn action_for_key_finds_the_holder() {
let m = KeyMap::default();
assert_eq!(m.action_for_key(InputKey::W), Some(Bindable::Forward));
assert_eq!(m.action_for_key(InputKey::Space), Some(Bindable::Jump));
assert_eq!(m.action_for_key(InputKey::Q), None);
}
#[test]
fn rebind_to_free_key_just_sets_it() {
let mut m = KeyMap::default();
m.rebind(Bindable::Forward, InputKey::Q);
assert_eq!(m.forward, InputKey::Q);
assert_eq!(m.backward, InputKey::S);
}
#[test]
fn rebind_to_own_key_is_a_noop() {
let mut m = KeyMap::default();
m.rebind(Bindable::Forward, InputKey::W);
assert_eq!(m, KeyMap::default());
}
#[test]
fn rebind_to_occupied_key_swaps() {
let mut m = KeyMap::default();
m.rebind(Bindable::Forward, InputKey::S);
assert_eq!(m.forward, InputKey::S);
assert_eq!(m.backward, InputKey::W);
for b in Bindable::ALL {
assert_eq!(m.action_for_key(m.get(b)), Some(b));
}
}
#[test]
fn cbor_round_trip_and_missing_field_defaults() {
let m = KeyMap {
forward: InputKey::Up,
..KeyMap::default()
};
let mut bytes = Vec::new();
ciborium::into_writer(&m, &mut bytes).unwrap();
let back: KeyMap = ciborium::from_reader(&bytes[..]).unwrap();
assert_eq!(back, m);
#[derive(Serialize)]
struct Partial {
forward: InputKey,
backward: InputKey,
left: InputKey,
right: InputKey,
sprint: InputKey,
jump: InputKey,
}
let partial = Partial {
forward: InputKey::Up,
backward: InputKey::S,
left: InputKey::A,
right: InputKey::D,
sprint: InputKey::Shift,
jump: InputKey::Space,
};
let mut bytes = Vec::new();
ciborium::into_writer(&partial, &mut bytes).unwrap();
let loaded: KeyMap = ciborium::from_reader(&bytes[..]).unwrap();
assert_eq!(loaded.forward, InputKey::Up);
assert_eq!(loaded.interact, InputKey::E);
}
}