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