Merge Window::set_cursor_icon() and Window::set_custom_cursor() (#3308)

This commit is contained in:
daxpedda 2023-12-25 07:20:52 +01:00 committed by GitHub
parent 34dd2cdba9
commit be4a660011
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 165 additions and 159 deletions

View file

@ -12,7 +12,8 @@ Unreleased` header.
# Unreleased
- On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
- Add `Window::set_custom_cursor`
- **Breaking:** Remove `Window::set_cursor_icon`
- Add `Window::set_cursor` which takes a `CursorIcon` or `CustomCursor`
- Add `CustomCursor`
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.

View file

@ -31,7 +31,7 @@ fn main() -> Result<(), impl std::error::Error> {
..
} => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
window.set_cursor_icon(CURSORS[cursor_idx]);
window.set_cursor(CURSORS[cursor_idx]);
if cursor_idx < CURSORS.len() - 1 {
cursor_idx += 1;
} else {

View file

@ -6,7 +6,7 @@ use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{EventLoop, EventLoopWindowTarget},
keyboard::Key,
window::{CustomCursor, WindowBuilder},
window::{CursorIcon, CustomCursor, WindowBuilder},
};
fn decode_cursor<T>(bytes: &[u8], window_target: &EventLoopWindowTarget<T>) -> CustomCursor {
@ -62,12 +62,12 @@ fn main() -> Result<(), impl std::error::Error> {
} => match key.as_ref() {
Key::Character("1") => {
log::debug!("Setting cursor to {:?}", cursor_idx);
window.set_custom_cursor(&custom_cursors[cursor_idx]);
window.set_cursor(custom_cursors[cursor_idx].clone());
cursor_idx = (cursor_idx + 1) % 2;
}
Key::Character("2") => {
log::debug!("Setting cursor icon to default");
window.set_cursor_icon(Default::default());
window.set_cursor(CursorIcon::default());
}
Key::Character("3") => {
cursor_visible = !cursor_visible;

View file

@ -84,7 +84,7 @@ fn main() -> Result<(), impl std::error::Error> {
"1" => window.set_window_level(WindowLevel::AlwaysOnTop),
"2" => window.set_window_level(WindowLevel::AlwaysOnBottom),
"3" => window.set_window_level(WindowLevel::Normal),
"c" => window.set_cursor_icon(match state {
"c" => window.set_cursor(match state {
true => CursorIcon::Progress,
false => CursorIcon::Default,
}),

View file

@ -40,7 +40,7 @@ fn main() -> Result<(), impl std::error::Error> {
if new_location != cursor_location {
cursor_location = new_location;
window.set_cursor_icon(cursor_direction_icon(cursor_location))
window.set_cursor(cursor_direction_icon(cursor_location))
}
}
}

View file

@ -3,6 +3,8 @@ use std::hash::Hasher;
use std::sync::Arc;
use std::{error::Error, hash::Hash};
use cursor_icon::CursorIcon;
use crate::event_loop::EventLoopWindowTarget;
use crate::platform_impl::{self, PlatformCustomCursor, PlatformCustomCursorBuilder};
@ -11,8 +13,35 @@ pub const MAX_CURSOR_SIZE: u16 = 2048;
const PIXEL_SIZE: usize = 4;
/// See [`Window::set_cursor()`](crate::window::Window::set_cursor) for more details.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum Cursor {
Icon(CursorIcon),
Custom(CustomCursor),
}
impl Default for Cursor {
fn default() -> Self {
Self::Icon(CursorIcon::default())
}
}
impl From<CursorIcon> for Cursor {
fn from(icon: CursorIcon) -> Self {
Self::Icon(icon)
}
}
impl From<CustomCursor> for Cursor {
fn from(custom: CustomCursor) -> Self {
Self::Custom(custom)
}
}
/// Use a custom image as a cursor (mouse pointer).
///
/// Is guaranteed to be cheap to clone.
///
/// ## Platform-specific
///
/// **Web**: Some browsers have limits on cursor sizes usually at 128x128.
@ -44,7 +73,7 @@ const PIXEL_SIZE: usize = 4;
/// let custom_cursor = builder.build(&event_loop);
///
/// let window = Window::new(&event_loop).unwrap();
/// window.set_custom_cursor(&custom_cursor);
/// window.set_cursor(custom_cursor.clone());
/// ```
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct CustomCursor {

View file

@ -18,6 +18,7 @@ use android_activity::{
use once_cell::sync::Lazy;
use crate::{
cursor::Cursor,
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error,
event::{self, Force, InnerSizeWriter, StartCause},
@ -908,9 +909,7 @@ impl Window {
pub fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
pub(crate) fn set_custom_cursor(&self, _: PlatformCustomCursor) {}
pub fn set_cursor(&self, _: Cursor) {}
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(

View file

@ -11,17 +11,18 @@ use super::app_state::EventWrapper;
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
use super::view::{WinitUIWindow, WinitView, WinitViewController};
use crate::{
cursor::Cursor,
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, WindowEvent},
icon::Icon,
platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations},
platform_impl::platform::{
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, PlatformCustomCursor,
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
},
window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel,
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowButtons, WindowId as RootWindowId, WindowLevel,
},
};
@ -173,12 +174,8 @@ impl Inner {
self.view.contentScaleFactor() as _
}
pub fn set_cursor_icon(&self, _cursor: CursorIcon) {
debug!("`Window::set_cursor_icon` ignored on iOS")
}
pub(crate) fn set_custom_cursor(&self, _: PlatformCustomCursor) {
debug!("`Window::set_custom_cursor` ignored on iOS")
pub fn set_cursor(&self, _cursor: Cursor) {
debug!("`Window::set_cursor` ignored on iOS")
}
pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> {

View file

@ -31,7 +31,7 @@ use crate::{
scancode::PhysicalKeyExtScancode,
},
window::{
ActivationToken, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme,
ActivationToken, Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme,
UserAttentionType, WindowAttributes, WindowButtons, WindowLevel,
},
};
@ -422,13 +422,8 @@ impl Window {
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor))
}
#[inline]
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
x11_or_wayland!(match self; Window(w) => w.set_custom_cursor(cursor))
pub fn set_cursor(&self, cursor: Cursor) {
x11_or_wayland!(match self; Window(w) => w.set_cursor(cursor))
}
#[inline]

View file

@ -20,11 +20,11 @@ use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{Ime, WindowEvent};
use crate::event_loop::AsyncRequestSerial;
use crate::platform_impl::{
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor,
PlatformIcon, PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
};
use crate::window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowLevel,
};
@ -502,16 +502,13 @@ impl Window {
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
self.window_state.lock().unwrap().set_cursor(cursor);
}
pub fn set_cursor(&self, cursor: Cursor) {
let window_state = &mut self.window_state.lock().unwrap();
#[inline]
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
self.window_state
.lock()
.unwrap()
.set_custom_cursor(&cursor.0);
match cursor {
Cursor::Icon(icon) => window_state.set_cursor(icon),
Cursor::Custom(cursor) => window_state.set_custom_cursor(&cursor.inner.0),
}
}
#[inline]

View file

@ -7,7 +7,6 @@ use std::{
sync::{Arc, Mutex, MutexGuard},
};
use cursor_icon::CursorIcon;
use x11rb::{
connection::Connection,
properties::{WmHints, WmHintsState, WmSizeHints, WmSizeHintsSpecification},
@ -21,6 +20,7 @@ use x11rb::{
};
use crate::{
cursor::Cursor,
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, InnerSizeWriter, WindowEvent},
@ -30,8 +30,8 @@ use crate::{
atoms::*, xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender,
X11Error,
},
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor,
PlatformIcon, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
},
window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
@ -1537,30 +1537,33 @@ impl UnownedWindow {
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let old_cursor = replace(
&mut *self.selected_cursor.lock().unwrap(),
SelectedCursor::Named(cursor),
);
pub fn set_cursor(&self, cursor: Cursor) {
match cursor {
Cursor::Icon(icon) => {
let old_cursor = replace(
&mut *self.selected_cursor.lock().unwrap(),
SelectedCursor::Named(icon),
);
#[allow(clippy::mutex_atomic)]
if SelectedCursor::Named(cursor) != old_cursor && *self.cursor_visible.lock().unwrap() {
self.xconn.set_cursor_icon(self.xwindow, Some(cursor));
#[allow(clippy::mutex_atomic)]
if SelectedCursor::Named(icon) != old_cursor && *self.cursor_visible.lock().unwrap()
{
self.xconn.set_cursor_icon(self.xwindow, Some(icon));
}
}
Cursor::Custom(cursor) => {
let new_cursor = unsafe { CustomCursor::new(&self.xconn, &cursor.inner.0) };
#[allow(clippy::mutex_atomic)]
if *self.cursor_visible.lock().unwrap() {
self.xconn.set_custom_cursor(self.xwindow, &new_cursor);
}
*self.selected_cursor.lock().unwrap() = SelectedCursor::Custom(new_cursor);
}
}
}
#[inline]
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
let new_cursor = unsafe { CustomCursor::new(&self.xconn, &cursor.0) };
#[allow(clippy::mutex_atomic)]
if *self.cursor_visible.lock().unwrap() {
self.xconn.set_custom_cursor(self.xwindow, &new_cursor);
}
*self.selected_cursor.lock().unwrap() = SelectedCursor::Custom(new_cursor);
}
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap();

View file

@ -6,6 +6,7 @@ use std::ops;
use std::sync::{Mutex, MutexGuard};
use crate::{
cursor::Cursor,
dpi::{
LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size, Size::Logical,
},
@ -20,11 +21,11 @@ use crate::{
monitor::{self, MonitorHandle, VideoMode},
view::WinitView,
window_delegate::WinitWindowDelegate,
Fullscreen, OsError, PlatformCustomCursor,
Fullscreen, OsError,
},
window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel,
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowButtons, WindowId as RootWindowId, WindowLevel,
},
};
use core_graphics::display::{CGDisplay, CGPoint};
@ -795,17 +796,19 @@ impl WinitWindow {
buttons
}
pub fn set_cursor_icon(&self, icon: CursorIcon) {
let view = self.view();
view.set_cursor_icon(cursor_from_icon(icon));
self.invalidateCursorRectsForView(&view);
}
#[inline]
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
let view = self.view();
view.set_cursor_icon(cursor.0.clone());
self.invalidateCursorRectsForView(&view);
pub fn set_cursor(&self, cursor: Cursor) {
match cursor {
Cursor::Icon(icon) => {
let view = self.view();
view.set_cursor_icon(cursor_from_icon(icon));
self.invalidateCursorRectsForView(&view);
}
Cursor::Custom(cursor) => {
let view = self.view();
view.set_cursor_icon(cursor.inner.0.clone());
self.invalidateCursorRectsForView(&view);
}
}
}
#[inline]

View file

@ -4,6 +4,7 @@ use std::{
};
use crate::{
cursor::Cursor,
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error,
platform_impl::Fullscreen,
@ -12,8 +13,8 @@ use crate::{
};
use super::{
EventLoopWindowTarget, MonitorHandle, PlatformCustomCursor,
PlatformSpecificWindowBuilderAttributes, RedoxSocket, TimeSocket, WindowId, WindowProperties,
EventLoopWindowTarget, MonitorHandle, PlatformSpecificWindowBuilderAttributes, RedoxSocket,
TimeSocket, WindowId, WindowProperties,
};
// These values match the values uses in the `window_new` function in orbital:
@ -350,9 +351,7 @@ impl Window {
pub fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
#[inline]
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
pub(crate) fn set_custom_cursor(&self, _: PlatformCustomCursor) {}
pub fn set_cursor(&self, _: Cursor) {}
#[inline]
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {

View file

@ -10,7 +10,7 @@ use std::{
};
use crate::{
cursor::{BadImage, CursorImage},
cursor::{BadImage, Cursor, CursorImage},
platform_impl::platform::r#async,
};
use cursor_icon::CursorIcon;
@ -141,35 +141,34 @@ impl CursorState {
})))
}
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
pub fn set_cursor(&self, cursor: Cursor) {
let mut this = self.0.borrow_mut();
if let SelectedCursor::ImageLoading { state, .. } = &this.cursor {
if let ImageState::Loading(state) = state.get().borrow_mut().deref_mut() {
state.take();
}
}
match cursor {
Cursor::Icon(icon) => {
if let SelectedCursor::ImageLoading { state, .. } = &this.cursor {
if let ImageState::Loading(state) = state.get().borrow_mut().deref_mut() {
state.take();
}
}
this.cursor = SelectedCursor::Named(cursor);
this.set_style();
}
pub(crate) fn set_custom_cursor(&self, cursor: CustomCursor) {
let mut this = self.0.borrow_mut();
match cursor.0.get().borrow_mut().deref_mut() {
ImageState::Loading(state) => {
this.cursor = SelectedCursor::ImageLoading {
state: cursor.0.clone(),
previous: mem::take(&mut this.cursor).into(),
};
*state = Some(Rc::downgrade(&self.0));
}
ImageState::Failed => log::error!("tried to load invalid cursor"),
ImageState::Ready(image) => {
this.cursor = SelectedCursor::ImageReady(image.clone());
this.cursor = SelectedCursor::Named(icon);
this.set_style();
}
Cursor::Custom(cursor) => match cursor.inner.0.get().borrow_mut().deref_mut() {
ImageState::Loading(state) => {
this.cursor = SelectedCursor::ImageLoading {
state: cursor.inner.0.clone(),
previous: mem::take(&mut this.cursor).into(),
};
*state = Some(Rc::downgrade(&self.0));
}
ImageState::Failed => log::error!("tried to load invalid cursor"),
ImageState::Ready(image) => {
this.cursor = SelectedCursor::ImageReady(image.clone());
this.set_style();
}
},
}
}

View file

@ -2,14 +2,13 @@ use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOE};
use crate::icon::Icon;
use crate::window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowId as RootWI, WindowLevel,
};
use crate::SendSyncWrapper;
use super::cursor::CursorState;
use super::r#async::Dispatcher;
use super::PlatformCustomCursor;
use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen};
use web_sys::HtmlCanvasElement;
@ -212,13 +211,8 @@ impl Inner {
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
self.cursor.set_cursor_icon(cursor)
}
#[inline]
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
self.cursor.set_custom_cursor(cursor)
pub fn set_cursor(&self, cursor: Cursor) {
self.cursor.set_cursor(cursor)
}
#[inline]

View file

@ -55,6 +55,7 @@ use windows_sys::Win32::{
};
use crate::{
cursor::Cursor,
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
icon::Icon,
@ -72,12 +73,11 @@ use crate::{
monitor::{self, MonitorHandle},
util,
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
Fullscreen, PlatformCustomCursor, PlatformSpecificWindowBuilderAttributes, SelectedCursor,
WindowId,
Fullscreen, PlatformSpecificWindowBuilderAttributes, SelectedCursor, WindowId,
},
window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowLevel,
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowButtons, WindowLevel,
},
};
@ -395,27 +395,30 @@ impl Window {
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
self.window_state_lock().mouse.selected_cursor = SelectedCursor::Named(cursor);
self.thread_executor.execute_in_thread(move || unsafe {
let cursor = LoadCursorW(0, util::to_windows_cursor(cursor));
SetCursor(cursor);
});
}
#[inline]
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
let new_cursor = match WinCursor::new(&cursor.0) {
Ok(cursor) => cursor,
Err(err) => {
warn!("Failed to create custom cursor: {err}");
return;
pub fn set_cursor(&self, cursor: Cursor) {
match cursor {
Cursor::Icon(icon) => {
self.window_state_lock().mouse.selected_cursor = SelectedCursor::Named(icon);
self.thread_executor.execute_in_thread(move || unsafe {
let cursor = LoadCursorW(0, util::to_windows_cursor(icon));
SetCursor(cursor);
});
}
};
self.window_state_lock().mouse.selected_cursor = SelectedCursor::Custom(new_cursor.clone());
self.thread_executor.execute_in_thread(move || unsafe {
SetCursor(new_cursor.as_raw_handle());
});
Cursor::Custom(cursor) => {
let new_cursor = match WinCursor::new(&cursor.inner.0) {
Ok(cursor) => cursor,
Err(err) => {
warn!("Failed to create custom cursor: {err}");
return;
}
};
self.window_state_lock().mouse.selected_cursor =
SelectedCursor::Custom(new_cursor.clone());
self.thread_executor.execute_in_thread(move || unsafe {
SetCursor(new_cursor.as_raw_handle());
});
}
}
}
#[inline]

View file

@ -9,7 +9,7 @@ use crate::{
platform_impl, SendSyncWrapper,
};
pub use crate::cursor::{BadImage, CustomCursor, CustomCursorBuilder, MAX_CURSOR_SIZE};
pub use crate::cursor::{BadImage, Cursor, CustomCursor, CustomCursorBuilder, MAX_CURSOR_SIZE};
pub use crate::icon::{BadIcon, Icon};
#[doc(inline)]
@ -1336,30 +1336,17 @@ impl Window {
/// Cursor functions.
impl Window {
/// Modifies the cursor icon of the window.
/// Overwrites cursors set in [`Window::set_custom_cursor`].
///
/// ## Platform-specific
///
/// - **iOS / Android / Orbital:** Unsupported.
/// - **Web:** Custom cursors have to be loaded and decoded first, until
/// then the previous cursor is shown.
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
pub fn set_cursor(&self, cursor: impl Into<Cursor>) {
let cursor = cursor.into();
self.window
.maybe_queue_on_main(move |w| w.set_cursor_icon(cursor))
}
/// Modifies the cursor icon of the window with a custom cursor.
/// Overwrites cursors set in [`Window::set_cursor_icon`].
///
/// ## Platform-specific
///
/// - **iOS / Android / Orbital:** Unsupported.
/// - **Web:** Cursor have to be loaded and decoded first, which introduces
/// a delay until cursors are shown.
#[inline]
pub fn set_custom_cursor(&self, cursor: &CustomCursor) {
let cursor = cursor.inner.clone();
self.window
.maybe_queue_on_main(move |w| w.set_custom_cursor(cursor))
.maybe_queue_on_main(move |w| w.set_cursor(cursor))
}
/// Changes the position of the cursor in window coordinates.