From 95c7f00e200cf7719c0675d06fb8dc307f5e4fe4 Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Sat, 29 May 2021 20:03:36 +0200 Subject: [PATCH] Revising the public API for better flexibility --- examples/app.rs | 26 ++++++++ lvgl-sys/build.rs | 1 + lvgl/Cargo.toml | 5 ++ lvgl/src/display.rs | 150 ++++++++++++++++++++++++++++++++++++++++++ lvgl/src/functions.rs | 16 +++++ lvgl/src/lib.rs | 5 +- lvgl/src/ui.rs | 11 ++-- 7 files changed, 209 insertions(+), 5 deletions(-) create mode 100644 examples/app.rs create mode 100644 lvgl/src/display.rs create mode 100644 lvgl/src/functions.rs diff --git a/examples/app.rs b/examples/app.rs new file mode 100644 index 0000000..c7119c6 --- /dev/null +++ b/examples/app.rs @@ -0,0 +1,26 @@ +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::prelude::*; +use embedded_graphics_simulator::{OutputSettingsBuilder, SimulatorDisplay, Window}; +use lvgl; +use lvgl::display::{Display, DisplayBuffer, DisplayDriver}; + +type ColorSpace = Rgb565; + +fn main() { + let mut embedded_graphics_display: SimulatorDisplay = SimulatorDisplay::new( + Size::new(lvgl_sys::LV_HOR_RES_MAX, lvgl_sys::LV_VER_RES_MAX), + ); + + let output_settings = OutputSettingsBuilder::new().scale(2).build(); + let mut window = Window::new("App Example", &output_settings); + + // LVGL usage + lvgl::init(); + + let mut display_diver: DisplayDriver = + DisplayDriver::new(DisplayBuffer::new(), |pixels| { + // Here we draw to the external display + let _ = embedded_graphics_display.draw_iter(pixels); + }); + let _display = lvgl::disp_drv_register(&mut display_diver).unwrap(); +} diff --git a/lvgl-sys/build.rs b/lvgl-sys/build.rs index 17c4269..57ed69e 100644 --- a/lvgl-sys/build.rs +++ b/lvgl-sys/build.rs @@ -114,6 +114,7 @@ fn main() { let bindings = bindgen::Builder::default() .header(shims_dir.join("lvgl_sys.h").to_str().unwrap()) .generate_comments(false) + .derive_default(true) .layout_tests(false) .use_core() .rustfmt_bindings(true) diff --git a/lvgl/Cargo.toml b/lvgl/Cargo.toml index dfa8971..aa947fe 100644 --- a/lvgl/Cargo.toml +++ b/lvgl/Cargo.toml @@ -32,6 +32,11 @@ lvgl-sys = { version = "0.5.2", path = "../lvgl-sys" } embedded-graphics-simulator = "0.2.1" heapless = "0.5.5" +[[example]] +name = "app" +path = "../examples/app.rs" +required-features = ["alloc"] + [[example]] name = "demo" path = "../examples/demo.rs" diff --git a/lvgl/src/display.rs b/lvgl/src/display.rs new file mode 100644 index 0000000..2efb3d4 --- /dev/null +++ b/lvgl/src/display.rs @@ -0,0 +1,150 @@ +use crate::{Color, LvError, LvResult, Obj}; +use core::marker::PhantomData; +use core::mem::{ManuallyDrop, MaybeUninit}; +use core::ptr; +use core::ptr::NonNull; +use embedded_graphics::drawable; +use embedded_graphics::prelude::*; + +// TODO: Make this an external configuration +const REFRESH_BUFFER_LEN: usize = 2; +// Declare a buffer for the refresh rate +pub(crate) const BUF_SIZE: usize = lvgl_sys::LV_HOR_RES_MAX as usize * REFRESH_BUFFER_LEN; + +pub enum DisplayError { + FailedToRegister, + NotRegistered, +} + +#[derive(Copy, Clone)] +pub struct Display { + disp: NonNull, +} + +impl Display { + pub(crate) fn from_raw(disp: NonNull) -> Self { + Self { disp } + } +} + +#[derive(Copy, Clone)] +pub struct DefaultDisplay {} + +impl DefaultDisplay { + pub fn get_screen_active() -> Result { + Err(DisplayError::NotRegistered) + } +} + +#[derive(Copy, Clone)] +pub struct DisplayBuffer { + disp_buf: lvgl_sys::lv_disp_buf_t, +} + +impl DisplayBuffer { + pub fn new() -> Self { + let disp_buf = unsafe { + let mut disp_buf = MaybeUninit::uninit(); + let mut refresh_buffer = ManuallyDrop::new([lvgl_sys::lv_color_t::default(); BUF_SIZE]); + + lvgl_sys::lv_disp_buf_init( + disp_buf.as_mut_ptr(), + refresh_buffer.as_mut_ptr() as *mut cty::c_void, + ptr::null_mut(), + lvgl_sys::LV_HOR_RES_MAX * REFRESH_BUFFER_LEN as u32, + ); + disp_buf.assume_init() + }; + + Self { disp_buf } + } +} + +#[derive(Copy, Clone)] +pub struct DisplayDriver +where + C: PixelColor + From, +{ + pub(crate) disp_drv: lvgl_sys::lv_disp_drv_t, + phantom: PhantomData, +} + +impl DisplayDriver +where + C: PixelColor + From, +{ + pub fn new(display_buffer: DisplayBuffer, callback: F) -> Self + where + I: IntoIterator>, + F: FnMut(I) + 'static, + { + let mut disp_buf = ManuallyDrop::new(display_buffer.disp_buf); + let mut callback = ManuallyDrop::new(callback); + let mut disp_drv = unsafe { + let mut disp_drv = MaybeUninit::uninit(); + lvgl_sys::lv_disp_drv_init(disp_drv.as_mut_ptr()); + disp_drv.assume_init() + }; + disp_drv.buffer = &mut *disp_buf as *mut lvgl_sys::lv_disp_buf_t; + disp_drv.user_data = &mut callback as *mut _ as lvgl_sys::lv_disp_drv_user_data_t; + disp_drv.flush_cb = Some(disp_flush_trampoline::); + + Self { + disp_drv, + phantom: PhantomData, + } + } +} + +// impl Default for DisplayDriver { +// fn default() -> Self { +// Self::new(DisplayBuffer::new()) +// } +// } + +pub struct DisplayDriverBuilder { + disp_buf: Option, +} + +impl DisplayDriverBuilder { + pub fn with_callback() {} +} + +unsafe extern "C" fn disp_flush_trampoline( + disp_drv: *mut lvgl_sys::lv_disp_drv_t, + area: *const lvgl_sys::lv_area_t, + color_p: *mut lvgl_sys::lv_color_t, +) where + C: PixelColor + From, + I: IntoIterator>, + F: FnMut(I) + 'static, +{ + let display_driver = *disp_drv; + if !display_driver.user_data.is_null() { + let callback = &mut *(display_driver.user_data as *mut F); + let x1 = (*area).x1; + let x2 = (*area).x2; + let y1 = (*area).y1; + let y2 = (*area).y2; + + let ys = y1..=y2; + let xs = (x1..=x2).enumerate(); + let x_len = (x2 - x1 + 1) as usize; + + // We use iterators here to ensure that the Rust compiler can apply all possible + // optimizations at compile time. + let pixels = ys + .enumerate() + .map(|(iy, y)| { + xs.clone().map(move |(ix, x)| { + let color_len = x_len * iy + ix; + let lv_color = unsafe { *color_p.add(color_len) }; + let raw_color = Color::from_raw(lv_color); + drawable::Pixel(Point::new(x as i32, y as i32), raw_color.into()) + }) + }) + .flatten(); + + callback(pixels); + } +} diff --git a/lvgl/src/functions.rs b/lvgl/src/functions.rs new file mode 100644 index 0000000..1a8f242 --- /dev/null +++ b/lvgl/src/functions.rs @@ -0,0 +1,16 @@ +use crate::display::{Display, DisplayDriver, DisplayError}; +use crate::Color; +use core::ptr::NonNull; +use embedded_graphics::drawable; +use embedded_graphics::prelude::*; + +pub fn disp_drv_register>( + disp_drv: &mut DisplayDriver, +) -> Result { + let disp_ptr = unsafe { + lvgl_sys::lv_disp_drv_register(&mut disp_drv.disp_drv as *mut lvgl_sys::lv_disp_drv_t) + }; + Ok(Display::from_raw( + NonNull::new(disp_ptr).ok_or(DisplayError::FailedToRegister)?, + )) +} diff --git a/lvgl/src/lib.rs b/lvgl/src/lib.rs index e46452c..3d72ab5 100644 --- a/lvgl/src/lib.rs +++ b/lvgl/src/lib.rs @@ -31,6 +31,8 @@ use ::alloc::boxed::Box; #[cfg(feature = "lvgl_alloc")] mod allocator; +pub mod display; +mod functions; mod support; mod ui; #[macro_use] @@ -47,6 +49,7 @@ pub(crate) mod mem; #[cfg(not(feature = "lvgl_alloc"))] use crate::mem::Box; +pub use functions::*; pub use lv_core::*; pub use support::*; pub use ui::*; @@ -56,7 +59,7 @@ use core::sync::atomic::{AtomicBool, Ordering}; // Initialize LVGL only once. static LVGL_INITIALIZED: AtomicBool = AtomicBool::new(false); -pub(crate) fn lvgl_init() { +pub fn init() { if LVGL_INITIALIZED .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) .is_ok() diff --git a/lvgl/src/ui.rs b/lvgl/src/ui.rs index b6190ed..604801a 100644 --- a/lvgl/src/ui.rs +++ b/lvgl/src/ui.rs @@ -1,7 +1,7 @@ use crate::Box; use crate::{Color, Event, LvError, LvResult, Obj, Widget}; use core::marker::PhantomData; -use core::mem::MaybeUninit; +use core::mem::{ManuallyDrop, MaybeUninit}; use core::ptr; use core::ptr::NonNull; use core::sync::atomic::{AtomicBool, Ordering}; @@ -47,7 +47,7 @@ where .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) .is_ok() { - crate::lvgl_init(); + crate::init(); Ok(Self { _not_sync: PhantomData, display_data: None, @@ -80,10 +80,13 @@ where // Basic initialization of the display driver lvgl_sys::lv_disp_drv_init(disp_drv.as_mut_ptr()); let mut disp_drv = Box::new(disp_drv.assume_init()); - // Assign the buffer to the display + // Assign the buffer to the display, the memory "leaks" here since + // the `disp_drv` is dropped in the end of this method. This is not a problem + // since this should live for the whole lifetime of the program anyways. disp_drv.buffer = Box::into_raw(Box::new(disp_buf.assume_init())); // Set your driver function disp_drv.flush_cb = Some(display_callback_wrapper::); + // The memory of `display_data` is kept because of the reference in `self` disp_drv.user_data = &mut self.display_data as *mut _ as *mut cty::c_void; // We need to remember to deallocate the `disp_drv` memory when dropping UI lvgl_sys::lv_disp_drv_register(Box::into_raw(disp_drv)); @@ -110,7 +113,7 @@ where } } - pub fn event_send(&mut self, obj: &mut W, event: Event) -> LvResult<()> + pub fn event_send(&self, obj: &mut W, event: Event) -> LvResult<()> where W: Widget, {