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
|
- 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
|
- 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
|
- 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
|
- 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
|
## v0.3.2 - 2025-04-14
|
||||||
|
|
|
@ -9,7 +9,7 @@ logic mostly like https://git.crispypin.cc/CrispyPin/marble
|
||||||
- engine tests
|
- engine tests
|
||||||
- blag post about marble movement logic?
|
- blag post about marble movement logic?
|
||||||
### bugs
|
### bugs
|
||||||
- Shift+A and A+Shift conflict
|
|
||||||
### features
|
### features
|
||||||
#### 0.3.x
|
#### 0.3.x
|
||||||
- more levels
|
- more levels
|
||||||
|
|
|
@ -39,6 +39,7 @@ impl Config {
|
||||||
return MenuReturn::ReturnCancel;
|
return MenuReturn::ReturnCancel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// self.input.update(d);
|
||||||
self.input.draw_edit(d, globals, y);
|
self.input.draw_edit(d, globals, y);
|
||||||
MenuReturn::Stay
|
MenuReturn::Stay
|
||||||
}
|
}
|
||||||
|
|
104
src/input.rs
104
src/input.rs
|
@ -94,8 +94,9 @@ impl Default for Input {
|
||||||
bind_key(ActionId::TileGroupCompare, vec![], H);
|
bind_key(ActionId::TileGroupCompare, vec![], H);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
bindings,
|
action_bindings: bindings,
|
||||||
states: Default::default(),
|
action_states: [BindingState::Off; ActionId::SIZE],
|
||||||
|
key_states: [BindingState::Off; Button::SIZE],
|
||||||
editing_binding: None,
|
editing_binding: None,
|
||||||
in_text_edit: false,
|
in_text_edit: false,
|
||||||
}
|
}
|
||||||
|
@ -116,8 +117,9 @@ type InputMap = BTreeMap<String, Vec<Binding>>;
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(from = "InputMap", into = "InputMap")]
|
#[serde(from = "InputMap", into = "InputMap")]
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
bindings: [Vec<Binding>; ActionId::SIZE],
|
action_bindings: [Vec<Binding>; ActionId::SIZE],
|
||||||
states: [BindingState; ActionId::SIZE],
|
action_states: [BindingState; ActionId::SIZE],
|
||||||
|
key_states: [BindingState; Button::SIZE],
|
||||||
editing_binding: Option<(ActionId, usize, BindingEdit)>,
|
editing_binding: Option<(ActionId, usize, BindingEdit)>,
|
||||||
pub in_text_edit: bool,
|
pub in_text_edit: bool,
|
||||||
}
|
}
|
||||||
|
@ -142,10 +144,13 @@ impl Input {
|
||||||
for action_index in 0..ActionId::SIZE {
|
for action_index in 0..ActionId::SIZE {
|
||||||
let action = ActionId::from_usize(action_index).unwrap();
|
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);
|
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") {
|
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();
|
self.update_modifier_blocks();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -162,7 +167,7 @@ impl Input {
|
||||||
d.draw_text(&modifiers, x, y + 5, 20, Color::LIGHTBLUE);
|
d.draw_text(&modifiers, x, y + 5, 20, Color::LIGHTBLUE);
|
||||||
x += 10 + d.measure_text(&modifiers, 20);
|
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() {
|
if !conflicts.is_empty() {
|
||||||
let conflict_text = format!("also used by: {conflicts:?}");
|
let conflict_text = format!("also used by: {conflicts:?}");
|
||||||
d.draw_text(&conflict_text, x, y + 5, 20, Color::ORANGERED);
|
d.draw_text(&conflict_text, x, y + 5, 20, Color::ORANGERED);
|
||||||
|
@ -176,8 +181,11 @@ impl Input {
|
||||||
y += 32;
|
y += 32;
|
||||||
}
|
}
|
||||||
if text_button(d, &globals.mouse, buttons_x, y, 130, "add binding") {
|
if text_button(d, &globals.mouse, buttons_x, y, 130, "add binding") {
|
||||||
self.editing_binding =
|
self.editing_binding = Some((
|
||||||
Some((action, self.bindings[action_index].len(), BindingEdit::Init));
|
action,
|
||||||
|
self.action_bindings[action_index].len(),
|
||||||
|
BindingEdit::Init,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
y += 45;
|
y += 45;
|
||||||
}
|
}
|
||||||
|
@ -253,7 +261,7 @@ impl Input {
|
||||||
let text = format!("{:?} + {:?}", b.modifiers, b.trigger);
|
let text = format!("{:?} + {:?}", b.modifiers, b.trigger);
|
||||||
d.draw_text(&text, x + 5, y + 5, 20, colour);
|
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() {
|
if !conflicts.is_empty() {
|
||||||
d.draw_text(
|
d.draw_text(
|
||||||
&format!("conflicts: {conflicts:?}"),
|
&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 text_button(d, &globals.mouse, ok_btn_x, ok_btn_y, ok_btn_width, "ok") {
|
||||||
if let BindingEdit::Releasing(binding) = edit_state {
|
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() {
|
if *binding_index < binding_list.len() {
|
||||||
binding_list[*binding_index] = binding.clone();
|
binding_list[*binding_index] = binding.clone();
|
||||||
} else {
|
} else {
|
||||||
|
@ -283,20 +291,10 @@ impl Input {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, rl: &RaylibHandle) {
|
pub fn update(&mut self, rl: &RaylibHandle) {
|
||||||
for i in 0..ActionId::SIZE {
|
for i in 0..Button::SIZE {
|
||||||
let bindings = &self.bindings[i];
|
let button = Button::from_usize(i).unwrap();
|
||||||
let mut is_active = false;
|
let state = &mut self.key_states[i];
|
||||||
if !self.in_text_edit {
|
*state = if button.is_down(rl) {
|
||||||
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 {
|
|
||||||
match state {
|
match state {
|
||||||
BindingState::Off | BindingState::Released => BindingState::Pressed,
|
BindingState::Off | BindingState::Released => BindingState::Pressed,
|
||||||
BindingState::Pressed | BindingState::Held => BindingState::Held,
|
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;
|
self.in_text_edit = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_pressed(&self, action: ActionId) -> bool {
|
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 {
|
pub fn is_held(&self, action: ActionId) -> bool {
|
||||||
self.states[action as usize] == BindingState::Pressed
|
self.action_states[action as usize] == BindingState::Pressed
|
||||||
|| self.states[action as usize] == BindingState::Held
|
|| self.action_states[action as usize] == BindingState::Held
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_released(&self, action: ActionId) -> bool {
|
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.
|
/// 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.
|
/// 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) {
|
pub fn update_modifier_blocks(&mut self) {
|
||||||
for i in 0..ActionId::SIZE {
|
for i in 0..ActionId::SIZE {
|
||||||
let bindings = &self.bindings[i];
|
let bindings = &self.action_bindings[i];
|
||||||
for binding_index in 0..bindings.len() {
|
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();
|
let mut blocking_mods = Vec::new();
|
||||||
for i in 0..ActionId::SIZE {
|
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 {
|
for other_binding in other_bindings {
|
||||||
if other_binding.trigger == binding.trigger {
|
if other_binding.trigger == binding.trigger {
|
||||||
for modifier in &other_binding.modifiers {
|
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,
|
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 {
|
impl From<InputMap> for Input {
|
||||||
fn from(map: InputMap) -> Self {
|
fn from(map: InputMap) -> Self {
|
||||||
let mut new = Self::default();
|
let mut new = Self::default();
|
||||||
for (action, loaded_bindings) in map {
|
for (action, loaded_bindings) in map {
|
||||||
let temp_json = format!("\"{action}\"");
|
let temp_json = format!("\"{action}\"");
|
||||||
if let Ok(action) = serde_json::from_str::<ActionId>(&temp_json) {
|
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 {
|
} else {
|
||||||
println!("'{action}' is not a valid action id, bindings discarded");
|
println!("'{action}' is not a valid action id, bindings discarded");
|
||||||
}
|
}
|
||||||
|
@ -413,7 +447,7 @@ impl From<InputMap> for Input {
|
||||||
impl From<Input> for InputMap {
|
impl From<Input> for InputMap {
|
||||||
fn from(value: Input) -> Self {
|
fn from(value: Input) -> Self {
|
||||||
value
|
value
|
||||||
.bindings
|
.action_bindings
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, b)| {
|
.map(|(i, b)| {
|
||||||
|
|
Loading…
Add table
Reference in a new issue