//! 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 raylib_sys::GetCharPressed; 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, local_bindings: StableVec<(Rect, HashMap)>, last_text: String, text_pipe: Option>, mouse_pos: Vec2, } 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_TAB) { Some('\t') } else if rl.is_key_pressed(KeyboardKey::KEY_BACKSPACE) { Some('\x7f') } else { let c_opt = unsafe { GetCharPressed() }; if c_opt > 0 { Some(c_opt as u8 as char) } else { None } }; /* 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(dbg!(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) -> 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) { 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> { if self.text_pipe.is_some() { return None; } let (tx, rx) = mpsc::channel(); self.text_pipe = Some(tx); Some(rx) } }