fix bindings not taking into account press order, making some bindings ambiguous
This commit is contained in:
parent
2c7e844d00
commit
440cd7a759
4 changed files with 72 additions and 36 deletions
|
@ -10,6 +10,7 @@ Game store page: https://crispypin.itch.io/marble-machinations
|
|||
- keybindings activated even when typing in a text field, making especially renaming blueprints difficult
|
||||
- grid rendering broken on right edge of the screen at some zoom levels and window sizes
|
||||
- crash when saving config if no user dir exists
|
||||
- bindings did not properly take into account order of pressing, so Shift+A and A+Shift were treated as the same thing
|
||||
- after removing a binding that was a superset of another, the remaining one did not stop being blocked by the removed ones additional modifiers until another binding was added or edited
|
||||
|
||||
## v0.3.2 - 2025-04-14
|
||||
|
|
|
@ -9,7 +9,7 @@ logic mostly like https://git.crispypin.cc/CrispyPin/marble
|
|||
- engine tests
|
||||
- blag post about marble movement logic?
|
||||
### bugs
|
||||
- Shift+A and A+Shift conflict
|
||||
|
||||
### features
|
||||
#### 0.3.x
|
||||
- more levels
|
||||
|
|
|
@ -39,6 +39,7 @@ impl Config {
|
|||
return MenuReturn::ReturnCancel;
|
||||
}
|
||||
|
||||
// self.input.update(d);
|
||||
self.input.draw_edit(d, globals, y);
|
||||
MenuReturn::Stay
|
||||
}
|
||||
|
|
104
src/input.rs
104
src/input.rs
|
@ -94,8 +94,9 @@ impl Default for Input {
|
|||
bind_key(ActionId::TileGroupCompare, vec![], H);
|
||||
|
||||
Self {
|
||||
bindings,
|
||||
states: Default::default(),
|
||||
action_bindings: bindings,
|
||||
action_states: [BindingState::Off; ActionId::SIZE],
|
||||
key_states: [BindingState::Off; Button::SIZE],
|
||||
editing_binding: None,
|
||||
in_text_edit: false,
|
||||
}
|
||||
|
@ -116,8 +117,9 @@ type InputMap = BTreeMap<String, Vec<Binding>>;
|
|||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(from = "InputMap", into = "InputMap")]
|
||||
pub struct Input {
|
||||
bindings: [Vec<Binding>; ActionId::SIZE],
|
||||
states: [BindingState; ActionId::SIZE],
|
||||
action_bindings: [Vec<Binding>; ActionId::SIZE],
|
||||
action_states: [BindingState; ActionId::SIZE],
|
||||
key_states: [BindingState; Button::SIZE],
|
||||
editing_binding: Option<(ActionId, usize, BindingEdit)>,
|
||||
pub in_text_edit: bool,
|
||||
}
|
||||
|
@ -142,10 +144,13 @@ impl Input {
|
|||
for action_index in 0..ActionId::SIZE {
|
||||
let action = ActionId::from_usize(action_index).unwrap();
|
||||
|
||||
// if self.action_states[action_index] == BindingState::Pressed {
|
||||
// d.draw_rectangle(200, y, 20, 20, Color::LIMEGREEN);
|
||||
// }
|
||||
d.draw_text(&format!("{action:?}"), 16, y, 20, Color::ORANGE);
|
||||
for (binding_index, binding) in self.bindings[action_index].iter().enumerate() {
|
||||
for (binding_index, binding) in self.action_bindings[action_index].iter().enumerate() {
|
||||
if text_button(d, &globals.mouse, buttons_x, y, 80, "remove") {
|
||||
self.bindings[action_index].remove(binding_index);
|
||||
self.action_bindings[action_index].remove(binding_index);
|
||||
self.update_modifier_blocks();
|
||||
return;
|
||||
}
|
||||
|
@ -162,7 +167,7 @@ impl Input {
|
|||
d.draw_text(&modifiers, x, y + 5, 20, Color::LIGHTBLUE);
|
||||
x += 10 + d.measure_text(&modifiers, 20);
|
||||
//
|
||||
let conflicts = conflicts(&self.bindings, binding, action);
|
||||
let conflicts = conflicts(&self.action_bindings, binding, action);
|
||||
if !conflicts.is_empty() {
|
||||
let conflict_text = format!("also used by: {conflicts:?}");
|
||||
d.draw_text(&conflict_text, x, y + 5, 20, Color::ORANGERED);
|
||||
|
@ -176,8 +181,11 @@ impl Input {
|
|||
y += 32;
|
||||
}
|
||||
if text_button(d, &globals.mouse, buttons_x, y, 130, "add binding") {
|
||||
self.editing_binding =
|
||||
Some((action, self.bindings[action_index].len(), BindingEdit::Init));
|
||||
self.editing_binding = Some((
|
||||
action,
|
||||
self.action_bindings[action_index].len(),
|
||||
BindingEdit::Init,
|
||||
));
|
||||
}
|
||||
y += 45;
|
||||
}
|
||||
|
@ -253,7 +261,7 @@ impl Input {
|
|||
let text = format!("{:?} + {:?}", b.modifiers, b.trigger);
|
||||
d.draw_text(&text, x + 5, y + 5, 20, colour);
|
||||
|
||||
let conflicts = conflicts(&self.bindings, b, *action);
|
||||
let conflicts = conflicts(&self.action_bindings, b, *action);
|
||||
if !conflicts.is_empty() {
|
||||
d.draw_text(
|
||||
&format!("conflicts: {conflicts:?}"),
|
||||
|
@ -266,7 +274,7 @@ impl Input {
|
|||
}
|
||||
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];
|
||||
let binding_list = &mut self.action_bindings[*action as usize];
|
||||
if *binding_index < binding_list.len() {
|
||||
binding_list[*binding_index] = binding.clone();
|
||||
} else {
|
||||
|
@ -283,20 +291,10 @@ 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;
|
||||
if !self.in_text_edit {
|
||||
for binding in bindings {
|
||||
if binding.modifiers.iter().all(|&m| m.is_down(rl))
|
||||
&& !binding.blocking_modifiers.iter().any(|&m| m.is_down(rl))
|
||||
{
|
||||
is_active |= binding.trigger.is_down(rl);
|
||||
}
|
||||
}
|
||||
}
|
||||
let state = &mut self.states[i];
|
||||
*state = if is_active {
|
||||
for i in 0..Button::SIZE {
|
||||
let button = Button::from_usize(i).unwrap();
|
||||
let state = &mut self.key_states[i];
|
||||
*state = if button.is_down(rl) {
|
||||
match state {
|
||||
BindingState::Off | BindingState::Released => BindingState::Pressed,
|
||||
BindingState::Pressed | BindingState::Held => BindingState::Held,
|
||||
|
@ -308,32 +306,52 @@ impl Input {
|
|||
}
|
||||
}
|
||||
}
|
||||
for i in 0..ActionId::SIZE {
|
||||
let bindings = &self.action_bindings[i];
|
||||
let mut is_active = BindingState::Off;
|
||||
if !self.in_text_edit {
|
||||
for binding in bindings {
|
||||
if binding
|
||||
.modifiers
|
||||
.iter()
|
||||
.all(|&m| self.key_states[m as usize] == BindingState::Held)
|
||||
&& binding
|
||||
.blocking_modifiers
|
||||
.iter()
|
||||
.all(|&m| self.key_states[m as usize] == BindingState::Off)
|
||||
{
|
||||
is_active = is_active.or(self.key_states[binding.trigger as usize]);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.action_states[i] = is_active;
|
||||
}
|
||||
self.in_text_edit = false;
|
||||
}
|
||||
|
||||
pub fn is_pressed(&self, action: ActionId) -> bool {
|
||||
self.states[action as usize] == BindingState::Pressed
|
||||
self.action_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
|
||||
self.action_states[action as usize] == BindingState::Pressed
|
||||
|| self.action_states[action as usize] == BindingState::Held
|
||||
}
|
||||
|
||||
pub fn is_released(&self, action: ActionId) -> bool {
|
||||
self.states[action as usize] == BindingState::Released
|
||||
self.action_states[action as usize] == BindingState::Released
|
||||
}
|
||||
|
||||
/// Must be called after any binding has changed.
|
||||
/// Ensures a binding "S" is not triggered by "Ctrl+S", if "Ctrl+S" is bound to something else.
|
||||
pub fn update_modifier_blocks(&mut self) {
|
||||
for i in 0..ActionId::SIZE {
|
||||
let bindings = &self.bindings[i];
|
||||
let bindings = &self.action_bindings[i];
|
||||
for binding_index in 0..bindings.len() {
|
||||
let binding = &self.bindings[i][binding_index];
|
||||
let binding = &self.action_bindings[i][binding_index];
|
||||
let mut blocking_mods = Vec::new();
|
||||
for i in 0..ActionId::SIZE {
|
||||
let other_bindings = &self.bindings[i];
|
||||
let other_bindings = &self.action_bindings[i];
|
||||
for other_binding in other_bindings {
|
||||
if other_binding.trigger == binding.trigger {
|
||||
for modifier in &other_binding.modifiers {
|
||||
|
@ -346,7 +364,7 @@ impl Input {
|
|||
}
|
||||
}
|
||||
}
|
||||
self.bindings[i][binding_index].blocking_modifiers = blocking_mods;
|
||||
self.action_bindings[i][binding_index].blocking_modifiers = blocking_mods;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -395,13 +413,29 @@ pub struct Binding {
|
|||
trigger: Button,
|
||||
}
|
||||
|
||||
impl BindingState {
|
||||
fn or(self, rhs: Self) -> Self {
|
||||
use BindingState::*;
|
||||
match (self, rhs) {
|
||||
(Off, other) => other,
|
||||
(other, Off) => other,
|
||||
(Held, _) => Held,
|
||||
(_, Held) => Held,
|
||||
(Pressed, Pressed) => Pressed,
|
||||
(Released, Released) => Released,
|
||||
(Pressed, Released) => Held,
|
||||
(Released, Pressed) => Held,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InputMap> for Input {
|
||||
fn from(map: InputMap) -> Self {
|
||||
let mut new = Self::default();
|
||||
for (action, loaded_bindings) in map {
|
||||
let temp_json = format!("\"{action}\"");
|
||||
if let Ok(action) = serde_json::from_str::<ActionId>(&temp_json) {
|
||||
new.bindings[action as usize] = loaded_bindings;
|
||||
new.action_bindings[action as usize] = loaded_bindings;
|
||||
} else {
|
||||
println!("'{action}' is not a valid action id, bindings discarded");
|
||||
}
|
||||
|
@ -413,7 +447,7 @@ impl From<InputMap> for Input {
|
|||
impl From<Input> for InputMap {
|
||||
fn from(value: Input) -> Self {
|
||||
value
|
||||
.bindings
|
||||
.action_bindings
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, b)| {
|
||||
|
|
Loading…
Add table
Reference in a new issue