| Crates.io | lv_bevy_ecs |
| lib.rs | lv_bevy_ecs |
| version | 0.6.5 |
| created_at | 2025-09-26 22:08:36.170505+00 |
| updated_at | 2026-01-09 21:51:12.950322+00 |
| description | Safe Rust bindings to the LVGL graphics library using Bevy's ECS framework |
| homepage | https://github.com/SakiiCode/lv_bevy_ecs |
| repository | https://github.com/SakiiCode/lv_bevy_ecs |
| max_upload_size | |
| id | 1856727 |
| size | 202,230 |
Safe Rust bindings to the LVGL library using bevy_ecs. Compatible with #![no_std] environments after setting default-features = false.
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.
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.
It is highly recommended to read Chapter 14 of the Unofficial Bevy Cheat Book before using this library.
cargo new or esp-generate, then cargo add lv_bevy_ecs
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 = "." }
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().# use lv_bevy_ecs::widgets::LvglWorld;
# use std::sync::{LazyLock, Mutex};
static WORLD: LazyLock<Mutex<LvglWorld>> = LazyLock::new(|| Mutex::new(LvglWorld::default()));
Last thing is to calculate frametime and call these LVGL functions in every loop cycle
If you are running this inside another framework (like FreeRTOS), you should use its tick counter instead to get precise and constant framerate.
# use lv_bevy_ecs::functions::*;
# use std::time::{Instant, Duration};
#
let mut prev_time = Instant::now();
// ...
loop {
let current_time = Instant::now();
let diff = current_time.duration_since(prev_time);
prev_time = current_time;
// ...
lv_tick_inc(diff);
lv_timer_handler();
# break;
}
Check the documentation and the examples for further usage.
sudo apt install libsdl2-dev
git clone git@github.com:SakiiCode/lv_bevy_ecs.git
cd lv_bevy_ecs
cargo run --example basic
There is an example project targeting the Cheap Yellow Display (ESP32) with std enabled: lvgl-bevy-demo
A global allocator 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.
| lv_bevy_ecs | bevy_ecs | lightvgl-sys | LVGL |
|---|---|---|---|
| 0.6.2 | 0.17.3 | 9.4.3 | 9.4.0 |
| 0.6.1 | 0.17.3 | 9.4.2 | 9.4.0 |
| 0.5.2 | 0.17.3 | 9.4.2 | 9.4.0 |
| 0.5.0 | 0.17.2 | 9.4.0 | 9.4.0 |
| 0.4.0 | 0.17.2 | 9.3.0 | 9.3.0 |
| 0.3.0 | 0.16.0 | 9.3.0 | 9.3.0 |
| 0.2.0 | 0.16.0 | 9.2.0 | 9.2.2 |
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.
You are probably on RISC-V. Please help your architecture get upstreamed into rust-ctor.
Until then set default-features = false and manually call lv_init(); in the main function.
std required by dtor because it does not declare #![no_std]Set default-features = false and manually call lv_init(); in the main function.
Try adding this environment variable to .cargo/config.toml:
BINDGEN_EXTRA_CLANG_ARGS = "-I/usr/include"
This project heavily builds upon the work in the the original lv_binding_rust repo.