Allow custom cursor caching (#3276)

This commit is contained in:
daxpedda 2023-12-22 22:20:41 +01:00 committed by GitHub
parent 0a7ea61834
commit 2c15de7cf9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 579 additions and 347 deletions

View file

@ -191,6 +191,7 @@ features = [
'FocusEvent', 'FocusEvent',
'HtmlCanvasElement', 'HtmlCanvasElement',
'HtmlElement', 'HtmlElement',
'HtmlImageElement',
'ImageBitmap', 'ImageBitmap',
'ImageBitmapOptions', 'ImageBitmapOptions',
'ImageBitmapRenderingContext', 'ImageBitmapRenderingContext',

View file

@ -4,17 +4,19 @@
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent}, event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop, event_loop::{EventLoop, EventLoopWindowTarget},
keyboard::Key, keyboard::Key,
window::{CustomCursor, WindowBuilder}, 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 img = image::load_from_memory(bytes).unwrap().to_rgba8();
let samples = img.into_flat_samples(); let samples = img.into_flat_samples();
let (_, w, h) = samples.extents(); let (_, w, h) = samples.extents();
let (w, h) = (w as u16, h as u16); 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))] #[cfg(not(wasm_platform))]
@ -43,8 +45,8 @@ fn main() -> Result<(), impl std::error::Error> {
let mut cursor_visible = true; let mut cursor_visible = true;
let custom_cursors = [ let custom_cursors = [
decode_cursor(include_bytes!("data/cross.png")), decode_cursor(include_bytes!("data/cross.png"), &event_loop),
decode_cursor(include_bytes!("data/cross2.png")), decode_cursor(include_bytes!("data/cross2.png"), &event_loop),
]; ];
event_loop.run(move |event, _elwt| match event { event_loop.run(move |event, _elwt| match event {

View file

@ -1,7 +1,10 @@
use core::fmt; 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`]. /// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`].
pub const MAX_CURSOR_SIZE: u16 = 2048; pub const MAX_CURSOR_SIZE: u16 = 2048;
@ -16,25 +19,52 @@ const PIXEL_SIZE: usize = 4;
/// ///
/// # Example /// # Example
/// ///
/// ``` /// ```no_run
/// use winit::window::CustomCursor; /// use winit::{
/// event::{Event, WindowEvent},
/// event_loop::{ControlFlow, EventLoop},
/// window::{CustomCursor, Window},
/// };
///
/// let mut event_loop = EventLoop::new().unwrap();
/// ///
/// let w = 10; /// let w = 10;
/// let h = 10; /// let h = 10;
/// let rgba = vec![255; (w * h * 4) as usize]; /// 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")] /// #[cfg(target_family = "wasm")]
/// let custom_cursor_url = { /// let builder = {
/// use winit::platform::web::CustomCursorExtWebSys; /// 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 struct CustomCursor {
pub(crate) inner: Arc<PlatformCustomCursor>, 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 { impl CustomCursor {
/// Creates a new cursor from an rgba buffer. /// Creates a new cursor from an rgba buffer.
/// ///
@ -48,20 +78,35 @@ impl CustomCursor {
height: u16, height: u16,
hotspot_x: u16, hotspot_x: u16,
hotspot_y: u16, hotspot_y: u16,
) -> Result<Self, BadImage> { ) -> Result<CustomCursorBuilder, BadImage> {
Ok(Self { Ok(CustomCursorBuilder {
inner: PlatformCustomCursor::from_rgba( inner: PlatformCustomCursor::from_rgba(
rgba.into(), rgba.into(),
width, width,
height, height,
hotspot_x, hotspot_x,
hotspot_y, 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. /// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum BadImage { pub enum BadImage {
@ -177,6 +222,10 @@ impl CursorImage {
hotspot_y, 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`. // Platforms that don't support cursors will export this as `PlatformCustomCursor`.
@ -195,4 +244,8 @@ impl NoCustomCursor {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?; CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
Ok(Self) Ok(Self)
} }
fn build<T>(self, _: &platform_impl::EventLoopWindowTarget<T>) -> Arc<NoCustomCursor> {
Arc::new(self)
}
} }

View file

@ -27,11 +27,12 @@
//! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border //! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
//! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding //! [`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::Event;
use crate::event_loop::EventLoop; use crate::event_loop::EventLoop;
use crate::event_loop::EventLoopWindowTarget; 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::window::{Window, WindowBuilder};
use crate::SendSyncWrapper; use crate::SendSyncWrapper;
@ -209,18 +210,17 @@ pub trait CustomCursorExtWebSys {
/// but browser support for image formats is inconsistent. Using [PNG] is recommended. /// but browser support for image formats is inconsistent. Using [PNG] is recommended.
/// ///
/// [PNG]: https://en.wikipedia.org/wiki/PNG /// [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 { impl CustomCursorExtWebSys for CustomCursor {
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Self { fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder {
Self { CustomCursorBuilder {
inner: PlatformCustomCursor::Url { inner: PlatformCustomCursorBuilder::Url {
url, url,
hotspot_x, hotspot_x,
hotspot_y, hotspot_y,
} },
.into(),
} }
} }
} }

View file

@ -18,7 +18,6 @@ use android_activity::{
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::{ use crate::{
cursor::CustomCursor,
dpi::{PhysicalPosition, PhysicalSize, Position, Size}, dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error, error,
event::{self, Force, InnerSizeWriter, StartCause}, event::{self, Force, InnerSizeWriter, StartCause},
@ -907,7 +906,7 @@ impl Window {
pub fn set_cursor_icon(&self, _: window::CursorIcon) {} 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> { pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported( Err(error::ExternalError::NotSupported(
@ -1035,6 +1034,7 @@ impl Display for OsError {
} }
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor; 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::icon::NoIcon as PlatformIcon;
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]

View file

@ -78,6 +78,7 @@ pub(crate) use self::{
use self::uikit::UIScreen; use self::uikit::UIScreen;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor; 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::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen; pub(crate) use crate::platform_impl::Fullscreen;

View file

@ -1,6 +1,7 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::collections::VecDeque; use std::collections::VecDeque;
use std::sync::Arc;
use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker}; use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker};
use objc2::rc::Id; use objc2::rc::Id;
@ -11,14 +12,13 @@ use super::app_state::EventWrapper;
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation}; use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
use super::view::{WinitUIWindow, WinitView, WinitViewController}; use super::view::{WinitUIWindow, WinitView, WinitViewController};
use crate::{ use crate::{
cursor::CustomCursor,
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError}, error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, WindowEvent}, event::{Event, WindowEvent},
icon::Icon, icon::Icon,
platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations}, platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations},
platform_impl::platform::{ platform_impl::platform::{
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, PlatformCustomCursor,
}, },
window::{ window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
@ -178,7 +178,7 @@ impl Inner {
debug!("`Window::set_cursor_icon` ignored on iOS") 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") debug!("`Window::set_custom_cursor` ignored on iOS")
} }

View file

@ -14,7 +14,6 @@ use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use smol_str::SmolStr; use smol_str::SmolStr;
use crate::cursor::CustomCursor;
#[cfg(x11_platform)] #[cfg(x11_platform)]
use crate::platform::x11::XlibErrorHook; use crate::platform::x11::XlibErrorHook;
use crate::{ use crate::{
@ -41,6 +40,7 @@ pub use x11::XNotSupported;
#[cfg(x11_platform)] #[cfg(x11_platform)]
use x11::{util::WindowType as XWindowType, X11Error, XConnection, XError}; 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::cursor::CursorImage as PlatformCustomCursor;
pub(crate) use crate::icon::RgbaIcon as PlatformIcon; pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen; pub(crate) use crate::platform_impl::Fullscreen;
@ -427,7 +427,7 @@ impl Window {
} }
#[inline] #[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)) x11_or_wayland!(match self; Window(w) => w.set_custom_cursor(cursor))
} }

View file

@ -15,14 +15,13 @@ use sctk::shell::xdg::window::Window as SctkWindow;
use sctk::shell::xdg::window::WindowDecorations; use sctk::shell::xdg::window::WindowDecorations;
use sctk::shell::WaylandSurface; use sctk::shell::WaylandSurface;
use crate::cursor::CustomCursor;
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}; use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{Ime, WindowEvent}; use crate::event::{Ime, WindowEvent};
use crate::event_loop::AsyncRequestSerial; use crate::event_loop::AsyncRequestSerial;
use crate::platform_impl::{ use crate::platform_impl::{
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon, Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor,
PlatformSpecificWindowBuilderAttributes as PlatformAttributes, PlatformIcon, PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
}; };
use crate::window::{ use crate::window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
@ -508,8 +507,8 @@ impl Window {
} }
#[inline] #[inline]
pub fn set_custom_cursor(&self, cursor: CustomCursor) { pub(crate) fn set_custom_cursor(&self, cursor: Arc<PlatformCustomCursor>) {
self.window_state.lock().unwrap().set_custom_cursor(cursor); self.window_state.lock().unwrap().set_custom_cursor(&cursor);
} }
#[inline] #[inline]

View file

@ -28,7 +28,7 @@ use sctk::shm::Shm;
use sctk::subcompositor::SubcompositorState; use sctk::subcompositor::SubcompositorState;
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur; 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::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
use crate::error::{ExternalError, NotSupportedError}; use crate::error::{ExternalError, NotSupportedError};
use crate::event::WindowEvent; use crate::event::WindowEvent;
@ -726,10 +726,10 @@ impl WindowState {
} }
/// Set the custom cursor icon. /// 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 cursor = {
let mut pool = self.custom_cursor_pool.lock().unwrap(); let mut pool = self.custom_cursor_pool.lock().unwrap();
CustomCursor::new(&mut pool, &cursor.inner) CustomCursor::new(&mut pool, cursor)
}; };
if self.cursor_visible { if self.cursor_visible {

View file

@ -7,8 +7,6 @@ use std::{
sync::{Arc, Mutex, MutexGuard}, sync::{Arc, Mutex, MutexGuard},
}; };
use crate::cursor::CustomCursor as RootCustomCursor;
use cursor_icon::CursorIcon; use cursor_icon::CursorIcon;
use x11rb::{ use x11rb::{
connection::Connection, connection::Connection,
@ -32,8 +30,8 @@ use crate::{
atoms::*, xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender, atoms::*, xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender,
X11Error, X11Error,
}, },
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon, Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor,
PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, PlatformIcon, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
}, },
window::{ window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
@ -1552,8 +1550,8 @@ impl UnownedWindow {
} }
#[inline] #[inline]
pub fn set_custom_cursor(&self, cursor: RootCustomCursor) { pub(crate) fn set_custom_cursor(&self, cursor: Arc<PlatformCustomCursor>) {
let new_cursor = unsafe { CustomCursor::new(&self.xconn, &cursor.inner) }; let new_cursor = unsafe { CustomCursor::new(&self.xconn, &cursor) };
#[allow(clippy::mutex_atomic)] #[allow(clippy::mutex_atomic)]
if *self.cursor_visible.lock().unwrap() { if *self.cursor_visible.lock().unwrap() {

View file

@ -29,6 +29,7 @@ use crate::event::DeviceId as RootDeviceId;
pub(crate) use self::window::Window; pub(crate) use self::window::Window;
pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor; 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::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen; pub(crate) use crate::platform_impl::Fullscreen;

View file

@ -5,9 +5,9 @@ use std::f64;
use std::ops; use std::ops;
use std::os::raw::c_void; use std::os::raw::c_void;
use std::ptr::NonNull; use std::ptr::NonNull;
use std::sync::Arc;
use std::sync::{Mutex, MutexGuard}; use std::sync::{Mutex, MutexGuard};
use crate::cursor::CustomCursor;
use crate::{ use crate::{
dpi::{ dpi::{
LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size, Size::Logical, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size, Size::Logical,
@ -25,7 +25,7 @@ use crate::{
util, util,
view::WinitView, view::WinitView,
window_delegate::WinitWindowDelegate, window_delegate::WinitWindowDelegate,
Fullscreen, OsError, Fullscreen, OsError, PlatformCustomCursor,
}, },
window::{ window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
@ -836,9 +836,9 @@ impl WinitWindow {
} }
#[inline] #[inline]
pub fn set_custom_cursor(&self, cursor: CustomCursor) { pub(crate) fn set_custom_cursor(&self, cursor: Arc<PlatformCustomCursor>) {
let view = self.view(); let view = self.view();
view.set_cursor_icon(NSCursor::from_image(&cursor.inner)); view.set_cursor_icon(NSCursor::from_image(&cursor));
self.invalidateCursorRectsForView(&view); self.invalidateCursorRectsForView(&view);
} }

View file

@ -194,6 +194,7 @@ impl Display for OsError {
} }
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor; 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::icon::NoIcon as PlatformIcon;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]

View file

@ -4,7 +4,6 @@ use std::{
}; };
use crate::{ use crate::{
cursor::CustomCursor,
dpi::{PhysicalPosition, PhysicalSize, Position, Size}, dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error, error,
platform_impl::Fullscreen, platform_impl::Fullscreen,
@ -13,8 +12,8 @@ use crate::{
}; };
use super::{ use super::{
EventLoopWindowTarget, MonitorHandle, PlatformSpecificWindowBuilderAttributes, RedoxSocket, EventLoopWindowTarget, MonitorHandle, PlatformCustomCursor,
TimeSocket, WindowId, WindowProperties, PlatformSpecificWindowBuilderAttributes, RedoxSocket, TimeSocket, WindowId, WindowProperties,
}; };
// These values match the values uses in the `window_new` function in orbital: // These values match the values uses in the `window_new` function in orbital:
@ -353,7 +352,7 @@ impl Window {
#[inline] #[inline]
pub fn set_cursor_icon(&self, _: window::CursorIcon) {} pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
pub fn set_custom_cursor(&self, _: CustomCursor) {} pub(crate) fn set_custom_cursor(&self, _: Arc<PlatformCustomCursor>) {}
#[inline] #[inline]
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {

View file

@ -6,65 +6,67 @@ use std::sync::mpsc::{self, Receiver, RecvError, SendError, Sender, TryRecvError
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::task::Poll; use std::task::Poll;
// NOTE: This channel doesn't wake up when all senders or receivers are
// dropped. This is acceptable as long as it's only used in `Dispatcher`, which
// has it's own `Drop` behavior.
pub fn channel<T>() -> (AsyncSender<T>, AsyncReceiver<T>) { pub fn channel<T>() -> (AsyncSender<T>, AsyncReceiver<T>) {
let (sender, receiver) = mpsc::channel(); let (sender, receiver) = mpsc::channel();
let sender = Arc::new(Mutex::new(sender)); let shared = Arc::new(Shared {
let inner = Arc::new(Inner {
closed: AtomicBool::new(false), closed: AtomicBool::new(false),
waker: AtomicWaker::new(), waker: AtomicWaker::new(),
}); });
let sender = AsyncSender { let sender = AsyncSender(Arc::new(SenderInner {
sender, sender: Mutex::new(sender),
inner: Arc::clone(&inner), shared: Arc::clone(&shared),
}; }));
let receiver = AsyncReceiver { let receiver = AsyncReceiver {
receiver: Rc::new(receiver), receiver: Rc::new(receiver),
inner, shared,
}; };
(sender, receiver) (sender, receiver)
} }
pub struct AsyncSender<T> { pub struct AsyncSender<T>(Arc<SenderInner<T>>);
struct SenderInner<T> {
// We need to wrap it into a `Mutex` to make it `Sync`. So the sender can't // We need to wrap it into a `Mutex` to make it `Sync`. So the sender can't
// be accessed on the main thread, as it could block. Additionally we need // be accessed on the main thread, as it could block. Additionally we need
// to wrap it in an `Arc` to make it clonable on the main thread without // to wrap `Sender` in an `Arc` to make it clonable on the main thread without
// having to block. // having to block.
sender: Arc<Mutex<Sender<T>>>, sender: Mutex<Sender<T>>,
inner: Arc<Inner>, shared: Arc<Shared>,
} }
impl<T> AsyncSender<T> { impl<T> AsyncSender<T> {
pub fn send(&self, event: T) -> Result<(), SendError<T>> { pub fn send(&self, event: T) -> Result<(), SendError<T>> {
self.sender.lock().unwrap().send(event)?; self.0.sender.lock().unwrap().send(event)?;
self.inner.waker.wake(); self.0.shared.waker.wake();
Ok(()) Ok(())
} }
}
pub fn close(&self) { impl<T> SenderInner<T> {
self.inner.closed.store(true, Ordering::Relaxed); fn close(&self) {
self.inner.waker.wake() self.shared.closed.store(true, Ordering::Relaxed);
self.shared.waker.wake();
} }
} }
impl<T> Clone for AsyncSender<T> { impl<T> Clone for AsyncSender<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self(Arc::clone(&self.0))
sender: Arc::clone(&self.sender), }
inner: Arc::clone(&self.inner), }
}
impl<T> Drop for SenderInner<T> {
fn drop(&mut self) {
self.close();
} }
} }
pub struct AsyncReceiver<T> { pub struct AsyncReceiver<T> {
receiver: Rc<Receiver<T>>, receiver: Rc<Receiver<T>>,
inner: Arc<Inner>, shared: Arc<Shared>,
} }
impl<T> AsyncReceiver<T> { impl<T> AsyncReceiver<T> {
@ -72,12 +74,12 @@ impl<T> AsyncReceiver<T> {
future::poll_fn(|cx| match self.receiver.try_recv() { future::poll_fn(|cx| match self.receiver.try_recv() {
Ok(event) => Poll::Ready(Ok(event)), Ok(event) => Poll::Ready(Ok(event)),
Err(TryRecvError::Empty) => { Err(TryRecvError::Empty) => {
self.inner.waker.register(cx.waker()); self.shared.waker.register(cx.waker());
match self.receiver.try_recv() { match self.receiver.try_recv() {
Ok(event) => Poll::Ready(Ok(event)), Ok(event) => Poll::Ready(Ok(event)),
Err(TryRecvError::Empty) => { Err(TryRecvError::Empty) => {
if self.inner.closed.load(Ordering::Relaxed) { if self.shared.closed.load(Ordering::Relaxed) {
Poll::Ready(Err(RecvError)) Poll::Ready(Err(RecvError))
} else { } else {
Poll::Pending Poll::Pending
@ -104,12 +106,18 @@ impl<T> Clone for AsyncReceiver<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
receiver: Rc::clone(&self.receiver), receiver: Rc::clone(&self.receiver),
inner: Arc::clone(&self.inner), shared: Arc::clone(&self.shared),
} }
} }
} }
struct Inner { impl<T> Drop for AsyncReceiver<T> {
fn drop(&mut self) {
self.shared.closed.store(true, Ordering::Relaxed);
}
}
struct Shared {
closed: AtomicBool, closed: AtomicBool,
waker: AtomicWaker, waker: AtomicWaker,
} }

View file

@ -82,12 +82,6 @@ impl<T> Dispatcher<T> {
} }
} }
impl<T> Drop for Dispatcher<T> {
fn drop(&mut self) {
self.0.with_sender_data(|sender| sender.close())
}
}
pub struct DispatchRunner<T: 'static> { pub struct DispatchRunner<T: 'static> {
wrapper: Wrapper<true, T, AsyncSender<Closure<T>>, Closure<T>>, wrapper: Wrapper<true, T, AsyncSender<Closure<T>>, Closure<T>>,
receiver: AsyncReceiver<Closure<T>>, receiver: AsyncReceiver<Closure<T>>,

View file

@ -3,7 +3,7 @@ mod dispatcher;
mod waker; mod waker;
mod wrapper; mod wrapper;
use self::channel::{channel, AsyncReceiver, AsyncSender}; pub use self::channel::{channel, AsyncReceiver, AsyncSender};
pub use self::dispatcher::{DispatchRunner, Dispatcher}; pub use self::dispatcher::{DispatchRunner, Dispatcher};
pub use self::waker::{Waker, WakerSpawner}; pub use self::waker::{Waker, WakerSpawner};
use self::wrapper::Wrapper; use self::wrapper::Wrapper;

View file

@ -1,22 +1,31 @@
use std::{ use std::{
cell::{Cell, RefCell}, cell::RefCell,
ops::Deref, future, mem,
rc::{Rc, Weak}, ops::DerefMut,
rc::{self, Rc},
sync::{self, Arc},
task::{Poll, Waker},
}; };
use crate::cursor::{BadImage, CursorImage}; use crate::{
cursor::{BadImage, CursorImage},
platform_impl::platform::r#async,
};
use cursor_icon::CursorIcon; use cursor_icon::CursorIcon;
use once_cell::sync::Lazy;
use wasm_bindgen::{closure::Closure, JsCast}; use wasm_bindgen::{closure::Closure, JsCast};
use wasm_bindgen_futures::JsFuture; use wasm_bindgen_futures::JsFuture;
use web_sys::{ use web_sys::{
Blob, Document, HtmlCanvasElement, ImageBitmap, ImageBitmapOptions, Blob, Document, HtmlCanvasElement, HtmlImageElement, ImageBitmap, ImageBitmapOptions,
ImageBitmapRenderingContext, ImageData, PremultiplyAlpha, Url, Window, ImageBitmapRenderingContext, ImageData, PremultiplyAlpha, Url, Window,
}; };
use super::backend::Style; use self::thread_safe::ThreadSafe;
#[derive(Debug, Clone, PartialEq, Eq)] use super::{backend::Style, r#async::AsyncSender, EventLoopWindowTarget};
pub enum WebCustomCursor {
#[derive(Debug)]
pub enum CustomCursorBuilder {
Image(CursorImage), Image(CursorImage),
Url { Url {
url: String, url: String,
@ -25,72 +34,163 @@ pub enum WebCustomCursor {
}, },
} }
impl WebCustomCursor { impl CustomCursorBuilder {
pub fn build<T>(self, window_target: &EventLoopWindowTarget<T>) -> Arc<CustomCursor> {
Lazy::force(&DROP_HANDLER);
match self {
Self::Image(image) => ImageState::from_rgba(
window_target.runner.window(),
window_target.runner.document().clone(),
&image,
),
Self::Url {
url,
hotspot_x,
hotspot_y,
} => ImageState::from_url(url, hotspot_x, hotspot_y),
}
}
}
#[derive(Debug)]
pub struct CustomCursor(Option<ThreadSafe<RefCell<ImageState>>>);
static DROP_HANDLER: Lazy<AsyncSender<ThreadSafe<RefCell<ImageState>>>> = Lazy::new(|| {
let (sender, receiver) = r#async::channel();
wasm_bindgen_futures::spawn_local(async move { while receiver.next().await.is_ok() {} });
sender
});
impl CustomCursor {
pub fn from_rgba( pub fn from_rgba(
rgba: Vec<u8>, rgba: Vec<u8>,
width: u16, width: u16,
height: u16, height: u16,
hotspot_x: u16, hotspot_x: u16,
hotspot_y: u16, hotspot_y: u16,
) -> Result<Self, BadImage> { ) -> Result<CustomCursorBuilder, BadImage> {
Ok(Self::Image(CursorImage::from_rgba( Ok(CustomCursorBuilder::Image(CursorImage::from_rgba(
rgba, width, height, hotspot_x, hotspot_y, rgba, width, height, hotspot_x, hotspot_y,
)?)) )?))
} }
pub(super) fn build( fn new() -> Arc<Self> {
&self, Arc::new(Self(Some(ThreadSafe::new(RefCell::new(
window: &Window, ImageState::Loading(None),
document: &Document, )))))
style: &Style, }
previous: SelectedCursor,
cursor_visible: Rc<Cell<bool>>,
) -> SelectedCursor {
let previous = previous.into();
match self { fn get(&self) -> &RefCell<ImageState> {
WebCustomCursor::Image(image) => SelectedCursor::Image(CursorImageState::from_image( self.0
window, .as_ref()
document.clone(), .expect("value has accidently already been dropped")
style.clone(), .get()
image, }
previous, }
cursor_visible,
)),
WebCustomCursor::Url {
url,
hotspot_x,
hotspot_y,
} => {
let value = previous.style_with_url(url, *hotspot_x, *hotspot_y);
if cursor_visible.get() { impl Drop for CustomCursor {
style.set("cursor", &value); fn drop(&mut self) {
} let value = self
.0
.take()
.expect("value has accidently already been dropped");
SelectedCursor::Url { if !value.in_origin_thread() {
style: value, DROP_HANDLER
previous, .send(value)
url: url.clone(), .expect("sender dropped in main thread")
hotspot_x: *hotspot_x,
hotspot_y: *hotspot_y,
}
}
} }
} }
} }
#[derive(Debug)] #[derive(Debug)]
pub enum SelectedCursor { pub struct CursorState(Rc<RefCell<State>>);
impl CursorState {
pub fn new(style: Style) -> Self {
Self(Rc::new(RefCell::new(State {
style,
visible: true,
cursor: SelectedCursor::default(),
})))
}
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let mut this = self.0.borrow_mut();
if let SelectedCursor::ImageLoading { state, .. } = &this.cursor {
if let ImageState::Loading(state) = state.get().borrow_mut().deref_mut() {
state.take();
}
}
this.cursor = SelectedCursor::Named(cursor);
this.set_style();
}
pub fn set_custom_cursor(&self, cursor: Arc<CustomCursor>) {
let mut this = self.0.borrow_mut();
match cursor.get().borrow_mut().deref_mut() {
ImageState::Loading(state) => {
this.cursor = SelectedCursor::ImageLoading {
state: cursor.clone(),
previous: mem::take(&mut this.cursor).into(),
};
*state = Some(Rc::downgrade(&self.0));
}
ImageState::Failed => log::error!("tried to load invalid cursor"),
ImageState::Ready(image) => {
this.cursor = SelectedCursor::ImageReady(image.clone());
this.set_style();
}
}
}
pub fn set_cursor_visible(&self, visible: bool) {
let mut state = self.0.borrow_mut();
if !visible && state.visible {
state.visible = false;
state.style.set("cursor", "none");
} else if visible && !state.visible {
state.visible = true;
state.set_style();
}
}
}
#[derive(Debug)]
struct State {
style: Style,
visible: bool,
cursor: SelectedCursor,
}
impl State {
pub fn set_style(&self) {
if self.visible {
let value = match &self.cursor {
SelectedCursor::Named(icon) => icon.name(),
SelectedCursor::ImageLoading { previous, .. } => previous.style(),
SelectedCursor::ImageReady(image) => &image.style,
};
self.style.set("cursor", value);
}
}
}
#[derive(Debug)]
enum SelectedCursor {
Named(CursorIcon), Named(CursorIcon),
Url { ImageLoading {
style: String, state: Arc<CustomCursor>,
previous: Previous, previous: Previous,
url: String,
hotspot_x: u16,
hotspot_y: u16,
}, },
Image(Rc<RefCell<Option<CursorImageState>>>), ImageReady(Rc<Image>),
} }
impl Default for SelectedCursor { impl Default for SelectedCursor {
@ -99,71 +199,26 @@ impl Default for SelectedCursor {
} }
} }
impl SelectedCursor { impl From<Previous> for SelectedCursor {
pub fn set_style(&self, style: &Style) { fn from(previous: Previous) -> Self {
let value = match self { match previous {
SelectedCursor::Named(icon) => icon.name(), Previous::Named(icon) => Self::Named(icon),
SelectedCursor::Url { style, .. } => style, Previous::Image(image) => Self::ImageReady(image),
SelectedCursor::Image(image) => { }
let image = image.borrow();
let value = match image.deref().as_ref().unwrap() {
CursorImageState::Loading { previous, .. } => previous.style(),
CursorImageState::Failed(previous) => previous.style(),
CursorImageState::Ready { style, .. } => style,
};
return style.set("cursor", value);
}
};
style.set("cursor", value);
} }
} }
#[derive(Debug)] #[derive(Debug)]
pub enum Previous { pub enum Previous {
Named(CursorIcon), Named(CursorIcon),
Url { Image(Rc<Image>),
style: String,
url: String,
hotspot_x: u16,
hotspot_y: u16,
},
Image {
style: String,
image: WebCursorImage,
},
} }
impl Previous { impl Previous {
fn style(&self) -> &str { fn style(&self) -> &str {
match self { match self {
Previous::Named(icon) => icon.name(), Previous::Named(icon) => icon.name(),
Previous::Url { style: url, .. } => url, Previous::Image(image) => &image.style,
Previous::Image { style, .. } => style,
}
}
fn style_with_url(&self, new_url: &str, new_hotspot_x: u16, new_hotspot_y: u16) -> String {
match self {
Previous::Named(icon) => format!("url({new_url}) {new_hotspot_x} {new_hotspot_y}, {}", icon.name()),
Previous::Url {
url,
hotspot_x,
hotspot_y,
..
}
| Previous::Image {
image:
WebCursorImage {
data_url: url,
hotspot_x,
hotspot_y,
..
},
..
} => format!(
"url({new_url}) {new_hotspot_x} {new_hotspot_y}, url({url}) {hotspot_x} {hotspot_y}, auto",
),
} }
} }
} }
@ -172,66 +227,34 @@ impl From<SelectedCursor> for Previous {
fn from(value: SelectedCursor) -> Self { fn from(value: SelectedCursor) -> Self {
match value { match value {
SelectedCursor::Named(icon) => Self::Named(icon), SelectedCursor::Named(icon) => Self::Named(icon),
SelectedCursor::Url { SelectedCursor::ImageLoading { previous, .. } => previous,
style, SelectedCursor::ImageReady(image) => Self::Image(image),
url,
hotspot_x,
hotspot_y,
..
} => Self::Url {
style,
url,
hotspot_x,
hotspot_y,
},
SelectedCursor::Image(image) => {
match Rc::try_unwrap(image).unwrap().into_inner().unwrap() {
CursorImageState::Loading { previous, .. } => previous,
CursorImageState::Failed(previous) => previous,
CursorImageState::Ready {
style,
image: current,
..
} => Self::Image {
style,
image: current,
},
}
}
} }
} }
} }
#[derive(Debug)] #[derive(Debug)]
pub enum CursorImageState { enum ImageState {
Loading { Loading(Option<rc::Weak<RefCell<State>>>),
style: Style, Failed,
cursor_visible: Rc<Cell<bool>>, Ready(Rc<Image>),
previous: Previous,
hotspot_x: u16,
hotspot_y: u16,
},
Failed(Previous),
Ready {
style: String,
image: WebCursorImage,
previous: Previous,
},
} }
impl CursorImageState { impl ImageState {
fn from_image( fn from_rgba(window: &Window, document: Document, image: &CursorImage) -> Arc<CustomCursor> {
window: &Window, // 1. Create an `ImageData` from the RGBA data.
document: Document, // 2. Create an `ImageBitmap` from the `ImageData`.
style: Style, // 3. Draw `ImageBitmap` on an `HTMLCanvasElement`.
image: &CursorImage, // 4. Create a `Blob` from the `HTMLCanvasElement`.
previous: Previous, // 5. Create an object URL from the `Blob`.
cursor_visible: Rc<Cell<bool>>, // 6. Decode the image on an `HTMLImageElement` from the URL.
) -> Rc<RefCell<Option<Self>>> { // 7. Change the `CursorState` if queued.
// Can't create array directly when backed by SharedArrayBuffer.
// 1. Create an `ImageData` from the RGBA data.
// Adapted from https://github.com/rust-windowing/softbuffer/blob/ab7688e2ed2e2eca51b3c4e1863a5bd7fe85800e/src/web.rs#L196-L223 // Adapted from https://github.com/rust-windowing/softbuffer/blob/ab7688e2ed2e2eca51b3c4e1863a5bd7fe85800e/src/web.rs#L196-L223
#[cfg(target_feature = "atomics")] #[cfg(target_feature = "atomics")]
let image_data = { // Can't share `SharedArrayBuffer` with `ImageData`.
let result = {
use js_sys::{Uint8Array, Uint8ClampedArray}; use js_sys::{Uint8Array, Uint8ClampedArray};
use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue; use wasm_bindgen::JsValue;
@ -250,115 +273,283 @@ impl CursorImageState {
ImageDataExt::new(array, image.width as u32) ImageDataExt::new(array, image.width as u32)
.map(JsValue::from) .map(JsValue::from)
.map(ImageData::unchecked_from_js) .map(ImageData::unchecked_from_js)
.unwrap()
}; };
#[cfg(not(target_feature = "atomics"))] #[cfg(not(target_feature = "atomics"))]
let image_data = ImageData::new_with_u8_clamped_array( let result = ImageData::new_with_u8_clamped_array(
wasm_bindgen::Clamped(&image.rgba), wasm_bindgen::Clamped(&image.rgba),
image.width as u32, image.width as u32,
) );
.unwrap(); let image_data = result.expect("found wrong image size");
// 2. Create an `ImageBitmap` from the `ImageData`.
//
// We call `createImageBitmap()` before spawning the future,
// to not have to clone the image buffer.
let mut options = ImageBitmapOptions::new(); let mut options = ImageBitmapOptions::new();
options.premultiply_alpha(PremultiplyAlpha::None); options.premultiply_alpha(PremultiplyAlpha::None);
let bitmap = JsFuture::from( let bitmap = JsFuture::from(
window window
.create_image_bitmap_with_image_data_and_image_bitmap_options(&image_data, &options) .create_image_bitmap_with_image_data_and_image_bitmap_options(&image_data, &options)
.unwrap(), .expect("unexpected exception in `createImageBitmap()`"),
); );
let state = Rc::new(RefCell::new(Some(Self::Loading { let this = CustomCursor::new();
style,
cursor_visible,
previous,
hotspot_x: image.hotspot_x,
hotspot_y: image.hotspot_y,
})));
wasm_bindgen_futures::spawn_local({ wasm_bindgen_futures::spawn_local({
let weak = Rc::downgrade(&state); let weak = Arc::downgrade(&this);
let CursorImage { width, height, .. } = *image; let CursorImage {
width,
height,
hotspot_x,
hotspot_y,
..
} = *image;
async move { async move {
// Keep checking if all references are dropped between every `await` call.
if weak.strong_count() == 0 { if weak.strong_count() == 0 {
return; return;
} }
let bitmap: ImageBitmap = bitmap.await.unwrap().unchecked_into(); let bitmap: ImageBitmap = bitmap
.await
.expect("found invalid state in `ImageData`")
.unchecked_into();
if weak.strong_count() == 0 { if weak.strong_count() == 0 {
return; return;
} }
let canvas: HtmlCanvasElement = let canvas: HtmlCanvasElement = document
document.create_element("canvas").unwrap().unchecked_into(); .create_element("canvas")
.expect("invalid tag name")
.unchecked_into();
#[allow(clippy::disallowed_methods)] #[allow(clippy::disallowed_methods)]
canvas.set_width(width as u32); canvas.set_width(width as u32);
#[allow(clippy::disallowed_methods)] #[allow(clippy::disallowed_methods)]
canvas.set_height(height as u32); canvas.set_height(height as u32);
// 3. Draw `ImageBitmap` on an `HTMLCanvasElement`.
let context: ImageBitmapRenderingContext = canvas let context: ImageBitmapRenderingContext = canvas
.get_context("bitmaprenderer") .get_context("bitmaprenderer")
.unwrap() .expect("unexpected exception in `HTMLCanvasElement.getContext()`")
.unwrap() .expect("`bitmaprenderer` context unsupported")
.unchecked_into(); .unchecked_into();
context.transfer_from_image_bitmap(&bitmap); context.transfer_from_image_bitmap(&bitmap);
thread_local! { // 4. Create a `Blob` from the `HTMLCanvasElement`.
static CURRENT_STATE: RefCell<Option<Weak<RefCell<Option<CursorImageState>>>>> = RefCell::new(None); //
// `HTMLCanvasElement.toBlob()` can't be interrupted. So we have to use a // To keep the `Closure` alive until `HTMLCanvasElement.toBlob()` is done,
// `Closure` that doesn't need to be garbage-collected. // we do the whole `Waker` strategy. Commonly on `Drop` the callback is aborted,
static CALLBACK: Closure<dyn Fn(Option<Blob>)> = Closure::new(|blob| { // but it would increase complexity and isn't possible in this case.
CURRENT_STATE.with(|weak| { // Keep in mind that `HTMLCanvasElement.toBlob()` can call the callback immediately.
let Some(state) = weak.borrow_mut().take().and_then(|weak| weak.upgrade()) else { let value = Rc::new(RefCell::new(None));
return; let waker = Rc::new(RefCell::<Option<Waker>>::new(None));
}; let callback = Closure::once({
let value = value.clone();
let waker = waker.clone();
move |blob: Option<Blob>| {
*value.borrow_mut() = Some(blob);
if let Some(waker) = waker.borrow_mut().take() {
waker.wake();
}
}
});
canvas
.to_blob(callback.as_ref().unchecked_ref())
.expect("failed with `SecurityError` despite only source coming from memory");
let blob = future::poll_fn(|cx| {
if let Some(blob) = value.borrow_mut().take() {
Poll::Ready(blob)
} else {
*waker.borrow_mut() = Some(cx.waker().clone());
Poll::Pending
}
})
.await;
let url = {
let Some(this) = weak.upgrade() else {
return;
};
let mut this = this.get().borrow_mut();
let Some(blob) = blob else {
log::error!("creating custom cursor failed");
let ImageState::Loading(state) = this.deref_mut() else {
unreachable!("found invalid state");
};
let state = state.take();
*this = ImageState::Failed;
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
// Extract old state. let SelectedCursor::ImageLoading { previous, .. } =
let CursorImageState::Loading { style, cursor_visible, previous, hotspot_x, hotspot_y, .. } = state.take().unwrap() else { mem::take(&mut state.cursor)
unreachable!("found invalid state") else {
unreachable!("found invalid state");
}; };
state.cursor = previous.into();
}
let Some(blob) = blob else { return;
*state = Some(CursorImageState::Failed(previous)); };
return;
};
let data_url = Url::create_object_url_with_blob(&blob).unwrap();
let value = previous.style_with_url(&data_url, hotspot_x, hotspot_y); // 5. Create an object URL from the `Blob`.
Url::create_object_url_with_blob(&blob)
.expect("unexpected exception in `URL.createObjectURL()`")
};
if cursor_visible.get() { Self::decode(weak, url, true, hotspot_x, hotspot_y).await;
style.set("cursor", &value);
}
*state = Some(
CursorImageState::Ready {
style: value,
image: WebCursorImage{ data_url, hotspot_x, hotspot_y },
previous,
});
});
});
}
CURRENT_STATE.with(|state| *state.borrow_mut() = Some(weak));
CALLBACK
.with(|callback| canvas.to_blob(callback.as_ref().unchecked_ref()).unwrap());
} }
}); });
state this
}
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Arc<CustomCursor> {
let this = CustomCursor::new();
wasm_bindgen_futures::spawn_local(Self::decode(
Arc::downgrade(&this),
url,
false,
hotspot_x,
hotspot_y,
));
this
}
async fn decode(
weak: sync::Weak<CustomCursor>,
url: String,
object: bool,
hotspot_x: u16,
hotspot_y: u16,
) {
if weak.strong_count() == 0 {
return;
}
// 6. Decode the image on an `HTMLImageElement` from the URL.
let image =
HtmlImageElement::new().expect("unexpected exception in `new HtmlImageElement`");
image.set_src(&url);
let result = JsFuture::from(image.decode()).await;
let Some(this) = weak.upgrade() else {
return;
};
let mut this = this.get().borrow_mut();
let ImageState::Loading(state) = this.deref_mut() else {
unreachable!("found invalid state");
};
let state = state.take();
if let Err(error) = result {
log::error!("creating custom cursor failed: {error:?}");
*this = ImageState::Failed;
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
let mut state = state.borrow_mut();
let SelectedCursor::ImageLoading { previous, .. } = mem::take(&mut state.cursor)
else {
unreachable!("found invalid state");
};
state.cursor = previous.into();
}
return;
}
let image = Image::new(url, object, image, hotspot_x, hotspot_y);
// 7. Change the `CursorState` if queued.
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
let mut state = state.borrow_mut();
state.cursor = SelectedCursor::ImageReady(image.clone());
state.set_style();
}
*this = ImageState::Ready(image);
} }
} }
#[derive(Debug)] #[derive(Debug)]
pub struct WebCursorImage { pub struct Image {
data_url: String, style: String,
hotspot_x: u16, url: String,
hotspot_y: u16, object: bool,
_image: HtmlImageElement,
} }
impl Drop for WebCursorImage { impl Drop for Image {
fn drop(&mut self) { fn drop(&mut self) {
Url::revoke_object_url(&self.data_url).unwrap(); if self.object {
Url::revoke_object_url(&self.url)
.expect("unexpected exception in `URL.revokeObjectURL()`");
}
} }
} }
impl Image {
fn new(
url: String,
object: bool,
image: HtmlImageElement,
hotspot_x: u16,
hotspot_y: u16,
) -> Rc<Self> {
let style = format!("url({url}) {hotspot_x} {hotspot_y}, auto");
Rc::new(Self {
style,
url,
object,
_image: image,
})
}
}
mod thread_safe {
use std::mem;
use std::thread::{self, ThreadId};
#[derive(Debug)]
pub struct ThreadSafe<T> {
origin_thread: ThreadId,
value: T,
}
impl<T> ThreadSafe<T> {
pub fn new(value: T) -> Self {
Self {
origin_thread: thread::current().id(),
value,
}
}
pub fn get(&self) -> &T {
if self.origin_thread == thread::current().id() {
&self.value
} else {
panic!("value not accessible outside its origin thread")
}
}
pub fn in_origin_thread(&self) -> bool {
self.origin_thread == thread::current().id()
}
}
impl<T> Drop for ThreadSafe<T> {
fn drop(&mut self) {
if mem::needs_drop::<T>() && self.origin_thread != thread::current().id() {
panic!("value can't be dropped outside its origin thread")
}
}
}
unsafe impl<T> Send for ThreadSafe<T> {}
unsafe impl<T> Sync for ThreadSafe<T> {}
}

View file

@ -40,4 +40,5 @@ pub use self::window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId
pub(crate) use self::keyboard::KeyEventExtra; pub(crate) use self::keyboard::KeyEventExtra;
pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen; pub(crate) use crate::platform_impl::Fullscreen;
pub(crate) use cursor::WebCustomCursor as PlatformCustomCursor; pub(crate) use cursor::CustomCursor as PlatformCustomCursor;
pub(crate) use cursor::CustomCursorBuilder as PlatformCustomCursorBuilder;

View file

@ -1,4 +1,3 @@
use crate::cursor::CustomCursor;
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size}; use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOE}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOE};
use crate::icon::Icon; use crate::icon::Icon;
@ -8,14 +7,16 @@ use crate::window::{
}; };
use crate::SendSyncWrapper; use crate::SendSyncWrapper;
use super::cursor::SelectedCursor; use super::cursor::CursorState;
use super::r#async::Dispatcher; use super::r#async::Dispatcher;
use super::PlatformCustomCursor;
use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen}; use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen};
use web_sys::HtmlCanvasElement; use web_sys::HtmlCanvasElement;
use std::cell::{Cell, RefCell}; use std::cell::RefCell;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc;
pub struct Window { pub struct Window {
inner: Dispatcher<Inner>, inner: Dispatcher<Inner>,
@ -25,8 +26,7 @@ pub struct Inner {
id: WindowId, id: WindowId,
pub window: web_sys::Window, pub window: web_sys::Window,
canvas: Rc<RefCell<backend::Canvas>>, canvas: Rc<RefCell<backend::Canvas>>,
selected_cursor: RefCell<SelectedCursor>, cursor: CursorState,
cursor_visible: Rc<Cell<bool>>,
destroy_fn: Option<Box<dyn FnOnce()>>, destroy_fn: Option<Box<dyn FnOnce()>>,
} }
@ -45,6 +45,7 @@ impl Window {
let canvas = let canvas =
backend::Canvas::create(id, window.clone(), document.clone(), &attr, platform_attr)?; backend::Canvas::create(id, window.clone(), document.clone(), &attr, platform_attr)?;
let canvas = Rc::new(RefCell::new(canvas)); let canvas = Rc::new(RefCell::new(canvas));
let cursor = CursorState::new(canvas.borrow().style().clone());
target.register(&canvas, id, prevent_default); target.register(&canvas, id, prevent_default);
@ -55,8 +56,7 @@ impl Window {
id, id,
window: window.clone(), window: window.clone(),
canvas, canvas,
selected_cursor: Default::default(), cursor,
cursor_visible: Rc::new(Cell::new(true)),
destroy_fn: Some(destroy_fn), destroy_fn: Some(destroy_fn),
}; };
@ -198,24 +198,12 @@ impl Inner {
#[inline] #[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) { pub fn set_cursor_icon(&self, cursor: CursorIcon) {
*self.selected_cursor.borrow_mut() = SelectedCursor::Named(cursor); self.cursor.set_cursor_icon(cursor)
if self.cursor_visible.get() {
self.canvas.borrow().style().set("cursor", cursor.name());
}
} }
#[inline] #[inline]
pub fn set_custom_cursor(&self, cursor: CustomCursor) { pub(crate) fn set_custom_cursor(&self, cursor: Arc<PlatformCustomCursor>) {
let canvas = self.canvas.borrow(); self.cursor.set_custom_cursor(cursor)
let new_cursor = cursor.inner.build(
canvas.window(),
canvas.document(),
canvas.style(),
self.selected_cursor.take(),
self.cursor_visible.clone(),
);
*self.selected_cursor.borrow_mut() = new_cursor;
} }
#[inline] #[inline]
@ -241,15 +229,7 @@ impl Inner {
#[inline] #[inline]
pub fn set_cursor_visible(&self, visible: bool) { pub fn set_cursor_visible(&self, visible: bool) {
if !visible && self.cursor_visible.get() { self.cursor.set_cursor_visible(visible)
self.canvas.borrow().style().set("cursor", "none");
self.cursor_visible.set(false);
} else if visible && !self.cursor_visible.get() {
self.selected_cursor
.borrow()
.set_style(self.canvas.borrow().style());
self.cursor_visible.set(true);
}
} }
#[inline] #[inline]

View file

@ -17,6 +17,7 @@ pub(crate) use self::{
pub use self::icon::WinIcon as PlatformIcon; pub use self::icon::WinIcon as PlatformIcon;
pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor; pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor;
pub(crate) use crate::cursor::CursorImage as PlatformCustomCursorBuilder;
use crate::platform_impl::Fullscreen; use crate::platform_impl::Fullscreen;
use crate::event::DeviceId as RootDeviceId; use crate::event::DeviceId as RootDeviceId;

View file

@ -55,7 +55,6 @@ use windows_sys::Win32::{
}; };
use crate::{ use crate::{
cursor::CustomCursor,
dpi::{PhysicalPosition, PhysicalSize, Position, Size}, dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError}, error::{ExternalError, NotSupportedError, OsError as RootOsError},
icon::Icon, icon::Icon,
@ -73,7 +72,8 @@ use crate::{
monitor::{self, MonitorHandle}, monitor::{self, MonitorHandle},
util, util,
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
Fullscreen, PlatformSpecificWindowBuilderAttributes, SelectedCursor, WindowId, Fullscreen, PlatformCustomCursor, PlatformSpecificWindowBuilderAttributes, SelectedCursor,
WindowId,
}, },
window::{ window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
@ -405,8 +405,8 @@ impl Window {
} }
#[inline] #[inline]
pub fn set_custom_cursor(&self, cursor: CustomCursor) { pub(crate) fn set_custom_cursor(&self, cursor: Arc<PlatformCustomCursor>) {
let new_cursor = match WinCursor::new(&cursor.inner) { let new_cursor = match WinCursor::new(&cursor) {
Ok(cursor) => cursor, Ok(cursor) => cursor,
Err(err) => { Err(err) => {
warn!("Failed to create custom cursor: {err}"); warn!("Failed to create custom cursor: {err}");

View file

@ -1,5 +1,5 @@
//! The [`Window`] struct and associated types. //! The [`Window`] struct and associated types.
use std::fmt; use std::{fmt, sync::Arc};
use crate::{ use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size}, dpi::{PhysicalPosition, PhysicalSize, Position, Size},
@ -9,7 +9,7 @@ use crate::{
platform_impl, SendSyncWrapper, platform_impl, SendSyncWrapper,
}; };
pub use crate::cursor::{BadImage, CustomCursor, MAX_CURSOR_SIZE}; pub use crate::cursor::{BadImage, CustomCursor, CustomCursorBuilder, MAX_CURSOR_SIZE};
pub use crate::icon::{BadIcon, Icon}; pub use crate::icon::{BadIcon, Icon};
#[doc(inline)] #[doc(inline)]
@ -1355,7 +1355,7 @@ impl Window {
/// - **iOS / Android / Orbital:** Unsupported. /// - **iOS / Android / Orbital:** Unsupported.
#[inline] #[inline]
pub fn set_custom_cursor(&self, cursor: &CustomCursor) { pub fn set_custom_cursor(&self, cursor: &CustomCursor) {
let cursor = cursor.clone(); let cursor = Arc::clone(&cursor.inner);
self.window self.window
.maybe_queue_on_main(move |w| w.set_custom_cursor(cursor)) .maybe_queue_on_main(move |w| w.set_custom_cursor(cursor))
} }

View file

@ -31,5 +31,6 @@ fn ids_send() {
#[test] #[test]
fn custom_cursor_send() { fn custom_cursor_send() {
needs_send::<winit::window::CustomCursorBuilder>();
needs_send::<winit::window::CustomCursor>(); needs_send::<winit::window::CustomCursor>();
} }

View file

@ -14,5 +14,6 @@ fn window_builder_sync() {
#[test] #[test]
fn custom_cursor_sync() { fn custom_cursor_sync() {
needs_sync::<winit::window::CustomCursorBuilder>();
needs_sync::<winit::window::CustomCursor>(); needs_sync::<winit::window::CustomCursor>();
} }