Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding the ability to change the mouse cursor for MacOS and Windows #128

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions src/macos/cursor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use cocoa::base::id;
use objc::{runtime::Sel, msg_send, sel, sel_impl, class};

use crate::MouseCursor;

#[derive(Debug)]
pub enum Cursor {
Native(&'static str),
Undocumented(&'static str),
}

impl From<MouseCursor> for Cursor {
fn from(cursor: MouseCursor) -> Self {
match cursor {
MouseCursor::Default => Cursor::Native("arrowCursor"),
MouseCursor::Pointer => Cursor::Native("pointingHandCursor"),
MouseCursor::Hand => Cursor::Native("openHandCursor"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is MouseCursor::Pointer needed? It might be a good idea to use the exact same cursor assignments as winit, since this list is based on winit's: https://github.com/rust-windowing/winit/blob/bdcbd7d1f99464fe945596b43a922a9632456e44/src/platform_impl/macos/appkit/cursor.rs#L196

That way GUI frameworks that support both winit and baseview will look the same. Now they'd use different cursors for baseview and winit.

MouseCursor::HandGrabbing => Cursor::Native("closedHandCursor"),
MouseCursor::Text => Cursor::Native("IBeamCursor"),
MouseCursor::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"),
MouseCursor::Copy => Cursor::Native("dragCopyCursor"),
MouseCursor::Alias => Cursor::Native("dragLinkCursor"),
MouseCursor::NotAllowed | MouseCursor::PtrNotAllowed => {
Cursor::Native("operationNotAllowedCursor")
}
// MouseCursor:: => Cursor::Native("contextualMenuCursor"),
MouseCursor::Crosshair => Cursor::Native("crosshairCursor"),
MouseCursor::EResize => Cursor::Native("resizeRightCursor"),
MouseCursor::NResize => Cursor::Native("resizeUpCursor"),
MouseCursor::WResize => Cursor::Native("resizeLeftCursor"),
MouseCursor::SResize => Cursor::Native("resizeDownCursor"),
MouseCursor::EwResize | MouseCursor::ColResize => Cursor::Native("resizeLeftRightCursor"),
MouseCursor::NsResize | MouseCursor::RowResize => Cursor::Native("resizeUpDownCursor"),

MouseCursor::Help => Cursor::Undocumented("_helpCursor"),
MouseCursor::ZoomIn => Cursor::Undocumented("_zoomInCursor"),
MouseCursor::ZoomOut => Cursor::Undocumented("_zoomOutCursor"),
MouseCursor::NeResize => Cursor::Undocumented("_windowResizeNorthEastCursor"),
MouseCursor::NwResize => Cursor::Undocumented("_windowResizeNorthWestCursor"),
MouseCursor::SeResize => Cursor::Undocumented("_windowResizeSouthEastCursor"),
MouseCursor::SwResize => Cursor::Undocumented("_windowResizeSouthWestCursor"),
MouseCursor::NeswResize => Cursor::Undocumented("_windowResizeNorthEastSouthWestCursor"),
MouseCursor::NwseResize => Cursor::Undocumented("_windowResizeNorthWestSouthEastCursor"),

MouseCursor::Working | MouseCursor::PtrWorking => {
Cursor::Undocumented("busyButClickableCursor")
}

_ => Cursor::Native("arrowCursor"),

// MouseCursor::Hidden => todo!(),
// MouseCursor::Move => todo!(),
// MouseCursor::AllScroll => todo!(),
// MouseCursor::Cell => todo!(),
}
}
}

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 {
sel!(arrowCursor)
};
msg_send![class, performSelector: sel]
}
}
}
}
1 change: 1 addition & 0 deletions src/macos/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod keyboard;
mod view;
mod window;
mod cursor;

pub use window::*;
15 changes: 14 additions & 1 deletion src/macos/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ use objc::{msg_send, runtime::Object, sel, sel_impl};
use raw_window_handle::{AppKitHandle, HasRawWindowHandle, RawWindowHandle};

use crate::{
Event, EventStatus, Size, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions,
Event, EventStatus, Size, MouseCursor, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions,
WindowScalePolicy,
};

use super::cursor::Cursor;
use super::keyboard::KeyboardState;
use super::view::{create_view, BASEVIEW_STATE_IVAR};

Expand Down Expand Up @@ -338,6 +339,18 @@ impl Window {
}
}

pub fn set_mouse_cursor(&self, cursor: MouseCursor) {
let native_cursor = Cursor::from(cursor);
unsafe {
let bounds: NSRect = msg_send![self.ns_view as id, bounds];
let cursor = native_cursor.load();
let _: () = msg_send![self.ns_view as id,
addCursorRect:bounds
cursor:cursor
];
}
}

#[cfg(feature = "opengl")]
pub fn gl_context(&self) -> Option<&GlContext> {
self.gl_context.as_ref()
Expand Down
1 change: 1 addition & 0 deletions src/mouse_cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub enum MouseCursor {
Default,
Hand,
HandGrabbing,
Pointer,
Help,

Hidden,
Expand Down
38 changes: 38 additions & 0 deletions src/win/cursor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use winapi::{
shared::ntdef::PCWSTR,
um::winuser::{
IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP, IDC_IBEAM, IDC_NO, IDC_SIZEALL, IDC_WAIT,
},
};

use crate::MouseCursor;

impl MouseCursor {
pub(crate) fn to_windows_cursor(self) -> PCWSTR {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

match self {
MouseCursor::Default => IDC_ARROW,
MouseCursor::Hand | MouseCursor::Pointer => IDC_HAND,
MouseCursor::HandGrabbing
| MouseCursor::Move
| MouseCursor::ZoomIn
| MouseCursor::ZoomOut
| MouseCursor::AllScroll => IDC_SIZEALL,
MouseCursor::Help => IDC_HELP,
MouseCursor::Text | MouseCursor::VerticalText => IDC_IBEAM,
MouseCursor::Working | MouseCursor::PtrWorking => IDC_WAIT,
MouseCursor::NotAllowed | MouseCursor::PtrNotAllowed => IDC_NO,
MouseCursor::Crosshair => IDC_CROSS,
MouseCursor::EResize
| MouseCursor::WResize
| MouseCursor::EwResize
| MouseCursor::ColResize => IDC_SIZEALL,
MouseCursor::NResize
| MouseCursor::SResize
| MouseCursor::NsResize
| MouseCursor::RowResize => IDC_SIZEALL,
MouseCursor::NeResize | MouseCursor::SwResize | MouseCursor::NeswResize => IDC_SIZEALL,
MouseCursor::NwResize | MouseCursor::SeResize | MouseCursor::NwseResize => IDC_SIZEALL,
_ => IDC_ARROW,
}
}
}
1 change: 1 addition & 0 deletions src/win/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod drop_target;
mod keyboard;
mod window;
mod cursor;

pub use window::*;
13 changes: 10 additions & 3 deletions src/win/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use winapi::um::ole2::{RegisterDragDrop, OleInitialize, RevokeDragDrop};
use winapi::um::oleidl::LPDROPTARGET;
use winapi::um::winuser::{
AdjustWindowRectEx, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW,
GetDpiForWindow, GetMessageW, GetWindowLongPtrW, LoadCursorW, PostMessageW, RegisterClassW,
GetDpiForWindow, GetMessageW, GetWindowLongPtrW, LoadCursorW, SetCursor, PostMessageW, RegisterClassW,
ReleaseCapture, SetCapture, SetProcessDpiAwarenessContext, SetTimer, SetWindowLongPtrW,
SetWindowPos, TranslateMessage, UnregisterClassW, CS_OWNDC, GET_XBUTTON_WPARAM, GWLP_USERDATA,
IDC_ARROW, MSG, SWP_NOMOVE, SWP_NOZORDER, WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE,
Expand All @@ -31,7 +31,7 @@ use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, Win32Handle};
const BV_WINDOW_MUST_CLOSE: UINT = WM_USER + 1;

use crate::{
Event, MouseButton, MouseEvent, PhyPoint, PhySize, ScrollDelta, Size, WindowEvent,
Event, MouseButton, MouseEvent, PhyPoint, PhySize, ScrollDelta, Size, MouseCursor, WindowEvent,
WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy,
};

Expand Down Expand Up @@ -433,7 +433,7 @@ unsafe fn register_wnd_class() -> ATOM {
cbClsExtra: 0,
cbWndExtra: 0,
hIcon: null_mut(),
hCursor: LoadCursorW(null_mut(), IDC_ARROW),
hCursor: null_mut(), // If the class cursor is not NULL, the system restores the class cursor each time the mouse is moved.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The downside to defaulting this to null_mut() is that this now breaks every application that doesn't set a custom mouse cursor on startup by hiding the cursor by default. This behavior is then also different between Windows, Linux, and macOS.

I'm not quite sure what the best way to tackle this is. One option is to store the last cursor set through Window::set_mouse_cursor() in WindowState, default this value to IDC_ARROW, and set the cursor to that value whenever the user enters the window. Another less hacky approach is to also change the window class's cursor using SetClassLong(hwnd, GCL_HCURSOR, cursor as i32);. Neither is particularly pretty, but the latter approach is at least pretty foolproof. If you want, you can use this patch for this: (for some reason GitHub doesn't let you upload .patch files, so I gzipped it)

0001-Revert-to-IDC_ARROW-as-default-window-class-cursor.patch.gz

hbrBackground: null_mut(),
lpszMenuName: null_mut(),
};
Expand Down Expand Up @@ -760,6 +760,13 @@ impl Window<'_> {
}
}

pub fn set_mouse_cursor(&mut self, cursor: MouseCursor) {
unsafe {
let cursor = LoadCursorW(null_mut(), cursor.to_windows_cursor());
SetCursor(cursor);
}
}

pub fn close(&mut self) {
unsafe {
PostMessageW(self.state.hwnd, BV_WINDOW_MUST_CLOSE, 0, 0);
Expand Down
6 changes: 6 additions & 0 deletions src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
use crate::event::{Event, EventStatus};
use crate::window_open_options::WindowOpenOptions;
use crate::Size;
use crate::MouseCursor;

#[cfg(target_os = "macos")]
use crate::macos as platform;
Expand Down Expand Up @@ -114,6 +115,11 @@ impl<'a> Window<'a> {
pub fn resize(&mut self, size: Size) {
self.window.resize(size);
}

/// Set the cursor to the given cursor type
pub fn set_mouse_cursor(&mut self, cursor: MouseCursor) {
self.window.set_mouse_cursor(cursor);
}

/// If provided, then an OpenGL context will be created for this window. You'll be able to
/// access this context through [crate::Window::gl_context].
Expand Down
1 change: 1 addition & 0 deletions src/x11/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ pub(super) fn get_xcursor(display: *mut x11::xlib::Display, cursor: MouseCursor)

MouseCursor::Hand => loadn(&[b"hand2\0", b"hand1\0"]),
MouseCursor::HandGrabbing => loadn(&[b"closedhand\0", b"grabbing\0"]),
MouseCursor::Pointer => loadn(&[b"hand2\0"]),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be load() since there's only a single cursor name here. Won't make any difference in practice, but then it's consistent with the other calls here. And as mentioned above, there probably shouldn't be another MouseCursor::Pointer so the list can stay identical to winit's.

These are winit's x11 cursor assignments:

https://github.com/rust-windowing/winit/blob/bdcbd7d1f99464fe945596b43a922a9632456e44/src/platform_impl/linux/x11/util/cursor.rs#L78

MouseCursor::Help => load(b"question_arrow\0"),

MouseCursor::Hidden => create_empty_cursor(display),
Expand Down