Continue execution after closing native eframe window (#1889)
This adds `NativeOptions::run_and_return` with default value `true`. If `true`, execution will continue after the eframe window is closed. This is a new behavior introduced in this PR. If `false`, the app will close once the eframe window is closed. The is the old behavior. This is `true` by default, and the `false` option is only there so we can revert if we find any bugs. When `true`, [`winit::platform::run_return::EventLoopExtRunReturn::run_return`](https://docs.rs/winit/latest/winit/platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return) is used. The winit docs warns of its usage, recommending `EventLoop::run`, but 🤷 When `false`, [`winit::event_loop::EventLoop::run`](https://docs.rs/winit/latest/winit/event_loop/struct.EventLoop.html#method.run) is used. This is a really useful feature. You can now use `eframe` to quickly open up a window and show some data or options, and then continue your program after the `eframe` window is closed My previous attempt at this caused some problems, but my new attempt seems to be working much better, at least on my Mac.
This commit is contained in:
parent
e3f993d7b4
commit
66c601f775
3 changed files with 533 additions and 251 deletions
|
@ -269,6 +269,20 @@ pub struct NativeOptions {
|
||||||
///
|
///
|
||||||
/// Default: `Theme::Dark`.
|
/// Default: `Theme::Dark`.
|
||||||
pub default_theme: Theme,
|
pub default_theme: Theme,
|
||||||
|
|
||||||
|
/// This controls what happens when you close the main eframe window.
|
||||||
|
///
|
||||||
|
/// If `true`, execution will continue after the eframe window is closed.
|
||||||
|
/// If `false`, the app will close once the eframe window is closed.
|
||||||
|
///
|
||||||
|
/// This is `true` by default, and the `false` option is only there
|
||||||
|
/// so we can revert if we find any bugs.
|
||||||
|
///
|
||||||
|
/// This feature was introduced in <https://github.com/emilk/egui/pull/1889>.
|
||||||
|
///
|
||||||
|
/// When `true`, [`winit::platform::run_return::EventLoopExtRunReturn::run_return`] is used.
|
||||||
|
/// When `false`, [`winit::event_loop::EventLoop::run`] is used.
|
||||||
|
pub run_and_return: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
@ -295,6 +309,7 @@ impl Default for NativeOptions {
|
||||||
renderer: Renderer::default(),
|
renderer: Renderer::default(),
|
||||||
follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"),
|
follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"),
|
||||||
default_theme: Theme::Dark,
|
default_theme: Theme::Dark,
|
||||||
|
run_and_return: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,20 +161,20 @@ mod native;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) -> ! {
|
pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) {
|
||||||
let renderer = native_options.renderer;
|
let renderer = native_options.renderer;
|
||||||
|
|
||||||
match renderer {
|
match renderer {
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
Renderer::Glow => {
|
Renderer::Glow => {
|
||||||
tracing::debug!("Using the glow renderer");
|
tracing::debug!("Using the glow renderer");
|
||||||
native::run::run_glow(app_name, &native_options, app_creator)
|
native::run::run_glow(app_name, &native_options, app_creator);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
Renderer::Wgpu => {
|
Renderer::Wgpu => {
|
||||||
tracing::debug!("Using the wgpu renderer");
|
tracing::debug!("Using the wgpu renderer");
|
||||||
native::run::run_wgpu(app_name, &native_options, app_creator)
|
native::run::run_wgpu(app_name, &native_options, app_creator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
use super::epi_integration;
|
//! Note that this file contains two similar paths - one for [`glow`], one for [`wgpu`].
|
||||||
use crate::epi;
|
//! When making changes to one you often also want to apply it to the other.
|
||||||
use egui_winit::winit;
|
|
||||||
|
|
||||||
|
use std::time::Instant;
|
||||||
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
use egui_winit::winit;
|
||||||
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
|
|
||||||
|
use super::epi_integration::{self, EpiIntegration};
|
||||||
|
use crate::epi;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct RequestRepaintEvent;
|
struct RequestRepaintEvent;
|
||||||
|
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
|
@ -9,7 +18,7 @@ struct RequestRepaintEvent;
|
||||||
fn create_display(
|
fn create_display(
|
||||||
native_options: &NativeOptions,
|
native_options: &NativeOptions,
|
||||||
window_builder: winit::window::WindowBuilder,
|
window_builder: winit::window::WindowBuilder,
|
||||||
event_loop: &winit::event_loop::EventLoop<RequestRepaintEvent>,
|
event_loop: &EventLoop<RequestRepaintEvent>,
|
||||||
) -> (
|
) -> (
|
||||||
glutin::WindowedContext<glutin::PossiblyCurrent>,
|
glutin::WindowedContext<glutin::PossiblyCurrent>,
|
||||||
glow::Context,
|
glow::Context,
|
||||||
|
@ -47,73 +56,261 @@ fn create_display(
|
||||||
|
|
||||||
pub use epi::NativeOptions;
|
pub use epi::NativeOptions;
|
||||||
|
|
||||||
/// Run an egui app
|
enum EventResult {
|
||||||
#[cfg(feature = "glow")]
|
Wait,
|
||||||
pub fn run_glow(
|
RepaintAsap,
|
||||||
app_name: &str,
|
RepaintAt(Instant),
|
||||||
native_options: &epi::NativeOptions,
|
Exit,
|
||||||
app_creator: epi::AppCreator,
|
}
|
||||||
) -> ! {
|
|
||||||
let storage = epi_integration::create_storage(app_name);
|
|
||||||
let window_settings = epi_integration::load_window_settings(storage.as_deref());
|
|
||||||
let event_loop = winit::event_loop::EventLoop::with_user_event();
|
|
||||||
|
|
||||||
let window_builder =
|
trait WinitApp {
|
||||||
epi_integration::window_builder(native_options, &window_settings).with_title(app_name);
|
fn is_focused(&self) -> bool;
|
||||||
let (gl_window, gl) = create_display(native_options, window_builder, &event_loop);
|
fn integration(&self) -> &EpiIntegration;
|
||||||
let gl = std::sync::Arc::new(gl);
|
fn window(&self) -> &winit::window::Window;
|
||||||
|
fn save_and_destroy(&mut self);
|
||||||
|
fn paint(&mut self) -> EventResult;
|
||||||
|
fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult;
|
||||||
|
}
|
||||||
|
|
||||||
let mut painter = egui_glow::Painter::new(gl.clone(), None, "")
|
fn run_and_return(mut event_loop: EventLoop<RequestRepaintEvent>, mut winit_app: impl WinitApp) {
|
||||||
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
|
use winit::platform::run_return::EventLoopExtRunReturn as _;
|
||||||
|
|
||||||
let system_theme = native_options.system_theme();
|
tracing::debug!("event_loop.run_return");
|
||||||
let mut integration = epi_integration::EpiIntegration::new(
|
|
||||||
&event_loop,
|
|
||||||
painter.max_texture_side(),
|
|
||||||
gl_window.window(),
|
|
||||||
system_theme,
|
|
||||||
storage,
|
|
||||||
Some(gl.clone()),
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
let theme = system_theme.unwrap_or(native_options.default_theme);
|
|
||||||
integration.egui_ctx.set_visuals(theme.egui_visuals());
|
|
||||||
|
|
||||||
{
|
let mut next_repaint_time = Instant::now();
|
||||||
let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy());
|
|
||||||
integration.egui_ctx.set_request_repaint_callback(move || {
|
|
||||||
event_loop_proxy.lock().send_event(RequestRepaintEvent).ok();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut app = app_creator(&epi::CreationContext {
|
event_loop.run_return(|event, _, control_flow| {
|
||||||
egui_ctx: integration.egui_ctx.clone(),
|
let event_result = match event {
|
||||||
integration_info: integration.frame.info(),
|
winit::event::Event::LoopDestroyed => EventResult::Exit,
|
||||||
storage: integration.frame.storage(),
|
|
||||||
gl: Some(gl.clone()),
|
// Platform-dependent event handlers to workaround a winit bug
|
||||||
#[cfg(feature = "wgpu")]
|
// See: https://github.com/rust-windowing/winit/issues/987
|
||||||
render_state: None,
|
// See: https://github.com/rust-windowing/winit/issues/1619
|
||||||
|
winit::event::Event::RedrawEventsCleared if cfg!(windows) => {
|
||||||
|
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||||
|
winit_app.paint()
|
||||||
|
}
|
||||||
|
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => {
|
||||||
|
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||||
|
winit_app.paint()
|
||||||
|
}
|
||||||
|
|
||||||
|
winit::event::Event::UserEvent(RequestRepaintEvent)
|
||||||
|
| winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
||||||
|
..
|
||||||
|
}) => EventResult::RepaintAsap,
|
||||||
|
|
||||||
|
event => winit_app.on_event(event),
|
||||||
|
};
|
||||||
|
|
||||||
|
match event_result {
|
||||||
|
EventResult::Wait => {}
|
||||||
|
EventResult::RepaintAsap => {
|
||||||
|
next_repaint_time = Instant::now();
|
||||||
|
}
|
||||||
|
EventResult::RepaintAt(repaint_time) => {
|
||||||
|
next_repaint_time = next_repaint_time.min(repaint_time);
|
||||||
|
}
|
||||||
|
EventResult::Exit => {
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*control_flow = match next_repaint_time.checked_duration_since(Instant::now()) {
|
||||||
|
None => {
|
||||||
|
winit_app.window().request_redraw();
|
||||||
|
ControlFlow::Poll
|
||||||
|
}
|
||||||
|
Some(time_until_next_repaint) => {
|
||||||
|
ControlFlow::WaitUntil(Instant::now() + time_until_next_repaint)
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if app.warm_up_enabled() {
|
tracing::debug!("eframe window closed");
|
||||||
integration.warm_up(app.as_mut(), gl_window.window());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut is_focused = true;
|
winit_app.save_and_destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_and_exit(
|
||||||
|
event_loop: EventLoop<RequestRepaintEvent>,
|
||||||
|
mut winit_app: impl WinitApp + 'static,
|
||||||
|
) -> ! {
|
||||||
|
tracing::debug!("event_loop.run");
|
||||||
|
|
||||||
|
let mut next_repaint_time = Instant::now();
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
let window = gl_window.window();
|
let event_result = match event {
|
||||||
|
winit::event::Event::LoopDestroyed => EventResult::Exit,
|
||||||
|
|
||||||
let mut redraw = || {
|
// Platform-dependent event handlers to workaround a winit bug
|
||||||
|
// See: https://github.com/rust-windowing/winit/issues/987
|
||||||
|
// See: https://github.com/rust-windowing/winit/issues/1619
|
||||||
|
winit::event::Event::RedrawEventsCleared if cfg!(windows) => {
|
||||||
|
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||||
|
winit_app.paint()
|
||||||
|
}
|
||||||
|
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => {
|
||||||
|
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||||
|
winit_app.paint()
|
||||||
|
}
|
||||||
|
|
||||||
|
winit::event::Event::UserEvent(RequestRepaintEvent)
|
||||||
|
| winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
||||||
|
..
|
||||||
|
}) => EventResult::RepaintAsap,
|
||||||
|
|
||||||
|
event => winit_app.on_event(event),
|
||||||
|
};
|
||||||
|
|
||||||
|
match event_result {
|
||||||
|
EventResult::Wait => {}
|
||||||
|
EventResult::RepaintAsap => {
|
||||||
|
next_repaint_time = Instant::now();
|
||||||
|
}
|
||||||
|
EventResult::RepaintAt(repaint_time) => {
|
||||||
|
next_repaint_time = next_repaint_time.min(repaint_time);
|
||||||
|
}
|
||||||
|
EventResult::Exit => {
|
||||||
|
tracing::debug!("Quitting…");
|
||||||
|
winit_app.save_and_destroy();
|
||||||
|
#[allow(clippy::exit)]
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*control_flow = match next_repaint_time.checked_duration_since(Instant::now()) {
|
||||||
|
None => {
|
||||||
|
winit_app.window().request_redraw();
|
||||||
|
ControlFlow::Poll
|
||||||
|
}
|
||||||
|
Some(time_until_next_repaint) => {
|
||||||
|
ControlFlow::WaitUntil(Instant::now() + time_until_next_repaint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Run an egui app
|
||||||
|
#[cfg(feature = "glow")]
|
||||||
|
mod glow_integration {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct GlowWinitApp {
|
||||||
|
gl_window: glutin::WindowedContext<glutin::PossiblyCurrent>,
|
||||||
|
gl: Arc<glow::Context>,
|
||||||
|
painter: egui_glow::Painter,
|
||||||
|
integration: epi_integration::EpiIntegration,
|
||||||
|
app: Box<dyn epi::App>,
|
||||||
|
is_focused: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlowWinitApp {
|
||||||
|
fn new(
|
||||||
|
event_loop: &EventLoop<RequestRepaintEvent>,
|
||||||
|
app_name: &str,
|
||||||
|
native_options: &epi::NativeOptions,
|
||||||
|
app_creator: epi::AppCreator,
|
||||||
|
) -> Self {
|
||||||
|
let storage = epi_integration::create_storage(app_name);
|
||||||
|
let window_settings = epi_integration::load_window_settings(storage.as_deref());
|
||||||
|
|
||||||
|
let window_builder = epi_integration::window_builder(native_options, &window_settings)
|
||||||
|
.with_title(app_name);
|
||||||
|
let (gl_window, gl) = create_display(native_options, window_builder, event_loop);
|
||||||
|
let gl = Arc::new(gl);
|
||||||
|
|
||||||
|
let painter = egui_glow::Painter::new(gl.clone(), None, "")
|
||||||
|
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
|
||||||
|
|
||||||
|
let system_theme = native_options.system_theme();
|
||||||
|
let mut integration = epi_integration::EpiIntegration::new(
|
||||||
|
event_loop,
|
||||||
|
painter.max_texture_side(),
|
||||||
|
gl_window.window(),
|
||||||
|
system_theme,
|
||||||
|
storage,
|
||||||
|
Some(gl.clone()),
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let theme = system_theme.unwrap_or(native_options.default_theme);
|
||||||
|
integration.egui_ctx.set_visuals(theme.egui_visuals());
|
||||||
|
|
||||||
|
{
|
||||||
|
let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy());
|
||||||
|
integration.egui_ctx.set_request_repaint_callback(move || {
|
||||||
|
event_loop_proxy.lock().send_event(RequestRepaintEvent).ok();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut app = app_creator(&epi::CreationContext {
|
||||||
|
egui_ctx: integration.egui_ctx.clone(),
|
||||||
|
integration_info: integration.frame.info(),
|
||||||
|
storage: integration.frame.storage(),
|
||||||
|
gl: Some(gl.clone()),
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
render_state: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
if app.warm_up_enabled() {
|
||||||
|
integration.warm_up(app.as_mut(), gl_window.window());
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
gl_window,
|
||||||
|
gl,
|
||||||
|
painter,
|
||||||
|
integration,
|
||||||
|
app,
|
||||||
|
is_focused: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WinitApp for GlowWinitApp {
|
||||||
|
fn is_focused(&self) -> bool {
|
||||||
|
self.is_focused
|
||||||
|
}
|
||||||
|
|
||||||
|
fn integration(&self) -> &EpiIntegration {
|
||||||
|
&self.integration
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window(&self) -> &winit::window::Window {
|
||||||
|
self.gl_window.window()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_and_destroy(&mut self) {
|
||||||
|
self.integration
|
||||||
|
.save(&mut *self.app, self.gl_window.window());
|
||||||
|
self.app.on_exit(Some(&self.gl));
|
||||||
|
self.painter.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) -> EventResult {
|
||||||
#[cfg(feature = "puffin")]
|
#[cfg(feature = "puffin")]
|
||||||
puffin::GlobalProfiler::lock().new_frame();
|
puffin::GlobalProfiler::lock().new_frame();
|
||||||
crate::profile_scope!("frame");
|
crate::profile_scope!("frame");
|
||||||
|
|
||||||
|
let Self {
|
||||||
|
gl_window,
|
||||||
|
gl,
|
||||||
|
app,
|
||||||
|
integration,
|
||||||
|
painter,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
|
let window = gl_window.window();
|
||||||
|
|
||||||
let screen_size_in_pixels: [u32; 2] = window.inner_size().into();
|
let screen_size_in_pixels: [u32; 2] = window.inner_size().into();
|
||||||
|
|
||||||
egui_glow::painter::clear(
|
egui_glow::painter::clear(
|
||||||
&gl,
|
gl,
|
||||||
screen_size_in_pixels,
|
screen_size_in_pixels,
|
||||||
app.clear_color(&integration.egui_ctx.style().visuals),
|
app.clear_color(&integration.egui_ctx.style().visuals),
|
||||||
);
|
);
|
||||||
|
@ -146,11 +343,10 @@ pub fn run_glow(
|
||||||
gl_window.swap_buffers().unwrap();
|
gl_window.swap_buffers().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
*control_flow = if integration.should_quit() {
|
let control_flow = if integration.should_quit() {
|
||||||
winit::event_loop::ControlFlow::Exit
|
EventResult::Exit
|
||||||
} else if repaint_after.is_zero() {
|
} else if repaint_after.is_zero() {
|
||||||
window.request_redraw();
|
EventResult::RepaintAsap
|
||||||
winit::event_loop::ControlFlow::Poll
|
|
||||||
} else if let Some(repaint_after_instant) =
|
} else if let Some(repaint_after_instant) =
|
||||||
std::time::Instant::now().checked_add(repaint_after)
|
std::time::Instant::now().checked_add(repaint_after)
|
||||||
{
|
{
|
||||||
|
@ -159,14 +355,14 @@ pub fn run_glow(
|
||||||
// technically, this might lead to some weird corner cases where the user *WANTS*
|
// technically, this might lead to some weird corner cases where the user *WANTS*
|
||||||
// winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own
|
// winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own
|
||||||
// egui backend impl i guess.
|
// egui backend impl i guess.
|
||||||
winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant)
|
EventResult::RepaintAt(repaint_after_instant)
|
||||||
} else {
|
} else {
|
||||||
winit::event_loop::ControlFlow::Wait
|
EventResult::Wait
|
||||||
};
|
};
|
||||||
|
|
||||||
integration.maybe_autosave(app.as_mut(), window);
|
integration.maybe_autosave(app.as_mut(), window);
|
||||||
|
|
||||||
if !is_focused {
|
if !self.is_focused {
|
||||||
// On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325
|
// On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325
|
||||||
// We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208
|
// We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208
|
||||||
// But we know if we are focused (in foreground). When minimized, we are not focused.
|
// But we know if we are focused (in foreground). When minimized, we are not focused.
|
||||||
|
@ -175,140 +371,203 @@ pub fn run_glow(
|
||||||
crate::profile_scope!("bg_sleep");
|
crate::profile_scope!("bg_sleep");
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
};
|
|
||||||
match event {
|
|
||||||
// Platform-dependent event handlers to workaround a winit bug
|
|
||||||
// See: https://github.com/rust-windowing/winit/issues/987
|
|
||||||
// See: https://github.com/rust-windowing/winit/issues/1619
|
|
||||||
winit::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(),
|
|
||||||
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(),
|
|
||||||
|
|
||||||
winit::event::Event::WindowEvent { event, .. } => {
|
control_flow
|
||||||
match &event {
|
|
||||||
winit::event::WindowEvent::Focused(new_focused) => {
|
|
||||||
is_focused = *new_focused;
|
|
||||||
}
|
|
||||||
winit::event::WindowEvent::Resized(physical_size) => {
|
|
||||||
// Resize with 0 width and height is used by winit to signal a minimize event on Windows.
|
|
||||||
// See: https://github.com/rust-windowing/winit/issues/208
|
|
||||||
// This solves an issue where the app would panic when minimizing on Windows.
|
|
||||||
if physical_size.width > 0 && physical_size.height > 0 {
|
|
||||||
gl_window.resize(*physical_size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
|
||||||
gl_window.resize(**new_inner_size);
|
|
||||||
}
|
|
||||||
winit::event::WindowEvent::CloseRequested if integration.should_quit() => {
|
|
||||||
*control_flow = winit::event_loop::ControlFlow::Exit;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
integration.on_event(app.as_mut(), &event);
|
|
||||||
if integration.should_quit() {
|
|
||||||
*control_flow = winit::event_loop::ControlFlow::Exit;
|
|
||||||
}
|
|
||||||
window.request_redraw(); // TODO(emilk): ask egui if the events warrants a repaint instead
|
|
||||||
}
|
|
||||||
winit::event::Event::LoopDestroyed => {
|
|
||||||
integration.save(&mut *app, window);
|
|
||||||
app.on_exit(Some(&gl));
|
|
||||||
painter.destroy();
|
|
||||||
}
|
|
||||||
winit::event::Event::UserEvent(RequestRepaintEvent) => window.request_redraw(),
|
|
||||||
winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult {
|
||||||
|
match event {
|
||||||
|
winit::event::Event::WindowEvent { event, .. } => {
|
||||||
|
match &event {
|
||||||
|
winit::event::WindowEvent::Focused(new_focused) => {
|
||||||
|
self.is_focused = *new_focused;
|
||||||
|
}
|
||||||
|
winit::event::WindowEvent::Resized(physical_size) => {
|
||||||
|
// Resize with 0 width and height is used by winit to signal a minimize event on Windows.
|
||||||
|
// See: https://github.com/rust-windowing/winit/issues/208
|
||||||
|
// This solves an issue where the app would panic when minimizing on Windows.
|
||||||
|
if physical_size.width > 0 && physical_size.height > 0 {
|
||||||
|
self.gl_window.resize(*physical_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
winit::event::WindowEvent::ScaleFactorChanged {
|
||||||
|
new_inner_size, ..
|
||||||
|
} => {
|
||||||
|
self.gl_window.resize(**new_inner_size);
|
||||||
|
}
|
||||||
|
winit::event::WindowEvent::CloseRequested
|
||||||
|
if self.integration.should_quit() =>
|
||||||
|
{
|
||||||
|
return EventResult::Exit
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.integration.on_event(self.app.as_mut(), &event);
|
||||||
|
|
||||||
|
if self.integration.should_quit() {
|
||||||
|
EventResult::Exit
|
||||||
|
} else {
|
||||||
|
// TODO(emilk): ask egui if the event warrants a repaint
|
||||||
|
EventResult::RepaintAsap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => EventResult::Wait,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_glow(
|
||||||
|
app_name: &str,
|
||||||
|
native_options: &epi::NativeOptions,
|
||||||
|
app_creator: epi::AppCreator,
|
||||||
|
) {
|
||||||
|
let event_loop = EventLoop::with_user_event();
|
||||||
|
let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator);
|
||||||
|
|
||||||
|
if native_options.run_and_return {
|
||||||
|
run_and_return(event_loop, glow_eframe);
|
||||||
|
} else {
|
||||||
|
run_and_exit(event_loop, glow_eframe);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(emilk): merge with with the clone above
|
#[cfg(feature = "glow")]
|
||||||
/// Run an egui app
|
pub use glow_integration::run_glow;
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
pub fn run_wgpu(
|
mod wgpu_integration {
|
||||||
app_name: &str,
|
use super::*;
|
||||||
native_options: &epi::NativeOptions,
|
|
||||||
app_creator: epi::AppCreator,
|
|
||||||
) -> ! {
|
|
||||||
let storage = epi_integration::create_storage(app_name);
|
|
||||||
let window_settings = epi_integration::load_window_settings(storage.as_deref());
|
|
||||||
let event_loop = winit::event_loop::EventLoop::with_user_event();
|
|
||||||
|
|
||||||
let window = epi_integration::window_builder(native_options, &window_settings)
|
struct WgpuWinitApp {
|
||||||
.with_title(app_name)
|
window: winit::window::Window,
|
||||||
.build(&event_loop)
|
painter: egui_wgpu::winit::Painter<'static>,
|
||||||
.unwrap();
|
integration: epi_integration::EpiIntegration,
|
||||||
|
app: Box<dyn epi::App>,
|
||||||
// SAFETY: `window` must outlive `painter`.
|
is_focused: bool,
|
||||||
#[allow(unsafe_code)]
|
|
||||||
let mut painter = unsafe {
|
|
||||||
let mut painter = egui_wgpu::winit::Painter::new(
|
|
||||||
wgpu::Backends::PRIMARY | wgpu::Backends::GL,
|
|
||||||
wgpu::PowerPreference::HighPerformance,
|
|
||||||
wgpu::DeviceDescriptor {
|
|
||||||
label: None,
|
|
||||||
features: wgpu::Features::default(),
|
|
||||||
limits: wgpu::Limits::default(),
|
|
||||||
},
|
|
||||||
wgpu::PresentMode::Fifo,
|
|
||||||
native_options.multisampling.max(1) as _,
|
|
||||||
);
|
|
||||||
#[cfg(not(target_os = "android"))]
|
|
||||||
painter.set_window(Some(&window));
|
|
||||||
painter
|
|
||||||
};
|
|
||||||
|
|
||||||
let render_state = painter.get_render_state().expect("Uninitialized");
|
|
||||||
|
|
||||||
let system_theme = native_options.system_theme();
|
|
||||||
let mut integration = epi_integration::EpiIntegration::new(
|
|
||||||
&event_loop,
|
|
||||||
painter.max_texture_side().unwrap_or(2048),
|
|
||||||
&window,
|
|
||||||
system_theme,
|
|
||||||
storage,
|
|
||||||
#[cfg(feature = "glow")]
|
|
||||||
None,
|
|
||||||
Some(render_state.clone()),
|
|
||||||
);
|
|
||||||
let theme = system_theme.unwrap_or(native_options.default_theme);
|
|
||||||
integration.egui_ctx.set_visuals(theme.egui_visuals());
|
|
||||||
|
|
||||||
{
|
|
||||||
let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy());
|
|
||||||
integration.egui_ctx.set_request_repaint_callback(move || {
|
|
||||||
event_loop_proxy.lock().send_event(RequestRepaintEvent).ok();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut app = app_creator(&epi::CreationContext {
|
impl WgpuWinitApp {
|
||||||
egui_ctx: integration.egui_ctx.clone(),
|
fn new(
|
||||||
integration_info: integration.frame.info(),
|
event_loop: &EventLoop<RequestRepaintEvent>,
|
||||||
storage: integration.frame.storage(),
|
app_name: &str,
|
||||||
#[cfg(feature = "glow")]
|
native_options: &epi::NativeOptions,
|
||||||
gl: None,
|
app_creator: epi::AppCreator,
|
||||||
render_state: Some(render_state),
|
) -> Self {
|
||||||
});
|
let storage = epi_integration::create_storage(app_name);
|
||||||
|
let window_settings = epi_integration::load_window_settings(storage.as_deref());
|
||||||
|
|
||||||
if app.warm_up_enabled() {
|
let window = epi_integration::window_builder(native_options, &window_settings)
|
||||||
integration.warm_up(app.as_mut(), &window);
|
.with_title(app_name)
|
||||||
|
.build(event_loop)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// SAFETY: `window` must outlive `painter`.
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
let painter = unsafe {
|
||||||
|
let mut painter = egui_wgpu::winit::Painter::new(
|
||||||
|
wgpu::Backends::PRIMARY | wgpu::Backends::GL,
|
||||||
|
wgpu::PowerPreference::HighPerformance,
|
||||||
|
wgpu::DeviceDescriptor {
|
||||||
|
label: None,
|
||||||
|
features: wgpu::Features::default(),
|
||||||
|
limits: wgpu::Limits::default(),
|
||||||
|
},
|
||||||
|
wgpu::PresentMode::Fifo,
|
||||||
|
native_options.multisampling.max(1) as _,
|
||||||
|
);
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
painter.set_window(Some(&window));
|
||||||
|
painter
|
||||||
|
};
|
||||||
|
|
||||||
|
let render_state = painter.get_render_state().expect("Uninitialized");
|
||||||
|
|
||||||
|
let system_theme = native_options.system_theme();
|
||||||
|
let mut integration = epi_integration::EpiIntegration::new(
|
||||||
|
event_loop,
|
||||||
|
painter.max_texture_side().unwrap_or(2048),
|
||||||
|
&window,
|
||||||
|
system_theme,
|
||||||
|
storage,
|
||||||
|
#[cfg(feature = "glow")]
|
||||||
|
None,
|
||||||
|
Some(render_state.clone()),
|
||||||
|
);
|
||||||
|
let theme = system_theme.unwrap_or(native_options.default_theme);
|
||||||
|
integration.egui_ctx.set_visuals(theme.egui_visuals());
|
||||||
|
|
||||||
|
{
|
||||||
|
let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy());
|
||||||
|
integration.egui_ctx.set_request_repaint_callback(move || {
|
||||||
|
event_loop_proxy.lock().send_event(RequestRepaintEvent).ok();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut app = app_creator(&epi::CreationContext {
|
||||||
|
egui_ctx: integration.egui_ctx.clone(),
|
||||||
|
integration_info: integration.frame.info(),
|
||||||
|
storage: integration.frame.storage(),
|
||||||
|
#[cfg(feature = "glow")]
|
||||||
|
gl: None,
|
||||||
|
render_state: Some(render_state),
|
||||||
|
});
|
||||||
|
|
||||||
|
if app.warm_up_enabled() {
|
||||||
|
integration.warm_up(app.as_mut(), &window);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
window,
|
||||||
|
painter,
|
||||||
|
integration,
|
||||||
|
app,
|
||||||
|
is_focused: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut is_focused = true;
|
impl WinitApp for WgpuWinitApp {
|
||||||
|
fn is_focused(&self) -> bool {
|
||||||
|
self.is_focused
|
||||||
|
}
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
fn integration(&self) -> &EpiIntegration {
|
||||||
let window = &window;
|
&self.integration
|
||||||
|
}
|
||||||
|
|
||||||
let mut redraw = || {
|
fn window(&self) -> &winit::window::Window {
|
||||||
|
&self.window
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_and_destroy(&mut self) {
|
||||||
|
self.integration.save(&mut *self.app, &self.window);
|
||||||
|
|
||||||
|
#[cfg(feature = "glow")]
|
||||||
|
self.app.on_exit(None);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "glow"))]
|
||||||
|
self.app.on_exit();
|
||||||
|
|
||||||
|
self.painter.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) -> EventResult {
|
||||||
#[cfg(feature = "puffin")]
|
#[cfg(feature = "puffin")]
|
||||||
puffin::GlobalProfiler::lock().new_frame();
|
puffin::GlobalProfiler::lock().new_frame();
|
||||||
crate::profile_scope!("frame");
|
crate::profile_scope!("frame");
|
||||||
|
|
||||||
|
let Self {
|
||||||
|
window,
|
||||||
|
app,
|
||||||
|
integration,
|
||||||
|
painter,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
|
|
||||||
let egui::FullOutput {
|
let egui::FullOutput {
|
||||||
platform_output,
|
platform_output,
|
||||||
repaint_after,
|
repaint_after,
|
||||||
|
@ -330,11 +589,10 @@ pub fn run_wgpu(
|
||||||
&textures_delta,
|
&textures_delta,
|
||||||
);
|
);
|
||||||
|
|
||||||
*control_flow = if integration.should_quit() {
|
let control_flow = if integration.should_quit() {
|
||||||
winit::event_loop::ControlFlow::Exit
|
EventResult::Exit
|
||||||
} else if repaint_after.is_zero() {
|
} else if repaint_after.is_zero() {
|
||||||
window.request_redraw();
|
EventResult::RepaintAsap
|
||||||
winit::event_loop::ControlFlow::Poll
|
|
||||||
} else if let Some(repaint_after_instant) =
|
} else if let Some(repaint_after_instant) =
|
||||||
std::time::Instant::now().checked_add(repaint_after)
|
std::time::Instant::now().checked_add(repaint_after)
|
||||||
{
|
{
|
||||||
|
@ -343,14 +601,14 @@ pub fn run_wgpu(
|
||||||
// technically, this might lead to some weird corner cases where the user *WANTS*
|
// technically, this might lead to some weird corner cases where the user *WANTS*
|
||||||
// winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own
|
// winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own
|
||||||
// egui backend impl i guess.
|
// egui backend impl i guess.
|
||||||
winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant)
|
EventResult::RepaintAt(repaint_after_instant)
|
||||||
} else {
|
} else {
|
||||||
winit::event_loop::ControlFlow::Wait
|
EventResult::Wait
|
||||||
};
|
};
|
||||||
|
|
||||||
integration.maybe_autosave(app.as_mut(), window);
|
integration.maybe_autosave(app.as_mut(), window);
|
||||||
|
|
||||||
if !is_focused {
|
if !self.is_focused {
|
||||||
// On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325
|
// On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325
|
||||||
// We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208
|
// We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208
|
||||||
// But we know if we are focused (in foreground). When minimized, we are not focused.
|
// But we know if we are focused (in foreground). When minimized, we are not focused.
|
||||||
|
@ -359,70 +617,79 @@ pub fn run_wgpu(
|
||||||
crate::profile_scope!("bg_sleep");
|
crate::profile_scope!("bg_sleep");
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
match event {
|
control_flow
|
||||||
// Platform-dependent event handlers to workaround a winit bug
|
|
||||||
// See: https://github.com/rust-windowing/winit/issues/987
|
|
||||||
// See: https://github.com/rust-windowing/winit/issues/1619
|
|
||||||
winit::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(),
|
|
||||||
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(),
|
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
winit::event::Event::Resumed => unsafe {
|
|
||||||
painter.set_window(Some(&window));
|
|
||||||
},
|
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
winit::event::Event::Paused => unsafe {
|
|
||||||
painter.set_window(None);
|
|
||||||
},
|
|
||||||
|
|
||||||
winit::event::Event::WindowEvent { event, .. } => {
|
|
||||||
match &event {
|
|
||||||
winit::event::WindowEvent::Focused(new_focused) => {
|
|
||||||
is_focused = *new_focused;
|
|
||||||
}
|
|
||||||
winit::event::WindowEvent::Resized(physical_size) => {
|
|
||||||
// Resize with 0 width and height is used by winit to signal a minimize event on Windows.
|
|
||||||
// See: https://github.com/rust-windowing/winit/issues/208
|
|
||||||
// This solves an issue where the app would panic when minimizing on Windows.
|
|
||||||
if physical_size.width > 0 && physical_size.height > 0 {
|
|
||||||
painter.on_window_resized(physical_size.width, physical_size.height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
|
||||||
painter.on_window_resized(new_inner_size.width, new_inner_size.height);
|
|
||||||
}
|
|
||||||
winit::event::WindowEvent::CloseRequested if integration.should_quit() => {
|
|
||||||
*control_flow = winit::event_loop::ControlFlow::Exit;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
integration.on_event(app.as_mut(), &event);
|
|
||||||
if integration.should_quit() {
|
|
||||||
*control_flow = winit::event_loop::ControlFlow::Exit;
|
|
||||||
}
|
|
||||||
window.request_redraw(); // TODO(emilk): ask egui if the events warrants a repaint instead
|
|
||||||
}
|
|
||||||
winit::event::Event::LoopDestroyed => {
|
|
||||||
integration.save(&mut *app, window);
|
|
||||||
|
|
||||||
#[cfg(feature = "glow")]
|
|
||||||
app.on_exit(None);
|
|
||||||
|
|
||||||
#[cfg(not(feature = "glow"))]
|
|
||||||
app.on_exit();
|
|
||||||
|
|
||||||
painter.destroy();
|
|
||||||
}
|
|
||||||
winit::event::Event::UserEvent(RequestRepaintEvent) => window.request_redraw(),
|
|
||||||
winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult {
|
||||||
|
match event {
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
winit::event::Event::Resumed => unsafe {
|
||||||
|
painter.set_window(Some(&window));
|
||||||
|
},
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
winit::event::Event::Paused => unsafe {
|
||||||
|
painter.set_window(None);
|
||||||
|
},
|
||||||
|
|
||||||
|
winit::event::Event::WindowEvent { event, .. } => {
|
||||||
|
match &event {
|
||||||
|
winit::event::WindowEvent::Focused(new_focused) => {
|
||||||
|
self.is_focused = *new_focused;
|
||||||
|
}
|
||||||
|
winit::event::WindowEvent::Resized(physical_size) => {
|
||||||
|
// Resize with 0 width and height is used by winit to signal a minimize event on Windows.
|
||||||
|
// See: https://github.com/rust-windowing/winit/issues/208
|
||||||
|
// This solves an issue where the app would panic when minimizing on Windows.
|
||||||
|
if physical_size.width > 0 && physical_size.height > 0 {
|
||||||
|
self.painter
|
||||||
|
.on_window_resized(physical_size.width, physical_size.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
winit::event::WindowEvent::ScaleFactorChanged {
|
||||||
|
new_inner_size, ..
|
||||||
|
} => {
|
||||||
|
self.painter
|
||||||
|
.on_window_resized(new_inner_size.width, new_inner_size.height);
|
||||||
|
}
|
||||||
|
winit::event::WindowEvent::CloseRequested
|
||||||
|
if self.integration.should_quit() =>
|
||||||
|
{
|
||||||
|
return EventResult::Exit
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.integration.on_event(self.app.as_mut(), &event);
|
||||||
|
if self.integration.should_quit() {
|
||||||
|
EventResult::Exit
|
||||||
|
} else {
|
||||||
|
// TODO(emilk): ask egui if the event warrants a repaint
|
||||||
|
EventResult::RepaintAsap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => EventResult::Wait,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_wgpu(
|
||||||
|
app_name: &str,
|
||||||
|
native_options: &epi::NativeOptions,
|
||||||
|
app_creator: epi::AppCreator,
|
||||||
|
) {
|
||||||
|
let event_loop = EventLoop::with_user_event();
|
||||||
|
let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator);
|
||||||
|
|
||||||
|
if native_options.run_and_return {
|
||||||
|
run_and_return(event_loop, wgpu_eframe);
|
||||||
|
} else {
|
||||||
|
run_and_exit(event_loop, wgpu_eframe);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
pub use wgpu_integration::run_wgpu;
|
||||||
|
|
Loading…
Reference in a new issue