use std::cell::Cell;
use mirui::render::canvas::{Canvas, Paint};
use mirui::render::command::CompositeMode;
use mirui::render::path::Path;
use mirui::render::texture::{ColorFormat, Texture};
use mirui::types::{Color, Fixed, Point, Rect, Transform};
use mirui_macros::compose_backend;
#[derive(Default)]
struct Counts {
fill_path: Cell<u32>,
stroke_path: Cell<u32>,
blit: Cell<u32>,
clear: Cell<u32>,
draw_label: Cell<u32>,
flush: Cell<u32>,
fill_rect: Cell<u32>,
stroke_rect: Cell<u32>,
draw_line: Cell<u32>,
draw_arc: Cell<u32>,
}
struct Dummy {
counts: Counts,
}
impl Dummy {
fn new() -> Self {
Self {
counts: Counts::default(),
}
}
}
impl Canvas for Dummy {
fn fill_path(
&mut self,
_: &Path,
_: &Rect,
_: &Paint,
_: u8,
_: ::mirui::render::raster::FillRule,
) {
self.counts.fill_path.set(self.counts.fill_path.get() + 1);
}
fn stroke_path(
&mut self,
_: &Path,
_: &Rect,
_: Fixed,
_: &Paint,
_: u8,
_: ::mirui::render::raster::LineCap,
_: ::mirui::render::raster::LineJoin,
_: ::mirui::types::Fixed,
_: &[Fixed],
) {
self.counts
.stroke_path
.set(self.counts.stroke_path.get() + 1);
}
fn blit(
&mut self,
_: &Texture,
_: &Rect,
_: Point,
_: Point,
_: &Rect,
_: u8,
_: Fixed,
_: CompositeMode,
) {
self.counts.blit.set(self.counts.blit.get() + 1);
}
fn clear(&mut self, _: &Rect, _: &Color) {
self.counts.clear.set(self.counts.clear.get() + 1);
}
fn draw_label(
&mut self,
_: &Point,
_: &str,
_: &mirui::render::font::Font,
_: &Rect,
_: &Color,
_: u8,
) {
self.counts.draw_label.set(self.counts.draw_label.get() + 1);
}
fn flush(&mut self) {
self.counts.flush.set(self.counts.flush.get() + 1);
}
fn fill_rect(&mut self, _: &Rect, _: &Rect, _: &Color, _: Fixed, _: u8) {
self.counts.fill_rect.set(self.counts.fill_rect.get() + 1);
}
fn stroke_rect(&mut self, _: &Rect, _: &Rect, _: Fixed, _: &Color, _: Fixed, _: u8) {
self.counts
.stroke_rect
.set(self.counts.stroke_rect.get() + 1);
}
fn draw_line(&mut self, _: Point, _: Point, _: &Rect, _: Fixed, _: &Color, _: u8) {
self.counts.draw_line.set(self.counts.draw_line.get() + 1);
}
fn draw_arc(
&mut self,
_: Point,
_: Fixed,
_: Fixed,
_: Fixed,
_: &Rect,
_: Fixed,
_: &Color,
_: u8,
) {
self.counts.draw_arc.set(self.counts.draw_arc.get() + 1);
}
}
compose_backend! {
pub struct Hybrid {
sw: Dummy,
gpu: Dummy,
}
route {
default => sw,
blit => gpu,
clear => gpu,
fill_rect => gpu,
}
}
fn fresh_hybrid() -> Hybrid<Dummy, Dummy> {
Hybrid {
sw: Dummy::new(),
gpu: Dummy::new(),
}
}
fn zero_rect() -> Rect {
Rect::new(0, 0, 4, 4)
}
fn dummy_texture_buf() -> Vec<u8> {
vec![0u8; 4 * 4 * 4]
}
#[test]
fn default_methods_route_to_sw() {
let mut h = fresh_hybrid();
let path = Path::new();
let rect = zero_rect();
let color = Color::rgb(0, 0, 0);
let paint = Paint::Color(color.into());
h.fill_path(
&path,
&rect,
&paint,
255,
::mirui::render::raster::FillRule::EvenOdd,
);
h.stroke_path(
&path,
&rect,
Fixed::ONE,
&paint,
255,
::mirui::render::raster::LineCap::Butt,
::mirui::render::raster::LineJoin::Miter,
::mirui::types::Fixed::from_int(4),
&[],
);
let font = mirui::render::font::Font::bitmap_8x8();
h.draw_label(&Point::ZERO, "x", &font, &rect, &color, 255);
h.flush();
assert_eq!(h.sw.counts.fill_path.get(), 1);
assert_eq!(h.sw.counts.stroke_path.get(), 1);
assert_eq!(h.sw.counts.draw_label.get(), 1);
assert_eq!(h.sw.counts.flush.get(), 1);
assert_eq!(h.gpu.counts.fill_path.get(), 0);
}
#[test]
fn explicit_routes_go_to_gpu() {
let mut h = fresh_hybrid();
let mut buf = dummy_texture_buf();
let tex = Texture::new(&mut buf, 4, 4, ColorFormat::RGBA8888);
let rect = zero_rect();
let color = Color::rgb(0, 0, 0);
h.blit(
&tex,
&rect,
Point::ZERO,
Point::ZERO,
&rect,
255,
Fixed::ZERO,
CompositeMode::SourceOver,
);
h.clear(&rect, &color);
h.fill_rect(&rect, &rect, &color, Fixed::ZERO, 255);
assert_eq!(h.gpu.counts.blit.get(), 1);
assert_eq!(h.gpu.counts.clear.get(), 1);
assert_eq!(h.gpu.counts.fill_rect.get(), 1);
assert_eq!(h.sw.counts.blit.get(), 0);
assert_eq!(h.sw.counts.clear.get(), 0);
assert_eq!(h.sw.counts.fill_rect.get(), 0);
}
#[test]
fn unrouted_default_impl_methods_fall_through_to_trait_default() {
let mut h = fresh_hybrid();
let rect = zero_rect();
let color = Color::rgb(0, 0, 0);
h.stroke_rect(&rect, &rect, Fixed::ONE, &color, Fixed::ZERO, 255);
h.draw_line(Point::ZERO, Point::ZERO, &rect, Fixed::ONE, &color, 255);
h.draw_arc(
Point::ZERO,
Fixed::from_int(4),
Fixed::ZERO,
Fixed::from_int(90),
&rect,
Fixed::ONE,
&color,
255,
);
assert_eq!(h.sw.counts.stroke_path.get(), 3);
assert_eq!(h.sw.counts.stroke_rect.get(), 0);
assert_eq!(h.sw.counts.draw_line.get(), 0);
assert_eq!(h.sw.counts.draw_arc.get(), 0);
}
struct BorrowedDummy<'fb> {
buf: &'fb mut [u8],
fills: Cell<u32>,
}
impl<'fb> BorrowedDummy<'fb> {
fn new(buf: &'fb mut [u8]) -> Self {
Self {
buf,
fills: Cell::new(0),
}
}
}
impl<'fb> Canvas for BorrowedDummy<'fb> {
fn fill_path(
&mut self,
_: &Path,
_: &Rect,
_: &Paint,
_: u8,
_: ::mirui::render::raster::FillRule,
) {
if !self.buf.is_empty() {
self.buf[0] = self.buf[0].wrapping_add(1);
}
self.fills.set(self.fills.get() + 1);
}
fn stroke_path(
&mut self,
_: &Path,
_: &Rect,
_: Fixed,
_: &Paint,
_: u8,
_: ::mirui::render::raster::LineCap,
_: ::mirui::render::raster::LineJoin,
_: ::mirui::types::Fixed,
_: &[Fixed],
) {
}
fn blit(
&mut self,
_: &Texture,
_: &Rect,
_: Point,
_: Point,
_: &Rect,
_: u8,
_: Fixed,
_: CompositeMode,
) {
}
fn clear(&mut self, _: &Rect, _: &Color) {}
fn draw_label(
&mut self,
_: &Point,
_: &str,
_: &mirui::render::font::Font,
_: &Rect,
_: &Color,
_: u8,
) {
}
fn flush(&mut self) {}
}
struct PlainDummy;
impl Canvas for PlainDummy {
fn fill_path(
&mut self,
_: &Path,
_: &Rect,
_: &Paint,
_: u8,
_: ::mirui::render::raster::FillRule,
) {
}
fn stroke_path(
&mut self,
_: &Path,
_: &Rect,
_: Fixed,
_: &Paint,
_: u8,
_: ::mirui::render::raster::LineCap,
_: ::mirui::render::raster::LineJoin,
_: ::mirui::types::Fixed,
_: &[Fixed],
) {
}
fn blit(
&mut self,
_: &Texture,
_: &Rect,
_: Point,
_: Point,
_: &Rect,
_: u8,
_: Fixed,
_: CompositeMode,
) {
}
fn clear(&mut self, _: &Rect, _: &Color) {}
fn draw_label(
&mut self,
_: &Point,
_: &str,
_: &mirui::render::font::Font,
_: &Rect,
_: &Color,
_: u8,
) {
}
fn flush(&mut self) {}
}
compose_backend! {
pub struct HybridWithLifetime {
borrowed: BorrowedDummy,
plain: PlainDummy,
}
route {
default => borrowed,
blit => plain,
}
}
#[test]
fn hybrid_is_a_renderer_and_dispatches_drawcommands() {
use mirui::render::DrawCommand;
use mirui::render::renderer::Renderer;
let mut h = fresh_hybrid();
let mut buf = dummy_texture_buf();
let tex = Texture::new(&mut buf, 4, 4, ColorFormat::RGBA8888);
let rect = zero_rect();
Renderer::draw(
&mut h,
&DrawCommand::Blit {
pos: Point::ZERO,
size: Point::ZERO,
transform: Transform::IDENTITY,
quad: None,
texture: &tex,
opa: 255,
radius: Fixed::ZERO,
composite: mirui::render::CompositeMode::SourceOver,
},
&rect,
);
assert_eq!(h.gpu.counts.blit.get(), 1);
assert_eq!(h.sw.counts.blit.get(), 0);
}
#[test]
fn hybrid_accepts_backend_with_lifetime_parameter() {
let mut buf = [0u8; 8];
let borrowed = BorrowedDummy::new(&mut buf);
let plain = PlainDummy;
let mut h: HybridWithLifetime<BorrowedDummy<'_>, PlainDummy> =
HybridWithLifetime { borrowed, plain };
let rect = Rect::new(0, 0, 4, 4);
let path = Path::new();
let paint = Paint::Color(Color::rgb(0, 0, 0).into());
h.fill_path(
&path,
&rect,
&paint,
255,
::mirui::render::raster::FillRule::EvenOdd,
);
assert_eq!(h.borrowed.fills.get(), 1);
assert_eq!(h.borrowed.buf[0], 1);
}