egui/crates/egui_kittest/src/builder.rs

259 lines
8.6 KiB
Rust

use crate::app_kind::AppKind;
use crate::{Harness, LazyRenderer, TestRenderer};
use egui::{Pos2, Rect, Vec2};
use std::marker::PhantomData;
/// Builder for [`Harness`].
pub struct HarnessBuilder<State = ()> {
pub(crate) screen_rect: Rect,
pub(crate) pixels_per_point: f32,
pub(crate) theme: egui::Theme,
pub(crate) os: egui::os::OperatingSystem,
pub(crate) max_steps: u64,
pub(crate) step_dt: f32,
pub(crate) state: PhantomData<State>,
pub(crate) renderer: Box<dyn TestRenderer>,
pub(crate) wait_for_pending_images: bool,
}
impl<State> Default for HarnessBuilder<State> {
fn default() -> Self {
Self {
screen_rect: Rect::from_min_size(Pos2::ZERO, Vec2::new(800.0, 600.0)),
pixels_per_point: 1.0,
theme: egui::Theme::Dark,
state: PhantomData,
renderer: Box::new(LazyRenderer::default()),
max_steps: 4,
step_dt: 1.0 / 4.0,
wait_for_pending_images: true,
os: egui::os::OperatingSystem::Nix,
}
}
}
impl<State> HarnessBuilder<State> {
/// Set the size of the window.
#[inline]
pub fn with_size(mut self, size: impl Into<Vec2>) -> Self {
let size = size.into();
self.screen_rect.set_width(size.x);
self.screen_rect.set_height(size.y);
self
}
/// Set the `pixels_per_point` of the window.
#[inline]
pub fn with_pixels_per_point(mut self, pixels_per_point: f32) -> Self {
self.pixels_per_point = pixels_per_point;
self
}
/// Set the desired theme (dark or light).
#[inline]
pub fn with_theme(mut self, theme: egui::Theme) -> Self {
self.theme = theme;
self
}
/// Override the [`egui::os::OperatingSystem`] reported to egui.
///
/// This affects e.g. the way shortcuts are displayed. So for snapshot tests,
/// it makes sense to set this to a specific OS, so snapshots don't change when running
/// the same tests on different OSes.
///
/// Default is [`egui::os::OperatingSystem::Nix`].
/// Use [`egui::os::OperatingSystem::from_target_os()`] to use the current OS (this restores
/// eguis default behavior).
#[inline]
pub fn with_os(mut self, os: egui::os::OperatingSystem) -> Self {
self.os = os;
self
}
/// Set the maximum number of steps to run when calling [`Harness::run`].
///
/// Default is 4.
/// With the default `step_dt`, this means 1 second of simulation.
#[inline]
pub fn with_max_steps(mut self, max_steps: u64) -> Self {
self.max_steps = max_steps;
self
}
/// Set the time delta for a single [`Harness::step`].
///
/// Default is 1.0 / 4.0 (4fps).
/// The default is low so we don't waste cpu waiting for animations.
#[inline]
pub fn with_step_dt(mut self, step_dt: f32) -> Self {
self.step_dt = step_dt;
self
}
/// Should we wait for pending images?
///
/// If `true`, [`Harness::run`] and related methods will check if there are pending images
/// (via [`egui::Context::has_pending_images`]) and sleep for [`Self::with_step_dt`] up to
/// [`Self::with_max_steps`] times.
///
/// Default: `true`
#[inline]
pub fn with_wait_for_pending_images(mut self, wait_for_pending_images: bool) -> Self {
self.wait_for_pending_images = wait_for_pending_images;
self
}
/// Set the [`TestRenderer`] to use for rendering.
///
/// By default, a [`LazyRenderer`] is used.
#[inline]
pub fn renderer(mut self, renderer: impl TestRenderer + 'static) -> Self {
self.renderer = Box::new(renderer);
self
}
/// Enable wgpu rendering with a default setup suitable for testing.
///
/// This sets up a [`crate::wgpu::WgpuTestRenderer`] with the default setup.
#[cfg(feature = "wgpu")]
pub fn wgpu(self) -> Self {
self.renderer(crate::wgpu::WgpuTestRenderer::default())
}
/// Enable wgpu rendering with the given setup.
#[cfg(feature = "wgpu")]
pub fn wgpu_setup(self, setup: egui_wgpu::WgpuSetup) -> Self {
self.renderer(crate::wgpu::WgpuTestRenderer::from_setup(setup))
}
/// Create a new Harness with the given app closure and a state.
///
/// The app closure will immediately be called once to create the initial ui.
///
/// If you don't need to create Windows / Panels, you can use [`HarnessBuilder::build_ui`] instead.
///
/// # Example
/// ```rust
/// # use egui::CentralPanel;
/// # use egui_kittest::{Harness, kittest::Queryable};
/// let checked = false;
/// let mut harness = Harness::builder()
/// .with_size(egui::Vec2::new(300.0, 200.0))
/// .build_state(|ctx, checked| {
/// CentralPanel::default().show(ctx, |ui| {
/// ui.checkbox(checked, "Check me!");
/// });
/// }, checked);
///
/// harness.get_by_label("Check me!").click();
/// harness.run();
///
/// assert_eq!(*harness.state(), true);
/// ```
pub fn build_state<'a>(
self,
app: impl FnMut(&egui::Context, &mut State) + 'a,
state: State,
) -> Harness<'a, State> {
Harness::from_builder(self, AppKind::ContextState(Box::new(app)), state, None)
}
/// Create a new Harness with the given ui closure and a state.
///
/// The ui closure will immediately be called once to create the initial ui.
///
/// If you need to create Windows / Panels, you can use [`HarnessBuilder::build`] instead.
///
/// # Example
/// ```rust
/// # use egui_kittest::{Harness, kittest::Queryable};
/// let mut checked = false;
/// let mut harness = Harness::builder()
/// .with_size(egui::Vec2::new(300.0, 200.0))
/// .build_ui_state(|ui, checked| {
/// ui.checkbox(checked, "Check me!");
/// }, checked);
///
/// harness.get_by_label("Check me!").click();
/// harness.run();
///
/// assert_eq!(*harness.state(), true);
/// ```
pub fn build_ui_state<'a>(
self,
app: impl FnMut(&mut egui::Ui, &mut State) + 'a,
state: State,
) -> Harness<'a, State> {
Harness::from_builder(self, AppKind::UiState(Box::new(app)), state, None)
}
/// Create a new [Harness] from the given eframe creation closure.
/// The app can be accessed via the [`Harness::state`] / [`Harness::state_mut`] methods.
#[cfg(feature = "eframe")]
pub fn build_eframe<'a>(
self,
build: impl FnOnce(&mut eframe::CreationContext<'a>) -> State,
) -> Harness<'a, State>
where
State: eframe::App,
{
let ctx = egui::Context::default();
let mut cc = eframe::CreationContext::_new_kittest(ctx.clone());
let mut frame = eframe::Frame::_new_kittest();
self.renderer.setup_eframe(&mut cc, &mut frame);
let app = build(&mut cc);
let kind = AppKind::Eframe((|state| state, frame));
Harness::from_builder(self, kind, app, Some(ctx))
}
}
impl HarnessBuilder {
/// Create a new Harness with the given app closure.
///
/// The app closure will immediately be called once to create the initial ui.
///
/// If you don't need to create Windows / Panels, you can use [`HarnessBuilder::build_ui`] instead.
///
/// # Example
/// ```rust
/// # use egui::CentralPanel;
/// # use egui_kittest::{Harness, kittest::Queryable};
/// let mut harness = Harness::builder()
/// .with_size(egui::Vec2::new(300.0, 200.0))
/// .build(|ctx| {
/// CentralPanel::default().show(ctx, |ui| {
/// ui.label("Hello, world!");
/// });
/// });
/// ```
#[must_use]
pub fn build<'a>(self, app: impl FnMut(&egui::Context) + 'a) -> Harness<'a> {
Harness::from_builder(self, AppKind::Context(Box::new(app)), (), None)
}
/// Create a new Harness with the given ui closure.
///
/// The ui closure will immediately be called once to create the initial ui.
///
/// If you need to create Windows / Panels, you can use [`HarnessBuilder::build`] instead.
///
/// # Example
/// ```rust
/// # use egui_kittest::{Harness, kittest::Queryable};
/// let mut harness = Harness::builder()
/// .with_size(egui::Vec2::new(300.0, 200.0))
/// .build_ui(|ui| {
/// ui.label("Hello, world!");
/// });
/// ```
#[must_use]
pub fn build_ui<'a>(self, app: impl FnMut(&mut egui::Ui) + 'a) -> Harness<'a> {
Harness::from_builder(self, AppKind::Ui(Box::new(app)), (), None)
}
}