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:
Emil Ernerfeldt 2022-08-05 08:20:31 +02:00 committed by GitHub
parent e3f993d7b4
commit 66c601f775
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 533 additions and 251 deletions

View file

@ -269,6 +269,20 @@ pub struct NativeOptions {
///
/// Default: `Theme::Dark`.
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"))]
@ -295,6 +309,7 @@ impl Default for NativeOptions {
renderer: Renderer::default(),
follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"),
default_theme: Theme::Dark,
run_and_return: true,
}
}
}

View file

@ -161,20 +161,20 @@ mod native;
/// ```
#[cfg(not(target_arch = "wasm32"))]
#[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;
match renderer {
#[cfg(feature = "glow")]
Renderer::Glow => {
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")]
Renderer::Wgpu => {
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);
}
}
}

View file

@ -1,7 +1,16 @@
use super::epi_integration;
use crate::epi;
use egui_winit::winit;
//! Note that this file contains two similar paths - one for [`glow`], one for [`wgpu`].
//! When making changes to one you often also want to apply it to the other.
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;
#[cfg(feature = "glow")]
@ -9,7 +18,7 @@ struct RequestRepaintEvent;
fn create_display(
native_options: &NativeOptions,
window_builder: winit::window::WindowBuilder,
event_loop: &winit::event_loop::EventLoop<RequestRepaintEvent>,
event_loop: &EventLoop<RequestRepaintEvent>,
) -> (
glutin::WindowedContext<glutin::PossiblyCurrent>,
glow::Context,
@ -47,73 +56,261 @@ fn create_display(
pub use epi::NativeOptions;
/// Run an egui app
#[cfg(feature = "glow")]
pub fn run_glow(
app_name: &str,
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();
enum EventResult {
Wait,
RepaintAsap,
RepaintAt(Instant),
Exit,
}
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 = std::sync::Arc::new(gl);
trait WinitApp {
fn is_focused(&self) -> bool;
fn integration(&self) -> &EpiIntegration;
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, "")
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
fn run_and_return(mut event_loop: EventLoop<RequestRepaintEvent>, mut winit_app: impl WinitApp) {
use winit::platform::run_return::EventLoopExtRunReturn as _;
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());
tracing::debug!("event_loop.run_return");
{
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 next_repaint_time = Instant::now();
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,
event_loop.run_return(|event, _, control_flow| {
let event_result = match event {
winit::event::Event::LoopDestroyed => EventResult::Exit,
// 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 => {
*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() {
integration.warm_up(app.as_mut(), gl_window.window());
}
tracing::debug!("eframe window closed");
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| {
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")]
puffin::GlobalProfiler::lock().new_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();
egui_glow::painter::clear(
&gl,
gl,
screen_size_in_pixels,
app.clear_color(&integration.egui_ctx.style().visuals),
);
@ -146,11 +343,10 @@ pub fn run_glow(
gl_window.swap_buffers().unwrap();
}
*control_flow = if integration.should_quit() {
winit::event_loop::ControlFlow::Exit
let control_flow = if integration.should_quit() {
EventResult::Exit
} else if repaint_after.is_zero() {
window.request_redraw();
winit::event_loop::ControlFlow::Poll
EventResult::RepaintAsap
} else if let Some(repaint_after_instant) =
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*
// winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own
// egui backend impl i guess.
winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant)
EventResult::RepaintAt(repaint_after_instant)
} else {
winit::event_loop::ControlFlow::Wait
EventResult::Wait
};
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
// 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.
@ -175,140 +371,203 @@ pub fn run_glow(
crate::profile_scope!("bg_sleep");
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, .. } => {
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();
}
_ => {}
control_flow
}
});
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
/// Run an egui app
#[cfg(feature = "glow")]
pub use glow_integration::run_glow;
// ----------------------------------------------------------------------------
#[cfg(feature = "wgpu")]
pub fn run_wgpu(
app_name: &str,
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();
mod wgpu_integration {
use super::*;
let window = epi_integration::window_builder(native_options, &window_settings)
.with_title(app_name)
.build(&event_loop)
.unwrap();
// SAFETY: `window` must outlive `painter`.
#[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();
});
struct WgpuWinitApp {
window: winit::window::Window,
painter: egui_wgpu::winit::Painter<'static>,
integration: epi_integration::EpiIntegration,
app: Box<dyn epi::App>,
is_focused: bool,
}
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),
});
impl WgpuWinitApp {
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());
if app.warm_up_enabled() {
integration.warm_up(app.as_mut(), &window);
let window = epi_integration::window_builder(native_options, &window_settings)
.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| {
let window = &window;
fn integration(&self) -> &EpiIntegration {
&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")]
puffin::GlobalProfiler::lock().new_frame();
crate::profile_scope!("frame");
let Self {
window,
app,
integration,
painter,
..
} = self;
let egui::FullOutput {
platform_output,
repaint_after,
@ -330,11 +589,10 @@ pub fn run_wgpu(
&textures_delta,
);
*control_flow = if integration.should_quit() {
winit::event_loop::ControlFlow::Exit
let control_flow = if integration.should_quit() {
EventResult::Exit
} else if repaint_after.is_zero() {
window.request_redraw();
winit::event_loop::ControlFlow::Poll
EventResult::RepaintAsap
} else if let Some(repaint_after_instant) =
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*
// winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own
// egui backend impl i guess.
winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant)
EventResult::RepaintAt(repaint_after_instant)
} else {
winit::event_loop::ControlFlow::Wait
EventResult::Wait
};
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
// 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.
@ -359,70 +617,79 @@ pub fn run_wgpu(
crate::profile_scope!("bg_sleep");
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(),
#[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();
}
_ => (),
control_flow
}
});
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;