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
10pub const MAX_CURSOR_SIZE: u16 = 2048;
12
13const PIXEL_SIZE: usize = 4;
14
15#[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#[derive(Clone, Debug, Eq, Hash, PartialEq)]
75pub struct CustomCursor {
76 pub(crate) inner: PlatformCustomCursor,
78}
79
80impl CustomCursor {
81 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#[derive(Debug, Clone, Eq, Hash, PartialEq)]
111pub struct CustomCursorSource {
112 #[allow(dead_code)]
114 pub(crate) inner: PlatformCustomCursorSource,
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
119#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
120pub enum BadImage {
121 TooLarge { width: u16, height: u16 },
125 ByteCountNotDivisibleBy4 { byte_count: usize },
128 DimensionsVsPixelCount { width: u16, height: u16, width_x_height: u64, pixel_count: u64 },
131 HotspotOutOfBounds { width: u16, height: u16, hotspot_x: u16, hotspot_y: u16 },
133}
134
135impl fmt::Display for BadImage {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 match self {
138 BadImage::TooLarge { width, height } => write!(
139 f,
140 "The specified dimensions ({width:?}x{height:?}) are too large. The maximum is \
141 {MAX_CURSOR_SIZE:?}x{MAX_CURSOR_SIZE:?}.",
142 ),
143 BadImage::ByteCountNotDivisibleBy4 { byte_count } => write!(
144 f,
145 "The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making \
146 it impossible to interpret as 32bpp RGBA pixels.",
147 ),
148 BadImage::DimensionsVsPixelCount { width, height, width_x_height, pixel_count } => {
149 write!(
150 f,
151 "The specified dimensions ({width:?}x{height:?}) don't match the number of \
152 pixels supplied by the `rgba` argument ({pixel_count:?}). For those \
153 dimensions, the expected pixel count is {width_x_height:?}.",
154 )
155 },
156 BadImage::HotspotOutOfBounds { width, height, hotspot_x, hotspot_y } => write!(
157 f,
158 "The specified hotspot ({hotspot_x:?}, {hotspot_y:?}) is outside the image bounds \
159 ({width:?}x{height:?}).",
160 ),
161 }
162 }
163}
164
165impl Error for BadImage {}
166
167#[allow(dead_code)]
170#[derive(Debug, Clone, Eq, Hash, PartialEq)]
171pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage);
172
173#[allow(dead_code)]
174impl OnlyCursorImageSource {
175 pub(crate) fn from_rgba(
176 rgba: Vec<u8>,
177 width: u16,
178 height: u16,
179 hotspot_x: u16,
180 hotspot_y: u16,
181 ) -> Result<Self, BadImage> {
182 CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self)
183 }
184}
185
186#[allow(dead_code)]
188#[derive(Debug, Clone)]
189pub(crate) struct OnlyCursorImage(pub(crate) Arc<CursorImage>);
190
191impl Hash for OnlyCursorImage {
192 fn hash<H: Hasher>(&self, state: &mut H) {
193 Arc::as_ptr(&self.0).hash(state);
194 }
195}
196
197impl PartialEq for OnlyCursorImage {
198 fn eq(&self, other: &Self) -> bool {
199 Arc::ptr_eq(&self.0, &other.0)
200 }
201}
202
203impl Eq for OnlyCursorImage {}
204
205#[derive(Debug, Clone, Eq, Hash, PartialEq)]
206#[allow(dead_code)]
207pub(crate) struct CursorImage {
208 pub(crate) rgba: Vec<u8>,
209 pub(crate) width: u16,
210 pub(crate) height: u16,
211 pub(crate) hotspot_x: u16,
212 pub(crate) hotspot_y: u16,
213}
214
215impl CursorImage {
216 pub(crate) fn from_rgba(
217 rgba: Vec<u8>,
218 width: u16,
219 height: u16,
220 hotspot_x: u16,
221 hotspot_y: u16,
222 ) -> Result<Self, BadImage> {
223 if width > MAX_CURSOR_SIZE || height > MAX_CURSOR_SIZE {
224 return Err(BadImage::TooLarge { width, height });
225 }
226
227 if rgba.len() % PIXEL_SIZE != 0 {
228 return Err(BadImage::ByteCountNotDivisibleBy4 { byte_count: rgba.len() });
229 }
230
231 let pixel_count = (rgba.len() / PIXEL_SIZE) as u64;
232 let width_x_height = width as u64 * height as u64;
233 if pixel_count != width_x_height {
234 return Err(BadImage::DimensionsVsPixelCount {
235 width,
236 height,
237 width_x_height,
238 pixel_count,
239 });
240 }
241
242 if hotspot_x >= width || hotspot_y >= height {
243 return Err(BadImage::HotspotOutOfBounds { width, height, hotspot_x, hotspot_y });
244 }
245
246 Ok(CursorImage { rgba, width, height, hotspot_x, hotspot_y })
247 }
248}
249
250#[derive(Debug, Clone, Hash, PartialEq, Eq)]
252pub(crate) struct NoCustomCursor;
253
254#[allow(dead_code)]
255impl NoCustomCursor {
256 pub(crate) fn from_rgba(
257 rgba: Vec<u8>,
258 width: u16,
259 height: u16,
260 hotspot_x: u16,
261 hotspot_y: u16,
262 ) -> Result<Self, BadImage> {
263 CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
264 Ok(Self)
265 }
266}