aboutsummaryrefslogtreecommitdiff
path: root/src/input
diff options
context:
space:
mode:
Diffstat (limited to 'src/input')
-rw-r--r--src/input/binding.rs123
-rw-r--r--src/input/button.rs177
-rw-r--r--src/input/mod.rs200
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)
+ }
+}