diff --git a/Cargo.toml b/Cargo.toml index e37fcad0..8d8a46ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,13 +86,13 @@ ndk-sys = "0.5.0" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] core-foundation = "0.9.3" -objc2 = "0.4.1" +objc2 = "0.5.0" [target.'cfg(target_os = "macos")'.dependencies] core-graphics = "0.23.1" [target.'cfg(target_os = "macos")'.dependencies.icrate] -version = "0.0.4" +version = "0.1.0" features = [ "dispatch", "Foundation", @@ -108,7 +108,7 @@ features = [ ] [target.'cfg(target_os = "ios")'.dependencies.icrate] -version = "0.0.4" +version = "0.1.0" features = [ "dispatch", "Foundation", diff --git a/src/platform_impl/ios/monitor.rs b/src/platform_impl/ios/monitor.rs index 90ed8504..45bace35 100644 --- a/src/platform_impl/ios/monitor.rs +++ b/src/platform_impl/ios/monitor.rs @@ -8,6 +8,7 @@ use std::{ use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSInteger}; use objc2::mutability::IsRetainable; use objc2::rc::Id; +use objc2::Message; use super::uikit::{UIScreen, UIScreenMode}; use crate::{ @@ -20,16 +21,15 @@ use crate::{ #[derive(Debug)] struct MainThreadBoundDelegateImpls(MainThreadBound>); -impl Clone for MainThreadBoundDelegateImpls { +impl Clone for MainThreadBoundDelegateImpls { fn clone(&self) -> Self { - Self( - self.0 - .get_on_main(|inner, mtm| MainThreadBound::new(Id::clone(inner), mtm)), - ) + Self(MainThreadMarker::run_on_main(|mtm| { + MainThreadBound::new(Id::clone(self.0.get(mtm)), mtm) + })) } } -impl hash::Hash for MainThreadBoundDelegateImpls { +impl hash::Hash for MainThreadBoundDelegateImpls { fn hash(&self, state: &mut H) { // SAFETY: Marker only used to get the pointer let mtm = unsafe { MainThreadMarker::new_unchecked() }; @@ -37,7 +37,7 @@ impl hash::Hash for MainThreadBoundDelegateImpls { } } -impl PartialEq for MainThreadBoundDelegateImpls { +impl PartialEq for MainThreadBoundDelegateImpls { fn eq(&self, other: &Self) -> bool { // SAFETY: Marker only used to get the pointer let mtm = unsafe { MainThreadMarker::new_unchecked() }; @@ -45,7 +45,7 @@ impl PartialEq for MainThreadBoundDelegateImpls { } } -impl Eq for MainThreadBoundDelegateImpls {} +impl Eq for MainThreadBoundDelegateImpls {} #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct VideoMode { @@ -100,11 +100,9 @@ pub struct MonitorHandle { impl Clone for MonitorHandle { fn clone(&self) -> Self { - Self { - ui_screen: self - .ui_screen - .get_on_main(|inner, mtm| MainThreadBound::new(inner.clone(), mtm)), - } + MainThreadMarker::run_on_main(|mtm| Self { + ui_screen: MainThreadBound::new(self.ui_screen.get(mtm).clone(), mtm), + }) } } @@ -168,16 +166,16 @@ impl MonitorHandle { } pub fn name(&self) -> Option { - self.ui_screen.get_on_main(|ui_screen, mtm| { + MainThreadMarker::run_on_main(|mtm| { let main = UIScreen::main(mtm); - if *ui_screen == main { + if *self.ui_screen(mtm) == main { Some("Primary".to_string()) - } else if *ui_screen == main.mirroredScreen() { + } else if *self.ui_screen(mtm) == main.mirroredScreen() { Some("Mirrored".to_string()) } else { UIScreen::screens(mtm) .iter() - .position(|rhs| rhs == &**ui_screen) + .position(|rhs| rhs == &**self.ui_screen(mtm)) .map(|idx| idx.to_string()) } }) @@ -186,31 +184,32 @@ impl MonitorHandle { pub fn size(&self) -> PhysicalSize { let bounds = self .ui_screen - .get_on_main(|ui_screen, _| ui_screen.nativeBounds()); + .get_on_main(|ui_screen| ui_screen.nativeBounds()); PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32) } pub fn position(&self) -> PhysicalPosition { let bounds = self .ui_screen - .get_on_main(|ui_screen, _| ui_screen.nativeBounds()); + .get_on_main(|ui_screen| ui_screen.nativeBounds()); (bounds.origin.x as f64, bounds.origin.y as f64).into() } pub fn scale_factor(&self) -> f64 { self.ui_screen - .get_on_main(|ui_screen, _| ui_screen.nativeScale()) as f64 + .get_on_main(|ui_screen| ui_screen.nativeScale()) as f64 } pub fn refresh_rate_millihertz(&self) -> Option { Some( self.ui_screen - .get_on_main(|ui_screen, _| refresh_rate_millihertz(ui_screen)), + .get_on_main(|ui_screen| refresh_rate_millihertz(ui_screen)), ) } pub fn video_modes(&self) -> impl Iterator { - self.ui_screen.get_on_main(|ui_screen, mtm| { + MainThreadMarker::run_on_main(|mtm| { + let ui_screen = self.ui_screen(mtm); // Use Ord impl of RootVideoMode let modes: BTreeSet<_> = ui_screen @@ -230,8 +229,12 @@ impl MonitorHandle { } pub fn preferred_video_mode(&self) -> VideoMode { - self.ui_screen.get_on_main(|ui_screen, mtm| { - VideoMode::new(ui_screen.clone(), ui_screen.preferredMode().unwrap(), mtm) + MainThreadMarker::run_on_main(|mtm| { + VideoMode::new( + self.ui_screen(mtm).clone(), + self.ui_screen(mtm).preferredMode().unwrap(), + mtm, + ) }) } } diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 17fb3c0e..9b456e6b 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -1,12 +1,12 @@ #![allow(clippy::unnecessary_cast)] use std::cell::Cell; -use std::ptr::NonNull; use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSObjectProtocol, NSSet}; -use objc2::declare::{Ivar, IvarDrop}; use objc2::rc::Id; use objc2::runtime::AnyClass; -use objc2::{declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType}; +use objc2::{ + declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType, DeclaredClass, +}; use super::app_state::{self, EventWrapper}; use super::uikit::{ @@ -37,6 +37,8 @@ declare_class!( const NAME: &'static str = "WinitUIView"; } + impl DeclaredClass for WinitView {} + unsafe impl WinitView { #[method(drawRect:)] fn draw_rect(&self, rect: CGRect) { @@ -274,11 +276,7 @@ pub struct ViewControllerState { } declare_class!( - pub(crate) struct WinitViewController { - state: IvarDrop, "_state">, - } - - mod view_controller_ivars; + pub(crate) struct WinitViewController; unsafe impl ClassType for WinitViewController { #[inherits(UIResponder, NSObject)] @@ -287,28 +285,8 @@ declare_class!( const NAME: &'static str = "WinitUIViewController"; } - unsafe impl WinitViewController { - #[method(init)] - unsafe fn init(this: *mut Self) -> Option> { - let this: Option<&mut Self> = msg_send![super(this), init]; - this.map(|this| { - // These are set in WinitViewController::new, it's just to set them - // to _something_. - Ivar::write( - &mut this.state, - Box::new(ViewControllerState { - prefers_status_bar_hidden: Cell::new(false), - preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default), - prefers_home_indicator_auto_hidden: Cell::new(false), - supported_orientations: Cell::new(UIInterfaceOrientationMask::All), - preferred_screen_edges_deferring_system_gestures: Cell::new( - UIRectEdge::NONE, - ), - }), - ); - NonNull::from(this) - }) - } + impl DeclaredClass for WinitViewController { + type Ivars = ViewControllerState; } unsafe impl WinitViewController { @@ -319,27 +297,27 @@ declare_class!( #[method(prefersStatusBarHidden)] fn prefers_status_bar_hidden(&self) -> bool { - self.state.prefers_status_bar_hidden.get() + self.ivars().prefers_status_bar_hidden.get() } #[method(preferredStatusBarStyle)] fn preferred_status_bar_style(&self) -> UIStatusBarStyle { - self.state.preferred_status_bar_style.get() + self.ivars().preferred_status_bar_style.get() } #[method(prefersHomeIndicatorAutoHidden)] fn prefers_home_indicator_auto_hidden(&self) -> bool { - self.state.prefers_home_indicator_auto_hidden.get() + self.ivars().prefers_home_indicator_auto_hidden.get() } #[method(supportedInterfaceOrientations)] fn supported_orientations(&self) -> UIInterfaceOrientationMask { - self.state.supported_orientations.get() + self.ivars().supported_orientations.get() } #[method(preferredScreenEdgesDeferringSystemGestures)] fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge { - self.state + self.ivars() .preferred_screen_edges_deferring_system_gestures .get() } @@ -348,17 +326,17 @@ declare_class!( impl WinitViewController { pub(crate) fn set_prefers_status_bar_hidden(&self, val: bool) { - self.state.prefers_status_bar_hidden.set(val); + self.ivars().prefers_status_bar_hidden.set(val); self.setNeedsStatusBarAppearanceUpdate(); } pub(crate) fn set_preferred_status_bar_style(&self, val: UIStatusBarStyle) { - self.state.preferred_status_bar_style.set(val); + self.ivars().preferred_status_bar_style.set(val); self.setNeedsStatusBarAppearanceUpdate(); } pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) { - self.state.prefers_home_indicator_auto_hidden.set(val); + self.ivars().prefers_home_indicator_auto_hidden.set(val); let os_capabilities = app_state::os_capabilities(); if os_capabilities.home_indicator_hidden { self.setNeedsUpdateOfHomeIndicatorAutoHidden(); @@ -368,7 +346,7 @@ impl WinitViewController { } pub(crate) fn set_preferred_screen_edges_deferring_system_gestures(&self, val: UIRectEdge) { - self.state + self.ivars() .preferred_screen_edges_deferring_system_gestures .set(val); let os_capabilities = app_state::os_capabilities(); @@ -401,7 +379,7 @@ impl WinitViewController { | UIInterfaceOrientationMask::PortraitUpsideDown } }; - self.state.supported_orientations.set(mask); + self.ivars().supported_orientations.set(mask); UIViewController::attemptRotationToDeviceOrientation(); } @@ -411,7 +389,15 @@ impl WinitViewController { platform_attributes: &PlatformSpecificWindowBuilderAttributes, view: &UIView, ) -> Id { - let this: Id = unsafe { msg_send_id![Self::alloc(), init] }; + // These are set properly below, we just to set them to something in the meantime. + let this = Self::alloc().set_ivars(ViewControllerState { + prefers_status_bar_hidden: Cell::new(false), + preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default), + prefers_home_indicator_auto_hidden: Cell::new(false), + supported_orientations: Cell::new(UIInterfaceOrientationMask::All), + preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::NONE), + }); + let this: Id = unsafe { msg_send_id![super(this), init] }; this.set_prefers_status_bar_hidden(platform_attributes.prefers_status_bar_hidden); @@ -446,6 +432,8 @@ declare_class!( const NAME: &'static str = "WinitUIWindow"; } + impl DeclaredClass for WinitUIWindow {} + unsafe impl WinitUIWindow { #[method(becomeKeyWindow)] fn become_key_window(&self) { @@ -518,6 +506,8 @@ declare_class!( const NAME: &'static str = "WinitApplicationDelegate"; } + impl DeclaredClass for WinitApplicationDelegate {} + // UIApplicationDelegate protocol unsafe impl WinitApplicationDelegate { #[method(application:didFinishLaunchingWithOptions:)] diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 2f0f8c09..e3480169 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -520,7 +520,7 @@ impl Window { } pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Inner) -> R + Send) -> R { - self.inner.get_on_main(|inner, _mtm| f(inner)) + self.inner.get_on_main(|inner| f(inner)) } #[cfg(feature = "rwh_06")] diff --git a/src/platform_impl/macos/app.rs b/src/platform_impl/macos/app.rs index 326805aa..b1e1fea3 100644 --- a/src/platform_impl/macos/app.rs +++ b/src/platform_impl/macos/app.rs @@ -1,14 +1,13 @@ #![allow(clippy::unnecessary_cast)] use icrate::Foundation::NSObject; -use objc2::{declare_class, msg_send, mutability, ClassType}; +use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass}; use super::appkit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder}; use super::{app_state::AppState, DEVICE_ID}; use crate::event::{DeviceEvent, ElementState, Event}; declare_class!( - #[derive(Debug, PartialEq, Eq, Hash)] pub(super) struct WinitApplication; unsafe impl ClassType for WinitApplication { @@ -18,6 +17,8 @@ declare_class!( const NAME: &'static str = "WinitApplication"; } + impl DeclaredClass for WinitApplication {} + unsafe impl WinitApplication { // Normally, holding Cmd + any key never sends us a `keyUp` event for that key. // Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196) diff --git a/src/platform_impl/macos/app_delegate.rs b/src/platform_impl/macos/app_delegate.rs index 4b0991f1..9ab35b4e 100644 --- a/src/platform_impl/macos/app_delegate.rs +++ b/src/platform_impl/macos/app_delegate.rs @@ -1,23 +1,20 @@ -use std::ptr::NonNull; - use icrate::Foundation::NSObject; -use objc2::declare::{IvarBool, IvarEncode}; use objc2::rc::Id; use objc2::runtime::AnyObject; -use objc2::{declare_class, msg_send, msg_send_id, mutability, ClassType}; +use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; use super::app_state::AppState; use super::appkit::NSApplicationActivationPolicy; -declare_class!( - #[derive(Debug)] - pub(super) struct ApplicationDelegate { - activation_policy: IvarEncode, - default_menu: IvarBool<"_default_menu">, - activate_ignoring_other_apps: IvarBool<"_activate_ignoring_other_apps">, - } +#[derive(Debug)] +pub(super) struct State { + activation_policy: NSApplicationActivationPolicy, + default_menu: bool, + activate_ignoring_other_apps: bool, +} - mod ivars; +declare_class!( + pub(super) struct ApplicationDelegate; unsafe impl ClassType for ApplicationDelegate { type Super = NSObject; @@ -25,30 +22,18 @@ declare_class!( const NAME: &'static str = "WinitApplicationDelegate"; } - unsafe impl ApplicationDelegate { - #[method(initWithActivationPolicy:defaultMenu:activateIgnoringOtherApps:)] - unsafe fn init( - this: *mut Self, - activation_policy: NSApplicationActivationPolicy, - default_menu: bool, - activate_ignoring_other_apps: bool, - ) -> Option> { - let this: Option<&mut Self> = unsafe { msg_send![super(this), init] }; - this.map(|this| { - *this.activation_policy = activation_policy; - *this.default_menu = default_menu; - *this.activate_ignoring_other_apps = activate_ignoring_other_apps; - NonNull::from(this) - }) - } + impl DeclaredClass for ApplicationDelegate { + type Ivars = State; + } + unsafe impl ApplicationDelegate { #[method(applicationDidFinishLaunching:)] fn did_finish_launching(&self, _sender: Option<&AnyObject>) { trace_scope!("applicationDidFinishLaunching:"); AppState::launched( - *self.activation_policy, - *self.default_menu, - *self.activate_ignoring_other_apps, + self.ivars().activation_policy, + self.ivars().default_menu, + self.ivars().activate_ignoring_other_apps, ); } @@ -67,13 +52,11 @@ impl ApplicationDelegate { default_menu: bool, activate_ignoring_other_apps: bool, ) -> Id { - unsafe { - msg_send_id![ - Self::alloc(), - initWithActivationPolicy: activation_policy, - defaultMenu: default_menu, - activateIgnoringOtherApps: activate_ignoring_other_apps, - ] - } + let this = Self::alloc().set_ivars(State { + activation_policy, + default_menu, + activate_ignoring_other_apps, + }); + unsafe { msg_send_id![super(this), init] } } } diff --git a/src/platform_impl/macos/appkit/cursor.rs b/src/platform_impl/macos/appkit/cursor.rs index de83f0a2..0a2a5e32 100644 --- a/src/platform_impl/macos/appkit/cursor.rs +++ b/src/platform_impl/macos/appkit/cursor.rs @@ -1,8 +1,8 @@ use once_cell::sync::Lazy; -use icrate::ns_string; use icrate::Foundation::{ - NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize, NSString, + ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize, + NSString, }; use objc2::rc::{DefaultId, Id}; use objc2::runtime::Sel; diff --git a/src/platform_impl/macos/appkit/screen.rs b/src/platform_impl/macos/appkit/screen.rs index aa101239..732f06e3 100644 --- a/src/platform_impl/macos/appkit/screen.rs +++ b/src/platform_impl/macos/appkit/screen.rs @@ -1,5 +1,6 @@ -use icrate::ns_string; -use icrate::Foundation::{CGFloat, NSArray, NSDictionary, NSNumber, NSObject, NSRect, NSString}; +use icrate::Foundation::{ + ns_string, CGFloat, NSArray, NSDictionary, NSNumber, NSObject, NSRect, NSString, +}; use objc2::rc::Id; use objc2::runtime::AnyObject; use objc2::{extern_class, extern_methods, mutability, ClassType}; diff --git a/src/platform_impl/macos/menu.rs b/src/platform_impl/macos/menu.rs index 978e0bff..5f03e8ed 100644 --- a/src/platform_impl/macos/menu.rs +++ b/src/platform_impl/macos/menu.rs @@ -1,5 +1,4 @@ -use icrate::ns_string; -use icrate::Foundation::{NSProcessInfo, NSString}; +use icrate::Foundation::{ns_string, NSProcessInfo, NSString}; use objc2::rc::Id; use objc2::runtime::Sel; use objc2::sel; diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index bac77f9e..68f9a032 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -1,17 +1,16 @@ #![allow(clippy::unnecessary_cast)] -use std::boxed::Box; use std::cell::{Cell, RefCell}; use std::collections::{HashMap, VecDeque}; -use std::ptr::NonNull; use icrate::Foundation::{ NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, }; -use objc2::declare::{Ivar, IvarDrop}; use objc2::rc::{Id, WeakId}; use objc2::runtime::{AnyObject, Sel}; -use objc2::{class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType}; +use objc2::{ + class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass, +}; use super::{ appkit::{ @@ -140,18 +139,13 @@ 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, } declare_class!( - #[derive(Debug)] - #[allow(non_snake_case)] - pub(super) struct WinitView { - // Weak reference because the window keeps a strong reference to the view - _ns_window: IvarDrop>, "__ns_window">, - state: IvarDrop, "_state">, - } - - mod ivars; + pub(super) struct WinitView; unsafe impl ClassType for WinitView { #[inherits(NSResponder, NSObject)] @@ -160,73 +154,33 @@ declare_class!( const NAME: &'static str = "WinitView"; } - unsafe impl WinitView { - #[method(initWithId:acceptsFirstMouse:)] - unsafe fn init_with_id( - this: *mut Self, - window: &WinitWindow, - accepts_first_mouse: bool, - ) -> Option> { - let this: Option<&mut Self> = unsafe { msg_send![super(this), init] }; - this.map(|this| { - let state = ViewState { - accepts_first_mouse, - ..Default::default() - }; - - Ivar::write( - &mut this._ns_window, - Box::new(WeakId::new(&window.retain())), - ); - Ivar::write(&mut this.state, Box::new(state)); - - this.setPostsFrameChangedNotifications(true); - - let notification_center: &AnyObject = - unsafe { msg_send![class!(NSNotificationCenter), defaultCenter] }; - // About frame change - let frame_did_change_notification_name = - NSString::from_str("NSViewFrameDidChangeNotification"); - #[allow(clippy::let_unit_value)] - unsafe { - let _: () = msg_send![ - notification_center, - addObserver: &*this, - selector: sel!(frameDidChange:), - name: &*frame_did_change_notification_name, - object: &*this, - ]; - } - - *this.state.input_source.borrow_mut() = this.current_input_source(); - NonNull::from(this) - }) - } + impl DeclaredClass for WinitView { + type Ivars = ViewState; } unsafe impl WinitView { #[method(viewDidMoveToWindow)] fn view_did_move_to_window(&self) { trace_scope!("viewDidMoveToWindow"); - if let Some(tracking_rect) = self.state.tracking_rect.take() { + if let Some(tracking_rect) = self.ivars().tracking_rect.take() { self.removeTrackingRect(tracking_rect); } let rect = self.frame(); let tracking_rect = self.add_tracking_rect(rect, false); - self.state.tracking_rect.set(Some(tracking_rect)); + self.ivars().tracking_rect.set(Some(tracking_rect)); } #[method(frameDidChange:)] fn frame_did_change(&self, _event: &NSEvent) { trace_scope!("frameDidChange:"); - if let Some(tracking_rect) = self.state.tracking_rect.take() { + if let Some(tracking_rect) = self.ivars().tracking_rect.take() { self.removeTrackingRect(tracking_rect); } let rect = self.frame(); let tracking_rect = self.add_tracking_rect(rect, false); - self.state.tracking_rect.set(Some(tracking_rect)); + self.ivars().tracking_rect.set(Some(tracking_rect)); // Emit resize event here rather than from windowDidResize because: // 1. When a new window is created as a tab, the frame size may change without a window resize occurring. @@ -241,7 +195,7 @@ declare_class!( 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._ns_window.load() { + if let Some(window) = self.ivars()._ns_window.load() { AppState::handle_redraw(WindowId(window.id())); } @@ -270,7 +224,7 @@ declare_class!( fn reset_cursor_rects(&self) { trace_scope!("resetCursorRects"); let bounds = self.bounds(); - let cursor_state = self.state.cursor_state.borrow(); + let cursor_state = self.ivars().cursor_state.borrow(); // We correctly invoke `addCursorRect` only from inside `resetCursorRects` if cursor_state.visible { self.addCursorRect(bounds, &cursor_state.cursor); @@ -284,13 +238,13 @@ declare_class!( #[method(hasMarkedText)] fn has_marked_text(&self) -> bool { trace_scope!("hasMarkedText"); - self.state.marked_text.borrow().length() > 0 + self.ivars().marked_text.borrow().length() > 0 } #[method(markedRange)] fn marked_range(&self) -> NSRange { trace_scope!("markedRange"); - let length = self.state.marked_text.borrow().length(); + let length = self.ivars().marked_text.borrow().length(); if length > 0 { NSRange::new(0, length) } else { @@ -333,19 +287,19 @@ declare_class!( }; // Update marked text. - *self.state.marked_text.borrow_mut() = marked_text; + *self.ivars().marked_text.borrow_mut() = marked_text; // Notify IME is active if application still doesn't know it. - if self.state.ime_state.get() == ImeState::Disabled { - *self.state.input_source.borrow_mut() = self.current_input_source(); + if self.ivars().ime_state.get() == ImeState::Disabled { + *self.ivars().input_source.borrow_mut() = self.current_input_source(); self.queue_event(WindowEvent::Ime(Ime::Enabled)); } if self.hasMarkedText() { - self.state.ime_state.set(ImeState::Preedit); + self.ivars().ime_state.set(ImeState::Preedit); } else { // In case the preedit was cleared, set IME into the Ground state. - self.state.ime_state.set(ImeState::Ground); + self.ivars().ime_state.set(ImeState::Ground); } // Empty string basically means that there's no preedit, so indicate that by sending @@ -363,15 +317,15 @@ declare_class!( #[method(unmarkText)] fn unmark_text(&self) { trace_scope!("unmarkText"); - *self.state.marked_text.borrow_mut() = NSMutableAttributedString::new(); + *self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new(); let input_context = self.inputContext().expect("input context"); input_context.discardMarkedText(); self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None))); if self.is_ime_enabled() { - // Leave the Preedit self.state - self.state.ime_state.set(ImeState::Ground); + // Leave the Preedit self.ivars() + self.ivars().ime_state.set(ImeState::Ground); } else { warn!("Expected to have IME enabled when receiving unmarkText"); } @@ -410,9 +364,9 @@ declare_class!( let content_rect = window.contentRectForFrameRect(window.frame()); let base_x = content_rect.origin.x as f64; let base_y = (content_rect.origin.y + content_rect.size.height) as f64; - let x = base_x + self.state.ime_position.get().x; - let y = base_y - self.state.ime_position.get().y; - let LogicalSize { width, height } = self.state.ime_size.get(); + let x = base_x + self.ivars().ime_position.get().x; + let y = base_y - self.ivars().ime_position.get().y; + let LogicalSize { width, height } = self.ivars().ime_size.get(); NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(width, height)) } @@ -437,7 +391,7 @@ declare_class!( if self.hasMarkedText() && self.is_ime_enabled() && !is_control { self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None))); self.queue_event(WindowEvent::Ime(Ime::Commit(string))); - self.state.ime_state.set(ImeState::Commited); + self.ivars().ime_state.set(ImeState::Commited); } } @@ -449,15 +403,15 @@ declare_class!( // We shouldn't forward any character from just commited text, since we'll end up sending // it twice with some IMEs like Korean one. We'll also always send `Enter` in that case, // which is not desired given it was used to confirm IME input. - if self.state.ime_state.get() == ImeState::Commited { + if self.ivars().ime_state.get() == ImeState::Commited { return; } - self.state.forward_key_to_app.set(true); + self.ivars().forward_key_to_app.set(true); - if self.hasMarkedText() && self.state.ime_state.get() == ImeState::Preedit { + if self.hasMarkedText() && self.ivars().ime_state.get() == ImeState::Preedit { // Leave preedit so that we also report the key-up for this key. - self.state.ime_state.set(ImeState::Ground); + self.ivars().ime_state.set(ImeState::Ground); } } } @@ -467,19 +421,19 @@ declare_class!( fn key_down(&self, event: &NSEvent) { trace_scope!("keyDown:"); { - let mut prev_input_source = self.state.input_source.borrow_mut(); + let mut prev_input_source = self.ivars().input_source.borrow_mut(); let current_input_source = self.current_input_source(); if *prev_input_source != current_input_source && self.is_ime_enabled() { *prev_input_source = current_input_source; drop(prev_input_source); - self.state.ime_state.set(ImeState::Disabled); + self.ivars().ime_state.set(ImeState::Disabled); self.queue_event(WindowEvent::Ime(Ime::Disabled)); } } // Get the characters from the event. - let old_ime_state = self.state.ime_state.get(); - self.state.forward_key_to_app.set(false); + let old_ime_state = self.ivars().ime_state.get(); + self.ivars().forward_key_to_app.set(false); let event = replace_event(event, self.window().option_as_alt()); // The `interpretKeyEvents` function might call @@ -488,31 +442,31 @@ declare_class!( // we must send the `KeyboardInput` event during IME if it triggered // `doCommandBySelector`. (doCommandBySelector means that the keyboard input // is not handled by IME and should be handled by the application) - if self.state.ime_allowed.get() { + if self.ivars().ime_allowed.get() { let events_for_nsview = NSArray::from_slice(&[&*event]); unsafe { self.interpretKeyEvents(&events_for_nsview) }; // If the text was commited we must treat the next keyboard event as IME related. - if self.state.ime_state.get() == ImeState::Commited { + if self.ivars().ime_state.get() == ImeState::Commited { // Remove any marked text, so normal input can continue. - *self.state.marked_text.borrow_mut() = NSMutableAttributedString::new(); + *self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new(); } } self.update_modifiers(&event, false); - let had_ime_input = match self.state.ime_state.get() { + let had_ime_input = match self.ivars().ime_state.get() { ImeState::Commited => { // Allow normal input after the commit. - self.state.ime_state.set(ImeState::Ground); + self.ivars().ime_state.set(ImeState::Ground); true } ImeState::Preedit => true, // `key_down` could result in preedit clear, so compare old and current state. - _ => old_ime_state != self.state.ime_state.get(), + _ => old_ime_state != self.ivars().ime_state.get(), }; - if !had_ime_input || self.state.forward_key_to_app.get() { + if !had_ime_input || self.ivars().forward_key_to_app.get() { let key_event = create_key_event(&event, true, event.is_a_repeat(), None); self.queue_event(WindowEvent::KeyboardInput { device_id: DEVICE_ID, @@ -531,7 +485,7 @@ declare_class!( // We want to send keyboard input when we are currently in the ground state. if matches!( - self.state.ime_state.get(), + self.ivars().ime_state.get(), ImeState::Ground | ImeState::Disabled ) { self.queue_event(WindowEvent::KeyboardInput { @@ -792,20 +746,40 @@ declare_class!( #[method(acceptsFirstMouse:)] fn accepts_first_mouse(&self, _event: &NSEvent) -> bool { trace_scope!("acceptsFirstMouse:"); - self.state.accepts_first_mouse + self.ivars().accepts_first_mouse } } ); impl WinitView { pub(super) fn new(window: &WinitWindow, accepts_first_mouse: bool) -> Id { + let this = Self::alloc().set_ivars(ViewState { + accepts_first_mouse, + _ns_window: WeakId::new(&window.retain()), + ..Default::default() + }); + let this: Id = unsafe { msg_send_id![super(this), init] }; + + this.setPostsFrameChangedNotifications(true); + let notification_center: &AnyObject = + unsafe { msg_send![class!(NSNotificationCenter), defaultCenter] }; + // About frame change + let frame_did_change_notification_name = + NSString::from_str("NSViewFrameDidChangeNotification"); + #[allow(clippy::let_unit_value)] unsafe { - msg_send_id![ - Self::alloc(), - initWithId: window, - acceptsFirstMouse: accepts_first_mouse, - ] + let _: () = msg_send![ + notification_center, + addObserver: &*this, + selector: sel!(frameDidChange:), + name: &*frame_did_change_notification_name, + object: &*this, + ]; } + + *this.ivars().input_source.borrow_mut() = this.current_input_source(); + + this } fn window(&self) -> Id { @@ -814,7 +788,10 @@ impl WinitView { // (which is incompatible with `frameDidChange:`) // // unsafe { msg_send_id![self, window] } - self._ns_window.load().expect("view to have a window") + self.ivars() + ._ns_window + .load() + .expect("view to have a window") } fn window_id(&self) -> WindowId { @@ -842,7 +819,7 @@ impl WinitView { } fn is_ime_enabled(&self) -> bool { - !matches!(self.state.ime_state.get(), ImeState::Disabled) + !matches!(self.ivars().ime_state.get(), ImeState::Disabled) } fn current_input_source(&self) -> String { @@ -854,7 +831,7 @@ impl WinitView { } pub(super) fn set_cursor_icon(&self, icon: Id) { - let mut cursor_state = self.state.cursor_state.borrow_mut(); + let mut cursor_state = self.ivars().cursor_state.borrow_mut(); cursor_state.cursor = icon; } @@ -862,7 +839,7 @@ impl WinitView { /// /// Returns whether the state changed. pub(super) fn set_cursor_visible(&self, visible: bool) -> bool { - let mut cursor_state = self.state.cursor_state.borrow_mut(); + let mut cursor_state = self.ivars().cursor_state.borrow_mut(); if visible != cursor_state.visible { cursor_state.visible = visible; true @@ -872,19 +849,19 @@ impl WinitView { } pub(super) fn set_ime_allowed(&self, ime_allowed: bool) { - if self.state.ime_allowed.get() == ime_allowed { + if self.ivars().ime_allowed.get() == ime_allowed { return; } - self.state.ime_allowed.set(ime_allowed); - if self.state.ime_allowed.get() { + self.ivars().ime_allowed.set(ime_allowed); + if self.ivars().ime_allowed.get() { return; } // Clear markedText - *self.state.marked_text.borrow_mut() = NSMutableAttributedString::new(); + *self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new(); - if self.state.ime_state.get() != ImeState::Disabled { - self.state.ime_state.set(ImeState::Disabled); + if self.ivars().ime_state.get() != ImeState::Disabled { + self.ivars().ime_state.set(ImeState::Disabled); self.queue_event(WindowEvent::Ime(Ime::Disabled)); } } @@ -894,17 +871,17 @@ impl WinitView { position: LogicalPosition, size: LogicalSize, ) { - self.state.ime_position.set(position); - self.state.ime_size.set(size); + self.ivars().ime_position.set(position); + self.ivars().ime_size.set(size); let input_context = self.inputContext().expect("input context"); input_context.invalidateCharacterCoordinates(); } /// Reset modifiers and emit a synthetic ModifiersChanged event if deemed necessary. pub(super) fn reset_modifiers(&self) { - if !self.state.modifiers.get().state().is_empty() { - self.state.modifiers.set(Modifiers::default()); - self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers.get())); + if !self.ivars().modifiers.get().state().is_empty() { + self.ivars().modifiers.set(Modifiers::default()); + self.queue_event(WindowEvent::ModifiersChanged(self.ivars().modifiers.get())); } } @@ -913,8 +890,8 @@ impl WinitView { use ElementState::{Pressed, Released}; let current_modifiers = event_mods(ns_event); - let prev_modifiers = self.state.modifiers.get(); - self.state.modifiers.set(current_modifiers); + let prev_modifiers = self.ivars().modifiers.get(); + self.ivars().modifiers.set(current_modifiers); // This function was called form the flagsChanged event, which is triggered // when the user presses/releases a modifier even if the same kind of modifier @@ -943,7 +920,7 @@ impl WinitView { event.location = code_to_location(physical_key); let location_mask = ModLocationMask::from_location(event.location); - let mut phys_mod_state = self.state.phys_modifiers.borrow_mut(); + let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut(); let phys_mod = phys_mod_state .entry(key) .or_insert(ModLocationMask::empty()); @@ -1021,7 +998,7 @@ impl WinitView { return; } - self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers.get())); + self.queue_event(WindowEvent::ModifiersChanged(self.ivars().modifiers.get())); } fn mouse_click(&self, event: &NSEvent, button_state: ElementState) { diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 91918216..06ba38bd 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -3,8 +3,6 @@ use std::collections::VecDeque; use std::f64; use std::ops; -use std::os::raw::c_void; -use std::ptr::NonNull; use std::sync::{Mutex, MutexGuard}; use crate::{ @@ -36,9 +34,8 @@ use icrate::Foundation::{ CGFloat, MainThreadBound, MainThreadMarker, NSArray, NSCopying, NSInteger, NSObject, NSPoint, NSRect, NSSize, NSString, }; -use objc2::declare::{Ivar, IvarDrop}; use objc2::rc::{autoreleasepool, Id}; -use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType}; +use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass}; use super::appkit::{ NSApp, NSAppKitVersion, NSAppearance, NSApplicationPresentationOptions, NSBackingStoreType, @@ -58,7 +55,7 @@ pub(crate) struct Window { impl Drop for Window { fn drop(&mut self) { self.window - .get_on_main(|window, _| autoreleasepool(|_| window.close())) + .get_on_main(|window| autoreleasepool(|_| window.close())) } } @@ -86,7 +83,7 @@ impl Window { &self, f: impl FnOnce(&WinitWindow) -> R + Send, ) -> R { - self.window.get_on_main(|window, _mtm| f(window)) + self.window.get_on_main(|window| f(window)) } #[cfg(feature = "rwh_06")] @@ -159,12 +156,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes { declare_class!( #[derive(Debug)] - pub struct WinitWindow { - // TODO: Fix unnecessary boxing here - shared_state: IvarDrop>, "_shared_state">, - } - - mod ivars; + pub struct WinitWindow; unsafe impl ClassType for WinitWindow { #[inherits(NSResponder, NSObject)] @@ -173,38 +165,8 @@ declare_class!( const NAME: &'static str = "WinitWindow"; } - unsafe impl WinitWindow { - #[method(initWithContentRect:styleMask:state:)] - unsafe fn init( - this: *mut Self, - frame: NSRect, - mask: NSWindowStyleMask, - state: *mut c_void, - ) -> Option> { - let this: Option<&mut Self> = unsafe { - msg_send![ - super(this), - initWithContentRect: frame, - styleMask: mask, - backing: NSBackingStoreType::NSBackingStoreBuffered, - defer: false, - ] - }; - - this.map(|this| { - // SAFETY: The pointer originally came from `Box::into_raw`. - Ivar::write(&mut this.shared_state, unsafe { - Box::from_raw(state as *mut Mutex) - }); - - // It is imperative to correct memory management that we - // disable the extra release that would otherwise happen when - // calling `clone` on the window. - this.setReleasedWhenClosed(false); - - NonNull::from(this) - }) - } + impl DeclaredClass for WinitWindow { + type Ivars = Mutex; } unsafe impl WinitWindow { @@ -384,18 +346,23 @@ impl WinitWindow { ..Default::default() }; - // Pass the state through FFI to the method declared on the class - let state_ptr: *mut c_void = Box::into_raw(Box::new(Mutex::new(state))).cast(); + let this = WinitWindow::alloc().set_ivars(Mutex::new(state)); let this: Option> = unsafe { msg_send_id![ - WinitWindow::alloc(), + super(this), initWithContentRect: frame, styleMask: masks, - state: state_ptr, + backing: NSBackingStoreType::NSBackingStoreBuffered, + defer: false, ] }; let this = this?; + // It is very important for correct memory management that we + // disable the extra release that would otherwise happen when + // calling `close` on the window. + this.setReleasedWhenClosed(false); + let resize_increments = match attrs .resize_increments .map(|i| i.to_logical::(this.scale_factor())) @@ -576,7 +543,7 @@ impl WinitWindow { &self, called_from_fn: &'static str, ) -> SharedStateMutexGuard<'_> { - SharedStateMutexGuard::new(self.shared_state.lock().unwrap(), called_from_fn) + SharedStateMutexGuard::new(self.ivars().lock().unwrap(), called_from_fn) } fn set_style_mask(&self, mask: NSWindowStyleMask) { diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index fb6df1b4..e5a62dec 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1,12 +1,13 @@ #![allow(clippy::unnecessary_cast)] use std::cell::Cell; -use std::ptr::{self, NonNull}; +use std::ptr; use icrate::Foundation::{NSArray, NSObject, NSSize, NSString}; -use objc2::declare::{Ivar, IvarDrop}; use objc2::rc::{autoreleasepool, Id}; use objc2::runtime::AnyObject; -use objc2::{class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType}; +use objc2::{ + class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass, +}; use super::appkit::{ NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState, @@ -24,7 +25,9 @@ use crate::{ }; #[derive(Debug)] -pub struct State { +pub(crate) struct State { + window: Id, + // This is set when WindowBuilder::with_fullscreen was set, // see comments of `window_did_fail_to_enter_fullscreen` initial_fullscreen: Cell, @@ -37,17 +40,7 @@ pub struct State { } declare_class!( - #[derive(Debug)] - pub(crate) struct WinitWindowDelegate { - window: IvarDrop, "_window">, - - // TODO: It may be possible for delegate methods to be called - // asynchronously, causing data races panics? - // TODO: Remove unnecessary boxing here - state: IvarDrop, "_state">, - } - - mod ivars; + pub(crate) struct WinitWindowDelegate; unsafe impl ClassType for WinitWindowDelegate { type Super = NSObject; @@ -55,50 +48,8 @@ declare_class!( const NAME: &'static str = "WinitWindowDelegate"; } - unsafe impl WinitWindowDelegate { - #[method(initWithWindow:initialFullscreen:)] - unsafe fn init_with_winit( - this: *mut Self, - window: &WinitWindow, - initial_fullscreen: bool, - ) -> Option> { - let this: Option<&mut Self> = unsafe { msg_send![super(this), init] }; - this.map(|this| { - let scale_factor = window.scale_factor(); - - Ivar::write(&mut this.window, window.retain()); - Ivar::write( - &mut this.state, - Box::new(State { - initial_fullscreen: Cell::new(initial_fullscreen), - previous_position: Cell::new(None), - previous_scale_factor: Cell::new(scale_factor), - }), - ); - - if scale_factor != 1.0 { - this.queue_static_scale_factor_changed_event(); - } - this.window.setDelegate(Some(this)); - - // Enable theme change event - let notification_center: Id = - unsafe { msg_send_id![class!(NSDistributedNotificationCenter), defaultCenter] }; - let notification_name = - NSString::from_str("AppleInterfaceThemeChangedNotification"); - let _: () = unsafe { - msg_send![ - ¬ification_center, - addObserver: &*this - selector: sel!(effectiveAppearanceDidChange:) - name: &*notification_name - object: ptr::null::() - ] - }; - - NonNull::from(this) - }) - } + impl DeclaredClass for WinitWindowDelegate { + type Ivars = State; } // NSWindowDelegate + NSDraggingDestination protocols @@ -117,7 +68,7 @@ declare_class!( autoreleasepool(|_| { // Since El Capitan, we need to be careful that delegate methods can't // be called after the window closes. - self.window.setDelegate(None); + self.ivars().window.setDelegate(None); }); self.queue_event(WindowEvent::Destroyed); } @@ -134,16 +85,19 @@ declare_class!( trace_scope!("windowWillStartLiveResize:"); let increments = self + .ivars() .window .lock_shared_state("window_will_enter_fullscreen") .resize_increments; - self.window.set_resize_increments_inner(increments); + self.ivars().window.set_resize_increments_inner(increments); } #[method(windowDidEndLiveResize:)] fn window_did_end_live_resize(&self, _: Option<&AnyObject>) { trace_scope!("windowDidEndLiveResize:"); - self.window.set_resize_increments_inner(NSSize::new(1., 1.)); + self.ivars() + .window + .set_resize_increments_inner(NSSize::new(1., 1.)); } // This won't be triggered if the move was part of a resize. @@ -177,7 +131,7 @@ declare_class!( // NSWindowDelegate, and as a result a tracked modifiers state can quite // easily fall out of synchrony with reality. This requires us to emit // a synthetic ModifiersChanged event when we lose focus. - self.window.view().reset_modifiers(); + self.ivars().window.view().reset_modifiers(); self.queue_event(WindowEvent::Focused(false)); } @@ -246,9 +200,10 @@ declare_class!( trace_scope!("windowWillEnterFullScreen:"); let mut shared_state = self + .ivars() .window .lock_shared_state("window_will_enter_fullscreen"); - shared_state.maximized = self.window.is_zoomed(); + shared_state.maximized = self.ivars().window.is_zoomed(); let fullscreen = shared_state.fullscreen.as_ref(); match fullscreen { // Exclusive mode sets the state in `set_fullscreen` as the user @@ -262,7 +217,7 @@ declare_class!( // Otherwise, we must've reached fullscreen by the user clicking // on the green fullscreen button. Update state! None => { - let current_monitor = self.window.current_monitor_inner(); + let current_monitor = self.ivars().window.current_monitor_inner(); shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor)) } } @@ -274,7 +229,10 @@ declare_class!( fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowWillExitFullScreen:"); - let mut shared_state = self.window.lock_shared_state("window_will_exit_fullscreen"); + let mut shared_state = self + .ivars() + .window + .lock_shared_state("window_will_exit_fullscreen"); shared_state.in_fullscreen_transition = true; } @@ -295,6 +253,7 @@ declare_class!( // user-provided options are ignored in exclusive fullscreen. let mut options = proposed_options; let shared_state = self + .ivars() .window .lock_shared_state("window_will_use_fullscreen_presentation_options"); if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen { @@ -310,13 +269,16 @@ declare_class!( #[method(windowDidEnterFullScreen:)] fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowDidEnterFullScreen:"); - self.state.initial_fullscreen.set(false); - let mut shared_state = self.window.lock_shared_state("window_did_enter_fullscreen"); + self.ivars().initial_fullscreen.set(false); + let mut shared_state = self + .ivars() + .window + .lock_shared_state("window_did_enter_fullscreen"); shared_state.in_fullscreen_transition = false; let target_fullscreen = shared_state.target_fullscreen.take(); drop(shared_state); if let Some(target_fullscreen) = target_fullscreen { - self.window.set_fullscreen(target_fullscreen); + self.ivars().window.set_fullscreen(target_fullscreen); } } @@ -325,13 +287,16 @@ declare_class!( fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowDidExitFullScreen:"); - self.window.restore_state_from_fullscreen(); - let mut shared_state = self.window.lock_shared_state("window_did_exit_fullscreen"); + self.ivars().window.restore_state_from_fullscreen(); + let mut shared_state = self + .ivars() + .window + .lock_shared_state("window_did_exit_fullscreen"); shared_state.in_fullscreen_transition = false; let target_fullscreen = shared_state.target_fullscreen.take(); drop(shared_state); if let Some(target_fullscreen) = target_fullscreen { - self.window.set_fullscreen(target_fullscreen); + self.ivars().window.set_fullscreen(target_fullscreen); } } @@ -355,22 +320,23 @@ declare_class!( fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowDidFailToEnterFullScreen:"); let mut shared_state = self + .ivars() .window .lock_shared_state("window_did_fail_to_enter_fullscreen"); shared_state.in_fullscreen_transition = false; shared_state.target_fullscreen = None; - if self.state.initial_fullscreen.get() { + if self.ivars().initial_fullscreen.get() { #[allow(clippy::let_unit_value)] unsafe { let _: () = msg_send![ - &*self.window, + &*self.ivars().window, performSelector: sel!(toggleFullScreen:), withObject: ptr::null::(), afterDelay: 0.5, ]; }; } else { - self.window.restore_state_from_fullscreen(); + self.ivars().window.restore_state_from_fullscreen(); } } @@ -380,6 +346,7 @@ declare_class!( trace_scope!("windowDidChangeOcclusionState:"); self.queue_event(WindowEvent::Occluded( !self + .ivars() .window .occlusionState() .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible), @@ -404,6 +371,7 @@ declare_class!( fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&AnyObject>) { let theme = get_ns_theme(); let mut shared_state = self + .ivars() .window .lock_shared_state("effective_appearance_did_change"); let current_theme = shared_state.current_theme; @@ -418,12 +386,13 @@ declare_class!( fn window_did_change_screen(&self, _: Option<&AnyObject>) { trace_scope!("windowDidChangeScreen:"); let is_simple_fullscreen = self + .ivars() .window .lock_shared_state("window_did_change_screen") .is_simple_fullscreen; if is_simple_fullscreen { - if let Some(screen) = self.window.screen() { - self.window.setFrame_display(screen.frame(), true); + if let Some(screen) = self.ivars().window.screen() { + self.ivars().window.setFrame_display(screen.frame(), true); } } } @@ -432,52 +401,74 @@ declare_class!( impl WinitWindowDelegate { pub fn new(window: &WinitWindow, initial_fullscreen: bool) -> Id { - unsafe { - msg_send_id![ - Self::alloc(), - initWithWindow: window, - initialFullscreen: initial_fullscreen, - ] + let scale_factor = window.scale_factor(); + let this = Self::alloc().set_ivars(State { + window: window.retain(), + initial_fullscreen: Cell::new(initial_fullscreen), + previous_position: Cell::new(None), + previous_scale_factor: Cell::new(scale_factor), + }); + let this: Id = unsafe { msg_send_id![super(this), init] }; + + if scale_factor != 1.0 { + this.queue_static_scale_factor_changed_event(); } + this.ivars().window.setDelegate(Some(&this)); + + // Enable theme change event + let notification_center: Id = + unsafe { msg_send_id![class!(NSDistributedNotificationCenter), defaultCenter] }; + let notification_name = NSString::from_str("AppleInterfaceThemeChangedNotification"); + let _: () = unsafe { + msg_send![ + ¬ification_center, + addObserver: &*this + selector: sel!(effectiveAppearanceDidChange:) + name: &*notification_name + object: ptr::null::() + ] + }; + + this } pub(crate) fn queue_event(&self, event: WindowEvent) { let event = Event::WindowEvent { - window_id: WindowId(self.window.id()), + window_id: WindowId(self.ivars().window.id()), event, }; AppState::queue_event(event); } fn queue_static_scale_factor_changed_event(&self) { - let scale_factor = self.window.scale_factor(); - if scale_factor == self.state.previous_scale_factor.get() { + let scale_factor = self.ivars().window.scale_factor(); + if scale_factor == self.ivars().previous_scale_factor.get() { return; }; - self.state.previous_scale_factor.set(scale_factor); + self.ivars().previous_scale_factor.set(scale_factor); let suggested_size = self.view_size(); AppState::queue_static_scale_factor_changed_event( - self.window.clone(), + self.ivars().window.clone(), suggested_size.to_physical(scale_factor), scale_factor, ); } fn emit_move_event(&self) { - let rect = self.window.frame(); + let rect = self.ivars().window.frame(); let x = rect.origin.x as f64; let y = util::bottom_left_to_top_left(rect); - if self.state.previous_position.get() != Some((x, y)) { - self.state.previous_position.set(Some((x, y))); - let scale_factor = self.window.scale_factor(); + if self.ivars().previous_position.get() != Some((x, y)) { + self.ivars().previous_position.set(Some((x, y))); + let scale_factor = self.ivars().window.scale_factor(); let physical_pos = LogicalPosition::::from((x, y)).to_physical(scale_factor); self.queue_event(WindowEvent::Moved(physical_pos)); } } fn view_size(&self) -> LogicalSize { - let size = self.window.contentView().frame().size; + let size = self.ivars().window.contentView().frame().size; LogicalSize::new(size.width as f64, size.height as f64) } }