From be3d72d524c8d8ebf2b10f5e30a78e0891981cbd Mon Sep 17 00:00:00 2001 From: Fredemus <46689307+Fredemus@users.noreply.github.com> Date: Tue, 2 Apr 2024 23:14:13 +0200 Subject: [PATCH] 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. --- src/win/cursor.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++++ src/win/mod.rs | 1 + src/win/window.rs | 50 ++++++++++++++++++++++++++++++++----------- 3 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 src/win/cursor.rs diff --git a/src/win/cursor.rs b/src/win/cursor.rs new file mode 100644 index 00000000..f9a04d3b --- /dev/null +++ b/src/win/cursor.rs @@ -0,0 +1,54 @@ +use crate::MouseCursor; +use winapi::{ + shared::ntdef::LPCWSTR, + um::winuser::{ + IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP, IDC_IBEAM, IDC_NO, IDC_SIZEALL, + IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE, IDC_WAIT, + }, +}; + +pub fn cursor_to_lpcwstr(cursor: MouseCursor) -> LPCWSTR { + match cursor { + MouseCursor::Default => IDC_ARROW, + MouseCursor::Hand => IDC_HAND, + MouseCursor::HandGrabbing => IDC_SIZEALL, + MouseCursor::Help => IDC_HELP, + // an empty LPCWSTR results in the cursor being hidden + MouseCursor::Hidden => std::ptr::null(), + + MouseCursor::Text => IDC_IBEAM, + MouseCursor::VerticalText => IDC_IBEAM, + + MouseCursor::Working => IDC_WAIT, + MouseCursor::PtrWorking => IDC_APPSTARTING, + + MouseCursor::NotAllowed => IDC_NO, + MouseCursor::PtrNotAllowed => IDC_NO, + + MouseCursor::ZoomIn => IDC_ARROW, + MouseCursor::ZoomOut => IDC_ARROW, + + MouseCursor::Alias => IDC_ARROW, + MouseCursor::Copy => IDC_ARROW, + MouseCursor::Move => IDC_SIZEALL, + MouseCursor::AllScroll => IDC_SIZEALL, + MouseCursor::Cell => IDC_CROSS, + MouseCursor::Crosshair => IDC_CROSS, + + MouseCursor::EResize => IDC_SIZEWE, + MouseCursor::NResize => IDC_SIZENS, + MouseCursor::NeResize => IDC_SIZENESW, + MouseCursor::NwResize => IDC_SIZENWSE, + MouseCursor::SResize => IDC_SIZENS, + MouseCursor::SeResize => IDC_SIZENWSE, + MouseCursor::SwResize => IDC_SIZENESW, + MouseCursor::WResize => IDC_SIZEWE, + MouseCursor::EwResize => IDC_SIZEWE, + MouseCursor::NsResize => IDC_SIZENS, + MouseCursor::NwseResize => IDC_SIZENWSE, + MouseCursor::NeswResize => IDC_SIZENESW, + + MouseCursor::ColResize => IDC_SIZEWE, + MouseCursor::RowResize => IDC_SIZENS, + } +} diff --git a/src/win/mod.rs b/src/win/mod.rs index f66c2bdb..00effa43 100644 --- a/src/win/mod.rs +++ b/src/win/mod.rs @@ -1,3 +1,4 @@ +mod cursor; mod drop_target; mod keyboard; mod window; diff --git a/src/win/window.rs b/src/win/window.rs index 8ed46b44..ac7824b9 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -1,5 +1,5 @@ use winapi::shared::guiddef::GUID; -use winapi::shared::minwindef::{ATOM, FALSE, LPARAM, LRESULT, UINT, WPARAM}; +use winapi::shared::minwindef::{ATOM, FALSE, LOWORD, LPARAM, LRESULT, UINT, WPARAM}; use winapi::shared::windef::{HWND, RECT}; use winapi::um::combaseapi::CoCreateGuid; use winapi::um::ole2::{OleInitialize, RegisterDragDrop, RevokeDragDrop}; @@ -7,15 +7,16 @@ use winapi::um::oleidl::LPDROPTARGET; use winapi::um::winuser::{ AdjustWindowRectEx, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetDpiForWindow, GetFocus, GetMessageW, GetWindowLongPtrW, LoadCursorW, PostMessageW, - RegisterClassW, ReleaseCapture, SetCapture, SetFocus, SetProcessDpiAwarenessContext, SetTimer, - SetWindowLongPtrW, SetWindowPos, TrackMouseEvent, TranslateMessage, UnregisterClassW, CS_OWNDC, - GET_XBUTTON_WPARAM, GWLP_USERDATA, IDC_ARROW, MSG, SWP_NOMOVE, SWP_NOZORDER, TRACKMOUSEEVENT, - WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DPICHANGED, WM_INPUTLANGCHANGE, WM_KEYDOWN, - WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, - WM_MOUSELEAVE, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCDESTROY, WM_RBUTTONDOWN, WM_RBUTTONUP, - WM_SHOWWINDOW, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TIMER, WM_USER, - WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_CAPTION, WS_CHILD, WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, - WS_MINIMIZEBOX, WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE, XBUTTON1, XBUTTON2, + RegisterClassW, ReleaseCapture, SetCapture, SetCursor, SetFocus, SetProcessDpiAwarenessContext, + SetTimer, SetWindowLongPtrW, SetWindowPos, TrackMouseEvent, TranslateMessage, UnregisterClassW, + CS_OWNDC, GET_XBUTTON_WPARAM, GWLP_USERDATA, HTCLIENT, IDC_ARROW, MSG, SWP_NOMOVE, + SWP_NOZORDER, TRACKMOUSEEVENT, WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DPICHANGED, + WM_INPUTLANGCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, + WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSELEAVE, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCDESTROY, + WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SHOWWINDOW, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, + WM_SYSKEYUP, WM_TIMER, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_CAPTION, WS_CHILD, + WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE, + XBUTTON1, XBUTTON2, }; use std::cell::{Cell, Ref, RefCell, RefMut}; @@ -37,6 +38,7 @@ use crate::{ WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, }; +use super::cursor::cursor_to_lpcwstr; use super::drop_target::DropTarget; use super::keyboard::KeyboardState; @@ -428,6 +430,24 @@ unsafe fn wnd_proc_inner( None } + // If WM_SETCURSOR returns `None`, WM_SETCURSOR continues to get handled by the outer window(s), + // If it returns `Some(1)`, the current window decides what the cursor is + WM_SETCURSOR => { + let low_word = LOWORD(lparam as u32) as isize; + let mouse_in_window = low_word == HTCLIENT; + if mouse_in_window { + // Here we need to set the cursor back to what the state says, since it can have changed when outside the window + let cursor = + LoadCursorW(null_mut(), cursor_to_lpcwstr(window_state.cursor_icon.get())); + unsafe { + SetCursor(cursor); + } + Some(1) + } else { + // Cursor is being changed by some other window, e.g. when having mouse on the borders to resize it + None + } + } // NOTE: `WM_NCDESTROY` is handled in the outer function because this deallocates the window // state BV_WINDOW_MUST_CLOSE => { @@ -480,6 +500,7 @@ pub(super) struct WindowState { keyboard_state: RefCell, mouse_button_counter: Cell, mouse_was_outside_window: RefCell, + cursor_icon: Cell, // Initialized late so the `Window` can hold a reference to this `WindowState` handler: RefCell>>, _drop_target: RefCell>>, @@ -685,6 +706,7 @@ impl Window<'_> { keyboard_state: RefCell::new(KeyboardState::new()), mouse_button_counter: Cell::new(0), mouse_was_outside_window: RefCell::new(true), + cursor_icon: Cell::new(MouseCursor::Default), // The Window refers to this `WindowState`, so this `handler` needs to be // initialized later handler: RefCell::new(None), @@ -790,8 +812,12 @@ impl Window<'_> { self.state.deferred_tasks.borrow_mut().push_back(task); } - pub fn set_mouse_cursor(&mut self, _mouse_cursor: MouseCursor) { - todo!() + pub fn set_mouse_cursor(&mut self, mouse_cursor: MouseCursor) { + self.state.cursor_icon.set(mouse_cursor); + unsafe { + let cursor = LoadCursorW(null_mut(), cursor_to_lpcwstr(mouse_cursor)); + SetCursor(cursor); + } } #[cfg(feature = "opengl")]