diff --git a/internal/backends/winit/event_loop.rs b/internal/backends/winit/event_loop.rs index 906e38132d7..7f618987044 100644 --- a/internal/backends/winit/event_loop.rs +++ b/internal/backends/winit/event_loop.rs @@ -28,7 +28,7 @@ use std::rc::{Rc, Weak}; use winit::event::WindowEvent; use winit::event_loop::ActiveEventLoop; use winit::event_loop::ControlFlow; -use winit::window::ResizeDirection; +use winit::window::{CustomCursor, ResizeDirection}; pub(crate) struct NotRunningEventLoop { #[cfg(not(target_arch = "wasm32"))] pub(crate) clipboard: Rc>, @@ -320,6 +320,8 @@ impl winit::application::ApplicationHandler for EventLoopState { } let runtime_window = WindowInner::from_pub(window.window()); + self.maybe_set_custom_cursor(&runtime_window, &window, &event_loop); + match event { WindowEvent::RedrawRequested => { self.loop_error = window.draw().err(); @@ -348,7 +350,6 @@ impl winit::application::ApplicationHandler for EventLoopState { ); } } - WindowEvent::KeyboardInput { event, is_synthetic, .. } => { let key_code = event.logical_key; // For now: Match Qt's behavior of mapping command to control and control to meta (LWin/RWin). @@ -720,6 +721,39 @@ impl EventLoopState { } } + pub fn maybe_set_custom_cursor( + &self, + runtime_window: &WindowInner, + window: &WinitWindowAdapter, + event_loop: &ActiveEventLoop, + ) { + let window_item = runtime_window.window_item(); + let window_ref = window_item.as_ref().unwrap().as_pin_ref(); + let cursor = window_ref.cursor(); + runtime_window.set_has_custom_cursor(false); + + // TODO: Only change the cursor if the cursor value becomes dirty + // `window_ref.cursor.is_dirty()` doesn't seem to work. + if cursor.size().width > 0 && cursor.size().height > 0 { + let hotspot_x = window_ref.cursor_hotspot_x().get(); + let hotspot_y = window_ref.cursor_hotspot_y().get(); + let rgba_vec = cursor.to_rgba8().unwrap().make_mut_slice().to_vec(); + let rgba = + rgba_vec.iter().map(|c| vec![c.r, c.g, c.b, c.a]).flatten().collect::>(); + let size = cursor.size(); + let source = CustomCursor::from_rgba( + rgba, + size.width as u16, + size.height as u16, + hotspot_x as u16, + hotspot_y as u16, + ); + let custom_cursor = event_loop.create_custom_cursor(source.unwrap()); + window.winit_window().unwrap().set_cursor(custom_cursor.clone()); + runtime_window.set_has_custom_cursor(true); + } + } + /// Runs the event loop and renders the items in the provided `component` in its /// own window. #[cfg(not(target_arch = "wasm32"))] diff --git a/internal/backends/winit/winitwindowadapter.rs b/internal/backends/winit/winitwindowadapter.rs index 08d58071c92..58c8b885200 100644 --- a/internal/backends/winit/winitwindowadapter.rs +++ b/internal/backends/winit/winitwindowadapter.rs @@ -939,6 +939,11 @@ impl WindowAdapter for WinitWindowAdapter { impl WindowAdapterInternal for WinitWindowAdapter { fn set_mouse_cursor(&self, cursor: MouseCursor) { + let runtime_window = WindowInner::from_pub(self.window()); + if runtime_window.has_custom_cursor() { + return; + } + let winit_cursor = match cursor { MouseCursor::Default => winit::window::CursorIcon::Default, MouseCursor::None => winit::window::CursorIcon::Default, diff --git a/internal/compiler/builtins.slint b/internal/compiler/builtins.slint index c1f33b5edd9..aca508da314 100644 --- a/internal/compiler/builtins.slint +++ b/internal/compiler/builtins.slint @@ -221,6 +221,9 @@ component WindowItem { in-out property default-font-size; // <=> StyleMetrics.default-font-size set in apply_default_properties_from_style in property default-font-weight; in property icon; + in property cursor; + in property cursor-hotspot-x; + in property cursor-hotspot-y; } export component Window inherits WindowItem { diff --git a/internal/core/items.rs b/internal/core/items.rs index f5e7d0ba07b..8188894350a 100644 --- a/internal/core/items.rs +++ b/internal/core/items.rs @@ -960,6 +960,9 @@ pub struct WindowItem { pub default_font_size: Property, pub default_font_weight: Property, pub cached_rendering_data: CachedRenderingData, + pub cursor: Property, + pub cursor_hotspot_x: Property, + pub cursor_hotspot_y: Property, } impl Item for WindowItem { diff --git a/internal/core/window.rs b/internal/core/window.rs index 6912981d01c..48b46a15c30 100644 --- a/internal/core/window.rs +++ b/internal/core/window.rs @@ -434,6 +434,8 @@ pub struct WindowInner { maximized: Cell, minimized: Cell, + has_custom_cursor: Cell, + /// Stack of currently active popups active_popups: RefCell>, next_popup_id: Cell, @@ -503,6 +505,7 @@ impl WindowInner { close_requested: Default::default(), click_state: ClickState::default(), prevent_focus_change: Default::default(), + has_custom_cursor: Cell::new(false), // The ctx is lazy so that a Window can be initialized before the backend. // (for example in test_empty_window) ctx: once_cell::unsync::Lazy::new(|| { @@ -1263,6 +1266,16 @@ impl WindowInner { pub fn from_pub(window: &crate::api::Window) -> &Self { &window.0 } + + /// Sets whether the window has a custom cursor set. + pub fn set_has_custom_cursor(&self, is_custom_cursor: bool) { + self.has_custom_cursor.set(is_custom_cursor); + } + + /// Returns whether the window has a custom cursor set. + pub fn has_custom_cursor(&self) -> bool { + self.has_custom_cursor.get() + } } /// Internal alias for `Rc`.