diff --git a/CHANGELOG.md b/CHANGELOG.md index b42b962..3711ebf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ Game store page: https://crispypin.itch.io/marble-machinations ## [Unreleased] ### added -- configurable key bindings for the basic editor actions +- configurable hotkeys (via file only, only some actions) - OS clipboard copy/paste, with fallback to old behavior when copying - in-grid text comments (not yet editable in-game) - changelog file diff --git a/src/input.rs b/src/input.rs index 0a9a93f..ada150b 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, mem::transmute, vec}; +use std::{collections::BTreeMap, mem::transmute}; use raylib::{ color::Color, @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize}; use crate::{ theme::{BG_DARK, BG_LIGHT}, ui::text_button, - util::{rect, screen_centered_rect}, + util::screen_centered_rect, Globals, }; @@ -27,28 +27,32 @@ pub enum ActionId { StopSim, StepSim, // just like in C, because this way doesn't need more dependencies - _EnumSize, + _ActionIdSize, } impl Default for Input { fn default() -> Self { - use Button::*; + use KeyboardKey::*; let mut bindings = [(); ActionId::SIZE].map(|_| Vec::new()); - let mut bind_key = |action, mods, trigger| { + let mut bind_key = |action, mods, key| { bindings[action as usize].push(Binding { modifiers: mods, - trigger, + trigger: InputTrigger::Key(key), }); }; - 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); + bind_key(ActionId::Undo, vec![KEY_LEFT_CONTROL], KEY_Z); + bind_key(ActionId::Redo, vec![KEY_LEFT_CONTROL], KEY_Y); + bind_key( + ActionId::Redo, + vec![KEY_LEFT_CONTROL, KEY_LEFT_SHIFT], + KEY_Z, + ); + bind_key(ActionId::Copy, vec![KEY_LEFT_CONTROL], KEY_C); + bind_key(ActionId::Paste, vec![KEY_LEFT_CONTROL], KEY_V); + bind_key(ActionId::ToggleMenu, vec![], KEY_ESCAPE); + bind_key(ActionId::StartSim, vec![], KEY_ENTER); + bind_key(ActionId::StopSim, vec![], KEY_ENTER); + bind_key(ActionId::StepSim, vec![], KEY_SPACE); Self { bindings, @@ -75,14 +79,7 @@ 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), + editing_input: Option<(ActionId, usize)>, } impl Input { @@ -105,7 +102,7 @@ impl Input { return; } if text_button(d, &globals.mouse, 245, y, 45, "edit") { - self.editing_input = Some((action, binding_index, BindingEdit::Init)); + self.editing_input = Some((action, binding_index)); } let trigger = format!("{:?}", binding.trigger); d.draw_text(&trigger, 300, y, 20, Color::LIMEGREEN); @@ -114,93 +111,21 @@ impl Input { 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; + y += 8; } - if let Some((action, binding_index, edit_state)) = &mut self.editing_input { - globals.mouse.update(d); - let border = screen_centered_rect(d, 368, 128); + if let Some((action, binding_index)) = &self.editing_input { + globals.mouse.update(d); // todo less scuffed + let border = screen_centered_rect(d, 208, 88); d.draw_rectangle_rec(border, BG_LIGHT); - let bounds = screen_centered_rect(d, 360, 120); + let bounds = screen_centered_rect(d, 200, 80); d.draw_rectangle_rec(bounds, BG_DARK); + + // TODO capture and display current input + 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 + 10, y + 40, 80, "ok") {} if text_button(d, &globals.mouse, x + 100, y + 40, 80, "cancel") { self.editing_input = None; } @@ -212,8 +137,11 @@ impl Input { 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); + 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]; @@ -246,11 +174,11 @@ impl Input { } impl ActionId { - pub const SIZE: usize = Self::_EnumSize as usize; + pub const SIZE: usize = Self::_ActionIdSize as usize; fn from_usize(val: usize) -> Option { if val < Self::SIZE { - Some(unsafe { transmute::(val as u8) }) + Some(unsafe { transmute::(val as u8) }) } else { None } @@ -258,11 +186,20 @@ impl ActionId { } #[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(from = "BindingSerde", into = "BindingSerde")] pub struct Binding { - modifiers: Vec