Update objc2 and icrate versions (#3256)

This commit is contained in:
Mads Marquart 2023-12-23 18:04:24 +01:00 committed by GitHub
parent 745cfaab2c
commit 7d5bee767c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 286 additions and 374 deletions

View file

@ -86,13 +86,13 @@ ndk-sys = "0.5.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
core-foundation = "0.9.3" core-foundation = "0.9.3"
objc2 = "0.4.1" objc2 = "0.5.0"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.23.1" core-graphics = "0.23.1"
[target.'cfg(target_os = "macos")'.dependencies.icrate] [target.'cfg(target_os = "macos")'.dependencies.icrate]
version = "0.0.4" version = "0.1.0"
features = [ features = [
"dispatch", "dispatch",
"Foundation", "Foundation",
@ -108,7 +108,7 @@ features = [
] ]
[target.'cfg(target_os = "ios")'.dependencies.icrate] [target.'cfg(target_os = "ios")'.dependencies.icrate]
version = "0.0.4" version = "0.1.0"
features = [ features = [
"dispatch", "dispatch",
"Foundation", "Foundation",

View file

@ -8,6 +8,7 @@ use std::{
use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSInteger}; use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSInteger};
use objc2::mutability::IsRetainable; use objc2::mutability::IsRetainable;
use objc2::rc::Id; use objc2::rc::Id;
use objc2::Message;
use super::uikit::{UIScreen, UIScreenMode}; use super::uikit::{UIScreen, UIScreenMode};
use crate::{ use crate::{
@ -20,16 +21,15 @@ use crate::{
#[derive(Debug)] #[derive(Debug)]
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Id<T>>); struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Id<T>>);
impl<T: IsRetainable> Clone for MainThreadBoundDelegateImpls<T> { impl<T: IsRetainable + Message> Clone for MainThreadBoundDelegateImpls<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self( Self(MainThreadMarker::run_on_main(|mtm| {
self.0 MainThreadBound::new(Id::clone(self.0.get(mtm)), mtm)
.get_on_main(|inner, mtm| MainThreadBound::new(Id::clone(inner), mtm)), }))
)
} }
} }
impl<T: IsRetainable> hash::Hash for MainThreadBoundDelegateImpls<T> { impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
fn hash<H: hash::Hasher>(&self, state: &mut H) { fn hash<H: hash::Hasher>(&self, state: &mut H) {
// SAFETY: Marker only used to get the pointer // SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() }; let mtm = unsafe { MainThreadMarker::new_unchecked() };
@ -37,7 +37,7 @@ impl<T: IsRetainable> hash::Hash for MainThreadBoundDelegateImpls<T> {
} }
} }
impl<T: IsRetainable> PartialEq for MainThreadBoundDelegateImpls<T> { impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
// SAFETY: Marker only used to get the pointer // SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() }; let mtm = unsafe { MainThreadMarker::new_unchecked() };
@ -45,7 +45,7 @@ impl<T: IsRetainable> PartialEq for MainThreadBoundDelegateImpls<T> {
} }
} }
impl<T: IsRetainable> Eq for MainThreadBoundDelegateImpls<T> {} impl<T: IsRetainable + Message> Eq for MainThreadBoundDelegateImpls<T> {}
#[derive(Debug, PartialEq, Eq, Hash, Clone)] #[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct VideoMode { pub struct VideoMode {
@ -100,11 +100,9 @@ pub struct MonitorHandle {
impl Clone for MonitorHandle { impl Clone for MonitorHandle {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { MainThreadMarker::run_on_main(|mtm| Self {
ui_screen: self ui_screen: MainThreadBound::new(self.ui_screen.get(mtm).clone(), mtm),
.ui_screen })
.get_on_main(|inner, mtm| MainThreadBound::new(inner.clone(), mtm)),
}
} }
} }
@ -168,16 +166,16 @@ impl MonitorHandle {
} }
pub fn name(&self) -> Option<String> { pub fn name(&self) -> Option<String> {
self.ui_screen.get_on_main(|ui_screen, mtm| { MainThreadMarker::run_on_main(|mtm| {
let main = UIScreen::main(mtm); let main = UIScreen::main(mtm);
if *ui_screen == main { if *self.ui_screen(mtm) == main {
Some("Primary".to_string()) Some("Primary".to_string())
} else if *ui_screen == main.mirroredScreen() { } else if *self.ui_screen(mtm) == main.mirroredScreen() {
Some("Mirrored".to_string()) Some("Mirrored".to_string())
} else { } else {
UIScreen::screens(mtm) UIScreen::screens(mtm)
.iter() .iter()
.position(|rhs| rhs == &**ui_screen) .position(|rhs| rhs == &**self.ui_screen(mtm))
.map(|idx| idx.to_string()) .map(|idx| idx.to_string())
} }
}) })
@ -186,31 +184,32 @@ impl MonitorHandle {
pub fn size(&self) -> PhysicalSize<u32> { pub fn size(&self) -> PhysicalSize<u32> {
let bounds = self let bounds = self
.ui_screen .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) PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
} }
pub fn position(&self) -> PhysicalPosition<i32> { pub fn position(&self) -> PhysicalPosition<i32> {
let bounds = self let bounds = self
.ui_screen .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() (bounds.origin.x as f64, bounds.origin.y as f64).into()
} }
pub fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f64 {
self.ui_screen 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<u32> { pub fn refresh_rate_millihertz(&self) -> Option<u32> {
Some( Some(
self.ui_screen 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<Item = VideoMode> { pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
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 // Use Ord impl of RootVideoMode
let modes: BTreeSet<_> = ui_screen let modes: BTreeSet<_> = ui_screen
@ -230,8 +229,12 @@ impl MonitorHandle {
} }
pub fn preferred_video_mode(&self) -> VideoMode { pub fn preferred_video_mode(&self) -> VideoMode {
self.ui_screen.get_on_main(|ui_screen, mtm| { MainThreadMarker::run_on_main(|mtm| {
VideoMode::new(ui_screen.clone(), ui_screen.preferredMode().unwrap(), mtm) VideoMode::new(
self.ui_screen(mtm).clone(),
self.ui_screen(mtm).preferredMode().unwrap(),
mtm,
)
}) })
} }
} }

View file

@ -1,12 +1,12 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::cell::Cell; use std::cell::Cell;
use std::ptr::NonNull;
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSObjectProtocol, NSSet}; use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSObjectProtocol, NSSet};
use objc2::declare::{Ivar, IvarDrop};
use objc2::rc::Id; use objc2::rc::Id;
use objc2::runtime::AnyClass; 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::app_state::{self, EventWrapper};
use super::uikit::{ use super::uikit::{
@ -37,6 +37,8 @@ declare_class!(
const NAME: &'static str = "WinitUIView"; const NAME: &'static str = "WinitUIView";
} }
impl DeclaredClass for WinitView {}
unsafe impl WinitView { unsafe impl WinitView {
#[method(drawRect:)] #[method(drawRect:)]
fn draw_rect(&self, rect: CGRect) { fn draw_rect(&self, rect: CGRect) {
@ -274,11 +276,7 @@ pub struct ViewControllerState {
} }
declare_class!( declare_class!(
pub(crate) struct WinitViewController { pub(crate) struct WinitViewController;
state: IvarDrop<Box<ViewControllerState>, "_state">,
}
mod view_controller_ivars;
unsafe impl ClassType for WinitViewController { unsafe impl ClassType for WinitViewController {
#[inherits(UIResponder, NSObject)] #[inherits(UIResponder, NSObject)]
@ -287,28 +285,8 @@ declare_class!(
const NAME: &'static str = "WinitUIViewController"; const NAME: &'static str = "WinitUIViewController";
} }
unsafe impl WinitViewController { impl DeclaredClass for WinitViewController {
#[method(init)] type Ivars = ViewControllerState;
unsafe fn init(this: *mut Self) -> Option<NonNull<Self>> {
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)
})
}
} }
unsafe impl WinitViewController { unsafe impl WinitViewController {
@ -319,27 +297,27 @@ declare_class!(
#[method(prefersStatusBarHidden)] #[method(prefersStatusBarHidden)]
fn prefers_status_bar_hidden(&self) -> bool { fn prefers_status_bar_hidden(&self) -> bool {
self.state.prefers_status_bar_hidden.get() self.ivars().prefers_status_bar_hidden.get()
} }
#[method(preferredStatusBarStyle)] #[method(preferredStatusBarStyle)]
fn preferred_status_bar_style(&self) -> UIStatusBarStyle { fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
self.state.preferred_status_bar_style.get() self.ivars().preferred_status_bar_style.get()
} }
#[method(prefersHomeIndicatorAutoHidden)] #[method(prefersHomeIndicatorAutoHidden)]
fn prefers_home_indicator_auto_hidden(&self) -> bool { 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)] #[method(supportedInterfaceOrientations)]
fn supported_orientations(&self) -> UIInterfaceOrientationMask { fn supported_orientations(&self) -> UIInterfaceOrientationMask {
self.state.supported_orientations.get() self.ivars().supported_orientations.get()
} }
#[method(preferredScreenEdgesDeferringSystemGestures)] #[method(preferredScreenEdgesDeferringSystemGestures)]
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge { fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
self.state self.ivars()
.preferred_screen_edges_deferring_system_gestures .preferred_screen_edges_deferring_system_gestures
.get() .get()
} }
@ -348,17 +326,17 @@ declare_class!(
impl WinitViewController { impl WinitViewController {
pub(crate) fn set_prefers_status_bar_hidden(&self, val: bool) { 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(); self.setNeedsStatusBarAppearanceUpdate();
} }
pub(crate) fn set_preferred_status_bar_style(&self, val: UIStatusBarStyle) { 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(); self.setNeedsStatusBarAppearanceUpdate();
} }
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) { 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(); let os_capabilities = app_state::os_capabilities();
if os_capabilities.home_indicator_hidden { if os_capabilities.home_indicator_hidden {
self.setNeedsUpdateOfHomeIndicatorAutoHidden(); self.setNeedsUpdateOfHomeIndicatorAutoHidden();
@ -368,7 +346,7 @@ impl WinitViewController {
} }
pub(crate) fn set_preferred_screen_edges_deferring_system_gestures(&self, val: UIRectEdge) { pub(crate) fn set_preferred_screen_edges_deferring_system_gestures(&self, val: UIRectEdge) {
self.state self.ivars()
.preferred_screen_edges_deferring_system_gestures .preferred_screen_edges_deferring_system_gestures
.set(val); .set(val);
let os_capabilities = app_state::os_capabilities(); let os_capabilities = app_state::os_capabilities();
@ -401,7 +379,7 @@ impl WinitViewController {
| UIInterfaceOrientationMask::PortraitUpsideDown | UIInterfaceOrientationMask::PortraitUpsideDown
} }
}; };
self.state.supported_orientations.set(mask); self.ivars().supported_orientations.set(mask);
UIViewController::attemptRotationToDeviceOrientation(); UIViewController::attemptRotationToDeviceOrientation();
} }
@ -411,7 +389,15 @@ impl WinitViewController {
platform_attributes: &PlatformSpecificWindowBuilderAttributes, platform_attributes: &PlatformSpecificWindowBuilderAttributes,
view: &UIView, view: &UIView,
) -> Id<Self> { ) -> Id<Self> {
let this: Id<Self> = 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<Self> = unsafe { msg_send_id![super(this), init] };
this.set_prefers_status_bar_hidden(platform_attributes.prefers_status_bar_hidden); this.set_prefers_status_bar_hidden(platform_attributes.prefers_status_bar_hidden);
@ -446,6 +432,8 @@ declare_class!(
const NAME: &'static str = "WinitUIWindow"; const NAME: &'static str = "WinitUIWindow";
} }
impl DeclaredClass for WinitUIWindow {}
unsafe impl WinitUIWindow { unsafe impl WinitUIWindow {
#[method(becomeKeyWindow)] #[method(becomeKeyWindow)]
fn become_key_window(&self) { fn become_key_window(&self) {
@ -518,6 +506,8 @@ declare_class!(
const NAME: &'static str = "WinitApplicationDelegate"; const NAME: &'static str = "WinitApplicationDelegate";
} }
impl DeclaredClass for WinitApplicationDelegate {}
// UIApplicationDelegate protocol // UIApplicationDelegate protocol
unsafe impl WinitApplicationDelegate { unsafe impl WinitApplicationDelegate {
#[method(application:didFinishLaunchingWithOptions:)] #[method(application:didFinishLaunchingWithOptions:)]

View file

@ -520,7 +520,7 @@ impl Window {
} }
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Inner) -> R + Send) -> R { pub(crate) fn maybe_wait_on_main<R: Send>(&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")] #[cfg(feature = "rwh_06")]

View file

@ -1,14 +1,13 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use icrate::Foundation::NSObject; 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::appkit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
use super::{app_state::AppState, DEVICE_ID}; use super::{app_state::AppState, DEVICE_ID};
use crate::event::{DeviceEvent, ElementState, Event}; use crate::event::{DeviceEvent, ElementState, Event};
declare_class!( declare_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(super) struct WinitApplication; pub(super) struct WinitApplication;
unsafe impl ClassType for WinitApplication { unsafe impl ClassType for WinitApplication {
@ -18,6 +17,8 @@ declare_class!(
const NAME: &'static str = "WinitApplication"; const NAME: &'static str = "WinitApplication";
} }
impl DeclaredClass for WinitApplication {}
unsafe impl WinitApplication { unsafe impl WinitApplication {
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key. // 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) // Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)

View file

@ -1,23 +1,20 @@
use std::ptr::NonNull;
use icrate::Foundation::NSObject; use icrate::Foundation::NSObject;
use objc2::declare::{IvarBool, IvarEncode};
use objc2::rc::Id; use objc2::rc::Id;
use objc2::runtime::AnyObject; 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::app_state::AppState;
use super::appkit::NSApplicationActivationPolicy; use super::appkit::NSApplicationActivationPolicy;
declare_class!( #[derive(Debug)]
#[derive(Debug)] pub(super) struct State {
pub(super) struct ApplicationDelegate { activation_policy: NSApplicationActivationPolicy,
activation_policy: IvarEncode<NSApplicationActivationPolicy, "_activation_policy">, default_menu: bool,
default_menu: IvarBool<"_default_menu">, activate_ignoring_other_apps: bool,
activate_ignoring_other_apps: IvarBool<"_activate_ignoring_other_apps">, }
}
mod ivars; declare_class!(
pub(super) struct ApplicationDelegate;
unsafe impl ClassType for ApplicationDelegate { unsafe impl ClassType for ApplicationDelegate {
type Super = NSObject; type Super = NSObject;
@ -25,30 +22,18 @@ declare_class!(
const NAME: &'static str = "WinitApplicationDelegate"; const NAME: &'static str = "WinitApplicationDelegate";
} }
unsafe impl ApplicationDelegate { impl DeclaredClass for ApplicationDelegate {
#[method(initWithActivationPolicy:defaultMenu:activateIgnoringOtherApps:)] type Ivars = State;
unsafe fn init( }
this: *mut Self,
activation_policy: NSApplicationActivationPolicy,
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Option<NonNull<Self>> {
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)
})
}
unsafe impl ApplicationDelegate {
#[method(applicationDidFinishLaunching:)] #[method(applicationDidFinishLaunching:)]
fn did_finish_launching(&self, _sender: Option<&AnyObject>) { fn did_finish_launching(&self, _sender: Option<&AnyObject>) {
trace_scope!("applicationDidFinishLaunching:"); trace_scope!("applicationDidFinishLaunching:");
AppState::launched( AppState::launched(
*self.activation_policy, self.ivars().activation_policy,
*self.default_menu, self.ivars().default_menu,
*self.activate_ignoring_other_apps, self.ivars().activate_ignoring_other_apps,
); );
} }
@ -67,13 +52,11 @@ impl ApplicationDelegate {
default_menu: bool, default_menu: bool,
activate_ignoring_other_apps: bool, activate_ignoring_other_apps: bool,
) -> Id<Self> { ) -> Id<Self> {
unsafe { let this = Self::alloc().set_ivars(State {
msg_send_id![ activation_policy,
Self::alloc(), default_menu,
initWithActivationPolicy: activation_policy, activate_ignoring_other_apps,
defaultMenu: default_menu, });
activateIgnoringOtherApps: activate_ignoring_other_apps, unsafe { msg_send_id![super(this), init] }
]
}
} }
} }

View file

@ -1,8 +1,8 @@
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use icrate::ns_string;
use icrate::Foundation::{ 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::rc::{DefaultId, Id};
use objc2::runtime::Sel; use objc2::runtime::Sel;

View file

@ -1,5 +1,6 @@
use icrate::ns_string; use icrate::Foundation::{
use icrate::Foundation::{CGFloat, NSArray, NSDictionary, NSNumber, NSObject, NSRect, NSString}; ns_string, CGFloat, NSArray, NSDictionary, NSNumber, NSObject, NSRect, NSString,
};
use objc2::rc::Id; use objc2::rc::Id;
use objc2::runtime::AnyObject; use objc2::runtime::AnyObject;
use objc2::{extern_class, extern_methods, mutability, ClassType}; use objc2::{extern_class, extern_methods, mutability, ClassType};

View file

@ -1,5 +1,4 @@
use icrate::ns_string; use icrate::Foundation::{ns_string, NSProcessInfo, NSString};
use icrate::Foundation::{NSProcessInfo, NSString};
use objc2::rc::Id; use objc2::rc::Id;
use objc2::runtime::Sel; use objc2::runtime::Sel;
use objc2::sel; use objc2::sel;

View file

@ -1,17 +1,16 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::boxed::Box;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use std::ptr::NonNull;
use icrate::Foundation::{ use icrate::Foundation::{
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
}; };
use objc2::declare::{Ivar, IvarDrop};
use objc2::rc::{Id, WeakId}; use objc2::rc::{Id, WeakId};
use objc2::runtime::{AnyObject, Sel}; 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::{ use super::{
appkit::{ appkit::{
@ -140,18 +139,13 @@ pub struct ViewState {
marked_text: RefCell<Id<NSMutableAttributedString>>, marked_text: RefCell<Id<NSMutableAttributedString>>,
accepts_first_mouse: bool, accepts_first_mouse: bool,
// Weak reference because the window keeps a strong reference to the view
_ns_window: WeakId<WinitWindow>,
} }
declare_class!( declare_class!(
#[derive(Debug)] pub(super) struct WinitView;
#[allow(non_snake_case)]
pub(super) struct WinitView {
// Weak reference because the window keeps a strong reference to the view
_ns_window: IvarDrop<Box<WeakId<WinitWindow>>, "__ns_window">,
state: IvarDrop<Box<ViewState>, "_state">,
}
mod ivars;
unsafe impl ClassType for WinitView { unsafe impl ClassType for WinitView {
#[inherits(NSResponder, NSObject)] #[inherits(NSResponder, NSObject)]
@ -160,73 +154,33 @@ declare_class!(
const NAME: &'static str = "WinitView"; const NAME: &'static str = "WinitView";
} }
unsafe impl WinitView { impl DeclaredClass for WinitView {
#[method(initWithId:acceptsFirstMouse:)] type Ivars = ViewState;
unsafe fn init_with_id(
this: *mut Self,
window: &WinitWindow,
accepts_first_mouse: bool,
) -> Option<NonNull<Self>> {
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)
})
}
} }
unsafe impl WinitView { unsafe impl WinitView {
#[method(viewDidMoveToWindow)] #[method(viewDidMoveToWindow)]
fn view_did_move_to_window(&self) { fn view_did_move_to_window(&self) {
trace_scope!("viewDidMoveToWindow"); 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); self.removeTrackingRect(tracking_rect);
} }
let rect = self.frame(); let rect = self.frame();
let tracking_rect = self.add_tracking_rect(rect, false); 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:)] #[method(frameDidChange:)]
fn frame_did_change(&self, _event: &NSEvent) { fn frame_did_change(&self, _event: &NSEvent) {
trace_scope!("frameDidChange:"); 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); self.removeTrackingRect(tracking_rect);
} }
let rect = self.frame(); let rect = self.frame();
let tracking_rect = self.add_tracking_rect(rect, false); 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: // 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. // 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:"); trace_scope!("drawRect:");
// It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`. // 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())); AppState::handle_redraw(WindowId(window.id()));
} }
@ -270,7 +224,7 @@ declare_class!(
fn reset_cursor_rects(&self) { fn reset_cursor_rects(&self) {
trace_scope!("resetCursorRects"); trace_scope!("resetCursorRects");
let bounds = self.bounds(); 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` // We correctly invoke `addCursorRect` only from inside `resetCursorRects`
if cursor_state.visible { if cursor_state.visible {
self.addCursorRect(bounds, &cursor_state.cursor); self.addCursorRect(bounds, &cursor_state.cursor);
@ -284,13 +238,13 @@ declare_class!(
#[method(hasMarkedText)] #[method(hasMarkedText)]
fn has_marked_text(&self) -> bool { fn has_marked_text(&self) -> bool {
trace_scope!("hasMarkedText"); trace_scope!("hasMarkedText");
self.state.marked_text.borrow().length() > 0 self.ivars().marked_text.borrow().length() > 0
} }
#[method(markedRange)] #[method(markedRange)]
fn marked_range(&self) -> NSRange { fn marked_range(&self) -> NSRange {
trace_scope!("markedRange"); trace_scope!("markedRange");
let length = self.state.marked_text.borrow().length(); let length = self.ivars().marked_text.borrow().length();
if length > 0 { if length > 0 {
NSRange::new(0, length) NSRange::new(0, length)
} else { } else {
@ -333,19 +287,19 @@ declare_class!(
}; };
// Update marked text. // 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. // Notify IME is active if application still doesn't know it.
if self.state.ime_state.get() == ImeState::Disabled { if self.ivars().ime_state.get() == ImeState::Disabled {
*self.state.input_source.borrow_mut() = self.current_input_source(); *self.ivars().input_source.borrow_mut() = self.current_input_source();
self.queue_event(WindowEvent::Ime(Ime::Enabled)); self.queue_event(WindowEvent::Ime(Ime::Enabled));
} }
if self.hasMarkedText() { if self.hasMarkedText() {
self.state.ime_state.set(ImeState::Preedit); self.ivars().ime_state.set(ImeState::Preedit);
} else { } else {
// In case the preedit was cleared, set IME into the Ground state. // 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 // Empty string basically means that there's no preedit, so indicate that by sending
@ -363,15 +317,15 @@ declare_class!(
#[method(unmarkText)] #[method(unmarkText)]
fn unmark_text(&self) { fn unmark_text(&self) {
trace_scope!("unmarkText"); 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"); let input_context = self.inputContext().expect("input context");
input_context.discardMarkedText(); input_context.discardMarkedText();
self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None))); self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None)));
if self.is_ime_enabled() { if self.is_ime_enabled() {
// Leave the Preedit self.state // Leave the Preedit self.ivars()
self.state.ime_state.set(ImeState::Ground); self.ivars().ime_state.set(ImeState::Ground);
} else { } else {
warn!("Expected to have IME enabled when receiving unmarkText"); warn!("Expected to have IME enabled when receiving unmarkText");
} }
@ -410,9 +364,9 @@ declare_class!(
let content_rect = window.contentRectForFrameRect(window.frame()); let content_rect = window.contentRectForFrameRect(window.frame());
let base_x = content_rect.origin.x as f64; let base_x = content_rect.origin.x as f64;
let base_y = (content_rect.origin.y + content_rect.size.height) 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 x = base_x + self.ivars().ime_position.get().x;
let y = base_y - self.state.ime_position.get().y; let y = base_y - self.ivars().ime_position.get().y;
let LogicalSize { width, height } = self.state.ime_size.get(); let LogicalSize { width, height } = self.ivars().ime_size.get();
NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(width, height)) 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 { 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::Preedit(String::new(), None)));
self.queue_event(WindowEvent::Ime(Ime::Commit(string))); 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 // 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, // 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. // 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; 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. // 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) { fn key_down(&self, event: &NSEvent) {
trace_scope!("keyDown:"); 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(); let current_input_source = self.current_input_source();
if *prev_input_source != current_input_source && self.is_ime_enabled() { if *prev_input_source != current_input_source && self.is_ime_enabled() {
*prev_input_source = current_input_source; *prev_input_source = current_input_source;
drop(prev_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)); self.queue_event(WindowEvent::Ime(Ime::Disabled));
} }
} }
// Get the characters from the event. // Get the characters from the event.
let old_ime_state = self.state.ime_state.get(); let old_ime_state = self.ivars().ime_state.get();
self.state.forward_key_to_app.set(false); self.ivars().forward_key_to_app.set(false);
let event = replace_event(event, self.window().option_as_alt()); let event = replace_event(event, self.window().option_as_alt());
// The `interpretKeyEvents` function might call // The `interpretKeyEvents` function might call
@ -488,31 +442,31 @@ declare_class!(
// we must send the `KeyboardInput` event during IME if it triggered // we must send the `KeyboardInput` event during IME if it triggered
// `doCommandBySelector`. (doCommandBySelector means that the keyboard input // `doCommandBySelector`. (doCommandBySelector means that the keyboard input
// is not handled by IME and should be handled by the application) // 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]); let events_for_nsview = NSArray::from_slice(&[&*event]);
unsafe { self.interpretKeyEvents(&events_for_nsview) }; unsafe { self.interpretKeyEvents(&events_for_nsview) };
// If the text was commited we must treat the next keyboard event as IME related. // 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. // 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); 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 => { ImeState::Commited => {
// Allow normal input after the commit. // Allow normal input after the commit.
self.state.ime_state.set(ImeState::Ground); self.ivars().ime_state.set(ImeState::Ground);
true true
} }
ImeState::Preedit => true, ImeState::Preedit => true,
// `key_down` could result in preedit clear, so compare old and current state. // `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); let key_event = create_key_event(&event, true, event.is_a_repeat(), None);
self.queue_event(WindowEvent::KeyboardInput { self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID, device_id: DEVICE_ID,
@ -531,7 +485,7 @@ declare_class!(
// We want to send keyboard input when we are currently in the ground state. // We want to send keyboard input when we are currently in the ground state.
if matches!( if matches!(
self.state.ime_state.get(), self.ivars().ime_state.get(),
ImeState::Ground | ImeState::Disabled ImeState::Ground | ImeState::Disabled
) { ) {
self.queue_event(WindowEvent::KeyboardInput { self.queue_event(WindowEvent::KeyboardInput {
@ -792,20 +746,40 @@ declare_class!(
#[method(acceptsFirstMouse:)] #[method(acceptsFirstMouse:)]
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool { fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
trace_scope!("acceptsFirstMouse:"); trace_scope!("acceptsFirstMouse:");
self.state.accepts_first_mouse self.ivars().accepts_first_mouse
} }
} }
); );
impl WinitView { impl WinitView {
pub(super) fn new(window: &WinitWindow, accepts_first_mouse: bool) -> Id<Self> { pub(super) fn new(window: &WinitWindow, accepts_first_mouse: bool) -> Id<Self> {
let this = Self::alloc().set_ivars(ViewState {
accepts_first_mouse,
_ns_window: WeakId::new(&window.retain()),
..Default::default()
});
let this: Id<Self> = 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 { unsafe {
msg_send_id![ let _: () = msg_send![
Self::alloc(), notification_center,
initWithId: window, addObserver: &*this,
acceptsFirstMouse: accepts_first_mouse, 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<WinitWindow> { fn window(&self) -> Id<WinitWindow> {
@ -814,7 +788,10 @@ impl WinitView {
// (which is incompatible with `frameDidChange:`) // (which is incompatible with `frameDidChange:`)
// //
// unsafe { msg_send_id![self, window] } // 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 { fn window_id(&self) -> WindowId {
@ -842,7 +819,7 @@ impl WinitView {
} }
fn is_ime_enabled(&self) -> bool { 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 { fn current_input_source(&self) -> String {
@ -854,7 +831,7 @@ impl WinitView {
} }
pub(super) fn set_cursor_icon(&self, icon: Id<NSCursor>) { pub(super) fn set_cursor_icon(&self, icon: Id<NSCursor>) {
let mut cursor_state = self.state.cursor_state.borrow_mut(); let mut cursor_state = self.ivars().cursor_state.borrow_mut();
cursor_state.cursor = icon; cursor_state.cursor = icon;
} }
@ -862,7 +839,7 @@ impl WinitView {
/// ///
/// Returns whether the state changed. /// Returns whether the state changed.
pub(super) fn set_cursor_visible(&self, visible: bool) -> bool { 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 { if visible != cursor_state.visible {
cursor_state.visible = visible; cursor_state.visible = visible;
true true
@ -872,19 +849,19 @@ impl WinitView {
} }
pub(super) fn set_ime_allowed(&self, ime_allowed: bool) { 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; return;
} }
self.state.ime_allowed.set(ime_allowed); self.ivars().ime_allowed.set(ime_allowed);
if self.state.ime_allowed.get() { if self.ivars().ime_allowed.get() {
return; return;
} }
// Clear markedText // 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 { if self.ivars().ime_state.get() != ImeState::Disabled {
self.state.ime_state.set(ImeState::Disabled); self.ivars().ime_state.set(ImeState::Disabled);
self.queue_event(WindowEvent::Ime(Ime::Disabled)); self.queue_event(WindowEvent::Ime(Ime::Disabled));
} }
} }
@ -894,17 +871,17 @@ impl WinitView {
position: LogicalPosition<f64>, position: LogicalPosition<f64>,
size: LogicalSize<f64>, size: LogicalSize<f64>,
) { ) {
self.state.ime_position.set(position); self.ivars().ime_position.set(position);
self.state.ime_size.set(size); self.ivars().ime_size.set(size);
let input_context = self.inputContext().expect("input context"); let input_context = self.inputContext().expect("input context");
input_context.invalidateCharacterCoordinates(); input_context.invalidateCharacterCoordinates();
} }
/// Reset modifiers and emit a synthetic ModifiersChanged event if deemed necessary. /// Reset modifiers and emit a synthetic ModifiersChanged event if deemed necessary.
pub(super) fn reset_modifiers(&self) { pub(super) fn reset_modifiers(&self) {
if !self.state.modifiers.get().state().is_empty() { if !self.ivars().modifiers.get().state().is_empty() {
self.state.modifiers.set(Modifiers::default()); self.ivars().modifiers.set(Modifiers::default());
self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers.get())); self.queue_event(WindowEvent::ModifiersChanged(self.ivars().modifiers.get()));
} }
} }
@ -913,8 +890,8 @@ impl WinitView {
use ElementState::{Pressed, Released}; use ElementState::{Pressed, Released};
let current_modifiers = event_mods(ns_event); let current_modifiers = event_mods(ns_event);
let prev_modifiers = self.state.modifiers.get(); let prev_modifiers = self.ivars().modifiers.get();
self.state.modifiers.set(current_modifiers); self.ivars().modifiers.set(current_modifiers);
// This function was called form the flagsChanged event, which is triggered // 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 // 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); event.location = code_to_location(physical_key);
let location_mask = ModLocationMask::from_location(event.location); 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 let phys_mod = phys_mod_state
.entry(key) .entry(key)
.or_insert(ModLocationMask::empty()); .or_insert(ModLocationMask::empty());
@ -1021,7 +998,7 @@ impl WinitView {
return; 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) { fn mouse_click(&self, event: &NSEvent, button_state: ElementState) {

View file

@ -3,8 +3,6 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use std::f64; use std::f64;
use std::ops; use std::ops;
use std::os::raw::c_void;
use std::ptr::NonNull;
use std::sync::{Mutex, MutexGuard}; use std::sync::{Mutex, MutexGuard};
use crate::{ use crate::{
@ -36,9 +34,8 @@ use icrate::Foundation::{
CGFloat, MainThreadBound, MainThreadMarker, NSArray, NSCopying, NSInteger, NSObject, NSPoint, CGFloat, MainThreadBound, MainThreadMarker, NSArray, NSCopying, NSInteger, NSObject, NSPoint,
NSRect, NSSize, NSString, NSRect, NSSize, NSString,
}; };
use objc2::declare::{Ivar, IvarDrop};
use objc2::rc::{autoreleasepool, Id}; 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::{ use super::appkit::{
NSApp, NSAppKitVersion, NSAppearance, NSApplicationPresentationOptions, NSBackingStoreType, NSApp, NSAppKitVersion, NSAppearance, NSApplicationPresentationOptions, NSBackingStoreType,
@ -58,7 +55,7 @@ pub(crate) struct Window {
impl Drop for Window { impl Drop for Window {
fn drop(&mut self) { fn drop(&mut self) {
self.window self.window
.get_on_main(|window, _| autoreleasepool(|_| window.close())) .get_on_main(|window| autoreleasepool(|_| window.close()))
} }
} }
@ -86,7 +83,7 @@ impl Window {
&self, &self,
f: impl FnOnce(&WinitWindow) -> R + Send, f: impl FnOnce(&WinitWindow) -> R + Send,
) -> R { ) -> R {
self.window.get_on_main(|window, _mtm| f(window)) self.window.get_on_main(|window| f(window))
} }
#[cfg(feature = "rwh_06")] #[cfg(feature = "rwh_06")]
@ -159,12 +156,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
declare_class!( declare_class!(
#[derive(Debug)] #[derive(Debug)]
pub struct WinitWindow { pub struct WinitWindow;
// TODO: Fix unnecessary boxing here
shared_state: IvarDrop<Box<Mutex<SharedState>>, "_shared_state">,
}
mod ivars;
unsafe impl ClassType for WinitWindow { unsafe impl ClassType for WinitWindow {
#[inherits(NSResponder, NSObject)] #[inherits(NSResponder, NSObject)]
@ -173,38 +165,8 @@ declare_class!(
const NAME: &'static str = "WinitWindow"; const NAME: &'static str = "WinitWindow";
} }
unsafe impl WinitWindow { impl DeclaredClass for WinitWindow {
#[method(initWithContentRect:styleMask:state:)] type Ivars = Mutex<SharedState>;
unsafe fn init(
this: *mut Self,
frame: NSRect,
mask: NSWindowStyleMask,
state: *mut c_void,
) -> Option<NonNull<Self>> {
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<SharedState>)
});
// 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)
})
}
} }
unsafe impl WinitWindow { unsafe impl WinitWindow {
@ -384,18 +346,23 @@ impl WinitWindow {
..Default::default() ..Default::default()
}; };
// Pass the state through FFI to the method declared on the class let this = WinitWindow::alloc().set_ivars(Mutex::new(state));
let state_ptr: *mut c_void = Box::into_raw(Box::new(Mutex::new(state))).cast();
let this: Option<Id<Self>> = unsafe { let this: Option<Id<Self>> = unsafe {
msg_send_id![ msg_send_id![
WinitWindow::alloc(), super(this),
initWithContentRect: frame, initWithContentRect: frame,
styleMask: masks, styleMask: masks,
state: state_ptr, backing: NSBackingStoreType::NSBackingStoreBuffered,
defer: false,
] ]
}; };
let this = this?; 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 let resize_increments = match attrs
.resize_increments .resize_increments
.map(|i| i.to_logical::<f64>(this.scale_factor())) .map(|i| i.to_logical::<f64>(this.scale_factor()))
@ -576,7 +543,7 @@ impl WinitWindow {
&self, &self,
called_from_fn: &'static str, called_from_fn: &'static str,
) -> SharedStateMutexGuard<'_> { ) -> 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) { fn set_style_mask(&self, mask: NSWindowStyleMask) {

View file

@ -1,12 +1,13 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::cell::Cell; use std::cell::Cell;
use std::ptr::{self, NonNull}; use std::ptr;
use icrate::Foundation::{NSArray, NSObject, NSSize, NSString}; use icrate::Foundation::{NSArray, NSObject, NSSize, NSString};
use objc2::declare::{Ivar, IvarDrop};
use objc2::rc::{autoreleasepool, Id}; use objc2::rc::{autoreleasepool, Id};
use objc2::runtime::AnyObject; 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::{ use super::appkit::{
NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState, NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState,
@ -24,7 +25,9 @@ use crate::{
}; };
#[derive(Debug)] #[derive(Debug)]
pub struct State { pub(crate) struct State {
window: Id<WinitWindow>,
// This is set when WindowBuilder::with_fullscreen was set, // This is set when WindowBuilder::with_fullscreen was set,
// see comments of `window_did_fail_to_enter_fullscreen` // see comments of `window_did_fail_to_enter_fullscreen`
initial_fullscreen: Cell<bool>, initial_fullscreen: Cell<bool>,
@ -37,17 +40,7 @@ pub struct State {
} }
declare_class!( declare_class!(
#[derive(Debug)] pub(crate) struct WinitWindowDelegate;
pub(crate) struct WinitWindowDelegate {
window: IvarDrop<Id<WinitWindow>, "_window">,
// TODO: It may be possible for delegate methods to be called
// asynchronously, causing data races panics?
// TODO: Remove unnecessary boxing here
state: IvarDrop<Box<State>, "_state">,
}
mod ivars;
unsafe impl ClassType for WinitWindowDelegate { unsafe impl ClassType for WinitWindowDelegate {
type Super = NSObject; type Super = NSObject;
@ -55,50 +48,8 @@ declare_class!(
const NAME: &'static str = "WinitWindowDelegate"; const NAME: &'static str = "WinitWindowDelegate";
} }
unsafe impl WinitWindowDelegate { impl DeclaredClass for WinitWindowDelegate {
#[method(initWithWindow:initialFullscreen:)] type Ivars = State;
unsafe fn init_with_winit(
this: *mut Self,
window: &WinitWindow,
initial_fullscreen: bool,
) -> Option<NonNull<Self>> {
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<AnyObject> =
unsafe { msg_send_id![class!(NSDistributedNotificationCenter), defaultCenter] };
let notification_name =
NSString::from_str("AppleInterfaceThemeChangedNotification");
let _: () = unsafe {
msg_send![
&notification_center,
addObserver: &*this
selector: sel!(effectiveAppearanceDidChange:)
name: &*notification_name
object: ptr::null::<AnyObject>()
]
};
NonNull::from(this)
})
}
} }
// NSWindowDelegate + NSDraggingDestination protocols // NSWindowDelegate + NSDraggingDestination protocols
@ -117,7 +68,7 @@ declare_class!(
autoreleasepool(|_| { autoreleasepool(|_| {
// Since El Capitan, we need to be careful that delegate methods can't // Since El Capitan, we need to be careful that delegate methods can't
// be called after the window closes. // be called after the window closes.
self.window.setDelegate(None); self.ivars().window.setDelegate(None);
}); });
self.queue_event(WindowEvent::Destroyed); self.queue_event(WindowEvent::Destroyed);
} }
@ -134,16 +85,19 @@ declare_class!(
trace_scope!("windowWillStartLiveResize:"); trace_scope!("windowWillStartLiveResize:");
let increments = self let increments = self
.ivars()
.window .window
.lock_shared_state("window_will_enter_fullscreen") .lock_shared_state("window_will_enter_fullscreen")
.resize_increments; .resize_increments;
self.window.set_resize_increments_inner(increments); self.ivars().window.set_resize_increments_inner(increments);
} }
#[method(windowDidEndLiveResize:)] #[method(windowDidEndLiveResize:)]
fn window_did_end_live_resize(&self, _: Option<&AnyObject>) { fn window_did_end_live_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidEndLiveResize:"); 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. // 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 // NSWindowDelegate, and as a result a tracked modifiers state can quite
// easily fall out of synchrony with reality. This requires us to emit // easily fall out of synchrony with reality. This requires us to emit
// a synthetic ModifiersChanged event when we lose focus. // 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)); self.queue_event(WindowEvent::Focused(false));
} }
@ -246,9 +200,10 @@ declare_class!(
trace_scope!("windowWillEnterFullScreen:"); trace_scope!("windowWillEnterFullScreen:");
let mut shared_state = self let mut shared_state = self
.ivars()
.window .window
.lock_shared_state("window_will_enter_fullscreen"); .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(); let fullscreen = shared_state.fullscreen.as_ref();
match fullscreen { match fullscreen {
// Exclusive mode sets the state in `set_fullscreen` as the user // 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 // Otherwise, we must've reached fullscreen by the user clicking
// on the green fullscreen button. Update state! // on the green fullscreen button. Update state!
None => { 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)) shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor))
} }
} }
@ -274,7 +229,10 @@ declare_class!(
fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) { fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillExitFullScreen:"); 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; shared_state.in_fullscreen_transition = true;
} }
@ -295,6 +253,7 @@ declare_class!(
// user-provided options are ignored in exclusive fullscreen. // user-provided options are ignored in exclusive fullscreen.
let mut options = proposed_options; let mut options = proposed_options;
let shared_state = self let shared_state = self
.ivars()
.window .window
.lock_shared_state("window_will_use_fullscreen_presentation_options"); .lock_shared_state("window_will_use_fullscreen_presentation_options");
if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen { if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen {
@ -310,13 +269,16 @@ declare_class!(
#[method(windowDidEnterFullScreen:)] #[method(windowDidEnterFullScreen:)]
fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) { fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidEnterFullScreen:"); trace_scope!("windowDidEnterFullScreen:");
self.state.initial_fullscreen.set(false); self.ivars().initial_fullscreen.set(false);
let mut shared_state = self.window.lock_shared_state("window_did_enter_fullscreen"); let mut shared_state = self
.ivars()
.window
.lock_shared_state("window_did_enter_fullscreen");
shared_state.in_fullscreen_transition = false; shared_state.in_fullscreen_transition = false;
let target_fullscreen = shared_state.target_fullscreen.take(); let target_fullscreen = shared_state.target_fullscreen.take();
drop(shared_state); drop(shared_state);
if let Some(target_fullscreen) = target_fullscreen { 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>) { fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidExitFullScreen:"); trace_scope!("windowDidExitFullScreen:");
self.window.restore_state_from_fullscreen(); self.ivars().window.restore_state_from_fullscreen();
let mut shared_state = self.window.lock_shared_state("window_did_exit_fullscreen"); let mut shared_state = self
.ivars()
.window
.lock_shared_state("window_did_exit_fullscreen");
shared_state.in_fullscreen_transition = false; shared_state.in_fullscreen_transition = false;
let target_fullscreen = shared_state.target_fullscreen.take(); let target_fullscreen = shared_state.target_fullscreen.take();
drop(shared_state); drop(shared_state);
if let Some(target_fullscreen) = target_fullscreen { 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>) { fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidFailToEnterFullScreen:"); trace_scope!("windowDidFailToEnterFullScreen:");
let mut shared_state = self let mut shared_state = self
.ivars()
.window .window
.lock_shared_state("window_did_fail_to_enter_fullscreen"); .lock_shared_state("window_did_fail_to_enter_fullscreen");
shared_state.in_fullscreen_transition = false; shared_state.in_fullscreen_transition = false;
shared_state.target_fullscreen = None; shared_state.target_fullscreen = None;
if self.state.initial_fullscreen.get() { if self.ivars().initial_fullscreen.get() {
#[allow(clippy::let_unit_value)] #[allow(clippy::let_unit_value)]
unsafe { unsafe {
let _: () = msg_send![ let _: () = msg_send![
&*self.window, &*self.ivars().window,
performSelector: sel!(toggleFullScreen:), performSelector: sel!(toggleFullScreen:),
withObject: ptr::null::<AnyObject>(), withObject: ptr::null::<AnyObject>(),
afterDelay: 0.5, afterDelay: 0.5,
]; ];
}; };
} else { } else {
self.window.restore_state_from_fullscreen(); self.ivars().window.restore_state_from_fullscreen();
} }
} }
@ -380,6 +346,7 @@ declare_class!(
trace_scope!("windowDidChangeOcclusionState:"); trace_scope!("windowDidChangeOcclusionState:");
self.queue_event(WindowEvent::Occluded( self.queue_event(WindowEvent::Occluded(
!self !self
.ivars()
.window .window
.occlusionState() .occlusionState()
.contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible), .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible),
@ -404,6 +371,7 @@ declare_class!(
fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&AnyObject>) { fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&AnyObject>) {
let theme = get_ns_theme(); let theme = get_ns_theme();
let mut shared_state = self let mut shared_state = self
.ivars()
.window .window
.lock_shared_state("effective_appearance_did_change"); .lock_shared_state("effective_appearance_did_change");
let current_theme = shared_state.current_theme; let current_theme = shared_state.current_theme;
@ -418,12 +386,13 @@ declare_class!(
fn window_did_change_screen(&self, _: Option<&AnyObject>) { fn window_did_change_screen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeScreen:"); trace_scope!("windowDidChangeScreen:");
let is_simple_fullscreen = self let is_simple_fullscreen = self
.ivars()
.window .window
.lock_shared_state("window_did_change_screen") .lock_shared_state("window_did_change_screen")
.is_simple_fullscreen; .is_simple_fullscreen;
if is_simple_fullscreen { if is_simple_fullscreen {
if let Some(screen) = self.window.screen() { if let Some(screen) = self.ivars().window.screen() {
self.window.setFrame_display(screen.frame(), true); self.ivars().window.setFrame_display(screen.frame(), true);
} }
} }
} }
@ -432,52 +401,74 @@ declare_class!(
impl WinitWindowDelegate { impl WinitWindowDelegate {
pub fn new(window: &WinitWindow, initial_fullscreen: bool) -> Id<Self> { pub fn new(window: &WinitWindow, initial_fullscreen: bool) -> Id<Self> {
unsafe { let scale_factor = window.scale_factor();
msg_send_id![ let this = Self::alloc().set_ivars(State {
Self::alloc(), window: window.retain(),
initWithWindow: window, initial_fullscreen: Cell::new(initial_fullscreen),
initialFullscreen: initial_fullscreen, previous_position: Cell::new(None),
] previous_scale_factor: Cell::new(scale_factor),
});
let this: Id<Self> = 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<AnyObject> =
unsafe { msg_send_id![class!(NSDistributedNotificationCenter), defaultCenter] };
let notification_name = NSString::from_str("AppleInterfaceThemeChangedNotification");
let _: () = unsafe {
msg_send![
&notification_center,
addObserver: &*this
selector: sel!(effectiveAppearanceDidChange:)
name: &*notification_name
object: ptr::null::<AnyObject>()
]
};
this
} }
pub(crate) fn queue_event(&self, event: WindowEvent) { pub(crate) fn queue_event(&self, event: WindowEvent) {
let event = Event::WindowEvent { let event = Event::WindowEvent {
window_id: WindowId(self.window.id()), window_id: WindowId(self.ivars().window.id()),
event, event,
}; };
AppState::queue_event(event); AppState::queue_event(event);
} }
fn queue_static_scale_factor_changed_event(&self) { fn queue_static_scale_factor_changed_event(&self) {
let scale_factor = self.window.scale_factor(); let scale_factor = self.ivars().window.scale_factor();
if scale_factor == self.state.previous_scale_factor.get() { if scale_factor == self.ivars().previous_scale_factor.get() {
return; return;
}; };
self.state.previous_scale_factor.set(scale_factor); self.ivars().previous_scale_factor.set(scale_factor);
let suggested_size = self.view_size(); let suggested_size = self.view_size();
AppState::queue_static_scale_factor_changed_event( AppState::queue_static_scale_factor_changed_event(
self.window.clone(), self.ivars().window.clone(),
suggested_size.to_physical(scale_factor), suggested_size.to_physical(scale_factor),
scale_factor, scale_factor,
); );
} }
fn emit_move_event(&self) { fn emit_move_event(&self) {
let rect = self.window.frame(); let rect = self.ivars().window.frame();
let x = rect.origin.x as f64; let x = rect.origin.x as f64;
let y = util::bottom_left_to_top_left(rect); let y = util::bottom_left_to_top_left(rect);
if self.state.previous_position.get() != Some((x, y)) { if self.ivars().previous_position.get() != Some((x, y)) {
self.state.previous_position.set(Some((x, y))); self.ivars().previous_position.set(Some((x, y)));
let scale_factor = self.window.scale_factor(); let scale_factor = self.ivars().window.scale_factor();
let physical_pos = LogicalPosition::<f64>::from((x, y)).to_physical(scale_factor); let physical_pos = LogicalPosition::<f64>::from((x, y)).to_physical(scale_factor);
self.queue_event(WindowEvent::Moved(physical_pos)); self.queue_event(WindowEvent::Moved(physical_pos));
} }
} }
fn view_size(&self) -> LogicalSize<f64> { fn view_size(&self) -> LogicalSize<f64> {
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) LogicalSize::new(size.width as f64, size.height as f64)
} }
} }