use core::ffi::c_char;
use oxivgl_sys::*;
#[derive(Debug)]
pub struct LvglDriver;
fn init_common() {
use core::sync::atomic::{AtomicBool, Ordering};
static INITIALIZED: AtomicBool = AtomicBool::new(false);
assert!(
!INITIALIZED.swap(true, Ordering::SeqCst),
"lv_init() called twice — only one LvglDriver may exist per process"
);
unsafe {
lv_init();
lv_log_register_print_cb(Some(lvgl_log_print));
lv_tick_set_cb(Some(get_tick_ms));
}
}
impl LvglDriver {
pub fn init(w: i32, h: i32) -> Self {
init_common();
#[cfg(not(target_os = "none"))]
unsafe {
init_host_display(w, h)
};
let _ = (w, h); Self
}
pub fn timer_handler(&self) -> u32 {
unsafe { lv_timer_handler() }
}
}
#[cfg(not(target_os = "none"))]
pub struct SdlBuilder {
w: i32,
h: i32,
title: Option<&'static core::ffi::CStr>,
mouse: bool,
keyboard: bool,
}
#[cfg(not(target_os = "none"))]
impl LvglDriver {
pub fn sdl(w: i32, h: i32) -> SdlBuilder {
SdlBuilder { w, h, title: None, mouse: true, keyboard: false }
}
}
#[cfg(not(target_os = "none"))]
impl SdlBuilder {
pub fn title(mut self, t: &'static core::ffi::CStr) -> Self {
self.title = Some(t);
self
}
pub fn mouse(mut self, enabled: bool) -> Self {
self.mouse = enabled;
self
}
pub fn keyboard(mut self, enabled: bool) -> Self {
self.keyboard = enabled;
self
}
pub fn build(self) -> LvglDriver {
init_common();
let disp = unsafe { lv_sdl_window_create(self.w, self.h) };
assert!(!disp.is_null(), "lv_sdl_window_create returned NULL");
if let Some(title) = self.title {
unsafe { lv_sdl_window_set_title(disp, title.as_ptr()) };
}
if self.mouse {
let indev = unsafe { lv_sdl_mouse_create() };
assert!(!indev.is_null(), "lv_sdl_mouse_create returned NULL");
}
if self.keyboard {
let indev = unsafe { lv_sdl_keyboard_create() };
assert!(!indev.is_null(), "lv_sdl_keyboard_create returned NULL");
}
LvglDriver
}
}
#[cfg(not(target_os = "none"))]
use std::time::{SystemTime, UNIX_EPOCH};
#[cfg(not(target_os = "none"))]
unsafe extern "C" fn flush_cb(drv: *mut lv_display_t, _area: *const lv_area_t, _px_map: *mut u8) {
unsafe { lv_display_flush_ready(drv) };
}
#[cfg(not(target_os = "none"))]
unsafe fn init_host_display(w: i32, h: i32) {
let buf_size = w as usize * h as usize * 2; let cbuf = Box::into_raw(vec![0u8; buf_size].into_boxed_slice()) as *mut std::ffi::c_void;
let disp = unsafe { lv_display_create(w, h) };
assert!(!disp.is_null(), "lv_display_create returned NULL");
unsafe { lv_display_set_color_format(disp, lv_color_format_t_LV_COLOR_FORMAT_RGB565) };
unsafe {
lv_display_set_buffers(
disp,
cbuf,
std::ptr::null_mut(),
buf_size as u32,
lv_display_render_mode_t_LV_DISPLAY_RENDER_MODE_FULL,
)
};
unsafe { lv_display_set_flush_cb(disp, Some(flush_cb)) };
}
#[cfg(not(target_os = "none"))]
pub unsafe extern "C" fn lvgl_log_print(_level: i8, c_str: *const c_char) {
if !c_str.is_null() {
let text = unsafe { std::ffi::CStr::from_ptr(c_str) };
eprintln!("LVGL: {}", text.to_str().unwrap_or("<invalid utf8>").trim());
}
}
#[cfg(target_os = "none")]
#[cfg_attr(feature = "esp-hal", esp_hal::ram)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lvgl_log_print(_level: i8, c_str: *const c_char) {
if c_str.is_null() {
return;
}
let text = unsafe { core::ffi::CStr::from_ptr(c_str) };
debug!("LVGL: {}", text.to_str().unwrap_or("").trim());
}
#[cfg(not(target_os = "none"))]
#[unsafe(no_mangle)]
pub extern "C" fn get_tick_ms() -> u32 {
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u32
}
#[cfg(target_os = "none")]
#[cfg_attr(feature = "esp-hal", esp_hal::ram)]
#[unsafe(no_mangle)]
pub extern "C" fn get_tick_ms() -> u32 {
embassy_time::Instant::now().as_millis() as u32
}