|
1 | 1 | use core::fmt; |
2 | 2 | use std::error::Error; |
3 | | -use std::hash::{Hash, Hasher}; |
| 3 | +use std::hash::Hash; |
| 4 | +use std::ops::Deref; |
4 | 5 | use std::sync::Arc; |
| 6 | +use std::time::Duration; |
5 | 7 |
|
6 | 8 | use cursor_icon::CursorIcon; |
7 | 9 |
|
8 | | -use crate::platform_impl::{PlatformCustomCursor, PlatformCustomCursorSource}; |
| 10 | +use crate::utils::{impl_dyn_casting, AsAny}; |
9 | 11 |
|
10 | | -/// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`]. |
| 12 | +/// The maximum width and height for a cursor when using [`CustomCursorSource::from_rgba`]. |
11 | 13 | pub const MAX_CURSOR_SIZE: u16 = 2048; |
12 | 14 |
|
13 | 15 | const PIXEL_SIZE: usize = 4; |
@@ -51,70 +53,117 @@ impl From<CustomCursor> for Cursor { |
51 | 53 | /// # use winit::event_loop::ActiveEventLoop; |
52 | 54 | /// # use winit::window::Window; |
53 | 55 | /// # fn scope(event_loop: &dyn ActiveEventLoop, window: &dyn Window) { |
54 | | -/// use winit::window::CustomCursor; |
| 56 | +/// use winit::window::CustomCursorSource; |
55 | 57 | /// |
56 | 58 | /// let w = 10; |
57 | 59 | /// let h = 10; |
58 | 60 | /// let rgba = vec![255; (w * h * 4) as usize]; |
59 | 61 | /// |
60 | 62 | /// #[cfg(not(target_family = "wasm"))] |
61 | | -/// let source = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap(); |
| 63 | +/// let source = CustomCursorSource::from_rgba(rgba, w, h, w / 2, h / 2).unwrap(); |
62 | 64 | /// |
63 | 65 | /// #[cfg(target_family = "wasm")] |
64 | | -/// let source = { |
65 | | -/// use winit::platform::web::CustomCursorExtWeb; |
66 | | -/// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0) |
| 66 | +/// let source = CustomCursorSource::Url { |
| 67 | +/// url: String::from("http://localhost:3000/cursor.png"), |
| 68 | +/// hotspot_x: 0, |
| 69 | +/// hotspot_y: 0, |
67 | 70 | /// }; |
68 | 71 | /// |
69 | 72 | /// if let Ok(custom_cursor) = event_loop.create_custom_cursor(source) { |
70 | 73 | /// window.set_cursor(custom_cursor.clone().into()); |
71 | 74 | /// } |
72 | 75 | /// # } |
73 | 76 | /// ``` |
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, |
| 77 | +#[derive(Clone, Debug)] |
| 78 | +pub struct CustomCursor(pub(crate) Arc<dyn CustomCursorProvider>); |
| 79 | + |
| 80 | +pub trait CustomCursorProvider: AsAny + fmt::Debug + Send + Sync { |
| 81 | + /// Whether a cursor was backed by animation. |
| 82 | + fn is_animated(&self) -> bool; |
| 83 | +} |
| 84 | + |
| 85 | +impl PartialEq for CustomCursor { |
| 86 | + fn eq(&self, other: &Self) -> bool { |
| 87 | + Arc::ptr_eq(&self.0, &other.0) |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +impl Eq for CustomCursor {} |
| 92 | + |
| 93 | +impl Hash for CustomCursor { |
| 94 | + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { |
| 95 | + Arc::as_ptr(&self.0).hash(state); |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +impl Deref for CustomCursor { |
| 100 | + type Target = dyn CustomCursorProvider; |
| 101 | + |
| 102 | + fn deref(&self) -> &Self::Target { |
| 103 | + self.0.deref() |
| 104 | + } |
| 105 | +} |
| 106 | + |
| 107 | +impl_dyn_casting!(CustomCursorProvider); |
| 108 | + |
| 109 | +/// Source for [`CustomCursor`]. |
| 110 | +/// |
| 111 | +/// See [`CustomCursor`] for more details. |
| 112 | +#[derive(Debug, Clone, Eq, Hash, PartialEq)] |
| 113 | +pub enum CustomCursorSource { |
| 114 | + /// Cursor that is backed by RGBA image. |
| 115 | + /// |
| 116 | + /// See [CustomCursorSource::from_rgba] for more. |
| 117 | + /// |
| 118 | + /// ## Platform-specific |
| 119 | + /// |
| 120 | + /// - **iOS / Android / Orbital:** Unsupported |
| 121 | + Image(CursorImage), |
| 122 | + /// Animated cursor. |
| 123 | + /// |
| 124 | + /// See [CustomCursorSource::from_animation] for more. |
| 125 | + /// |
| 126 | + /// ## Platform-specific |
| 127 | + /// |
| 128 | + /// - **iOS / Android / Wayland / Windows / X11 / macOS / Orbital:** Unsupported |
| 129 | + Animation(CursorAnimation), |
| 130 | + /// Creates a new cursor from a URL pointing to an image. |
| 131 | + /// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url), |
| 132 | + /// but browser support for image formats is inconsistent. Using [PNG] is recommended. |
| 133 | + /// |
| 134 | + /// [PNG]: https://en.wikipedia.org/wiki/PNG |
| 135 | + /// |
| 136 | + /// ## Platform-specific |
| 137 | + /// |
| 138 | + /// - **iOS / Android / Wayland / Windows / X11 / macOS / Orbital:** Unsupported |
| 139 | + Url { hotspot_x: u16, hotspot_y: u16, url: String }, |
78 | 140 | } |
79 | 141 |
|
80 | | -impl CustomCursor { |
| 142 | +impl CustomCursorSource { |
81 | 143 | /// Creates a new cursor from an rgba buffer. |
82 | 144 | /// |
83 | 145 | /// The alpha channel is assumed to be **not** premultiplied. |
84 | 146 | pub fn from_rgba( |
85 | | - rgba: impl Into<Vec<u8>>, |
| 147 | + rgba: Vec<u8>, |
86 | 148 | width: u16, |
87 | 149 | height: u16, |
88 | 150 | hotspot_x: u16, |
89 | 151 | 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 | | - }) |
| 152 | + ) -> Result<Self, BadImage> { |
| 153 | + CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self::Image) |
104 | 154 | } |
105 | | -} |
106 | 155 |
|
107 | | -/// Source for [`CustomCursor`]. |
108 | | -/// |
109 | | -/// See [`CustomCursor`] for more details. |
110 | | -#[derive(Debug, Clone, Eq, Hash, PartialEq)] |
111 | | -pub struct CustomCursorSource { |
112 | | - // Some platforms don't support custom cursors. |
113 | | - #[allow(dead_code)] |
114 | | - pub(crate) inner: PlatformCustomCursorSource, |
| 156 | + /// Crates a new animated cursor from multiple [`CustomCursor`]s |
| 157 | + /// Supplied `cursors` can't be empty or other animations. |
| 158 | + pub fn from_animation( |
| 159 | + duration: Duration, |
| 160 | + cursors: Vec<CustomCursor>, |
| 161 | + ) -> Result<Self, BadAnimation> { |
| 162 | + CursorAnimation::new(duration, cursors).map(Self::Animation) |
| 163 | + } |
115 | 164 | } |
116 | 165 |
|
117 | | -/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments. |
| 166 | +/// An error produced when using [`CustomCursorSource::from_rgba`] with invalid arguments. |
118 | 167 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
119 | 168 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
120 | 169 | pub enum BadImage { |
@@ -164,47 +213,30 @@ impl fmt::Display for BadImage { |
164 | 213 |
|
165 | 214 | impl Error for BadImage {} |
166 | 215 |
|
167 | | -/// Platforms export this directly as `PlatformCustomCursorSource` if they need to only work with |
168 | | -/// images. |
169 | | -#[allow(dead_code)] |
170 | | -#[derive(Debug, Clone, Eq, Hash, PartialEq)] |
171 | | -pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage); |
172 | | - |
173 | | -#[allow(dead_code)] |
174 | | -impl 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 | | -/// Platforms export this directly as `PlatformCustomCursor` if they don't implement caching. |
187 | | -#[allow(dead_code)] |
188 | | -#[derive(Debug, Clone)] |
189 | | -pub(crate) struct OnlyCursorImage(pub(crate) Arc<CursorImage>); |
190 | | - |
191 | | -impl Hash for OnlyCursorImage { |
192 | | - fn hash<H: Hasher>(&self, state: &mut H) { |
193 | | - Arc::as_ptr(&self.0).hash(state); |
194 | | - } |
| 216 | +/// An error produced when using [`CustomCursorSource::from_animation`] with invalid arguments. |
| 217 | +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] |
| 218 | +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
| 219 | +pub enum BadAnimation { |
| 220 | + /// Produced when no cursors were supplied. |
| 221 | + Empty, |
| 222 | + /// Produced when a supplied cursor is an animation. |
| 223 | + Animation, |
195 | 224 | } |
196 | 225 |
|
197 | | -impl PartialEq for OnlyCursorImage { |
198 | | - fn eq(&self, other: &Self) -> bool { |
199 | | - Arc::ptr_eq(&self.0, &other.0) |
| 226 | +impl fmt::Display for BadAnimation { |
| 227 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 228 | + match self { |
| 229 | + Self::Empty => write!(f, "No cursors supplied"), |
| 230 | + Self::Animation => write!(f, "A supplied cursor is an animation"), |
| 231 | + } |
200 | 232 | } |
201 | 233 | } |
202 | 234 |
|
203 | | -impl Eq for OnlyCursorImage {} |
| 235 | +impl Error for BadAnimation {} |
204 | 236 |
|
205 | 237 | #[derive(Debug, Clone, Eq, Hash, PartialEq)] |
206 | 238 | #[allow(dead_code)] |
207 | | -pub(crate) struct CursorImage { |
| 239 | +pub struct CursorImage { |
208 | 240 | pub(crate) rgba: Vec<u8>, |
209 | 241 | pub(crate) width: u16, |
210 | 242 | pub(crate) height: u16, |
@@ -247,20 +279,22 @@ impl CursorImage { |
247 | 279 | } |
248 | 280 | } |
249 | 281 |
|
250 | | -// Platforms that don't support cursors will export this as `PlatformCustomCursor`. |
251 | | -#[derive(Debug, Clone, Hash, PartialEq, Eq)] |
252 | | -pub(crate) struct NoCustomCursor; |
| 282 | +#[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| 283 | +pub struct CursorAnimation { |
| 284 | + pub(crate) duration: Duration, |
| 285 | + pub(crate) cursors: Vec<CustomCursor>, |
| 286 | +} |
253 | 287 |
|
254 | | -#[allow(dead_code)] |
255 | | -impl 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) |
| 288 | +impl CursorAnimation { |
| 289 | + pub fn new(duration: Duration, cursors: Vec<CustomCursor>) -> Result<Self, BadAnimation> { |
| 290 | + if cursors.is_empty() { |
| 291 | + return Err(BadAnimation::Empty); |
| 292 | + } |
| 293 | + |
| 294 | + if cursors.iter().any(|cursor| cursor.is_animated()) { |
| 295 | + return Err(BadAnimation::Animation); |
| 296 | + } |
| 297 | + |
| 298 | + Ok(Self { duration, cursors }) |
265 | 299 | } |
266 | 300 | } |
0 commit comments