API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPAnimation.j
Go to the documentation of this file.
1 /*
2  * CPAnimation.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
25 
26 /*
27  @global
28  @group CPAnimationCurve
29 */
31 /*
32  @global
33  @group CPAnimationCurve
34 */
36 /*
37  @global
38  @group CPAnimationCurve
39 */
41 /*
42  @global
43  @group CPAnimationCurve
44 */
46 
48 
78 @implementation CPAnimation : CPObject
79 {
80  CPTimeInterval _lastTime;
81  CPTimeInterval _duration;
82 
83  CPAnimationCurve _animationCurve;
84  CAMediaTimingFunction _timingFunction;
85 
86  float _frameRate;
87  float _progress;
88 
89  id _delegate;
90  CPTimer _timer;
91 }
92 
99 - (id)initWithDuration:(float)aDuration animationCurve:(CPAnimationCurve)anAnimationCurve
100 {
101  self = [super init];
102 
103  if (self)
104  {
105  _progress = 0.0;
106  _duration = MAX(0.0, aDuration);
107  _frameRate = 60.0;
108 
109  [self setAnimationCurve:anAnimationCurve];
110  }
111 
112  return self;
113 }
114 
120 - (void)setAnimationCurve:(CPAnimationCurve)anAnimationCurve
121 {
122  var timingFunctionName;
123 
124  switch (anAnimationCurve)
125  {
127  timingFunctionName = kCAMediaTimingFunctionEaseInEaseOut;
128  break;
129 
130  case CPAnimationEaseIn:
131  timingFunctionName = kCAMediaTimingFunctionEaseIn;
132  break;
133 
134  case CPAnimationEaseOut:
135  timingFunctionName = kCAMediaTimingFunctionEaseOut;
136  break;
137 
138  case CPAnimationLinear:
139  timingFunctionName = kCAMediaTimingFunctionLinear;
140  break;
141 
142  default:
143  [CPException raise:CPInvalidArgumentException
144  reason:@"Invalid value provided for animation curve"];
145  break;
146  }
147 
148  _animationCurve = anAnimationCurve;
149  _timingFunction = [CAMediaTimingFunction functionWithName:timingFunctionName];
150 }
151 
155 - (CPAnimationCurve)animationCurve
156 {
157  return _animationCurve;
158 }
159 
165 - (void)setDuration:(CPTimeInterval)aDuration
166 {
167  if (aDuration < 0)
168  [CPException raise:CPInvalidArgumentException reason:"aDuration can't be negative"];
169 
170  _duration = aDuration;
171 }
172 
176 - (CPTimeInterval)duration
177 {
178  return _duration;
179 }
180 
186 - (void)setFrameRate:(float)frameRate
187 {
188  if (frameRate < 0)
189  [CPException raise:CPInvalidArgumentException reason:"frameRate can't be negative"];
190 
191  _frameRate = frameRate;
192 }
193 
197 - (float)frameRate
198 {
199  return _frameRate;
200 }
201 
205 - (id)delegate
206 {
207  return _delegate;
208 }
209 
214 - (void)setDelegate:(id)aDelegate
215 {
216  _delegate = aDelegate;
217 }
218 
224 - (void)startAnimation
225 {
226  // If we're already animating, or our delegate stops us, animate.
227  if (_timer || _delegate && [_delegate respondsToSelector:@selector(animationShouldStart:)] && ![_delegate animationShouldStart:self])
228  return;
229 
230  if (_progress === 1.0)
231  _progress = 0.0;
232 
233  ACTUAL_FRAME_RATE = 0;
234  _lastTime = new Date();
235 
236  var timerInterval = _frameRate <= 0.0 ? 0.0001 : 1.0/_frameRate;
237 
238  _timer = [CPTimer scheduledTimerWithTimeInterval:timerInterval target:self selector:@selector(animationTimerDidFire:) userInfo:nil repeats:YES];
239 }
240 
241 /*
242  @ignore
243 */
244 - (void)animationTimerDidFire:(CPTimer)aTimer
245 {
246  var currentTime = new Date(),
247  progress = MIN(1.0, [self currentProgress] + (currentTime - _lastTime) / (_duration * 1000.0));
248 
249  _lastTime = currentTime;
250 
252 
253  [self setCurrentProgress:progress];
254 
255  if (progress === 1.0)
256  {
257  [_timer invalidate];
258  _timer = nil;
259 
260  if ([_delegate respondsToSelector:@selector(animationDidEnd:)])
261  [_delegate animationDidEnd:self];
262  }
263 }
264 
268 - (void)stopAnimation
269 {
270  if (!_timer)
271  return;
272 
273  [_timer invalidate];
274  _timer = nil;
275 
276  if ([_delegate respondsToSelector:@selector(animationDidStop:)])
277  [_delegate animationDidStop:self];
278 }
279 
284 - (BOOL)isAnimating
285 {
286  return _timer;
287 }
288 
293 - (void)setCurrentProgress:(float)aProgress
294 {
295  _progress = aProgress;
296 }
297 
301 - (float)currentProgress
302 {
303  return _progress;
304 }
305 
309 - (float)currentValue
310 {
311  var t = [self currentProgress];
312 
313  if ([_delegate respondsToSelector:@selector(animation:valueForProgress:)])
314  return [_delegate animation:self valueForProgress:t];
315 
316  if (_animationCurve == CPAnimationLinear)
317  return t;
318 
319  var c1 = [],
320  c2 = [];
321 
322  [_timingFunction getControlPointAtIndex:1 values:c1];
323  [_timingFunction getControlPointAtIndex:2 values:c2];
324 
325  return CubicBezierAtTime(t, c1[0], c1[1], c2[0], c2[1], _duration);
326 }
327 
328 @end
329 
330 // currently used function to determine time
331 // 1:1 conversion to js from webkit source files
332 // UnitBezier.h, WebCore_animation_AnimationBase.cpp
333 var CubicBezierAtTime = function(t, p1x, p1y, p2x, p2y, duration)
334 {
335  var ax = 0,
336  bx = 0,
337  cx = 0,
338  ay = 0,
339  by = 0,
340  cy = 0;
341  // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
342  function sampleCurveX(t)
343  {
344  return ((ax * t + bx) * t + cx) * t;
345  }
346 
347  function sampleCurveY(t)
348  {
349  return ((ay * t + by) * t + cy) * t;
350  }
351 
352  function sampleCurveDerivativeX(t)
353  {
354  return (3.0 * ax * t + 2.0 * bx) * t + cx;
355  }
356 
357  // 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.
358  function solveEpsilon(duration)
359  {
360  return 1.0 / (200.0 * duration);
361  }
362 
363  function solve(x, epsilon)
364  {
365  return sampleCurveY(solveCurveX(x, epsilon));
366  }
367 
368  // Given an x value, find a parametric value it came from.
369  function solveCurveX(x, epsilon)
370  {
371  var t0,
372  t1,
373  t2 = x,
374  x2,
375  d2,
376  i = 0;
377 
378  // First try a few iterations of Newton's method -- normally very fast.
379  for (; i < 8; i++)
380  {
381  x2 = sampleCurveX(t2) - x;
382 
383  if (ABS(x2) < epsilon)
384  return t2;
385 
386  d2 = sampleCurveDerivativeX(t2);
387 
388  if (ABS(d2) < 1e-6)
389  break;
390 
391  t2 = t2 - x2 / d2;
392  }
393 
394  // Fall back to the bisection method for reliability.
395  t0 = 0.0;
396  t1 = 1.0;
397  t2 = x;
398 
399  if (t2 < t0)
400  return t0;
401 
402  if (t2 > t1)
403  return t1;
404 
405  while (t0 < t1)
406  {
407  x2 = sampleCurveX(t2);
408 
409  if (ABS(x2 - x) < epsilon)
410  return t2;
411 
412  if (x > x2)
413  t0 = t2;
414 
415  else
416  t1 = t2;
417 
418  t2 = (t1 - t0) * 0.5 + t0;
419  }
420 
421  return t2; // Failure.
422  };
423  // Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
424  cx = 3.0 * p1x;
425  bx = 3.0 * (p2x - p1x) - cx;
426  ax = 1.0 - cx - bx;
427  cy = 3.0 * p1y;
428  by = 3.0 * (p2y - p1y) - cy;
429  ay = 1.0 - cy - by;
430 
431  // Convert from input time to parametric value in curve, then from that to output time.
432  return solve(t, solveEpsilon(duration));
433 };