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 = tracing::debug_span!(
92 "rio_window::Cursor::from_rgba",
93 width,
94 height,
95 hotspot_x,
96 hotspot_y
97 )
98 .entered();
99
100 Ok(CustomCursorSource {
101 inner: PlatformCustomCursorSource::from_rgba(
102 rgba.into(),
103 width,
104 height,
105 hotspot_x,
106 hotspot_y,
107 )?,
108 })
109 }
110}
111
112#[derive(Debug)]
116pub struct CustomCursorSource {
117 pub(crate) inner: PlatformCustomCursorSource,
118}
119
120#[derive(Debug, Clone)]
122pub enum BadImage {
123 TooLarge { width: u16, height: u16 },
127 ByteCountNotDivisibleBy4 { byte_count: usize },
130 DimensionsVsPixelCount {
133 width: u16,
134 height: u16,
135 width_x_height: u64,
136 pixel_count: u64,
137 },
138 HotspotOutOfBounds {
140 width: u16,
141 height: u16,
142 hotspot_x: u16,
143 hotspot_y: u16,
144 },
145}
146
147impl fmt::Display for BadImage {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 match self {
150 BadImage::TooLarge { width, height } => write!(
151 f,
152 "The specified dimensions ({width:?}x{height:?}) are too large. The maximum is \
153 {MAX_CURSOR_SIZE:?}x{MAX_CURSOR_SIZE:?}.",
154 ),
155 BadImage::ByteCountNotDivisibleBy4 { byte_count } => write!(
156 f,
157 "The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making \
158 it impossible to interpret as 32bpp RGBA pixels.",
159 ),
160 BadImage::DimensionsVsPixelCount { width, height, width_x_height, pixel_count } => {
161 write!(
162 f,
163 "The specified dimensions ({width:?}x{height:?}) don't match the number of \
164 pixels supplied by the `rgba` argument ({pixel_count:?}). For those \
165 dimensions, the expected pixel count is {width_x_height:?}.",
166 )
167 },
168 BadImage::HotspotOutOfBounds { width, height, hotspot_x, hotspot_y } => write!(
169 f,
170 "The specified hotspot ({hotspot_x:?}, {hotspot_y:?}) is outside the image bounds \
171 ({width:?}x{height:?}).",
172 ),
173 }
174 }
175}
176
177impl Error for BadImage {}
178
179#[allow(dead_code)]
182#[derive(Debug)]
183pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage);
184
185#[allow(dead_code)]
186impl OnlyCursorImageSource {
187 pub(crate) fn from_rgba(
188 rgba: Vec<u8>,
189 width: u16,
190 height: u16,
191 hotspot_x: u16,
192 hotspot_y: u16,
193 ) -> Result<Self, BadImage> {
194 CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self)
195 }
196}
197
198#[allow(dead_code)]
200#[derive(Debug, Clone)]
201pub(crate) struct OnlyCursorImage(pub(crate) Arc<CursorImage>);
202
203impl Hash for OnlyCursorImage {
204 fn hash<H: Hasher>(&self, state: &mut H) {
205 Arc::as_ptr(&self.0).hash(state);
206 }
207}
208
209impl PartialEq for OnlyCursorImage {
210 fn eq(&self, other: &Self) -> bool {
211 Arc::ptr_eq(&self.0, &other.0)
212 }
213}
214
215impl Eq for OnlyCursorImage {}
216
217#[derive(Debug)]
218#[allow(dead_code)]
219pub(crate) struct CursorImage {
220 pub(crate) rgba: Vec<u8>,
221 pub(crate) width: u16,
222 pub(crate) height: u16,
223 pub(crate) hotspot_x: u16,
224 pub(crate) hotspot_y: u16,
225}
226
227impl CursorImage {
228 pub(crate) fn from_rgba(
229 rgba: Vec<u8>,
230 width: u16,
231 height: u16,
232 hotspot_x: u16,
233 hotspot_y: u16,
234 ) -> Result<Self, BadImage> {
235 if width > MAX_CURSOR_SIZE || height > MAX_CURSOR_SIZE {
236 return Err(BadImage::TooLarge { width, height });
237 }
238
239 if rgba.len() % PIXEL_SIZE != 0 {
240 return Err(BadImage::ByteCountNotDivisibleBy4 {
241 byte_count: rgba.len(),
242 });
243 }
244
245 let pixel_count = (rgba.len() / PIXEL_SIZE) as u64;
246 let width_x_height = width as u64 * height as u64;
247 if pixel_count != width_x_height {
248 return Err(BadImage::DimensionsVsPixelCount {
249 width,
250 height,
251 width_x_height,
252 pixel_count,
253 });
254 }
255
256 if hotspot_x >= width || hotspot_y >= height {
257 return Err(BadImage::HotspotOutOfBounds {
258 width,
259 height,
260 hotspot_x,
261 hotspot_y,
262 });
263 }
264
265 Ok(CursorImage {
266 rgba,
267 width,
268 height,
269 hotspot_x,
270 hotspot_y,
271 })
272 }
273}
274
275#[derive(Debug, Clone, Hash, PartialEq, Eq)]
277pub(crate) struct NoCustomCursor;
278
279#[allow(dead_code)]
280impl NoCustomCursor {
281 pub(crate) fn from_rgba(
282 rgba: Vec<u8>,
283 width: u16,
284 height: u16,
285 hotspot_x: u16,
286 hotspot_y: u16,
287 ) -> Result<Self, BadImage> {
288 CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
289 Ok(Self)
290 }
291}