Fix AppKit live resize redraw timing

This commit is contained in:
Vitaly Kravchenko
2026-06-04 21:43:18 +01:00
committed by Simon Hausmann
parent 56fde0791b
commit 27e17e3f29
2 changed files with 53 additions and 13 deletions

View File

@@ -3,13 +3,13 @@ use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use std::rc::Rc; use std::rc::Rc;
use dpi::{LogicalPosition, LogicalSize}; use dpi::{LogicalPosition, PhysicalSize};
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel}; use objc2::runtime::{AnyObject, Sel};
use objc2::{AnyThread, DefinedClass, MainThreadMarker, define_class, msg_send}; use objc2::{AnyThread, DefinedClass, MainThreadMarker, define_class, msg_send};
use objc2_app_kit::{ use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingArea, NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingArea,
NSTrackingAreaOptions, NSView, NSWindow, NSTrackingAreaOptions, NSView, NSViewLayerContentsRedrawPolicy, NSWindow,
}; };
use objc2_core_foundation::CGRect; use objc2_core_foundation::CGRect;
use objc2_foundation::{ use objc2_foundation::{
@@ -161,10 +161,20 @@ define_class!(
// resize occurring. // resize occurring.
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong // 2. Even when a window resize does occur on a new tabbed window, it contains the wrong
// size (includes tab height). // size (includes tab height).
let rect = self.frame(); self.surface_resized();
let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); // During live resize, AppKit may not let the normal event loop reach its next redraw
let size = logical_size.to_physical::<u32>(self.scale_factor()); // point before stretching the current layer contents. Redraw immediately after the
self.queue_event(WindowEvent::SurfaceResized(size)); // app has observed the new surface size.
self.redraw_during_live_resize();
}
#[unsafe(method(viewDidChangeBackingProperties))]
fn view_did_change_backing_properties(&self) {
let _entered = debug_span!("viewDidChangeBackingProperties").entered();
// Moving between displays or changing scale can alter the drawable backing size
// without a matching frame-size change.
self.surface_resized();
self.redraw_during_live_resize();
} }
#[unsafe(method(drawRect:))] #[unsafe(method(drawRect:))]
@@ -795,6 +805,10 @@ impl WinitView {
let this: Retained<Self> = unsafe { msg_send![super(this), init] }; let this: Retained<Self> = unsafe { msg_send![super(this), init] };
*this.ivars().input_source.borrow_mut() = this.current_input_source(); *this.ivars().input_source.borrow_mut() = this.current_input_source();
// Ask AppKit to redisplay the layer while the view is being resized so layer-backed
// surfaces keep painting.
this.setLayerContentsRedrawPolicy(NSViewLayerContentsRedrawPolicy::DuringViewResize);
// `MouseEnteredAndExited` enables receiving events through `mouseEntered:` and // `MouseEnteredAndExited` enables receiving events through `mouseEntered:` and
// `mouseExited:`. // `mouseExited:`.
// //
@@ -853,6 +867,37 @@ impl WinitView {
}); });
} }
fn surface_resized(&self) {
let Some(window) = (**self).window() else {
return;
};
let size = self.surface_size();
let window_id = window_id(&window);
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, WindowEvent::SurfaceResized(size));
});
}
/// Returns the drawable size from the view's backing-coordinate bounds.
pub(super) fn surface_size(&self) -> PhysicalSize<u32> {
// The view bounds are authoritative for full-size content views and during live resize.
// Deriving this from the window frame can exclude custom titlebar content or be stale.
let backing_bounds = self.convertRectToBacking(self.bounds());
PhysicalSize::new(
backing_bounds.size.width.round().max(0.0) as u32,
backing_bounds.size.height.round().max(0.0) as u32,
)
}
fn redraw_during_live_resize(&self) {
let Some(window) = (**self).window() else {
return;
};
if window.inLiveResize() {
self.ivars().app_state.handle_redraw(window_id(&window));
}
}
fn scale_factor(&self) -> f64 { fn scale_factor(&self) -> f64 {
self.window().backingScaleFactor() as f64 self.window().backingScaleFactor() as f64
} }

View File

@@ -912,10 +912,7 @@ impl WindowDelegate {
fn handle_scale_factor_changed(&self, scale_factor: CGFloat) { fn handle_scale_factor_changed(&self, scale_factor: CGFloat) {
let window = self.window(); let window = self.window();
let content_size = window.contentRectForFrameRect(window.frame()).size; let suggested_size = self.view().surface_size();
let content_size = LogicalSize::new(content_size.width, content_size.height);
let suggested_size = content_size.to_physical(scale_factor);
let new_surface_size = Arc::new(Mutex::new(suggested_size)); let new_surface_size = Arc::new(Mutex::new(suggested_size));
self.queue_event(WindowEvent::ScaleFactorChanged { self.queue_event(WindowEvent::ScaleFactorChanged {
scale_factor, scale_factor,
@@ -1040,9 +1037,7 @@ impl WindowDelegate {
#[inline] #[inline]
pub fn surface_size(&self) -> PhysicalSize<u32> { pub fn surface_size(&self) -> PhysicalSize<u32> {
let content_rect = self.window().contentRectForFrameRect(self.window().frame()); self.view().surface_size()
let logical = LogicalSize::new(content_rect.size.width, content_rect.size.height);
logical.to_physical(self.scale_factor())
} }
#[inline] #[inline]