| 1 | use core::fmt; |
| 2 | use std::error::Error; |
| 3 | use std::hash::{Hash, Hasher}; |
| 4 | use std::sync::Arc; |
| 5 | |
| 6 | use cursor_icon::CursorIcon; |
| 7 | |
| 8 | use crate::platform_impl::{PlatformCustomCursor, PlatformCustomCursorSource}; |
| 9 | |
| 10 | /// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`]. |
| 11 | pub const MAX_CURSOR_SIZE: u16 = 2048; |
| 12 | |
| 13 | const PIXEL_SIZE: usize = 4; |
| 14 | |
| 15 | /// See [`Window::set_cursor()`][crate::window::Window::set_cursor] for more details. |
| 16 | #[derive (Clone, Debug, Eq, Hash, PartialEq)] |
| 17 | pub enum Cursor { |
| 18 | Icon(CursorIcon), |
| 19 | Custom(CustomCursor), |
| 20 | } |
| 21 | |
| 22 | impl Default for Cursor { |
| 23 | fn default() -> Self { |
| 24 | Self::Icon(CursorIcon::default()) |
| 25 | } |
| 26 | } |
| 27 | |
| 28 | impl From<CursorIcon> for Cursor { |
| 29 | fn from(icon: CursorIcon) -> Self { |
| 30 | Self::Icon(icon) |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | impl From<CustomCursor> for Cursor { |
| 35 | fn from(custom: CustomCursor) -> Self { |
| 36 | Self::Custom(custom) |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | /// Use a custom image as a cursor (mouse pointer). |
| 41 | /// |
| 42 | /// Is guaranteed to be cheap to clone. |
| 43 | /// |
| 44 | /// ## Platform-specific |
| 45 | /// |
| 46 | /// **Web**: Some browsers have limits on cursor sizes usually at 128x128. |
| 47 | /// |
| 48 | /// # Example |
| 49 | /// |
| 50 | /// ```no_run |
| 51 | /// # use winit::event_loop::ActiveEventLoop; |
| 52 | /// # use winit::window::Window; |
| 53 | /// # fn scope(event_loop: &ActiveEventLoop, window: &Window) { |
| 54 | /// use winit::window::CustomCursor; |
| 55 | /// |
| 56 | /// let w = 10; |
| 57 | /// let h = 10; |
| 58 | /// let rgba = vec![255; (w * h * 4) as usize]; |
| 59 | /// |
| 60 | /// #[cfg(not(target_family = "wasm" ))] |
| 61 | /// let source = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap(); |
| 62 | /// |
| 63 | /// #[cfg(target_family = "wasm" )] |
| 64 | /// let source = { |
| 65 | /// use winit::platform::web::CustomCursorExtWebSys; |
| 66 | /// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png" ), 0, 0) |
| 67 | /// }; |
| 68 | /// |
| 69 | /// let custom_cursor = event_loop.create_custom_cursor(source); |
| 70 | /// |
| 71 | /// window.set_cursor(custom_cursor.clone()); |
| 72 | /// # } |
| 73 | /// ``` |
| 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, |
| 78 | } |
| 79 | |
| 80 | impl CustomCursor { |
| 81 | /// Creates a new cursor from an rgba buffer. |
| 82 | /// |
| 83 | /// The alpha channel is assumed to be **not** premultiplied. |
| 84 | 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 | /// Source for [`CustomCursor`]. |
| 108 | /// |
| 109 | /// See [`CustomCursor`] for more details. |
| 110 | #[derive (Debug)] |
| 111 | pub struct CustomCursorSource { |
| 112 | pub(crate) inner: PlatformCustomCursorSource, |
| 113 | } |
| 114 | |
| 115 | /// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments. |
| 116 | #[derive (Debug, Clone)] |
| 117 | pub enum BadImage { |
| 118 | /// Produced when the image dimensions are larger than [`MAX_CURSOR_SIZE`]. This doesn't |
| 119 | /// guarantee that the cursor will work, but should avoid many platform and device specific |
| 120 | /// limits. |
| 121 | TooLarge { width: u16, height: u16 }, |
| 122 | /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be |
| 123 | /// safely interpreted as 32bpp RGBA pixels. |
| 124 | ByteCountNotDivisibleBy4 { byte_count: usize }, |
| 125 | /// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`. |
| 126 | /// At least one of your arguments is incorrect. |
| 127 | DimensionsVsPixelCount { width: u16, height: u16, width_x_height: u64, pixel_count: u64 }, |
| 128 | /// Produced when the hotspot is outside the image bounds |
| 129 | HotspotOutOfBounds { width: u16, height: u16, hotspot_x: u16, hotspot_y: u16 }, |
| 130 | } |
| 131 | |
| 132 | impl fmt::Display for BadImage { |
| 133 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 134 | match self { |
| 135 | BadImage::TooLarge { width, height } => write!( |
| 136 | f, |
| 137 | "The specified dimensions ( {width:?}x {height:?}) are too large. The maximum is \ |
| 138 | {MAX_CURSOR_SIZE:?}x {MAX_CURSOR_SIZE:?}." , |
| 139 | ), |
| 140 | BadImage::ByteCountNotDivisibleBy4 { byte_count } => write!( |
| 141 | f, |
| 142 | "The length of the `rgba` argument ( {byte_count:?}) isn't divisible by 4, making \ |
| 143 | it impossible to interpret as 32bpp RGBA pixels." , |
| 144 | ), |
| 145 | BadImage::DimensionsVsPixelCount { width, height, width_x_height, pixel_count } => { |
| 146 | write!( |
| 147 | f, |
| 148 | "The specified dimensions ( {width:?}x {height:?}) don't match the number of \ |
| 149 | pixels supplied by the `rgba` argument ( {pixel_count:?}). For those \ |
| 150 | dimensions, the expected pixel count is {width_x_height:?}." , |
| 151 | ) |
| 152 | }, |
| 153 | BadImage::HotspotOutOfBounds { width, height, hotspot_x, hotspot_y } => write!( |
| 154 | f, |
| 155 | "The specified hotspot ( {hotspot_x:?}, {hotspot_y:?}) is outside the image bounds \ |
| 156 | ( {width:?}x {height:?})." , |
| 157 | ), |
| 158 | } |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | impl Error for BadImage {} |
| 163 | |
| 164 | /// Platforms export this directly as `PlatformCustomCursorSource` if they need to only work with |
| 165 | /// images. |
| 166 | #[allow (dead_code)] |
| 167 | #[derive (Debug)] |
| 168 | pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage); |
| 169 | |
| 170 | #[allow (dead_code)] |
| 171 | impl OnlyCursorImageSource { |
| 172 | pub(crate) fn from_rgba( |
| 173 | rgba: Vec<u8>, |
| 174 | width: u16, |
| 175 | height: u16, |
| 176 | hotspot_x: u16, |
| 177 | hotspot_y: u16, |
| 178 | ) -> Result<Self, BadImage> { |
| 179 | CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self) |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | /// Platforms export this directly as `PlatformCustomCursor` if they don't implement caching. |
| 184 | #[allow (dead_code)] |
| 185 | #[derive (Debug, Clone)] |
| 186 | pub(crate) struct OnlyCursorImage(pub(crate) Arc<CursorImage>); |
| 187 | |
| 188 | impl Hash for OnlyCursorImage { |
| 189 | fn hash<H: Hasher>(&self, state: &mut H) { |
| 190 | Arc::as_ptr(&self.0).hash(state); |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | impl PartialEq for OnlyCursorImage { |
| 195 | fn eq(&self, other: &Self) -> bool { |
| 196 | Arc::ptr_eq(&self.0, &other.0) |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | impl Eq for OnlyCursorImage {} |
| 201 | |
| 202 | #[derive (Debug)] |
| 203 | #[allow (dead_code)] |
| 204 | pub(crate) struct CursorImage { |
| 205 | pub(crate) rgba: Vec<u8>, |
| 206 | pub(crate) width: u16, |
| 207 | pub(crate) height: u16, |
| 208 | pub(crate) hotspot_x: u16, |
| 209 | pub(crate) hotspot_y: u16, |
| 210 | } |
| 211 | |
| 212 | impl CursorImage { |
| 213 | pub(crate) fn from_rgba( |
| 214 | rgba: Vec<u8>, |
| 215 | width: u16, |
| 216 | height: u16, |
| 217 | hotspot_x: u16, |
| 218 | hotspot_y: u16, |
| 219 | ) -> Result<Self, BadImage> { |
| 220 | if width > MAX_CURSOR_SIZE || height > MAX_CURSOR_SIZE { |
| 221 | return Err(BadImage::TooLarge { width, height }); |
| 222 | } |
| 223 | |
| 224 | if rgba.len() % PIXEL_SIZE != 0 { |
| 225 | return Err(BadImage::ByteCountNotDivisibleBy4 { byte_count: rgba.len() }); |
| 226 | } |
| 227 | |
| 228 | let pixel_count = (rgba.len() / PIXEL_SIZE) as u64; |
| 229 | let width_x_height = width as u64 * height as u64; |
| 230 | if pixel_count != width_x_height { |
| 231 | return Err(BadImage::DimensionsVsPixelCount { |
| 232 | width, |
| 233 | height, |
| 234 | width_x_height, |
| 235 | pixel_count, |
| 236 | }); |
| 237 | } |
| 238 | |
| 239 | if hotspot_x >= width || hotspot_y >= height { |
| 240 | return Err(BadImage::HotspotOutOfBounds { width, height, hotspot_x, hotspot_y }); |
| 241 | } |
| 242 | |
| 243 | Ok(CursorImage { rgba, width, height, hotspot_x, hotspot_y }) |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | // Platforms that don't support cursors will export this as `PlatformCustomCursor`. |
| 248 | #[derive (Debug, Clone, Hash, PartialEq, Eq)] |
| 249 | pub(crate) struct NoCustomCursor; |
| 250 | |
| 251 | #[allow (dead_code)] |
| 252 | impl NoCustomCursor { |
| 253 | pub(crate) fn from_rgba( |
| 254 | rgba: Vec<u8>, |
| 255 | width: u16, |
| 256 | height: u16, |
| 257 | hotspot_x: u16, |
| 258 | hotspot_y: u16, |
| 259 | ) -> Result<Self, BadImage> { |
| 260 | CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?; |
| 261 | Ok(Self) |
| 262 | } |
| 263 | } |
| 264 | |