Implement TouchArea::scroll-event

Closes #1280
This commit is contained in:
Olivier Goffart 2023-10-06 16:49:45 +02:00 committed by Olivier Goffart
parent 2f4e110111
commit 4c0a435196
12 changed files with 116 additions and 19 deletions

View file

@ -17,6 +17,7 @@ All notable changes to this project are documented in this file.
- Added `Number`, `Decimal` variant to enum `InputType`
- Added `spacing-horizontal` and `spacing-vertical` in `GridLayout`
- Fixed conversion in array of array of structs (#3574)
- Added `scroll-event` callback to `TouchArea`
### Rust API

View file

@ -186,6 +186,7 @@ fn default_config() -> cbindgen::Config {
("VoidArg".into(), "void".into()),
("KeyEventArg".into(), "KeyEvent".into()),
("PointerEventArg".into(), "PointerEvent".into()),
("PointerScrollEventArg".into(), "PointerScrollEvent".into()),
("PointArg".into(), "slint::LogicalPosition".into()),
("FloatArg".into(), "float".into()),
("Coord".into(), "float".into()),
@ -277,6 +278,7 @@ fn gen_corelib(
"PointerEventKind",
"PointerEventButton",
"PointerEvent",
"PointerScrollEvent",
"Rect",
"SortOrder",
"BitmapFont",
@ -325,6 +327,7 @@ fn gen_corelib(
"VoidArg",
"KeyEventArg",
"PointerEventArg",
"PointerScrollEventArg",
"PointArg",
"Point",
"slint_color_brighter",

View file

@ -59,6 +59,7 @@ using ItemArray = slint::cbindgen_private::Slice<ItemArrayEntry>;
using cbindgen_private::KeyboardModifiers;
using cbindgen_private::KeyEvent;
using cbindgen_private::PointerEvent;
using cbindgen_private::PointerScrollEvent;
using cbindgen_private::TableColumn;
constexpr inline ItemTreeNode make_item_node(uint32_t child_count, uint32_t child_index,

View file

@ -145,8 +145,7 @@ widgets, then the following algorithm is used to distinguish between the user's
interacting with `TouchArea` elements:
1. If the `Flickable`'s `interactive` property is `false`, all events are forwarded to elements underneath.
2. Any wheel events are interpreted as attempt of the user to scroll.
3. If a press event is received where the event's coordinates interact with a `TouchArea`, the event is stored
2. If a press event is received where the event's coordinates interact with a `TouchArea`, the event is stored
and any subsequent move and release events are handled as follows:
1. If 100ms elapse without any events, the stored press event is delivered to the `TouchArea`.
2. If a release event is received before 100ms have elapsed, the stored press event as well as the
@ -157,7 +156,7 @@ interacting with `TouchArea` elements:
2. The distance to the press event exceeds 8 logical pixels in an orientation in which we are allowed to move.
If `Flickable` decides to flick, any press event sent previously to a `TouchArea`, is followed up
by an exit event. During the phase of receiving move events, the flickable follows the coordinates.
4. If the interaction of press, move, and release events begins at coordinates that do not intersect with
3. If the interaction of press, move, and release events begins at coordinates that do not intersect with
a `TouchArea`, then `Flickable` will flick immediately on pointer move events when the euclidean distance
to the coordinates of the press event exceeds 8 logical pixels.
@ -246,7 +245,7 @@ Alternatively, the item can be put in a `Row` element.
### Properties
- **`spacing`** (_in_ _length_): The distance between the elements in the layout.
- **`spacing-horizontal`**, **`spacing-vertical`** (_in_ _length_):
- **`spacing-horizontal`**, **`spacing-vertical`** (_in_ _length_):
Set these properties to override the spacing on specific directions.
- **`padding`** (_in_ _length_): The padding within the layout.
- **`padding-left`**, **`padding-right`**, **`padding-top`** and **`padding-bottom`** (_in_ _length_):
@ -753,8 +752,12 @@ When not part of a layout, its width or height default to 100% of the parent ele
- **`clicked()`**: Invoked when clicked: The mouse is pressed, then released on this element.
- **`moved()`**: The mouse has been moved. This will only be called if the mouse is also pressed.
- **`pointer-event(PointerEvent)`**: Invoked when a button was pressed or released The [_`PointerEvent`_](structs.md#pointerevent)
- **`pointer-event(PointerEvent)`**: Invoked when a button was pressed or released. The [_`PointerEvent`_](structs.md#pointerevent)
argument contains information such which button was pressed and any active keyboard modifiers.
- **`scroll-event(PointerScrollEvent) -> EventResult`**: Invoked when the mouse wheel was rotated or another scroll gesture was made.
The [_`PointerEvent`_](structs.md#pointerscrollevent) argument contains information about how much to scroll in what direction.
The returned [`EventResult`](enums.md#eventresult) indicates whether to accept or ignore the event. Ignored events are
forwarded to the parent element.
### Example

View file

@ -51,7 +51,7 @@ macro_rules! for_each_builtin_structs {
}
/// Represents a Pointer event sent by the windowing system.
/// This structure is generated and passed to the `pointer-event` callback of the `TouchArea` element.
/// This structure is passed to the `pointer-event` callback of the `TouchArea` element.
struct PointerEvent {
@name = "slint::private_api::PointerEvent"
export {
@ -66,6 +66,22 @@ macro_rules! for_each_builtin_structs {
}
}
/// Represents a Pointer scroll (or wheel) event sent by the windowing system.
/// This structure is passed to the `scroll-event` callback of the `TouchArea` element.
struct PointerScrollEvent {
@name = "slint::private_api::PointerScrollEvent"
export {
/// The amount of pixel in the horizontal direction
delta_x: Coord,
/// The amount of pixel in the vertical direction
delta_y: Coord,
/// The keyboard modifiers pressed during the event
modifiers: KeyboardModifiers,
}
private {
}
}
/// This structure is generated and passed to the key press and release callbacks of the `FocusScope` element.
struct KeyEvent {
@name = "slint::private_api::KeyEvent"

View file

@ -100,6 +100,7 @@ export component TouchArea {
callback clicked;
callback moved;
callback pointer-event(PointerEvent);
callback scroll-event(PointerScrollEvent) -> EventResult;
//-default_size_binding:expands_to_parent_geometry
}

View file

@ -473,8 +473,8 @@ impl Window {
crate::platform::WindowEvent::PointerScrolled { position, delta_x, delta_y } => {
self.0.process_mouse_input(MouseEvent::Wheel {
position: position.to_euclid().cast(),
delta_x,
delta_y,
delta_x: delta_x as _,
delta_y: delta_y as _,
});
}
crate::platform::WindowEvent::PointerExited => {

View file

@ -5,6 +5,7 @@
*/
#![warn(missing_docs)]
use crate::component::ComponentRc;
use crate::item_tree::{ItemRc, ItemWeak, VisitChildrenResult};
pub use crate::items::PointerEventButton;
use crate::items::{ItemRef, TextCursorDirection};
@ -12,8 +13,7 @@ pub use crate::items::{KeyEvent, KeyboardModifiers};
use crate::lengths::{LogicalPoint, LogicalVector};
use crate::timers::Timer;
use crate::window::{WindowAdapter, WindowInner};
use crate::Property;
use crate::{component::ComponentRc, SharedString};
use crate::{Coord, Property, SharedString};
use alloc::rc::Rc;
use alloc::vec::Vec;
use const_field_offset::FieldOffsets;
@ -44,7 +44,7 @@ pub enum MouseEvent {
/// `pos` is the position of the mouse when the event happens.
/// `delta_x` is the amount of pixels to scroll in horizontal direction,
/// `delta_y` is the amount of pixels to scroll in vertical direction.
Wheel { position: LogicalPoint, delta_x: f32, delta_y: f32 },
Wheel { position: LogicalPoint, delta_x: Coord, delta_y: Coord },
/// The mouse exited the item or component
Exit,
}

View file

@ -63,6 +63,7 @@ type ItemRendererRef<'a> = &'a mut dyn crate::item_rendering::ItemRenderer;
pub type VoidArg = ();
pub type KeyEventArg = (KeyEvent,);
type PointerEventArg = (PointerEvent,);
type PointerScrollEventArg = (PointerScrollEvent,);
type PointArg = (Point,);
#[cfg(all(feature = "ffi", windows))]
@ -425,6 +426,7 @@ pub struct TouchArea {
pub clicked: Callback<VoidArg>,
pub moved: Callback<VoidArg>,
pub pointer_event: Callback<PointerEventArg>,
pub scroll_event: Callback<PointerScrollEventArg, EventResult>,
/// FIXME: remove this
pub cached_rendering_data: CachedRenderingData,
/// true when we are currently grabbing the mouse
@ -536,12 +538,20 @@ impl Item for TouchArea {
InputEventResult::EventAccepted
}
}
MouseEvent::Wheel { .. } => {
MouseEvent::Wheel { delta_x, delta_y, .. } => {
let modifiers = window_adapter.window().0.modifiers.get().into();
let r = Self::FIELD_OFFSETS
.scroll_event
.apply_pin(self)
.call(&(PointerScrollEvent { delta_x, delta_y, modifiers },));
return if self.grabbed.get() {
InputEventResult::GrabMouse
} else {
InputEventResult::EventAccepted
}
match r {
EventResult::Reject => InputEventResult::EventIgnored,
EventResult::Accept => InputEventResult::EventAccepted,
}
};
}
};
result

View file

@ -251,9 +251,7 @@ impl FlickableData {
InputEventFilterResult::ForwardEvent
}
}
MouseEvent::Wheel { position, .. } => {
InputEventFilterResult::InterceptAndDispatch(MouseEvent::Moved { position })
}
MouseEvent::Wheel { .. } => InputEventFilterResult::ForwardEvent,
// Not the left button
MouseEvent::Pressed { .. } | MouseEvent::Released { .. } => {
InputEventFilterResult::ForwardAndIgnore
@ -324,9 +322,9 @@ impl FlickableData {
&& !cfg!(target_os = "macos")
{
// Shift invert coordinate for the purpose of scrolling. But not on macOs because there the OS already take care of the change
LogicalVector::new(delta_y as _, delta_x as _)
LogicalVector::new(delta_y, delta_x)
} else {
LogicalVector::new(delta_x as _, delta_y as _)
LogicalVector::new(delta_x, delta_y)
};
let new_pos = ensure_in_bound(flick, old_pos + delta, flick_rc);
(Flickable::FIELD_OFFSETS.viewport_x).apply_pin(flick).set(new_pos.x_length());

View file

@ -42,6 +42,7 @@ macro_rules! declare_ValueType_2 {
crate::Brush,
crate::graphics::Point,
crate::items::PointerEvent,
crate::items::PointerScrollEvent,
crate::lengths::LogicalLength,
crate::component_factory::ComponentFactory,
$(crate::items::$Name,)*

View file

@ -0,0 +1,63 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
export component TestCase inherits Window {
width: 500px;
height: 500px;
in-out property<string> result;
f := Flickable {
viewport_width: 2100px;
viewport_height: 2100px;
ta1 := TouchArea {
x: 20px;
y: 20px;
width: 50px;
height: 50px;
Rectangle { background: red; }
scroll-event(e) => {
result += "ta1{" + e.delta-x/1px + "," + e.delta-y/1px + " at " + self.mouse-x/1px + "," + self.mouse-y/1px + "}";
accept
}
}
ta2 := TouchArea {
x: 120px;
y: 20px;
width: 50px;
height: 50px;
Rectangle { background: green; }
scroll-event(e) => {
result += "ta2{" + e.delta-x/1px + "," + e.delta-y/1px + " at " + self.mouse-x/1px + "," + self.mouse-y/1px + "}";
EventResult.reject
}
}
}
out property<length> offset_x: -f.viewport_x;
out property<length> offset_y: -f.viewport_y;
}
/*
```rust
// Test wheel events
use slint::{LogicalPosition, platform::WindowEvent };
let instance = TestCase::new().unwrap();
instance.window().dispatch_event(WindowEvent::PointerScrolled { position: LogicalPosition::new(25.0, 30.0), delta_x: -3.0, delta_y: -50.0 });
assert_eq!(instance.get_result(), "ta1{-3,-50 at 5,10}");
assert_eq!(instance.get_offset_y(), 0.0);
assert_eq!(instance.get_offset_x(), 0.0);
instance.set_result("".into());
instance.window().dispatch_event(WindowEvent::PointerScrolled { position: LogicalPosition::new(155.0, 50.0), delta_x: -30.0, delta_y: -50.0 });
assert_eq!(instance.get_result(), "ta2{-30,-50 at 35,30}");
assert_eq!(instance.get_offset_x(), 30.0);
assert_eq!(instance.get_offset_y(), 50.0);
```
*/