1use core::fmt;
2use std::error::Error;
3use std::hash::{Hash, Hasher};
4use std::sync::Arc;
5
6use cursor_icon::CursorIcon;
7
8use crate::platform_impl::{PlatformCustomCursor, PlatformCustomCursorSource};
9
10/// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`].
11pub const MAX_CURSOR_SIZE: u16 = 2048;
12
13const PIXEL_SIZE: usize = 4;
14
15/// See [`Window::set_cursor()`][crate::window::Window::set_cursor] for more details.
16#[derive(Clone, Debug, Eq, Hash, PartialEq)]
17pub enum Cursor {
18 Icon(CursorIcon),
19 Custom(CustomCursor),
20}
21
22impl Default for Cursor {
23 fn default() -> Self {
24 Self::Icon(CursorIcon::default())
25 }
26}
27
28impl From<CursorIcon> for Cursor {
29 fn from(icon: CursorIcon) -> Self {
30 Self::Icon(icon)
31 }
32}
33
34impl From<CustomCursor> for Cursor {
35 fn from(custom: CustomCursor) -> Self {
36 Self::Custom(custom)
37 }
38}
39
40/// Use a custom image as a cursor (mouse pointer).
41///
42/// Is guaranteed to be cheap to clone.
43///
44/// ## Platform-specific
45///
46/// **Web**: Some browsers have limits on cursor sizes usually at 128x128.
47///
48/// # Example
49///
50/// ```no_run
51/// # use winit::event_loop::ActiveEventLoop;
52/// # use winit::window::Window;
53/// # fn scope(event_loop: &ActiveEventLoop, window: &Window) {
54/// use winit::window::CustomCursor;
55///
56/// let w = 10;
57/// let h = 10;
58/// let rgba = vec![255; (w * h * 4) as usize];
59///
60/// #[cfg(not(target_family = "wasm"))]
61/// let source = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
62///
63/// #[cfg(target_family = "wasm")]
64/// let source = {
65/// use winit::platform::web::CustomCursorExtWebSys;
66/// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0)
67/// };
68///
69/// let custom_cursor = event_loop.create_custom_cursor(source);
70///
71/// window.set_cursor(custom_cursor.clone());
72/// # }
73/// ```
74#[derive(Clone, Debug, Eq, Hash, PartialEq)]
75pub struct CustomCursor {
76 /// Platforms should make sure this is cheap to clone.
77 pub(crate) inner: PlatformCustomCursor,
78}
79
80impl CustomCursor {
81 /// Creates a new cursor from an rgba buffer.
82 ///
83 /// The alpha channel is assumed to be **not** premultiplied.
84 pub fn from_rgba(
85 rgba: impl Into<Vec<u8>>,
86 width: u16,
87 height: u16,
88 hotspot_x: u16,
89 hotspot_y: u16,
90 ) -> Result<CustomCursorSource, BadImage> {
91 let _span =
92 tracing::debug_span!("winit::Cursor::from_rgba", width, height, hotspot_x, hotspot_y)
93 .entered();
94
95 Ok(CustomCursorSource {
96 inner: PlatformCustomCursorSource::from_rgba(
97 rgba.into(),
98 width,
99 height,
100 hotspot_x,
101 hotspot_y,
102 )?,
103 })
104 }
105}
106
107/// Source for [`CustomCursor`].
108///
109/// See [`CustomCursor`] for more details.
110#[derive(Debug)]
111pub struct CustomCursorSource {
112 pub(crate) inner: PlatformCustomCursorSource,
113}
114
115/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
116#[derive(Debug, Clone)]
117pub enum BadImage {
118 /// Produced when the image dimensions are larger than [`MAX_CURSOR_SIZE`]. This doesn't
119 /// guarantee that the cursor will work, but should avoid many platform and device specific
120 /// limits.
121 TooLarge { width: u16, height: u16 },
122 /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
123 /// safely interpreted as 32bpp RGBA pixels.
124 ByteCountNotDivisibleBy4 { byte_count: usize },
125 /// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
126 /// At least one of your arguments is incorrect.
127 DimensionsVsPixelCount { width: u16, height: u16, width_x_height: u64, pixel_count: u64 },
128 /// Produced when the hotspot is outside the image bounds
129 HotspotOutOfBounds { width: u16, height: u16, hotspot_x: u16, hotspot_y: u16 },
130}
131
132impl fmt::Display for BadImage {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 match self {
135 BadImage::TooLarge { width, height } => write!(
136 f,
137 "The specified dimensions ({width:?}x{height:?}) are too large. The maximum is \
138 {MAX_CURSOR_SIZE:?}x{MAX_CURSOR_SIZE:?}.",
139 ),
140 BadImage::ByteCountNotDivisibleBy4 { byte_count } => write!(
141 f,
142 "The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making \
143 it impossible to interpret as 32bpp RGBA pixels.",
144 ),
145 BadImage::DimensionsVsPixelCount { width, height, width_x_height, pixel_count } => {
146 write!(
147 f,
148 "The specified dimensions ({width:?}x{height:?}) don't match the number of \
149 pixels supplied by the `rgba` argument ({pixel_count:?}). For those \
150 dimensions, the expected pixel count is {width_x_height:?}.",
151 )
152 },
153 BadImage::HotspotOutOfBounds { width, height, hotspot_x, hotspot_y } => write!(
154 f,
155 "The specified hotspot ({hotspot_x:?}, {hotspot_y:?}) is outside the image bounds \
156 ({width:?}x{height:?}).",
157 ),
158 }
159 }
160}
161
162impl Error for BadImage {}
163
164/// Platforms export this directly as `PlatformCustomCursorSource` if they need to only work with
165/// images.
166#[allow(dead_code)]
167#[derive(Debug)]
168pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage);
169
170#[allow(dead_code)]
171impl OnlyCursorImageSource {
172 pub(crate) fn from_rgba(
173 rgba: Vec<u8>,
174 width: u16,
175 height: u16,
176 hotspot_x: u16,
177 hotspot_y: u16,
178 ) -> Result<Self, BadImage> {
179 CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self)
180 }
181}
182
183/// Platforms export this directly as `PlatformCustomCursor` if they don't implement caching.
184#[allow(dead_code)]
185#[derive(Debug, Clone)]
186pub(crate) struct OnlyCursorImage(pub(crate) Arc<CursorImage>);
187
188impl Hash for OnlyCursorImage {
189 fn hash<H: Hasher>(&self, state: &mut H) {
190 Arc::as_ptr(&self.0).hash(state);
191 }
192}
193
194impl PartialEq for OnlyCursorImage {
195 fn eq(&self, other: &Self) -> bool {
196 Arc::ptr_eq(&self.0, &other.0)
197 }
198}
199
200impl Eq for OnlyCursorImage {}
201
202#[derive(Debug)]
203#[allow(dead_code)]
204pub(crate) struct CursorImage {
205 pub(crate) rgba: Vec<u8>,
206 pub(crate) width: u16,
207 pub(crate) height: u16,
208 pub(crate) hotspot_x: u16,
209 pub(crate) hotspot_y: u16,
210}
211
212impl CursorImage {
213 pub(crate) fn from_rgba(
214 rgba: Vec<u8>,
215 width: u16,
216 height: u16,
217 hotspot_x: u16,
218 hotspot_y: u16,
219 ) -> Result<Self, BadImage> {
220 if width > MAX_CURSOR_SIZE || height > MAX_CURSOR_SIZE {
221 return Err(BadImage::TooLarge { width, height });
222 }
223
224 if rgba.len() % PIXEL_SIZE != 0 {
225 return Err(BadImage::ByteCountNotDivisibleBy4 { byte_count: rgba.len() });
226 }
227
228 let pixel_count = (rgba.len() / PIXEL_SIZE) as u64;
229 let width_x_height = width as u64 * height as u64;
230 if pixel_count != width_x_height {
231 return Err(BadImage::DimensionsVsPixelCount {
232 width,
233 height,
234 width_x_height,
235 pixel_count,
236 });
237 }
238
239 if hotspot_x >= width || hotspot_y >= height {
240 return Err(BadImage::HotspotOutOfBounds { width, height, hotspot_x, hotspot_y });
241 }
242
243 Ok(CursorImage { rgba, width, height, hotspot_x, hotspot_y })
244 }
245}
246
247// Platforms that don't support cursors will export this as `PlatformCustomCursor`.
248#[derive(Debug, Clone, Hash, PartialEq, Eq)]
249pub(crate) struct NoCustomCursor;
250
251#[allow(dead_code)]
252impl NoCustomCursor {
253 pub(crate) fn from_rgba(
254 rgba: Vec<u8>,
255 width: u16,
256 height: u16,
257 hotspot_x: u16,
258 hotspot_y: u16,
259 ) -> Result<Self, BadImage> {
260 CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
261 Ok(Self)
262 }
263}
264