@@ -25,6 +25,7 @@ use std::char;
2525use std:: collections:: BTreeMap ;
2626use std:: mem;
2727use std:: ops:: { Deref , DerefMut } ;
28+ use std:: time:: Instant ;
2829
2930use direct2d:: math:: * ;
3031use direct2d:: RenderTarget ;
@@ -81,6 +82,18 @@ pub struct LayoutCtx {
8182 /// Bounding box of each widget. The position is relative to the parent.
8283 geom : Vec < Geometry > ,
8384
85+ /// Additional state per widget.
86+ ///
87+ /// A case can be made to fold `geom` here instead of having a separate array;
88+ /// this is the general SOA vs AOS discussion.
89+ per_widget : Vec < PerWidgetState > ,
90+
91+ /// State of animation request.
92+ anim_state : AnimState ,
93+
94+ /// The time of the last paint cycle.
95+ prev_paint_time : Option < Instant > ,
96+
8497 /// Queue of events to distribute to listeners
8598 event_q : Vec < ( Id , Box < Any > ) > ,
8699
@@ -108,6 +121,18 @@ pub struct Geometry {
108121 pub size : ( f32 , f32 ) ,
109122}
110123
124+ #[ derive( Default ) ]
125+ struct PerWidgetState {
126+ anim_frame_requested : bool ,
127+ }
128+
129+ enum AnimState {
130+ Idle ,
131+ InvalidationRequested ,
132+ AnimFrameStart ,
133+ AnimFrameRequested ,
134+ }
135+
111136#[ derive( Clone , Copy ) ]
112137pub struct BoxConstraints {
113138 min_width : f32 ,
@@ -188,6 +213,9 @@ impl UiState {
188213 c : LayoutCtx {
189214 dwrite_factory : directwrite:: Factory :: new ( ) . unwrap ( ) ,
190215 geom : Vec :: new ( ) ,
216+ per_widget : Vec :: new ( ) ,
217+ anim_state : AnimState :: Idle ,
218+ prev_paint_time : None ,
191219 handle : Default :: default ( ) ,
192220 event_q : Vec :: new ( ) ,
193221 focused : None ,
@@ -385,6 +413,35 @@ impl UiState {
385413 }
386414 }
387415
416+ // Process an animation frame. This consists mostly of calling anim_frame on
417+ // widgets that have requested a frame.
418+ fn anim_frame ( & mut self ) {
419+ // TODO: this is just wall-clock time, which will have jitter making
420+ // animations not as smooth. Should be extracting actual refresh rate
421+ // from presentation statistics and then doing some processing.
422+ let this_paint_time = Instant :: now ( ) ;
423+ let interval = if let Some ( last) = self . c . prev_paint_time {
424+ let duration = this_paint_time. duration_since ( last) ;
425+ 1_000_000_000 * duration. as_secs ( ) + ( duration. subsec_nanos ( ) as u64 )
426+ } else {
427+ 0
428+ } ;
429+ self . c . anim_state = AnimState :: AnimFrameStart ;
430+ for node in 0 ..self . widgets . len ( ) {
431+ if self . c . per_widget [ node] . anim_frame_requested {
432+ self . c . per_widget [ node] . anim_frame_requested = false ;
433+ self . inner . widgets [ node] . anim_frame ( interval,
434+ & mut HandlerCtx {
435+ id : node,
436+ c : & mut self . inner . c ,
437+ }
438+ ) ;
439+ }
440+ }
441+ self . c . prev_paint_time = Some ( this_paint_time) ;
442+ self . dispatch_events ( ) ;
443+ }
444+
388445 /// Translate coordinates to local coordinates of widget
389446 fn xy_to_local ( & mut self , mut node : Id , mut x : f32 , mut y : f32 ) -> ( f32 , f32 ) {
390447 loop {
@@ -444,6 +501,7 @@ impl UiInner {
444501 let id = self . graph . alloc_node ( ) ;
445502 self . widgets . push ( Box :: new ( widget) ) ;
446503 self . c . geom . push ( Default :: default ( ) ) ;
504+ self . c . per_widget . push ( Default :: default ( ) ) ;
447505 for & child in children {
448506 self . graph . append_child ( id, child) ;
449507 }
@@ -545,8 +603,14 @@ impl LayoutCtx {
545603impl < ' a > HandlerCtx < ' a > {
546604 /// Invalidate this widget. Finer-grained invalidation is not yet implemented,
547605 /// but when it is, this method will invalidate the widget's bounding box.
548- pub fn invalidate ( & self ) {
549- self . c . handle . invalidate ( ) ;
606+ pub fn invalidate ( & mut self ) {
607+ match self . c . anim_state {
608+ AnimState :: Idle => {
609+ self . c . handle . invalidate ( ) ;
610+ self . c . anim_state = AnimState :: InvalidationRequested ;
611+ }
612+ _ => ( ) ,
613+ }
550614 }
551615
552616 /// Send an event, to be handled by listeners.
@@ -570,6 +634,23 @@ impl<'a> HandlerCtx<'a> {
570634 pub fn is_hot ( & self ) -> bool {
571635 self . c . hot == Some ( self . id ) && ( self . is_active ( ) || self . c . active . is_none ( ) )
572636 }
637+
638+ /// Request an animation frame.
639+ ///
640+ /// Calling this schedules an animation frame, and also causes `anim_frame` to be
641+ /// called on this widget at the beginning of that frame.
642+ pub fn request_anim_frame ( & mut self ) {
643+ self . c . per_widget [ self . id ] . anim_frame_requested = true ;
644+ match self . c . anim_state {
645+ AnimState :: Idle => {
646+ self . invalidate ( ) ;
647+ }
648+ AnimState :: AnimFrameStart => {
649+ self . c . anim_state = AnimState :: AnimFrameRequested ;
650+ }
651+ _ => ( ) ,
652+ }
653+ }
573654}
574655
575656impl < ' a > Deref for ListenerCtx < ' a > {
@@ -640,6 +721,8 @@ impl WinHandler for UiMain {
640721 }
641722
642723 fn paint ( & self , paint_ctx : & mut paint:: PaintCtx ) -> bool {
724+ let mut state = self . state . borrow_mut ( ) ;
725+ state. anim_frame ( ) ;
643726 let size;
644727 {
645728 let rt = paint_ctx. render_target ( ) ;
@@ -648,13 +731,19 @@ impl WinHandler for UiMain {
648731 let bg = SolidColorBrush :: create ( rt) . with_color ( 0x272822 ) . build ( ) . unwrap ( ) ;
649732 rt. fill_rectangle ( rect, & bg) ;
650733 }
651- let mut state = self . state . borrow_mut ( ) ;
652734 let root = state. graph . root ;
653735 let bc = BoxConstraints :: tight ( ( size. width , size. height ) ) ;
654736 // TODO: be lazier about relayout
655737 state. layout ( & bc, root) ;
656738 state. paint ( paint_ctx, root) ;
657- false
739+ match state. c . anim_state {
740+ AnimState :: AnimFrameRequested => true ,
741+ _ => {
742+ state. c . anim_state = AnimState :: Idle ;
743+ state. c . prev_paint_time = None ;
744+ false
745+ }
746+ }
658747 }
659748
660749 fn command ( & self , id : u32 ) {
0 commit comments