1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-29 04:40:03 -04:00

Upgrade winit to 0.30.2 (#4849)

* Closes https://github.com/emilk/egui/issues/1918
* Closes https://github.com/emilk/egui/issues/4437
* Closes https://github.com/emilk/egui/issues/4709
* [x] I have followed the instructions in the PR template

Hiya,

I need new winit for a specific fix for a android_native_actvity. There
are already two PRs, but both don't seem to have a lot of movement, or
are entirely complete:

https://github.com/emilk/egui/pull/4466
Seems to have gone stale & is missing some bits.

https://github.com/emilk/egui/pull/4702
Also seems stale (if less so), and is missing a refactor to
run_on_demand. I also *think* the accesskit integration has a mistake
and can't be enabled. I've marked them as a co-author on this as I
started from this branch. (I think! Haven't done that on git before...).

Sorry for the wall of text but just dumping some details / thoughts
here:

- There's an issue with creating child windows in winit 0.30.1 and up on
macOS. The multiple_viewports, "create immediate viewport" example
crashes on anything later 0.30.1, with a stack overflow in unsafe code.
I've create [a winit
issue](https://github.com/rust-windowing/winit/issues/3800), it *might*
already be fixed in 0.31.0 but I can't test as 0.31 will likely require
another refactoring. For now I have just pinned things to 0.30.0 exatly.

- Winit has deprecated run_on_demand, instead requiring the
ApplicationHandler interface. In 0.31.0 run_on_demand is removed. I've
refactored both the integration and the WinitApp trait to follow this
pattern. I've left user_events a bit more opaque, as it seems 0.31.0 is
doing a rework of UserEvents too.

- I've used the new lazy init approach for access kit from this branch
https://github.com/mwcampbell/egui/tree/accesskit-new-lazy-init and
marked Matt as co-author, thanks Matt!

- There was very similair but not quite the same code for run_and_return
and run_and_exit. I've merged them, but looking at the github issues
graveyard it seems vey finnicky. I *hope* this is more robust than
before but it's a bit scary.

- when receiving new_events this also used to check the redraw timing
dictionary. That doesn't seem necesarry so left this out, but that is a
slight behaviour change?

- I have reeneabled serial_windows on macOS. I wondered whether it was
fixed after this PR and does seem to be! However, even before this PR it
seems to work, so maybe winit has sorted things out before that...
Windows also works fine now without the extra hack.

- I've done a very basic test of AccessKit on Windows and screen reader
seems ok but I'm really not knowleadgable enough to say whether it's all
good or not.

- I've tested cargo tests & all examples on Windows & macOS, and ran a
basic Android app. Still, testing native platforms is wel... hard so if
anyone can test linux / iOs / older mac versions / windows 10 would
probably be a good idea!

- For consistencys sake I've made all event like functions in WinitApp
return a `Result<EventResult>`. There's quite a bit of Ok-wrapping now,
maybe too annoying? Not sure.

Thank you for having a look!

# Tested on
* [x] macOS
* [x] Windows
* [x] Wayland (thanks [SiebenCorgie](https://github.com/SiebenCorgie))
* [x] X11 (thanks
[crumblingstatue](https://github.com/crumblingstatue)!,
[SiebenCorgie](https://github.com/SiebenCorgie))


# TODO
* [x] Fix "follow system theme" not working on initial startup (winit
issue, pinning to 0.30.2 for now).
* [x] Fix `request_repaint_after`

---------

Co-authored-by: mwcampbell <mattcampbell@pobox.com>
Co-authored-by: j-axa <josef.axa@gmail.com>
Co-authored-by: DataTriny <datatriny@gmail.com>
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
Arthur Brussee
2024-07-31 07:43:16 +00:00
committed by GitHub
parent 37b1e1504d
commit 6f2f006885
25 changed files with 1313 additions and 1165 deletions

View File

@@ -59,14 +59,7 @@ android-native-activity = ["egui-winit/android-native-activity"]
default_fonts = ["egui/default_fonts"]
## Use [`glow`](https://github.com/grovesNL/glow) for painting, via [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow).
glow = [
"dep:egui_glow",
"dep:glow",
"dep:glutin-winit",
"dep:glutin",
"dep:rwh_05",
"winit/rwh_05",
]
glow = ["dep:egui_glow", "dep:glow", "dep:glutin-winit", "dep:glutin"]
## Enable saving app state to disk.
persistence = [
@@ -141,10 +134,6 @@ web-time.workspace = true
egui_glow = { workspace = true, optional = true, default-features = false }
glow = { workspace = true, optional = true }
# glutin stuck on old version of raw-window-handle:
rwh_05 = { package = "raw-window-handle", version = "0.5.2", optional = true, features = [
"std",
] }
ron = { workspace = true, optional = true, features = ["integer128"] }
serde = { workspace = true, optional = true }

View File

@@ -23,7 +23,7 @@ use static_assertions::assert_not_impl_any;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
pub use winit::{event_loop::EventLoopBuilder, window::WindowBuilder};
pub use winit::{event_loop::EventLoopBuilder, window::WindowAttributes};
/// Hook into the building of an event loop before it is run
///

View File

@@ -3,7 +3,7 @@
use web_time::Instant;
use std::path::PathBuf;
use winit::event_loop::EventLoopWindowTarget;
use winit::event_loop::ActiveEventLoop;
use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _};
@@ -12,9 +12,9 @@ use egui_winit::{EventResponse, WindowSettings};
use crate::{epi, Theme};
pub fn viewport_builder<E>(
pub fn viewport_builder(
egui_zoom_factor: f32,
event_loop: &EventLoopWindowTarget<E>,
event_loop: &ActiveEventLoop,
native_options: &mut epi::NativeOptions,
window_settings: Option<WindowSettings>,
) -> ViewportBuilder {
@@ -95,10 +95,7 @@ pub fn apply_window_settings(
}
}
fn largest_monitor_point_size<E>(
egui_zoom_factor: f32,
event_loop: &EventLoopWindowTarget<E>,
) -> egui::Vec2 {
fn largest_monitor_point_size(egui_zoom_factor: f32, event_loop: &ActiveEventLoop) -> egui::Vec2 {
crate::profile_function!();
let mut max_size = egui::Vec2::ZERO;
@@ -229,27 +226,6 @@ impl EpiIntegration {
}
}
#[cfg(feature = "accesskit")]
pub fn init_accesskit<E: From<egui_winit::accesskit_winit::ActionRequestEvent> + Send>(
&self,
egui_winit: &mut egui_winit::State,
window: &winit::window::Window,
event_loop_proxy: winit::event_loop::EventLoopProxy<E>,
) {
crate::profile_function!();
let egui_ctx = self.egui_ctx.clone();
egui_winit.init_accesskit(window, event_loop_proxy, move || {
// This function is called when an accessibility client
// (e.g. screen reader) makes its first request. If we got here,
// we know that an accessibility tree is actually wanted.
egui_ctx.enable_accesskit();
// Enqueue a repaint so we'll receive a full tree update soon.
egui_ctx.request_repaint();
egui_ctx.accesskit_placeholder_tree_update()
});
}
/// If `true`, it is time to close the native window.
pub fn should_close(&self) -> bool {
self.close

View File

@@ -0,0 +1,54 @@
use std::cell::Cell;
use winit::event_loop::ActiveEventLoop;
thread_local! {
static CURRENT_EVENT_LOOP: Cell<Option<*const ActiveEventLoop>> = Cell::new(None);
}
struct EventLoopGuard;
impl EventLoopGuard {
fn new(event_loop: &ActiveEventLoop) -> Self {
CURRENT_EVENT_LOOP.with(|cell| {
assert!(
cell.get().is_none(),
"Attempted to set a new event loop while one is already set"
);
cell.set(Some(event_loop as *const ActiveEventLoop));
});
Self
}
}
impl Drop for EventLoopGuard {
fn drop(&mut self) {
CURRENT_EVENT_LOOP.with(|cell| cell.set(None));
}
}
// Helper function to safely use the current event loop
#[allow(unsafe_code)]
pub fn with_current_event_loop<F, R>(f: F) -> Option<R>
where
F: FnOnce(&ActiveEventLoop) -> R,
{
CURRENT_EVENT_LOOP.with(|cell| {
cell.get().map(|ptr| {
// SAFETY:
// 1. The pointer is guaranteed to be valid when it's Some, as the EventLoopGuard that created it
// lives at least as long as the reference, and clears it when it's dropped. Only run_with_event_loop creates
// a new EventLoopGuard, and does not leak it.
// 2. Since the pointer was created from a borrow which lives at least as long as this pointer there are
// no mutable references to the ActiveEventLoop.
let event_loop = unsafe { &*ptr };
f(event_loop)
})
})
}
// The only public interface to use the event loop
pub fn with_event_loop_context(event_loop: &ActiveEventLoop, f: impl FnOnce()) {
// NOTE: For safety, this guard must NOT be leaked.
let _guard = EventLoopGuard::new(event_loop);
f();
}

View File

@@ -21,8 +21,9 @@ use glutin::{
prelude::{GlDisplay, PossiblyCurrentGlContext},
surface::GlSurface,
};
use raw_window_handle::HasWindowHandle;
use winit::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy},
window::{Window, WindowId},
};
@@ -145,7 +146,7 @@ impl GlowWinitApp {
#[allow(unsafe_code)]
fn create_glutin_windowed_context(
egui_ctx: &egui::Context,
event_loop: &EventLoopWindowTarget<UserEvent>,
event_loop: &ActiveEventLoop,
storage: Option<&dyn Storage>,
native_options: &mut NativeOptions,
) -> Result<(GlutinWindowContext, egui_glow::Painter)> {
@@ -194,10 +195,7 @@ impl GlowWinitApp {
Ok((glutin_window_context, painter))
}
fn init_run_state(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
) -> Result<&mut GlowWinitRunning> {
fn init_run_state(&mut self, event_loop: &ActiveEventLoop) -> Result<&mut GlowWinitRunning> {
crate::profile_function!();
let storage = if let Some(file) = &self.native_options.persistence_path {
@@ -279,7 +277,7 @@ impl GlowWinitApp {
..
} = viewport
{
integration.init_accesskit(egui_winit, window, event_loop_proxy);
egui_winit.init_accesskit(window, event_loop_proxy);
}
}
@@ -330,17 +328,9 @@ impl GlowWinitApp {
let painter = Rc::downgrade(&painter);
let beginning = integration.beginning;
let event_loop: *const EventLoopWindowTarget<UserEvent> = event_loop;
egui::Context::set_immediate_viewport_renderer(move |egui_ctx, immediate_viewport| {
if let (Some(glutin), Some(painter)) = (glutin.upgrade(), painter.upgrade()) {
// SAFETY: the event loop lives longer than
// the Rc:s we just upgraded above.
#[allow(unsafe_code)]
let event_loop = unsafe { event_loop.as_ref().unwrap() };
render_immediate_viewport(
event_loop,
egui_ctx,
&glutin,
&painter,
@@ -401,116 +391,108 @@ impl WinitApp for GlowWinitApp {
fn run_ui_and_paint(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
event_loop: &ActiveEventLoop,
window_id: WindowId,
) -> EventResult {
) -> Result<EventResult> {
if let Some(running) = &mut self.running {
running.run_ui_and_paint(event_loop, window_id)
} else {
EventResult::Wait
Ok(EventResult::Wait)
}
}
fn on_event(
fn resumed(&mut self, event_loop: &ActiveEventLoop) -> crate::Result<EventResult> {
log::debug!("Event::Resumed");
let running = if let Some(running) = &mut self.running {
// Not the first resume event. Create all outstanding windows.
running
.glutin
.borrow_mut()
.initialize_all_windows(event_loop);
running
} else {
// First resume event. Create our root window etc.
self.init_run_state(event_loop)?
};
let window_id = running.glutin.borrow().window_from_viewport[&ViewportId::ROOT];
Ok(EventResult::RepaintNow(window_id))
}
fn suspended(&mut self, _: &ActiveEventLoop) -> crate::Result<EventResult> {
if let Some(running) = &mut self.running {
running.glutin.borrow_mut().on_suspend()?;
}
Ok(EventResult::Wait)
}
fn device_event(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
event: &winit::event::Event<UserEvent>,
_: &ActiveEventLoop,
_: winit::event::DeviceId,
event: winit::event::DeviceEvent,
) -> crate::Result<EventResult> {
if let winit::event::DeviceEvent::MouseMotion { delta } = event {
if let Some(running) = &mut self.running {
let mut glutin = running.glutin.borrow_mut();
if let Some(viewport) = glutin
.focused_viewport
.and_then(|viewport| glutin.viewports.get_mut(&viewport))
{
if let Some(egui_winit) = viewport.egui_winit.as_mut() {
egui_winit.on_mouse_motion(delta);
}
if let Some(window) = viewport.window.as_ref() {
return Ok(EventResult::RepaintNext(window.id()));
}
}
}
}
Ok(EventResult::Wait)
}
fn window_event(
&mut self,
_: &ActiveEventLoop,
window_id: WindowId,
event: winit::event::WindowEvent,
) -> Result<EventResult> {
crate::profile_function!(winit_integration::short_event_description(event));
if let Some(running) = &mut self.running {
Ok(running.on_window_event(window_id, &event))
} else {
Ok(EventResult::Wait)
}
}
Ok(match event {
winit::event::Event::Resumed => {
log::debug!("Event::Resumed");
let running = if let Some(running) = &mut self.running {
// Not the first resume event. Create all outstanding windows.
running
.glutin
.borrow_mut()
.initialize_all_windows(event_loop);
running
} else {
// First resume event. Created our root window etc.
self.init_run_state(event_loop)?
};
let window_id = running.glutin.borrow().window_from_viewport[&ViewportId::ROOT];
EventResult::RepaintNow(window_id)
}
winit::event::Event::Suspended => {
if let Some(running) = &mut self.running {
running.glutin.borrow_mut().on_suspend()?;
}
EventResult::Wait
}
winit::event::Event::WindowEvent { event, window_id } => {
if let Some(running) = &mut self.running {
running.on_window_event(*window_id, event)
} else {
EventResult::Wait
}
}
winit::event::Event::DeviceEvent {
device_id: _,
event: winit::event::DeviceEvent::MouseMotion { delta },
} => {
if let Some(running) = &mut self.running {
let mut glutin = running.glutin.borrow_mut();
if let Some(viewport) = glutin
.focused_viewport
.and_then(|viewport| glutin.viewports.get_mut(&viewport))
{
if let Some(egui_winit) = viewport.egui_winit.as_mut() {
egui_winit.on_mouse_motion(*delta);
}
if let Some(window) = viewport.window.as_ref() {
EventResult::RepaintNext(window.id())
} else {
EventResult::Wait
}
} else {
EventResult::Wait
#[cfg(feature = "accesskit")]
fn on_accesskit_event(&mut self, event: accesskit_winit::Event) -> crate::Result<EventResult> {
if let Some(running) = &self.running {
let mut glutin = running.glutin.borrow_mut();
if let Some(viewport_id) = glutin.viewport_from_window.get(&event.window_id).copied() {
if let Some(viewport) = glutin.viewports.get_mut(&viewport_id) {
if let Some(egui_winit) = &mut viewport.egui_winit {
return Ok(winit_integration::on_accesskit_window_event(
egui_winit,
event.window_id,
&event.window_event,
));
}
} else {
EventResult::Wait
}
}
}
#[cfg(feature = "accesskit")]
winit::event::Event::UserEvent(UserEvent::AccessKitActionRequest(
accesskit_winit::ActionRequestEvent { request, window_id },
)) => {
if let Some(running) = &self.running {
let mut glutin = running.glutin.borrow_mut();
if let Some(viewport_id) = glutin.viewport_from_window.get(window_id).copied() {
if let Some(viewport) = glutin.viewports.get_mut(&viewport_id) {
if let Some(egui_winit) = &mut viewport.egui_winit {
crate::profile_scope!("on_accesskit_action_request");
egui_winit.on_accesskit_action_request(request.clone());
}
}
}
// As a form of user input, accessibility actions should
// lead to a repaint.
EventResult::RepaintNext(*window_id)
} else {
EventResult::Wait
}
}
_ => EventResult::Wait,
})
Ok(EventResult::Wait)
}
}
impl GlowWinitRunning {
fn run_ui_and_paint(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
event_loop: &ActiveEventLoop,
window_id: WindowId,
) -> EventResult {
) -> Result<EventResult> {
crate::profile_function!();
let Some(viewport_id) = self
@@ -520,7 +502,7 @@ impl GlowWinitRunning {
.get(&window_id)
.copied()
else {
return EventResult::Wait;
return Ok(EventResult::Wait);
};
#[cfg(feature = "puffin")]
@@ -538,10 +520,10 @@ impl GlowWinitRunning {
// That means that the viewport cannot be rendered by itself and needs his parent to be rendered.
if let Some(parent_viewport) = glutin.viewports.get(&viewport.ids.parent) {
if let Some(window) = parent_viewport.window.as_ref() {
return EventResult::RepaintNext(window.id());
return Ok(EventResult::RepaintNext(window.id()));
}
}
return EventResult::Wait;
return Ok(EventResult::Wait);
}
}
@@ -549,15 +531,15 @@ impl GlowWinitRunning {
let mut glutin = self.glutin.borrow_mut();
let egui_ctx = glutin.egui_ctx.clone();
let Some(viewport) = glutin.viewports.get_mut(&viewport_id) else {
return EventResult::Wait;
return Ok(EventResult::Wait);
};
let Some(window) = viewport.window.as_ref() else {
return EventResult::Wait;
return Ok(EventResult::Wait);
};
egui_winit::update_viewport_info(&mut viewport.info, &egui_ctx, window, false);
let Some(egui_winit) = viewport.egui_winit.as_mut() else {
return EventResult::Wait;
return Ok(EventResult::Wait);
};
let mut raw_input = egui_winit.take_egui_input(window);
let viewport_ui_cb = viewport.viewport_ui_cb.clone();
@@ -593,10 +575,10 @@ impl GlowWinitRunning {
} = &mut *glutin;
let viewport = &viewports[&viewport_id];
let Some(window) = viewport.window.as_ref() else {
return EventResult::Wait;
return Ok(EventResult::Wait);
};
let Some(gl_surface) = viewport.gl_surface.as_ref() else {
return EventResult::Wait;
return Ok(EventResult::Wait);
};
let screen_size_in_pixels: [u32; 2] = window.inner_size().into();
@@ -651,7 +633,7 @@ impl GlowWinitRunning {
} = &mut *glutin;
let Some(viewport) = viewports.get_mut(&viewport_id) else {
return EventResult::Wait;
return Ok(EventResult::Wait);
};
viewport.info.events.clear(); // they should have been processed
@@ -723,13 +705,13 @@ impl GlowWinitRunning {
// vsync - don't count as frame-time:
frame_timer.pause();
crate::profile_scope!("swap_buffers");
if let Err(err) = gl_surface.swap_buffers(
current_gl_context
.as_ref()
.expect("failed to get current context to swap buffers"),
) {
log::error!("swap_buffers failed: {err}");
}
let context = current_gl_context
.as_ref()
.ok_or(egui_glow::PainterError::from(
"failed to get current context to swap buffers".to_owned(),
))?;
gl_surface.swap_buffers(context)?;
frame_timer.resume();
}
@@ -755,9 +737,9 @@ impl GlowWinitRunning {
}
if integration.should_close() {
EventResult::Exit
Ok(EventResult::Exit)
} else {
EventResult::Wait
Ok(EventResult::Wait)
}
}
@@ -766,8 +748,6 @@ impl GlowWinitRunning {
window_id: WindowId,
event: &winit::event::WindowEvent,
) -> EventResult {
crate::profile_function!(egui_winit::short_window_event_description(event));
let mut glutin = self.glutin.borrow_mut();
let viewport_id = glutin.viewport_from_window.get(&window_id).copied();
@@ -908,7 +888,7 @@ impl GlutinWindowContext {
egui_ctx: &egui::Context,
viewport_builder: ViewportBuilder,
native_options: &NativeOptions,
event_loop: &EventLoopWindowTarget<UserEvent>,
event_loop: &ActiveEventLoop,
) -> Result<Self> {
crate::profile_function!();
@@ -957,7 +937,7 @@ impl GlutinWindowContext {
let display_builder = glutin_winit::DisplayBuilder::new()
// we might want to expose this option to users in the future. maybe using an env var or using native_options.
.with_preference(glutin_winit::ApiPreference::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
.with_window_builder(Some(egui_winit::create_winit_window_builder(
.with_window_attributes(Some(egui_winit::create_winit_window_attributes(
egui_ctx,
event_loop,
viewport_builder.clone(),
@@ -993,8 +973,9 @@ impl GlutinWindowContext {
gl_display.supported_features()
);
let glutin_raw_window_handle = window.as_ref().map(|w| {
use rwh_05::HasRawWindowHandle as _; // glutin stuck on old version of raw-window-handle
w.raw_window_handle()
w.window_handle()
.expect("Failed to get window handle")
.as_raw()
});
log::debug!("creating gl context using raw window handle: {glutin_raw_window_handle:?}");
@@ -1080,7 +1061,7 @@ impl GlutinWindowContext {
/// Create a surface, window, and winit integration for all viewports lacking any of that.
///
/// Errors will be logged.
fn initialize_all_windows(&mut self, event_loop: &EventLoopWindowTarget<UserEvent>) {
fn initialize_all_windows(&mut self, event_loop: &ActiveEventLoop) {
crate::profile_function!();
let viewports: Vec<ViewportId> = self.viewports.keys().copied().collect();
@@ -1097,7 +1078,7 @@ impl GlutinWindowContext {
pub(crate) fn initialize_window(
&mut self,
viewport_id: ViewportId,
event_loop: &EventLoopWindowTarget<UserEvent>,
event_loop: &ActiveEventLoop,
) -> Result {
crate::profile_function!();
@@ -1110,17 +1091,18 @@ impl GlutinWindowContext {
window
} else {
log::debug!("Creating a window for viewport {viewport_id:?}");
let window_builder = egui_winit::create_winit_window_builder(
let window_attributes = egui_winit::create_winit_window_attributes(
&self.egui_ctx,
event_loop,
viewport.builder.clone(),
);
if window_builder.transparent() && self.gl_config.supports_transparency() == Some(false)
if window_attributes.transparent()
&& self.gl_config.supports_transparency() == Some(false)
{
log::error!("Cannot create transparent window: the GL config does not support it");
}
let window =
glutin_winit::finalize_window(event_loop, window_builder, &self.gl_config)?;
glutin_winit::finalize_window(event_loop, window_attributes, &self.gl_config)?;
egui_winit::apply_viewport_builder_to_window(
&self.egui_ctx,
&window,
@@ -1150,9 +1132,15 @@ impl GlutinWindowContext {
let width_px = NonZeroU32::new(width_px).unwrap_or(NonZeroU32::MIN);
let height_px = NonZeroU32::new(height_px).unwrap_or(NonZeroU32::MIN);
let surface_attributes = {
use rwh_05::HasRawWindowHandle as _; // glutin stuck on old version of raw-window-handle
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
.build(window.raw_window_handle(), width_px, height_px)
.build(
window
.window_handle()
.expect("Failed to get display handle")
.as_raw(),
width_px,
height_px,
)
};
log::trace!("creating surface with attributes: {surface_attributes:?}");
@@ -1267,7 +1255,7 @@ impl GlutinWindowContext {
fn handle_viewport_output(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
event_loop: &ActiveEventLoop,
egui_ctx: &egui::Context,
viewport_output: &ViewportIdMap<ViewportOutput>,
) {
@@ -1389,7 +1377,6 @@ fn initialize_or_update_viewport(
/// This is called (via a callback) by user code to render immediate viewports,
/// i.e. viewport that are directly nested inside a parent viewport.
fn render_immediate_viewport(
event_loop: &EventLoopWindowTarget<UserEvent>,
egui_ctx: &egui::Context,
glutin: &RefCell<GlutinWindowContext>,
painter: &RefCell<egui_glow::Painter>,
@@ -1417,7 +1404,11 @@ fn render_immediate_viewport(
None,
);
if let Err(err) = glutin.initialize_window(viewport_id, event_loop) {
let ret = event_loop_context::with_current_event_loop(|event_loop| {
glutin.initialize_window(viewport_id, event_loop)
});
if let Some(Err(err)) = ret {
log::error!(
"Failed to initialize a window for immediate viewport {viewport_id:?}: {err}"
);
@@ -1523,7 +1514,9 @@ fn render_immediate_viewport(
egui_winit.handle_platform_output(window, platform_output);
glutin.handle_viewport_output(event_loop, egui_ctx, &viewport_output);
event_loop_context::with_current_event_loop(|event_loop| {
glutin.handle_viewport_output(event_loop, egui_ctx, &viewport_output);
});
}
#[cfg(feature = "__screenshot")]

View File

@@ -1,5 +1,6 @@
mod app_icon;
mod epi_integration;
mod event_loop_context;
pub mod run;
/// File storage which can be used by native backends.

View File

@@ -1,35 +1,28 @@
use std::{cell::RefCell, time::Instant};
use winit::event_loop::{EventLoop, EventLoopBuilder};
use winit::{
application::ApplicationHandler,
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
window::WindowId,
};
use ahash::HashMap;
use super::winit_integration::{UserEvent, WinitApp};
use crate::{
epi,
native::winit_integration::{short_event_description, EventResult},
native::{event_loop_context, winit_integration::EventResult},
Result,
};
use super::winit_integration::{UserEvent, WinitApp};
// ----------------------------------------------------------------------------
fn create_event_loop_builder(
native_options: &mut epi::NativeOptions,
) -> EventLoopBuilder<UserEvent> {
crate::profile_function!();
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
}
fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result<EventLoop<UserEvent>> {
crate::profile_function!();
let mut builder = create_event_loop_builder(native_options);
let mut builder = winit::event_loop::EventLoop::with_user_event();
if let Some(hook) = std::mem::take(&mut native_options.event_loop_builder) {
hook(&mut builder);
}
crate::profile_scope!("EventLoopBuilder::build");
Ok(builder.build()?)
@@ -59,306 +52,126 @@ fn with_event_loop<R>(
})
}
#[cfg(not(target_os = "ios"))]
fn run_and_return(event_loop: &mut EventLoop<UserEvent>, mut winit_app: impl WinitApp) -> Result {
use winit::{event_loop::ControlFlow, platform::run_on_demand::EventLoopExtRunOnDemand};
/// Wraps a [`WinitApp`] to implement [`ApplicationHandler`]. This handles redrawing, exit states, and
/// some events, but otherwise forwards events to the [`WinitApp`].
struct WinitAppWrapper<T: WinitApp> {
windows_next_repaint_times: HashMap<WindowId, Instant>,
winit_app: T,
return_result: Result<(), crate::Error>,
run_and_return: bool,
}
log::trace!("Entering the winit event loop (run_on_demand)…");
// When to repaint what window
let mut windows_next_repaint_times = HashMap::default();
let mut returned_result = Ok(());
event_loop.run_on_demand(|event, event_loop_window_target| {
crate::profile_scope!("winit_event", short_event_description(&event));
log::trace!("winit event: {event:?}");
if matches!(event, winit::event::Event::AboutToWait) {
return; // early-out: don't trigger another wait
impl<T: WinitApp> WinitAppWrapper<T> {
fn new(winit_app: T, run_and_return: bool) -> Self {
Self {
windows_next_repaint_times: HashMap::default(),
winit_app,
return_result: Ok(()),
run_and_return,
}
}
let event_result = match &event {
winit::event::Event::LoopExiting => {
// On Mac, Cmd-Q we get here and then `run_on_demand` doesn't return (despite its name),
// so we need to save state now:
log::debug!("Received Event::LoopExiting - saving app state…");
winit_app.save_and_destroy();
return;
}
fn handle_event_result(
&mut self,
event_loop: &ActiveEventLoop,
event_result: Result<EventResult>,
) {
let mut exit = false;
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::RedrawRequested,
window_id,
} => {
windows_next_repaint_times.remove(window_id);
winit_app.run_ui_and_paint(event_loop_window_target, *window_id)
}
log::trace!("event_result: {event_result:?}");
winit::event::Event::UserEvent(UserEvent::RequestRepaint {
when,
frame_nr,
viewport_id,
}) => {
let current_frame_nr = winit_app.frame_nr(*viewport_id);
if current_frame_nr == *frame_nr || current_frame_nr == *frame_nr + 1 {
log::trace!("UserEvent::RequestRepaint scheduling repaint at {when:?}");
if let Some(window_id) = winit_app.window_id_from_viewport_id(*viewport_id) {
EventResult::RepaintAt(window_id, *when)
let combined_result = event_result.and_then(|event_result| {
match event_result {
EventResult::Wait => {
event_loop.set_control_flow(ControlFlow::Wait);
Ok(event_result)
}
EventResult::RepaintNow(window_id) => {
log::trace!("RepaintNow of {window_id:?}",);
if cfg!(target_os = "windows") {
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
self.windows_next_repaint_times.remove(&window_id);
self.winit_app.run_ui_and_paint(event_loop, window_id)
} else {
EventResult::Wait
// Fix for https://github.com/emilk/egui/issues/2425
self.windows_next_repaint_times
.insert(window_id, Instant::now());
Ok(event_result)
}
} else {
log::trace!("Got outdated UserEvent::RequestRepaint");
EventResult::Wait // old request - we've already repainted
}
}
winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
..
}) => {
log::trace!("Woke up to check next_repaint_time");
EventResult::Wait
}
event => match winit_app.on_event(event_loop_window_target, event) {
Ok(event_result) => {
log::trace!("event_result: {event_result:?}");
event_result
EventResult::RepaintNext(window_id) => {
log::trace!("RepaintNext of {window_id:?}",);
self.windows_next_repaint_times
.insert(window_id, Instant::now());
Ok(event_result)
}
Err(err) => {
log::error!("Exiting because of error: {err} during event {event:?}");
returned_result = Err(err);
EventResult::Exit
EventResult::RepaintAt(window_id, repaint_time) => {
self.windows_next_repaint_times.insert(
window_id,
self.windows_next_repaint_times
.get(&window_id)
.map_or(repaint_time, |last| (*last).min(repaint_time)),
);
Ok(event_result)
}
},
};
match event_result {
EventResult::Wait => {
event_loop_window_target.set_control_flow(ControlFlow::Wait);
}
EventResult::RepaintNow(window_id) => {
log::trace!(
"RepaintNow of {window_id:?} caused by {}",
short_event_description(&event)
);
if cfg!(target_os = "windows") {
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
windows_next_repaint_times.remove(&window_id);
winit_app.run_ui_and_paint(event_loop_window_target, window_id);
} else {
// Fix for https://github.com/emilk/egui/issues/2425
windows_next_repaint_times.insert(window_id, Instant::now());
EventResult::Exit => {
exit = true;
Ok(event_result)
}
}
EventResult::RepaintNext(window_id) => {
log::trace!(
"RepaintNext of {window_id:?} caused by {}",
short_event_description(&event)
);
windows_next_repaint_times.insert(window_id, Instant::now());
}
EventResult::RepaintAt(window_id, repaint_time) => {
windows_next_repaint_times.insert(
window_id,
windows_next_repaint_times
.get(&window_id)
.map_or(repaint_time, |last| (*last).min(repaint_time)),
);
}
EventResult::Exit => {
log::debug!("Asking to exit event loop…");
winit_app.save_and_destroy();
event_loop_window_target.exit();
return;
}
}
let mut next_repaint_time = windows_next_repaint_times.values().min().copied();
windows_next_repaint_times.retain(|window_id, repaint_time| {
if Instant::now() < *repaint_time {
return true; // not yet ready
};
next_repaint_time = None;
event_loop_window_target.set_control_flow(ControlFlow::Poll);
if let Some(window) = winit_app.window(*window_id) {
log::trace!("request_redraw for {window_id:?}");
let is_minimized = window.is_minimized().unwrap_or(false);
if is_minimized {
false
} else {
window.request_redraw();
true
}
} else {
log::trace!("No window found for {window_id:?}");
false
}
});
if let Some(next_repaint_time) = next_repaint_time {
event_loop_window_target.set_control_flow(ControlFlow::WaitUntil(next_repaint_time));
};
})?;
log::debug!("eframe window closed");
drop(winit_app);
// On Windows this clears out events so that we can later create another window.
// See https://github.com/emilk/egui/pull/1889 for details.
//
// Note that this approach may cause issues on macOS (emilk/egui#2768); therefore,
// we only apply this approach on Windows to minimize the affect.
#[cfg(target_os = "windows")]
{
event_loop
.run_on_demand(|_, event_loop_window_target| {
event_loop_window_target.exit();
})
.ok();
}
returned_result
}
fn run_and_exit(
event_loop: EventLoop<UserEvent>,
mut winit_app: impl WinitApp + 'static,
) -> Result {
use winit::event_loop::ControlFlow;
log::trace!("Entering the winit event loop (run)…");
// When to repaint what window
let mut windows_next_repaint_times = HashMap::default();
event_loop.run(move |event, event_loop_window_target| {
crate::profile_scope!("winit_event", short_event_description(&event));
log::trace!("winit event: {event:?}");
if matches!(event, winit::event::Event::AboutToWait) {
return; // early-out: don't trigger another wait
}
let event_result = match &event {
winit::event::Event::LoopExiting => {
log::debug!("Received Event::LoopExiting");
EventResult::Exit
}
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::RedrawRequested,
window_id,
} => {
windows_next_repaint_times.remove(window_id);
winit_app.run_ui_and_paint(event_loop_window_target, *window_id)
}
winit::event::Event::UserEvent(UserEvent::RequestRepaint {
when,
frame_nr,
viewport_id,
}) => {
let current_frame_nr = winit_app.frame_nr(*viewport_id);
if current_frame_nr == *frame_nr || current_frame_nr == *frame_nr + 1 {
if let Some(window_id) = winit_app.window_id_from_viewport_id(*viewport_id) {
EventResult::RepaintAt(window_id, *when)
} else {
EventResult::Wait
}
} else {
log::trace!("Got outdated UserEvent::RequestRepaint");
EventResult::Wait // old request - we've already repainted
}
}
winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
..
}) => {
log::trace!("Woke up to check next_repaint_time");
EventResult::Wait
}
event => match winit_app.on_event(event_loop_window_target, event) {
Ok(event_result) => {
log::trace!("event_result: {event_result:?}");
event_result
}
Err(err) => {
panic!("eframe encountered a fatal error: {err} during event {event:?}");
}
},
if let Err(err) = combined_result {
log::error!("Exiting because of error: {err}");
exit = true;
self.return_result = Err(err);
};
match event_result {
EventResult::Wait => {
event_loop_window_target.set_control_flow(ControlFlow::Wait);
}
EventResult::RepaintNow(window_id) => {
log::trace!("RepaintNow caused by {}", short_event_description(&event));
if cfg!(target_os = "windows") {
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
windows_next_repaint_times.remove(&window_id);
winit_app.run_ui_and_paint(event_loop_window_target, window_id);
} else {
// Fix for https://github.com/emilk/egui/issues/2425
windows_next_repaint_times.insert(window_id, Instant::now());
}
}
EventResult::RepaintNext(window_id) => {
log::trace!("RepaintNext caused by {}", short_event_description(&event));
windows_next_repaint_times.insert(window_id, Instant::now());
}
EventResult::RepaintAt(window_id, repaint_time) => {
windows_next_repaint_times.insert(
window_id,
windows_next_repaint_times
.get(&window_id)
.map_or(repaint_time, |last| (*last).min(repaint_time)),
);
}
EventResult::Exit => {
if exit {
if self.run_and_return {
log::debug!("Asking to exit event loop…");
event_loop.exit();
} else {
log::debug!("Quitting - saving app state…");
winit_app.save_and_destroy();
self.winit_app.save_and_destroy();
log::debug!("Exiting with return code 0");
#[allow(clippy::exit)]
std::process::exit(0);
}
}
let mut next_repaint_time = windows_next_repaint_times.values().min().copied();
self.check_redraw_requests(event_loop);
}
windows_next_repaint_times.retain(|window_id, repaint_time| {
if Instant::now() < *repaint_time {
return true; // not yet ready
}
fn check_redraw_requests(&mut self, event_loop: &ActiveEventLoop) {
let mut next_repaint_time = self.windows_next_repaint_times.values().min().copied();
next_repaint_time = None;
event_loop_window_target.set_control_flow(ControlFlow::Poll);
self.windows_next_repaint_times
.retain(|window_id, repaint_time| {
if Instant::now() < *repaint_time {
return true; // not yet ready
};
if let Some(window) = winit_app.window(*window_id) {
log::trace!("request_redraw for {window_id:?}");
let is_minimized = window.is_minimized().unwrap_or(false);
if is_minimized {
false
next_repaint_time = None;
event_loop.set_control_flow(ControlFlow::Poll);
if let Some(window) = self.winit_app.window(*window_id) {
log::trace!("request_redraw for {window_id:?}");
let is_minimized = window.is_minimized().unwrap_or(false);
if is_minimized {
false
} else {
window.request_redraw();
true
}
} else {
window.request_redraw();
true
log::trace!("No window found for {window_id:?}");
false
}
} else {
log::trace!("No window found for {window_id:?}");
false
}
});
});
if let Some(next_repaint_time) = next_repaint_time {
// WaitUntil seems to not work on iOS
@@ -371,12 +184,144 @@ fn run_and_exit(
.map(|window| window.request_redraw())
});
event_loop_window_target.set_control_flow(ControlFlow::WaitUntil(next_repaint_time));
event_loop.set_control_flow(ControlFlow::WaitUntil(next_repaint_time));
};
})?;
}
}
impl<T: WinitApp> ApplicationHandler<UserEvent> for WinitAppWrapper<T> {
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
crate::profile_function!("Event::Suspended");
event_loop_context::with_event_loop_context(event_loop, move || {
let event_result = self.winit_app.suspended(event_loop);
self.handle_event_result(event_loop, event_result);
});
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
crate::profile_function!("Event::Resumed");
// Nb: Make sure this guard is dropped after this function returns.
event_loop_context::with_event_loop_context(event_loop, move || {
let event_result = self.winit_app.resumed(event_loop);
self.handle_event_result(event_loop, event_result);
});
}
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
// On Mac, Cmd-Q we get here and then `run_app_on_demand` doesn't return (despite its name),
// so we need to save state now:
log::debug!("Received Event::LoopExiting - saving app state…");
event_loop_context::with_event_loop_context(event_loop, move || {
self.winit_app.save_and_destroy();
});
}
fn device_event(
&mut self,
event_loop: &ActiveEventLoop,
device_id: winit::event::DeviceId,
event: winit::event::DeviceEvent,
) {
crate::profile_function!(egui_winit::short_device_event_description(&event));
// Nb: Make sure this guard is dropped after this function returns.
event_loop_context::with_event_loop_context(event_loop, move || {
let event_result = self.winit_app.device_event(event_loop, device_id, event);
self.handle_event_result(event_loop, event_result);
});
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
crate::profile_function!(match &event {
UserEvent::RequestRepaint { .. } => "UserEvent::RequestRepaint",
#[cfg(feature = "accesskit")]
UserEvent::AccessKitActionRequest(_) => "UserEvent::AccessKitActionRequest",
});
event_loop_context::with_event_loop_context(event_loop, move || {
let event_result = match event {
UserEvent::RequestRepaint {
when,
frame_nr,
viewport_id,
} => {
let current_frame_nr = self.winit_app.frame_nr(viewport_id);
if current_frame_nr == frame_nr || current_frame_nr == frame_nr + 1 {
log::trace!("UserEvent::RequestRepaint scheduling repaint at {when:?}");
if let Some(window_id) =
self.winit_app.window_id_from_viewport_id(viewport_id)
{
Ok(EventResult::RepaintAt(window_id, when))
} else {
Ok(EventResult::Wait)
}
} else {
log::trace!("Got outdated UserEvent::RequestRepaint");
Ok(EventResult::Wait) // old request - we've already repainted
}
}
#[cfg(feature = "accesskit")]
UserEvent::AccessKitActionRequest(request) => {
self.winit_app.on_accesskit_event(request)
}
};
self.handle_event_result(event_loop, event_result);
});
}
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
if let winit::event::StartCause::ResumeTimeReached { .. } = cause {
log::trace!("Woke up to check next_repaint_time");
}
self.check_redraw_requests(event_loop);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: winit::event::WindowEvent,
) {
crate::profile_function!(egui_winit::short_window_event_description(&event));
// Nb: Make sure this guard is dropped after this function returns.
event_loop_context::with_event_loop_context(event_loop, move || {
let event_result = match event {
winit::event::WindowEvent::RedrawRequested => {
self.windows_next_repaint_times.remove(&window_id);
self.winit_app.run_ui_and_paint(event_loop, window_id)
}
_ => self.winit_app.window_event(event_loop, window_id, event),
};
self.handle_event_result(event_loop, event_result);
});
}
}
#[cfg(not(target_os = "ios"))]
fn run_and_return(event_loop: &mut EventLoop<UserEvent>, winit_app: impl WinitApp) -> Result {
use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
log::trace!("Entering the winit event loop (run_app_on_demand)…");
let mut app = WinitAppWrapper::new(winit_app, true);
event_loop.run_app_on_demand(&mut app)?;
log::debug!("eframe window closed");
app.return_result
}
fn run_and_exit(event_loop: EventLoop<UserEvent>, winit_app: impl WinitApp + 'static) -> Result {
log::trace!("Entering the winit event loop (run_app)…");
// When to repaint what window
let mut app = WinitAppWrapper::new(winit_app, false);
event_loop.run_app(&mut app)?;
log::debug!("winit event loop unexpectedly returned");
Ok(())
}

View File

@@ -11,7 +11,7 @@ use egui_winit::ActionRequested;
use parking_lot::Mutex;
use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _};
use winit::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy},
window::{Window, WindowId},
};
@@ -22,10 +22,11 @@ use egui::{
};
#[cfg(feature = "accesskit")]
use egui_winit::accesskit_winit;
use winit_integration::UserEvent;
use crate::{
native::{epi_integration::EpiIntegration, winit_integration::EventResult},
App, AppCreator, CreationContext, NativeOptions, Result, Storage, UserEvent,
App, AppCreator, CreationContext, NativeOptions, Result, Storage,
};
use super::{winit_integration::WinitApp, *};
@@ -119,7 +120,7 @@ impl WgpuWinitApp {
}
/// Create a window for all viewports lacking one.
fn initialized_all_windows(&mut self, event_loop: &EventLoopWindowTarget<UserEvent>) {
fn initialized_all_windows(&mut self, event_loop: &ActiveEventLoop) {
let Some(running) = &mut self.running else {
return;
};
@@ -142,11 +143,7 @@ impl WgpuWinitApp {
}
#[cfg(target_os = "android")]
fn recreate_window(
&self,
event_loop: &EventLoopWindowTarget<UserEvent>,
running: &WgpuWinitRunning,
) {
fn recreate_window(&self, event_loop: &ActiveEventLoop, running: &WgpuWinitRunning) {
let SharedState {
egui_ctx,
viewports,
@@ -178,7 +175,7 @@ impl WgpuWinitApp {
fn init_run_state(
&mut self,
egui_ctx: egui::Context,
event_loop: &EventLoopWindowTarget<UserEvent>,
event_loop: &ActiveEventLoop,
storage: Option<Box<dyn Storage>>,
window: Window,
builder: ViewportBuilder,
@@ -252,7 +249,7 @@ impl WgpuWinitApp {
#[cfg(feature = "accesskit")]
{
let event_loop_proxy = self.repaint_proxy.lock().clone();
integration.init_accesskit(&mut egui_winit, &window, event_loop_proxy);
egui_winit.init_accesskit(&window, event_loop_proxy);
}
let theme = system_theme.unwrap_or(self.native_options.default_theme);
egui_ctx.set_visuals(theme.egui_visuals());
@@ -311,16 +308,9 @@ impl WgpuWinitApp {
let shared = Rc::downgrade(&shared);
let beginning = integration.beginning;
let event_loop: *const EventLoopWindowTarget<UserEvent> = event_loop;
egui::Context::set_immediate_viewport_renderer(move |_egui_ctx, immediate_viewport| {
if let Some(shared) = shared.upgrade() {
// SAFETY: the event loop lives longer than
// the Rc:s we just upgraded above.
#[allow(unsafe_code)]
let event_loop = unsafe { event_loop.as_ref().unwrap() };
render_immediate_viewport(event_loop, beginning, &shared, immediate_viewport);
render_immediate_viewport(beginning, &shared, immediate_viewport);
} else {
log::warn!("render_sync_callback called after window closed");
}
@@ -377,134 +367,127 @@ impl WinitApp for WgpuWinitApp {
fn run_ui_and_paint(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
event_loop: &ActiveEventLoop,
window_id: WindowId,
) -> EventResult {
) -> Result<EventResult> {
self.initialized_all_windows(event_loop);
if let Some(running) = &mut self.running {
running.run_ui_and_paint(window_id)
} else {
EventResult::Wait
Ok(EventResult::Wait)
}
}
fn on_event(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
event: &winit::event::Event<UserEvent>,
) -> Result<EventResult> {
crate::profile_function!(winit_integration::short_event_description(event));
fn resumed(&mut self, event_loop: &ActiveEventLoop) -> crate::Result<EventResult> {
log::debug!("Event::Resumed");
let running = if let Some(running) = &self.running {
#[cfg(target_os = "android")]
self.recreate_window(event_loop, running);
running
} else {
let storage = if let Some(file) = &self.native_options.persistence_path {
epi_integration::create_storage_with_file(file)
} else {
epi_integration::create_storage(
self.native_options
.viewport
.app_id
.as_ref()
.unwrap_or(&self.app_name),
)
};
let egui_ctx = winit_integration::create_egui_context(storage.as_deref());
let (window, builder) = create_window(
&egui_ctx,
event_loop,
storage.as_deref(),
&mut self.native_options,
)?;
self.init_run_state(egui_ctx, event_loop, storage, window, builder)?
};
let viewport = &running.shared.borrow().viewports[&ViewportId::ROOT];
if let Some(window) = &viewport.window {
Ok(EventResult::RepaintNow(window.id()))
} else {
Ok(EventResult::Wait)
}
}
fn suspended(&mut self, _: &ActiveEventLoop) -> crate::Result<EventResult> {
#[cfg(target_os = "android")]
self.drop_window()?;
Ok(EventResult::Wait)
}
fn device_event(
&mut self,
_: &ActiveEventLoop,
_: winit::event::DeviceId,
event: winit::event::DeviceEvent,
) -> crate::Result<EventResult> {
if let winit::event::DeviceEvent::MouseMotion { delta } = event {
if let Some(running) = &mut self.running {
let mut shared = running.shared.borrow_mut();
if let Some(viewport) = shared
.focused_viewport
.and_then(|viewport| shared.viewports.get_mut(&viewport))
{
if let Some(egui_winit) = viewport.egui_winit.as_mut() {
egui_winit.on_mouse_motion(delta);
}
if let Some(window) = viewport.window.as_ref() {
return Ok(EventResult::RepaintNext(window.id()));
}
}
}
}
Ok(EventResult::Wait)
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: winit::event::WindowEvent,
) -> crate::Result<EventResult> {
self.initialized_all_windows(event_loop);
Ok(match event {
winit::event::Event::Resumed => {
log::debug!("Event::Resumed");
if let Some(running) = &mut self.running {
Ok(running.on_window_event(window_id, &event))
} else {
Ok(EventResult::Wait)
}
}
let running = if let Some(running) = &self.running {
#[cfg(target_os = "android")]
self.recreate_window(event_loop, running);
running
} else {
let storage = if let Some(file) = &self.native_options.persistence_path {
epi_integration::create_storage_with_file(file)
} else {
epi_integration::create_storage(
self.native_options
.viewport
.app_id
.as_ref()
.unwrap_or(&self.app_name),
)
};
let egui_ctx = winit_integration::create_egui_context(storage.as_deref());
let (window, builder) = create_window(
&egui_ctx,
event_loop,
storage.as_deref(),
&mut self.native_options,
)?;
self.init_run_state(egui_ctx, event_loop, storage, window, builder)?
};
let viewport = &running.shared.borrow().viewports[&ViewportId::ROOT];
if let Some(window) = &viewport.window {
EventResult::RepaintNow(window.id())
} else {
EventResult::Wait
#[cfg(feature = "accesskit")]
fn on_accesskit_event(&mut self, event: accesskit_winit::Event) -> crate::Result<EventResult> {
if let Some(running) = &mut self.running {
let mut shared_lock = running.shared.borrow_mut();
let SharedState {
viewport_from_window,
viewports,
..
} = &mut *shared_lock;
if let Some(viewport) = viewport_from_window
.get(&event.window_id)
.and_then(|id| viewports.get_mut(id))
{
if let Some(egui_winit) = &mut viewport.egui_winit {
return Ok(winit_integration::on_accesskit_window_event(
egui_winit,
event.window_id,
&event.window_event,
));
}
}
}
winit::event::Event::Suspended => {
#[cfg(target_os = "android")]
self.drop_window()?;
EventResult::Wait
}
winit::event::Event::WindowEvent { event, window_id } => {
if let Some(running) = &mut self.running {
running.on_window_event(*window_id, event)
} else {
EventResult::Wait
}
}
winit::event::Event::DeviceEvent {
device_id: _,
event: winit::event::DeviceEvent::MouseMotion { delta },
} => {
if let Some(running) = &mut self.running {
let mut shared = running.shared.borrow_mut();
if let Some(viewport) = shared
.focused_viewport
.and_then(|viewport| shared.viewports.get_mut(&viewport))
{
if let Some(egui_winit) = viewport.egui_winit.as_mut() {
egui_winit.on_mouse_motion(*delta);
}
if let Some(window) = viewport.window.as_ref() {
EventResult::RepaintNext(window.id())
} else {
EventResult::Wait
}
} else {
EventResult::Wait
}
} else {
EventResult::Wait
}
}
#[cfg(feature = "accesskit")]
winit::event::Event::UserEvent(UserEvent::AccessKitActionRequest(
accesskit_winit::ActionRequestEvent { request, window_id },
)) => {
if let Some(running) = &mut self.running {
let mut shared_lock = running.shared.borrow_mut();
let SharedState {
viewport_from_window,
viewports,
..
} = &mut *shared_lock;
if let Some(viewport) = viewport_from_window
.get(window_id)
.and_then(|id| viewports.get_mut(id))
{
if let Some(egui_winit) = &mut viewport.egui_winit {
egui_winit.on_accesskit_action_request(request.clone());
}
}
// As a form of user input, accessibility actions should
// lead to a repaint.
EventResult::RepaintNext(*window_id)
} else {
EventResult::Wait
}
}
_ => EventResult::Wait,
})
Ok(EventResult::Wait)
}
}
@@ -527,7 +510,7 @@ impl WgpuWinitRunning {
}
/// This is called both for the root viewport, and all deferred viewports
fn run_ui_and_paint(&mut self, window_id: WindowId) -> EventResult {
fn run_ui_and_paint(&mut self, window_id: WindowId) -> Result<EventResult> {
crate::profile_function!();
let Some(viewport_id) = self
@@ -537,7 +520,7 @@ impl WgpuWinitRunning {
.get(&window_id)
.copied()
else {
return EventResult::Wait;
return Ok(EventResult::Wait);
};
#[cfg(feature = "puffin")]
@@ -562,7 +545,7 @@ impl WgpuWinitRunning {
if viewport_id != ViewportId::ROOT {
let Some(viewport) = viewports.get(&viewport_id) else {
return EventResult::Wait;
return Ok(EventResult::Wait);
};
if viewport.viewport_ui_cb.is_none() {
@@ -570,15 +553,15 @@ impl WgpuWinitRunning {
// That means that the viewport cannot be rendered by itself and needs his parent to be rendered.
if let Some(viewport) = viewports.get(&viewport.ids.parent) {
if let Some(window) = viewport.window.as_ref() {
return EventResult::RepaintNext(window.id());
return Ok(EventResult::RepaintNext(window.id()));
}
}
return EventResult::Wait;
return Ok(EventResult::Wait);
}
}
let Some(viewport) = viewports.get_mut(&viewport_id) else {
return EventResult::Wait;
return Ok(EventResult::Wait);
};
let Viewport {
@@ -592,21 +575,17 @@ impl WgpuWinitRunning {
let viewport_ui_cb = viewport_ui_cb.clone();
let Some(window) = window else {
return EventResult::Wait;
return Ok(EventResult::Wait);
};
egui_winit::update_viewport_info(info, &integration.egui_ctx, window, false);
{
crate::profile_scope!("set_window");
if let Err(err) =
pollster::block_on(painter.set_window(viewport_id, Some(window.clone())))
{
log::warn!("Failed to set window: {err}");
}
pollster::block_on(painter.set_window(viewport_id, Some(window.clone())))?;
}
let Some(egui_winit) = egui_winit.as_mut() else {
return EventResult::Wait;
return Ok(EventResult::Wait);
};
let mut raw_input = egui_winit.take_egui_input(window);
@@ -650,7 +629,7 @@ impl WgpuWinitRunning {
remove_viewports_not_in(viewports, painter, viewport_from_window, &viewport_output);
let Some(viewport) = viewports.get_mut(&viewport_id) else {
return EventResult::Wait;
return Ok(EventResult::Wait);
};
viewport.info.events.clear(); // they should have been processed
@@ -661,7 +640,7 @@ impl WgpuWinitRunning {
..
} = viewport
else {
return EventResult::Wait;
return Ok(EventResult::Wait);
};
egui_winit.handle_platform_output(window, platform_output);
@@ -751,9 +730,9 @@ impl WgpuWinitRunning {
}
if integration.should_close() {
EventResult::Exit
Ok(EventResult::Exit)
} else {
EventResult::Wait
Ok(EventResult::Wait)
}
}
@@ -762,8 +741,6 @@ impl WgpuWinitRunning {
window_id: WindowId,
event: &winit::event::WindowEvent,
) -> EventResult {
crate::profile_function!(egui_winit::short_window_event_description(event));
let Self {
integration,
shared,
@@ -865,7 +842,7 @@ impl Viewport {
/// Create winit window, if needed.
fn initialize_window(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
event_loop: &ActiveEventLoop,
egui_ctx: &egui::Context,
windows_id: &mut HashMap<WindowId, ViewportId>,
painter: &mut egui_wgpu::winit::Painter,
@@ -910,7 +887,7 @@ impl Viewport {
fn create_window(
egui_ctx: &egui::Context,
event_loop: &EventLoopWindowTarget<UserEvent>,
event_loop: &ActiveEventLoop,
storage: Option<&dyn Storage>,
native_options: &mut NativeOptions,
) -> Result<(Window, ViewportBuilder), winit::error::OsError> {
@@ -931,7 +908,6 @@ fn create_window(
}
fn render_immediate_viewport(
event_loop: &EventLoopWindowTarget<UserEvent>,
beginning: Instant,
shared: &RefCell<SharedState>,
immediate_viewport: ImmediateViewport<'_>,
@@ -956,7 +932,9 @@ fn render_immediate_viewport(
let viewport =
initialize_or_update_viewport(viewports, ids, ViewportClass::Immediate, builder, None);
if viewport.window.is_none() {
viewport.initialize_window(event_loop, egui_ctx, viewport_from_window, painter);
event_loop_context::with_current_event_loop(|event_loop| {
viewport.initialize_window(event_loop, egui_ctx, viewport_from_window, painter);
});
}
let (Some(window), Some(egui_winit)) = (&viewport.window, &mut viewport.egui_winit) else {

View File

@@ -1,7 +1,7 @@
use std::{sync::Arc, time::Instant};
use winit::{
event_loop::EventLoopWindowTarget,
event_loop::ActiveEventLoop,
window::{Window, WindowId},
};
@@ -48,12 +48,12 @@ pub enum UserEvent {
/// A request related to [`accesskit`](https://accesskit.dev/).
#[cfg(feature = "accesskit")]
AccessKitActionRequest(accesskit_winit::ActionRequestEvent),
AccessKitActionRequest(accesskit_winit::Event),
}
#[cfg(feature = "accesskit")]
impl From<accesskit_winit::ActionRequestEvent> for UserEvent {
fn from(inner: accesskit_winit::ActionRequestEvent) -> Self {
impl From<accesskit_winit::Event> for UserEvent {
fn from(inner: accesskit_winit::Event) -> Self {
Self::AccessKitActionRequest(inner)
}
}
@@ -70,15 +70,30 @@ pub trait WinitApp {
fn run_ui_and_paint(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
event_loop: &ActiveEventLoop,
window_id: WindowId,
) -> EventResult;
fn on_event(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
event: &winit::event::Event<UserEvent>,
) -> crate::Result<EventResult>;
fn suspended(&mut self, event_loop: &ActiveEventLoop) -> crate::Result<EventResult>;
fn resumed(&mut self, event_loop: &ActiveEventLoop) -> crate::Result<EventResult>;
fn device_event(
&mut self,
event_loop: &ActiveEventLoop,
device_id: winit::event::DeviceId,
event: winit::event::DeviceEvent,
) -> crate::Result<EventResult>;
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: winit::event::WindowEvent,
) -> crate::Result<EventResult>;
#[cfg(feature = "accesskit")]
fn on_accesskit_event(&mut self, event: accesskit_winit::Event) -> crate::Result<EventResult>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -113,15 +128,35 @@ pub fn system_theme(window: &Window, options: &crate::NativeOptions) -> Option<c
}
}
/// Short and fast description of an event.
/// Useful for logging and profiling.
pub fn short_event_description(event: &winit::event::Event<UserEvent>) -> &'static str {
#[cfg(feature = "accesskit")]
pub(crate) fn on_accesskit_window_event(
egui_winit: &mut egui_winit::State,
window_id: WindowId,
event: &accesskit_winit::WindowEvent,
) -> EventResult {
match event {
winit::event::Event::UserEvent(user_event) => match user_event {
UserEvent::RequestRepaint { .. } => "UserEvent::RequestRepaint",
#[cfg(feature = "accesskit")]
UserEvent::AccessKitActionRequest(_) => "UserEvent::AccessKitActionRequest",
},
_ => egui_winit::short_generic_event_description(event),
accesskit_winit::WindowEvent::InitialTreeRequested => {
egui_winit.egui_ctx().enable_accesskit();
// Because we can't provide the initial tree synchronously
// (because that would require the activation handler to access
// the same mutable state as the winit event handler), some
// AccessKit platform adapters will use a placeholder tree
// until we send the first tree update. To minimize the possible
// bad effects of that workaround, repaint and send the tree
// immediately.
EventResult::RepaintNow(window_id)
}
accesskit_winit::WindowEvent::ActionRequested(request) => {
egui_winit.on_accesskit_action_request(request.clone());
// As a form of user input, accessibility actions should cause
// a repaint, but not until the next regular frame.
EventResult::RepaintNext(window_id)
}
accesskit_winit::WindowEvent::AccessibilityDeactivated => {
egui_winit.egui_ctx().disable_accesskit();
// Disabling AccessKit support should have no visible effect,
// so there's no need to repaint.
EventResult::Wait
}
}
}