From 29419d6c388db8e3d7df0a501d408fb3a878222a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Fri, 2 Sep 2022 21:02:40 +0200 Subject: [PATCH] Refactor macOS cursor code (#2463) --- src/platform_impl/macos/appkit/cursor.rs | 241 +++++++++++++++++++++++ src/platform_impl/macos/appkit/image.rs | 37 ++++ src/platform_impl/macos/appkit/mod.rs | 7 + src/platform_impl/macos/appkit/view.rs | 20 +- src/platform_impl/macos/util/cursor.rs | 165 ---------------- src/platform_impl/macos/util/mod.rs | 3 +- src/platform_impl/macos/view.rs | 42 ++-- src/platform_impl/macos/window.rs | 59 +++--- 8 files changed, 352 insertions(+), 222 deletions(-) create mode 100644 src/platform_impl/macos/appkit/cursor.rs create mode 100644 src/platform_impl/macos/appkit/image.rs delete mode 100644 src/platform_impl/macos/util/cursor.rs diff --git a/src/platform_impl/macos/appkit/cursor.rs b/src/platform_impl/macos/appkit/cursor.rs new file mode 100644 index 00000000..2ec700db --- /dev/null +++ b/src/platform_impl/macos/appkit/cursor.rs @@ -0,0 +1,241 @@ +use once_cell::sync::Lazy; + +use objc2::foundation::{NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSString}; +use objc2::rc::{DefaultId, Id, Shared}; +use objc2::runtime::Sel; +use objc2::{extern_class, extern_methods, msg_send_id, ns_string, ClassType}; + +use super::NSImage; +use crate::window::CursorIcon; + +extern_class!( + /// + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSCursor; + + unsafe impl ClassType for NSCursor { + type Super = NSObject; + } +); + +// SAFETY: NSCursor is immutable, stated here: +// https://developer.apple.com/documentation/appkit/nscursor/1527062-image?language=objc +unsafe impl Send for NSCursor {} +unsafe impl Sync for NSCursor {} + +macro_rules! def_cursor { + {$( + $(#[$($m:meta)*])* + pub fn $name:ident(); + )*} => {$( + $(#[$($m)*])* + pub fn $name() -> Id { + unsafe { msg_send_id![Self::class(), $name] } + } + )*}; +} + +macro_rules! def_undocumented_cursor { + {$( + $(#[$($m:meta)*])* + pub fn $name:ident(); + )*} => {$( + $(#[$($m)*])* + pub fn $name() -> Id { + unsafe { Self::from_selector(sel!($name)).unwrap_or_else(|| Default::default()) } + } + )*}; +} + +extern_methods!( + /// Documented cursors + unsafe impl NSCursor { + def_cursor!( + pub fn arrowCursor(); + pub fn pointingHandCursor(); + pub fn openHandCursor(); + pub fn closedHandCursor(); + pub fn IBeamCursor(); + pub fn IBeamCursorForVerticalLayout(); + pub fn dragCopyCursor(); + pub fn dragLinkCursor(); + pub fn operationNotAllowedCursor(); + pub fn contextualMenuCursor(); + pub fn crosshairCursor(); + pub fn resizeRightCursor(); + pub fn resizeUpCursor(); + pub fn resizeLeftCursor(); + pub fn resizeDownCursor(); + pub fn resizeLeftRightCursor(); + pub fn resizeUpDownCursor(); + ); + + // Creating cursors should be thread-safe, though using them for anything probably isn't. + pub fn new(image: &NSImage, hotSpot: NSPoint) -> Id { + let this = unsafe { msg_send_id![Self::class(), alloc] }; + unsafe { msg_send_id![this, initWithImage: image, hotSpot: hotSpot] } + } + + pub fn invisible() -> Id { + // 16x16 GIF data for invisible cursor + // You can reproduce this via ImageMagick. + // $ convert -size 16x16 xc:none cursor.gif + static CURSOR_BYTES: &[u8] = &[ + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9, + 0xCB, 0xED, 0x0F, 0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B, + ]; + + static CURSOR: Lazy> = Lazy::new(|| { + // TODO: Consider using `dataWithBytesNoCopy:` + let data = NSData::with_bytes(CURSOR_BYTES); + let image = NSImage::new_with_data(&data); + NSCursor::new(&image, NSPoint::new(0.0, 0.0)) + }); + + CURSOR.clone() + } + } + + /// Undocumented cursors + unsafe impl NSCursor { + #[sel(respondsToSelector:)] + fn class_responds_to(sel: Sel) -> bool; + + unsafe fn from_selector_unchecked(sel: Sel) -> Id { + unsafe { msg_send_id![Self::class(), performSelector: sel] } + } + + unsafe fn from_selector(sel: Sel) -> Option> { + if Self::class_responds_to(sel) { + Some(unsafe { Self::from_selector_unchecked(sel) }) + } else { + warn!("Cursor `{:?}` appears to be invalid", sel); + None + } + } + + def_undocumented_cursor!( + // Undocumented cursors: https://stackoverflow.com/a/46635398/5435443 + pub fn _helpCursor(); + pub fn _zoomInCursor(); + pub fn _zoomOutCursor(); + pub fn _windowResizeNorthEastCursor(); + pub fn _windowResizeNorthWestCursor(); + pub fn _windowResizeSouthEastCursor(); + pub fn _windowResizeSouthWestCursor(); + pub fn _windowResizeNorthEastSouthWestCursor(); + pub fn _windowResizeNorthWestSouthEastCursor(); + + // While these two are available, the former just loads a white arrow, + // and the latter loads an ugly deflated beachball! + // pub fn _moveCursor(); + // pub fn _waitCursor(); + + // An even more undocumented cursor... + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349 + pub fn busyButClickableCursor(); + ); + } + + /// Webkit cursors + unsafe impl NSCursor { + // Note that loading `busybutclickable` with this code won't animate + // the frames; instead you'll just get them all in a column. + unsafe fn load_webkit_cursor(name: &NSString) -> Id { + // Snatch a cursor from WebKit; They fit the style of the native + // cursors, and will seem completely standard to macOS users. + // + // https://stackoverflow.com/a/21786835/5435443 + let root = ns_string!("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors"); + let cursor_path = root.join_path(name); + + let pdf_path = cursor_path.join_path(ns_string!("cursor.pdf")); + let image = NSImage::new_by_referencing_file(&pdf_path); + + // TODO: Handle PLists better + let info_path = cursor_path.join_path(ns_string!("info.plist")); + let info: Id, Shared> = unsafe { + msg_send_id![ + >::class(), + dictionaryWithContentsOfFile: &*info_path, + ] + }; + let mut x = 0.0; + if let Some(n) = info.get(&*ns_string!("hotx")) { + if n.is_kind_of::() { + let ptr: *const NSObject = n; + let ptr: *const NSNumber = ptr.cast(); + x = unsafe { &*ptr }.as_cgfloat() + } + } + let mut y = 0.0; + if let Some(n) = info.get(&*ns_string!("hotx")) { + if n.is_kind_of::() { + let ptr: *const NSObject = n; + let ptr: *const NSNumber = ptr.cast(); + y = unsafe { &*ptr }.as_cgfloat() + } + } + + let hotspot = NSPoint::new(x, y); + Self::new(&image, hotspot) + } + + pub fn moveCursor() -> Id { + unsafe { Self::load_webkit_cursor(ns_string!("move")) } + } + + pub fn cellCursor() -> Id { + unsafe { Self::load_webkit_cursor(ns_string!("cell")) } + } + } +); + +impl NSCursor { + pub fn from_icon(icon: CursorIcon) -> Id { + match icon { + CursorIcon::Default => Default::default(), + CursorIcon::Arrow => Self::arrowCursor(), + CursorIcon::Hand => Self::pointingHandCursor(), + CursorIcon::Grab => Self::openHandCursor(), + CursorIcon::Grabbing => Self::closedHandCursor(), + CursorIcon::Text => Self::IBeamCursor(), + CursorIcon::VerticalText => Self::IBeamCursorForVerticalLayout(), + CursorIcon::Copy => Self::dragCopyCursor(), + CursorIcon::Alias => Self::dragLinkCursor(), + CursorIcon::NotAllowed | CursorIcon::NoDrop => Self::operationNotAllowedCursor(), + CursorIcon::ContextMenu => Self::contextualMenuCursor(), + CursorIcon::Crosshair => Self::crosshairCursor(), + CursorIcon::EResize => Self::resizeRightCursor(), + CursorIcon::NResize => Self::resizeUpCursor(), + CursorIcon::WResize => Self::resizeLeftCursor(), + CursorIcon::SResize => Self::resizeDownCursor(), + CursorIcon::EwResize | CursorIcon::ColResize => Self::resizeLeftRightCursor(), + CursorIcon::NsResize | CursorIcon::RowResize => Self::resizeUpDownCursor(), + CursorIcon::Help => Self::_helpCursor(), + CursorIcon::ZoomIn => Self::_zoomInCursor(), + CursorIcon::ZoomOut => Self::_zoomOutCursor(), + CursorIcon::NeResize => Self::_windowResizeNorthEastCursor(), + CursorIcon::NwResize => Self::_windowResizeNorthWestCursor(), + CursorIcon::SeResize => Self::_windowResizeSouthEastCursor(), + CursorIcon::SwResize => Self::_windowResizeSouthWestCursor(), + CursorIcon::NeswResize => Self::_windowResizeNorthEastSouthWestCursor(), + CursorIcon::NwseResize => Self::_windowResizeNorthWestSouthEastCursor(), + // This is the wrong semantics for `Wait`, but it's the same as + // what's used in Safari and Chrome. + CursorIcon::Wait | CursorIcon::Progress => Self::busyButClickableCursor(), + CursorIcon::Move | CursorIcon::AllScroll => Self::moveCursor(), + CursorIcon::Cell => Self::cellCursor(), + } + } +} + +impl DefaultId for NSCursor { + type Ownership = Shared; + + fn default_id() -> Id { + Self::arrowCursor() + } +} diff --git a/src/platform_impl/macos/appkit/image.rs b/src/platform_impl/macos/appkit/image.rs new file mode 100644 index 00000000..af5674d3 --- /dev/null +++ b/src/platform_impl/macos/appkit/image.rs @@ -0,0 +1,37 @@ +use objc2::foundation::{NSData, NSObject, NSString}; +use objc2::rc::{Id, Shared}; +use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; + +extern_class!( + // TODO: Can this be mutable? + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSImage; + + unsafe impl ClassType for NSImage { + type Super = NSObject; + } +); + +// Documented Thread-Unsafe, but: +// > One thread can create an NSImage object, draw to the image buffer, +// > and pass it off to the main thread for drawing. The underlying image +// > cache is shared among all threads. +// +// +// So really only unsafe to mutate on several threads. +unsafe impl Send for NSImage {} +unsafe impl Sync for NSImage {} + +extern_methods!( + unsafe impl NSImage { + pub fn new_by_referencing_file(path: &NSString) -> Id { + let this = unsafe { msg_send_id![Self::class(), alloc] }; + unsafe { msg_send_id![this, initByReferencingFile: path] } + } + + pub fn new_with_data(data: &NSData) -> Id { + let this = unsafe { msg_send_id![Self::class(), alloc] }; + unsafe { msg_send_id![this, initWithData: data] } + } + } +); diff --git a/src/platform_impl/macos/appkit/mod.rs b/src/platform_impl/macos/appkit/mod.rs index 9690ae43..02c3effd 100644 --- a/src/platform_impl/macos/appkit/mod.rs +++ b/src/platform_impl/macos/appkit/mod.rs @@ -1,11 +1,18 @@ #![deny(unsafe_op_in_unsafe_fn)] +// Objective-C methods have different conventions, and it's much easier to +// understand if we just use the same names +#![allow(non_snake_case)] mod application; +mod cursor; +mod image; mod responder; mod view; mod window; pub(crate) use self::application::NSApplication; +pub(crate) use self::cursor::NSCursor; +pub(crate) use self::image::NSImage; pub(crate) use self::responder::NSResponder; pub(crate) use self::view::NSView; pub(crate) use self::window::NSWindow; diff --git a/src/platform_impl/macos/appkit/view.rs b/src/platform_impl/macos/appkit/view.rs index 9f64f5d6..a66720e2 100644 --- a/src/platform_impl/macos/appkit/view.rs +++ b/src/platform_impl/macos/appkit/view.rs @@ -1,7 +1,7 @@ -use objc2::foundation::NSObject; -use objc2::{extern_class, ClassType}; +use objc2::foundation::{NSObject, NSRect}; +use objc2::{extern_class, extern_methods, ClassType}; -use super::NSResponder; +use super::{NSCursor, NSResponder}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] @@ -12,3 +12,17 @@ extern_class!( type Super = NSResponder; } ); + +extern_methods!( + /// Getter methods + unsafe impl NSView { + #[sel(bounds)] + pub fn bounds(&self) -> NSRect; + } + + unsafe impl NSView { + #[sel(addCursorRect:cursor:)] + // NSCursor safe to take by shared reference since it is already immutable + pub fn addCursorRect(&self, rect: NSRect, cursor: &NSCursor); + } +); diff --git a/src/platform_impl/macos/util/cursor.rs b/src/platform_impl/macos/util/cursor.rs deleted file mode 100644 index 5c48720b..00000000 --- a/src/platform_impl/macos/util/cursor.rs +++ /dev/null @@ -1,165 +0,0 @@ -use cocoa::{ - appkit::NSImage, - base::{id, nil}, - foundation::{NSDictionary, NSPoint, NSString}, -}; -use objc::runtime::Sel; -use std::cell::RefCell; - -use crate::window::CursorIcon; - -pub enum Cursor { - Native(&'static str), - Undocumented(&'static str), - WebKit(&'static str), -} - -impl From for Cursor { - fn from(cursor: CursorIcon) -> Self { - // See native cursors at https://developer.apple.com/documentation/appkit/nscursor?language=objc. - match cursor { - CursorIcon::Arrow | CursorIcon::Default => Cursor::Native("arrowCursor"), - CursorIcon::Hand => Cursor::Native("pointingHandCursor"), - CursorIcon::Grab => Cursor::Native("openHandCursor"), - CursorIcon::Grabbing => Cursor::Native("closedHandCursor"), - CursorIcon::Text => Cursor::Native("IBeamCursor"), - CursorIcon::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"), - CursorIcon::Copy => Cursor::Native("dragCopyCursor"), - CursorIcon::Alias => Cursor::Native("dragLinkCursor"), - CursorIcon::NotAllowed | CursorIcon::NoDrop => { - Cursor::Native("operationNotAllowedCursor") - } - CursorIcon::ContextMenu => Cursor::Native("contextualMenuCursor"), - CursorIcon::Crosshair => Cursor::Native("crosshairCursor"), - CursorIcon::EResize => Cursor::Native("resizeRightCursor"), - CursorIcon::NResize => Cursor::Native("resizeUpCursor"), - CursorIcon::WResize => Cursor::Native("resizeLeftCursor"), - CursorIcon::SResize => Cursor::Native("resizeDownCursor"), - CursorIcon::EwResize | CursorIcon::ColResize => Cursor::Native("resizeLeftRightCursor"), - CursorIcon::NsResize | CursorIcon::RowResize => Cursor::Native("resizeUpDownCursor"), - - // Undocumented cursors: https://stackoverflow.com/a/46635398/5435443 - CursorIcon::Help => Cursor::Undocumented("_helpCursor"), - CursorIcon::ZoomIn => Cursor::Undocumented("_zoomInCursor"), - CursorIcon::ZoomOut => Cursor::Undocumented("_zoomOutCursor"), - CursorIcon::NeResize => Cursor::Undocumented("_windowResizeNorthEastCursor"), - CursorIcon::NwResize => Cursor::Undocumented("_windowResizeNorthWestCursor"), - CursorIcon::SeResize => Cursor::Undocumented("_windowResizeSouthEastCursor"), - CursorIcon::SwResize => Cursor::Undocumented("_windowResizeSouthWestCursor"), - CursorIcon::NeswResize => Cursor::Undocumented("_windowResizeNorthEastSouthWestCursor"), - CursorIcon::NwseResize => Cursor::Undocumented("_windowResizeNorthWestSouthEastCursor"), - - // While these are available, the former just loads a white arrow, - // and the latter loads an ugly deflated beachball! - // CursorIcon::Move => Cursor::Undocumented("_moveCursor"), - // CursorIcon::Wait => Cursor::Undocumented("_waitCursor"), - - // An even more undocumented cursor... - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349 - // This is the wrong semantics for `Wait`, but it's the same as - // what's used in Safari and Chrome. - CursorIcon::Wait | CursorIcon::Progress => { - Cursor::Undocumented("busyButClickableCursor") - } - - // For the rest, we can just snatch the cursors from WebKit... - // They fit the style of the native cursors, and will seem - // completely standard to macOS users. - // https://stackoverflow.com/a/21786835/5435443 - CursorIcon::Move | CursorIcon::AllScroll => Cursor::WebKit("move"), - CursorIcon::Cell => Cursor::WebKit("cell"), - } - } -} - -impl Default for Cursor { - fn default() -> Self { - Cursor::Native("arrowCursor") - } -} - -impl Cursor { - pub unsafe fn load(&self) -> id { - match self { - Cursor::Native(cursor_name) => { - let sel = Sel::register(cursor_name); - msg_send![class!(NSCursor), performSelector: sel] - } - Cursor::Undocumented(cursor_name) => { - let class = class!(NSCursor); - let sel = Sel::register(cursor_name); - let sel = if msg_send![class, respondsToSelector: sel] { - sel - } else { - warn!("Cursor `{}` appears to be invalid", cursor_name); - sel!(arrowCursor) - }; - msg_send![class, performSelector: sel] - } - Cursor::WebKit(cursor_name) => load_webkit_cursor(cursor_name), - } - } -} - -// Note that loading `busybutclickable` with this code won't animate the frames; -// instead you'll just get them all in a column. -pub unsafe fn load_webkit_cursor(cursor_name: &str) -> id { - static CURSOR_ROOT: &str = "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors"; - let cursor_root = NSString::alloc(nil).init_str(CURSOR_ROOT); - let cursor_name = NSString::alloc(nil).init_str(cursor_name); - let cursor_pdf = NSString::alloc(nil).init_str("cursor.pdf"); - let cursor_plist = NSString::alloc(nil).init_str("info.plist"); - let key_x = NSString::alloc(nil).init_str("hotx"); - let key_y = NSString::alloc(nil).init_str("hoty"); - - let cursor_path: id = msg_send![cursor_root, stringByAppendingPathComponent: cursor_name]; - let pdf_path: id = msg_send![cursor_path, stringByAppendingPathComponent: cursor_pdf]; - let info_path: id = msg_send![cursor_path, stringByAppendingPathComponent: cursor_plist]; - - let image = NSImage::alloc(nil).initByReferencingFile_(pdf_path); - let info = NSDictionary::dictionaryWithContentsOfFile_(nil, info_path); - let x = info.valueForKey_(key_x); - let y = info.valueForKey_(key_y); - let point = NSPoint::new(msg_send![x, doubleValue], msg_send![y, doubleValue]); - let cursor: id = msg_send![class!(NSCursor), alloc]; - msg_send![cursor, - initWithImage:image - hotSpot:point - ] -} - -pub unsafe fn invisible_cursor() -> id { - // 16x16 GIF data for invisible cursor - // You can reproduce this via ImageMagick. - // $ convert -size 16x16 xc:none cursor.gif - static CURSOR_BYTES: &[u8] = &[ - 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, - 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9, 0xCB, 0xED, 0x0F, - 0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B, - ]; - - thread_local! { - // We can't initialize this at startup. - static CURSOR_OBJECT: RefCell = RefCell::new(nil); - } - - CURSOR_OBJECT.with(|cursor_obj| { - if *cursor_obj.borrow() == nil { - // Create a cursor from `CURSOR_BYTES` - let cursor_data: id = msg_send![ - class!(NSData), - dataWithBytesNoCopy: CURSOR_BYTES.as_ptr(), - length: CURSOR_BYTES.len(), - freeWhenDone: false, - ]; - - let ns_image: id = msg_send![class!(NSImage), alloc]; - let _: id = msg_send![ns_image, initWithData: cursor_data]; - let cursor: id = msg_send![class!(NSCursor), alloc]; - *cursor_obj.borrow_mut() = - msg_send![cursor, initWithImage:ns_image hotSpot: NSPoint::new(0.0, 0.0)]; - } - *cursor_obj.borrow() - }) -} diff --git a/src/platform_impl/macos/util/mod.rs b/src/platform_impl/macos/util/mod.rs index 48988beb..0eac0f10 100644 --- a/src/platform_impl/macos/util/mod.rs +++ b/src/platform_impl/macos/util/mod.rs @@ -1,7 +1,6 @@ mod r#async; -mod cursor; -pub use self::{cursor::*, r#async::*}; +pub use self::r#async::*; use std::ops::{BitAnd, Deref}; use std::os::raw::c_uchar; diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index dc40d2be..9c2e6ae3 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -5,7 +5,7 @@ use std::{ ptr, slice, str, sync::{ atomic::{compiler_fence, Ordering}, - Arc, Mutex, Weak, + Mutex, }, }; @@ -15,10 +15,11 @@ use cocoa::{ foundation::{NSPoint, NSRect, NSSize, NSString}, }; use objc2::foundation::{NSInteger, NSObject, NSRange, NSUInteger}; +use objc2::rc::{Id, Shared}; use objc2::runtime::{Bool, Object, Sel}; use objc2::{declare_class, ClassType}; -use super::appkit::{NSResponder, NSView as NSViewClass}; +use super::appkit::{NSCursor, NSResponder, NSView as NSViewClass}; use crate::{ dpi::{LogicalPosition, LogicalSize}, event::{ @@ -41,7 +42,7 @@ use crate::{ pub struct CursorState { pub visible: bool, - pub cursor: util::Cursor, + pub(super) cursor: Id, } impl Default for CursorState { @@ -70,7 +71,7 @@ enum ImeState { pub(super) struct ViewState { ns_window: id, - pub cursor_state: Arc>, + pub cursor_state: Mutex, ime_position: LogicalPosition, pub(super) modifiers: ModifiersState, tracking_rect: Option, @@ -97,12 +98,10 @@ impl ViewState { } } -pub fn new_view(ns_window: id) -> (IdRef, Weak>) { - let cursor_state = Default::default(); - let cursor_access = Arc::downgrade(&cursor_state); +pub fn new_view(ns_window: id) -> IdRef { let state = ViewState { ns_window, - cursor_state, + cursor_state: Default::default(), ime_position: LogicalPosition::new(0.0, 0.0), modifiers: Default::default(), tracking_rect: None, @@ -115,10 +114,7 @@ pub fn new_view(ns_window: id) -> (IdRef, Weak>) { // This is free'd in `dealloc` let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void; let ns_view: id = msg_send![WinitView::class(), alloc]; - ( - IdRef::new(msg_send![ns_view, initWithWinit: state_ptr]), - cursor_access, - ) + IdRef::new(msg_send![ns_view, initWithWinit: state_ptr]) } } @@ -432,21 +428,17 @@ declare_class!( #[sel(resetCursorRects)] fn reset_cursor_rects(&self) { trace_scope!("resetCursorRects"); - unsafe { + let state = unsafe { let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); + &mut *(state_ptr as *mut ViewState) + }; - let bounds: NSRect = msg_send![self, bounds]; - let cursor_state = state.cursor_state.lock().unwrap(); - let cursor = if cursor_state.visible { - cursor_state.cursor.load() - } else { - util::invisible_cursor() - }; - let _: () = msg_send![self, - addCursorRect:bounds - cursor:cursor - ]; + let bounds = self.bounds(); + let cursor_state = state.cursor_state.lock().unwrap(); + if cursor_state.visible { + self.addCursorRect(bounds, &cursor_state.cursor); + } else { + self.addCursorRect(bounds, &NSCursor::invisible()); } } } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index f043cc85..3fa514a3 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -5,7 +5,7 @@ use std::{ os::raw::c_void, sync::{ atomic::{AtomicBool, Ordering}, - Arc, Mutex, MutexGuard, Weak, + Arc, Mutex, MutexGuard, }, }; @@ -26,8 +26,7 @@ use crate::{ ffi, monitor::{self, MonitorHandle, VideoMode}, util::{self, IdRef}, - view::CursorState, - view::{self, new_view}, + view::{self, new_view, ViewState}, window_delegate::new_delegate, OsError, }, @@ -50,7 +49,7 @@ use objc2::rc::autoreleasepool; use objc2::runtime::{Bool, Object}; use objc2::{declare_class, ClassType}; -use super::appkit::{NSResponder, NSWindow as NSWindowClass}; +use super::appkit::{NSCursor, NSResponder, NSWindow as NSWindowClass}; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(pub usize); @@ -112,8 +111,8 @@ impl Default for PlatformSpecificWindowBuilderAttributes { unsafe fn create_view( ns_window: id, pl_attribs: &PlatformSpecificWindowBuilderAttributes, -) -> Option<(IdRef, Weak>)> { - let (ns_view, cursor_state) = new_view(ns_window); +) -> Option { + let ns_view = new_view(ns_window); ns_view.non_nil().map(|ns_view| { // The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until // macos 10.14 and `true` after 10.15, we should set it to `YES` or `NO` to avoid @@ -133,7 +132,7 @@ unsafe fn create_view( ns_view.setWantsLayer(Bool::YES.as_raw()); } - (ns_view, cursor_state) + ns_view }) } @@ -375,7 +374,6 @@ pub struct UnownedWindow { pub ns_view: IdRef, // never changes shared_state: Arc>, decorations: AtomicBool, - cursor_state: Weak>, pub inner_rect: Option>, } @@ -395,7 +393,7 @@ impl UnownedWindow { let ns_window = create_window(&win_attribs, &pl_attribs) .ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSWindow`")))?; - let (ns_view, cursor_state) = unsafe { create_view(*ns_window, &pl_attribs) } + let ns_view = unsafe { create_view(*ns_window, &pl_attribs) } .ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSView`")))?; // Configure the new view as the "key view" for the window @@ -448,7 +446,6 @@ impl UnownedWindow { ns_window, shared_state: Arc::new(Mutex::new(win_attribs.into())), decorations: AtomicBool::new(decorations), - cursor_state, inner_rect, }); @@ -616,14 +613,19 @@ impl UnownedWindow { unsafe { msg_send![*self.ns_window, isResizable] } } - pub fn set_cursor_icon(&self, cursor: CursorIcon) { - let cursor = util::Cursor::from(cursor); - if let Some(cursor_access) = self.cursor_state.upgrade() { - cursor_access.lock().unwrap().cursor = cursor; - } + pub fn set_cursor_icon(&self, icon: CursorIcon) { + let view_state: &ViewState = unsafe { + let ns_view: &Object = (*self.ns_view).as_ref().expect("failed to deref"); + let state_ptr: *const c_void = *ns_view.ivar("winitState"); + &*(state_ptr as *const ViewState) + }; + let mut cursor_state = view_state.cursor_state.lock().unwrap(); + cursor_state.cursor = NSCursor::from_icon(icon); + drop(cursor_state); unsafe { - let _: () = msg_send![*self.ns_window, - invalidateCursorRectsForView:*self.ns_view + let _: () = msg_send![ + *self.ns_window, + invalidateCursorRectsForView: *self.ns_view, ]; } } @@ -645,16 +647,19 @@ impl UnownedWindow { #[inline] pub fn set_cursor_visible(&self, visible: bool) { - if let Some(cursor_access) = self.cursor_state.upgrade() { - let mut cursor_state = cursor_access.lock().unwrap(); - if visible != cursor_state.visible { - cursor_state.visible = visible; - drop(cursor_state); - unsafe { - let _: () = msg_send![*self.ns_window, - invalidateCursorRectsForView:*self.ns_view - ]; - } + let view_state: &ViewState = unsafe { + let ns_view: &Object = (*self.ns_view).as_ref().expect("failed to deref"); + let state_ptr: *const c_void = *ns_view.ivar("winitState"); + &*(state_ptr as *const ViewState) + }; + let mut cursor_state = view_state.cursor_state.lock().unwrap(); + if visible != cursor_state.visible { + cursor_state.visible = visible; + drop(cursor_state); + unsafe { + let _: () = msg_send![*self.ns_window, + invalidateCursorRectsForView:*self.ns_view + ]; } } }