diff --git a/examples/demo/src/main.rs b/examples/demo/src/main.rs index 3cf9fb4..592dc4e 100644 --- a/examples/demo/src/main.rs +++ b/examples/demo/src/main.rs @@ -1,13 +1,14 @@ +use embedded_graphics::pixelcolor::Rgb565; use embedded_graphics::prelude::*; use embedded_graphics_simulator::{ OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, }; use lvgl; -use lvgl::Object; +use lvgl::{Object, UI}; use lvgl_sys; -use std::sync::mpsc; +use std::sync::{mpsc, Arc, Mutex}; +use std::thread::sleep; use std::time::Duration; -use embedded_graphics::pixelcolor::{Rgb565}; fn main() -> Result<(), String> { let mut display: SimulatorDisplay = SimulatorDisplay::new(Size::new( @@ -16,17 +17,16 @@ fn main() -> Result<(), String> { )); let output_settings = OutputSettingsBuilder::new().scale(4).build(); - let mut window = Window::new("Hello World", &output_settings); + let mut window = Window::new("PineTime", &output_settings); - unsafe { - lvgl_sys::lv_init(); - } + let mut ui = UI::init().unwrap(); // Implement and register your display: - let mut display_driver = lvgl::DisplayDriver::new(&mut display); + let display_driver = lvgl::DisplayDriver::new(&mut display); + ui.disp_drv_register(display_driver); // Create screen and widgets - let mut screen = display_driver.get_active_screen(); + let mut screen = ui.scr_act(); let font_roboto_28 = unsafe { &lvgl_sys::lv_font_roboto_28 }; let font_noto_sans_numeric_28 = unsafe { ¬o_sans_numeric_80 }; @@ -34,45 +34,48 @@ fn main() -> Result<(), String> { let mut screen_style = lvgl::Style::new(); screen_style.set_body_main_color(lvgl::Color::from_rgb((0, 0, 0))); screen_style.set_body_grad_color(lvgl::Color::from_rgb((0, 0, 0))); - screen.set_style(&mut screen_style); + screen.set_style(screen_style); let mut time = lvgl::Label::new(&mut screen); let mut style_time = lvgl::Style::new(); style_time.set_text_font(font_noto_sans_numeric_28); style_time.set_text_color(lvgl::Color::from_rgb((255, 255, 255))); - time.set_style(&mut style_time); + time.set_style(style_time); time.set_align(&mut screen, lvgl::Align::InLeftMid, 20, 0); - time.set_text("20:46\0"); + time.set_text("20:46"); time.set_width(240); time.set_height(240); let mut bt = lvgl::Label::new(&mut screen); let mut style_bt = lvgl::Style::new(); style_bt.set_text_font(font_roboto_28); - let mut style_power = style_bt.clone(); - bt.set_style(&mut style_bt); + let style_power = style_bt.clone(); + bt.set_style(style_bt); bt.set_width(50); bt.set_height(80); bt.set_recolor(true); - bt.set_text("#5794f2 \u{F293}#\0"); + bt.set_text("#5794f2 \u{F293}#"); bt.set_label_align(lvgl::LabelAlign::Left); bt.set_align(&mut screen, lvgl::Align::InTopLeft, 0, 0); let mut power = lvgl::Label::new(&mut screen); - power.set_style(&mut style_power); + power.set_style(style_power); power.set_recolor(true); power.set_width(80); power.set_height(20); - power.set_text("#fade2a 20%#\0"); + power.set_text("#fade2a 20%#"); power.set_label_align(lvgl::LabelAlign::Right); power.set_align(&mut screen, lvgl::Align::InTopRight, 0, 0); + let threaded_ui = Arc::new(Mutex::new(ui)); + let (stop_ch, read_ch) = mpsc::channel(); + let closure_ui = threaded_ui.clone(); let tick_thr = std::thread::spawn(move || loop { - ::std::thread::sleep(Duration::from_millis(5)); - unsafe { - lvgl_sys::lv_tick_inc(5); - } + let period = Duration::from_millis(5); + closure_ui.lock().unwrap().tick_inc(period); + + sleep(period); if read_ch.try_recv().is_ok() { break; } @@ -83,16 +86,14 @@ fn main() -> Result<(), String> { if i > 59 { i = 0; } - time.set_text(format!("21:{:02}\0", i).as_str()); + time.set_text(format!("21:{:02}", i).as_str()); i = 1 + i; - ::std::thread::sleep(Duration::from_millis( + sleep(Duration::from_millis( lvgl_sys::LV_DISP_DEF_REFR_PERIOD as u64, )); - unsafe { - lvgl_sys::lv_task_handler(); - } + threaded_ui.lock().unwrap().task_handler(); window.update(&display); @@ -111,6 +112,13 @@ fn main() -> Result<(), String> { } // Reference to native font for LittlevGL, defined in the file: "fonts_noto_sans_numeric_80.c" +// TODO: Create a macro for defining a safe wrapper for fonts. +// Maybe sometihng like: +// +// font! { +// NotoSansNumeric80 = noto_sans_numeric_80; +// }; +// extern "C" { pub static mut noto_sans_numeric_80: lvgl_sys::lv_font_t; } diff --git a/lvgl/Cargo.toml b/lvgl/Cargo.toml index db865c1..2165e32 100644 --- a/lvgl/Cargo.toml +++ b/lvgl/Cargo.toml @@ -16,3 +16,4 @@ keywords = ["littlevgl", "lvgl", "graphical_interfaces"] lvgl-sys = {path="../lvgl-sys", version="0.1"} cty = "0.2" embedded-graphics = "0.6" +cstr_core = { version = "0.2", default-features = false, features = ["alloc"] } diff --git a/lvgl/src/display.rs b/lvgl/src/display.rs index 5f641f6..edc63ee 100644 --- a/lvgl/src/display.rs +++ b/lvgl/src/display.rs @@ -1,105 +1,84 @@ -use crate::objx::ObjectX; -use core::marker::PhantomData; +use crate::Color; +use alloc::boxed::Box; +use alloc::rc::Rc; +use core::cell::RefCell; use core::mem::MaybeUninit; -use core::ptr; use embedded_graphics; -use embedded_graphics::pixelcolor::{Rgb565, Rgb888}; use embedded_graphics::prelude::*; use embedded_graphics::{drawable, DrawTarget}; -use lvgl_sys::lv_color_t; -pub struct DisplayDriver<'a, T, C> -where - T: DrawTarget, - C: PixelColor + From -{ - raw: lvgl_sys::lv_disp_drv_t, - display_buffer: MaybeUninit, - refresh_buffer: [MaybeUninit; lvgl_sys::LV_HOR_RES_MAX as usize * 10], - phantom: &'a PhantomData, - phantom2: PhantomData, +pub struct DisplayDriver { + pub(crate) raw: lvgl_sys::lv_disp_drv_t, } -impl<'a, T, C> DisplayDriver<'a, T, C> -where - T: DrawTarget, - C: PixelColor + From -{ - pub fn new(device: &'a mut T) -> Self { - // Create a display buffer for LittlevGL - let mut display_buffer = MaybeUninit::::uninit(); - // Declare a buffer for 10 lines - let mut refresh_buffer: [MaybeUninit; - lvgl_sys::LV_HOR_RES_MAX as usize * 10] = - unsafe { MaybeUninit::uninit().assume_init() }; - // Initialize the display buffer - unsafe { +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 { + // Create a display buffer for LittlevGL + let mut display_buffer = + Box::new(MaybeUninit::::uninit().assume_init()); + + // Declare a buffer for the refresh rate + let refresh_buffer = Box::new( + MaybeUninit::< + [MaybeUninit; lvgl_sys::LV_HOR_RES_MAX as usize * 10], + >::uninit() + .assume_init(), + ); + + // Initialize the display buffer lvgl_sys::lv_disp_buf_init( - display_buffer.as_mut_ptr(), - refresh_buffer.as_mut_ptr() as *mut cty::c_void, + display_buffer.as_mut(), + Box::into_raw(refresh_buffer) as *mut cty::c_void, core::ptr::null_mut(), (lvgl_sys::LV_HOR_RES_MAX * 10) as u32, ); - } - let mut disp_drv = unsafe { + // Descriptor of a display driver let mut disp_drv = MaybeUninit::::uninit().assume_init(); + // Basic initialization lvgl_sys::lv_disp_drv_init(&mut disp_drv); + + // 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 disp_drv.user_data = device as *mut _ as *mut cty::c_void; + disp_drv }; - // Assign the buffer to the display - disp_drv.buffer = display_buffer.as_mut_ptr(); - // Finally register the driver - unsafe { - lvgl_sys::lv_disp_drv_register(&mut disp_drv); - } - Self { - raw: disp_drv, - display_buffer, - refresh_buffer, - phantom: &PhantomData, - phantom2: PhantomData, - } - } - - pub fn get_active_screen(&mut self) -> ObjectX<'static> { - get_active_screen() + Self { raw: disp_drv } } } -pub struct ColorRgb(lv_color_t); +// 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!() +// } +// } -impl From for Rgb888 { - fn from(color: ColorRgb) -> Self { - // Convert Lvgl to embedded-graphics color - let raw_color = color.0; - unsafe { - Rgb888::new( - lvgl_sys::_LV_COLOR_GET_R(raw_color) as u8, - lvgl_sys::_LV_COLOR_GET_G(raw_color) as u8, - lvgl_sys::_LV_COLOR_GET_B(raw_color) as u8, - ) - } - } -} - -impl From for Rgb565 { - fn from(color: ColorRgb) -> Self { - // Convert Lvgl to embedded-graphics color - let raw_color = color.0; - unsafe { - Rgb565::new( - lvgl_sys::_LV_COLOR_GET_R(raw_color) as u8, - lvgl_sys::_LV_COLOR_GET_G(raw_color) as u8, - lvgl_sys::_LV_COLOR_GET_B(raw_color) as u8, - ) - } - } -} +// 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, @@ -107,7 +86,7 @@ unsafe extern "C" fn display_callback_wrapper( color_p: *mut lvgl_sys::lv_color_t, ) where T: DrawTarget, - C: PixelColor + From + C: PixelColor + From, { // We need to make sure panics can't escape across the FFI boundary. //let _ = std::panic::catch_unwind(|| { @@ -121,10 +100,13 @@ unsafe extern "C" fn display_callback_wrapper( //let image_buffer = for y in (*area).y1..=(*area).y2 { for x in (*area).x1..=(*area).x2 { - let raw_color = ColorRgb(*color_p.add(i)); + let raw_color = Color::from_raw(*color_p.add(i)); i = i + 1; // TODO: Use device.draw_iter - let _ = device.draw_pixel(drawable::Pixel(Point::new(x as i32, y as i32), raw_color.into())); + let _ = device.draw_pixel(drawable::Pixel( + Point::new(x as i32, y as i32), + raw_color.into(), + )); } } @@ -132,9 +114,3 @@ unsafe extern "C" fn display_callback_wrapper( lvgl_sys::lv_disp_flush_ready(disp_drv); //}); // end of panic::catch_unwind } - -pub fn get_active_screen() -> ObjectX<'static> { - let raw = - unsafe { ptr::NonNull::new_unchecked(lvgl_sys::lv_disp_get_scr_act(ptr::null_mut())) }; - ObjectX::new(raw) -} diff --git a/lvgl/src/global.rs b/lvgl/src/global.rs new file mode 100644 index 0000000..521af32 --- /dev/null +++ b/lvgl/src/global.rs @@ -0,0 +1,68 @@ +use crate::{DisplayDriver, ObjectX}; +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); + +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum LvError { + Uninitialized, + AlreadyInUse, +} + +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() -> Result { + if LVGL_IN_USE.compare_and_swap(false, true, Ordering::SeqCst) == false { + 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) -> ObjectX { + unsafe { + let screen = lvgl_sys::lv_disp_get_scr_act(ptr::null_mut()); + ObjectX::from_raw(NonNull::new_unchecked(screen)) + } + } + + 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 aa5e592..56268a8 100644 --- a/lvgl/src/lib.rs +++ b/lvgl/src/lib.rs @@ -1,7 +1,12 @@ #![no_std] -pub mod display; -mod objx; +extern crate alloc; +mod global; +mod display; +mod support; +mod widgets; + +pub use global::{UI, LvError}; pub use display::DisplayDriver; -pub use objx::*; +pub use support::*; diff --git a/lvgl/src/objx.rs b/lvgl/src/support.rs similarity index 79% rename from lvgl/src/objx.rs rename to lvgl/src/support.rs index 8aa5f13..bb545d2 100644 --- a/lvgl/src/objx.rs +++ b/lvgl/src/support.rs @@ -1,30 +1,31 @@ +use alloc::boxed::Box; use core::mem; use core::ptr; -use cty; +use cstr_core::CString; +use embedded_graphics::pixelcolor::{Rgb565, Rgb888}; use lvgl_sys; pub trait NativeObject { fn raw(&self) -> ptr::NonNull; } -pub struct ObjectX<'a> { +pub struct ObjectX { raw: ptr::NonNull, - style: Option<&'a mut Style>, } -impl<'a> ObjectX<'a> { - pub(crate) fn new(raw: ptr::NonNull) -> Self { - Self { raw, style: None } +impl ObjectX { + pub(crate) fn from_raw(raw: ptr::NonNull) -> Self { + Self { raw } } } -impl<'a> NativeObject for ObjectX<'a> { +impl NativeObject for ObjectX { fn raw(&self) -> ptr::NonNull { unsafe { ptr::NonNull::new_unchecked(self.raw.as_ptr()) } } } -pub trait Object<'a>: NativeObject { +pub trait Object: NativeObject { fn set_pos(&mut self, x: i16, y: i16) { unsafe { lvgl_sys::lv_obj_set_pos( @@ -95,36 +96,36 @@ pub trait Object<'a>: NativeObject { } } - fn set_style(&mut self, style: &'a mut Style); + fn set_style(&mut self, style: Style); } -impl<'a> Object<'a> for ObjectX<'a> { - fn set_style(&mut self, style: &'a mut Style) { +impl Object for ObjectX { + fn set_style(&mut self, style: Style) { unsafe { - lvgl_sys::lv_obj_set_style(self.raw().as_mut(), style.raw()); + let boxed = Box::new(style.raw); + lvgl_sys::lv_obj_set_style(self.raw().as_mut(), Box::into_raw(boxed)); }; - self.style = Some(style); } } macro_rules! define_object { ($item:ident) => { - pub struct $item<'a> { - core: ObjectX<'a>, + pub struct $item { + core: ObjectX, } - impl<'a> NativeObject for $item<'a> { + impl NativeObject for $item { fn raw(&self) -> ptr::NonNull { self.core.raw() } } - impl<'a> Object<'a> for $item<'a> { - fn set_style(&mut self, style: &'a mut Style) { + impl Object for $item { + fn set_style(&mut self, style: Style) { unsafe { - lvgl_sys::lv_obj_set_style(self.raw().as_mut(), style.raw()); + let boxed = Box::new(style.raw); + lvgl_sys::lv_obj_set_style(self.raw().as_mut(), Box::into_raw(boxed)); }; - self.core.style = Some(style); } } }; @@ -132,7 +133,7 @@ macro_rules! define_object { define_object!(Button); -impl<'a> Button<'a> { +impl Button { pub fn new(parent: &mut C) -> Self where C: NativeObject, @@ -141,7 +142,7 @@ impl<'a> Button<'a> { let ptr = lvgl_sys::lv_btn_create(parent.raw().as_mut(), ptr::null_mut()); ptr::NonNull::new_unchecked(ptr) }; - let core = ObjectX::new(raw); + let core = ObjectX::from_raw(raw); Self { core } } } @@ -155,7 +156,7 @@ pub enum LabelAlign { define_object!(Label); -impl<'a> Label<'a> { +impl Label { pub fn new(parent: &mut C) -> Self where C: NativeObject, @@ -164,16 +165,14 @@ impl<'a> Label<'a> { let ptr = lvgl_sys::lv_label_create(parent.raw().as_mut(), ptr::null_mut()); ptr::NonNull::new_unchecked(ptr) }; - let core = ObjectX::new(raw); + let core = ObjectX::from_raw(raw); Self { core } } pub fn set_text(&mut self, text: &str) { + let text = CString::new(text).unwrap(); unsafe { - lvgl_sys::lv_label_set_text( - self.core.raw().as_mut(), - text.as_ptr() as *const cty::c_char, - ); + lvgl_sys::lv_label_set_text(self.core.raw().as_mut(), text.as_ptr()); } } @@ -233,10 +232,6 @@ impl Style { pub fn set_text_font(&mut self, font: &lvgl_sys::lv_font_t) { self.raw.text.font = font; } - - fn raw(&mut self) -> *const lvgl_sys::lv_style_t { - &mut self.raw - } } impl Clone for Style { @@ -264,6 +259,34 @@ impl Color { let raw = unsafe { lvgl_sys::_LV_COLOR_MAKE(r, g, b) }; Self { raw } } + + pub fn from_raw(raw: lvgl_sys::lv_color_t) -> Self { + Self { raw } + } +} + +impl From for Rgb888 { + fn from(color: Color) -> Self { + unsafe { + Rgb888::new( + lvgl_sys::_LV_COLOR_GET_R(color.raw) as u8, + lvgl_sys::_LV_COLOR_GET_G(color.raw) as u8, + lvgl_sys::_LV_COLOR_GET_B(color.raw) as u8, + ) + } + } +} + +impl From for Rgb565 { + fn from(color: Color) -> Self { + unsafe { + Rgb565::new( + lvgl_sys::_LV_COLOR_GET_R(color.raw) as u8, + lvgl_sys::_LV_COLOR_GET_G(color.raw) as u8, + lvgl_sys::_LV_COLOR_GET_B(color.raw) as u8, + ) + } + } } pub enum Align { diff --git a/lvgl/src/widgets.rs b/lvgl/src/widgets.rs new file mode 100644 index 0000000..e69de29