diff options
| author | Arne Dußin | 2021-01-17 13:33:04 +0100 |
|---|---|---|
| committer | Arne Dußin | 2021-01-17 13:33:04 +0100 |
| commit | 51b7747e62c189d430318c67368a5c84e50ece61 (patch) | |
| tree | 328be6230d392027eb106fd963b5ec97b9034f9f /src/input | |
| parent | b1179849c28e50c39ac3c94af9dda86ee24beca0 (diff) | |
| download | graf_karto-input.tar.gz graf_karto-input.zip | |
Input revamp to make keybindings controlable.input
Diffstat (limited to 'src/input')
| -rw-r--r-- | src/input/binding.rs | 123 | ||||
| -rw-r--r-- | src/input/button.rs | 177 | ||||
| -rw-r--r-- | src/input/mod.rs | 200 |
3 files changed, 500 insertions, 0 deletions
diff --git a/src/input/binding.rs b/src/input/binding.rs new file mode 100644 index 0000000..386fb66 --- /dev/null +++ b/src/input/binding.rs @@ -0,0 +1,123 @@ +//! Bindings module, which is a key combination that does something when pressed. + +use super::Button; +use raylib::RaylibHandle; +use serde::{Deserialize, Serialize}; + +/// Binding struct, which holds any number of buttons (keyboard and mouse may be mixed, if desired) +#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct Binding { + buttons: Vec<Button>, +} + +impl Binding { + /// Create a new binding from a range of buttons. The button order does not matter, but at least + /// one button must be supplied. + pub fn new(buttons: Vec<Button>) -> Self { + if buttons.is_empty() { + panic!("Tried to create a binding without any keys."); + } + + Self { buttons } + } + + /// Returns `true` if only mouse buttons are present in this binding, otherwise false. + pub fn mouse_only(&self) -> bool { + for button in &self.buttons { + match button { + Button::Mouse(_) => continue, + _ => return false, + } + } + + true + } + + /// Returns `true` if only keyboard buttons are present in this binding, otherwise false. + pub fn keyboard_only(&self) -> bool { + for button in &self.buttons { + match button { + Button::Scancode(_) | Button::Text(_) => continue, + _ => return false, + } + } + + true + } + + /// Returns `true` if at least one mouse button is required for this binding to work. + pub fn has_mouse_component(&self) -> bool { + self.buttons.iter().any(|b| { + if let Button::Mouse(_) = b { + true + } else { + false + } + }) + } + + /// Returns `true` if at least one keyboard button is required for this binding to work. + pub fn has_keyboard_component(&self) -> bool { + self.buttons.iter().any(|b| match b { + Button::Scancode(_) | Button::Text(_) => true, + _ => false, + }) + } + + /// Checks if this binding was pressed this frame. Heavily dependent on input struct working + /// correctly. + pub(super) fn is_pressed( + &self, + allow_mouse: bool, + allow_keyboard: bool, + text: &str, + rl: &RaylibHandle, + ) -> bool { + let mut distinct_press = false; + for button in &self.buttons { + match *button { + Button::Mouse(mouse_button) => { + if !allow_mouse || !rl.is_mouse_button_down(mouse_button.into()) { + return false; + } + + /* Check if the button has been pressed in this frame exactly. + * This prevents activating the same keybinding every frame + * while the buttons are being held down. + */ + if rl.is_mouse_button_pressed(mouse_button.into()) { + distinct_press = true; + } + } + Button::Scancode(code) => { + if !allow_keyboard || !rl.is_key_down(code.into()) { + return false; + } + + // Check the same as with the mouse button. + if rl.is_key_pressed(code.into()) { + distinct_press = true; + } + } + Button::Text(c) => { + if !allow_keyboard || !text.contains(c) { + return false; + } + + // Always distinct, since on triggering, the text is cleared. + distinct_press = true; + } + } + } + + distinct_press + } +} + +impl From<Button> for Binding { + fn from(button: Button) -> Self { + Self { + buttons: vec![button], + } + } +} diff --git a/src/input/button.rs b/src/input/button.rs new file mode 100644 index 0000000..e9ef45e --- /dev/null +++ b/src/input/button.rs @@ -0,0 +1,177 @@ +//! Reimplementation crate of the KeyboardKey and MouseButton structs of raylib, because they do +//! not implement `serde::Serialize` and `serde::Deserialize`. If you have a better idea on how to +//! handle it, feel free. + +use raylib::ffi::{KeyboardKey as rlKeyboardKey, MouseButton as rlMouseButton}; +use serde::{Deserialize, Serialize}; +use std::mem; + +/// Abstraction over different key-types. A binding can be constructed from this button-type or +/// multiple button-presses can be chained together to create a binding. Just be careful to not +/// have bindings where one binding is included in another. This includes bindings where on your +/// keyboard you have Scancode(Shift) + Scancode(A) and another binding of Text(A) (Text(a) would +/// be okay) +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum Button { + /// A button on the mouse (raylib supports just three :/ ) + Mouse(MouseButton), + /// Scancode that is sent by the OS. This can change between OSes, but stays the same between + /// runs and layout changes in the keyboard. + Scancode(Scancode), + /// The text input read from the operating system. This means even characters composed or + /// non-ASCII characters can be used. I mean, who doesn't want to bind the wall tool to 壁? + Text(char), +} + +#[allow(missing_docs)] +#[repr(u32)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum MouseButton { + Left = 0, + Right = 1, + Middle = 2, +} + +#[allow(missing_docs)] +#[repr(u32)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum Scancode { + Apostrophe = 39, + Comma = 44, + Minus = 45, + Period = 46, + Slash = 47, + Zero = 48, + One = 49, + Two = 50, + Three = 51, + Four = 52, + Five = 53, + Six = 54, + Seven = 55, + Eight = 56, + Nine = 57, + Semicolon = 59, + Equal = 61, + A = 65, + B = 66, + C = 67, + D = 68, + E = 69, + F = 70, + G = 71, + H = 72, + I = 73, + J = 74, + K = 75, + L = 76, + M = 77, + N = 78, + O = 79, + P = 80, + Q = 81, + R = 82, + S = 83, + T = 84, + U = 85, + V = 86, + W = 87, + X = 88, + Y = 89, + Z = 90, + Space = 32, + Escape = 256, + Enter = 257, + Tab = 258, + Backspace = 259, + Insert = 260, + Delete = 261, + Right = 262, + Left = 263, + Down = 264, + Up = 265, + PageUp = 266, + PageDown = 267, + Home = 268, + End = 269, + CapsLock = 280, + ScrollLock = 281, + NumLock = 282, + PrintScreen = 283, + Pause = 284, + F1 = 290, + F2 = 291, + F3 = 292, + F4 = 293, + F5 = 294, + F6 = 295, + F7 = 296, + F8 = 297, + F9 = 298, + F10 = 299, + F11 = 300, + F12 = 301, + LeftShift = 340, + LeftControl = 341, + LeftAlt = 342, + LeftSuper = 343, + RightShift = 344, + RightControl = 345, + RightAlt = 346, + RightSuper = 347, + Menu = 348, + LeftBracket = 91, + Backslash = 92, + RightBracket = 93, + Grave = 96, + Keypad0 = 320, + Keypad1 = 321, + Keypad2 = 322, + Keypad3 = 323, + Keypad4 = 324, + Keypad5 = 325, + Keypad6 = 326, + Keypad7 = 327, + Keypad8 = 328, + Keypad9 = 329, + KeypadDecimal = 330, + KeypadDivide = 331, + KeypadMultiply = 332, + KeypadSubtract = 333, + KeypadAdd = 334, + KeypadEnter = 335, + KeypadEqual = 336, +} + +impl From<rlMouseButton> for Button { + fn from(button: rlMouseButton) -> Self { + Self::Mouse(MouseButton::from(button)) + } +} +impl From<rlKeyboardKey> for Button { + fn from(key: rlKeyboardKey) -> Self { + Self::Scancode(Scancode::from(key)) + } +} + +impl From<rlMouseButton> for MouseButton { + fn from(button: rlMouseButton) -> Self { + unsafe { mem::transmute(button as u32) } + } +} +impl Into<rlMouseButton> for MouseButton { + fn into(self) -> rlMouseButton { + unsafe { mem::transmute(self as u32) } + } +} + +impl From<rlKeyboardKey> for Scancode { + fn from(key: rlKeyboardKey) -> Self { + unsafe { mem::transmute(key as u32) } + } +} +impl Into<rlKeyboardKey> for Scancode { + fn into(self) -> rlKeyboardKey { + unsafe { mem::transmute(self as u32) } + } +} diff --git a/src/input/mod.rs b/src/input/mod.rs new file mode 100644 index 0000000..e8b1821 --- /dev/null +++ b/src/input/mod.rs @@ -0,0 +1,200 @@ +//! Input with binding abstraction +//! +//! Binding keys or combinations to specific actions with just raylib alone is difficult to handle in +//! input-heavy applications, as opposed to games. This is an optimisation effort. To understand how +//! it works, the first thing to know is that there are two main modes for bindings, which are local +//! and global. A local binding is specific to a certain area of the window and is used to block the +//! mouse from being sent to many different targets (think of a pop-up window over the editor, which +//! must capture the mouse). Global bindings will be processed as long as no local binding has +//! prevalence, but they do not have an area that needs to be managed by a handler. +//! +//! In summary, the local <-> global distinction is used to capture the mouse. +//! +//! Some elements want to capture the keyboard, for instance, when activating a text box, the text +//! input should only go to this box, but should a tool be bound to a character, it should not +//! activate when typing. For this purpose, any element may seize control as long as no other element +//! still has the focus. A channel is opened and no bindings will be processed. Instead the text +//! together with a few control characters is relayed directly to the channel, until the receiver +//! hangs up. +//! +//! In summary, a channel is used to seize control of the keyboard when typing into an element. + +pub mod binding; +pub mod button; + +pub use binding::*; +pub use button::*; + +use crate::math::{ExactSurface, Rect, Vec2}; +use crate::stable_vec::StableVec; +use raylib::ffi::KeyboardKey; +use raylib::RaylibHandle; +use std::collections::HashMap; +use std::sync::mpsc::{self, Receiver, Sender}; + +/// Input and binding handler this should only be created once per instance. +pub struct Input { + global_bindings: HashMap<Binding, bool>, + local_bindings: StableVec<(Rect<u16>, HashMap<Binding, bool>)>, + last_text: String, + text_pipe: Option<Sender<char>>, + mouse_pos: Vec2<u16>, +} + +impl Input { + /// Create a new Input and binding handler. + pub fn new(rl: &RaylibHandle) -> Self { + Self { + global_bindings: HashMap::new(), + local_bindings: StableVec::new(), + last_text: String::new(), + text_pipe: None, + mouse_pos: Vec2::new(rl.get_mouse_x() as u16, rl.get_mouse_y() as u16), + } + } + + /// Must be called on every frame of the program, since keypresses will be processed here. This + /// will not activate the binding function directly, since raylib is heavily polling focused. + pub fn update(&mut self, rl: &mut RaylibHandle) { + self.mouse_pos = Vec2::new(rl.get_mouse_x() as u16, rl.get_mouse_y() as u16); + /* Read the next character to be sent with some extra characters + * raylib doesn't recognize to be valid. + */ + let c = if rl.is_key_pressed(KeyboardKey::KEY_ENTER) { + Some('\n') + } else if rl.is_key_pressed(KeyboardKey::KEY_ESCAPE) { + Some('\x1B') + } else if rl.is_key_pressed(KeyboardKey::KEY_BACKSPACE) { + Some('\x7f') + } else { + rl.get_key_pressed_number().map(|c| c as u8 as char) + }; + + /* Send the character to the listening entity or push it to the text that + * is currently being read for the keybindings. + */ + if let Some(text_pipe) = self.text_pipe.as_mut() { + if let Some(c) = c { + if text_pipe.send(c).is_err() { + self.last_text.push(c); + self.text_pipe = None; + } + } + } else if let Some(c) = c { + self.last_text.push(c); + } + + /* Update the local parts. The local stack has priority over the global + * bindings, so it is processed first, with the priority going from the + * top of the stack to the bottom in that order (reversed vec order) + */ + let mut mouse_blocked = false; + for (_, (rect, bindings)) in self.local_bindings.id_iter_mut().rev() { + if rect.contains_point(&self.mouse_pos) { + for (binding, state) in &mut bindings.iter_mut() { + *state = binding.is_pressed( + !mouse_blocked, + self.text_pipe.is_none(), + &self.last_text, + rl, + ); + + if *state { + self.last_text.clear(); + } + } + + mouse_blocked = true; + break; + } + } + + /* Process the global bindings, as long as nothing prevents the bindings + * from being processed like a local binding or the text being captured. + */ + for (binding, state) in self.global_bindings.iter_mut() { + *state = binding.is_pressed( + !mouse_blocked, + self.text_pipe.is_none(), + &self.last_text, + rl, + ); + + if *state { + self.last_text.clear(); + } + } + } + + /// Add a global binding. This is necessary so the input knows which key presses to monitor. + pub fn add_global(&mut self, binding: Binding) -> bool { + self.global_bindings.insert(binding, false).is_none() + } + + /// Add a local binding handler for the given area. Returns a unique and unchanging handler id. + /// Handlers with higher ids (that have been added later) are preferred over old handlers. + pub fn add_local_handler(&mut self, area: Rect<u16>) -> usize { + self.local_bindings.push((area, HashMap::new())) + } + + /// Add a local binding for the given handler. + pub fn add_local(&mut self, handler_id: usize, binding: Binding) -> bool { + self.local_bindings + .get_mut(handler_id) + .expect("Handler does not exist") + .1 + .insert(binding, false) + .is_none() + } + + /// Update the binding rectangle of a handler. + pub fn set_binding_rect(&mut self, handler_id: usize, rect: Rect<u16>) { + self.local_bindings + .get_mut(handler_id) + .expect("Handler does not exist") + .0 = rect; + } + + /// Check if a global binding has been activated this frame. If so, it returns true. + /// This will only activate once, so there is no need to worry about multiple function calls + /// when the user keeps the button down. + pub fn poll_global(&mut self, binding: &Binding) -> bool { + let state = self.global_bindings.get_mut(&binding); + if state.is_none() { + error!("Tried to poll binding that isn't registered."); + return false; + } + + *state.unwrap() + } + + /// Like `poll_global` bun instead checks the bindings of the local handler with the given id. + pub fn poll_local(&mut self, handler_id: usize, binding: &Binding) -> bool { + let (_, bindings) = self + .local_bindings + .get_mut(handler_id) + .expect("Invalid binding handler id"); + + let state = bindings.get_mut(&binding); + if state.is_none() { + error!("Tried to poll binding that isn't registered."); + return false; + } + + *state.unwrap() + } + + /// Attempts to capture all keyboard input from here on. If no other component is currently + /// capturing, it returns a receiver that can be used. When the entity no longer wants to + /// capture the keyboard, control must be returned by dropping the receiver. + pub fn try_capture_keyboard(&mut self) -> Option<Receiver<char>> { + if self.text_pipe.is_some() { + return None; + } + + let (tx, rx) = mpsc::channel(); + self.text_pipe = Some(tx); + + Some(rx) + } +} |
