From 805cdfc567fee47a23e7ec7c751a2343066b9fca Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Sat, 29 May 2021 20:03:36 +0200 Subject: [PATCH 1/3] Squash api-review branch Revising the public API for better flexibility Use old approach, write directly to DrawTarget Simplified API for display initialization Possibility to register a shared native display Fix shared display Update demo app to use new api Use ref Add basic API functions Add required features Update examples Make disp buffer and driver statically allocated Allow to create statically allocated DrawBuffer Make DrawBuffer customizable using const generics Update default features to empty Allow users to define a display refresh callback closure Make use of embedded_graphics optional Update app example Remove dependency on heapless Wrap alloc extension in its own module --- examples/Cargo.toml | 1 - examples/app.rs | 40 ++++++ examples/arc.rs | 50 ++++--- examples/demo.rs | 93 +++++++++--- examples/include/lv_conf.h | 121 ++++++++++++---- lvgl-codegen/src/analysis.rs | 66 +++++++++ lvgl-codegen/src/lib.rs | 53 +++++-- lvgl-sys/build.rs | 1 + lvgl/Cargo.toml | 20 ++- lvgl/src/allocator.rs | 4 +- lvgl/src/display.rs | 268 +++++++++++++++++++++++++++++++++++ lvgl/src/functions.rs | 56 ++++++++ lvgl/src/lib.rs | 64 ++++++--- lvgl/src/lv_core/obj.rs | 50 +++++-- lvgl/src/mem.rs | 48 ++++--- lvgl/src/support.rs | 18 ++- lvgl/src/ui.rs | 231 ------------------------------ lvgl/src/widgets/label.rs | 31 ++++ 18 files changed, 842 insertions(+), 373 deletions(-) create mode 100644 examples/app.rs create mode 100644 lvgl-codegen/src/analysis.rs create mode 100644 lvgl/src/display.rs create mode 100644 lvgl/src/functions.rs delete mode 100644 lvgl/src/ui.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 52dd9342..278e6fcc 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -10,7 +10,6 @@ lvgl = { path = "../lvgl" } lvgl-sys = { path = "../lvgl-sys" } embedded-graphics = "0.6" embedded-graphics-simulator = "0.2.0" -heapless = "0.5.5" cstr_core = { version = "0.2.0", features = ["alloc"] } [[example]] diff --git a/examples/app.rs b/examples/app.rs new file mode 100644 index 00000000..a77e0e6f --- /dev/null +++ b/examples/app.rs @@ -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 = 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 = 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(); +} diff --git a/examples/arc.rs b/examples/arc.rs index 28c03f63..c3eb3476 100644 --- a/examples/arc.rs +++ b/examples/arc.rs @@ -4,11 +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 std::time::Instant; +use lvgl_sys; +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 { @@ -35,58 +40,67 @@ fn main() -> Result<(), LvError> { } fn run_arc_demo() -> Result<(), LvError> { + lvgl::init(); let display: SimulatorDisplay = SimulatorDisplay::new(Size::new(lvgl::HOR_RES_MAX, lvgl::VER_RES_MAX)); 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(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 { @@ -94,9 +108,7 @@ fn run_arc_demo() -> Result<(), LvError> { _ => {} } } - - ui.tick_inc(loop_started.elapsed()); - loop_started = Instant::now(); + thread::sleep(Duration::from_millis(15)); } Ok(()) diff --git a/examples/demo.rs b/examples/demo.rs index aaaaaee2..21a44e5f 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -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,8 +8,11 @@ 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}; @@ -16,50 +20,77 @@ fn main() -> Result<(), LvError> { let display: SimulatorDisplay = SimulatorDisplay::new(Size::new(lvgl::HOR_RES_MAX, lvgl::VER_RES_MAX)); - 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 = DrawBuffer::new(); + // + // const NUMBER_OF_DISPLAYS: usize = 1; + // static DISPLAY_REGISTRY: DisplayRegistry = 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; @@ -68,8 +99,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 { @@ -77,12 +111,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(()) } @@ -97,3 +131,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 +} diff --git a/examples/include/lv_conf.h b/examples/include/lv_conf.h index d6be99da..cc5f026d 100644 --- a/examples/include/lv_conf.h +++ b/examples/include/lv_conf.h @@ -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) @@ -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 /*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)*/ diff --git a/lvgl-codegen/src/analysis.rs b/lvgl-codegen/src/analysis.rs new file mode 100644 index 00000000..3b9f7fe5 --- /dev/null +++ b/lvgl-codegen/src/analysis.rs @@ -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, +} + +#[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, + + pub kind: FunctionKind, + + pub parameters: Vec, + + pub ret: Return, +} + +#[derive(Clone, Debug)] +pub enum Return { + Value(Option), + + /// If the return is a LVGL result + ResultError(CParameter), +} diff --git a/lvgl-codegen/src/lib.rs b/lvgl-codegen/src/lib.rs index ac234ed7..fe2a8ad8 100644 --- a/lvgl-codegen/src/lib.rs +++ b/lvgl-codegen/src/lib.rs @@ -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, } +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 = 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 { + 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(parent: &mut C) -> crate::LvResult - where - C: crate::NativeObject, - { + pub fn create(parent: &mut impl crate::NativeObject, copy: Option<&#widget_name>) -> crate::LvResult { 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 = ::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 { + Ok(Self::create(parent, None)?) + } + + pub fn new() -> crate::LvResult { + 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(parent: &mut C) -> crate::LvResult - where - C: crate::NativeObject, - { - + pub fn create(parent: &mut impl crate::NativeObject, copy: Option<&Arc>) -> crate::LvResult { 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 = ::from_raw(raw); Ok(Self { core }) @@ -646,6 +666,15 @@ mod test { } } } + + pub fn create_at(parent: &mut impl crate::NativeObject) -> crate::LvResult { + Ok(Self::create(parent, None)?) + } + + pub fn new() -> crate::LvResult { + let mut parent = crate::display::DefaultDisplay::get_scr_act()?; + Ok(Self::create_at(&mut parent)?) + } } }; diff --git a/lvgl-sys/build.rs b/lvgl-sys/build.rs index c427ed1b..71452852 100644 --- a/lvgl-sys/build.rs +++ b/lvgl-sys/build.rs @@ -118,6 +118,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) diff --git a/lvgl/Cargo.toml b/lvgl/Cargo.toml index a7b25267..82bfb257 100644 --- a/lvgl/Cargo.toml +++ b/lvgl/Cargo.toml @@ -14,11 +14,14 @@ build = "build.rs" [dependencies] lvgl-sys = { version = "0.5.2", path = "../lvgl-sys" } cty = "0.2.2" -embedded-graphics = "0.7.1" +embedded-graphics = { version = "0.7.1", optional = true } cstr_core = { version = "0.2.6", default-features = false, features = ["alloc"] } bitflags = "1.3.2" +parking_lot = "0.11.1" [features] +default = [] +embedded_graphics = ["embedded-graphics"] alloc = ["cstr_core/alloc"] lvgl_alloc = ["alloc"] use-vendored-config = ["lvgl-sys/use-vendored-config"] @@ -31,28 +34,33 @@ lvgl-sys = { version = "0.5.2", path = "../lvgl-sys" } [dev-dependencies] embedded-graphics-simulator = "0.4.0" -heapless = "0.7.16" + +[[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"] diff --git a/lvgl/src/allocator.rs b/lvgl/src/allocator.rs index 1c4cd06e..8d414f07 100644 --- a/lvgl/src/allocator.rs +++ b/lvgl/src/allocator.rs @@ -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 cty::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) } } diff --git a/lvgl/src/display.rs b/lvgl/src/display.rs new file mode 100644 index 00000000..c140e3f6 --- /dev/null +++ b/lvgl/src/display.rs @@ -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 = result::Result; + +pub struct Display { + pub(crate) disp: NonNull, +} + +impl Display { + pub(crate) fn from_raw(disp: NonNull) -> Self { + Self { disp } + } + + pub fn register( + draw_buffer: &'static DrawBuffer, + display_update: F, + ) -> Result + where + F: FnMut(&DisplayRefresh) + '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 { + 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 { + Ok(get_str_act(None)?) + } +} + +pub struct DrawBuffer { + initialized: RunOnce, + refresh_buffer: Mutex; N]>>, +} + +impl DrawBuffer { + pub const fn new() -> Self { + Self { + initialized: RunOnce::new(), + refresh_buffer: const_mutex(RefCell::new([MaybeUninit::uninit(); N])), + } + } + + fn get_ptr(&self) -> Option> { + 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 = 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( + draw_buffer: &'static DrawBuffer, + display_update_callback: F, + ) -> Result + where + F: FnMut(&DisplayRefresh) + '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::); + + // 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 { + 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 DisplayRefresh { + pub fn as_pixels(&self) -> impl IntoIterator> + '_ + where + C: PixelColor + From, + { + 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( + 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) + '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 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(()) + } +} diff --git a/lvgl/src/functions.rs b/lvgl/src/functions.rs new file mode 100644 index 00000000..a3e06c44 --- /dev/null +++ b/lvgl/src/functions.rs @@ -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 = result::Result; + +/// Register own buffer +pub(crate) fn disp_drv_register(disp_drv: &mut DisplayDriver) -> Result { + 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 { + 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 { + 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() }; +} diff --git a/lvgl/src/lib.rs b/lvgl/src/lib.rs index 47e527ca..d51f4292 100644 --- a/lvgl/src/lib.rs +++ b/lvgl/src/lib.rs @@ -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,13 +34,6 @@ use ::alloc::boxed::Box; #[cfg(feature = "lvgl_alloc")] mod allocator; -mod support; -mod ui; -#[macro_use] -mod lv_core; -pub mod input_device; -pub mod widgets; - #[cfg(not(feature = "lvgl_alloc"))] pub(crate) mod mem; @@ -48,25 +44,57 @@ 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}; pub const HOR_RES_MAX: u32 = lvgl_sys::LV_HOR_RES_MAX; pub const VER_RES_MAX: u32 = lvgl_sys::LV_VER_RES_MAX; -// Initialize LVGL only once. -static LVGL_INITIALIZED: AtomicBool = AtomicBool::new(false); +struct RunOnce(AtomicBool); -pub(crate) fn lvgl_init() { - if LVGL_INITIALIZED - .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) - .is_ok() - { +impl RunOnce { + const fn new() -> Self { + Self(AtomicBool::new(false)) + } + + 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 = DrawBuffer::new(); + static ONCE_INIT: RunOnce = RunOnce::new(); + + if ONCE_INIT.swap_and_check() { + let _ = Display::register(&DRAW_BUFFER, |_| {}).unwrap(); + } + } +} diff --git a/lvgl/src/lv_core/obj.rs b/lvgl/src/lv_core/obj.rs index e498725e..6779afb3 100644 --- a/lvgl/src/lv_core/obj.rs +++ b/lvgl/src/lv_core/obj.rs @@ -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) -> Self; + fn from_raw(raw_pointer: ptr::NonNull) -> 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) -> Self { + fn from_raw(raw: ptr::NonNull) -> 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) -> Self { + fn from_raw(raw_pointer: core::ptr::NonNull) -> 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 { +// 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 = ::from_raw(raw); +// Ok(Self { core }) +// } else { +// Err(crate::LvError::InvalidReference) +// } +// } +// } +// +// pub fn create_at(parent: &mut impl crate::NativeObject) -> crate::LvResult { +// Ok(Self::create(parent, None)?) +// } +// +// pub fn new() -> crate::LvResult { +// let mut parent = crate::display::DefaultDisplay::get_scr_act()?; +// Ok(Self::create_at(&mut parent)?) +// } +// } + bitflags! { pub struct State: u32 { /// Normal, released diff --git a/lvgl/src/mem.rs b/lvgl/src/mem.rs index f2adb874..8b096cf0 100644 --- a/lvgl/src/mem.rs +++ b/lvgl/src/mem.rs @@ -11,7 +11,7 @@ pub(crate) struct Box(NonNull); impl Box { /// Allocate memory using LVGL memory API and place `T` in the LVGL tracked memory. - pub fn new(value: T) -> Box { + pub fn new(value: T) -> Self { let size = mem::size_of::(); let inner = unsafe { let ptr = lvgl_sys::lv_mem_alloc(size as cty::size_t) as *mut T; @@ -29,10 +29,10 @@ impl Box { 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 { @@ -75,10 +75,29 @@ impl Clone for Box { } } +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 core::mem::MaybeUninit; + use crate::mem::mem_info; + use crate::*; use std::vec::Vec; fn init() { @@ -95,7 +114,7 @@ mod test { #[test] fn place_value_in_lv_mem() { - crate::lvgl_init(); + tests::initialize_test(); let v = Box::new(5); drop(v); @@ -107,7 +126,7 @@ mod test { #[test] fn place_complex_value_in_lv_mem() { - crate::lvgl_init(); + tests::initialize_test(); #[repr(C)] #[derive(Debug)] @@ -161,7 +180,7 @@ mod test { #[test] fn clone_object_in_lv_mem() { - crate::lvgl_init(); + crate::init(); let v1 = Box::new(5); let v2 = v1.clone(); @@ -171,21 +190,4 @@ mod test { // They should have different memory addresses, however. assert_ne!(v1.into_raw() as usize, v2.into_raw() as usize); } - - 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 - } } diff --git a/lvgl/src/support.rs b/lvgl/src/support.rs index b58029d6..a4b9d38b 100644 --- a/lvgl/src/support.rs +++ b/lvgl/src/support.rs @@ -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 = Result; @@ -13,7 +16,18 @@ pub enum LvError { AlreadyInUse, } -#[derive(Clone)] +impl From 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 for Rgb888 { fn from(color: Color) -> Self { unsafe { @@ -53,6 +68,7 @@ impl From for Rgb888 { } } +#[cfg(feature = "embedded_graphics")] impl From for Rgb565 { fn from(color: Color) -> Self { unsafe { diff --git a/lvgl/src/ui.rs b/lvgl/src/ui.rs deleted file mode 100644 index 82dc631d..00000000 --- a/lvgl/src/ui.rs +++ /dev/null @@ -1,231 +0,0 @@ -use crate::input_device::{generic::DisplayDriver, pointer::Pointer}; -use crate::Box; -use crate::{lv_core::obj::NativeObject, 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::draw_target::DrawTarget; -use embedded_graphics::prelude::*; -use embedded_graphics::{pixelcolor::PixelColor, Pixel}; - -// 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 -where - T: DrawTarget, - C: PixelColor + From, -{ - // 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>, -} - -// LVGL does not use thread locals. -unsafe impl Send for UI -where - T: DrawTarget, - C: PixelColor + From, -{ -} - -impl UI -where - T: DrawTarget, - C: PixelColor + From, -{ - pub fn init() -> LvResult { - if LVGL_IN_USE - .compare_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 indev_drv_register_pointer(&mut self, input_device: &mut Pointer) -> LvResult<()> { - // This function _has_ to be pointer device type specific w/o a refactor - // that is probably not worth doing, especially since there may be merit - // in doing pointer device type specific logic anyways in the future - if self.display_data.is_none() { - // TODO: Better yet would be to create a display struct that one register the - // input device in that instance. Represents better the LVGL correct usage. Also it's - // inline with unrepresentable invalid states using Rust type system. - // ```rust - // let disp = ui.disp_drv_register(embed_graph_disp)?; - // disp.indev_drv_register(disp); - // ... - // window.update(&disp) - // ``` - return Err(LvError::Uninitialized); - } - unsafe { - let descr = lvgl_sys::lv_indev_drv_register(&mut input_device.driver as *mut _); - input_device.set_descriptor(descr)?; - } - Ok(()) - } - - 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::::uninit(); - let mut disp_drv = MaybeUninit::::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::); - 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 { - 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 load_scr(&mut self, screen: &mut Obj) -> LvResult<()> { - unsafe { - lvgl_sys::lv_disp_load_scr(screen.raw()?.as_mut()); - } - Ok(()) - } - - pub fn event_send(&mut self, obj: &mut W, event: Event) -> LvResult<()> - where - W: Widget, - { - unsafe { - lvgl_sys::lv_event_send(obj.raw()?.as_mut(), event.into(), ptr::null_mut()); - } - Ok(()) - } - - pub fn tick_inc(&mut self, tick_period: Duration) { - unsafe { - lvgl_sys::lv_tick_inc(tick_period.as_millis() as u32); - } - } - - pub fn task_handler(&mut self) { - unsafe { - lvgl_sys::lv_task_handler(); - } - } -} - -pub(crate) struct DisplayUserData -where - T: DrawTarget, - C: PixelColor + From, -{ - display: T, - phantom: PhantomData, -} - -unsafe extern "C" fn display_callback_wrapper( - disp_drv: *mut lvgl_sys::lv_disp_drv_t, - area: *const lvgl_sys::lv_area_t, - color_p: *mut lvgl_sys::lv_color_t, -) where - T: DrawTarget, - C: PixelColor + From, -{ - // In the `std` world we would make sure to capture panics here and make them not escape across - // the FFI boundary. Since this library is focused on embedded platforms, we don't - // have an standard unwinding mechanism to rely upon. - let display_driver = *disp_drv; - // Rust code closure reference - if !display_driver.user_data.is_null() { - let user_data = &mut *(display_driver.user_data as *mut DisplayUserData); - let x1 = (*area).x1; - let x2 = (*area).x2; - let y1 = (*area).y1; - let y2 = (*area).y2; - // TODO: Can we do anything when there is a error while flushing? - let _ = display_flush(&mut user_data.display, (x1, x2), (y1, y2), color_p); - } - // Indicate to 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( - display: &mut T, - (x1, x2): (i16, i16), - (y1, y2): (i16, i16), - color_p: *mut lvgl_sys::lv_color_t, -) -> Result<(), T::Error> -where - T: DrawTarget, - C: PixelColor + From, -{ - let ys = y1..=y2; - let xs = (x1..=x2).enumerate(); - let x_len = (x2 - x1 + 1) as usize; - - // We use iterators here to ensure that the Rust compiler can apply all possible - // optimizations at compile time. - let pixels = ys.enumerate().flat_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); - Pixel(Point::new(x as i32, y as i32), raw_color.into()) - }) - }); - - display.draw_iter(pixels) -} diff --git a/lvgl/src/widgets/label.rs b/lvgl/src/widgets/label.rs index a18ecc30..68cdabd5 100644 --- a/lvgl/src/widgets/label.rs +++ b/lvgl/src/widgets/label.rs @@ -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> From 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> TryFrom for Label { + // type Error = LvError; + // fn try_from(text: S) -> Result { + // 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 { From abaf8e130b0f654d053742f12a7096ca81487e14 Mon Sep 17 00:00:00 2001 From: Nia Espera Date: Thu, 2 Mar 2023 13:54:25 +0100 Subject: [PATCH 2/3] update examples & extend functionality --- examples/Cargo.toml | 37 ----------------------- examples/app.rs | 26 ++++++---------- examples/arc.rs | 40 +++++++++++++----------- examples/bar.rs | 48 +++++++++++++++++------------ examples/button_click.rs | 49 ++++++++++++++++-------------- examples/demo.rs | 40 ++++++++++-------------- examples/gauge.rs | 38 ++++++++++++++--------- examples/simple.rs | 52 -------------------------------- lvgl-codegen/src/analysis.rs | 2 ++ lvgl/Cargo.toml | 3 +- lvgl/src/display.rs | 30 +++++++++--------- lvgl/src/functions.rs | 21 ++++++++++++- lvgl/src/input_device/generic.rs | 3 +- lvgl/src/input_device/pointer.rs | 18 ++++++----- lvgl/src/lib.rs | 20 +++++++----- lvgl/src/mem.rs | 2 +- lvgl/src/widgets/label.rs | 5 ++- 17 files changed, 194 insertions(+), 240 deletions(-) delete mode 100644 examples/Cargo.toml delete mode 100644 examples/simple.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml deleted file mode 100644 index 278e6fcc..00000000 --- a/examples/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "demo" -version = "0.1.0" -authors = ["Rafael Caricio "] -edition = "2018" -publish = false - -[dev-dependencies] -lvgl = { path = "../lvgl" } -lvgl-sys = { path = "../lvgl-sys" } -embedded-graphics = "0.6" -embedded-graphics-simulator = "0.2.0" -cstr_core = { version = "0.2.0", features = ["alloc"] } - -[[example]] -name = "simple" -path = "simple.rs" - -[[example]] -name = "demo" -path = "demo.rs" - -[[example]] -name = "bar" -path = "bar.rs" - -[[example]] -name = "button_click" -path = "button_click.rs" - -[[example]] -name = "gauge" -path = "gauge.rs" - -[[example]] -name = "arc" -path = "arc.rs" diff --git a/examples/app.rs b/examples/app.rs index a77e0e6f..5886fbda 100644 --- a/examples/app.rs +++ b/examples/app.rs @@ -3,36 +3,30 @@ 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 lvgl::{Display, DrawBuffer, DISP_HOR_RES, DISP_VER_RES}; +use std::borrow::Borrow; use std::cell::RefCell; -use std::sync::Arc; type ColorSpace = Rgb565; fn main() { - let embedded_graphics_display: SimulatorDisplay = SimulatorDisplay::new(Size::new( - lvgl::DISP_HOR_RES as u32, - lvgl::DISP_VER_RES as u32, - )); + let embedded_graphics_display: SimulatorDisplay = + SimulatorDisplay::new(Size::new(DISP_HOR_RES as u32, 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)); + let mut shared_native_display = RefCell::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 = DrawBuffer::new(); + let buffer = DrawBuffer::<{ (DISP_HOR_RES * DISP_VER_RES) as usize }>::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()); - } + let display = Display::register(&buffer, |refresh| { + shared_native_display + .borrow_mut() + .draw_iter(refresh.as_pixels()); }) .unwrap(); diff --git a/examples/arc.rs b/examples/arc.rs index c3eb3476..cdadef19 100644 --- a/examples/arc.rs +++ b/examples/arc.rs @@ -4,14 +4,15 @@ use embedded_graphics::prelude::*; use embedded_graphics_simulator::{ OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, }; -use lvgl::display::Display; +use lvgl; use lvgl::style::Style; use lvgl::widgets::{Arc, Label, LabelAlign}; -use lvgl::{self, Align, Color, Part, State}; -use lvgl::{LvError, Widget}; +use lvgl::{ + Align, Color, Display, DisplayRefresh, DrawBuffer, LvError, Part, State, Widget, HOR_RES_MAX, + VER_RES_MAX, +}; use lvgl_sys; -use parking_lot::Mutex; -use std::sync::Arc as SyncArc; +use std::cell::RefCell; use std::thread; use std::time::{Duration, Instant}; @@ -41,16 +42,24 @@ fn main() -> Result<(), LvError> { fn run_arc_demo() -> Result<(), LvError> { lvgl::init(); - let display: SimulatorDisplay = - SimulatorDisplay::new(Size::new(lvgl::HOR_RES_MAX, lvgl::VER_RES_MAX)); + let sim_display: SimulatorDisplay = + SimulatorDisplay::new(Size::new(HOR_RES_MAX, VER_RES_MAX)); let output_settings = OutputSettingsBuilder::new().scale(2).build(); let mut window = Window::new("Arc Example", &output_settings); - let shared_native_display = SyncArc::new(Mutex::new(display)); - let display = Display::register_shared(&shared_native_display)?; + let shared_native_display = RefCell::new(sim_display); + + let buffer = DrawBuffer::<{ (HOR_RES_MAX * VER_RES_MAX) as usize }>::new(); - let mut screen = display.get_str_act()?; + let display = Display::register(&buffer, |refresh| { + shared_native_display + .borrow_mut() + .draw_iter(refresh.as_pixels()) + .unwrap(); + })?; + + let mut screen = display.get_scr_act()?; let mut screen_style = Style::default(); screen_style.set_bg_color(State::DEFAULT, Color::from_rgb((255, 255, 255))); @@ -58,13 +67,13 @@ fn run_arc_demo() -> Result<(), LvError> { screen.add_style(Part::Main, &mut screen_style)?; // Create the arc object - let mut arc = Arc::new()?; + let mut arc = Arc::create(&mut screen, None)?; 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()?; + let mut loading_lbl = Label::create(&mut screen, None)?; 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)?; @@ -97,10 +106,7 @@ fn run_arc_demo() -> Result<(), LvError> { i += 1; lvgl::task_handler(); - { - let eg_display = shared_native_display.lock(); - window.update(&eg_display); - } + window.update(&shared_native_display.borrow()); for event in window.events() { match event { @@ -108,7 +114,7 @@ fn run_arc_demo() -> Result<(), LvError> { _ => {} } } - thread::sleep(Duration::from_millis(15)); + lvgl::tick_inc(Duration::from_millis(15)); } Ok(()) diff --git a/examples/bar.rs b/examples/bar.rs index ea428d57..89570b21 100644 --- a/examples/bar.rs +++ b/examples/bar.rs @@ -4,33 +4,44 @@ use embedded_graphics::prelude::*; use embedded_graphics_simulator::{ OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, }; +use lvgl; use lvgl::style::Style; use lvgl::widgets::{Bar, Label, LabelAlign}; -use lvgl::{self, Align, Animation, Color, Event, LvError, Part, State, Widget, UI}; -use std::time::Instant; +use lvgl::{ + Align, Animation, Color, Display, DrawBuffer, Event, LvError, Part, State, Widget, HOR_RES_MAX, + VER_RES_MAX, +}; +use std::cell::RefCell; +use std::time::{Duration, Instant}; fn main() -> Result<(), LvError> { - let display: SimulatorDisplay = - SimulatorDisplay::new(Size::new(lvgl::HOR_RES_MAX, lvgl::VER_RES_MAX)); + lvgl::init(); + let sim_display: SimulatorDisplay = + SimulatorDisplay::new(Size::new(HOR_RES_MAX, VER_RES_MAX)); let output_settings = OutputSettingsBuilder::new().scale(2).build(); let mut window = Window::new("Bar Example", &output_settings); - let mut ui = UI::init()?; + let shared_native_display = RefCell::new(sim_display); + + let buffer = DrawBuffer::<{ (HOR_RES_MAX * VER_RES_MAX) as usize }>::new(); - // Implement and register your display: - ui.disp_drv_register(display).unwrap(); + let display = Display::register(&buffer, |refresh| { + shared_native_display + .borrow_mut() + .draw_iter(refresh.as_pixels()) + .unwrap(); + })?; - // Create screen and widgets - let mut screen = ui.scr_act()?; + let mut screen = display.get_scr_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 bar object - let mut bar = Bar::new(&mut screen)?; + let mut bar = Bar::create(&mut screen, None)?; bar.set_size(175, 20)?; bar.set_align(&mut screen, Align::Center, 0, 10)?; bar.set_range(0, 100)?; @@ -41,29 +52,29 @@ fn main() -> Result<(), LvError> { // // Set the indicator style for the bar object let mut ind_style = Style::default(); ind_style.set_bg_color(State::DEFAULT, Color::from_rgb((100, 245, 100))); - bar.add_style(Part::All, ind_style)?; + bar.add_style(Part::All, &mut ind_style)?; - let mut loading_lbl = Label::new(&mut screen)?; + let mut loading_lbl = Label::create(&mut screen, None)?; loading_lbl.set_text(CString::new("Loading...").unwrap().as_c_str())?; loading_lbl.set_align(&mut bar, Align::OutTopMid, 0, -10)?; loading_lbl.set_label_align(LabelAlign::Center)?; 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 i = 0; let mut loop_started = Instant::now(); 'running: loop { if i > 100 { i = 0; - ui.event_send(&mut bar, Event::Clicked)?; + lvgl::event_send(&mut bar, Event::Clicked)?; } bar.set_value(i, Animation::ON)?; i += 1; - ui.task_handler(); - window.update(ui.get_display_ref().unwrap()); + lvgl::task_handler(); + window.update(&shared_native_display.borrow()); for event in window.events() { match event { @@ -72,8 +83,7 @@ fn main() -> Result<(), LvError> { } } - ui.tick_inc(loop_started.elapsed()); - loop_started = Instant::now(); + lvgl::tick_inc(Duration::from_millis(10)); } Ok(()) diff --git a/examples/button_click.rs b/examples/button_click.rs index 5b45047f..b8c6f04c 100644 --- a/examples/button_click.rs +++ b/examples/button_click.rs @@ -5,48 +5,57 @@ use embedded_graphics_simulator::{ OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, }; +use lvgl; use lvgl::input_device::{ - generic::DisplayDriver, + generic::InputDriver, pointer::{Pointer, PointerInputData}, }; use lvgl::style::Style; use lvgl::widgets::{Btn, Label}; -use lvgl::{self, Align, Color, LvError, Part, State, Widget, UI}; - +use lvgl::{ + Align, Color, Display, DrawBuffer, LvError, Part, State, Widget, HOR_RES_MAX, VER_RES_MAX, +}; +use std::cell::RefCell; use std::thread::sleep; use std::time::{Duration, Instant}; fn main() -> Result<(), LvError> { - let display: SimulatorDisplay = - SimulatorDisplay::new(Size::new(lvgl::HOR_RES_MAX, lvgl::VER_RES_MAX)); + lvgl::init(); + let sim_display: SimulatorDisplay = + SimulatorDisplay::new(Size::new(HOR_RES_MAX, VER_RES_MAX)); let output_settings = OutputSettingsBuilder::new().scale(2).build(); - let mut window = Window::new("Bar Example", &output_settings); + let mut window = Window::new("Button Example", &output_settings); + + let shared_native_display = RefCell::new(sim_display); - let mut ui = UI::init()?; + let buffer = DrawBuffer::<{ (HOR_RES_MAX * VER_RES_MAX) as usize }>::new(); - // Register your display: - ui.disp_drv_register(display)?; + let display = Display::register(&buffer, |refresh| { + shared_native_display + .borrow_mut() + .draw_iter(refresh.as_pixels()) + .unwrap(); + })?; // Define the initial state of your input let mut latest_touch_status = PointerInputData::Touch(Point::new(0, 0)).released().once(); // Register a new input device that's capable of reading the current state of the input let mut touch_screen = Pointer::new(|| latest_touch_status); - ui.indev_drv_register_pointer(&mut touch_screen)?; + lvgl::indev_drv_register(&mut touch_screen)?; // Create screen and widgets - let mut screen = ui.scr_act()?; + let mut screen = display.get_scr_act()?; let mut screen_style = Style::default(); screen_style.set_bg_color(State::DEFAULT, Color::from_rgb((0, 0, 0))); - screen.add_style(Part::Main, screen_style)?; - + screen.add_style(Part::Main, &mut screen_style)?; // Create the button - let mut button = Btn::new(&mut screen)?; + let mut button = Btn::create(&mut screen, None)?; button.set_align(&mut screen, Align::InLeftMid, 30, 0)?; button.set_size(180, 80)?; - let mut btn_lbl = Label::new(&mut button)?; + let mut btn_lbl = Label::create(&mut button, None)?; btn_lbl.set_text(CString::new("Click me!").unwrap().as_c_str())?; let mut btn_state = false; @@ -65,11 +74,10 @@ fn main() -> Result<(), LvError> { } })?; - let mut loop_started = Instant::now(); let mut latest_touch_point = Point::new(0, 0); 'running: loop { - ui.task_handler(); - window.update(ui.get_display_ref().unwrap()); + lvgl::task_handler(); + window.update(&shared_native_display.borrow()); let mut events = window.events().peekable(); @@ -95,10 +103,7 @@ fn main() -> Result<(), LvError> { } } - sleep(Duration::from_millis(15)); - - ui.tick_inc(loop_started.elapsed()); - loop_started = Instant::now(); + lvgl::tick_inc(Duration::from_millis(15)); } Ok(()) diff --git a/examples/demo.rs b/examples/demo.rs index 21a44e5f..7d30f6c9 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -1,5 +1,4 @@ use cstr_core::CString; -use embedded_graphics::drawable; use embedded_graphics::pixelcolor::Rgb565; use embedded_graphics::prelude::*; use embedded_graphics_simulator::{ @@ -8,30 +7,28 @@ use embedded_graphics_simulator::{ use lvgl; use lvgl::style::Style; use lvgl::widgets::{Label, LabelAlign}; -use lvgl::{Align, Color, DefaultDisplay, Display, DrawBuffer, LvError, Part, State, Widget}; +use lvgl::{ + Align, Color, DefaultDisplay, Display, DrawBuffer, LvError, Part, State, Widget, HOR_RES_MAX, + VER_RES_MAX, +}; use lvgl_sys; -use parking_lot::Mutex; -use std::sync::Arc as SyncArc; +use std::cell::RefCell; use std::thread; use std::thread::sleep; use std::time::{Duration, Instant}; fn main() -> Result<(), LvError> { - let display: SimulatorDisplay = - SimulatorDisplay::new(Size::new(lvgl::HOR_RES_MAX, lvgl::VER_RES_MAX)); + lvgl::init(); + let sim_display: SimulatorDisplay = + SimulatorDisplay::new(Size::new(HOR_RES_MAX, VER_RES_MAX)); let output_settings = OutputSettingsBuilder::new().scale(1).build(); let mut window = Window::new("PineTime", &output_settings); - let shared_native_display = SyncArc::new(Mutex::new(embedded_graphics_display)); - - // LVGL-rs usage starts here - lvgl::init(); - + let shared_native_display = RefCell::new(sim_display); // 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 = DrawBuffer::new(); + // display. The buffer size can be set freely. + let buffer = DrawBuffer::<{ (HOR_RES_MAX * VER_RES_MAX) as usize }>::new(); // // const NUMBER_OF_DISPLAYS: usize = 1; // static DISPLAY_REGISTRY: DisplayRegistry = DisplayRegistry::empty(); @@ -40,12 +37,10 @@ fn main() -> Result<(), LvError> { // 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()); - } + let display = Display::register(&buffer, |refresh| { + shared_native_display + .borrow_mut() + .draw_iter(refresh.as_pixels()); })?; // Create screen and widgets @@ -100,10 +95,7 @@ fn main() -> Result<(), LvError> { i = 1 + i; lvgl::task_handler(); - { - let native_display = shared_native_display.lock(); - window.update(&native_display); - } + window.update(&shared_native_display.borrow()); for event in window.events() { match event { diff --git a/examples/gauge.rs b/examples/gauge.rs index ca236d4a..4352dcc2 100644 --- a/examples/gauge.rs +++ b/examples/gauge.rs @@ -3,29 +3,39 @@ use embedded_graphics::prelude::*; use embedded_graphics_simulator::{ OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, }; +use lvgl; use lvgl::style::{Opacity, Style}; use lvgl::widgets::Gauge; -use lvgl::{self, Align, Color, LvError, Part, State, Widget, UI}; +use lvgl::{ + Align, Color, Display, DrawBuffer, LvError, Part, State, Widget, HOR_RES_MAX, VER_RES_MAX, +}; +use std::cell::RefCell; use std::time::Instant; fn main() -> Result<(), LvError> { - let display: SimulatorDisplay = - SimulatorDisplay::new(Size::new(lvgl::HOR_RES_MAX, lvgl::VER_RES_MAX)); + lvgl::init(); + let sim_display: SimulatorDisplay = + SimulatorDisplay::new(Size::new(HOR_RES_MAX, VER_RES_MAX)); let output_settings = OutputSettingsBuilder::new().scale(2).build(); let mut window = Window::new("Gauge Example", &output_settings); - let mut ui = UI::init()?; + let shared_native_display = RefCell::new(sim_display); + + let buffer = DrawBuffer::<{ (HOR_RES_MAX * VER_RES_MAX) as usize }>::new(); - // Implement and register your display: - ui.disp_drv_register(display)?; + let display = Display::register(&buffer, |refresh| { + shared_native_display + .borrow_mut() + .draw_iter(refresh.as_pixels()) + .unwrap(); + })?; - // Create screen and widgets - let mut screen = ui.scr_act()?; + let mut screen = display.get_scr_act()?; let mut screen_style = Style::default(); screen_style.set_bg_color(State::DEFAULT, Color::from_rgb((0, 0, 0))); - screen.add_style(Part::Main, screen_style)?; + screen.add_style(Part::Main, &mut screen_style)?; // Create the gauge let mut gauge_style = Style::default(); @@ -46,8 +56,8 @@ fn main() -> Result<(), LvError> { gauge_style.set_scale_end_line_width(State::DEFAULT, 4); gauge_style.set_scale_end_border_width(State::DEFAULT, 4); - let mut gauge = Gauge::new(&mut screen)?; - gauge.add_style(Part::Main, gauge_style)?; + let mut gauge = Gauge::create(&mut screen, None)?; + gauge.add_style(Part::Main, &mut gauge_style)?; gauge.set_align(&mut screen, Align::Center, 0, 0)?; gauge.set_value(0, 50)?; @@ -56,8 +66,8 @@ fn main() -> Result<(), LvError> { 'running: loop { gauge.set_value(0, i)?; - ui.task_handler(); - window.update(ui.get_display_ref().unwrap()); + lvgl::task_handler(); + window.update(&shared_native_display.borrow()); for event in window.events() { match event { @@ -78,7 +88,7 @@ fn main() -> Result<(), LvError> { i = i + 1; } - ui.tick_inc(loop_started.elapsed()); + lvgl::tick_inc(loop_started.elapsed()); loop_started = Instant::now(); } diff --git a/examples/simple.rs b/examples/simple.rs deleted file mode 100644 index 1a05bd0b..00000000 --- a/examples/simple.rs +++ /dev/null @@ -1,52 +0,0 @@ -use embedded_graphics::pixelcolor::Rgb565; -use embedded_graphics::prelude::*; -use embedded_graphics_simulator::{ - OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, -}; -use lvgl::widgets::Keyboard; -use lvgl::LvError; -use lvgl::UI; -use std::time::Instant; - -fn main() -> Result<(), LvError> { - let display: SimulatorDisplay = - SimulatorDisplay::new(Size::new(lvgl::HOR_RES_MAX, lvgl::VER_RES_MAX)); - - let output_settings = OutputSettingsBuilder::new().scale(2).build(); - let mut window = Window::new("Simple Example", &output_settings); - - // Initialize LVGL - let mut ui = UI::init()?; - - // Register your display - ui.disp_drv_register(display)?; - - // Get the active screen - let mut screen = ui.scr_act()?; - - // Create a Keyboard widget on the screen - let _ = Keyboard::new(&mut screen)?; - - let mut loop_started = Instant::now(); - 'running: loop { - // Tell LVGL to process UI related tasks - ui.task_handler(); - - // Update your window with the latest display image - window.update(ui.get_display_ref().unwrap()); - - for event in window.events() { - match event { - SimulatorEvent::Quit => break 'running, - _ => {} - } - } - - // Tell LVGL how much time has past since last loop - ui.tick_inc(loop_started.elapsed()); - - loop_started = Instant::now(); - } - - Ok(()) -} diff --git a/lvgl-codegen/src/analysis.rs b/lvgl-codegen/src/analysis.rs index 3b9f7fe5..082f4e48 100644 --- a/lvgl-codegen/src/analysis.rs +++ b/lvgl-codegen/src/analysis.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + /// A parameter of C functions. /// /// This struct represents all relevant information we can extract from the C function declaration diff --git a/lvgl/Cargo.toml b/lvgl/Cargo.toml index 82bfb257..c2289362 100644 --- a/lvgl/Cargo.toml +++ b/lvgl/Cargo.toml @@ -17,10 +17,9 @@ cty = "0.2.2" embedded-graphics = { version = "0.7.1", optional = true } cstr_core = { version = "0.2.6", default-features = false, features = ["alloc"] } bitflags = "1.3.2" -parking_lot = "0.11.1" [features] -default = [] +default = ["embedded_graphics"] embedded_graphics = ["embedded-graphics"] alloc = ["cstr_core/alloc"] lvgl_alloc = ["alloc"] diff --git a/lvgl/src/display.rs b/lvgl/src/display.rs index c140e3f6..80c7130a 100644 --- a/lvgl/src/display.rs +++ b/lvgl/src/display.rs @@ -6,8 +6,6 @@ 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; @@ -25,17 +23,17 @@ pub struct Display { pub(crate) disp: NonNull, } -impl Display { +impl<'a> Display { pub(crate) fn from_raw(disp: NonNull) -> Self { Self { disp } } pub fn register( - draw_buffer: &'static DrawBuffer, + draw_buffer: &'a DrawBuffer, display_update: F, ) -> Result where - F: FnMut(&DisplayRefresh) + 'static, + F: FnMut(&DisplayRefresh) + 'a, { let mut display_diver = DisplayDriver::new(draw_buffer, display_update)?; Ok(disp_drv_register(&mut display_diver)?) @@ -64,14 +62,14 @@ impl DefaultDisplay { pub struct DrawBuffer { initialized: RunOnce, - refresh_buffer: Mutex; N]>>, + refresh_buffer: RefCell<[MaybeUninit; N]>, } impl DrawBuffer { pub const fn new() -> Self { Self { initialized: RunOnce::new(), - refresh_buffer: const_mutex(RefCell::new([MaybeUninit::uninit(); N])), + refresh_buffer: RefCell::new([MaybeUninit::uninit(); N]), } } @@ -81,7 +79,7 @@ impl DrawBuffer { // 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 = MaybeUninit::uninit(); - let primary_buffer_guard = self.refresh_buffer.lock(); + let primary_buffer_guard = &self.refresh_buffer; let draw_buf = unsafe { lvgl_sys::lv_disp_buf_init( inner.as_mut_ptr(), @@ -102,13 +100,13 @@ pub struct DisplayDriver { pub(crate) disp_drv: lvgl_sys::lv_disp_drv_t, } -impl DisplayDriver { +impl<'a> DisplayDriver { pub fn new( - draw_buffer: &'static DrawBuffer, + draw_buffer: &'a DrawBuffer, display_update_callback: F, ) -> Result where - F: FnMut(&DisplayRefresh) + 'static, + F: FnMut(&DisplayRefresh) + 'a, { let mut disp_drv = unsafe { let mut inner = MaybeUninit::uninit(); @@ -152,11 +150,11 @@ pub struct DisplayRefresh { #[cfg(feature = "embedded_graphics")] mod embedded_graphics_impl { use crate::{Color, DisplayRefresh}; - use embedded_graphics::drawable; use embedded_graphics::prelude::*; + use embedded_graphics::Pixel; impl DisplayRefresh { - pub fn as_pixels(&self) -> impl IntoIterator> + '_ + pub fn as_pixels(&self) -> impl IntoIterator> + '_ where C: PixelColor + From, { @@ -177,7 +175,7 @@ mod embedded_graphics_impl { 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()) + Pixel(Point::new(x as i32, y as i32), raw_color.into()) }) }) .flatten() @@ -185,12 +183,12 @@ mod embedded_graphics_impl { } } -unsafe extern "C" fn disp_flush_trampoline( +unsafe extern "C" fn disp_flush_trampoline<'a, 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) + 'static, + F: FnMut(&DisplayRefresh) + 'a, { let display_driver = *disp_drv; if !display_driver.user_data.is_null() { diff --git a/lvgl/src/functions.rs b/lvgl/src/functions.rs index a3e06c44..914eaf1f 100644 --- a/lvgl/src/functions.rs +++ b/lvgl/src/functions.rs @@ -1,5 +1,6 @@ use crate::display::{Display, DisplayDriver}; -use crate::{Obj, Widget}; +use crate::input_device::generic::InputDriver; +use crate::{Event, LvResult, Obj, Widget}; use core::ptr::NonNull; use core::time::Duration; use core::{ptr, result}; @@ -54,3 +55,21 @@ pub fn tick_inc(tick_period: Duration) { pub fn task_handler() { unsafe { lvgl_sys::lv_task_handler() }; } + +/// Directly send an event to a widget +#[inline] +pub fn event_send(obj: &mut W, event: Event) -> LvResult<()> { + unsafe { + lvgl_sys::lv_event_send(obj.raw()?.as_mut(), event.into(), ptr::null_mut()); + }; + Ok(()) +} + +/// Register an input device driver to LVGL +pub fn indev_drv_register(input_device: &mut impl InputDriver) -> LvResult<()> { + unsafe { + let descr = lvgl_sys::lv_indev_drv_register(&mut input_device.get_driver() as *mut _); + input_device.set_descriptor(descr)?; + }; + Ok(()) +} diff --git a/lvgl/src/input_device/generic.rs b/lvgl/src/input_device/generic.rs index 72564274..f16e88a3 100644 --- a/lvgl/src/input_device/generic.rs +++ b/lvgl/src/input_device/generic.rs @@ -28,10 +28,11 @@ pub enum BufferStatus { Buffered(InputState), } -pub trait DisplayDriver { +pub trait InputDriver { fn new(handler: F) -> D where F: Fn() -> BufferStatus; + fn get_driver(&self) -> lvgl_sys::lv_indev_drv_t; unsafe fn set_descriptor(&mut self, descriptor: *mut lvgl_sys::lv_indev_t) -> LvResult<()>; } diff --git a/lvgl/src/input_device/pointer.rs b/lvgl/src/input_device/pointer.rs index c13811d5..1d463c38 100644 --- a/lvgl/src/input_device/pointer.rs +++ b/lvgl/src/input_device/pointer.rs @@ -3,7 +3,7 @@ use crate::{LvError, LvResult}; use core::mem::MaybeUninit; use embedded_graphics::geometry::Point; -use super::generic::{BufferStatus, Data, DisplayDriver, InputState}; +use super::generic::{BufferStatus, Data, InputDriver, InputState}; #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] pub enum PointerInputData { @@ -26,7 +26,7 @@ pub struct Pointer { pub(crate) descriptor: Option, } -impl DisplayDriver for Pointer { +impl InputDriver for Pointer { fn new(handler: F) -> Self where F: Fn() -> BufferStatus, @@ -47,8 +47,12 @@ impl DisplayDriver for Pointer { } } + fn get_driver(&self) -> lvgl_sys::lv_indev_drv_t { + self.driver + } + unsafe fn set_descriptor(&mut self, descriptor: *mut lvgl_sys::lv_indev_t) -> LvResult<()> { - if !(descriptor.is_null() || self.descriptor.is_none()) { + if descriptor.is_null() || self.descriptor.is_none() { self.descriptor = Some(*descriptor); } else { return Err(LvError::AlreadyInUse); @@ -111,7 +115,6 @@ where #[cfg(test)] mod test { use super::*; - use crate::UI; use core::marker::PhantomData; use embedded_graphics::draw_target::DrawTarget; use embedded_graphics::geometry::Size; @@ -155,11 +158,12 @@ mod test { // We cannot test right now by having instances of UI global state... :( // I need to find a way to test while having global state... fn pointer_input_device() -> LvResult<()> { - let mut ui = UI::init()?; + crate::init(); + //FIXME let disp: FakeDisplay = FakeDisplay { p: PhantomData }; - ui.disp_drv_register(disp)?; + //ui.disp_drv_register(disp)?; fn read_touchpad_device() -> BufferStatus { PointerInputData::Touch(Point::new(120, 23)) @@ -169,7 +173,7 @@ mod test { let mut touch_screen = Pointer::new(|| read_touchpad_device()); - ui.indev_drv_register_pointer(&mut touch_screen)?; + crate::indev_drv_register(&mut touch_screen)?; Ok(()) } diff --git a/lvgl/src/lib.rs b/lvgl/src/lib.rs index d51f4292..398b1345 100644 --- a/lvgl/src/lib.rs +++ b/lvgl/src/lib.rs @@ -44,16 +44,20 @@ 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 display::*; pub use functions::*; pub use lv_core::*; pub use support::*; +mod display; +mod functions; +mod support; + +pub mod input_device; +pub mod widgets; + pub const HOR_RES_MAX: u32 = lvgl_sys::LV_HOR_RES_MAX; pub const VER_RES_MAX: u32 = lvgl_sys::LV_VER_RES_MAX; @@ -89,12 +93,12 @@ pub(crate) mod tests { pub(crate) fn initialize_test() { init(); - const REFRESH_BUFFER_SIZE: usize = 64 * 64 / 10; - static DRAW_BUFFER: DrawBuffer = DrawBuffer::new(); static ONCE_INIT: RunOnce = RunOnce::new(); + const REFRESH_BUFFER_SIZE: usize = 64 * 64 / 10; + let buffer = DrawBuffer::::new(); if ONCE_INIT.swap_and_check() { - let _ = Display::register(&DRAW_BUFFER, |_| {}).unwrap(); + let _ = Display::register(&buffer, |_| {}).unwrap(); } } } diff --git a/lvgl/src/mem.rs b/lvgl/src/mem.rs index 8b096cf0..df622b09 100644 --- a/lvgl/src/mem.rs +++ b/lvgl/src/mem.rs @@ -95,9 +95,9 @@ fn mem_info() -> lvgl_sys::lv_mem_monitor_t { #[cfg(test)] mod test { use super::*; - use core::mem::MaybeUninit; use crate::mem::mem_info; use crate::*; + use core::mem::MaybeUninit; use std::vec::Vec; fn init() { diff --git a/lvgl/src/widgets/label.rs b/lvgl/src/widgets/label.rs index 68cdabd5..acbb7858 100644 --- a/lvgl/src/widgets/label.rs +++ b/lvgl/src/widgets/label.rs @@ -13,9 +13,9 @@ impl Label { #[cfg(feature = "alloc")] mod alloc_imp { use crate::widgets::Label; - use crate::LvError; + //use crate::LvError; use cstr_core::CString; - use core::convert::TryFrom; + //use core::convert::TryFrom; impl> From for Label { fn from(text: S) -> Self { @@ -40,7 +40,6 @@ mod alloc_imp { // } } - #[derive(Debug, Copy, Clone, PartialEq)] #[repr(u8)] pub enum LabelAlign { From 184d4f12414be7f5614a570dd6a30aac545033b2 Mon Sep 17 00:00:00 2001 From: Nia Espera Date: Thu, 2 Mar 2023 21:22:12 +0100 Subject: [PATCH 3/3] documentation and deduplication get rid of warnings --- examples/app.rs | 13 +++++++------ examples/arc.rs | 4 ++-- examples/bar.rs | 3 +-- examples/button_click.rs | 4 ++-- examples/demo.rs | 6 +++--- lvgl/src/allocator.rs | 1 + lvgl/src/display.rs | 24 +++++++++++++++--------- lvgl/src/functions.rs | 12 ++++++------ lvgl/src/input_device/generic.rs | 8 +++++++- lvgl/src/input_device/pointer.rs | 11 +++++++---- lvgl/src/lib.rs | 3 +++ lvgl/src/lv_core/obj.rs | 9 +++++++-- lvgl/src/mem.rs | 3 ++- lvgl/src/support.rs | 15 ++++++++++----- 14 files changed, 73 insertions(+), 43 deletions(-) diff --git a/examples/app.rs b/examples/app.rs index 5886fbda..84ab7e95 100644 --- a/examples/app.rs +++ b/examples/app.rs @@ -3,30 +3,31 @@ use embedded_graphics::prelude::*; use embedded_graphics_simulator::{OutputSettingsBuilder, SimulatorDisplay, Window}; use lvgl; use lvgl::widgets::Label; -use lvgl::{Display, DrawBuffer, DISP_HOR_RES, DISP_VER_RES}; -use std::borrow::Borrow; +use lvgl::{Display, DrawBuffer, HOR_RES_MAX, VER_RES_MAX}; use std::cell::RefCell; type ColorSpace = Rgb565; +#[allow(unused_mut)] +#[allow(unused_variables)] fn main() { let embedded_graphics_display: SimulatorDisplay = - SimulatorDisplay::new(Size::new(DISP_HOR_RES as u32, DISP_VER_RES as u32)); + SimulatorDisplay::new(Size::new(HOR_RES_MAX as u32, VER_RES_MAX as u32)); let output_settings = OutputSettingsBuilder::new().scale(2).build(); let mut window = Window::new("App Example", &output_settings); - let mut shared_native_display = RefCell::new(embedded_graphics_display); + let shared_native_display = RefCell::new(embedded_graphics_display); // LVGL usage lvgl::init(); - let buffer = DrawBuffer::<{ (DISP_HOR_RES * DISP_VER_RES) as usize }>::new(); + let buffer = DrawBuffer::<{ (HOR_RES_MAX * VER_RES_MAX) as usize }>::new(); let display = Display::register(&buffer, |refresh| { shared_native_display .borrow_mut() - .draw_iter(refresh.as_pixels()); + .draw_iter(refresh.as_pixels()).unwrap(); }) .unwrap(); diff --git a/examples/arc.rs b/examples/arc.rs index cdadef19..162a58f4 100644 --- a/examples/arc.rs +++ b/examples/arc.rs @@ -8,13 +8,13 @@ use lvgl; use lvgl::style::Style; use lvgl::widgets::{Arc, Label, LabelAlign}; use lvgl::{ - Align, Color, Display, DisplayRefresh, DrawBuffer, LvError, Part, State, Widget, HOR_RES_MAX, + Align, Color, Display, DrawBuffer, LvError, Part, State, Widget, HOR_RES_MAX, VER_RES_MAX, }; use lvgl_sys; use std::cell::RefCell; use std::thread; -use std::time::{Duration, Instant}; +use std::time::Duration; fn mem_info() -> lvgl_sys::lv_mem_monitor_t { let mut info = lvgl_sys::lv_mem_monitor_t { diff --git a/examples/bar.rs b/examples/bar.rs index 89570b21..0a3df17b 100644 --- a/examples/bar.rs +++ b/examples/bar.rs @@ -12,7 +12,7 @@ use lvgl::{ VER_RES_MAX, }; use std::cell::RefCell; -use std::time::{Duration, Instant}; +use std::time::Duration; fn main() -> Result<(), LvError> { lvgl::init(); @@ -64,7 +64,6 @@ fn main() -> Result<(), LvError> { loading_lbl.add_style(Part::Main, &mut loading_style)?; let mut i = 0; - let mut loop_started = Instant::now(); 'running: loop { if i > 100 { i = 0; diff --git a/examples/button_click.rs b/examples/button_click.rs index b8c6f04c..7d809ba6 100644 --- a/examples/button_click.rs +++ b/examples/button_click.rs @@ -16,9 +16,9 @@ use lvgl::{ Align, Color, Display, DrawBuffer, LvError, Part, State, Widget, HOR_RES_MAX, VER_RES_MAX, }; use std::cell::RefCell; -use std::thread::sleep; -use std::time::{Duration, Instant}; +use std::time::Duration; +#[allow(unused_assignments)] fn main() -> Result<(), LvError> { lvgl::init(); let sim_display: SimulatorDisplay = diff --git a/examples/demo.rs b/examples/demo.rs index 7d30f6c9..bc776177 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -8,14 +8,14 @@ use lvgl; use lvgl::style::Style; use lvgl::widgets::{Label, LabelAlign}; use lvgl::{ - Align, Color, DefaultDisplay, Display, DrawBuffer, LvError, Part, State, Widget, HOR_RES_MAX, + Align, Color, Display, DrawBuffer, LvError, Part, State, Widget, HOR_RES_MAX, VER_RES_MAX, }; use lvgl_sys; use std::cell::RefCell; use std::thread; use std::thread::sleep; -use std::time::{Duration, Instant}; +use std::time::Duration; fn main() -> Result<(), LvError> { lvgl::init(); @@ -40,7 +40,7 @@ fn main() -> Result<(), LvError> { let display = Display::register(&buffer, |refresh| { shared_native_display .borrow_mut() - .draw_iter(refresh.as_pixels()); + .draw_iter(refresh.as_pixels()).unwrap(); })?; // Create screen and widgets diff --git a/lvgl/src/allocator.rs b/lvgl/src/allocator.rs index 8d414f07..f25ce844 100644 --- a/lvgl/src/allocator.rs +++ b/lvgl/src/allocator.rs @@ -4,6 +4,7 @@ use core::alloc::{GlobalAlloc, Layout}; #[global_allocator] static ALLOCATOR: LvglAlloc = LvglAlloc; +/// LVGL allocator. Enabled by toggling the `lvgl_alloc` or `alloc` features. pub struct LvglAlloc; unsafe impl GlobalAlloc for LvglAlloc { diff --git a/lvgl/src/display.rs b/lvgl/src/display.rs index 80c7130a..5175233e 100644 --- a/lvgl/src/display.rs +++ b/lvgl/src/display.rs @@ -7,9 +7,7 @@ use core::mem::MaybeUninit; use core::ptr::NonNull; use core::{ptr, result}; -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; - +/// Error in interacting with a `Display`. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum DisplayError { NotAvailable, @@ -19,6 +17,7 @@ pub enum DisplayError { type Result = result::Result; +/// An LVGL-registered display. Equivalent to an `lv_disp_t`. pub struct Display { pub(crate) disp: NonNull, } @@ -28,6 +27,8 @@ impl<'a> Display { Self { disp } } + /// Registers a given `DrawBuffer` with an associated update function to + /// LVGL. `display_update` takes a `&DisplayRefresh`. pub fn register( draw_buffer: &'a DrawBuffer, display_update: F, @@ -51,21 +52,25 @@ impl Default for Display { } #[derive(Copy, Clone)] -pub struct DefaultDisplay {} +pub(crate) struct DefaultDisplay {} impl DefaultDisplay { - /// Gets the screen active of the default display. - pub fn get_scr_act() -> Result { + /// Gets the active screen of the default display. + pub(crate) fn get_scr_act() -> Result { Ok(get_str_act(None)?) } } +/// A buffer of size `N` representing `N` pixels. `N` can be smaller than the +/// entire number of pixels on the screen, in which case the screen will be +/// drawn to multiple times per frame. pub struct DrawBuffer { initialized: RunOnce, refresh_buffer: RefCell<[MaybeUninit; N]>, } impl DrawBuffer { + /// Constructs an empty `DrawBuffer`. pub const fn new() -> Self { Self { initialized: RunOnce::new(), @@ -96,7 +101,7 @@ impl DrawBuffer { } } -pub struct DisplayDriver { +pub(crate) struct DisplayDriver { pub(crate) disp_drv: lvgl_sys::lv_disp_drv_t, } @@ -140,8 +145,9 @@ pub struct Area { 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. +/// An 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 { pub area: Area, pub colors: [Color; N], diff --git a/lvgl/src/functions.rs b/lvgl/src/functions.rs index 914eaf1f..3450f735 100644 --- a/lvgl/src/functions.rs +++ b/lvgl/src/functions.rs @@ -5,6 +5,7 @@ use core::ptr::NonNull; use core::time::Duration; use core::{ptr, result}; +/// Internal LVGL error. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum CoreError { ResourceNotAvailable, @@ -40,9 +41,8 @@ pub(crate) fn get_str_act(disp: Option<&Display>) -> Result { )) } -/// You have to call this function periodically. -/// Expects a `tick_period` duration as argument which is the call period of this -/// function in milliseconds. +/// Runs an LVGL tick lasting a given `core::time::Duration`. This function +/// should be called periodically. #[inline] pub fn tick_inc(tick_period: Duration) { unsafe { @@ -50,13 +50,13 @@ pub fn tick_inc(tick_period: Duration) { } } -/// Call it periodically to handle tasks. +/// Calls the LVGL task handler. This function should be called periodically. #[inline] pub fn task_handler() { unsafe { lvgl_sys::lv_task_handler() }; } -/// Directly send an event to a widget +/// Directly send an event to a specific widget. #[inline] pub fn event_send(obj: &mut W, event: Event) -> LvResult<()> { unsafe { @@ -65,7 +65,7 @@ pub fn event_send(obj: &mut W, event: Event) -> LvRe Ok(()) } -/// Register an input device driver to LVGL +/// Register an input device driver to LVGL. pub fn indev_drv_register(input_device: &mut impl InputDriver) -> LvResult<()> { unsafe { let descr = lvgl_sys::lv_indev_drv_register(&mut input_device.get_driver() as *mut _); diff --git a/lvgl/src/input_device/generic.rs b/lvgl/src/input_device/generic.rs index f16e88a3..cf533322 100644 --- a/lvgl/src/input_device/generic.rs +++ b/lvgl/src/input_device/generic.rs @@ -1,11 +1,14 @@ use super::pointer::*; use crate::LvResult; +/// Generic data which can be associated with an input device driver. Varies +/// based on the concrete type of the input device driver #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] pub enum Data { Pointer(PointerInputData), } +/// Boolean states for an input. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] pub enum InputState { Released(Data), @@ -13,21 +16,24 @@ pub enum InputState { } impl InputState { + /// Represents a non-buffered input device. pub fn once(self) -> BufferStatus { BufferStatus::Once(self) } - + /// Represents a buffered input device. pub fn and_continued(self) -> BufferStatus { BufferStatus::Buffered(self) } } +/// Boolean buffering states for an input device driver. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] pub enum BufferStatus { Once(InputState), Buffered(InputState), } +/// A generic input driver trait. pub trait InputDriver { fn new(handler: F) -> D where diff --git a/lvgl/src/input_device/pointer.rs b/lvgl/src/input_device/pointer.rs index 1d463c38..43e75b65 100644 --- a/lvgl/src/input_device/pointer.rs +++ b/lvgl/src/input_device/pointer.rs @@ -2,9 +2,9 @@ use crate::Box; use crate::{LvError, LvResult}; use core::mem::MaybeUninit; use embedded_graphics::geometry::Point; - use super::generic::{BufferStatus, Data, InputDriver, InputState}; +/// Pointer-specific input data. Contains the point clicked and the key. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] pub enum PointerInputData { Touch(Point), @@ -21,6 +21,7 @@ impl PointerInputData { } } +/// Represents a pointer-type input driver. pub struct Pointer { pub(crate) driver: lvgl_sys::lv_indev_drv_t, pub(crate) descriptor: Option, @@ -114,12 +115,12 @@ where #[cfg(test)] mod test { - use super::*; + //use super::*; use core::marker::PhantomData; use embedded_graphics::draw_target::DrawTarget; use embedded_graphics::geometry::Size; use embedded_graphics::pixelcolor::PixelColor; - use embedded_graphics::pixelcolor::Rgb565; + //use embedded_graphics::pixelcolor::Rgb565; use embedded_graphics::prelude::OriginDimensions; use embedded_graphics::Pixel; @@ -137,7 +138,7 @@ mod test { type Color = C; type Error = (); - fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + fn draw_iter(&mut self, _pixels: I) -> Result<(), Self::Error> where I: IntoIterator>, { @@ -157,6 +158,7 @@ mod test { //#[test] // We cannot test right now by having instances of UI global state... :( // I need to find a way to test while having global state... + /* fn pointer_input_device() -> LvResult<()> { crate::init(); @@ -177,4 +179,5 @@ mod test { Ok(()) } + */ } diff --git a/lvgl/src/lib.rs b/lvgl/src/lib.rs index 398b1345..41b804c2 100644 --- a/lvgl/src/lib.rs +++ b/lvgl/src/lib.rs @@ -58,7 +58,9 @@ mod support; pub mod input_device; pub mod widgets; +/// Maximum horizontal display resolution, as defined in `lv_conf.h`. pub const HOR_RES_MAX: u32 = lvgl_sys::LV_HOR_RES_MAX; +/// Maximum vertical display resolution, as defined in `lv_conf.h`. pub const VER_RES_MAX: u32 = lvgl_sys::LV_VER_RES_MAX; struct RunOnce(AtomicBool); @@ -77,6 +79,7 @@ impl RunOnce { static LVGL_INITIALIZED: RunOnce = RunOnce::new(); +/// Initializes LVGL. Call at the start of the program. pub fn init() { if LVGL_INITIALIZED.swap_and_check() { unsafe { diff --git a/lvgl/src/lv_core/obj.rs b/lvgl/src/lv_core/obj.rs index 6779afb3..d1adcbf7 100644 --- a/lvgl/src/lv_core/obj.rs +++ b/lvgl/src/lv_core/obj.rs @@ -2,7 +2,7 @@ use crate::lv_core::style::Style; use crate::{Align, LvError, LvResult}; use core::ptr; -/// Represents a native LVGL object +/// Represents a native LVGL object. pub trait NativeObject { /// Provide common way to access to the underlying native object pointer. fn raw(&self) -> LvResult>; @@ -33,9 +33,9 @@ pub trait Widget: NativeObject { type Part: Into; /// Construct an instance of the object from a raw pointer. - /// fn from_raw(raw_pointer: ptr::NonNull) -> Self; + /// Adds a `Style` to a given widget. fn add_style(&self, part: Self::Part, style: &mut Style) -> LvResult<()> { unsafe { lvgl_sys::lv_obj_add_style( @@ -47,6 +47,7 @@ pub trait Widget: NativeObject { Ok(()) } + /// Sets a widget's position relative to its parent. fn set_pos(&mut self, x: i16, y: i16) -> LvResult<()> { unsafe { lvgl_sys::lv_obj_set_pos( @@ -58,6 +59,7 @@ pub trait Widget: NativeObject { Ok(()) } + /// Sets a widget's size. Alternatively, use `set_width()` and `set_height()`. fn set_size(&mut self, w: i16, h: i16) -> LvResult<()> { unsafe { lvgl_sys::lv_obj_set_size( @@ -69,6 +71,7 @@ pub trait Widget: NativeObject { Ok(()) } + /// Sets a widget's width. Alternatively, use `set_size()`. fn set_width(&mut self, w: u32) -> LvResult<()> { unsafe { lvgl_sys::lv_obj_set_width(self.raw()?.as_mut(), w as lvgl_sys::lv_coord_t); @@ -76,6 +79,7 @@ pub trait Widget: NativeObject { Ok(()) } + /// Sets a widget's height. Alternatively, use `set_size()`. fn set_height(&mut self, h: u32) -> LvResult<()> { unsafe { lvgl_sys::lv_obj_set_height(self.raw()?.as_mut(), h as lvgl_sys::lv_coord_t); @@ -83,6 +87,7 @@ pub trait Widget: NativeObject { Ok(()) } + /// Sets a widget's align relative to its parent along with an offset. fn set_align(&mut self, base: &mut C, align: Align, x_mod: i32, y_mod: i32) -> LvResult<()> where C: NativeObject, diff --git a/lvgl/src/mem.rs b/lvgl/src/mem.rs index df622b09..fbcca38d 100644 --- a/lvgl/src/mem.rs +++ b/lvgl/src/mem.rs @@ -97,14 +97,15 @@ mod test { use super::*; use crate::mem::mem_info; use crate::*; - use core::mem::MaybeUninit; use std::vec::Vec; + /* fn init() { unsafe { lvgl_sys::lv_init(); }; } + */ fn teardown() { unsafe { diff --git a/lvgl/src/support.rs b/lvgl/src/support.rs index a4b9d38b..c3d373ac 100644 --- a/lvgl/src/support.rs +++ b/lvgl/src/support.rs @@ -8,6 +8,7 @@ use embedded_graphics::pixelcolor::{Rgb565, Rgb888}; pub type LvResult = Result; +/// Generic LVGL error. All other errors can be coerced into it. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub enum LvError { InvalidReference, @@ -27,29 +28,31 @@ impl From for LvError { } } +/// An LVGL color. Equivalent to `lv_color_t`. #[derive(Copy, Clone, Default)] pub struct Color { pub(crate) raw: lvgl_sys::lv_color_t, } impl Color { + /// Creates a `Color` from red, green, and blue values. pub fn from_rgb((r, g, b): (u8, u8, u8)) -> Self { let raw = unsafe { lvgl_sys::_LV_COLOR_MAKE(r, g, b) }; Self { raw } } - + /// Creates a `Color` from a native `lv_color_t` instance. pub fn from_raw(raw: lvgl_sys::lv_color_t) -> Self { Self { raw } } - + /// Returns the value of the red channel. pub fn r(&self) -> u8 { unsafe { lvgl_sys::_LV_COLOR_GET_R(self.raw) as u8 } } - + /// Returns the value of the green channel. pub fn g(&self) -> u8 { unsafe { lvgl_sys::_LV_COLOR_GET_G(self.raw) as u8 } } - + /// Returns the value of the blue channel. pub fn b(&self) -> u8 { unsafe { lvgl_sys::_LV_COLOR_GET_B(self.raw) as u8 } } @@ -170,7 +173,7 @@ impl From> for lvgl_sys::lv_event_t { } } -/// These events are sent only by pointer-like input devices (E.g. mouse or touchpad) +/// Events sent only by pointer-like input devices (e.g. mouse or touchpad) #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] pub enum PointerEvent { DragBegin, @@ -197,6 +200,7 @@ pub(crate) unsafe extern "C" fn event_callback( } } +/// Possible LVGL alignments for widgets. pub enum Align { Center, InTopLeft, @@ -250,6 +254,7 @@ impl From for u8 { } } +/// Boolean for determining whether animations are enabled. pub enum Animation { ON, OFF,