Skip to content

Commit e4c102f

Browse files
committed
Add support for setting custom bitmap cursors
Right now you can set the mouse cursor shape in a TouchArea to a pre-defined list of cursor shapes, but sometimes you have your own custom cursor set for actions that aren't in the CSS spec. This change introduces new API to set a mouse cursor to an Image, using the same property (mouse-cursor) and a changeable hotspot.
1 parent a9f9e43 commit e4c102f

36 files changed

Lines changed: 773 additions & 217 deletions

api/cpp/cbindgen.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,14 @@ fn gen_corelib(
722722
.body
723723
.insert("Flickable".to_owned(), " inline Flickable(); inline ~Flickable();".into());
724724
config.export.pre_body.insert("FlickableDataBox".to_owned(), "struct FlickableData;".into());
725+
config.export.body.insert(
726+
"MouseCursor".to_owned(),
727+
" constexpr MouseCursor(MouseCursor::Tag tag = Tag::Default) : tag(tag) {}
728+
MouseCursor(MouseCursor::Tag tag, Image image, int hotspot_x, int hotspot_y) : tag(tag), custom_cursor(image, hotspot_x, hotspot_y) {}
729+
MouseCursor& operator=(const MouseCursor &other) { tag = other.tag; custom_cursor = other.custom_cursor; return *this; }
730+
~MouseCursor() {}
731+
".into()
732+
);
725733

726734
cbindgen::Builder::new()
727735
.with_config(config)

api/cpp/include/slint.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,17 @@ inline bool operator==(const EasingCurve &a, const EasingCurve &b)
101101
}
102102
return true;
103103
}
104+
inline bool operator==(const MouseCursor &a, const MouseCursor &b)
105+
{
106+
if (a.tag != b.tag) {
107+
return false;
108+
} else if (a.tag == MouseCursor::Tag::CustomCursor) {
109+
return a.custom_cursor.image == b.custom_cursor.image
110+
&& a.custom_cursor.hotspot_x == b.custom_cursor.hotspot_x
111+
&& a.custom_cursor.hotspot_y == b.custom_cursor.hotspot_y;
112+
}
113+
return true;
114+
}
104115
}
105116

106117
namespace private_api {

api/node/rust/interpreter/value.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ pub fn to_value(env: &Env, unknown: JsUnknown, typ: &Type) -> Result<Value> {
294294
| Type::LayoutCache
295295
| Type::ArrayOfU16
296296
| Type::ElementReference
297-
| Type::StyledText => Err(napi::Error::from_reason("reason")),
297+
| Type::StyledText => Err(napi::Error::from_reason("reason")) | Type::Cursor,
298298
}
299299
}
300300

internal/backends/qt/qt_window.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1984,6 +1984,15 @@ impl WindowAdapterInternal for QtWindow {
19841984

19851985
fn set_mouse_cursor(&self, cursor: MouseCursor) {
19861986
let widget_ptr = self.widget_ptr();
1987+
if let MouseCursor::CustomCursor { image, hotspot_x, hotspot_y } = cursor {
1988+
let pixmap: qttypes::QPixmap =
1989+
crate::qt_window::image_to_pixmap((&image).into(), None).unwrap_or_default();
1990+
cpp! {unsafe [widget_ptr as "QWidget*", pixmap as "QPixmap", hotspot_x as "int", hotspot_y as "int"] {
1991+
widget_ptr->setCursor(QCursor{pixmap, hotspot_x, hotspot_y});
1992+
}};
1993+
return;
1994+
}
1995+
19871996
//unidirectional resize cursors are replaced with bidirectional ones
19881997
let cursor_shape = match cursor {
19891998
MouseCursor::Default => key_generated::Qt_CursorShape_ArrowCursor,

internal/backends/testing/testing_backend.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ pub struct TestingWindow {
108108
window: i_slint_core::api::Window,
109109
size: Cell<PhysicalSize>,
110110
pub ime_requests: RefCell<Vec<InputMethodRequest>>,
111-
pub mouse_cursor: Cell<i_slint_core::items::MouseCursor>,
111+
pub mouse_cursor: RefCell<i_slint_core::items::MouseCursor>,
112112
}
113113

114114
impl WindowAdapterInternal for TestingWindow {
@@ -117,7 +117,7 @@ impl WindowAdapterInternal for TestingWindow {
117117
}
118118

119119
fn set_mouse_cursor(&self, cursor: i_slint_core::items::MouseCursor) {
120-
self.mouse_cursor.set(cursor);
120+
self.mouse_cursor.replace(cursor);
121121
}
122122
}
123123

internal/backends/winit/event_loop.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*/
1010
use crate::EventResult;
1111
use crate::drag_resize_window::{handle_cursor_move_for_resize, handle_resize};
12-
use crate::winitwindowadapter::WindowVisibility;
12+
use crate::winitwindowadapter::{WindowVisibility, WinitWindowAdapter};
1313
use crate::{SharedBackendData, SlintEvent};
1414
use corelib::graphics::euclid;
1515
use corelib::input::{KeyEvent, KeyEventType, MouseEvent};
@@ -175,6 +175,8 @@ impl winit::application::ApplicationHandler<SlintEvent> for EventLoopState {
175175
}
176176

177177
let runtime_window = WindowInner::from_pub(window.window());
178+
self.maybe_set_custom_cursor(&window, &event_loop);
179+
178180
match event {
179181
WindowEvent::RedrawRequested => {
180182
self.loop_error = window.draw().err();
@@ -603,6 +605,20 @@ impl EventLoopState {
603605
}
604606
}
605607

608+
/// Sets the cursor to a custom source, if it needs to be set.
609+
pub fn maybe_set_custom_cursor(
610+
&self,
611+
window: &WinitWindowAdapter,
612+
event_loop: &ActiveEventLoop,
613+
) {
614+
// If there is a new custom cursor, update it.
615+
let custom_cursor_source = window.custom_cursor_source.take();
616+
if let Some(source) = custom_cursor_source {
617+
let custom_cursor = event_loop.create_custom_cursor(source);
618+
window.winit_window().unwrap().set_cursor(custom_cursor);
619+
}
620+
}
621+
606622
/// Runs the event loop and renders the items in the provided `component` in its
607623
/// own window.
608624
#[cfg(all(not(target_arch = "wasm32"), not(ios_and_friends)))]

internal/backends/winit/winitwindowadapter.rs

Lines changed: 61 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use i_slint_core::{self as corelib};
4747
use std::cell::OnceCell;
4848
#[cfg(any(enable_accesskit, muda))]
4949
use winit::event_loop::EventLoopProxy;
50-
use winit::window::{WindowAttributes, WindowButtons};
50+
use winit::window::{CustomCursor, CustomCursorSource, WindowAttributes, WindowButtons};
5151

5252
pub(crate) fn position_to_winit(pos: &corelib::api::WindowPosition) -> winit::dpi::Position {
5353
match pos {
@@ -368,6 +368,8 @@ pub struct WinitWindowAdapter {
368368
window_icon_cache_key: RefCell<Option<ImageCacheKey>>,
369369

370370
frame_throttle: Box<dyn crate::frame_throttle::FrameThrottle>,
371+
372+
pub(crate) custom_cursor_source: Cell<Option<CustomCursorSource>>,
371373
}
372374

373375
impl WinitWindowAdapter {
@@ -416,6 +418,7 @@ impl WinitWindowAdapter {
416418
self_weak.clone(),
417419
shared_backend_data.is_wayland,
418420
),
421+
custom_cursor_source: Cell::new(None),
419422
});
420423

421424
self_rc.shared_backend_data.register_inactive_window((self_rc.clone()) as _);
@@ -1324,41 +1327,66 @@ impl WindowAdapter for WinitWindowAdapter {
13241327

13251328
impl WindowAdapterInternal for WinitWindowAdapter {
13261329
fn set_mouse_cursor(&self, cursor: MouseCursor) {
1327-
let winit_cursor = match cursor {
1328-
MouseCursor::Default => winit::window::CursorIcon::Default,
1329-
MouseCursor::None => winit::window::CursorIcon::Default,
1330-
MouseCursor::Help => winit::window::CursorIcon::Help,
1331-
MouseCursor::Pointer => winit::window::CursorIcon::Pointer,
1332-
MouseCursor::Progress => winit::window::CursorIcon::Progress,
1333-
MouseCursor::Wait => winit::window::CursorIcon::Wait,
1334-
MouseCursor::Crosshair => winit::window::CursorIcon::Crosshair,
1335-
MouseCursor::Text => winit::window::CursorIcon::Text,
1336-
MouseCursor::Alias => winit::window::CursorIcon::Alias,
1337-
MouseCursor::Copy => winit::window::CursorIcon::Copy,
1338-
MouseCursor::Move => winit::window::CursorIcon::Move,
1339-
MouseCursor::NoDrop => winit::window::CursorIcon::NoDrop,
1340-
MouseCursor::NotAllowed => winit::window::CursorIcon::NotAllowed,
1341-
MouseCursor::Grab => winit::window::CursorIcon::Grab,
1342-
MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing,
1343-
MouseCursor::ColResize => winit::window::CursorIcon::ColResize,
1344-
MouseCursor::RowResize => winit::window::CursorIcon::RowResize,
1345-
MouseCursor::NResize => winit::window::CursorIcon::NResize,
1346-
MouseCursor::EResize => winit::window::CursorIcon::EResize,
1347-
MouseCursor::SResize => winit::window::CursorIcon::SResize,
1348-
MouseCursor::WResize => winit::window::CursorIcon::WResize,
1349-
MouseCursor::NeResize => winit::window::CursorIcon::NeResize,
1350-
MouseCursor::NwResize => winit::window::CursorIcon::NwResize,
1351-
MouseCursor::SeResize => winit::window::CursorIcon::SeResize,
1352-
MouseCursor::SwResize => winit::window::CursorIcon::SwResize,
1353-
MouseCursor::EwResize => winit::window::CursorIcon::EwResize,
1354-
MouseCursor::NsResize => winit::window::CursorIcon::NsResize,
1355-
MouseCursor::NeswResize => winit::window::CursorIcon::NeswResize,
1356-
MouseCursor::NwseResize => winit::window::CursorIcon::NwseResize,
1357-
_ => winit::window::CursorIcon::Default,
1330+
let winit_cursor = match &cursor {
1331+
MouseCursor::Default => Some(winit::window::CursorIcon::Default),
1332+
MouseCursor::None => Some(winit::window::CursorIcon::Default),
1333+
MouseCursor::Help => Some(winit::window::CursorIcon::Help),
1334+
MouseCursor::Pointer => Some(winit::window::CursorIcon::Pointer),
1335+
MouseCursor::Progress => Some(winit::window::CursorIcon::Progress),
1336+
MouseCursor::Wait => Some(winit::window::CursorIcon::Wait),
1337+
MouseCursor::Crosshair => Some(winit::window::CursorIcon::Crosshair),
1338+
MouseCursor::Text => Some(winit::window::CursorIcon::Text),
1339+
MouseCursor::Alias => Some(winit::window::CursorIcon::Alias),
1340+
MouseCursor::Copy => Some(winit::window::CursorIcon::Copy),
1341+
MouseCursor::Move => Some(winit::window::CursorIcon::Move),
1342+
MouseCursor::NoDrop => Some(winit::window::CursorIcon::NoDrop),
1343+
MouseCursor::NotAllowed => Some(winit::window::CursorIcon::NotAllowed),
1344+
MouseCursor::Grab => Some(winit::window::CursorIcon::Grab),
1345+
MouseCursor::Grabbing => Some(winit::window::CursorIcon::Grabbing),
1346+
MouseCursor::ColResize => Some(winit::window::CursorIcon::ColResize),
1347+
MouseCursor::RowResize => Some(winit::window::CursorIcon::RowResize),
1348+
MouseCursor::NResize => Some(winit::window::CursorIcon::NResize),
1349+
MouseCursor::EResize => Some(winit::window::CursorIcon::EResize),
1350+
MouseCursor::SResize => Some(winit::window::CursorIcon::SResize),
1351+
MouseCursor::WResize => Some(winit::window::CursorIcon::WResize),
1352+
MouseCursor::NeResize => Some(winit::window::CursorIcon::NeResize),
1353+
MouseCursor::NwResize => Some(winit::window::CursorIcon::NwResize),
1354+
MouseCursor::SeResize => Some(winit::window::CursorIcon::SeResize),
1355+
MouseCursor::SwResize => Some(winit::window::CursorIcon::SwResize),
1356+
MouseCursor::EwResize => Some(winit::window::CursorIcon::EwResize),
1357+
MouseCursor::NsResize => Some(winit::window::CursorIcon::NsResize),
1358+
MouseCursor::NeswResize => Some(winit::window::CursorIcon::NeswResize),
1359+
MouseCursor::NwseResize => Some(winit::window::CursorIcon::NwseResize),
1360+
MouseCursor::CustomCursor { image, hotspot_x, hotspot_y } => {
1361+
if let Some(rgba8) = image.to_rgba8() {
1362+
let rgba_vec = rgba8.as_slice().to_vec();
1363+
let rgba = rgba_vec
1364+
.iter()
1365+
.map(|c| vec![c.r, c.g, c.b, c.a])
1366+
.flatten()
1367+
.collect::<Vec<u8>>();
1368+
let size = image.size();
1369+
let source = CustomCursor::from_rgba(
1370+
rgba,
1371+
size.width as u16,
1372+
size.height as u16,
1373+
*hotspot_x as u16,
1374+
*hotspot_y as u16,
1375+
);
1376+
1377+
// Custom cursors have to be set during the event loop
1378+
self.custom_cursor_source.set(source.ok());
1379+
}
1380+
None
1381+
}
1382+
_ => None,
13581383
};
13591384
if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
13601385
winit_window.set_cursor_visible(cursor != MouseCursor::None);
1361-
winit_window.set_cursor(winit_cursor);
1386+
1387+
if let Some(cursor) = winit_cursor {
1388+
winit_window.set_cursor(cursor);
1389+
}
13621390
}
13631391
}
13641392

internal/common/enums.rs

Lines changed: 0 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -187,77 +187,6 @@ macro_rules! for_each_enums {
187187
Forward,
188188
}
189189

190-
/// This enum represents different types of mouse cursors. It's a subset of the mouse cursors available in CSS.
191-
/// For details and pictograms see the [MDN Documentation for cursor](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#values).
192-
/// Depending on the backend and used OS unidirectional resize cursors may be replaced with bidirectional ones.
193-
#[non_exhaustive]
194-
enum MouseCursor {
195-
/// The systems default cursor.
196-
Default,
197-
/// No cursor is displayed.
198-
None,
199-
//context_menu,
200-
/// A cursor indicating help information.
201-
Help,
202-
/// A pointing hand indicating a link.
203-
Pointer,
204-
/// The program is busy but can still be interacted with.
205-
Progress,
206-
/// The program is busy.
207-
Wait,
208-
//cell,
209-
/// A crosshair.
210-
Crosshair,
211-
/// A cursor indicating selectable text.
212-
Text,
213-
//vertical_text,
214-
/// An alias or shortcut is being created.
215-
Alias,
216-
/// A copy is being created.
217-
Copy,
218-
/// Something is to be moved.
219-
Move,
220-
/// Something can't be dropped here.
221-
NoDrop,
222-
/// An action isn't allowed
223-
NotAllowed,
224-
/// Something is grabbable.
225-
Grab,
226-
/// Something is being grabbed.
227-
Grabbing,
228-
//all_scroll,
229-
/// Indicating that a column is resizable horizontally.
230-
ColResize,
231-
/// Indicating that a row is resizable vertically.
232-
RowResize,
233-
/// Unidirectional resize north.
234-
NResize,
235-
/// Unidirectional resize east.
236-
EResize,
237-
/// Unidirectional resize south.
238-
SResize,
239-
/// Unidirectional resize west.
240-
WResize,
241-
/// Unidirectional resize north-east.
242-
NeResize,
243-
/// Unidirectional resize north-west.
244-
NwResize,
245-
/// Unidirectional resize south-east.
246-
SeResize,
247-
/// Unidirectional resize south-west.
248-
SwResize,
249-
/// Bidirectional resize east-west.
250-
EwResize,
251-
/// Bidirectional resize north-south.
252-
NsResize,
253-
/// Bidirectional resize north-east-south-west.
254-
NeswResize,
255-
/// Bidirectional resize north-west-south-east.
256-
NwseResize,
257-
//zoom_in,
258-
//zoom_out,
259-
}
260-
261190
/// This enum defines how the source image shall fit into an `Image` element.
262191
#[non_exhaustive]
263192
enum ImageFit {

0 commit comments

Comments
 (0)