1use core::fmt;
2use std::any::Any;
3use std::error::Error;
4use std::hash::Hash;
5use std::ops::Deref;
6use std::sync::Arc;
7use std::time::Duration;
8
9#[doc(inline)]
10pub use cursor_icon::CursorIcon;
11
12pub const MAX_CURSOR_SIZE: u16 = 2048;
14
15const PIXEL_SIZE: usize = 4;
16
17#[derive(Clone, Debug, Eq, Hash, PartialEq)]
19#[allow(clippy::exhaustive_enums)]
20pub enum Cursor {
21 Icon(CursorIcon),
22 Custom(CustomCursor),
23}
24
25impl Default for Cursor {
26 fn default() -> Self {
27 Self::Icon(CursorIcon::default())
28 }
29}
30
31impl From<CursorIcon> for Cursor {
32 fn from(icon: CursorIcon) -> Self {
33 Self::Icon(icon)
34 }
35}
36
37impl From<CustomCursor> for Cursor {
38 fn from(custom: CustomCursor) -> Self {
39 Self::Custom(custom)
40 }
41}
42
43#[derive(Clone, Debug)]
79pub struct CustomCursor(pub Arc<dyn CustomCursorProvider>);
80
81pub trait CustomCursorProvider: Any + fmt::Debug + Send + Sync {
82 fn is_animated(&self) -> bool;
84}
85
86impl PartialEq for CustomCursor {
87 fn eq(&self, other: &Self) -> bool {
88 Arc::ptr_eq(&self.0, &other.0)
89 }
90}
91
92impl Eq for CustomCursor {}
93
94impl Hash for CustomCursor {
95 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
96 Arc::as_ptr(&self.0).hash(state);
97 }
98}
99
100impl Deref for CustomCursor {
101 type Target = dyn CustomCursorProvider;
102
103 fn deref(&self) -> &Self::Target {
104 self.0.deref()
105 }
106}
107
108impl_dyn_casting!(CustomCursorProvider);
109
110#[derive(Debug, Clone, Eq, Hash, PartialEq)]
114#[non_exhaustive]
115pub enum CustomCursorSource {
116 Image(CursorImage),
124 Animation(CursorAnimation),
132 Url { hotspot_x: u16, hotspot_y: u16, url: String },
142}
143
144impl CustomCursorSource {
145 pub fn from_rgba(
149 rgba: Vec<u8>,
150 width: u16,
151 height: u16,
152 hotspot_x: u16,
153 hotspot_y: u16,
154 ) -> Result<Self, BadImage> {
155 CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self::Image)
156 }
157
158 pub fn from_animation(
161 duration: Duration,
162 cursors: Vec<CustomCursor>,
163 ) -> Result<Self, BadAnimation> {
164 CursorAnimation::new(duration, cursors).map(Self::Animation)
165 }
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
170#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
171#[non_exhaustive]
172pub enum BadImage {
173 TooLarge { width: u16, height: u16 },
177 ByteCountNotDivisibleBy4 { byte_count: usize },
180 DimensionsVsPixelCount { width: u16, height: u16, width_x_height: u64, pixel_count: u64 },
183 HotspotOutOfBounds { width: u16, height: u16, hotspot_x: u16, hotspot_y: u16 },
185}
186
187impl fmt::Display for BadImage {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 match self {
190 BadImage::TooLarge { width, height } => write!(
191 f,
192 "The specified dimensions ({width:?}x{height:?}) are too large. The maximum is \
193 {MAX_CURSOR_SIZE:?}x{MAX_CURSOR_SIZE:?}.",
194 ),
195 BadImage::ByteCountNotDivisibleBy4 { byte_count } => write!(
196 f,
197 "The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making \
198 it impossible to interpret as 32bpp RGBA pixels.",
199 ),
200 BadImage::DimensionsVsPixelCount { width, height, width_x_height, pixel_count } => {
201 write!(
202 f,
203 "The specified dimensions ({width:?}x{height:?}) don't match the number of \
204 pixels supplied by the `rgba` argument ({pixel_count:?}). For those \
205 dimensions, the expected pixel count is {width_x_height:?}.",
206 )
207 },
208 BadImage::HotspotOutOfBounds { width, height, hotspot_x, hotspot_y } => write!(
209 f,
210 "The specified hotspot ({hotspot_x:?}, {hotspot_y:?}) is outside the image bounds \
211 ({width:?}x{height:?}).",
212 ),
213 }
214 }
215}
216
217impl Error for BadImage {}
218
219#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
221#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
222#[non_exhaustive]
223pub enum BadAnimation {
224 Empty,
226 Animation,
228}
229
230impl fmt::Display for BadAnimation {
231 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232 match self {
233 Self::Empty => write!(f, "No cursors supplied"),
234 Self::Animation => write!(f, "A supplied cursor is an animation"),
235 }
236 }
237}
238
239impl Error for BadAnimation {}
240
241#[derive(Debug, Clone, Eq, Hash, PartialEq)]
242pub struct CursorImage {
243 pub(crate) rgba: Vec<u8>,
244 pub(crate) width: u16,
245 pub(crate) height: u16,
246 pub(crate) hotspot_x: u16,
247 pub(crate) hotspot_y: u16,
248}
249
250impl CursorImage {
251 pub(crate) fn from_rgba(
252 rgba: Vec<u8>,
253 width: u16,
254 height: u16,
255 hotspot_x: u16,
256 hotspot_y: u16,
257 ) -> Result<Self, BadImage> {
258 if width > MAX_CURSOR_SIZE || height > MAX_CURSOR_SIZE {
259 return Err(BadImage::TooLarge { width, height });
260 }
261
262 if rgba.len() % PIXEL_SIZE != 0 {
263 return Err(BadImage::ByteCountNotDivisibleBy4 { byte_count: rgba.len() });
264 }
265
266 let pixel_count = (rgba.len() / PIXEL_SIZE) as u64;
267 let width_x_height = width as u64 * height as u64;
268 if pixel_count != width_x_height {
269 return Err(BadImage::DimensionsVsPixelCount {
270 width,
271 height,
272 width_x_height,
273 pixel_count,
274 });
275 }
276
277 if hotspot_x >= width || hotspot_y >= height {
278 return Err(BadImage::HotspotOutOfBounds { width, height, hotspot_x, hotspot_y });
279 }
280
281 Ok(CursorImage { rgba, width, height, hotspot_x, hotspot_y })
282 }
283
284 pub fn buffer(&self) -> &[u8] {
285 self.rgba.as_slice()
286 }
287
288 pub fn buffer_mut(&mut self) -> &mut [u8] {
289 self.rgba.as_mut_slice()
290 }
291
292 pub fn width(&self) -> u16 {
293 self.width
294 }
295
296 pub fn height(&self) -> u16 {
297 self.height
298 }
299
300 pub fn hotspot_x(&self) -> u16 {
301 self.hotspot_x
302 }
303
304 pub fn hotspot_y(&self) -> u16 {
305 self.hotspot_y
306 }
307}
308
309#[derive(Debug, Clone, PartialEq, Eq, Hash)]
310pub struct CursorAnimation {
311 pub(crate) duration: Duration,
312 pub(crate) cursors: Vec<CustomCursor>,
313}
314
315impl CursorAnimation {
316 pub fn new(duration: Duration, cursors: Vec<CustomCursor>) -> Result<Self, BadAnimation> {
317 if cursors.is_empty() {
318 return Err(BadAnimation::Empty);
319 }
320
321 if cursors.iter().any(|cursor| cursor.is_animated()) {
322 return Err(BadAnimation::Animation);
323 }
324
325 Ok(Self { duration, cursors })
326 }
327
328 pub fn duration(&self) -> Duration {
329 self.duration
330 }
331
332 pub fn cursors(&self) -> &[CustomCursor] {
333 self.cursors.as_slice()
334 }
335
336 pub fn into_raw(self) -> (Duration, Vec<CustomCursor>) {
337 (self.duration, self.cursors)
338 }
339}