From 03ffd204f04879b80e9289c42c96e8f08e3f390c Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 8 May 2024 03:33:17 +0200 Subject: [PATCH] macOS: Avoid redundant initial resize event The `NSViewFrameDidChangeNotification` that we listen to is emitted when `-[NSWindow setContentView]` is called, since that sets the frame of the view as well. So now we register the notification later, so that it's not triggered at window creation. --- src/changelog/unreleased.md | 1 + src/platform_impl/apple/appkit/view.rs | 54 +++++++------------ .../apple/appkit/window_delegate.rs | 34 +++++++++--- 3 files changed, 46 insertions(+), 43 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 6f55ff7f2c..e856ab3295 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -154,3 +154,4 @@ changelog entry. ### Fixed - On Orbital, `MonitorHandle::name()` now returns `None` instead of a dummy name. +- On macOS, fixed redundant `SurfaceResized` event at window creation. diff --git a/src/platform_impl/apple/appkit/view.rs b/src/platform_impl/apple/appkit/view.rs index 0ba46a6338..83ecc7da6a 100644 --- a/src/platform_impl/apple/appkit/view.rs +++ b/src/platform_impl/apple/appkit/view.rs @@ -4,17 +4,17 @@ use std::collections::{HashMap, VecDeque}; use std::ptr; use std::rc::Rc; -use objc2::rc::{Retained, WeakId}; +use objc2::rc::Retained; use objc2::runtime::{AnyObject, Sel}; -use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass}; +use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; use objc2_app_kit::{ NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, - NSTrackingRectTag, NSView, NSViewFrameDidChangeNotification, + NSTrackingRectTag, NSView, }; use objc2_foundation::{ MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, - NSMutableAttributedString, NSNotFound, NSNotificationCenter, NSObject, NSObjectProtocol, - NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, + NSMutableAttributedString, NSNotFound, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, + NSSize, NSString, NSUInteger, }; use super::app_state::AppState; @@ -136,9 +136,6 @@ pub struct ViewState { marked_text: RefCell>, accepts_first_mouse: bool, - // Weak reference because the window keeps a strong reference to the view - _ns_window: WeakId, - /// The state of the `Option` as `Alt`. option_as_alt: Cell, } @@ -179,9 +176,10 @@ declare_class!( self.ivars().tracking_rect.set(Some(tracking_rect)); } - #[method(frameDidChange:)] - fn frame_did_change(&self, _event: &NSEvent) { - trace_scope!("frameDidChange:"); + // Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`. + #[method(viewFrameDidChangeNotification:)] + fn frame_did_change(&self, _notification: Option<&AnyObject>) { + trace_scope!("NSViewFrameDidChangeNotification"); if let Some(tracking_rect) = self.ivars().tracking_rect.take() { self.removeTrackingRect(tracking_rect); } @@ -205,10 +203,7 @@ declare_class!( fn draw_rect(&self, _rect: NSRect) { trace_scope!("drawRect:"); - // It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`. - if let Some(window) = self.ivars()._ns_window.load() { - self.ivars().app_state.handle_redraw(window.id()); - } + self.ivars().app_state.handle_redraw(self.window().id()); // This is a direct subclass of NSView, no need to call superclass' drawRect: } @@ -784,11 +779,10 @@ declare_class!( impl WinitView { pub(super) fn new( app_state: &Rc, - window: &WinitWindow, accepts_first_mouse: bool, option_as_alt: OptionAsAlt, + mtm: MainThreadMarker, ) -> Retained { - let mtm = MainThreadMarker::from(window); let this = mtm.alloc().set_ivars(ViewState { app_state: Rc::clone(app_state), cursor_state: Default::default(), @@ -803,34 +797,24 @@ impl WinitView { forward_key_to_app: Default::default(), marked_text: Default::default(), accepts_first_mouse, - _ns_window: WeakId::new(&window.retain()), option_as_alt: Cell::new(option_as_alt), }); let this: Retained = unsafe { msg_send_id![super(this), init] }; - this.setPostsFrameChangedNotifications(true); - let notification_center = unsafe { NSNotificationCenter::defaultCenter() }; - unsafe { - notification_center.addObserver_selector_name_object( - &this, - sel!(frameDidChange:), - Some(NSViewFrameDidChangeNotification), - Some(&this), - ) - } - *this.ivars().input_source.borrow_mut() = this.current_input_source(); this } fn window(&self) -> Retained { - // TODO: Simply use `window` property on `NSView`. - // That only returns a window _after_ the view has been attached though! - // (which is incompatible with `frameDidChange:`) - // - // unsafe { msg_send_id![self, window] } - self.ivars()._ns_window.load().expect("view to have a window") + let window = (**self).window().expect("view must be installed in a window"); + + if !window.isKindOfClass(WinitWindow::class()) { + unreachable!("view installed in non-WinitWindow"); + } + + // SAFETY: Just checked that the window is `WinitWindow` + unsafe { Retained::cast(window) } } fn queue_event(&self, event: WindowEvent) { diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index fdad0c515e..8cce22c28f 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -15,15 +15,16 @@ use objc2_app_kit::{ NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization, NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType, NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard, - NSRequestUserAttentionType, NSScreen, NSView, NSWindowButton, NSWindowDelegate, - NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode, - NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility, + NSRequestUserAttentionType, NSScreen, NSView, NSViewFrameDidChangeNotification, NSWindowButton, + NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, + NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, + NSWindowTitleVisibility, }; use objc2_foundation::{ ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSKeyValueChangeKey, - NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, NSKeyValueObservingOptions, NSObject, - NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, - NSRect, NSSize, NSString, + NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, NSKeyValueObservingOptions, + NSNotificationCenter, NSObject, NSObjectNSDelayedPerforming, + NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, NSRect, NSSize, NSString, }; use tracing::{trace, warn}; @@ -164,7 +165,7 @@ declare_class!( #[method(windowDidResize:)] fn window_did_resize(&self, _: Option<&AnyObject>) { trace_scope!("windowDidResize:"); - // NOTE: WindowEvent::SurfaceResized is reported in frameDidChange. + // NOTE: WindowEvent::SurfaceResized is reported using NSViewFrameDidChangeNotification. self.emit_move_event(); } @@ -632,9 +633,9 @@ fn new_window( let view = WinitView::new( app_state, - &window, attrs.platform_specific.accepts_first_mouse, attrs.platform_specific.option_as_alt, + mtm, ); // The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until @@ -656,6 +657,23 @@ fn new_window( window.setContentView(Some(&view)); window.setInitialFirstResponder(Some(&view)); + // Configure the view to send notifications whenever its frame rectangle changes. + // + // We explicitly do this _after_ setting the view as the content view of the window, to + // avoid a resize event when creating the window. + view.setPostsFrameChangedNotifications(true); + // `setPostsFrameChangedNotifications` posts the notification immediately, so register the + // observer _after_, again so that the event isn't triggered initially. + let notification_center = unsafe { NSNotificationCenter::defaultCenter() }; + unsafe { + notification_center.addObserver_selector_name_object( + &view, + sel!(viewFrameDidChangeNotification:), + Some(NSViewFrameDidChangeNotification), + Some(&view), + ) + } + if attrs.transparent { window.setOpaque(false); // See `set_transparent` for details on why we do this.