use alloc::string::String;
use alloc::vec::Vec;
use crate::raster::{Obb, PointF};
use crate::renderer::Renderer;
use crate::widget::{Color, Rect};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum BlendMode {
Replace,
SrcOver,
Add,
Multiply,
}
#[derive(Clone, Debug)]
pub enum Cmd {
FillRect {
rect: Rect,
color: Color,
blend: BlendMode,
},
FillObb {
obb: Obb,
color: Color,
blend: BlendMode,
},
FillDisc {
center: PointF,
radius: f32,
color: Color,
blend: BlendMode,
},
FillArc {
center: PointF,
r_outer: f32,
r_inner: f32,
start_cos: f32,
start_sin: f32,
end_cos: f32,
end_sin: f32,
extent: f32,
color: Color,
blend: BlendMode,
},
StrokeLine {
a: PointF,
b: PointF,
width: f32,
color: Color,
blend: BlendMode,
},
DrawText {
position: (i32, i32),
text: String,
color: Color,
},
DrawPixels {
position: (i32, i32),
pixels: Vec<Color>,
width: u32,
height: u32,
},
SetClip {
rect: Option<Rect>,
},
Barrier,
}
impl Cmd {
pub fn dispatch_to<R: Renderer + ?Sized>(&self, r: &mut R) {
match self {
Cmd::FillRect { rect, color, blend } => match blend {
BlendMode::Replace => r.fill_rect(*rect, *color),
BlendMode::SrcOver => r.blend_rect(*rect, *color),
BlendMode::Add | BlendMode::Multiply => r.blend_rect(*rect, *color),
},
Cmd::FillObb { obb, color, .. } => r.fill_obb_aa(*obb, *color),
Cmd::FillDisc {
center,
radius,
color,
..
} => r.fill_disc_aa(*center, *radius, *color),
Cmd::FillArc {
center,
r_outer,
r_inner,
start_cos,
start_sin,
end_cos,
end_sin,
extent,
color,
..
} => r.fill_arc_aa(
*center, *r_outer, *r_inner, *start_cos, *start_sin, *end_cos, *end_sin, *extent,
*color,
),
Cmd::StrokeLine {
a, b, width, color, ..
} => r.stroke_line_aa(*a, *b, *width, *color),
Cmd::DrawText {
position,
text,
color,
} => r.draw_text(*position, text, *color),
Cmd::DrawPixels {
position,
pixels,
width,
height,
} => r.draw_pixels(*position, pixels, *width, *height),
Cmd::SetClip { .. } | Cmd::Barrier => {}
}
}
pub fn aabb(&self) -> Option<Rect> {
match self {
Cmd::FillRect { rect, .. } => Some(*rect),
Cmd::FillObb { obb, .. } => Some(obb.aabb()),
Cmd::FillDisc { center, radius, .. }
| Cmd::FillArc {
center,
r_outer: radius,
..
} => {
let pad = radius + 1.0;
Some(Rect {
x: (center.x - pad) as i32 - 1,
y: (center.y - pad) as i32 - 1,
width: (pad * 2.0) as i32 + 3,
height: (pad * 2.0) as i32 + 3,
})
}
Cmd::StrokeLine { a, b, width, .. } => {
let pad = width * 0.5 + 1.0;
let x0 = (a.x.min(b.x) - pad) as i32 - 1;
let y0 = (a.y.min(b.y) - pad) as i32 - 1;
let x1 = (a.x.max(b.x) + pad) as i32 + 2;
let y1 = (a.y.max(b.y) + pad) as i32 + 2;
Some(Rect {
x: x0,
y: y0,
width: x1 - x0,
height: y1 - y0,
})
}
Cmd::DrawText { .. } => None, Cmd::DrawPixels {
position,
width,
height,
..
} => Some(Rect {
x: position.0,
y: position.1,
width: *width as i32,
height: *height as i32,
}),
Cmd::SetClip { .. } | Cmd::Barrier => None,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct CommandList {
cmds: Vec<Cmd>,
}
impl CommandList {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, cmd: Cmd) {
self.cmds.push(cmd);
}
pub fn len(&self) -> usize {
self.cmds.len()
}
pub fn is_empty(&self) -> bool {
self.cmds.is_empty()
}
pub fn iter(&self) -> core::slice::Iter<'_, Cmd> {
self.cmds.iter()
}
pub fn clear(&mut self) {
self.cmds.clear();
}
pub fn replay<R: Renderer + ?Sized>(&self, r: &mut R) {
for cmd in &self.cmds {
cmd.dispatch_to(r);
}
}
pub fn dirty_union(&self) -> Option<Rect> {
let mut union: Option<Rect> = None;
for cmd in &self.cmds {
if let Some(bb) = cmd.aabb() {
union = Some(match union {
None => bb,
Some(u) => rect_union(u, bb),
});
}
}
union
}
}
fn rect_union(a: Rect, b: Rect) -> Rect {
a.union(b)
}
pub trait CommandSink {
fn submit(&mut self, list: &CommandList);
}
pub struct RendererSink<'a, R: Renderer + ?Sized> {
pub inner: &'a mut R,
}
impl<'a, R: Renderer + ?Sized> RendererSink<'a, R> {
pub fn new(inner: &'a mut R) -> Self {
Self { inner }
}
}
impl<R: Renderer + ?Sized> CommandSink for RendererSink<'_, R> {
fn submit(&mut self, list: &CommandList) {
list.replay(self.inner);
}
}
pub struct Recorder<'a> {
list: &'a mut CommandList,
passthrough: &'a mut dyn Renderer,
}
impl<'a> Recorder<'a> {
pub fn new(list: &'a mut CommandList, passthrough: &'a mut dyn Renderer) -> Self {
Self { list, passthrough }
}
}
impl Renderer for Recorder<'_> {
fn fill_rect(&mut self, rect: Rect, color: Color) {
self.list.push(Cmd::FillRect {
rect,
color,
blend: BlendMode::Replace,
});
}
fn blend_rect(&mut self, rect: Rect, color: Color) {
self.list.push(Cmd::FillRect {
rect,
color,
blend: BlendMode::SrcOver,
});
}
fn fill_obb_aa(&mut self, obb: Obb, color: Color) {
self.list.push(Cmd::FillObb {
obb,
color,
blend: BlendMode::SrcOver,
});
}
fn fill_disc_aa(&mut self, center: PointF, radius: f32, color: Color) {
self.list.push(Cmd::FillDisc {
center,
radius,
color,
blend: BlendMode::SrcOver,
});
}
#[allow(clippy::too_many_arguments)]
fn fill_arc_aa(
&mut self,
center: PointF,
r_outer: f32,
r_inner: f32,
start_cos: f32,
start_sin: f32,
end_cos: f32,
end_sin: f32,
extent: f32,
color: Color,
) {
self.list.push(Cmd::FillArc {
center,
r_outer,
r_inner,
start_cos,
start_sin,
end_cos,
end_sin,
extent,
color,
blend: BlendMode::SrcOver,
});
}
fn stroke_line_aa(&mut self, a: PointF, b: PointF, width: f32, color: Color) {
self.list.push(Cmd::StrokeLine {
a,
b,
width,
color,
blend: BlendMode::SrcOver,
});
}
fn draw_text(&mut self, position: (i32, i32), text: &str, color: Color) {
self.passthrough.draw_text(position, text, color);
}
fn draw_pixels(&mut self, position: (i32, i32), pixels: &[Color], width: u32, height: u32) {
self.passthrough
.draw_pixels(position, pixels, width, height);
}
fn blend_row(&mut self, x: i32, y: i32, color: Color, coverage: &[u8]) {
self.passthrough.blend_row(x, y, color, coverage);
}
}