From 8b2879f93bc89b9f95ef6cbed6406743cbfdb482 Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Tue, 16 Jun 2020 23:19:26 +0200 Subject: [PATCH] Remove alloc dependency --- examples/bar.rs | 27 +++-- examples/button_click.rs | 31 +++--- examples/demo.rs | 55 ++++++++-- lvgl-codegen/src/lib.rs | 23 ++-- lvgl/src/display.rs | 136 ----------------------- lvgl/src/global.rs | 72 ------------- lvgl/src/lib.rs | 10 +- lvgl/src/lv_core/obj.rs | 8 +- lvgl/src/lv_core/style.rs | 11 +- lvgl/src/mem.rs | 100 +++++++++++++++++ lvgl/src/support.rs | 18 +--- lvgl/src/ui.rs | 222 ++++++++++++++++++++++++++++++++++++++ lvgl/src/widgets/mod.rs | 2 +- 13 files changed, 429 insertions(+), 286 deletions(-) delete mode 100644 lvgl/src/display.rs delete mode 100644 lvgl/src/global.rs create mode 100644 lvgl/src/mem.rs create mode 100644 lvgl/src/ui.rs diff --git a/examples/bar.rs b/examples/bar.rs index d85542f..3fcf529 100644 --- a/examples/bar.rs +++ b/examples/bar.rs @@ -1,18 +1,19 @@ +use cstr_core::CString; use embedded_graphics::pixelcolor::Rgb565; use embedded_graphics::prelude::*; use embedded_graphics_simulator::{ OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, }; use lvgl::style::Style; -use lvgl::widgets::{Bar, BarPart, Label, LabelAlign}; -use lvgl::{self, Align, Animation, Color, DisplayDriver, Event, LvError, Part, State, Widget, UI}; +use lvgl::widgets::{Bar, Label, LabelAlign}; +use lvgl::{self, Align, Animation, Color, Event, LvError, Part, State, Widget, UI}; use lvgl_sys; use std::sync::{mpsc, Arc, Mutex}; use std::thread::sleep; use std::time::Duration; fn main() -> Result<(), LvError> { - let mut display: SimulatorDisplay = SimulatorDisplay::new(Size::new( + let display: SimulatorDisplay = SimulatorDisplay::new(Size::new( lvgl_sys::LV_HOR_RES_MAX, lvgl_sys::LV_VER_RES_MAX, )); @@ -23,8 +24,7 @@ fn main() -> Result<(), LvError> { let mut ui = UI::init()?; // Implement and register your display: - let display_driver = DisplayDriver::new(&mut display); - ui.disp_drv_register(display_driver); + ui.disp_drv_register(display).unwrap(); // Create screen and widgets let mut screen = ui.scr_act()?; @@ -39,6 +39,9 @@ fn main() -> Result<(), LvError> { bar.set_size(175, 20)?; bar.set_align(&mut screen, Align::Center, 0, 10)?; bar.set_range(0, 100)?; + bar.on_event(|_b, _e| { + println!("received"); + })?; // // Set the indicator style for the bar object let mut ind_style = Style::default(); @@ -46,7 +49,7 @@ fn main() -> Result<(), LvError> { bar.add_style(Part::All, ind_style)?; let mut loading_lbl = Label::new(&mut screen)?; - loading_lbl.set_text("Loading...")?; + loading_lbl.set_text(CString::new("Loading...").unwrap().as_c_str())?; loading_lbl.set_align(&mut bar, Align::OutTopMid, 0, -10)?; loading_lbl.set_label_align(LabelAlign::Center)?; @@ -75,16 +78,18 @@ fn main() -> Result<(), LvError> { threaded_ui .lock() .unwrap() - .event_send(&mut loading_lbl, Event::Clicked)? + .event_send(&mut bar, Event::Clicked)? } bar.set_value(i, Animation::OFF)?; i += 1; - sleep(Duration::from_millis(25)); + sleep(Duration::from_millis(50)); - threaded_ui.lock().unwrap().task_handler(); - - window.update(&display); + let mut ui = threaded_ui.lock().unwrap(); + ui.task_handler(); + if let Some(disp) = ui.get_display_ref() { + window.update(disp); + } for event in window.events() { match event { diff --git a/examples/button_click.rs b/examples/button_click.rs index 420ab84..db1b22c 100644 --- a/examples/button_click.rs +++ b/examples/button_click.rs @@ -1,3 +1,4 @@ +use cstr_core::CString; use embedded_graphics::pixelcolor::Rgb565; use embedded_graphics::prelude::*; use embedded_graphics_simulator::{ @@ -5,14 +6,14 @@ use embedded_graphics_simulator::{ }; use lvgl::style::Style; use lvgl::widgets::{Btn, Label}; -use lvgl::{self, Align, Color, DisplayDriver, Event, LvError, Part, State, Widget, UI}; +use lvgl::{self, Align, Color, Event, LvError, Part, State, Widget, UI}; use lvgl_sys; use std::sync::{mpsc, Arc, Mutex}; use std::thread::sleep; use std::time::Duration; fn main() -> Result<(), LvError> { - let mut display: SimulatorDisplay = SimulatorDisplay::new(Size::new( + let display: SimulatorDisplay = SimulatorDisplay::new(Size::new( lvgl_sys::LV_HOR_RES_MAX, lvgl_sys::LV_VER_RES_MAX, )); @@ -23,8 +24,7 @@ fn main() -> Result<(), LvError> { let mut ui = UI::init()?; // Implement and register your display: - let display_driver = DisplayDriver::new(&mut display); - ui.disp_drv_register(display_driver); + ui.disp_drv_register(display)?; // Create screen and widgets let mut screen = ui.scr_act()?; @@ -38,15 +38,19 @@ fn main() -> Result<(), LvError> { button.set_align(&mut screen, Align::InLeftMid, 30, 0)?; button.set_size(180, 80)?; let mut btn_lbl = Label::new(&mut button)?; - btn_lbl.set_text("Click me!")?; + btn_lbl.set_text(CString::new("Click me!").unwrap().as_c_str())?; let mut btn_state = false; button.on_event(|mut btn, event| { if let lvgl::Event::Clicked = event { if btn_state { - btn_lbl.set_text("Click me!").unwrap(); + btn_lbl + .set_text(CString::new("Click me!").unwrap().as_c_str()) + .unwrap(); } else { - btn_lbl.set_text("Clicked!").unwrap(); + btn_lbl + .set_text(CString::new("Clicked!").unwrap().as_c_str()) + .unwrap(); } btn_state = !btn_state; println!("Clicked!"); @@ -71,9 +75,11 @@ fn main() -> Result<(), LvError> { }); 'running: loop { - threaded_ui.lock().unwrap().task_handler(); - - window.update(&display); + let mut ui = threaded_ui.lock().unwrap(); + ui.task_handler(); + if let Some(disp) = ui.get_display_ref() { + window.update(disp); + } for event in window.events() { match event { SimulatorEvent::MouseButtonUp { @@ -82,10 +88,7 @@ fn main() -> Result<(), LvError> { } => { println!("Clicked on: {:?}", point); // Send a event to the button directly - threaded_ui - .lock() - .unwrap() - .event_send(&mut button, Event::Clicked)?; + ui.event_send(&mut button, Event::Clicked)?; } SimulatorEvent::Quit => break 'running, _ => {} diff --git a/examples/demo.rs b/examples/demo.rs index 2af638e..c432d6e 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -12,9 +12,46 @@ use lvgl_sys; use std::sync::{mpsc, Arc, Mutex}; use std::thread::sleep; use std::time::Duration; +// +// struct MyApp { +// time: Label, +// bt_label: Label, +// } +// +// impl MyApp { +// fn initialize(screen: &Obj) -> Result { +// let mut screen_style = Style::default(); +// screen_style.set_bg_color(State::DEFAULT, Color::from_rgb((0, 0, 0))); +// screen_style.set_radius(State::DEFAULT, 0); +// +// let mut style_time = Style::default(); +// style_time.set_text_color(State::DEFAULT, Color::from_rgb((255, 255, 255))); +// +// let time = screen.create_label()?; +// time.set_align(&screen, Align::Center, 0, 0)?; +// time.set_text(CString::new("20:46").unwrap().as_c_str())?; +// time.set_width(240)?; +// time.set_height(240)?; +// +// let bt = screen.create_label()?; +// bt.set_height(80)?; +// bt.set_recolor(true)?; +// bt.set_height(80)?; +// bt.set_recolor(true)?; +// bt.set_text(CString::new("#5794f2 \u{F293}#").unwrap().as_c_str())?; +// bt.set_label_align(LabelAlign::Left)?; +// bt.set_align(&screen, Align::InTopLeft, 0, 0)?; +// +// // attach styles +// screen.add_style(Part::Main, screen_style)?; +// time.add_style(Part::Main, style_time)?; +// +// Ok(MyApp { time, bt_label: bt }) +// } +// } fn main() -> Result<(), LvError> { - let mut display: SimulatorDisplay = SimulatorDisplay::new(Size::new( + let display: SimulatorDisplay = SimulatorDisplay::new(Size::new( lvgl_sys::LV_HOR_RES_MAX, lvgl_sys::LV_VER_RES_MAX, )); @@ -25,15 +62,11 @@ fn main() -> Result<(), LvError> { let mut ui = UI::init()?; // Implement and register your display: - let display_driver = lvgl::DisplayDriver::new(&mut display); - ui.disp_drv_register(display_driver); + ui.disp_drv_register(display).unwrap(); // Create screen and widgets let mut screen = ui.scr_act()?; - let font_roboto_28 = unsafe { &lvgl_sys::lv_theme_get_font_normal() }; - let font_noto_sans_numeric_28 = unsafe { ¬o_sans_numeric_80 }; - let mut screen_style = Style::default(); screen_style.set_bg_color(State::DEFAULT, Color::from_rgb((0, 0, 0))); screen_style.set_radius(State::DEFAULT, 0); @@ -68,7 +101,7 @@ fn main() -> Result<(), LvError> { let mut t: heapless::String = heapless::String::from("test"); t.push('\0').unwrap(); set_text(CStr::from_bytes_with_nul(t.as_bytes()).unwrap()).unwrap(); - set_text(CStr::from_bytes_with_nul("test\0".as_bytes()).unwrap()).unwrap(); + set_text(CStr::from_bytes_with_nul(("test\0").as_bytes()).unwrap()).unwrap(); set_text(cstr_core::CString::new("test").unwrap().as_c_str()).unwrap(); let mut power = Label::new(&mut screen)?; @@ -104,9 +137,11 @@ fn main() -> Result<(), LvError> { sleep(Duration::from_secs(1)); - threaded_ui.lock().unwrap().task_handler(); - - window.update(&display); + let mut ui = threaded_ui.lock().unwrap(); + ui.task_handler(); + if let Some(disp) = ui.get_display_ref() { + window.update(disp); + } for event in window.events() { match event { diff --git a/lvgl-codegen/src/lib.rs b/lvgl-codegen/src/lib.rs index d0778b1..2e51afd 100644 --- a/lvgl-codegen/src/lib.rs +++ b/lvgl-codegen/src/lib.rs @@ -50,7 +50,7 @@ impl Rusty for LvWidget { fn code(&self, _parent: &Self::Parent) -> WrapperResult { // We don't generate for the generic Obj - if self.name.eq("obj") { + if self.name.as_str().eq("obj") { return Err(WrapperError::Skip); } @@ -97,7 +97,7 @@ impl Rusty for LvFunc { let original_func_name = format_ident!("{}", self.name.as_str()); // generate constructor - if new_name.eq("create") { + if new_name.as_str().eq("create") { return Ok(quote! { pub fn new(parent: &mut C) -> crate::LvResult where @@ -105,9 +105,12 @@ impl Rusty for LvFunc { { unsafe { let ptr = lvgl_sys::#original_func_name(parent.raw()?.as_mut(), core::ptr::null_mut()); - let raw = core::ptr::NonNull::new(ptr)?; - let core = ::from_raw(raw); - Ok(Self { core }) + if let Some(raw) = core::ptr::NonNull::new(ptr) { + let core = ::from_raw(raw); + Ok(Self { core }) + } else { + Err(crate::LvError::InvalidReference) + } } } }); @@ -631,11 +634,15 @@ mod test { where C: crate::NativeObject, { + unsafe { let ptr = lvgl_sys::lv_arc_create(parent.raw()?.as_mut(), core::ptr::null_mut()); - let raw = core::ptr::NonNull::new(ptr)?; - let core = ::from_raw(raw); - Ok(Self { core }) + if let Some(raw) = core::ptr::NonNull::new(ptr) { + let core = ::from_raw(raw); + Ok(Self { core }) + } else { + Err(crate::LvError::InvalidReference) + } } } } diff --git a/lvgl/src/display.rs b/lvgl/src/display.rs deleted file mode 100644 index 5741002..0000000 --- a/lvgl/src/display.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::Color; -use alloc::boxed::Box; -use core::mem::MaybeUninit; -use embedded_graphics::prelude::*; -use embedded_graphics::{drawable, DrawTarget}; - -pub struct DisplayDriver { - pub(crate) raw: lvgl_sys::lv_disp_drv_t, -} - -impl DisplayDriver { - // we should accept a Rc> and throw it in a box and add to the user_data of the callback handler function - pub fn new(device: &mut T) -> Self - where - T: DrawTarget, - C: PixelColor + From, - { - let disp_drv = unsafe { - // Declare a buffer for the refresh rate - // TODO: Make this an external configuration - const REFRESH_BUFFER_LEN: usize = 2; - const BUF_SIZE: usize = lvgl_sys::LV_HOR_RES_MAX as usize * REFRESH_BUFFER_LEN; - - let refresh_buffer1: [lvgl_sys::lv_color_t; BUF_SIZE] = - [Color::from_rgb((0, 0, 0)).raw; BUF_SIZE]; - - let refresh_buffer2: [lvgl_sys::lv_color_t; BUF_SIZE] = - [Color::from_rgb((0, 0, 0)).raw; BUF_SIZE]; - - // Create a display buffer for LittlevGL - let mut display_buffer = MaybeUninit::::uninit(); - // Initialize the display buffer - lvgl_sys::lv_disp_buf_init( - display_buffer.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, - ); - let display_buffer = Box::new(display_buffer.assume_init()); - - // Basic initialization of the display driver - let mut disp_drv = MaybeUninit::::uninit(); - lvgl_sys::lv_disp_drv_init(disp_drv.as_mut_ptr()); - let mut disp_drv = disp_drv.assume_init(); - // Assign the buffer to the display - disp_drv.buffer = Box::into_raw(display_buffer); - // Set your driver function - disp_drv.flush_cb = Some(display_callback_wrapper::); - // TODO: DrawHandler type here - // Safety: `user_data` is set to NULL in C code. - disp_drv.user_data = device as *mut _ as *mut cty::c_void; - disp_drv - }; - Self { raw: disp_drv } - } -} - -// We need to keep a reference to the DisplayDriver in UI if we implement Drop -// impl Drop for DisplayDriver { -// fn drop(&mut self) { -// // grab the user data and deref the DrawHandler to free the instance for dealloc in the Rust universe. -// unimplemented!() -// } -// } - -// a reference is kept to the external drawing target (T) -// the reference is kept in the callback function of the drawing handler -// we need a reference counter for the drawing target and free the ref counter when the display is -// destroyed. -//type DrawHandler = Rc>; -// -// impl Drop for DrawHandler { -// fn drop(&mut self) { -// unimplemented!() -// } -// } - -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 device = &mut *(display_driver.user_data as *mut T); - 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(device, (x1, x2), (y1, y2), color_p); - } - // Indicate to LittlevGL 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 LittlevGL 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)?) -} diff --git a/lvgl/src/global.rs b/lvgl/src/global.rs deleted file mode 100644 index cc4fdd0..0000000 --- a/lvgl/src/global.rs +++ /dev/null @@ -1,72 +0,0 @@ -use crate::{DisplayDriver, Event, LvError, LvResult, Obj, Widget}; -use alloc::boxed::Box; -use core::marker::PhantomData; -use core::ptr; -use core::ptr::NonNull; -use core::sync::atomic::{AtomicBool, Ordering}; -use core::time::Duration; - -// There can only be a single reference to LittlevGL library. -static LVGL_IN_USE: AtomicBool = AtomicBool::new(false); - -pub struct UI { - // LittlevGL is not thread-safe by default. - _not_sync: PhantomData<*mut ()>, -} - -// LittlevGL does not use thread locals. -unsafe impl Send for UI {} - -impl UI { - pub fn init() -> LvResult { - if !LVGL_IN_USE.compare_and_swap(false, true, Ordering::SeqCst) { - unsafe { - lvgl_sys::lv_init(); - } - Ok(Self { - _not_sync: PhantomData, - }) - } else { - Err(LvError::AlreadyInUse) - } - } - - pub fn disp_drv_register(&mut self, display: DisplayDriver) { - // Throw display driver into a box and add to user data (if we need to get the display back) - // or simply forget the display pointer/object to prevent Drop to be called - // register it - unsafe { - let boxed = Box::new(display.raw); - lvgl_sys::lv_disp_drv_register(Box::into_raw(boxed)); - } - } - - pub fn scr_act(&self) -> LvResult { - unsafe { - let screen = lvgl_sys::lv_disp_get_scr_act(ptr::null_mut()); - Ok(Obj::from_raw(NonNull::new(screen)?)) - } - } - - pub fn event_send(&mut self, obj: &mut T, event: Event) -> LvResult<()> - where - T: 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(); - } - } -} diff --git a/lvgl/src/lib.rs b/lvgl/src/lib.rs index 1f7f6be..6ef5b17 100644 --- a/lvgl/src/lib.rs +++ b/lvgl/src/lib.rs @@ -1,19 +1,15 @@ -#![feature(try_trait)] #![no_std] -#[macro_use] -extern crate alloc; #[macro_use] extern crate bitflags; -mod display; -mod global; +pub mod mem; mod support; +mod ui; #[macro_use] mod lv_core; pub mod widgets; -pub use display::DisplayDriver; -pub use global::UI; pub use lv_core::*; pub use support::*; +pub use ui::*; diff --git a/lvgl/src/lv_core/obj.rs b/lvgl/src/lv_core/obj.rs index feffc11..1129c0a 100644 --- a/lvgl/src/lv_core/obj.rs +++ b/lvgl/src/lv_core/obj.rs @@ -1,6 +1,6 @@ use crate::lv_core::style::Style; +use crate::mem::Box; use crate::{Align, LvError, LvResult}; -use alloc::boxed::Box; use core::ptr; /// Represents a native LittlevGL object @@ -40,7 +40,7 @@ pub trait Widget: NativeObject { /// unsafe fn from_raw(raw_pointer: ptr::NonNull) -> Self; - fn add_style(&mut self, part: Self::Part, style: Style) -> LvResult<()> { + fn add_style(&self, part: Self::Part, style: Style) -> LvResult<()> { unsafe { lvgl_sys::lv_obj_add_style(self.raw()?.as_mut(), part.into(), Box::into_raw(style.raw)); }; @@ -146,8 +146,8 @@ macro_rules! define_object { unsafe { let mut raw = self.raw()?; let obj = raw.as_mut(); - let user_closure = alloc::boxed::Box::new(f); - obj.user_data = alloc::boxed::Box::into_raw(user_closure) as *mut cty::c_void; + let user_closure = $crate::mem::Box::new(f)?; + obj.user_data = $crate::mem::Box::into_raw(user_closure) as *mut cty::c_void; lvgl_sys::lv_obj_set_event_cb( obj, lvgl_sys::lv_event_cb_t::Some($crate::support::event_callback::), diff --git a/lvgl/src/lv_core/style.rs b/lvgl/src/lv_core/style.rs index c1979f0..ace0c46 100644 --- a/lvgl/src/lv_core/style.rs +++ b/lvgl/src/lv_core/style.rs @@ -1,7 +1,7 @@ +use crate::mem::Box; use crate::{Color, LvResult, State}; -use alloc::boxed::Box; use core::mem; -use cstr_core::CString; +use cstr_core::CStr; pub enum Themes { Pretty, @@ -12,15 +12,14 @@ pub struct Style { } impl Style { - pub fn set_value_str(&mut self, state: State, value: &str) -> LvResult<()> { + pub fn set_value_str(&mut self, state: State, value: &CStr) -> LvResult<()> { let native_state: u32 = state.get_bits(); - let string = CString::new(value)?; unsafe { lvgl_sys::_lv_style_set_ptr( self.raw.as_mut(), (lvgl_sys::LV_STYLE_VALUE_STR | (native_state << lvgl_sys::LV_STYLE_STATE_POS as u32)) as u16, - string.into_raw() as *mut cty::c_void, + value.as_ptr() as *mut cty::c_void, ); } Ok(()) @@ -32,7 +31,7 @@ impl Default for Style { let raw = unsafe { let mut style = mem::MaybeUninit::::uninit(); lvgl_sys::lv_style_init(style.as_mut_ptr()); - Box::new(style.assume_init()) + Box::new(style.assume_init()).unwrap() }; Self { raw } } diff --git a/lvgl/src/mem.rs b/lvgl/src/mem.rs new file mode 100644 index 0000000..7f1bae9 --- /dev/null +++ b/lvgl/src/mem.rs @@ -0,0 +1,100 @@ +use crate::{LvError, LvResult}; +use core::mem; +use core::ops::{Deref, DerefMut}; +use core::ptr; +use core::ptr::NonNull; + +/// Places `T` into LVGL memory. +pub struct Box(NonNull); + +impl Box { + pub fn new(inner: T) -> LvResult> { + let layout = mem::size_of::(); + let inner = unsafe { + let ptr = lvgl_sys::lv_mem_alloc(layout as lvgl_sys::size_t) as *mut T; + match NonNull::new(ptr) { + Some(v) => { + // Place value in new mem + ptr::write(ptr, inner); + Ok(v) + } + None => Err(LvError::LvOOMemory), + } + }; + Ok(Box(inner?)) + } + + pub fn into_raw(b: Box) -> *mut T { + let b = mem::ManuallyDrop::new(b); + b.0.as_ptr() + } +} + +impl Drop for Box { + fn drop(&mut self) { + unsafe { + lvgl_sys::lv_mem_free(self.0.as_ptr() as *const cty::c_void); + } + } +} + +impl Deref for Box { + type Target = T; + + fn deref(&self) -> &T { + unsafe { self.0.as_ref() } + } +} + +impl DerefMut for Box { + fn deref_mut(&mut self) -> &mut T { + unsafe { self.0.as_mut() } + } +} + +impl AsMut for Box { + fn as_mut(&mut self) -> &mut T { + unsafe { self.0.as_mut() } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn place_value_in_lv_mem() { + unsafe { + lvgl_sys::_lv_mem_init(); + }; + let v = Box::new(5).unwrap(); + drop(v); + let v = Box::new(10).unwrap(); + drop(v); + } + + #[test] + fn place_complex_value_in_lv_mem() { + unsafe { + lvgl_sys::_lv_mem_init(); + }; + + struct Point { + x: u64, + y: u64, + disp: i32, + } + + let p = Point { + x: 32, + y: 240, + disp: -100, + }; + + let b = Box::new(p).unwrap(); + + assert_eq!(b.x, 32); + assert_eq!(b.y, 240); + assert_eq!(b.disp, -100); + } +} diff --git a/lvgl/src/support.rs b/lvgl/src/support.rs index 0111652..e614de0 100644 --- a/lvgl/src/support.rs +++ b/lvgl/src/support.rs @@ -1,8 +1,6 @@ use crate::Widget; -use bitflags::_core::option::NoneError; use core::convert::{TryFrom, TryInto}; use core::ptr::NonNull; -use cstr_core::NulError; use embedded_graphics::pixelcolor::{Rgb565, Rgb888}; pub type LvResult = Result; @@ -11,24 +9,10 @@ pub type LvResult = Result; pub enum LvError { InvalidReference, Uninitialized, - InvalidNulByteString, - StringSizeTooShort, - StringCannotAppendNulByte, + LvOOMemory, AlreadyInUse, } -impl From for LvError { - fn from(_: NoneError) -> Self { - LvError::InvalidReference - } -} - -impl From for LvError { - fn from(_: NulError) -> Self { - LvError::InvalidNulByteString - } -} - #[derive(Clone)] pub struct Color { pub(crate) raw: lvgl_sys::lv_color_t, diff --git a/lvgl/src/ui.rs b/lvgl/src/ui.rs new file mode 100644 index 0000000..f05d049 --- /dev/null +++ b/lvgl/src/ui.rs @@ -0,0 +1,222 @@ +use crate::{Color, Event, LvError, LvResult, Obj, Widget}; +use core::marker::PhantomData; +use core::mem; +use core::mem::{ManuallyDrop, 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 LittlevGL 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 +const BUF_SIZE: usize = lvgl_sys::LV_HOR_RES_MAX as usize * REFRESH_BUFFER_LEN; + +type RefreshBuffer = [lvgl_sys::lv_color_t; BUF_SIZE]; + +pub struct UI +where + T: DrawTarget, + C: PixelColor + From, +{ + // LittlevGL 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>, +} + +// LittlevGL 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_and_swap(false, true, Ordering::SeqCst) { + unsafe { + lvgl_sys::lv_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, + }); + + unsafe { + // Create a display buffer for LittlevGL + // Never initialized in Rust side (don't call `assume_init`, this is C managed memory)! + let disp_buf = lvgl_sys::lv_mem_alloc( + mem::size_of::() as lvgl_sys::size_t + ) as *mut lvgl_sys::lv_disp_buf_t; + // Initialize the display buffer + let buffer_size = mem::size_of::(); + let buf1 = lvgl_sys::lv_mem_alloc(buffer_size as lvgl_sys::size_t); + if buf1.is_null() { + lvgl_sys::lv_mem_free(disp_buf as *mut cty::c_void); + return Err(LvError::LvOOMemory); + } + let buf2 = lvgl_sys::lv_mem_alloc(buffer_size as lvgl_sys::size_t); + if buf2.is_null() { + lvgl_sys::lv_mem_free(disp_buf as *mut cty::c_void); + lvgl_sys::lv_mem_free(buf1); + return Err(LvError::LvOOMemory); + } + + lvgl_sys::lv_disp_buf_init( + disp_buf, + buf1, + buf2, + lvgl_sys::LV_HOR_RES_MAX * REFRESH_BUFFER_LEN as u32, + ); + + // Basic initialization of the display driver + let mut disp_drv = MaybeUninit::::uninit(); + lvgl_sys::lv_disp_drv_init(disp_drv.as_mut_ptr()); + // Since this is C managed memory, we don't want to drop it using Rust, thus `ManuallyDrop` wrapping. + let mut disp_drv = ManuallyDrop::new(disp_drv.assume_init()); + // Assign the buffer to the display + disp_drv.buffer = disp_buf; + // Set your driver function + disp_drv.flush_cb = Some(display_callback_wrapper::); + // TODO: DrawHandler type here + // Safety: `user_data` is set to NULL in C code. + disp_drv.user_data = &mut self.display_data as *mut _ as *mut cty::c_void; + lvgl_sys::lv_disp_drv_register( + &mut ManuallyDrop::take(&mut disp_drv) as *mut lvgl_sys::lv_disp_drv_t + ); + }; + + 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(&mut 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 LittlevGL 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 LittlevGL 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)?) +} diff --git a/lvgl/src/widgets/mod.rs b/lvgl/src/widgets/mod.rs index f5721ff..6457a85 100644 --- a/lvgl/src/widgets/mod.rs +++ b/lvgl/src/widgets/mod.rs @@ -5,7 +5,7 @@ mod label; include!(concat!(env!("OUT_DIR"), "/generated.rs")); -use crate::{NativeObject, Widget}; +use crate::NativeObject; pub use arc::*; pub use bar::*; pub use gauge::*;