feat(all): Custom cursor images for all desktop platforms

There seems to be many PRs relating to this issue, but they don't include all
platforms and for some reason lost steam. This PR again tries to make this
feature happen, and does it for all desktop platforms (x11, wayland, macos,
windows, web).

I think the best user of this feature and the reason I'm doing this is Bevy and
game engines in general. There non laggy hardware cursors with custom images are
very important. Game devs also like their PNGs so supporting platform native
cursor files is not that important, but I guess could be added too.

Co-authored-by: daxpedda <daxpedda@gmail.com>
Co-authored-by: Mads Marquart <mads@marquart.dk>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
This commit is contained in:
Eero Lehtinen 2023-12-16 22:02:17 +02:00 committed by GitHub
parent 7f6b16a6af
commit af93167237
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 1243 additions and 57 deletions

View file

@ -0,0 +1,56 @@
use std::ffi::c_uchar;
use icrate::Foundation::{NSInteger, NSObject, NSString};
use objc2::rc::Id;
use objc2::runtime::Bool;
use objc2::{extern_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct NSImageRep;
unsafe impl ClassType for NSImageRep {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern "C" {
static NSDeviceRGBColorSpace: &'static NSString;
}
extern_class!(
// <https://developer.apple.com/documentation/appkit/nsbitmapimagerep?language=objc>
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSBitmapImageRep;
unsafe impl ClassType for NSBitmapImageRep {
type Super = NSImageRep;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl NSBitmapImageRep {
pub fn init_rgba(width: NSInteger, height: NSInteger) -> Id<Self> {
unsafe {
msg_send_id![Self::alloc(),
initWithBitmapDataPlanes: std::ptr::null_mut::<*mut c_uchar>(),
pixelsWide: width,
pixelsHigh: height,
bitsPerSample: 8 as NSInteger,
samplesPerPixel: 4 as NSInteger,
hasAlpha: Bool::new(true),
isPlanar: Bool::new(false),
colorSpaceName: NSDeviceRGBColorSpace,
bytesPerRow: width * 4,
bitsPerPixel: 32 as NSInteger,
]
}
}
pub fn bitmap_data(&self) -> *mut u8 {
unsafe { msg_send![self, bitmapData] }
}
}
);

View file

@ -2,13 +2,14 @@ use once_cell::sync::Lazy;
use icrate::ns_string;
use icrate::Foundation::{
NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSString,
NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize, NSString,
};
use objc2::rc::{DefaultId, Id};
use objc2::runtime::Sel;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, sel, ClassType};
use super::NSImage;
use super::{NSBitmapImageRep, NSImage};
use crate::cursor::CursorImage;
use crate::window::CursorIcon;
extern_class!(
@ -232,6 +233,23 @@ impl NSCursor {
_ => Default::default(),
}
}
pub fn from_image(cursor: &CursorImage) -> Id<Self> {
let width = cursor.width;
let height = cursor.height;
let bitmap = NSBitmapImageRep::init_rgba(width as isize, height as isize);
let bitmap_data =
unsafe { std::slice::from_raw_parts_mut(bitmap.bitmap_data(), cursor.rgba.len()) };
bitmap_data.copy_from_slice(&cursor.rgba);
let image = NSImage::init_with_size(NSSize::new(width.into(), height.into()));
image.add_representation(&bitmap);
let hotspot = NSPoint::new(cursor.hotspot_x as f64, cursor.hotspot_y as f64);
NSCursor::new(&image, hotspot)
}
}
impl DefaultId for NSCursor {

View file

@ -1,6 +1,8 @@
use icrate::Foundation::{NSData, NSObject, NSString};
use icrate::Foundation::{NSData, NSObject, NSSize, NSString};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use objc2::{extern_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
use super::NSBitmapImageRep;
extern_class!(
// TODO: Can this be mutable?
@ -32,5 +34,13 @@ extern_methods!(
pub fn new_with_data(data: &NSData) -> Id<Self> {
unsafe { msg_send_id![Self::alloc(), initWithData: data] }
}
pub fn init_with_size(size: NSSize) -> Id<Self> {
unsafe { msg_send_id![Self::alloc(), initWithSize: size] }
}
pub fn add_representation(&self, representation: &NSBitmapImageRep) {
unsafe { msg_send![self, addRepresentation: representation] }
}
}
);

View file

@ -13,6 +13,7 @@
mod appearance;
mod application;
mod bitmap_image_rep;
mod button;
mod color;
mod control;
@ -36,6 +37,7 @@ pub(crate) use self::application::{
NSApp, NSApplication, NSApplicationActivationPolicy, NSApplicationPresentationOptions,
NSRequestUserAttentionType,
};
pub(crate) use self::bitmap_image_rep::NSBitmapImageRep;
pub(crate) use self::button::NSButton;
pub(crate) use self::color::NSColor;
pub(crate) use self::control::NSControl;

View file

@ -28,6 +28,7 @@ pub(crate) use self::{
use crate::event::DeviceId as RootDeviceId;
pub(crate) use self::window::Window;
pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;

View file

@ -7,6 +7,7 @@ use std::os::raw::c_void;
use std::ptr::NonNull;
use std::sync::{Mutex, MutexGuard};
use crate::cursor::CustomCursor;
use crate::{
dpi::{
LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size, Size::Logical,
@ -834,6 +835,13 @@ impl WinitWindow {
self.invalidateCursorRectsForView(&view);
}
#[inline]
pub fn set_custom_cursor(&self, cursor: CustomCursor) {
let view = self.view();
view.set_cursor_icon(NSCursor::from_image(&cursor.inner));
self.invalidateCursorRectsForView(&view);
}
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
let associate_mouse_cursor = match mode {