Skip to content

Commit ae28eea

Browse files
authored
cursor: refactor CustomCursor to be dyn
cursor: refactor `CustomCursor` to be `dyn` Same as for `MonitorHandle`, the source was changed to support all kinds of sources.
1 parent a0464ae commit ae28eea

File tree

26 files changed

+325
-329
lines changed

26 files changed

+325
-329
lines changed

examples/application.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use winit::platform::startup_notify::{
3131
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
3232
};
3333
#[cfg(web_platform)]
34-
use winit::platform::web::{ActiveEventLoopExtWeb, CustomCursorExtWeb, WindowAttributesExtWeb};
34+
use winit::platform::web::{ActiveEventLoopExtWeb, WindowAttributesExtWeb};
3535
#[cfg(x11_platform)]
3636
use winit::platform::x11::WindowAttributesExtX11;
3737
use winit::window::{
@@ -836,7 +836,7 @@ impl WindowState {
836836
custom_cursors[1].clone(),
837837
event_loop.create_custom_cursor(url_custom_cursor())?,
838838
];
839-
let cursor = CustomCursor::from_animation(Duration::from_secs(3), cursors).unwrap();
839+
let cursor = CustomCursorSource::from_animation(Duration::from_secs(3), cursors).unwrap();
840840
let cursor = event_loop.create_custom_cursor(cursor)?;
841841

842842
self.window.set_cursor(cursor.into());
@@ -1097,7 +1097,7 @@ fn decode_cursor(bytes: &[u8]) -> CustomCursorSource {
10971097
let samples = img.into_flat_samples();
10981098
let (_, w, h) = samples.extents();
10991099
let (w, h) = (w as u16, h as u16);
1100-
CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap()
1100+
CustomCursorSource::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap()
11011101
}
11021102

11031103
#[cfg(web_platform)]
@@ -1106,11 +1106,14 @@ fn url_custom_cursor() -> CustomCursorSource {
11061106

11071107
static URL_COUNTER: AtomicU64 = AtomicU64::new(0);
11081108

1109-
CustomCursor::from_url(
1110-
format!("https://picsum.photos/128?random={}", URL_COUNTER.fetch_add(1, Ordering::Relaxed)),
1111-
64,
1112-
64,
1113-
)
1109+
CustomCursorSource::Url {
1110+
hotspot_x: 64,
1111+
hotspot_y: 64,
1112+
url: format!(
1113+
"https://picsum.photos/128?random={}",
1114+
URL_COUNTER.fetch_add(1, Ordering::Relaxed)
1115+
),
1116+
}
11141117
}
11151118

11161119
fn load_icon(bytes: &[u8]) -> Icon {

src/changelog/unreleased.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ changelog entry.
7474
- On Windows, add `IconExtWindows::from_resource_name`.
7575
- Implement `MonitorHandleProvider` for `MonitorHandle` to access common monitor API.
7676
- On X11, set an "area" attribute on XIM input connection to convey the cursor area.
77+
- Implement `CustomCursorProvider` for `CustomCursor` to access cursor API.
78+
- Add `CustomCursorSource::Url`, `CustomCursorSource::from_animation`.
7779

7880
### Changed
7981

@@ -223,7 +225,9 @@ changelog entry.
223225
- Remove `WindowEvent::Touch` and `Touch` in favor of the new `PointerKind`, `PointerSource` and
224226
`ButtonSource` as part of the new pointer event overhaul.
225227
- Remove `Force::altitude_angle`.
226-
- Removed `Window::inner_position`, use the new `Window::surface_position` instead.
228+
- Remove `Window::inner_position`, use the new `Window::surface_position` instead.
229+
- Remove `CustomCursorExtWeb`, use the `CustomCursorSource`.
230+
- Remove `CustomCursor::from_rgba`, use `CustomCursorSource` instead.
227231

228232
### Fixed
229233

src/cursor.rs

Lines changed: 119 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
use core::fmt;
22
use std::error::Error;
3-
use std::hash::{Hash, Hasher};
3+
use std::hash::Hash;
4+
use std::ops::Deref;
45
use std::sync::Arc;
6+
use std::time::Duration;
57

68
use cursor_icon::CursorIcon;
79

8-
use crate::platform_impl::{PlatformCustomCursor, PlatformCustomCursorSource};
10+
use crate::utils::{impl_dyn_casting, AsAny};
911

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`].
1113
pub const MAX_CURSOR_SIZE: u16 = 2048;
1214

1315
const PIXEL_SIZE: usize = 4;
@@ -51,70 +53,117 @@ impl From<CustomCursor> for Cursor {
5153
/// # use winit::event_loop::ActiveEventLoop;
5254
/// # use winit::window::Window;
5355
/// # fn scope(event_loop: &dyn ActiveEventLoop, window: &dyn Window) {
54-
/// use winit::window::CustomCursor;
56+
/// use winit::window::CustomCursorSource;
5557
///
5658
/// let w = 10;
5759
/// let h = 10;
5860
/// let rgba = vec![255; (w * h * 4) as usize];
5961
///
6062
/// #[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();
6264
///
6365
/// #[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,
6770
/// };
6871
///
6972
/// if let Ok(custom_cursor) = event_loop.create_custom_cursor(source) {
7073
/// window.set_cursor(custom_cursor.clone().into());
7174
/// }
7275
/// # }
7376
/// ```
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 },
78140
}
79141

80-
impl CustomCursor {
142+
impl CustomCursorSource {
81143
/// Creates a new cursor from an rgba buffer.
82144
///
83145
/// The alpha channel is assumed to be **not** premultiplied.
84146
pub fn from_rgba(
85-
rgba: impl Into<Vec<u8>>,
147+
rgba: Vec<u8>,
86148
width: u16,
87149
height: u16,
88150
hotspot_x: u16,
89151
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)
104154
}
105-
}
106155

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+
}
115164
}
116165

117-
/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
166+
/// An error produced when using [`CustomCursorSource::from_rgba`] with invalid arguments.
118167
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
119168
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
120169
pub enum BadImage {
@@ -164,47 +213,30 @@ impl fmt::Display for BadImage {
164213

165214
impl Error for BadImage {}
166215

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,
195224
}
196225

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+
}
200232
}
201233
}
202234

203-
impl Eq for OnlyCursorImage {}
235+
impl Error for BadAnimation {}
204236

205237
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
206238
#[allow(dead_code)]
207-
pub(crate) struct CursorImage {
239+
pub struct CursorImage {
208240
pub(crate) rgba: Vec<u8>,
209241
pub(crate) width: u16,
210242
pub(crate) height: u16,
@@ -247,20 +279,22 @@ impl CursorImage {
247279
}
248280
}
249281

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+
}
253287

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 })
265299
}
266300
}

0 commit comments

Comments
 (0)