diff --git a/internal/backends/qt/qt_window.rs b/internal/backends/qt/qt_window.rs index 6c69d8f5237..6f8a4d0aa07 100644 --- a/internal/backends/qt/qt_window.rs +++ b/internal/backends/qt/qt_window.rs @@ -2066,43 +2066,6 @@ impl WindowAdapter for QtWindow { fn internal(&self, _: i_slint_core::InternalToken) -> Option<&dyn WindowAdapterInternal> { Some(self) } -} - -fn into_qsize(logical_size: i_slint_core::api::LogicalSize) -> qttypes::QSize { - qttypes::QSize { - width: logical_size.width.round() as _, - height: logical_size.height.round() as _, - } -} - -impl WindowAdapterInternal for QtWindow { - fn register_item_tree(&self) { - self.tree_structure_changed.replace(true); - } - - fn unregister_item_tree( - &self, - _component: ItemTreeRef, - _: &mut dyn Iterator>>, - ) { - self.tree_structure_changed.replace(true); - } - - fn create_popup(&self, geometry: LogicalRect) -> Option> { - let popup_window = QtWindow::new(); - - let size = qttypes::QSize { width: geometry.width() as _, height: geometry.height() as _ }; - - let popup_ptr = popup_window.widget_ptr(); - let pos = qttypes::QPoint { x: geometry.origin.x as _, y: geometry.origin.y as _ }; - let widget_ptr = self.widget_ptr(); - cpp! {unsafe [widget_ptr as "QWidget*", popup_ptr as "QWidget*", pos as "QPoint", size as "QSize"] { - popup_ptr->setParent(widget_ptr, Qt::Popup); - popup_ptr->setGeometry(QRect(pos + widget_ptr->mapToGlobal(QPoint(0,0)), size)); - popup_ptr->show(); - }}; - Some(popup_window as _) - } fn set_mouse_cursor(&self, cursor: MouseCursor) { let widget_ptr = self.widget_ptr(); @@ -2137,11 +2100,50 @@ impl WindowAdapterInternal for QtWindow { MouseCursor::NsResize => key_generated::Qt_CursorShape_SizeVerCursor, MouseCursor::NeswResize => key_generated::Qt_CursorShape_SizeBDiagCursor, MouseCursor::NwseResize => key_generated::Qt_CursorShape_SizeFDiagCursor, + _ => key_generated::Qt_CursorShape_ArrowCursor, }; cpp! {unsafe [widget_ptr as "QWidget*", cursor_shape as "Qt::CursorShape"] { widget_ptr->setCursor(QCursor{cursor_shape}); }}; } +} + +fn into_qsize(logical_size: i_slint_core::api::LogicalSize) -> qttypes::QSize { + qttypes::QSize { + width: logical_size.width.round() as _, + height: logical_size.height.round() as _, + } +} + +impl WindowAdapterInternal for QtWindow { + fn register_item_tree(&self) { + self.tree_structure_changed.replace(true); + } + + fn unregister_item_tree( + &self, + _component: ItemTreeRef, + _: &mut dyn Iterator>>, + ) { + self.tree_structure_changed.replace(true); + } + + fn create_popup(&self, geometry: LogicalRect) -> Option> { + let popup_window = QtWindow::new(); + + let size = qttypes::QSize { width: geometry.width() as _, height: geometry.height() as _ }; + + let popup_ptr = popup_window.widget_ptr(); + let pos = qttypes::QPoint { x: geometry.origin.x as _, y: geometry.origin.y as _ }; + let widget_ptr = self.widget_ptr(); + cpp! {unsafe [widget_ptr as "QWidget*", popup_ptr as "QWidget*", pos as "QPoint", size as "QSize"] { + popup_ptr->setParent(widget_ptr, Qt::Popup); + popup_ptr->setGeometry(QRect(pos + widget_ptr->mapToGlobal(QPoint(0,0)), size)); + popup_ptr->show(); + }}; + Some(popup_window as _) + } + fn input_method_request(&self, request: i_slint_core::window::InputMethodRequest) { let widget_ptr = self.widget_ptr(); diff --git a/internal/backends/testing/testing_backend.rs b/internal/backends/testing/testing_backend.rs index 85bbdcce49c..1765ce9a567 100644 --- a/internal/backends/testing/testing_backend.rs +++ b/internal/backends/testing/testing_backend.rs @@ -119,10 +119,6 @@ impl WindowAdapterInternal for TestingWindow { fn input_method_request(&self, request: i_slint_core::window::InputMethodRequest) { self.ime_requests.borrow_mut().push(request) } - - fn set_mouse_cursor(&self, cursor: i_slint_core::items::MouseCursor) { - self.mouse_cursor.set(cursor); - } } impl WindowAdapter for TestingWindow { @@ -159,6 +155,10 @@ impl WindowAdapter for TestingWindow { fn internal(&self, _: i_slint_core::InternalToken) -> Option<&dyn WindowAdapterInternal> { Some(self) } + + fn set_mouse_cursor(&self, cursor: i_slint_core::items::MouseCursor) { + self.mouse_cursor.set(cursor); + } } impl RendererSealed for TestingWindow { diff --git a/internal/backends/winit/winitwindowadapter.rs b/internal/backends/winit/winitwindowadapter.rs index 048dfb7ac37..c240db64ba7 100644 --- a/internal/backends/winit/winitwindowadapter.rs +++ b/internal/backends/winit/winitwindowadapter.rs @@ -1244,9 +1244,7 @@ impl WindowAdapter for WinitWindowAdapter { fn internal(&self, _: corelib::InternalToken) -> Option<&dyn WindowAdapterInternal> { Some(self) } -} -impl WindowAdapterInternal for WinitWindowAdapter { fn set_mouse_cursor(&self, cursor: MouseCursor) { let winit_cursor = match cursor { MouseCursor::Default => winit::window::CursorIcon::Default, @@ -1278,13 +1276,16 @@ impl WindowAdapterInternal for WinitWindowAdapter { MouseCursor::NsResize => winit::window::CursorIcon::NsResize, MouseCursor::NeswResize => winit::window::CursorIcon::NeswResize, MouseCursor::NwseResize => winit::window::CursorIcon::NwseResize, + _ => winit::window::CursorIcon::Default, }; if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() { winit_window.set_cursor_visible(cursor != MouseCursor::None); winit_window.set_cursor(winit_cursor); } } +} +impl WindowAdapterInternal for WinitWindowAdapter { fn input_method_request(&self, request: corelib::window::InputMethodRequest) { #[cfg(not(target_arch = "wasm32"))] if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() { diff --git a/internal/common/enums.rs b/internal/common/enums.rs index dc3a0a1efbc..43f8db5733f 100644 --- a/internal/common/enums.rs +++ b/internal/common/enums.rs @@ -177,76 +177,6 @@ macro_rules! for_each_enums { Forward, } - /// This enum represents different types of mouse cursors. It's a subset of the mouse cursors available in CSS. - /// For details and pictograms see the [MDN Documentation for cursor](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#values). - /// Depending on the backend and used OS unidirectional resize cursors may be replaced with bidirectional ones. - enum MouseCursor { - /// The systems default cursor. - Default, - /// No cursor is displayed. - None, - //context_menu, - /// A cursor indicating help information. - Help, - /// A pointing hand indicating a link. - Pointer, - /// The program is busy but can still be interacted with. - Progress, - /// The program is busy. - Wait, - //cell, - /// A crosshair. - Crosshair, - /// A cursor indicating selectable text. - Text, - //vertical_text, - /// An alias or shortcut is being created. - Alias, - /// A copy is being created. - Copy, - /// Something is to be moved. - Move, - /// Something can't be dropped here. - NoDrop, - /// An action isn't allowed - NotAllowed, - /// Something is grabbable. - Grab, - /// Something is being grabbed. - Grabbing, - //all_scroll, - /// Indicating that a column is resizable horizontally. - ColResize, - /// Indicating that a row is resizable vertically. - RowResize, - /// Unidirectional resize north. - NResize, - /// Unidirectional resize east. - EResize, - /// Unidirectional resize south. - SResize, - /// Unidirectional resize west. - WResize, - /// Unidirectional resize north-east. - NeResize, - /// Unidirectional resize north-west. - NwResize, - /// Unidirectional resize south-east. - SeResize, - /// Unidirectional resize south-west. - SwResize, - /// Bidirectional resize east-west. - EwResize, - /// Bidirectional resize north-south. - NsResize, - /// Bidirectional resize north-east-south-west. - NeswResize, - /// Bidirectional resize north-west-south-east. - NwseResize, - //zoom_in, - //zoom_out, - } - /// This enum defines how the source image shall fit into an `Image` element. enum ImageFit { /// Scales and stretches the source image to fit the width and height of the `Image` element. @@ -491,6 +421,71 @@ macro_rules! for_each_enums { /// This variant is reported when the operating system is none of the above. Other, } + + /// This enum represents different types of mouse cursors. It's a subset of the mouse cursors available in CSS. + /// For details and pictograms see the [MDN Documentation for cursor](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#values). + /// Depending on the backend and used OS unidirectional resize cursors may be replaced with bidirectional ones. + #[non_exhaustive] + enum MouseCursor { + /// The system's default cursor. + Default, + /// No cursor is displayed. + None, + /// A cursor indicating help information. + Help, + /// A pointing hand indicating a link. + Pointer, + /// The program is busy but can still be interacted with. + Progress, + /// The program is busy. + Wait, + /// A crosshair. + Crosshair, + /// A cursor indicating selectable text. + Text, + /// An alias or shortcut is being created. + Alias, + /// A copy is being created. + Copy, + /// Something is to be moved. + Move, + /// Something can't be dropped here. + NoDrop, + /// An action isn't allowed. + NotAllowed, + /// Something is grabbable. + Grab, + /// Something is being grabbed. + Grabbing, + /// Indicating that a column is resizable horizontally. + ColResize, + /// Indicating that a row is resizable vertically. + RowResize, + /// Unidirectional resize north. + NResize, + /// Unidirectional resize east. + EResize, + /// Unidirectional resize south. + SResize, + /// Unidirectional resize west. + WResize, + /// Unidirectional resize north-east. + NeResize, + /// Unidirectional resize north-west. + NwResize, + /// Unidirectional resize south-east. + SeResize, + /// Unidirectional resize south-west. + SwResize, + /// Bidirectional resize east-west. + EwResize, + /// Bidirectional resize north-south. + NsResize, + /// Bidirectional resize north-east-south-west. + NeswResize, + /// Bidirectional resize north-west-south-east. + NwseResize, + } ]; }; } diff --git a/internal/core/api.rs b/internal/core/api.rs index 1f0b493e36d..d3d6f283a1b 100644 --- a/internal/core/api.rs +++ b/internal/core/api.rs @@ -11,6 +11,7 @@ This module contains types that are public and re-exported in the slint-rs as we pub use crate::future::*; use crate::graphics::{Rgba8Pixel, SharedPixelBuffer}; use crate::input::{KeyEventType, MouseEvent}; +pub use crate::items::MouseCursor; use crate::window::{WindowAdapter, WindowInner}; use alloc::boxed::Box; use alloc::string::String; @@ -706,6 +707,11 @@ impl Window { pub fn take_snapshot(&self) -> Result, PlatformError> { self.0.window_adapter().renderer().take_snapshot() } + + /// Sets the mouse cursor for this window. + pub fn set_mouse_cursor(&self, cursor: crate::items::MouseCursor) { + self.0.window_adapter().set_mouse_cursor(cursor); + } } pub use crate::SharedString; diff --git a/internal/core/items/drag_n_drop.rs b/internal/core/items/drag_n_drop.rs index 6618d62098b..19cfc7353fd 100644 --- a/internal/core/items/drag_n_drop.rs +++ b/internal/core/items/drag_n_drop.rs @@ -1,14 +1,13 @@ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 -use super::{ - DropEvent, Item, ItemConsts, ItemRc, MouseCursor, PointerEventButton, RenderingResult, -}; +use super::{DropEvent, Item, ItemConsts, ItemRc, PointerEventButton, RenderingResult}; use crate::input::{ FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, KeyEvent, KeyEventResult, MouseEvent, }; use crate::item_rendering::{CachedRenderingData, ItemRenderer}; +use crate::items::MouseCursor; use crate::layout::{LayoutInfo, Orientation}; use crate::lengths::{LogicalPoint, LogicalRect, LogicalSize}; #[cfg(feature = "rtti")] @@ -249,9 +248,7 @@ impl Item for DropArea { let r = Self::FIELD_OFFSETS.can_drop.apply_pin(self).call(&(event.clone(),)); if r { self.contains_drag.set(true); - if let Some(window_adapter) = window_adapter.internal(crate::InternalToken) { - window_adapter.set_mouse_cursor(MouseCursor::Copy); - } + window_adapter.set_mouse_cursor(MouseCursor::Copy); InputEventResult::EventAccepted } else { self.contains_drag.set(false); diff --git a/internal/core/items/input_items.rs b/internal/core/items/input_items.rs index fe59bee4631..d439b46ee24 100644 --- a/internal/core/items/input_items.rs +++ b/internal/core/items/input_items.rs @@ -3,8 +3,8 @@ use super::{ EventResult, FocusReasonArg, Item, ItemConsts, ItemRc, ItemRendererRef, KeyEventArg, - MouseCursor, PointerEvent, PointerEventArg, PointerEventButton, PointerEventKind, - PointerScrollEvent, PointerScrollEventArg, RenderingResult, VoidArg, + PointerEvent, PointerEventArg, PointerEventButton, PointerEventKind, PointerScrollEvent, + PointerScrollEventArg, RenderingResult, VoidArg, }; use crate::api::LogicalPosition; use crate::input::{ @@ -12,6 +12,7 @@ use crate::input::{ KeyEventResult, KeyEventType, MouseEvent, }; use crate::item_rendering::CachedRenderingData; +use crate::items::MouseCursor; use crate::layout::{LayoutInfo, Orientation}; use crate::lengths::{LogicalLength, LogicalPoint, LogicalRect, LogicalSize, PointLengths}; #[cfg(feature = "rtti")] @@ -94,9 +95,7 @@ impl Item for TouchArea { let hovering = !matches!(event, MouseEvent::Exit); Self::FIELD_OFFSETS.has_hover.apply_pin(self).set(hovering); if hovering { - if let Some(x) = window_adapter.internal(crate::InternalToken) { - x.set_mouse_cursor(self.mouse_cursor()); - } + window_adapter.set_mouse_cursor(self.mouse_cursor()); } InputEventFilterResult::ForwardAndInterceptGrab } @@ -109,9 +108,7 @@ impl Item for TouchArea { ) -> InputEventResult { if matches!(event, MouseEvent::Exit) { Self::FIELD_OFFSETS.has_hover.apply_pin(self).set(false); - if let Some(x) = window_adapter.internal(crate::InternalToken) { - x.set_mouse_cursor(MouseCursor::Default); - } + window_adapter.set_mouse_cursor(MouseCursor::Default); } if !self.enabled() { return InputEventResult::EventIgnored; diff --git a/internal/core/items/text.rs b/internal/core/items/text.rs index c2043deef2e..63c54d6ecd3 100644 --- a/internal/core/items/text.rs +++ b/internal/core/items/text.rs @@ -19,6 +19,7 @@ use crate::input::{ KeyEvent, KeyboardModifiers, MouseEvent, StandardShortcut, TextShortcut, }; use crate::item_rendering::{CachedRenderingData, ItemRenderer, RenderText}; +use crate::items::MouseCursor; use crate::layout::{LayoutInfo, Orientation}; use crate::lengths::{ LogicalLength, LogicalPoint, LogicalRect, LogicalSize, ScaleFactor, SizeLengths, @@ -661,15 +662,11 @@ impl Item for TextInput { self.paste_clipboard(window_adapter, self_rc, Clipboard::SelectionClipboard); } MouseEvent::Exit => { - if let Some(x) = window_adapter.internal(crate::InternalToken) { - x.set_mouse_cursor(super::MouseCursor::Default); - } + window_adapter.set_mouse_cursor(MouseCursor::Default); self.as_ref().pressed.set(0) } MouseEvent::Moved { position } => { - if let Some(x) = window_adapter.internal(crate::InternalToken) { - x.set_mouse_cursor(super::MouseCursor::Text); - } + window_adapter.set_mouse_cursor(MouseCursor::Text); let pressed = self.as_ref().pressed.get(); if pressed > 0 { let clicked_offset = diff --git a/internal/core/lib.rs b/internal/core/lib.rs index d65850963a5..d2dd8e87e36 100644 --- a/internal/core/lib.rs +++ b/internal/core/lib.rs @@ -7,7 +7,6 @@ #![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")] #![deny(unsafe_code)] #![no_std] - extern crate alloc; #[cfg(feature = "std")] extern crate std; diff --git a/internal/core/tests.rs b/internal/core/tests.rs index 9720fb3d305..11380ba2e27 100644 --- a/internal/core/tests.rs +++ b/internal/core/tests.rs @@ -123,3 +123,29 @@ pub fn default_debug_log(_arguments: core::fmt::Arguments) { macro_rules! debug_log { ($($t:tt)*) => ($crate::tests::debug_log_impl(format_args!($($t)*))) } + +#[cfg(test)] +mod tests { + use crate::items::MouseCursor; + + #[test] + fn test_mouse_cursor_api() { + // Test that MouseCursor enum has the expected variants + let default_cursor = MouseCursor::Default; + let pointer_cursor = MouseCursor::Pointer; + let text_cursor = MouseCursor::Text; + + // Test that Default trait works + let default = MouseCursor::default(); + assert_eq!(default, MouseCursor::Default); + + // Test that we can compare cursors + assert_eq!(default_cursor, MouseCursor::Default); + assert_ne!(default_cursor, pointer_cursor); + assert_ne!(pointer_cursor, text_cursor); + + // Test that we can clone and copy + let cloned = default_cursor; + assert_eq!(cloned, default_cursor); + } +} diff --git a/internal/core/window.rs b/internal/core/window.rs index fff98853ece..91b08a20ea6 100644 --- a/internal/core/window.rs +++ b/internal/core/window.rs @@ -18,7 +18,8 @@ use crate::item_tree::{ ItemRc, ItemTreeRc, ItemTreeRef, ItemTreeVTable, ItemTreeWeak, ItemWeak, ParentItemTraversalMode, }; -use crate::items::{ColorScheme, InputType, ItemRef, MouseCursor, PopupClosePolicy}; +use crate::items::MouseCursor; +use crate::items::{ColorScheme, InputType, ItemRef, PopupClosePolicy}; use crate::lengths::{LogicalLength, LogicalPoint, LogicalRect, SizeLengths}; use crate::menus::MenuVTable; use crate::properties::{Property, PropertyTracker}; @@ -158,6 +159,11 @@ pub trait WindowAdapter { ) -> Result, raw_window_handle_06::HandleError> { Err(raw_window_handle_06::HandleError::NotSupported) } + + /// Sets the mouse cursor for this window. + /// + /// Default implementation does nothing. + fn set_mouse_cursor(&self, _cursor: crate::items::MouseCursor) {} } /// Implementation details behind [`WindowAdapter`], but since this @@ -188,10 +194,6 @@ pub trait WindowAdapterInternal { None } - /// Set the mouse cursor - // TODO: Make the enum public and make public - fn set_mouse_cursor(&self, _cursor: MouseCursor) {} - /// This method allow editable input field to communicate with the platform about input methods fn input_method_request(&self, _: InputMethodRequest) {} @@ -587,17 +589,13 @@ impl WindowInner { if let Some(mut drop_event) = mouse_input_state.drag_data.clone() { match &event { MouseEvent::Released { position, button: PointerEventButton::Left, .. } => { - if let Some(window_adapter) = window_adapter.internal(crate::InternalToken) { - window_adapter.set_mouse_cursor(MouseCursor::Default); - } + window_adapter.set_mouse_cursor(MouseCursor::Default); drop_event.position = crate::lengths::logical_position_to_api(*position); event = MouseEvent::Drop(drop_event); mouse_input_state.drag_data = None; } MouseEvent::Moved { position } => { - if let Some(window_adapter) = window_adapter.internal(crate::InternalToken) { - window_adapter.set_mouse_cursor(MouseCursor::NoDrop); - } + window_adapter.set_mouse_cursor(MouseCursor::NoDrop); drop_event.position = crate::lengths::logical_position_to_api(*position); event = MouseEvent::DragMove(drop_event); } diff --git a/tatus b/tatus new file mode 100644 index 00000000000..a5fa64ee594 --- /dev/null +++ b/tatus @@ -0,0 +1,5 @@ +79fa8741d (HEAD -> feature/public-mouse-cursor, origin/feature/public-mouse-cursor) fix: move set_mouse_cursor to WindowAdapter trait in remaining backends +067b9b880 [autofix.ci] apply automated fixes +eb0131c4c feat(api): expose MouseCursor and set_mouse_cursor in public API +186dd4525 (origin/master, origin/HEAD, master) QT: Hook up `cursorFlashTime` and pass the value to `TextCursorBlinker` (#9311) +51041a213 doc: fix cargo features link diff --git a/tools/docsnapper/headless.rs b/tools/docsnapper/headless.rs index 5e6e2acbb83..8deb286119c 100644 --- a/tools/docsnapper/headless.rs +++ b/tools/docsnapper/headless.rs @@ -109,10 +109,6 @@ impl WindowAdapterInternal for HeadlessWindow { fn input_method_request(&self, request: i_slint_core::window::InputMethodRequest) { self.ime_requests.borrow_mut().push(request) } - - fn set_mouse_cursor(&self, cursor: i_slint_core::items::MouseCursor) { - self.mouse_cursor.set(cursor); - } } impl WindowAdapter for HeadlessWindow { @@ -149,6 +145,10 @@ impl WindowAdapter for HeadlessWindow { fn internal(&self, _: i_slint_core::InternalToken) -> Option<&dyn WindowAdapterInternal> { Some(self) } + + fn set_mouse_cursor(&self, cursor: i_slint_core::items::MouseCursor) { + self.mouse_cursor.set(cursor); + } } enum Event {