diff --git a/CHANGELOG.md b/CHANGELOG.md index eda12502b0..0c3c548b2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,10 +25,10 @@ Unreleased` header. - On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example. - **Breaking:** Remove `Window::set_cursor_icon` - Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a `CursorIcon` or `CustomCursor` - - Add `CustomCursor` - Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data. - Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs. - Add `CustomCursorExtWebSys::from_animation` to allow creating animated cursors from other `CustomCursor`s. + - Add `{Active,}EventLoop::create_custom_cursor` to load custom cursor image sources. - On macOS, add services menu. - **Breaking:** On Web, remove queuing fullscreen request in absence of transient activation. - On Web, fix setting cursor icon overriding cursor visibility. diff --git a/examples/window.rs b/examples/window.rs index a539ee95c5..3aa7f99096 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -20,7 +20,8 @@ use winit::event::{MouseButton, MouseScrollDelta}; use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::keyboard::{Key, ModifiersState}; use winit::window::{ - Cursor, CursorGrabMode, CustomCursor, Fullscreen, Icon, ResizeDirection, Theme, + Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, Icon, ResizeDirection, + Theme, }; use winit::window::{Window, WindowId}; @@ -57,7 +58,6 @@ fn main() -> Result<(), Box> { Event::Resumed => { println!("Resumed the event loop"); state.dump_monitors(event_loop); - state.load_custom_cursors(event_loop); // Create initial window. state @@ -108,11 +108,11 @@ struct Application { } impl Application { - fn new(_event_loop: &EventLoop) -> Self { + fn new(event_loop: &EventLoop) -> Self { // SAFETY: the context is dropped inside the loop, since the state we're using // is moved inside the closure. #[cfg(not(any(android_platform, ios_platform)))] - let context = unsafe { Context::from_raw(_event_loop.raw_display_handle()).unwrap() }; + let context = unsafe { Context::from_raw(event_loop.raw_display_handle()).unwrap() }; // You'll have to choose an icon size at your own discretion. On X11, the desired size varies // by WM, and on Windows, you still have to account for screen scaling. Here we use 32px, @@ -122,12 +122,19 @@ impl Application { let icon = load_icon(Path::new(path)); + println!("Loading cursor assets"); + let custom_cursors = vec![ + event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/cross.png"))), + event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/cross2.png"))), + event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/gradient.png"))), + ]; + Self { #[cfg(not(any(android_platform, ios_platform)))] context, + custom_cursors, icon, windows: Default::default(), - custom_cursors: Vec::new(), } } @@ -411,15 +418,6 @@ impl Application { } } - fn load_custom_cursors(&mut self, event_loop: &ActiveEventLoop) { - println!("Loading cursor assets"); - self.custom_cursors = vec![ - decode_cursor(include_bytes!("data/cross.png"), event_loop), - decode_cursor(include_bytes!("data/cross2.png"), event_loop), - decode_cursor(include_bytes!("data/gradient.png"), event_loop), - ]; - } - /// Process the key binding. fn process_key_binding(key: &str, mods: &ModifiersState) -> Option { KEY_BINDINGS.iter().find_map(|binding| { @@ -852,14 +850,12 @@ impl fmt::Display for Action { } } -fn decode_cursor(bytes: &[u8], window_target: &ActiveEventLoop) -> CustomCursor { +fn decode_cursor(bytes: &[u8]) -> CustomCursorSource { 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); - let builder = CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap(); - - builder.build(window_target) + CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap() } fn load_icon(path: &Path) -> Icon { diff --git a/src/cursor.rs b/src/cursor.rs index 2e8da938a9..67f6ab1bb4 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -5,8 +5,7 @@ use std::{error::Error, hash::Hash}; use cursor_icon::CursorIcon; -use crate::event_loop::ActiveEventLoop; -use crate::platform_impl::{self, PlatformCustomCursor, PlatformCustomCursorBuilder}; +use crate::platform_impl::{PlatformCustomCursor, PlatformCustomCursorSource}; /// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`]. pub const MAX_CURSOR_SIZE: u16 = 2048; @@ -59,15 +58,15 @@ impl From for Cursor { /// let rgba = vec![255; (w * h * 4) as usize]; /// /// #[cfg(not(target_family = "wasm"))] -/// let builder = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap(); +/// let source = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap(); /// /// #[cfg(target_family = "wasm")] -/// let builder = { +/// let source = { /// use winit::platform::web::CustomCursorExtWebSys; /// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0) /// }; /// -/// let custom_cursor = builder.build(&event_loop); +/// let custom_cursor = event_loop.create_custom_cursor(source); /// /// window.set_cursor(custom_cursor.clone()); /// # } @@ -88,9 +87,9 @@ impl CustomCursor { height: u16, hotspot_x: u16, hotspot_y: u16, - ) -> Result { - Ok(CustomCursorBuilder { - inner: PlatformCustomCursorBuilder::from_rgba( + ) -> Result { + Ok(CustomCursorSource { + inner: PlatformCustomCursorSource::from_rgba( rgba.into(), width, height, @@ -101,20 +100,12 @@ impl CustomCursor { } } -/// Builds a [`CustomCursor`]. +/// Source for [`CustomCursor`]. /// /// See [`CustomCursor`] for more details. #[derive(Debug)] -pub struct CustomCursorBuilder { - pub(crate) inner: PlatformCustomCursorBuilder, -} - -impl CustomCursorBuilder { - pub fn build(self, window_target: &ActiveEventLoop) -> CustomCursor { - CustomCursor { - inner: PlatformCustomCursor::build(self.inner, &window_target.p), - } - } +pub struct CustomCursorSource { + pub(crate) inner: PlatformCustomCursorSource, } /// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments. @@ -175,12 +166,14 @@ impl fmt::Display for BadImage { impl Error for BadImage {} -/// Platforms export this directly as `PlatformCustomCursorBuilder` if they need to only work with images. +/// Platforms export this directly as `PlatformCustomCursorSource` if they need to only work with +/// images. +#[allow(dead_code)] #[derive(Debug)] -pub(crate) struct OnlyCursorImageBuilder(pub(crate) CursorImage); +pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage); #[allow(dead_code)] -impl OnlyCursorImageBuilder { +impl OnlyCursorImageSource { pub(crate) fn from_rgba( rgba: Vec, width: u16, @@ -210,16 +203,6 @@ impl PartialEq for OnlyCursorImage { impl Eq for OnlyCursorImage {} -#[allow(dead_code)] -impl OnlyCursorImage { - pub(crate) fn build( - builder: OnlyCursorImageBuilder, - _: &platform_impl::ActiveEventLoop, - ) -> Self { - Self(Arc::new(builder.0)) - } -} - #[derive(Debug)] #[allow(dead_code)] pub(crate) struct CursorImage { @@ -294,8 +277,4 @@ impl NoCustomCursor { CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?; Ok(Self) } - - fn build(self, _: &platform_impl::ActiveEventLoop) -> NoCustomCursor { - self - } } diff --git a/src/event_loop.rs b/src/event_loop.rs index 45092beebd..a0cd9343f5 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -19,7 +19,7 @@ use std::time::{Duration, Instant}; use web_time::{Duration, Instant}; use crate::error::{EventLoopError, OsError}; -use crate::window::{Window, WindowAttributes}; +use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes}; use crate::{event::Event, monitor::MonitorHandle, platform_impl}; /// Provides a way to retrieve events from the system and from the windows that were registered to @@ -299,6 +299,14 @@ impl EventLoop { platform_impl::Window::new(&self.event_loop.window_target().p, window_attributes)?; Ok(Window { window }) } + + /// Create custom cursor. + pub fn create_custom_cursor(&self, custom_cursor: CustomCursorSource) -> CustomCursor { + self.event_loop + .window_target() + .p + .create_custom_cursor(custom_cursor) + } } #[cfg(feature = "rwh_06")] @@ -359,6 +367,11 @@ impl ActiveEventLoop { Ok(Window { window }) } + /// Create custom cursor. + pub fn create_custom_cursor(&self, custom_cursor: CustomCursorSource) -> CustomCursor { + self.p.create_custom_cursor(custom_cursor) + } + /// Returns the list of all the monitors available on the system. #[inline] pub fn available_monitors(&self) -> impl Iterator { diff --git a/src/platform/web.rs b/src/platform/web.rs index 56a69ffcd0..ed8071f9f7 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -37,12 +37,12 @@ use std::time::Duration; #[cfg(web_platform)] use web_sys::HtmlCanvasElement; -use crate::cursor::CustomCursorBuilder; +use crate::cursor::CustomCursorSource; use crate::event::Event; use crate::event_loop::{ActiveEventLoop, EventLoop}; #[cfg(web_platform)] use crate::platform_impl::CustomCursorFuture as PlatformCustomCursorFuture; -use crate::platform_impl::{PlatformCustomCursor, PlatformCustomCursorBuilder}; +use crate::platform_impl::PlatformCustomCursorSource; use crate::window::{CustomCursor, Window, WindowAttributes}; #[cfg(not(web_platform))] @@ -200,9 +200,18 @@ pub trait ActiveEventLoopExtWebSys { /// /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll fn poll_strategy(&self) -> PollStrategy; + + /// Async version of [`ActiveEventLoop::create_custom_cursor()`] which waits until the + /// cursor has completely finished loading. + fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture; } impl ActiveEventLoopExtWebSys for ActiveEventLoop { + #[inline] + fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture { + self.p.create_custom_cursor_async(source) + } + #[inline] fn set_poll_strategy(&self, strategy: PollStrategy) { self.p.set_poll_strategy(strategy); @@ -249,14 +258,14 @@ 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) -> CustomCursorBuilder; + 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, - ) -> Result; + ) -> Result; } impl CustomCursorExtWebSys for CustomCursor { @@ -264,9 +273,9 @@ impl CustomCursorExtWebSys for CustomCursor { self.inner.animation } - fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder { - CustomCursorBuilder { - inner: PlatformCustomCursorBuilder::Url { + fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource { + CustomCursorSource { + inner: PlatformCustomCursorSource::Url { url, hotspot_x, hotspot_y, @@ -277,7 +286,7 @@ impl CustomCursorExtWebSys for CustomCursor { fn from_animation( duration: Duration, cursors: Vec, - ) -> Result { + ) -> Result { if cursors.is_empty() { return Err(BadAnimation::Empty); } @@ -286,8 +295,8 @@ impl CustomCursorExtWebSys for CustomCursor { return Err(BadAnimation::Animation); } - Ok(CustomCursorBuilder { - inner: PlatformCustomCursorBuilder::Animation { duration, cursors }, + Ok(CustomCursorSource { + inner: PlatformCustomCursorSource::Animation { duration, cursors }, }) } } @@ -312,26 +321,11 @@ impl fmt::Display for BadAnimation { impl Error for BadAnimation {} -pub trait CustomCursorBuilderExtWebSys { - /// Async version of [`CustomCursorBuilder::build()`] which waits until the - /// cursor has completely finished loading. - fn build_async(self, window_target: &ActiveEventLoop) -> CustomCursorFuture; -} - -impl CustomCursorBuilderExtWebSys for CustomCursorBuilder { - fn build_async(self, window_target: &ActiveEventLoop) -> CustomCursorFuture { - CustomCursorFuture(PlatformCustomCursor::build_async( - self.inner, - &window_target.p, - )) - } -} - #[cfg(not(web_platform))] struct PlatformCustomCursorFuture; #[derive(Debug)] -pub struct CustomCursorFuture(PlatformCustomCursorFuture); +pub struct CustomCursorFuture(pub(crate) PlatformCustomCursorFuture); impl Future for CustomCursorFuture { type Output = Result; diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 59b5694ec4..f974c1d251 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -27,13 +27,18 @@ use crate::{ event_loop::{self, ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents}, platform::pump_events::PumpStatus, window::{ - self, CursorGrabMode, ImePurpose, ResizeDirection, Theme, WindowButtons, WindowLevel, + self, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose, ResizeDirection, Theme, + WindowButtons, WindowLevel, }, }; use crate::{error::EventLoopError, platform_impl::Fullscreen}; mod keycodes; +pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor; +pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorSource; +pub(crate) use crate::icon::NoIcon as PlatformIcon; + static HAS_FOCUS: Lazy> = Lazy::new(|| RwLock::new(true)); /// Returns the minimum `Option`, taking into account that `None` @@ -673,6 +678,13 @@ impl ActiveEventLoop { Some(MonitorHandle::new(self.app.clone())) } + pub fn create_custom_cursor(&self, source: CustomCursorSource) -> CustomCursor { + let _ = source.inner; + CustomCursor { + inner: PlatformCustomCursor, + } + } + pub fn available_monitors(&self) -> VecDeque { let mut v = VecDeque::with_capacity(1); v.push_back(MonitorHandle::new(self.app.clone())); @@ -1054,10 +1066,6 @@ 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)] pub struct MonitorHandle { app: AndroidApp, diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index eabeb0ae4f..da3a918104 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -24,6 +24,7 @@ use crate::{ }, platform::ios::Idiom, platform_impl::platform::app_state::{EventLoopHandler, HandlePendingUserEvents}, + window::{CustomCursor, CustomCursorSource}, }; use super::{app_state, monitor, view, MonitorHandle}; @@ -38,6 +39,13 @@ pub struct ActiveEventLoop { } impl ActiveEventLoop { + pub fn create_custom_cursor(&self, source: CustomCursorSource) -> CustomCursor { + let _ = source.inner; + CustomCursor { + inner: super::PlatformCustomCursor, + } + } + pub fn available_monitors(&self) -> VecDeque { monitor::uiscreens(self.mtm) } diff --git a/src/platform_impl/ios/mod.rs b/src/platform_impl/ios/mod.rs index 9550d0a5c8..1a38df0e33 100644 --- a/src/platform_impl/ios/mod.rs +++ b/src/platform_impl/ios/mod.rs @@ -79,7 +79,7 @@ pub(crate) use self::{ window::{PlatformSpecificWindowAttributes, Window, WindowId}, }; pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor; -pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder; +pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorSource; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index e38a088c72..becaadd2c7 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -18,6 +18,7 @@ use smol_str::SmolStr; use self::x11::{X11Error, XConnection, XError, XNotSupported}; #[cfg(x11_platform)] use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook}; +use crate::window::{CustomCursor, CustomCursorSource}; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError}, @@ -34,7 +35,7 @@ use crate::{ }; pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey}; -pub(crate) use crate::cursor::OnlyCursorImageBuilder as PlatformCustomCursorBuilder; +pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource; pub(crate) use crate::icon::RgbaIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; @@ -643,19 +644,6 @@ pub(crate) enum PlatformCustomCursor { #[cfg(x11_platform)] X(x11::CustomCursor), } -impl PlatformCustomCursor { - pub(crate) fn build( - builder: PlatformCustomCursorBuilder, - p: &ActiveEventLoop, - ) -> PlatformCustomCursor { - match p { - #[cfg(wayland_platform)] - ActiveEventLoop::Wayland(_) => Self::Wayland(wayland::CustomCursor::build(builder, p)), - #[cfg(x11_platform)] - ActiveEventLoop::X(p) => Self::X(x11::CustomCursor::build(builder, p)), - } - } -} /// Hooks for X11 errors. #[cfg(x11_platform)] @@ -867,6 +855,10 @@ impl ActiveEventLoop { } } + pub fn create_custom_cursor(&self, cursor: CustomCursorSource) -> CustomCursor { + x11_or_wayland!(match self; ActiveEventLoop(evlp) => evlp.create_custom_cursor(cursor)) + } + #[inline] pub fn available_monitors(&self) -> VecDeque { match *self { diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index 0842b4c478..ebfdde4b52 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -15,13 +15,17 @@ use sctk::reexports::calloop_wayland_source::WaylandSource; use sctk::reexports::client::globals; use sctk::reexports::client::{Connection, QueueHandle}; +use crate::cursor::OnlyCursorImage; use crate::dpi::LogicalSize; use crate::error::{EventLoopError, OsError as RootOsError}; use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent}; use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents}; use crate::platform::pump_events::PumpStatus; use crate::platform_impl::platform::min_timeout; -use crate::platform_impl::{ActiveEventLoop as PlatformActiveEventLoop, OsError}; +use crate::platform_impl::{ + ActiveEventLoop as PlatformActiveEventLoop, OsError, PlatformCustomCursor, +}; +use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource}; mod proxy; pub mod sink; @@ -686,6 +690,12 @@ impl ActiveEventLoop { #[inline] pub fn listen_device_events(&self, _allowed: DeviceEvents) {} + pub(crate) fn create_custom_cursor(&self, cursor: CustomCursorSource) -> RootCustomCursor { + RootCustomCursor { + inner: PlatformCustomCursor::Wayland(OnlyCursorImage(Arc::from(cursor.inner.0))), + } + } + #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 1dfea98d8e..6abd6e4707 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -28,16 +28,16 @@ use x11rb::protocol::xproto::{self, ConnectionExt as _}; use x11rb::x11_utils::X11Error as LogicalError; use x11rb::xcb_ffi::ReplyOrIdError; -use super::{ControlFlow, OsError}; -use crate::{ - error::{EventLoopError, OsError as RootOsError}, - event::{Event, StartCause, WindowEvent}, - event_loop::{ActiveEventLoop as RootAEL, DeviceEvents, EventLoopClosed}, - platform::pump_events::PumpStatus, - platform_impl::common::xkb::Context, - platform_impl::platform::{min_timeout, WindowId}, - window::WindowAttributes, +use crate::error::{EventLoopError, OsError as RootOsError}; +use crate::event::{Event, StartCause, WindowEvent}; +use crate::event_loop::{ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents, EventLoopClosed}; +use crate::platform::pump_events::PumpStatus; +use crate::platform_impl::common::xkb::Context; +use crate::platform_impl::platform::{min_timeout, WindowId}; +use crate::platform_impl::{ + ActiveEventLoop as PlatformActiveEventLoop, OsError, PlatformCustomCursor, }; +use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, WindowAttributes}; mod activation; mod atoms; @@ -310,7 +310,7 @@ impl EventLoop { window_target.update_listen_device_events(true); let root_window_target = RootAEL { - p: super::ActiveEventLoop::X(window_target), + p: PlatformActiveEventLoop::X(window_target), _marker: PhantomData, }; @@ -670,6 +670,12 @@ impl ActiveEventLoop { self.xconn.primary_monitor().ok() } + pub(crate) fn create_custom_cursor(&self, cursor: CustomCursorSource) -> RootCustomCursor { + RootCustomCursor { + inner: PlatformCustomCursor::X(CustomCursor::new(self, cursor.inner)), + } + } + pub fn listen_device_events(&self, allowed: DeviceEvents) { self.device_events.set(allowed); } diff --git a/src/platform_impl/linux/x11/util/cursor.rs b/src/platform_impl/linux/x11/util/cursor.rs index f85f6a7321..f86907a464 100644 --- a/src/platform_impl/linux/x11/util/cursor.rs +++ b/src/platform_impl/linux/x11/util/cursor.rs @@ -7,7 +7,7 @@ use std::{ use x11rb::connection::Connection; -use crate::{platform_impl::PlatformCustomCursorBuilder, window::CursorIcon}; +use crate::{platform_impl::PlatformCustomCursorSource, window::CursorIcon}; use super::super::ActiveEventLoop; use super::*; @@ -124,32 +124,36 @@ impl PartialEq for CustomCursor { impl Eq for CustomCursor {} impl CustomCursor { - pub(crate) fn build(builder: PlatformCustomCursorBuilder, p: &ActiveEventLoop) -> CustomCursor { + pub(crate) fn new( + event_loop: &ActiveEventLoop, + cursor: PlatformCustomCursorSource, + ) -> CustomCursor { unsafe { - let ximage = (p.xconn.xcursor.XcursorImageCreate)( - builder.0.width as i32, - builder.0.height as i32, + let ximage = (event_loop.xconn.xcursor.XcursorImageCreate)( + cursor.0.width as i32, + cursor.0.height as i32, ); if ximage.is_null() { panic!("failed to allocate cursor image"); } - (*ximage).xhot = builder.0.hotspot_x as u32; - (*ximage).yhot = builder.0.hotspot_y as u32; + (*ximage).xhot = cursor.0.hotspot_x as u32; + (*ximage).yhot = cursor.0.hotspot_y as u32; (*ximage).delay = 0; - let dst = slice::from_raw_parts_mut((*ximage).pixels, builder.0.rgba.len() / 4); - for (dst, chunk) in dst.iter_mut().zip(builder.0.rgba.chunks_exact(4)) { + let dst = slice::from_raw_parts_mut((*ximage).pixels, cursor.0.rgba.len() / 4); + for (dst, chunk) in dst.iter_mut().zip(cursor.0.rgba.chunks_exact(4)) { *dst = (chunk[0] as u32) << 16 | (chunk[1] as u32) << 8 | (chunk[2] as u32) | (chunk[3] as u32) << 24; } - let cursor = (p.xconn.xcursor.XcursorImageLoadCursor)(p.xconn.display, ximage); - (p.xconn.xcursor.XcursorImageDestroy)(ximage); + let cursor = + (event_loop.xconn.xcursor.XcursorImageLoadCursor)(event_loop.xconn.display, ximage); + (event_loop.xconn.xcursor.XcursorImageDestroy)(ximage); Self { inner: Arc::new(CustomCursorInner { - xconn: p.xconn.clone(), + xconn: event_loop.xconn.clone(), cursor, }), } diff --git a/src/platform_impl/macos/cursor.rs b/src/platform_impl/macos/cursor.rs index 3d26da1d19..a7fb54e5ca 100644 --- a/src/platform_impl/macos/cursor.rs +++ b/src/platform_impl/macos/cursor.rs @@ -10,9 +10,8 @@ use once_cell::sync::Lazy; use std::ffi::c_uchar; use std::slice; -use super::ActiveEventLoop; use crate::cursor::CursorImage; -use crate::cursor::OnlyCursorImageBuilder; +use crate::cursor::OnlyCursorImageSource; use crate::window::CursorIcon; #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -24,7 +23,7 @@ unsafe impl Send for CustomCursor {} unsafe impl Sync for CustomCursor {} impl CustomCursor { - pub(crate) fn build(cursor: OnlyCursorImageBuilder, _: &ActiveEventLoop) -> CustomCursor { + pub(crate) fn new(cursor: OnlyCursorImageSource) -> CustomCursor { Self(cursor_from_image(&cursor.0)) } } diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 8a7d648e66..984291a6a4 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -35,6 +35,8 @@ use super::{ monitor::{self, MonitorHandle}, observer::setup_control_flow_observers, }; +use crate::platform_impl::platform::cursor::CustomCursor; +use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource}; use crate::{ error::EventLoopError, event::Event, @@ -77,6 +79,12 @@ pub struct ActiveEventLoop { } impl ActiveEventLoop { + pub fn create_custom_cursor(&self, source: CustomCursorSource) -> RootCustomCursor { + RootCustomCursor { + inner: CustomCursor::new(source.inner), + } + } + #[inline] pub fn available_monitors(&self) -> VecDeque { monitor::available_monitors() diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 2eaa010047..6ad3083428 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -30,7 +30,7 @@ use crate::event::DeviceId as RootDeviceId; pub(crate) use self::cursor::CustomCursor as PlatformCustomCursor; pub(crate) use self::window::Window; -pub(crate) use crate::cursor::OnlyCursorImageBuilder as PlatformCustomCursorBuilder; +pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; diff --git a/src/platform_impl/orbital/event_loop.rs b/src/platform_impl/orbital/event_loop.rs index 4cd113ca01..e4956704cb 100644 --- a/src/platform_impl/orbital/event_loop.rs +++ b/src/platform_impl/orbital/event_loop.rs @@ -22,7 +22,7 @@ use crate::{ Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode, PhysicalKey, }, - window::WindowId as RootWindowId, + window::{CustomCursor as RootCustomCursor, CustomCursorSource, WindowId as RootWindowId}, }; use super::{ @@ -805,6 +805,13 @@ pub struct ActiveEventLoop { } impl ActiveEventLoop { + pub fn create_custom_cursor(&self, source: CustomCursorSource) -> RootCustomCursor { + let _ = source.inner; + RootCustomCursor { + inner: super::PlatformCustomCursor, + } + } + pub fn primary_monitor(&self) -> Option { Some(MonitorHandle) } diff --git a/src/platform_impl/orbital/mod.rs b/src/platform_impl/orbital/mod.rs index a4def5a0ef..c9be8b55ea 100644 --- a/src/platform_impl/orbital/mod.rs +++ b/src/platform_impl/orbital/mod.rs @@ -199,7 +199,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::cursor::NoCustomCursor as PlatformCustomCursorSource; pub(crate) use crate::icon::NoIcon as PlatformIcon; #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] diff --git a/src/platform_impl/web/cursor.rs b/src/platform_impl/web/cursor.rs index 79c1d61b20..a6063017e0 100644 --- a/src/platform_impl/web/cursor.rs +++ b/src/platform_impl/web/cursor.rs @@ -27,7 +27,7 @@ use crate::cursor::{BadImage, Cursor, CursorImage, CustomCursor as RootCustomCur use crate::platform::web::CustomCursorError; #[derive(Debug)] -pub(crate) enum CustomCursorBuilder { +pub(crate) enum CustomCursorSource { Image(CursorImage), Url { url: String, @@ -40,15 +40,15 @@ pub(crate) enum CustomCursorBuilder { }, } -impl CustomCursorBuilder { +impl CustomCursorSource { pub fn from_rgba( rgba: Vec, width: u16, height: u16, hotspot_x: u16, hotspot_y: u16, - ) -> Result { - Ok(CustomCursorBuilder::Image(CursorImage::from_rgba( + ) -> Result { + Ok(CustomCursorSource::Image(CursorImage::from_rgba( rgba, width, height, hotspot_x, hotspot_y, )?)) } @@ -75,30 +75,30 @@ impl PartialEq for CustomCursor { impl Eq for CustomCursor {} impl CustomCursor { - pub(crate) fn build(builder: CustomCursorBuilder, window_target: &ActiveEventLoop) -> Self { - match builder { - CustomCursorBuilder::Image(image) => Self::build_spawn( - window_target, + pub(crate) fn new(event_loop: &ActiveEventLoop, source: CustomCursorSource) -> Self { + match source { + CustomCursorSource::Image(image) => Self::build_spawn( + event_loop, from_rgba( - window_target.runner.window(), - window_target.runner.document().clone(), + event_loop.runner.window(), + event_loop.runner.document().clone(), &image, ), false, ), - CustomCursorBuilder::Url { + CustomCursorSource::Url { url, hotspot_x, hotspot_y, } => Self::build_spawn( - window_target, + event_loop, from_url(UrlType::Plain(url), hotspot_x, hotspot_y), false, ), - CustomCursorBuilder::Animation { duration, cursors } => Self::build_spawn( - window_target, + CustomCursorSource::Animation { duration, cursors } => Self::build_spawn( + event_loop, from_animation( - window_target.runner.main_thread(), + event_loop.runner.main_thread(), duration, cursors.into_iter().map(|cursor| cursor.inner), ), @@ -163,12 +163,12 @@ impl CustomCursor { this } - pub(crate) fn build_async( - builder: CustomCursorBuilder, - window_target: &ActiveEventLoop, + pub(crate) fn new_async( + event_loop: &ActiveEventLoop, + source: CustomCursorSource, ) -> CustomCursorFuture { - let CustomCursor { animation, state } = Self::build(builder, window_target); - let binding = state.get(window_target.runner.main_thread()).borrow(); + let CustomCursor { animation, state } = Self::new(event_loop, source); + let binding = state.get(event_loop.runner.main_thread()).borrow(); let ImageState::Loading { notifier, .. } = binding.deref() else { unreachable!("found invalid state") }; @@ -725,7 +725,7 @@ async fn from_animation( } ImageState::Failed(error) => return Err(error.clone()), ImageState::Image(_) => drop(state), - ImageState::Animation(_) => unreachable!("check in `CustomCursorBuilder` failed"), + ImageState::Animation(_) => unreachable!("check in `CustomCursorSource` failed"), } let state = cursor.state.get(main_thread).borrow(); diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index f5833278a1..3d23d9ce84 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -19,9 +19,13 @@ use crate::event::{ }; use crate::event_loop::{ControlFlow, DeviceEvents}; use crate::keyboard::ModifiersState; +use crate::platform::web::CustomCursorFuture; use crate::platform::web::PollStrategy; +use crate::platform_impl::platform::cursor::CustomCursor; use crate::platform_impl::platform::r#async::Waker; -use crate::window::{Theme, WindowId as RootWindowId}; +use crate::window::{ + CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId, +}; #[derive(Default)] struct ModifiersShared(Rc>); @@ -65,6 +69,16 @@ impl ActiveEventLoop { WindowId(self.runner.generate_id()) } + pub fn create_custom_cursor(&self, source: CustomCursorSource) -> RootCustomCursor { + RootCustomCursor { + inner: CustomCursor::new(self, source.inner), + } + } + + pub fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture { + CustomCursorFuture(CustomCursor::new_async(self, source.inner)) + } + pub fn register(&self, canvas: &Rc>, id: WindowId) { let canvas_clone = canvas.clone(); let mut canvas = canvas.borrow_mut(); diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index 7dd019399e..7098b866f2 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -43,5 +43,5 @@ pub(crate) use self::keyboard::KeyEventExtra; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; pub(crate) use cursor::CustomCursor as PlatformCustomCursor; -pub(crate) use cursor::CustomCursorBuilder as PlatformCustomCursorBuilder; pub(crate) use cursor::CustomCursorFuture; +pub(crate) use cursor::CustomCursorSource as PlatformCustomCursorSource; diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 3d47d91dd8..b496e920f9 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -81,6 +81,7 @@ use crate::{ dark_mode::try_theme, dpi::{become_dpi_aware, dpi_to_scale_factor}, drop_handler::FileDropHandler, + icon::WinCursor, ime::ImeContext, keyboard::KeyEventBuilder, keyboard_layout::LAYOUT_CACHE, @@ -90,7 +91,7 @@ use crate::{ window_state::{CursorFlags, ImeState, WindowFlags, WindowState}, wrap_device_id, Fullscreen, WindowId, DEVICE_ID, }, - window::WindowId as RootWindowId, + window::{CustomCursor as RootCustomCursor, CustomCursorSource, WindowId as RootWindowId}, }; use runner::{EventLoopRunner, EventLoopRunnerShared}; @@ -531,6 +532,18 @@ impl ActiveEventLoop { } } + pub fn create_custom_cursor(&self, source: CustomCursorSource) -> RootCustomCursor { + let inner = match WinCursor::new(&source.inner.0) { + Ok(cursor) => cursor, + Err(err) => { + log::warn!("Failed to create custom cursor: {err}"); + WinCursor::Failed + } + }; + + RootCustomCursor { inner } + } + // TODO: Investigate opportunities for caching pub fn available_monitors(&self) -> VecDeque { monitor::available_monitors() diff --git a/src/platform_impl/windows/icon.rs b/src/platform_impl/windows/icon.rs index de8bd4910f..c885efab63 100644 --- a/src/platform_impl/windows/icon.rs +++ b/src/platform_impl/windows/icon.rs @@ -17,12 +17,9 @@ use windows_sys::{ }; use crate::icon::*; -use crate::{ - cursor::{CursorImage, OnlyCursorImageBuilder}, - dpi::PhysicalSize, -}; +use crate::{cursor::CursorImage, dpi::PhysicalSize}; -use super::{util, ActiveEventLoop}; +use super::util; impl Pixel { fn convert_to_bgra(&mut self) { @@ -188,7 +185,7 @@ pub enum WinCursor { } impl WinCursor { - fn new(image: &CursorImage) -> Result { + pub(crate) fn new(image: &CursorImage) -> Result { let mut bgra = image.rgba.clone(); bgra.chunks_exact_mut(4).for_each(|chunk| chunk.swap(0, 2)); @@ -236,16 +233,6 @@ impl WinCursor { Ok(Self::Cursor(Arc::new(RaiiCursor { handle }))) } } - - pub(crate) fn build(cursor: OnlyCursorImageBuilder, _: &ActiveEventLoop) -> Self { - match Self::new(&cursor.0) { - Ok(cursor) => cursor, - Err(err) => { - log::warn!("Failed to create custom cursor: {err}"); - Self::Failed - } - } - } } #[derive(Debug, Hash, Eq, PartialEq)] diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 0e600de78d..8b5ad9d7bf 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -19,7 +19,7 @@ pub(crate) use self::{ pub(crate) use self::icon::WinCursor as PlatformCustomCursor; pub use self::icon::WinIcon as PlatformIcon; -pub(crate) use crate::cursor::OnlyCursorImageBuilder as PlatformCustomCursorBuilder; +pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource; use crate::platform_impl::Fullscreen; use crate::event::DeviceId as RootDeviceId; diff --git a/src/window.rs b/src/window.rs index e6cecac464..a8533ba374 100644 --- a/src/window.rs +++ b/src/window.rs @@ -8,7 +8,7 @@ use crate::{ platform_impl::{self, PlatformSpecificWindowAttributes}, }; -pub use crate::cursor::{BadImage, Cursor, CustomCursor, CustomCursorBuilder, MAX_CURSOR_SIZE}; +pub use crate::cursor::{BadImage, Cursor, CustomCursor, CustomCursorSource, MAX_CURSOR_SIZE}; pub use crate::icon::{BadIcon, Icon}; #[doc(inline)] diff --git a/tests/send_objects.rs b/tests/send_objects.rs index b61e2d7525..b8a5d3b73a 100644 --- a/tests/send_objects.rs +++ b/tests/send_objects.rs @@ -31,6 +31,6 @@ fn ids_send() { #[test] fn custom_cursor_send() { - needs_send::(); + needs_send::(); needs_send::(); } diff --git a/tests/sync_object.rs b/tests/sync_object.rs index 49a7b5d00a..c7abbeb208 100644 --- a/tests/sync_object.rs +++ b/tests/sync_object.rs @@ -23,6 +23,6 @@ fn window_builder_sync() { #[test] fn custom_cursor_sync() { - needs_sync::(); + needs_sync::(); needs_sync::(); }