prevent bindings from triggering when a modifier is held down that matches a different binding

This commit is contained in:
Crispy 2025-04-12 20:51:55 +02:00
parent 660746f16b
commit a755678567
4 changed files with 440 additions and 12 deletions

View file

@ -54,6 +54,7 @@ impl Default for Input {
let mut bindings = [(); ActionId::SIZE].map(|_| Vec::new());
let mut bind_key = |action, mods, trigger| {
bindings[action as usize].push(Binding {
blocking_modifiers: Vec::new(),
modifiers: mods,
trigger,
});
@ -141,21 +142,26 @@ impl Input {
if text_button(d, &globals.mouse, buttons_x + 85, y, 45, "edit") {
self.editing_binding = Some((action, binding_index, BindingEdit::Init));
}
//
let trigger = format!("{:?}", binding.trigger);
d.draw_text(&trigger, binding_text_x, y + 5, 20, Color::LIMEGREEN);
let x = binding_text_x + 10 + d.measure_text(&trigger, 20);
let mut x = binding_text_x;
d.draw_text(&trigger, x, y + 5, 20, Color::LIMEGREEN);
x += 10 + d.measure_text(&trigger, 20);
//
let modifiers = format!("{:?}", binding.modifiers);
d.draw_text(&modifiers, x, y + 5, 20, Color::LIGHTBLUE);
x += 10 + d.measure_text(&modifiers, 20);
//
let conflicts = conflicts(&self.bindings, binding, action);
if !conflicts.is_empty() {
let x = x + 10 + d.measure_text(&modifiers, 20);
d.draw_text(
&format!("also used by: {conflicts:?}"),
x,
y + 5,
20,
Color::ORANGERED,
);
let conflict_text = format!("also used by: {conflicts:?}");
d.draw_text(&conflict_text, x, y + 5, 20, Color::ORANGERED);
x += 10 + d.measure_text(&conflict_text, 20);
}
//
if !binding.blocking_modifiers.is_empty() {
let blocking_text = format!("not while: {:?}", binding.blocking_modifiers);
d.draw_text(&blocking_text, x, y + 5, 20, Color::GRAY);
}
y += 32;
}
@ -193,6 +199,7 @@ impl Input {
BindingEdit::Init => {
if key.just_pressed(d) {
*edit_state = BindingEdit::Adding(Binding {
blocking_modifiers: Vec::new(),
modifiers: Vec::new(),
trigger: key,
});
@ -218,6 +225,7 @@ impl Input {
globals.mouse.is_over(ok_btn_rect) && key == Button::MouseLeft;
if key.just_pressed(d) && !clicking_ok {
*edit_state = BindingEdit::Adding(Binding {
blocking_modifiers: Vec::new(),
modifiers: Vec::new(),
trigger: key,
});
@ -255,6 +263,7 @@ impl Input {
binding_list.push(binding.clone());
}
self.editing_binding = None;
self.update_modifier_blocks();
}
}
if text_button(d, &globals.mouse, x + 100, y + 40, 80, "cancel") {
@ -268,7 +277,9 @@ 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)) {
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);
}
}
@ -299,6 +310,33 @@ impl Input {
pub fn is_released(&self, action: ActionId) -> bool {
self.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];
for binding_index in 0..bindings.len() {
let binding = &self.bindings[i][binding_index];
let mut blocking_mods = Vec::new();
for i in 0..ActionId::SIZE {
let other_bindings = &self.bindings[i];
for other_binding in other_bindings {
if other_binding.trigger == binding.trigger {
for modifier in &other_binding.modifiers {
if !blocking_mods.contains(modifier)
&& !binding.modifiers.contains(modifier)
{
blocking_mods.push(*modifier);
}
}
}
}
}
self.bindings[i][binding_index].blocking_modifiers = blocking_mods;
}
}
}
}
fn conflicts(
@ -338,6 +376,8 @@ impl ActionId {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Binding {
#[serde(skip)]
blocking_modifiers: Vec<Button>,
modifiers: Vec<Button>,
trigger: Button,
}

245
src/layout.rs Normal file
View file

@ -0,0 +1,245 @@
use raylib::{
color::Color,
drawing::{RaylibDraw, RaylibDrawHandle},
RaylibHandle,
};
#[derive(Debug, Default)]
pub struct Ui {
root: Node,
width: i32,
height: i32,
dirty: bool,
}
#[derive(Debug, Default, Clone, Copy)]
pub enum Axis {
#[default]
Horizontal,
Vertical,
}
impl Ui {
pub fn new() -> Self {
Self::default()
}
pub fn update(&mut self, rl: &RaylibHandle) {
let w = rl.get_screen_width();
let h = rl.get_screen_height();
if w != self.width || h != self.height {
self.dirty = true;
}
self.width = w;
self.height = h;
if self.dirty {
self.dirty = false;
self.layout();
}
}
fn layout(&mut self) {
// fit sizing widths
self.root.layout_width();
// grow & shrink widths
// wrap text
self.root.layout_height();
// fit sizing heights
// grow & shrink heights
// positions
self.root.layout_position(0, 0);
}
pub fn draw(&self, rl: &mut RaylibDrawHandle) {
self.root.draw(rl);
}
pub fn set_root(&mut self, node: Node) {
// self.nodes = nodes;
self.root = node;
self.dirty = true;
}
}
#[derive(Debug, Default, Clone)]
pub enum SizeAxis {
#[default]
Fit,
Fixed(i32),
// Grow(),
}
#[derive(Debug, Default, Clone)]
pub struct Node {
// id: NodeId,
hidden: bool,
children: Vec<Node>,
computed_width: i32,
computed_height: i32,
computed_x: i32,
computed_y: i32,
// min_width: i32,
// min_height: i32,
// pref_width: i32,
// pref_height: i32,
// max_width: i32,
// max_height: i32,
width: SizeAxis,
height: SizeAxis,
padding: i32,
color: Color,
name: String, // todo replace with identification enum
axis: Axis,
}
impl Node {
pub fn new() -> Self {
Self::default()
}
fn layout_width(&mut self) -> i32 {
self.computed_width = match self.width {
SizeAxis::Fixed(val) => val,
SizeAxis::Fit => match self.axis {
Axis::Horizontal => {
let mut width = self.padding;
for c in &mut self.children {
width += c.layout_width();
width += self.padding;
}
width
}
Axis::Vertical => {
let mut width = self.padding;
for c in &mut self.children {
width = width.max(c.layout_width());
}
width + self.padding * 2
}
},
};
self.computed_width
}
fn layout_height(&mut self) -> i32 {
self.computed_height = match self.height {
SizeAxis::Fixed(val) => val,
SizeAxis::Fit => match self.axis {
Axis::Horizontal => {
let mut height = self.padding;
for c in &mut self.children {
height = height.max(c.layout_height());
}
height + self.padding * 2
}
Axis::Vertical => {
let mut height = self.padding;
for c in &mut self.children {
height += c.layout_height();
height += self.padding;
}
height
}
},
};
self.computed_height
}
fn layout_position(&mut self, root_x: i32, root_y: i32) {
self.computed_x = root_x;
self.computed_y = root_y;
let mut x = root_x + self.padding;
let mut y = root_y + self.padding;
for c in &mut self.children {
c.layout_position(x, y);
match self.axis {
Axis::Horizontal => x += c.computed_width + self.padding,
Axis::Vertical => y += c.computed_height + self.padding,
}
}
}
fn draw(&self, d: &mut RaylibDrawHandle) {
d.draw_rectangle(
self.computed_x,
self.computed_y,
self.computed_width,
self.computed_height,
self.color,
);
// todo text/image content
for c in &self.children {
c.draw(d);
}
}
pub fn name(mut self, text: &str) -> Self {
self.name = text.to_owned();
self
}
pub fn width(mut self, width: SizeAxis) -> Self {
self.width = width;
self
}
pub fn height(mut self, height: SizeAxis) -> Self {
self.height = height;
self
}
pub fn exact(mut self, width: i32, height: i32) -> Self {
self.height = SizeAxis::Fixed(height);
self.width = SizeAxis::Fixed(width);
self
}
pub fn axis(mut self, axis: Axis) -> Self {
self.axis = axis;
self
}
// pub fn exact_width(mut self, val: i32) -> Self {
// self.min_width = val;
// self.pref_width = val;
// self.max_width = val;
// self
// }
// pub fn shrink_width(mut self, val: i32) -> Self {
// self.min_width = val;
// self.pref_width = val;
// self.max_width = val;
// self
// }
// pub fn grow_width(mut self, val: i32) -> Self {
// self.min_width = val;
// self.pref_width = val;
// self.max_width = i32::MAX;
// self
// }
// pub fn grow_height(mut self, val: i32) -> Self {
// self.min_height = val;
// self.pref_height = val;
// self.max_height = i32::MAX;
// self
// }
pub fn color(mut self, col: Color) -> Self {
self.color = col;
self
}
pub fn with_children(mut self, children: Vec<Node>) -> Self {
self.children = children;
self
}
pub fn pad(mut self, padding: i32) -> Self {
self.padding = padding;
self
}
}

View file

@ -36,10 +36,11 @@ impl Globals {
textures.load_dir("assets/digits", rl, thread);
let config_path = userdata_dir().join(CONFIG_FILE_NAME);
let config = fs::read_to_string(config_path)
let mut config: Config = fs::read_to_string(config_path)
.ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default();
config.input.update_modifier_blocks();
Self {
clipboard: Clipboard::new()

142
src/ui2.rs Normal file
View file

@ -0,0 +1,142 @@
use raylib::{
color::Color,
drawing::{RaylibDraw, RaylibDrawHandle},
RaylibHandle,
};
pub type EId = String;
// pub enum EId{
// }
#[derive(Debug, Default)]
pub struct UiCtx {
active: Option<EId>,
hovered: Option<EId>,
kb_selected: Option<EId>,
layout: LayoutState,
draw_queue: Vec<DrawCommand>,
}
#[derive(Debug, Default, Clone, Copy)]
pub enum Axis {
#[default]
Horizontal,
Vertical,
}
#[derive(Debug, Clone)]
pub enum DrawCommand {
Rect(Rect, Color),
Text(String),
}
#[derive(Debug, Clone)]
pub struct Rect {
x: i32,
y: i32,
width: i32,
height: i32,
}
#[derive(Debug, Default, Clone)]
pub struct LayoutState {
pub direction: Axis,
cursor_x: i32,
cursor_y: i32,
width: i32,
height: i32,
pub padding: i32,
}
type Widget = fn(&mut UiCtx) -> ();
// type Widget = FnMut<;
impl UiCtx {
pub fn layout(&mut self) -> &mut LayoutState {
&mut self.layout
}
pub fn begin(&mut self, d: &mut RaylibDrawHandle) {
self.layout = LayoutState::default();
}
pub fn draw(&mut self, d: &mut RaylibDrawHandle) {
while let Some(cmd) = self.draw_queue.pop() {
match cmd {
DrawCommand::Rect(r, col) => d.draw_rectangle(r.x, r.y, r.width, r.height, col),
DrawCommand::Text(_) => todo!(),
}
}
}
pub fn padded_container(&mut self, color: Color, axis: Axis, widget: Widget) {
self.layout.padding = 4;
self.layout.direction = axis;
self.layout.width = 0;
self.layout.height = 0;
let old_layout_state = self.layout.clone();
widget(self);
let width = self.layout.width;
let height = self.layout.height;
self.layout = old_layout_state;
self.layout.update_size_with_child(width, height);
// old_layout_state.update_size_with_child(self.layout.width, self.layout.height);
// old_layout_state.step(self.layout.width, self.layout.height);
self.draw_queue
.push(DrawCommand::Rect(self.layout.rect(), color));
self.layout.step(width, height);
// self.layout.step(width, height);
}
pub fn square(&mut self, color: Color, width: i32) {
let height = 50;
// self.layout.update_size_with_child(width, height);
self.layout.width = width;
self.layout.height = height;
self.draw_queue.push(DrawCommand::Rect(
self.layout.rect(),
color,
));
self.layout.step(width, height)
// self.layout.width = width;
// self.layout.height = height;
// self.layout.step(width, height)
}
}
impl LayoutState {
fn step(&mut self, width: i32, height: i32) {
match self.direction {
Axis::Horizontal => self.cursor_x += width + self.padding,
Axis::Vertical => self.cursor_y += height + self.padding,
}
}
fn update_size_with_child(&mut self, width: i32, height: i32) {
match self.direction {
Axis::Horizontal => {
self.width += width + self.padding;
self.height = self.height.max(height + self.padding);
}
Axis::Vertical => {
self.width = self.width.max(width + self.padding);
self.height += height + self.padding;
}
}
}
fn rect(&self) -> Rect {
Rect::new(self.cursor_x, self.cursor_y, self.width, self.height)
}
}
impl Rect {
pub fn new(x: i32, y: i32, width: i32, height: i32) -> Self {
Self {
x,
y,
width,
height,
}
}
}