From 64bb536859c7b7eb8c0b3746a765c04e6e2a43c5 Mon Sep 17 00:00:00 2001 From: Lars Westermann Date: Fri, 23 Feb 2024 17:42:11 +0100 Subject: [PATCH] Add button event handlers --- Cargo.toml | 3 +- src/ev3.rs | 115 ++++++++++++++++++++++++++---------- src/ev3_button_functions.rs | 59 ++++++++++++++++++ src/lib.rs | 4 ++ 4 files changed, 149 insertions(+), 32 deletions(-) create mode 100644 src/ev3_button_functions.rs diff --git a/Cargo.toml b/Cargo.toml index d3c752e..970e42d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ev3dev-lang-rust" -version = "0.13.0" +version = "0.14.0" edition = "2021" authors = ["Lars Westermann "] @@ -26,6 +26,7 @@ ev3dev-lang-rust-derive = { path = "ev3dev_lang_rust_derive", version="0.10" } libc = "0.2" framebuffer = { version = "0.3", optional = true } image = { version = "0.24", optional = true } +paste = "1.0" [workspace] members = [ diff --git a/src/ev3.rs b/src/ev3.rs index bbe28a8..88b8ece 100644 --- a/src/ev3.rs +++ b/src/ev3.rs @@ -11,6 +11,8 @@ use std::os::unix::io::AsRawFd; use std::path::Path; use std::rc::Rc; +use paste::paste; + use crate::driver::DRIVER_PATH; use crate::utils::OrErr; use crate::{Attribute, Ev3Result}; @@ -201,27 +203,41 @@ struct ButtonMapEntry { pub key_code: u32, } +type ButtonHandler = Box; + /// This implementation depends on the availability of the EVIOCGKEY ioctl /// to be able to read the button state buffer. See Linux kernel source /// in /include/uapi/linux/input.h for details. -#[derive(Debug)] struct ButtonFileHandler { file_map: HashMap, button_map: HashMap, + button_handlers: HashMap, pressed_buttons: HashSet, } +impl std::fmt::Debug for ButtonFileHandler { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ButtonFileHandler") + .field("file_map", &self.file_map) + .field("button_map", &self.button_map) + .field("button_handlers", &self.button_map.keys()) + .field("pressed_buttons", &self.pressed_buttons) + .finish() + } +} + impl ButtonFileHandler { /// Create a new instance. fn new() -> Self { ButtonFileHandler { file_map: HashMap::new(), button_map: HashMap::new(), + button_handlers: HashMap::new(), pressed_buttons: HashSet::new(), } } - /// Add a button the the file handler. + /// Add a button to the file handler. fn add_button(&mut self, name: &str, file_name: &str, key_code: u32) -> Ev3Result<()> { if !self.file_map.contains_key(file_name) { let file = File::open(file_name)?; @@ -242,6 +258,16 @@ impl ButtonFileHandler { Ok(()) } + /// Sets an event listener for the given button. + fn set_button_listener(&mut self, name: &str, listener: Option) { + if let Some(listener) = listener { + self.button_handlers.insert(name.to_owned(), listener); + } else { + self.button_handlers.remove(name); + } + } + + /// Gets a copy of the currently pressed buttons. fn get_pressed_buttons(&self) -> HashSet { self.pressed_buttons.clone() } @@ -264,6 +290,7 @@ impl ButtonFileHandler { } } + let old_pressed_buttons = self.pressed_buttons.clone(); self.pressed_buttons.clear(); for ( @@ -280,6 +307,13 @@ impl ButtonFileHandler { self.pressed_buttons.insert(btn_name.to_owned()); } } + + let difference = old_pressed_buttons.symmetric_difference(&self.pressed_buttons); + for button in difference { + if self.button_handlers.contains_key(button) { + self.button_handlers[button](self.get_button_state(button)); + } + } } } @@ -297,6 +331,10 @@ impl ButtonFileHandler { /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { /// let button = Button::new()?; /// +/// button.set_down_handler(|is_pressed| { +/// println("Is 'down' pressed: {is_pressed}"); +/// }); +/// /// loop { /// button.process(); /// @@ -335,42 +373,57 @@ impl Button { /// Check for currently pressed buttons. If the new state differs from the /// old state, call the appropriate button event handlers. + /// ```no_run + /// use ev3dev_lang_rust::Button; + /// use std::thread; + /// use std::time::Duration; + /// + /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { + /// let button = Button::new()?; + /// + /// button.set_down_handler(|is_pressed| { + /// println("Is 'down' pressed: {is_pressed}"); + /// }); + /// + /// loop { + /// button.process(); + /// + /// println!("Is 'up' pressed: {}", button.is_up()); + /// println!("Pressed buttons: {:?}", button.get_pressed_buttons()); + /// + /// thread::sleep(Duration::from_millis(100)); + /// } + /// # } + /// ``` pub fn process(&self) { self.button_handler.borrow_mut().process() } /// Get all pressed buttons by name. + /// + /// ```no_run + /// use ev3dev_lang_rust::Button; + /// use std::thread; + /// use std::time::Duration; + /// + /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { + /// let button = Button::new()?; + /// + /// loop { + /// button.process(); + /// println!("Pressed buttons: {:?}", button.get_pressed_buttons()); + /// thread::sleep(Duration::from_millis(100)); + /// } + /// # } + /// ``` pub fn get_pressed_buttons(&self) -> HashSet { self.button_handler.borrow().get_pressed_buttons() } - /// Check if 'up' button is pressed. - pub fn is_up(&self) -> bool { - self.button_handler.borrow().get_button_state("up") - } - - /// Check if 'down' button is pressed. - pub fn is_down(&self) -> bool { - self.button_handler.borrow().get_button_state("down") - } - - /// Check if 'left' button is pressed. - pub fn is_left(&self) -> bool { - self.button_handler.borrow().get_button_state("left") - } - - /// Check if 'right' button is pressed. - pub fn is_right(&self) -> bool { - self.button_handler.borrow().get_button_state("right") - } - - /// Check if 'enter' button is pressed. - pub fn is_enter(&self) -> bool { - self.button_handler.borrow().get_button_state("enter") - } - - /// Check if 'backspace' button is pressed. - pub fn is_backspace(&self) -> bool { - self.button_handler.borrow().get_button_state("backspace") - } + ev3_button_functions!(up); + ev3_button_functions!(down); + ev3_button_functions!(left); + ev3_button_functions!(right); + ev3_button_functions!(enter); + ev3_button_functions!(backspace); } diff --git a/src/ev3_button_functions.rs b/src/ev3_button_functions.rs new file mode 100644 index 0000000..ac97a40 --- /dev/null +++ b/src/ev3_button_functions.rs @@ -0,0 +1,59 @@ +/// Helper macro to create all necessary functions for a button +#[macro_export] +macro_rules! ev3_button_functions { + ($button_name:ident) => { + paste! { + #[doc = "Check if the `" $button_name "` button is pressed."] + #[doc = "```no_run"] + #[doc = "use ev3dev_lang_rust::Button;"] + #[doc = "use std::thread;"] + #[doc = "use std::time::Duration;"] + #[doc = ""] + #[doc = "# fn main() -> ev3dev_lang_rust::Ev3Result<()> {"] + #[doc = "let button = Button::new()?;"] + #[doc = ""] + #[doc = "loop {"] + #[doc = " button.process();"] + #[doc = " println(\"Is '" $button_name "' pressed: {}\", button.is_" $button_name "());"] + #[doc = " thread::sleep(Duration::from_millis(100));"] + #[doc = "}"] + #[doc = "# }"] + #[doc = "```"] + pub fn [] (&self) -> bool { + self.button_handler.borrow().get_button_state("$button_name") + } + + #[doc = "Set an event handler for changes in the pressed state of the `" $button_name "` button."] + #[doc = "```no_run"] + #[doc = "use ev3dev_lang_rust::Button;"] + #[doc = "use std::thread;"] + #[doc = "use std::time::Duration;"] + #[doc = ""] + #[doc = "# fn main() -> ev3dev_lang_rust::Ev3Result<()> {"] + #[doc = "let button = Button::new()?;"] + #[doc = ""] + #[doc = "button.set_" $button_name "_handler(|is_pressed| {"] + #[doc = " println(\"Is '" $button_name "' pressed: {is_pressed}\");"] + #[doc = "});"] + #[doc = ""] + #[doc = "loop {"] + #[doc = " button.process();"] + #[doc = " thread::sleep(Duration::from_millis(100));"] + #[doc = "}"] + #[doc = "# }"] + #[doc = "```"] + pub fn [](&mut self, handler: impl Fn(bool) + 'static) { + self.button_handler + .borrow_mut() + .set_button_listener("$button_name", Some(Box::new(handler))); + } + + #[doc = "Removes the event handler of the `" $button_name "` button."] + pub fn [](&mut self) { + self.button_handler + .borrow_mut() + .set_button_listener("$button_name", None); + } + } + }; +} diff --git a/src/lib.rs b/src/lib.rs index 5713de3..8a2be7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,10 +43,14 @@ extern crate image; #[macro_use] extern crate ev3dev_lang_rust_derive; extern crate libc; +extern crate paste; #[macro_use] mod findable; +#[macro_use] +mod ev3_button_functions; + mod attribute; pub use attribute::Attribute; mod driver;