1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-29 04:40:03 -04:00

Add Harness::spawn_eframe_app (#8120)

This lets you start up the test app from within the test itself, which
can be very useful when you have a specific test scenario set up that
you need to debug.

### Related
* Previous attempt: https://github.com/emilk/egui/pull/5418

### macOS
On macOS, you may only run UIs on the main loop, so you need a few
additional steps. Not ideal, but works!


```diff
diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml
index f9a153268..4e0cc14ee 100644
--- a/crates/egui_demo_app/Cargo.toml
+++ b/crates/egui_demo_app/Cargo.toml
@@ -84,3 +84,7 @@ web-sys.workspace = true
 
 [dev-dependencies]
 egui_kittest = { workspace = true, features = ["eframe", "snapshot", "wgpu"] }
+
+[[test]]
+name = "test_demo_app"
+harness = false
diff --git a/crates/egui_demo_app/tests/test_demo_app.rs b/crates/egui_demo_app/tests/test_demo_app.rs
index e083c8455..7ad9ed516 100644
--- a/crates/egui_demo_app/tests/test_demo_app.rs
+++ b/crates/egui_demo_app/tests/test_demo_app.rs
@@ -4,7 +4,10 @@ use egui_demo_app::{Anchor, WrapApp};
 use egui_kittest::SnapshotResults;
 use egui_kittest::kittest::Queryable as _;
 
-#[test]
+fn main() {
+    test_demo_app();
+}
+
 fn test_demo_app() {
     let mut harness = egui_kittest::Harness::builder()
         .with_size(Vec2::new(900.0, 600.0))
@@ -73,5 +76,8 @@ fn test_demo_app() {
         harness.run_steps(4);
 
         results.add(harness.try_snapshot(anchor.to_string()));
+
+        harness.spawn_eframe_app();
+        break;
     }
 }
```
This commit is contained in:
Emil Ernerfeldt
2026-04-20 14:07:45 +02:00
committed by GitHub
parent d7e55b8381
commit 56aabda7b3
10 changed files with 192 additions and 26 deletions

View File

@@ -257,8 +257,27 @@ pub mod icon_data;
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
#[allow(clippy::allow_attributes, clippy::needless_pass_by_value)]
pub fn run_native(
app_name: &str,
native_options: NativeOptions,
app_creator: AppCreator<'_>,
) -> Result {
run_native_ext(app_name, native_options, None, app_creator)
}
/// Like [`run_native`], but lets you supply a pre-existing [`egui::Context`].
///
/// If `egui_ctx` is `Some`, that context will be used by eframe instead of creating a fresh one.
/// If it is `None`, eframe creates a new context (same behavior as [`run_native`]).
///
/// # Errors
/// This function can fail if we fail to set up a graphics context.
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
#[allow(clippy::allow_attributes, clippy::needless_pass_by_value)]
pub fn run_native_ext(
app_name: &str,
mut native_options: NativeOptions,
egui_ctx: Option<egui::Context>,
app_creator: AppCreator<'_>,
) -> Result {
let renderer = init_native(app_name, &mut native_options);
@@ -267,13 +286,13 @@ pub fn run_native(
#[cfg(feature = "glow")]
Renderer::Glow => {
log::debug!("Using the glow renderer");
native::run::run_glow(app_name, native_options, app_creator)
native::run::run_glow(app_name, native_options, egui_ctx, app_creator)
}
#[cfg(feature = "wgpu_no_default_features")]
Renderer::Wgpu => {
log::debug!("Using the wgpu renderer");
native::run::run_wgpu(app_name, native_options, app_creator)
native::run::run_wgpu(app_name, native_options, egui_ctx, app_creator)
}
}
}

View File

@@ -214,15 +214,17 @@ impl EpiIntegration {
Self {
frame,
last_auto_save: Instant::now(),
egui_ctx,
pending_full_output: Default::default(),
close: false,
can_drag_window: false,
#[cfg(feature = "persistence")]
persist_window: native_options.persist_window,
app_icon_setter,
beginning: Instant::now(),
beginning: Instant::now()
.checked_sub(web_time::Duration::from_secs_f64(egui_ctx.time()))
.unwrap_or_else(Instant::now),
is_first_frame: true,
egui_ctx,
}
}

View File

@@ -55,6 +55,10 @@ pub struct GlowWinitApp<'app> {
// re-initializing the `GlowWinitRunning` state on Android if the application
// suspends and resumes.
app_creator: Option<AppCreator<'app>>,
/// An optional pre-existing egui context. If `Some`, it is used instead of
/// creating a new one via [`create_egui_context`]. Taken during initialization.
egui_ctx: Option<egui::Context>,
}
/// State that is initialized when the application is first starts running via
@@ -128,6 +132,7 @@ impl<'app> GlowWinitApp<'app> {
event_loop: &EventLoop<UserEvent>,
app_name: &str,
native_options: NativeOptions,
egui_ctx: Option<egui::Context>,
app_creator: AppCreator<'app>,
) -> Self {
profiling::function_scope!();
@@ -137,6 +142,7 @@ impl<'app> GlowWinitApp<'app> {
native_options,
running: None,
app_creator: Some(app_creator),
egui_ctx,
}
}
@@ -209,7 +215,10 @@ impl<'app> GlowWinitApp<'app> {
)
};
let egui_ctx = create_egui_context(storage.as_deref());
let egui_ctx = self
.egui_ctx
.take()
.unwrap_or_else(|| create_egui_context(storage.as_deref()));
let (mut glutin, painter) = Self::create_glutin_windowed_context(
&egui_ctx,

View File

@@ -399,6 +399,7 @@ fn run_and_exit(event_loop: EventLoop<UserEvent>, winit_app: impl WinitApp) -> R
pub fn run_glow(
app_name: &str,
mut native_options: epi::NativeOptions,
egui_ctx: Option<egui::Context>,
app_creator: epi::AppCreator<'_>,
) -> Result {
use super::glow_integration::GlowWinitApp;
@@ -406,13 +407,15 @@ pub fn run_glow(
#[cfg(not(target_os = "ios"))]
if native_options.run_and_return {
return with_event_loop(native_options, |event_loop, native_options| {
let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator);
let glow_eframe =
GlowWinitApp::new(event_loop, app_name, native_options, egui_ctx, app_creator);
run_and_return(event_loop, glow_eframe)
})?;
}
let event_loop = create_event_loop(&mut native_options)?;
let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator);
let glow_eframe =
GlowWinitApp::new(&event_loop, app_name, native_options, egui_ctx, app_creator);
run_and_exit(event_loop, glow_eframe)
}
@@ -425,7 +428,7 @@ pub fn create_glow<'a>(
) -> impl ApplicationHandler<UserEvent> + 'a {
use super::glow_integration::GlowWinitApp;
let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator);
let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, None, app_creator);
WinitAppWrapper::new(glow_eframe, true)
}
@@ -435,6 +438,7 @@ pub fn create_glow<'a>(
pub fn run_wgpu(
app_name: &str,
mut native_options: epi::NativeOptions,
egui_ctx: Option<egui::Context>,
app_creator: epi::AppCreator<'_>,
) -> Result {
use super::wgpu_integration::WgpuWinitApp;
@@ -442,13 +446,15 @@ pub fn run_wgpu(
#[cfg(not(target_os = "ios"))]
if native_options.run_and_return {
return with_event_loop(native_options, |event_loop, native_options| {
let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
let wgpu_eframe =
WgpuWinitApp::new(event_loop, app_name, native_options, egui_ctx, app_creator);
run_and_return(event_loop, wgpu_eframe)
})?;
}
let event_loop = create_event_loop(&mut native_options)?;
let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator);
let wgpu_eframe =
WgpuWinitApp::new(&event_loop, app_name, native_options, egui_ctx, app_creator);
run_and_exit(event_loop, wgpu_eframe)
}
@@ -461,7 +467,7 @@ pub fn create_wgpu<'a>(
) -> impl ApplicationHandler<UserEvent> + 'a {
use super::wgpu_integration::WgpuWinitApp;
let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, None, app_creator);
WinitAppWrapper::new(wgpu_eframe, true)
}

View File

@@ -48,6 +48,10 @@ pub struct WgpuWinitApp<'app> {
/// Set when we are actually up and running.
running: Option<WgpuWinitRunning<'app>>,
/// An optional pre-existing egui context. If `Some`, it is used instead of
/// creating a new one via [`winit_integration::create_egui_context`]. Taken during initialization.
egui_ctx: Option<egui::Context>,
}
/// State that is initialized when the application is first starts running via
@@ -105,6 +109,7 @@ impl<'app> WgpuWinitApp<'app> {
event_loop: &EventLoop<UserEvent>,
app_name: &str,
native_options: NativeOptions,
egui_ctx: Option<egui::Context>,
app_creator: AppCreator<'app>,
) -> Self {
profiling::function_scope!();
@@ -121,6 +126,7 @@ impl<'app> WgpuWinitApp<'app> {
native_options,
running: None,
app_creator: Some(app_creator),
egui_ctx,
}
}
@@ -428,7 +434,10 @@ impl WinitApp for WgpuWinitApp<'_> {
.unwrap_or(&self.app_name),
)
};
let egui_ctx = winit_integration::create_egui_context(storage.as_deref());
let egui_ctx = self
.egui_ctx
.take()
.unwrap_or_else(|| winit_integration::create_egui_context(storage.as_deref()));
let (window, builder) = create_window(
&egui_ctx,
event_loop,