Skip to content

Commit

Permalink
Add custom cursor icon support on Windows
Browse files Browse the repository at this point in the history
  • Loading branch information
rofferom committed Jan 6, 2022
1 parent 438d286 commit 78999c9
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 33 deletions.
7 changes: 6 additions & 1 deletion src/platform_impl/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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))
Expand Down
5 changes: 4 additions & 1 deletion src/platform_impl/linux/wayland/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down
5 changes: 4 additions & 1 deletion src/platform_impl/linux/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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();
Expand Down
41 changes: 21 additions & 20 deletions src/platform_impl/windows/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -1717,26 +1717,27 @@ unsafe fn public_window_callback_inner<T: 'static>(
}

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)
}
}

Expand Down
21 changes: 18 additions & 3 deletions src/platform_impl/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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();
Expand Down
113 changes: 106 additions & 7 deletions src/platform_impl/windows/window_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -42,9 +48,102 @@ pub struct SavedWindow {
pub placement: winuser::WINDOWPLACEMENT,
}

#[derive(Clone)]
pub struct RgbaHandle {
pub hcursor: AtomicPtr<HICON__>,
}

impl RgbaHandle {
pub fn new(cursor: &CursorRgba) -> RgbaHandle {
unsafe {
let mut bmh: BITMAPV4HEADER = std::mem::zeroed();

bmh.bV4Size = std::mem::size_of::<BITMAPV4HEADER>() 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::<u32>(),
);

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<PhysicalPosition<f64>>,
Expand Down Expand Up @@ -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,
Expand Down
14 changes: 14 additions & 0 deletions src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<u32>,
}

/// Fullscreen modes.
#[derive(Clone, Debug, PartialEq)]
pub enum Fullscreen {
Expand Down

0 comments on commit 78999c9

Please sign in to comment.