diff --git a/Cargo.lock b/Cargo.lock index 43a25113bd8..eb32a0c1a1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1388,6 +1388,7 @@ dependencies = [ "document-features", "egui", "egui-wgpu", + "egui_kittest", "image", "kittest", "pollster", diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index 60c9bccf0a9..5dfe27d3c1b 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -300,15 +300,12 @@ mod tests { date: Some(chrono::NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()), ..Default::default() }; - let app = |ctx: &Context| { - CentralPanel::default().show(ctx, |ui| { - demo.ui(ui); - }); - }; - let harness = Harness::builder() - .with_size(Vec2::new(380.0, 550.0)) + let mut harness = Harness::builder() .with_dpi(2.0) - .build(app); + .with_size(Vec2::new(380.0, 550.0)) + .build_ui(|ui| demo.ui(ui)); + + harness.fit_contents(); harness.wgpu_snapshot("widget_gallery"); } diff --git a/crates/egui_demo_lib/src/rendering_test.rs b/crates/egui_demo_lib/src/rendering_test.rs index 3ae4f111808..a5a8dde7c99 100644 --- a/crates/egui_demo_lib/src/rendering_test.rs +++ b/crates/egui_demo_lib/src/rendering_test.rs @@ -435,10 +435,7 @@ fn pixel_test_strokes(ui: &mut Ui) { let thickness_pixels = thickness_pixels as f32; let thickness_points = thickness_pixels / pixels_per_point; let num_squares = (pixels_per_point * 10.0).round().max(10.0) as u32; - let size_pixels = vec2( - ui.available_width(), - num_squares as f32 + thickness_pixels * 2.0, - ); + let size_pixels = vec2(ui.min_size().x, num_squares as f32 + thickness_pixels * 2.0); let size_points = size_pixels / pixels_per_point + Vec2::splat(2.0); let (response, painter) = ui.allocate_painter(size_points, Sense::hover()); @@ -680,3 +677,36 @@ fn mul_color_gamma(left: Color32, right: Color32) -> Color32 { (left.a() as f32 * right.a() as f32 / 255.0).round() as u8, ) } + +#[cfg(test)] +mod tests { + use crate::ColorTest; + use egui::vec2; + + #[test] + pub fn rendering_test() { + let mut errors = vec![]; + for dpi in [1.0, 1.25, 1.5, 1.75, 1.6666667, 2.0] { + let mut color_test = ColorTest::default(); + let mut harness = egui_kittest::Harness::builder() + .with_size(vec2(2000.0, 2000.0)) + .with_dpi(dpi) + .build_ui(|ui| { + color_test.ui(ui); + }); + + //harness.set_size(harness.ctx.used_size()); + + harness.fit_contents(); + + let result = harness.try_wgpu_snapshot(&format!("rendering_test/dpi_{:.2}", dpi)); + if let Err(err) = result { + errors.push(err); + } + } + + if !errors.is_empty() { + panic!("Errors: {:#?}", errors); + } + } +} diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.00.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.00.png new file mode 100644 index 00000000000..e5870bd8fe3 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf774de50f485096b1190093e2ef9a9e598f1d19941045386f182b7e51744d0c +size 277095 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.25.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.25.png new file mode 100644 index 00000000000..fc42214700b --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.25.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1cf07a6f55057730133d192bcbf26d95583377d8c6147f3788eefa2d527972d4 +size 375597 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.50.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.50.png new file mode 100644 index 00000000000..4d5ec243d56 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.50.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06bc9f9adfbd017ae51cf1be8910db9db4552d6e741205065832cc29654bd38d +size 462813 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.67.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.67.png new file mode 100644 index 00000000000..3f1d5c8f417 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.67.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:567d52eb9b96942670e920c6e88bbfb5adc2690f6d5a2b6b87996ff96584899e +size 533920 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.75.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.75.png new file mode 100644 index 00000000000..5db5db3a7fa --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.75.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5837945b9b8f60a6dbc334eb84eb0df6217f240ca7fda3a12f6c79ba21a23ac1 +size 567531 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_2.00.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_2.00.png new file mode 100644 index 00000000000..26eb302bfd8 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_2.00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06de15c3e72805174fd9efc3ec9697b6360d3f54d67f2e837630c6887aeb1c15 +size 659759 diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery.png index 273b85a6303..8aa817094cd 100644 --- a/crates/egui_demo_lib/tests/snapshots/widget_gallery.png +++ b/crates/egui_demo_lib/tests/snapshots/widget_gallery.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5dc632962f8894c4f20a48c9b9e57d60470f3f83ef7f19d05854dba718610a2f -size 161820 +oid sha256:3833dc1709543abbb320ce32f47880bd40299801fa633c7f0eee27bed643e5ab +size 158260 diff --git a/crates/egui_kittest/Cargo.toml b/crates/egui_kittest/Cargo.toml index 5063b59658b..f9f98198c82 100644 --- a/crates/egui_kittest/Cargo.toml +++ b/crates/egui_kittest/Cargo.toml @@ -39,6 +39,7 @@ dify = { workspace = true, optional = true } document-features = { workspace = true, optional = true } [dev-dependencies] +egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] } wgpu = { workspace = true, features = ["metal"] } image = { workspace = true, features = ["png"] } egui = { workspace = true, features = ["default_fonts"] } diff --git a/crates/egui_kittest/src/builder.rs b/crates/egui_kittest/src/builder.rs index bf1daf3622b..381cea1e625 100644 --- a/crates/egui_kittest/src/builder.rs +++ b/crates/egui_kittest/src/builder.rs @@ -1,3 +1,4 @@ +use crate::harness_kind::AppKind; use crate::Harness; use egui::{Pos2, Rect, Vec2}; @@ -50,6 +51,10 @@ impl HarnessBuilder { /// }); /// ``` pub fn build<'a>(self, app: impl FnMut(&egui::Context) + 'a) -> Harness<'a> { - Harness::from_builder(&self, app) + Harness::from_builder(&self, AppKind::Context(Box::new(app))) + } + + pub fn build_ui<'a>(self, app: impl FnMut(&mut egui::Ui) + 'a) -> Harness<'a> { + Harness::from_builder(&self, AppKind::Ui(Box::new(app))) } } diff --git a/crates/egui_kittest/src/harness_kind.rs b/crates/egui_kittest/src/harness_kind.rs new file mode 100644 index 00000000000..25e37f6b6c4 --- /dev/null +++ b/crates/egui_kittest/src/harness_kind.rs @@ -0,0 +1,72 @@ +use egui::Frame; + +type AppKindContext<'a> = Box; +type AppKindUi<'a> = Box; + +pub enum AppKind<'a> { + Context(AppKindContext<'a>), + Ui(AppKindUi<'a>), +} + +// TODO: These aren't working unfortunately :( +// I think they should work though: https://geo-ant.github.io/blog/2021/rust-traits-and-variadic-functions/ +// pub trait IntoAppKind<'a, UiKind> { +// fn into_harness_kind(self) -> AppKind<'a>; +// } +// +// impl<'a, F> IntoAppKind<'a, &egui::Context> for F +// where +// F: FnMut(&egui::Context) + 'a, +// { +// fn into_harness_kind(self) -> AppKind<'a> { +// AppKind::Context(Box::new(self)) +// } +// } +// +// impl<'a, F> IntoAppKind<'a, &mut egui::Ui> for F +// where +// F: FnMut(&mut egui::Ui) + 'a, +// { +// fn into_harness_kind(self) -> AppKind<'a> { +// AppKind::Ui(Box::new(self)) +// } +// } + +impl<'a> AppKind<'a> { + pub fn run(&mut self, ctx: &egui::Context) -> Option { + match self { + AppKind::Context(f) => { + f(ctx); + None + } + AppKind::Ui(f) => Some(Self::run_ui(f, ctx, false)), + } + } + + pub fn run_sizing_pass(&mut self, ctx: &egui::Context) -> Option { + match self { + AppKind::Context(f) => { + f(ctx); + None + } + AppKind::Ui(f) => Some(Self::run_ui(f, ctx, true)), + } + } + + fn run_ui(f: &mut AppKindUi<'a>, ctx: &egui::Context, sizing_pass: bool) -> egui::Response { + egui::CentralPanel::default() + .frame( + Frame::central_panel(&ctx.style()) + .inner_margin(0.0) + .outer_margin(0.0), + ) + .show(ctx, |ui| { + let mut builder = egui::UiBuilder::new(); + if sizing_pass { + builder.sizing_pass = true; + } + ui.scope_builder(builder, f).response + }) + .inner + } +} diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index f4f00ad652f..7ad9b4aa880 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -11,6 +11,7 @@ mod snapshot; #[cfg(feature = "snapshot")] pub use snapshot::*; use std::fmt::{Debug, Formatter}; +pub mod harness_kind; #[cfg(feature = "wgpu")] mod texture_to_image; #[cfg(feature = "wgpu")] @@ -20,8 +21,9 @@ pub use kittest; use std::mem; use crate::event::EventState; +use crate::harness_kind::AppKind; pub use builder::*; -use egui::{Pos2, Rect, TexturesDelta, Vec2, ViewportId}; +use egui::{CentralPanel, Pos2, Rect, TexturesDelta, UiBuilder, Vec2, ViewportId}; use kittest::{Node, Queryable}; /// The test Harness. This contains everything needed to run the test. @@ -32,8 +34,9 @@ pub struct Harness<'a> { kittest: kittest::State, output: egui::FullOutput, texture_deltas: Vec, - update_fn: Box, + app: AppKind<'a>, event_state: EventState, + response: Option, } impl<'a> Debug for Harness<'a> { @@ -43,10 +46,7 @@ impl<'a> Debug for Harness<'a> { } impl<'a> Harness<'a> { - pub(crate) fn from_builder( - builder: &HarnessBuilder, - mut app: impl FnMut(&egui::Context) + 'a, - ) -> Self { + pub(crate) fn from_builder(builder: &HarnessBuilder, mut app: AppKind<'a>) -> Self { let ctx = egui::Context::default(); ctx.enable_accesskit(); let mut input = egui::RawInput { @@ -56,12 +56,16 @@ impl<'a> Harness<'a> { let viewport = input.viewports.get_mut(&ViewportId::ROOT).unwrap(); viewport.native_pixels_per_point = Some(builder.dpi); + let mut response = None; + // We need to run egui for a single frame so that the AccessKit state can be initialized // and users can immediately start querying for widgets. - let mut output = ctx.run(input.clone(), &mut app); + let mut output = ctx.run(input.clone(), |ctx| { + response = app.run(ctx); + }); let mut harness = Self { - update_fn: Box::new(app), + app, ctx, input, kittest: kittest::State::new( @@ -73,6 +77,7 @@ impl<'a> Harness<'a> { ), texture_deltas: vec![mem::take(&mut output.textures_delta)], output, + response, event_state: EventState::default(), }; // Run the harness until it is stable, ensuring that all Areas are shown and animations are done @@ -104,6 +109,10 @@ impl<'a> Harness<'a> { Self::builder().build(app) } + pub fn new_ui(app: impl FnMut(&mut egui::Ui) + 'a) -> Self { + Self::builder().build_ui(app) + } + /// Set the size of the window. /// Note: If you only want to set the size once at the beginning, /// prefer using [`HarnessBuilder::with_size`]. @@ -125,13 +134,23 @@ impl<'a> Harness<'a> { /// Run a frame. /// This will call the app closure with the current context and update the Harness. pub fn step(&mut self) { + self._step(false); + } + + fn _step(&mut self, sizing_pass: bool) { for event in self.kittest.take_events() { if let Some(event) = self.event_state.kittest_event_to_egui(event) { self.input.events.push(event); } } - let mut output = self.ctx.run(self.input.take(), self.update_fn.as_mut()); + let mut output = self.ctx.run(self.input.take(), |ctx| { + if sizing_pass { + self.response = self.app.run_sizing_pass(ctx); + } else { + self.response = self.app.run(ctx); + } + }); self.kittest.update( output .platform_output @@ -144,6 +163,14 @@ impl<'a> Harness<'a> { self.output = output; } + pub fn fit_contents(&mut self) { + self._step(true); + if let Some(response) = &self.response { + self.set_size(response.rect.size()); + } + self.run(); + } + /// Run a few frames. /// This will soon be changed to run the app until it is "stable", meaning /// - all animations are done diff --git a/crates/egui_kittest/tests/snapshots/test_shrink.png b/crates/egui_kittest/tests/snapshots/test_shrink.png new file mode 100644 index 00000000000..25dbe1172b1 --- /dev/null +++ b/crates/egui_kittest/tests/snapshots/test_shrink.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d03cf541a9e281bae54de648ddb43faffb054e6472ce56387ca720dd81ef905a +size 13640 diff --git a/crates/egui_kittest/tests/tests.rs b/crates/egui_kittest/tests/tests.rs new file mode 100644 index 00000000000..4978cbeb775 --- /dev/null +++ b/crates/egui_kittest/tests/tests.rs @@ -0,0 +1,14 @@ +use egui_kittest::Harness; + +#[test] +fn test_shrink() { + let mut harness = Harness::new_ui(|ui| { + ui.label("Hello, world!"); + ui.separator(); + ui.label("This is a test"); + }); + + harness.fit_contents(); + + harness.wgpu_snapshot("test_shrink"); +}