From c6acca96db0890b54d23efe23abd08972b6237b4 Mon Sep 17 00:00:00 2001 From: Vitaly Kravchenko Date: Tue, 30 Jun 2026 10:57:48 +0100 Subject: [PATCH] Defer macOS native transitions during event handling --- winit-appkit/src/app_state.rs | 4 ++++ winit-appkit/src/window_delegate.rs | 33 ++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/winit-appkit/src/app_state.rs b/winit-appkit/src/app_state.rs index 5b057b79b3..1c870c172a 100644 --- a/winit-appkit/src/app_state.rs +++ b/winit-appkit/src/app_state.rs @@ -236,6 +236,10 @@ impl AppState { self.control_flow.set(value) } + pub(super) fn is_handling_event(&self) -> bool { + self.event_handler.in_use() + } + pub fn control_flow(&self) -> ControlFlow { self.control_flow.get() } diff --git a/winit-appkit/src/window_delegate.rs b/winit-appkit/src/window_delegate.rs index 4697cf820f..e0c6f38dc7 100644 --- a/winit-appkit/src/window_delegate.rs +++ b/winit-appkit/src/window_delegate.rs @@ -909,6 +909,20 @@ impl WindowDelegate { }); } + fn defer_if_handling_event(&self, f: impl FnOnce(Retained) + 'static) -> bool { + // AppKit state transitions such as zoom/fullscreen can synchronously run resize/display + // callbacks. Starting them from inside a winit event callback prevents those callbacks + // from being delivered immediately, so defer the transition to the next run-loop turn. + if !self.ivars().app_state.is_handling_event() { + return false; + } + + let mtm = MainThreadMarker::from(self); + let this = self.retain(); + MainRunLoop::get(mtm).queue_closure(move || f(this)); + true + } + fn handle_scale_factor_changed(&self, scale_factor: CGFloat) { let window = self.window(); @@ -1378,6 +1392,10 @@ impl WindowDelegate { #[inline] pub fn set_maximized(&self, maximized: bool) { + if self.defer_if_handling_event(move |this| this.set_maximized(maximized)) { + return; + } + let mtm = MainThreadMarker::from(self); let is_zoomed = self.is_zoomed(); if is_zoomed == maximized { @@ -1423,9 +1441,6 @@ impl WindowDelegate { #[inline] pub(crate) fn set_fullscreen(&self, fullscreen: Option) { - let mtm = MainThreadMarker::from(self); - let app = NSApplication::sharedApplication(mtm); - if self.ivars().is_simple_fullscreen.get() { return; } @@ -1440,6 +1455,18 @@ impl WindowDelegate { return; } + if !self.ivars().initial_fullscreen.get() + && self.defer_if_handling_event({ + let fullscreen = fullscreen.clone(); + move |this| this.set_fullscreen(fullscreen) + }) + { + return; + } + + let mtm = MainThreadMarker::from(self); + let app = NSApplication::sharedApplication(mtm); + // If the fullscreen is on a different monitor, we must move the window // to that monitor before we toggle fullscreen (as `toggleFullScreen` // does not take a screen parameter, but uses the current screen)