use crate::core::style::TextStyle;
use crate::core::{Color, Position, Rect, Size};
use crate::paint::Painter;
#[derive(Debug, Clone)]
pub enum RenderOp {
FillRect {
rect: Rect,
color: Color,
corner_radius: f32,
},
StrokeRect {
rect: Rect,
color: Color,
width: f32,
corner_radius: f32,
},
FillCircle {
center: Position,
radius: f32,
color: Color,
},
StrokeCircle {
center: Position,
radius: f32,
color: Color,
width: f32,
},
Text {
position: Position,
text: String,
size: f32,
color: Color,
},
Line {
from: Position,
to: Position,
color: Color,
width: f32,
},
PushClip { rect: Rect },
PopClip,
}
pub struct TestBackend {
width: f32,
height: f32,
ops: Vec<RenderOp>,
}
impl TestBackend {
pub fn new(width: f32, height: f32) -> Self {
Self {
width,
height,
ops: Vec::new(),
}
}
pub fn area(&self) -> Rect {
Rect::new(0.0, 0.0, self.width, self.height)
}
pub fn ops(&self) -> &[RenderOp] {
&self.ops
}
pub fn clear(&mut self) {
self.ops.clear();
}
pub fn count_fill_rects(&self) -> usize {
self.ops
.iter()
.filter(|op| matches!(op, RenderOp::FillRect { .. }))
.count()
}
pub fn count_texts(&self) -> usize {
self.ops
.iter()
.filter(|op| matches!(op, RenderOp::Text { .. }))
.count()
}
pub fn find_text(&self, needle: &str) -> Vec<&RenderOp> {
self.ops
.iter()
.filter(|op| {
if let RenderOp::Text { text, .. } = op {
text.contains(needle)
} else {
false
}
})
.collect()
}
}
impl Painter for TestBackend {
fn fill_rect(&mut self, rect: Rect, color: Color, corner_radius: f32) {
self.ops.push(RenderOp::FillRect {
rect,
color,
corner_radius,
});
}
fn stroke_rect(&mut self, rect: Rect, color: Color, width: f32, corner_radius: f32) {
self.ops.push(RenderOp::StrokeRect {
rect,
color,
width,
corner_radius,
});
}
fn fill_circle(&mut self, center: Position, radius: f32, color: Color) {
self.ops.push(RenderOp::FillCircle {
center,
radius,
color,
});
}
fn stroke_circle(&mut self, center: Position, radius: f32, color: Color, width: f32) {
self.ops.push(RenderOp::StrokeCircle {
center,
radius,
color,
width,
});
}
fn line(&mut self, from: Position, to: Position, color: Color, width: f32) {
self.ops.push(RenderOp::Line {
from,
to,
color,
width,
});
}
fn text(&mut self, pos: Position, text: &str, style: &TextStyle) {
self.ops.push(RenderOp::Text {
position: pos,
text: text.to_string(),
size: style.font_size,
color: style.color,
});
}
fn measure_text(&self, text: &str, style: &TextStyle) -> Size {
let w = style.font_size * 0.6 * text.len() as f32;
Size::new(w, style.font_size * 1.2)
}
fn push_clip(&mut self, rect: Rect) {
self.ops.push(RenderOp::PushClip { rect });
}
fn pop_clip(&mut self) {
self.ops.push(RenderOp::PopClip);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::style::TextStyle;
#[test]
fn test_backend_records_ops() {
let mut backend = TestBackend::new(800.0, 600.0);
backend.fill_rect(Rect::new(0.0, 0.0, 100.0, 50.0), Color::RED, 0.0);
backend.text(
Position::new(10.0, 10.0),
"Hello",
&TextStyle {
font_size: 16.0,
color: Color::WHITE,
..Default::default()
},
);
assert_eq!(backend.count_fill_rects(), 1);
assert_eq!(backend.count_texts(), 1);
assert_eq!(backend.find_text("Hello").len(), 1);
assert!(backend.find_text("missing").is_empty());
}
}