@@ -11,14 +11,19 @@ use crate::core::touch;
1111use crate :: core:: widget;
1212use crate :: core:: widget:: operation:: { self , Operation } ;
1313use crate :: core:: widget:: tree:: { self , Tree } ;
14+ use crate :: core:: window;
1415use crate :: core:: {
1516 self , Background , Clipboard , Color , Element , Layout , Length , Padding ,
1617 Pixels , Point , Rectangle , Shell , Size , Theme , Vector , Widget ,
1718} ;
18- use crate :: runtime:: task:: { self , Task } ;
19- use crate :: runtime:: Action ;
20-
19+ use crate :: runtime:: {
20+ task:: { self , Task } ,
21+ Action ,
22+ } ;
23+ use lilt:: Animated ;
24+ use lilt:: Easing :: EaseOut ;
2125pub use operation:: scrollable:: { AbsoluteOffset , RelativeOffset } ;
26+ use std:: time:: Instant ;
2227
2328/// A widget that can vertically display an infinite amount of content with a
2429/// scrollbar.
@@ -39,6 +44,7 @@ pub struct Scrollable<
3944 content : Element < ' a , Message , Theme , Renderer > ,
4045 on_scroll : Option < Box < dyn Fn ( Viewport ) -> Message + ' a > > ,
4146 class : Theme :: Class < ' a > ,
47+ animation_duration_ms : f32 ,
4248}
4349
4450impl < ' a , Message , Theme , Renderer > Scrollable < ' a , Message , Theme , Renderer >
5864 content : content. into ( ) ,
5965 on_scroll : None ,
6066 class : Theme :: default ( ) ,
67+ animation_duration_ms : 200. ,
6168 }
6269 . validate ( )
6370 }
@@ -338,7 +345,7 @@ where
338345 }
339346
340347 fn state ( & self ) -> tree:: State {
341- tree:: State :: new ( State :: new ( ) )
348+ tree:: State :: new ( State :: new ( self . animation_duration_ms ) )
342349 }
343350
344351 fn children ( & self ) -> Vec < Tree > {
@@ -479,10 +486,14 @@ where
479486 return event:: Status :: Ignored ;
480487 } ;
481488
482- state. scroll_y_to ( scrollbar. scroll_percentage_y (
483- scroller_grabbed_at,
484- cursor_position,
485- ) ) ;
489+ state. scroll_y_to (
490+ scrollbar. scroll_percentage_y (
491+ scroller_grabbed_at,
492+ cursor_position,
493+ ) ,
494+ true ,
495+ ) ;
496+ shell. request_redraw ( window:: RedrawRequest :: NextFrame ) ;
486497
487498 let _ = notify_on_scroll (
488499 state,
@@ -511,10 +522,14 @@ where
511522 scrollbars. grab_y_scroller ( cursor_position) ,
512523 scrollbars. y ,
513524 ) {
514- state. scroll_y_to ( scrollbar. scroll_percentage_y (
515- scroller_grabbed_at,
516- cursor_position,
517- ) ) ;
525+ state. scroll_y_to (
526+ scrollbar. scroll_percentage_y (
527+ scroller_grabbed_at,
528+ cursor_position,
529+ ) ,
530+ false ,
531+ ) ;
532+ shell. request_redraw ( window:: RedrawRequest :: NextFrame ) ;
518533
519534 state. y_scroller_grabbed_at = Some ( scroller_grabbed_at) ;
520535
@@ -542,10 +557,14 @@ where
542557 } ;
543558
544559 if let Some ( scrollbar) = scrollbars. x {
545- state. scroll_x_to ( scrollbar. scroll_percentage_x (
546- scroller_grabbed_at,
547- cursor_position,
548- ) ) ;
560+ state. scroll_x_to (
561+ scrollbar. scroll_percentage_x (
562+ scroller_grabbed_at,
563+ cursor_position,
564+ ) ,
565+ true ,
566+ ) ;
567+ shell. request_redraw ( window:: RedrawRequest :: NextFrame ) ;
549568
550569 let _ = notify_on_scroll (
551570 state,
@@ -574,10 +593,14 @@ where
574593 scrollbars. grab_x_scroller ( cursor_position) ,
575594 scrollbars. x ,
576595 ) {
577- state. scroll_x_to ( scrollbar. scroll_percentage_x (
578- scroller_grabbed_at,
579- cursor_position,
580- ) ) ;
596+ state. scroll_x_to (
597+ scrollbar. scroll_percentage_x (
598+ scroller_grabbed_at,
599+ cursor_position,
600+ ) ,
601+ false ,
602+ ) ;
603+ shell. request_redraw ( window:: RedrawRequest :: NextFrame ) ;
581604
582605 state. x_scroller_grabbed_at = Some ( scroller_grabbed_at) ;
583606
@@ -644,6 +667,19 @@ where
644667 state. x_scroller_grabbed_at = None ;
645668 state. y_scroller_grabbed_at = None ;
646669
670+ // Reset animations durations from instantaneous to default.
671+ // This is necessary because we change the animation duration when
672+ // grabbing the scrollbars, and are unable to access the animation
673+ // duration in all methods, such as `scroll_to` and `snap_to`.
674+ state. y_animation = state
675+ . y_animation
676+ . clone ( )
677+ . duration ( self . animation_duration_ms ) ;
678+ state. x_animation = state
679+ . x_animation
680+ . clone ( )
681+ . duration ( self . animation_duration_ms ) ;
682+
647683 return event_status;
648684 }
649685
@@ -682,6 +718,7 @@ where
682718 } ;
683719
684720 state. scroll ( delta, self . direction , bounds, content_bounds) ;
721+ shell. request_redraw ( window:: RedrawRequest :: NextFrame ) ;
685722
686723 event_status = if notify_on_scroll (
687724 state,
@@ -727,6 +764,9 @@ where
727764 bounds,
728765 content_bounds,
729766 ) ;
767+ shell. request_redraw (
768+ window:: RedrawRequest :: NextFrame ,
769+ ) ;
730770
731771 state. scroll_area_touched_at =
732772 Some ( cursor_position) ;
@@ -746,6 +786,13 @@ where
746786
747787 event_status = event:: Status :: Captured ;
748788 }
789+ Event :: Window ( window:: Event :: RedrawRequested ( now) ) => {
790+ if state. x_animation . in_progress ( now)
791+ || state. y_animation . in_progress ( now)
792+ {
793+ shell. request_redraw ( window:: RedrawRequest :: NextFrame ) ;
794+ }
795+ }
749796 _ => { }
750797 }
751798
@@ -1122,7 +1169,7 @@ fn notify_on_scroll<Message>(
11221169 true
11231170}
11241171
1125- #[ derive( Debug , Clone , Copy ) ]
1172+ #[ derive( Debug , Clone ) ]
11261173struct State {
11271174 scroll_area_touched_at : Option < Point > ,
11281175 offset_y_relative : f32 ,
@@ -1131,20 +1178,8 @@ struct State {
11311178 x_scroller_grabbed_at : Option < f32 > ,
11321179 keyboard_modifiers : keyboard:: Modifiers ,
11331180 last_notified : Option < Viewport > ,
1134- }
1135-
1136- impl Default for State {
1137- fn default ( ) -> Self {
1138- Self {
1139- scroll_area_touched_at : None ,
1140- offset_y_relative : 0.0 ,
1141- y_scroller_grabbed_at : None ,
1142- offset_x_relative : 0.0 ,
1143- x_scroller_grabbed_at : None ,
1144- keyboard_modifiers : keyboard:: Modifiers :: default ( ) ,
1145- last_notified : None ,
1146- }
1147- }
1181+ y_animation : Animated < f32 , Instant > ,
1182+ x_animation : Animated < f32 , Instant > ,
11481183}
11491184
11501185impl operation:: Scrollable for State {
@@ -1261,8 +1296,22 @@ impl Viewport {
12611296
12621297impl State {
12631298 /// Creates a new [`State`] with the scrollbar(s) at the beginning.
1264- pub fn new ( ) -> Self {
1265- State :: default ( )
1299+ pub fn new ( animation_duration_ms : f32 ) -> Self {
1300+ Self {
1301+ scroll_area_touched_at : None ,
1302+ offset_y_relative : 0.0 ,
1303+ y_scroller_grabbed_at : None ,
1304+ offset_x_relative : 0.0 ,
1305+ x_scroller_grabbed_at : None ,
1306+ keyboard_modifiers : keyboard:: Modifiers :: default ( ) ,
1307+ last_notified : None ,
1308+ y_animation : Animated :: new ( 0.0 )
1309+ . easing ( EaseOut )
1310+ . duration ( animation_duration_ms) ,
1311+ x_animation : Animated :: new ( 0.0 )
1312+ . easing ( EaseOut )
1313+ . duration ( animation_duration_ms) ,
1314+ }
12661315 }
12671316
12681317 /// Apply a scrolling offset to the current [`State`], given the bounds of
@@ -1294,13 +1343,15 @@ impl State {
12941343 align ( vertical_alignment, delta. y ) ,
12951344 ) ;
12961345
1346+ let now = Instant :: now ( ) ;
12971347 if bounds. height < content_bounds. height {
12981348 self . offset_y_relative =
12991349 ( ( Offset :: Relative ( self . offset_y_relative )
13001350 . absolute ( bounds. height , content_bounds. height )
13011351 - delta. y )
13021352 . clamp ( 0.0 , content_bounds. height - bounds. height ) )
13031353 / ( content_bounds. height - bounds. height ) ;
1354+ self . y_animation . transition ( self . offset_y_relative , now) ;
13041355 }
13051356
13061357 if bounds. width < content_bounds. width {
@@ -1310,29 +1361,51 @@ impl State {
13101361 - delta. x )
13111362 . clamp ( 0.0 , content_bounds. width - bounds. width ) )
13121363 / ( content_bounds. width - bounds. width ) ;
1364+ self . x_animation . transition ( self . offset_x_relative , now) ;
13131365 }
13141366 }
13151367
13161368 /// Scrolls the [`Scrollable`] to a relative amount along the y axis.
13171369 ///
13181370 /// `0` represents scrollbar at the beginning, while `1` represents scrollbar at
13191371 /// the end.
1320- pub fn scroll_y_to ( & mut self , percentage : f32 ) {
1321- self . offset_y_relative = percentage. clamp ( 0.0 , 1.0 ) ;
1372+ ///
1373+ /// When `instantaneous` is set to `true`, the transition uses no animation.
1374+ pub fn scroll_y_to ( & mut self , percentage : f32 , instantaneous : bool ) {
1375+ let percentage = percentage. clamp ( 0.0 , 1.0 ) ;
1376+ self . offset_y_relative = percentage;
1377+ if instantaneous {
1378+ self . y_animation
1379+ . transition_instantaneous ( percentage, Instant :: now ( ) ) ;
1380+ } else {
1381+ self . y_animation . transition ( percentage, Instant :: now ( ) ) ;
1382+ }
13221383 }
13231384
13241385 /// Scrolls the [`Scrollable`] to a relative amount along the x axis.
13251386 ///
13261387 /// `0` represents scrollbar at the beginning, while `1` represents scrollbar at
13271388 /// the end.
1328- pub fn scroll_x_to ( & mut self , percentage : f32 ) {
1329- self . offset_x_relative = percentage. clamp ( 0.0 , 1.0 ) ;
1389+ ///
1390+ /// When `instantaneous` is set to `true`, the transition uses no animation.
1391+ pub fn scroll_x_to ( & mut self , percentage : f32 , instantaneous : bool ) {
1392+ let percentage = percentage. clamp ( 0.0 , 1.0 ) ;
1393+ self . offset_x_relative = percentage;
1394+ if instantaneous {
1395+ self . x_animation
1396+ . transition_instantaneous ( percentage, Instant :: now ( ) ) ;
1397+ } else {
1398+ self . x_animation . transition ( percentage, Instant :: now ( ) ) ;
1399+ }
13301400 }
13311401
13321402 /// Snaps the scroll position to a [`RelativeOffset`].
13331403 pub fn snap_to ( & mut self , offset : RelativeOffset ) {
1404+ let now = Instant :: now ( ) ;
13341405 self . offset_x_relative = offset. x . clamp ( 0.0 , 1.0 ) ;
13351406 self . offset_y_relative = offset. y . clamp ( 0.0 , 1.0 ) ;
1407+ self . x_animation . transition ( self . offset_x_relative , now) ;
1408+ self . y_animation . transition ( self . offset_y_relative , now) ;
13361409 }
13371410
13381411 /// Scroll to the provided [`AbsoluteOffset`].
@@ -1342,10 +1415,15 @@ impl State {
13421415 bounds : Rectangle ,
13431416 content_bounds : Rectangle ,
13441417 ) {
1418+ let now = Instant :: now ( ) ;
13451419 self . offset_x_relative = Offset :: Absolute ( offset. x . max ( 0.0 ) )
1346- . relative ( bounds. width , content_bounds. width ) ;
1420+ . relative ( bounds. width , content_bounds. width )
1421+ . clamp ( 0.0 , 1.0 ) ;
13471422 self . offset_y_relative = Offset :: Absolute ( offset. y . max ( 0.0 ) )
1348- . relative ( bounds. height , content_bounds. height ) ;
1423+ . relative ( bounds. height , content_bounds. height )
1424+ . clamp ( 0.0 , 1.0 ) ;
1425+ self . x_animation . transition ( self . offset_x_relative , now) ;
1426+ self . y_animation . transition ( self . offset_y_relative , now) ;
13491427 }
13501428
13511429 /// Returns the scrolling translation of the [`State`], given a [`Direction`],
@@ -1358,7 +1436,10 @@ impl State {
13581436 ) -> Vector {
13591437 Vector :: new (
13601438 if let Some ( horizontal) = direction. horizontal ( ) {
1361- Offset :: Relative ( self . offset_x_relative ) . translation (
1439+ Offset :: Relative (
1440+ self . x_animation . animate ( |target| target, Instant :: now ( ) ) ,
1441+ )
1442+ . translation (
13621443 bounds. width ,
13631444 content_bounds. width ,
13641445 horizontal. alignment ,
@@ -1367,7 +1448,10 @@ impl State {
13671448 0.0
13681449 } ,
13691450 if let Some ( vertical) = direction. vertical ( ) {
1370- Offset :: Relative ( self . offset_y_relative ) . translation (
1451+ Offset :: Relative (
1452+ self . y_animation . animate ( |target| target, Instant :: now ( ) ) ,
1453+ )
1454+ . translation (
13711455 bounds. height ,
13721456 content_bounds. height ,
13731457 vertical. alignment ,
0 commit comments