API  0.9.6
 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  switch (anAnimationCurve)
124  {
126  break;
127 
128  case CPAnimationEaseIn: timingFunctionName = kCAMediaTimingFunctionEaseIn;
129  break;
130 
131  case CPAnimationEaseOut: timingFunctionName = kCAMediaTimingFunctionEaseOut;
132  break;
133 
134  case CPAnimationLinear: timingFunctionName = kCAMediaTimingFunctionLinear;
135  break;
136 
137  default: [CPException raise:CPInvalidArgumentException
138  reason:"Invalid value provided for animation curve"];
139  break;
140  }
141 
142  _animationCurve = anAnimationCurve;
143  _timingFunction = [CAMediaTimingFunction functionWithName:timingFunctionName];
144 }
145 
149 - (CPAnimationCurve)animationCurve
150 {
151  return _animationCurve;
152 }
153 
159 - (void)setDuration:(CPTimeInterval)aDuration
160 {
161  if (aDuration < 0)
162  [CPException raise:CPInvalidArgumentException reason:"aDuration can't be negative"];
163 
164  _duration = aDuration;
165 }
166 
170 - (CPTimeInterval)duration
171 {
172  return _duration;
173 }
174 
180 - (void)setFrameRate:(float)frameRate
181 {
182  if (frameRate < 0)
183  [CPException raise:CPInvalidArgumentException reason:"frameRate can't be negative"];
184 
185  _frameRate = frameRate;
186 }
187 
191 - (float)frameRate
192 {
193  return _frameRate;
194 }
195 
199 - (id)delegate
200 {
201  return _delegate;
202 }
203 
208 - (void)setDelegate:(id)aDelegate
209 {
210  _delegate = aDelegate;
211 }
212 
218 - (void)startAnimation
219 {
220  // If we're already animating, or our delegate stops us, animate.
221  if (_timer || _delegate && [_delegate respondsToSelector:@selector(animationShouldStart:)] && ![_delegate animationShouldStart:self])
222  return;
223 
224  if (_progress === 1.0)
225  _progress = 0.0;
226 
227  ACTUAL_FRAME_RATE = 0;
228  _lastTime = new Date();
229 
231 }
232 
233 /*
234  @ignore
235 */
236 - (void)animationTimerDidFire:(CPTimer)aTimer
237 {
238  var currentTime = new Date(),
239  progress = MIN(1.0, [self currentProgress] + (currentTime - _lastTime) / (_duration * 1000.0));
240 
241  _lastTime = currentTime;
242 
244 
245  [self setCurrentProgress:progress];
246 
247  if (progress === 1.0)
248  {
249  [_timer invalidate];
250  _timer = nil;
251 
252  if ([_delegate respondsToSelector:@selector(animationDidEnd:)])
253  [_delegate animationDidEnd:self];
254  }
255 }
256 
260 - (void)stopAnimation
261 {
262  if (!_timer)
263  return;
264 
265  [_timer invalidate];
266  _timer = nil;
267 
268  if ([_delegate respondsToSelector:@selector(animationDidStop:)])
269  [_delegate animationDidStop:self];
270 }
271 
276 - (BOOL)isAnimating
277 {
278  return _timer;
279 }
280 
285 - (void)setCurrentProgress:(float)aProgress
286 {
287  _progress = aProgress;
288 }
289 
293 - (float)currentProgress
294 {
295  return _progress;
296 }
297 
301 - (float)currentValue
302 {
303  var t = [self currentProgress];
304 
305  if ([_delegate respondsToSelector:@selector(animation:valueForProgress:)])
306  return [_delegate animation:self valueForProgress:t];
307 
308  if (_animationCurve == CPAnimationLinear)
309  return t;
310 
311  var c1 = [],
312  c2 = [];
313 
314  [_timingFunction getControlPointAtIndex:1 values:c1];
315  [_timingFunction getControlPointAtIndex:2 values:c2];
316 
317  return CubicBezierAtTime(t, c1[0], c1[1], c2[0], c2[1], _duration);
318 }
319 
320 @end
321 
322 // currently used function to determine time
323 // 1:1 conversion to js from webkit source files
324 // UnitBezier.h, WebCore_animation_AnimationBase.cpp
325 var CubicBezierAtTime = function CubicBezierAtTime(t, p1x, p1y, p2x, p2y, duration)
326 {
327  var ax = 0,
328  bx = 0,
329  cx = 0,
330  ay = 0,
331  by = 0,
332  cy = 0;
333  // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
334  function sampleCurveX(t)
335  {
336  return ((ax * t + bx) * t + cx) * t;
337  }
338 
339  function sampleCurveY(t)
340  {
341  return ((ay * t + by) * t + cy) * t;
342  }
343 
344  function sampleCurveDerivativeX(t)
345  {
346  return (3.0 * ax * t + 2.0 * bx) * t + cx;
347  }
348 
349  // 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.
350  function solveEpsilon(duration)
351  {
352  return 1.0 / (200.0 * duration);
353  }
354 
355  function solve(x, epsilon)
356  {
357  return sampleCurveY(solveCurveX(x, epsilon));
358  }
359 
360  // Given an x value, find a parametric value it came from.
361  function solveCurveX(x, epsilon)
362  {
363  var t0,
364  t1,
365  t2 = x,
366  x2,
367  d2,
368  i = 0;
369 
370  // First try a few iterations of Newton's method -- normally very fast.
371  for (; i < 8; i++)
372  {
373  x2 = sampleCurveX(t2) - x;
374 
375  if (ABS(x2) < epsilon)
376  return t2;
377 
378  d2 = sampleCurveDerivativeX(t2);
379 
380  if (ABS(d2) < 1e-6)
381  break;
382 
383  t2 = t2 - x2 / d2;
384  }
385 
386  // Fall back to the bisection method for reliability.
387  t0 = 0.0;
388  t1 = 1.0;
389  t2 = x;
390 
391  if (t2 < t0)
392  return t0;
393 
394  if (t2 > t1)
395  return t1;
396 
397  while (t0 < t1)
398  {
399  x2 = sampleCurveX(t2);
400 
401  if (ABS(x2 - x) < epsilon)
402  return t2;
403 
404  if (x > x2)
405  t0 = t2;
406 
407  else
408  t1 = t2;
409 
410  t2 = (t1 - t0) * 0.5 + t0;
411  }
412 
413  return t2; // Failure.
414  };
415  // Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
416  cx = 3.0 * p1x;
417  bx = 3.0 * (p2x - p1x) - cx;
418  ax = 1.0 - cx - bx;
419  cy = 3.0 * p1y;
420  by = 3.0 * (p2y - p1y) - cy;
421  ay = 1.0 - cy - by;
422 
423  // Convert from input time to parametric value in curve, then from that to output time.
424  return solve(t, solveEpsilon(duration));
425 };