mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-23 22:01:31 +11:00
Refactor macOS cursor code (#2463)
This commit is contained in:
parent
e517e468f8
commit
29419d6c38
241
src/platform_impl/macos/appkit/cursor.rs
Normal file
241
src/platform_impl/macos/appkit/cursor.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
37
src/platform_impl/macos/appkit/image.rs
Normal file
37
src/platform_impl/macos/appkit/image.rs
Normal 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] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -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()
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue