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

Custom cursor icon support #3039

Closed
wants to merge 9 commits into from
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ And please only add new entries to the top of this list, right below the `# Unre

# Unreleased

- **Breaking**: Rename `CursorIcon` to `NamedCursorIcon`
- `Window::set_cursor_icon` now accepts `Icon`

# 0.29.1-beta

- **Breaking:** Bump `ndk` version to `0.8.0-beta.0`, ndk-sys to `v0.5.0-beta.0`, `android-activity` to `0.5.0-beta.1`.
Expand Down
72 changes: 36 additions & 36 deletions examples/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
window::{CursorIcon, WindowBuilder},
window::{NamedCursorIcon, WindowBuilder},
};

#[path = "util/fill.rs"]
Expand Down Expand Up @@ -57,39 +57,39 @@ fn main() -> Result<(), impl std::error::Error> {
})
}

const CURSORS: &[CursorIcon] = &[
CursorIcon::Default,
CursorIcon::Crosshair,
CursorIcon::Pointer,
CursorIcon::Move,
CursorIcon::Text,
CursorIcon::Wait,
CursorIcon::Help,
CursorIcon::Progress,
CursorIcon::NotAllowed,
CursorIcon::ContextMenu,
CursorIcon::Cell,
CursorIcon::VerticalText,
CursorIcon::Alias,
CursorIcon::Copy,
CursorIcon::NoDrop,
CursorIcon::Grab,
CursorIcon::Grabbing,
CursorIcon::AllScroll,
CursorIcon::ZoomIn,
CursorIcon::ZoomOut,
CursorIcon::EResize,
CursorIcon::NResize,
CursorIcon::NeResize,
CursorIcon::NwResize,
CursorIcon::SResize,
CursorIcon::SeResize,
CursorIcon::SwResize,
CursorIcon::WResize,
CursorIcon::EwResize,
CursorIcon::NsResize,
CursorIcon::NeswResize,
CursorIcon::NwseResize,
CursorIcon::ColResize,
CursorIcon::RowResize,
const CURSORS: &[NamedCursorIcon] = &[
NamedCursorIcon::Default,
NamedCursorIcon::Crosshair,
NamedCursorIcon::Pointer,
NamedCursorIcon::Move,
NamedCursorIcon::Text,
NamedCursorIcon::Wait,
NamedCursorIcon::Help,
NamedCursorIcon::Progress,
NamedCursorIcon::NotAllowed,
NamedCursorIcon::ContextMenu,
NamedCursorIcon::Cell,
NamedCursorIcon::VerticalText,
NamedCursorIcon::Alias,
NamedCursorIcon::Copy,
NamedCursorIcon::NoDrop,
NamedCursorIcon::Grab,
NamedCursorIcon::Grabbing,
NamedCursorIcon::AllScroll,
NamedCursorIcon::ZoomIn,
NamedCursorIcon::ZoomOut,
NamedCursorIcon::EResize,
NamedCursorIcon::NResize,
NamedCursorIcon::NeResize,
NamedCursorIcon::NwResize,
NamedCursorIcon::SResize,
NamedCursorIcon::SeResize,
NamedCursorIcon::SwResize,
NamedCursorIcon::WResize,
NamedCursorIcon::EwResize,
NamedCursorIcon::NsResize,
NamedCursorIcon::NeswResize,
NamedCursorIcon::NwseResize,
NamedCursorIcon::ColResize,
NamedCursorIcon::RowResize,
];
53 changes: 53 additions & 0 deletions examples/cursor_icon.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#![allow(clippy::single_match)]

use std::path::Path;

use simple_logger::SimpleLogger;
use winit::{
event::Event,
event_loop::EventLoop,
window::{Icon, WindowBuilder},
};

#[path = "util/fill.rs"]
mod fill;

fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();

let event_loop = EventLoop::new().unwrap();

let window = WindowBuilder::new()
.with_title("An iconic window!")
.build(&event_loop)
.unwrap();

event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();

if let Event::WindowEvent { event, .. } = event {
use winit::event::WindowEvent::*;
match event {
CloseRequested => control_flow.set_exit(),
DroppedFile(path) => {
window.set_cursor_icon(load_icon(&path));
}
_ => (),
}
} else if let Event::RedrawRequested(_) = event {
fill::fill_window(&window);
}
})
}

fn load_icon(path: &Path) -> Icon {
let (icon_rgba, icon_width, icon_height) = {
let image = image::open(path)
.expect("Failed to open icon path")
.into_rgba8();
let (width, height) = image.dimensions();
let rgba = image.into_raw();
(rgba, width, height)
};
Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
}
6 changes: 3 additions & 3 deletions examples/multithreaded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn main() -> Result<(), impl std::error::Error> {
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, ModifiersState},
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder, WindowLevel},
window::{CursorGrabMode, Fullscreen, NamedCursorIcon, WindowBuilder, WindowLevel},
};

const WINDOW_COUNT: usize = 3;
Expand Down Expand Up @@ -85,8 +85,8 @@ fn main() -> Result<(), impl std::error::Error> {
"2" => window.set_window_level(WindowLevel::AlwaysOnBottom),
"3" => window.set_window_level(WindowLevel::Normal),
"c" => window.set_cursor_icon(match state {
true => CursorIcon::Progress,
false => CursorIcon::Default,
true => NamedCursorIcon::Progress,
false => NamedCursorIcon::Default,
}),
"d" => window.set_decorations(!state),
"f" => window.set_fullscreen(match (state, modifiers.alt_key()) {
Expand Down
22 changes: 11 additions & 11 deletions examples/window_drag_resize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use winit::{
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
keyboard::Key,
window::{CursorIcon, ResizeDirection, WindowBuilder},
window::{NamedCursorIcon, ResizeDirection, WindowBuilder},
};

const BORDER: f64 = 8.0;
Expand Down Expand Up @@ -77,19 +77,19 @@ fn main() -> Result<(), impl std::error::Error> {
})
}

fn cursor_direction_icon(resize_direction: Option<ResizeDirection>) -> CursorIcon {
fn cursor_direction_icon(resize_direction: Option<ResizeDirection>) -> NamedCursorIcon {
match resize_direction {
Some(resize_direction) => match resize_direction {
ResizeDirection::East => CursorIcon::EResize,
ResizeDirection::North => CursorIcon::NResize,
ResizeDirection::NorthEast => CursorIcon::NeResize,
ResizeDirection::NorthWest => CursorIcon::NwResize,
ResizeDirection::South => CursorIcon::SResize,
ResizeDirection::SouthEast => CursorIcon::SeResize,
ResizeDirection::SouthWest => CursorIcon::SwResize,
ResizeDirection::West => CursorIcon::WResize,
ResizeDirection::East => NamedCursorIcon::EResize,
ResizeDirection::North => NamedCursorIcon::NResize,
ResizeDirection::NorthEast => NamedCursorIcon::NeResize,
ResizeDirection::NorthWest => NamedCursorIcon::NwResize,
ResizeDirection::South => NamedCursorIcon::SResize,
ResizeDirection::SouthEast => NamedCursorIcon::SeResize,
ResizeDirection::SouthWest => NamedCursorIcon::SwResize,
ResizeDirection::West => NamedCursorIcon::WResize,
},
None => CursorIcon::Default,
None => NamedCursorIcon::Default,
}
}

Expand Down
40 changes: 19 additions & 21 deletions src/platform_impl/windows/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,23 @@ use windows_sys::Win32::{
},
WindowsAndMessaging::{
CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetCursorPos,
GetMenu, GetMessageW, KillTimer, LoadCursorW, PeekMessageW, PostMessageW,
RegisterClassExW, RegisterWindowMessageA, SetCursor, SetTimer, SetWindowPos,
TranslateMessage, CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA,
HTCAPTION, HTCLIENT, MINMAXINFO, MNC_CLOSE, MSG, NCCALCSIZE_PARAMS, PM_REMOVE, PT_PEN,
PT_TOUCH, RI_KEY_E0, RI_KEY_E1, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE,
SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA,
WINDOWPOS, WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED,
WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION,
WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT,
WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN,
WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE,
WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY,
WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE,
WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE,
WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED,
WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, WS_EX_LAYERED,
WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP,
WS_VISIBLE,
GetMenu, GetMessageW, KillTimer, PeekMessageW, PostMessageW, RegisterClassExW,
RegisterWindowMessageA, SetCursor, SetTimer, SetWindowPos, TranslateMessage,
CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA, HTCAPTION,
HTCLIENT, MINMAXINFO, MNC_CLOSE, MSG, NCCALCSIZE_PARAMS, PM_REMOVE, PT_PEN, PT_TOUCH,
RI_KEY_E0, RI_KEY_E1, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED,
SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS,
WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE,
WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION,
WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE,
WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN,
WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE,
WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN,
WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR,
WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP,
WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP,
WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT,
WS_OVERLAPPED, WS_POPUP, WS_VISIBLE,
},
},
};
Expand Down Expand Up @@ -1950,16 +1949,15 @@ unsafe fn public_window_callback_inner<T: 'static>(
// `WM_MOUSEMOVE` seems to come after `WM_SETCURSOR` for a given cursor movement.
let in_client_area = super::loword(lparam as u32) as u32 == HTCLIENT;
if in_client_area {
Some(window_state.mouse.cursor)
Some(window_state.mouse.cursor.clone())
} else {
None
}
};

match set_cursor_to {
Some(cursor) => {
let cursor = LoadCursorW(0, util::to_windows_cursor(cursor));
SetCursor(cursor);
SetCursor(util::to_windows_cursor(cursor));
result = ProcResult::Value(0);
}
None => result = ProcResult::DefWindowProc(wparam),
Expand Down
71 changes: 43 additions & 28 deletions src/platform_impl/windows/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::{

use once_cell::sync::Lazy;
use windows_sys::{
core::{HRESULT, PCWSTR},
core::HRESULT,
Win32::{
Foundation::{BOOL, HMODULE, HWND, RECT},
Graphics::Gdi::{ClientToScreen, HMONITOR},
Expand All @@ -24,16 +24,17 @@ use windows_sys::{
Input::KeyboardAndMouse::GetActiveWindow,
WindowsAndMessaging::{
ClipCursor, GetClientRect, GetClipCursor, GetSystemMetrics, GetWindowPlacement,
GetWindowRect, IsIconic, ShowCursor, 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, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN,
SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, SW_MAXIMIZE, WINDOWPLACEMENT,
GetWindowRect, IsIconic, LoadCursorW, ShowCursor, HCURSOR, 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, SM_CXVIRTUALSCREEN,
SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, SW_MAXIMIZE,
WINDOWPLACEMENT,
},
},
},
};

use crate::window::CursorIcon;
use crate::window::{CursorIcon, NamedCursorIcon};

pub fn encode_wide(string: impl AsRef<OsStr>) -> Vec<u16> {
string.as_ref().encode_wide().chain(once(0)).collect()
Expand Down Expand Up @@ -164,30 +165,44 @@ pub fn get_instance_handle() -> HMODULE {
unsafe { &__ImageBase as *const _ as _ }
}

pub(crate) fn to_windows_cursor(cursor: CursorIcon) -> PCWSTR {
pub(crate) fn to_windows_cursor(cursor: CursorIcon) -> HCURSOR {
match cursor {
CursorIcon::Default => IDC_ARROW,
CursorIcon::Pointer => IDC_HAND,
CursorIcon::Crosshair => IDC_CROSS,
CursorIcon::Text | CursorIcon::VerticalText => IDC_IBEAM,
CursorIcon::NotAllowed | CursorIcon::NoDrop => IDC_NO,
CursorIcon::Grab | CursorIcon::Grabbing | CursorIcon::Move | CursorIcon::AllScroll => {
IDC_SIZEALL
CursorIcon::Named(named_icon) => {
unsafe {
LoadCursorW(
0,
match named_icon {
NamedCursorIcon::Pointer => IDC_HAND,
NamedCursorIcon::Crosshair => IDC_CROSS,
NamedCursorIcon::Text | NamedCursorIcon::VerticalText => IDC_IBEAM,
NamedCursorIcon::NotAllowed | NamedCursorIcon::NoDrop => IDC_NO,
NamedCursorIcon::Grab
| NamedCursorIcon::Grabbing
| NamedCursorIcon::Move
| NamedCursorIcon::AllScroll => IDC_SIZEALL,
NamedCursorIcon::EResize
| NamedCursorIcon::WResize
| NamedCursorIcon::EwResize
| NamedCursorIcon::ColResize => IDC_SIZEWE,
NamedCursorIcon::NResize
| NamedCursorIcon::SResize
| NamedCursorIcon::NsResize
| NamedCursorIcon::RowResize => IDC_SIZENS,
NamedCursorIcon::NeResize
| NamedCursorIcon::SwResize
| NamedCursorIcon::NeswResize => IDC_SIZENESW,
NamedCursorIcon::NwResize
| NamedCursorIcon::SeResize
| NamedCursorIcon::NwseResize => IDC_SIZENWSE,
NamedCursorIcon::Wait => IDC_WAIT,
NamedCursorIcon::Progress => IDC_APPSTARTING,
NamedCursorIcon::Help => IDC_HELP,
_ => IDC_ARROW, // use arrow as default.
},
)
}
}
CursorIcon::EResize
| CursorIcon::WResize
| CursorIcon::EwResize
| CursorIcon::ColResize => IDC_SIZEWE,
CursorIcon::NResize
| CursorIcon::SResize
| CursorIcon::NsResize
| CursorIcon::RowResize => IDC_SIZENS,
CursorIcon::NeResize | CursorIcon::SwResize | CursorIcon::NeswResize => IDC_SIZENESW,
CursorIcon::NwResize | CursorIcon::SeResize | CursorIcon::NwseResize => IDC_SIZENWSE,
CursorIcon::Wait => IDC_WAIT,
CursorIcon::Progress => IDC_APPSTARTING,
CursorIcon::Help => IDC_HELP,
_ => IDC_ARROW, // use arrow for the missing cases.
CursorIcon::Custom(icon) => icon.inner.as_raw_handle(),
}
}

Expand Down
17 changes: 8 additions & 9 deletions src/platform_impl/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ use windows_sys::Win32::{
WindowsAndMessaging::{
CreateWindowExW, FlashWindowEx, GetClientRect, GetCursorPos, GetForegroundWindow,
GetSystemMetrics, GetWindowPlacement, GetWindowTextLengthW, GetWindowTextW,
IsWindowVisible, LoadCursorW, PeekMessageW, PostMessageW, RegisterClassExW, SetCursor,
SetCursorPos, SetForegroundWindow, SetWindowDisplayAffinity, SetWindowPlacement,
SetWindowPos, SetWindowTextW, CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, FLASHWINFO,
FLASHW_ALL, FLASHW_STOP, FLASHW_TIMERNOFG, FLASHW_TRAY, GWLP_HINSTANCE, HTBOTTOM,
HTBOTTOMLEFT, HTBOTTOMRIGHT, HTCAPTION, HTLEFT, HTRIGHT, HTTOP, HTTOPLEFT, HTTOPRIGHT,
NID_READY, PM_NOREMOVE, SM_DIGITIZER, SWP_ASYNCWINDOWPOS, SWP_NOACTIVATE, SWP_NOSIZE,
IsWindowVisible, PeekMessageW, PostMessageW, RegisterClassExW, SetCursor, SetCursorPos,
SetForegroundWindow, SetWindowDisplayAffinity, SetWindowPlacement, SetWindowPos,
SetWindowTextW, CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, FLASHWINFO, FLASHW_ALL,
FLASHW_STOP, FLASHW_TIMERNOFG, FLASHW_TRAY, GWLP_HINSTANCE, HTBOTTOM, HTBOTTOMLEFT,
HTBOTTOMRIGHT, HTCAPTION, HTLEFT, HTRIGHT, HTTOP, HTTOPLEFT, HTTOPRIGHT, NID_READY,
PM_NOREMOVE, SM_DIGITIZER, SWP_ASYNCWINDOWPOS, SWP_NOACTIVATE, SWP_NOSIZE,
SWP_NOZORDER, WDA_EXCLUDEFROMCAPTURE, WDA_NONE, WM_NCLBUTTONDOWN, WNDCLASSEXW,
},
},
Expand Down Expand Up @@ -347,10 +347,9 @@ impl Window {

#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
self.window_state_lock().mouse.cursor = cursor;
self.window_state_lock().mouse.cursor = cursor.clone();
self.thread_executor.execute_in_thread(move || unsafe {
let cursor = LoadCursorW(0, util::to_windows_cursor(cursor));
SetCursor(cursor);
SetCursor(util::to_windows_cursor(cursor.clone()));
});
}

Expand Down
Loading