Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix flickable in flickable #1787

Merged
merged 2 commits into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 43 additions & 23 deletions internal/core/items/flickable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ use crate::lengths::{
#[cfg(feature = "rtti")]
use crate::rtti::*;
use crate::window::WindowAdapter;
use crate::Coord;
use crate::Property;
use alloc::boxed::Box;
use alloc::rc::Rc;
Expand Down Expand Up @@ -182,7 +181,7 @@ impl core::ops::Deref for FlickableDataBox {
}

/// The distance required before it starts flicking if there is another item intercepting the mouse.
const DISTANCE_THRESHOLD: Coord = 8 as _;
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);

Expand Down Expand Up @@ -241,19 +240,31 @@ impl FlickableData {
if crate::animations::current_tick() - pressed_time > DURATION_THRESHOLD {
return false;
}
let can_move_horiz = (Flickable::FIELD_OFFSETS.viewport
+ Empty::FIELD_OFFSETS.width)
.apply_pin(flick)
.get()
> flick.width();
let can_move_vert = (Flickable::FIELD_OFFSETS.viewport
+ Empty::FIELD_OFFSETS.height)
.apply_pin(flick)
.get()
> flick.height();
// Check if the mouse was moved more than the DISTANCE_THRESHEOLD in a
// direction in which the flickable can flick
let diff = position - inner.pressed_pos;
(can_move_horiz && diff.x.abs() > DISTANCE_THRESHOLD)
|| (can_move_vert && diff.y.abs() > DISTANCE_THRESHOLD)
let vp = Flickable::FIELD_OFFSETS.viewport;
let check_horizontal = {
let gap_h = (vp + Empty::FIELD_OFFSETS.width).apply_pin(flick).get()
- flick.width();
gap_h > LogicalLength::default() && {
let x = (vp + Empty::FIELD_OFFSETS.x).apply_pin(flick).get();
(x < LogicalLength::default()
&& diff.x_length() > DISTANCE_THRESHOLD)
|| (x > -gap_h && diff.x_length() < -DISTANCE_THRESHOLD)
}
};
let check_vertical = {
let gap_v = (vp + Empty::FIELD_OFFSETS.height).apply_pin(flick).get()
- flick.height();
gap_v > LogicalLength::default() && {
let y = (vp + Empty::FIELD_OFFSETS.y).apply_pin(flick).get();
(y < LogicalLength::default()
&& diff.y_length() > DISTANCE_THRESHOLD)
|| (y > -gap_v && diff.y_length() < -DISTANCE_THRESHOLD)
}
};
check_horizontal || check_vertical
ogoffart marked this conversation as resolved.
Show resolved Hide resolved
});
if do_intercept {
InputEventFilterResult::Intercept
Expand Down Expand Up @@ -286,18 +297,25 @@ impl FlickableData {
}
MouseEvent::Moved { position } => {
if inner.pressed_time.is_some() {
inner.capture_events = true;
let new_pos = ensure_in_bound(
flick,
inner.pressed_viewport_pos + (position - inner.pressed_pos),
);
(Flickable::FIELD_OFFSETS.viewport + Empty::FIELD_OFFSETS.x)
.apply_pin(flick)
.set(new_pos.x_length());
(Flickable::FIELD_OFFSETS.viewport + Empty::FIELD_OFFSETS.y)
.apply_pin(flick)
.set(new_pos.y_length());
InputEventResult::GrabMouse
let x = (Flickable::FIELD_OFFSETS.viewport + Empty::FIELD_OFFSETS.x)
.apply_pin(flick);
let y = (Flickable::FIELD_OFFSETS.viewport + Empty::FIELD_OFFSETS.y)
.apply_pin(flick);
if inner.capture_events
|| (new_pos - LogicalPoint::from_lengths(x.get(), y.get())).square_length()
>= (DISTANCE_THRESHOLD.get() * DISTANCE_THRESHOLD.get()) as _
{
x.set(new_pos.x_length());
y.set(new_pos.y_length());
inner.capture_events = true;
InputEventResult::GrabMouse
} else {
InputEventResult::EventIgnored
}
} else {
inner.capture_events = false;
InputEventResult::EventIgnored
Expand Down Expand Up @@ -332,7 +350,9 @@ impl FlickableData {
let dist = (pos - inner.pressed_pos).cast::<f32>();

let millis = (crate::animations::current_tick() - pressed_time).as_millis();
if dist.square_length() > (DISTANCE_THRESHOLD * DISTANCE_THRESHOLD) as f32 && millis > 1
if inner.capture_events
&& dist.square_length() > (DISTANCE_THRESHOLD.get() * DISTANCE_THRESHOLD.get()) as _
&& millis > 1
{
let speed = dist / (millis as f32);

Expand Down
126 changes: 126 additions & 0 deletions tests/cases/elements/flickable_in_flickable.slint
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial

TestCase := Window {
width: 500px;
height: 500px;
no-frame: false;

outer := Flickable {
x: 10px;
y: 10px;
width: parent.width - 20px;
height: parent.height - 20px;
viewport_width: width;
viewport_height: 980px;

inner := Flickable {
viewport_width: 1500px;
Rectangle {
background: @radial-gradient(circle, yellow, blue, red, green);
}
}
}

property<length> outer_y: - outer.viewport-y;
property<length> inner_x: - inner.viewport-x;

property <bool> test: outer.viewport-x == 0 && inner.viewport-y == 0;

}

/*

```rust
use slint::{WindowEvent, PointerEventButton, LogicalPosition};
let instance = TestCase::new();

// First, try to scroll up
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(150.0, 200.0) });
slint_testing::mock_elapsed_time(16);
instance.window().dispatch_event(WindowEvent::PointerPressed { position: LogicalPosition::new(150.0, 200.0), button: PointerEventButton::Left });
slint_testing::mock_elapsed_time(16);
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(150.0, 205.0) });
slint_testing::mock_elapsed_time(16);
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(150.0, 290.0) });
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(150.0, 300.0) });
// shouldn't move because we are already up
assert_eq!(instance.get_inner_x(), 0.);
assert_eq!(instance.get_outer_y(), 0.);

// now, scroll down
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(150.0, 202.0) });
assert_eq!(instance.get_inner_x(), 0.);
assert_eq!(instance.get_outer_y(), 0.);
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(150.0, 200.0) });
assert_eq!(instance.get_inner_x(), 0.);
assert_eq!(instance.get_outer_y(), 0.);
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(150.0, 198.0) });
// (still not above threshold : 200 - 198 < threshold)
assert_eq!(instance.get_inner_x(), 0.);
assert_eq!(instance.get_outer_y(), 0.);
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(150.0, 170.0) });
//instance.run();
assert_eq!(instance.get_inner_x(), 0.);
assert_eq!(instance.get_outer_y(), 30.);
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(150.0, 150.0) });
assert_eq!(instance.get_inner_x(), 0.);
assert_eq!(instance.get_outer_y(), 50.);
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(100.0, 150.0) });
assert_eq!(instance.get_inner_x(), 0.);
assert_eq!(instance.get_outer_y(), 50.);
slint_testing::mock_elapsed_time(100);
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(100.0, -2000.0) });
assert_eq!(instance.get_inner_x(), 0.);
// all the way down
assert_eq!(instance.get_outer_y(), 500.);
slint_testing::mock_elapsed_time(100);
instance.window().dispatch_event(WindowEvent::PointerReleased { position: LogicalPosition::new(250.0, -2000.0), button: PointerEventButton::Left });
slint_testing::mock_elapsed_time(1000);
assert_eq!(instance.get_inner_x(), 0.);
assert_eq!(instance.get_outer_y(), 500.);

// Now, try to go to the right and the bottom at the same time: we should go to the right because that's the only option
instance.window().dispatch_event(WindowEvent::PointerPressed { position: LogicalPosition::new(100.0, 100.0), button: PointerEventButton::Left });
slint_testing::mock_elapsed_time(16);
assert_eq!(instance.get_inner_x(), 0.);
assert_eq!(instance.get_outer_y(), 500.);
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(90.0, 90.0) });
slint_testing::mock_elapsed_time(120); // we need to wait enough because of the delay in the outer one
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(90.0, 90.0) });
assert_eq!(instance.get_inner_x(), 10.);
assert_eq!(instance.get_outer_y(), 500.);
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(20.0, 90.0) });
slint_testing::mock_elapsed_time(16);
assert_eq!(instance.get_inner_x(), 80.);
assert_eq!(instance.get_outer_y(), 500.);

// Scolling up still work as the outer flickable now intersepcts it, and then the inner one lost
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(10.0, 190.0) });
slint_testing::mock_elapsed_time(16);
assert_eq!(instance.get_inner_x(), 80.);
assert_eq!(instance.get_outer_y(), 410.);
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(150.0, 200.0) });
slint_testing::mock_elapsed_time(16);
assert_eq!(instance.get_inner_x(), 80.);
assert_eq!(instance.get_outer_y(), 400.);
slint_testing::mock_elapsed_time(1600);
instance.window().dispatch_event(WindowEvent::PointerReleased { position: LogicalPosition::new(150.0, 200.0), button: PointerEventButton::Left });
slint_testing::mock_elapsed_time(16000); // finish the animation


// scroll to the left
let old_outer_y = instance.get_outer_y();
instance.window().dispatch_event(WindowEvent::PointerPressed { position: LogicalPosition::new(100.0, 100.0), button: PointerEventButton::Left });
slint_testing::mock_elapsed_time(160);
assert_eq!(instance.get_inner_x(), 80.);
assert_eq!(instance.get_outer_y(), old_outer_y);
// 103 is bellow the threshold, 120 is not
instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPosition::new(120.0, 103.0) });
slint_testing::mock_elapsed_time(16);
assert_eq!(instance.get_inner_x(), 60.);
assert_eq!(instance.get_outer_y(), old_outer_y);

```

*/