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>; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(from = "InputMap", into = "InputMap")] pub struct Input { bindings: [Vec; 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 { if val < Self::SIZE { Some(unsafe { transmute::(val as u8) }) } else { None } } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Binding { modifiers: Vec