This commit is contained in:
Rafael Caricio 2022-01-12 09:02:15 +01:00 committed by GitHub
commit a84024c027
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 846 additions and 346 deletions

View file

@ -30,4 +30,5 @@ jobs:
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
# LVGL is not thread safe, we need to run tests sequentially
run: cargo test --verbose -- --nocapture --test-threads 1

40
examples/app.rs Normal file
View file

@ -0,0 +1,40 @@
use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::*;
use embedded_graphics_simulator::{OutputSettingsBuilder, SimulatorDisplay, Window};
use lvgl;
use lvgl::widgets::Label;
use lvgl::{Display, DrawBuffer};
use parking_lot::Mutex;
use std::cell::RefCell;
use std::sync::Arc;
type ColorSpace = Rgb565;
fn main() {
let embedded_graphics_display: SimulatorDisplay<ColorSpace> = SimulatorDisplay::new(Size::new(
lvgl::DISP_HOR_RES as u32,
lvgl::DISP_VER_RES as u32,
));
let output_settings = OutputSettingsBuilder::new().scale(2).build();
let mut window = Window::new("App Example", &output_settings);
let mut shared_native_display = Arc::new(Mutex::new(embedded_graphics_display));
// LVGL usage
lvgl::init();
const REFRESH_BUFFER_SIZE: usize = lvgl::DISP_HOR_RES * lvgl::DISP_VER_RES / 10;
static DRAW_BUFFER: DrawBuffer<REFRESH_BUFFER_SIZE> = DrawBuffer::new();
let display = Display::register(&DRAW_BUFFER, {
let shared_display = Arc::clone(&shared_native_display);
move |update| {
let mut embedded_graphics_display = shared_display.lock();
embedded_graphics_display.draw_iter(update.as_pixels());
}
})
.unwrap();
let label: Label = "Nice!".into();
}

View file

@ -4,12 +4,16 @@ use embedded_graphics::prelude::*;
use embedded_graphics_simulator::{
OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window,
};
use lvgl::display::Display;
use lvgl::style::Style;
use lvgl::widgets::{Arc, Label, LabelAlign};
use lvgl::{self, Align, Color, Part, State, UI};
use lvgl::{self, Align, Color, Part, State};
use lvgl::{LvError, Widget};
use lvgl_sys;
use std::time::Instant;
use parking_lot::Mutex;
use std::sync::Arc as SyncArc;
use std::thread;
use std::time::{Duration, Instant};
fn mem_info() -> lvgl_sys::lv_mem_monitor_t {
let mut info = lvgl_sys::lv_mem_monitor_t {
@ -36,7 +40,9 @@ fn main() -> Result<(), LvError> {
}
fn run_arc_demo() -> Result<(), LvError> {
let display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new(
lvgl::init();
let embedded_graphics_display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new(
lvgl_sys::LV_HOR_RES_MAX,
lvgl_sys::LV_VER_RES_MAX,
));
@ -44,52 +50,60 @@ fn run_arc_demo() -> Result<(), LvError> {
let output_settings = OutputSettingsBuilder::new().scale(2).build();
let mut window = Window::new("Arc Example", &output_settings);
let mut ui = UI::init()?;
let shared_native_display = SyncArc::new(Mutex::new(embedded_graphics_display));
let display = Display::register_shared(&shared_native_display)?;
// Implement and register your display:
ui.disp_drv_register(display)?;
// Create screen and widgets
let mut screen = ui.scr_act()?;
let mut screen = display.get_str_act()?;
let mut screen_style = Style::default();
screen_style.set_bg_color(State::DEFAULT, Color::from_rgb((255, 255, 255)));
screen_style.set_radius(State::DEFAULT, 0);
screen.add_style(Part::Main, screen_style)?;
screen.add_style(Part::Main, &mut screen_style)?;
// Create the arc object
let mut arc = Arc::new(&mut screen)?;
let mut arc = Arc::new()?;
arc.set_size(150, 150)?;
arc.set_align(&mut screen, Align::Center, 0, 10)?;
arc.set_start_angle(135)?;
arc.set_end_angle(135)?;
let mut loading_lbl = Label::new(&mut screen)?;
let mut loading_lbl = Label::new()?;
loading_lbl.set_text(CString::new("Loading...").unwrap().as_c_str())?;
loading_lbl.set_align(&mut arc, Align::OutTopMid, 0, -10)?;
loading_lbl.set_label_align(LabelAlign::Center)?;
let mut loading_style = Style::default();
loading_style.set_text_color(State::DEFAULT, Color::from_rgb((0, 0, 0)));
loading_lbl.add_style(Part::Main, loading_style)?;
loading_lbl.add_style(Part::Main, &mut loading_style)?;
let mut angle = 0;
let mut forward = true;
let mut i = 0;
let mut loop_started = Instant::now();
// LVGL timer thread
thread::spawn(|| {
let interval = Duration::from_millis(5);
loop {
thread::sleep(interval);
lvgl::tick_inc(interval);
}
});
'running: loop {
if i > 270 {
forward = if forward { false } else { true };
i = 1;
println!("meminfo running: {:?}", mem_info());
println!("mem info running: {:?}", mem_info());
}
angle = if forward { angle + 1 } else { angle - 1 };
arc.set_end_angle(angle + 135)?;
i += 1;
ui.task_handler();
window.update(ui.get_display_ref().unwrap());
lvgl::task_handler();
{
let eg_display = shared_native_display.lock();
window.update(&eg_display);
}
for event in window.events() {
match event {
@ -97,9 +111,7 @@ fn run_arc_demo() -> Result<(), LvError> {
_ => {}
}
}
ui.tick_inc(loop_started.elapsed());
loop_started = Instant::now();
thread::sleep(Duration::from_millis(15));
}
Ok(())

View file

@ -1,4 +1,5 @@
use cstr_core::CString;
use embedded_graphics::drawable;
use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::*;
use embedded_graphics_simulator::{
@ -7,61 +8,91 @@ use embedded_graphics_simulator::{
use lvgl;
use lvgl::style::Style;
use lvgl::widgets::{Label, LabelAlign};
use lvgl::{Align, Color, LvError, Part, State, Widget, UI};
use lvgl::{Align, Color, DefaultDisplay, Display, DrawBuffer, LvError, Part, State, Widget};
use lvgl_sys;
use parking_lot::Mutex;
use std::sync::Arc as SyncArc;
use std::thread;
use std::thread::sleep;
use std::time::{Duration, Instant};
fn main() -> Result<(), LvError> {
let display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new(
lvgl_sys::LV_HOR_RES_MAX,
lvgl_sys::LV_VER_RES_MAX,
let embedded_graphics_display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new(
lvgl::DISP_HOR_RES as u32,
lvgl::DISP_VER_RES as u32,
));
let output_settings = OutputSettingsBuilder::new().scale(2).build();
let output_settings = OutputSettingsBuilder::new().scale(1).build();
let mut window = Window::new("PineTime", &output_settings);
let mut ui = UI::init()?;
let shared_native_display = SyncArc::new(Mutex::new(embedded_graphics_display));
// Implement and register your display:
ui.disp_drv_register(display).unwrap();
// LVGL-rs usage starts here
lvgl::init();
// LVGL will render the graphics here first, and seed the rendered image to the
// display. The buffer size can be set freely but 1/10 screen size is a good starting point.
const REFRESH_BUFFER_SIZE: usize = lvgl::DISP_HOR_RES * lvgl::DISP_VER_RES / 10;
static DRAW_BUFFER: DrawBuffer<REFRESH_BUFFER_SIZE> = DrawBuffer::new();
//
// const NUMBER_OF_DISPLAYS: usize = 1;
// static DISPLAY_REGISTRY: DisplayRegistry<NUMBER_OF_DISPLAYS> = DisplayRegistry::empty();
// // static DISPLAY_REGISTRY: SingleDisplayRegistry = DisplayRegistry::empty();
// let display = DISPLAY_REGISTRY.register_shared(&DRAW_BUFFER, shared_native_display.clone())?;
// Register your display update callback with LVGL. The closure you pass here will be called
// whenever LVGL has updates to be painted to the display.
let display = Display::register(&DRAW_BUFFER, {
let shared_disp_inner = SyncArc::clone(&shared_native_display);
move |update| {
let mut em_disp = shared_disp_inner.lock();
em_disp.draw_iter(update.as_pixels());
}
})?;
// Create screen and widgets
let mut screen = ui.scr_act()?;
let mut screen = display.get_scr_act()?;
println!("Before all widgets: {:?}", mem_info());
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);
screen.add_style(Part::Main, screen_style)?;
screen.add_style(Part::Main, &mut screen_style)?;
let mut time = Label::new(&mut screen)?;
let mut time = Label::from("20:46");
let mut style_time = Style::default();
//style_time.set_text_font(font_noto_sans_numeric_28);
// style_time.set_text_font(font_noto_sans_numeric_28);
style_time.set_text_color(State::DEFAULT, Color::from_rgb((255, 255, 255)));
time.add_style(Part::Main, style_time)?;
time.add_style(Part::Main, &mut style_time)?;
time.set_align(&mut 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 mut bt = Label::new(&mut screen)?;
let mut bt = Label::from("#5794f2 \u{F293}#");
bt.set_width(50)?;
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(&mut screen, Align::InTopLeft, 0, 0)?;
let mut power = Label::new(&mut screen)?;
let mut power: Label = "#fade2a 20%#".into();
power.set_recolor(true)?;
power.set_width(80)?;
power.set_height(20)?;
power.set_text(CString::new("#fade2a 20%#").unwrap().as_c_str())?;
power.set_label_align(LabelAlign::Right)?;
power.set_align(&mut screen, Align::InTopRight, 0, 0)?;
// LVGL timer thread
thread::spawn(|| {
let interval = Duration::from_millis(5);
loop {
thread::sleep(interval);
lvgl::tick_inc(interval);
}
});
let mut i = 0;
let mut loop_started = Instant::now();
'running: loop {
if i > 59 {
i = 0;
@ -70,8 +101,11 @@ fn main() -> Result<(), LvError> {
time.set_text(&val)?;
i = 1 + i;
ui.task_handler();
window.update(ui.get_display_ref().unwrap());
lvgl::task_handler();
{
let native_display = shared_native_display.lock();
window.update(&native_display);
}
for event in window.events() {
match event {
@ -79,12 +113,12 @@ fn main() -> Result<(), LvError> {
_ => {}
}
}
println!("During run: {:?}", mem_info());
sleep(Duration::from_secs(1));
ui.tick_inc(loop_started.elapsed());
loop_started = Instant::now();
}
println!("Final part of demo app: {:?}", mem_info());
Ok(())
}
@ -99,3 +133,20 @@ fn main() -> Result<(), LvError> {
extern "C" {
pub static mut noto_sans_numeric_80: lvgl_sys::lv_font_t;
}
fn mem_info() -> lvgl_sys::lv_mem_monitor_t {
let mut info = lvgl_sys::lv_mem_monitor_t {
total_size: 0,
free_cnt: 0,
free_size: 0,
free_biggest_size: 0,
used_cnt: 0,
max_used: 0,
used_pct: 0,
frag_pct: 0,
};
unsafe {
lvgl_sys::lv_mem_monitor(&mut info as *mut _);
}
info
}

View file

@ -1,6 +1,6 @@
/**
* @file lv_conf.h
*
* Configuration file for v7.10.1
*/
/*
@ -80,9 +80,9 @@ typedef int16_t lv_coord_t;
#define LV_MEM_CUSTOM 0
#if LV_MEM_CUSTOM == 0
/* Size of the memory used by `lv_mem_alloc` in bytes (>= 2kB)*/
# define LV_MEM_SIZE (1048576U) // 1Mb
# define LV_MEM_SIZE (14U * 1024U)
/* Complier prefix for a big array declaration */
/* Compiler prefix for a big array declaration */
# define LV_MEM_ATTR
/* Set an address for the memory pool instead of allocating it as an array.
@ -97,6 +97,10 @@ typedef int16_t lv_coord_t;
# define LV_MEM_CUSTOM_FREE free /*Wrapper to free*/
#endif /*LV_MEM_CUSTOM*/
/* Use the standard memcpy and memset instead of LVGL's own functions.
* The standard functions might or might not be faster depending on their implementation. */
#define LV_MEMCPY_MEMSET_STD 0
/* Garbage Collector settings
* Used if lvgl is binded to higher level language and the memory is managed by that language */
#define LV_ENABLE_GC 0
@ -123,14 +127,13 @@ typedef int16_t lv_coord_t;
#define LV_INDEV_DEF_DRAG_THROW 10
/* Long press time in milliseconds.
* Time to send `LV_EVENT_LONG_PRESSSED`) */
* Time to send `LV_EVENT_LONG_PRESSED`) */
#define LV_INDEV_DEF_LONG_PRESS_TIME 400
/* Repeated trigger period in long press [ms]
* Time between `LV_EVENT_LONG_PRESSED_REPEAT */
#define LV_INDEV_DEF_LONG_PRESS_REP_TIME 100
/* Gesture threshold in pixels */
#define LV_INDEV_DEF_GESTURE_LIMIT 50
@ -150,7 +153,7 @@ typedef void * lv_anim_user_data_t;
#endif
/* 1: Enable shadow drawing*/
/* 1: Enable shadow drawing on rectangles*/
#define LV_USE_SHADOW 1
#if LV_USE_SHADOW
/* Allow buffering some shadow calculation
@ -160,6 +163,15 @@ typedef void * lv_anim_user_data_t;
#define LV_SHADOW_CACHE_SIZE 0
#endif
/*1: enable outline drawing on rectangles*/
#define LV_USE_OUTLINE 1
/*1: enable pattern drawing on rectangles*/
#define LV_USE_PATTERN 1
/*1: enable value string drawing on rectangles*/
#define LV_USE_VALUE_STR 1
/* 1: Use other blend modes than normal (`LV_BLEND_MODE_...`)*/
#define LV_USE_BLEND_MODES 1
@ -178,6 +190,22 @@ typedef void * lv_group_user_data_t;
/* 1: Enable GPU interface*/
#define LV_USE_GPU 1 /*Only enables `gpu_fill_cb` and `gpu_blend_cb` in the disp. drv- */
#define LV_USE_GPU_STM32_DMA2D 0
/*If enabling LV_USE_GPU_STM32_DMA2D, LV_GPU_DMA2D_CMSIS_INCLUDE must be defined to include path of CMSIS header of target processor
e.g. "stm32f769xx.h" or "stm32f429xx.h" */
#define LV_GPU_DMA2D_CMSIS_INCLUDE
/*1: Use PXP for CPU off-load on NXP RTxxx platforms */
#define LV_USE_GPU_NXP_PXP 0
/*1: Add default bare metal and FreeRTOS interrupt handling routines for PXP (lv_gpu_nxp_pxp_osa.c)
* and call lv_gpu_nxp_pxp_init() automatically during lv_init(). Note that symbol FSL_RTOS_FREE_RTOS
* has to be defined in order to use FreeRTOS OSA, otherwise bare-metal implementation is selected.
*0: lv_gpu_nxp_pxp_init() has to be called manually before lv_init()
* */
#define LV_USE_GPU_NXP_PXP_AUTO_INIT 0
/*1: Use VG-Lite for CPU offload on NXP RTxxx platforms */
#define LV_USE_GPU_NXP_VG_LITE 0
/* 1: Enable file system (might be required for images */
#define LV_USE_FILESYSTEM 1
@ -194,6 +222,7 @@ typedef void * lv_fs_drv_user_data_t;
/*1: Use the functions and types from the older API if possible */
#define LV_USE_API_EXTENSION_V6 1
#define LV_USE_API_EXTENSION_V7 1
/*========================
* Image decoder and cache
@ -210,7 +239,7 @@ typedef void * lv_fs_drv_user_data_t;
* (I.e. no new image decoder is added)
* With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images.
* However the opened images might consume additional RAM.
* LV_IMG_CACHE_DEF_SIZE must be >= 1 */
* Set it to 0 to disable caching */
#define LV_IMG_CACHE_DEF_SIZE 1
/*Declare the type of the user data of image decoder (can be e.g. `void *`, `int`, `struct`)*/
@ -219,6 +248,10 @@ typedef void * lv_img_decoder_user_data_t;
/*=====================
* Compiler settings
*====================*/
/* For big endian systems set to 1 */
#define LV_BIG_ENDIAN_SYSTEM 0
/* Define a custom attribute to `lv_tick_inc` function */
#define LV_ATTRIBUTE_TICK_INC
@ -228,9 +261,14 @@ typedef void * lv_img_decoder_user_data_t;
/* Define a custom attribute to `lv_disp_flush_ready` function */
#define LV_ATTRIBUTE_FLUSH_READY
/* Required alignment size for buffers */
#define LV_ATTRIBUTE_MEM_ALIGN_SIZE
/* With size optimization (-Os) the compiler might not align data to
* 4 or 8 byte boundary. This alignment will be explicitly applied where needed.
* E.g. __attribute__((aligned(4))) */
* 4 or 8 byte boundary. Some HW may need even 32 or 64 bytes.
* This alignment will be explicitly applied where needed.
* LV_ATTRIBUTE_MEM_ALIGN_SIZE should be used to specify required align size.
* E.g. __attribute__((aligned(LV_ATTRIBUTE_MEM_ALIGN_SIZE))) */
#define LV_ATTRIBUTE_MEM_ALIGN
/* Attribute to mark large constant arrays for example
@ -249,6 +287,10 @@ typedef void * lv_img_decoder_user_data_t;
*/
#define LV_EXPORT_CONST_INT(int_value) struct _silence_gcc_warning
/* Prefix variables that are used in GPU accelerated operations, often these need to be
* placed in RAM sections that are DMA accessible */
#define LV_ATTRIBUTE_DMA
/*===================
* HAL settings
*==================*/
@ -257,8 +299,8 @@ typedef void * lv_img_decoder_user_data_t;
* It removes the need to manually update the tick with `lv_tick_inc`) */
#define LV_TICK_CUSTOM 0
#if LV_TICK_CUSTOM == 1
#define LV_TICK_CUSTOM_INCLUDE "something.h" /*Header for the sys time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) /*Expression evaluating to current systime in ms*/
#define LV_TICK_CUSTOM_INCLUDE "Arduino.h" /*Header for the system time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) /*Expression evaluating to current system time in ms*/
#endif /*LV_TICK_CUSTOM*/
typedef void * lv_disp_drv_user_data_t; /*Type of user data in the display driver*/
@ -293,7 +335,7 @@ typedef void * lv_indev_drv_user_data_t; /*Type of user data in the i
* If an invalid parameter is found an error log message is printed and
* the MCU halts at the error. (`LV_USE_LOG` should be enabled)
* If you are debugging the MCU you can pause
* the debugger to see exactly where the issue is.
* the debugger to see exactly where the issue is.
*
* The behavior of asserts can be overwritten by redefining them here.
* E.g. #define LV_ASSERT_MEM(p) <my_assert_code>
@ -328,17 +370,19 @@ typedef void * lv_indev_drv_user_data_t; /*Type of user data in the i
* FONT USAGE
*===================*/
/* The built-in fonts contains the ASCII range and some Symbols with 4 bit-per-pixel.
/* The built-in fonts contains the ASCII range and some Symbols with 4 bit-per-pixel.
* The symbols are available via `LV_SYMBOL_...` defines
* More info about fonts: https://docs.lvgl.com/#Fonts
* More info about fonts: https://docs.lvgl.io/v7/en/html/overview/font.html
* To create a new font go to: https://lvgl.com/ttf-font-to-c-array
*/
/* Montserrat fonts with bpp = 4
* https://fonts.google.com/specimen/Montserrat */
#define LV_FONT_MONTSERRAT_8 0
#define LV_FONT_MONTSERRAT_10 0
#define LV_FONT_MONTSERRAT_12 0
#define LV_FONT_MONTSERRAT_14 0
#define LV_FONT_MONTSERRAT_16 1
#define LV_FONT_MONTSERRAT_14 1
#define LV_FONT_MONTSERRAT_16 0
#define LV_FONT_MONTSERRAT_18 0
#define LV_FONT_MONTSERRAT_20 0
#define LV_FONT_MONTSERRAT_22 0
@ -365,6 +409,7 @@ typedef void * lv_indev_drv_user_data_t; /*Type of user data in the i
/*Pixel perfect monospace font
* http://pelulamu.net/unscii/ */
#define LV_FONT_UNSCII_8 0
#define LV_FONT_UNSCII_16 0
/* Optionally declare your custom fonts here.
* You can use these fonts as default font too
@ -379,11 +424,20 @@ typedef void * lv_indev_drv_user_data_t; /*Type of user data in the i
* but with > 10,000 characters if you see issues probably you need to enable it.*/
#define LV_FONT_FMT_TXT_LARGE 0
/* Enables/disables support for compressed fonts. If it's disabled, compressed
* glyphs cannot be processed by the library and won't be rendered.
*/
#define LV_USE_FONT_COMPRESSED 1
/* Enable subpixel rendering */
#define LV_USE_FONT_SUBPX 1
#if LV_USE_FONT_SUBPX
/* Set the pixel order of the display.
* Important only if "subpx fonts" are used.
* With "normal" font it doesn't matter.
*/
#define LV_FONT_SUBPX_BGR 0
#endif
/*Declare the type of the user data of fonts (can be e.g. `void *`, `int`, `struct`)*/
typedef void * lv_font_user_data_t;
@ -396,34 +450,37 @@ typedef void * lv_font_user_data_t;
/* No theme, you can apply your styles as you need
* No flags. Set LV_THEME_DEFAULT_FLAG 0 */
#define LV_USE_THEME_EMPTY 1
#define LV_USE_THEME_EMPTY 1
/*Simple to the create your theme based on it
* No flags. Set LV_THEME_DEFAULT_FLAG 0 */
#define LV_USE_THEME_TEMPLATE 1
#define LV_USE_THEME_TEMPLATE 1
/* A fast and impressive theme.
* Flags:
* LV_THEME_MATERIAL_FLAG_LIGHT: light theme
* LV_THEME_MATERIAL_FLAG_DARK: dark theme*/
#define LV_USE_THEME_MATERIAL 1
* LV_THEME_MATERIAL_FLAG_DARK: dark theme
* LV_THEME_MATERIAL_FLAG_NO_TRANSITION: disable transitions (state change animations)
* LV_THEME_MATERIAL_FLAG_NO_FOCUS: disable indication of focused state)
* */
#define LV_USE_THEME_MATERIAL 1
/* Mono-color theme for monochrome displays.
* If LV_THEME_DEFAULT_COLOR_PRIMARY is LV_COLOR_BLACK the
* texts and borders will be black and the background will be
* white. Else the colors are inverted.
* No flags. Set LV_THEME_DEFAULT_FLAG 0 */
#define LV_USE_THEME_MONO 1
#define LV_USE_THEME_MONO 1
#define LV_THEME_DEFAULT_INCLUDE <stdint.h> /*Include a header for the init. function*/
#define LV_THEME_DEFAULT_INIT lv_theme_material_init
#define LV_THEME_DEFAULT_COLOR_PRIMARY LV_COLOR_RED
#define LV_THEME_DEFAULT_COLOR_SECONDARY LV_COLOR_BLUE
#define LV_THEME_DEFAULT_COLOR_PRIMARY lv_color_hex(0x01a2b1)
#define LV_THEME_DEFAULT_COLOR_SECONDARY lv_color_hex(0x44d1b6)
#define LV_THEME_DEFAULT_FLAG LV_THEME_MATERIAL_FLAG_LIGHT
#define LV_THEME_DEFAULT_FONT_SMALL &lv_font_montserrat_16
#define LV_THEME_DEFAULT_FONT_NORMAL &lv_font_montserrat_16
#define LV_THEME_DEFAULT_FONT_SUBTITLE &lv_font_montserrat_16
#define LV_THEME_DEFAULT_FONT_TITLE &lv_font_montserrat_16
#define LV_THEME_DEFAULT_FONT_SMALL &lv_font_montserrat_14
#define LV_THEME_DEFAULT_FONT_NORMAL &lv_font_montserrat_14
#define LV_THEME_DEFAULT_FONT_SUBTITLE &lv_font_montserrat_14
#define LV_THEME_DEFAULT_FONT_TITLE &lv_font_montserrat_14
/*=================
* Text settings
@ -456,7 +513,7 @@ typedef void * lv_font_user_data_t;
/* Support bidirectional texts.
* Allows mixing Left-to-Right and Right-to-Left texts.
* The direction will be processed according to the Unicode Bidirectioanl Algorithm:
* The direction will be processed according to the Unicode Bidirectional Algorithm:
* https://www.w3.org/International/articles/inline-bidi-markup/uba-basics*/
#define LV_USE_BIDI 0
#if LV_USE_BIDI
@ -498,7 +555,7 @@ typedef void * lv_obj_user_data_t;
#endif
#endif
/*1: enable `lv_obj_realaign()` based on `lv_obj_align()` parameters*/
/*1: enable `lv_obj_realign()` based on `lv_obj_align()` parameters*/
#define LV_USE_OBJ_REALIGN 1
/* Enable to make the object clickable on a larger area.
@ -529,6 +586,9 @@ typedef void * lv_obj_user_data_t;
/*Calendar (dependencies: -)*/
#define LV_USE_CALENDAR 1
#if LV_USE_CALENDAR
# define LV_CALENDAR_WEEK_STARTS_MONDAY 0
#endif
/*Canvas (dependencies: lv_img)*/
#define LV_USE_CANVAS 1
@ -613,7 +673,7 @@ typedef void * lv_obj_user_data_t;
* 1: Some extra precision
* 2: Best precision
*/
# define LV_LINEMETER_PRECISE 0
# define LV_LINEMETER_PRECISE 1
#endif
/*Mask (dependencies: -)*/
@ -667,6 +727,7 @@ typedef void * lv_obj_user_data_t;
#define LV_USE_TABLE 1
#if LV_USE_TABLE
# define LV_TABLE_COL_MAX 12
# define LV_TABLE_CELL_STYLE_CNT 4
#endif
/*Tab (dependencies: lv_page, lv_btnm)*/

View file

@ -0,0 +1,66 @@
/// A parameter of C functions.
///
/// This struct represents all relevant information we can extract from the C function declaration
/// of a LVGL public interface. We can use this information to do inference for how the parameter
/// should be represented in a safe Rust API.
#[derive(Clone, Debug)]
pub struct CParameter {
/// The name of the parameter in the C code.
pub name: String,
/// This is the raw representation of the Rust equivalent of the C type.
pub c_type: String,
/// Takes a pointer to a type that is referenced by the LVGL code permanently.
pub scope: ParameterScope,
/// The pointer is not marked as `*const` so the referenced object can be mutated.
pub mutable: bool,
/// We need to check if the value is optional in the C code. We need to check
/// the function comments for this information.
/// - "if NULL then"
/// - "if not NULL then"
/// - "NULL to"
pub allow_none: bool,
/// Comment associated with the parameter, if exists.
pub comment: Option<String>,
}
#[derive(Clone, Debug)]
pub enum ParameterScope {
Call,
Static,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FunctionKind {
Constructor,
Method,
Function,
}
/// Inference from a LVGL C API function.
#[derive(Clone, Debug)]
pub struct Function {
/// Name of the function in the LVGL C API.
pub name: String,
/// Comment associated with the function, if exists.
pub comment: Option<String>,
pub kind: FunctionKind,
pub parameters: Vec<CParameter>,
pub ret: Return,
}
#[derive(Clone, Debug)]
pub enum Return {
Value(Option<CParameter>),
/// If the return is a LVGL result
ResultError(CParameter),
}

View file

@ -1,3 +1,5 @@
mod analysis;
use inflector::cases::pascalcase::to_pascal_case;
use lazy_static::lazy_static;
use proc_macro2::{Ident, TokenStream};
@ -44,6 +46,12 @@ pub struct LvWidget {
methods: Vec<LvFunc>,
}
impl LvWidget {
fn pascal_name(&self) -> String {
to_pascal_case(&self.name)
}
}
impl Rusty for LvWidget {
type Parent = ();
@ -53,7 +61,7 @@ impl Rusty for LvWidget {
return Err(WrapperError::Skip);
}
let widget_name = format_ident!("{}", to_pascal_case(self.name.as_str()));
let widget_name = format_ident!("{}", self.pascal_name());
let methods: Vec<TokenStream> = self.methods.iter().flat_map(|m| m.code(self)).collect();
Ok(quote! {
define_object!(#widget_name);
@ -90,6 +98,7 @@ impl Rusty for LvFunc {
type Parent = LvWidget;
fn code(&self, parent: &Self::Parent) -> WrapperResult<TokenStream> {
let widget_name = format_ident!("{}", parent.pascal_name());
let templ = format!("{}{}_", LIB_PREFIX, parent.name.as_str());
let new_name = self.name.replace(templ.as_str(), "");
let func_name = format_ident!("{}", new_name);
@ -99,12 +108,12 @@ impl Rusty for LvFunc {
if new_name.as_str().eq("create") {
return Ok(quote! {
pub fn new<C>(parent: &mut C) -> crate::LvResult<Self>
where
C: crate::NativeObject,
{
pub fn create(parent: &mut impl crate::NativeObject, copy: Option<&#widget_name>) -> crate::LvResult<Self> {
unsafe {
let ptr = lvgl_sys::#original_func_name(parent.raw()?.as_mut(), core::ptr::null_mut());
let ptr = lvgl_sys::#original_func_name(
parent.raw()?.as_mut(),
copy.map(|c| c.raw().unwrap().as_mut() as *mut lvgl_sys::lv_obj_t).unwrap_or(core::ptr::null_mut() as *mut lvgl_sys::lv_obj_t),
);
if let Some(raw) = core::ptr::NonNull::new(ptr) {
let core = <crate::Obj as crate::Widget>::from_raw(raw);
Ok(Self { core })
@ -114,6 +123,15 @@ impl Rusty for LvFunc {
}
}
pub fn create_at(parent: &mut impl crate::NativeObject) -> crate::LvResult<Self> {
Ok(Self::create(parent, None)?)
}
pub fn new() -> crate::LvResult<Self> {
let mut parent = crate::display::DefaultDisplay::get_scr_act()?;
Ok(Self::create_at(&mut parent)?)
}
});
}
@ -338,6 +356,9 @@ impl Rusty for LvType {
Some(name) => {
let val = if self.is_str() {
quote!(&cstr_core::CStr)
} else if self.literal_name.contains("lv_") {
let ident = format_ident!("{}", name);
quote!(&#ident)
} else {
let ident = format_ident!("{}", name);
quote!(#ident)
@ -631,13 +652,12 @@ mod test {
define_object!(Arc);
impl Arc {
pub fn new<C>(parent: &mut C) -> crate::LvResult<Self>
where
C: crate::NativeObject,
{
pub fn create(parent: &mut impl crate::NativeObject, copy: Option<&Arc>) -> crate::LvResult<Self> {
unsafe {
let ptr = lvgl_sys::lv_arc_create(parent.raw()?.as_mut(), core::ptr::null_mut());
let ptr = lvgl_sys::lv_arc_create(
parent.raw()?.as_mut(),
copy.map(|c| c.raw().unwrap().as_mut() as *mut lvgl_sys::lv_obj_t).unwrap_or(core::ptr::null_mut() as *mut lvgl_sys::lv_obj_t),
);
if let Some(raw) = core::ptr::NonNull::new(ptr) {
let core = <crate::Obj as crate::Widget>::from_raw(raw);
Ok(Self { core })
@ -646,6 +666,15 @@ mod test {
}
}
}
pub fn create_at(parent: &mut impl crate::NativeObject) -> crate::LvResult<Self> {
Ok(Self::create(parent, None)?)
}
pub fn new() -> crate::LvResult<Self> {
let mut parent = crate::display::DefaultDisplay::get_scr_act()?;
Ok(Self::create_at(&mut parent)?)
}
}
};

View file

@ -114,6 +114,7 @@ fn main() {
let bindings = bindgen::Builder::default()
.header(shims_dir.join("lvgl_sys.h").to_str().unwrap())
.generate_comments(false)
.derive_default(true)
.layout_tests(false)
.use_core()
.rustfmt_bindings(true)

View file

@ -14,11 +14,14 @@ build = "build.rs"
[dependencies]
lvgl-sys = { version = "0.5.2", path = "../lvgl-sys" }
cty = "0.2.1"
embedded-graphics = "0.6.2"
cstr_core = "0.2.3"
bitflags = "1.2.1"
parking_lot = "0.11.1"
embedded-graphics = { version = "0.6.2", optional = true }
[features]
default = []
embedded_graphics = ["embedded-graphics"]
alloc = ["cstr_core/alloc"]
lvgl_alloc = ["alloc"]
@ -30,28 +33,33 @@ lvgl-sys = { version = "0.5.2", path = "../lvgl-sys" }
[dev-dependencies]
embedded-graphics-simulator = "0.2.1"
heapless = "0.5.5"
[[example]]
name = "app"
path = "../examples/app.rs"
required-features = ["alloc", "embedded_graphics"]
[[example]]
name = "demo"
path = "../examples/demo.rs"
required-features = ["alloc"]
required-features = ["alloc", "embedded_graphics"]
[[example]]
name = "bar"
path = "../examples/bar.rs"
required-features = ["alloc"]
required-features = ["alloc", "embedded_graphics"]
[[example]]
name = "button_click"
path = "../examples/button_click.rs"
required-features = ["alloc"]
required-features = ["alloc", "embedded_graphics"]
[[example]]
name = "gauge"
path = "../examples/gauge.rs"
required-features = ["alloc", "embedded_graphics"]
[[example]]
name = "arc"
path = "../examples/arc.rs"
required-features = ["lvgl_alloc"]
required-features = ["alloc", "embedded_graphics"]

View file

@ -9,12 +9,12 @@ pub struct LvglAlloc;
unsafe impl GlobalAlloc for LvglAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
// Make sure LVGL is initialized!
crate::lvgl_init();
crate::init();
lvgl_sys::lv_mem_alloc(layout.size() as lvgl_sys::size_t) as *mut u8
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
crate::lvgl_init();
crate::init();
lvgl_sys::lv_mem_free(ptr as *const cty::c_void)
}
}

268
lvgl/src/display.rs Normal file
View file

@ -0,0 +1,268 @@
use crate::functions::CoreError;
use crate::{disp_drv_register, disp_get_default, get_str_act};
use crate::{Box, RunOnce};
use crate::{Color, Obj};
use core::cell::RefCell;
use core::mem::MaybeUninit;
use core::ptr::NonNull;
use core::{ptr, result};
use parking_lot::const_mutex;
use parking_lot::Mutex;
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;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum DisplayError {
NotAvailable,
FailedToRegister,
NotRegistered,
}
type Result<T> = result::Result<T, DisplayError>;
pub struct Display {
pub(crate) disp: NonNull<lvgl_sys::lv_disp_t>,
}
impl Display {
pub(crate) fn from_raw(disp: NonNull<lvgl_sys::lv_disp_t>) -> Self {
Self { disp }
}
pub fn register<F, const N: usize>(
draw_buffer: &'static DrawBuffer<N>,
display_update: F,
) -> Result<Self>
where
F: FnMut(&DisplayRefresh<N>) + 'static,
{
let mut display_diver = DisplayDriver::new(draw_buffer, display_update)?;
Ok(disp_drv_register(&mut display_diver)?)
}
pub fn get_scr_act(&self) -> Result<Obj> {
Ok(get_str_act(Some(&self))?)
}
}
impl Default for Display {
fn default() -> Self {
disp_get_default().expect("LVGL must be INITIALIZED")
}
}
#[derive(Copy, Clone)]
pub struct DefaultDisplay {}
impl DefaultDisplay {
/// Gets the screen active of the default display.
pub fn get_scr_act() -> Result<Obj> {
Ok(get_str_act(None)?)
}
}
pub struct DrawBuffer<const N: usize> {
initialized: RunOnce,
refresh_buffer: Mutex<RefCell<[MaybeUninit<lvgl_sys::lv_color_t>; N]>>,
}
impl<const N: usize> DrawBuffer<N> {
pub const fn new() -> Self {
Self {
initialized: RunOnce::new(),
refresh_buffer: const_mutex(RefCell::new([MaybeUninit::uninit(); N])),
}
}
fn get_ptr(&self) -> Option<Box<lvgl_sys::lv_disp_buf_t>> {
if self.initialized.swap_and_check() {
// TODO: needs to be 'static somehow
// Cannot be in the DrawBuffer struct because the type `lv_disp_buf_t` contains a raw
// pointer and raw pointers are not Send and consequently cannot be in `static` variables.
let mut inner: MaybeUninit<lvgl_sys::lv_disp_buf_t> = MaybeUninit::uninit();
let primary_buffer_guard = self.refresh_buffer.lock();
let draw_buf = unsafe {
lvgl_sys::lv_disp_buf_init(
inner.as_mut_ptr(),
primary_buffer_guard.borrow_mut().as_mut_ptr() as *mut _ as *mut cty::c_void,
ptr::null_mut(),
N as u32,
);
inner.assume_init()
};
Some(Box::new(draw_buf))
} else {
None
}
}
}
pub struct DisplayDriver {
pub(crate) disp_drv: lvgl_sys::lv_disp_drv_t,
}
impl DisplayDriver {
pub fn new<F, const N: usize>(
draw_buffer: &'static DrawBuffer<N>,
display_update_callback: F,
) -> Result<Self>
where
F: FnMut(&DisplayRefresh<N>) + 'static,
{
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)?;
disp_drv.user_data = Box::into_raw(Box::new(display_update_callback)) as *mut _
as lvgl_sys::lv_disp_drv_user_data_t;
// Sets trampoline pointer to the function implementation that uses the `F` type for a
// refresh buffer of size N specifically.
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.
Ok(Self { disp_drv })
}
}
/// Represents a sub-area of the display that is being updated.
pub struct Area {
pub x1: i16,
pub x2: i16,
pub y1: 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 area: Area,
pub colors: [Color; N],
}
#[cfg(feature = "embedded_graphics")]
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,
area: *const lvgl_sys::lv_area_t,
color_p: *mut lvgl_sys::lv_color_t,
) where
F: FnMut(&DisplayRefresh<N>) + 'static,
{
let display_driver = *disp_drv;
if !display_driver.user_data.is_null() {
let callback = &mut *(display_driver.user_data as *mut F);
let mut colors = [Color::default(); N];
let mut color_len = 0;
for color in &mut colors {
let lv_color = *color_p.add(color_len);
*color = Color::from_raw(lv_color);
color_len += 1;
}
let update = DisplayRefresh {
area: Area {
x1: (*area).x1,
x2: (*area).x2,
y1: (*area).y1,
y2: (*area).y2,
},
colors,
};
callback(&update);
}
// Indicate to LVGL that we are ready with the flushing
lvgl_sys::lv_disp_flush_ready(disp_drv);
}
impl From<CoreError> for DisplayError {
fn from(err: CoreError) -> Self {
use DisplayError::*;
match err {
CoreError::ResourceNotAvailable => NotAvailable,
CoreError::OperationFailed => NotAvailable,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests;
#[test]
fn get_scr_act_return_display() {
tests::initialize_test();
let _screen = get_str_act(None).expect("We can get the active screen");
}
#[test]
fn get_default_display() {
tests::initialize_test();
let display = Display::default();
let _screen_direct = display
.get_scr_act()
.expect("Return screen directly from the display instance");
let _screen_default =
DefaultDisplay::get_scr_act().expect("Return screen from the default display");
}
#[test]
fn register_display_directly() -> Result<()> {
tests::initialize_test();
let display = Display::default();
let _screen = display
.get_scr_act()
.expect("Return screen directly from the display instance");
Ok(())
}
}

56
lvgl/src/functions.rs Normal file
View file

@ -0,0 +1,56 @@
use crate::display::{Display, DisplayDriver};
use crate::{Obj, Widget};
use core::ptr::NonNull;
use core::time::Duration;
use core::{ptr, result};
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum CoreError {
ResourceNotAvailable,
OperationFailed,
}
type Result<T> = result::Result<T, CoreError>;
/// Register own buffer
pub(crate) fn disp_drv_register(disp_drv: &mut DisplayDriver) -> Result<Display> {
let disp_ptr = unsafe { lvgl_sys::lv_disp_drv_register(&mut disp_drv.disp_drv as *mut _) };
Ok(Display::from_raw(
NonNull::new(disp_ptr).ok_or(CoreError::OperationFailed)?,
))
}
pub(crate) fn disp_get_default() -> Result<Display> {
let disp_ptr = unsafe { lvgl_sys::lv_disp_get_default() };
Ok(Display::from_raw(
NonNull::new(disp_ptr).ok_or(CoreError::OperationFailed)?,
))
}
pub(crate) fn get_str_act(disp: Option<&Display>) -> Result<Obj> {
let scr_ptr = unsafe {
lvgl_sys::lv_disp_get_scr_act(
disp.map(|d| d.disp.as_ptr())
.unwrap_or(ptr::null_mut() as *mut lvgl_sys::lv_disp_t),
)
};
Ok(Obj::from_raw(
NonNull::new(scr_ptr).ok_or(CoreError::ResourceNotAvailable)?,
))
}
/// You have to call this function periodically.
/// Expects a `tick_period` duration as argument which is the call period of this
/// function in milliseconds.
#[inline]
pub fn tick_inc(tick_period: Duration) {
unsafe {
lvgl_sys::lv_tick_inc(tick_period.as_millis() as u32);
}
}
/// Call it periodically to handle tasks.
#[inline]
pub fn task_handler() {
unsafe { lvgl_sys::lv_task_handler() };
}

View file

@ -17,7 +17,10 @@
#[macro_use]
extern crate bitflags;
#[cfg(feature = "lvgl_alloc")]
#[macro_use]
mod lv_core;
#[cfg(feature = "alloc")]
extern crate alloc;
// We can ONLY use `alloc::boxed::Box` if `lvgl_alloc` is enabled.
@ -31,12 +34,6 @@ use ::alloc::boxed::Box;
#[cfg(feature = "lvgl_alloc")]
mod allocator;
mod support;
mod ui;
#[macro_use]
mod lv_core;
pub mod widgets;
#[cfg(not(feature = "lvgl_alloc"))]
pub(crate) mod mem;
@ -47,22 +44,54 @@ pub(crate) mod mem;
#[cfg(not(feature = "lvgl_alloc"))]
use crate::mem::Box;
mod display;
pub use display::*;
mod functions;
mod support;
pub mod widgets;
use core::sync::atomic::{AtomicBool, Ordering};
pub use functions::*;
pub use lv_core::*;
pub use support::*;
pub use ui::*;
use core::sync::atomic::{AtomicBool, Ordering};
struct RunOnce(AtomicBool);
// Initialize LVGL only once.
static LVGL_INITIALIZED: AtomicBool = AtomicBool::new(false);
impl RunOnce {
const fn new() -> Self {
Self(AtomicBool::new(false))
}
pub(crate) fn lvgl_init() {
if LVGL_INITIALIZED
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
fn swap_and_check(&self) -> bool {
self.0
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
}
}
static LVGL_INITIALIZED: RunOnce = RunOnce::new();
pub fn init() {
if LVGL_INITIALIZED.swap_and_check() {
unsafe {
lvgl_sys::lv_init();
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::display::{Display, DrawBuffer};
pub(crate) fn initialize_test() {
init();
const REFRESH_BUFFER_SIZE: usize = 64 * 64 / 10;
static DRAW_BUFFER: DrawBuffer<REFRESH_BUFFER_SIZE> = DrawBuffer::new();
static ONCE_INIT: RunOnce = RunOnce::new();
if ONCE_INIT.swap_and_check() {
let _ = Display::register(&DRAW_BUFFER, |_| {}).unwrap();
}
}
}

View file

@ -1,5 +1,4 @@
use crate::lv_core::style::Style;
use crate::Box;
use crate::{Align, LvError, LvResult};
use core::ptr;
@ -35,14 +34,15 @@ pub trait Widget: NativeObject {
/// Construct an instance of the object from a raw pointer.
///
/// # Safety
/// Provided the LVGL library can allocate memory this should be safe.
///
unsafe fn from_raw(raw_pointer: ptr::NonNull<lvgl_sys::lv_obj_t>) -> Self;
fn from_raw(raw_pointer: ptr::NonNull<lvgl_sys::lv_obj_t>) -> Self;
fn add_style(&self, part: Self::Part, style: Style) -> LvResult<()> {
fn add_style(&self, part: Self::Part, style: &mut Style) -> LvResult<()> {
unsafe {
lvgl_sys::lv_obj_add_style(self.raw()?.as_mut(), part.into(), Box::into_raw(style.raw));
lvgl_sys::lv_obj_add_style(
self.raw()?.as_mut(),
part.into(),
style.raw.as_mut() as *mut _,
);
};
Ok(())
}
@ -104,7 +104,7 @@ impl Widget for Obj {
type SpecialEvent = ();
type Part = Part;
unsafe fn from_raw(raw: ptr::NonNull<lvgl_sys::lv_obj_t>) -> Self {
fn from_raw(raw: ptr::NonNull<lvgl_sys::lv_obj_t>) -> Self {
Self { raw: raw.as_ptr() }
}
}
@ -167,7 +167,7 @@ macro_rules! define_object {
type SpecialEvent = $event_type;
type Part = $part_type;
unsafe fn from_raw(raw_pointer: core::ptr::NonNull<lvgl_sys::lv_obj_t>) -> Self {
fn from_raw(raw_pointer: core::ptr::NonNull<lvgl_sys::lv_obj_t>) -> Self {
Self {
core: $crate::Obj::from_raw(raw_pointer),
}
@ -176,6 +176,38 @@ macro_rules! define_object {
};
}
// define_object!(Rafael);
//
// impl Rafael {
// pub fn create(
// parent: &mut impl crate::NativeObject,
// copy: Option<&Rafael>,
// ) -> crate::LvResult<Self> {
// unsafe {
// let ptr = lvgl_sys::lv_arc_create(
// parent.raw()?.as_mut(),
// copy.map(|c| c.raw().unwrap().as_mut() as *mut lvgl_sys::lv_obj_t)
// .unwrap_or(core::ptr::null_mut() as *mut lvgl_sys::lv_obj_t),
// );
// if let Some(raw) = core::ptr::NonNull::new(ptr) {
// let core = <crate::Obj as crate::Widget>::from_raw(raw);
// Ok(Self { core })
// } else {
// Err(crate::LvError::InvalidReference)
// }
// }
// }
//
// pub fn create_at(parent: &mut impl crate::NativeObject) -> crate::LvResult<Self> {
// Ok(Self::create(parent, None)?)
// }
//
// pub fn new() -> crate::LvResult<Self> {
// let mut parent = crate::display::DefaultDisplay::get_scr_act()?;
// Ok(Self::create_at(&mut parent)?)
// }
// }
bitflags! {
pub struct State: u32 {
/// Normal, released

View file

@ -11,7 +11,7 @@ pub(crate) struct Box<T>(NonNull<T>);
impl<T> Box<T> {
/// Allocate memory using LVGL memory API and place `T` in the LVGL tracked memory.
pub fn new(value: T) -> Box<T> {
pub fn new(value: T) -> Self {
let size = mem::size_of::<T>();
let inner = unsafe {
let ptr = lvgl_sys::lv_mem_alloc(size as lvgl_sys::size_t) as *mut T;
@ -29,10 +29,10 @@ impl<T> Box<T> {
p
})
.unwrap_or_else(|| {
panic!("Could not allocate memory {} bytes", size);
panic!("Could not allocate memory {} bytes: {:?}", size, mem_info());
})
};
Box(inner)
Self(inner)
}
pub fn into_raw(self) -> *mut T {
@ -69,14 +69,33 @@ impl<T> AsMut<T> for Box<T> {
}
}
fn mem_info() -> lvgl_sys::lv_mem_monitor_t {
let mut info = lvgl_sys::lv_mem_monitor_t {
total_size: 0,
free_cnt: 0,
free_size: 0,
free_biggest_size: 0,
used_cnt: 0,
max_used: 0,
used_pct: 0,
frag_pct: 0,
};
unsafe {
lvgl_sys::lv_mem_monitor(&mut info as *mut _);
}
info
}
#[cfg(test)]
mod test {
use super::*;
use crate::mem::mem_info;
use crate::*;
use std::vec::Vec;
#[test]
fn place_value_in_lv_mem() {
crate::lvgl_init();
tests::initialize_test();
let v = Box::new(5);
drop(v);
@ -86,7 +105,7 @@ mod test {
#[test]
fn place_complex_value_in_lv_mem() {
crate::lvgl_init();
tests::initialize_test();
#[repr(C)]
#[derive(Debug)]
@ -135,21 +154,4 @@ mod test {
// If this fails, we are leaking memory! BOOM! \o/
assert_eq!(initial_mem_info.free_size, final_info.free_size)
}
fn mem_info() -> lvgl_sys::lv_mem_monitor_t {
let mut info = lvgl_sys::lv_mem_monitor_t {
total_size: 0,
free_cnt: 0,
free_size: 0,
free_biggest_size: 0,
used_cnt: 0,
max_used: 0,
used_pct: 0,
frag_pct: 0,
};
unsafe {
lvgl_sys::lv_mem_monitor(&mut info as *mut _);
}
info
}
}

View file

@ -1,6 +1,9 @@
use crate::display::DisplayError;
use crate::Widget;
use core::convert::{TryFrom, TryInto};
use core::ptr::NonNull;
#[cfg(feature = "embedded_graphics")]
use embedded_graphics::pixelcolor::{Rgb565, Rgb888};
pub type LvResult<T> = Result<T, LvError>;
@ -13,7 +16,18 @@ pub enum LvError {
AlreadyInUse,
}
#[derive(Clone)]
impl From<DisplayError> for LvError {
fn from(err: DisplayError) -> Self {
use LvError::*;
match err {
DisplayError::NotAvailable => Uninitialized,
DisplayError::FailedToRegister => InvalidReference,
DisplayError::NotRegistered => Uninitialized,
}
}
}
#[derive(Copy, Clone, Default)]
pub struct Color {
pub(crate) raw: lvgl_sys::lv_color_t,
}
@ -41,6 +55,7 @@ impl Color {
}
}
#[cfg(feature = "embedded_graphics")]
impl From<Color> for Rgb888 {
fn from(color: Color) -> Self {
unsafe {
@ -53,6 +68,7 @@ impl From<Color> for Rgb888 {
}
}
#[cfg(feature = "embedded_graphics")]
impl From<Color> for Rgb565 {
fn from(color: Color) -> Self {
unsafe {

View file

@ -1,203 +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::lvgl_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
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>);
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>(&mut 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)?)
}

View file

@ -10,6 +10,37 @@ impl Label {
}
}
#[cfg(feature = "alloc")]
mod alloc_imp {
use crate::widgets::Label;
use crate::LvError;
use cstr_core::CString;
use core::convert::TryFrom;
impl<S: AsRef<str>> From<S> for Label {
fn from(text: S) -> Self {
// text.try_into().unwrap()
let text_cstr = CString::new(text.as_ref()).unwrap();
let mut label = Label::new().unwrap();
label.set_text(text_cstr.as_c_str()).unwrap();
label
}
}
// Issue link: https://github.com/rust-lang/rust/issues/50133
//
// impl<S: AsRef<str>> TryFrom<S> for Label {
// type Error = LvError;
// fn try_from(text: S) -> Result<Self, Self::Error> {
// let text_cstr = CString::new(text.as_ref())?;
// let mut label = Label::new()?;
// label.set_text(text_cstr.as_c_str())?;
// Ok(label)
// }
// }
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(u8)]
pub enum LabelAlign {