Skip to content

Commit

Permalink
Allow custom cursor caching (#3276)
Browse files Browse the repository at this point in the history
  • Loading branch information
daxpedda authored Dec 22, 2023
1 parent 0a7ea61 commit 2c15de7
Show file tree
Hide file tree
Showing 26 changed files with 579 additions and 347 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ features = [
'FocusEvent',
'HtmlCanvasElement',
'HtmlElement',
'HtmlImageElement',
'ImageBitmap',
'ImageBitmapOptions',
'ImageBitmapRenderingContext',
Expand Down
12 changes: 7 additions & 5 deletions examples/custom_cursors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
event_loop::{EventLoop, EventLoopWindowTarget},
keyboard::Key,
window::{CustomCursor, WindowBuilder},
};

fn decode_cursor(bytes: &[u8]) -> CustomCursor {
fn decode_cursor<T>(bytes: &[u8], window_target: &EventLoopWindowTarget<T>) -> CustomCursor {
let img = image::load_from_memory(bytes).unwrap().to_rgba8();
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()
let builder = CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap();

builder.build(window_target)
}

#[cfg(not(wasm_platform))]
Expand Down Expand Up @@ -43,8 +45,8 @@ fn main() -> Result<(), impl std::error::Error> {
let mut cursor_visible = true;

let custom_cursors = [
decode_cursor(include_bytes!("data/cross.png")),
decode_cursor(include_bytes!("data/cross2.png")),
decode_cursor(include_bytes!("data/cross.png"), &event_loop),
decode_cursor(include_bytes!("data/cross2.png"), &event_loop),
];

event_loop.run(move |event, _elwt| match event {
Expand Down
77 changes: 65 additions & 12 deletions src/cursor.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use core::fmt;
use std::{error::Error, sync::Arc};
use std::hash::Hasher;
use std::sync::Arc;
use std::{error::Error, hash::Hash};

use crate::platform_impl::PlatformCustomCursor;
use crate::event_loop::EventLoopWindowTarget;
use crate::platform_impl::{self, PlatformCustomCursor, PlatformCustomCursorBuilder};

/// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`].
pub const MAX_CURSOR_SIZE: u16 = 2048;
Expand All @@ -16,25 +19,52 @@ const PIXEL_SIZE: usize = 4;
///
/// # Example
///
/// ```
/// use winit::window::CustomCursor;
/// ```no_run
/// use winit::{
/// event::{Event, WindowEvent},
/// event_loop::{ControlFlow, EventLoop},
/// window::{CustomCursor, Window},
/// };
///
/// let mut event_loop = EventLoop::new().unwrap();
///
/// let w = 10;
/// let h = 10;
/// let rgba = vec![255; (w * h * 4) as usize];
/// let custom_cursor = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
///
/// #[cfg(not(target_family = "wasm"))]
/// let builder = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
///
/// #[cfg(target_family = "wasm")]
/// let custom_cursor_url = {
/// let builder = {
/// use winit::platform::web::CustomCursorExtWebSys;
/// CustomCursor::from_url("http://localhost:3000/cursor.png", 0, 0).unwrap()
/// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0)
/// };
///
/// let custom_cursor = builder.build(&event_loop);
///
/// let window = Window::new(&event_loop).unwrap();
/// window.set_custom_cursor(&custom_cursor);
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Clone, Debug)]
pub struct CustomCursor {
pub(crate) inner: Arc<PlatformCustomCursor>,
}

impl Hash for CustomCursor {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.inner).hash(state);
}
}

impl PartialEq for CustomCursor {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
}

impl Eq for CustomCursor {}

impl CustomCursor {
/// Creates a new cursor from an rgba buffer.
///
Expand All @@ -48,20 +78,35 @@ impl CustomCursor {
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
Ok(Self {
) -> Result<CustomCursorBuilder, BadImage> {
Ok(CustomCursorBuilder {
inner: PlatformCustomCursor::from_rgba(
rgba.into(),
width,
height,
hotspot_x,
hotspot_y,
)?
.into(),
)?,
})
}
}

/// Builds a [`CustomCursor`].
///
/// See [`CustomCursor`] for more details.
#[derive(Debug)]
pub struct CustomCursorBuilder {
pub(crate) inner: PlatformCustomCursorBuilder,
}

impl CustomCursorBuilder {
pub fn build<T>(self, window_target: &EventLoopWindowTarget<T>) -> CustomCursor {
CustomCursor {
inner: self.inner.build(&window_target.p),
}
}
}

/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
#[derive(Debug, Clone)]
pub enum BadImage {
Expand Down Expand Up @@ -177,6 +222,10 @@ impl CursorImage {
hotspot_y,
})
}

fn build<T>(self, _: &platform_impl::EventLoopWindowTarget<T>) -> Arc<CursorImage> {
Arc::new(self)
}
}

// Platforms that don't support cursors will export this as `PlatformCustomCursor`.
Expand All @@ -195,4 +244,8 @@ impl NoCustomCursor {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
Ok(Self)
}

fn build<T>(self, _: &platform_impl::EventLoopWindowTarget<T>) -> Arc<NoCustomCursor> {
Arc::new(self)
}
}
16 changes: 8 additions & 8 deletions src/platform/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@
//! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
//! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding

use crate::cursor::CustomCursor;
use crate::cursor::CustomCursorBuilder;
use crate::event::Event;
use crate::event_loop::EventLoop;
use crate::event_loop::EventLoopWindowTarget;
use crate::platform_impl::PlatformCustomCursor;
use crate::platform_impl::PlatformCustomCursorBuilder;
use crate::window::CustomCursor;
use crate::window::{Window, WindowBuilder};
use crate::SendSyncWrapper;

Expand Down Expand Up @@ -209,18 +210,17 @@ pub trait CustomCursorExtWebSys {
/// 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) -> Self;
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder;
}

impl CustomCursorExtWebSys for CustomCursor {
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Self {
Self {
inner: PlatformCustomCursor::Url {
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder {
CustomCursorBuilder {
inner: PlatformCustomCursorBuilder::Url {
url,
hotspot_x,
hotspot_y,
}
.into(),
},
}
}
}
4 changes: 2 additions & 2 deletions src/platform_impl/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use android_activity::{
use once_cell::sync::Lazy;

use crate::{
cursor::CustomCursor,
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error,
event::{self, Force, InnerSizeWriter, StartCause},
Expand Down Expand Up @@ -907,7 +906,7 @@ impl Window {

pub fn set_cursor_icon(&self, _: window::CursorIcon) {}

pub fn set_custom_cursor(&self, _: CustomCursor) {}
pub(crate) fn set_custom_cursor(&self, _: Arc<PlatformCustomCursor>) {}

pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
Expand Down Expand Up @@ -1035,6 +1034,7 @@ impl Display for OsError {
}

pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder;
pub(crate) use crate::icon::NoIcon as PlatformIcon;

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
Expand Down
1 change: 1 addition & 0 deletions src/platform_impl/ios/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ pub(crate) use self::{

use self::uikit::UIScreen;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;

Expand Down
6 changes: 3 additions & 3 deletions src/platform_impl/ios/window.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![allow(clippy::unnecessary_cast)]

use std::collections::VecDeque;
use std::sync::Arc;

use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker};
use objc2::rc::Id;
Expand All @@ -11,14 +12,13 @@ use super::app_state::EventWrapper;
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
use super::view::{WinitUIWindow, WinitView, WinitViewController};
use crate::{
cursor::CustomCursor,
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, WindowEvent},
icon::Icon,
platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations},
platform_impl::platform::{
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, PlatformCustomCursor,
},
window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
Expand Down Expand Up @@ -178,7 +178,7 @@ impl Inner {
debug!("`Window::set_cursor_icon` ignored on iOS")
}

pub fn set_custom_cursor(&self, _: CustomCursor) {
pub(crate) fn set_custom_cursor(&self, _: Arc<PlatformCustomCursor>) {
debug!("`Window::set_custom_cursor` ignored on iOS")
}

Expand Down
4 changes: 2 additions & 2 deletions src/platform_impl/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
use once_cell::sync::Lazy;
use smol_str::SmolStr;

use crate::cursor::CustomCursor;
#[cfg(x11_platform)]
use crate::platform::x11::XlibErrorHook;
use crate::{
Expand All @@ -41,6 +40,7 @@ pub use x11::XNotSupported;
#[cfg(x11_platform)]
use x11::{util::WindowType as XWindowType, X11Error, XConnection, XError};

pub(crate) use crate::cursor::CursorImage as PlatformCustomCursorBuilder;
pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor;
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;
Expand Down Expand Up @@ -427,7 +427,7 @@ impl Window {
}

#[inline]
pub fn set_custom_cursor(&self, cursor: CustomCursor) {
pub(crate) fn set_custom_cursor(&self, cursor: Arc<PlatformCustomCursor>) {
x11_or_wayland!(match self; Window(w) => w.set_custom_cursor(cursor))
}

Expand Down
9 changes: 4 additions & 5 deletions src/platform_impl/linux/wayland/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@ use sctk::shell::xdg::window::Window as SctkWindow;
use sctk::shell::xdg::window::WindowDecorations;
use sctk::shell::WaylandSurface;

use crate::cursor::CustomCursor;
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{Ime, WindowEvent};
use crate::event_loop::AsyncRequestSerial;
use crate::platform_impl::{
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor,
PlatformIcon, PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
};
use crate::window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
Expand Down Expand Up @@ -508,8 +507,8 @@ impl Window {
}

#[inline]
pub fn set_custom_cursor(&self, cursor: CustomCursor) {
self.window_state.lock().unwrap().set_custom_cursor(cursor);
pub(crate) fn set_custom_cursor(&self, cursor: Arc<PlatformCustomCursor>) {
self.window_state.lock().unwrap().set_custom_cursor(&cursor);
}

#[inline]
Expand Down
6 changes: 3 additions & 3 deletions src/platform_impl/linux/wayland/window/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use sctk::shm::Shm;
use sctk::subcompositor::SubcompositorState;
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;

use crate::cursor::CustomCursor as RootCustomCursor;
use crate::cursor::CursorImage;
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
use crate::error::{ExternalError, NotSupportedError};
use crate::event::WindowEvent;
Expand Down Expand Up @@ -726,10 +726,10 @@ impl WindowState {
}

/// Set the custom cursor icon.
pub fn set_custom_cursor(&mut self, cursor: RootCustomCursor) {
pub fn set_custom_cursor(&mut self, cursor: &CursorImage) {
let cursor = {
let mut pool = self.custom_cursor_pool.lock().unwrap();
CustomCursor::new(&mut pool, &cursor.inner)
CustomCursor::new(&mut pool, cursor)
};

if self.cursor_visible {
Expand Down
10 changes: 4 additions & 6 deletions src/platform_impl/linux/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ use std::{
sync::{Arc, Mutex, MutexGuard},
};

use crate::cursor::CustomCursor as RootCustomCursor;

use cursor_icon::CursorIcon;
use x11rb::{
connection::Connection,
Expand All @@ -32,8 +30,8 @@ use crate::{
atoms::*, xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender,
X11Error,
},
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor,
PlatformIcon, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
},
window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
Expand Down Expand Up @@ -1552,8 +1550,8 @@ impl UnownedWindow {
}

#[inline]
pub fn set_custom_cursor(&self, cursor: RootCustomCursor) {
let new_cursor = unsafe { CustomCursor::new(&self.xconn, &cursor.inner) };
pub(crate) fn set_custom_cursor(&self, cursor: Arc<PlatformCustomCursor>) {
let new_cursor = unsafe { CustomCursor::new(&self.xconn, &cursor) };

#[allow(clippy::mutex_atomic)]
if *self.cursor_visible.lock().unwrap() {
Expand Down
1 change: 1 addition & 0 deletions src/platform_impl/macos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use crate::event::DeviceId as RootDeviceId;

pub(crate) use self::window::Window;
pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor;
pub(crate) use crate::cursor::CursorImage as PlatformCustomCursorBuilder;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;

Expand Down
Loading

0 comments on commit 2c15de7

Please sign in to comment.