1 | use core::fmt; |
2 | use std::error::Error; |
3 | use std::hash::{Hash, Hasher}; |
4 | use std::sync::Arc; |
5 | |
6 | use cursor_icon::CursorIcon; |
7 | |
8 | use crate::platform_impl::{PlatformCustomCursor, PlatformCustomCursorSource}; |
9 | |
10 | /// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`]. |
11 | pub const MAX_CURSOR_SIZE: u16 = 2048; |
12 | |
13 | const 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)] |
17 | pub enum Cursor { |
18 | Icon(CursorIcon), |
19 | Custom(CustomCursor), |
20 | } |
21 | |
22 | impl Default for Cursor { |
23 | fn default() -> Self { |
24 | Self::Icon(CursorIcon::default()) |
25 | } |
26 | } |
27 | |
28 | impl From<CursorIcon> for Cursor { |
29 | fn from(icon: CursorIcon) -> Self { |
30 | Self::Icon(icon) |
31 | } |
32 | } |
33 | |
34 | impl 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)] |
75 | pub struct CustomCursor { |
76 | /// Platforms should make sure this is cheap to clone. |
77 | pub(crate) inner: PlatformCustomCursor, |
78 | } |
79 | |
80 | impl 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)] |
111 | pub 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)] |
117 | pub 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 | |
132 | impl 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 | |
162 | impl 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)] |
168 | pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage); |
169 | |
170 | #[allow (dead_code)] |
171 | impl 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)] |
186 | pub(crate) struct OnlyCursorImage(pub(crate) Arc<CursorImage>); |
187 | |
188 | impl Hash for OnlyCursorImage { |
189 | fn hash<H: Hasher>(&self, state: &mut H) { |
190 | Arc::as_ptr(&self.0).hash(state); |
191 | } |
192 | } |
193 | |
194 | impl PartialEq for OnlyCursorImage { |
195 | fn eq(&self, other: &Self) -> bool { |
196 | Arc::ptr_eq(&self.0, &other.0) |
197 | } |
198 | } |
199 | |
200 | impl Eq for OnlyCursorImage {} |
201 | |
202 | #[derive (Debug)] |
203 | #[allow (dead_code)] |
204 | pub(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 | |
212 | impl 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)] |
249 | pub(crate) struct NoCustomCursor; |
250 | |
251 | #[allow (dead_code)] |
252 | impl 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 | |