@@ -2,50 +2,191 @@ export * from './animation-common';
22export * from './animation-shared' ;
33export * from './animation-types' ;
44
5- import { AnimationBase } from './animation-common' ;
5+ import { AnimationBase , Properties } from './animation-common' ;
6+ import type { PropertyAnimation } from './animation-shared' ;
7+ import { CubicBezierAnimationCurve } from '../../core-types/animation-types' ;
8+ import { CoreTypes } from '../../core-types' ;
9+ import { Color } from '../../color' ;
10+ import { requestAnimationFrame , cancelAnimationFrame } from '../../animation-frame' ;
611
7- export function _resolveAnimationCurve ( curve : any ) : any {
8- return curve ;
12+ type Easing = ( t : number ) => number ;
13+
14+ /** Cubic bezier easing (same control points as CSS / Core Animation). */
15+ function bezier ( x1 : number , y1 : number , x2 : number , y2 : number ) : Easing {
16+ const a = ( p1 : number , p2 : number ) => 1 - 3 * p2 + 3 * p1 ;
17+ const b = ( p1 : number , p2 : number ) => 3 * p2 - 6 * p1 ;
18+ const c = ( p1 : number ) => 3 * p1 ;
19+ const at = ( t : number , p1 : number , p2 : number ) => ( ( a ( p1 , p2 ) * t + b ( p1 , p2 ) ) * t + c ( p1 ) ) * t ;
20+ const slope = ( t : number , p1 : number , p2 : number ) => 3 * a ( p1 , p2 ) * t * t + 2 * b ( p1 , p2 ) * t + c ( p1 ) ;
21+ return ( x : number ) => {
22+ if ( x <= 0 || x >= 1 ) return x ;
23+ // Newton-Raphson for t(x)
24+ let t = x ;
25+ for ( let i = 0 ; i < 8 ; i ++ ) {
26+ const dx = at ( t , x1 , x2 ) - x ;
27+ const d = slope ( t , x1 , x2 ) ;
28+ if ( Math . abs ( dx ) < 1e-5 || Math . abs ( d ) < 1e-6 ) break ;
29+ t -= dx / d ;
30+ }
31+ return at ( Math . min ( 1 , Math . max ( 0 , t ) ) , y1 , y2 ) ;
32+ } ;
33+ }
34+
35+ const CURVES : Record < string , Easing > = {
36+ [ CoreTypes . AnimationCurve . linear ] : ( t ) => t ,
37+ [ CoreTypes . AnimationCurve . ease ] : bezier ( 0.25 , 0.1 , 0.25 , 1 ) ,
38+ [ CoreTypes . AnimationCurve . easeIn ] : bezier ( 0.42 , 0 , 1 , 1 ) ,
39+ [ CoreTypes . AnimationCurve . easeOut ] : bezier ( 0 , 0 , 0.58 , 1 ) ,
40+ [ CoreTypes . AnimationCurve . easeInOut ] : bezier ( 0.42 , 0 , 0.58 , 1 ) ,
41+ [ CoreTypes . AnimationCurve . spring ] : bezier ( 0.5 , 1.6 , 0.4 , 0.8 ) ,
42+ } ;
43+
44+ export function _resolveAnimationCurve ( curve : any ) : Easing {
45+ if ( curve instanceof CubicBezierAnimationCurve ) {
46+ return bezier ( curve . x1 , curve . y1 , curve . x2 , curve . y2 ) ;
47+ }
48+ if ( typeof curve === 'function' ) {
49+ return curve ;
50+ }
51+ return CURVES [ curve ] ?? CURVES [ CoreTypes . AnimationCurve . easeInOut ] ;
952}
1053
11- // TODO(linux): frame-clock driven animations; for now targets are applied at once.
54+ const mix = ( from : number , to : number , t : number ) => from + ( to - from ) * t ;
55+
56+ /** Reads the current value and returns an applier for progress 0..1. */
57+ function animator ( animation : PropertyAnimation ) : ( ( t : number ) => void ) | null {
58+ const target : any = animation . target ;
59+ const to : any = animation . value ;
60+ switch ( animation . property ) {
61+ case Properties . opacity : {
62+ const from = target . opacity ?? 1 ;
63+ return ( t ) => ( target . opacity = mix ( from , to , t ) ) ;
64+ }
65+ case Properties . backgroundColor : {
66+ const from : Color = target . backgroundColor instanceof Color ? target . backgroundColor : new Color ( 0 , 0 , 0 , 0 ) ;
67+ const end : Color = to instanceof Color ? to : new Color ( to ) ;
68+ return ( t ) => ( target . backgroundColor = new Color ( Math . round ( mix ( from . a , end . a , t ) ) , Math . round ( mix ( from . r , end . r , t ) ) , Math . round ( mix ( from . g , end . g , t ) ) , Math . round ( mix ( from . b , end . b , t ) ) ) ) ;
69+ }
70+ case Properties . translate : {
71+ const from = [ target . translateX || 0 , target . translateY || 0 ] ;
72+ return ( t ) => {
73+ target . translateX = mix ( from [ 0 ] , to . x , t ) ;
74+ target . translateY = mix ( from [ 1 ] , to . y , t ) ;
75+ } ;
76+ }
77+ case Properties . scale : {
78+ const from = [ target . scaleX ?? 1 , target . scaleY ?? 1 ] ;
79+ return ( t ) => {
80+ target . scaleX = mix ( from [ 0 ] , to . x , t ) ;
81+ target . scaleY = mix ( from [ 1 ] , to . y , t ) ;
82+ } ;
83+ }
84+ case Properties . rotate : {
85+ const from = target . rotate || 0 ;
86+ const end = typeof to === 'number' ? to : to . z ;
87+ return ( t ) => ( target . rotate = mix ( from , end , t ) ) ;
88+ }
89+ case Properties . width :
90+ case Properties . height : {
91+ const key = animation . property ;
92+ const measured = key === Properties . width ? target . getActualSize ?.( ) . width : target . getActualSize ?.( ) . height ;
93+ const from = typeof target [ key ] === 'number' ? target [ key ] : measured ;
94+ // percentages / auto can't be tweened: jump at the end
95+ if ( typeof to !== 'number' || typeof from !== 'number' ) {
96+ return ( t ) => t >= 1 && ( target [ key ] = to ) ;
97+ }
98+ return ( t ) => ( target [ key ] = mix ( from , to , t ) ) ;
99+ }
100+ default :
101+ return null ;
102+ }
103+ }
104+
105+ /** Frame-driven tweens of the view properties (the views render through GTK). */
12106export class Animation extends AnimationBase {
107+ private _frame = 0 ;
108+ private _cancelled = false ;
109+
13110 public play ( ) : any {
111+ if ( this . isPlaying ) {
112+ return this . _rejectAlreadyPlaying ( ) ;
113+ }
14114 const promise = super . play ( ) ;
15- for ( const property of this . _propertyAnimations ) {
16- const target : any = property . target ;
17- const value : any = property . value ;
18- switch ( property . property ) {
19- case 'opacity' :
20- target . opacity = value ;
21- break ;
22- case 'backgroundColor' :
23- target . backgroundColor = value ;
24- break ;
25- case 'translate' :
26- target . translateX = value . x ;
27- target . translateY = value . y ;
28- break ;
29- case 'scale' :
30- target . scaleX = value . x ;
31- target . scaleY = value . y ;
32- break ;
33- case 'rotate' :
34- target . rotate = typeof value === 'number' ? value : value . z ;
35- break ;
36- case 'width' :
37- target . width = value ;
38- break ;
39- case 'height' :
40- target . height = value ;
41- break ;
42- }
115+ this . _cancelled = false ;
116+ const animations = this . _propertyAnimations ?? [ ] ;
117+ if ( this . _playSequentially ) {
118+ animations
119+ . reduce ( ( chain , animation ) => chain . then ( ( ) => this . _run ( [ animation ] ) ) , Promise . resolve ( ) )
120+ . then (
121+ ( ) => this . _done ( ) ,
122+ ( ) => { } ,
123+ ) ;
124+ } else {
125+ this . _run ( animations ) . then (
126+ ( ) => this . _done ( ) ,
127+ ( ) => { } ,
128+ ) ;
43129 }
44- setTimeout ( ( ) => this . _resolveAnimationFinishedPromise ( ) , 0 ) ;
45130 return promise ;
46131 }
47132
133+ private _done ( ) {
134+ if ( ! this . _cancelled ) {
135+ this . _resolveAnimationFinishedPromise ( ) ;
136+ }
137+ }
138+
139+ /** Runs animations in parallel on one frame loop. */
140+ private _run ( animations : PropertyAnimation [ ] ) : Promise < void > {
141+ return new Promise ( ( resolve , reject ) => {
142+ const running = animations . map ( ( animation ) => ( { animation, apply : null as ( ( t : number ) => void ) | null , start : 0 , easing : _resolveAnimationCurve ( animation . curve ) } ) ) . filter ( ( r ) => r . animation . target ) ;
143+ const startedAt = Date . now ( ) ;
144+ const tick = ( ) => {
145+ if ( this . _cancelled ) {
146+ reject ( ) ;
147+ return ;
148+ }
149+ const now = Date . now ( ) ;
150+ let pending = false ;
151+ for ( const r of running ) {
152+ const { animation } = r ;
153+ const delay = animation . delay ?? 0 ;
154+ const duration = Math . max ( 1 , animation . duration ?? 300 ) ;
155+ const iterations = animation . iterations ?? 1 ;
156+ const elapsed = now - startedAt - delay ;
157+ if ( elapsed < 0 ) {
158+ pending = true ;
159+ continue ;
160+ }
161+ // capture start values when the property starts moving
162+ r . apply ??= animator ( animation ) ;
163+ if ( ! r . apply ) continue ;
164+ const cycle = elapsed / duration ;
165+ const finished = cycle >= iterations ;
166+ const t = finished ? 1 : cycle % 1 ;
167+ r . apply ( r . easing ( t ) ) ;
168+ pending ||= ! finished ;
169+ }
170+ if ( pending ) {
171+ this . _frame = requestAnimationFrame ( tick ) ;
172+ } else {
173+ resolve ( ) ;
174+ }
175+ } ;
176+ this . _frame = requestAnimationFrame ( tick ) ;
177+ } ) ;
178+ }
179+
180+ public cancel ( ) : void {
181+ if ( ! this . isPlaying ) {
182+ return ;
183+ }
184+ this . _cancelled = true ;
185+ cancelAnimationFrame ( this . _frame ) ;
186+ this . _rejectAnimationFinishedPromise ( ) ;
187+ }
188+
48189 public _resolveAnimationCurve ( curve : any ) : any {
49- return curve ;
190+ return _resolveAnimationCurve ( curve ) ;
50191 }
51192}
0 commit comments