use std::{
ffi::{CStr, CString, c_void},
process::exit,
str::FromStr,
sync::{
Mutex,
atomic::{AtomicBool, Ordering},
},
thread::sleep,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use lv_bevy_ecs::{
animation::Animation,
bevy::{component::Component, entity::Entity, hierarchy::Children, query::With, world::World},
display::{Display, DrawBuffer},
error,
events::Event,
functions::*,
info,
input::{BufferStatus, InputDevice, InputEvent, InputState, Pointer},
styles::Style,
subjects::Subject,
support::{LV_SIZE_CONTENT, OpacityLevel},
sys::{
LV_ANIM_REPEAT_INFINITE, LV_GRID_CONTENT, LV_GRID_TEMPLATE_LAST, LV_SYMBOL_FILE,
lv_align_t_LV_ALIGN_BOTTOM_RIGHT, lv_anim_path_ease_out, lv_anim_set_path_cb,
lv_anim_set_repeat_count, lv_area_t, lv_buttonmatrix_ctrl_t_LV_BUTTONMATRIX_CTRL_CHECKED,
lv_buttonmatrix_ctrl_t_LV_BUTTONMATRIX_CTRL_DISABLED,
lv_chart_axis_t_LV_CHART_AXIS_PRIMARY_X, lv_chart_set_type,
lv_chart_type_t_LV_CHART_TYPE_BAR, lv_chart_type_t_LV_CHART_TYPE_LINE,
lv_color_format_t_LV_COLOR_FORMAT_RGB565, lv_color_t, lv_draw_buf_align,
lv_draw_image_dsc_t, lv_draw_line_dsc_t, lv_event_t, lv_flex_flow_t_LV_FLEX_FLOW_COLUMN,
lv_font_montserrat_24, lv_grid_align_t_LV_GRID_ALIGN_CENTER,
lv_grid_align_t_LV_GRID_ALIGN_START, lv_grid_align_t_LV_GRID_ALIGN_STRETCH, lv_layer_t,
lv_obj_flag_t_LV_OBJ_FLAG_HIDDEN, lv_obj_flag_t_LV_OBJ_FLAG_IGNORE_LAYOUT, lv_obj_t,
lv_observer_get_target, lv_observer_t, lv_palette_t_LV_PALETTE_BLUE,
lv_part_t_LV_PART_ITEMS, lv_state_t_LV_STATE_CHECKED, lv_subject_get_int, lv_subject_t,
},
widgets::{
Button, Buttonmatrix, Canvas, Chart, Dropdown, Image, Label, LvglWorld, Wdg, Widget,
},
};
use embedded_graphics::{
draw_target::DrawTarget,
pixelcolor::Rgb565,
prelude::{Point, Size},
};
use embedded_graphics_simulator::{
OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window,
};
#[derive(Component)]
#[component(storage = "SparseSet")]
struct DynamicLabel;
fn main() {
lv_init();
lv_bevy_ecs::logging::lv_log_init();
const HOR_RES: u32 = 800;
const VER_RES: u32 = 480;
const LINE_HEIGHT: u32 = 10;
let mut sim_display: SimulatorDisplay<Rgb565> =
SimulatorDisplay::new(Size::new(HOR_RES, VER_RES));
let output_settings = OutputSettingsBuilder::new().scale(1).build();
let mut window = Window::new("Bindings Test Example", &output_settings);
let mut display = Display::create(HOR_RES as i32, VER_RES as i32);
let buffer =
DrawBuffer::<{ (HOR_RES * LINE_HEIGHT) as usize }, Rgb565>::create(HOR_RES, LINE_HEIGHT);
display.register(buffer, |refresh| {
sim_display
.fill_contiguous(&refresh.rectangle, refresh.colors.iter().cloned())
.unwrap();
});
let _touch_screen = InputDevice::<Pointer>::create(|| get_touch_input(window.events()));
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
});
let mut world = LvglWorld::default();
create_ui(&mut world);
window.update(&sim_display);
loop {
lv_timer_handler();
window.update(&sim_display);
}
}
fn create_ui(world: &mut World) {
let c1: lv_color_t = lv_color_hex(0xff0000);
let c2: lv_color_t = lv_palette_darken(lv_palette_t_LV_PALETTE_BLUE, 2);
let c3: lv_color_t = lv_color_mix(c1, c2, OpacityLevel::Percent60 as u8);
let mut style_big_font = Style::default();
unsafe {
lv_style_set_text_font(&mut style_big_font, &lv_font_montserrat_24);
}
let grid_cols = [
300 as i32,
lv_grid_fr(3) as i32,
lv_grid_fr(2) as i32,
LV_GRID_TEMPLATE_LAST as i32,
];
let grid_rows = [
100 as i32,
lv_grid_fr(1) as i32,
LV_GRID_CONTENT as i32,
LV_GRID_TEMPLATE_LAST as i32,
];
let mut active_screen = lv_screen_active().unwrap();
lv_obj_set_grid_dsc_array(&mut active_screen, &grid_cols[0], &grid_rows[0]);
let mut chart_type_subject = Subject::new_int(0);
let mut dropdown = Dropdown::create_widget();
lv_dropdown_set_options(&mut dropdown, c"Lines\nBars");
lv_obj_set_grid_cell(
&mut dropdown,
lv_grid_align_t_LV_GRID_ALIGN_CENTER,
0,
1,
lv_grid_align_t_LV_GRID_ALIGN_CENTER,
0,
1,
);
lv_dropdown_bind_value(&mut dropdown, chart_type_subject.raw_mut());
world.spawn((Dropdown, dropdown));
let mut chart = Chart::create_widget();
lv_obj_set_grid_cell(
&mut chart,
lv_grid_align_t_LV_GRID_ALIGN_STRETCH,
0,
1,
lv_grid_align_t_LV_GRID_ALIGN_CENTER,
1,
1,
);
let mut series =
lv_chart_add_series(&mut chart, c3, lv_chart_axis_t_LV_CHART_AXIS_PRIMARY_X).unwrap();
let mut chart_y_array = [10, 25, 50, 40, 30, 35, 60, 65, 70, 75];
unsafe {
lv_chart_set_series_ext_y_array(&mut chart, series.as_mut(), &mut chart_y_array[0]);
}
lv_subject_add_observer_obj(&mut chart_type_subject, &mut chart, chart_type_observer_cb);
lv_subject_set_int(&mut chart_type_subject, 1);
world.spawn(chart_type_subject);
world.spawn((Chart, chart));
let mut label = Label::create_widget();
lv_obj_set_grid_cell(
&mut label,
lv_grid_align_t_LV_GRID_ALIGN_START,
1,
1,
lv_grid_align_t_LV_GRID_ALIGN_CENTER,
0,
1,
);
lv_obj_set_style_bg_opa(&mut label, OpacityLevel::Percent70 as u8, 0);
lv_obj_set_style_bg_color(&mut label, c1, 0);
lv_obj_set_style_text_color(&mut label, c2, 0);
let mut label_entity = world.spawn((DynamicLabel, Label, label));
label_entity.insert(style_big_font.clone());
let btnmatrix_options = {
let options = ["First", "Second", "\n", "Third", ""];
let combined = options.map(|s| CString::from_str(s).unwrap());
let ptrs = combined.map(|cs| {
let ptr = cs.as_c_str().as_ptr();
core::mem::forget(cs);
ptr
});
ptrs
};
let btnmatrix_ctrl = Box::new([
lv_buttonmatrix_ctrl_t_LV_BUTTONMATRIX_CTRL_DISABLED,
2 | lv_buttonmatrix_ctrl_t_LV_BUTTONMATRIX_CTRL_CHECKED,
1,
]);
let mut btnmatrix = Buttonmatrix::create_widget();
lv_obj_set_grid_cell(
&mut btnmatrix,
lv_grid_align_t_LV_GRID_ALIGN_STRETCH,
1,
1,
lv_grid_align_t_LV_GRID_ALIGN_STRETCH,
1,
1,
);
lv_buttonmatrix_set_map(&mut btnmatrix, &btnmatrix_options);
lv_buttonmatrix_set_ctrl_map(&mut btnmatrix, &Box::leak(btnmatrix_ctrl)[0]);
lv_buttonmatrix_set_selected_button(&mut btnmatrix, 1);
lv_obj_add_event_cb(&mut btnmatrix, Event::ValueChanged, |mut event| {
buttonmatrix_event_cb(world, &mut event);
});
let mut btnmatrix_entity = world.spawn((Buttonmatrix, btnmatrix));
let mut style_big_font_2 = Style::new(lv_part_t_LV_PART_ITEMS | lv_state_t_LV_STATE_CHECKED);
unsafe {
lv_style_set_text_font(&mut style_big_font_2, &lv_font_montserrat_24);
}
btnmatrix_entity.insert(style_big_font_2);
let mut cont = Widget::default();
lv_obj_set_grid_cell(
&mut cont,
lv_grid_align_t_LV_GRID_ALIGN_STRETCH,
2,
1,
lv_grid_align_t_LV_GRID_ALIGN_STRETCH,
0,
2,
);
lv_obj_set_flex_flow(&mut cont, lv_flex_flow_t_LV_FLEX_FLOW_COLUMN);
let cont_entity = world.spawn(cont);
let cont_id = cont_entity.id();
let mut fourth = None;
for i in 0..10u32 {
let btn_id = list_button_create(world, cont_id);
if i == 0 {
let mut btn_entity = world.get_entity_mut(btn_id).unwrap();
let mut a = Animation::new(
Duration::from_millis(300),
OpacityLevel::Cover as i32,
OpacityLevel::Percent50 as i32,
|widget, value| {
lv_obj_set_style_opa(widget, value as u8, 0);
},
);
unsafe {
lv_anim_set_path_cb(a.raw_mut(), Some(lv_anim_path_ease_out));
}
btn_entity.insert(a);
}
if i == 1 {
let mut btn_entity = world.get_entity_mut(btn_id).unwrap();
let mut btn = btn_entity.get_mut::<Widget>().unwrap();
lv_obj_add_flag(&mut btn, lv_obj_flag_t_LV_OBJ_FLAG_HIDDEN);
}
if i == 2 {
let label_id = {
let btn_entity = world.get_entity_mut(btn_id).unwrap();
let children = btn_entity.get::<Children>().unwrap();
children.first().unwrap().to_owned()
};
let mut btn_label_entity = world.get_entity_mut(label_id).unwrap();
let mut btn_label = btn_label_entity.get_mut::<Widget>().unwrap();
lv_label_set_text(&mut btn_label, c"A multi-line text with a ° symbol");
lv_obj_set_width(&mut btn_label, lv_pct(100));
}
if i == 3 {
let mut btn_entity = world.get_entity_mut(btn_id).unwrap();
fourth = Some(btn_id);
let mut a = Animation::new(
Duration::from_millis(300),
OpacityLevel::Cover as i32,
OpacityLevel::Percent50 as i32,
opa_anim_cb,
);
unsafe {
lv_anim_set_repeat_count(a.raw_mut(), LV_ANIM_REPEAT_INFINITE);
}
btn_entity.insert(a);
}
}
sleep(Duration::from_millis(300));
if let Some(fourth) = fourth {
world.despawn(fourth);
}
let mut canvas_buf = [0u8; 400 * 100 * 4];
let mut canvas = Canvas::create_widget();
lv_obj_set_grid_cell(
&mut canvas,
lv_grid_align_t_LV_GRID_ALIGN_START,
0,
2,
lv_grid_align_t_LV_GRID_ALIGN_START,
2,
1,
);
unsafe {
let buf = lv_draw_buf_align(
(canvas_buf.as_mut_ptr() as *mut c_void).as_mut().unwrap(),
lv_color_format_t_LV_COLOR_FORMAT_RGB565,
);
lv_canvas_set_buffer(
&mut canvas,
buf.as_mut().unwrap(),
400,
100,
lv_color_format_t_LV_COLOR_FORMAT_RGB565,
);
}
lv_canvas_fill_bg(&mut canvas, c2, OpacityLevel::Cover as u8);
draw_to_canvas(&mut canvas);
world.spawn((Canvas, canvas));
let test_img_lvgl_logo_png_path = c"A:examples/assets/test_img_lvgl_logo.png";
let test_img_lvgl_logo_png = unsafe { test_img_lvgl_logo_png_path.as_ptr().as_ref().unwrap() };
let test_img_lvgl_logo_jpg_path = c"A:examples/assets/test_img_lvgl_logo.jpg";
let test_img_lvgl_logo_jpg = unsafe { test_img_lvgl_logo_jpg_path.as_ptr().as_ref().unwrap() };
let mut img = Image::create_widget();
lv_image_set_src(&mut img, test_img_lvgl_logo_jpg);
lv_obj_align(&mut img, lv_align_t_LV_ALIGN_BOTTOM_RIGHT, -20, -20);
lv_obj_add_flag(&mut img, lv_obj_flag_t_LV_OBJ_FLAG_IGNORE_LAYOUT);
world.spawn((Image, img));
let mut img = Image::create_widget();
lv_image_set_src(&mut img, test_img_lvgl_logo_png);
lv_obj_set_pos(&mut img, 500, 420);
lv_obj_add_flag(&mut img, lv_obj_flag_t_LV_OBJ_FLAG_IGNORE_LAYOUT);
lv_image_set_rotation(&mut img, 200);
lv_image_set_scale_x(&mut img, 400);
world.spawn((Image, img));
}
fn opa_anim_cb(widget: &mut Wdg, value: i32) {
lv_obj_set_style_opa(widget, value as u8, 0);
}
fn chart_type_observer_cb(observer: *mut lv_observer_t, subject: *mut lv_subject_t) {
info!("chart_type_observer_cb");
unsafe {
let v = lv_subject_get_int(subject);
let chart = lv_observer_get_target(observer) as *mut lv_obj_t;
let type_ = if v == 0 {
lv_chart_type_t_LV_CHART_TYPE_LINE
} else {
lv_chart_type_t_LV_CHART_TYPE_BAR
};
lv_chart_set_type(chart, type_);
}
}
fn buttonmatrix_event_cb(world: &mut World, e: &mut lv_event_t) {
let buttonmatrix = Wdg::from_ptr(lv_event_get_target(e) as *mut lv_obj_t);
let idx = lv_buttonmatrix_get_selected_button(&buttonmatrix);
let text = lv_buttonmatrix_get_button_text(&buttonmatrix, idx);
let mut label = world
.query_filtered::<&mut Widget, With<DynamicLabel>>()
.single_mut(world)
.unwrap();
lv_label_set_text(&mut label, text);
}
fn list_button_create(world: &mut World, parent: Entity) -> Entity {
let mut btn = Button::create_widget();
lv_obj_set_size(&mut btn, lv_pct(100), LV_SIZE_CONTENT as i32);
let btn_id = world.spawn((Button, btn)).id();
let mut parent = world.entity_mut(parent);
parent.add_child(btn_id);
let idx = lv_obj_get_index(&mut world.get_mut::<Widget>(btn_id).unwrap());
info!("Spawning button {}", idx);
let mut label = Label::create_widget();
let file_icon_str = CStr::from_bytes_with_nul(LV_SYMBOL_FILE).unwrap();
let file_icon = file_icon_str.to_string_lossy();
lv_label_set_text(
&mut label,
CString::new(format!("{} Item {}", file_icon, idx))
.unwrap()
.as_c_str(),
);
let label_id = world.spawn((Label, label)).id();
world.get_entity_mut(btn_id).unwrap().add_child(label_id);
btn_id
}
fn draw_to_canvas(canvas: &mut Widget) {
let mut layer = unsafe {
let mut layer = std::mem::MaybeUninit::<lv_layer_t>::uninit();
lightvgl_sys::lv_canvas_init_layer(canvas.raw_mut(), layer.as_mut_ptr());
layer.assume_init()
};
let test_img_lvgl_logo_png_path = c"A:examples/assets/test_img_lvgl_logo.png".as_ptr();
let test_img_lvgl_logo_png = unsafe {
(test_img_lvgl_logo_png_path as *mut c_void)
.as_mut()
.unwrap()
};
let mut image_draw_dsc = unsafe {
let mut image_draw_dsc = std::mem::MaybeUninit::<lv_draw_image_dsc_t>::uninit();
lightvgl_sys::lv_draw_image_dsc_init(image_draw_dsc.as_mut_ptr());
image_draw_dsc.assume_init()
};
image_draw_dsc.src = test_img_lvgl_logo_png;
const WIDTH: i32 = 105;
const HEIGHT: i32 = 40;
let mut coords: lv_area_t = lv_area_t {
x1: 10,
y1: 10,
x2: 10 + WIDTH - 1,
y2: 10 + HEIGHT - 1,
};
unsafe {
lightvgl_sys::lv_draw_image(&mut layer, &image_draw_dsc, &coords);
lightvgl_sys::lv_area_move(&mut coords, 40, 40);
image_draw_dsc.opa = OpacityLevel::Percent50 as u8;
lightvgl_sys::lv_draw_image(&mut layer, &image_draw_dsc, &coords);
let mut line_draw_dsc = std::mem::MaybeUninit::<lv_draw_line_dsc_t>::uninit();
lightvgl_sys::lv_draw_line_dsc_init(line_draw_dsc.as_mut_ptr());
let mut line_draw_dsc = line_draw_dsc.assume_init();
line_draw_dsc.color = lv_color_hex3(0xCA8);
line_draw_dsc.width = 8;
line_draw_dsc.set_round_start(1);
line_draw_dsc.set_round_end(1);
lightvgl_sys::lv_point_precise_set(&mut line_draw_dsc.p1, 150, 30);
lightvgl_sys::lv_point_precise_set(&mut line_draw_dsc.p2, 350, 55);
lightvgl_sys::lv_draw_line(&mut layer, &line_draw_dsc);
lightvgl_sys::lv_canvas_finish_layer(canvas.raw_mut(), &mut layer);
let c = lv_color_make(255, 0, 0);
for i in 0..50 {
lightvgl_sys::lv_canvas_set_px(
canvas.raw_mut(),
100 + i * 2,
10,
c,
OpacityLevel::Cover as u8,
);
}
}
}
fn get_touch_input(events: impl Iterator<Item = SimulatorEvent>) -> InputEvent<Pointer> {
static IS_POINTER_DOWN: AtomicBool = AtomicBool::new(false);
static LATEST_TOUCH_STATUS: Mutex<InputEvent<Pointer>> =
Mutex::new(InputEvent::new(Point::zero()));
let mut next_touch_status = None;
for event in events {
match event {
SimulatorEvent::MouseButtonDown {
mouse_btn: _,
point,
} => {
next_touch_status = Some(InputEvent {
status: BufferStatus::Once,
state: InputState::Pressed,
data: point,
});
IS_POINTER_DOWN.store(true, Ordering::Relaxed);
}
SimulatorEvent::MouseButtonUp {
mouse_btn: _,
point,
} => {
next_touch_status = Some(InputEvent {
status: BufferStatus::Once,
state: InputState::Released,
data: point,
});
IS_POINTER_DOWN.store(false, Ordering::Relaxed);
}
SimulatorEvent::MouseMove { point } => {
if IS_POINTER_DOWN.load(Ordering::Relaxed) {
next_touch_status = Some(InputEvent {
status: BufferStatus::Once,
state: InputState::Pressed,
data: point,
});
}
}
SimulatorEvent::Quit => exit(0),
_ => {}
}
}
let mut lock = LATEST_TOUCH_STATUS.lock().unwrap();
if let Some(latest_touch_status) = next_touch_status {
*lock = latest_touch_status;
}
return *lock;
}
#[unsafe(no_mangle)]
pub fn get_memory_stats(monitor: &mut lv_bevy_ecs::sys::lv_mem_monitor_t) {
if let Some(stats) = memory_stats::memory_stats() {
let memory = stats.physical_mem;
let virtual_memory = stats.virtual_mem;
(*monitor).total_size = (virtual_memory) as usize;
(*monitor).free_size = (virtual_memory - memory) as usize;
(*monitor).max_used = usize::max((memory) as usize, (*monitor).max_used);
(*monitor).used_pct = (memory as f64 / virtual_memory as f64 * 100.0) as u8;
} else {
error!("Could not retrieve memory stats");
}
}