Compare commits

...

4 commits

15 changed files with 316 additions and 43 deletions

View file

@ -13,6 +13,10 @@ embedded-graphics-simulator = "0.2.0"
heapless = "0.5.5" heapless = "0.5.5"
cstr_core = { version = "0.2.0", features = ["alloc"] } cstr_core = { version = "0.2.0", features = ["alloc"] }
[[example]]
name = "simple"
path = "simple.rs"
[[example]] [[example]]
name = "demo" name = "demo"
path = "demo.rs" path = "demo.rs"

View file

@ -8,14 +8,11 @@ use lvgl::style::Style;
use lvgl::widgets::{Arc, Label, LabelAlign}; use lvgl::widgets::{Arc, Label, LabelAlign};
use lvgl::{self, Align, Color, Part, State, UI}; use lvgl::{self, Align, Color, Part, State, UI};
use lvgl::{LvError, Widget}; use lvgl::{LvError, Widget};
use lvgl_sys;
use std::time::Instant; use std::time::Instant;
fn main() -> Result<(), LvError> { fn main() -> Result<(), LvError> {
let display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new( let display: SimulatorDisplay<Rgb565> =
lvgl_sys::LV_HOR_RES_MAX, SimulatorDisplay::new(Size::new(lvgl::HOR_RES_MAX, lvgl::VER_RES_MAX));
lvgl_sys::LV_VER_RES_MAX,
));
let output_settings = OutputSettingsBuilder::new().scale(2).build(); let output_settings = OutputSettingsBuilder::new().scale(2).build();
let mut window = Window::new("Arc Example", &output_settings); let mut window = Window::new("Arc Example", &output_settings);

View file

@ -7,14 +7,11 @@ use embedded_graphics_simulator::{
use lvgl::style::Style; use lvgl::style::Style;
use lvgl::widgets::{Bar, Label, LabelAlign}; use lvgl::widgets::{Bar, Label, LabelAlign};
use lvgl::{self, Align, Animation, Color, Event, LvError, Part, State, Widget, UI}; use lvgl::{self, Align, Animation, Color, Event, LvError, Part, State, Widget, UI};
use lvgl_sys;
use std::time::Instant; use std::time::Instant;
fn main() -> Result<(), LvError> { fn main() -> Result<(), LvError> {
let display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new( let display: SimulatorDisplay<Rgb565> =
lvgl_sys::LV_HOR_RES_MAX, SimulatorDisplay::new(Size::new(lvgl::HOR_RES_MAX, lvgl::VER_RES_MAX));
lvgl_sys::LV_VER_RES_MAX,
));
let output_settings = OutputSettingsBuilder::new().scale(2).build(); let output_settings = OutputSettingsBuilder::new().scale(2).build();
let mut window = Window::new("Bar Example", &output_settings); let mut window = Window::new("Bar Example", &output_settings);

View file

@ -4,17 +4,18 @@ use embedded_graphics::prelude::*;
use embedded_graphics_simulator::{ use embedded_graphics_simulator::{
OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window,
}; };
use lvgl::input_device::{BufferStatus, InputData, Pointer};
use lvgl::style::Style; use lvgl::style::Style;
use lvgl::widgets::{Btn, Label}; use lvgl::widgets::{Btn, Label};
use lvgl::{self, Align, Color, Event, LvError, Part, State, Widget, UI}; use lvgl::{self, Align, Color, LvError, Part, State, Widget, UI};
use lvgl_sys; use std::cell::RefCell;
use std::time::Instant; use std::rc::Rc;
use std::thread::sleep;
use std::time::{Duration, Instant};
fn main() -> Result<(), LvError> { fn main() -> Result<(), LvError> {
let display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new( let display: SimulatorDisplay<Rgb565> =
lvgl_sys::LV_HOR_RES_MAX, SimulatorDisplay::new(Size::new(lvgl::HOR_RES_MAX, lvgl::VER_RES_MAX));
lvgl_sys::LV_VER_RES_MAX,
));
let output_settings = OutputSettingsBuilder::new().scale(2).build(); let output_settings = OutputSettingsBuilder::new().scale(2).build();
let mut window = Window::new("Bar Example", &output_settings); let mut window = Window::new("Bar Example", &output_settings);
@ -24,6 +25,21 @@ fn main() -> Result<(), LvError> {
// Implement and register your display: // Implement and register your display:
ui.disp_drv_register(display)?; ui.disp_drv_register(display)?;
// Initial state of input
let latest_touch_status: Rc<RefCell<BufferStatus>> = Rc::new(RefCell::new(
InputData::Touch(Point::new(0, 0)).released().once(),
));
// Register the input mode
let internal = Rc::clone(&latest_touch_status);
let mut touch_screen = Pointer::new(move || {
let input_status = internal.borrow().clone();
//println!("input_status = {:?}", input_status);
input_status
});
ui.indev_drv_register(&mut touch_screen)?;
// Create screen and widgets // Create screen and widgets
let mut screen = ui.scr_act()?; let mut screen = ui.scr_act()?;
@ -40,6 +56,7 @@ fn main() -> Result<(), LvError> {
let mut btn_state = false; let mut btn_state = false;
button.on_event(|mut btn, event| { button.on_event(|mut btn, event| {
println!("Some event! {:?}", event);
if let lvgl::Event::Clicked = event { if let lvgl::Event::Clicked = event {
if btn_state { if btn_state {
let nt = CString::new("Click me!").unwrap(); let nt = CString::new("Click me!").unwrap();
@ -55,11 +72,22 @@ fn main() -> Result<(), LvError> {
})?; })?;
let mut loop_started = Instant::now(); let mut loop_started = Instant::now();
let mut latest_touch_point = Point::new(0, 0);
'running: loop { 'running: loop {
ui.task_handler(); ui.task_handler();
window.update(ui.get_display_ref().unwrap()); window.update(ui.get_display_ref().unwrap());
for event in window.events() { let mut events = window.events().peekable();
if events.peek().is_none() {
latest_touch_status.replace(
InputData::Touch(latest_touch_point.clone())
.released()
.once(),
);
}
for event in events {
match event { match event {
SimulatorEvent::MouseButtonUp { SimulatorEvent::MouseButtonUp {
mouse_btn: _, mouse_btn: _,
@ -67,13 +95,16 @@ fn main() -> Result<(), LvError> {
} => { } => {
println!("Clicked on: {:?}", point); println!("Clicked on: {:?}", point);
// Send a event to the button directly // Send a event to the button directly
ui.event_send(&mut button, Event::Clicked)?; latest_touch_point = point.clone();
latest_touch_status.replace(InputData::Touch(point).pressed().once());
} }
SimulatorEvent::Quit => break 'running, SimulatorEvent::Quit => break 'running,
_ => {} _ => {}
} }
} }
sleep(Duration::from_millis(50));
ui.tick_inc(loop_started.elapsed()); ui.tick_inc(loop_started.elapsed());
loop_started = Instant::now(); loop_started = Instant::now();
} }

View file

@ -13,10 +13,8 @@ use std::thread::sleep;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
fn main() -> Result<(), LvError> { fn main() -> Result<(), LvError> {
let display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new( let display: SimulatorDisplay<Rgb565> =
lvgl_sys::LV_HOR_RES_MAX, SimulatorDisplay::new(Size::new(lvgl::HOR_RES_MAX, lvgl::VER_RES_MAX));
lvgl_sys::LV_VER_RES_MAX,
));
let output_settings = OutputSettingsBuilder::new().scale(2).build(); let output_settings = OutputSettingsBuilder::new().scale(2).build();
let mut window = Window::new("PineTime", &output_settings); let mut window = Window::new("PineTime", &output_settings);

View file

@ -6,14 +6,11 @@ use embedded_graphics_simulator::{
use lvgl::style::{Opacity, Style}; use lvgl::style::{Opacity, Style};
use lvgl::widgets::Gauge; use lvgl::widgets::Gauge;
use lvgl::{self, Align, Color, LvError, Part, State, Widget, UI}; use lvgl::{self, Align, Color, LvError, Part, State, Widget, UI};
use lvgl_sys;
use std::time::Instant; use std::time::Instant;
fn main() -> Result<(), LvError> { fn main() -> Result<(), LvError> {
let display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new( let display: SimulatorDisplay<Rgb565> =
lvgl_sys::LV_HOR_RES_MAX, SimulatorDisplay::new(Size::new(lvgl::HOR_RES_MAX, lvgl::VER_RES_MAX));
lvgl_sys::LV_VER_RES_MAX,
));
let output_settings = OutputSettingsBuilder::new().scale(2).build(); let output_settings = OutputSettingsBuilder::new().scale(2).build();
let mut window = Window::new("Gauge Example", &output_settings); let mut window = Window::new("Gauge Example", &output_settings);
@ -36,7 +33,7 @@ fn main() -> Result<(), LvError> {
gauge_style.set_radius(State::DEFAULT, 5); gauge_style.set_radius(State::DEFAULT, 5);
gauge_style.set_bg_opa(State::DEFAULT, Opacity::OPA_COVER); gauge_style.set_bg_opa(State::DEFAULT, Opacity::OPA_COVER);
gauge_style.set_bg_color(State::DEFAULT, Color::from_rgb((192, 192, 192))); gauge_style.set_bg_color(State::DEFAULT, Color::from_rgb((192, 192, 192)));
// Set some paddings // Set some padding's
gauge_style.set_pad_inner(State::DEFAULT, 20); gauge_style.set_pad_inner(State::DEFAULT, 20);
gauge_style.set_pad_top(State::DEFAULT, 20); gauge_style.set_pad_top(State::DEFAULT, 20);
gauge_style.set_pad_left(State::DEFAULT, 5); gauge_style.set_pad_left(State::DEFAULT, 5);

52
examples/simple.rs Normal file
View file

@ -0,0 +1,52 @@
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<Rgb565> =
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(())
}

View file

@ -1,6 +1,6 @@
[package] [package]
name = "lvgl-codegen" name = "lvgl-codegen"
version = "0.3.3" version = "0.4.0"
description = "Code generation based on LVGL source code" description = "Code generation based on LVGL source code"
authors = ["Rafael Caricio <crates.lvgl@caric.io>"] authors = ["Rafael Caricio <crates.lvgl@caric.io>"]
readme = "README.md" readme = "README.md"

View file

@ -1,7 +1,7 @@
[package] [package]
name = "lvgl-sys" name = "lvgl-sys"
description = "Raw bindings to the LittlevGL C library." description = "Raw bindings to the LittlevGL C library."
version = "0.3.3" version = "0.4.0"
authors = ["Rafael Caricio <crates.lvgl-sys@caric.io>"] authors = ["Rafael Caricio <crates.lvgl-sys@caric.io>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"

View file

@ -1,7 +1,7 @@
[package] [package]
name = "lvgl" name = "lvgl"
description = "LittlevGL bindings for Rust. A powerful and easy-to-use embedded GUI with many widgets, advanced visual effects (opacity, antialiasing, animations) and low memory requirements (16K RAM, 64K Flash)." description = "LittlevGL bindings for Rust. A powerful and easy-to-use embedded GUI with many widgets, advanced visual effects (opacity, antialiasing, animations) and low memory requirements (16K RAM, 64K Flash)."
version = "0.3.3" version = "0.4.0"
authors = ["Rafael Caricio <crates.lvgl@caric.io>"] authors = ["Rafael Caricio <crates.lvgl@caric.io>"]
edition = "2018" edition = "2018"
repository = "https://github.com/rafaelcaricio/lvgl-rs" repository = "https://github.com/rafaelcaricio/lvgl-rs"
@ -12,7 +12,7 @@ keywords = ["littlevgl", "lvgl", "graphical_interfaces"]
build = "build.rs" build = "build.rs"
[dependencies] [dependencies]
lvgl-sys = { version = "0.3.3", path = "../lvgl-sys" } lvgl-sys = { version = "0.4.0", path = "../lvgl-sys" }
cty = "0.2.1" cty = "0.2.1"
embedded-graphics = "0.6.2" embedded-graphics = "0.6.2"
cstr_core = { version = "0.2.0" } cstr_core = { version = "0.2.0" }
@ -21,6 +21,6 @@ bitflags = "1.2.1"
[build-dependencies] [build-dependencies]
quote = "1.0.7" quote = "1.0.7"
proc-macro2 = "1.0.18" proc-macro2 = "1.0.18"
lvgl-codegen = { version = "0.3.3", path = "../lvgl-codegen" } lvgl-codegen = { version = "0.4.0", path = "../lvgl-codegen" }
lvgl-sys = { version = "0.3.3", path = "../lvgl-sys" } lvgl-sys = { version = "0.4.0", path = "../lvgl-sys" }

177
lvgl/src/input_device.rs Normal file
View file

@ -0,0 +1,177 @@
use crate::mem::Box;
use crate::LvResult;
use core::mem::MaybeUninit;
use embedded_graphics::geometry::Point;
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum InputData {
Touch(Point),
Key(u32),
}
impl InputData {
pub fn released(self) -> InputState {
InputState::Released(self)
}
pub fn pressed(self) -> InputState {
InputState::Pressed(self)
}
}
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum InputState {
Released(InputData),
Pressed(InputData),
}
impl InputState {
pub fn once(self) -> BufferStatus {
BufferStatus::Once(self)
}
pub fn and_continued(self) -> BufferStatus {
BufferStatus::Buffered(self)
}
}
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum BufferStatus {
Once(InputState),
Buffered(InputState),
}
pub struct Pointer {
pub(crate) driver: lvgl_sys::lv_indev_drv_t,
pub(crate) descriptor: Option<lvgl_sys::lv_indev_t>,
}
impl Pointer {
pub fn new<F>(mut handler: F) -> Self
where
F: Fn() -> BufferStatus,
{
let driver = unsafe {
let mut indev_drv = MaybeUninit::uninit();
lvgl_sys::lv_indev_drv_init(indev_drv.as_mut_ptr());
let mut indev_drv = indev_drv.assume_init();
indev_drv.type_ = lvgl_sys::LV_INDEV_TYPE_POINTER as lvgl_sys::lv_indev_type_t;
indev_drv.read_cb = Some(read_input::<F>);
indev_drv.user_data = Box::into_raw(Box::new(handler).unwrap()) as *mut _
as lvgl_sys::lv_indev_drv_user_data_t;
indev_drv
};
Self {
driver,
descriptor: None,
}
}
pub(crate) unsafe fn set_descriptor(
&mut self,
descriptor: *mut lvgl_sys::lv_indev_t,
) -> LvResult<()> {
// TODO: check if not null && check if `self.descriptor` is not already set!
self.descriptor = Some(*descriptor);
Ok(())
}
}
unsafe extern "C" fn read_input<F>(
indev_drv: *mut lvgl_sys::lv_indev_drv_t,
data: *mut lvgl_sys::lv_indev_data_t,
) -> bool
where
F: Fn() -> BufferStatus,
{
// convert user data to function
let user_closure = &mut *((*indev_drv).user_data as *mut F);
// call user data
let info: BufferStatus = user_closure();
match info {
BufferStatus::Once(InputState::Pressed(InputData::Touch(point))) => {
(*data).point.x = point.x as lvgl_sys::lv_coord_t;
(*data).point.y = point.y as lvgl_sys::lv_coord_t;
(*data).state = lvgl_sys::LV_INDEV_STATE_PR as lvgl_sys::lv_indev_state_t;
false
}
BufferStatus::Once(InputState::Released(InputData::Touch(point))) => {
(*data).point.x = point.x as lvgl_sys::lv_coord_t;
(*data).point.y = point.y as lvgl_sys::lv_coord_t;
(*data).state = lvgl_sys::LV_INDEV_STATE_REL as lvgl_sys::lv_indev_state_t;
false
}
BufferStatus::Buffered(InputState::Pressed(InputData::Touch(point))) => {
(*data).point.x = point.x as lvgl_sys::lv_coord_t;
(*data).point.y = point.y as lvgl_sys::lv_coord_t;
(*data).state = lvgl_sys::LV_INDEV_STATE_PR as lvgl_sys::lv_indev_state_t;
true
}
BufferStatus::Buffered(InputState::Released(InputData::Touch(point))) => {
(*data).point.x = point.x as lvgl_sys::lv_coord_t;
(*data).point.y = point.y as lvgl_sys::lv_coord_t;
(*data).state = lvgl_sys::LV_INDEV_STATE_REL as lvgl_sys::lv_indev_state_t;
true
}
BufferStatus::Once(InputState::Released(InputData::Key(_))) => false,
BufferStatus::Once(InputState::Pressed(InputData::Key(_))) => false,
BufferStatus::Buffered(InputState::Released(InputData::Key(_))) => true,
BufferStatus::Buffered(InputState::Pressed(InputData::Key(_))) => true,
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{Color, UI};
use core::marker::PhantomData;
use embedded_graphics::drawable::Pixel;
use embedded_graphics::geometry::Size;
use embedded_graphics::pixelcolor::PixelColor;
use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::DrawTarget;
struct FakeDisplay<C>
where
C: PixelColor + From<Color>,
{
p: PhantomData<C>,
}
impl<C> DrawTarget<C> for FakeDisplay<C>
where
C: PixelColor + From<Color>,
{
type Error = ();
fn draw_pixel(&mut self, item: Pixel<C>) -> Result<(), Self::Error> {
Ok(())
}
fn size(&self) -> Size {
Size::new(crate::VER_RES_MAX, crate::HOR_RES_MAX)
}
}
//#[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()?;
let disp: FakeDisplay<Rgb565> = FakeDisplay { p: PhantomData };
ui.disp_drv_register(disp);
fn read_touchpad_device() -> Point {
Point::new(120, 23)
}
let mut touch_screen =
Pointer::new(|| InputData::Touch(read_touchpad_device()).pressed().once());
ui.indev_drv_register(&mut touch_screen)?;
Ok(())
}
}

View file

@ -3,6 +3,7 @@
#[macro_use] #[macro_use]
extern crate bitflags; extern crate bitflags;
pub mod input_device;
pub(crate) mod mem; pub(crate) mod mem;
mod support; mod support;
mod ui; mod ui;
@ -13,3 +14,6 @@ pub mod widgets;
pub use lv_core::*; pub use lv_core::*;
pub use support::*; pub use support::*;
pub use ui::*; pub use ui::*;
pub const HOR_RES_MAX: u32 = lvgl_sys::LV_HOR_RES_MAX;
pub const VER_RES_MAX: u32 = lvgl_sys::LV_VER_RES_MAX;

View file

@ -70,16 +70,17 @@ impl<T> AsMut<T> for Box<T> {
mod test { mod test {
use super::*; use super::*;
use core::mem::MaybeUninit; use core::mem::MaybeUninit;
use std::sync::Once;
static INIT_LVGL: Once = Once::new();
fn init() { fn init() {
INIT_LVGL.call_once(|| {
unsafe { unsafe {
lvgl_sys::lv_init(); lvgl_sys::lv_init();
}; };
}); }
fn teardown() {
unsafe {
lvgl_sys::lv_deinit();
}
} }
#[test] #[test]
@ -90,6 +91,8 @@ mod test {
drop(v); drop(v);
let v = Box::new(10).unwrap(); let v = Box::new(10).unwrap();
drop(v); drop(v);
teardown();
} }
#[test] #[test]
@ -129,6 +132,8 @@ mod test {
} }
assert_eq!(point.x, i); assert_eq!(point.x, i);
} }
teardown();
} }
fn print_mem_info() { fn print_mem_info() {

View file

@ -61,6 +61,7 @@ impl From<Color> for Rgb565 {
/// ///
/// All objects (such as Buttons/Labels/Sliders etc.) receive these generic events /// All objects (such as Buttons/Labels/Sliders etc.) receive these generic events
/// regardless of their type. /// regardless of their type.
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum Event<T> { pub enum Event<T> {
/// The object has been pressed /// The object has been pressed
Pressed, Pressed,
@ -140,6 +141,7 @@ impl<S> From<Event<S>> for lvgl_sys::lv_event_t {
} }
/// These events are sent only by pointer-like input devices (E.g. mouse or touchpad) /// These events are sent only by pointer-like input devices (E.g. mouse or touchpad)
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum PointerEvent { pub enum PointerEvent {
DragBegin, DragBegin,
DragEnd, DragEnd,

View file

@ -1,3 +1,4 @@
use crate::input_device::{BufferStatus, Pointer};
use crate::mem::Box; use crate::mem::Box;
use crate::{Color, Event, LvError, LvResult, Obj, Widget}; use crate::{Color, Event, LvError, LvResult, Obj, Widget};
use core::marker::PhantomData; use core::marker::PhantomData;
@ -56,6 +57,14 @@ where
} }
} }
pub fn indev_drv_register(&mut self, input_device: &mut Pointer) -> LvResult<()> {
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<()> { pub fn disp_drv_register(&mut self, display: T) -> LvResult<()> {
self.display_data = Some(DisplayUserData { self.display_data = Some(DisplayUserData {
display, display,