diff --git a/examples/demo.rs b/examples/demo.rs index 4645609..42e0ecc 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -40,37 +40,14 @@ fn main() -> Result<(), LvError> { // // static DISPLAY_REGISTRY: SingleDisplayRegistry = DisplayRegistry::empty(); // let display = DISPLAY_REGISTRY.register_shared(&DRAW_BUFFER, shared_native_display.clone())?; - // Register your native display with LVGL. We use the `Display::register_shared()` method here, - // but that's because the Simulator needs a mutable reference to the display so it can draw - // updates. On your embedded device code, you will use `Display::register()`. - let shared_disp_inner = shared_native_display.clone(); - let display = Display::register_shared(&DRAW_BUFFER, move |update| { - // make this a `.into_pixels()` method in DisplayRefresh or `From for T where T: IntoIterator>` - let area = &update.area; - 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 raw_color = update.colors[color_len]; - drawable::Pixel(Point::new(x as i32, y as i32), raw_color.into()) - }) - }) - .flatten(); - - let mut em_disp = shared_disp_inner.lock(); - em_disp.draw_iter(pixels); + // Register your display update callback with LVGL. The closure you pass here will be called + // whenever LVGL has updates to be painted to the display. + let display = Display::register(&DRAW_BUFFER, { + let shared_disp_inner = SyncArc::clone(&shared_native_display); + move |update| { + let mut em_disp = shared_disp_inner.lock(); + em_disp.draw_iter(update.as_pixels()); + } })?; // Create screen and widgets diff --git a/lvgl/Cargo.toml b/lvgl/Cargo.toml index 98f35b1..415e39f 100644 --- a/lvgl/Cargo.toml +++ b/lvgl/Cargo.toml @@ -14,7 +14,7 @@ build = "build.rs" [dependencies] lvgl-sys = { version = "0.5.2", path = "../lvgl-sys" } cty = "0.2.1" -embedded-graphics = "0.6.2" +embedded-graphics = { version = "0.6.2", optional = true } cstr_core = "0.2.3" bitflags = "1.2.1" parking_lot = "0.11.1" @@ -22,6 +22,7 @@ heapless = "0.7.1" [features] default = [] +embedded_graphics = ["embedded-graphics"] alloc = ["cstr_core/alloc"] lvgl_alloc = ["alloc"] @@ -37,29 +38,29 @@ embedded-graphics-simulator = "0.2.1" [[example]] name = "app" path = "../examples/app.rs" -required-features = ["alloc"] +required-features = ["alloc", "embedded_graphics"] [[example]] name = "demo" path = "../examples/demo.rs" -required-features = ["alloc"] +required-features = ["alloc", "embedded_graphics"] [[example]] name = "bar" path = "../examples/bar.rs" -required-features = ["alloc"] +required-features = ["alloc", "embedded_graphics"] [[example]] name = "button_click" path = "../examples/button_click.rs" -required-features = ["alloc"] +required-features = ["alloc", "embedded_graphics"] [[example]] name = "gauge" path = "../examples/gauge.rs" -required-features = ["alloc"] +required-features = ["alloc", "embedded_graphics"] [[example]] name = "arc" path = "../examples/arc.rs" -required-features = ["alloc"] +required-features = ["alloc", "embedded_graphics"] diff --git a/lvgl/src/display.rs b/lvgl/src/display.rs index 3c7bbfd..b172ab5 100644 --- a/lvgl/src/display.rs +++ b/lvgl/src/display.rs @@ -3,18 +3,12 @@ use crate::{disp_drv_register, disp_get_default, get_str_act}; use crate::{Box, RunOnce}; use crate::{Color, Obj}; use core::cell::RefCell; -use core::marker::PhantomData; use core::mem::MaybeUninit; use core::ptr::NonNull; use core::{ptr, result}; -use embedded_graphics::drawable; -use embedded_graphics::prelude::*; use parking_lot::const_mutex; use parking_lot::Mutex; -#[cfg(feature = "alloc")] -use alloc::sync::Arc; - pub const DISP_HOR_RES: usize = lvgl_sys::LV_HOR_RES_MAX as usize; pub const DISP_VER_RES: usize = lvgl_sys::LV_VER_RES_MAX as usize; @@ -27,9 +21,6 @@ pub enum DisplayError { type Result = result::Result; -#[cfg(feature = "alloc")] -pub type SharedNativeDisplay = Arc>; - pub struct Display { pub(crate) disp: NonNull, } @@ -39,27 +30,14 @@ impl Display { Self { disp } } - // pub fn register( - // draw_buffer: &'static DrawBuffer, - // native_display: T, - // ) -> Result - // where - // T: DrawTarget, - // C: PixelColor + From, - // { - // let mut display_diver = DisplayDriver::new(draw_buffer, native_display)?; - // Ok(disp_drv_register(&mut display_diver)?) - // } - - #[cfg(feature = "alloc")] - pub fn register_shared( + pub fn register( draw_buffer: &'static DrawBuffer, display_update: F, ) -> Result where F: FnMut(&DisplayRefresh) + 'static, { - let mut display_diver = DisplayDriver::new_shared(draw_buffer, display_update)?; + let mut display_diver = DisplayDriver::new(draw_buffer, display_update)?; Ok(disp_drv_register(&mut display_diver)?) } @@ -125,45 +103,9 @@ pub struct DisplayDriver { } impl DisplayDriver { - // pub fn new( - // draw_buffer: &'static DrawBuffer, - // native_display: T, - // ) -> Result { - // let mut disp_drv = unsafe { - // let mut inner = MaybeUninit::uninit(); - // lvgl_sys::lv_disp_drv_init(inner.as_mut_ptr()); - // inner.assume_init() - // }; - // - // // Safety: The variable `draw_buffer` is statically allocated, no need to worry about this being dropped. - // disp_drv.buffer = draw_buffer - // .get_ptr() - // .map(|ptr| Box::into_raw(ptr) as *mut _) - // .ok_or(DisplayError::FailedToRegister)?; - // - // let native_display = DisplayUserData { - // display: native_display, - // phantom: PhantomData, - // }; - // disp_drv.user_data = - // Box::into_raw(Box::new(native_display)) as *mut _ as lvgl_sys::lv_disp_drv_user_data_t; - // - // // Sets trampoline pointer to the function implementation using the types (T, C) that - // // are used in this instance of `DisplayDriver`. - // disp_drv.flush_cb = Some(disp_flush_trampoline::); - // - // // We do not store any memory that can be accidentally deallocated by on the Rust side. - // Ok(Self { - // disp_drv, - // phantom_color: PhantomData, - // phantom_display: PhantomData, - // }) - // } - - #[cfg(feature = "alloc")] - pub fn new_shared( + pub fn new( draw_buffer: &'static DrawBuffer, - display_update: F, + display_update_callback: F, ) -> Result where F: FnMut(&DisplayRefresh) + 'static, @@ -180,78 +122,19 @@ impl DisplayDriver { .map(|ptr| Box::into_raw(ptr) as *mut _) .ok_or(DisplayError::FailedToRegister)?; - disp_drv.user_data = - Box::into_raw(Box::new(display_update)) as *mut _ as lvgl_sys::lv_disp_drv_user_data_t; + disp_drv.user_data = Box::into_raw(Box::new(display_update_callback)) as *mut _ + as lvgl_sys::lv_disp_drv_user_data_t; - // Sets trampoline pointer to the function implementation using the types (T, C) that - // are used in this instance of `DisplayDriver`. - disp_drv.flush_cb = Some(shared_disp_flush_trampoline::); + // Sets trampoline pointer to the function implementation that uses the `F` type for a + // refresh buffer of size N specifically. + disp_drv.flush_cb = Some(disp_flush_trampoline::); // We do not store any memory that can be accidentally deallocated by on the Rust side. Ok(Self { disp_drv }) } } -pub(crate) struct DisplayUserData -where - T: DrawTarget, - C: PixelColor + From, -{ - display: T, - phantom: PhantomData, -} - -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 - T: DrawTarget, - C: PixelColor + From, -{ - let display_driver = *disp_drv; - if !display_driver.user_data.is_null() { - let user_data = &mut *(display_driver.user_data as *mut DisplayUserData); - 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(); - - let _ = user_data.display.draw_iter(pixels); - } - - // Indicate to LVGL that we are ready with the flushing - lvgl_sys::lv_disp_flush_ready(disp_drv); -} - -#[cfg(feature = "alloc")] -pub(crate) struct SharedDisplayUserData -where - T: DrawTarget, - C: PixelColor + From, -{ - display: SharedNativeDisplay, - phantom: PhantomData, -} - +/// Represents a sub-area of the display that is being updated. pub struct Area { pub x1: i16, pub x2: i16, @@ -259,13 +142,50 @@ pub struct Area { pub y2: i16, } +/// It's a update to the display information, contains the area that is being updated and the color +/// of the pixels that need to be updated. The colors are represented in a contiguous array. pub struct DisplayRefresh { pub area: Area, pub colors: [Color; N], } -#[cfg(feature = "alloc")] -unsafe extern "C" fn shared_disp_flush_trampoline( +#[cfg(feature = "embedded_graphics")] +mod embedded_graphics_impl { + use crate::{Color, DisplayRefresh}; + use embedded_graphics::drawable; + use embedded_graphics::prelude::*; + + impl DisplayRefresh { + pub fn as_pixels(&self) -> impl IntoIterator> + '_ + where + C: PixelColor + From, + { + let area = &self.area; + 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. + ys.enumerate() + .map(move |(iy, y)| { + xs.clone().map(move |(ix, x)| { + let color_len = x_len * iy + ix; + let raw_color = self.colors[color_len]; + drawable::Pixel(Point::new(x as i32, y as i32), raw_color.into()) + }) + }) + .flatten() + } + } +} + +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, @@ -279,7 +199,7 @@ unsafe extern "C" fn shared_disp_flush_trampoline( let mut colors = [Color::default(); N]; let mut color_len = 0; for color in &mut colors { - let lv_color = unsafe { *color_p.add(color_len) }; + let lv_color = *color_p.add(color_len); *color = Color::from_raw(lv_color); color_len += 1; } diff --git a/lvgl/src/functions.rs b/lvgl/src/functions.rs index dcb659c..a3e06c4 100644 --- a/lvgl/src/functions.rs +++ b/lvgl/src/functions.rs @@ -1,9 +1,8 @@ use crate::display::{Display, DisplayDriver}; -use crate::{Color, Obj, Widget}; +use crate::{Obj, Widget}; use core::ptr::NonNull; use core::time::Duration; use core::{ptr, result}; -use embedded_graphics::prelude::*; #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum CoreError { diff --git a/lvgl/src/lib.rs b/lvgl/src/lib.rs index 779f20b..985ea0a 100644 --- a/lvgl/src/lib.rs +++ b/lvgl/src/lib.rs @@ -48,13 +48,11 @@ mod display; pub use display::*; mod functions; mod support; -mod ui; pub mod widgets; use core::sync::atomic::{AtomicBool, Ordering}; pub use functions::*; pub use lv_core::*; pub use support::*; -pub use ui::*; struct RunOnce(AtomicBool); @@ -84,20 +82,16 @@ pub fn init() { pub(crate) mod tests { use super::*; use crate::display::{Display, DrawBuffer}; - use embedded_graphics::mock_display::MockDisplay; - use embedded_graphics::pixelcolor::Rgb565; pub(crate) fn initialize_test() { init(); - let embedded_graphics_display: MockDisplay = Default::default(); - const REFRESH_BUFFER_SIZE: usize = 64 * 64 / 10; static DRAW_BUFFER: DrawBuffer = DrawBuffer::new(); static ONCE_INIT: RunOnce = RunOnce::new(); if ONCE_INIT.swap_and_check() { - let _ = Display::register(&DRAW_BUFFER, embedded_graphics_display).unwrap(); + let _ = Display::register(&DRAW_BUFFER, |_| {}).unwrap(); } } } diff --git a/lvgl/src/lv_core/obj.rs b/lvgl/src/lv_core/obj.rs index 5804582..e78d670 100644 --- a/lvgl/src/lv_core/obj.rs +++ b/lvgl/src/lv_core/obj.rs @@ -1,5 +1,4 @@ use crate::lv_core::style::Style; -use crate::Box; use crate::{Align, LvError, LvResult}; use core::ptr; diff --git a/lvgl/src/support.rs b/lvgl/src/support.rs index 540f376..4c73221 100644 --- a/lvgl/src/support.rs +++ b/lvgl/src/support.rs @@ -2,6 +2,8 @@ use crate::display::DisplayError; use crate::Widget; use core::convert::{TryFrom, TryInto}; use core::ptr::NonNull; + +#[cfg(feature = "embedded_graphics")] use embedded_graphics::pixelcolor::{Rgb565, Rgb888}; pub type LvResult = Result; @@ -53,6 +55,7 @@ impl Color { } } +#[cfg(feature = "embedded_graphics")] impl From for Rgb888 { fn from(color: Color) -> Self { unsafe { @@ -65,6 +68,7 @@ impl From for Rgb888 { } } +#[cfg(feature = "embedded_graphics")] impl From for Rgb565 { fn from(color: Color) -> Self { unsafe { diff --git a/lvgl/src/ui.rs b/lvgl/src/ui.rs deleted file mode 100644 index 6b36463..0000000 --- a/lvgl/src/ui.rs +++ /dev/null @@ -1,206 +0,0 @@ -use crate::Box; -use crate::{Color, Event, LvError, LvResult, Obj, Widget}; -use core::marker::PhantomData; -use core::mem::MaybeUninit; -use core::ptr; -use core::ptr::NonNull; -use core::sync::atomic::{AtomicBool, Ordering}; -use core::time::Duration; -use embedded_graphics::pixelcolor::PixelColor; -use embedded_graphics::prelude::*; -use embedded_graphics::{drawable, DrawTarget}; - -// There can only be a single reference to LVGL library. -static LVGL_IN_USE: AtomicBool = AtomicBool::new(false); - -// 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 struct UI -where - T: DrawTarget, - C: PixelColor + From, -{ - // LVGL is not thread-safe by default. - _not_sync: PhantomData<*mut ()>, - // Later we can add possibility to have multiple displays by using `heapless::Vec` - display_data: Option>, -} - -// LVGL does not use thread locals. -unsafe impl Send for UI -where - T: DrawTarget, - C: PixelColor + From, -{ -} - -impl UI -where - T: DrawTarget, - C: PixelColor + From, -{ - pub fn init() -> LvResult { - if LVGL_IN_USE - .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) - .is_ok() - { - crate::init(); - Ok(Self { - _not_sync: PhantomData, - display_data: None, - }) - } else { - Err(LvError::AlreadyInUse) - } - } - - pub fn disp_drv_register(&mut self, display: T) -> LvResult<()> { - self.display_data = Some(DisplayUserData { - display, - phantom: PhantomData, - }); - - let refresh_buffer1 = [Color::from_rgb((0, 0, 0)).raw; BUF_SIZE]; - let refresh_buffer2 = [Color::from_rgb((0, 0, 0)).raw; BUF_SIZE]; - - let mut disp_buf = MaybeUninit::::uninit(); - let mut disp_drv = MaybeUninit::::uninit(); - - unsafe { - // Initialize the display buffer - lvgl_sys::lv_disp_buf_init( - disp_buf.as_mut_ptr(), - Box::into_raw(Box::new(refresh_buffer1)) as *mut cty::c_void, - Box::into_raw(Box::new(refresh_buffer2)) as *mut cty::c_void, - lvgl_sys::LV_HOR_RES_MAX * REFRESH_BUFFER_LEN as u32, - ); - // 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, 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)); - }; - - Ok(()) - } - - pub fn get_display_ref(&self) -> Option<&T> { - match self.display_data.as_ref() { - None => None, - Some(v) => Some(&v.display), - } - } - - pub fn scr_act(&self) -> LvResult { - unsafe { - let screen = lvgl_sys::lv_disp_get_scr_act(ptr::null_mut()); - if let Some(v) = NonNull::new(screen) { - Ok(Obj::from_raw(v)) - } else { - Err(LvError::InvalidReference) - } - } - } - - pub fn event_send(&self, obj: &mut W, event: Event) -> LvResult<()> - where - W: Widget, - { - unsafe { - lvgl_sys::lv_event_send(obj.raw()?.as_mut(), event.into(), ptr::null_mut()); - } - Ok(()) - } - - pub fn tick_inc(&mut self, tick_period: Duration) { - unsafe { - lvgl_sys::lv_tick_inc(tick_period.as_millis() as u32); - } - } - - pub fn task_handler(&mut self) { - unsafe { - lvgl_sys::lv_task_handler(); - } - } -} - -pub(crate) struct DisplayUserData -where - T: DrawTarget, - C: PixelColor + From, -{ - display: T, - phantom: PhantomData, -} - -unsafe extern "C" fn display_callback_wrapper( - 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 - T: DrawTarget, - C: PixelColor + From, -{ - // In the `std` world we would make sure to capture panics here and make them not escape across - // the FFI boundary. Since this library is focused on embedded platforms, we don't - // have an standard unwinding mechanism to rely upon. - let display_driver = *disp_drv; - // Rust code closure reference - if !display_driver.user_data.is_null() { - let user_data = &mut *(display_driver.user_data as *mut DisplayUserData); - let x1 = (*area).x1; - let x2 = (*area).x2; - let y1 = (*area).y1; - let y2 = (*area).y2; - // TODO: Can we do anything when there is a error while flushing? - let _ = display_flush(&mut user_data.display, (x1, x2), (y1, y2), color_p); - } - // Indicate to LVGL that we are ready with the flushing - lvgl_sys::lv_disp_flush_ready(disp_drv); -} - -// We separate this display flush function to reduce the amount of unsafe code we need to write. -// This also provides a good separation of concerns, what is necessary from LVGL to work and -// what is the lvgl-rs wrapper responsibility. -fn display_flush( - display: &mut T, - (x1, x2): (i16, i16), - (y1, y2): (i16, i16), - color_p: *mut lvgl_sys::lv_color_t, -) -> Result<(), T::Error> -where - T: DrawTarget, - C: PixelColor + From, -{ - 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(); - - Ok(display.draw_iter(pixels)?) -}