use crate::chrome::{self, WindowChrome};
use crate::event::{Event, EventCtx};
use crate::font::Font;
use crate::geometry::{Rect, Size};
use crate::painter::Painter;
use crate::theme::Theme;
use crate::widget::Widget;
pub struct MockBackend {
logical_size: Size,
scale: f32,
theme: Theme,
font: Option<Font>,
mono_font: Option<Font>,
}
impl MockBackend {
pub fn new(width: i32, height: i32) -> Self {
Self {
logical_size: Size::new(width.max(1), height.max(1)),
scale: 1.0,
theme: Theme::default(),
font: None,
mono_font: None,
}
}
pub fn with_scale(mut self, scale: f32) -> Self {
self.scale = scale.max(0.01);
self
}
pub fn with_theme(mut self, theme: Theme) -> Self {
self.theme = theme;
self
}
pub fn with_font(mut self, font: Font) -> Self {
self.font = Some(font);
self
}
pub fn with_mono_font(mut self, font: Font) -> Self {
self.mono_font = Some(font);
self
}
pub fn physical_size(&self) -> Size {
let w = (self.logical_size.w as f32 * self.scale).round().max(1.0) as i32;
let h = (self.logical_size.h as f32 * self.scale).round().max(1.0) as i32;
Size::new(w, h)
}
pub fn dispatch(&self, root: &mut dyn Widget, event: &Event) -> DispatchOutcome {
let mut ctx = EventCtx::new();
root.event(event, &mut ctx);
DispatchOutcome {
paint_requested: ctx.paint_requested,
close_requested: ctx.close_requested,
}
}
pub fn render(&self, root: &mut dyn Widget) -> Snapshot {
let physical = self.physical_size();
let logical_w = (physical.w as f32 / self.scale).round().max(1.0) as i32;
let logical_h = (physical.h as f32 / self.scale).round().max(1.0) as i32;
root.layout(Rect::new(0, 0, logical_w, logical_h));
let mut pixels = vec![0u32; (physical.w * physical.h) as usize];
let (origin_x, origin_y) = origin_centered(self.logical_size, self.scale, physical);
{
let mut painter = Painter::with_popup_anchor(
&mut pixels,
physical.w,
physical.h,
self.scale,
origin_x,
origin_y,
self.font.as_ref(),
self.mono_font.as_ref(),
None,
);
painter.fill(self.theme.background);
root.paint(&mut painter, &self.theme);
}
let mut popups = Vec::new();
root.collect_popups(&mut popups);
for req in &popups {
let popup_phys_x = origin_x + (req.rect.x as f32 * self.scale).round() as i32;
let popup_phys_y = origin_y + (req.rect.y as f32 * self.scale).round() as i32;
let popup_phys_w = (req.rect.w as f32 * self.scale).round() as i32;
let popup_phys_h = (req.rect.h as f32 * self.scale).round() as i32;
let mut painter = Painter::with_popup_anchor(
&mut pixels,
physical.w,
physical.h,
self.scale,
origin_x,
origin_y,
self.font.as_ref(),
self.mono_font.as_ref(),
Some(req.rect),
);
painter.set_clip_phys(popup_phys_x, popup_phys_y, popup_phys_w, popup_phys_h);
root.paint(&mut painter, &self.theme);
painter.clear_clip();
}
Snapshot {
width: physical.w,
height: physical.h,
pixels,
}
}
pub fn render_framed(&self, root: &mut dyn Widget, chrome: &WindowChrome) -> Snapshot {
let content = self.render(root);
let content_size = Size::new(content.width, content.height);
let m = chrome::metrics(content_size, self.scale, chrome);
let mut pixels = vec![0u32; (m.buffer.w * m.buffer.h) as usize];
{
let mut painter = Painter::new(
&mut pixels,
m.buffer.w,
m.buffer.h,
1.0,
0,
0,
self.font.as_ref(),
self.mono_font.as_ref(),
);
chrome::paint(&mut painter, &m, chrome);
}
let cw = content.width as usize;
for row in 0..content.height {
let src = (row * content.width) as usize;
let dst = ((m.content.y + row) * m.buffer.w + m.content.x) as usize;
pixels[dst..dst + cw].copy_from_slice(&content.pixels[src..src + cw]);
}
Snapshot {
width: m.buffer.w,
height: m.buffer.h,
pixels,
}
}
}
fn origin_centered(logical: Size, scale: f32, physical: Size) -> (i32, i32) {
let content_w = (logical.w as f32 * scale).round() as i32;
let content_h = (logical.h as f32 * scale).round() as i32;
let ox = ((physical.w - content_w) / 2).max(0);
let oy = ((physical.h - content_h) / 2).max(0);
(ox, oy)
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct DispatchOutcome {
pub paint_requested: bool,
pub close_requested: bool,
}
pub struct Snapshot {
width: i32,
height: i32,
pixels: Vec<u32>,
}
impl Snapshot {
pub fn width(&self) -> i32 {
self.width
}
pub fn height(&self) -> i32 {
self.height
}
pub fn pixels(&self) -> &[u32] {
&self.pixels
}
pub fn to_png(&self) -> Vec<u8> {
let mut buf = Vec::new();
{
let mut encoder = png::Encoder::new(&mut buf, self.width as u32, self.height as u32);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header().expect("saudade::mock: png header");
let mut rgba = Vec::with_capacity(self.pixels.len() * 4);
for &px in &self.pixels {
let a = ((px >> 24) & 0xFF) as u8;
let r = ((px >> 16) & 0xFF) as u8;
let g = ((px >> 8) & 0xFF) as u8;
let b = (px & 0xFF) as u8;
rgba.extend_from_slice(&[r, g, b, a]);
}
writer
.write_image_data(&rgba)
.expect("saudade::mock: png data");
}
buf
}
}