API re-design #9

Merged
rafaelcaricio merged 6 commits from api_design_studies into master 2020-05-29 22:25:28 +00:00
7 changed files with 214 additions and 348 deletions
Showing only changes of commit 8856b83461 - Show all commits

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(
@ -18,15 +19,14 @@ fn main() -> Result<(), String> {
let output_settings = OutputSettingsBuilder::new().scale(4).build();
let mut window = Window::new("Hello World", &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,13 +34,13 @@ 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_width(240);
@ -49,8 +49,8 @@ fn main() -> Result<(), String> {
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);
@ -59,7 +59,7 @@ fn main() -> Result<(), String> {
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);
@ -67,12 +67,15 @@ fn main() -> Result<(), String> {
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;
}
@ -86,13 +89,11 @@ fn main() -> Result<(), String> {
time.set_text(format!("21:{:02}\0", 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);

View file

@ -1,211 +0,0 @@
use core::marker::PhantomData;
use core::time::Duration;
use core::sync::atomic::{AtomicBool, Ordering};
use core::cell::{RefCell, RefMut};
// 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 {
AlreadyInUse,
}
pub struct UI {
// LittlevGL is not thread-safe by default.
_not_send: PhantomData<*const ()>,
display: Option<DisplayDriver>,
}
impl UI {
pub fn init() -> Result<Self, LvError> {
if LVGL_IN_USE.compare_and_swap(false, true, Ordering::SeqCst) == false {
// lvgl_sys::lv_init();
Ok(Self {
_not_send: PhantomData,
display: None,
})
} else {
Err(LvError::AlreadyInUse)
}
}
pub fn disp_drv_register(&mut self, display: DisplayDriver) {
// register it
// lvgl_sys::lv_disp_drv_register(&mut disp_drv);
self.display = Some(display);
}
pub fn scr_act(&self) -> Option<&Screen> {
match self.display.as_ref() {
Some(drv) => {
Some(drv.scr_act())
},
None => None
}
}
pub fn tick_inc(&mut self, _tick_period: Duration) {
// lvgl_sys::lv_tick_inc(tick_period);
}
pub fn task_handler(&mut self) {
// lvgl_sys::lv_task_handler();
}
}
pub struct DisplayDriver {
refresh_lines: u32,
current_screen: Screen,
}
impl DisplayDriver {
pub fn new(refresh_lines: u32) -> Self {
Self{ refresh_lines, current_screen: Screen::new() }
}
// Ensure cannot delete the current loaded screen
pub fn load_scr(&mut self, screen: Screen) {
self.current_screen = screen;
}
pub fn scr_act(&self) -> &Screen {
&self.current_screen
}
}
pub trait LvObject {
fn label_create(&self) -> Label where Self: Sized {
Label{ parent: self }
}
fn btn_create<F>(&self) -> Button<F> where Self: Sized, F: FnMut() {
Button{ parent: self, event_callback: None }
}
}
pub struct Screen {
_not_send: PhantomData<*const ()>
}
impl Screen {
pub fn new() -> Self {
Self { _not_send: PhantomData }
}
}
impl LvObject for Screen {}
pub struct Label<'a> {
parent: &'a dyn LvObject,
}
impl<'a> Label<'a> {
pub fn set_text(&mut self, _text: &str) {
// set text call to unsafe...
}
}
impl<'a> LvObject for Label<'a> {}
pub struct Button<'a, F> where F: FnMut() {
parent: &'a dyn LvObject,
event_callback: Option<F>,
}
impl<'a, F> Button<'a, F> where F: FnMut() {
pub fn on_event(&mut self, callback: F) {
// add callback
self.event_callback = Some(callback);
}
}
impl<'a, F> LvObject for Button<'a, F> where F: FnMut() {}
#[cfg(test)]
mod test {
use crate::api::{UI, DisplayDriver, LvObject};
use core::time::Duration;
#[test]
fn basic_usage() {
let mut ui = UI::init().unwrap();
let refresh_lines = 10;
let display = DisplayDriver::new(refresh_lines);
ui.disp_drv_register(display);
{
let screen = ui.scr_act().unwrap();
let mut button = screen.btn_create();
button.on_event(|| {
// something
let screen = ui.scr_act().unwrap();
let mut label = screen.label_create();
label.set_text("Clicked");
});
let mut label = button.label_create();
label.set_text("Click me!");
};
{
let screen = ui.scr_act().unwrap();
let mut button2 = screen.btn_create();
button2.on_event(|| {
// else
});
let mut label2 = button2.label_create();
label2.set_text("Else");
};
ui.tick_inc(Duration::from_millis(5));
ui.task_handler();
}
#[test]
fn test_usage() {
// // can use Arc<Mutex<lvgl::App>> to share between threads
// let mut app = lvgl::App::init().unwrap();
//
// let disp = DisplayDriver::new();
// app.register_display_driver(&disp); // takes (&mut self, ...)
//
// let screen: &mut lvgl::Screen = disp.new_screen(); // takes (&mut self)
// disp.load_screen(&screen); // takes (&self) because it just need the ref to
// // the screen to load, as all the screens are already internal.
//
// let button = screen.new_button(); // takes (&self)
// button.on_event(|&mut app, &mut btn, ev| {
// let mut label: &mut Label = app.cur_screen().new_label();
// if let lvgl::Event::Clicked = ev {
// btn.set_text("clicked!");
// }
// app.load_screen(&screen);
// });
//
// let mut lbl_click = button.new_label();
// lbl_click.set_text("Click me!");
//
//
// // lvgl::Timer returns reference to same internal object
// app.tick(Duration::from_millis(1)); // takes (&mut self)
//
// app.task_handler(); // takes (&mut self)
//
// // Multiple Displays can be instantiated
// // Multiple Screen's in each Display
// // Screen can be instantiated independently
// // Any specific object impl Screenable(trait) (can be used anywhere Screen is used)
// // To get active screen from a Display use `lv_disp_get_scr_act(disp)`
// let screen = disp.get_active_screen();
// // To set an active screen in a Display use `lv_disp_load_scr(disp, scr)`
// disp.load_screen(screen);
//
// let mut screen = Screen::new();
// let mut button = screen.add_button();
// let mut btn_lbl = button.create_label();
}
}

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>
where
T: DrawTarget<C>,
C: PixelColor + From<ColorRgb>
{
pub fn new(device: &'a mut T) -> Self {
// 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() };
// Initialize the display buffer
unsafe {
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<Color>,
{
let disp_drv = unsafe {
// Create a display buffer for LittlevGL
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
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,
}
}
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<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)
}

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

@ -0,0 +1,70 @@
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,
}
type LvResult<T> = Result<T, LvError>;
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,11 +1,12 @@
#![no_std]
pub mod display;
mod objx;
extern crate alloc;
mod api;
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 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,7 +165,7 @@ 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 }
}
@ -264,6 +265,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