use std::path::Path;
use crate::core::{CommandQueue, WidgetState};
use crate::piet::{BitmapTarget, Device, Error, ImageFormat, Piet};
use crate::*;
pub(crate) const DEFAULT_SIZE: Size = Size::new(400., 400.);
pub struct Harness<'a, T> {
piet: Piet<'a>,
inner: Inner<T>,
window_size: Size,
}
struct Inner<T> {
data: T,
env: Env,
window: Window<T>,
cmds: CommandQueue,
}
pub struct TargetGuard<'a>(Option<BitmapTarget<'a>>);
impl<'a> TargetGuard<'a> {
#[allow(dead_code)]
pub fn into_raw(mut self) -> Vec<u8> {
let raw_target = self.0.take().unwrap();
let raw_pixels: Vec<u8> = raw_target.into_raw_pixels(ImageFormat::RgbaPremul).unwrap();
raw_pixels
}
#[allow(dead_code)]
pub fn into_png<P: AsRef<Path>>(mut self, path: P) -> Result<(), Error> {
let raw_target = self.0.take().unwrap();
raw_target.save_to_file(path)
}
}
impl<T: Data> Harness<'_, T> {
pub fn create_simple(
data: T,
root: impl Widget<T> + 'static,
harness_closure: impl FnMut(&mut Harness<T>),
) {
Self::create_with_render(data, root, DEFAULT_SIZE, harness_closure, |_target| {})
}
pub fn create_with_render(
data: T,
root: impl Widget<T> + 'static,
window_size: Size,
mut harness_closure: impl FnMut(&mut Harness<T>),
mut render_context_closure: impl FnMut(TargetGuard),
) {
let mut device = Device::new().expect("harness failed to get device");
let target = device
.bitmap_target(window_size.width as usize, window_size.height as usize, 1.0)
.expect("bitmap_target");
let mut target = TargetGuard(Some(target));
{
let piet = target.0.as_mut().unwrap().render_context();
let desc = WindowDesc::new(|| root);
let window = Window::new(WindowId::next(), Default::default(), desc);
let inner = Inner {
data,
env: theme::init(),
window,
cmds: Default::default(),
};
let mut harness = Harness {
piet,
inner,
window_size,
};
harness_closure(&mut harness);
}
render_context_closure(target)
}
pub fn set_initial_size(&mut self, size: Size) {
self.window_size = size;
}
pub fn window(&self) -> &Window<T> {
&self.inner.window
}
#[allow(dead_code)]
pub fn window_mut(&mut self) -> &mut Window<T> {
&mut self.inner.window
}
#[allow(dead_code)]
pub fn data(&self) -> &T {
&self.inner.data
}
pub(crate) fn get_state(&mut self, widget: WidgetId) -> WidgetState {
match self.try_get_state(widget) {
Some(thing) => thing,
None => panic!("get_state failed for widget {:?}", widget),
}
}
pub(crate) fn try_get_state(&mut self, widget: WidgetId) -> Option<WidgetState> {
let cell = StateCell::default();
let state_cell = cell.clone();
self.lifecycle(LifeCycle::Internal(InternalLifeCycle::DebugRequestState {
widget,
state_cell,
}));
cell.take()
}
pub(crate) fn inspect_state(&mut self, f: impl Fn(&WidgetState) + 'static) {
let checkfn = StateCheckFn::new(f);
self.lifecycle(LifeCycle::Internal(InternalLifeCycle::DebugInspectState(
checkfn,
)))
}
pub fn submit_command(&mut self, cmd: impl Into<Command>, target: impl Into<Option<Target>>) {
let target = target.into().unwrap_or_else(|| self.inner.window.id.into());
let event = Event::Internal(InternalEvent::TargetedCommand(target, cmd.into()));
self.event(event);
}
pub fn send_initial_events(&mut self) {
self.event(Event::WindowConnected);
self.event(Event::WindowSize(self.window_size));
}
pub fn event(&mut self, event: Event) {
self.inner.event(event);
self.process_commands();
self.update();
}
fn process_commands(&mut self) {
loop {
let cmd = self.inner.cmds.pop_front();
match cmd {
Some((target, cmd)) => {
self.event(Event::Internal(InternalEvent::TargetedCommand(target, cmd)))
}
None => break,
}
}
}
fn lifecycle(&mut self, event: LifeCycle) {
self.inner.lifecycle(event)
}
fn update(&mut self) {
self.inner.update()
}
pub fn just_layout(&mut self) {
self.inner.layout()
}
pub fn paint_rect(&mut self, invalid_rect: Rect) {
self.inner.paint_rect(&mut self.piet, invalid_rect)
}
#[allow(dead_code)]
pub fn paint(&mut self) {
self.paint_rect(self.window_size.to_rect())
}
}
impl<T: Data> Inner<T> {
fn event(&mut self, event: Event) {
self.window
.event(&mut self.cmds, event, &mut self.data, &self.env);
}
fn lifecycle(&mut self, event: LifeCycle) {
self.window
.lifecycle(&mut self.cmds, &event, &self.data, &self.env, false);
}
fn update(&mut self) {
self.window.update(&mut self.cmds, &self.data, &self.env);
}
fn layout(&mut self) {
self.window
.just_layout(&mut self.cmds, &self.data, &self.env);
}
#[allow(dead_code)]
fn paint_rect(&mut self, piet: &mut Piet, invalid_rect: Rect) {
self.window
.do_paint(piet, invalid_rect, &mut self.cmds, &self.data, &self.env);
}
}
impl<T> Drop for Harness<'_, T> {
fn drop(&mut self) {
if let Err(err) = self.piet.finish() {
log::error!("piet finish failed: {}", err);
}
}
}
impl Drop for TargetGuard<'_> {
fn drop(&mut self) {
let _ = self
.0
.take()
.map(|t| t.into_raw_pixels(piet::ImageFormat::RgbaPremul));
}
}