1 goog.provide('lime.animation.Animation');
  2 goog.provide('lime.animation.Easing');
  3 goog.provide('lime.animation.actionManager');
  4 
  5 
  6 goog.require('goog.array');
  7 goog.require('goog.events.EventTarget');
  8 goog.require('goog.fx.easing');
  9 goog.require('lime.scheduleManager');
 10 
 11 
 12 /**
 13  * General object for running animations on nodes
 14  * @constructor
 15  * extends goog.events.EventTarget
 16  */
 17 lime.animation.Animation = function() {
 18     goog.events.EventTarget.call(this);
 19 
 20     /**
 21      * Array of active target nodes
 22      * @type {Array.<lime.Node>}
 23      * @protected
 24      */
 25     this.targets = [];
 26     this.initTargets_ = [];
 27 
 28     this.targetProp_ = {};
 29 
 30     /**
 31      * Animation is playing?
 32      * @type {boolean}
 33      * @private
 34      */
 35     this.isPlaying_ = false;
 36 
 37     this.duration_ = 1.0;
 38 
 39     this.ease = lime.animation.Easing.EASEINOUT;
 40 
 41     this.status_ = 0;
 42 
 43 };
 44 goog.inherits(lime.animation.Animation, goog.events.EventTarget);
 45 
 46 /**
 47  * @type {string}
 48  */
 49 lime.animation.Animation.prototype.scope = '';
 50 
 51 /** @typedef {Array.<number|Function>} */
 52 lime.animation.EasingFunction;
 53 
 54 
 55 /**
 56  * Animation event types
 57  * @enum {string}
 58  */
 59 lime.animation.Event = {
 60   START: 'start',
 61   STOP: 'stop'
 62 };
 63 
 64 /**
 65  * Return duration of the animation in seconds
 66  * @return {number} Animation duration.
 67  */
 68 lime.animation.Animation.prototype.getDuration = function() {
 69     return this.duration_;
 70 };
 71 
 72 /**
 73  * Set the duration of the animation in seconds
 74  * @param {number} value New duration.
 75  * @return {lime.animation.Animation} object itself.
 76  */
 77  lime.animation.Animation.prototype.setDuration = function(value) {
 78      this.duration_ = value;
 79      return this;
 80  };
 81 
 82 /**
 83  * Set easing function for current animation
 84  * @param {lime.animation.EasingFunction} ease Easing function.
 85  * @return {lime.animation.Animation} object itself.
 86  */
 87 lime.animation.Animation.prototype.setEasing = function(ease) {
 88     this.ease = ease;
 89     return this;
 90 };
 91 
 92 /**
 93  * Return current easing function
 94  * @return {lime.animation.EasingFunction} Easing function.
 95  */
 96 lime.animation.Animation.prototype.getEasing = function() {
 97     return this.ease;
 98 };
 99 
100 /**
101  * Add target node to animation
102  * @param {lime.Node} target Target node.
103  * @return {lime.animation.Animation} object itself.
104  */
105 lime.animation.Animation.prototype.addTarget = function(target) {
106     goog.array.insert(this.targets, target);
107     return this;
108 };
109 
110 /**
111  * Remove target node from animation
112  * @param {lime.Node} target Node to be removed.
113  * @return {lime.animation.Animation} object itself.
114  */
115 lime.animation.Animation.prototype.removeTarget = function(target) {
116     goog.array.remove(this.targets, target);
117     return this;
118 };
119 
120 /**
121  * Start playing the animation
122  */
123 lime.animation.Animation.prototype.play = function() {
124     this.playTime_ = 0;
125     this.status_ = 1;
126     this.firstFrame_ = 1;
127     lime.scheduleManager.schedule(this.step_, this);
128     this.dispatchEvent({type: lime.animation.Event.START});
129 };
130 
131 /**
132  * Stop playing the animstion
133  */
134 lime.animation.Animation.prototype.stop = function() {
135     if (this.status_ != 0) {
136         var targets = this.initTargets_;
137         if (this.useTransitions() && this.clearTransition) {
138             var i = targets.length;
139             while (--i >= 0) {
140                 this.clearTransition(targets[i]);
141             }
142         }
143         this.initTargets_ = [];
144         this.targetProp_ = {};
145         this.status_ = 0;
146         lime.scheduleManager.unschedule(this.step_, this);
147         this.dispatchEvent({type: lime.animation.Event.STOP});
148     }
149 };
150 
151 /**
152  * Make property object for target that hold animation helper values.
153  * @param {lime.Node} target Target node.
154  * @return {Object} Properties object.
155  */
156 lime.animation.Animation.prototype.makeTargetProp = function(target) {
157     return {};
158 };
159 
160 /**
161  * Get properties object for target.
162  * @param {lime.Node} target Target node.
163  * @return {Object} Properties object.
164  */
165 lime.animation.Animation.prototype.getTargetProp = function(target) {
166     var uid = goog.getUid(target);
167     if (!goog.isDef(this.targetProp_[uid])) {
168         this.initTarget(target);
169         this.targetProp_[uid] = this.makeTargetProp(target);
170     }
171     return this.targetProp_[uid];
172 };
173 
174 /**
175  * Initalize the aniamtion for a target
176  * @param {lime.Node} target Target node.
177  */
178 lime.animation.Animation.prototype.initTarget = function(target) {
179     lime.animation.actionManager.register(this, target);
180     //todo: check if all this status_ mess can be removed now
181     this.status_ = 1;
182     goog.array.insert(this.initTargets_, target);
183     if(this.speed_ && !this.speedCalcDone_ && this.calcDurationFromSpeed_){
184         this.calcDurationFromSpeed_();
185     }
186 };
187 
188 /**
189  * Get the director related to the animation targets.
190  * @return {lime.Director} Director.
191  */
192 lime.animation.Animation.prototype.getDirector = function() {
193     return this.targets[0] ? this.targets[0].getDirector() : null;
194 };
195 
196 /**
197  * Iterate time for animation
198  * @private
199  * @throws {WrongParam} If easing function is not correct
200  * @param {number} dt Time difference since last run.
201  */
202 lime.animation.Animation.prototype.step_ = function(dt) {
203     if(this.speed_ && !this.speedCalcDone_ && this.calcDurationFromSpeed_){
204         this.calcDurationFromSpeed_();
205     }
206     if(this.firstFrame_){
207         delete this.firstFrame_;
208         dt = 1;
209     }
210     
211     this.playTime_ += dt;
212     this.dt_ = dt;
213     var t = this.playTime_ / (this.duration_ * 1000);
214     if (isNaN(t) || t>=1) t = 1;
215     t = this.updateAll(t,this.targets); 
216 
217     if (t == 1) {
218         this.stop();
219     }
220 };
221 
222 /**
223  * Update all targets to new values.
224  * @param {number} t Time position of animation[0-1].
225  * @param {Array.<lime.Node>} targets All target nodes to update.
226  * @return {number} New time position(eased value).
227  */
228 lime.animation.Animation.prototype.updateAll = function(t,targets){
229    t = this.getEasing()[0](t);
230    if (isNaN(t)) {
231         t = 1;
232     }
233     
234     var i = targets.length;
235     while (--i >= 0) {
236         this.update(t, targets[i]);
237     }
238     return t;
239 }
240 
241 /**
242  * Returns true if CSS transitions are used to make the animation.
243  * Performes better on iOS devices.
244  * @return {boolean} Transitions are being used?
245  */
246 lime.animation.Animation.prototype.useTransitions = function() {
247     return this.duration_ > 0 && lime.style.isTransitionsSupported &&
248         this.optimizations_ &&
249     //  goog.userAgent.MOBILE &&  // I see no boost on mac, only on iOS
250         !lime.userAgent.ANDROID && // bug in 2.2 http://code.google.com/p/android/issues/detail?id=12451
251         !goog.userAgent.GECKO; // still many bugs on FF4Beta Mac when hardware acceleration in ON
252 };
253 
254 /**
255  * Enable CSS3 transitions to make animation. Usually performes better
256  * but is limited to single parallel transform action.
257  * @param {boolean=} opt_value Enable or disable.
258  * @return {lime.animation.Animation} object itself.
259  */
260 lime.animation.Animation.prototype.enableOptimizations = function(opt_value) {
261     var bool = goog.isDef(opt_value) ? opt_value : true;
262     this.optimizations_ = bool;
263     return this;
264 };
265 
266 /**
267  * Update targets to new values
268  * @param {number} t Time position of animation[0-1].
269  * @param {lime.Node} target Target node to update.
270  */
271 lime.animation.Animation.prototype.update = goog.abstractMethod;
272 
273 /**
274  * Clone animation parmaters from another animation
275  * @protected
276  */
277 lime.animation.Animation.prototype.cloneParam = function(origin){
278     return this.setDuration(origin.getDuration()).enableOptimizations(origin.optimizations_);
279 }
280 
281 /**
282  * Return new animation with reveresed parameters from original
283  * @throws {NotSupported} No reverese animation possible.
284  * @return {?lime.animation.Animation} New animation.
285  */
286 lime.animation.Animation.prototype.reverse = function() {
287     throw('Reverseform not supported for this animation');
288 };
289 
290 
291 /**
292  * ActionManager. Doesn't let animations that modify same parameters
293  * run together on same targets.
294  * @constructor
295  */
296 lime.animation.actionManager = new (function() {
297     this.actions = {};
298 });
299 
300 /**
301  * Register animation in the manager.
302  * @param {lime.animation.Animation} action Action.
303  * @param {lime.Node} target Taget node.
304  * @this {lime.animation.actionManager}
305  */
306 lime.animation.actionManager.register = function(action, target) {
307     //Todo: probably needs some garbage collection
308     if (!action.scope.length) return;
309     var id = goog.getUid(target);
310     if (!goog.isDef(this.actions[id])) {
311         this.actions[id] = {};
312     }
313     if (goog.isDef(this.actions[id][action.scope])) {
314         this.actions[id][action.scope].stop();
315     }
316     this.actions[id][action.scope] = action;
317 };
318 
319 /**
320  * Stop all animations on target.
321  * @param {lime.Node} target Target node.
322  * @this {lime.animation.actionManager}
323  */
324 lime.animation.actionManager.stopAll = function(target) {
325     // todo: doesn't stop scopless action atm. (like sequence)
326     var id = goog.getUid(target);
327     if (goog.isDef(this.actions[id])) {
328         for (var i in this.actions[id]) {
329             this.actions[id][i].stop();
330             delete this.actions[id][i];
331         }
332     }
333 };
334 
335 
336 (function(){
337     
338     // www.netzgesta.de/dev/cubic-bezier-timing-function.html
339     // currently used function to determine time
340     // 1:1 conversion to js from webkit source files
341     // UnitBezier.h, WebCore_animation_AnimationBase.cpp
342     var ax=0,bx=0,cx=0,ay=0,by=0,cy=0;
343 	// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
344     function sampleCurveX(t) {return ((ax*t+bx)*t+cx)*t;};
345     function sampleCurveY(t) {return ((ay*t+by)*t+cy)*t;};
346     function sampleCurveDerivativeX(t) {return (3.0*ax*t+2.0*bx)*t+cx;};
347 	// The epsilon value to pass given that the animation is going to run over |dur| seconds. The longer the
348 	// animation, the more precision is needed in the timing function result to avoid ugly discontinuities.
349 	function solveEpsilon(duration) {return 1.0/(200.0*duration);};
350     function solve(x,epsilon) {return sampleCurveY(solveCurveX(x,epsilon));};
351 	// Given an x value, find a parametric value it came from.
352 	function fabs(n) {if(n>=0) {return n;}else {return 0-n;}}; 
353     function solveCurveX(x,epsilon) {var t0,t1,t2,x2,d2,i;
354         // First try a few iterations of Newton's method -- normally very fast.
355         for(t2=x, i=0; i<8; i++) {x2=sampleCurveX(t2)-x; if(fabs(x2)<epsilon) {return t2;} d2=sampleCurveDerivativeX(t2); if(fabs(d2)<1e-6) {break;} t2=t2-x2/d2;}
356         // Fall back to the bisection method for reliability.
357         t0=0.0; t1=1.0; t2=x; if(t2<t0) {return t0;} if(t2>t1) {return t1;}
358         while(t0<t1) {x2=sampleCurveX(t2); if(fabs(x2-x)<epsilon) {return t2;} if(x>x2) {t0=t2;}else {t1=t2;} t2=(t1-t0)*.5+t0;}
359         return t2; // Failure.
360     };
361     function CubicBezierAtTime(t,p1x,p1y,p2x,p2y,duration) {
362 		// Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
363 		cx=3.0*p1x; bx=3.0*(p2x-p1x)-cx; ax=1.0-cx-bx; cy=3.0*p1y; by=3.0*(p2y-p1y)-cy; ay=1.0-cy-by;
364 		// Convert from input time to parametric value in curve, then from that to output time.
365     	return solve(t, solveEpsilon(duration));
366 	};
367  
368 
369 /**
370  * Return easing function from Bezier curce points
371  * @see http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
372  * @param {number} p1x Point one X axis value.
373  * @param {number} p1y Point one Y axis value.
374  * @param {number} p2x Point two X axis value.
375  * @param {number} p2y Point two Y axis value.
376  * @return {lime.animation.EasingFunction} Easing function.
377  */
378 lime.animation.getEasingFunction = function(p1x, p1y, p2x, p2y) {
379     var that = lime.animation;
380     return [function(t) {
381         return CubicBezierAtTime(t,p1x,p1y,p2x,p2y,100);
382     },p1x, p1y, p2x, p2y];
383 
384 };
385 
386     
387     
388 })();
389 
390 
391 
392 
393 /**
394  * Predefined Easing functions
395  * @enum {lime.animation.EasingFunction}
396  */
397 lime.animation.Easing = {
398     EASE: lime.animation.getEasingFunction(.25, .1, .25, 1),
399     LINEAR: lime.animation.getEasingFunction(0, 0, 1, 1),
400     EASEIN: lime.animation.getEasingFunction(.42, 0, 1, 1),
401     EASEOUT: lime.animation.getEasingFunction(0, 0, .58, 1),
402     EASEINOUT: lime.animation.getEasingFunction(.42, 0, .58, 1)
403 };
404 
405 
406 
407