From d06f5b5dfcd714811ea0ae590cb410771d0706c1 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 28 Jul 2026 10:54:45 +0200 Subject: [PATCH] Handle `ViewportCommand::InnerSize` in `egui_kittest` (#8350) Useful to support resizing headless apps via `egui_inspection` --- crates/egui_kittest/src/lib.rs | 40 +++++++++++++++++++++++++----- crates/egui_kittest/tests/tests.rs | 31 +++++++++++++++++++++++ 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index 4f691c8c4..6873be579 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -175,10 +175,9 @@ impl<'a, State> Harness<'a, State> { #[cfg(feature = "snapshot")] snapshot_results: SnapshotResults::default(), }; - // Fulfill any screenshot requested during the initial frame above (which didn't go - // through `_step`). - #[cfg(any(feature = "wgpu", feature = "snapshot"))] - harness.handle_screenshots(); + // Handle any viewport commands (e.g. a screenshot or resize) requested during the initial + // frame above (which didn't go through `_step`). + harness.handle_viewport_commands(); // Run the harness until it is stable, ensuring that all Areas are shown and animations are done harness.run_ok(); @@ -273,8 +272,7 @@ impl<'a, State> Harness<'a, State> { self.renderer.handle_delta(&output.textures_delta); self.output = output; - #[cfg(any(feature = "wgpu", feature = "snapshot"))] - self.handle_screenshots(); + self.handle_viewport_commands(); } /// Calculate the rect that includes all popups and tooltips. @@ -668,6 +666,36 @@ impl<'a, State> Harness<'a, State> { self.renderer.render(&self.ctx, &output) } + /// Apply the [`egui::ViewportCommand`]s the app emitted during the last frame. + fn handle_viewport_commands(&mut self) { + self.handle_inner_size(); + + #[cfg(any(feature = "wgpu", feature = "snapshot"))] + self.handle_screenshots(); + } + + /// Resize the harness to the last [`egui::ViewportCommand::InnerSize`] requested by the app + /// during the last frame, if any. + fn handle_inner_size(&mut self) { + let new_inner_size = + self.root_viewport_output() + .commands + .iter() + .rev() + .find_map(|command| { + if let egui::ViewportCommand::InnerSize(size) = command { + Some(*size) + } else { + None + } + }); + + if let Some(size) = new_inner_size { + self.set_size(size); + self.ctx.request_repaint(); + } + } + /// Fulfill any [`egui::ViewportCommand::Screenshot`] requests made by the app during the /// last frame. /// diff --git a/crates/egui_kittest/tests/tests.rs b/crates/egui_kittest/tests/tests.rs index 37d88da9a..9bdd2b6c1 100644 --- a/crates/egui_kittest/tests/tests.rs +++ b/crates/egui_kittest/tests/tests.rs @@ -262,3 +262,34 @@ fn test_ime_composition_visuals() { harness.run(); harness.snapshot("test_ime_composition_visuals_cursor"); } + +#[test] +fn inner_size_viewport_command() { + let new_size = Vec2::new(300.0, 200.0); + + #[derive(Default)] + struct State { + requested: bool, + observed_size: Option, + } + + let mut harness = Harness::builder() + .with_size(Vec2::new(100.0, 80.0)) + .build_ui_state( + |ui, state: &mut State| { + // Request the resize once. + if !state.requested { + state.requested = true; + ui.ctx() + .send_viewport_cmd(egui::ViewportCommand::InnerSize(new_size)); + } + + state.observed_size = Some(ui.ctx().viewport_rect().size()); + }, + State::default(), + ); + + harness.run(); + + assert_eq!(harness.state().observed_size, Some(new_size)); +}