API  0.9.10
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 @protocol CPAnimationDelegate <CPObject>
27 
28 @optional
29 - (BOOL)animationShouldStart:(CPAnimation)animation;
30 - (float)animation:(CPAnimation)animation valueForProgress:(float)progress;
31 - (void)animationDidEnd:(CPAnimation)animation;
32 - (void)animationDidStop:(CPAnimation)animation;
33 
34 @end
35 
36 
41 
42 @typedef CPAnimationCurve
47 
49 
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 <CPAnimationDelegate> _delegate;
90  CPTimer _timer;
91  unsigned _implementedDelegateMethods;
92 }
93 
100 - (id)initWithDuration:(float)aDuration animationCurve:(CPAnimationCurve)anAnimationCurve
101 {
102  self = [super init];
103 
104  if (self)
105  {
106  _progress = 0.0;
107  _duration = MAX(0.0, aDuration);
108  _frameRate = 60.0;
109 
110  [self setAnimationCurve:anAnimationCurve];
111  }
112 
113  return self;
114 }
115 
121 - (void)setAnimationCurve:(CPAnimationCurve)anAnimationCurve
122 {
123  var timingFunctionName;
124 
125  switch (anAnimationCurve)
126  {
128  timingFunctionName = kCAMediaTimingFunctionEaseInEaseOut;
129  break;
130 
131  case CPAnimationEaseIn:
132  timingFunctionName = kCAMediaTimingFunctionEaseIn;
133  break;
134 
135  case CPAnimationEaseOut:
136  timingFunctionName = kCAMediaTimingFunctionEaseOut;
137  break;
138 
139  case CPAnimationLinear:
140  timingFunctionName = kCAMediaTimingFunctionLinear;
141  break;
142 
143  default:
144  [CPException raise:CPInvalidArgumentException
145  reason:@"Invalid value provided for animation curve"];
146  break;
147  }
148 
149  _animationCurve = anAnimationCurve;
150  _timingFunction = [CAMediaTimingFunction functionWithName:timingFunctionName];
151 }
152 
156 - (CPAnimationCurve)animationCurve
157 {
158  return _animationCurve;
159 }
160 
166 - (void)setDuration:(CPTimeInterval)aDuration
167 {
168  if (aDuration < 0)
169  [CPException raise:CPInvalidArgumentException reason:"aDuration can't be negative"];
170 
171  _duration = aDuration;
172 }
173 
177 - (CPTimeInterval)duration
178 {
179  return _duration;
180 }
181 
187 - (void)setFrameRate:(float)frameRate
188 {
189  if (frameRate < 0)
190  [CPException raise:CPInvalidArgumentException reason:"frameRate can't be negative"];
191 
192  _frameRate = frameRate;
193 }
194 
198 - (float)frameRate
199 {
200  return _frameRate;
201 }
202 
206 - (id)delegate
207 {
208  return _delegate;
209 }
210 
215 - (void)setDelegate:(id <CPAnimationDelegate>)aDelegate
216 {
217  if (_delegate === aDelegate)
218  return;
219 
220  _delegate = aDelegate;
221  _implementedDelegateMethods = 0;
222 
223  if ([_delegate respondsToSelector:@selector(animationShouldStart:)])
224  _implementedDelegateMethods |= CPAnimationDelegate_animationShouldStart_;
225 
226  if ([_delegate respondsToSelector:@selector(animationDidEnd:)])
227  _implementedDelegateMethods |= CPAnimationDelegate_animationDidEnd_;
228 
229  if ([_delegate respondsToSelector:@selector(animationDidStop:)])
230  _implementedDelegateMethods |= CPAnimationDelegate_animationDidStop_;
231 
232  if ([_delegate respondsToSelector:@selector(animation:valueForProgress:)])
233  _implementedDelegateMethods |= CPAnimationDelegate_animation_valueForProgress_;
234 }
235 
242 {
243  // If we're already animating, or our delegate stops us, animate.
244  if (_timer || ![self _sendDelegateAnimationShouldStart])
245  return;
246 
247  if (_progress === 1.0)
248  _progress = 0.0;
249 
250  ACTUAL_FRAME_RATE = 0;
251  _lastTime = new Date();
252 
253  var timerInterval = _frameRate <= 0.0 ? 0.0001 : 1.0 / _frameRate;
254 
255  _timer = [CPTimer scheduledTimerWithTimeInterval:timerInterval target:self selector:@selector(animationTimerDidFire:) userInfo:nil repeats:YES];
256 }
257 
258 /*
259  @ignore
260 */
261 - (void)animationTimerDidFire:(CPTimer)aTimer
262 {
263  var currentTime = new Date(),
264  progress = MIN(1.0, [self currentProgress] + (currentTime - _lastTime) / (_duration * 1000.0));
265 
266  _lastTime = currentTime;
267 
269 
270  [self setCurrentProgress:progress];
271 
272  if (progress === 1.0)
273  {
274  [_timer invalidate];
275  _timer = nil;
276 
277  [self _sendDelegateAnimationDidEnd];
278  }
279 }
280 
285 {
286  if (!_timer)
287  return;
288 
289  [_timer invalidate];
290  _timer = nil;
291 
292  [self _sendDelegateAnimationDidStop];
293 }
294 
299 - (BOOL)isAnimating
300 {
301  return _timer;
302 }
303 
308 - (void)setCurrentProgress:(float)aProgress
309 {
310  _progress = aProgress;
311 }
312 
317 {
318  return _progress;
319 }
320 
324 - (float)currentValue
325 {
326  var t = [self currentProgress];
327 
328  if ([self _delegateRespondsToAnimationValueForProgress])
329  return [self _sendDelegateAnimationValueForProgress:t];
330 
331  if (_animationCurve == CPAnimationLinear)
332  return t;
333 
334  var c1 = [],
335  c2 = [];
336 
337  [_timingFunction getControlPointAtIndex:1 values:c1];
338  [_timingFunction getControlPointAtIndex:2 values:c2];
339 
340  return CubicBezierAtTime(t, c1[0], c1[1], c2[0], c2[1], _duration);
341 }
342 
343 @end
344 
345 
347 
352 - (BOOL)_delegateRespondsToAnimationValueForProgress
353 {
354  return _implementedDelegateMethods & CPAnimationDelegate_animation_valueForProgress_;
355 }
356 
361 - (BOOL)_sendDelegateAnimationShouldStart
362 {
363  if (!(_implementedDelegateMethods & CPAnimationDelegate_animationShouldStart_))
364  return YES;
365 
366  return [_delegate animationShouldStart:self];
367 }
368 
373 - (float)_sendDelegateAnimationValueForProgress:(float)aProgress
374 {
375  if (!(_implementedDelegateMethods & CPAnimationDelegate_animation_valueForProgress_))
376  return aProgress;
377 
378  return [_delegate animation:self valueForProgress:aProgress];
379 }
380 
385 - (void)_sendDelegateAnimationDidEnd
386 {
387  if (!(_implementedDelegateMethods & CPAnimationDelegate_animationDidEnd_))
388  return;
389 
390  [_delegate animationDidEnd:self];
391 }
392 
397 - (void)_sendDelegateAnimationDidStop
398 {
399  if (!(_implementedDelegateMethods & CPAnimationDelegate_animationDidStop_))
400  return;
401 
402  [_delegate animationDidStop:self];
403 }
404 
405 @end
406 
407 // currently used function to determine time
408 // 1:1 conversion to js from webkit source files
409 // UnitBezier.h, WebCore_animation_AnimationBase.cpp
410 var CubicBezierAtTime = function(t, p1x, p1y, p2x, p2y, duration)
411 {
412  var ax = 0,
413  bx = 0,
414  cx = 0,
415  ay = 0,
416  by = 0,
417  cy = 0;
418  // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
419  function sampleCurveX(t)
420  {
421  return ((ax * t + bx) * t + cx) * t;
422  }
423 
424  function sampleCurveY(t)
425  {
426  return ((ay * t + by) * t + cy) * t;
427  }
428 
429  function sampleCurveDerivativeX(t)
430  {
431  return (3.0 * ax * t + 2.0 * bx) * t + cx;
432  }
433 
434  // 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.
435  function solveEpsilon(duration)
436  {
437  return 1.0 / (200.0 * duration);
438  }
439 
440  function solve(x, epsilon)
441  {
442  return sampleCurveY(solveCurveX(x, epsilon));
443  }
444 
445  // Given an x value, find a parametric value it came from.
446  function solveCurveX(x, epsilon)
447  {
448  var t0,
449  t1,
450  t2 = x,
451  x2,
452  d2,
453  i = 0;
454 
455  // First try a few iterations of Newton's method -- normally very fast.
456  for (; i < 8; i++)
457  {
458  x2 = sampleCurveX(t2) - x;
459 
460  if (ABS(x2) < epsilon)
461  return t2;
462 
463  d2 = sampleCurveDerivativeX(t2);
464 
465  if (ABS(d2) < 1e-6)
466  break;
467 
468  t2 = t2 - x2 / d2;
469  }
470 
471  // Fall back to the bisection method for reliability.
472  t0 = 0.0;
473  t1 = 1.0;
474  t2 = x;
475 
476  if (t2 < t0)
477  return t0;
478 
479  if (t2 > t1)
480  return t1;
481 
482  while (t0 < t1)
483  {
484  x2 = sampleCurveX(t2);
485 
486  if (ABS(x2 - x) < epsilon)
487  return t2;
488 
489  if (x > x2)
490  t0 = t2;
491 
492  else
493  t1 = t2;
494 
495  t2 = (t1 - t0) * 0.5 + t0;
496  }
497 
498  return t2; // Failure.
499  };
500  // Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
501  cx = 3.0 * p1x;
502  bx = 3.0 * (p2x - p1x) - cx;
503  ax = 1.0 - cx - bx;
504  cy = 3.0 * p1y;
505  by = 3.0 * (p2y - p1y) - cy;
506  ay = 1.0 - cy - by;
507 
508  // Convert from input time to parametric value in curve, then from that to output time.
509  return solve(t, solveEpsilon(duration));
510 };
Used to implement exception handling (creating & raising).
Definition: CPException.h:2
void setAnimationCurve:(CPAnimationCurve anAnimationCurve)
Definition: CPAnimation.j:121
CPAnimationCurve animationCurve()
Definition: CPAnimation.j:156
ACTUAL_FRAME_RATE
Definition: CPAnimation.j:48
float currentValue()
Definition: CPAnimation.j:324
float frameRate()
Definition: CPAnimation.j:198
var CubicBezierAtTime
Definition: CPAnimation.j:410
var CPAnimationDelegate_animation_valueForProgress_
Definition: CPAnimation.j:38
void stopAnimation()
Definition: CPAnimation.j:284
void raise:reason:(CPString aName, [reason] CPString aReason)
Definition: CPException.j:66
CPTimeInterval duration()
Definition: CPAnimation.j:177
float currentProgress()
Definition: CPAnimation.j:316
var CPAnimationDelegate_animationDidStop_
Definition: CPAnimation.j:40
CPAnimationEaseOut
Definition: CPAnimation.j:45
BOOL isAnimating()
Definition: CPAnimation.j:299
CPAnimationEaseIn
Definition: CPAnimation.j:44
void startAnimation()
Definition: CPAnimation.j:241
CPAnimationLinear
Definition: CPAnimation.j:46
var CPAnimationDelegate_animationShouldStart_
Definition: CPAnimation.j:37
CPTimer scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:(CPTimeInterval seconds, [target] id aTarget, [selector] SEL aSelector, [userInfo] id userInfo, [repeats] BOOL shouldRepeat)
Definition: CPTimer.j:58
var CPAnimationDelegate_animationDidEnd_
Definition: CPAnimation.j:39
CPAnimationCurve CPAnimationEaseInOut
Definition: CPAnimation.j:43
kCAMediaTimingFunctionEaseOut
A timer object that can send a message after the given time interval.
Definition: CPTimer.h:2
kCAMediaTimingFunctionEaseIn
id init()
Definition: CPObject.j:145
id functionWithName:(CPString aName)
void setCurrentProgress:(float aProgress)
Definition: CPAnimation.j:308
kCAMediaTimingFunctionEaseInEaseOut
kCAMediaTimingFunctionLinear