Skip to content

Commit f92a728

Browse files
committed
feat: Add smooth scroll to Flickable
1 parent c5ff907 commit f92a728

File tree

9 files changed

+79
-24
lines changed

9 files changed

+79
-24
lines changed

internal/compiler/builtins.slint

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ export component Flickable inherits Empty {
155155
in property <length> viewport-width;
156156
in-out property <length> viewport-x;
157157
in-out property <length> viewport-y;
158+
in property <bool> smooth-scroll: true;
158159
in property <bool> interactive: true;
159160
callback flicked();
160161
//-default_size_binding:expands_to_parent_geometry

internal/compiler/widgets/cosmic/scrollview.slint

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export component ScrollView {
9090
in-out property <length> viewport-height <=> flickable.viewport-height;
9191
in-out property <length> viewport-x <=> flickable.viewport-x;
9292
in-out property <length> viewport-y <=> flickable.viewport-y;
93+
in property <bool> smooth-scroll: true;
9394
in property <ScrollBarPolicy> vertical-scrollbar-policy <=> vertical-bar.policy;
9495
in property <ScrollBarPolicy> horizontal-scrollbar-policy <=> horizontal-bar.policy;
9596
in property <bool> mouse-drag-pan-enabled <=> flickable.interactive;
@@ -108,6 +109,7 @@ export component ScrollView {
108109

109110
flickable := Flickable {
110111
interactive: false;
112+
smooth-scroll: root.smooth-scroll;
111113
viewport-y <=> vertical-bar.value;
112114
viewport-x <=> horizontal-bar.value;
113115
width: 100%;

internal/compiler/widgets/cupertino/scrollview.slint

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export component ScrollView {
9898
in-out property <length> viewport-height <=> flickable.viewport-height;
9999
in-out property <length> viewport-x <=> flickable.viewport-x;
100100
in-out property <length> viewport-y <=> flickable.viewport-y;
101+
in property <bool> smooth-scroll: true;
101102
in property <ScrollBarPolicy> vertical-scrollbar-policy <=> vertical-bar.policy;
102103
in property <ScrollBarPolicy> horizontal-scrollbar-policy <=> horizontal-bar.policy;
103104
in property <bool> mouse-drag-pan-enabled <=> flickable.interactive;
@@ -118,6 +119,7 @@ export component ScrollView {
118119
x: 2px;
119120
y: 2px;
120121
interactive: false;
122+
smooth-scroll: root.smooth-scroll;
121123
viewport-y <=> vertical-bar.value;
122124
viewport-x <=> horizontal-bar.value;
123125
width: parent.width - 4px;

internal/compiler/widgets/fluent/scrollview.slint

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ export component ScrollView {
143143
in-out property <length> viewport-height <=> flickable.viewport-height;
144144
in-out property <length> viewport-x <=> flickable.viewport-x;
145145
in-out property <length> viewport-y <=> flickable.viewport-y;
146+
in property <bool> smooth-scroll: true;
146147
in property <ScrollBarPolicy> vertical-scrollbar-policy <=> vertical-bar.policy;
147148
in property <ScrollBarPolicy> horizontal-scrollbar-policy <=> horizontal-bar.policy;
148149
in property <bool> mouse-drag-pan-enabled <=> flickable.interactive;
@@ -163,6 +164,7 @@ export component ScrollView {
163164
interactive: false;
164165
viewport-y <=> vertical-bar.value;
165166
viewport-x <=> horizontal-bar.value;
167+
smooth-scroll: root.smooth-scroll;
166168
width: parent.width;
167169
height: parent.height;
168170

internal/compiler/widgets/material/scrollview.slint

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export component ScrollView {
102102
in-out property <length> viewport-height <=> flickable.viewport-height;
103103
in-out property <length> viewport-x <=> flickable.viewport-x;
104104
in-out property <length> viewport-y <=> flickable.viewport-y;
105+
in property <bool> smooth-scroll: true;
105106
in property <ScrollBarPolicy> vertical-scrollbar-policy <=> vertical-bar.policy;
106107
in property <ScrollBarPolicy> horizontal-scrollbar-policy <=> horizontal-bar.policy;
107108
in property <bool> mouse-drag-pan-enabled <=> flickable.interactive;
@@ -120,6 +121,7 @@ export component ScrollView {
120121
y: 0;
121122
viewport-y <=> vertical-bar.value;
122123
viewport-x <=> horizontal-bar.value;
124+
smooth-scroll: root.smooth-scroll;
123125
width: parent.width - vertical-bar.width - 4px;
124126
height: parent.height - horizontal-bar.height - 4px;
125127

internal/compiler/widgets/qt/internal-scrollview.slint

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export component InternalScrollView {
1515
out property <length> visible-height <=> fli.height;
1616
in-out property <bool> has-focus <=> native.has-focus;
1717
in property <bool> enabled <=> native.enabled;
18+
in property <bool> smooth-scroll: true;
1819
in property <ScrollBarPolicy> vertical-scrollbar-policy <=> native.vertical-scrollbar-policy;
1920
in property <ScrollBarPolicy> horizontal-scrollbar-policy <=> native.horizontal-scrollbar-policy;
2021
in property <bool> mouse-drag-pan-enabled <=> fli.interactive;
@@ -49,6 +50,7 @@ export component InternalScrollView {
4950

5051
@children
5152
interactive: false;
53+
smooth-scroll: root.smooth-scroll;
5254
viewport-y <=> native.vertical-value;
5355
viewport-x <=> native.horizontal-value;
5456
}

internal/compiler/widgets/qt/scrollview.slint

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export component ScrollView {
1212
in-out property <length> viewport-height <=> internal.viewport-height;
1313
in-out property <length> viewport-x <=> internal.viewport-x;
1414
in-out property <length> viewport-y <=> internal.viewport-y;
15+
in property <bool> smooth-scroll: true;
1516
in property <ScrollBarPolicy> vertical-scrollbar-policy <=> internal.vertical-scrollbar-policy;
1617
in property <ScrollBarPolicy> horizontal-scrollbar-policy <=> internal.horizontal-scrollbar-policy;
1718
in property <bool> mouse-drag-pan-enabled <=> internal.mouse-drag-pan-enabled;
@@ -26,6 +27,7 @@ export component ScrollView {
2627
preferred-width: 100%;
2728

2829
internal := InternalScrollView {
30+
smooth-scroll: root.smooth-scroll;
2931
@children
3032
}
3133
}

internal/core/items/flickable.rs

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::input::{
1414
FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, KeyEvent, MouseEvent,
1515
};
1616
use crate::item_rendering::CachedRenderingData;
17-
use crate::items::PropertyAnimation;
17+
use crate::items::{AnimationDirection, PropertyAnimation};
1818
use crate::layout::{LayoutInfo, Orientation};
1919
use crate::lengths::{
2020
LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalRect, LogicalSize, LogicalVector,
@@ -48,6 +48,7 @@ pub struct Flickable {
4848
pub viewport_height: Property<LogicalLength>,
4949

5050
pub interactive: Property<bool>,
51+
pub smooth_scroll: Property<bool>,
5152

5253
pub flicked: Callback<VoidArg>,
5354

@@ -242,6 +243,15 @@ pub(super) const DURATION_THRESHOLD: Duration = Duration::from_millis(500);
242243
/// The delay to which press are forwarded to the inner item
243244
pub(super) const FORWARD_DELAY: Duration = Duration::from_millis(100);
244245

246+
const SMOOTH_SCROLL_DURATION: i32 = 250;
247+
const SMOOTH_SCROLL_ANIM: PropertyAnimation = PropertyAnimation {
248+
duration: SMOOTH_SCROLL_DURATION,
249+
easing: EasingCurve::CubicBezier([0.0, 0.0, 0.58, 1.0]),
250+
delay: 0,
251+
iteration_count: 1.,
252+
direction: AnimationDirection::Normal,
253+
};
254+
245255
#[derive(Default, Debug)]
246256
struct FlickableDataInner {
247257
/// The position in which the press was made
@@ -250,6 +260,8 @@ struct FlickableDataInner {
250260
pressed_viewport_pos: LogicalPoint,
251261
/// Set to true if the flickable is flicking and capturing all mouse event, not forwarding back to the children
252262
capture_events: bool,
263+
smooth_scroll_time: Option<Instant>,
264+
smooth_scroll_target: LogicalPoint,
253265
}
254266

255267
#[derive(Default, Debug)]
@@ -395,7 +407,7 @@ impl FlickableData {
395407
}
396408
}
397409
MouseEvent::Wheel { delta_x, delta_y, .. } => {
398-
let delta = if window_adapter.window().0.modifiers.get().shift()
410+
let mut delta = if window_adapter.window().0.modifiers.get().shift()
399411
&& !cfg!(target_os = "macos")
400412
{
401413
// Shift invert coordinate for the purpose of scrolling. But not on macOs because there the OS already take care of the change
@@ -413,18 +425,50 @@ impl FlickableData {
413425
return InputEventResult::EventIgnored;
414426
}
415427

416-
let old_pos = LogicalPoint::from_lengths(
417-
(Flickable::FIELD_OFFSETS.viewport_x).apply_pin(flick).get(),
418-
(Flickable::FIELD_OFFSETS.viewport_y).apply_pin(flick).get(),
419-
);
420-
let new_pos = ensure_in_bound(flick, old_pos + delta, flick_rc);
421-
422428
let viewport_x = (Flickable::FIELD_OFFSETS.viewport_x).apply_pin(flick);
423429
let viewport_y = (Flickable::FIELD_OFFSETS.viewport_y).apply_pin(flick);
424-
let old_pos = (viewport_x.get(), viewport_y.get());
425-
viewport_x.set(new_pos.x_length());
426-
viewport_y.set(new_pos.y_length());
427-
if old_pos.0 != new_pos.x_length() || old_pos.1 != new_pos.y_length() {
430+
431+
let old_pos = LogicalPoint::from_lengths(viewport_x.get(), viewport_y.get());
432+
433+
let smooth_scroll = (Flickable::FIELD_OFFSETS.smooth_scroll).apply_pin(flick).get();
434+
435+
if smooth_scroll {
436+
// Accumulate scroll delta
437+
if let Some(smooth_scroll_time) = inner.smooth_scroll_time.take() {
438+
let millis = (crate::animations::current_tick() - smooth_scroll_time)
439+
.as_millis() as i32;
440+
441+
if millis < SMOOTH_SCROLL_DURATION {
442+
let remaining_delta = inner.smooth_scroll_target - old_pos;
443+
444+
// Only if is in the same direction.
445+
// `Default` is because in embedded `dot` returns `i32`
446+
// but in any other platform it returns `f32`
447+
if delta.dot(remaining_delta) > Default::default() {
448+
delta += remaining_delta;
449+
}
450+
}
451+
}
452+
}
453+
454+
let new_pos = ensure_in_bound(flick, old_pos + delta, flick_rc);
455+
456+
if smooth_scroll {
457+
inner.smooth_scroll_target = new_pos;
458+
inner.smooth_scroll_time = Some(Instant::now());
459+
460+
// Discard previous animation
461+
viewport_x.set_binding(|| euclid::Length::default());
462+
viewport_y.set_binding(|| euclid::Length::default());
463+
464+
viewport_x.set_animated_value(new_pos.x_length(), SMOOTH_SCROLL_ANIM);
465+
viewport_y.set_animated_value(new_pos.y_length(), SMOOTH_SCROLL_ANIM);
466+
} else {
467+
viewport_x.set(new_pos.x_length());
468+
viewport_y.set(new_pos.y_length());
469+
}
470+
471+
if old_pos != new_pos {
428472
(Flickable::FIELD_OFFSETS.flicked).apply_pin(flick).call(&());
429473
}
430474
InputEventResult::EventAccepted
@@ -449,24 +493,21 @@ impl FlickableData {
449493
{
450494
let speed = dist / (millis as f32);
451495

452-
let duration = 250;
453496
let final_pos = ensure_in_bound(
454497
flick,
455-
(inner.pressed_viewport_pos.cast() + dist + speed * (duration as f32)).cast(),
498+
(inner.pressed_viewport_pos.cast()
499+
+ dist
500+
+ speed * (SMOOTH_SCROLL_DURATION as f32))
501+
.cast(),
456502
flick_rc,
457503
);
458-
let anim = PropertyAnimation {
459-
duration,
460-
easing: EasingCurve::CubicBezier([0.0, 0.0, 0.58, 1.0]),
461-
..PropertyAnimation::default()
462-
};
463504

464505
let viewport_x = (Flickable::FIELD_OFFSETS.viewport_x).apply_pin(flick);
465506
let viewport_y = (Flickable::FIELD_OFFSETS.viewport_y).apply_pin(flick);
466-
let old_pos = (viewport_x.get(), viewport_y.get());
467-
viewport_x.set_animated_value(final_pos.x_length(), anim.clone());
468-
viewport_y.set_animated_value(final_pos.y_length(), anim);
469-
if old_pos.0 != final_pos.x_length() || old_pos.1 != final_pos.y_length() {
507+
let old_pos = LogicalPoint::from_lengths(viewport_x.get(), viewport_y.get());
508+
viewport_x.set_animated_value(final_pos.x_length(), SMOOTH_SCROLL_ANIM);
509+
viewport_y.set_animated_value(final_pos.y_length(), SMOOTH_SCROLL_ANIM);
510+
if old_pos != final_pos {
470511
(Flickable::FIELD_OFFSETS.flicked).apply_pin(flick).call(&());
471512
}
472513
}

internal/core/model.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1281,7 +1281,8 @@ impl<C: RepeatedItemTree + 'static> Repeater<C> {
12811281
viewport_height.set(inner.cached_item_height * row_count as Coord);
12821282
viewport_width.set(vp_width);
12831283
let new_viewport_y = -inner.anchor_y + new_offset_y;
1284-
viewport_y.set(new_viewport_y);
1284+
// XXX: This `set` prevents smooth scroll animated value
1285+
// viewport_y.set(new_viewport_y);
12851286
inner.previous_viewport_y = new_viewport_y;
12861287
break;
12871288
}

0 commit comments

Comments
 (0)