mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 10:50:00 +00:00
SwipeGestureRecognizer (#6005)
This commit is contained in:
parent
0feb674612
commit
5663ddd9cc
11 changed files with 535 additions and 23 deletions
|
@ -281,6 +281,7 @@ fn gen_corelib(
|
|||
"ClippedImage",
|
||||
"TouchArea",
|
||||
"FocusScope",
|
||||
"SwipeGestureRecognizer",
|
||||
"Flickable",
|
||||
"SimpleText",
|
||||
"ComplexText",
|
||||
|
@ -391,6 +392,7 @@ fn gen_corelib(
|
|||
"Coord",
|
||||
"LogicalRect",
|
||||
"LogicalPoint",
|
||||
"LogicalPosition",
|
||||
"LogicalLength",
|
||||
]
|
||||
.iter()
|
||||
|
|
|
@ -656,6 +656,80 @@ export component Example inherits Window {
|
|||
}
|
||||
```
|
||||
|
||||
## `SwipeGestureRecognizer`
|
||||
|
||||
Use the `SwipeGestureRecognizer` to react to swipes gesture in some particular direction. Recognition is limited to the element's geometry.
|
||||
|
||||
Specify the different swipe directions you'd like to recognise by setting the `swipe-left/right/up/down` properties and react to the gesture in the `swiped` callback.
|
||||
|
||||
Pointer press events on the recognizer's area are forwarded to the children with a small delay.
|
||||
If the pointer moves by more than 8 logical pixels in one of the enabled swipe directions, the gesture is recognized, and events are no longer forwarded to the children.
|
||||
|
||||
### Properties
|
||||
|
||||
- **`enabled`** (_in_ _bool_): When disabled, the `SwipeGestureRecognizer` doesn't recognize any gestures.
|
||||
(default value: `true`)
|
||||
- **`swipe-left`**, **`swipe-right`**, **`swipe-up`**, **`swipe-down`** (_out_ _bool_): Enable recognition of swipes in
|
||||
the corresponding direction. (default value: `false`)
|
||||
- **`pressed-position`** (_out_ _Point_): The position of the pointer when the swipe started.
|
||||
- **`current-position`** (_out_ _Point_): The current pointer position.
|
||||
- **`swiping`** (_out_ _bool_): `true` while the gesture is recognized, false otherwise.
|
||||
|
||||
### Callbacks
|
||||
|
||||
- **`moved()`**: Invoked when the pointer is moved.
|
||||
- **`swiped()`**: Invoked after the swipe gesture was recognised and the pointer was released.
|
||||
- **`cancelled()`**: Invoked when the swipe is cancelled programatically or if the window loses focus.
|
||||
|
||||
### Functions
|
||||
|
||||
- **`cancel()`**: Cancel any on-going swipe gesture recognition.
|
||||
|
||||
### Example
|
||||
|
||||
This example implements swiping between pages of different colors.
|
||||
|
||||
```slint
|
||||
export component Example inherits Window {
|
||||
width: 270px;
|
||||
height: 100px;
|
||||
|
||||
property <int> current-page: 0;
|
||||
|
||||
sgr := SwipeGestureRecognizer {
|
||||
swipe-right: current-page > 0;
|
||||
swipe-left: current-page < 5;
|
||||
swiped => {
|
||||
if self.current-position.x > self.pressed-position.x + self.width / 4 {
|
||||
current-page -= 1;
|
||||
} else if self.current-position.x < self.pressed-position.x - self.width / 4 {
|
||||
current-page += 1;
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
property <length> position: - current-page * root.width;
|
||||
animate position { duration: 200ms; easing: ease-in-out; }
|
||||
property <length> swipe-offset;
|
||||
x: position + swipe-offset;
|
||||
states [
|
||||
swiping when sgr.swiping : {
|
||||
swipe-offset: sgr.current-position.x - sgr.pressed-position.x;
|
||||
out { animate swipe-offset { duration: 200ms; easing: ease-in-out; } }
|
||||
}
|
||||
]
|
||||
|
||||
Rectangle { width: root.width; background: green; }
|
||||
Rectangle { width: root.width; background: limegreen; }
|
||||
Rectangle { width: root.width; background: yellow; }
|
||||
Rectangle { width: root.width; background: orange; }
|
||||
Rectangle { width: root.width; background: red; }
|
||||
Rectangle { width: root.width; background: violet; }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `TextInput`
|
||||
|
||||
The `TextInput` is a lower-level item that shows text and allows entering text.
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import { Theme } from "theme.slint";
|
||||
|
||||
export component Carousel inherits FocusScope {
|
||||
export component Carousel {
|
||||
in-out property <int> selected-index;
|
||||
in property <length> spacing;
|
||||
in property <length> itemWidth;
|
||||
|
@ -26,6 +26,7 @@ export component Carousel inherits FocusScope {
|
|||
|
||||
forward-focus: focus-scope;
|
||||
height: Theme.size-big;
|
||||
preferred-width: 100%;
|
||||
|
||||
focus-scope:= FocusScope {
|
||||
key-pressed(event) => {
|
||||
|
@ -48,29 +49,47 @@ export component Carousel inherits FocusScope {
|
|||
}
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
focus-scope.focus()
|
||||
swipe := SwipeGestureRecognizer {
|
||||
swipe-left: true;
|
||||
swipe-right: true;
|
||||
|
||||
swiped => {
|
||||
if self.current-position.x > self.pressed-position.x + root.itemWidth / 2 {
|
||||
root.move-left();
|
||||
} else if self.current-position.x < self.pressed-position.x - root.itemWidth / 2 {
|
||||
root.move-right();
|
||||
}
|
||||
}
|
||||
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
}
|
||||
TouchArea {
|
||||
clicked => {
|
||||
focus-scope.focus()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
clip: true;
|
||||
background: transparent;
|
||||
Rectangle {
|
||||
clip: true;
|
||||
|
||||
Flickable {
|
||||
interactive: false;
|
||||
viewport-x: root.center-x - root.selected-index * (root.itemWidth + root.spacing);
|
||||
Rectangle {
|
||||
property <length> viewport-x: root.center-x - root.selected-index * (root.itemWidth + root.spacing);
|
||||
animate viewport-x { duration: root.duration; easing: ease-in; }
|
||||
property <length> swipe-offset: 0;
|
||||
x: self.viewport-x + swipe-offset;
|
||||
width: inner-layout.preferred-width;
|
||||
|
||||
animate viewport-x { duration: root.duration; easing: ease-in; }
|
||||
states [
|
||||
swipping when swipe.swiping : {
|
||||
//x: self.viewport-x + swipe-offset;
|
||||
swipe-offset: (swipe.current-position.x - swipe.pressed-position.x).clamp(-root.itemWidth, root.itemWidth);
|
||||
out { animate swipe-offset { duration: root.duration; easing: ease-in; } }
|
||||
}
|
||||
]
|
||||
|
||||
HorizontalLayout {
|
||||
spacing <=> root.spacing;
|
||||
inner-layout := HorizontalLayout {
|
||||
spacing <=> root.spacing;
|
||||
|
||||
@children
|
||||
@children
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,6 +151,37 @@ export component Flickable inherits Empty {
|
|||
//-default_size_binding:expands_to_parent_geometry
|
||||
}
|
||||
|
||||
export component SwipeGestureRecognizer {
|
||||
in property <bool> enabled: true;
|
||||
in property <bool> swipe-left;
|
||||
in property <bool> swipe-right;
|
||||
in property <bool> swipe-up;
|
||||
in property <bool> swipe-down;
|
||||
|
||||
// For the future
|
||||
//in property <length> swipe-distance-threshold: 8px;
|
||||
//in property <duration> swipe-duration-threshold: 500ms;
|
||||
// in property <bool> delays-propgataion;
|
||||
//in property <duration> propgataion-delay: 100ms;
|
||||
// in property <int> required-touch-points: 1;
|
||||
//callback swipe-recognized();
|
||||
|
||||
out property <Point> pressed-position;
|
||||
out property <Point> current-position;
|
||||
out property <bool> swiping;
|
||||
|
||||
callback moved();
|
||||
// the cursor is released and so the swipe is finished
|
||||
callback swiped();
|
||||
// the cursor is released and so the swipe is finished
|
||||
callback cancelled();
|
||||
|
||||
// clears state, invokes swipe-cancelled()
|
||||
function cancel() {}
|
||||
|
||||
//-default_size_binding:expands_to_parent_geometry
|
||||
}
|
||||
|
||||
component WindowItem {
|
||||
in-out property <length> width;
|
||||
in-out property <length> height;
|
||||
|
|
|
@ -11,7 +11,7 @@ When adding an item or a property, it needs to be kept in sync with different pl
|
|||
|
||||
- It needs to be changed in this module
|
||||
- In the compiler: builtins.slint
|
||||
- In the interpreter (new item only): dynamic_component.rs
|
||||
- In the interpreter (new item only): dynamic_item_tree.rs
|
||||
- For the C++ code (new item only): the cbindgen.rs to export the new item
|
||||
- Don't forget to update the documentation
|
||||
*/
|
||||
|
@ -44,7 +44,7 @@ use vtable::*;
|
|||
mod component_container;
|
||||
pub use self::component_container::*;
|
||||
mod flickable;
|
||||
pub use flickable::*;
|
||||
pub use flickable::Flickable;
|
||||
mod text;
|
||||
pub use text::*;
|
||||
mod input_items;
|
||||
|
@ -534,6 +534,10 @@ declare_item_vtable! {
|
|||
fn slint_get_FocusScopeVTable() -> FocusScopeVTable for FocusScope
|
||||
}
|
||||
|
||||
declare_item_vtable! {
|
||||
fn slint_get_SwipeGestureRecognizerVTable() -> SwipeGestureRecognizerVTable for SwipeGestureRecognizer
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(FieldOffsets, Default, SlintElement)]
|
||||
#[pin]
|
||||
|
|
|
@ -180,9 +180,11 @@ impl core::ops::Deref for FlickableDataBox {
|
|||
}
|
||||
|
||||
/// The distance required before it starts flicking if there is another item intercepting the mouse.
|
||||
const DISTANCE_THRESHOLD: LogicalLength = LogicalLength::new(8 as _);
|
||||
pub(super) const DISTANCE_THRESHOLD: LogicalLength = LogicalLength::new(8 as _);
|
||||
/// Time required before we stop caring about child event if the mouse hasn't been moved
|
||||
const DURATION_THRESHOLD: Duration = Duration::from_millis(500);
|
||||
pub(super) const DURATION_THRESHOLD: Duration = Duration::from_millis(500);
|
||||
/// The delay to which press are forwarded to the inner item
|
||||
pub(super) const FORWARD_DELAY: Duration = Duration::from_millis(100);
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct FlickableDataInner {
|
||||
|
@ -218,7 +220,7 @@ impl FlickableData {
|
|||
if inner.capture_events {
|
||||
InputEventFilterResult::Intercept
|
||||
} else {
|
||||
InputEventFilterResult::DelayForwarding(100)
|
||||
InputEventFilterResult::DelayForwarding(FORWARD_DELAY.as_millis() as _)
|
||||
}
|
||||
}
|
||||
MouseEvent::Exit | MouseEvent::Released { button: PointerEventButton::Left, .. } => {
|
||||
|
|
|
@ -6,6 +6,8 @@ use super::{
|
|||
PointerEventArg, PointerEventButton, PointerEventKind, PointerScrollEvent,
|
||||
PointerScrollEventArg, RenderingResult, VoidArg,
|
||||
};
|
||||
use crate::animations::Instant;
|
||||
use crate::api::LogicalPosition;
|
||||
use crate::input::{
|
||||
FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, KeyEvent,
|
||||
KeyEventResult, KeyEventType, MouseEvent,
|
||||
|
@ -16,7 +18,7 @@ use crate::lengths::{LogicalLength, LogicalPoint, LogicalRect, LogicalSize, Poin
|
|||
#[cfg(feature = "rtti")]
|
||||
use crate::rtti::*;
|
||||
use crate::window::{WindowAdapter, WindowInner};
|
||||
use crate::{Callback, Property};
|
||||
use crate::{Callback, Coord, Property};
|
||||
use alloc::rc::Rc;
|
||||
use const_field_offset::FieldOffsets;
|
||||
use core::cell::Cell;
|
||||
|
@ -337,3 +339,218 @@ impl ItemConsts for FocusScope {
|
|||
CachedRenderingData,
|
||||
> = FocusScope::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(FieldOffsets, Default, SlintElement)]
|
||||
#[pin]
|
||||
pub struct SwipeGestureRecognizer {
|
||||
pub enabled: Property<bool>,
|
||||
pub swipe_left: Property<bool>,
|
||||
pub swipe_right: Property<bool>,
|
||||
pub swipe_up: Property<bool>,
|
||||
pub swipe_down: Property<bool>,
|
||||
|
||||
pub moved: Callback<VoidArg>,
|
||||
pub swiped: Callback<VoidArg>,
|
||||
pub cancelled: Callback<VoidArg>,
|
||||
|
||||
pub pressed_position: Property<LogicalPosition>,
|
||||
pub current_position: Property<LogicalPosition>,
|
||||
pub swiping: Property<bool>,
|
||||
|
||||
pressed_time: Cell<Instant>,
|
||||
// true when the cursor is pressed down and we haven't cancelled yet for another reason
|
||||
pressed: Cell<bool>,
|
||||
// capture_events: Cell<bool>,
|
||||
/// FIXME: remove this
|
||||
pub cached_rendering_data: CachedRenderingData,
|
||||
}
|
||||
|
||||
impl Item for SwipeGestureRecognizer {
|
||||
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
|
||||
|
||||
fn layout_info(
|
||||
self: Pin<&Self>,
|
||||
_orientation: Orientation,
|
||||
_window_adapter: &Rc<dyn WindowAdapter>,
|
||||
) -> LayoutInfo {
|
||||
LayoutInfo { stretch: 1., ..LayoutInfo::default() }
|
||||
}
|
||||
|
||||
fn input_event_filter_before_children(
|
||||
self: Pin<&Self>,
|
||||
event: MouseEvent,
|
||||
_window_adapter: &Rc<dyn WindowAdapter>,
|
||||
_self_rc: &ItemRc,
|
||||
) -> InputEventFilterResult {
|
||||
if !self.enabled() {
|
||||
if self.pressed.get() {
|
||||
self.cancel_impl();
|
||||
}
|
||||
return InputEventFilterResult::ForwardAndIgnore;
|
||||
}
|
||||
|
||||
match event {
|
||||
MouseEvent::Pressed { position, button: PointerEventButton::Left, .. } => {
|
||||
Self::FIELD_OFFSETS
|
||||
.pressed_position
|
||||
.apply_pin(self)
|
||||
.set(crate::lengths::logical_position_to_api(position));
|
||||
self.pressed.set(true);
|
||||
self.pressed_time.set(crate::animations::current_tick());
|
||||
InputEventFilterResult::DelayForwarding(
|
||||
super::flickable::FORWARD_DELAY.as_millis() as _
|
||||
)
|
||||
}
|
||||
MouseEvent::Exit => {
|
||||
self.cancel_impl();
|
||||
InputEventFilterResult::ForwardAndIgnore
|
||||
}
|
||||
MouseEvent::Released { button: PointerEventButton::Left, .. } => {
|
||||
if self.swiping() {
|
||||
InputEventFilterResult::Intercept
|
||||
} else {
|
||||
self.pressed.set(false);
|
||||
InputEventFilterResult::ForwardEvent
|
||||
}
|
||||
}
|
||||
MouseEvent::Moved { position } => {
|
||||
if self.swiping() {
|
||||
InputEventFilterResult::Intercept
|
||||
} else if !self.pressed.get() {
|
||||
InputEventFilterResult::ForwardEvent
|
||||
} else if crate::animations::current_tick() - self.pressed_time.get()
|
||||
> super::flickable::DURATION_THRESHOLD
|
||||
{
|
||||
self.pressed.set(false);
|
||||
InputEventFilterResult::ForwardAndIgnore
|
||||
} else {
|
||||
let pressed_pos = self.pressed_position();
|
||||
let dx = position.x - pressed_pos.x as Coord;
|
||||
let dy = position.y - pressed_pos.y as Coord;
|
||||
let threshold = super::flickable::DISTANCE_THRESHOLD.get();
|
||||
if (self.swipe_down() && dy > threshold)
|
||||
|| (self.swipe_up() && dy < -threshold)
|
||||
|| (self.swipe_left() && dx < -threshold)
|
||||
|| (self.swipe_right() && dx > threshold)
|
||||
{
|
||||
InputEventFilterResult::Intercept
|
||||
} else {
|
||||
InputEventFilterResult::ForwardAndInterceptGrab
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseEvent::Wheel { .. } => InputEventFilterResult::ForwardAndIgnore,
|
||||
// Not the left button
|
||||
MouseEvent::Pressed { .. } | MouseEvent::Released { .. } => {
|
||||
InputEventFilterResult::ForwardAndIgnore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn input_event(
|
||||
self: Pin<&Self>,
|
||||
event: MouseEvent,
|
||||
_window_adapter: &Rc<dyn WindowAdapter>,
|
||||
_self_rc: &ItemRc,
|
||||
) -> InputEventResult {
|
||||
match event {
|
||||
MouseEvent::Pressed { .. } => InputEventResult::GrabMouse,
|
||||
MouseEvent::Exit => {
|
||||
self.cancel_impl();
|
||||
InputEventResult::EventIgnored
|
||||
}
|
||||
MouseEvent::Released { .. } => {
|
||||
self.pressed.set(false);
|
||||
if self.swiping() {
|
||||
Self::FIELD_OFFSETS.swiping.apply_pin(self).set(false);
|
||||
Self::FIELD_OFFSETS.swiped.apply_pin(self).call(&());
|
||||
InputEventResult::EventAccepted
|
||||
} else {
|
||||
InputEventResult::EventIgnored
|
||||
}
|
||||
}
|
||||
MouseEvent::Moved { position } => {
|
||||
if !self.pressed.get() {
|
||||
return InputEventResult::EventIgnored;
|
||||
}
|
||||
self.current_position.set(crate::lengths::logical_position_to_api(position));
|
||||
if !self.swiping() {
|
||||
let pressed_pos = self.pressed_position();
|
||||
let dx = position.x - pressed_pos.x as Coord;
|
||||
let dy = position.y - pressed_pos.y as Coord;
|
||||
let threshold = super::flickable::DISTANCE_THRESHOLD.get();
|
||||
let start_swipe = if dy > threshold {
|
||||
self.swipe_down()
|
||||
} else if dy < -threshold {
|
||||
self.swipe_up()
|
||||
} else if dx < -threshold {
|
||||
self.swipe_left()
|
||||
} else if dx > threshold {
|
||||
self.swipe_right()
|
||||
} else {
|
||||
return InputEventResult::EventIgnored;
|
||||
};
|
||||
if start_swipe {
|
||||
Self::FIELD_OFFSETS.swiping.apply_pin(self).set(true);
|
||||
} else {
|
||||
self.cancel_impl();
|
||||
return InputEventResult::EventIgnored;
|
||||
}
|
||||
}
|
||||
Self::FIELD_OFFSETS.moved.apply_pin(self).call(&());
|
||||
InputEventResult::EventAccepted
|
||||
}
|
||||
MouseEvent::Wheel { .. } => InputEventResult::EventIgnored,
|
||||
}
|
||||
}
|
||||
|
||||
fn key_event(
|
||||
self: Pin<&Self>,
|
||||
_: &KeyEvent,
|
||||
_window_adapter: &Rc<dyn WindowAdapter>,
|
||||
_self_rc: &ItemRc,
|
||||
) -> KeyEventResult {
|
||||
KeyEventResult::EventIgnored
|
||||
}
|
||||
|
||||
fn focus_event(
|
||||
self: Pin<&Self>,
|
||||
_: &FocusEvent,
|
||||
_window_adapter: &Rc<dyn WindowAdapter>,
|
||||
_self_rc: &ItemRc,
|
||||
) -> FocusEventResult {
|
||||
FocusEventResult::FocusIgnored
|
||||
}
|
||||
|
||||
fn render(
|
||||
self: Pin<&Self>,
|
||||
_backend: &mut ItemRendererRef,
|
||||
_self_rc: &ItemRc,
|
||||
_size: LogicalSize,
|
||||
) -> RenderingResult {
|
||||
RenderingResult::ContinueRenderingChildren
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemConsts for SwipeGestureRecognizer {
|
||||
const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
|
||||
Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
|
||||
}
|
||||
|
||||
impl SwipeGestureRecognizer {
|
||||
pub fn copy(self: Pin<&Self>, _: &Rc<dyn WindowAdapter>, _: &ItemRc) {
|
||||
self.cancel_impl();
|
||||
}
|
||||
|
||||
fn cancel_impl(self: Pin<&Self>) {
|
||||
if !self.pressed.replace(false) {
|
||||
debug_assert!(!self.swiping());
|
||||
return;
|
||||
}
|
||||
if self.swiping() {
|
||||
Self::FIELD_OFFSETS.swiping.apply_pin(self).set(false);
|
||||
Self::FIELD_OFFSETS.cancelled.apply_pin(self).call(&());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ macro_rules! declare_ValueType_2 {
|
|||
crate::items::PointerScrollEvent,
|
||||
crate::lengths::LogicalLength,
|
||||
crate::component_factory::ComponentFactory,
|
||||
crate::api::LogicalPosition,
|
||||
$(crate::items::$Name,)*
|
||||
];
|
||||
};
|
||||
|
|
|
@ -325,6 +325,7 @@ macro_rules! handle_private {
|
|||
|
||||
declare_value_struct_conversion!(struct i_slint_core::layout::LayoutInfo { min, max, min_percent, max_percent, preferred, stretch });
|
||||
declare_value_struct_conversion!(struct i_slint_core::graphics::Point { x, y, ..Default::default()});
|
||||
declare_value_struct_conversion!(struct i_slint_core::api::LogicalPosition { x, y });
|
||||
|
||||
i_slint_common::for_each_builtin_structs!(declare_value_struct_conversion);
|
||||
|
||||
|
|
|
@ -933,6 +933,7 @@ pub(crate) fn generate_item_tree<'id>(
|
|||
rtti_for::<BorderRectangle>(),
|
||||
rtti_for::<TouchArea>(),
|
||||
rtti_for::<FocusScope>(),
|
||||
rtti_for::<SwipeGestureRecognizer>(),
|
||||
rtti_for::<Path>(),
|
||||
rtti_for::<Flickable>(),
|
||||
rtti_for::<WindowItem>(),
|
||||
|
|
160
tests/cases/elements/swipegesturerecognizer.slint
Normal file
160
tests/cases/elements/swipegesturerecognizer.slint
Normal file
|
@ -0,0 +1,160 @@
|
|||
// 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 TestCase inherits Window {
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
|
||||
in-out property <string> r;
|
||||
in property <bool> enabled <=> right-gesture.enabled;
|
||||
out property ta_hover <=> ta.has-hover;
|
||||
out property ta_pressed <=> ta.pressed;
|
||||
out property down-swiping <=> down-gesture.swiping;
|
||||
out property left-swiping <=> left-gesture.swiping;
|
||||
out property right-swiping <=> right-gesture.swiping;
|
||||
|
||||
function distance(a: Point, b: Point) -> float {
|
||||
return sqrt((a.x/1px - b.x/1px).pow(2) + (a.y/1px - b.y/1px).pow(2));
|
||||
}
|
||||
|
||||
down-gesture := SwipeGestureRecognizer {
|
||||
swipe-down: true;
|
||||
swipe-left: false;
|
||||
swiped => {
|
||||
r += "S1(" + distance(self.current-position, self.pressed-position) + ")";
|
||||
}
|
||||
cancelled => { r += "C1(" + distance(self.current-position, self.pressed-position) + ")"; }
|
||||
VerticalLayout {
|
||||
left-gesture := SwipeGestureRecognizer {
|
||||
swipe-left: true;
|
||||
swiped => {
|
||||
r += "S2(" + distance(self.current-position, self.pressed-position) + ")";
|
||||
}
|
||||
moved => {
|
||||
r += "M2(" + distance(self.current-position, self.pressed-position) + ")";
|
||||
}
|
||||
cancelled => { r += "C2(" + distance(self.current-position, self.pressed-position) + ")"; }
|
||||
|
||||
}
|
||||
right-gesture := SwipeGestureRecognizer {
|
||||
swipe-right: true;
|
||||
ta := TouchArea {
|
||||
clicked => { r += "clicked()" }
|
||||
}
|
||||
swiped => {
|
||||
r += "S3(" + distance(self.current-position, self.pressed-position) + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
```rust
|
||||
let instance = TestCase::new().unwrap();
|
||||
|
||||
assert_eq!(instance.get_enabled(), true);
|
||||
assert_eq!(instance.get_r(), "");
|
||||
assert_eq!(instance.get_ta_hover(), false);
|
||||
assert_eq!(instance.get_ta_pressed(), false);
|
||||
assert_eq!(instance.get_down_swiping(), false);
|
||||
assert_eq!(instance.get_left_swiping(), false);
|
||||
assert_eq!(instance.get_right_swiping(), false);
|
||||
|
||||
use slint::{platform::WindowEvent, LogicalPosition, platform::PointerEventButton};
|
||||
|
||||
// click
|
||||
slint_testing::send_mouse_click(&instance, 500., 500.);
|
||||
assert_eq!(instance.get_r(), "clicked()");
|
||||
assert_eq!(instance.get_ta_hover(), true);
|
||||
assert_eq!(instance.get_ta_pressed(), false);
|
||||
assert_eq!(instance.get_down_swiping(), false);
|
||||
assert_eq!(instance.get_left_swiping(), false);
|
||||
assert_eq!(instance.get_right_swiping(), false);
|
||||
instance.set_r("".into());
|
||||
|
||||
|
||||
// Down
|
||||
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(4.0, 4.0) });
|
||||
assert_eq!(instance.get_ta_hover(), false);
|
||||
slint_testing::mock_elapsed_time(20);
|
||||
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(300.0, 400.0) });
|
||||
instance.window().dispatch_event(WindowEvent::PointerPressed { position: LogicalPosition::new(300.0, 400.0), button: PointerEventButton::Left });
|
||||
slint_testing::mock_elapsed_time(20);
|
||||
assert_eq!(instance.get_ta_pressed(), false); // because we might recognize it as a gesture
|
||||
assert_eq!(instance.get_ta_hover(), true, "must be hover");
|
||||
assert_eq!(instance.get_down_swiping(), false);
|
||||
assert_eq!(instance.get_left_swiping(), false);
|
||||
assert_eq!(instance.get_right_swiping(), false);
|
||||
|
||||
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(299.0, 402.0) });
|
||||
slint_testing::mock_elapsed_time(20);
|
||||
assert_eq!(instance.get_ta_pressed(), false);
|
||||
assert_eq!(instance.get_ta_hover(), true);
|
||||
assert_eq!(instance.get_down_swiping(), false);
|
||||
assert_eq!(instance.get_left_swiping(), false);
|
||||
assert_eq!(instance.get_right_swiping(), false);
|
||||
|
||||
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(298.0, 452.0) });
|
||||
slint_testing::mock_elapsed_time(20);
|
||||
assert_eq!(instance.get_ta_pressed(), false);
|
||||
assert_eq!(instance.get_ta_hover(), true);
|
||||
assert_eq!(instance.get_down_swiping(), true);
|
||||
assert_eq!(instance.get_left_swiping(), false);
|
||||
assert_eq!(instance.get_right_swiping(), false);
|
||||
slint_testing::mock_elapsed_time(220);
|
||||
|
||||
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(350.0, 482.0) });
|
||||
slint_testing::mock_elapsed_time(20);
|
||||
assert_eq!(instance.get_ta_pressed(), false);
|
||||
assert_eq!(instance.get_ta_hover(), true);
|
||||
assert_eq!(instance.get_down_swiping(), true);
|
||||
assert_eq!(instance.get_left_swiping(), false);
|
||||
assert_eq!(instance.get_right_swiping(), false);
|
||||
assert_eq!(instance.get_r(), "");
|
||||
|
||||
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(316.0, 463.0) });
|
||||
instance.window().dispatch_event(WindowEvent::PointerReleased { position: LogicalPosition::new(316.0, 463.0), button: PointerEventButton::Left });
|
||||
assert_eq!(instance.get_r(), "S1(65)");
|
||||
assert_eq!(instance.get_ta_pressed(), false);
|
||||
assert_eq!(instance.get_ta_hover(), true);
|
||||
assert_eq!(instance.get_down_swiping(), false);
|
||||
assert_eq!(instance.get_left_swiping(), false);
|
||||
assert_eq!(instance.get_right_swiping(), false);
|
||||
instance.set_r("".into());
|
||||
|
||||
// FIXME: this shouldn'g be necessary, but otherwise we don't send exit event to the toucharea
|
||||
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(316.0, 463.0) });
|
||||
|
||||
// Left
|
||||
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(100.0, 100.0) });
|
||||
assert_eq!(instance.get_ta_hover(), false);
|
||||
assert_eq!(instance.get_down_swiping(), false);
|
||||
assert_eq!(instance.get_left_swiping(), false);
|
||||
assert_eq!(instance.get_right_swiping(), false);
|
||||
slint_testing::mock_elapsed_time(20);
|
||||
instance.window().dispatch_event(WindowEvent::PointerPressed { position: LogicalPosition::new(100.0, 100.0), button: PointerEventButton::Left });
|
||||
slint_testing::mock_elapsed_time(120); // FIXME: it should also work with smaller delay, but now the top swipe catches it
|
||||
assert_eq!(instance.get_ta_hover(), false);
|
||||
assert_eq!(instance.get_down_swiping(), false);
|
||||
assert_eq!(instance.get_left_swiping(), false);
|
||||
assert_eq!(instance.get_right_swiping(), false);
|
||||
assert_eq!(instance.get_r(), "");
|
||||
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(80.0, 100.0) });
|
||||
assert_eq!(instance.get_ta_hover(), false);
|
||||
assert_eq!(instance.get_down_swiping(), false);
|
||||
assert_eq!(instance.get_right_swiping(), false);
|
||||
assert_eq!(instance.get_left_swiping(), true);
|
||||
// the root gesture cause the mouse event to be repeated twice
|
||||
assert_eq!(instance.get_r(), "M2(20)M2(20)");
|
||||
instance.window().dispatch_event(WindowEvent::PointerExited);
|
||||
assert_eq!(instance.get_r(), "M2(20)M2(20)C2(20)");
|
||||
assert_eq!(instance.get_down_swiping(), false);
|
||||
assert_eq!(instance.get_left_swiping(), false);
|
||||
assert_eq!(instance.get_right_swiping(), false);
|
||||
assert_eq!(instance.get_ta_hover(), false);
|
||||
```
|
||||
|
||||
|
||||
*/
|
Loading…
Add table
Add a link
Reference in a new issue