Make use of embedded_graphics optional
This commit is contained in:
parent
085495ebc4
commit
ac27d49c06
8 changed files with 72 additions and 384 deletions
|
@ -40,37 +40,14 @@ fn main() -> Result<(), LvError> {
|
||||||
// // static DISPLAY_REGISTRY: SingleDisplayRegistry = DisplayRegistry::empty();
|
// // static DISPLAY_REGISTRY: SingleDisplayRegistry = DisplayRegistry::empty();
|
||||||
// let display = DISPLAY_REGISTRY.register_shared(&DRAW_BUFFER, shared_native_display.clone())?;
|
// 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 display update callback with LVGL. The closure you pass here will be called
|
||||||
// but that's because the Simulator needs a mutable reference to the display so it can draw
|
// whenever LVGL has updates to be painted to the display.
|
||||||
// updates. On your embedded device code, you will use `Display::register()`.
|
let display = Display::register(&DRAW_BUFFER, {
|
||||||
let shared_disp_inner = shared_native_display.clone();
|
let shared_disp_inner = SyncArc::clone(&shared_native_display);
|
||||||
let display = Display::register_shared(&DRAW_BUFFER, move |update| {
|
move |update| {
|
||||||
// make this a `.into_pixels()` method in DisplayRefresh or `From<DisplayRefresh> for T where T: IntoIterator<Item = drawable::Pixel<C>>`
|
let mut em_disp = shared_disp_inner.lock();
|
||||||
let area = &update.area;
|
em_disp.draw_iter(update.as_pixels());
|
||||||
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
|
||||||
|
|
|
@ -14,7 +14,7 @@ build = "build.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lvgl-sys = { version = "0.5.2", path = "../lvgl-sys" }
|
lvgl-sys = { version = "0.5.2", path = "../lvgl-sys" }
|
||||||
cty = "0.2.1"
|
cty = "0.2.1"
|
||||||
embedded-graphics = "0.6.2"
|
embedded-graphics = { version = "0.6.2", optional = true }
|
||||||
cstr_core = "0.2.3"
|
cstr_core = "0.2.3"
|
||||||
bitflags = "1.2.1"
|
bitflags = "1.2.1"
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
|
@ -22,6 +22,7 @@ heapless = "0.7.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
embedded_graphics = ["embedded-graphics"]
|
||||||
alloc = ["cstr_core/alloc"]
|
alloc = ["cstr_core/alloc"]
|
||||||
lvgl_alloc = ["alloc"]
|
lvgl_alloc = ["alloc"]
|
||||||
|
|
||||||
|
@ -37,29 +38,29 @@ embedded-graphics-simulator = "0.2.1"
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "app"
|
name = "app"
|
||||||
path = "../examples/app.rs"
|
path = "../examples/app.rs"
|
||||||
required-features = ["alloc"]
|
required-features = ["alloc", "embedded_graphics"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "demo"
|
name = "demo"
|
||||||
path = "../examples/demo.rs"
|
path = "../examples/demo.rs"
|
||||||
required-features = ["alloc"]
|
required-features = ["alloc", "embedded_graphics"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "bar"
|
name = "bar"
|
||||||
path = "../examples/bar.rs"
|
path = "../examples/bar.rs"
|
||||||
required-features = ["alloc"]
|
required-features = ["alloc", "embedded_graphics"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "button_click"
|
name = "button_click"
|
||||||
path = "../examples/button_click.rs"
|
path = "../examples/button_click.rs"
|
||||||
required-features = ["alloc"]
|
required-features = ["alloc", "embedded_graphics"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "gauge"
|
name = "gauge"
|
||||||
path = "../examples/gauge.rs"
|
path = "../examples/gauge.rs"
|
||||||
required-features = ["alloc"]
|
required-features = ["alloc", "embedded_graphics"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "arc"
|
name = "arc"
|
||||||
path = "../examples/arc.rs"
|
path = "../examples/arc.rs"
|
||||||
required-features = ["alloc"]
|
required-features = ["alloc", "embedded_graphics"]
|
||||||
|
|
|
@ -3,18 +3,12 @@ use crate::{disp_drv_register, disp_get_default, get_str_act};
|
||||||
use crate::{Box, RunOnce};
|
use crate::{Box, RunOnce};
|
||||||
use crate::{Color, Obj};
|
use crate::{Color, Obj};
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use core::marker::PhantomData;
|
|
||||||
use core::mem::MaybeUninit;
|
use core::mem::MaybeUninit;
|
||||||
use core::ptr::NonNull;
|
use core::ptr::NonNull;
|
||||||
use core::{ptr, result};
|
use core::{ptr, result};
|
||||||
use embedded_graphics::drawable;
|
|
||||||
use embedded_graphics::prelude::*;
|
|
||||||
use parking_lot::const_mutex;
|
use parking_lot::const_mutex;
|
||||||
use parking_lot::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_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;
|
pub const DISP_VER_RES: usize = lvgl_sys::LV_VER_RES_MAX as usize;
|
||||||
|
|
||||||
|
@ -27,9 +21,6 @@ pub enum DisplayError {
|
||||||
|
|
||||||
type Result<T> = result::Result<T, DisplayError>;
|
type Result<T> = result::Result<T, DisplayError>;
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub type SharedNativeDisplay<T> = Arc<Mutex<T>>;
|
|
||||||
|
|
||||||
pub struct Display {
|
pub struct Display {
|
||||||
pub(crate) disp: NonNull<lvgl_sys::lv_disp_t>,
|
pub(crate) disp: NonNull<lvgl_sys::lv_disp_t>,
|
||||||
}
|
}
|
||||||
|
@ -39,27 +30,14 @@ impl Display {
|
||||||
Self { disp }
|
Self { disp }
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn register<T, C, const N: usize>(
|
pub fn register<F, const N: usize>(
|
||||||
// draw_buffer: &'static DrawBuffer<N>,
|
|
||||||
// native_display: T,
|
|
||||||
// ) -> Result<Self>
|
|
||||||
// where
|
|
||||||
// T: DrawTarget<C>,
|
|
||||||
// C: PixelColor + From<Color>,
|
|
||||||
// {
|
|
||||||
// let mut display_diver = DisplayDriver::new(draw_buffer, native_display)?;
|
|
||||||
// Ok(disp_drv_register(&mut display_diver)?)
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
pub fn register_shared<F, const N: usize>(
|
|
||||||
draw_buffer: &'static DrawBuffer<N>,
|
draw_buffer: &'static DrawBuffer<N>,
|
||||||
display_update: F,
|
display_update: F,
|
||||||
) -> Result<Self>
|
) -> Result<Self>
|
||||||
where
|
where
|
||||||
F: FnMut(&DisplayRefresh<N>) + 'static,
|
F: FnMut(&DisplayRefresh<N>) + '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)?)
|
Ok(disp_drv_register(&mut display_diver)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,45 +103,9 @@ pub struct DisplayDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplayDriver {
|
impl DisplayDriver {
|
||||||
// pub fn new<const N: usize>(
|
pub fn new<F, const N: usize>(
|
||||||
// draw_buffer: &'static DrawBuffer<N>,
|
|
||||||
// native_display: T,
|
|
||||||
// ) -> Result<Self> {
|
|
||||||
// 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::<T, C>);
|
|
||||||
//
|
|
||||||
// // 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<F, const N: usize>(
|
|
||||||
draw_buffer: &'static DrawBuffer<N>,
|
draw_buffer: &'static DrawBuffer<N>,
|
||||||
display_update: F,
|
display_update_callback: F,
|
||||||
) -> Result<Self>
|
) -> Result<Self>
|
||||||
where
|
where
|
||||||
F: FnMut(&DisplayRefresh<N>) + 'static,
|
F: FnMut(&DisplayRefresh<N>) + 'static,
|
||||||
|
@ -180,78 +122,19 @@ impl DisplayDriver {
|
||||||
.map(|ptr| Box::into_raw(ptr) as *mut _)
|
.map(|ptr| Box::into_raw(ptr) as *mut _)
|
||||||
.ok_or(DisplayError::FailedToRegister)?;
|
.ok_or(DisplayError::FailedToRegister)?;
|
||||||
|
|
||||||
disp_drv.user_data =
|
disp_drv.user_data = Box::into_raw(Box::new(display_update_callback)) as *mut _
|
||||||
Box::into_raw(Box::new(display_update)) as *mut _ as lvgl_sys::lv_disp_drv_user_data_t;
|
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 that uses the `F` type for a
|
||||||
// are used in this instance of `DisplayDriver`.
|
// refresh buffer of size N specifically.
|
||||||
disp_drv.flush_cb = Some(shared_disp_flush_trampoline::<F, N>);
|
disp_drv.flush_cb = Some(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 { disp_drv })
|
Ok(Self { disp_drv })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct DisplayUserData<T, C>
|
/// Represents a sub-area of the display that is being updated.
|
||||||
where
|
|
||||||
T: DrawTarget<C>,
|
|
||||||
C: PixelColor + From<Color>,
|
|
||||||
{
|
|
||||||
display: T,
|
|
||||||
phantom: PhantomData<C>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe extern "C" fn disp_flush_trampoline<T, C>(
|
|
||||||
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>,
|
|
||||||
C: PixelColor + From<Color>,
|
|
||||||
{
|
|
||||||
let display_driver = *disp_drv;
|
|
||||||
if !display_driver.user_data.is_null() {
|
|
||||||
let user_data = &mut *(display_driver.user_data as *mut DisplayUserData<T, C>);
|
|
||||||
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::<C>(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<T, C>
|
|
||||||
where
|
|
||||||
T: DrawTarget<C>,
|
|
||||||
C: PixelColor + From<Color>,
|
|
||||||
{
|
|
||||||
display: SharedNativeDisplay<T>,
|
|
||||||
phantom: PhantomData<C>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Area {
|
pub struct Area {
|
||||||
pub x1: i16,
|
pub x1: i16,
|
||||||
pub x2: i16,
|
pub x2: i16,
|
||||||
|
@ -259,13 +142,50 @@ pub struct Area {
|
||||||
pub y2: i16,
|
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<const N: usize> {
|
pub struct DisplayRefresh<const N: usize> {
|
||||||
pub area: Area,
|
pub area: Area,
|
||||||
pub colors: [Color; N],
|
pub colors: [Color; N],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "embedded_graphics")]
|
||||||
unsafe extern "C" fn shared_disp_flush_trampoline<F, const N: usize>(
|
mod embedded_graphics_impl {
|
||||||
|
use crate::{Color, DisplayRefresh};
|
||||||
|
use embedded_graphics::drawable;
|
||||||
|
use embedded_graphics::prelude::*;
|
||||||
|
|
||||||
|
impl<const N: usize> DisplayRefresh<N> {
|
||||||
|
pub fn as_pixels<C>(&self) -> impl IntoIterator<Item = drawable::Pixel<C>> + '_
|
||||||
|
where
|
||||||
|
C: PixelColor + From<Color>,
|
||||||
|
{
|
||||||
|
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<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,
|
||||||
|
@ -279,7 +199,7 @@ unsafe extern "C" fn shared_disp_flush_trampoline<F, const N: usize>(
|
||||||
let mut colors = [Color::default(); N];
|
let mut colors = [Color::default(); N];
|
||||||
let mut color_len = 0;
|
let mut color_len = 0;
|
||||||
for color in &mut colors {
|
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 = Color::from_raw(lv_color);
|
||||||
color_len += 1;
|
color_len += 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
use crate::display::{Display, DisplayDriver};
|
use crate::display::{Display, DisplayDriver};
|
||||||
use crate::{Color, Obj, Widget};
|
use crate::{Obj, Widget};
|
||||||
use core::ptr::NonNull;
|
use core::ptr::NonNull;
|
||||||
use core::time::Duration;
|
use core::time::Duration;
|
||||||
use core::{ptr, result};
|
use core::{ptr, result};
|
||||||
use embedded_graphics::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum CoreError {
|
pub enum CoreError {
|
||||||
|
|
|
@ -48,13 +48,11 @@ mod display;
|
||||||
pub use display::*;
|
pub use display::*;
|
||||||
mod functions;
|
mod functions;
|
||||||
mod support;
|
mod support;
|
||||||
mod ui;
|
|
||||||
pub mod widgets;
|
pub mod widgets;
|
||||||
use core::sync::atomic::{AtomicBool, Ordering};
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
pub use functions::*;
|
pub use functions::*;
|
||||||
pub use lv_core::*;
|
pub use lv_core::*;
|
||||||
pub use support::*;
|
pub use support::*;
|
||||||
pub use ui::*;
|
|
||||||
|
|
||||||
struct RunOnce(AtomicBool);
|
struct RunOnce(AtomicBool);
|
||||||
|
|
||||||
|
@ -84,20 +82,16 @@ pub fn init() {
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::display::{Display, DrawBuffer};
|
use crate::display::{Display, DrawBuffer};
|
||||||
use embedded_graphics::mock_display::MockDisplay;
|
|
||||||
use embedded_graphics::pixelcolor::Rgb565;
|
|
||||||
|
|
||||||
pub(crate) fn initialize_test() {
|
pub(crate) fn initialize_test() {
|
||||||
init();
|
init();
|
||||||
|
|
||||||
let embedded_graphics_display: MockDisplay<Rgb565> = Default::default();
|
|
||||||
|
|
||||||
const REFRESH_BUFFER_SIZE: usize = 64 * 64 / 10;
|
const REFRESH_BUFFER_SIZE: usize = 64 * 64 / 10;
|
||||||
static DRAW_BUFFER: DrawBuffer<REFRESH_BUFFER_SIZE> = DrawBuffer::new();
|
static DRAW_BUFFER: DrawBuffer<REFRESH_BUFFER_SIZE> = DrawBuffer::new();
|
||||||
static ONCE_INIT: RunOnce = RunOnce::new();
|
static ONCE_INIT: RunOnce = RunOnce::new();
|
||||||
|
|
||||||
if ONCE_INIT.swap_and_check() {
|
if ONCE_INIT.swap_and_check() {
|
||||||
let _ = Display::register(&DRAW_BUFFER, embedded_graphics_display).unwrap();
|
let _ = Display::register(&DRAW_BUFFER, |_| {}).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::lv_core::style::Style;
|
use crate::lv_core::style::Style;
|
||||||
use crate::Box;
|
|
||||||
use crate::{Align, LvError, LvResult};
|
use crate::{Align, LvError, LvResult};
|
||||||
use core::ptr;
|
use core::ptr;
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@ use crate::display::DisplayError;
|
||||||
use crate::Widget;
|
use crate::Widget;
|
||||||
use core::convert::{TryFrom, TryInto};
|
use core::convert::{TryFrom, TryInto};
|
||||||
use core::ptr::NonNull;
|
use core::ptr::NonNull;
|
||||||
|
|
||||||
|
#[cfg(feature = "embedded_graphics")]
|
||||||
use embedded_graphics::pixelcolor::{Rgb565, Rgb888};
|
use embedded_graphics::pixelcolor::{Rgb565, Rgb888};
|
||||||
|
|
||||||
pub type LvResult<T> = Result<T, LvError>;
|
pub type LvResult<T> = Result<T, LvError>;
|
||||||
|
@ -53,6 +55,7 @@ impl Color {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "embedded_graphics")]
|
||||||
impl From<Color> for Rgb888 {
|
impl From<Color> for Rgb888 {
|
||||||
fn from(color: Color) -> Self {
|
fn from(color: Color) -> Self {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -65,6 +68,7 @@ impl From<Color> for Rgb888 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "embedded_graphics")]
|
||||||
impl From<Color> for Rgb565 {
|
impl From<Color> for Rgb565 {
|
||||||
fn from(color: Color) -> Self {
|
fn from(color: Color) -> Self {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
206
lvgl/src/ui.rs
206
lvgl/src/ui.rs
|
@ -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<T, C>
|
|
||||||
where
|
|
||||||
T: DrawTarget<C>,
|
|
||||||
C: PixelColor + From<Color>,
|
|
||||||
{
|
|
||||||
// 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<DisplayUserData<T, C>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// LVGL does not use thread locals.
|
|
||||||
unsafe impl<T, C> Send for UI<T, C>
|
|
||||||
where
|
|
||||||
T: DrawTarget<C>,
|
|
||||||
C: PixelColor + From<Color>,
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, C> UI<T, C>
|
|
||||||
where
|
|
||||||
T: DrawTarget<C>,
|
|
||||||
C: PixelColor + From<Color>,
|
|
||||||
{
|
|
||||||
pub fn init() -> LvResult<Self> {
|
|
||||||
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::<lvgl_sys::lv_disp_buf_t>::uninit();
|
|
||||||
let mut disp_drv = MaybeUninit::<lvgl_sys::lv_disp_drv_t>::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::<T, C>);
|
|
||||||
// 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<Obj> {
|
|
||||||
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<W>(&self, obj: &mut W, event: Event<W::SpecialEvent>) -> 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<T, C>
|
|
||||||
where
|
|
||||||
T: DrawTarget<C>,
|
|
||||||
C: PixelColor + From<Color>,
|
|
||||||
{
|
|
||||||
display: T,
|
|
||||||
phantom: PhantomData<C>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe extern "C" fn display_callback_wrapper<T, C>(
|
|
||||||
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>,
|
|
||||||
C: PixelColor + From<Color>,
|
|
||||||
{
|
|
||||||
// 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<T, C>);
|
|
||||||
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<T, C>(
|
|
||||||
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>,
|
|
||||||
C: PixelColor + From<Color>,
|
|
||||||
{
|
|
||||||
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)?)
|
|
||||||
}
|
|
Loading…
Reference in a new issue