implement keybinding editing

This commit is contained in:
Crispy 2025-04-03 00:19:05 +02:00
parent 7a4c9014c8
commit 8d81f94b70
2 changed files with 109 additions and 15 deletions

View file

@ -3,7 +3,7 @@ Game store page: https://crispypin.itch.io/marble-machinations
## [Unreleased]
### added
- configurable hotkeys (via file only, only some actions)
- configurable key bindings for the basic editor actions
- OS clipboard copy/paste, with fallback to old behavior when copying
- in-grid text comments (not yet editable in-game)
- changelog file

View file

@ -1,4 +1,4 @@
use std::{collections::BTreeMap, mem::transmute};
use std::{collections::BTreeMap, mem::transmute, vec};
use raylib::{
color::Color,
@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
use crate::{
theme::{BG_DARK, BG_LIGHT},
ui::text_button,
util::screen_centered_rect,
util::{rect, screen_centered_rect},
Globals,
};
@ -75,7 +75,14 @@ pub struct Input {
bindings: [Vec<Binding>; ActionId::SIZE],
states: [BindingState; ActionId::SIZE],
#[serde(skip)]
editing_input: Option<(ActionId, usize)>,
editing_input: Option<(ActionId, usize, BindingEdit)>,
}
#[derive(Clone, Debug)]
enum BindingEdit {
Init,
Adding(Binding),
Releasing(Binding),
}
impl Input {
@ -98,7 +105,7 @@ impl Input {
return;
}
if text_button(d, &globals.mouse, 245, y, 45, "edit") {
self.editing_input = Some((action, binding_index));
self.editing_input = Some((action, binding_index, BindingEdit::Init));
}
let trigger = format!("{:?}", binding.trigger);
d.draw_text(&trigger, 300, y, 20, Color::LIMEGREEN);
@ -107,21 +114,93 @@ impl Input {
d.draw_text(&modifiers, x, y, 20, Color::LIGHTBLUE);
y += 32;
}
y += 8;
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)) = &self.editing_input {
globals.mouse.update(d); // todo less scuffed
let border = screen_centered_rect(d, 208, 88);
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, 200, 80);
let bounds = screen_centered_rect(d, 360, 120);
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;
if text_button(d, &globals.mouse, x + 10, y + 40, 80, "ok") {}
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;
}
@ -206,7 +285,8 @@ impl From<Input> for InputMap {
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
#[repr(u8)]
enum Button {
MouseLeft,
MouseRight,
@ -350,6 +430,20 @@ impl Button {
}
}
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::*;