Skip to content
Open
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
7 changes: 0 additions & 7 deletions internal/compiler/widgets/cosmic/scrollview.slint
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ export component ScrollBar {
in-out property <length> value;
in property <ScrollBarPolicy> policy: ScrollBarPolicy.as-needed;

callback scrolled();

private property <length> track-size: root.horizontal ? root.width - 2 * root.offset : root.height - 2 * offset;
private property <length> step-size: 10px;
private property <length> offset: 2px;
Expand Down Expand Up @@ -65,7 +63,6 @@ export component ScrollBar {
root.horizontal ? (touch-area.mouse-x - touch-area.pressed-x) * (root.maximum / (root.track-size - thumb.width))
: (touch-area.mouse-y - touch-area.pressed-y) * (root.maximum / (root.track-size - thumb.height))
)));
root.scrolled();
}
}

Expand Down Expand Up @@ -125,8 +122,6 @@ export component ScrollView {
horizontal: false;
maximum: flickable.viewport-height - flickable.height;
page-size: flickable.height;

scrolled => {root.scrolled()}
}

horizontal-bar := ScrollBar {
Expand All @@ -138,7 +133,5 @@ export component ScrollView {
horizontal: true;
maximum: flickable.viewport-width - flickable.width;
page-size: flickable.width;

scrolled => {root.scrolled()}
}
}
7 changes: 0 additions & 7 deletions internal/compiler/widgets/cupertino/scrollview.slint
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ export component ScrollBar inherits Rectangle {
in-out property <length> value;
in property <ScrollBarPolicy> policy: ScrollBarPolicy.as-needed;

callback scrolled();

property <length> track-size: root.horizontal ? root.width - 2 * root.offset : root.height - 2 * offset;
property <length> step-size: 10px;
property <length> offset: 2px;
Expand Down Expand Up @@ -73,7 +71,6 @@ export component ScrollBar inherits Rectangle {
root.horizontal ? (touch-area.mouse-x - touch-area.pressed-x) * (root.maximum / (root.track-size - thumb.width))
: (touch-area.mouse-y - touch-area.pressed-y) * (root.maximum / (root.track-size - thumb.height))
)));
root.scrolled();
}
}

Expand Down Expand Up @@ -135,8 +132,6 @@ export component ScrollView {
horizontal: false;
maximum: flickable.viewport-height - flickable.height;
page-size: flickable.height;

scrolled => {root.scrolled()}
}

horizontal-bar := ScrollBar {
Expand All @@ -148,7 +143,5 @@ export component ScrollView {
horizontal: true;
maximum: flickable.viewport-width - flickable.width;
page-size: flickable.width;

scrolled => {root.scrolled()}
}
}
7 changes: 0 additions & 7 deletions internal/compiler/widgets/fluent/scrollview.slint
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ component ScrollBar inherits Rectangle {
in property <ScrollBarPolicy> policy: ScrollBarPolicy.as-needed;
in property <bool> enabled;

callback scrolled();

property <length> offset: 16px;
property <length> size: 2px;
property <length> track-size: root.horizontal ? root.width - 2 * root.offset : root.height - 2 * offset;
Expand Down Expand Up @@ -94,7 +92,6 @@ component ScrollBar inherits Rectangle {
root.horizontal ? (touch-area.mouse-x - touch-area.pressed-x) * (root.maximum / (root.track-size - thumb.width))
: (touch-area.mouse-y - touch-area.pressed-y) * (root.maximum / (root.track-size - thumb.height))
)));
root.scrolled();
}
}

Expand Down Expand Up @@ -178,8 +175,6 @@ export component ScrollView {
horizontal: false;
maximum: flickable.viewport-height - flickable.height;
page-size: flickable.height;

scrolled => {root.scrolled()}
}

horizontal-bar := ScrollBar {
Expand All @@ -191,7 +186,5 @@ export component ScrollView {
horizontal: true;
maximum: flickable.viewport-width - flickable.width;
page-size: flickable.width;

scrolled => {root.scrolled()}
}
}
7 changes: 0 additions & 7 deletions internal/compiler/widgets/material/scrollview.slint
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ component ScrollBar inherits Rectangle {
in-out property <bool> enabled <=> touch-area.enabled;
in property <ScrollBarPolicy> policy: ScrollBarPolicy.as-needed;

callback scrolled();

states [
disabled when !touch-area.enabled : {
background.border-color: MaterialPalette.control-foreground;
Expand Down Expand Up @@ -75,7 +73,6 @@ component ScrollBar inherits Rectangle {
root.horizontal ? (touch-area.mouse-x - touch-area.pressed-x) * (root.maximum / (root.width - handle.width))
: (touch-area.mouse-y - touch-area.pressed-y) * (root.maximum / (root.height - handle.height))
)));
root.scrolled();
}
}

Expand Down Expand Up @@ -135,8 +132,6 @@ export component ScrollView {
maximum: flickable.viewport-height - flickable.height;
page-size: flickable.height;
enabled: root.enabled;

scrolled => {root.scrolled()}
}

horizontal-bar := ScrollBar {
Expand All @@ -148,7 +143,5 @@ export component ScrollView {
maximum: flickable.viewport-width - flickable.width;
page-size: flickable.width;
enabled: root.enabled;

scrolled => {root.scrolled()}
}
}
98 changes: 68 additions & 30 deletions internal/core/items/flickable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::input::{
FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, KeyEvent, MouseEvent,
};
use crate::item_rendering::CachedRenderingData;
use crate::items::PropertyAnimation;
use crate::items::{AnimationDirection, PropertyAnimation};
use crate::layout::{LayoutInfo, Orientation};
use crate::lengths::{
LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalRect, LogicalSize, LogicalVector,
Expand Down Expand Up @@ -92,6 +92,26 @@ impl Item for Flickable {
}
},
);

self.data.viewport_change_handler.init_delayed(
self_rc.downgrade(),
|self_weak| {
let Some(flick_rc) = self_weak.upgrade() else { return Default::default() };
let Some(flick) = flick_rc.downcast::<Flickable>() else {
return Default::default();
};
let flick = flick.as_pin_ref();

(flick.viewport_x().get(), flick.viewport_y().get())
},
|self_weak, _| {
let Some(flick_rc) = self_weak.upgrade() else { return };
let Some(flick) = flick_rc.downcast::<Flickable>() else { return };
let flick = flick.as_pin_ref();

flick.flicked.call(&())
},
);
}

fn layout_info(
Expand Down Expand Up @@ -242,6 +262,15 @@ 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);

const SMOOTH_SCROLL_DURATION: i32 = 250;
const SMOOTH_SCROLL_ANIM: PropertyAnimation = PropertyAnimation {
duration: SMOOTH_SCROLL_DURATION,
easing: EasingCurve::CubicBezier([0.0, 0.0, 0.58, 1.0]),
delay: 0,
iteration_count: 1.,
direction: AnimationDirection::Normal,
};

#[derive(Default, Debug)]
struct FlickableDataInner {
/// The position in which the press was made
Expand All @@ -250,13 +279,17 @@ struct FlickableDataInner {
pressed_viewport_pos: LogicalPoint,
/// Set to true if the flickable is flicking and capturing all mouse event, not forwarding back to the children
capture_events: bool,
smooth_scroll_time: Option<Instant>,
smooth_scroll_target: LogicalPoint,
}

#[derive(Default, Debug)]
pub struct FlickableData {
inner: RefCell<FlickableDataInner>,
/// Tracker that tracks the property to make sure that the flickable is in bounds
in_bound_change_handler: crate::properties::ChangeTracker,
// Scroll trackers for flicked callback
viewport_change_handler: crate::properties::ChangeTracker,
}

impl FlickableData {
Expand Down Expand Up @@ -372,12 +405,8 @@ impl FlickableData {
if inner.capture_events || should_capture() {
let new_pos = ensure_in_bound(flick, new_pos, flick_rc);

let old_pos = (x.get(), y.get());
x.set(new_pos.x_length());
y.set(new_pos.y_length());
if old_pos.0 != new_pos.x_length() || old_pos.1 != new_pos.y_length() {
(Flickable::FIELD_OFFSETS.flicked).apply_pin(flick).call(&());
}

inner.capture_events = true;
InputEventResult::GrabMouse
Expand All @@ -395,7 +424,7 @@ impl FlickableData {
}
}
MouseEvent::Wheel { delta_x, delta_y, .. } => {
let delta = if window_adapter.window().0.modifiers.get().shift()
let mut delta = if window_adapter.window().0.modifiers.get().shift()
&& !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
Expand All @@ -413,20 +442,36 @@ impl FlickableData {
return InputEventResult::EventIgnored;
}

let old_pos = LogicalPoint::from_lengths(
(Flickable::FIELD_OFFSETS.viewport_x).apply_pin(flick).get(),
(Flickable::FIELD_OFFSETS.viewport_y).apply_pin(flick).get(),
);
let new_pos = ensure_in_bound(flick, old_pos + delta, flick_rc);

let viewport_x = (Flickable::FIELD_OFFSETS.viewport_x).apply_pin(flick);
let viewport_y = (Flickable::FIELD_OFFSETS.viewport_y).apply_pin(flick);
let old_pos = (viewport_x.get(), viewport_y.get());
viewport_x.set(new_pos.x_length());
viewport_y.set(new_pos.y_length());
if old_pos.0 != new_pos.x_length() || old_pos.1 != new_pos.y_length() {
(Flickable::FIELD_OFFSETS.flicked).apply_pin(flick).call(&());

let old_pos = LogicalPoint::from_lengths(viewport_x.get(), viewport_y.get());

// Accumulate scroll delta
if let Some(smooth_scroll_time) = inner.smooth_scroll_time.take() {
let millis =
(crate::animations::current_tick() - smooth_scroll_time).as_millis() as i32;

if millis < SMOOTH_SCROLL_DURATION {
let remaining_delta = inner.smooth_scroll_target - old_pos;

// Only if is in the same direction.
// `Default` is because `dot` returns `i32` in embedded
// but it returns `f32` in any other platform
if delta.dot(remaining_delta) > Default::default() {
delta += remaining_delta;
}
}
}

let new_pos = ensure_in_bound(flick, old_pos + delta, flick_rc);

inner.smooth_scroll_target = new_pos;
inner.smooth_scroll_time = Some(Instant::now());

viewport_y.set_animated_value(new_pos.y_length(), SMOOTH_SCROLL_ANIM);
viewport_x.set_animated_value(new_pos.x_length(), SMOOTH_SCROLL_ANIM);

InputEventResult::EventAccepted
}
MouseEvent::DragMove(..) | MouseEvent::Drop(..) => InputEventResult::EventIgnored,
Expand All @@ -449,26 +494,19 @@ impl FlickableData {
{
let speed = dist / (millis as f32);

let duration = 250;
let final_pos = ensure_in_bound(
flick,
(inner.pressed_viewport_pos.cast() + dist + speed * (duration as f32)).cast(),
(inner.pressed_viewport_pos.cast()
+ dist
+ speed * (SMOOTH_SCROLL_DURATION as f32))
.cast(),
flick_rc,
);
let anim = PropertyAnimation {
duration,
easing: EasingCurve::CubicBezier([0.0, 0.0, 0.58, 1.0]),
..PropertyAnimation::default()
};

let viewport_x = (Flickable::FIELD_OFFSETS.viewport_x).apply_pin(flick);
let viewport_y = (Flickable::FIELD_OFFSETS.viewport_y).apply_pin(flick);
let old_pos = (viewport_x.get(), viewport_y.get());
viewport_x.set_animated_value(final_pos.x_length(), anim.clone());
viewport_y.set_animated_value(final_pos.y_length(), anim);
if old_pos.0 != final_pos.x_length() || old_pos.1 != final_pos.y_length() {
(Flickable::FIELD_OFFSETS.flicked).apply_pin(flick).call(&());
}
viewport_x.set_animated_value(final_pos.x_length(), SMOOTH_SCROLL_ANIM);
viewport_y.set_animated_value(final_pos.y_length(), SMOOTH_SCROLL_ANIM);
}
}
inner.capture_events = false; // FIXME: should only be set to false once the flick animation is over
Expand Down
4 changes: 3 additions & 1 deletion internal/core/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1281,7 +1281,9 @@ impl<C: RepeatedItemTree + 'static> Repeater<C> {
viewport_height.set(inner.cached_item_height * row_count as Coord);
viewport_width.set(vp_width);
let new_viewport_y = -inner.anchor_y + new_offset_y;
viewport_y.set(new_viewport_y);
if viewport_y.get() != new_viewport_y {
viewport_y.set(new_viewport_y);
}
inner.previous_viewport_y = new_viewport_y;
break;
}
Expand Down
8 changes: 6 additions & 2 deletions tests/cases/elements/flickable.slint
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ TestCase := Window {
property<bool> inner_ta_has_hover: inner_ta.has_hover;
property<int> clicked;
property<int> double-clicked;
property <int> flicked;
property<int> flicked;
}

/*
Expand Down Expand Up @@ -233,6 +233,7 @@ assert!((instance.get_offset_y() - 55.).abs() < 5.);
use slint::{LogicalPosition, platform::{WindowEvent, Key} };
let instance = TestCase::new().unwrap();
instance.window().dispatch_event(WindowEvent::PointerScrolled { position: LogicalPosition::new(175.0, 175.0), delta_x: -30.0, delta_y: -50.0 });
slint_testing::mock_elapsed_time(250);
assert_eq!(instance.get_offset_x(), 30.);
assert_eq!(instance.get_offset_y(), 50.);

Expand All @@ -242,6 +243,7 @@ if !cfg!(target_os = "macos") {
slint_testing::send_keyboard_char(&instance, Key::Shift.into(), true);
instance.window().dispatch_event(WindowEvent::PointerScrolled { position: LogicalPosition::new(175.0, 175.0), delta_x: 15.0, delta_y: -60.0 });
slint_testing::send_keyboard_char(&instance, Key::Shift.into(), false);
slint_testing::mock_elapsed_time(250);
assert_eq!(instance.get_offset_x(), 30. + 60.);
assert_eq!(instance.get_offset_y(), 50. - 15.);
}
Expand All @@ -255,6 +257,7 @@ assert_eq!(instance.get_flicked(), 0);

// test scrolling behaviour
instance.window().dispatch_event(WindowEvent::PointerScrolled { position: LogicalPosition::new(175.0, 175.0), delta_x: -30.0, delta_y: -50.0 });
slint_testing::mock_elapsed_time(250);
dbg!(instance.get_flicked());
assert_eq!(instance.get_flicked(), -3000050); //flicked got called after scrolling
instance.set_flicked(0);
Expand All @@ -268,7 +271,8 @@ slint_testing::mock_elapsed_time(10000);
assert_eq!(instance.get_flicked(), -10500105); //flicked got called during drag
instance.set_flicked(0);
instance.window().dispatch_event(WindowEvent::PointerReleased { position: LogicalPosition::new(100.0, 120.0), button: PointerEventButton::Left });
assert_eq!(instance.get_flicked(), -10500105); //flicked got called after drag
slint_testing::mock_elapsed_time(250);
assert_eq!(instance.get_flicked(), -10682145); //flicked got called after drag
instance.set_flicked(0);

```
Expand Down
4 changes: 3 additions & 1 deletion tests/cases/elements/flickable2.slint
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ TestCase := Window {
}
}

Flickable { for i in 5: Rectangle {} }
Flickable {
for i in 5: Rectangle {}
}

property<bool> all_ok: r1.ok && r2.ok && r3.ok && r4.ok;
property<bool> test: all_ok;
Expand Down
3 changes: 3 additions & 0 deletions tests/cases/elements/flickable3.slint
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,14 @@ assert_eq!(instance.get_t1_has_hover(), true);
assert_eq!(instance.get_t1sec_has_hover(), false);
assert_eq!(instance.get_t2_has_hover(), false);
instance.window().dispatch_event(WindowEvent::PointerScrolled { position: LogicalPosition::new(25.0, 25.0), delta_x: 0.0, delta_y: -30.0 });
slint_testing::mock_elapsed_time(250);
assert_eq!(instance.get_t1_has_hover(), false);
assert_eq!(instance.get_t1sec_has_hover(), true);
assert_eq!(instance.get_t2_has_hover(), false);
assert_eq!(instance.get_f1_pos(), -30.0);

instance.window().dispatch_event(WindowEvent::PointerScrolled { position: LogicalPosition::new(25.0, 25.0), delta_x: 0.0, delta_y: -30.0 });
slint_testing::mock_elapsed_time(250);
assert_eq!(instance.get_t1_has_hover(), false);
assert_eq!(instance.get_t1sec_has_hover(), false);
assert_eq!(instance.get_t2_has_hover(), false);
Expand All @@ -88,6 +90,7 @@ instance.window().dispatch_event(WindowEvent::PointerMoved { position: LogicalPo
assert_eq!(instance.get_t2_has_hover(), true);
assert_eq!(instance.get_t1_has_hover(), false);
instance.window().dispatch_event(WindowEvent::PointerScrolled { position: LogicalPosition::new(275.0, 25.0), delta_x: -30.0, delta_y: 0.0 });
slint_testing::mock_elapsed_time(250);
assert_eq!(instance.get_t2_has_hover(), false);
assert_eq!(instance.get_t1_has_hover(), false);

Expand Down
Loading
Loading