diff --git a/examples/Cargo.toml b/examples/Cargo.toml deleted file mode 100644 index 52dd9342..00000000 --- a/examples/Cargo.toml +++ /dev/null @@ -1,38 +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" -heapless = "0.5.5" -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 new file mode 100644 index 00000000..84ab7e95 --- /dev/null +++ b/examples/app.rs @@ -0,0 +1,35 @@ +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, 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(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 shared_native_display = RefCell::new(embedded_graphics_display); + + // LVGL usage + lvgl::init(); + + 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()).unwrap(); + }) + .unwrap(); + + let label: Label = "Nice!".into(); +} diff --git a/examples/arc.rs b/examples/arc.rs index 28c03f63..162a58f4 100644 --- a/examples/arc.rs +++ b/examples/arc.rs @@ -4,11 +4,17 @@ use embedded_graphics::prelude::*; use embedded_graphics_simulator::{ OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, }; +use lvgl; use lvgl::style::Style; use lvgl::widgets::{Arc, Label, LabelAlign}; -use lvgl::{self, Align, Color, Part, State, UI}; -use lvgl::{LvError, Widget}; -use std::time::Instant; +use lvgl::{ + 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; fn mem_info() -> lvgl_sys::lv_mem_monitor_t { let mut info = lvgl_sys::lv_mem_monitor_t { @@ -35,58 +41,72 @@ fn main() -> Result<(), LvError> { } fn run_arc_demo() -> 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("Arc 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((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::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(&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 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(); + window.update(&shared_native_display.borrow()); for event in window.events() { match event { @@ -94,9 +114,7 @@ fn run_arc_demo() -> Result<(), LvError> { _ => {} } } - - ui.tick_inc(loop_started.elapsed()); - loop_started = Instant::now(); + lvgl::tick_inc(Duration::from_millis(15)); } Ok(()) diff --git a/examples/bar.rs b/examples/bar.rs index ea428d57..0a3df17b 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; 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,28 @@ 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 +82,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..7d809ba6 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 std::thread::sleep; -use std::time::{Duration, Instant}; +use lvgl::{ + Align, Color, Display, DrawBuffer, LvError, Part, State, Widget, HOR_RES_MAX, VER_RES_MAX, +}; +use std::cell::RefCell; +use std::time::Duration; +#[allow(unused_assignments)] 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 aaaaaee2..bc776177 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -7,59 +7,85 @@ 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, 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> { - 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 output_settings = OutputSettingsBuilder::new().scale(1).build(); let mut window = Window::new("PineTime", &output_settings); - let mut ui = UI::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. + let buffer = DrawBuffer::<{ (HOR_RES_MAX * VER_RES_MAX) as usize }>::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())?; - // Implement and register your display: - ui.disp_drv_register(display).unwrap(); + // 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(&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()?; + + 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 +94,8 @@ fn main() -> Result<(), LvError> { time.set_text(&val)?; i = 1 + 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 { @@ -77,12 +103,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 +123,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/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/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/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 new file mode 100644 index 00000000..082f4e48 --- /dev/null +++ b/lvgl-codegen/src/analysis.rs @@ -0,0 +1,68 @@ +#![allow(dead_code)] + +/// 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..c2289362 100644 --- a/lvgl/Cargo.toml +++ b/lvgl/Cargo.toml @@ -14,11 +14,13 @@ 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" [features] +default = ["embedded_graphics"] +embedded_graphics = ["embedded-graphics"] alloc = ["cstr_core/alloc"] lvgl_alloc = ["alloc"] use-vendored-config = ["lvgl-sys/use-vendored-config"] @@ -31,28 +33,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..f25ce844 100644 --- a/lvgl/src/allocator.rs +++ b/lvgl/src/allocator.rs @@ -4,17 +4,18 @@ 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 { 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..5175233e --- /dev/null +++ b/lvgl/src/display.rs @@ -0,0 +1,272 @@ +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}; + +/// Error in interacting with a `Display`. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum DisplayError { + NotAvailable, + FailedToRegister, + NotRegistered, +} + +type Result = result::Result; + +/// An LVGL-registered display. Equivalent to an `lv_disp_t`. +pub struct Display { + pub(crate) disp: NonNull, +} + +impl<'a> Display { + pub(crate) fn from_raw(disp: NonNull) -> Self { + 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, + ) -> Result + where + F: FnMut(&DisplayRefresh) + 'a, + { + 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(crate) struct DefaultDisplay {} + +impl DefaultDisplay { + /// 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(), + refresh_buffer: 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; + 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(crate) struct DisplayDriver { + pub(crate) disp_drv: lvgl_sys::lv_disp_drv_t, +} + +impl<'a> DisplayDriver { + pub fn new( + draw_buffer: &'a DrawBuffer, + display_update_callback: F, + ) -> Result + where + F: FnMut(&DisplayRefresh) + 'a, + { + 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, +} + +/// 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], +} + +#[cfg(feature = "embedded_graphics")] +mod embedded_graphics_impl { + use crate::{Color, DisplayRefresh}; + use embedded_graphics::prelude::*; + use embedded_graphics::Pixel; + + 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]; + Pixel(Point::new(x as i32, y as i32), raw_color.into()) + }) + }) + .flatten() + } + } +} + +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) + 'a, +{ + 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..3450f735 --- /dev/null +++ b/lvgl/src/functions.rs @@ -0,0 +1,75 @@ +use crate::display::{Display, DisplayDriver}; +use crate::input_device::generic::InputDriver; +use crate::{Event, LvResult, Obj, Widget}; +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, + 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)?, + )) +} + +/// 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 { + lvgl_sys::lv_tick_inc(tick_period.as_millis() as u32); + } +} + +/// 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 specific 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..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,25 +16,29 @@ 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), } -pub trait DisplayDriver { +/// A generic input driver trait. +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..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}; -use super::generic::{BufferStatus, Data, DisplayDriver, 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,12 +21,13 @@ impl PointerInputData { } } +/// Represents a pointer-type input driver. pub struct Pointer { pub(crate) driver: lvgl_sys::lv_indev_drv_t, pub(crate) descriptor: Option, } -impl DisplayDriver for Pointer { +impl InputDriver for Pointer { fn new(handler: F) -> Self where F: Fn() -> BufferStatus, @@ -47,8 +48,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); @@ -110,13 +115,12 @@ where #[cfg(test)] mod test { - use super::*; - use crate::UI; + //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; @@ -134,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>, { @@ -154,12 +158,14 @@ 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<()> { - 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,8 +175,9 @@ 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 47e527ca..41b804c2 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,64 @@ pub(crate) mod mem; #[cfg(not(feature = "lvgl_alloc"))] use crate::mem::Box; +use core::sync::atomic::{AtomicBool, Ordering}; + +pub use display::*; +pub use functions::*; pub use lv_core::*; pub use support::*; -pub use ui::*; -use core::sync::atomic::{AtomicBool, Ordering}; +mod display; +mod functions; +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; -// Initialize LVGL only once. -static LVGL_INITIALIZED: AtomicBool = AtomicBool::new(false); +struct RunOnce(AtomicBool); + +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(crate) fn lvgl_init() { - if LVGL_INITIALIZED - .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) - .is_ok() - { +/// Initializes LVGL. Call at the start of the program. +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(); + + 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(&buffer, |_| {}).unwrap(); + } + } +} diff --git a/lvgl/src/lv_core/obj.rs b/lvgl/src/lv_core/obj.rs index e498725e..d1adcbf7 100644 --- a/lvgl/src/lv_core/obj.rs +++ b/lvgl/src/lv_core/obj.rs @@ -1,9 +1,8 @@ use crate::lv_core::style::Style; -use crate::Box; 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>; @@ -34,19 +33,21 @@ pub trait Widget: NativeObject { type Part: Into; /// 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<()> { + /// 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(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(()) } + /// 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, @@ -104,7 +109,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 +172,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 +181,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..fbcca38d 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,17 +75,37 @@ 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() { unsafe { lvgl_sys::lv_init(); }; } + */ fn teardown() { unsafe { @@ -95,7 +115,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 +127,7 @@ mod test { #[test] fn place_complex_value_in_lv_mem() { - crate::lvgl_init(); + tests::initialize_test(); #[repr(C)] #[derive(Debug)] @@ -161,7 +181,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 +191,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..c3d373ac 100644 --- a/lvgl/src/support.rs +++ b/lvgl/src/support.rs @@ -1,10 +1,14 @@ +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; +/// Generic LVGL error. All other errors can be coerced into it. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub enum LvError { InvalidReference, @@ -13,34 +17,48 @@ 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, + } + } +} + +/// 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 } } } +#[cfg(feature = "embedded_graphics")] impl From for Rgb888 { fn from(color: Color) -> Self { unsafe { @@ -53,6 +71,7 @@ impl From for Rgb888 { } } +#[cfg(feature = "embedded_graphics")] impl From for Rgb565 { fn from(color: Color) -> Self { unsafe { @@ -154,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, @@ -181,6 +200,7 @@ pub(crate) unsafe extern "C" fn event_callback( } } +/// Possible LVGL alignments for widgets. pub enum Align { Center, InTopLeft, @@ -234,6 +254,7 @@ impl From for u8 { } } +/// Boolean for determining whether animations are enabled. pub enum Animation { ON, OFF, 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..acbb7858 100644 --- a/lvgl/src/widgets/label.rs +++ b/lvgl/src/widgets/label.rs @@ -10,6 +10,36 @@ 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 {