Skip to content

Commit be3d72d

Browse files
authored
Handle set_mouse_cursor method on windows (#186)
This adds the ability to change the cursor on Windows platforms. For some reason there are very few default cursors included in Windows so a lot of cursors available on other platforms aren't available yet, which is why many of the `MouseCursor` options just maps to `IDC_ARROW`. I'll look into adding custom cursors next. `LoadCursorW` is supposedly superseded by [LoadImageW](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadcursorw) but I couldn't find a way to do it with that one that doesn't crash. Tested with Vizia's cursor icon example [here](https://github.com/Fredemus/vizia/tree/baseview-window-events) and everything seems to work as it should. Also tested by hovering over some buttons on a plugin.
1 parent 905854d commit be3d72d

File tree

3 files changed

+93
-12
lines changed

3 files changed

+93
-12
lines changed

src/win/cursor.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use crate::MouseCursor;
2+
use winapi::{
3+
shared::ntdef::LPCWSTR,
4+
um::winuser::{
5+
IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP, IDC_IBEAM, IDC_NO, IDC_SIZEALL,
6+
IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE, IDC_WAIT,
7+
},
8+
};
9+
10+
pub fn cursor_to_lpcwstr(cursor: MouseCursor) -> LPCWSTR {
11+
match cursor {
12+
MouseCursor::Default => IDC_ARROW,
13+
MouseCursor::Hand => IDC_HAND,
14+
MouseCursor::HandGrabbing => IDC_SIZEALL,
15+
MouseCursor::Help => IDC_HELP,
16+
// an empty LPCWSTR results in the cursor being hidden
17+
MouseCursor::Hidden => std::ptr::null(),
18+
19+
MouseCursor::Text => IDC_IBEAM,
20+
MouseCursor::VerticalText => IDC_IBEAM,
21+
22+
MouseCursor::Working => IDC_WAIT,
23+
MouseCursor::PtrWorking => IDC_APPSTARTING,
24+
25+
MouseCursor::NotAllowed => IDC_NO,
26+
MouseCursor::PtrNotAllowed => IDC_NO,
27+
28+
MouseCursor::ZoomIn => IDC_ARROW,
29+
MouseCursor::ZoomOut => IDC_ARROW,
30+
31+
MouseCursor::Alias => IDC_ARROW,
32+
MouseCursor::Copy => IDC_ARROW,
33+
MouseCursor::Move => IDC_SIZEALL,
34+
MouseCursor::AllScroll => IDC_SIZEALL,
35+
MouseCursor::Cell => IDC_CROSS,
36+
MouseCursor::Crosshair => IDC_CROSS,
37+
38+
MouseCursor::EResize => IDC_SIZEWE,
39+
MouseCursor::NResize => IDC_SIZENS,
40+
MouseCursor::NeResize => IDC_SIZENESW,
41+
MouseCursor::NwResize => IDC_SIZENWSE,
42+
MouseCursor::SResize => IDC_SIZENS,
43+
MouseCursor::SeResize => IDC_SIZENWSE,
44+
MouseCursor::SwResize => IDC_SIZENESW,
45+
MouseCursor::WResize => IDC_SIZEWE,
46+
MouseCursor::EwResize => IDC_SIZEWE,
47+
MouseCursor::NsResize => IDC_SIZENS,
48+
MouseCursor::NwseResize => IDC_SIZENWSE,
49+
MouseCursor::NeswResize => IDC_SIZENESW,
50+
51+
MouseCursor::ColResize => IDC_SIZEWE,
52+
MouseCursor::RowResize => IDC_SIZENS,
53+
}
54+
}

src/win/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod cursor;
12
mod drop_target;
23
mod keyboard;
34
mod window;

src/win/window.rs

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
use winapi::shared::guiddef::GUID;
2-
use winapi::shared::minwindef::{ATOM, FALSE, LPARAM, LRESULT, UINT, WPARAM};
2+
use winapi::shared::minwindef::{ATOM, FALSE, LOWORD, LPARAM, LRESULT, UINT, WPARAM};
33
use winapi::shared::windef::{HWND, RECT};
44
use winapi::um::combaseapi::CoCreateGuid;
55
use winapi::um::ole2::{OleInitialize, RegisterDragDrop, RevokeDragDrop};
66
use winapi::um::oleidl::LPDROPTARGET;
77
use winapi::um::winuser::{
88
AdjustWindowRectEx, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW,
99
GetDpiForWindow, GetFocus, GetMessageW, GetWindowLongPtrW, LoadCursorW, PostMessageW,
10-
RegisterClassW, ReleaseCapture, SetCapture, SetFocus, SetProcessDpiAwarenessContext, SetTimer,
11-
SetWindowLongPtrW, SetWindowPos, TrackMouseEvent, TranslateMessage, UnregisterClassW, CS_OWNDC,
12-
GET_XBUTTON_WPARAM, GWLP_USERDATA, IDC_ARROW, MSG, SWP_NOMOVE, SWP_NOZORDER, TRACKMOUSEEVENT,
13-
WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DPICHANGED, WM_INPUTLANGCHANGE, WM_KEYDOWN,
14-
WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL,
15-
WM_MOUSELEAVE, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCDESTROY, WM_RBUTTONDOWN, WM_RBUTTONUP,
16-
WM_SHOWWINDOW, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TIMER, WM_USER,
17-
WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_CAPTION, WS_CHILD, WS_CLIPSIBLINGS, WS_MAXIMIZEBOX,
18-
WS_MINIMIZEBOX, WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE, XBUTTON1, XBUTTON2,
10+
RegisterClassW, ReleaseCapture, SetCapture, SetCursor, SetFocus, SetProcessDpiAwarenessContext,
11+
SetTimer, SetWindowLongPtrW, SetWindowPos, TrackMouseEvent, TranslateMessage, UnregisterClassW,
12+
CS_OWNDC, GET_XBUTTON_WPARAM, GWLP_USERDATA, HTCLIENT, IDC_ARROW, MSG, SWP_NOMOVE,
13+
SWP_NOZORDER, TRACKMOUSEEVENT, WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DPICHANGED,
14+
WM_INPUTLANGCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN,
15+
WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSELEAVE, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCDESTROY,
16+
WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SHOWWINDOW, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN,
17+
WM_SYSKEYUP, WM_TIMER, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_CAPTION, WS_CHILD,
18+
WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE,
19+
XBUTTON1, XBUTTON2,
1920
};
2021

2122
use std::cell::{Cell, Ref, RefCell, RefMut};
@@ -37,6 +38,7 @@ use crate::{
3738
WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy,
3839
};
3940

41+
use super::cursor::cursor_to_lpcwstr;
4042
use super::drop_target::DropTarget;
4143
use super::keyboard::KeyboardState;
4244

@@ -428,6 +430,24 @@ unsafe fn wnd_proc_inner(
428430

429431
None
430432
}
433+
// If WM_SETCURSOR returns `None`, WM_SETCURSOR continues to get handled by the outer window(s),
434+
// If it returns `Some(1)`, the current window decides what the cursor is
435+
WM_SETCURSOR => {
436+
let low_word = LOWORD(lparam as u32) as isize;
437+
let mouse_in_window = low_word == HTCLIENT;
438+
if mouse_in_window {
439+
// Here we need to set the cursor back to what the state says, since it can have changed when outside the window
440+
let cursor =
441+
LoadCursorW(null_mut(), cursor_to_lpcwstr(window_state.cursor_icon.get()));
442+
unsafe {
443+
SetCursor(cursor);
444+
}
445+
Some(1)
446+
} else {
447+
// Cursor is being changed by some other window, e.g. when having mouse on the borders to resize it
448+
None
449+
}
450+
}
431451
// NOTE: `WM_NCDESTROY` is handled in the outer function because this deallocates the window
432452
// state
433453
BV_WINDOW_MUST_CLOSE => {
@@ -480,6 +500,7 @@ pub(super) struct WindowState {
480500
keyboard_state: RefCell<KeyboardState>,
481501
mouse_button_counter: Cell<usize>,
482502
mouse_was_outside_window: RefCell<bool>,
503+
cursor_icon: Cell<MouseCursor>,
483504
// Initialized late so the `Window` can hold a reference to this `WindowState`
484505
handler: RefCell<Option<Box<dyn WindowHandler>>>,
485506
_drop_target: RefCell<Option<Rc<DropTarget>>>,
@@ -685,6 +706,7 @@ impl Window<'_> {
685706
keyboard_state: RefCell::new(KeyboardState::new()),
686707
mouse_button_counter: Cell::new(0),
687708
mouse_was_outside_window: RefCell::new(true),
709+
cursor_icon: Cell::new(MouseCursor::Default),
688710
// The Window refers to this `WindowState`, so this `handler` needs to be
689711
// initialized later
690712
handler: RefCell::new(None),
@@ -790,8 +812,12 @@ impl Window<'_> {
790812
self.state.deferred_tasks.borrow_mut().push_back(task);
791813
}
792814

793-
pub fn set_mouse_cursor(&mut self, _mouse_cursor: MouseCursor) {
794-
todo!()
815+
pub fn set_mouse_cursor(&mut self, mouse_cursor: MouseCursor) {
816+
self.state.cursor_icon.set(mouse_cursor);
817+
unsafe {
818+
let cursor = LoadCursorW(null_mut(), cursor_to_lpcwstr(mouse_cursor));
819+
SetCursor(cursor);
820+
}
795821
}
796822

797823
#[cfg(feature = "opengl")]

0 commit comments

Comments
 (0)