use ::alloc::boxed::Box;
use ::core::{ffi::c_void, marker::PhantomData, ptr::NonNull};
use embedded_graphics::{
Pixel,
prelude::{PixelColor, Point, Size},
primitives::Rectangle,
};
use lightvgl_sys::{lv_display_t, lv_draw_buf_t};
use crate::{info, support::LvglColorFormat, warn};
pub struct Display {
raw: NonNull<lv_display_t>,
}
impl Display {
pub fn create(hor_res: i32, ver_res: i32) -> Self {
crate::support::assert_lv_initialized!();
unsafe {
let raw = NonNull::new(lightvgl_sys::lv_display_create(hor_res, ver_res)).unwrap();
Self { raw }
}
}
pub fn register<F, const N: usize, C: LvglColorFormat>(
&mut self,
buffer: DrawBuffer<N, C>,
callback: F,
) where
F: FnMut(&mut DisplayRefresh<N, C>),
{
unsafe {
lightvgl_sys::lv_display_set_draw_buffers(
self.raw(),
buffer.raw.as_ptr(),
::core::ptr::null_mut(),
);
register_display(self.raw.as_ptr(), callback);
}
info!("Display Registered");
}
pub fn raw(&self) -> *mut lv_display_t {
self.raw.as_ptr()
}
}
pub struct Area {
pub x1: i16,
pub x2: i16,
pub y1: i16,
pub y2: i16,
}
pub struct DisplayRefresh<'a, const N: usize, C> {
pub rectangle: Rectangle,
pub colors: &'a [C],
}
unsafe fn register_display<F, const N: usize, C>(display: *mut lv_display_t, callback: F)
where
F: FnMut(&mut DisplayRefresh<N, C>),
{
unsafe {
lightvgl_sys::lv_display_set_flush_cb(display, Some(disp_flush_trampoline::<F, N, C>));
lightvgl_sys::lv_display_set_user_data(
display,
Box::into_raw(Box::new(callback)) as *mut _ as *mut c_void,
);
}
}
unsafe extern "C" fn disp_flush_trampoline<F, const N: usize, C>(
display: *mut lightvgl_sys::lv_display_t,
area: *const lightvgl_sys::lv_area_t,
color_p: *mut u8,
) where
F: FnMut(&mut DisplayRefresh<N, C>),
{
unsafe {
let display_driver = *display;
if !display_driver.user_data.is_null() {
let callback = &mut *(display_driver.user_data as *mut F);
let buf = color_p as *mut C;
let w = (*area).x2 - (*area).x1 + 1;
let h = (*area).y2 - (*area).y1 + 1;
let rectangle = Rectangle {
size: Size {
width: w as u32,
height: h as u32,
},
top_left: Point {
x: (*area).x1,
y: (*area).y1,
},
};
let slice = ::core::slice::from_raw_parts(buf, (w * h) as usize);
let mut update = DisplayRefresh {
rectangle,
colors: slice,
};
callback(&mut update);
} else {
warn!("Display callback user data was null, this should never happen!");
}
lightvgl_sys::lv_display_flush_ready(display);
}
}
impl<const N: usize, C> DisplayRefresh<'_, N, C> {
pub fn as_pixels<PC>(&self) -> impl IntoIterator<Item = Pixel<PC>>
where
C: Clone,
PC: PixelColor + From<C>,
{
let area = &self.rectangle;
let top_left = area.top_left;
let Point { x: x1, y: y1 } = top_left;
let bottom_right = area.bottom_right().unwrap();
let Point { x: x2, y: y2 } = bottom_right;
let ys = y1..=y2;
let xs = (x1..=x2).enumerate();
let x_len = (x2 - x1 + 1) as usize;
ys.enumerate().flat_map(move |(iy, y)| {
xs.clone().map(move |(ix, x)| {
let color_len = x_len * iy + ix;
let raw_color = self.colors[color_len].clone();
let color: PC = raw_color.into();
Pixel(Point::new(x, y), color)
})
})
}
}
pub struct DrawBuffer<const N: usize, C: LvglColorFormat> {
raw: NonNull<lv_draw_buf_t>,
color_depth: PhantomData<C>,
}
impl<const N: usize, C: LvglColorFormat> DrawBuffer<N, C> {
pub fn create(w: u32, h: u32) -> Self {
assert_eq!(w * h, N as u32);
let cf = C::as_lv_color_format_t();
unsafe {
let raw = NonNull::new(lightvgl_sys::lv_draw_buf_create(w, h, cf, 0)).unwrap();
Self {
raw,
color_depth: PhantomData,
}
}
}
pub fn raw(&self) -> *mut lv_draw_buf_t {
self.raw.as_ptr()
}
}
#[macro_export]
macro_rules! setup_test_display {
() => {
use embedded_graphics::draw_target::DrawTarget;
use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::{Point, Size};
use embedded_graphics_simulator::SimulatorDisplay;
use lv_bevy_ecs::display::{Display, DrawBuffer};
const HOR_RES: u32 = 320;
const VER_RES: u32 = 240;
const LINE_HEIGHT: u32 = 16;
let mut sim_display: SimulatorDisplay<Rgb565> =
SimulatorDisplay::new(Size::new(HOR_RES, VER_RES));
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.draw_iter(refresh.as_pixels()).unwrap();
sim_display
.fill_contiguous(&refresh.rectangle, refresh.colors.iter().cloned())
.unwrap();
});
};
}