diff --git a/api/cpp/cbindgen.rs b/api/cpp/cbindgen.rs index 27316247972..1988647e73c 100644 --- a/api/cpp/cbindgen.rs +++ b/api/cpp/cbindgen.rs @@ -722,6 +722,14 @@ fn gen_corelib( .body .insert("Flickable".to_owned(), " inline Flickable(); inline ~Flickable();".into()); config.export.pre_body.insert("FlickableDataBox".to_owned(), "struct FlickableData;".into()); + config.export.body.insert( + "MouseCursor".to_owned(), + " constexpr MouseCursor(MouseCursor::Tag tag = Tag::Default) : tag(tag) {} + MouseCursor(MouseCursor::Tag tag, Image image, int hotspot_x, int hotspot_y) : tag(tag), custom_cursor(image, hotspot_x, hotspot_y) {} + MouseCursor& operator=(const MouseCursor &other) { tag = other.tag; custom_cursor = other.custom_cursor; return *this; } + ~MouseCursor() {} + ".into() + ); cbindgen::Builder::new() .with_config(config) diff --git a/api/cpp/include/slint.h b/api/cpp/include/slint.h index 455b9dbf1a8..9b981d97dee 100644 --- a/api/cpp/include/slint.h +++ b/api/cpp/include/slint.h @@ -101,6 +101,17 @@ inline bool operator==(const EasingCurve &a, const EasingCurve &b) } return true; } +inline bool operator==(const MouseCursor &a, const MouseCursor &b) +{ + if (a.tag != b.tag) { + return false; + } else if (a.tag == MouseCursor::Tag::CustomCursor) { + return a.custom_cursor.image == b.custom_cursor.image + && a.custom_cursor.hotspot_x == b.custom_cursor.hotspot_x + && a.custom_cursor.hotspot_y == b.custom_cursor.hotspot_y; + } + return true; +} } namespace private_api { diff --git a/api/node/rust/interpreter/value.rs b/api/node/rust/interpreter/value.rs index b6dae5952d1..35447dea3fe 100644 --- a/api/node/rust/interpreter/value.rs +++ b/api/node/rust/interpreter/value.rs @@ -294,7 +294,7 @@ pub fn to_value(env: &Env, unknown: JsUnknown, typ: &Type) -> Result { | Type::LayoutCache | Type::ArrayOfU16 | Type::ElementReference - | Type::StyledText => Err(napi::Error::from_reason("reason")), + | Type::StyledText => Err(napi::Error::from_reason("reason")) | Type::Cursor, } } diff --git a/internal/backends/qt/qt_window.rs b/internal/backends/qt/qt_window.rs index 23c341e78e8..65be2e80b54 100644 --- a/internal/backends/qt/qt_window.rs +++ b/internal/backends/qt/qt_window.rs @@ -1984,6 +1984,15 @@ impl WindowAdapterInternal for QtWindow { fn set_mouse_cursor(&self, cursor: MouseCursor) { let widget_ptr = self.widget_ptr(); + if let MouseCursor::CustomCursor { image, hotspot_x, hotspot_y } = cursor { + let pixmap: qttypes::QPixmap = + crate::qt_window::image_to_pixmap((&image).into(), None).unwrap_or_default(); + cpp! {unsafe [widget_ptr as "QWidget*", pixmap as "QPixmap", hotspot_x as "int", hotspot_y as "int"] { + widget_ptr->setCursor(QCursor{pixmap, hotspot_x, hotspot_y}); + }}; + return; + } + //unidirectional resize cursors are replaced with bidirectional ones let cursor_shape = match cursor { MouseCursor::Default => key_generated::Qt_CursorShape_ArrowCursor, diff --git a/internal/backends/testing/testing_backend.rs b/internal/backends/testing/testing_backend.rs index ef9fe0afcf4..e6c401cd900 100644 --- a/internal/backends/testing/testing_backend.rs +++ b/internal/backends/testing/testing_backend.rs @@ -108,7 +108,7 @@ pub struct TestingWindow { window: i_slint_core::api::Window, size: Cell, pub ime_requests: RefCell>, - pub mouse_cursor: Cell, + pub mouse_cursor: RefCell, } impl WindowAdapterInternal for TestingWindow { @@ -117,7 +117,7 @@ impl WindowAdapterInternal for TestingWindow { } fn set_mouse_cursor(&self, cursor: i_slint_core::items::MouseCursor) { - self.mouse_cursor.set(cursor); + self.mouse_cursor.replace(cursor); } } diff --git a/internal/backends/winit/event_loop.rs b/internal/backends/winit/event_loop.rs index 8b9475cedcd..2329d29336d 100644 --- a/internal/backends/winit/event_loop.rs +++ b/internal/backends/winit/event_loop.rs @@ -9,7 +9,7 @@ */ use crate::EventResult; use crate::drag_resize_window::{handle_cursor_move_for_resize, handle_resize}; -use crate::winitwindowadapter::WindowVisibility; +use crate::winitwindowadapter::{WindowVisibility, WinitWindowAdapter}; use crate::{SharedBackendData, SlintEvent}; use corelib::graphics::euclid; use corelib::input::{KeyEvent, KeyEventType, MouseEvent}; @@ -175,6 +175,8 @@ impl winit::application::ApplicationHandler for EventLoopState { } let runtime_window = WindowInner::from_pub(window.window()); + self.maybe_set_custom_cursor(&window, &event_loop); + match event { WindowEvent::RedrawRequested => { self.loop_error = window.draw().err(); @@ -603,6 +605,20 @@ impl EventLoopState { } } + /// Sets the cursor to a custom source, if it needs to be set. + pub fn maybe_set_custom_cursor( + &self, + window: &WinitWindowAdapter, + event_loop: &ActiveEventLoop, + ) { + // If there is a new custom cursor, update it. + let custom_cursor_source = window.custom_cursor_source.take(); + if let Some(source) = custom_cursor_source { + let custom_cursor = event_loop.create_custom_cursor(source); + window.winit_window().unwrap().set_cursor(custom_cursor); + } + } + /// Runs the event loop and renders the items in the provided `component` in its /// own window. #[cfg(all(not(target_arch = "wasm32"), not(ios_and_friends)))] diff --git a/internal/backends/winit/winitwindowadapter.rs b/internal/backends/winit/winitwindowadapter.rs index 2b24552b000..5fa71a2983b 100644 --- a/internal/backends/winit/winitwindowadapter.rs +++ b/internal/backends/winit/winitwindowadapter.rs @@ -47,7 +47,7 @@ use i_slint_core::{self as corelib}; use std::cell::OnceCell; #[cfg(any(enable_accesskit, muda))] use winit::event_loop::EventLoopProxy; -use winit::window::{WindowAttributes, WindowButtons}; +use winit::window::{CustomCursor, CustomCursorSource, WindowAttributes, WindowButtons}; pub(crate) fn position_to_winit(pos: &corelib::api::WindowPosition) -> winit::dpi::Position { match pos { @@ -368,6 +368,8 @@ pub struct WinitWindowAdapter { window_icon_cache_key: RefCell>, frame_throttle: Box, + + pub(crate) custom_cursor_source: Cell>, } impl WinitWindowAdapter { @@ -416,6 +418,7 @@ impl WinitWindowAdapter { self_weak.clone(), shared_backend_data.is_wayland, ), + custom_cursor_source: Cell::new(None), }); self_rc.shared_backend_data.register_inactive_window((self_rc.clone()) as _); @@ -1324,41 +1327,66 @@ impl WindowAdapter for WinitWindowAdapter { impl WindowAdapterInternal for WinitWindowAdapter { fn set_mouse_cursor(&self, cursor: MouseCursor) { - let winit_cursor = match cursor { - MouseCursor::Default => winit::window::CursorIcon::Default, - MouseCursor::None => winit::window::CursorIcon::Default, - MouseCursor::Help => winit::window::CursorIcon::Help, - MouseCursor::Pointer => winit::window::CursorIcon::Pointer, - MouseCursor::Progress => winit::window::CursorIcon::Progress, - MouseCursor::Wait => winit::window::CursorIcon::Wait, - MouseCursor::Crosshair => winit::window::CursorIcon::Crosshair, - MouseCursor::Text => winit::window::CursorIcon::Text, - MouseCursor::Alias => winit::window::CursorIcon::Alias, - MouseCursor::Copy => winit::window::CursorIcon::Copy, - MouseCursor::Move => winit::window::CursorIcon::Move, - MouseCursor::NoDrop => winit::window::CursorIcon::NoDrop, - MouseCursor::NotAllowed => winit::window::CursorIcon::NotAllowed, - MouseCursor::Grab => winit::window::CursorIcon::Grab, - MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, - MouseCursor::ColResize => winit::window::CursorIcon::ColResize, - MouseCursor::RowResize => winit::window::CursorIcon::RowResize, - MouseCursor::NResize => winit::window::CursorIcon::NResize, - MouseCursor::EResize => winit::window::CursorIcon::EResize, - MouseCursor::SResize => winit::window::CursorIcon::SResize, - MouseCursor::WResize => winit::window::CursorIcon::WResize, - MouseCursor::NeResize => winit::window::CursorIcon::NeResize, - MouseCursor::NwResize => winit::window::CursorIcon::NwResize, - MouseCursor::SeResize => winit::window::CursorIcon::SeResize, - MouseCursor::SwResize => winit::window::CursorIcon::SwResize, - MouseCursor::EwResize => winit::window::CursorIcon::EwResize, - MouseCursor::NsResize => winit::window::CursorIcon::NsResize, - MouseCursor::NeswResize => winit::window::CursorIcon::NeswResize, - MouseCursor::NwseResize => winit::window::CursorIcon::NwseResize, - _ => winit::window::CursorIcon::Default, + let winit_cursor = match &cursor { + MouseCursor::Default => Some(winit::window::CursorIcon::Default), + MouseCursor::None => Some(winit::window::CursorIcon::Default), + MouseCursor::Help => Some(winit::window::CursorIcon::Help), + MouseCursor::Pointer => Some(winit::window::CursorIcon::Pointer), + MouseCursor::Progress => Some(winit::window::CursorIcon::Progress), + MouseCursor::Wait => Some(winit::window::CursorIcon::Wait), + MouseCursor::Crosshair => Some(winit::window::CursorIcon::Crosshair), + MouseCursor::Text => Some(winit::window::CursorIcon::Text), + MouseCursor::Alias => Some(winit::window::CursorIcon::Alias), + MouseCursor::Copy => Some(winit::window::CursorIcon::Copy), + MouseCursor::Move => Some(winit::window::CursorIcon::Move), + MouseCursor::NoDrop => Some(winit::window::CursorIcon::NoDrop), + MouseCursor::NotAllowed => Some(winit::window::CursorIcon::NotAllowed), + MouseCursor::Grab => Some(winit::window::CursorIcon::Grab), + MouseCursor::Grabbing => Some(winit::window::CursorIcon::Grabbing), + MouseCursor::ColResize => Some(winit::window::CursorIcon::ColResize), + MouseCursor::RowResize => Some(winit::window::CursorIcon::RowResize), + MouseCursor::NResize => Some(winit::window::CursorIcon::NResize), + MouseCursor::EResize => Some(winit::window::CursorIcon::EResize), + MouseCursor::SResize => Some(winit::window::CursorIcon::SResize), + MouseCursor::WResize => Some(winit::window::CursorIcon::WResize), + MouseCursor::NeResize => Some(winit::window::CursorIcon::NeResize), + MouseCursor::NwResize => Some(winit::window::CursorIcon::NwResize), + MouseCursor::SeResize => Some(winit::window::CursorIcon::SeResize), + MouseCursor::SwResize => Some(winit::window::CursorIcon::SwResize), + MouseCursor::EwResize => Some(winit::window::CursorIcon::EwResize), + MouseCursor::NsResize => Some(winit::window::CursorIcon::NsResize), + MouseCursor::NeswResize => Some(winit::window::CursorIcon::NeswResize), + MouseCursor::NwseResize => Some(winit::window::CursorIcon::NwseResize), + MouseCursor::CustomCursor { image, hotspot_x, hotspot_y } => { + if let Some(rgba8) = image.to_rgba8() { + let rgba = rgba8 + .as_slice() + .iter() + .map(|c| vec![c.r, c.g, c.b, c.a]) + .flatten() + .collect::>(); + let size = image.size(); + let source = CustomCursor::from_rgba( + rgba, + size.width as u16, + size.height as u16, + *hotspot_x as u16, + *hotspot_y as u16, + ); + + // Custom cursors have to be set during the event loop + self.custom_cursor_source.set(source.ok()); + } + None + } + _ => None, }; 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); + + if let Some(cursor) = winit_cursor { + winit_window.set_cursor(cursor); + } } } diff --git a/internal/common/enums.rs b/internal/common/enums.rs index c7c481f983a..b1f46440c46 100644 --- a/internal/common/enums.rs +++ b/internal/common/enums.rs @@ -187,77 +187,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. - #[non_exhaustive] - 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. #[non_exhaustive] enum ImageFit { diff --git a/internal/compiler/builtin_macros.rs b/internal/compiler/builtin_macros.rs index 60a0530cabc..f3ac3ebae7c 100644 --- a/internal/compiler/builtin_macros.rs +++ b/internal/compiler/builtin_macros.rs @@ -6,7 +6,8 @@ use crate::diagnostics::{BuildDiagnostics, Spanned}; use crate::expression_tree::{ - BuiltinFunction, BuiltinMacroFunction, Callable, EasingCurve, Expression, MinMaxOp, Unit, + BuiltinFunction, BuiltinMacroFunction, Callable, EasingCurve, Expression, ImageReference, + MinMaxOp, MouseCursor, Unit, }; use crate::langtype::Type; use crate::parser::NodeOrToken; @@ -87,6 +88,71 @@ pub fn lower_macro( } BuiltinMacroFunction::Rgb => rgb_macro(n, sub_expr.collect(), diag), BuiltinMacroFunction::Hsv => hsv_macro(n, sub_expr.collect(), diag), + BuiltinMacroFunction::CustomCursor => { + let mut has_error = None; + let image_expected_argument_type_error = + "First argument to custom cursor must be image"; + let hotspot_expected_argument_type_error = + "Last arguments to custom cursor must be number literal"; + let mut image = || match sub_expr.next() { + None => { + has_error.get_or_insert((n.to_source_location(), "Not enough arguments")); + ImageReference::None + } + Some((Expression::ImageReference { resource_ref, .. }, _)) => resource_ref, + Some((e, n)) => { + has_error.get_or_insert(( + n.to_source_location(), + image_expected_argument_type_error, + )); + ImageReference::None + } + }; + let image = image(); + + let mut hotspot_coord = || match sub_expr.next() { + None => { + has_error.get_or_insert((n.to_source_location(), "Not enough arguments")); + 0 + } + Some((Expression::NumberLiteral(val, Unit::None), _)) => val as i32, + // handle negative numbers + Some((Expression::UnaryOp { sub, op: '-' }, n)) => match *sub { + Expression::NumberLiteral(val, Unit::None) => -val as i32, + _ => { + has_error.get_or_insert(( + n.to_source_location(), + hotspot_expected_argument_type_error, + )); + 0 + } + }, + Some((_, n)) => { + has_error.get_or_insert(( + n.to_source_location(), + hotspot_expected_argument_type_error, + )); + 0 + } + }; + + let expr = Expression::MouseCursor(MouseCursor::CustomCursor( + image, + hotspot_coord(), + hotspot_coord(), + )); + if let Some((_, n)) = sub_expr.next() { + has_error.get_or_insert(( + n.to_source_location(), + "Too many arguments for custom cursor", + )); + } + if let Some((n, msg)) = has_error { + diag.push_error(msg.into(), &n); + } + + expr + } } } @@ -348,6 +414,7 @@ fn to_debug_string( | Type::Brush | Type::Image | Type::Easing + | Type::Cursor | Type::StyledText | Type::Array(_) => { Expression::StringLiteral("".into()) diff --git a/internal/compiler/expression_tree.rs b/internal/compiler/expression_tree.rs index 9627240a990..cce3d0bb143 100644 --- a/internal/compiler/expression_tree.rs +++ b/internal/compiler/expression_tree.rs @@ -15,7 +15,6 @@ use smol_str::{SmolStr, format_smolstr}; use std::cell::Cell; use std::collections::HashMap; use std::rc::{Rc, Weak}; - // FIXME remove the pub pub use crate::namedreference::NamedReference; pub use crate::passes::resolving; @@ -144,6 +143,7 @@ pub enum BuiltinMacroFunction { Hsv, /// transform `debug(a, b, c)` into debug `a + " " + b + " " + c` Debug, + CustomCursor, } macro_rules! declare_builtin_function_types { @@ -729,6 +729,8 @@ pub enum Expression { EasingCurve(EasingCurve), + MouseCursor(MouseCursor), + LinearGradient { angle: Box, /// First expression in the tuple is a color, second expression is the stop position @@ -901,6 +903,7 @@ impl Expression { Expression::StoreLocalVariable { .. } => Type::Void, Expression::ReadLocalVariable { ty, .. } => ty.clone(), Expression::EasingCurve(_) => Type::Easing, + Expression::MouseCursor(_) => Type::Cursor, Expression::LinearGradient { .. } => Type::Brush, Expression::RadialGradient { .. } => Type::Brush, Expression::ConicGradient { .. } => Type::Brush, @@ -983,6 +986,7 @@ impl Expression { Expression::StoreLocalVariable { value, .. } => visitor(value), Expression::ReadLocalVariable { .. } => {} Expression::EasingCurve(_) => {} + Expression::MouseCursor(_) => {} Expression::LinearGradient { angle, stops } => { visitor(angle); for (c, s) in stops { @@ -1090,6 +1094,7 @@ impl Expression { Expression::StoreLocalVariable { value, .. } => visitor(value), Expression::ReadLocalVariable { .. } => {} Expression::EasingCurve(_) => {} + Expression::MouseCursor(_) => {} Expression::LinearGradient { angle, stops } => { visitor(angle); for (c, s) in stops { @@ -1197,6 +1202,7 @@ impl Expression { // We only load what we store, and stores are alredy checked Expression::ReadLocalVariable { .. } => true, Expression::EasingCurve(_) => true, + Expression::MouseCursor(_) => true, Expression::LinearGradient { angle, stops } => { angle.is_constant(ga) && stops.iter().all(|(c, s)| c.is_constant(ga) && s.is_constant(ga)) @@ -1454,6 +1460,7 @@ impl Expression { .collect(), }, Type::Easing => Expression::EasingCurve(EasingCurve::default()), + Type::Cursor => Expression::MouseCursor(MouseCursor::default()), Type::Brush => Expression::Cast { from: Box::new(Expression::default_value_for_type(&Type::Color)), to: Type::Brush, @@ -1730,6 +1737,41 @@ pub enum EasingCurve { // Custom(Boxf32>), } +#[derive(Clone, Debug, Default)] +pub enum MouseCursor { + #[default] + Default, + None, + Help, + Pointer, + Progress, + Wait, + Crosshair, + Text, + Alias, + Copy, + Move, + NoDrop, + NotAllowed, + Grab, + Grabbing, + ColResize, + RowResize, + NResize, + EResize, + SResize, + WResize, + NeResize, + NwResize, + SeResize, + SwResize, + EwResize, + NsResize, + NeswResize, + NwseResize, + CustomCursor(ImageReference, i32, i32), +} + // The compiler generates ResourceReference::AbsolutePath for all references like @image-url("foo.png") // and the resource lowering path may change this to EmbeddedData if configured. #[derive(Clone, Debug)] @@ -1846,6 +1888,7 @@ pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std } Expression::PathData(data) => write!(f, "{data:?}"), Expression::EasingCurve(e) => write!(f, "{e:?}"), + Expression::MouseCursor(m) => write!(f, "{m:?}"), Expression::LinearGradient { angle, stops } => { write!(f, "@linear-gradient(")?; pretty_print(f, angle)?; diff --git a/internal/compiler/generator/cpp.rs b/internal/compiler/generator/cpp.rs index 608c7b8d131..886ebc95384 100644 --- a/internal/compiler/generator/cpp.rs +++ b/internal/compiler/generator/cpp.rs @@ -7,7 +7,7 @@ // cSpell:ignore cmath constexpr cstdlib decltype intptr itertools nullptr prepended struc subcomponent uintptr vals use std::collections::HashSet; -use std::fmt::Write; +use std::fmt::{Formatter, Write}; use std::io::BufWriter; use std::sync::OnceLock; @@ -457,7 +457,7 @@ pub mod cpp_ast { } use crate::CompilerConfiguration; -use crate::expression_tree::{BuiltinFunction, EasingCurve, MinMaxOp}; +use crate::expression_tree::{BuiltinFunction, EasingCurve, MinMaxOp, MouseCursor}; use crate::langtype::{ BuiltinPrivateStruct, BuiltinPublicStruct, Enumeration, EnumerationValue, NativeClass, StructName, Type, @@ -559,6 +559,7 @@ impl CppType for Type { Type::ArrayOfU16 => Some("slint::SharedVector".into()), Type::Easing => Some("slint::cbindgen_private::EasingCurve".into()), Type::StyledText => Some("slint::StyledText".into()), + Type::Cursor => Some("slint::cbindgen_private::MouseCursor".into()), _ => None, } } @@ -3260,6 +3261,33 @@ fn native_prop_info<'a, 'b>( (&sub_component.items[*item_index].ty, prop_name) } +impl std::fmt::Display for crate::expression_tree::ImageReference { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + crate::expression_tree::ImageReference::None => write!(f, r#"slint::Image()"#), + crate::expression_tree::ImageReference::AbsolutePath(path) => write!( + f, + r#"slint::Image::load_from_path(slint::SharedString(u8"{}"))"#, + escape_string(path.as_str()) + ), + crate::expression_tree::ImageReference::EmbeddedData { resource_id, extension } => { + let symbol = format!("slint_embedded_resource_{resource_id}"); + write!( + f, + r#"slint::private_api::load_image_from_embedded_data({symbol}, "{}")"#, + escape_string(extension) + ) + } + crate::expression_tree::ImageReference::EmbeddedTexture { resource_id } => { + write!( + f, + "slint::private_api::image_from_embedded_textures(&slint_embedded_resource_{resource_id})" + ) + } + } + } +} + fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String { use llr::Expression; match expr { @@ -3568,35 +3596,14 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String Expression::UnaryOp { sub, op } => { format!("({op} {sub})", sub = compile_expression(sub, ctx), op = op,) } - Expression::ImageReference { resource_ref, nine_slice } => { - let image = match resource_ref { - crate::expression_tree::ImageReference::None => r#"slint::Image()"#.to_string(), - crate::expression_tree::ImageReference::AbsolutePath(path) => format!( - r#"slint::Image::load_from_path(slint::SharedString(u8"{}"))"#, - escape_string(path.as_str()) - ), - crate::expression_tree::ImageReference::EmbeddedData { resource_id, extension } => { - let symbol = format!("slint_embedded_resource_{resource_id}"); - format!( - r#"slint::private_api::load_image_from_embedded_data({symbol}, "{}")"#, - escape_string(extension) - ) - } - crate::expression_tree::ImageReference::EmbeddedTexture { resource_id } => { - format!( - "slint::private_api::image_from_embedded_textures(&slint_embedded_resource_{resource_id})" - ) - } - }; - match &nine_slice { - Some([a, b, c, d]) => { - format!( - "([&] {{ auto image = {image}; image.set_nine_slice_edges({a}, {b}, {c}, {d}); return image; }})()" - ) - } - None => image, + Expression::ImageReference { resource_ref, nine_slice } => match &nine_slice { + Some([a, b, c, d]) => { + format!( + "([&] {{ auto image = {resource_ref}; image.set_nine_slice_edges({a}, {b}, {c}, {d}); return image; }})()" + ) } - } + None => format!("{resource_ref}"), + }, Expression::Condition { condition, true_expr, false_expr } => { let ty = expr.ty(ctx); let cond_code = compile_expression(condition, ctx); @@ -3661,6 +3668,96 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String ) } } + Expression::MouseCursor(MouseCursor::Default) => { + "slint::cbindgen_private::MouseCursor::Tag::Default".into() + } + Expression::MouseCursor(MouseCursor::None) => { + "slint::cbindgen_private::MouseCursor::Tag::None".into() + } + Expression::MouseCursor(MouseCursor::Help) => { + "slint::cbindgen_private::MouseCursor::Tag::Help".into() + } + Expression::MouseCursor(MouseCursor::Pointer) => { + "slint::cbindgen_private::MouseCursor::Tag::Pointer".into() + } + Expression::MouseCursor(MouseCursor::Progress) => { + "slint::cbindgen_private::MouseCursor::Tag::Progress".into() + } + Expression::MouseCursor(MouseCursor::Wait) => { + "slint::cbindgen_private::MouseCursor::Tag::Wait".into() + } + Expression::MouseCursor(MouseCursor::Crosshair) => { + "slint::cbindgen_private::MouseCursor::Tag::Crosshair".into() + } + Expression::MouseCursor(MouseCursor::Text) => { + "slint::cbindgen_private::MouseCursor::Tag::Text".into() + } + Expression::MouseCursor(MouseCursor::Alias) => { + "slint::cbindgen_private::MouseCursor::Tag::Alias".into() + } + Expression::MouseCursor(MouseCursor::Copy) => { + "slint::cbindgen_private::MouseCursor::Tag::Copy".into() + } + Expression::MouseCursor(MouseCursor::Move) => { + "slint::cbindgen_private::MouseCursor::Tag::Move".into() + } + Expression::MouseCursor(MouseCursor::NoDrop) => { + "slint::cbindgen_private::MouseCursor::Tag::NoDrop".into() + } + Expression::MouseCursor(MouseCursor::NotAllowed) => { + "slint::cbindgen_private::MouseCursor::Tag::NotAllowed".into() + } + Expression::MouseCursor(MouseCursor::Grab) => { + "slint::cbindgen_private::MouseCursor::Tag::Grab".into() + } + Expression::MouseCursor(MouseCursor::Grabbing) => { + "slint::cbindgen_private::MouseCursor::Tag::Grabbing".into() + } + Expression::MouseCursor(MouseCursor::ColResize) => { + "slint::cbindgen_private::MouseCursor::Tag::ColResize".into() + } + Expression::MouseCursor(MouseCursor::RowResize) => { + "slint::cbindgen_private::MouseCursor::Tag::RowResize".into() + } + Expression::MouseCursor(MouseCursor::NResize) => { + "slint::cbindgen_private::MouseCursor::Tag::NResize".into() + } + Expression::MouseCursor(MouseCursor::EResize) => { + "slint::cbindgen_private::MouseCursor::Tag::EResize".into() + } + Expression::MouseCursor(MouseCursor::SResize) => { + "slint::cbindgen_private::MouseCursor::Tag::SResize".into() + } + Expression::MouseCursor(MouseCursor::WResize) => { + "slint::cbindgen_private::MouseCursor::Tag::WResize".into() + } + Expression::MouseCursor(MouseCursor::NeResize) => { + "slint::cbindgen_private::MouseCursor::Tag::NeResize".into() + } + Expression::MouseCursor(MouseCursor::NwResize) => { + "slint::cbindgen_private::MouseCursor::Tag::NwResize".into() + } + Expression::MouseCursor(MouseCursor::SeResize) => { + "slint::cbindgen_private::MouseCursor::Tag::SeResize".into() + } + Expression::MouseCursor(MouseCursor::SwResize) => { + "slint::cbindgen_private::MouseCursor::Tag::SwResize".into() + } + Expression::MouseCursor(MouseCursor::EwResize) => { + "slint::cbindgen_private::MouseCursor::Tag::EwResize".into() + } + Expression::MouseCursor(MouseCursor::NsResize) => { + "slint::cbindgen_private::MouseCursor::Tag::NsResize".into() + } + Expression::MouseCursor(MouseCursor::NeswResize) => { + "slint::cbindgen_private::MouseCursor::Tag::NeswResize".into() + } + Expression::MouseCursor(MouseCursor::NwseResize) => { + "slint::cbindgen_private::MouseCursor::Tag::NwseResize".into() + } + Expression::MouseCursor(MouseCursor::CustomCursor(image, hotspot_x, hotspot_y)) => format!( + "slint::cbindgen_private::MouseCursor(slint::cbindgen_private::MouseCursor::Tag::CustomCursor, {image}, {hotspot_x}, {hotspot_y})" + ), Expression::EasingCurve(EasingCurve::Linear) => { "slint::cbindgen_private::EasingCurve()".into() } diff --git a/internal/compiler/generator/rust.rs b/internal/compiler/generator/rust.rs index a6cd357bfdf..17dcea7e524 100644 --- a/internal/compiler/generator/rust.rs +++ b/internal/compiler/generator/rust.rs @@ -13,7 +13,7 @@ Some convention used in the generated code: */ use crate::CompilerConfiguration; -use crate::expression_tree::{BuiltinFunction, EasingCurve, MinMaxOp, OperatorClass}; +use crate::expression_tree::{BuiltinFunction, EasingCurve, MinMaxOp, MouseCursor, OperatorClass}; use crate::langtype::{Enumeration, EnumerationValue, Struct, StructName, Type}; use crate::layout::Orientation; use crate::llr::{ @@ -24,7 +24,7 @@ use crate::object_tree::Document; use crate::typeloader::LibraryInfo; use itertools::Either; use proc_macro2::{Ident, TokenStream, TokenTree}; -use quote::{format_ident, quote}; +use quote::{ToTokens, format_ident, quote}; use smol_str::SmolStr; use std::collections::{BTreeMap, BTreeSet}; use std::str::FromStr; @@ -82,6 +82,7 @@ pub fn rust_primitive_type(ty: &Type) -> Option { Type::String => Some(quote!(sp::SharedString)), Type::Color => Some(quote!(sp::Color)), Type::Easing => Some(quote!(sp::EasingCurve)), + Type::Cursor => Some(quote!(sp::MouseCursor)), Type::ComponentFactory => Some(quote!(slint::ComponentFactory)), Type::Duration => Some(quote!(i64)), Type::Angle => Some(quote!(f32)), @@ -127,6 +128,7 @@ fn rust_property_type(ty: &Type) -> Option { match ty { Type::LogicalLength => Some(quote!(sp::LogicalLength)), Type::Easing => Some(quote!(sp::EasingCurve)), + Type::Cursor => Some(quote!(sp::MouseCursor)), _ => rust_primitive_type(ty), } } @@ -2304,6 +2306,32 @@ fn access_item_rc(pr: &llr::MemberReference, ctx: &EvaluationContext) -> TokenSt quote!(&sp::ItemRc::new(#component_rc_tokens, #item_index_tokens)) } +impl quote::ToTokens for crate::expression_tree::ImageReference { + fn to_tokens(&self, tokens: &mut TokenStream) { + let tks = match self { + crate::expression_tree::ImageReference::None => { + quote!(sp::Image::default()) + } + crate::expression_tree::ImageReference::AbsolutePath(path) => { + let path = path.as_str(); + quote!(sp::Image::load_from_path(::std::path::Path::new(#path)).unwrap_or_default()) + } + crate::expression_tree::ImageReference::EmbeddedData { resource_id, extension } => { + let symbol = format_ident!("SLINT_EMBEDDED_RESOURCE_{}", resource_id); + let format = proc_macro2::Literal::byte_string(extension.as_bytes()); + quote!(sp::load_image_from_embedded_data(#symbol.into(), sp::Slice::from_slice(#format))) + } + crate::expression_tree::ImageReference::EmbeddedTexture { resource_id } => { + let symbol = format_ident!("SLINT_EMBEDDED_RESOURCE_{}", resource_id); + quote!( + sp::Image::from(sp::ImageInner::StaticTextures(&#symbol)) + ) + } + }; + tokens.extend(tks); + } +} + fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream { match expr { Expression::StringLiteral(s) => { @@ -2572,31 +2600,11 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream quote!( (#op #sub) ) } Expression::ImageReference { resource_ref, nine_slice } => { - let image = match resource_ref { - crate::expression_tree::ImageReference::None => { - quote!(sp::Image::default()) - } - crate::expression_tree::ImageReference::AbsolutePath(path) => { - let path = path.as_str(); - quote!(sp::Image::load_from_path(::std::path::Path::new(#path)).unwrap_or_default()) - } - crate::expression_tree::ImageReference::EmbeddedData { resource_id, extension } => { - let symbol = format_ident!("SLINT_EMBEDDED_RESOURCE_{}", resource_id); - let format = proc_macro2::Literal::byte_string(extension.as_bytes()); - quote!(sp::load_image_from_embedded_data(#symbol.into(), sp::Slice::from_slice(#format))) - } - crate::expression_tree::ImageReference::EmbeddedTexture { resource_id } => { - let symbol = format_ident!("SLINT_EMBEDDED_RESOURCE_{}", resource_id); - quote!( - sp::Image::from(sp::ImageInner::StaticTextures(&#symbol)) - ) - } - }; match &nine_slice { Some([a, b, c, d]) => { - quote! {{ let mut image = #image; image.set_nine_slice_edges(#a, #b, #c, #d); image }} + quote! {{ let mut image = #resource_ref; image.set_nine_slice_edges(#a, #b, #c, #d); image }} } - None => image, + None => resource_ref.to_token_stream(), } } Expression::Condition { condition, true_expr, false_expr } => { @@ -2660,6 +2668,96 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream let name = ident(name); quote!(#name.clone()) } + Expression::MouseCursor(MouseCursor::Default) => { + quote!(sp::MouseCursor::Default) + } + Expression::MouseCursor(MouseCursor::None) => { + quote!(sp::MouseCursor::None) + } + Expression::MouseCursor(MouseCursor::Help) => { + quote!(sp::MouseCursor::Help) + } + Expression::MouseCursor(MouseCursor::Pointer) => { + quote!(sp::MouseCursor::Pointer) + } + Expression::MouseCursor(MouseCursor::Progress) => { + quote!(sp::MouseCursor::Progress) + } + Expression::MouseCursor(MouseCursor::Wait) => { + quote!(sp::MouseCursor::Wait) + } + Expression::MouseCursor(MouseCursor::Crosshair) => { + quote!(sp::MouseCursor::Crosshair) + } + Expression::MouseCursor(MouseCursor::Text) => { + quote!(sp::MouseCursor::Text) + } + Expression::MouseCursor(MouseCursor::Alias) => { + quote!(sp::MouseCursor::Alias) + } + Expression::MouseCursor(MouseCursor::Copy) => { + quote!(sp::MouseCursor::Copy) + } + Expression::MouseCursor(MouseCursor::Move) => { + quote!(sp::MouseCursor::Move) + } + Expression::MouseCursor(MouseCursor::NoDrop) => { + quote!(sp::MouseCursor::NoDrop) + } + Expression::MouseCursor(MouseCursor::NotAllowed) => { + quote!(sp::MouseCursor::NotAllowed) + } + Expression::MouseCursor(MouseCursor::Grab) => { + quote!(sp::MouseCursor::Grab) + } + Expression::MouseCursor(MouseCursor::Grabbing) => { + quote!(sp::MouseCursor::Grabbing) + } + Expression::MouseCursor(MouseCursor::ColResize) => { + quote!(sp::MouseCursor::ColResize) + } + Expression::MouseCursor(MouseCursor::RowResize) => { + quote!(sp::MouseCursor::RowResize) + } + Expression::MouseCursor(MouseCursor::NResize) => { + quote!(sp::MouseCursor::NResize) + } + Expression::MouseCursor(MouseCursor::EResize) => { + quote!(sp::MouseCursor::EResize) + } + Expression::MouseCursor(MouseCursor::SResize) => { + quote!(sp::MouseCursor::SResize) + } + Expression::MouseCursor(MouseCursor::WResize) => { + quote!(sp::MouseCursor::WResize) + } + Expression::MouseCursor(MouseCursor::NeResize) => { + quote!(sp::MouseCursor::NeResize) + } + Expression::MouseCursor(MouseCursor::NwResize) => { + quote!(sp::MouseCursor::NwResize) + } + Expression::MouseCursor(MouseCursor::SeResize) => { + quote!(sp::MouseCursor::SeResize) + } + Expression::MouseCursor(MouseCursor::SwResize) => { + quote!(sp::MouseCursor::SwResize) + } + Expression::MouseCursor(MouseCursor::EwResize) => { + quote!(sp::MouseCursor::EwResize) + } + Expression::MouseCursor(MouseCursor::NsResize) => { + quote!(sp::MouseCursor::NsResize) + } + Expression::MouseCursor(MouseCursor::NeswResize) => { + quote!(sp::MouseCursor::NeswResize) + } + Expression::MouseCursor(MouseCursor::NwseResize) => { + quote!(sp::MouseCursor::NwseResize) + } + Expression::MouseCursor(MouseCursor::CustomCursor(image, hotspot_x, hotspot_y)) => { + quote!(sp::MouseCursor::CustomCursor { image: #image, hotspot_y: #hotspot_x, hotspot_x: #hotspot_y }) + } Expression::EasingCurve(EasingCurve::Linear) => { quote!(sp::EasingCurve::Linear) } diff --git a/internal/compiler/langtype.rs b/internal/compiler/langtype.rs index 2a76c0af4a5..460763499eb 100644 --- a/internal/compiler/langtype.rs +++ b/internal/compiler/langtype.rs @@ -68,6 +68,7 @@ pub enum Type { ArrayOfU16, StyledText, + Cursor, } impl core::cmp::PartialEq for Type { @@ -99,6 +100,7 @@ impl core::cmp::PartialEq for Type { Type::Model => matches!(other, Type::Model), Type::PathData => matches!(other, Type::PathData), Type::Easing => matches!(other, Type::Easing), + Type::Cursor => matches!(other, Type::Cursor), Type::Brush => matches!(other, Type::Brush), Type::Array(a) => matches!(other, Type::Array(b) if a == b), Type::Struct(lhs) => { @@ -164,6 +166,7 @@ impl Display for Type { Type::Struct(t) => write!(f, "{t}"), Type::PathData => write!(f, "pathdata"), Type::Easing => write!(f, "easing"), + Type::Cursor => write!(f, "MouseCursor"), Type::Brush => write!(f, "brush"), Type::Enumeration(enumeration) => write!(f, "enum {}", enumeration.name), Type::UnitProduct(vec) => { @@ -215,6 +218,7 @@ impl Type { | Self::Image | Self::Bool | Self::Easing + | Self::Cursor | Self::Enumeration(_) | Self::ElementReference | Self::Struct { .. } @@ -316,6 +320,7 @@ impl Type { Type::Model => None, Type::PathData => None, Type::Easing => None, + Type::Cursor => None, Type::Brush => None, Type::Array(_) => None, Type::Struct { .. } => None, diff --git a/internal/compiler/llr/expression.rs b/internal/compiler/llr/expression.rs index b59a0f5c0f7..748a96c4e0e 100644 --- a/internal/compiler/llr/expression.rs +++ b/internal/compiler/llr/expression.rs @@ -147,6 +147,8 @@ pub enum Expression { EasingCurve(crate::expression_tree::EasingCurve), + MouseCursor(crate::expression_tree::MouseCursor), + LinearGradient { angle: Box, /// First expression in the tuple is a color, second expression is the stop position @@ -263,6 +265,7 @@ impl Expression { .collect::>()?, }, Type::Easing => Expression::EasingCurve(crate::expression_tree::EasingCurve::default()), + Type::Cursor => Expression::MouseCursor(crate::expression_tree::MouseCursor::default()), Type::Brush => Expression::Cast { from: Box::new(Expression::default_value_for_type(&Type::Color)?), to: Type::Brush, @@ -321,6 +324,7 @@ impl Expression { Self::Array { element_ty, .. } => Type::Array(element_ty.clone().into()), Self::Struct { ty, .. } => ty.clone().into(), Self::EasingCurve(_) => Type::Easing, + Self::MouseCursor(_) => Type::Cursor, Self::LinearGradient { .. } => Type::Brush, Self::RadialGradient { .. } => Type::Brush, Self::ConicGradient { .. } => Type::Brush, @@ -382,6 +386,7 @@ macro_rules! visit_impl { Expression::Array { values, .. } => values.$iter().for_each($visitor), Expression::Struct { values, .. } => values.$values().for_each($visitor), Expression::EasingCurve(_) => {} + Expression::MouseCursor(_) => {} Expression::LinearGradient { angle, stops } => { $visitor(angle); for (a, b) in stops { diff --git a/internal/compiler/llr/lower_expression.rs b/internal/compiler/llr/lower_expression.rs index 6d968a29940..9f5c86754c7 100644 --- a/internal/compiler/llr/lower_expression.rs +++ b/internal/compiler/llr/lower_expression.rs @@ -223,6 +223,7 @@ pub fn lower_expression( }, tree_Expression::PathData(data) => compile_path(data, ctx), tree_Expression::EasingCurve(x) => llr_Expression::EasingCurve(x.clone()), + tree_Expression::MouseCursor(x) => llr_Expression::MouseCursor(x.clone()), tree_Expression::LinearGradient { angle, stops } => llr_Expression::LinearGradient { angle: Box::new(lower_expression(angle, ctx)), stops: stops diff --git a/internal/compiler/llr/optim_passes/inline_expressions.rs b/internal/compiler/llr/optim_passes/inline_expressions.rs index 5d17b7b2292..fe966705798 100644 --- a/internal/compiler/llr/optim_passes/inline_expressions.rs +++ b/internal/compiler/llr/optim_passes/inline_expressions.rs @@ -61,6 +61,7 @@ fn expression_cost(exp: &Expression, ctx: &EvaluationContext) -> isize { Expression::Array { .. } => return isize::MAX, Expression::Struct { .. } => 1, Expression::EasingCurve(_) => 1, + Expression::MouseCursor(_) => 1, Expression::LinearGradient { .. } => ALLOC_COST, Expression::RadialGradient { .. } => ALLOC_COST, Expression::ConicGradient { .. } => ALLOC_COST, diff --git a/internal/compiler/llr/pretty_print.rs b/internal/compiler/llr/pretty_print.rs index eaf8c922c1d..ad34e2b601a 100644 --- a/internal/compiler/llr/pretty_print.rs +++ b/internal/compiler/llr/pretty_print.rs @@ -374,6 +374,7 @@ impl<'a, T> Display for DisplayExpression<'a, T> { values.iter().map(|(k, v)| format!("{}: {}", k, e(v))).join(", ") ), Expression::EasingCurve(x) => write!(f, "{x:?}"), + Expression::MouseCursor(x) => write!(f, "{x:?}"), Expression::LinearGradient { angle, stops } => write!( f, "@linear-gradient({}, {})", diff --git a/internal/compiler/lookup.rs b/internal/compiler/lookup.rs index ab5140796d6..b89c51c2a16 100644 --- a/internal/compiler/lookup.rs +++ b/internal/compiler/lookup.rs @@ -7,7 +7,7 @@ use std::rc::Rc; use crate::diagnostics::{BuildDiagnostics, Spanned}; use crate::expression_tree::{ - BuiltinFunction, BuiltinMacroFunction, Callable, EasingCurve, Expression, Unit, + BuiltinFunction, BuiltinMacroFunction, Callable, EasingCurve, Expression, MouseCursor, Unit, }; use crate::langtype::{ElementType, Enumeration, EnumerationValue, Type}; use crate::namedreference::NamedReference; @@ -117,6 +117,7 @@ pub enum LookupResultCallable { pub enum BuiltinNamespace { Colors, Easing, + Cursor, Math, Key, SlintInternal, @@ -198,6 +199,9 @@ impl LookupObject for LookupResult { LookupResult::Namespace(BuiltinNamespace::Easing) => { EasingSpecific.for_each_entry(ctx, f) } + LookupResult::Namespace(BuiltinNamespace::Cursor) => { + CursorSpecific.for_each_entry(ctx, f) + } LookupResult::Namespace(BuiltinNamespace::Math) => MathFunctions.for_each_entry(ctx, f), LookupResult::Namespace(BuiltinNamespace::Key) => KeysLookup.for_each_entry(ctx, f), LookupResult::Namespace(BuiltinNamespace::SlintInternal) => { @@ -215,6 +219,7 @@ impl LookupObject for LookupResult { (ColorSpecific, ColorFunctions).lookup(ctx, name) } LookupResult::Namespace(BuiltinNamespace::Easing) => EasingSpecific.lookup(ctx, name), + LookupResult::Namespace(BuiltinNamespace::Cursor) => CursorSpecific.lookup(ctx, name), LookupResult::Namespace(BuiltinNamespace::Math) => MathFunctions.lookup(ctx, name), LookupResult::Namespace(BuiltinNamespace::Key) => KeysLookup.lookup(ctx, name), LookupResult::Namespace(BuiltinNamespace::SlintInternal) => { @@ -591,6 +596,7 @@ impl LookupObject for ReturnTypeSpecificLookup { Type::Color => ColorSpecific.for_each_entry(ctx, f), Type::Brush => ColorSpecific.for_each_entry(ctx, f), Type::Easing => EasingSpecific.for_each_entry(ctx, f), + Type::Cursor => CursorSpecific.for_each_entry(ctx, f), Type::Enumeration(enumeration) => enumeration.clone().for_each_entry(ctx, f), _ => None, } @@ -601,6 +607,7 @@ impl LookupObject for ReturnTypeSpecificLookup { Type::Color => ColorSpecific.lookup(ctx, name), Type::Brush => ColorSpecific.lookup(ctx, name), Type::Easing => EasingSpecific.lookup(ctx, name), + Type::Cursor => CursorSpecific.lookup(ctx, name), Type::Enumeration(enumeration) => enumeration.clone().lookup(ctx, name), _ => None, } @@ -705,6 +712,48 @@ impl LookupObject for EasingSpecific { } } +struct CursorSpecific; +impl LookupObject for CursorSpecific { + fn for_each_entry( + &self, + _ctx: &LookupCtx, + f: &mut impl FnMut(&SmolStr, LookupResult) -> Option, + ) -> Option { + let mut curve = |n, e| f(&SmolStr::new_static(n), Expression::MouseCursor(e).into()); + let r = None + .or_else(|| curve("default", MouseCursor::Default)) + .or_else(|| curve("none", MouseCursor::None)) + .or_else(|| curve("help", MouseCursor::Help)) + .or_else(|| curve("pointer", MouseCursor::Pointer)) + .or_else(|| curve("progress", MouseCursor::Progress)) + .or_else(|| curve("wait", MouseCursor::Wait)) + .or_else(|| curve("crosshair", MouseCursor::Crosshair)) + .or_else(|| curve("text", MouseCursor::Text)) + .or_else(|| curve("alias", MouseCursor::Alias)) + .or_else(|| curve("copy", MouseCursor::Copy)) + .or_else(|| curve("move", MouseCursor::Move)) + .or_else(|| curve("no-drop", MouseCursor::NoDrop)) + .or_else(|| curve("not-allowed", MouseCursor::NotAllowed)) + .or_else(|| curve("grab", MouseCursor::Grab)) + .or_else(|| curve("grabbing", MouseCursor::Grabbing)) + .or_else(|| curve("col-resize", MouseCursor::ColResize)) + .or_else(|| curve("row-resize", MouseCursor::RowResize)) + .or_else(|| curve("n-resize", MouseCursor::NResize)) + .or_else(|| curve("e-resize", MouseCursor::EResize)) + .or_else(|| curve("s-resize", MouseCursor::SResize)) + .or_else(|| curve("w-resize", MouseCursor::WResize)) + .or_else(|| curve("ne-resize", MouseCursor::NeResize)) + .or_else(|| curve("nw-resize", MouseCursor::NwResize)) + .or_else(|| curve("se-resize", MouseCursor::SeResize)) + .or_else(|| curve("sw-resize", MouseCursor::SwResize)) + .or_else(|| curve("ew-resize", MouseCursor::EwResize)) + .or_else(|| curve("ns-resize", MouseCursor::NsResize)) + .or_else(|| curve("nesw-resize", MouseCursor::NeswResize)) + .or_else(|| curve("nwse-resize", MouseCursor::NwseResize)); + r.or_else(|| f(&SmolStr::new_static("custom"), BuiltinMacroFunction::CustomCursor.into())) + } +} + impl LookupObject for Rc { fn for_each_entry( &self, @@ -848,6 +897,7 @@ impl LookupObject for BuiltinNamespaceLookup { let mut f = |s, res| f(&SmolStr::new_static(s), res); None.or_else(|| f("Colors", LookupResult::Namespace(BuiltinNamespace::Colors))) .or_else(|| f("Easing", LookupResult::Namespace(BuiltinNamespace::Easing))) + .or_else(|| f("MouseCursor", LookupResult::Namespace(BuiltinNamespace::Cursor))) .or_else(|| f("Math", LookupResult::Namespace(BuiltinNamespace::Math))) .or_else(|| f("Key", LookupResult::Namespace(BuiltinNamespace::Key))) .or_else(|| { diff --git a/internal/compiler/passes/resolving/remove_noop.rs b/internal/compiler/passes/resolving/remove_noop.rs index 147d1c5cb7f..1a3a4e7b96f 100644 --- a/internal/compiler/passes/resolving/remove_noop.rs +++ b/internal/compiler/passes/resolving/remove_noop.rs @@ -82,6 +82,7 @@ fn without_side_effects(expression: &Expression) -> bool { Expression::Struct { ty: _, values } => values.values().all(without_side_effects), Expression::PathData(_) => true, Expression::EasingCurve(_) => true, + Expression::MouseCursor(_) => true, Expression::LinearGradient { angle, stops } => { without_side_effects(angle) && stops diff --git a/internal/compiler/tests/syntax/elements/toucharea.slint b/internal/compiler/tests/syntax/elements/toucharea.slint new file mode 100644 index 00000000000..9e2b59e3402 --- /dev/null +++ b/internal/compiler/tests/syntax/elements/toucharea.slint @@ -0,0 +1,66 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +export component Def { + // Unnamespaced access + TouchArea { + mouse-cursor: ew-resize; + } + + // Namespaced access + TouchArea { + mouse-cursor: MouseCursor.ew-resize; + } + + // Custom cursor macro + TouchArea { + mouse-cursor: MouseCursor.custom(@image-url("cursor.png"), 0, 0); + } + + // Custom cursor macro (without namespace) + TouchArea { + mouse-cursor: custom(@image-url("cursor.png"), 0, 0); + } + + // Invalid unnamespaced access + TouchArea { + mouse-cursor: square-resize; +// > +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +/// 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] +#[repr(C, u32)] +#[derive(Debug, Clone, PartialEq, Default)] +pub enum MouseCursor { + /// The systems default cursor. + #[default] + 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, + /// Custom cursor from an `Image`. + CustomCursor { + /// Image backing for this cursor. + image: crate::graphics::Image, + /// Hotspot X. + hotspot_x: i32, + /// Hotspot Y. + hotspot_y: i32, + }, +} diff --git a/internal/core/rtti.rs b/internal/core/rtti.rs index b9db495a0c3..807ef3841a1 100644 --- a/internal/core/rtti.rs +++ b/internal/core/rtti.rs @@ -55,6 +55,7 @@ macro_rules! declare_ValueType_2 { crate::items::DropEvent, crate::model::ModelRc, crate::api::StyledText, + crate::items::MouseCursor, $(crate::items::$Name,)* ]; }; diff --git a/internal/interpreter/api.rs b/internal/interpreter/api.rs index 05075d3fb17..6b5f412b7df 100644 --- a/internal/interpreter/api.rs +++ b/internal/interpreter/api.rs @@ -134,6 +134,9 @@ pub enum Value { StyledText(i_slint_core::api::StyledText) = 13, #[doc(hidden)] ArrayOfU16(SharedVector) = 14, + #[doc(hidden)] + /// A mouse cursor. + MouseCursor(i_slint_core::items::MouseCursor) = 15, } impl Value { @@ -183,6 +186,7 @@ impl PartialEq for Value { Value::StyledText(lhs) => { matches!(other, Value::StyledText(rhs) if lhs == rhs) } + Value::MouseCursor(lhs) => matches!(other, Value::MouseCursor(rhs) if lhs == rhs), } } } @@ -211,6 +215,7 @@ impl std::fmt::Debug for Value { Value::ArrayOfU16(data) => { write!(f, "Value::ArrayOfU16({data:?})") } + Value::MouseCursor(m) => write!(f, "Value::MouseCursor({m:?})"), } } } @@ -251,6 +256,7 @@ declare_value_conversion!(Struct => [Struct] ); declare_value_conversion!(Brush => [Brush] ); declare_value_conversion!(PathData => [PathData]); declare_value_conversion!(EasingCurve => [i_slint_core::animations::EasingCurve]); +declare_value_conversion!(MouseCursor => [i_slint_core::items::MouseCursor]); declare_value_conversion!(LayoutCache => [SharedVector] ); declare_value_conversion!(ComponentFactory => [ComponentFactory] ); declare_value_conversion!(StyledText => [i_slint_core::api::StyledText] ); diff --git a/internal/interpreter/dynamic_item_tree.rs b/internal/interpreter/dynamic_item_tree.rs index 9d1ad70de8e..c7ca4ab104f 100644 --- a/internal/interpreter/dynamic_item_tree.rs +++ b/internal/interpreter/dynamic_item_tree.rs @@ -1242,6 +1242,7 @@ pub(crate) fn generate_item_tree<'id>( Type::Struct(_) => property_info::(), Type::Array(_) => property_info::(), Type::Easing => property_info::(), + Type::Cursor => property_info::(), Type::Percent => animated_property_info::(), Type::Enumeration(e) => { macro_rules! match_enum_type { diff --git a/internal/interpreter/eval.rs b/internal/interpreter/eval.rs index a5dc272dda1..58933a82489 100644 --- a/internal/interpreter/eval.rs +++ b/internal/interpreter/eval.rs @@ -15,8 +15,8 @@ use corelib::rtti::AnimatedBindingKind; use corelib::window::WindowInner; use corelib::{Brush, Color, PathData, SharedString, SharedVector}; use i_slint_compiler::expression_tree::{ - BuiltinFunction, Callable, EasingCurve, Expression, MinMaxOp, Path as ExprPath, - PathElement as ExprPathElement, + BuiltinFunction, Callable, EasingCurve, Expression, ImageReference, MinMaxOp, MouseCursor, + Path as ExprPath, PathElement as ExprPathElement, }; use i_slint_compiler::langtype::Type; use i_slint_compiler::namedreference::NamedReference; @@ -411,6 +411,54 @@ pub fn eval_expression(expression: &Expression, local_context: &mut EvalLocalCon corelib::animations::EasingCurve::CubicBezier([*a, *b, *c, *d]) } }), + Expression::MouseCursor(cursor) => Value::MouseCursor(match cursor { + MouseCursor::Default => corelib::items::MouseCursor::Default, + MouseCursor::None => corelib::items::MouseCursor::None, + MouseCursor::Help => corelib::items::MouseCursor::Help, + MouseCursor::Pointer => corelib::items::MouseCursor::Pointer, + MouseCursor::Progress => corelib::items::MouseCursor::Progress, + MouseCursor::Wait => corelib::items::MouseCursor::Wait, + MouseCursor::Crosshair => corelib::items::MouseCursor::Crosshair, + MouseCursor::Text => corelib::items::MouseCursor::Text, + MouseCursor::Alias => corelib::items::MouseCursor::Alias, + MouseCursor::Copy => corelib::items::MouseCursor::Copy, + MouseCursor::Move => corelib::items::MouseCursor::Move, + MouseCursor::NoDrop => corelib::items::MouseCursor::NoDrop, + MouseCursor::NotAllowed => corelib::items::MouseCursor::NotAllowed, + MouseCursor::Grab => corelib::items::MouseCursor::Grab, + MouseCursor::Grabbing => corelib::items::MouseCursor::Grabbing, + MouseCursor::ColResize => corelib::items::MouseCursor::ColResize, + MouseCursor::RowResize => corelib::items::MouseCursor::RowResize, + MouseCursor::NResize => corelib::items::MouseCursor::NResize, + MouseCursor::EResize => corelib::items::MouseCursor::EResize, + MouseCursor::SResize => corelib::items::MouseCursor::SResize, + MouseCursor::WResize => corelib::items::MouseCursor::WResize, + MouseCursor::NeResize => corelib::items::MouseCursor::NeResize, + MouseCursor::NwResize => corelib::items::MouseCursor::NwResize, + MouseCursor::SeResize => corelib::items::MouseCursor::SeResize, + MouseCursor::SwResize => corelib::items::MouseCursor::SwResize, + MouseCursor::EwResize => corelib::items::MouseCursor::EwResize, + MouseCursor::NsResize => corelib::items::MouseCursor::NsResize, + MouseCursor::NeswResize => corelib::items::MouseCursor::NeswResize, + MouseCursor::NwseResize => corelib::items::MouseCursor::NwseResize, + MouseCursor::CustomCursor(image, hotspot_x, hotspot_y) => { + let image = match image { + ImageReference::None => i_slint_core::graphics::Image::default(), + ImageReference::AbsolutePath(path) => { + i_slint_core::graphics::Image::load_from_path(std::path::Path::new(path)) + .unwrap_or_default() + } + ImageReference::EmbeddedData { .. } => todo!(), + ImageReference::EmbeddedTexture { .. } => todo!(), + }; + + corelib::items::MouseCursor::CustomCursor { + image, + hotspot_x: *hotspot_x, + hotspot_y: *hotspot_y, + } + } + }), Expression::LinearGradient { angle, stops } => { let angle = eval_expression(angle, local_context); Value::Brush(Brush::LinearGradient(LinearGradientBrush::new( @@ -1836,6 +1884,7 @@ fn check_value_type(value: &mut Value, ty: &Type) -> bool { } Type::PathData => matches!(value, Value::PathData(_)), Type::Easing => matches!(value, Value::EasingCurve(_)), + Type::Cursor => matches!(value, Value::MouseCursor(_)), Type::Brush => matches!(value, Value::Brush(_)), Type::Array(inner) => { matches!(value, Value::Model(m) if m.iter().all(|mut v| check_value_type(&mut v, inner))) @@ -2150,6 +2199,7 @@ pub fn default_value_for_type(ty: &Type) -> Value { e.values.get(e.default_value).unwrap().to_string(), ), Type::Easing => Value::EasingCurve(Default::default()), + Type::Cursor => Value::MouseCursor(Default::default()), Type::Void | Type::Invalid => Value::Void, Type::UnitProduct(_) => Value::Number(0.), Type::PathData => Value::PathData(Default::default()), diff --git a/tests/cases/elements/event_rotation.slint b/tests/cases/elements/event_rotation.slint index 376edd690b8..d7a2c7cb7b5 100644 --- a/tests/cases/elements/event_rotation.slint +++ b/tests/cases/elements/event_rotation.slint @@ -202,7 +202,7 @@ let double_click = |x, y| { slint_testing::send_mouse_click(&instance, x, y); }; -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default); // Test clicks on end of rotated child rectangle of rotated rectangle. // Click locations come from a screenshot diff --git a/tests/cases/elements/event_scaling.slint b/tests/cases/elements/event_scaling.slint index 5829a595de2..08f062f2847 100644 --- a/tests/cases/elements/event_scaling.slint +++ b/tests/cases/elements/event_scaling.slint @@ -89,7 +89,7 @@ let instance = TestCase::new().unwrap(); slint_testing::send_mouse_click(&instance, x, y); };*/ -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default); assert_eq!(instance.get_touch1(), 0); slint_testing::send_mouse_click(&instance, 1.0,1.0); assert_eq!(instance.get_touch1(), 1); diff --git a/tests/cases/elements/popupwindow_cursor.slint b/tests/cases/elements/popupwindow_cursor.slint index 3a00285b3cb..ce172400351 100644 --- a/tests/cases/elements/popupwindow_cursor.slint +++ b/tests/cases/elements/popupwindow_cursor.slint @@ -37,26 +37,26 @@ use slint::{platform::WindowEvent, LogicalPosition}; use slint::private_unstable_api::re_exports::MouseCursor; let instance = TestCase::new().unwrap(); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default); instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(35.0, 35.0) }); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Help); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Help); slint_testing::send_mouse_click(&instance, 35., 35.); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default); instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(135.0, 35.0) }); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::NotAllowed); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::NotAllowed); instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(35.0, 35.0) }); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default); // Close the popup slint_testing::send_mouse_click(&instance, 135., 35.); // FIXME: it takes two events to get that correctly -// assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Help); +// assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Help); instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(135.0, 35.0) }); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Help); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Help); ``` */ diff --git a/tests/cases/elements/toucharea.slint b/tests/cases/elements/toucharea.slint index 4bced4ee6b7..c736a16db79 100644 --- a/tests/cases/elements/toucharea.slint +++ b/tests/cases/elements/toucharea.slint @@ -132,35 +132,35 @@ use slint::{platform::WindowEvent, platform::PointerEventButton, platform::Key, use slint::private_unstable_api::re_exports::MouseCursor; let instance = TestCase::new().unwrap(); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default); // does not click on anything slint_testing::send_mouse_click(&instance, 5., 5.); assert_eq!(instance.get_touch1(), 0); assert_eq!(instance.get_touch2(), 0); assert_eq!(instance.get_touch3(), 0); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default); // click on second one slint_testing::send_mouse_click(&instance, 101., 101.); assert_eq!(instance.get_touch1(), 0); assert_eq!(instance.get_touch2(), 1); assert_eq!(instance.get_touch3(), 0); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Pointer); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Pointer); // click on first one only slint_testing::send_mouse_click(&instance, 108., 108.); assert_eq!(instance.get_touch1(), 1); assert_eq!(instance.get_touch2(), 1); assert_eq!(instance.get_touch3(), 0); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Move); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Move); // click on the third slint_testing::send_mouse_click(&instance, 106., 103.); assert_eq!(instance.get_touch1(), 1); assert_eq!(instance.get_touch2(), 1); assert_eq!(instance.get_touch3(), 1); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default); // The final moveother is added by the grab handler! assert_eq!(instance.get_pointer_event_test().as_str(), "moveotherdownleftclickupleftmoveother"); @@ -190,7 +190,7 @@ use slint::{platform::WindowEvent, platform::PointerEventButton, LogicalPosition use slint::private_unstable_api::re_exports::MouseCursor; let instance = TestCase::new().unwrap(); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default); // press on second one instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(102.0, 102.0) }); diff --git a/tests/cases/elements/toucharea_doubleclick.slint b/tests/cases/elements/toucharea_doubleclick.slint index c899cabf67c..29a53ba9ba2 100644 --- a/tests/cases/elements/toucharea_doubleclick.slint +++ b/tests/cases/elements/toucharea_doubleclick.slint @@ -259,7 +259,7 @@ let double_click = |x, y| { }; -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default, +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default, "Unexpected mousecursor at start"); // does not click on anything @@ -271,7 +271,7 @@ assert_eq!(instance.get_touch3(), 0, "Mis-click registered at touch3"); assert_eq!(instance.get_touch_double1(), 0, "Mis-click registered at touch1 as double-click"); assert_eq!(instance.get_touch_double2(), 0, "Mis-click registered at touch2 as double-click"); assert_eq!(instance.get_touch_double3(), 0, "Mis-click registered at touch3 as double-click"); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default, +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default, "Mis-click changed mouse cursor"); // click on second one @@ -284,7 +284,7 @@ assert_eq!(instance.get_touch3(), 0, "Click on 2 registered at touch3"); assert_eq!(instance.get_touch_double1(), 0, "Click on 2 registered at touch1 as double-click"); assert_eq!(instance.get_touch_double2(), 0, "Click on 2 registered at touch2 as double-click"); assert_eq!(instance.get_touch_double3(), 0, "Click on 2 registered at touch3 as double-click"); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Pointer, +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Pointer, "Click on 1 did not change mouse pointer"); // click on first one only @@ -296,7 +296,7 @@ assert_eq!(instance.get_touch3(), 0, "Click on 1 registered at touch3"); assert_eq!(instance.get_touch_double1(), 0, "Click on 1 registered at touch1 as double-click"); assert_eq!(instance.get_touch_double2(), 0, "Click on 1 registered at touch2 as double-click"); assert_eq!(instance.get_touch_double3(), 0, "Click on 1 registered at touch3 as double-click"); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Move, +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Move, "Click on 2 did not change mouse pointer"); // click on the third @@ -310,7 +310,7 @@ assert_eq!(instance.get_touch_double2(), 0, "Click on 3 registered at touch2 as assert_eq!(instance.get_touch_double3(), 0, "Click on 3 registered at touch3 as double-click"); assert_eq!(instance.get_pointer_event_test().as_str(), "move.other:down.left:click:up.left:move.other:", "Click on 3 produced an unexpected sequence of events"); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default, +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default, "Click on 3 did not change mouse pointer"); // does not double-click on anything @@ -322,7 +322,7 @@ assert_eq!(instance.get_touch3(), 1, "Mis-double-click registered at touch3"); assert_eq!(instance.get_touch_double1(), 0, "Mis-double-click registered at touch1 as double-click"); assert_eq!(instance.get_touch_double2(), 0, "Mis-double-click registered at touch2 as double-click"); assert_eq!(instance.get_touch_double3(), 0, "Mis-double-click registered at touch3 as double-click"); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default, +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default, "Mis-double-click on 3 did not change mouse pointer"); // double-click on second one @@ -337,7 +337,7 @@ assert_eq!(instance.get_touch_double2(), 1, "Double-click on 2 did not register assert_eq!(instance.get_touch_double3(), 0, "Double-click on 2 registered at touch1 as double-click"); assert_eq!(instance.get_pointer_event_test().as_str(), "move.other:down.left:click:up.left:move.other:move.other:down.left:click:double_click:up.left:move.other:", "Double-click on 2 produced an unexpected sequence of events"); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Pointer, +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Pointer, "Double-click on 2 did not change mouse pointer"); // double-click on first one only @@ -349,7 +349,7 @@ assert_eq!(instance.get_touch3(), 1, "Double-click on 1 registered at touch3"); assert_eq!(instance.get_touch_double1(), 1, "Double-click on 1 did not register at touch1 as double-click"); assert_eq!(instance.get_touch_double2(), 1, "Double-click on 1 registered at touch2 as double-click"); assert_eq!(instance.get_touch_double3(), 0, "Double-click on 1 registered at touch3 as double-click"); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Move, +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Move, "Double-click on 1 did not change mouse pointer"); // double-click on the third @@ -361,7 +361,7 @@ assert_eq!(instance.get_touch3(), 3, "Double-click on 3 did not registered at to assert_eq!(instance.get_touch_double1(), 1, "Double-click on 3 registered at touch1 as double-click"); assert_eq!(instance.get_touch_double2(), 1, "Double-click on 3 registered at touch2 as double-click"); assert_eq!(instance.get_touch_double3(), 1, "Double-click on 3 did not register at touch3 as double-click"); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default, +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default, "Double-click on 3 did not change mouse pointer"); // triple-click on the third (treated as a double click, followed by a single click) @@ -381,7 +381,7 @@ assert_eq!(instance.get_touch3(), 6, "Triple-click on 3 registered at touch1"); assert_eq!(instance.get_touch_double1(), 1, "Triple-click on 3 registered at touch1"); assert_eq!(instance.get_touch_double2(), 1, "Triple-click on 3 registered at touch2"); assert_eq!(instance.get_touch_double3(), 2, "Triple-click on 3 did not register at touch3 as double-click"); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default, +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default, "Triple-click on 3 did not change mouse pointer"); // click really quickly on two different mouse areas @@ -398,7 +398,7 @@ assert_eq!(instance.get_touch3(), 6, "click on different touch areas registered assert_eq!(instance.get_touch_double1(), 1, "click on different touch areas registered on touch1 as double-click"); assert_eq!(instance.get_touch_double2(), 1, "click on different touch areas registered on touch2 as double-click"); assert_eq!(instance.get_touch_double3(), 2, "click on different touch areas registered on touch3 as double-click"); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default, +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default, "click on different touch areas changed mouse pointer"); assert_eq!(instance.get_pointer_event_test().as_str(), "move.other:down.left:click:up.left:move.other:", "click on different touch areas produced an unexpected sequence of events"); @@ -417,7 +417,7 @@ assert_eq!(instance.get_touch3(), 6, "Slow double click did not register on touc assert_eq!(instance.get_touch_double1(), 1, "Slow double click registered on touch1 as double-click"); assert_eq!(instance.get_touch_double2(), 1, "Slow double click registered on touch2 as double-click"); assert_eq!(instance.get_touch_double3(), 2, "Slow double click registered on touch3 as double-click"); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default, +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default, "click on different touch areas changed mouse pointer"); assert_eq!(instance.get_pointer_event_test().as_str(), "move.other:down.left:click:up.left:move.other:move.other:down.left:click:up.left:move.other:", "click on different touch areas produced an unexpected sequence of events"); diff --git a/tests/cases/focus/focus_change.slint b/tests/cases/focus/focus_change.slint index 00e636a5c6a..2f20328692e 100644 --- a/tests/cases/focus/focus_change.slint +++ b/tests/cases/focus/focus_change.slint @@ -48,7 +48,7 @@ let mut ime_requests = slint_testing::access_testing_window(instance.window(), | assert!(matches!(ime_requests.next(), Some(InputMethodRequest::Enable(props)) if props.input_type == InputType::Text)); assert!(matches!(ime_requests.next(), Some(InputMethodRequest::Update(..)))); assert!(ime_requests.next().is_none()); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Text); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Text); slint_testing::send_keyboard_string_sequence(&instance, "Only for field 1"); assert_eq!(instance.get_input1_text(), "Only for field 1"); @@ -77,7 +77,7 @@ assert!(instance.get_input3_focused()); let mut ime_requests = slint_testing::access_testing_window(instance.window(), |window| window.ime_requests.take()).into_iter(); assert!(matches!(ime_requests.next(), Some(InputMethodRequest::Disable))); assert!(ime_requests.next().is_none()); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Text); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Text); ``` ```cpp diff --git a/tests/cases/issues/issue_2717_has-hover.slint b/tests/cases/issues/issue_2717_has-hover.slint index a6efee9eb1e..8a711cabbed 100644 --- a/tests/cases/issues/issue_2717_has-hover.slint +++ b/tests/cases/issues/issue_2717_has-hover.slint @@ -49,45 +49,45 @@ let instance = TestCase::new().unwrap(); assert!(!instance.get_has_hover1()); assert!(!instance.get_has_hover2()); assert!(!instance.get_has_hover3()); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default); instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(50.0, 50.0) }); assert!(!instance.get_has_hover1()); assert!(!instance.get_has_hover2()); assert!(!instance.get_has_hover3()); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default); instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(240.0, 150.0) }); assert!(instance.get_has_hover1()); assert!(!instance.get_has_hover2()); assert!(!instance.get_has_hover3()); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Copy); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Copy); instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(290.0, 150.0) }); // We Since the touch area are not children, only one is active assert!(!instance.get_has_hover1()); assert!(instance.get_has_hover2()); assert!(!instance.get_has_hover3()); //FIXME: it currently takes two events for the mouse cursor to change when going from one MouseArea to another -//assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Alias); +//assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Alias); instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(330.0, 150.0) }); assert!(!instance.get_has_hover1()); assert!(instance.get_has_hover2()); assert!(!instance.get_has_hover3()); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Alias); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Alias); instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(370.0, 150.0) }); assert!(!instance.get_has_hover1()); // here 2 and 3 are both active since one is a children of the other assert!(instance.get_has_hover2()); assert!(instance.get_has_hover3()); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default); instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(390.0, 150.0) }); assert!(!instance.get_has_hover1()); assert!(!instance.get_has_hover2()); assert!(instance.get_has_hover3()); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default); instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(510.0, 150.0) }); assert!(!instance.get_has_hover1()); assert!(!instance.get_has_hover2()); assert!(!instance.get_has_hover3()); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default); // Now grab let button = PointerEventButton::Left; @@ -95,37 +95,37 @@ instance.window().dispatch_event(WindowEvent::PointerPressed { position: Logical assert!(instance.get_has_hover1()); assert!(!instance.get_has_hover2()); assert!(!instance.get_has_hover3()); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Copy); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Copy); instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(290.0, 150.0) }); assert!(instance.get_has_hover1()); assert!(!instance.get_has_hover2()); assert!(!instance.get_has_hover3()); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Copy); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Copy); instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(330.0, 150.0) }); assert!(instance.get_has_hover1()); assert!(!instance.get_has_hover2()); assert!(!instance.get_has_hover3()); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Copy); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Copy); instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(370.0, 150.0) }); assert!(instance.get_has_hover1()); assert!(!instance.get_has_hover2()); assert!(!instance.get_has_hover3()); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Copy); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Copy); instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(390.0, 150.0) }); assert!(instance.get_has_hover1()); assert!(!instance.get_has_hover2()); assert!(!instance.get_has_hover3()); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Copy); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Copy); instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(510.0, 150.0) }); assert!(instance.get_has_hover1()); assert!(!instance.get_has_hover2()); assert!(!instance.get_has_hover3()); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Copy); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Copy); instance.window().dispatch_event(WindowEvent::PointerReleased { position: LogicalPosition::new(370.0, 150.0), button }); assert!(!instance.get_has_hover1()); assert!(instance.get_has_hover2()); assert!(instance.get_has_hover3()); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default); ``` */ diff --git a/tests/cases/widgets/textedit.slint b/tests/cases/widgets/textedit.slint index 826151a54fb..0135292a54a 100644 --- a/tests/cases/widgets/textedit.slint +++ b/tests/cases/widgets/textedit.slint @@ -51,13 +51,13 @@ assert_eq!(edits.borrow().clone(), vec!["h", "he", "hel", "hell", "hello"]); // Test mouse cursor for issue 6444 use slint::{LogicalPosition, platform::{WindowEvent, PointerEventButton}}; use slint::private_unstable_api::re_exports::MouseCursor; -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Text, "after previous click"); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Text, "after previous click"); instance.window().dispatch_event(WindowEvent::PointerPressed { position: LogicalPosition::new(50.0, 50.0), button: PointerEventButton::Middle }); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Text, "Middle button pressed"); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Text, "Middle button pressed"); instance.window().dispatch_event(WindowEvent::PointerReleased { position: LogicalPosition::new(50.0, 50.0), button: PointerEventButton::Middle }); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Text, "Middle button released"); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Text, "Middle button released"); instance.window().dispatch_event(WindowEvent::PointerExited { }); -assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.get()), MouseCursor::Default); +assert_eq!(slint_testing::access_testing_window(instance.window(), |window| window.mouse_cursor.borrow().clone()), MouseCursor::Default); // test page up/down for x in 0..10 { diff --git a/tools/docsnapper/headless.rs b/tools/docsnapper/headless.rs index a40cf5bf986..12199435c60 100644 --- a/tools/docsnapper/headless.rs +++ b/tools/docsnapper/headless.rs @@ -97,7 +97,7 @@ pub struct HeadlessWindow { window: i_slint_core::api::Window, size: Cell, pub ime_requests: RefCell>, - pub mouse_cursor: Cell, + pub mouse_cursor: RefCell, renderer: SkiaRenderer, } @@ -107,7 +107,7 @@ impl WindowAdapterInternal for HeadlessWindow { } fn set_mouse_cursor(&self, cursor: i_slint_core::items::MouseCursor) { - self.mouse_cursor.set(cursor); + self.mouse_cursor.replace(cursor); } }