API re-design (#9)

* New API test using std/alloc objects

* Use RefCell to create multiple mut refs

* UI saves display obj, makes possible for global call to active display

* Reflect restrictions of LvGL

* No need for manual nul byte

* Fix typo
This commit is contained in:
Rafael Caricio 2020-05-30 00:25:27 +02:00 committed by GitHub
parent 63a160d54e
commit 893f0054c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 229 additions and 148 deletions

View file

@ -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<Rgb565> = 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 { &noto_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;
}

View file

@ -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"] }

View file

@ -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>,
C: PixelColor + From<ColorRgb>
{
raw: lvgl_sys::lv_disp_drv_t,
display_buffer: MaybeUninit<lvgl_sys::lv_disp_buf_t>,
refresh_buffer: [MaybeUninit<lvgl_sys::lv_color_t>; lvgl_sys::LV_HOR_RES_MAX as usize * 10],
phantom: &'a PhantomData<T>,
phantom2: PhantomData<C>,
pub struct DisplayDriver {
pub(crate) raw: lvgl_sys::lv_disp_drv_t,
}
impl<'a, T, C> DisplayDriver<'a, T, C>
impl DisplayDriver {
// we should accept a Rc<RefCell<T>> and throw it in a box and add to the user_data of the callback handler function
pub fn new<T, C>(device: &mut T) -> Self
where
T: DrawTarget<C>,
C: PixelColor + From<ColorRgb>
C: PixelColor + From<Color>,
{
pub fn new(device: &'a mut T) -> Self {
let disp_drv = unsafe {
// Create a display buffer for LittlevGL
let mut display_buffer = MaybeUninit::<lvgl_sys::lv_disp_buf_t>::uninit();
// Declare a buffer for 10 lines
let mut refresh_buffer: [MaybeUninit<lvgl_sys::lv_color_t>;
lvgl_sys::LV_HOR_RES_MAX as usize * 10] =
unsafe { MaybeUninit::uninit().assume_init() };
let mut display_buffer =
Box::new(MaybeUninit::<lvgl_sys::lv_disp_buf_t>::uninit().assume_init());
// Declare a buffer for the refresh rate
let refresh_buffer = Box::new(
MaybeUninit::<
[MaybeUninit<lvgl_sys::lv_color_t>; lvgl_sys::LV_HOR_RES_MAX as usize * 10],
>::uninit()
.assume_init(),
);
// Initialize the display buffer
unsafe {
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::<lvgl_sys::lv_disp_drv_t>::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::<T, C>);
// 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,
Self { raw: disp_drv }
}
}
pub fn get_active_screen(&mut self) -> ObjectX<'static> {
get_active_screen()
}
}
// 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!()
// }
// }
pub struct ColorRgb(lv_color_t);
impl From<ColorRgb> 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<ColorRgb> 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<RefCell<u8>>;
//
// impl Drop for DrawHandler {
// fn drop(&mut self) {
// unimplemented!()
// }
// }
unsafe extern "C" fn display_callback_wrapper<T, C>(
disp_drv: *mut lvgl_sys::lv_disp_drv_t,
@ -107,7 +86,7 @@ unsafe extern "C" fn display_callback_wrapper<T, C>(
color_p: *mut lvgl_sys::lv_color_t,
) where
T: DrawTarget<C>,
C: PixelColor + From<ColorRgb>
C: PixelColor + From<Color>,
{
// 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<T, C>(
//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<T, C>(
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)
}

68
lvgl/src/global.rs Normal file
View file

@ -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<Self, LvError> {
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();
}
}
}

View file

@ -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::*;

View file

@ -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<lvgl_sys::lv_obj_t>;
}
pub struct ObjectX<'a> {
pub struct ObjectX {
raw: ptr::NonNull<lvgl_sys::lv_obj_t>,
style: Option<&'a mut Style>,
}
impl<'a> ObjectX<'a> {
pub(crate) fn new(raw: ptr::NonNull<lvgl_sys::lv_obj_t>) -> Self {
Self { raw, style: None }
impl ObjectX {
pub(crate) fn from_raw(raw: ptr::NonNull<lvgl_sys::lv_obj_t>) -> Self {
Self { raw }
}
}
impl<'a> NativeObject for ObjectX<'a> {
impl NativeObject for ObjectX {
fn raw(&self) -> ptr::NonNull<lvgl_sys::lv_obj_t> {
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<lvgl_sys::lv_obj_t> {
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<C>(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<C>(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<Color> 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<Color> 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 {

0
lvgl/src/widgets.rs Normal file
View file