diff --git a/Cargo.lock b/Cargo.lock index 6d1d01b1d8e..c2d246045aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1423,6 +1423,16 @@ dependencies = [ "wgpu", ] +[[package]] +name = "egui_tests" +version = "0.31.1" +dependencies = [ + "egui", + "egui_extras", + "egui_kittest", + "image", +] + [[package]] name = "ehttp" version = "0.5.0" diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index 59bd5c056ac..38521d1016a 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -274,6 +274,7 @@ impl<'a, State> Harness<'a, State> { /// /// See also: /// - [`Harness::try_run`]. + /// - [`Harness::try_run_realtime`]. /// - [`Harness::run_ok`]. /// - [`Harness::step`]. /// - [`Harness::run_steps`]. @@ -287,22 +288,7 @@ impl<'a, State> Harness<'a, State> { } } - /// Run until - /// - all animations are done - /// - no more repaints are requested - /// - the maximum number of steps is reached (See [`HarnessBuilder::with_max_steps`]) - /// - /// Returns the number of steps that were run. - /// - /// # Errors - /// Returns an error if the maximum number of steps is exceeded. - /// - /// See also: - /// - [`Harness::run`]. - /// - [`Harness::run_ok`]. - /// - [`Harness::step`]. - /// - [`Harness::run_steps`]. - pub fn try_run(&mut self) -> Result { + fn _try_run(&mut self, sleep: bool) -> Result { let mut steps = 0; loop { steps += 1; @@ -310,6 +296,8 @@ impl<'a, State> Harness<'a, State> { // We only care about immediate repaints if self.root_viewport_output().repaint_delay != Duration::ZERO { break; + } else if sleep { + std::thread::sleep(Duration::from_secs_f32(self.step_dt)); } if steps > self.max_steps { return Err(ExceededMaxStepsError { @@ -321,6 +309,26 @@ impl<'a, State> Harness<'a, State> { Ok(steps) } + /// Run until + /// - all animations are done + /// - no more repaints are requested + /// - the maximum number of steps is reached (See [`HarnessBuilder::with_max_steps`]) + /// + /// Returns the number of steps that were run. + /// + /// # Errors + /// Returns an error if the maximum number of steps is exceeded. + /// + /// See also: + /// - [`Harness::run`]. + /// - [`Harness::run_ok`]. + /// - [`Harness::step`]. + /// - [`Harness::run_steps`]. + /// - [`Harness::try_run_realtime`]. + pub fn try_run(&mut self) -> Result { + self._try_run(false) + } + /// Run until /// - all animations are done /// - no more repaints are requested @@ -333,10 +341,34 @@ impl<'a, State> Harness<'a, State> { /// - [`Harness::try_run`]. /// - [`Harness::step`]. /// - [`Harness::run_steps`]. + /// - [`Harness::try_run_realtime`]. pub fn run_ok(&mut self) -> Option { self.try_run().ok() } + /// Run multiple frames, sleeping for [`HarnessBuilder::with_step_dt`] between frames. + /// + /// This is useful to e.g. wait for an async operation to complete (e.g. loading of images). + /// Runs until + /// - all animations are done + /// - no more repaints are requested + /// - the maximum number of steps is reached (See [`HarnessBuilder::with_max_steps`]) + /// + /// Returns the number of steps that were run. + /// + /// # Errors + /// Returns an error if the maximum number of steps is exceeded. + /// + /// See also: + /// - [`Harness::run`]. + /// - [`Harness::run_ok`]. + /// - [`Harness::step`]. + /// - [`Harness::run_steps`]. + /// - [`Harness::try_run`]. + pub fn try_run_realtime(&mut self) -> Result { + self._try_run(true) + } + /// Run a number of steps. /// Equivalent to calling [`Harness::step`] x times. pub fn run_steps(&mut self, steps: usize) { diff --git a/tests/egui_tests/Cargo.toml b/tests/egui_tests/Cargo.toml new file mode 100644 index 00000000000..ee2531831c1 --- /dev/null +++ b/tests/egui_tests/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "egui_tests" +edition.workspace = true +license.workspace = true +rust-version.workspace = true +version.workspace = true + +[dev-dependencies] +egui = { workspace = true, default-features = true } +egui_kittest = { workspace = true, features = ["snapshot", "wgpu"] } +egui_extras = { workspace = true, features = ["image"]} +image = { workspace = true, features = ["png"] } + +[lints] +workspace = true diff --git a/tests/egui_tests/tests/snapshots/layout/button.png b/tests/egui_tests/tests/snapshots/layout/button.png new file mode 100644 index 00000000000..7232c72c834 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ccd7bdd86e587bcf0577c92e10ed7c3c35195e37df109a84554ceb30a434768d +size 315482 diff --git a/tests/egui_tests/tests/snapshots/layout/button_image.png b/tests/egui_tests/tests/snapshots/layout/button_image.png new file mode 100644 index 00000000000..737f0670cd7 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/button_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:975c279d6da2a2cb000df72bf5d9f3bdd200bb20adc00e29e8fd9ed4d2c6f6b1 +size 340923 diff --git a/tests/egui_tests/tests/snapshots/layout/button_image_shortcut.png b/tests/egui_tests/tests/snapshots/layout/button_image_shortcut.png new file mode 100644 index 00000000000..7dbda11d974 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/button_image_shortcut.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad14068e60fa678ee749925dd3713ee2b12a83ec1bca9c413bdeb9bc27d8ac20 +size 407795 diff --git a/tests/egui_tests/tests/snapshots/layout/checkbox.png b/tests/egui_tests/tests/snapshots/layout/checkbox.png new file mode 100644 index 00000000000..b8c014727d5 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/checkbox.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:996e02c1c10a0c76fa295160d117aceb764ef506608b151bafbdf263106dbe57 +size 385129 diff --git a/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png b/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png new file mode 100644 index 00000000000..66ae8115fc6 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/checkbox_checked.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aaf9b032037d0708894e568cc8e256b32be9cfb586eaffdc6167143b85562b37 +size 415016 diff --git a/tests/egui_tests/tests/snapshots/layout/drag_value.png b/tests/egui_tests/tests/snapshots/layout/drag_value.png new file mode 100644 index 00000000000..a9a64c55860 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/drag_value.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:043be3ece0697ea7114b7bd743e5c958610ae38ac359b6f8120886edff8541d8 +size 239522 diff --git a/tests/egui_tests/tests/snapshots/layout/radio.png b/tests/egui_tests/tests/snapshots/layout/radio.png new file mode 100644 index 00000000000..1e1a1faf37a --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/radio.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f7fbeeba8ae9e34c5400727690ac7941e2711f72f2dc23e3342cb06904e4a35 +size 335775 diff --git a/tests/egui_tests/tests/snapshots/layout/radio_checked.png b/tests/egui_tests/tests/snapshots/layout/radio_checked.png new file mode 100644 index 00000000000..323426ee9de --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/radio_checked.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96ae7be40161b0b42959b44c8f72b62fd2cd4b3b463fc7d5bcd02ead445edca1 +size 355550 diff --git a/tests/egui_tests/tests/snapshots/layout/selectable_value.png b/tests/egui_tests/tests/snapshots/layout/selectable_value.png new file mode 100644 index 00000000000..42794b19e09 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/selectable_value.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4991fdf58542ca14162cbd7f59b6a30d6c3d752a1215cc1890359bc3a1eb23c9 +size 388912 diff --git a/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png b/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png new file mode 100644 index 00000000000..554bbbf4134 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/selectable_value_selected.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ac8cbcdeed098d52009be77c8815931553d979f5aaf0baf0a9296daf6373605 +size 402699 diff --git a/tests/egui_tests/tests/snapshots/layout/slider.png b/tests/egui_tests/tests/snapshots/layout/slider.png new file mode 100644 index 00000000000..9017347f217 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/slider.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:605091767a73a934981d10d0ed59ff561772ed61e7691303b75b35ae01163ecc +size 336722 diff --git a/tests/egui_tests/tests/snapshots/layout/text_edit.png b/tests/egui_tests/tests/snapshots/layout/text_edit.png new file mode 100644 index 00000000000..fae07202c6f --- /dev/null +++ b/tests/egui_tests/tests/snapshots/layout/text_edit.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:465e34d94bf734a2a7a1e8e4a71ce64c908c737a7c4fa2a6f812351f2aaa6808 +size 233018 diff --git a/tests/egui_tests/tests/snapshots/visuals/button.png b/tests/egui_tests/tests/snapshots/visuals/button.png new file mode 100644 index 00000000000..8c8e9630c73 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99f64e581b97df6694cb7c85ee7728a955e3c1a851ab660e8b6091eee1885bbe +size 9719 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image.png b/tests/egui_tests/tests/snapshots/visuals/button_image.png new file mode 100644 index 00000000000..c71c2aeb09d --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/button_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d39ec25b91f5f5d68305d2cb7cc0285d715fe30ccbd66369efbe7327d1899b52 +size 10753 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png new file mode 100644 index 00000000000..42f8ff02a55 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46d86987ba895ead9b28efcc37e1b4374f34eedebac83d1db9eaa8e5a3202ee3 +size 13203 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png new file mode 100644 index 00000000000..3ff34c6be99 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09c5904877c8895d3ad41b7082019ef87db40c6a91ad47401bb9b8ac79a62bdc +size 12914 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox.png b/tests/egui_tests/tests/snapshots/visuals/checkbox.png new file mode 100644 index 00000000000..a0e6e18d75c --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/checkbox.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1cd5e9ad416c3a0b6824debc343f196e6db90509fd201c60c7c1f9b022f37c1d +size 12322 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png b/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png new file mode 100644 index 00000000000..40852f3c239 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e03cf99a3d28f73d4a72c0e616dc54198663b94bf5cffda694cf4eb4dee01be8 +size 13445 diff --git a/tests/egui_tests/tests/snapshots/visuals/drag_value.png b/tests/egui_tests/tests/snapshots/visuals/drag_value.png new file mode 100644 index 00000000000..dbe3c13b644 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/drag_value.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e86a37c7b259a6bad61897545d927d75e8307916dc78d256e4d33c410fcd6876 +size 7306 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio.png b/tests/egui_tests/tests/snapshots/visuals/radio.png new file mode 100644 index 00000000000..9c14f303284 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/radio.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:583fa78f79b39522a44c871642114ead9ed1d177bb8a3807d2c9e2cd89bf0b44 +size 11076 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio_checked.png b/tests/egui_tests/tests/snapshots/visuals/radio_checked.png new file mode 100644 index 00000000000..a42ad5012e7 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/radio_checked.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1a172cfadc91467529e5546e686673be73ba0071a55d55abc7a41fb1d07214d +size 11700 diff --git a/tests/egui_tests/tests/snapshots/visuals/selectable_value.png b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png new file mode 100644 index 00000000000..c2cbd733488 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eed80e11dd3ba478217cf004654934214b522ea666074e023dda9a323473615a +size 12452 diff --git a/tests/egui_tests/tests/snapshots/visuals/selectable_value_selected.png b/tests/egui_tests/tests/snapshots/visuals/selectable_value_selected.png new file mode 100644 index 00000000000..81f99551541 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/selectable_value_selected.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ef91dfedc74cae59099bce32b2e42cb04649e84442e8010282a9c1ff2a7f2c8 +size 12469 diff --git a/tests/egui_tests/tests/snapshots/visuals/slider.png b/tests/egui_tests/tests/snapshots/visuals/slider.png new file mode 100644 index 00000000000..6c83485590c --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/slider.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1892358a4552af3f529141d314cd18e4cf55a629d870798278a5470e3e0a8a94 +size 11030 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit.png b/tests/egui_tests/tests/snapshots/visuals/text_edit.png new file mode 100644 index 00000000000..5f2a64b8d46 --- /dev/null +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7300a0b88d4fdb6c1e543bfaf50e8964b2f84aaaf8197267b671d0cf3c8da30a +size 7033 diff --git a/tests/egui_tests/tests/test_widgets.rs b/tests/egui_tests/tests/test_widgets.rs new file mode 100644 index 00000000000..264b0052f65 --- /dev/null +++ b/tests/egui_tests/tests/test_widgets.rs @@ -0,0 +1,370 @@ +use egui::load::SizedTexture; +use egui::{ + include_image, Align, Button, Color32, ColorImage, Direction, DragValue, Event, Grid, Layout, + PointerButton, Pos2, Response, Slider, Stroke, StrokeKind, TextWrapMode, TextureHandle, + TextureOptions, Ui, UiBuilder, Vec2, Widget, +}; +use egui_kittest::kittest::{by, Node, Queryable}; +use egui_kittest::{Harness, SnapshotResult, SnapshotResults}; + +#[test] +fn widget_tests() { + let mut results = SnapshotResults::new(); + + test_widget("button", |ui| ui.button("Button"), &mut results); + test_widget( + "button_image", + |ui| { + Button::image_and_text( + include_image!("../../../crates/eframe/data/icon.png"), + "Button", + ) + .ui(ui) + }, + &mut results, + ); + test_widget( + "button_image_shortcut", + |ui| { + Button::image_and_text( + include_image!("../../../crates/eframe/data/icon.png"), + "Open", + ) + .shortcut_text("⌘O") + .ui(ui) + }, + &mut results, + ); + results.add(VisualTests::test("button_image_shortcut_selected", |ui| { + Button::image_and_text( + include_image!("../../../crates/eframe/data/icon.png"), + "Open", + ) + .shortcut_text("⌘O") + .selected(true) + .ui(ui) + })); + + test_widget( + "selectable_value", + |ui| ui.selectable_label(false, "Selectable"), + &mut results, + ); + test_widget( + "selectable_value_selected", + |ui| ui.selectable_label(true, "Selectable"), + &mut results, + ); + + test_widget( + "checkbox", + |ui| ui.checkbox(&mut false, "Checkbox"), + &mut results, + ); + test_widget( + "checkbox_checked", + |ui| ui.checkbox(&mut true, "Checkbox"), + &mut results, + ); + test_widget("radio", |ui| ui.radio(false, "Radio"), &mut results); + test_widget("radio_checked", |ui| ui.radio(true, "Radio"), &mut results); + + test_widget( + "drag_value", + |ui| DragValue::new(&mut 12.0).ui(ui), + &mut results, + ); + + test_widget( + "text_edit", + |ui| { + ui.spacing_mut().text_edit_width = 45.0; + ui.text_edit_singleline(&mut "Hi!".to_owned()) + }, + &mut results, + ); + + test_widget( + "slider", + |ui| { + ui.spacing_mut().slider_width = 45.0; + Slider::new(&mut 12.0, 0.0..=100.0).ui(ui) + }, + &mut results, + ); +} + +fn test_widget(name: &str, mut w: impl FnMut(&mut Ui) -> Response, results: &mut SnapshotResults) { + results.add(test_widget_layout(name, &mut w)); + results.add(VisualTests::test(name, &mut w)); +} + +fn test_widget_layout(name: &str, mut w: impl FnMut(&mut Ui) -> Response) -> SnapshotResult { + let test_size = Vec2::new(110.0, 45.0); + + struct Row { + main_dir: Direction, + main_align: Align, + main_justify: bool, + } + + struct Col { + cross_align: Align, + cross_justify: bool, + } + + let mut rows = Vec::new(); + let mut cols = Vec::new(); + + for main_justify in [false, true] { + for main_dir in [ + Direction::LeftToRight, + Direction::TopDown, + Direction::RightToLeft, + Direction::BottomUp, + ] { + for main_align in [Align::Min, Align::Center, Align::Max] { + rows.push(Row { + main_dir, + main_align, + main_justify, + }); + } + } + } + + for cross_justify in [false, true] { + for cross_align in [Align::Min, Align::Center, Align::Max] { + cols.push(Col { + cross_align, + cross_justify, + }); + } + } + + let mut harness = Harness::builder().build_ui(|ui| { + egui_extras::install_image_loaders(ui.ctx()); + + { + let mut wrap_test_size = test_size; + wrap_test_size.x /= 3.0; + ui.heading("Wrapping"); + + let modes = [ + TextWrapMode::Extend, + TextWrapMode::Truncate, + TextWrapMode::Wrap, + ]; + Grid::new("wrapping") + .spacing(Vec2::new(test_size.x / 2.0, 4.0)) + .show(ui, |ui| { + for mode in &modes { + ui.label(format!("{mode:?}")); + } + ui.end_row(); + + for mode in &modes { + let (_, rect) = ui.allocate_space(wrap_test_size); + + let mut child_ui = ui.new_child(UiBuilder::new().max_rect(rect)); + child_ui.style_mut().wrap_mode = Some(*mode); + w(&mut child_ui); + + ui.painter().rect_stroke( + rect, + 0.0, + Stroke::new(1.0, Color32::WHITE), + StrokeKind::Outside, + ); + } + }); + } + + ui.heading("Layout"); + Grid::new("layout").striped(true).show(ui, |ui| { + ui.label(""); + for col in &cols { + ui.label(format!( + "cross_align: {:?}\ncross_justify:{:?}", + col.cross_align, col.cross_justify + )); + } + ui.end_row(); + + for row in &rows { + ui.label(format!( + "main_dir: {:?}\nmain_align: {:?}\nmain_justify: {:?}", + row.main_dir, row.main_align, row.main_justify + )); + for col in &cols { + let layout = Layout { + main_dir: row.main_dir, + main_align: row.main_align, + main_justify: row.main_justify, + cross_align: col.cross_align, + cross_justify: col.cross_justify, + main_wrap: false, + }; + + let (_, rect) = ui.allocate_space(test_size); + + let mut child_ui = ui.new_child(UiBuilder::new().layout(layout).max_rect(rect)); + w(&mut child_ui); + + ui.painter().rect_stroke( + rect, + 0.0, + Stroke::new(1.0, Color32::WHITE), + StrokeKind::Outside, + ); + } + + ui.end_row(); + } + }); + }); + + harness.fit_contents(); + harness.try_snapshot(&format!("layout/{name}")) +} + +/// Utility to create a snapshot test of the different states of a egui widget. +/// This renders each state to a texture to work around the fact only a single widget can be +/// hovered / pressed / focused at a time. +struct VisualTests<'a> { + name: String, + w: &'a mut dyn FnMut(&mut Ui) -> Response, + results: Vec<(String, ColorImage)>, +} + +impl<'a> VisualTests<'a> { + pub fn test(name: &str, mut w: impl FnMut(&mut Ui) -> Response) -> SnapshotResult { + let mut vis = VisualTests::new(name, &mut w); + vis.add_default_states(); + vis.render() + } + + pub fn new(name: &str, w: &'a mut dyn FnMut(&mut Ui) -> Response) -> Self { + Self { + name: name.to_owned(), + w, + results: Vec::new(), + } + } + + fn add_default_states(&mut self) { + self.add("idle", |_| {}); + self.add_node("hover", |node| { + node.hover(); + }); + self.add("pressed", |harness| { + harness.get_next().hover(); + let rect = harness.get_next().bounding_box().unwrap(); + let pos = Pos2::new( + ((rect.x0 + rect.x1) / 2.0) as f32, + ((rect.y0 + rect.y1) / 2.0) as f32, + ); + harness.input_mut().events.push(Event::PointerButton { + button: PointerButton::Primary, + pos, + pressed: true, + modifiers: Default::default(), + }); + }); + self.add_node("focussed", |node| { + node.focus(); + }); + self.add_disabled(); + } + + fn single_test(&mut self, f: impl FnOnce(&mut Harness<'_>), enabled: bool) -> ColorImage { + let mut harness = Harness::builder().with_step_dt(0.05).build_ui(|ui| { + egui_extras::install_image_loaders(ui.ctx()); + ui.add_enabled_ui(enabled, |ui| { + (self.w)(ui); + }); + }); + + harness.fit_contents(); + + // Wait for images to load + harness.try_run_realtime().ok(); + + f(&mut harness); + + harness.step(); + + let image = harness.render().expect("Failed to render harness"); + + ColorImage::from_rgba_unmultiplied( + [image.width() as usize, image.height() as usize], + image.as_ref(), + ) + } + + pub fn add(&mut self, name: &str, test: impl FnOnce(&mut Harness<'_>)) { + let image = self.single_test(test, true); + self.results.push((name.to_owned(), image)); + } + + pub fn add_disabled(&mut self) { + let image = self.single_test(|_| {}, false); + self.results.push(("disabled".to_owned(), image)); + } + + pub fn add_node(&mut self, name: &str, test: impl FnOnce(&Node<'_>)) { + self.add(name, |harness| { + let node = harness.get_next(); + test(&node); + }); + } + + pub fn render(self) -> SnapshotResult { + let mut results = Some(self.results); + let mut images: Option> = None; + + let mut harness = Harness::new_ui(|ui| { + let results = images.get_or_insert_with(|| { + results + .take() + .unwrap() + .into_iter() + .map(|(name, image)| { + let size = Vec2::new(image.width() as f32, image.height() as f32); + let texture_handle = + ui.ctx() + .load_texture(name.clone(), image, TextureOptions::default()); + let texture = SizedTexture::new(texture_handle.id(), size); + (name.clone(), texture_handle, texture) + }) + .collect() + }); + + Grid::new("results").show(ui, |ui| { + for (name, _, image) in results { + ui.label(&*name); + + ui.scope(|ui| { + ui.image(*image); + }); + + ui.end_row(); + } + }); + }); + + harness.fit_contents(); + + harness.try_snapshot(&format!("visuals/{}", self.name)) + } +} + +trait HarnessExt { + fn get_next(&self) -> Node<'_>; +} + +impl HarnessExt for Harness<'_> { + fn get_next(&self) -> Node<'_> { + self.get_all(by()).next().unwrap() + } +}