diff --git a/examples/button_click.rs b/examples/button_click.rs index f6d47ce..25f3b76 100644 --- a/examples/button_click.rs +++ b/examples/button_click.rs @@ -4,9 +4,12 @@ use embedded_graphics::prelude::*; use embedded_graphics_simulator::{ OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, }; +use lvgl::input_device::{InputData, Pointer}; use lvgl::style::Style; use lvgl::widgets::{Btn, Label}; -use lvgl::{self, Align, Color, Event, LvError, Part, State, Widget, UI}; +use lvgl::{self, Align, Color, LvError, Part, State, Widget, UI}; +use std::cell::RefCell; +use std::rc::Rc; use std::time::Instant; fn main() -> Result<(), LvError> { @@ -21,6 +24,22 @@ fn main() -> Result<(), LvError> { // Implement and register your display: ui.disp_drv_register(display)?; + // Register the input mode + let latest_touch_point: Rc>> = Rc::new(RefCell::new(None)); + let internal = Rc::clone(&latest_touch_point); + let mut touch_screen = Pointer::new(move || { + let info = internal.borrow().clone(); + if info.is_some() { + let point = info.unwrap(); + println!("Changed to {:?}", point); + Some(InputData::Touch(point).pressed().once()) + } else { + None + } + }); + + ui.indev_drv_register(&mut touch_screen)?; + // Create screen and widgets let mut screen = ui.scr_act()?; @@ -64,7 +83,7 @@ fn main() -> Result<(), LvError> { } => { println!("Clicked on: {:?}", point); // Send a event to the button directly - ui.event_send(&mut button, Event::Clicked)?; + latest_touch_point.borrow_mut().replace(point); } SimulatorEvent::Quit => break 'running, _ => {} diff --git a/lvgl/src/input_device.rs b/lvgl/src/input_device.rs new file mode 100644 index 0000000..dca2241 --- /dev/null +++ b/lvgl/src/input_device.rs @@ -0,0 +1,185 @@ +use crate::LvResult; +use core::mem::MaybeUninit; +use embedded_graphics::geometry::Point; + +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub enum InputData { + Touch(Point), + Key(u32), +} + +impl InputData { + pub fn released(self) -> InputState { + InputState::Released(self) + } + + pub fn pressed(self) -> InputState { + InputState::Pressed(self) + } +} + +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub enum InputState { + Released(InputData), + Pressed(InputData), +} + +impl InputState { + pub fn once(self) -> BufferStatus { + BufferStatus::Empty(self) + } + + pub fn and_continued(self) -> BufferStatus { + BufferStatus::Buffered(self) + } +} + +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub enum BufferStatus { + Empty(InputState), + Buffered(InputState), +} + +pub struct Pointer +where + F: Fn() -> Option, +{ + pub(crate) driver: lvgl_sys::lv_indev_drv_t, + pub(crate) descriptor: Option, + handler: F, +} + +impl Pointer +where + F: Fn() -> Option, +{ + pub fn new(mut handler: F) -> Self { + let driver = unsafe { + let mut indev_drv = MaybeUninit::uninit(); + lvgl_sys::lv_indev_drv_init(indev_drv.as_mut_ptr()); + let mut indev_drv = indev_drv.assume_init(); + indev_drv.type_ = lvgl_sys::LV_INDEV_TYPE_POINTER as lvgl_sys::lv_indev_type_t; + indev_drv.read_cb = Some(read_input::); + indev_drv.user_data = &mut handler as *mut _ as lvgl_sys::lv_indev_drv_user_data_t; + indev_drv + }; + Self { + handler, + driver, + descriptor: None, + } + } + + pub(crate) unsafe fn set_descriptor( + &mut self, + descriptor: *mut lvgl_sys::lv_indev_t, + ) -> LvResult<()> { + // TODO: check if not null && check if `self.descriptor` is not already set! + self.descriptor = Some(*descriptor); + Ok(()) + } +} + +unsafe extern "C" fn read_input( + indev_drv: *mut lvgl_sys::lv_indev_drv_t, + data: *mut lvgl_sys::lv_indev_data_t, +) -> bool +where + F: Fn() -> Option, +{ + let mut data = *data; + // convert user data to function + let user_closure = &mut *((*indev_drv).user_data as *mut F); + // call user data + let result: Option = user_closure(); + return if let Some(info) = result { + match info { + BufferStatus::Empty(InputState::Pressed(InputData::Touch(point))) => { + data.point.x = point.x as lvgl_sys::lv_coord_t; + data.point.y = point.y as lvgl_sys::lv_coord_t; + data.state = lvgl_sys::LV_INDEV_STATE_PR as lvgl_sys::lv_indev_state_t; + false + } + BufferStatus::Empty(InputState::Released(InputData::Touch(point))) => { + data.point.x = point.x as lvgl_sys::lv_coord_t; + data.point.y = point.y as lvgl_sys::lv_coord_t; + data.state = lvgl_sys::LV_INDEV_STATE_REL as lvgl_sys::lv_indev_state_t; + false + } + BufferStatus::Buffered(InputState::Pressed(InputData::Touch(point))) => { + data.point.x = point.x as lvgl_sys::lv_coord_t; + data.point.y = point.y as lvgl_sys::lv_coord_t; + data.state = lvgl_sys::LV_INDEV_STATE_PR as lvgl_sys::lv_indev_state_t; + true + } + BufferStatus::Buffered(InputState::Released(InputData::Touch(point))) => { + data.point.x = point.x as lvgl_sys::lv_coord_t; + data.point.y = point.y as lvgl_sys::lv_coord_t; + data.state = lvgl_sys::LV_INDEV_STATE_REL as lvgl_sys::lv_indev_state_t; + true + } + BufferStatus::Empty(InputState::Released(InputData::Key(_))) => false, + BufferStatus::Empty(InputState::Pressed(InputData::Key(_))) => false, + BufferStatus::Buffered(InputState::Released(InputData::Key(_))) => true, + BufferStatus::Buffered(InputState::Pressed(InputData::Key(_))) => true, + } + } else { + false + }; +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{Color, UI}; + use core::marker::PhantomData; + use embedded_graphics::drawable::Pixel; + use embedded_graphics::geometry::Size; + use embedded_graphics::pixelcolor::PixelColor; + use embedded_graphics::pixelcolor::Rgb565; + use embedded_graphics::DrawTarget; + + struct FakeDisplay + where + C: PixelColor + From, + { + p: PhantomData, + } + + impl DrawTarget for FakeDisplay + where + C: PixelColor + From, + { + type Error = (); + + fn draw_pixel(&mut self, item: Pixel) -> Result<(), Self::Error> { + Ok(()) + } + + fn size(&self) -> Size { + Size::new(crate::VER_RES_MAX, crate::HOR_RES_MAX) + } + } + + //#[test] + // We cannot test right now by having instances of UI global state... :( + // I need to find a way to test while having global state... + fn pointer_input_device() -> LvResult<()> { + let mut ui = UI::init()?; + + let disp: FakeDisplay = FakeDisplay { p: PhantomData }; + + ui.disp_drv_register(disp); + + fn read_touchpad_device() -> Point { + Point::new(120, 23) + } + + let mut touch_screen = + Pointer::new(|| InputData::Touch(read_touchpad_device()).pressed().once()); + + ui.indev_drv_register(&mut touch_screen)?; + + Ok(()) + } +} diff --git a/lvgl/src/lib.rs b/lvgl/src/lib.rs index adb763c..6ae501f 100644 --- a/lvgl/src/lib.rs +++ b/lvgl/src/lib.rs @@ -3,6 +3,7 @@ #[macro_use] extern crate bitflags; +pub mod input_device; pub(crate) mod mem; mod support; mod ui; diff --git a/lvgl/src/mem.rs b/lvgl/src/mem.rs index 9167858..e041cd4 100644 --- a/lvgl/src/mem.rs +++ b/lvgl/src/mem.rs @@ -70,16 +70,17 @@ impl AsMut for Box { mod test { use super::*; use core::mem::MaybeUninit; - use std::sync::Once; - - static INIT_LVGL: Once = Once::new(); fn init() { - INIT_LVGL.call_once(|| { - unsafe { - lvgl_sys::lv_init(); - }; - }); + unsafe { + lvgl_sys::lv_init(); + }; + } + + fn teardown() { + unsafe { + lvgl_sys::lv_deinit(); + } } #[test] @@ -90,6 +91,8 @@ mod test { drop(v); let v = Box::new(10).unwrap(); drop(v); + + teardown(); } #[test] @@ -129,6 +132,8 @@ mod test { } assert_eq!(point.x, i); } + + teardown(); } fn print_mem_info() { diff --git a/lvgl/src/ui.rs b/lvgl/src/ui.rs index 699741c..5a4f223 100644 --- a/lvgl/src/ui.rs +++ b/lvgl/src/ui.rs @@ -1,3 +1,4 @@ +use crate::input_device::{BufferStatus, Pointer}; use crate::mem::Box; use crate::{Color, Event, LvError, LvResult, Obj, Widget}; use core::marker::PhantomData; @@ -56,6 +57,17 @@ where } } + pub fn indev_drv_register(&mut self, input_device: &mut Pointer) -> LvResult<()> + where + F: Fn() -> Option, + { + unsafe { + let descr = lvgl_sys::lv_indev_drv_register(&mut input_device.driver as *mut _); + input_device.set_descriptor(descr)?; + } + Ok(()) + } + pub fn disp_drv_register(&mut self, display: T) -> LvResult<()> { self.display_data = Some(DisplayUserData { display,