From 78999c9cdb577868d4ab37a0e96669b2a8de9d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Romain=20Roff=C3=A9?= Date: Tue, 28 Dec 2021 16:20:04 +0100 Subject: [PATCH] Add custom cursor icon support on Windows --- src/platform_impl/linux/mod.rs | 7 +- src/platform_impl/linux/wayland/window/mod.rs | 5 +- src/platform_impl/linux/x11/window.rs | 5 +- src/platform_impl/windows/event_loop.rs | 41 +++---- src/platform_impl/windows/window.rs | 21 +++- src/platform_impl/windows/window_state.rs | 113 ++++++++++++++++-- src/window.rs | 14 +++ 7 files changed, 173 insertions(+), 33 deletions(-) diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index dfcc489fc47..12d953b2873 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -30,7 +30,7 @@ use crate::{ event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, icon::Icon, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, - window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes}, + window::{CursorIcon, CursorRgba, Fullscreen, UserAttentionType, WindowAttributes}, }; pub(crate) use crate::icon::RgbaIcon as PlatformIcon; @@ -348,6 +348,11 @@ impl Window { x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor)) } + #[inline] + pub fn set_cursor_rgba(&self, cursor: CursorRgba) { + x11_or_wayland!(match self; Window(w) => w.set_cursor_rgba(cursor)) + } + #[inline] pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(grab)) diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 8af9ca20b18..5dfbd742648 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -17,7 +17,7 @@ use crate::platform_impl::{ MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes as PlatformAttributes, }; -use crate::window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes}; +use crate::window::{CursorIcon, CursorRgba, Fullscreen, UserAttentionType, WindowAttributes}; use super::env::WindowingFeatures; use super::event_loop::WinitState; @@ -409,6 +409,9 @@ impl Window { self.send_request(WindowRequest::NewCursorIcon(cursor)); } + #[inline] + pub fn set_cursor_rgba(&self, _cursor: CursorRgba) {} + #[inline] pub fn set_cursor_visible(&self, visible: bool) { self.send_request(WindowRequest::ShowCursor(visible)); diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 35beb01441a..c41dd2be6a4 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -22,7 +22,7 @@ use crate::{ MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, }, - window::{CursorIcon, Fullscreen, Icon, UserAttentionType, WindowAttributes}, + window::{CursorIcon, CursorRgba, Fullscreen, Icon, UserAttentionType, WindowAttributes}, }; use super::{ @@ -1239,6 +1239,9 @@ impl UnownedWindow { } } + #[inline] + pub fn set_cursor_rgba(&self, _cursor: CursorRgba) {} + #[inline] pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { let mut grabbed_lock = self.cursor_grabbed.lock(); diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index b7bc1cd42bb..bb291b1cf1c 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -44,7 +44,7 @@ use crate::{ monitor::{self, MonitorHandle}, raw_input, util, window::InitData, - window_state::{CursorFlags, WindowFlags, WindowState}, + window_state::{CursorFlags, CursorHandle, WindowFlags, WindowState}, wrap_device_id, WindowId, DEVICE_ID, }, window::{Fullscreen, WindowId as RootWindowId}, @@ -1717,26 +1717,27 @@ unsafe fn public_window_callback_inner( } winuser::WM_SETCURSOR => { - let set_cursor_to = { - let window_state = userdata.window_state.lock(); - // The return value for the preceding `WM_NCHITTEST` message is conveniently - // provided through the low-order word of lParam. We use that here since - // `WM_MOUSEMOVE` seems to come after `WM_SETCURSOR` for a given cursor movement. - let in_client_area = LOWORD(lparam as DWORD) == winuser::HTCLIENT as WORD; - if in_client_area { - Some(window_state.mouse.cursor) - } else { - None - } - }; - - match set_cursor_to { - Some(cursor) => { - let cursor = winuser::LoadCursorW(ptr::null_mut(), cursor.to_windows_cursor()); - winuser::SetCursor(cursor); - 0 + let window_state = userdata.window_state.lock(); + // The return value for the preceding `WM_NCHITTEST` message is conveniently + // provided through the low-order word of lParam. We use that here since + // `WM_MOUSEMOVE` seems to come after `WM_SETCURSOR` for a given cursor movement. + let in_client_area = LOWORD(lparam as DWORD) == winuser::HTCLIENT as WORD; + if in_client_area { + match &window_state.mouse.cursor { + CursorHandle::Icon(cursor) => { + let cursor = + winuser::LoadCursorW(ptr::null_mut(), cursor.to_windows_cursor()); + winuser::SetCursor(cursor); + 0 + } + CursorHandle::Rgba(cursor) => { + cursor.display(); + 0 + } + CursorHandle::None => winuser::DefWindowProcW(window, msg, wparam, lparam), } - None => winuser::DefWindowProcW(window, msg, wparam, lparam), + } else { + winuser::DefWindowProcW(window, msg, wparam, lparam) } } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 7809cf971c9..695172f0c9b 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -43,10 +43,12 @@ use crate::{ event_loop::{self, EventLoopWindowTarget, WindowLongPtr, DESTROY_MSG_ID}, icon::{self, IconType}, monitor, util, - window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, + window_state::{ + CursorFlags, CursorHandle, RgbaHandle, SavedWindow, WindowFlags, WindowState, + }, Parent, PlatformSpecificWindowBuilderAttributes, WindowId, }, - window::{CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes}, + window::{CursorIcon, CursorRgba, Fullscreen, Theme, UserAttentionType, WindowAttributes}, }; /// The Win32 implementation of the main `Window` object. @@ -241,13 +243,26 @@ impl Window { #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { - self.window_state.lock().mouse.cursor = cursor; + self.window_state.lock().mouse.cursor = CursorHandle::Icon(cursor); self.thread_executor.execute_in_thread(move || unsafe { let cursor = winuser::LoadCursorW(ptr::null_mut(), cursor.to_windows_cursor()); winuser::SetCursor(cursor); }); } + #[inline] + pub fn set_cursor_rgba(&self, cursor: CursorRgba) { + let window_state = Arc::clone(&self.window_state); + + self.thread_executor.execute_in_thread(move || { + let cursor = RgbaHandle::new(&cursor); + + cursor.display(); + + window_state.lock().mouse.cursor = CursorHandle::Rgba(cursor); + }) + } + #[inline] pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { let window = self.window.clone(); diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 9e41bd7925f..6ea05a9ac4d 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -3,16 +3,22 @@ use crate::{ event::ModifiersState, icon::Icon, platform_impl::platform::{event_loop, util}, - window::{CursorIcon, Fullscreen, Theme, WindowAttributes}, + window::{CursorIcon, CursorRgba, Fullscreen, Theme, WindowAttributes}, }; use parking_lot::MutexGuard; +use std::sync::atomic::{AtomicPtr, Ordering}; use std::{io, ptr}; + use winapi::{ + ctypes::c_void, shared::{ - minwindef::DWORD, - windef::{HWND, RECT}, + minwindef::{DWORD, FALSE, LPVOID}, + windef::{HICON, HICON__, HWND, RECT}, + }, + um::{ + wingdi::{self, BITMAPINFO, BITMAPV4HEADER, BI_BITFIELDS, DIB_RGB_COLORS}, + winuser::{self, ICONINFO}, }, - um::winuser, }; /// Contains information about states and the window that the callback is going to use. @@ -42,9 +48,102 @@ pub struct SavedWindow { pub placement: winuser::WINDOWPLACEMENT, } -#[derive(Clone)] +pub struct RgbaHandle { + pub hcursor: AtomicPtr, +} + +impl RgbaHandle { + pub fn new(cursor: &CursorRgba) -> RgbaHandle { + unsafe { + let mut bmh: BITMAPV4HEADER = std::mem::zeroed(); + + bmh.bV4Size = std::mem::size_of::() as u32; + bmh.bV4Width = cursor.width as i32; + bmh.bV4Height = -(cursor.height as i32); + bmh.bV4Planes = 1; + bmh.bV4BitCount = 32; + bmh.bV4V4Compression = BI_BITFIELDS; + bmh.bV4AlphaMask = 0xFF000000; + bmh.bV4RedMask = 0x00FF0000; + bmh.bV4GreenMask = 0x0000FF00; + bmh.bV4BlueMask = 0x000000FF; + + let hdc = winuser::GetDC(ptr::null_mut()); + + let maskbitlen = (cursor.width * cursor.height) as usize; + let maskbits = vec![0xFF; maskbitlen]; + + let mut pixels: LPVOID = ptr::null_mut(); + let mut ii: ICONINFO = std::mem::zeroed(); + + ii.fIcon = FALSE; + ii.xHotspot = cursor.xhot.into(); + ii.yHotspot = cursor.yhot.into(); + + ii.hbmColor = wingdi::CreateDIBSection( + hdc, + (&bmh as *const _) as *const BITMAPINFO, + DIB_RGB_COLORS, + &mut pixels, + ptr::null_mut(), + 0, + ); + + ii.hbmMask = wingdi::CreateBitmap( + cursor.width.into(), + cursor.height.into(), + 1, + 1, + maskbits.as_ptr() as *const c_void, + ); + + std::ptr::copy_nonoverlapping( + cursor.data.as_ptr() as *const u8, + pixels as *mut u8, + cursor.data.len() * std::mem::size_of::(), + ); + + let hicon: HICON = winuser::CreateIconIndirect(&mut ii); + if hicon.is_null() { + panic!("CreateIconIndirect() failed"); + } + + let handle = Self { + hcursor: AtomicPtr::new(hicon as *mut HICON__), + }; + + wingdi::DeleteDC(hdc); + + handle + } + } + + pub fn display(&self) { + let hcursor = self.hcursor.load(Ordering::Relaxed); + unsafe { winuser::SetCursor(hcursor as HICON) }; + } +} + +impl Drop for RgbaHandle { + fn drop(&mut self) { + println!("Destroy"); + + let hcursor = self.hcursor.load(Ordering::Relaxed); + unsafe { + winuser::DestroyIcon(hcursor as HICON); + } + } +} + +#[allow(dead_code)] +pub enum CursorHandle { + None, + Icon(CursorIcon), + Rgba(RgbaHandle), +} + pub struct MouseProperties { - pub cursor: CursorIcon, + pub cursor: CursorHandle, pub capture_count: u32, cursor_flags: CursorFlags, pub last_position: Option>, @@ -102,7 +201,7 @@ impl WindowState { ) -> WindowState { WindowState { mouse: MouseProperties { - cursor: CursorIcon::default(), + cursor: CursorHandle::Icon(CursorIcon::default()), capture_count: 0, cursor_flags: CursorFlags::empty(), last_position: None, diff --git a/src/window.rs b/src/window.rs index 59c8c59f2ed..04035d0d4e3 100644 --- a/src/window.rs +++ b/src/window.rs @@ -781,6 +781,11 @@ impl Window { self.window.set_cursor_icon(cursor); } + #[inline] + pub fn set_cursor_rgba(&self, cursor: CursorRgba) { + self.window.set_cursor_rgba(cursor); + } + /// Changes the position of the cursor in window coordinates. /// /// ## Platform-specific @@ -961,6 +966,15 @@ impl Default for CursorIcon { } } +#[derive(Clone)] +pub struct CursorRgba { + pub xhot: u16, + pub yhot: u16, + pub width: u16, + pub height: u16, + pub data: Vec, +} + /// Fullscreen modes. #[derive(Clone, Debug, PartialEq)] pub enum Fullscreen {