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