use smallvec::SmallVec;
use crate::geometry::{BorderRadius, Color, Point, Rect};
const DEFAULT_COMMAND_CAPACITY: usize = 256;
#[derive(Debug)]
pub struct Painter {
commands: Vec<DrawCommand>,
clip_stack: SmallVec<[Rect; 8]>,
transform_stack: SmallVec<[Transform; 8]>,
}
impl Default for Painter {
fn default() -> Self {
Self::new()
}
}
impl Painter {
#[inline]
pub fn new() -> Self {
Self::with_capacity(DEFAULT_COMMAND_CAPACITY)
}
pub fn with_capacity(capacity: usize) -> Self {
let mut transform_stack = SmallVec::new();
transform_stack.push(Transform::identity());
Self {
commands: Vec::with_capacity(capacity),
clip_stack: SmallVec::new(),
transform_stack,
}
}
#[inline]
pub fn finish(self) -> Vec<DrawCommand> {
self.commands
}
#[inline]
pub fn clear(&mut self) {
self.commands.clear();
}
#[inline]
pub fn len(&self) -> usize {
self.commands.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.commands.is_empty()
}
#[inline]
pub fn fill_rect(&mut self, rect: Rect, color: Color) {
self.fill_rounded_rect(rect, color, BorderRadius::ZERO);
}
#[inline]
pub fn fill_rounded_rect(&mut self, rect: Rect, color: Color, radius: BorderRadius) {
let rect = self.transform_rect(rect);
self.commands.push(DrawCommand::Rect { rect, color, radius });
}
pub fn stroke_rect(&mut self, rect: Rect, color: Color, width: f32) {
let rect = self.transform_rect(rect);
self.draw_line(
Point::new(rect.x(), rect.y()),
Point::new(rect.max_x(), rect.y()),
color,
width,
);
self.draw_line(
Point::new(rect.max_x(), rect.y()),
Point::new(rect.max_x(), rect.max_y()),
color,
width,
);
self.draw_line(
Point::new(rect.max_x(), rect.max_y()),
Point::new(rect.x(), rect.max_y()),
color,
width,
);
self.draw_line(
Point::new(rect.x(), rect.max_y()),
Point::new(rect.x(), rect.y()),
color,
width,
);
}
pub fn stroke_rounded_rect(&mut self, rect: Rect, color: Color, radius: BorderRadius, width: f32) {
let rect = self.transform_rect(rect);
self.commands.push(DrawCommand::StrokeRoundedRect { rect, color, radius, width });
}
#[inline]
pub fn draw_line(&mut self, from: Point, to: Point, color: Color, width: f32) {
let from = self.transform_point(from);
let to = self.transform_point(to);
self.commands.push(DrawCommand::Line { from, to, color, width });
}
#[inline]
pub fn draw_text(&mut self, text: &str, position: Point, color: Color, size: f32) {
let position = self.transform_point(position);
self.commands.push(DrawCommand::Text {
text: text.to_string(),
position,
color,
size,
});
}
#[inline]
pub fn draw_image(&mut self, rect: Rect, image_id: u64) {
let rect = self.transform_rect(rect);
self.commands.push(DrawCommand::Image { rect, image_id });
}
#[inline]
pub fn draw_path(&mut self, path: &str, rect: Rect, color: Color, viewbox: (f32, f32, f32, f32)) {
let rect = self.transform_rect(rect);
self.commands.push(DrawCommand::Path {
path: path.to_string(),
rect,
color,
viewbox,
});
}
#[inline]
pub fn draw_icon(&mut self, path: &str, position: Point, size: f32, color: Color, viewbox: (f32, f32, f32, f32)) {
let rect = Rect::new(position.x, position.y, size, size);
self.draw_path(path, rect, color, viewbox);
}
#[inline]
pub fn push_clip(&mut self, rect: Rect) {
let rect = self.transform_rect(rect);
self.clip_stack.push(rect);
}
#[inline]
pub fn pop_clip(&mut self) {
self.clip_stack.pop();
}
#[inline]
pub fn save(&mut self) {
if let Some(current) = self.transform_stack.last() {
self.transform_stack.push(*current);
}
}
#[inline]
pub fn restore(&mut self) {
if self.transform_stack.len() > 1 {
self.transform_stack.pop();
}
}
#[inline]
pub fn translate(&mut self, dx: f32, dy: f32) {
if let Some(transform) = self.transform_stack.last_mut() {
transform.tx += dx;
transform.ty += dy;
}
}
#[inline]
pub fn scale(&mut self, sx: f32, sy: f32) {
if let Some(transform) = self.transform_stack.last_mut() {
transform.sx *= sx;
transform.sy *= sy;
}
}
fn transform_point(&self, point: Point) -> Point {
if let Some(t) = self.transform_stack.last() {
Point::new(point.x * t.sx + t.tx, point.y * t.sy + t.ty)
} else {
point
}
}
fn transform_rect(&self, rect: Rect) -> Rect {
let origin = self.transform_point(rect.origin);
if let Some(t) = self.transform_stack.last() {
Rect::new(origin.x, origin.y, rect.width() * t.sx, rect.height() * t.sy)
} else {
Rect::from_origin_size(origin, rect.size)
}
}
}
#[derive(Debug, Clone)]
pub enum DrawCommand {
Rect {
rect: Rect,
color: Color,
radius: BorderRadius,
},
StrokeRoundedRect {
rect: Rect,
color: Color,
radius: BorderRadius,
width: f32,
},
Text {
text: String,
position: Point,
color: Color,
size: f32,
},
Line {
from: Point,
to: Point,
color: Color,
width: f32,
},
Image {
rect: Rect,
image_id: u64,
},
Path {
path: String,
rect: Rect,
color: Color,
viewbox: (f32, f32, f32, f32),
},
}
#[derive(Debug, Clone, Copy)]
struct Transform {
tx: f32,
ty: f32,
sx: f32,
sy: f32,
}
impl Transform {
fn identity() -> Self {
Self {
tx: 0.0,
ty: 0.0,
sx: 1.0,
sy: 1.0,
}
}
}
impl Default for Transform {
fn default() -> Self {
Self::identity()
}
}