Skip to content

Commit

Permalink
Add NativeOptions::exit_on_window_close option
Browse files Browse the repository at this point in the history
`true` by default. Only implemented for glow so far.
  • Loading branch information
emilk committed Aug 3, 2022
1 parent 657ddc5 commit f45cfd7
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 38 deletions.
15 changes: 15 additions & 0 deletions eframe/src/epi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,20 @@ pub enum HardwareAcceleration {
#[cfg(not(target_arch = "wasm32"))]
#[derive(Clone)]
pub struct NativeOptions {
/// If `true`, the app will close once the egui window is closed.
/// If `false`, execution will continue.
///
/// This is `true` by default, because setting it to `false` has several downsides,
/// at least on Mac:
///
/// * Window resizing is now longer instantaneous
/// * CPU usage is higher when idle
/// * [`Frame::drag_window`] doesn't work as expected
///
/// When `true`, [`winit::event_loop::EventLoop::run`] is used.
/// When `false`, [`winit::platform::run_return::EventLoopExtRunReturn::run_return`] is used.
pub exit_on_window_close: bool,

/// Sets whether or not the window will always be on top of other windows.
pub always_on_top: bool,

Expand Down Expand Up @@ -275,6 +289,7 @@ pub struct NativeOptions {
impl Default for NativeOptions {
fn default() -> Self {
Self {
exit_on_window_close: true,
always_on_top: false,
maximized: false,
decorated: true,
Expand Down
4 changes: 2 additions & 2 deletions eframe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,14 @@ 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")]
Expand Down
4 changes: 4 additions & 0 deletions eframe/src/native/epi_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,10 @@ impl EpiIntegration {
storage.flush();
}
}

pub fn files_are_hovering(&self) -> bool {
!self.egui_winit.egui_input().hovered_files.is_empty()
}
}

#[cfg(feature = "persistence")]
Expand Down
175 changes: 139 additions & 36 deletions eframe/src/native/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@
use std::sync::Arc;

use egui_winit::winit;
use winit::event_loop::ControlFlow;
use winit::event_loop::{ControlFlow, EventLoop};

use super::epi_integration;
use crate::epi;

#[derive(Debug)]
struct RequestRepaintEvent;

#[cfg(feature = "glow")]
#[allow(unsafe_code)]
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,
Expand Down Expand Up @@ -63,6 +64,8 @@ enum EventResult {
/// Run an egui app
#[cfg(feature = "glow")]
mod glow_integration {
use std::time::{Duration, Instant};

use super::*;

struct GlowEframe {
Expand All @@ -76,7 +79,7 @@ mod glow_integration {

impl GlowEframe {
fn new(
event_loop: &winit::event_loop::EventLoop<RequestRepaintEvent>,
event_loop: &EventLoop<RequestRepaintEvent>,
app_name: &str,
native_options: &epi::NativeOptions,
app_creator: epi::AppCreator,
Expand Down Expand Up @@ -136,6 +139,13 @@ mod glow_integration {
}
}

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) -> ControlFlow {
#[cfg(feature = "puffin")]
puffin::GlobalProfiler::lock().new_frame();
Expand Down Expand Up @@ -221,15 +231,6 @@ mod glow_integration {
}

fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult {
let Self {
gl_window,
gl,
integration,
painter,
..
} = self;
let window = gl_window.window();

match event {
// Platform-dependent event handlers to workaround a winit bug
// See: https://github.com/rust-windowing/winit/issues/987
Expand All @@ -247,39 +248,39 @@ mod glow_integration {
// 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);
self.gl_window.resize(*physical_size);
}
}
winit::event::WindowEvent::ScaleFactorChanged {
new_inner_size, ..
} => {
gl_window.resize(**new_inner_size);
self.gl_window.resize(**new_inner_size);
}
winit::event::WindowEvent::CloseRequested if integration.should_quit() => {
winit::event::WindowEvent::CloseRequested
if self.integration.should_quit() =>
{
return EventResult::Exit
}
_ => {}
}

integration.on_event(self.app.as_mut(), &event);
if integration.should_quit() {
self.integration.on_event(self.app.as_mut(), &event);

if self.integration.should_quit() {
EventResult::Exit
} else {
window.request_redraw(); // TODO(emilk): ask egui if the events warrants a repaint instead
self.gl_window.window().request_redraw(); // TODO(emilk): ask egui if the event warrants a repaint
EventResult::Continue
}
}
winit::event::Event::LoopDestroyed => {
integration.save(&mut *self.app, window);
self.app.on_exit(Some(gl));
painter.destroy();
EventResult::Continue
unreachable!("Should be handled outside this function!")
}
winit::event::Event::UserEvent(RequestRepaintEvent)
| winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
..
}) => {
window.request_redraw();
self.gl_window.window().request_redraw();
EventResult::Continue
}
_ => EventResult::Continue,
Expand All @@ -291,19 +292,121 @@ mod glow_integration {
app_name: &str,
native_options: &epi::NativeOptions,
app_creator: epi::AppCreator,
) -> ! {
let event_loop = winit::event_loop::EventLoop::with_user_event();
let mut glow_eframe = GlowEframe::new(&event_loop, app_name, native_options, app_creator);
) {
let event_loop = EventLoop::with_user_event();
let glow_eframe = GlowEframe::new(&event_loop, app_name, native_options, app_creator);

if native_options.exit_on_window_close {
run_then_exit(event_loop, glow_eframe);
} else {
run_and_continue(event_loop, glow_eframe);
}
}

event_loop.run(move |event, _, control_flow| {
let event_result = glow_eframe.on_event(event);
match event_result {
EventResult::Continue => {}
EventResult::Repaint => {
*control_flow = glow_eframe.paint();
fn suggest_sleep_duration(glow_eframe: &GlowEframe) -> Duration {
if glow_eframe.is_focused || glow_eframe.integration.files_are_hovering() {
Duration::from_millis(10)
} else {
Duration::from_millis(50)
}
}

fn run_and_continue(
mut event_loop: EventLoop<RequestRepaintEvent>,
mut glow_eframe: GlowEframe,
) {
let mut running = true;
let mut needs_repaint_by = Instant::now();

while running {
use winit::platform::run_return::EventLoopExtRunReturn as _;
event_loop.run_return(|event, _, control_flow| {
*control_flow = match event {
winit::event::Event::LoopDestroyed => ControlFlow::Exit,
winit::event::Event::MainEventsCleared => {
// eprintln!("Event::MainEventsCleared");
ControlFlow::Wait
}
event => {
let event_result = glow_eframe.on_event(event);
match event_result {
EventResult::Continue => {
// eprintln!("EventResult::Continue");
ControlFlow::Wait
}
EventResult::Repaint => {
// eprintln!("EventResult::Repaint");
needs_repaint_by = Instant::now();
ControlFlow::Exit
}
EventResult::Exit => {
// eprintln!("EventResult::Exit");
running = false;
ControlFlow::Exit
}
}
}
};

match needs_repaint_by.checked_duration_since(Instant::now()) {
None => {
// eprintln!("Time to repaint, breaking event loop");
*control_flow = ControlFlow::Exit; // Time to redraw
}
Some(duration_until_repaint) => {
if *control_flow == ControlFlow::Wait {
// On Mac, ControlFlow::WaitUntil doesn't sleep enough. It uses a lot of CPU.
// So we sleep manually. But, it still uses 1-3% CPU :(
let sleep_duration =
duration_until_repaint.min(suggest_sleep_duration(&glow_eframe));
// eprintln!("Sleeping for {} ms", sleep_duration.as_millis());
std::thread::sleep(sleep_duration);

*control_flow = ControlFlow::WaitUntil(needs_repaint_by);
}
}
}
EventResult::Exit => {
*control_flow = ControlFlow::Exit;
});

// eprintln!("checking if we need to repaint…");

if running && needs_repaint_by <= Instant::now() {
// eprintln!("REPAINTING!");
let paint_result = glow_eframe.paint();
match paint_result {
ControlFlow::Poll => {
needs_repaint_by = Instant::now();
}
ControlFlow::Wait => {
// wait a long time unless something happens
needs_repaint_by = Instant::now() + Duration::from_secs(3600);
}
ControlFlow::WaitUntil(repaint_time) => {
needs_repaint_by = repaint_time;
}
ControlFlow::Exit => {
running = false;
}
}
}
}
glow_eframe.save_and_destroy();
}

fn run_then_exit(event_loop: EventLoop<RequestRepaintEvent>, mut glow_eframe: GlowEframe) -> ! {
event_loop.run(move |event, _, control_flow| {
if let winit::event::Event::LoopDestroyed = event {
glow_eframe.save_and_destroy();
} else {
let event_result = glow_eframe.on_event(event);
match event_result {
EventResult::Continue => {}
EventResult::Repaint => {
*control_flow = glow_eframe.paint();
}
EventResult::Exit => {
*control_flow = ControlFlow::Exit;
}
}
}
})
Expand All @@ -325,7 +428,7 @@ pub fn run_wgpu(
) -> ! {
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 event_loop = EventLoop::with_user_event();

let window = epi_integration::window_builder(native_options, &window_settings)
.with_title(app_name)
Expand Down Expand Up @@ -491,7 +594,7 @@ pub fn run_wgpu(
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
window.request_redraw(); // TODO(emilk): ask egui if the event warrants a repaint
}
winit::event::Event::LoopDestroyed => {
integration.save(&mut *app, window);
Expand Down

0 comments on commit f45cfd7

Please sign in to comment.