API 0.9.5
AppKit/CPAnimation.j
Go to the documentation of this file.
00001 /*
00002  * CPAnimation.j
00003  * AppKit
00004  *
00005  * Created by Francisco Tolmasky.
00006  * Copyright 2008, 280 North, Inc.
00007  *
00008  * This library is free software; you can redistribute it and/or
00009  * modify it under the terms of the GNU Lesser General Public
00010  * License as published by the Free Software Foundation; either
00011  * version 2.1 of the License, or (at your option) any later version.
00012  *
00013  * This library is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00016  * Lesser General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU Lesser General Public
00019  * License along with this library; if not, write to the Free Software
00020  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
00021  */
00022 
00023 
00024 
00025 
00026 /*
00027     @global
00028     @group CPAnimationCurve
00029 */
00030 CPAnimationEaseInOut    = 0;
00031 /*
00032     @global
00033     @group CPAnimationCurve
00034 */
00035 CPAnimationEaseIn       = 1;
00036 /*
00037     @global
00038     @group CPAnimationCurve
00039 */
00040 CPAnimationEaseOut      = 2;
00041 /*
00042     @global
00043     @group CPAnimationCurve
00044 */
00045 CPAnimationLinear       = 3;
00046 
00047 ACTUAL_FRAME_RATE = 0;
00048 
00078 @implementation CPAnimation : CPObject
00079 {
00080     CPTimeInterval          _lastTime;
00081     CPTimeInterval          _duration;
00082 
00083     CPAnimationCurve        _animationCurve;
00084     CAMediaTimingFunction   _timingFunction;
00085 
00086     float                   _frameRate;
00087     float                   _progress;
00088 
00089     id                      _delegate;
00090     CPTimer                 _timer;
00091 }
00092 
00099 - (id)initWithDuration:(float)aDuration animationCurve:(CPAnimationCurve)anAnimationCurve
00100 {
00101     self = [super init];
00102 
00103     if (self)
00104     {
00105         _progress = 0.0;
00106         _duration = MAX(0.0, aDuration);
00107         _frameRate = 60.0;
00108 
00109         [self setAnimationCurve:anAnimationCurve];
00110     }
00111 
00112     return self;
00113 }
00114 
00120 - (void)setAnimationCurve:(CPAnimationCurve)anAnimationCurve
00121 {
00122     switch (anAnimationCurve)
00123     {
00124         case CPAnimationEaseInOut:  timingFunctionName = kCAMediaTimingFunctionEaseInEaseOut;
00125                                     break;
00126 
00127         case CPAnimationEaseIn:     timingFunctionName = kCAMediaTimingFunctionEaseIn;
00128                                     break;
00129 
00130         case CPAnimationEaseOut:    timingFunctionName = kCAMediaTimingFunctionEaseOut;
00131                                     break;
00132 
00133         case CPAnimationLinear:     timingFunctionName = kCAMediaTimingFunctionLinear;
00134                                     break;
00135 
00136         default:                    [CPException raise:CPInvalidArgumentException
00137                                                 reason:"Invalid value provided for animation curve"];
00138                                     break;
00139     }
00140 
00141     _animationCurve = anAnimationCurve;
00142     _timingFunction = [CAMediaTimingFunction functionWithName:timingFunctionName];
00143 }
00144 
00148 - (CPAnimationCurve)animationCurve
00149 {
00150     return _animationCurve;
00151 }
00152 
00158 - (void)setDuration:(CPTimeInterval)aDuration
00159 {
00160     if (aDuration < 0)
00161         [CPException raise:CPInvalidArgumentException reason:"aDuration can't be negative"];
00162 
00163     _duration = aDuration;
00164 }
00165 
00169 - (CPTimeInterval)duration
00170 {
00171     return _duration;
00172 }
00173 
00179 - (void)setFrameRate:(float)frameRate
00180 {
00181     if (frameRate < 0)
00182         [CPException raise:CPInvalidArgumentException reason:"frameRate can't be negative"];
00183 
00184     _frameRate = frameRate;
00185 }
00186 
00190 - (float)frameRate
00191 {
00192     return _frameRate;
00193 }
00194 
00198 - (id)delegate
00199 {
00200     return _delegate;
00201 }
00202 
00207 - (void)setDelegate:(id)aDelegate
00208 {
00209     _delegate = aDelegate;
00210 }
00211 
00217 - (void)startAnimation
00218 {
00219     // If we're already animating, or our delegate stops us, animate.
00220     if (_timer || _delegate && [_delegate respondsToSelector:@selector(animationShouldStart:)] && ![_delegate animationShouldStart:self])
00221         return;
00222 
00223     if (_progress === 1.0)
00224         _progress = 0.0;
00225 
00226     ACTUAL_FRAME_RATE = 0;
00227     _lastTime = new Date();
00228 
00229     _timer = [CPTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(animationTimerDidFire:) userInfo:nil repeats:YES];
00230 }
00231 
00232 /*
00233     @ignore
00234 */
00235 - (void)animationTimerDidFire:(CPTimer)aTimer
00236 {
00237     var currentTime = new Date(),
00238         progress = MIN(1.0, [self currentProgress] + (currentTime - _lastTime) / (_duration * 1000.0));
00239 
00240     _lastTime = currentTime;
00241 
00242     ++ACTUAL_FRAME_RATE;
00243 
00244     [self setCurrentProgress:progress];
00245 
00246     if (progress === 1.0)
00247     {
00248         [_timer invalidate];
00249         _timer = nil;
00250 
00251         if ([_delegate respondsToSelector:@selector(animationDidEnd:)])
00252             [_delegate animationDidEnd:self];
00253     }
00254 }
00255 
00259 - (void)stopAnimation
00260 {
00261     if (!_timer)
00262         return;
00263 
00264     [_timer invalidate];
00265     _timer = nil;
00266 
00267     if ([_delegate respondsToSelector:@selector(animationDidStop:)])
00268         [_delegate animationDidStop:self];
00269 }
00270 
00275 - (BOOL)isAnimating
00276 {
00277     return _timer;
00278 }
00279 
00284 - (void)setCurrentProgress:(float)aProgress
00285 {
00286     _progress = aProgress;
00287 }
00288 
00292 - (float)currentProgress
00293 {
00294     return _progress;
00295 }
00296 
00300 - (float)currentValue
00301 {
00302     var t = [self currentProgress];
00303 
00304     if ([_delegate respondsToSelector:@selector(animation:valueForProgress:)])
00305         return [_delegate animation:self valueForProgress:t];
00306     
00307     if (_animationCurve == CPAnimationLinear)
00308         return t;
00309 
00310     var c1 = [],
00311         c2 = [];
00312 
00313     [_timingFunction getControlPointAtIndex:1 values:c1];
00314     [_timingFunction getControlPointAtIndex:2 values:c2];
00315 
00316     return CubicBezierAtTime(t, c1[0], c1[1], c2[0], c2[1], _duration);
00317 }
00318 
00319 @end
00320 
00321 // currently used function to determine time
00322 // 1:1 conversion to js from webkit source files
00323 // UnitBezier.h, WebCore_animation_AnimationBase.cpp
00324 var CubicBezierAtTime = function CubicBezierAtTime(t, p1x, p1y, p2x, p2y, duration)
00325 {
00326     var ax = 0,
00327         bx = 0,
00328         cx = 0,
00329         ay = 0,
00330         by = 0,
00331         cy = 0;
00332     // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
00333     function sampleCurveX(t)
00334     {
00335         return ((ax * t + bx) * t + cx) * t;
00336     }
00337 
00338     function sampleCurveY(t)
00339     {
00340         return ((ay * t + by) * t + cy) * t;
00341     }
00342 
00343     function sampleCurveDerivativeX(t)
00344     {
00345         return (3.0 * ax * t + 2.0 * bx) * t + cx;
00346     }
00347 
00348     // The epsilon value to pass given that the animation is going to run over |duration| seconds. The longer the animation, the more precision is needed in the timing function result to avoid ugly discontinuities.
00349     function solveEpsilon(duration)
00350     {
00351         return 1.0 / (200.0 * duration);
00352     }
00353 
00354     function solve(x, epsilon)
00355     {
00356         return sampleCurveY(solveCurveX(x, epsilon));
00357     }
00358 
00359     // Given an x value, find a parametric value it came from.
00360     function solveCurveX(x, epsilon)
00361     {
00362         var t0,
00363             t1,
00364             t2 = x,
00365             x2,
00366             d2,
00367             i = 0;
00368 
00369         // First try a few iterations of Newton's method -- normally very fast.
00370         for (; i < 8; i++)
00371         {
00372             x2 = sampleCurveX(t2) - x;
00373 
00374             if (ABS(x2) < epsilon)
00375                 return t2;
00376 
00377             d2 = sampleCurveDerivativeX(t2);
00378 
00379             if (ABS(d2) < 1e-6)
00380                 break;
00381 
00382             t2 = t2 - x2 / d2;
00383         }
00384 
00385         // Fall back to the bisection method for reliability.
00386         t0 = 0.0;
00387         t1 = 1.0;
00388         t2 = x;
00389 
00390         if (t2 < t0)
00391             return t0;
00392 
00393         if (t2 > t1)
00394             return t1;
00395 
00396         while (t0 < t1)
00397         {
00398             x2 = sampleCurveX(t2);
00399 
00400             if (ABS(x2 - x) < epsilon)
00401                 return t2;
00402 
00403             if (x > x2)
00404                 t0 = t2;
00405 
00406             else
00407                 t1 = t2;
00408 
00409             t2 = (t1 - t0) * 0.5 + t0;
00410         }
00411 
00412         return t2; // Failure.
00413     };
00414     // Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
00415     cx = 3.0 * p1x;
00416     bx = 3.0 * (p2x - p1x) - cx;
00417     ax = 1.0 - cx - bx;
00418     cy = 3.0 * p1y;
00419     by = 3.0 * (p2y - p1y) - cy;
00420     ay = 1.0 - cy - by;
00421 
00422     // Convert from input time to parametric value in curve, then from that to output time.
00423     return solve(t, solveEpsilon(duration));
00424 };
 All Classes Files Functions Variables Defines