From 7f6ce843b749db1bb29d108594d8e9916df35d01 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 21 Aug 2022 14:52:36 +0100 Subject: [PATCH] eframe: allow hooking into EventLoop building 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: #1951 --- crates/eframe/CHANGELOG.md | 2 +- crates/eframe/src/epi.rs | 33 +++++++++++++++++++- crates/eframe/src/lib.rs | 4 +-- crates/eframe/src/native/run.rs | 54 +++++++++++++++++++++++---------- 4 files changed, 73 insertions(+), 20 deletions(-) diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index a861d26a7bd0..b50d446620da 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -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)). diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 9fa339bdb6f6..ee42b06c7a6d 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -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)>; + /// This is how your app is created. /// /// You can use the [`CreationContext`] to setup egui, restore state, setup OpenGL things, etc. @@ -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, @@ -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, +} + +#[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"))] @@ -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, } } } diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index 42ddc9433624..088382cb5d50 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -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); } } } diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 9331a8c18777..9e659f5436f2 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -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)] @@ -72,16 +72,38 @@ trait WinitApp { fn on_event(&mut self, event: winit::event::Event<'_, RequestRepaintEvent>) -> EventResult; } +#[allow(unused)] +fn create_event_loop_builder( + native_options: &mut epi::NativeOptions, +) -> EventLoopBuilder { + 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)) { +fn with_event_loop( + mut native_options: epi::NativeOptions, + f: impl FnOnce(&mut EventLoop, NativeOptions), +) { use std::cell::RefCell; - thread_local!(static EVENT_LOOP: RefCell> = RefCell::new(winit::event_loop::EventLoopBuilder::with_user_event().build())); + thread_local!(static EVENT_LOOP: RefCell>> = 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); }); } @@ -243,15 +265,15 @@ mod glow_integration { fn new( event_loop: &EventLoop, 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, "") @@ -449,17 +471,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); } @@ -487,13 +509,13 @@ mod wgpu_integration { fn new( event_loop: &EventLoop, 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(); @@ -712,17 +734,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); }