use std::path::Path;
use std::sync::Arc;
use crate::app::PendingWindow;
use crate::core::{CommandQueue, WidgetState};
use crate::ext_event::ExtEventHost;
use crate::piet::{BitmapTarget, Device, Error, ImageFormat, Piet};
use crate::*;
use crate::debug_state::DebugState;
pub(crate) const DEFAULT_SIZE: Size = Size::new(400., 400.);
pub struct Harness<'a, T> {
piet: Piet<'a>,
mock_app: MockAppState<T>,
window_size: Size,
}
struct MockAppState<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) -> Arc<[u8]> {
let mut raw_target = self.0.take().unwrap();
raw_target
.to_image_buf(ImageFormat::RgbaPremul)
.unwrap()
.raw_pixels_shared()
}
#[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)
}
}
#[allow(missing_docs)]
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 ext_host = ExtEventHost::default();
let ext_handle = ext_host.make_sink();
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 pending = PendingWindow::new(root);
let window = Window::new(WindowId::next(), Default::default(), pending, ext_handle);
let mock_app = MockAppState {
data,
env: Env::with_default_i10n(),
window,
cmds: Default::default(),
};
let mut harness = Harness {
piet,
mock_app,
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.mock_app.window
}
#[allow(dead_code)]
pub fn window_mut(&mut self) -> &mut Window<T> {
&mut self.mock_app.window
}
#[allow(dead_code)]
pub fn data(&self) -> &T {
&self.mock_app.data
}
pub 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 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 fn get_root_debug_state(&self) -> DebugState {
self.mock_app.root_debug_state()
}
pub fn get_debug_state(&mut self, widget_id: WidgetId) -> DebugState {
match self.try_get_debug_state(widget_id) {
Some(thing) => thing,
None => panic!("get_debug_state failed for widget {widget_id:?}"),
}
}
pub fn try_get_debug_state(&mut self, widget_id: WidgetId) -> Option<DebugState> {
let cell = DebugStateCell::default();
let state_cell = cell.clone();
self.lifecycle(LifeCycle::Internal(
InternalLifeCycle::DebugRequestDebugState {
widget: widget_id,
state_cell,
},
));
cell.take()
}
pub 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>) {
let command = cmd.into().default_to(self.mock_app.window.id.into());
let event = Event::Internal(InternalEvent::TargetedCommand(command));
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.mock_app.event(event);
self.process_commands();
self.update();
}
fn process_commands(&mut self) {
loop {
let cmd = self.mock_app.cmds.pop_front();
match cmd {
Some(cmd) => self.event(Event::Internal(InternalEvent::TargetedCommand(cmd))),
None => break,
}
}
}
pub(crate) fn lifecycle(&mut self, event: LifeCycle) {
self.mock_app.lifecycle(event)
}
fn update(&mut self) {
self.mock_app.update()
}
pub fn just_layout(&mut self) {
self.mock_app.layout()
}
#[allow(dead_code)]
pub fn paint_invalid(&mut self) {
let invalid = std::mem::replace(self.window_mut().invalid_mut(), Region::EMPTY);
self.mock_app.paint_region(&mut self.piet, &invalid);
}
#[allow(dead_code)]
pub fn paint(&mut self) {
self.window_mut().invalid_mut().clear();
self.mock_app
.paint_region(&mut self.piet, &self.window_size.to_rect().into());
}
pub fn root_debug_state(&self) -> DebugState {
self.mock_app.root_debug_state()
}
}
impl<T: Data> MockAppState<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_region(&mut self, piet: &mut Piet, invalid: &Region) {
self.window
.do_paint(piet, invalid, &mut self.cmds, &self.data, &self.env);
}
pub fn root_debug_state(&self) -> DebugState {
self.window.root_debug_state(&self.data)
}
}
impl<T> Drop for Harness<'_, T> {
fn drop(&mut self) {
if let Err(err) = self.piet.finish() {
tracing::error!("piet finish failed: {}", err);
}
}
}
impl Drop for TargetGuard<'_> {
fn drop(&mut self) {
let _ = self
.0
.take()
.map(|mut t| t.to_image_buf(piet::ImageFormat::RgbaPremul));
}
}