Skip to main content

Crate lv_bevy_ecs

Crate lv_bevy_ecs 

Source
Expand description

§lv_bevy_ecs

Safe Rust bindings to the LVGL library using bevy_ecs. Compatible with #![no_std] environments by default.

Crates.io Docs License

§What is an ECS?

ECS stands for Entity Component System. You can think of it as a database with rows (entities), columns (components) and jobs (systems).

You have to move LVGL objects into this database, so that they don’t go out of scope and get deallocated. Bevy’s Observers will mirror these database operations to LVGL’s world.

§But I don’t want to use an ECS…

Enabling the no_ecs feature unlocks some functions that allow you to bring your own storage solution.

If you don’t care about storage at all, and know in advance that a Widget will live for the rest of the program’s execution, you can call Widget::leak() to leak memory and prevent calling the destructor.

Check out no_ecs.rs on how to use these.

§Usage

It is highly recommended to read Chapter 14 of the Unofficial Bevy Cheat Book before using this library.

  1. Create a project with cargo new or esp-generate, then
        cargo add lv_bevy_ecs
  1. This package depends on lightvgl-sys to generate the raw unsafe bindings. It needs an environment variable called DEP_LV_CONFIG_PATH that specifies the path to the folder containing lv_conf.h file.

    It is recommended to put it into .cargo/config.toml

[env]
DEP_LV_CONFIG_PATH = { relative = true, value = "." }
  1. Assign a tick callback that measures elapsed time in milliseconds. This must be done before creating the UI. For other frameworks (like FreeRTOS), you should use its tick counter instead to get precise and constant framerate.
lv_tick_set_cb(|| {
    let current_time = SystemTime::now();
    let since_epoch = current_time
        .duration_since(UNIX_EPOCH)
        .expect("Time should only go forward");
    since_epoch.as_millis() as u32
});
  1. You have to obtain a World instance with LvglWorld::default();. This is a global variable, it can be stored in a LazyLock or passed around in an Arc<Mutex<>> if needed elsewhere than in main().

static WORLD: LazyLock<Mutex<LvglWorld>> = LazyLock::new(|| Mutex::new(LvglWorld::default()));
  1. Last thing is to call lv_timer_handle() in every loop cycle.
loop {
    lv_timer_handler();
}

Check the documentation and the examples for further usage.

§Running the demo

sudo apt install libsdl2-dev

git clone git@github.com:SakiiCode/lv_bevy_ecs.git
cd lv_bevy_ecs
cargo run --example basic

§Building for embedded

There is an example project targeting the Cheap Yellow Display (ESP32) with std enabled: lvgl-bevy-demo

§Heap Allocation

§lvgl-alloc feature

If you don’t have an allocator, a GlobalAlloc for Rust leveraging the LVGL memory allocator is provided, but not enabled by default. Can be enabled with the feature lvgl-alloc. This will make all dynamic memory to be allocated by LVGL internal memory manager.

§rust-alloc feature

If you already have an allocator, you can enable the rust-alloc feature to forward the LVGL memory allocator functions to the Rust alloc crate. This needs LV_USE_STDLIB_MALLOC set to LV_STDLIB_CUSTOM in lv_conf.h.

§Minimizing binary size

In order to remove even more unused functions, the Cross-language Link-Time Optimization functionality of LLVM can be enabled. Unfortunately, this is not available on every platform, especially those that use gcc as the linker. Make sure to match your clang version with your rustc version.

.cargo/config.toml

[target.YOUR-TARGET-TRIPLE]
rustflags = ["-C", "linker-plugin-lto", "-C", "link-arg=-fuse-ld=lld"]
linker = "clang-21"

[env]
LV_COMPILE_ARGS = "-flto=full"
CC = "clang-21"

Cargo.toml

[profile.dev]
opt-level = "z"
lto = "fat"
codegen-units = 1

Additionally, an implementation of the get_memory_stats(&mut lv_mem_monitor_t) function must be provided. Check the examples for x86_64 implementation. It can be empty if not needed.

#[unsafe(no_mangle)]
pub fn get_memory_stats(monitor: &mut lv_bevy_ecs::sys::lv_mem_monitor_t) {
}

§Features

  • Displays
  • Widgets
  • Widget functions
  • Events
  • Styles
  • Input devices
  • Animations
  • Timers, lv_async_call
  • Subjects
  • Logging
  • LVGL allocator
  • “no_ecs” mode
  • #![no_std] compatibility
  • LVGL docstrings
  • Cross-language LTO
  • Defmt support
  • Auto-generated enums
  • File system
  • Custom fonts
  • Snapshots
  • Non-widget functions
  • Layouts
  • XML UI

§Compatibility table

lv_bevy_ecsbevy_ecslightvgl-sys
0.9.x0.18.x9.5.x
0.8.x0.18.x9.5.x
0.7.x0.18.x9.4.x
0.6.x0.17.x9.4.x
0.5.x0.17.x9.4.x
0.4.x0.17.x9.3.x
0.3.x0.16.x9.3.x
0.2.x0.16.x9.2.x

§Contributing

Feel free to open issues for features you find important and missing. I am not completely satisfied with the API, so open to API improvement ideas as well.

§Troubleshooting

§Unable to generate bindings: fatal error: ‘inttypes.h’ file not found

Try adding this environment variable to .cargo/config.toml:

BINDGEN_EXTRA_CLANG_ARGS = "-I/usr/include"

§Thanks

This project heavily builds upon the work in the the original lv_binding_rust repo.

Re-exports§

pub use logging::error_ as error;
pub use logging::info_ as info;
pub use logging::trace_ as trace;
pub use logging::warn_ as warn;

Modules§

animation
Animations
bevy
Re-exported modules from bevy_ecs
display
Display
events
Events
functions
Auto-generated safe bindings to LVGL functions
input
Input
logging
Logging
styles
Styles
subjects
Subjects
support
Utility structs and functions
sys
Re-exported modules from lightvgl_sys
timers
Timers
widgets
Widgets

Macros§

cstr
error_
func
info_
setup_test_display
Using a macro because #[cfg(doctest)] does not work properly
trace_
warn_