Upgrade to ExtJS 3.1.0 - Released 12/16/2009
[extjs.git] / src / adapter / core / ext-base-anim.js
1 /*!
2  * Ext JS Library 3.1.0
3  * Copyright(c) 2006-2009 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 (function(){    \r
8     var EXTLIB = Ext.lib,\r
9         noNegatives = /width|height|opacity|padding/i,\r
10         offsetAttribute = /^((width|height)|(top|left))$/,\r
11         defaultUnit = /width|height|top$|bottom$|left$|right$/i,\r
12         offsetUnit =  /\d+(em|%|en|ex|pt|in|cm|mm|pc)$/i,\r
13         isset = function(v){\r
14             return typeof v !== 'undefined';\r
15         },\r
16         now = function(){\r
17             return new Date();    \r
18         };\r
19         \r
20     EXTLIB.Anim = {\r
21         motion : function(el, args, duration, easing, cb, scope) {\r
22             return this.run(el, args, duration, easing, cb, scope, Ext.lib.Motion);\r
23         },\r
24 \r
25         run : function(el, args, duration, easing, cb, scope, type) {\r
26             type = type || Ext.lib.AnimBase;\r
27             if (typeof easing == "string") {\r
28                 easing = Ext.lib.Easing[easing];\r
29             }\r
30             var anim = new type(el, args, duration, easing);\r
31             anim.animateX(function() {\r
32                 if(Ext.isFunction(cb)){\r
33                     cb.call(scope);\r
34                 }\r
35             });\r
36             return anim;\r
37         }\r
38     };\r
39     \r
40     EXTLIB.AnimBase = function(el, attributes, duration, method) {\r
41         if (el) {\r
42             this.init(el, attributes, duration, method);\r
43         }\r
44     };\r
45 \r
46     EXTLIB.AnimBase.prototype = {\r
47         doMethod: function(attr, start, end) {\r
48             var me = this;\r
49             return me.method(me.curFrame, start, end - start, me.totalFrames);\r
50         },\r
51 \r
52 \r
53         setAttr: function(attr, val, unit) {\r
54             if (noNegatives.test(attr) && val < 0) {\r
55                 val = 0;\r
56             }\r
57             Ext.fly(this.el, '_anim').setStyle(attr, val + unit);\r
58         },\r
59 \r
60 \r
61         getAttr: function(attr) {\r
62             var el = Ext.fly(this.el),\r
63                 val = el.getStyle(attr),\r
64                 a = offsetAttribute.exec(attr) || []\r
65 \r
66             if (val !== 'auto' && !offsetUnit.test(val)) {\r
67                 return parseFloat(val);\r
68             }\r
69 \r
70             return (!!(a[2]) || (el.getStyle('position') == 'absolute' && !!(a[3]))) ? el.dom['offset' + a[0].charAt(0).toUpperCase() + a[0].substr(1)] : 0;\r
71         },\r
72 \r
73 \r
74         getDefaultUnit: function(attr) {\r
75             return defaultUnit.test(attr) ? 'px' : '';\r
76         },\r
77 \r
78         animateX : function(callback, scope) {\r
79             var me = this,\r
80                 f = function() {\r
81                 me.onComplete.removeListener(f);\r
82                 if (Ext.isFunction(callback)) {\r
83                     callback.call(scope || me, me);\r
84                 }\r
85             };\r
86             me.onComplete.addListener(f, me);\r
87             me.animate();\r
88         },\r
89 \r
90 \r
91         setRunAttr: function(attr) {            \r
92             var me = this,\r
93                 a = this.attributes[attr],\r
94                 to = a.to,\r
95                 by = a.by,\r
96                 from = a.from,\r
97                 unit = a.unit,\r
98                 ra = (this.runAttrs[attr] = {}),\r
99                 end;\r
100 \r
101             if (!isset(to) && !isset(by)){\r
102                 return false;\r
103             }\r
104 \r
105             var start = isset(from) ? from : me.getAttr(attr);\r
106             if (isset(to)) {\r
107                 end = to;\r
108             }else if(isset(by)) {\r
109                 if (Ext.isArray(start)){\r
110                     end = [];\r
111                                         for(var i=0,len=start.length; i<len; i++) {\r
112                                                 end[i] = start[i] + by[i];\r
113                                         }\r
114                 }else{\r
115                     end = start + by;\r
116                 }\r
117             }\r
118 \r
119             Ext.apply(ra, {\r
120                 start: start,\r
121                 end: end,\r
122                 unit: isset(unit) ? unit : me.getDefaultUnit(attr)\r
123             });\r
124         },\r
125 \r
126 \r
127         init: function(el, attributes, duration, method) {\r
128             var me = this,\r
129                 actualFrames = 0,\r
130                 mgr = EXTLIB.AnimMgr;\r
131                 \r
132             Ext.apply(me, {\r
133                 isAnimated: false,\r
134                 startTime: null,\r
135                 el: Ext.getDom(el),\r
136                 attributes: attributes || {},\r
137                 duration: duration || 1,\r
138                 method: method || EXTLIB.Easing.easeNone,\r
139                 useSec: true,\r
140                 curFrame: 0,\r
141                 totalFrames: mgr.fps,\r
142                 runAttrs: {},\r
143                 animate: function(){\r
144                     var me = this,\r
145                         d = me.duration;\r
146                     \r
147                     if(me.isAnimated){\r
148                         return false;\r
149                     }\r
150 \r
151                     me.curFrame = 0;\r
152                     me.totalFrames = me.useSec ? Math.ceil(mgr.fps * d) : d;\r
153                     mgr.registerElement(me); \r
154                 },\r
155                 \r
156                 stop: function(finish){\r
157                     var me = this;\r
158                 \r
159                     if(finish){\r
160                         me.curFrame = me.totalFrames;\r
161                         me._onTween.fire();\r
162                     }\r
163                     mgr.stop(me);\r
164                 }\r
165             });\r
166 \r
167             var onStart = function(){\r
168                 var me = this,\r
169                     attr;\r
170                 \r
171                 me.onStart.fire();\r
172                 me.runAttrs = {};\r
173                 for(attr in this.attributes){\r
174                     this.setRunAttr(attr);\r
175                 }\r
176 \r
177                 me.isAnimated = true;\r
178                 me.startTime = now();\r
179                 actualFrames = 0;\r
180             };\r
181 \r
182 \r
183             var onTween = function(){\r
184                 var me = this;\r
185 \r
186                 me.onTween.fire({\r
187                     duration: now() - me.startTime,\r
188                     curFrame: me.curFrame\r
189                 });\r
190 \r
191                 var ra = me.runAttrs;\r
192                 for (var attr in ra) {\r
193                     this.setAttr(attr, me.doMethod(attr, ra[attr].start, ra[attr].end), ra[attr].unit);\r
194                 }\r
195 \r
196                 ++actualFrames;\r
197             };\r
198 \r
199             var onComplete = function() {\r
200                 var me = this,\r
201                     actual = (now() - me.startTime) / 1000,\r
202                     data = {\r
203                         duration: actual,\r
204                         frames: actualFrames,\r
205                         fps: actualFrames / actual\r
206                     };\r
207 \r
208                 me.isAnimated = false;\r
209                 actualFrames = 0;\r
210                 me.onComplete.fire(data);\r
211             };\r
212 \r
213             me.onStart = new Ext.util.Event(me);\r
214             me.onTween = new Ext.util.Event(me);            \r
215             me.onComplete = new Ext.util.Event(me);\r
216             (me._onStart = new Ext.util.Event(me)).addListener(onStart);\r
217             (me._onTween = new Ext.util.Event(me)).addListener(onTween);\r
218             (me._onComplete = new Ext.util.Event(me)).addListener(onComplete); \r
219         }\r
220     };\r
221 \r
222 \r
223     Ext.lib.AnimMgr = new function() {\r
224         var me = this,\r
225             thread = null,\r
226             queue = [],\r
227             tweenCount = 0;\r
228 \r
229 \r
230         Ext.apply(me, {\r
231             fps: 1000,\r
232             delay: 1,\r
233             registerElement: function(tween){\r
234                 queue.push(tween);\r
235                 ++tweenCount;\r
236                 tween._onStart.fire();\r
237                 me.start();\r
238             },\r
239             \r
240             unRegister: function(tween, index){\r
241                 tween._onComplete.fire();\r
242                 index = index || getIndex(tween);\r
243                 if (index != -1) {\r
244                     queue.splice(index, 1);\r
245                 }\r
246 \r
247                 if (--tweenCount <= 0) {\r
248                     me.stop();\r
249                 }\r
250             },\r
251             \r
252             start: function(){\r
253                 if(thread === null){\r
254                     thread = setInterval(me.run, me.delay);\r
255                 }\r
256             },\r
257             \r
258             stop: function(tween){\r
259                 if(!tween){\r
260                     clearInterval(thread);\r
261                     for(var i = 0, len = queue.length; i < len; ++i){\r
262                         if(queue[0].isAnimated){\r
263                             me.unRegister(queue[0], 0);\r
264                         }\r
265                     }\r
266 \r
267                     queue = [];\r
268                     thread = null;\r
269                     tweenCount = 0;\r
270                 }else{\r
271                     me.unRegister(tween);\r
272                 }\r
273             },\r
274             \r
275             run: function(){\r
276                 var tf, i, len, tween;\r
277                 for(i = 0, len = queue.length; i<len; i++) {\r
278                     tween = queue[i];\r
279                     if(tween && tween.isAnimated){\r
280                         tf = tween.totalFrames;\r
281                         if(tween.curFrame < tf || tf === null){\r
282                             ++tween.curFrame;\r
283                             if(tween.useSec){\r
284                                 correctFrame(tween);\r
285                             }\r
286                             tween._onTween.fire();\r
287                         }else{\r
288                             me.stop(tween);\r
289                         }\r
290                     }                   \r
291                 }\r
292             }\r
293         });\r
294 \r
295         var getIndex = function(anim) {\r
296             var i, len;\r
297             for(i = 0, len = queue.length; i<len; i++) {\r
298                 if(queue[i] === anim) {\r
299                     return i;\r
300                 }\r
301             }\r
302             return -1;\r
303         };\r
304 \r
305         var correctFrame = function(tween) {\r
306             var frames = tween.totalFrames,\r
307                 frame = tween.curFrame,\r
308                 duration = tween.duration,\r
309                 expected = (frame * duration * 1000 / frames),\r
310                 elapsed = (now() - tween.startTime),\r
311                 tweak = 0;\r
312 \r
313             if(elapsed < duration * 1000){\r
314                 tweak = Math.round((elapsed / expected - 1) * frame);\r
315             }else{\r
316                 tweak = frames - (frame + 1);\r
317             }\r
318             if(tweak > 0 && isFinite(tweak)){\r
319                 if(tween.curFrame + tweak >= frames){\r
320                     tweak = frames - (frame + 1);\r
321                 }\r
322                 tween.curFrame += tweak;\r
323             }\r
324         };\r
325     };\r
326 \r
327     EXTLIB.Bezier = new function() {\r
328 \r
329         this.getPosition = function(points, t) {\r
330             var n = points.length,\r
331                 tmp = [],\r
332                 c = 1 - t, \r
333                 i,\r
334                 j;\r
335 \r
336             for (i = 0; i < n; ++i) {\r
337                 tmp[i] = [points[i][0], points[i][1]];\r
338             }\r
339 \r
340             for (j = 1; j < n; ++j) {\r
341                 for (i = 0; i < n - j; ++i) {\r
342                     tmp[i][0] = c * tmp[i][0] + t * tmp[parseInt(i + 1, 10)][0];\r
343                     tmp[i][1] = c * tmp[i][1] + t * tmp[parseInt(i + 1, 10)][1];\r
344                 }\r
345             }\r
346 \r
347             return [ tmp[0][0], tmp[0][1] ];\r
348 \r
349         };\r
350     };\r
351 \r
352 \r
353     EXTLIB.Easing = {\r
354         easeNone: function (t, b, c, d) {\r
355             return c * t / d + b;\r
356         },\r
357 \r
358 \r
359         easeIn: function (t, b, c, d) {\r
360             return c * (t /= d) * t + b;\r
361         },\r
362 \r
363 \r
364         easeOut: function (t, b, c, d) {\r
365             return -c * (t /= d) * (t - 2) + b;\r
366         }\r
367     };\r
368 \r
369     (function() {\r
370         EXTLIB.Motion = function(el, attributes, duration, method) {\r
371             if (el) {\r
372                 EXTLIB.Motion.superclass.constructor.call(this, el, attributes, duration, method);\r
373             }\r
374         };\r
375 \r
376         Ext.extend(EXTLIB.Motion, Ext.lib.AnimBase);\r
377 \r
378         var superclass = EXTLIB.Motion.superclass,\r
379             proto = EXTLIB.Motion.prototype,\r
380             pointsRe = /^points$/i;\r
381 \r
382         Ext.apply(EXTLIB.Motion.prototype, {\r
383             setAttr: function(attr, val, unit){\r
384                 var me = this,\r
385                     setAttr = superclass.setAttr;\r
386                     \r
387                 if (pointsRe.test(attr)) {\r
388                     unit = unit || 'px';\r
389                     setAttr.call(me, 'left', val[0], unit);\r
390                     setAttr.call(me, 'top', val[1], unit);\r
391                 } else {\r
392                     setAttr.call(me, attr, val, unit);\r
393                 }\r
394             },\r
395             \r
396             getAttr: function(attr){\r
397                 var me = this,\r
398                     getAttr = superclass.getAttr;\r
399                     \r
400                 return pointsRe.test(attr) ? [getAttr.call(me, 'left'), getAttr.call(me, 'top')] : getAttr.call(me, attr);\r
401             },\r
402             \r
403             doMethod: function(attr, start, end){\r
404                 var me = this;\r
405                 \r
406                 return pointsRe.test(attr)\r
407                         ? EXTLIB.Bezier.getPosition(me.runAttrs[attr], me.method(me.curFrame, 0, 100, me.totalFrames) / 100)\r
408                         : superclass.doMethod.call(me, attr, start, end);\r
409             },\r
410             \r
411             setRunAttr: function(attr){\r
412                 if(pointsRe.test(attr)){\r
413                     \r
414                     var me = this,\r
415                         el = this.el,\r
416                         points = this.attributes.points,\r
417                         control = points.control || [],\r
418                         from = points.from,\r
419                         to = points.to,\r
420                         by = points.by,\r
421                         DOM = EXTLIB.Dom,\r
422                         start,\r
423                         i,\r
424                         end,\r
425                         len,\r
426                         ra;\r
427                   \r
428 \r
429                     if(control.length > 0 && !Ext.isArray(control[0])){\r
430                         control = [control];\r
431                     }else{\r
432                         /*\r
433                         var tmp = [];\r
434                         for (i = 0,len = control.length; i < len; ++i) {\r
435                             tmp[i] = control[i];\r
436                         }\r
437                         control = tmp;\r
438                         */\r
439                     }\r
440 \r
441                     Ext.fly(el, '_anim').position();\r
442                     DOM.setXY(el, isset(from) ? from : DOM.getXY(el));\r
443                     start = me.getAttr('points');\r
444 \r
445 \r
446                     if(isset(to)){\r
447                         end = translateValues.call(me, to, start);\r
448                         for (i = 0,len = control.length; i < len; ++i) {\r
449                             control[i] = translateValues.call(me, control[i], start);\r
450                         }\r
451                     } else if (isset(by)) {\r
452                         end = [start[0] + by[0], start[1] + by[1]];\r
453 \r
454                         for (i = 0,len = control.length; i < len; ++i) {\r
455                             control[i] = [ start[0] + control[i][0], start[1] + control[i][1] ];\r
456                         }\r
457                     }\r
458 \r
459                     ra = this.runAttrs[attr] = [start];\r
460                     if (control.length > 0) {\r
461                         ra = ra.concat(control);\r
462                     }\r
463 \r
464                     ra[ra.length] = end;\r
465                 }else{\r
466                     superclass.setRunAttr.call(this, attr);\r
467                 }\r
468             }\r
469         });\r
470 \r
471         var translateValues = function(val, start) {\r
472             var pageXY = EXTLIB.Dom.getXY(this.el);\r
473             return [val[0] - pageXY[0] + start[0], val[1] - pageXY[1] + start[1]];\r
474         };\r
475     })();\r
476 })();