Skip to content

Commit c839cde

Browse files
committed
Add NativeOptions::exit_on_window_close option
`true` by default. Only implemented for glow so far.
1 parent 657ddc5 commit c839cde

File tree

4 files changed

+147
-38
lines changed

4 files changed

+147
-38
lines changed

eframe/src/epi.rs

+15
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,20 @@ pub enum HardwareAcceleration {
170170
#[cfg(not(target_arch = "wasm32"))]
171171
#[derive(Clone)]
172172
pub struct NativeOptions {
173+
/// If `true`, the app will close once the egui window is closed.
174+
/// If `false`, execution will continue.
175+
///
176+
/// This is `true` by default, because setting it to `false` has several downsides,
177+
/// at least on Mac:
178+
///
179+
/// * Window resizing is now longer instantaneous
180+
/// * CPU usage is higher when idle
181+
/// * [`Frame::drag_window`] doesn't work as expected
182+
///
183+
/// When `true`, [`winit::event_loop::EventLoop::run`] is used.
184+
/// When `false`, [`winit::platform::run_return::EventLoopExtRunReturn::run_return`] is used.
185+
pub exit_on_window_close: bool,
186+
173187
/// Sets whether or not the window will always be on top of other windows.
174188
pub always_on_top: bool,
175189

@@ -275,6 +289,7 @@ pub struct NativeOptions {
275289
impl Default for NativeOptions {
276290
fn default() -> Self {
277291
Self {
292+
exit_on_window_close: true,
278293
always_on_top: false,
279294
maximized: false,
280295
decorated: true,

eframe/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,14 @@ mod native;
161161
/// ```
162162
#[cfg(not(target_arch = "wasm32"))]
163163
#[allow(clippy::needless_pass_by_value)]
164-
pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) -> ! {
164+
pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) {
165165
let renderer = native_options.renderer;
166166

167167
match renderer {
168168
#[cfg(feature = "glow")]
169169
Renderer::Glow => {
170170
tracing::debug!("Using the glow renderer");
171-
native::run::run_glow(app_name, &native_options, app_creator)
171+
native::run::run_glow(app_name, &native_options, app_creator);
172172
}
173173

174174
#[cfg(feature = "wgpu")]

eframe/src/native/epi_integration.rs

+4
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,10 @@ impl EpiIntegration {
350350
storage.flush();
351351
}
352352
}
353+
354+
pub fn files_are_hovering(&self) -> bool {
355+
!self.egui_winit.egui_input().hovered_files.is_empty()
356+
}
353357
}
354358

355359
#[cfg(feature = "persistence")]

eframe/src/native/run.rs

+126-36
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@
44
use std::sync::Arc;
55

66
use egui_winit::winit;
7-
use winit::event_loop::ControlFlow;
7+
use winit::event_loop::{ControlFlow, EventLoop};
88

99
use super::epi_integration;
1010
use crate::epi;
1111

12+
#[derive(Debug)]
1213
struct RequestRepaintEvent;
1314

1415
#[cfg(feature = "glow")]
1516
#[allow(unsafe_code)]
1617
fn create_display(
1718
native_options: &NativeOptions,
1819
window_builder: winit::window::WindowBuilder,
19-
event_loop: &winit::event_loop::EventLoop<RequestRepaintEvent>,
20+
event_loop: &EventLoop<RequestRepaintEvent>,
2021
) -> (
2122
glutin::WindowedContext<glutin::PossiblyCurrent>,
2223
glow::Context,
@@ -63,6 +64,8 @@ enum EventResult {
6364
/// Run an egui app
6465
#[cfg(feature = "glow")]
6566
mod glow_integration {
67+
use std::time::{Duration, Instant};
68+
6669
use super::*;
6770

6871
struct GlowEframe {
@@ -76,7 +79,7 @@ mod glow_integration {
7679

7780
impl GlowEframe {
7881
fn new(
79-
event_loop: &winit::event_loop::EventLoop<RequestRepaintEvent>,
82+
event_loop: &EventLoop<RequestRepaintEvent>,
8083
app_name: &str,
8184
native_options: &epi::NativeOptions,
8285
app_creator: epi::AppCreator,
@@ -136,6 +139,13 @@ mod glow_integration {
136139
}
137140
}
138141

142+
fn save_and_destroy(&mut self) {
143+
self.integration
144+
.save(&mut *self.app, self.gl_window.window());
145+
self.app.on_exit(Some(&self.gl));
146+
self.painter.destroy();
147+
}
148+
139149
fn paint(&mut self) -> ControlFlow {
140150
#[cfg(feature = "puffin")]
141151
puffin::GlobalProfiler::lock().new_frame();
@@ -221,15 +231,6 @@ mod glow_integration {
221231
}
222232

223233
fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult {
224-
let Self {
225-
gl_window,
226-
gl,
227-
integration,
228-
painter,
229-
..
230-
} = self;
231-
let window = gl_window.window();
232-
233234
match event {
234235
// Platform-dependent event handlers to workaround a winit bug
235236
// See: https://github.com/rust-windowing/winit/issues/987
@@ -247,39 +248,39 @@ mod glow_integration {
247248
// See: https://github.com/rust-windowing/winit/issues/208
248249
// This solves an issue where the app would panic when minimizing on Windows.
249250
if physical_size.width > 0 && physical_size.height > 0 {
250-
gl_window.resize(*physical_size);
251+
self.gl_window.resize(*physical_size);
251252
}
252253
}
253254
winit::event::WindowEvent::ScaleFactorChanged {
254255
new_inner_size, ..
255256
} => {
256-
gl_window.resize(**new_inner_size);
257+
self.gl_window.resize(**new_inner_size);
257258
}
258-
winit::event::WindowEvent::CloseRequested if integration.should_quit() => {
259+
winit::event::WindowEvent::CloseRequested
260+
if self.integration.should_quit() =>
261+
{
259262
return EventResult::Exit
260263
}
261264
_ => {}
262265
}
263266

264-
integration.on_event(self.app.as_mut(), &event);
265-
if integration.should_quit() {
267+
self.integration.on_event(self.app.as_mut(), &event);
268+
269+
if self.integration.should_quit() {
266270
EventResult::Exit
267271
} else {
268-
window.request_redraw(); // TODO(emilk): ask egui if the events warrants a repaint instead
272+
self.gl_window.window().request_redraw(); // TODO(emilk): ask egui if the event warrants a repaint
269273
EventResult::Continue
270274
}
271275
}
272276
winit::event::Event::LoopDestroyed => {
273-
integration.save(&mut *self.app, window);
274-
self.app.on_exit(Some(gl));
275-
painter.destroy();
276-
EventResult::Continue
277+
unreachable!("Should be handled outside this function!")
277278
}
278279
winit::event::Event::UserEvent(RequestRepaintEvent)
279280
| winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
280281
..
281282
}) => {
282-
window.request_redraw();
283+
self.gl_window.window().request_redraw();
283284
EventResult::Continue
284285
}
285286
_ => EventResult::Continue,
@@ -291,19 +292,108 @@ mod glow_integration {
291292
app_name: &str,
292293
native_options: &epi::NativeOptions,
293294
app_creator: epi::AppCreator,
294-
) -> ! {
295-
let event_loop = winit::event_loop::EventLoop::with_user_event();
296-
let mut glow_eframe = GlowEframe::new(&event_loop, app_name, native_options, app_creator);
295+
) {
296+
let event_loop = EventLoop::with_user_event();
297+
let glow_eframe = GlowEframe::new(&event_loop, app_name, native_options, app_creator);
298+
299+
if native_options.exit_on_window_close {
300+
run_then_exit(event_loop, glow_eframe);
301+
} else {
302+
run_and_continue(event_loop, glow_eframe);
303+
}
304+
}
297305

298-
event_loop.run(move |event, _, control_flow| {
299-
let event_result = glow_eframe.on_event(event);
300-
match event_result {
301-
EventResult::Continue => {}
302-
EventResult::Repaint => {
303-
*control_flow = glow_eframe.paint();
306+
fn suggest_sleep_duration(glow_eframe: &GlowEframe) -> Duration {
307+
if glow_eframe.is_focused || glow_eframe.integration.files_are_hovering() {
308+
Duration::from_millis(10)
309+
} else {
310+
Duration::from_millis(50)
311+
}
312+
}
313+
314+
fn run_and_continue(
315+
mut event_loop: EventLoop<RequestRepaintEvent>,
316+
mut glow_eframe: GlowEframe,
317+
) {
318+
let mut running = true;
319+
let mut needs_repaint_by = Instant::now();
320+
321+
while running {
322+
use winit::platform::run_return::EventLoopExtRunReturn as _;
323+
event_loop.run_return(|event, _, control_flow| {
324+
*control_flow = match event {
325+
winit::event::Event::LoopDestroyed => ControlFlow::Exit,
326+
winit::event::Event::MainEventsCleared => ControlFlow::Wait,
327+
event => {
328+
let event_result = glow_eframe.on_event(event);
329+
match event_result {
330+
EventResult::Continue => ControlFlow::Wait,
331+
EventResult::Repaint => {
332+
needs_repaint_by = Instant::now();
333+
ControlFlow::Exit
334+
}
335+
EventResult::Exit => {
336+
running = false;
337+
ControlFlow::Exit
338+
}
339+
}
340+
}
341+
};
342+
343+
match needs_repaint_by.checked_duration_since(Instant::now()) {
344+
None => {
345+
*control_flow = ControlFlow::Exit; // Time to redraw
346+
}
347+
Some(duration_until_repaint) => {
348+
if *control_flow == ControlFlow::Wait {
349+
// On Mac, ControlFlow::WaitUntil doesn't sleep enough. It uses a lot of CPU.
350+
// So we sleep manually. But, it still uses 1-3% CPU :(
351+
let sleep_duration =
352+
duration_until_repaint.min(suggest_sleep_duration(&glow_eframe));
353+
std::thread::sleep(sleep_duration);
354+
355+
*control_flow = ControlFlow::WaitUntil(needs_repaint_by);
356+
}
357+
}
358+
}
359+
});
360+
361+
if running && needs_repaint_by <= Instant::now() {
362+
let paint_result = glow_eframe.paint();
363+
match paint_result {
364+
ControlFlow::Poll => {
365+
needs_repaint_by = Instant::now();
366+
}
367+
ControlFlow::Wait => {
368+
// wait a long time unless something happens
369+
needs_repaint_by = Instant::now() + Duration::from_secs(3600);
370+
}
371+
ControlFlow::WaitUntil(repaint_time) => {
372+
needs_repaint_by = repaint_time;
373+
}
374+
ControlFlow::Exit => {
375+
running = false;
376+
}
304377
}
305-
EventResult::Exit => {
306-
*control_flow = ControlFlow::Exit;
378+
}
379+
}
380+
glow_eframe.save_and_destroy();
381+
}
382+
383+
fn run_then_exit(event_loop: EventLoop<RequestRepaintEvent>, mut glow_eframe: GlowEframe) -> ! {
384+
event_loop.run(move |event, _, control_flow| {
385+
if let winit::event::Event::LoopDestroyed = event {
386+
glow_eframe.save_and_destroy();
387+
} else {
388+
let event_result = glow_eframe.on_event(event);
389+
match event_result {
390+
EventResult::Continue => {}
391+
EventResult::Repaint => {
392+
*control_flow = glow_eframe.paint();
393+
}
394+
EventResult::Exit => {
395+
*control_flow = ControlFlow::Exit;
396+
}
307397
}
308398
}
309399
})
@@ -325,7 +415,7 @@ pub fn run_wgpu(
325415
) -> ! {
326416
let storage = epi_integration::create_storage(app_name);
327417
let window_settings = epi_integration::load_window_settings(storage.as_deref());
328-
let event_loop = winit::event_loop::EventLoop::with_user_event();
418+
let event_loop = EventLoop::with_user_event();
329419

330420
let window = epi_integration::window_builder(native_options, &window_settings)
331421
.with_title(app_name)
@@ -491,7 +581,7 @@ pub fn run_wgpu(
491581
if integration.should_quit() {
492582
*control_flow = winit::event_loop::ControlFlow::Exit;
493583
}
494-
window.request_redraw(); // TODO(emilk): ask egui if the events warrants a repaint instead
584+
window.request_redraw(); // TODO(emilk): ask egui if the event warrants a repaint
495585
}
496586
winit::event::Event::LoopDestroyed => {
497587
integration.save(&mut *app, window);

0 commit comments

Comments
 (0)