implement key binding system

This commit is contained in:
Crispy 2025-03-30 03:14:45 +02:00
parent 57512a4c6b
commit 70fd50d3bc
7 changed files with 410 additions and 57 deletions

323
src/input.rs Normal file
View file

@ -0,0 +1,323 @@
use std::{collections::HashMap, mem::transmute};
use raylib::{
ffi::{KeyboardKey, MouseButton},
RaylibHandle,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum ActionId {
StepSim,
Undo,
Redo,
// just like in C, because this way doesn't need more depenedencies
_ActionIdSize,
}
impl Default for Input {
fn default() -> Self {
use KeyboardKey::*;
let mut bindings = [(); ActionId::SIZE].map(|_| Vec::new());
let mut bind_key = |action, mods, key| {
bindings[action as usize] = vec![Binding {
modifiers: mods,
trigger: InputTrigger::Key(key),
}];
};
bind_key(ActionId::StepSim, vec![], KEY_SPACE);
bind_key(ActionId::Undo, vec![KEY_LEFT_CONTROL], KEY_Z);
bind_key(ActionId::Redo, vec![KEY_LEFT_CONTROL], KEY_Y);
Self {
bindings,
states: Default::default(),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
enum BindingState {
#[default]
Off,
Pressed,
Held,
Released,
}
type InputMap = HashMap<ActionId, Vec<Binding>>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(from = "InputMap", into = "InputMap")]
pub struct Input {
bindings: [Vec<Binding>; ActionId::SIZE],
states: [BindingState; ActionId::SIZE],
}
impl Input {
pub fn update(&mut self, rl: &RaylibHandle) {
for i in 0..ActionId::SIZE {
let bindings = &self.bindings[i];
let mut is_active = false;
for binding in bindings {
if binding.modifiers.iter().all(|&m| rl.is_key_down(m)) {
is_active |= match binding.trigger {
InputTrigger::Mouse(btn) => rl.is_mouse_button_down(btn),
InputTrigger::Key(key) => rl.is_key_down(key),
}
}
}
let state = &mut self.states[i];
*state = if is_active {
match state {
BindingState::Off | BindingState::Released => BindingState::Pressed,
BindingState::Pressed | BindingState::Held => BindingState::Held,
}
} else {
match state {
BindingState::Off | BindingState::Released => BindingState::Off,
BindingState::Pressed | BindingState::Held => BindingState::Released,
}
}
}
}
pub fn is_pressed(&self, action: ActionId) -> bool {
self.states[action as usize] == BindingState::Pressed
}
pub fn is_held(&self, action: ActionId) -> bool {
self.states[action as usize] == BindingState::Pressed
|| self.states[action as usize] == BindingState::Held
}
pub fn is_released(&self, action: ActionId) -> bool {
self.states[action as usize] == BindingState::Released
}
}
impl ActionId {
pub const SIZE: usize = Self::_ActionIdSize as usize;
fn from_usize(val: usize) -> Option<Self> {
if val < Self::SIZE {
Some(unsafe { transmute::<u8, ActionId>(val as u8) })
} else {
None
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(from = "BindingSerde", into = "BindingSerde")]
pub struct Binding {
modifiers: Vec<KeyboardKey>,
trigger: InputTrigger,
}
#[derive(Clone, Debug)]
pub enum InputTrigger {
Mouse(MouseButton),
Key(KeyboardKey),
}
// ###### everything below is for serialization of key bindings ######
impl From<InputMap> for Input {
fn from(value: InputMap) -> Self {
let mut new = Self::default();
for (action, saved_bindings) in value {
new.bindings[action as usize] = saved_bindings;
}
new
}
}
impl From<Input> for InputMap {
fn from(value: Input) -> Self {
value
.bindings
.iter()
.enumerate()
// for this to panic, the .bindings array would have to be larger than ActionId::SIZE
.map(|(i, b)| (ActionId::from_usize(i).unwrap(), b.clone()))
.collect()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct BindingSerde {
modifiers: Vec<String>,
trigger: InputTriggerSerde,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
enum InputTriggerSerde {
Mouse(String),
Key(String),
}
impl From<BindingSerde> for Binding {
fn from(value: BindingSerde) -> Self {
Self {
modifiers: value
.modifiers
.iter()
.map(|s| key_string_to_enum(s))
.collect(),
trigger: value.trigger.into(),
}
}
}
impl From<Binding> for BindingSerde {
fn from(value: Binding) -> Self {
Self {
modifiers: value.modifiers.iter().map(|c| format!("{c:?}")).collect(),
trigger: value.trigger.into(),
}
}
}
impl From<InputTrigger> for InputTriggerSerde {
fn from(value: InputTrigger) -> Self {
match value {
InputTrigger::Mouse(btn) => InputTriggerSerde::Mouse(format!("{btn:?}")),
InputTrigger::Key(key) => InputTriggerSerde::Key(format!("{key:?}")),
}
}
}
impl From<InputTriggerSerde> for InputTrigger {
fn from(value: InputTriggerSerde) -> Self {
match value {
InputTriggerSerde::Mouse(btn) => InputTrigger::Mouse(match btn.as_str() {
"MOUSE_BUTTON_LEFT" => MouseButton::MOUSE_BUTTON_LEFT,
"MOUSE_BUTTON_RIGHT" => MouseButton::MOUSE_BUTTON_RIGHT,
"MOUSE_BUTTON_MIDDLE" => MouseButton::MOUSE_BUTTON_MIDDLE,
"MOUSE_BUTTON_SIDE" => MouseButton::MOUSE_BUTTON_SIDE,
"MOUSE_BUTTON_EXTRA" => MouseButton::MOUSE_BUTTON_EXTRA,
"MOUSE_BUTTON_FORWARD" => MouseButton::MOUSE_BUTTON_FORWARD,
"MOUSE_BUTTON_BACK" => MouseButton::MOUSE_BUTTON_BACK,
_ => panic!("{btn} not a valid mouse button"),
}),
InputTriggerSerde::Key(key) => InputTrigger::Key(key_string_to_enum(key.as_str())),
}
}
}
fn key_string_to_enum(key: &str) -> KeyboardKey {
match key {
"KEY_NULL" => KeyboardKey::KEY_NULL,
"KEY_APOSTROPHE" => KeyboardKey::KEY_APOSTROPHE,
"KEY_COMMA" => KeyboardKey::KEY_COMMA,
"KEY_MINUS" => KeyboardKey::KEY_MINUS,
"KEY_PERIOD" => KeyboardKey::KEY_PERIOD,
"KEY_SLASH" => KeyboardKey::KEY_SLASH,
"KEY_ZERO" => KeyboardKey::KEY_ZERO,
"KEY_ONE" => KeyboardKey::KEY_ONE,
"KEY_TWO" => KeyboardKey::KEY_TWO,
"KEY_THREE" => KeyboardKey::KEY_THREE,
"KEY_FOUR" => KeyboardKey::KEY_FOUR,
"KEY_FIVE" => KeyboardKey::KEY_FIVE,
"KEY_SIX" => KeyboardKey::KEY_SIX,
"KEY_SEVEN" => KeyboardKey::KEY_SEVEN,
"KEY_EIGHT" => KeyboardKey::KEY_EIGHT,
"KEY_NINE" => KeyboardKey::KEY_NINE,
"KEY_SEMICOLON" => KeyboardKey::KEY_SEMICOLON,
"KEY_EQUAL" => KeyboardKey::KEY_EQUAL,
"KEY_A" => KeyboardKey::KEY_A,
"KEY_B" => KeyboardKey::KEY_B,
"KEY_C" => KeyboardKey::KEY_C,
"KEY_D" => KeyboardKey::KEY_D,
"KEY_E" => KeyboardKey::KEY_E,
"KEY_F" => KeyboardKey::KEY_F,
"KEY_G" => KeyboardKey::KEY_G,
"KEY_H" => KeyboardKey::KEY_H,
"KEY_I" => KeyboardKey::KEY_I,
"KEY_J" => KeyboardKey::KEY_J,
"KEY_K" => KeyboardKey::KEY_K,
"KEY_L" => KeyboardKey::KEY_L,
"KEY_M" => KeyboardKey::KEY_M,
"KEY_N" => KeyboardKey::KEY_N,
"KEY_O" => KeyboardKey::KEY_O,
"KEY_P" => KeyboardKey::KEY_P,
"KEY_Q" => KeyboardKey::KEY_Q,
"KEY_R" => KeyboardKey::KEY_R,
"KEY_S" => KeyboardKey::KEY_S,
"KEY_T" => KeyboardKey::KEY_T,
"KEY_U" => KeyboardKey::KEY_U,
"KEY_V" => KeyboardKey::KEY_V,
"KEY_W" => KeyboardKey::KEY_W,
"KEY_X" => KeyboardKey::KEY_X,
"KEY_Y" => KeyboardKey::KEY_Y,
"KEY_Z" => KeyboardKey::KEY_Z,
"KEY_LEFT_BRACKET" => KeyboardKey::KEY_LEFT_BRACKET,
"KEY_BACKSLASH" => KeyboardKey::KEY_BACKSLASH,
"KEY_RIGHT_BRACKET" => KeyboardKey::KEY_RIGHT_BRACKET,
"KEY_GRAVE" => KeyboardKey::KEY_GRAVE,
"KEY_SPACE" => KeyboardKey::KEY_SPACE,
"KEY_ESCAPE" => KeyboardKey::KEY_ESCAPE,
"KEY_ENTER" => KeyboardKey::KEY_ENTER,
"KEY_TAB" => KeyboardKey::KEY_TAB,
"KEY_BACKSPACE" => KeyboardKey::KEY_BACKSPACE,
"KEY_INSERT" => KeyboardKey::KEY_INSERT,
"KEY_DELETE" => KeyboardKey::KEY_DELETE,
"KEY_RIGHT" => KeyboardKey::KEY_RIGHT,
"KEY_LEFT" => KeyboardKey::KEY_LEFT,
"KEY_DOWN" => KeyboardKey::KEY_DOWN,
"KEY_UP" => KeyboardKey::KEY_UP,
"KEY_PAGE_UP" => KeyboardKey::KEY_PAGE_UP,
"KEY_PAGE_DOWN" => KeyboardKey::KEY_PAGE_DOWN,
"KEY_HOME" => KeyboardKey::KEY_HOME,
"KEY_END" => KeyboardKey::KEY_END,
"KEY_CAPS_LOCK" => KeyboardKey::KEY_CAPS_LOCK,
"KEY_SCROLL_LOCK" => KeyboardKey::KEY_SCROLL_LOCK,
"KEY_NUM_LOCK" => KeyboardKey::KEY_NUM_LOCK,
"KEY_PRINT_SCREEN" => KeyboardKey::KEY_PRINT_SCREEN,
"KEY_PAUSE" => KeyboardKey::KEY_PAUSE,
"KEY_F1" => KeyboardKey::KEY_F1,
"KEY_F2" => KeyboardKey::KEY_F2,
"KEY_F3" => KeyboardKey::KEY_F3,
"KEY_F4" => KeyboardKey::KEY_F4,
"KEY_F5" => KeyboardKey::KEY_F5,
"KEY_F6" => KeyboardKey::KEY_F6,
"KEY_F7" => KeyboardKey::KEY_F7,
"KEY_F8" => KeyboardKey::KEY_F8,
"KEY_F9" => KeyboardKey::KEY_F9,
"KEY_F10" => KeyboardKey::KEY_F10,
"KEY_F11" => KeyboardKey::KEY_F11,
"KEY_F12" => KeyboardKey::KEY_F12,
"KEY_LEFT_SHIFT" => KeyboardKey::KEY_LEFT_SHIFT,
"KEY_LEFT_CONTROL" => KeyboardKey::KEY_LEFT_CONTROL,
"KEY_LEFT_ALT" => KeyboardKey::KEY_LEFT_ALT,
"KEY_LEFT_SUPER" => KeyboardKey::KEY_LEFT_SUPER,
"KEY_RIGHT_SHIFT" => KeyboardKey::KEY_RIGHT_SHIFT,
"KEY_RIGHT_CONTROL" => KeyboardKey::KEY_RIGHT_CONTROL,
"KEY_RIGHT_ALT" => KeyboardKey::KEY_RIGHT_ALT,
"KEY_RIGHT_SUPER" => KeyboardKey::KEY_RIGHT_SUPER,
"KEY_KB_MENU" => KeyboardKey::KEY_KB_MENU,
"KEY_KP_0" => KeyboardKey::KEY_KP_0,
"KEY_KP_1" => KeyboardKey::KEY_KP_1,
"KEY_KP_2" => KeyboardKey::KEY_KP_2,
"KEY_KP_3" => KeyboardKey::KEY_KP_3,
"KEY_KP_4" => KeyboardKey::KEY_KP_4,
"KEY_KP_5" => KeyboardKey::KEY_KP_5,
"KEY_KP_6" => KeyboardKey::KEY_KP_6,
"KEY_KP_7" => KeyboardKey::KEY_KP_7,
"KEY_KP_8" => KeyboardKey::KEY_KP_8,
"KEY_KP_9" => KeyboardKey::KEY_KP_9,
"KEY_KP_DECIMAL" => KeyboardKey::KEY_KP_DECIMAL,
"KEY_KP_DIVIDE" => KeyboardKey::KEY_KP_DIVIDE,
"KEY_KP_MULTIPLY" => KeyboardKey::KEY_KP_MULTIPLY,
"KEY_KP_SUBTRACT" => KeyboardKey::KEY_KP_SUBTRACT,
"KEY_KP_ADD" => KeyboardKey::KEY_KP_ADD,
"KEY_KP_ENTER" => KeyboardKey::KEY_KP_ENTER,
"KEY_KP_EQUAL" => KeyboardKey::KEY_KP_EQUAL,
"KEY_BACK" => KeyboardKey::KEY_BACK,
"KEY_VOLUME_UP" => KeyboardKey::KEY_VOLUME_UP,
"KEY_VOLUME_DOWN" => KeyboardKey::KEY_VOLUME_DOWN,
_ => panic!("{key} not a known key name"),
}
}