Skip to content

Commit 3657506

Browse files
authored
macOS: Avoid redundant initial resize event (#3913)
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. This behaviour is well described in the documentation: https://developer.apple.com/documentation/appkit/nsview/postsframechangednotifications?language=objc
1 parent edca3eb commit 3657506

File tree

3 files changed

+44
-42
lines changed

3 files changed

+44
-42
lines changed

src/changelog/unreleased.md

+1
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,4 @@ changelog entry.
211211
- On iOS, fixed `SurfaceResized` and `Window::surface_size` not reporting the size of the actual surface.
212212
- On macOS, fixed the scancode conversion for audio volume keys.
213213
- On macOS, fixed the scancode conversion for `IntlBackslash`.
214+
- On macOS, fixed redundant `SurfaceResized` event at window creation.

src/platform_impl/apple/appkit/view.rs

+19-35
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ use std::collections::{HashMap, VecDeque};
44
use std::ptr;
55
use std::rc::Rc;
66

7-
use objc2::rc::{Retained, WeakId};
7+
use objc2::rc::Retained;
88
use objc2::runtime::{AnyObject, Sel};
9-
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
9+
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
1010
use objc2_app_kit::{
1111
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
12-
NSTrackingRectTag, NSView, NSViewFrameDidChangeNotification,
12+
NSTrackingRectTag, NSView,
1313
};
1414
use objc2_foundation::{
1515
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
16-
NSMutableAttributedString, NSNotFound, NSNotificationCenter, NSObject, NSObjectProtocol,
17-
NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
16+
NSMutableAttributedString, NSNotFound, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect,
17+
NSSize, NSString, NSUInteger,
1818
};
1919

2020
use super::app_state::AppState;
@@ -134,9 +134,6 @@ pub struct ViewState {
134134
marked_text: RefCell<Retained<NSMutableAttributedString>>,
135135
accepts_first_mouse: bool,
136136

137-
// Weak reference because the window keeps a strong reference to the view
138-
_ns_window: WeakId<WinitWindow>,
139-
140137
/// The state of the `Option` as `Alt`.
141138
option_as_alt: Cell<OptionAsAlt>,
142139
}
@@ -177,9 +174,10 @@ declare_class!(
177174
self.ivars().tracking_rect.set(Some(tracking_rect));
178175
}
179176

180-
#[method(frameDidChange:)]
181-
fn frame_did_change(&self, _event: &NSEvent) {
182-
trace_scope!("frameDidChange:");
177+
// Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`.
178+
#[method(viewFrameDidChangeNotification:)]
179+
fn frame_did_change(&self, _notification: Option<&AnyObject>) {
180+
trace_scope!("NSViewFrameDidChangeNotification");
183181
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
184182
self.removeTrackingRect(tracking_rect);
185183
}
@@ -203,10 +201,7 @@ declare_class!(
203201
fn draw_rect(&self, _rect: NSRect) {
204202
trace_scope!("drawRect:");
205203

206-
// It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`.
207-
if let Some(window) = self.ivars()._ns_window.load() {
208-
self.ivars().app_state.handle_redraw(window.id());
209-
}
204+
self.ivars().app_state.handle_redraw(self.window().id());
210205

211206
// This is a direct subclass of NSView, no need to call superclass' drawRect:
212207
}
@@ -806,11 +801,10 @@ declare_class!(
806801
impl WinitView {
807802
pub(super) fn new(
808803
app_state: &Rc<AppState>,
809-
window: &WinitWindow,
810804
accepts_first_mouse: bool,
811805
option_as_alt: OptionAsAlt,
806+
mtm: MainThreadMarker,
812807
) -> Retained<Self> {
813-
let mtm = MainThreadMarker::from(window);
814808
let this = mtm.alloc().set_ivars(ViewState {
815809
app_state: Rc::clone(app_state),
816810
cursor_state: Default::default(),
@@ -825,34 +819,24 @@ impl WinitView {
825819
forward_key_to_app: Default::default(),
826820
marked_text: Default::default(),
827821
accepts_first_mouse,
828-
_ns_window: WeakId::new(&window.retain()),
829822
option_as_alt: Cell::new(option_as_alt),
830823
});
831824
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
832825

833-
this.setPostsFrameChangedNotifications(true);
834-
let notification_center = unsafe { NSNotificationCenter::defaultCenter() };
835-
unsafe {
836-
notification_center.addObserver_selector_name_object(
837-
&this,
838-
sel!(frameDidChange:),
839-
Some(NSViewFrameDidChangeNotification),
840-
Some(&this),
841-
)
842-
}
843-
844826
*this.ivars().input_source.borrow_mut() = this.current_input_source();
845827

846828
this
847829
}
848830

849831
fn window(&self) -> Retained<WinitWindow> {
850-
// TODO: Simply use `window` property on `NSView`.
851-
// That only returns a window _after_ the view has been attached though!
852-
// (which is incompatible with `frameDidChange:`)
853-
//
854-
// unsafe { msg_send_id![self, window] }
855-
self.ivars()._ns_window.load().expect("view to have a window")
832+
let window = (**self).window().expect("view must be installed in a window");
833+
834+
if !window.isKindOfClass(WinitWindow::class()) {
835+
unreachable!("view installed in non-WinitWindow");
836+
}
837+
838+
// SAFETY: Just checked that the window is `WinitWindow`
839+
unsafe { Retained::cast(window) }
856840
}
857841

858842
fn queue_event(&self, event: WindowEvent) {

src/platform_impl/apple/appkit/window_delegate.rs

+24-7
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ use objc2_app_kit::{
1515
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization,
1616
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
1717
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
18-
NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSWindowButton, NSWindowDelegate,
19-
NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode,
20-
NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility,
21-
NSWindowToolbarStyle,
18+
NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification,
19+
NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
20+
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
21+
NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle,
2222
};
2323
use objc2_foundation::{
2424
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSEdgeInsets,
2525
NSKeyValueChangeKey, NSKeyValueChangeNewKey, NSKeyValueChangeOldKey,
26-
NSKeyValueObservingOptions, NSObject, NSObjectNSDelayedPerforming,
26+
NSKeyValueObservingOptions, NSNotificationCenter, NSObject, NSObjectNSDelayedPerforming,
2727
NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, NSRect, NSSize, NSString,
2828
};
2929
use tracing::{trace, warn};
@@ -170,7 +170,7 @@ declare_class!(
170170
#[method(windowDidResize:)]
171171
fn window_did_resize(&self, _: Option<&AnyObject>) {
172172
trace_scope!("windowDidResize:");
173-
// NOTE: WindowEvent::SurfaceResized is reported in frameDidChange.
173+
// NOTE: WindowEvent::SurfaceResized is reported using NSViewFrameDidChangeNotification.
174174
self.emit_move_event();
175175
}
176176

@@ -658,9 +658,9 @@ fn new_window(
658658

659659
let view = WinitView::new(
660660
app_state,
661-
&window,
662661
attrs.platform_specific.accepts_first_mouse,
663662
attrs.platform_specific.option_as_alt,
663+
mtm,
664664
);
665665

666666
// The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until
@@ -682,6 +682,23 @@ fn new_window(
682682
window.setContentView(Some(&view));
683683
window.setInitialFirstResponder(Some(&view));
684684

685+
// Configure the view to send notifications whenever its frame rectangle changes.
686+
//
687+
// We explicitly do this _after_ setting the view as the content view of the window, to
688+
// avoid a resize event when creating the window.
689+
view.setPostsFrameChangedNotifications(true);
690+
// `setPostsFrameChangedNotifications` posts the notification immediately, so register the
691+
// observer _after_, again so that the event isn't triggered initially.
692+
let notification_center = unsafe { NSNotificationCenter::defaultCenter() };
693+
unsafe {
694+
notification_center.addObserver_selector_name_object(
695+
&view,
696+
sel!(viewFrameDidChangeNotification:),
697+
Some(NSViewFrameDidChangeNotification),
698+
Some(&view),
699+
)
700+
}
701+
685702
if attrs.transparent {
686703
window.setOpaque(false);
687704
// See `set_transparent` for details on why we do this.

0 commit comments

Comments
 (0)