marble-machinations/src/input.rs

569 lines
14 KiB
Rust

use std::{collections::BTreeMap, mem::transmute, vec};
use raylib::{
color::Color,
drawing::{RaylibDraw, RaylibDrawHandle},
ffi::{KeyboardKey, MouseButton},
RaylibHandle,
};
use serde::{Deserialize, Serialize};
use crate::{
theme::{BG_DARK, BG_LIGHT},
ui::text_button,
util::{rect, screen_centered_rect},
Globals,
};
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum ActionId {
Undo,
Redo,
Copy,
Paste,
ToggleMenu,
StartSim,
StopSim,
StepSim,
// just like in C, because this way doesn't need more dependencies
_EnumSize,
}
impl Default for Input {
fn default() -> Self {
use Button::*;
let mut bindings = [(); ActionId::SIZE].map(|_| Vec::new());
let mut bind_key = |action, mods, trigger| {
bindings[action as usize].push(Binding {
modifiers: mods,
trigger,
});
};
bind_key(ActionId::Undo, vec![LCtrl], Z);
bind_key(ActionId::Redo, vec![LCtrl], Y);
bind_key(ActionId::Redo, vec![LCtrl, LShift], Z);
bind_key(ActionId::Copy, vec![LCtrl], C);
bind_key(ActionId::Paste, vec![LCtrl], V);
bind_key(ActionId::ToggleMenu, vec![], Escape);
bind_key(ActionId::StartSim, vec![], Enter);
bind_key(ActionId::StopSim, vec![], Enter);
bind_key(ActionId::StepSim, vec![], Space);
Self {
bindings,
states: Default::default(),
editing_input: None,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
enum BindingState {
#[default]
Off,
Pressed,
Held,
Released,
}
type InputMap = BTreeMap<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],
#[serde(skip)]
editing_input: Option<(ActionId, usize, BindingEdit)>,
}
#[derive(Clone, Debug)]
enum BindingEdit {
Init,
Adding(Binding),
Releasing(Binding),
}
impl Input {
pub fn draw_edit(&mut self, d: &mut RaylibDrawHandle, globals: &mut Globals) {
let mut y = 96;
if self.editing_input.is_some() {
globals.mouse.clear();
}
for action_index in 0..ActionId::SIZE {
let action = ActionId::from_usize(action_index).unwrap();
d.draw_text(&format!("{action:?}"), 16, y, 20, Color::ORANGE);
if self.bindings[action_index].is_empty() {
y += 32;
}
for (binding_index, binding) in self.bindings[action_index].iter().enumerate() {
if text_button(d, &globals.mouse, 160, y, 80, "remove") {
self.bindings[action_index].remove(binding_index);
return;
}
if text_button(d, &globals.mouse, 245, y, 45, "edit") {
self.editing_input = Some((action, binding_index, BindingEdit::Init));
}
let trigger = format!("{:?}", binding.trigger);
d.draw_text(&trigger, 300, y, 20, Color::LIMEGREEN);
let x = 310 + d.measure_text(&trigger, 20);
let modifiers = format!("{:?}", binding.modifiers);
d.draw_text(&modifiers, x, y, 20, Color::LIGHTBLUE);
y += 32;
}
if text_button(d, &globals.mouse, 160, y, 130, "add binding") {
self.editing_input =
Some((action, self.bindings[action_index].len(), BindingEdit::Init));
}
y += 45;
}
if let Some((action, binding_index, edit_state)) = &mut self.editing_input {
globals.mouse.update(d);
let border = screen_centered_rect(d, 368, 128);
d.draw_rectangle_rec(border, BG_LIGHT);
let bounds = screen_centered_rect(d, 360, 120);
d.draw_rectangle_rec(bounds, BG_DARK);
let x = bounds.x as i32;
let y = bounds.y as i32;
d.draw_text(
&format!("editing binding for {action:?}"),
x + 5,
y + 5,
20,
Color::WHITE,
);
let y = y + 30;
let ok_btn_x = x + 10;
let ok_btn_y = y + 40;
let ok_btn_width = 80;
let ok_btn_rect = rect(ok_btn_x, ok_btn_y, ok_btn_width, 30);
for key_index in 0..Button::SIZE {
let key = Button::from_usize(key_index).unwrap();
match edit_state {
BindingEdit::Init => {
if key.just_pressed(d) {
*edit_state = BindingEdit::Adding(Binding {
modifiers: Vec::new(),
trigger: key,
});
}
}
BindingEdit::Adding(binding) => {
if key.just_pressed(d) {
if key != binding.trigger && !binding.modifiers.contains(&key) {
binding.modifiers.push(binding.trigger);
binding.trigger = key;
}
} else if key.released(d) {
if let Some(i) = binding.modifiers.iter().position(|&k| k == key) {
binding.modifiers.remove(i);
binding.modifiers.push(binding.trigger);
binding.trigger = key;
}
*edit_state = BindingEdit::Releasing(binding.clone());
}
}
BindingEdit::Releasing(_binding) => {
let clicking_ok =
globals.mouse.is_over(ok_btn_rect) && key == Button::MouseLeft;
if key.just_pressed(d) && !clicking_ok {
*edit_state = BindingEdit::Adding(Binding {
modifiers: Vec::new(),
trigger: key,
});
}
}
}
}
if let BindingEdit::Adding(b) | BindingEdit::Releasing(b) = &edit_state {
let colour = if matches!(edit_state, BindingEdit::Releasing(_)) {
Color::GREEN
} else {
Color::ORANGE
};
let text = format!("{:?} + {:?}", b.modifiers, b.trigger);
d.draw_text(&text, x + 5, y + 5, 20, colour);
}
if text_button(d, &globals.mouse, ok_btn_x, ok_btn_y, ok_btn_width, "ok") {
if let BindingEdit::Releasing(binding) = edit_state {
let binding_list = &mut self.bindings[*action as usize];
if *binding_index < binding_list.len() {
binding_list[*binding_index] = binding.clone();
} else {
binding_list.push(binding.clone());
}
self.editing_input = None;
}
}
if text_button(d, &globals.mouse, x + 100, y + 40, 80, "cancel") {
self.editing_input = None;
}
}
}
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| m.is_down(rl)) {
is_active |= binding.trigger.is_down(rl);
}
}
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::_EnumSize as usize;
fn from_usize(val: usize) -> Option<Self> {
if val < Self::SIZE {
Some(unsafe { transmute::<u8, Self>(val as u8) })
} else {
None
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Binding {
modifiers: Vec<Button>,
trigger: Button,
}
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(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
#[repr(u8)]
enum Button {
MouseLeft,
MouseRight,
MouseMiddle,
Mouse3,
Mouse4,
Mouse5,
Mouse6,
Apostrophe,
Comma,
Minus,
Period,
Slash,
Zero,
One,
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
Semicolon,
Equal,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
LeftBracket,
Backslash,
RightBracket,
Grave,
Space,
Escape,
Enter,
Tab,
Backspace,
Insert,
Delete,
Right,
Left,
Down,
Up,
PageUp,
PageDown,
Home,
End,
CapsLock,
ScrollLock,
NumLock,
PrintScreen,
Pause,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
LShift,
LCtrl,
LAlt,
LeftSuper,
RShift,
RCtrl,
RAlt,
RightSuper,
Menu,
Kp0,
Kp1,
Kp2,
Kp3,
Kp4,
Kp5,
Kp6,
Kp7,
Kp8,
Kp9,
KpDecimal,
KpDivide,
KpMultiply,
KpSubtract,
KpAdd,
KpEnter,
KpEqual,
Back,
VolumeUp,
VolumeDown,
//
_EnumSize,
}
enum RlInput {
Key(KeyboardKey),
Mouse(MouseButton),
}
impl Button {
const SIZE: usize = Self::_EnumSize as usize;
fn from_usize(val: usize) -> Option<Self> {
if val < Self::SIZE {
Some(unsafe { transmute::<u8, Self>(val as u8) })
} else {
None
}
}
fn is_down(self, rl: &RaylibHandle) -> bool {
match self.to_raylib() {
RlInput::Key(key) => rl.is_key_down(key),
RlInput::Mouse(btn) => rl.is_mouse_button_down(btn),
}
}
fn just_pressed(self, rl: &RaylibHandle) -> bool {
match self.to_raylib() {
RlInput::Key(key) => rl.is_key_pressed(key),
RlInput::Mouse(btn) => rl.is_mouse_button_pressed(btn),
}
}
fn released(self, rl: &RaylibHandle) -> bool {
match self.to_raylib() {
RlInput::Key(key) => rl.is_key_released(key),
RlInput::Mouse(btn) => rl.is_mouse_button_released(btn),
}
}
fn to_raylib(self) -> RlInput {
use KeyboardKey::*;
use RlInput::*;
match self {
Button::MouseLeft => Mouse(MouseButton::MOUSE_BUTTON_LEFT),
Button::MouseRight => Mouse(MouseButton::MOUSE_BUTTON_RIGHT),
Button::MouseMiddle => Mouse(MouseButton::MOUSE_BUTTON_MIDDLE),
Button::Mouse3 => Mouse(MouseButton::MOUSE_BUTTON_SIDE),
Button::Mouse4 => Mouse(MouseButton::MOUSE_BUTTON_EXTRA),
Button::Mouse5 => Mouse(MouseButton::MOUSE_BUTTON_FORWARD),
Button::Mouse6 => Mouse(MouseButton::MOUSE_BUTTON_BACK),
Button::Apostrophe => Key(KEY_APOSTROPHE),
Button::Comma => Key(KEY_COMMA),
Button::Minus => Key(KEY_MINUS),
Button::Period => Key(KEY_PERIOD),
Button::Slash => Key(KEY_SLASH),
Button::Zero => Key(KEY_ZERO),
Button::One => Key(KEY_ONE),
Button::Two => Key(KEY_TWO),
Button::Three => Key(KEY_THREE),
Button::Four => Key(KEY_FOUR),
Button::Five => Key(KEY_FIVE),
Button::Six => Key(KEY_SIX),
Button::Seven => Key(KEY_SEVEN),
Button::Eight => Key(KEY_EIGHT),
Button::Nine => Key(KEY_NINE),
Button::Semicolon => Key(KEY_SEMICOLON),
Button::Equal => Key(KEY_EQUAL),
Button::A => Key(KEY_A),
Button::B => Key(KEY_B),
Button::C => Key(KEY_C),
Button::D => Key(KEY_D),
Button::E => Key(KEY_E),
Button::F => Key(KEY_F),
Button::G => Key(KEY_G),
Button::H => Key(KEY_H),
Button::I => Key(KEY_I),
Button::J => Key(KEY_J),
Button::K => Key(KEY_K),
Button::L => Key(KEY_L),
Button::M => Key(KEY_M),
Button::N => Key(KEY_N),
Button::O => Key(KEY_O),
Button::P => Key(KEY_P),
Button::Q => Key(KEY_Q),
Button::R => Key(KEY_R),
Button::S => Key(KEY_S),
Button::T => Key(KEY_T),
Button::U => Key(KEY_U),
Button::V => Key(KEY_V),
Button::W => Key(KEY_W),
Button::X => Key(KEY_X),
Button::Y => Key(KEY_Y),
Button::Z => Key(KEY_Z),
Button::LeftBracket => Key(KEY_LEFT_BRACKET),
Button::Backslash => Key(KEY_BACKSLASH),
Button::RightBracket => Key(KEY_RIGHT_BRACKET),
Button::Grave => Key(KEY_GRAVE),
Button::Space => Key(KEY_SPACE),
Button::Escape => Key(KEY_ESCAPE),
Button::Enter => Key(KEY_ENTER),
Button::Tab => Key(KEY_TAB),
Button::Backspace => Key(KEY_BACKSPACE),
Button::Insert => Key(KEY_INSERT),
Button::Delete => Key(KEY_DELETE),
Button::Right => Key(KEY_RIGHT),
Button::Left => Key(KEY_LEFT),
Button::Down => Key(KEY_DOWN),
Button::Up => Key(KEY_UP),
Button::PageUp => Key(KEY_PAGE_UP),
Button::PageDown => Key(KEY_PAGE_DOWN),
Button::Home => Key(KEY_HOME),
Button::End => Key(KEY_END),
Button::CapsLock => Key(KEY_CAPS_LOCK),
Button::ScrollLock => Key(KEY_SCROLL_LOCK),
Button::NumLock => Key(KEY_NUM_LOCK),
Button::PrintScreen => Key(KEY_PRINT_SCREEN),
Button::Pause => Key(KEY_PAUSE),
Button::F1 => Key(KEY_F1),
Button::F2 => Key(KEY_F2),
Button::F3 => Key(KEY_F3),
Button::F4 => Key(KEY_F4),
Button::F5 => Key(KEY_F5),
Button::F6 => Key(KEY_F6),
Button::F7 => Key(KEY_F7),
Button::F8 => Key(KEY_F8),
Button::F9 => Key(KEY_F9),
Button::F10 => Key(KEY_F10),
Button::F11 => Key(KEY_F11),
Button::F12 => Key(KEY_F12),
Button::LShift => Key(KEY_LEFT_SHIFT),
Button::LCtrl => Key(KEY_LEFT_CONTROL),
Button::LAlt => Key(KEY_LEFT_ALT),
Button::LeftSuper => Key(KEY_LEFT_SUPER),
Button::RShift => Key(KEY_RIGHT_SHIFT),
Button::RCtrl => Key(KEY_RIGHT_CONTROL),
Button::RAlt => Key(KEY_RIGHT_ALT),
Button::RightSuper => Key(KEY_RIGHT_SUPER),
Button::Menu => Key(KEY_KB_MENU),
Button::Kp0 => Key(KEY_KP_0),
Button::Kp1 => Key(KEY_KP_1),
Button::Kp2 => Key(KEY_KP_2),
Button::Kp3 => Key(KEY_KP_3),
Button::Kp4 => Key(KEY_KP_4),
Button::Kp5 => Key(KEY_KP_5),
Button::Kp6 => Key(KEY_KP_6),
Button::Kp7 => Key(KEY_KP_7),
Button::Kp8 => Key(KEY_KP_8),
Button::Kp9 => Key(KEY_KP_9),
Button::KpDecimal => Key(KEY_KP_DECIMAL),
Button::KpDivide => Key(KEY_KP_DIVIDE),
Button::KpMultiply => Key(KEY_KP_MULTIPLY),
Button::KpSubtract => Key(KEY_KP_SUBTRACT),
Button::KpAdd => Key(KEY_KP_ADD),
Button::KpEnter => Key(KEY_KP_ENTER),
Button::KpEqual => Key(KEY_KP_EQUAL),
Button::Back => Key(KEY_BACK),
Button::VolumeUp => Key(KEY_VOLUME_UP),
Button::VolumeDown => Key(KEY_VOLUME_DOWN),
Button::_EnumSize => unreachable!(),
}
}
}