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.
This commit is contained in:
Kirill Chibisov 2025-03-13 17:18:37 +03:00 committed by GitHub
parent a0464ae83b
commit ae28eea406
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 329 additions and 333 deletions

View file

@ -31,7 +31,7 @@ use winit::platform::startup_notify::{
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
};
#[cfg(web_platform)]
use winit::platform::web::{ActiveEventLoopExtWeb, CustomCursorExtWeb, WindowAttributesExtWeb};
use winit::platform::web::{ActiveEventLoopExtWeb, WindowAttributesExtWeb};
#[cfg(x11_platform)]
use winit::platform::x11::WindowAttributesExtX11;
use winit::window::{
@ -836,7 +836,7 @@ impl WindowState {
custom_cursors[1].clone(),
event_loop.create_custom_cursor(url_custom_cursor())?,
];
let cursor = CustomCursor::from_animation(Duration::from_secs(3), cursors).unwrap();
let cursor = CustomCursorSource::from_animation(Duration::from_secs(3), cursors).unwrap();
let cursor = event_loop.create_custom_cursor(cursor)?;
self.window.set_cursor(cursor.into());
@ -1097,7 +1097,7 @@ fn decode_cursor(bytes: &[u8]) -> CustomCursorSource {
let samples = img.into_flat_samples();
let (_, w, h) = samples.extents();
let (w, h) = (w as u16, h as u16);
CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap()
CustomCursorSource::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap()
}
#[cfg(web_platform)]
@ -1106,11 +1106,14 @@ fn url_custom_cursor() -> CustomCursorSource {
static URL_COUNTER: AtomicU64 = AtomicU64::new(0);
CustomCursor::from_url(
format!("https://picsum.photos/128?random={}", URL_COUNTER.fetch_add(1, Ordering::Relaxed)),
64,
64,
)
CustomCursorSource::Url {
hotspot_x: 64,
hotspot_y: 64,
url: format!(
"https://picsum.photos/128?random={}",
URL_COUNTER.fetch_add(1, Ordering::Relaxed)
),
}
}
fn load_icon(bytes: &[u8]) -> Icon {

View file

@ -74,6 +74,8 @@ changelog entry.
- On Windows, add `IconExtWindows::from_resource_name`.
- Implement `MonitorHandleProvider` for `MonitorHandle` to access common monitor API.
- On X11, set an "area" attribute on XIM input connection to convey the cursor area.
- Implement `CustomCursorProvider` for `CustomCursor` to access cursor API.
- Add `CustomCursorSource::Url`, `CustomCursorSource::from_animation`.
### Changed
@ -223,7 +225,9 @@ changelog entry.
- Remove `WindowEvent::Touch` and `Touch` in favor of the new `PointerKind`, `PointerSource` and
`ButtonSource` as part of the new pointer event overhaul.
- Remove `Force::altitude_angle`.
- Removed `Window::inner_position`, use the new `Window::surface_position` instead.
- Remove `Window::inner_position`, use the new `Window::surface_position` instead.
- Remove `CustomCursorExtWeb`, use the `CustomCursorSource`.
- Remove `CustomCursor::from_rgba`, use `CustomCursorSource` instead.
### Fixed

View file

@ -1,13 +1,15 @@
use core::fmt;
use std::error::Error;
use std::hash::{Hash, Hasher};
use std::hash::Hash;
use std::ops::Deref;
use std::sync::Arc;
use std::time::Duration;
use cursor_icon::CursorIcon;
use crate::platform_impl::{PlatformCustomCursor, PlatformCustomCursorSource};
use crate::utils::{impl_dyn_casting, AsAny};
/// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`].
/// The maximum width and height for a cursor when using [`CustomCursorSource::from_rgba`].
pub const MAX_CURSOR_SIZE: u16 = 2048;
const PIXEL_SIZE: usize = 4;
@ -51,19 +53,20 @@ impl From<CustomCursor> for Cursor {
/// # use winit::event_loop::ActiveEventLoop;
/// # use winit::window::Window;
/// # fn scope(event_loop: &dyn ActiveEventLoop, window: &dyn Window) {
/// use winit::window::CustomCursor;
/// use winit::window::CustomCursorSource;
///
/// let w = 10;
/// let h = 10;
/// let rgba = vec![255; (w * h * 4) as usize];
///
/// #[cfg(not(target_family = "wasm"))]
/// let source = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
/// let source = CustomCursorSource::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
///
/// #[cfg(target_family = "wasm")]
/// let source = {
/// use winit::platform::web::CustomCursorExtWeb;
/// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0)
/// let source = CustomCursorSource::Url {
/// url: String::from("http://localhost:3000/cursor.png"),
/// hotspot_x: 0,
/// hotspot_y: 0,
/// };
///
/// if let Ok(custom_cursor) = event_loop.create_custom_cursor(source) {
@ -71,50 +74,96 @@ impl From<CustomCursor> for Cursor {
/// }
/// # }
/// ```
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct CustomCursor {
/// Platforms should make sure this is cheap to clone.
pub(crate) inner: PlatformCustomCursor,
#[derive(Clone, Debug)]
pub struct CustomCursor(pub(crate) Arc<dyn CustomCursorProvider>);
pub trait CustomCursorProvider: AsAny + fmt::Debug + Send + Sync {
/// Whether a cursor was backed by animation.
fn is_animated(&self) -> bool;
}
impl CustomCursor {
/// Creates a new cursor from an rgba buffer.
///
/// The alpha channel is assumed to be **not** premultiplied.
pub fn from_rgba(
rgba: impl Into<Vec<u8>>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<CustomCursorSource, BadImage> {
let _span =
tracing::debug_span!("winit::Cursor::from_rgba", width, height, hotspot_x, hotspot_y)
.entered();
Ok(CustomCursorSource {
inner: PlatformCustomCursorSource::from_rgba(
rgba.into(),
width,
height,
hotspot_x,
hotspot_y,
)?,
})
impl PartialEq for CustomCursor {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for CustomCursor {}
impl Hash for CustomCursor {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.0).hash(state);
}
}
impl Deref for CustomCursor {
type Target = dyn CustomCursorProvider;
fn deref(&self) -> &Self::Target {
self.0.deref()
}
}
impl_dyn_casting!(CustomCursorProvider);
/// Source for [`CustomCursor`].
///
/// See [`CustomCursor`] for more details.
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct CustomCursorSource {
// Some platforms don't support custom cursors.
#[allow(dead_code)]
pub(crate) inner: PlatformCustomCursorSource,
pub enum CustomCursorSource {
/// Cursor that is backed by RGBA image.
///
/// See [CustomCursorSource::from_rgba] for more.
///
/// ## Platform-specific
///
/// - **iOS / Android / Orbital:** Unsupported
Image(CursorImage),
/// Animated cursor.
///
/// See [CustomCursorSource::from_animation] for more.
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / Windows / X11 / macOS / Orbital:** Unsupported
Animation(CursorAnimation),
/// Creates a new cursor from a URL pointing to an image.
/// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url),
/// but browser support for image formats is inconsistent. Using [PNG] is recommended.
///
/// [PNG]: https://en.wikipedia.org/wiki/PNG
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / Windows / X11 / macOS / Orbital:** Unsupported
Url { hotspot_x: u16, hotspot_y: u16, url: String },
}
/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
impl CustomCursorSource {
/// Creates a new cursor from an rgba buffer.
///
/// The alpha channel is assumed to be **not** premultiplied.
pub fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self::Image)
}
/// Crates a new animated cursor from multiple [`CustomCursor`]s
/// Supplied `cursors` can't be empty or other animations.
pub fn from_animation(
duration: Duration,
cursors: Vec<CustomCursor>,
) -> Result<Self, BadAnimation> {
CursorAnimation::new(duration, cursors).map(Self::Animation)
}
}
/// An error produced when using [`CustomCursorSource::from_rgba`] with invalid arguments.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BadImage {
@ -164,47 +213,30 @@ impl fmt::Display for BadImage {
impl Error for BadImage {}
/// Platforms export this directly as `PlatformCustomCursorSource` if they need to only work with
/// images.
#[allow(dead_code)]
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage);
/// An error produced when using [`CustomCursorSource::from_animation`] with invalid arguments.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BadAnimation {
/// Produced when no cursors were supplied.
Empty,
/// Produced when a supplied cursor is an animation.
Animation,
}
#[allow(dead_code)]
impl OnlyCursorImageSource {
pub(crate) fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self)
impl fmt::Display for BadAnimation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "No cursors supplied"),
Self::Animation => write!(f, "A supplied cursor is an animation"),
}
}
}
/// Platforms export this directly as `PlatformCustomCursor` if they don't implement caching.
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub(crate) struct OnlyCursorImage(pub(crate) Arc<CursorImage>);
impl Hash for OnlyCursorImage {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.0).hash(state);
}
}
impl PartialEq for OnlyCursorImage {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for OnlyCursorImage {}
impl Error for BadAnimation {}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
#[allow(dead_code)]
pub(crate) struct CursorImage {
pub struct CursorImage {
pub(crate) rgba: Vec<u8>,
pub(crate) width: u16,
pub(crate) height: u16,
@ -247,20 +279,22 @@ impl CursorImage {
}
}
// Platforms that don't support cursors will export this as `PlatformCustomCursor`.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub(crate) struct NoCustomCursor;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CursorAnimation {
pub(crate) duration: Duration,
pub(crate) cursors: Vec<CustomCursor>,
}
#[allow(dead_code)]
impl NoCustomCursor {
pub(crate) fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
Ok(Self)
impl CursorAnimation {
pub fn new(duration: Duration, cursors: Vec<CustomCursor>) -> Result<Self, BadAnimation> {
if cursors.is_empty() {
return Err(BadAnimation::Empty);
}
if cursors.iter().any(|cursor| cursor.is_animated()) {
return Err(BadAnimation::Animation);
}
Ok(Self { duration, cursors })
}
}

View file

@ -46,8 +46,8 @@ use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::Duration;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
@ -59,6 +59,7 @@ use crate::cursor::CustomCursorSource;
use crate::error::NotSupportedError;
use crate::event_loop::{ActiveEventLoop, EventLoop};
use crate::monitor::MonitorHandleProvider;
use crate::platform_impl::MonitorHandle as WebMonitorHandle;
#[cfg(web_platform)]
use crate::platform_impl::{
CustomCursorFuture as PlatformCustomCursorFuture,
@ -66,7 +67,6 @@ use crate::platform_impl::{
MonitorPermissionFuture as PlatformMonitorPermissionFuture,
OrientationLockFuture as PlatformOrientationLockFuture,
};
use crate::platform_impl::{MonitorHandle as WebMonitorHandle, PlatformCustomCursorSource};
use crate::window::{CustomCursor, Window, WindowAttributes};
#[cfg(not(web_platform))]
@ -486,73 +486,6 @@ pub enum WaitUntilStrategy {
Worker,
}
pub trait CustomCursorExtWeb {
/// Returns if this cursor is an animation.
fn is_animation(&self) -> bool;
/// Creates a new cursor from a URL pointing to an image.
/// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url),
/// but browser support for image formats is inconsistent. Using [PNG] is recommended.
///
/// [PNG]: https://en.wikipedia.org/wiki/PNG
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource;
/// Crates a new animated cursor from multiple [`CustomCursor`]s.
/// Supplied `cursors` can't be empty or other animations.
fn from_animation(
duration: Duration,
cursors: Vec<CustomCursor>,
) -> Result<CustomCursorSource, BadAnimation>;
}
impl CustomCursorExtWeb for CustomCursor {
fn is_animation(&self) -> bool {
self.inner.animation
}
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource {
CustomCursorSource { inner: PlatformCustomCursorSource::Url { url, hotspot_x, hotspot_y } }
}
fn from_animation(
duration: Duration,
cursors: Vec<CustomCursor>,
) -> Result<CustomCursorSource, BadAnimation> {
if cursors.is_empty() {
return Err(BadAnimation::Empty);
}
if cursors.iter().any(CustomCursor::is_animation) {
return Err(BadAnimation::Animation);
}
Ok(CustomCursorSource {
inner: PlatformCustomCursorSource::Animation { duration, cursors },
})
}
}
/// An error produced when using [`CustomCursor::from_animation`] with invalid arguments.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum BadAnimation {
/// Produced when no cursors were supplied.
Empty,
/// Produced when a supplied cursor is an animation.
Animation,
}
impl fmt::Display for BadAnimation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "No cursors supplied"),
Self::Animation => write!(f, "A supplied cursor is an animation"),
}
}
}
impl Error for BadAnimation {}
#[cfg(not(web_platform))]
struct PlatformCustomCursorFuture;
@ -563,7 +496,7 @@ impl Future for CustomCursorFuture {
type Output = Result<CustomCursor, CustomCursorError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0).poll(cx).map_ok(|cursor| CustomCursor { inner: cursor })
Pin::new(&mut self.0).poll(cx).map_ok(|cursor| CustomCursor(Arc::new(cursor)))
}
}

View file

@ -20,6 +20,7 @@ use crate::event_loop::{
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;
use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use crate::platform::pump_events::PumpStatus;
use crate::window::{
@ -29,11 +30,6 @@ use crate::window::{
mod keycodes;
pub(crate) use crate::cursor::{
NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;
static HAS_FOCUS: AtomicBool = AtomicBool::new(true);
/// Returns the minimum `Option<Duration>`, taking into account that `None`

View file

@ -10,21 +10,34 @@ use objc2_foundation::{
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString,
};
use crate::cursor::{CursorImage, OnlyCursorImageSource};
use crate::error::RequestError;
use crate::cursor::{CursorImage, CustomCursorProvider, CustomCursorSource};
use crate::error::{NotSupportedError, RequestError};
use crate::window::CursorIcon;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CustomCursor(pub(crate) Retained<NSCursor>);
impl CustomCursorProvider for CustomCursor {
fn is_animated(&self) -> bool {
false
}
}
// SAFETY: NSCursor is immutable and thread-safe
// TODO(madsmtm): Put this logic in objc2-app-kit itself
unsafe impl Send for CustomCursor {}
unsafe impl Sync for CustomCursor {}
impl CustomCursor {
pub(crate) fn new(cursor: OnlyCursorImageSource) -> Result<CustomCursor, RequestError> {
cursor_from_image(&cursor.0).map(Self)
pub(crate) fn new(cursor: CustomCursorSource) -> Result<CustomCursor, RequestError> {
let cursor = match cursor {
CustomCursorSource::Image(cursor_image) => cursor_image,
CustomCursorSource::Animation { .. } | CustomCursorSource::Url { .. } => {
return Err(NotSupportedError::new("unsupported cursor kind").into())
},
};
cursor_from_image(&cursor).map(Self)
}
}

View file

@ -33,7 +33,7 @@ use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::platform::macos::ActivationPolicy;
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::Window;
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme};
use crate::window::{CustomCursor as CoreCustomCursor, CustomCursorSource, Theme};
#[derive(Default)]
pub struct PanicInfo {
@ -110,8 +110,8 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn create_custom_cursor(
&self,
source: CustomCursorSource,
) -> Result<RootCustomCursor, RequestError> {
Ok(RootCustomCursor { inner: CustomCursor::new(source.inner)? })
) -> Result<CoreCustomCursor, RequestError> {
Ok(CoreCustomCursor(Arc::new(CustomCursor::new(source)?)))
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {

View file

@ -14,7 +14,6 @@ mod view;
mod window;
mod window_delegate;
pub(crate) use self::cursor::CustomCursor as PlatformCustomCursor;
pub(crate) use self::event::{physicalkey_to_scancode, scancode_to_physicalkey};
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
@ -22,5 +21,4 @@ pub(crate) use self::event_loop::{
pub(crate) use self::monitor::MonitorHandle;
pub(crate) use self::window::Window;
pub(crate) use self::window_delegate::PlatformSpecificWindowAttributes;
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
pub(crate) use crate::icon::NoIcon as PlatformIcon;

View file

@ -36,7 +36,7 @@ use objc2_foundation::{
use tracing::{trace, warn};
use super::app_state::AppState;
use super::cursor::cursor_from_icon;
use super::cursor::{cursor_from_icon, CustomCursor};
use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
use super::observer::RunLoop;
use super::util::cgerr;
@ -1249,7 +1249,13 @@ impl WindowDelegate {
let cursor = match cursor {
Cursor::Icon(icon) => cursor_from_icon(icon),
Cursor::Custom(cursor) => cursor.inner.0,
Cursor::Custom(cursor) => match cursor.cast_ref::<CustomCursor>() {
Some(cursor) => cursor.0.clone(),
None => {
tracing::error!("unrecognized cursor passed to macOS backend");
return;
},
},
};
if view.cursor_icon() == cursor {

View file

@ -14,9 +14,6 @@ pub(crate) use self::event_loop::{
};
pub(crate) use self::monitor::MonitorHandle;
pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window};
pub(crate) use crate::cursor::{
NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;
#[derive(Debug)]

View file

@ -9,7 +9,6 @@ use std::time::Duration;
pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey};
use crate::application::ApplicationHandler;
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
#[cfg(x11_platform)]
use crate::dpi::Size;
use crate::error::{EventLoopError, NotSupportedError};
@ -120,14 +119,6 @@ macro_rules! x11_or_wayland {
};
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) enum PlatformCustomCursor {
#[cfg(wayland_platform)]
Wayland(wayland::CustomCursor),
#[cfg(x11_platform)]
X(x11::CustomCursor),
}
#[derive(Debug)]
pub enum EventLoop {
#[cfg(wayland_platform)]

View file

@ -12,9 +12,8 @@ use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::{globals, Connection, QueueHandle};
use crate::application::ApplicationHandler;
use crate::cursor::OnlyCursorImage;
use crate::dpi::LogicalSize;
use crate::error::{EventLoopError, OsError, RequestError};
use crate::error::{EventLoopError, NotSupportedError, OsError, RequestError};
use crate::event::{DeviceEvent, StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
@ -23,8 +22,8 @@ use crate::event_loop::{
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::platform::min_timeout;
use crate::platform_impl::PlatformCustomCursor;
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme};
use crate::platform_impl::wayland::types::cursor::WaylandCustomCursor;
use crate::window::{CustomCursor as CoreCustomCursor, CustomCursorSource, Theme};
mod proxy;
pub mod sink;
@ -604,10 +603,15 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn create_custom_cursor(
&self,
cursor: CustomCursorSource,
) -> Result<RootCustomCursor, RequestError> {
Ok(RootCustomCursor {
inner: PlatformCustomCursor::Wayland(OnlyCursorImage(Arc::from(cursor.inner.0))),
})
) -> Result<CoreCustomCursor, RequestError> {
let cursor_image = match cursor {
CustomCursorSource::Image(cursor_image) => cursor_image,
CustomCursorSource::Animation { .. } | CustomCursorSource::Url { .. } => {
return Err(NotSupportedError::new("unsupported cursor kind").into())
},
};
Ok(CoreCustomCursor(Arc::new(WaylandCustomCursor(cursor_image))))
}
#[inline]

View file

@ -3,7 +3,6 @@
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Proxy;
pub(super) use crate::cursor::OnlyCursorImage as CustomCursor;
use crate::dpi::{LogicalSize, PhysicalSize};
use crate::window::WindowId;

View file

@ -2,7 +2,16 @@ use cursor_icon::CursorIcon;
use sctk::reexports::client::protocol::wl_shm::Format;
use sctk::shm::slot::{Buffer, SlotPool};
use crate::cursor::CursorImage;
use crate::cursor::{CursorImage, CustomCursorProvider};
// Wrap in our own type to not impl trait on global type.
#[derive(Debug)]
pub struct WaylandCustomCursor(pub(crate) CursorImage);
impl CustomCursorProvider for WaylandCustomCursor {
fn is_animated(&self) -> bool {
false
}
}
#[derive(Debug)]
pub enum SelectedCursor {
@ -26,7 +35,8 @@ pub struct CustomCursor {
}
impl CustomCursor {
pub(crate) fn new(pool: &mut SlotPool, image: &CursorImage) -> Self {
pub(crate) fn new(pool: &mut SlotPool, image: &WaylandCustomCursor) -> Self {
let image = &image.0;
let (buffer, canvas) = pool
.create_buffer(
image.width as i32,

View file

@ -28,7 +28,7 @@ use sctk::subcompositor::SubcompositorState;
use tracing::{info, warn};
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use crate::cursor::CustomCursor as RootCustomCursor;
use crate::cursor::CustomCursor as CoreCustomCursor;
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
use crate::error::{NotSupportedError, RequestError};
use crate::platform_impl::wayland::event_loop::OwnedDisplayHandle;
@ -37,9 +37,10 @@ use crate::platform_impl::wayland::seat::{
PointerConstraintsState, WinitPointerData, WinitPointerDataExt, ZwpTextInputV3Ext,
};
use crate::platform_impl::wayland::state::{WindowCompositorUpdate, WinitState};
use crate::platform_impl::wayland::types::cursor::{CustomCursor, SelectedCursor};
use crate::platform_impl::wayland::types::cursor::{
CustomCursor, SelectedCursor, WaylandCustomCursor,
};
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
use crate::platform_impl::PlatformCustomCursor;
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, WindowId};
#[cfg(feature = "sctk-adwaita")]
@ -702,19 +703,18 @@ impl WindowState {
}
/// Set the custom cursor icon.
pub(crate) fn set_custom_cursor(&mut self, cursor: RootCustomCursor) {
let cursor = match cursor {
RootCustomCursor { inner: PlatformCustomCursor::Wayland(cursor) } => cursor.0,
#[cfg(x11_platform)]
RootCustomCursor { inner: PlatformCustomCursor::X(_) } => {
tracing::error!("passed a X11 cursor to Wayland backend");
pub(crate) fn set_custom_cursor(&mut self, cursor: CoreCustomCursor) {
let cursor = match cursor.cast_ref::<WaylandCustomCursor>() {
Some(cursor) => cursor,
None => {
tracing::error!("unrecognized cursor passed to Wayland backend");
return;
},
};
let cursor = {
let mut pool = self.custom_cursor_pool.lock().unwrap();
CustomCursor::new(&mut pool, &cursor)
CustomCursor::new(&mut pool, cursor)
};
if self.cursor_visible {

View file

@ -36,10 +36,9 @@ use crate::platform::x11::XlibErrorHook;
use crate::platform_impl::common::xkb::Context;
use crate::platform_impl::platform::min_timeout;
use crate::platform_impl::x11::window::Window;
use crate::platform_impl::PlatformCustomCursor;
use crate::utils::Lazy;
use crate::window::{
CustomCursor as RootCustomCursor, CustomCursorSource, Theme, Window as CoreWindow,
CustomCursor as CoreCustomCursor, CustomCursorSource, Theme, Window as CoreWindow,
WindowAttributes, WindowId,
};
@ -731,10 +730,8 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn create_custom_cursor(
&self,
custom_cursor: CustomCursorSource,
) -> Result<RootCustomCursor, RequestError> {
Ok(RootCustomCursor {
inner: PlatformCustomCursor::X(CustomCursor::new(self, custom_cursor.inner)?),
})
) -> Result<CoreCustomCursor, RequestError> {
Ok(CoreCustomCursor(Arc::new(CustomCursor::new(self, custom_cursor)?)))
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {

View file

@ -9,8 +9,8 @@ use x11rb::protocol::xproto;
use super::super::ActiveEventLoop;
use super::*;
use crate::error::RequestError;
use crate::platform_impl::PlatformCustomCursorSource;
use crate::cursor::{CustomCursorProvider, CustomCursorSource};
use crate::error::{NotSupportedError, RequestError};
use crate::window::CursorIcon;
impl XConnection {
@ -36,7 +36,7 @@ impl XConnection {
window: xproto::Window,
cursor: &CustomCursor,
) -> Result<(), X11Error> {
self.update_cursor(window, cursor.inner.cursor)
self.update_cursor(window, cursor.cursor)
}
/// Create a cursor from an image.
@ -170,32 +170,45 @@ pub enum SelectedCursor {
Named(CursorIcon),
}
impl Default for SelectedCursor {
fn default() -> Self {
SelectedCursor::Named(Default::default())
}
}
#[derive(Debug, Clone)]
pub struct CustomCursor {
inner: Arc<CustomCursorInner>,
xconn: Arc<XConnection>,
cursor: xproto::Cursor,
}
impl Hash for CustomCursor {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.inner).hash(state);
self.cursor.hash(state);
}
}
impl PartialEq for CustomCursor {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
self.cursor == other.cursor
}
}
impl Eq for CustomCursor {}
impl CustomCursor {
pub(crate) fn new(
event_loop: &ActiveEventLoop,
mut cursor: PlatformCustomCursorSource,
cursor: CustomCursorSource,
) -> Result<CustomCursor, RequestError> {
let mut cursor = match cursor {
CustomCursorSource::Image(cursor_image) => cursor_image,
CustomCursorSource::Animation { .. } | CustomCursorSource::Url { .. } => {
return Err(NotSupportedError::new("unsupported cursor kind").into())
},
};
// Reverse RGBA order to BGRA.
cursor.0.rgba.chunks_mut(4).for_each(|chunk| {
cursor.rgba.chunks_mut(4).for_each(|chunk| {
let chunk: &mut [u8; 4] = chunk.try_into().unwrap();
chunk[0..3].reverse();
@ -209,32 +222,26 @@ impl CustomCursor {
let cursor = event_loop
.xconn
.create_cursor_from_image(
cursor.0.width,
cursor.0.height,
cursor.0.hotspot_x,
cursor.0.hotspot_y,
&cursor.0.rgba,
cursor.width,
cursor.height,
cursor.hotspot_x,
cursor.hotspot_y,
&cursor.rgba,
)
.map_err(|err| os_error!(err))?;
Ok(Self { inner: Arc::new(CustomCursorInner { xconn: event_loop.xconn.clone(), cursor }) })
Ok(Self { xconn: event_loop.xconn.clone(), cursor })
}
}
#[derive(Debug)]
struct CustomCursorInner {
xconn: Arc<XConnection>,
cursor: xproto::Cursor,
}
impl Drop for CustomCursorInner {
impl Drop for CustomCursor {
fn drop(&mut self) {
self.xconn.xcb_connection().free_cursor(self.cursor).map(|r| r.ignore_error()).ok();
}
}
impl Default for SelectedCursor {
fn default() -> Self {
SelectedCursor::Named(Default::default())
impl CustomCursorProvider for CustomCursor {
fn is_animated(&self) -> bool {
false
}
}

View file

@ -19,10 +19,11 @@ use x11rb::protocol::{randr, xinput};
use super::util::{self, SelectedCursor};
use super::{
ffi, ActiveEventLoop, CookieResultExt, ImeRequest, ImeSender, VoidCookie, XConnection,
ffi, ActiveEventLoop, CookieResultExt, CustomCursor, ImeRequest, ImeSender, VoidCookie,
XConnection,
};
use crate::application::ApplicationHandler;
use crate::cursor::{Cursor, CustomCursor as RootCustomCursor};
use crate::cursor::Cursor;
use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{NotSupportedError, RequestError};
use crate::event::{SurfaceSizeWriter, WindowEvent};
@ -35,7 +36,7 @@ use crate::platform_impl::x11::atoms::*;
use crate::platform_impl::x11::{
xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender, X11Error,
};
use crate::platform_impl::{common, PlatformCustomCursor, PlatformIcon};
use crate::platform_impl::{common, PlatformIcon};
use crate::window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, Window as CoreWindow,
WindowAttributes, WindowButtons, WindowId, WindowLevel,
@ -1808,19 +1809,23 @@ impl UnownedWindow {
}
}
},
Cursor::Custom(RootCustomCursor { inner: PlatformCustomCursor::X(cursor) }) => {
Cursor::Custom(cursor) => {
let cursor = match cursor.cast_ref::<CustomCursor>() {
Some(cursor) => cursor,
None => {
tracing::error!("unrecognized cursor passed to X11 backend");
return;
},
};
#[allow(clippy::mutex_atomic)]
if *self.cursor_visible.lock().unwrap() {
if let Err(err) = self.xconn.set_custom_cursor(self.xwindow, &cursor) {
if let Err(err) = self.xconn.set_custom_cursor(self.xwindow, cursor) {
tracing::error!("failed to set window icon: {err}");
}
}
*self.selected_cursor.lock().unwrap() = SelectedCursor::Custom(cursor);
},
#[cfg(wayland_platform)]
Cursor::Custom(RootCustomCursor { inner: PlatformCustomCursor::Wayland(_) }) => {
tracing::error!("passed a Wayland cursor to X11 backend")
*self.selected_cursor.lock().unwrap() = SelectedCursor::Custom(cursor.clone());
},
}
}

View file

@ -4,15 +4,11 @@ use std::{fmt, str};
pub(crate) use self::event_loop::{ActiveEventLoop, EventLoop};
pub use self::window::Window;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
mod event_loop;
mod window;
pub(crate) use crate::cursor::{
NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;
#[derive(Debug)]
struct RedoxSocket {
fd: usize,

View file

@ -24,50 +24,17 @@ use super::backend::Style;
use super::main_thread::{MainThreadMarker, MainThreadSafe};
use super::r#async::{AbortHandle, Abortable, DropAbortHandle, Notified, Notifier};
use super::ActiveEventLoop;
use crate::cursor::{BadImage, Cursor, CursorImage, CustomCursor as RootCustomCursor};
use crate::cursor::{
Cursor, CursorAnimation, CursorImage, CustomCursorProvider, CustomCursorSource,
};
use crate::platform::web::CustomCursorError;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) enum CustomCursorSource {
Image(CursorImage),
Url { url: String, hotspot_x: u16, hotspot_y: u16 },
Animation { duration: Duration, cursors: Vec<RootCustomCursor> },
}
impl CustomCursorSource {
pub fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<CustomCursorSource, BadImage> {
Ok(CustomCursorSource::Image(CursorImage::from_rgba(
rgba, width, height, hotspot_x, hotspot_y,
)?))
}
}
#[derive(Clone, Debug)]
pub struct CustomCursor {
pub(crate) animation: bool,
state: Arc<MainThreadSafe<RefCell<ImageState>>>,
}
impl Hash for CustomCursor {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.state).hash(state);
}
}
impl PartialEq for CustomCursor {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.state, &other.state)
}
}
impl Eq for CustomCursor {}
impl CustomCursor {
pub(crate) fn new(event_loop: &ActiveEventLoop, source: CustomCursorSource) -> Self {
match source {
@ -81,15 +48,13 @@ impl CustomCursor {
from_url(UrlType::Plain(url), hotspot_x, hotspot_y),
false,
),
CustomCursorSource::Animation { duration, cursors } => Self::build_spawn(
event_loop,
from_animation(
event_loop.runner.main_thread(),
duration,
cursors.into_iter().map(|cursor| cursor.inner),
),
true,
),
CustomCursorSource::Animation(CursorAnimation { duration, cursors }) => {
Self::build_spawn(
event_loop,
from_animation(event_loop.runner.main_thread(), duration, cursors.into_iter()),
true,
)
},
}
}
@ -163,6 +128,26 @@ impl CustomCursor {
}
}
impl CustomCursorProvider for CustomCursor {
fn is_animated(&self) -> bool {
self.animation
}
}
impl Hash for CustomCursor {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.state).hash(state);
}
}
impl PartialEq for CustomCursor {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.state, &other.state)
}
}
impl Eq for CustomCursor {}
#[derive(Debug)]
pub struct CustomCursorFuture {
notified: Notified<Result<(), CustomCursorError>>,
@ -230,13 +215,16 @@ impl CursorHandler {
this.set_style();
},
Cursor::Custom(cursor) => {
let cursor = cursor.inner;
let cursor = match cursor.cast_ref::<CustomCursor>() {
Some(cursor) => cursor,
None => todo!(),
};
if let SelectedCursor::Loading { cursor: old_cursor, .. }
| SelectedCursor::Image(old_cursor)
| SelectedCursor::Animation { cursor: old_cursor, .. } = &this.cursor
{
if *old_cursor == cursor {
if old_cursor == cursor {
return;
}
}
@ -263,7 +251,7 @@ impl CursorHandler {
drop(state);
this.cursor = SelectedCursor::Loading {
cursor,
cursor: cursor.clone(),
previous: mem::take(&mut this.cursor).into(),
_handle: handle,
};
@ -275,7 +263,7 @@ impl CursorHandler {
},
ImageState::Image(_) => {
drop(state);
this.cursor = SelectedCursor::Image(cursor);
this.cursor = SelectedCursor::Image(cursor.clone());
this.set_style();
},
ImageState::Animation(animation) => {
@ -292,7 +280,7 @@ impl CursorHandler {
this.cursor = SelectedCursor::Animation {
animation: AnimationDropper(animation),
cursor,
cursor: cursor.clone(),
};
this.set_style();
},
@ -650,12 +638,13 @@ async fn from_url(
async fn from_animation(
main_thread: MainThreadMarker,
duration: Duration,
cursors: impl ExactSizeIterator<Item = CustomCursor>,
cursors: impl ExactSizeIterator<Item = crate::cursor::CustomCursor>,
) -> Result<Animation, CustomCursorError> {
let keyframes = Array::new();
let mut images = Vec::with_capacity(cursors.len());
for cursor in cursors {
let cursor = cursor.cast_ref::<CustomCursor>().unwrap();
let state = cursor.state.get(main_thread).borrow();
match state.deref() {
@ -680,7 +669,7 @@ async fn from_animation(
keyframes.push(&keyframe);
drop(state);
images.push(cursor);
images.push(cursor.clone());
}
keyframes.push(&keyframes.get(0));

View file

@ -23,7 +23,7 @@ use crate::platform::web::{CustomCursorFuture, PollStrategy, WaitUntilStrategy};
use crate::platform_impl::platform::cursor::CustomCursor;
use crate::platform_impl::web::event_loop::proxy::EventLoopProxy;
use crate::platform_impl::Window;
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId};
use crate::window::{CustomCursor as CoreCustomCursor, CustomCursorSource, Theme, WindowId};
#[derive(Default, Debug)]
struct ModifiersShared(Rc<Cell<ModifiersState>>);
@ -65,7 +65,7 @@ impl ActiveEventLoop {
}
pub fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture {
CustomCursorFuture(CustomCursor::new_async(self, source.inner))
CustomCursorFuture(CustomCursor::new_async(self, source))
}
pub fn register(&self, canvas: &Rc<backend::Canvas>, window_id: WindowId) {
@ -498,8 +498,8 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn create_custom_cursor(
&self,
source: CustomCursorSource,
) -> Result<RootCustomCursor, RequestError> {
Ok(RootCustomCursor { inner: CustomCursor::new(self, source.inner) })
) -> Result<CoreCustomCursor, RequestError> {
Ok(CoreCustomCursor(Arc::new(CustomCursor::new(self, source))))
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoremMonitorHandle>> {

View file

@ -32,10 +32,7 @@ mod monitor;
mod web_sys;
mod window;
pub(crate) use cursor::{
CustomCursor as PlatformCustomCursor, CustomCursorFuture,
CustomCursorSource as PlatformCustomCursorSource,
};
pub(crate) use cursor::CustomCursorFuture;
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,

View file

@ -64,7 +64,7 @@ use super::window::set_skip_taskbar;
use super::SelectedCursor;
use crate::application::ApplicationHandler;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::error::{EventLoopError, RequestError};
use crate::error::{EventLoopError, NotSupportedError, RequestError};
use crate::event::{
DeviceEvent, DeviceId, FingerId, Force, Ime, RawKeyEvent, SurfaceSizeWriter, TouchPhase,
WindowEvent,
@ -93,7 +93,7 @@ use crate::platform_impl::platform::{raw_input, util, wrap_device_id};
use crate::platform_impl::Window;
use crate::utils::Lazy;
use crate::window::{
CustomCursor as RootCustomCursor, CustomCursorSource, Theme, Window as CoreWindow,
CustomCursor as CoreCustomCursor, CustomCursorSource, Theme, Window as CoreWindow,
WindowAttributes, WindowId,
};
@ -421,8 +421,15 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn create_custom_cursor(
&self,
source: CustomCursorSource,
) -> Result<RootCustomCursor, RequestError> {
Ok(RootCustomCursor { inner: WinCursor::new(&source.inner.0)? })
) -> Result<CoreCustomCursor, RequestError> {
let cursor = match source {
CustomCursorSource::Image(cursor) => cursor,
CustomCursorSource::Animation { .. } | CustomCursorSource::Url { .. } => {
return Err(NotSupportedError::new("unsupported cursor kind").into())
},
};
Ok(CoreCustomCursor(Arc::new(WinCursor::new(&cursor)?)))
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {

View file

@ -15,7 +15,7 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
};
use super::util;
use crate::cursor::CursorImage;
use crate::cursor::{CursorImage, CustomCursorProvider};
use crate::dpi::PhysicalSize;
use crate::error::RequestError;
use crate::icon::*;
@ -196,6 +196,12 @@ impl Default for SelectedCursor {
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct WinCursor(pub(super) Arc<RaiiCursor>);
impl CustomCursorProvider for WinCursor {
fn is_animated(&self) -> bool {
false
}
}
impl WinCursor {
pub(crate) fn new(image: &CursorImage) -> Result<Self, RequestError> {
let mut bgra = image.rgba.clone();

View file

@ -2,12 +2,10 @@ use windows_sys::Win32::Foundation::HWND;
use windows_sys::Win32::UI::WindowsAndMessaging::{HMENU, WINDOW_LONG_PTR_INDEX};
pub(crate) use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes};
pub use self::icon::WinIcon as PlatformIcon;
pub(crate) use self::icon::{SelectedCursor, WinCursor as PlatformCustomCursor, WinIcon};
pub(crate) use self::icon::{SelectedCursor, WinIcon as PlatformIcon, WinIcon};
pub(crate) use self::keyboard::{physicalkey_to_scancode, scancode_to_physicalkey};
pub(crate) use self::monitor::MonitorHandle;
pub(crate) use self::window::Window;
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
use crate::event::DeviceId;
use crate::icon::Icon;
use crate::platform::windows::{BackdropType, Color, CornerPreference};

View file

@ -47,6 +47,7 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
WDA_EXCLUDEFROMCAPTURE, WDA_NONE, WM_NCLBUTTONDOWN, WM_SYSCOMMAND, WNDCLASSEXW,
};
use super::icon::WinCursor;
use super::MonitorHandle;
use crate::cursor::Cursor;
use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
@ -597,10 +598,15 @@ impl CoreWindow for Window {
});
},
Cursor::Custom(cursor) => {
let cursor = match cursor.cast_ref::<WinCursor>() {
Some(cursor) => cursor,
None => return,
};
self.window_state_lock().mouse.selected_cursor =
SelectedCursor::Custom(cursor.inner.0.clone());
SelectedCursor::Custom(cursor.0.clone());
let handle = cursor.0.clone();
self.thread_executor.execute_in_thread(move || unsafe {
SetCursor(cursor.inner.0.as_raw_handle());
SetCursor(handle.as_raw_handle());
});
},
}