1use core::fmt;
2use std::error::Error;
3use std::hash::Hash;
4use std::ops::Deref;
5use std::sync::Arc;
6use std::time::Duration;
7
8#[doc(inline)]
9pub use cursor_icon::CursorIcon;
10
11use crate::as_any::AsAny;
12
13pub const MAX_CURSOR_SIZE: u16 = 2048;
15
16const PIXEL_SIZE: usize = 4;
17
18#[derive(Clone, Debug, Eq, Hash, PartialEq)]
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: AsAny + 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)]
114pub enum CustomCursorSource {
115 Image(CursorImage),
123 Animation(CursorAnimation),
131 Url { hotspot_x: u16, hotspot_y: u16, url: String },
141}
142
143impl CustomCursorSource {
144 pub fn from_rgba(
148 rgba: Vec<u8>,
149 width: u16,
150 height: u16,
151 hotspot_x: u16,
152 hotspot_y: u16,
153 ) -> Result<Self, BadImage> {
154 CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self::Image)
155 }
156
157 pub fn from_animation(
160 duration: Duration,
161 cursors: Vec<CustomCursor>,
162 ) -> Result<Self, BadAnimation> {
163 CursorAnimation::new(duration, cursors).map(Self::Animation)
164 }
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
169#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
170pub enum BadImage {
171 TooLarge { width: u16, height: u16 },
175 ByteCountNotDivisibleBy4 { byte_count: usize },
178 DimensionsVsPixelCount { width: u16, height: u16, width_x_height: u64, pixel_count: u64 },
181 HotspotOutOfBounds { width: u16, height: u16, hotspot_x: u16, hotspot_y: u16 },
183}
184
185impl fmt::Display for BadImage {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 match self {
188 BadImage::TooLarge { width, height } => write!(
189 f,
190 "The specified dimensions ({width:?}x{height:?}) are too large. The maximum is \
191 {MAX_CURSOR_SIZE:?}x{MAX_CURSOR_SIZE:?}.",
192 ),
193 BadImage::ByteCountNotDivisibleBy4 { byte_count } => write!(
194 f,
195 "The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making \
196 it impossible to interpret as 32bpp RGBA pixels.",
197 ),
198 BadImage::DimensionsVsPixelCount { width, height, width_x_height, pixel_count } => {
199 write!(
200 f,
201 "The specified dimensions ({width:?}x{height:?}) don't match the number of \
202 pixels supplied by the `rgba` argument ({pixel_count:?}). For those \
203 dimensions, the expected pixel count is {width_x_height:?}.",
204 )
205 },
206 BadImage::HotspotOutOfBounds { width, height, hotspot_x, hotspot_y } => write!(
207 f,
208 "The specified hotspot ({hotspot_x:?}, {hotspot_y:?}) is outside the image bounds \
209 ({width:?}x{height:?}).",
210 ),
211 }
212 }
213}
214
215impl Error for BadImage {}
216
217#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
219#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
220pub enum BadAnimation {
221 Empty,
223 Animation,
225}
226
227impl fmt::Display for BadAnimation {
228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229 match self {
230 Self::Empty => write!(f, "No cursors supplied"),
231 Self::Animation => write!(f, "A supplied cursor is an animation"),
232 }
233 }
234}
235
236impl Error for BadAnimation {}
237
238#[derive(Debug, Clone, Eq, Hash, PartialEq)]
239pub struct CursorImage {
240 pub(crate) rgba: Vec<u8>,
241 pub(crate) width: u16,
242 pub(crate) height: u16,
243 pub(crate) hotspot_x: u16,
244 pub(crate) hotspot_y: u16,
245}
246
247impl CursorImage {
248 pub(crate) fn from_rgba(
249 rgba: Vec<u8>,
250 width: u16,
251 height: u16,
252 hotspot_x: u16,
253 hotspot_y: u16,
254 ) -> Result<Self, BadImage> {
255 if width > MAX_CURSOR_SIZE || height > MAX_CURSOR_SIZE {
256 return Err(BadImage::TooLarge { width, height });
257 }
258
259 if rgba.len() % PIXEL_SIZE != 0 {
260 return Err(BadImage::ByteCountNotDivisibleBy4 { byte_count: rgba.len() });
261 }
262
263 let pixel_count = (rgba.len() / PIXEL_SIZE) as u64;
264 let width_x_height = width as u64 * height as u64;
265 if pixel_count != width_x_height {
266 return Err(BadImage::DimensionsVsPixelCount {
267 width,
268 height,
269 width_x_height,
270 pixel_count,
271 });
272 }
273
274 if hotspot_x >= width || hotspot_y >= height {
275 return Err(BadImage::HotspotOutOfBounds { width, height, hotspot_x, hotspot_y });
276 }
277
278 Ok(CursorImage { rgba, width, height, hotspot_x, hotspot_y })
279 }
280
281 pub fn buffer(&self) -> &[u8] {
282 self.rgba.as_slice()
283 }
284
285 pub fn buffer_mut(&mut self) -> &mut [u8] {
286 self.rgba.as_mut_slice()
287 }
288
289 pub fn width(&self) -> u16 {
290 self.width
291 }
292
293 pub fn height(&self) -> u16 {
294 self.height
295 }
296
297 pub fn hotspot_x(&self) -> u16 {
298 self.hotspot_x
299 }
300
301 pub fn hotspot_y(&self) -> u16 {
302 self.hotspot_y
303 }
304}
305
306#[derive(Debug, Clone, PartialEq, Eq, Hash)]
307pub struct CursorAnimation {
308 pub(crate) duration: Duration,
309 pub(crate) cursors: Vec<CustomCursor>,
310}
311
312impl CursorAnimation {
313 pub fn new(duration: Duration, cursors: Vec<CustomCursor>) -> Result<Self, BadAnimation> {
314 if cursors.is_empty() {
315 return Err(BadAnimation::Empty);
316 }
317
318 if cursors.iter().any(|cursor| cursor.is_animated()) {
319 return Err(BadAnimation::Animation);
320 }
321
322 Ok(Self { duration, cursors })
323 }
324
325 pub fn duration(&self) -> Duration {
326 self.duration
327 }
328
329 pub fn cursors(&self) -> &[CustomCursor] {
330 self.cursors.as_slice()
331 }
332
333 pub fn into_raw(self) -> (Duration, Vec<CustomCursor>) {
334 (self.duration, self.cursors)
335 }
336}