Skip to content

Commit

Permalink
eframe: allow hooking into EventLoop building
Browse files Browse the repository at this point in the history
This enables native applications to add an `event_loop_builder` callback
to the `NativeOptions` struct that lets them modify the Winit
`EventLoopBuilder` before the final `EventLoop` is built and run.

This makes it practical for applications to change platform
specific config options that Egui doesn't need to be directly aware of.

For example the `android-activity` glue crate that supports writing
Android applications in Rust requires that the Winit event loop be
passed a reference to the `AndroidApp` that is given to the
`android_main` entrypoint for the application.

Since the `AndroidApp` itself is abstracted by Winit then there's no
real need for Egui/EFrame to have a dependency on the `android-activity`
crate just for the sake of associating this state with the event loop.

Addresses: emilk#1951
  • Loading branch information
rib committed Aug 21, 2022
1 parent 8797c02 commit 6ff1019
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 20 deletions.
2 changes: 1 addition & 1 deletion crates/eframe/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/C


## Unreleased

* Added `NativeOptions::event_loop_builder` hook for apps to change platform specific event loop options ([#1952](https://github.com/emilk/egui/pull/1952)).

## 0.19.0 - 2022-08-20
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
Expand Down
33 changes: 32 additions & 1 deletion crates/eframe/src/epi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@

#![warn(missing_docs)] // Let's keep `epi` well-documented.

#[cfg(not(target_arch = "wasm32"))]
pub use crate::native::run::RequestRepaintEvent;
#[cfg(not(target_arch = "wasm32"))]
pub use winit::event_loop::EventLoopBuilder;

/// Hook into the building of an event loop before it is run
///
/// You can configure any platform specific details required on top of the default configuration
/// done by `EFrame`.
#[cfg(not(target_arch = "wasm32"))]
pub type EventLoopBuilderHook = Box<dyn FnOnce(&mut EventLoopBuilder<RequestRepaintEvent>)>;

/// This is how your app is created.
///
/// You can use the [`CreationContext`] to setup egui, restore state, setup OpenGL things, etc.
Expand Down Expand Up @@ -177,7 +189,6 @@ pub enum HardwareAcceleration {
///
/// Only a single native window is currently supported.
#[cfg(not(target_arch = "wasm32"))]
#[derive(Clone)]
pub struct NativeOptions {
/// Sets whether or not the window will always be on top of other windows.
pub always_on_top: bool,
Expand Down Expand Up @@ -292,6 +303,25 @@ pub struct NativeOptions {
/// 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,

/// Hook into the building of an event loop before it is run.
///
/// Specify a callback here in case you need to make platform specific changes to the
/// event loop before it is run.
///
/// Note: A [`NativeOptions`] clone will not include any `event_loop_builder` hook.
pub event_loop_builder: Option<EventLoopBuilderHook>,
}

#[cfg(not(target_arch = "wasm32"))]
impl Clone for NativeOptions {
fn clone(&self) -> Self {
Self {
icon_data: self.icon_data.clone(),
event_loop_builder: None, // Skip any builder callbacks if cloning
..*self
}
}
}

#[cfg(not(target_arch = "wasm32"))]
Expand Down Expand Up @@ -319,6 +349,7 @@ impl Default for NativeOptions {
follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"),
default_theme: Theme::Dark,
run_and_return: true,
event_loop_builder: None,
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/eframe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,13 @@ pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: Ap
#[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);
}
}
}
Expand Down
53 changes: 37 additions & 16 deletions crates/eframe/src/native/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ use std::time::Duration;
use std::time::Instant;

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

use super::epi_integration::{self, EpiIntegration};
use crate::epi;

#[derive(Debug)]
struct RequestRepaintEvent;
pub struct RequestRepaintEvent;

#[cfg(feature = "glow")]
#[allow(unsafe_code)]
Expand Down Expand Up @@ -72,16 +72,37 @@ trait WinitApp {
fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult;
}

fn create_event_loop_builder(
native_options: &mut epi::NativeOptions,
) -> EventLoopBuilder<RequestRepaintEvent> {
let mut event_loop_builder = winit::event_loop::EventLoopBuilder::with_user_event();

if let Some(hook) = std::mem::take(&mut native_options.event_loop_builder) {
hook(&mut event_loop_builder);
}

event_loop_builder
}

/// Access a thread-local event loop.
///
/// We reuse the event-loop so we can support closing and opening an eframe window
/// multiple times. This is just a limitation of winit.
fn with_event_loop(f: impl FnOnce(&mut EventLoop<RequestRepaintEvent>)) {
fn with_event_loop(
mut native_options: epi::NativeOptions,
f: impl FnOnce(&mut EventLoop<RequestRepaintEvent>, NativeOptions),
) {
use std::cell::RefCell;
thread_local!(static EVENT_LOOP: RefCell<EventLoop<RequestRepaintEvent>> = RefCell::new(winit::event_loop::EventLoopBuilder::with_user_event().build()));
thread_local!(static EVENT_LOOP: RefCell<Option<EventLoop<RequestRepaintEvent>>> = RefCell::new(None));

EVENT_LOOP.with(|event_loop| {
f(&mut *event_loop.borrow_mut());
// Since we want to reference NativeOptions when creating the EventLoop we can't
// do that as part of the lazy thread local storage initialization and so we instead
// create the event loop lazily here
let mut event_loop = event_loop.borrow_mut();
let event_loop = event_loop
.get_or_insert_with(|| create_event_loop_builder(&mut native_options).build());
f(event_loop, native_options);
});
}

Expand Down Expand Up @@ -243,15 +264,15 @@ mod glow_integration {
fn new(
event_loop: &EventLoop<RequestRepaintEvent>,
app_name: &str,
native_options: &epi::NativeOptions,
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)
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_window, gl) = create_display(&native_options, window_builder, event_loop);
let gl = Arc::new(gl);

let painter = egui_glow::Painter::new(gl.clone(), None, "")
Expand Down Expand Up @@ -449,17 +470,17 @@ mod glow_integration {

pub fn run_glow(
app_name: &str,
native_options: &epi::NativeOptions,
mut native_options: epi::NativeOptions,
app_creator: epi::AppCreator,
) {
if native_options.run_and_return {
with_event_loop(|event_loop| {
with_event_loop(native_options, |event_loop, native_options| {
let glow_eframe =
GlowWinitApp::new(event_loop, app_name, native_options, app_creator);
run_and_return(event_loop, glow_eframe);
});
} else {
let event_loop = winit::event_loop::EventLoopBuilder::with_user_event().build();
let event_loop = create_event_loop_builder(&mut native_options).build();
let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator);
run_and_exit(event_loop, glow_eframe);
}
Expand Down Expand Up @@ -487,13 +508,13 @@ mod wgpu_integration {
fn new(
event_loop: &EventLoop<RequestRepaintEvent>,
app_name: &str,
native_options: &epi::NativeOptions,
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 = epi_integration::window_builder(native_options, &window_settings)
let window = epi_integration::window_builder(&native_options, &window_settings)
.with_title(app_name)
.build(event_loop)
.unwrap();
Expand Down Expand Up @@ -712,17 +733,17 @@ mod wgpu_integration {

pub fn run_wgpu(
app_name: &str,
native_options: &epi::NativeOptions,
mut native_options: epi::NativeOptions,
app_creator: epi::AppCreator,
) {
if native_options.run_and_return {
with_event_loop(|event_loop| {
with_event_loop(native_options, |event_loop, native_options| {
let wgpu_eframe =
WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
run_and_return(event_loop, wgpu_eframe);
});
} else {
let event_loop = winit::event_loop::EventLoopBuilder::with_user_event().build();
let event_loop = create_event_loop_builder(&mut native_options).build();
let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator);
run_and_exit(event_loop, wgpu_eframe);
}
Expand Down

0 comments on commit 6ff1019

Please sign in to comment.