Refactor macOS cursor code (#2463)

This commit is contained in:
Mads Marquart 2022-09-02 21:02:40 +02:00 committed by GitHub
parent e517e468f8
commit 29419d6c38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 352 additions and 222 deletions

View file

@ -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!(
/// <https://developer.apple.com/documentation/appkit/nscursor?language=objc>
#[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<Self, Shared> {
unsafe { msg_send_id![Self::class(), $name] }
}
)*};
}
macro_rules! def_undocumented_cursor {
{$(
$(#[$($m:meta)*])*
pub fn $name:ident();
)*} => {$(
$(#[$($m)*])*
pub fn $name() -> Id<Self, Shared> {
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<Self, Shared> {
let this = unsafe { msg_send_id![Self::class(), alloc] };
unsafe { msg_send_id![this, initWithImage: image, hotSpot: hotSpot] }
}
pub fn invisible() -> Id<Self, Shared> {
// 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<Id<NSCursor, Shared>> = 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<Self, Shared> {
unsafe { msg_send_id![Self::class(), performSelector: sel] }
}
unsafe fn from_selector(sel: Sel) -> Option<Id<Self, Shared>> {
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<Self, Shared> {
// 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<NSDictionary<NSObject, NSObject>, Shared> = unsafe {
msg_send_id![
<NSDictionary<NSObject, NSObject>>::class(),
dictionaryWithContentsOfFile: &*info_path,
]
};
let mut x = 0.0;
if let Some(n) = info.get(&*ns_string!("hotx")) {
if n.is_kind_of::<NSNumber>() {
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::<NSNumber>() {
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<Self, Shared> {
unsafe { Self::load_webkit_cursor(ns_string!("move")) }
}
pub fn cellCursor() -> Id<Self, Shared> {
unsafe { Self::load_webkit_cursor(ns_string!("cell")) }
}
}
);
impl NSCursor {
pub fn from_icon(icon: CursorIcon) -> Id<Self, Shared> {
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, Shared> {
Self::arrowCursor()
}
}

View file

@ -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.
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-126728>
//
// 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<Self, Shared> {
let this = unsafe { msg_send_id![Self::class(), alloc] };
unsafe { msg_send_id![this, initByReferencingFile: path] }
}
pub fn new_with_data(data: &NSData) -> Id<Self, Shared> {
let this = unsafe { msg_send_id![Self::class(), alloc] };
unsafe { msg_send_id![this, initWithData: data] }
}
}
);

View file

@ -1,11 +1,18 @@
#![deny(unsafe_op_in_unsafe_fn)] #![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 application;
mod cursor;
mod image;
mod responder; mod responder;
mod view; mod view;
mod window; mod window;
pub(crate) use self::application::NSApplication; 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::responder::NSResponder;
pub(crate) use self::view::NSView; pub(crate) use self::view::NSView;
pub(crate) use self::window::NSWindow; pub(crate) use self::window::NSWindow;

View file

@ -1,7 +1,7 @@
use objc2::foundation::NSObject; use objc2::foundation::{NSObject, NSRect};
use objc2::{extern_class, ClassType}; use objc2::{extern_class, extern_methods, ClassType};
use super::NSResponder; use super::{NSCursor, NSResponder};
extern_class!( extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
@ -12,3 +12,17 @@ extern_class!(
type Super = NSResponder; 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);
}
);

View file

@ -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<CursorIcon> 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<id> = 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()
})
}

View file

@ -1,7 +1,6 @@
mod r#async; mod r#async;
mod cursor;
pub use self::{cursor::*, r#async::*}; pub use self::r#async::*;
use std::ops::{BitAnd, Deref}; use std::ops::{BitAnd, Deref};
use std::os::raw::c_uchar; use std::os::raw::c_uchar;

View file

@ -5,7 +5,7 @@ use std::{
ptr, slice, str, ptr, slice, str,
sync::{ sync::{
atomic::{compiler_fence, Ordering}, atomic::{compiler_fence, Ordering},
Arc, Mutex, Weak, Mutex,
}, },
}; };
@ -15,10 +15,11 @@ use cocoa::{
foundation::{NSPoint, NSRect, NSSize, NSString}, foundation::{NSPoint, NSRect, NSSize, NSString},
}; };
use objc2::foundation::{NSInteger, NSObject, NSRange, NSUInteger}; use objc2::foundation::{NSInteger, NSObject, NSRange, NSUInteger};
use objc2::rc::{Id, Shared};
use objc2::runtime::{Bool, Object, Sel}; use objc2::runtime::{Bool, Object, Sel};
use objc2::{declare_class, ClassType}; use objc2::{declare_class, ClassType};
use super::appkit::{NSResponder, NSView as NSViewClass}; use super::appkit::{NSCursor, NSResponder, NSView as NSViewClass};
use crate::{ use crate::{
dpi::{LogicalPosition, LogicalSize}, dpi::{LogicalPosition, LogicalSize},
event::{ event::{
@ -41,7 +42,7 @@ use crate::{
pub struct CursorState { pub struct CursorState {
pub visible: bool, pub visible: bool,
pub cursor: util::Cursor, pub(super) cursor: Id<NSCursor, Shared>,
} }
impl Default for CursorState { impl Default for CursorState {
@ -70,7 +71,7 @@ enum ImeState {
pub(super) struct ViewState { pub(super) struct ViewState {
ns_window: id, ns_window: id,
pub cursor_state: Arc<Mutex<CursorState>>, pub cursor_state: Mutex<CursorState>,
ime_position: LogicalPosition<f64>, ime_position: LogicalPosition<f64>,
pub(super) modifiers: ModifiersState, pub(super) modifiers: ModifiersState,
tracking_rect: Option<NSInteger>, tracking_rect: Option<NSInteger>,
@ -97,12 +98,10 @@ impl ViewState {
} }
} }
pub fn new_view(ns_window: id) -> (IdRef, Weak<Mutex<CursorState>>) { pub fn new_view(ns_window: id) -> IdRef {
let cursor_state = Default::default();
let cursor_access = Arc::downgrade(&cursor_state);
let state = ViewState { let state = ViewState {
ns_window, ns_window,
cursor_state, cursor_state: Default::default(),
ime_position: LogicalPosition::new(0.0, 0.0), ime_position: LogicalPosition::new(0.0, 0.0),
modifiers: Default::default(), modifiers: Default::default(),
tracking_rect: None, tracking_rect: None,
@ -115,10 +114,7 @@ pub fn new_view(ns_window: id) -> (IdRef, Weak<Mutex<CursorState>>) {
// This is free'd in `dealloc` // This is free'd in `dealloc`
let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void; let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void;
let ns_view: id = msg_send![WinitView::class(), alloc]; let ns_view: id = msg_send![WinitView::class(), alloc];
( IdRef::new(msg_send![ns_view, initWithWinit: state_ptr])
IdRef::new(msg_send![ns_view, initWithWinit: state_ptr]),
cursor_access,
)
} }
} }
@ -432,21 +428,17 @@ declare_class!(
#[sel(resetCursorRects)] #[sel(resetCursorRects)]
fn reset_cursor_rects(&self) { fn reset_cursor_rects(&self) {
trace_scope!("resetCursorRects"); trace_scope!("resetCursorRects");
unsafe { let state = unsafe {
let state_ptr: *mut c_void = *self.ivar("winitState"); 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 bounds = self.bounds();
let cursor_state = state.cursor_state.lock().unwrap(); let cursor_state = state.cursor_state.lock().unwrap();
let cursor = if cursor_state.visible { if cursor_state.visible {
cursor_state.cursor.load() self.addCursorRect(bounds, &cursor_state.cursor);
} else { } else {
util::invisible_cursor() self.addCursorRect(bounds, &NSCursor::invisible());
};
let _: () = msg_send![self,
addCursorRect:bounds
cursor:cursor
];
} }
} }
} }

View file

@ -5,7 +5,7 @@ use std::{
os::raw::c_void, os::raw::c_void,
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
Arc, Mutex, MutexGuard, Weak, Arc, Mutex, MutexGuard,
}, },
}; };
@ -26,8 +26,7 @@ use crate::{
ffi, ffi,
monitor::{self, MonitorHandle, VideoMode}, monitor::{self, MonitorHandle, VideoMode},
util::{self, IdRef}, util::{self, IdRef},
view::CursorState, view::{self, new_view, ViewState},
view::{self, new_view},
window_delegate::new_delegate, window_delegate::new_delegate,
OsError, OsError,
}, },
@ -50,7 +49,7 @@ use objc2::rc::autoreleasepool;
use objc2::runtime::{Bool, Object}; use objc2::runtime::{Bool, Object};
use objc2::{declare_class, ClassType}; 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)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(pub usize); pub struct WindowId(pub usize);
@ -112,8 +111,8 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
unsafe fn create_view( unsafe fn create_view(
ns_window: id, ns_window: id,
pl_attribs: &PlatformSpecificWindowBuilderAttributes, pl_attribs: &PlatformSpecificWindowBuilderAttributes,
) -> Option<(IdRef, Weak<Mutex<CursorState>>)> { ) -> Option<IdRef> {
let (ns_view, cursor_state) = new_view(ns_window); let ns_view = new_view(ns_window);
ns_view.non_nil().map(|ns_view| { ns_view.non_nil().map(|ns_view| {
// The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until // 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 // 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.setWantsLayer(Bool::YES.as_raw());
} }
(ns_view, cursor_state) ns_view
}) })
} }
@ -375,7 +374,6 @@ pub struct UnownedWindow {
pub ns_view: IdRef, // never changes pub ns_view: IdRef, // never changes
shared_state: Arc<Mutex<SharedState>>, shared_state: Arc<Mutex<SharedState>>,
decorations: AtomicBool, decorations: AtomicBool,
cursor_state: Weak<Mutex<CursorState>>,
pub inner_rect: Option<PhysicalSize<u32>>, pub inner_rect: Option<PhysicalSize<u32>>,
} }
@ -395,7 +393,7 @@ impl UnownedWindow {
let ns_window = create_window(&win_attribs, &pl_attribs) let ns_window = create_window(&win_attribs, &pl_attribs)
.ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSWindow`")))?; .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`")))?; .ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSView`")))?;
// Configure the new view as the "key view" for the window // Configure the new view as the "key view" for the window
@ -448,7 +446,6 @@ impl UnownedWindow {
ns_window, ns_window,
shared_state: Arc::new(Mutex::new(win_attribs.into())), shared_state: Arc::new(Mutex::new(win_attribs.into())),
decorations: AtomicBool::new(decorations), decorations: AtomicBool::new(decorations),
cursor_state,
inner_rect, inner_rect,
}); });
@ -616,14 +613,19 @@ impl UnownedWindow {
unsafe { msg_send![*self.ns_window, isResizable] } unsafe { msg_send![*self.ns_window, isResizable] }
} }
pub fn set_cursor_icon(&self, cursor: CursorIcon) { pub fn set_cursor_icon(&self, icon: CursorIcon) {
let cursor = util::Cursor::from(cursor); let view_state: &ViewState = unsafe {
if let Some(cursor_access) = self.cursor_state.upgrade() { let ns_view: &Object = (*self.ns_view).as_ref().expect("failed to deref");
cursor_access.lock().unwrap().cursor = cursor; 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 { unsafe {
let _: () = msg_send![*self.ns_window, let _: () = msg_send![
invalidateCursorRectsForView:*self.ns_view *self.ns_window,
invalidateCursorRectsForView: *self.ns_view,
]; ];
} }
} }
@ -645,16 +647,19 @@ impl UnownedWindow {
#[inline] #[inline]
pub fn set_cursor_visible(&self, visible: bool) { pub fn set_cursor_visible(&self, visible: bool) {
if let Some(cursor_access) = self.cursor_state.upgrade() { let view_state: &ViewState = unsafe {
let mut cursor_state = cursor_access.lock().unwrap(); let ns_view: &Object = (*self.ns_view).as_ref().expect("failed to deref");
if visible != cursor_state.visible { let state_ptr: *const c_void = *ns_view.ivar("winitState");
cursor_state.visible = visible; &*(state_ptr as *const ViewState)
drop(cursor_state); };
unsafe { let mut cursor_state = view_state.cursor_state.lock().unwrap();
let _: () = msg_send![*self.ns_window, if visible != cursor_state.visible {
invalidateCursorRectsForView:*self.ns_view cursor_state.visible = visible;
]; drop(cursor_state);
} unsafe {
let _: () = msg_send![*self.ns_window,
invalidateCursorRectsForView:*self.ns_view
];
} }
} }
} }