This commit is contained in:
Joshua Goins 2025-12-22 09:51:21 +01:00 committed by GitHub
commit 55cd440891
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 773 additions and 217 deletions

View file

@ -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)

View file

@ -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 {

View file

@ -294,7 +294,7 @@ pub fn to_value(env: &Env, unknown: JsUnknown, typ: &Type) -> Result<Value> {
| Type::LayoutCache
| Type::ArrayOfU16
| Type::ElementReference
| Type::StyledText => Err(napi::Error::from_reason("reason")),
| Type::StyledText => Err(napi::Error::from_reason("reason")) | Type::Cursor,
}
}

View file

@ -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,

View file

@ -108,7 +108,7 @@ pub struct TestingWindow {
window: i_slint_core::api::Window,
size: Cell<PhysicalSize>,
pub ime_requests: RefCell<Vec<InputMethodRequest>>,
pub mouse_cursor: Cell<i_slint_core::items::MouseCursor>,
pub mouse_cursor: RefCell<i_slint_core::items::MouseCursor>,
}
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);
}
}

View file

@ -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<SlintEvent> 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)))]

View file

@ -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<Option<ImageCacheKey>>,
frame_throttle: Box<dyn crate::frame_throttle::FrameThrottle>,
pub(crate) custom_cursor_source: Cell<Option<CustomCursorSource>>,
}
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_vec = rgba8.as_slice().to_vec();
let rgba = rgba_vec
.iter()
.map(|c| vec![c.r, c.g, c.b, c.a])
.flatten()
.collect::<Vec<u8>>();
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);
}
}
}

View file

@ -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 {

View file

@ -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,69 @@ 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((_, 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 argument for custom cursor"));
}
if let Some((n, msg)) = has_error {
diag.push_error(msg.into(), &n);
}
expr
}
}
}
@ -348,6 +412,7 @@ fn to_debug_string(
| Type::Brush
| Type::Image
| Type::Easing
| Type::Cursor
| Type::StyledText
| Type::Array(_) => {
Expression::StringLiteral("<debug-of-this-type-not-yet-implemented>".into())

View file

@ -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<Expression>,
/// First expression in the tuple is a color, second expression is the stop position
@ -896,6 +898,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,
@ -978,6 +981,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 {
@ -1085,6 +1089,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 {
@ -1192,6 +1197,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))
@ -1449,6 +1455,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,
@ -1725,6 +1732,41 @@ pub enum EasingCurve {
// Custom(Box<dyn Fn(f32)->f32>),
}
#[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)]
@ -1841,6 +1883,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)?;

View file

@ -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<uint16_t>".into()),
Type::Easing => Some("slint::cbindgen_private::EasingCurve".into()),
Type::StyledText => Some("slint::StyledText".into()),
Type::Cursor => Some("slint::cbindgen_private::MouseCursor".into()),
_ => None,
}
}
@ -3242,6 +3243,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 {
@ -3550,35 +3578,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);
@ -3643,6 +3650,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()
}

View file

@ -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<proc_macro2::TokenStream> {
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<proc_macro2::TokenStream> {
match ty {
Type::LogicalLength => Some(quote!(sp::LogicalLength)),
Type::Easing => Some(quote!(sp::EasingCurve)),
Type::Cursor => Some(quote!(sp::MouseCursor)),
_ => rust_primitive_type(ty),
}
}
@ -2272,6 +2274,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) => {
@ -2540,31 +2568,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 } => {
@ -2628,6 +2636,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)
}

View file

@ -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,

View file

@ -147,6 +147,8 @@ pub enum Expression {
EasingCurve(crate::expression_tree::EasingCurve),
MouseCursor(crate::expression_tree::MouseCursor),
LinearGradient {
angle: Box<Expression>,
/// First expression in the tuple is a color, second expression is the stop position
@ -251,6 +253,7 @@ impl Expression {
.collect::<Option<_>>()?,
},
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,
@ -309,6 +312,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,
@ -369,6 +373,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 {

View file

@ -214,6 +214,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

View file

@ -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,

View file

@ -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({}, {})",

View file

@ -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<R>(
&self,
_ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
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<Enumeration> {
fn for_each_entry<R>(
&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(|| {

View file

@ -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

View file

@ -0,0 +1,49 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// 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);
}
// Invalid unnamespaced access
TouchArea {
mouse-cursor: square-resize;
// > <error{Unknown unqualified identifier 'square-resize'}
}
// Invalid namespaced access
TouchArea {
mouse-cursor: MouseCursor.square-resize;
// > <error{'square-resize' is not a member of the namespace Cursor}
}
// Custom cursor macro (one missing argument)
TouchArea {
mouse-cursor: MouseCursor.custom(@image-url("cursor.png"), 0);
// > <error{Not enough arguments}
}
// Custom cursor macro (two missing arguments)
TouchArea {
mouse-cursor: MouseCursor.custom(@image-url("cursor.png"));
// > <error{Not enough arguments}
}
// Custom cursor macro (missing all arguments)
TouchArea {
mouse-cursor: MouseCursor.custom();
// > <error{Not enough arguments}
}
}

View file

@ -400,6 +400,7 @@ impl TypeRegister {
register.insert_type(Type::Model);
register.insert_type(Type::Percent);
register.insert_type(Type::Easing);
register.insert_type(Type::Cursor);
register.insert_type(Type::Angle);
register.insert_type(Type::Brush);
register.insert_type(Type::Rem);

View file

@ -63,6 +63,8 @@ pub use drag_n_drop::*;
mod path;
#[cfg(feature = "std")]
pub use path::*;
pub(crate) mod cursor;
pub use cursor::*;
/// Alias for `&mut dyn ItemRenderer`. Required so cbindgen generates the ItemVTable
/// despite the presence of trait object

View file

@ -0,0 +1,79 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// 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,
},
}

View file

@ -55,6 +55,7 @@ macro_rules! declare_ValueType_2 {
crate::items::DropEvent,
crate::model::ModelRc<crate::items::MenuEntry>,
crate::api::StyledText,
crate::items::MouseCursor,
$(crate::items::$Name,)*
];
};

View file

@ -147,6 +147,8 @@ pub enum Value {
StyledText(i_slint_core::api::StyledText) = 13,
#[doc(hidden)]
ArrayOfU16(SharedVector<u16>) = 14,
/// A mouse cursor.
MouseCursor(i_slint_core::items::MouseCursor) = 15,
}
impl Value {
@ -196,6 +198,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),
}
}
}
@ -224,6 +227,7 @@ impl std::fmt::Debug for Value {
Value::ArrayOfU16(data) => {
write!(f, "Value::ArrayOfU16({data:?})")
}
Value::MouseCursor(m) => write!(f, "Value::MouseCursor({m:?})"),
}
}
}
@ -264,6 +268,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<f32>] );
declare_value_conversion!(ComponentFactory => [ComponentFactory] );
declare_value_conversion!(StyledText => [i_slint_core::api::StyledText] );

View file

@ -1243,6 +1243,7 @@ pub(crate) fn generate_item_tree<'id>(
Type::Struct(_) => property_info::<Value>(),
Type::Array(_) => property_info::<Value>(),
Type::Easing => property_info::<i_slint_core::animations::EasingCurve>(),
Type::Cursor => property_info::<i_slint_core::items::MouseCursor>(),
Type::Percent => animated_property_info::<f32>(),
Type::Enumeration(e) => {
macro_rules! match_enum_type {

View file

@ -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(
@ -1832,6 +1880,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)))
@ -2146,6 +2195,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()),

View file

@ -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

View file

@ -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);

View file

@ -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);
```
*/

View file

@ -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) });

View file

@ -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");

View file

@ -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

View file

@ -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);
```
*/

View file

@ -50,13 +50,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 {

View file

@ -97,7 +97,7 @@ pub struct HeadlessWindow {
window: i_slint_core::api::Window,
size: Cell<PhysicalSize>,
pub ime_requests: RefCell<Vec<InputMethodRequest>>,
pub mouse_cursor: Cell<i_slint_core::items::MouseCursor>,
pub mouse_cursor: RefCell<i_slint_core::items::MouseCursor>,
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);
}
}