SwipeGestureRecognizer (#6005)

This commit is contained in:
Olivier Goffart 2024-09-09 14:40:55 +02:00 committed by GitHub
parent 0feb674612
commit 5663ddd9cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 535 additions and 23 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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, .. } => {

View file

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

View file

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

View file

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

View file

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

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