use std::cell::Cell;
use std::marker::PhantomData;
use crate::{
Color, Context, Node,
ui::{Event, View, event::EventResult},
};
pub struct PixelBuffer<'a> {
pub width: u32,
pub height: u32,
pub pixels: &'a mut [u32],
pub(crate) frame_requested: &'a Cell<bool>,
}
impl<'a> PixelBuffer<'a> {
#[inline]
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> u32 {
u32::from_ne_bytes([r, g, b, a])
}
#[inline]
pub const fn rgb(r: u8, g: u8, b: u8) -> u32 {
Self::rgba(r, g, b, 255)
}
#[inline]
pub fn new(
width: u32,
height: u32,
pixels: &'a mut [u32],
frame_requested: &'a Cell<bool>,
) -> Self {
Self {
width,
height,
pixels,
frame_requested,
}
}
#[inline]
pub fn request_frame(&self) {
self.frame_requested.set(true);
}
#[inline]
pub fn set_pixel(&mut self, x: u32, y: u32, color: u32) {
if x < self.width && y < self.height {
let index = (y * self.width + x) as usize;
if index < self.pixels.len() {
self.pixels[index] = color;
}
}
}
#[inline]
pub fn set_pixel_with_color(&mut self, x: u32, y: u32, color: Color) {
self.set_pixel(x, y, color.to_rgba_u32());
}
#[inline]
pub fn set_pixel_color(&mut self, x: u32, y: u32, color: Color) {
self.set_pixel_with_color(x, y, color);
}
#[inline]
pub fn get_pixel(&self, x: u32, y: u32) -> Option<u32> {
if x < self.width && y < self.height {
let index = (y * self.width + x) as usize;
self.pixels.get(index).copied()
} else {
None
}
}
#[inline]
pub fn get_pixel_by_color(&self, x: u32, y: u32) -> Option<Color> {
self.get_pixel(x, y).map(Color::from_rgba_u32)
}
#[inline]
pub fn get_pixel_color(&self, x: u32, y: u32) -> Option<Color> {
self.get_pixel_by_color(x, y)
}
#[inline]
pub fn fill(&mut self, color: u32) {
self.pixels.fill(color);
}
#[inline]
pub fn fill_with_color(&mut self, color: Color) {
self.fill(color.to_rgba_u32());
}
#[inline]
pub fn fill_color(&mut self, color: Color) {
self.fill_with_color(color);
}
#[inline]
pub fn clear(&mut self) {
self.pixels.fill(0);
}
pub fn fill_rect(&mut self, x: i32, y: i32, w: u32, h: u32, color: u32) {
let x_start = x.max(0) as u32;
let y_start = y.max(0) as u32;
let x_end = ((x + w as i32).max(0) as u32).min(self.width);
let y_end = ((y + h as i32).max(0) as u32).min(self.height);
for row in y_start..y_end {
let row_offset = (row * self.width) as usize;
for col in x_start..x_end {
let idx = row_offset + col as usize;
if idx < self.pixels.len() {
self.pixels[idx] = color;
}
}
}
}
#[inline]
pub fn fill_rect_with_color(&mut self, x: i32, y: i32, w: u32, h: u32, color: Color) {
self.fill_rect(x, y, w, h, color.to_rgba_u32());
}
#[inline]
pub fn fill_rect_color(&mut self, x: i32, y: i32, w: u32, h: u32, color: Color) {
self.fill_rect_with_color(x, y, w, h, color);
}
pub fn blit(&mut self, src: &[u32], src_w: u32, src_h: u32, dst_x: i32, dst_y: i32) {
for row in 0..src_h {
let target_y = dst_y + row as i32;
if target_y < 0 || target_y >= self.height as i32 {
continue;
}
for col in 0..src_w {
let target_x = dst_x + col as i32;
if target_x < 0 || target_x >= self.width as i32 {
continue;
}
let src_idx = (row * src_w + col) as usize;
let dst_idx = (target_y as u32 * self.width + target_x as u32) as usize;
if src_idx < src.len() && dst_idx < self.pixels.len() {
self.pixels[dst_idx] = src[src_idx];
}
}
}
}
#[inline]
pub fn blit_colors(&mut self, src: &[Color], src_w: u32, src_h: u32, dst_x: i32, dst_y: i32) {
let src_u32 = bytemuck::cast_slice::<Color, u32>(src);
self.blit(src_u32, src_w, src_h, dst_x, dst_y);
}
#[inline]
pub fn blit_bytes(&mut self, src_bytes: &[u8], src_w: u32, src_h: u32, dst_x: i32, dst_y: i32) {
let src_u32 = bytemuck::cast_slice::<u8, u32>(src_bytes);
self.blit(src_u32, src_w, src_h, dst_x, dst_y);
}
#[inline]
pub fn as_colors(&self) -> &[Color] {
bytemuck::cast_slice(self.pixels)
}
#[inline]
pub fn as_colors_mut(&mut self) -> &mut [Color] {
bytemuck::cast_slice_mut(self.pixels)
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
bytemuck::cast_slice(self.pixels)
}
#[inline]
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
bytemuck::cast_slice_mut(self.pixels)
}
}
pub trait PixelPainter: 'static {
fn paint(&mut self, buffer: &mut PixelBuffer);
}
impl<F> PixelPainter for F
where
F: FnMut(&mut PixelBuffer) + 'static,
{
fn paint(&mut self, buffer: &mut PixelBuffer) {
(self)(buffer);
}
}
pub struct PaintContext<'a> {
pub device: &'a wgpu::Device,
pub queue: &'a wgpu::Queue,
pub encoder: &'a mut wgpu::CommandEncoder,
pub target: &'a wgpu::TextureView,
pub width: u32,
pub height: u32,
pub format: wgpu::TextureFormat,
pub dt: f32,
pub(crate) frame_requested: &'a Cell<bool>,
}
impl<'a> PaintContext<'a> {
#[inline]
pub fn request_frame(&self) {
self.frame_requested.set(true);
}
}
pub trait WgpuPainter: 'static {
fn init(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, format: wgpu::TextureFormat) {
let _ = (device, queue, format);
}
fn resize(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, width: u32, height: u32) {
let _ = (device, queue, width, height);
}
fn prepare(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
let _ = (device, queue);
}
fn paint(&mut self, ctx: &mut PaintContext);
}
impl<F> WgpuPainter for F
where
F: FnMut(&mut PaintContext) + 'static,
{
fn paint(&mut self, ctx: &mut PaintContext) {
(self)(ctx);
}
}
pub enum CanvasPainterKind {
Pixel(Box<dyn PixelPainter>),
Wgpu(Box<dyn WgpuPainter>),
}
pub struct CanvasData {
pub painter: CanvasPainterKind,
pub initialized: bool,
pub cpu_buffer: Vec<u32>,
pub width: u32,
pub height: u32,
}
#[derive(Clone, Copy, Debug)]
pub struct CanvasEventDetails {
pub local_x: f32,
pub local_y: f32,
pub uv_x: f32,
pub uv_y: f32,
}
pub struct Canvas<State, Msg> {
painter_fn: Box<dyn Fn() -> CanvasPainterKind>,
on_event_fn: Option<Box<dyn Fn(&State, Event, CanvasEventDetails) -> Option<Msg>>>,
source_loc: Option<crate::debugger::SourceLocation>,
_marker: PhantomData<(State, Msg)>,
}
#[track_caller]
pub fn pixel_canvas<P, State, Msg>(painter: P) -> Canvas<State, Msg>
where
P: PixelPainter + Clone,
{
Canvas {
painter_fn: Box::new(move || CanvasPainterKind::Pixel(Box::new(painter.clone()))),
on_event_fn: None,
source_loc: Some(crate::debugger::SourceLocation::here("PixelCanvas")),
_marker: PhantomData,
}
}
#[track_caller]
pub fn wgpu_canvas<P, State, Msg>(painter: P) -> Canvas<State, Msg>
where
P: WgpuPainter + Clone,
{
Canvas {
painter_fn: Box::new(move || CanvasPainterKind::Wgpu(Box::new(painter.clone()))),
on_event_fn: None,
source_loc: Some(crate::debugger::SourceLocation::here("WgpuCanvas")),
_marker: PhantomData,
}
}
impl<State, Msg> Canvas<State, Msg> {
pub fn on_event<F>(mut self, handler: F) -> Self
where
F: Fn(&State, Event, CanvasEventDetails) -> Option<Msg> + 'static,
{
self.on_event_fn = Some(Box::new(handler));
self
}
}
impl<State: 'static, Msg: 'static> View<State> for Canvas<State, Msg> {
type Element = Node;
type Message = Msg;
fn build(&self, ctx: &mut Context) -> Self::Element {
let node = ctx.create_node();
if let Some(loc) = self.source_loc {
ctx.set_node_source(node, loc);
}
let painter = (self.painter_fn)();
ctx.canvases.borrow_mut().insert(
node,
CanvasData {
painter,
initialized: false,
cpu_buffer: Vec::new(),
width: 0,
height: 0,
},
);
node
}
fn rebuild(&self, _prev: &Self, ctx: &mut Context, element: &mut Self::Element) {
let mut canvases = ctx.canvases.borrow_mut();
if let Some(canvas_data) = canvases.get_mut(element) {
match (&mut canvas_data.painter, (self.painter_fn)()) {
(CanvasPainterKind::Pixel(_), new_p @ CanvasPainterKind::Pixel(_)) => {
canvas_data.painter = new_p;
}
(CanvasPainterKind::Wgpu(_), CanvasPainterKind::Wgpu(_)) => {
}
(_, new_p) => {
canvas_data.painter = new_p;
canvas_data.initialized = false;
}
}
}
}
fn teardown(&self, ctx: &mut Context, element: &mut Self::Element) {
ctx.canvases.borrow_mut().remove(element);
element.remove(ctx);
ctx.destroy_node(*element);
}
fn get_node(&self, element: &Self::Element) -> Node {
*element
}
fn handle_event(
&self,
element: &mut Self::Element,
state: &State,
event: Event,
ctx: &mut Context,
) -> (EventResult, Option<Self::Message>) {
if let Some(on_event) = &self.on_event_fn {
let (cursor_x, cursor_y, is_hit) = match &event {
Event::CursorMoved {
x, y, hit_nodes, ..
} => (*x, *y, hit_nodes.contains(element)),
Event::MouseInput {
x, y, hit_nodes, ..
} => (*x, *y, hit_nodes.contains(element)),
Event::MouseWheel { hit_nodes, .. } => (0.0, 0.0, hit_nodes.contains(element)),
_ => (0.0, 0.0, false),
};
if is_hit {
if let Some(computed) = element.get_computed(ctx) {
let local_x = (cursor_x - computed.x).max(0.0);
let local_y = (cursor_y - computed.y).max(0.0);
let uv_x = if computed.w > 0.0 {
(local_x / computed.w).clamp(0.0, 1.0)
} else {
0.0
};
let uv_y = if computed.h > 0.0 {
(local_y / computed.h).clamp(0.0, 1.0)
} else {
0.0
};
let details = CanvasEventDetails {
local_x,
local_y,
uv_x,
uv_y,
};
if let Some(msg) = (on_event)(state, event, details) {
return (EventResult::Handled, Some(msg));
}
}
}
}
(EventResult::Ignored, None)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pixel_buffer_operations() {
let mut data = vec![0u32; 100]; let frame_requested = Cell::new(false);
let mut buf = PixelBuffer::new(10, 10, &mut data, &frame_requested);
buf.set_pixel(2, 3, 0xFF112233);
assert_eq!(buf.get_pixel(2, 3), Some(0xFF112233));
assert_eq!(buf.get_pixel(0, 0), Some(0));
assert_eq!(buf.get_pixel(10, 10), None);
buf.fill(0xFFAAAAAA);
assert_eq!(buf.get_pixel(0, 0), Some(0xFFAAAAAA));
assert_eq!(buf.get_pixel(9, 9), Some(0xFFAAAAAA));
buf.clear();
assert_eq!(buf.get_pixel(5, 5), Some(0));
buf.fill_rect(2, 2, 4, 4, 0xFFEEFF00);
assert_eq!(buf.get_pixel(2, 2), Some(0xFFEEFF00));
assert_eq!(buf.get_pixel(5, 5), Some(0xFFEEFF00));
assert_eq!(buf.get_pixel(6, 6), Some(0));
let src = [0xFF010203, 0xFF040506, 0xFF070809, 0xFF0A0B0C];
buf.blit(&src, 2, 2, 0, 0);
assert_eq!(buf.get_pixel(0, 0), Some(0xFF010203));
assert_eq!(buf.get_pixel(1, 0), Some(0xFF040506));
assert_eq!(buf.get_pixel(0, 1), Some(0xFF070809));
assert_eq!(buf.get_pixel(1, 1), Some(0xFF0A0B0C));
}
#[test]
fn test_pixel_buffer_color_operations() {
let mut data = vec![0u32; 100]; let frame_requested = Cell::new(false);
let mut buf = PixelBuffer::new(10, 10, &mut data, &frame_requested);
let red = Color::new(255, 0, 0, 255);
let blue = Color::new(0, 0, 255, 255);
let green = Color::new(0, 255, 0, 255);
buf.fill_with_color(red);
assert_eq!(buf.get_pixel_by_color(0, 0), Some(red));
assert_eq!(buf.get_pixel_by_color(9, 9), Some(red));
buf.set_pixel_with_color(4, 5, blue);
assert_eq!(buf.get_pixel_by_color(4, 5), Some(blue));
assert_eq!(buf.get_pixel_by_color(4, 6), Some(red));
buf.fill_rect_with_color(1, 1, 3, 3, green);
assert_eq!(buf.get_pixel_by_color(1, 1), Some(green));
assert_eq!(buf.get_pixel_by_color(3, 3), Some(green));
assert_eq!(buf.get_pixel_by_color(4, 4), Some(red));
let color_src = [blue, green, red, blue];
buf.blit_colors(&color_src, 2, 2, 0, 0);
assert_eq!(buf.get_pixel_by_color(0, 0), Some(blue));
assert_eq!(buf.get_pixel_by_color(1, 0), Some(green));
assert_eq!(buf.get_pixel_by_color(0, 1), Some(red));
assert_eq!(buf.get_pixel_by_color(1, 1), Some(blue));
let colors = buf.as_colors();
assert_eq!(colors.len(), 100);
assert_eq!(colors[0], blue);
}
#[test]
fn test_canvas_view_lifecycle() {
let mut ctx = Context::new();
let canvas_widget = pixel_canvas::<_, (), ()>(|buf: &mut PixelBuffer| {
buf.fill_with_color(Color::green);
});
let element = canvas_widget.build(&mut ctx);
assert!(ctx.canvases.borrow().contains_key(&element));
canvas_widget.teardown(&mut ctx, &mut { element });
assert!(!ctx.canvases.borrow().contains_key(&element));
}
}