Allow users to define a display refresh callback closure

This commit is contained in:
Rafael Caricio 2021-06-06 11:59:06 +02:00
parent bc7c06cc8c
commit 085495ebc4
Signed by: rafaelcaricio
GPG key ID: 3C86DBCE8E93C947
4 changed files with 129 additions and 111 deletions

View file

@ -1,4 +1,5 @@
use cstr_core::CString; use cstr_core::CString;
use embedded_graphics::drawable;
use embedded_graphics::pixelcolor::Rgb565; use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::*; use embedded_graphics::prelude::*;
use embedded_graphics_simulator::{ use embedded_graphics_simulator::{
@ -33,11 +34,44 @@ fn main() -> Result<(), LvError> {
// display. The buffer size can be set freely but 1/10 screen size is a good starting point. // display. The buffer size can be set freely but 1/10 screen size is a good starting point.
const REFRESH_BUFFER_SIZE: usize = lvgl::DISP_HOR_RES * lvgl::DISP_VER_RES / 10; const REFRESH_BUFFER_SIZE: usize = lvgl::DISP_HOR_RES * lvgl::DISP_VER_RES / 10;
static DRAW_BUFFER: DrawBuffer<REFRESH_BUFFER_SIZE> = DrawBuffer::new(); static DRAW_BUFFER: DrawBuffer<REFRESH_BUFFER_SIZE> = DrawBuffer::new();
//
// const NUMBER_OF_DISPLAYS: usize = 1;
// static DISPLAY_REGISTRY: DisplayRegistry<NUMBER_OF_DISPLAYS> = DisplayRegistry::empty();
// // 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, // 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 // 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()`. // updates. On your embedded device code, you will use `Display::register()`.
let display = Display::register_shared(&DRAW_BUFFER, &shared_native_display)?; 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<DisplayRefresh> for T where T: IntoIterator<Item = drawable::Pixel<C>>`
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);
})?;
// Create screen and widgets // Create screen and widgets
let mut screen = display.get_scr_act()?; let mut screen = display.get_scr_act()?;

View file

@ -39,29 +39,27 @@ impl Display {
Self { disp } Self { disp }
} }
pub fn register<T, C, const N: usize>( // pub fn register<T, C, const N: usize>(
draw_buffer: &'static DrawBuffer<N>, // draw_buffer: &'static DrawBuffer<N>,
native_display: T, // native_display: T,
) -> Result<Self> // ) -> Result<Self>
where // where
T: DrawTarget<C>, // T: DrawTarget<C>,
C: PixelColor + From<Color>, // C: PixelColor + From<Color>,
{ // {
let mut display_diver = DisplayDriver::new(draw_buffer, native_display)?; // let mut display_diver = DisplayDriver::new(draw_buffer, native_display)?;
Ok(disp_drv_register(&mut display_diver)?) // Ok(disp_drv_register(&mut display_diver)?)
} // }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub fn register_shared<T, C, const N: usize>( pub fn register_shared<F, const N: usize>(
draw_buffer: &'static DrawBuffer<N>, draw_buffer: &'static DrawBuffer<N>,
shared_native_display: &SharedNativeDisplay<T>, display_update: F,
) -> Result<Self> ) -> Result<Self>
where where
T: DrawTarget<C>, F: FnMut(&DisplayRefresh<N>) + 'static,
C: PixelColor + From<Color>,
{ {
let mut display_diver = let mut display_diver = DisplayDriver::new_shared(draw_buffer, display_update)?;
DisplayDriver::new_shared(draw_buffer, Arc::clone(shared_native_display))?;
Ok(disp_drv_register(&mut display_diver)?) Ok(disp_drv_register(&mut display_diver)?)
} }
@ -122,61 +120,54 @@ impl<const N: usize> DrawBuffer<N> {
} }
} }
pub struct DisplayDriver<T, C> pub struct DisplayDriver {
where
T: DrawTarget<C>,
C: PixelColor + From<Color>,
{
pub(crate) disp_drv: lvgl_sys::lv_disp_drv_t, pub(crate) disp_drv: lvgl_sys::lv_disp_drv_t,
phantom_display: PhantomData<T>,
phantom_color: PhantomData<C>,
} }
impl<T, C> DisplayDriver<T, C> impl DisplayDriver {
where // pub fn new<const N: usize>(
T: DrawTarget<C>, // draw_buffer: &'static DrawBuffer<N>,
C: PixelColor + From<Color>, // native_display: T,
{ // ) -> Result<Self> {
pub fn new<const N: usize>( // let mut disp_drv = unsafe {
draw_buffer: &'static DrawBuffer<N>, // let mut inner = MaybeUninit::uninit();
native_display: T, // lvgl_sys::lv_disp_drv_init(inner.as_mut_ptr());
) -> Result<Self> { // inner.assume_init()
let mut disp_drv = unsafe { // };
let mut inner = MaybeUninit::uninit(); //
lvgl_sys::lv_disp_drv_init(inner.as_mut_ptr()); // // Safety: The variable `draw_buffer` is statically allocated, no need to worry about this being dropped.
inner.assume_init() // disp_drv.buffer = draw_buffer
}; // .get_ptr()
// .map(|ptr| Box::into_raw(ptr) as *mut _)
// Safety: The variable `draw_buffer` is statically allocated, no need to worry about this being dropped. // .ok_or(DisplayError::FailedToRegister)?;
disp_drv.buffer = draw_buffer //
.get_ptr() // let native_display = DisplayUserData {
.map(|ptr| Box::into_raw(ptr) as *mut _) // display: native_display,
.ok_or(DisplayError::FailedToRegister)?; // phantom: PhantomData,
// };
let native_display = DisplayUserData { // disp_drv.user_data =
display: native_display, // Box::into_raw(Box::new(native_display)) as *mut _ as lvgl_sys::lv_disp_drv_user_data_t;
phantom: PhantomData, //
}; // // Sets trampoline pointer to the function implementation using the types (T, C) that
disp_drv.user_data = // // are used in this instance of `DisplayDriver`.
Box::into_raw(Box::new(native_display)) as *mut _ as lvgl_sys::lv_disp_drv_user_data_t; // disp_drv.flush_cb = Some(disp_flush_trampoline::<T, C>);
//
// Sets trampoline pointer to the function implementation using the types (T, C) that // // We do not store any memory that can be accidentally deallocated by on the Rust side.
// are used in this instance of `DisplayDriver`. // Ok(Self {
disp_drv.flush_cb = Some(disp_flush_trampoline::<T, C>); // disp_drv,
// phantom_color: PhantomData,
// We do not store any memory that can be accidentally deallocated by on the Rust side. // phantom_display: PhantomData,
Ok(Self { // })
disp_drv, // }
phantom_color: PhantomData,
phantom_display: PhantomData,
})
}
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub fn new_shared<const N: usize>( pub fn new_shared<F, const N: usize>(
draw_buffer: &'static DrawBuffer<N>, draw_buffer: &'static DrawBuffer<N>,
shared_native_display: SharedNativeDisplay<T>, display_update: F,
) -> Result<Self> { ) -> Result<Self>
where
F: FnMut(&DisplayRefresh<N>) + 'static,
{
let mut disp_drv = unsafe { let mut disp_drv = unsafe {
let mut inner = MaybeUninit::uninit(); let mut inner = MaybeUninit::uninit();
lvgl_sys::lv_disp_drv_init(inner.as_mut_ptr()); lvgl_sys::lv_disp_drv_init(inner.as_mut_ptr());
@ -189,23 +180,15 @@ where
.map(|ptr| Box::into_raw(ptr) as *mut _) .map(|ptr| Box::into_raw(ptr) as *mut _)
.ok_or(DisplayError::FailedToRegister)?; .ok_or(DisplayError::FailedToRegister)?;
let native_display = SharedDisplayUserData {
display: shared_native_display,
phantom: PhantomData,
};
disp_drv.user_data = disp_drv.user_data =
Box::into_raw(Box::new(native_display)) as *mut _ as lvgl_sys::lv_disp_drv_user_data_t; Box::into_raw(Box::new(display_update)) as *mut _ as lvgl_sys::lv_disp_drv_user_data_t;
// Sets trampoline pointer to the function implementation using the types (T, C) that // Sets trampoline pointer to the function implementation using the types (T, C) that
// are used in this instance of `DisplayDriver`. // are used in this instance of `DisplayDriver`.
disp_drv.flush_cb = Some(shared_disp_flush_trampoline::<T, C>); disp_drv.flush_cb = Some(shared_disp_flush_trampoline::<F, N>);
// We do not store any memory that can be accidentally deallocated by on the Rust side. // We do not store any memory that can be accidentally deallocated by on the Rust side.
Ok(Self { Ok(Self { disp_drv })
disp_drv,
phantom_color: PhantomData,
phantom_display: PhantomData,
})
} }
} }
@ -269,45 +252,48 @@ where
phantom: PhantomData<C>, phantom: PhantomData<C>,
} }
pub struct Area {
pub x1: i16,
pub x2: i16,
pub y1: i16,
pub y2: i16,
}
pub struct DisplayRefresh<const N: usize> {
pub area: Area,
pub colors: [Color; N],
}
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
unsafe extern "C" fn shared_disp_flush_trampoline<T, C>( unsafe extern "C" fn shared_disp_flush_trampoline<F, const N: usize>(
disp_drv: *mut lvgl_sys::lv_disp_drv_t, disp_drv: *mut lvgl_sys::lv_disp_drv_t,
area: *const lvgl_sys::lv_area_t, area: *const lvgl_sys::lv_area_t,
color_p: *mut lvgl_sys::lv_color_t, color_p: *mut lvgl_sys::lv_color_t,
) where ) where
T: DrawTarget<C>, F: FnMut(&DisplayRefresh<N>) + 'static,
C: PixelColor + From<Color>,
{ {
let display_driver = *disp_drv; let display_driver = *disp_drv;
if !display_driver.user_data.is_null() { if !display_driver.user_data.is_null() {
let user_data = &mut *(display_driver.user_data as *mut SharedDisplayUserData<T, C>); 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 mut colors = [Color::default(); N];
let xs = (x1..=x2).enumerate(); let mut color_len = 0;
let x_len = (x2 - x1 + 1) as usize; for color in &mut colors {
let lv_color = unsafe { *color_p.add(color_len) };
*color = Color::from_raw(lv_color);
color_len += 1;
}
// We use iterators here to ensure that the Rust compiler can apply all possible let update = DisplayRefresh {
// optimizations at compile time. area: Area {
let pixels = ys x1: (*area).x1,
.enumerate() x2: (*area).x2,
.map(|(iy, y)| { y1: (*area).y1,
xs.clone().map(move |(ix, x)| { y2: (*area).y2,
let color_len = x_len * iy + ix; },
let lv_color = unsafe { *color_p.add(color_len) }; colors,
let raw_color = Color::from_raw(lv_color); };
drawable::Pixel::<C>(Point::new(x as i32, y as i32), raw_color.into()) callback(&update);
})
})
.flatten();
let _ = user_data
.display
.try_lock()
.map(move |mut display| display.draw_iter(pixels));
} }
// Indicate to LVGL that we are ready with the flushing // Indicate to LVGL that we are ready with the flushing

View file

@ -14,9 +14,7 @@ pub enum CoreError {
type Result<T> = result::Result<T, CoreError>; type Result<T> = result::Result<T, CoreError>;
/// Register own buffer /// Register own buffer
pub(crate) fn disp_drv_register<C: PixelColor + From<Color>, T: DrawTarget<C>>( pub(crate) fn disp_drv_register(disp_drv: &mut DisplayDriver) -> Result<Display> {
disp_drv: &mut DisplayDriver<T, C>,
) -> Result<Display> {
let disp_ptr = unsafe { lvgl_sys::lv_disp_drv_register(&mut disp_drv.disp_drv as *mut _) }; let disp_ptr = unsafe { lvgl_sys::lv_disp_drv_register(&mut disp_drv.disp_drv as *mut _) };
Ok(Display::from_raw( Ok(Display::from_raw(
NonNull::new(disp_ptr).ok_or(CoreError::OperationFailed)?, NonNull::new(disp_ptr).ok_or(CoreError::OperationFailed)?,

View file

@ -25,7 +25,7 @@ impl From<DisplayError> for LvError {
} }
} }
#[derive(Clone)] #[derive(Copy, Clone, Default)]
pub struct Color { pub struct Color {
pub(crate) raw: lvgl_sys::lv_color_t, pub(crate) raw: lvgl_sys::lv_color_t,
} }