@@ -14,7 +14,7 @@ use crate::input::{
14
14
FocusEvent , FocusEventResult , InputEventFilterResult , InputEventResult , KeyEvent , MouseEvent ,
15
15
} ;
16
16
use crate :: item_rendering:: CachedRenderingData ;
17
- use crate :: items:: PropertyAnimation ;
17
+ use crate :: items:: { AnimationDirection , PropertyAnimation } ;
18
18
use crate :: layout:: { LayoutInfo , Orientation } ;
19
19
use crate :: lengths:: {
20
20
LogicalBorderRadius , LogicalLength , LogicalPoint , LogicalRect , LogicalSize , LogicalVector ,
@@ -48,6 +48,7 @@ pub struct Flickable {
48
48
pub viewport_height : Property < LogicalLength > ,
49
49
50
50
pub interactive : Property < bool > ,
51
+ pub smooth_scroll : Property < bool > ,
51
52
52
53
pub flicked : Callback < VoidArg > ,
53
54
@@ -242,6 +243,15 @@ pub(super) const DURATION_THRESHOLD: Duration = Duration::from_millis(500);
242
243
/// The delay to which press are forwarded to the inner item
243
244
pub ( super ) const FORWARD_DELAY : Duration = Duration :: from_millis ( 100 ) ;
244
245
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
+
245
255
#[ derive( Default , Debug ) ]
246
256
struct FlickableDataInner {
247
257
/// The position in which the press was made
@@ -250,6 +260,8 @@ struct FlickableDataInner {
250
260
pressed_viewport_pos : LogicalPoint ,
251
261
/// Set to true if the flickable is flicking and capturing all mouse event, not forwarding back to the children
252
262
capture_events : bool ,
263
+ smooth_scroll_time : Option < Instant > ,
264
+ smooth_scroll_target : LogicalPoint ,
253
265
}
254
266
255
267
#[ derive( Default , Debug ) ]
@@ -395,7 +407,7 @@ impl FlickableData {
395
407
}
396
408
}
397
409
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 ( )
399
411
&& !cfg ! ( target_os = "macos" )
400
412
{
401
413
// 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 {
413
425
return InputEventResult :: EventIgnored ;
414
426
}
415
427
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
-
422
428
let viewport_x = ( Flickable :: FIELD_OFFSETS . viewport_x ) . apply_pin ( flick) ;
423
429
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 {
428
472
( Flickable :: FIELD_OFFSETS . flicked ) . apply_pin ( flick) . call ( & ( ) ) ;
429
473
}
430
474
InputEventResult :: EventAccepted
@@ -449,24 +493,21 @@ impl FlickableData {
449
493
{
450
494
let speed = dist / ( millis as f32 ) ;
451
495
452
- let duration = 250 ;
453
496
let final_pos = ensure_in_bound (
454
497
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 ( ) ,
456
502
flick_rc,
457
503
) ;
458
- let anim = PropertyAnimation {
459
- duration,
460
- easing : EasingCurve :: CubicBezier ( [ 0.0 , 0.0 , 0.58 , 1.0 ] ) ,
461
- ..PropertyAnimation :: default ( )
462
- } ;
463
504
464
505
let viewport_x = ( Flickable :: FIELD_OFFSETS . viewport_x ) . apply_pin ( flick) ;
465
506
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 {
470
511
( Flickable :: FIELD_OFFSETS . flicked ) . apply_pin ( flick) . call ( & ( ) ) ;
471
512
}
472
513
}
0 commit comments