Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / fx / Manager.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.fx.Manager
17  * Animation Manager which keeps track of all current animations and manages them on a frame by frame basis.
18  * @private
19  * @singleton
20  */
21
22 Ext.define('Ext.fx.Manager', {
23
24     /* Begin Definitions */
25
26     singleton: true,
27
28     requires: ['Ext.util.MixedCollection',
29                'Ext.fx.target.Element',
30                'Ext.fx.target.CompositeElement',
31                'Ext.fx.target.Sprite',
32                'Ext.fx.target.CompositeSprite',
33                'Ext.fx.target.Component'],
34
35     mixins: {
36         queue: 'Ext.fx.Queue'
37     },
38
39     /* End Definitions */
40
41     constructor: function() {
42         this.items = Ext.create('Ext.util.MixedCollection');
43         this.mixins.queue.constructor.call(this);
44
45         // this.requestAnimFrame = (function() {
46         //     var raf = window.requestAnimationFrame ||
47         //               window.webkitRequestAnimationFrame ||
48         //               window.mozRequestAnimationFrame ||
49         //               window.oRequestAnimationFrame ||
50         //               window.msRequestAnimationFrame;
51         //     if (raf) {
52         //         return function(callback, element) {
53         //             raf(callback);
54         //         };
55         //     }
56         //     else {
57         //         return function(callback, element) {
58         //             window.setTimeout(callback, Ext.fx.Manager.interval);
59         //         };
60         //     }
61         // })();
62     },
63
64     /**
65      * @cfg {Number} interval Default interval in miliseconds to calculate each frame.  Defaults to 16ms (~60fps)
66      */
67     interval: 16,
68
69     /**
70      * @cfg {Boolean} forceJS Turn off to not use CSS3 transitions when they are available
71      */
72     forceJS: true,
73
74     // @private Target factory
75     createTarget: function(target) {
76         var me = this,
77             useCSS3 = !me.forceJS && Ext.supports.Transitions,
78             targetObj;
79
80         me.useCSS3 = useCSS3;
81
82         // dom id
83         if (Ext.isString(target)) {
84             target = Ext.get(target);
85         }
86         // dom element
87         if (target && target.tagName) {
88             target = Ext.get(target);
89             targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
90             me.targets.add(targetObj);
91             return targetObj;
92         }
93         if (Ext.isObject(target)) {
94             // Element
95             if (target.dom) {
96                 targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
97             }
98             // Element Composite
99             else if (target.isComposite) {
100                 targetObj = Ext.create('Ext.fx.target.' + 'CompositeElement' + (useCSS3 ? 'CSS' : ''), target);
101             }
102             // Draw Sprite
103             else if (target.isSprite) {
104                 targetObj = Ext.create('Ext.fx.target.Sprite', target);
105             }
106             // Draw Sprite Composite
107             else if (target.isCompositeSprite) {
108                 targetObj = Ext.create('Ext.fx.target.CompositeSprite', target);
109             }
110             // Component
111             else if (target.isComponent) {
112                 targetObj = Ext.create('Ext.fx.target.Component', target);
113             }
114             else if (target.isAnimTarget) {
115                 return target;
116             }
117             else {
118                 return null;
119             }
120             me.targets.add(targetObj);
121             return targetObj;
122         }
123         else {
124             return null;
125         }
126     },
127
128     /**
129      * Add an Anim to the manager. This is done automatically when an Anim instance is created.
130      * @param {Ext.fx.Anim} anim
131      */
132     addAnim: function(anim) {
133         var items = this.items,
134             task = this.task;
135         // var me = this,
136         //     items = me.items,
137         //     cb = function() {
138         //         if (items.length) {
139         //             me.task = true;
140         //             me.runner();
141         //             me.requestAnimFrame(cb);
142         //         }
143         //         else {
144         //             me.task = false;
145         //         }
146         //     };
147
148         items.add(anim);
149
150         // Start the timer if not already running
151         if (!task && items.length) {
152             task = this.task = {
153                 run: this.runner,
154                 interval: this.interval,
155                 scope: this
156             };
157             Ext.TaskManager.start(task);
158         }
159
160         // //Start the timer if not already running
161         // if (!me.task && items.length) {
162         //     me.requestAnimFrame(cb);
163         // }
164     },
165
166     /**
167      * Remove an Anim from the manager. This is done automatically when an Anim ends.
168      * @param {Ext.fx.Anim} anim
169      */
170     removeAnim: function(anim) {
171         // this.items.remove(anim);
172         var items = this.items,
173             task = this.task;
174         items.remove(anim);
175         // Stop the timer if there are no more managed Anims
176         if (task && !items.length) {
177             Ext.TaskManager.stop(task);
178             delete this.task;
179         }
180     },
181
182     /**
183      * @private
184      * Filter function to determine which animations need to be started
185      */
186     startingFilter: function(o) {
187         return o.paused === false && o.running === false && o.iterations > 0;
188     },
189
190     /**
191      * @private
192      * Filter function to determine which animations are still running
193      */
194     runningFilter: function(o) {
195         return o.paused === false && o.running === true && o.isAnimator !== true;
196     },
197
198     /**
199      * @private
200      * Runner function being called each frame
201      */
202     runner: function() {
203         var me = this,
204             items = me.items;
205
206         me.targetData = {};
207         me.targetArr = {};
208
209         // Single timestamp for all animations this interval
210         me.timestamp = new Date();
211
212         // Start any items not current running
213         items.filterBy(me.startingFilter).each(me.startAnim, me);
214
215         // Build the new attributes to be applied for all targets in this frame
216         items.filterBy(me.runningFilter).each(me.runAnim, me);
217
218         // Apply all the pending changes to their targets
219         me.applyPendingAttrs();
220     },
221
222     /**
223      * @private
224      * Start the individual animation (initialization)
225      */
226     startAnim: function(anim) {
227         anim.start(this.timestamp);
228     },
229
230     /**
231      * @private
232      * Run the individual animation for this frame
233      */
234     runAnim: function(anim) {
235         if (!anim) {
236             return;
237         }
238         var me = this,
239             targetId = anim.target.getId(),
240             useCSS3 = me.useCSS3 && anim.target.type == 'element',
241             elapsedTime = me.timestamp - anim.startTime,
242             target, o;
243
244         this.collectTargetData(anim, elapsedTime, useCSS3);
245
246         // For CSS3 animation, we need to immediately set the first frame's attributes without any transition
247         // to get a good initial state, then add the transition properties and set the final attributes.
248         if (useCSS3) {
249             // Flush the collected attributes, without transition
250             anim.target.setAttr(me.targetData[targetId], true);
251
252             // Add the end frame data
253             me.targetData[targetId] = [];
254             me.collectTargetData(anim, anim.duration, useCSS3);
255
256             // Pause the animation so runAnim doesn't keep getting called
257             anim.paused = true;
258
259             target = anim.target.target;
260             // We only want to attach an event on the last element in a composite
261             if (anim.target.isComposite) {
262                 target = anim.target.target.last();
263             }
264
265             // Listen for the transitionend event
266             o = {};
267             o[Ext.supports.CSS3TransitionEnd] = anim.lastFrame;
268             o.scope = anim;
269             o.single = true;
270             target.on(o);
271         }
272         // For JS animation, trigger the lastFrame handler if this is the final frame
273         else if (elapsedTime >= anim.duration) {
274             me.applyPendingAttrs(true);
275             delete me.targetData[targetId];
276             delete me.targetArr[targetId];
277             anim.lastFrame();
278         }
279     },
280
281     /**
282      * Collect target attributes for the given Anim object at the given timestamp
283      * @param {Ext.fx.Anim} anim The Anim instance
284      * @param {Number} timestamp Time after the anim's start time
285      */
286     collectTargetData: function(anim, elapsedTime, useCSS3) {
287         var targetId = anim.target.getId(),
288             targetData = this.targetData[targetId],
289             data;
290         
291         if (!targetData) {
292             targetData = this.targetData[targetId] = [];
293             this.targetArr[targetId] = anim.target;
294         }
295
296         data = {
297             duration: anim.duration,
298             easing: (useCSS3 && anim.reverse) ? anim.easingFn.reverse().toCSS3() : anim.easing,
299             attrs: {}
300         };
301         Ext.apply(data.attrs, anim.runAnim(elapsedTime));
302         targetData.push(data);
303     },
304
305     /**
306      * @private
307      * Apply all pending attribute changes to their targets
308      */
309     applyPendingAttrs: function(isLastFrame) {
310         var targetData = this.targetData,
311             targetArr = this.targetArr,
312             targetId;
313         for (targetId in targetData) {
314             if (targetData.hasOwnProperty(targetId)) {
315                 targetArr[targetId].setAttr(targetData[targetId], false, isLastFrame);
316             }
317         }
318     }
319 });
320