use core::ffi::{c_char, c_void};
use std::sync::{
Once,
atomic::{AtomicUsize, Ordering},
};
use oxiforge::{
driver::LvglDriver,
prelude::{AsLvHandle, Screen},
};
pub type LvRawObj = c_void;
unsafe extern "C" {
fn lv_obj_create(parent: *mut LvRawObj) -> *mut LvRawObj;
fn lv_screen_load(scr: *mut LvRawObj);
fn lv_refr_now(disp: *mut LvRawObj);
fn lv_obj_get_child_count(obj: *const LvRawObj) -> u32;
fn lv_obj_get_child(obj: *const LvRawObj, id: i32) -> *mut LvRawObj;
fn lv_label_get_text(obj: *mut LvRawObj) -> *const c_char;
}
static INIT: Once = Once::new();
static mut DRIVER: Option<LvglDriver> = None;
static INIT_THREAD: AtomicUsize = AtomicUsize::new(0);
pub fn ensure_init() {
let tid = thread_id();
let prev = INIT_THREAD.compare_exchange(0, tid, Ordering::SeqCst, Ordering::SeqCst);
match prev {
Ok(_) => {}
Err(first_tid) => {
assert_eq!(
first_tid, tid,
"LVGL tests must run single-threaded (--test-threads=1). \
First init on thread {first_tid}, now called from {tid}."
);
}
}
INIT.call_once(|| {
assert!(std::env::var("SDL_VIDEODRIVER").is_ok(), "SDL_VIDEODRIVER not set — run via: ./run_runtime_tests.sh");
unsafe { DRIVER = Some(LvglDriver::init(320, 240)) };
});
}
pub fn fresh_screen() -> Screen {
ensure_init();
unsafe {
let new = lv_obj_create(core::ptr::null_mut());
lv_screen_load(new);
}
Screen::active().expect("no active screen after init")
}
pub fn pump() {
let driver = unsafe { (*core::ptr::addr_of!(DRIVER)).as_ref() }.expect("pump() called before ensure_init()");
driver.timer_handler();
unsafe { lv_refr_now(core::ptr::null_mut()) };
}
pub unsafe fn child_count_raw(handle: *mut LvRawObj) -> u32 {
unsafe { lv_obj_get_child_count(handle) }
}
pub unsafe fn get_child_raw(handle: *mut LvRawObj, idx: i32) -> *mut LvRawObj {
unsafe { lv_obj_get_child(handle, idx) }
}
pub unsafe fn get_label_text(handle: *mut LvRawObj) -> String {
let ptr = unsafe { lv_label_get_text(handle) };
if ptr.is_null() {
return String::new();
}
unsafe { std::ffi::CStr::from_ptr(ptr) }.to_str().expect("label text is valid UTF-8").to_owned()
}
pub fn as_raw<W: AsLvHandle>(widget: &W) -> *mut LvRawObj {
widget.lv_handle().cast::<LvRawObj>()
}
fn thread_id() -> usize {
thread_local! { static ANCHOR: u8 = const { 0 }; }
ANCHOR.with(|a| a as *const u8 as usize)
}