/** * @class Ext.draw.Animator * * Singleton class that manages the animation pool. */Ext.define('Ext.draw.Animator', { uses: ['Ext.draw.Draw'], singleton: true, frameCallbacks: {}, frameCallbackId: 0, scheduled: 0, frameStartTimeOffset: Ext.now(), animations: [], running: false, /** * Cross platform `animationTime` implementation. * @return {Number} */ animationTime: function() { return Ext.AnimationQueue.frameStartTime - this.frameStartTimeOffset; }, /** * Adds an animated object to the animation pool. * * @param {Object} animation The animation descriptor to add to the pool. */ add: function(animation) { var me = this; if (!me.contains(animation)) { me.animations.push(animation); me.ignite(); if ('fireEvent' in animation) { animation.fireEvent('animationstart', animation); } } }, /** * Removes an animation from the pool. * TODO: This is broken when called within `step` method. * @param {Object} animation The animation to remove from the pool. */ remove: function(animation) { var me = this, animations = me.animations, i = 0, l = animations.length; for (; i < l; ++i) { if (animations[i] === animation) { animations.splice(i, 1); if ('fireEvent' in animation) { animation.fireEvent('animationend', animation); } return; } } }, /** * Returns `true` or `false` whether it contains the given animation or not. * * @param {Object} animation The animation to check for. * @return {Boolean} */ contains: function(animation) { return Ext.Array.indexOf(this.animations, animation) > -1; }, /** * Returns `true` or `false` whether the pool is empty or not. * @return {Boolean} */ empty: function() { return this.animations.length === 0; }, idle: function() { return this.scheduled === 0 && this.animations.length === 0; }, /** * Given a frame time it will filter out finished animations from the pool. * * @param {Number} frameTime The frame's start time, in milliseconds. */ step: function(frameTime) { var me = this, animations = me.animations, animation, i = 0, ln = animations.length; for (; i < ln; i++) { animation = animations[i]; animation.step(frameTime); if (!animation.animating) { animations.splice(i, 1); i--; ln--; if (animation.fireEvent) { animation.fireEvent('animationend', animation); } } } }, /** * Register a one-time callback that will be called at the next frame. * @param {Function/String} callback * @param {Object} scope * @return {String} The ID of the scheduled callback. */ schedule: function(callback, scope) { var id = 'frameCallback' + (this.frameCallbackId++); scope = scope || this; if (Ext.isString(callback)) { callback = scope[callback]; } Ext.draw.Animator.frameCallbacks[id] = { fn: callback, scope: scope, once: true }; this.scheduled++; Ext.draw.Animator.ignite(); return id; }, /** * Register a one-time callback that will be called at the next frame, * if that callback (with a matching function and scope) isn't already scheduled. * @param {Function/String} callback * @param {Object} scope * @return {String/null} The ID of the scheduled callback or null, if that callback * has already been scheduled. */ scheduleIf: function(callback, scope) { var frameCallbacks = Ext.draw.Animator.frameCallbacks, cb, id; scope = scope || this; if (Ext.isString(callback)) { callback = scope[callback]; } for (id in frameCallbacks) { cb = frameCallbacks[id]; if (cb.once && cb.fn === callback && cb.scope === scope) { return null; } } return this.schedule(callback, scope); }, /** * Cancel a registered one-time callback * @param {String} id */ cancel: function(id) { if (Ext.draw.Animator.frameCallbacks[id] && Ext.draw.Animator.frameCallbacks[id].once) { this.scheduled = Math.max(--this.scheduled, 0); delete Ext.draw.Animator.frameCallbacks[id]; Ext.draw.Draw.endUpdateIOS(); } if (this.idle()) { this.extinguish(); } }, clear: function() { this.animations.length = 0; Ext.draw.Animator.frameCallbacks = {}; this.extinguish(); }, /** * Register a recursive callback that will be called at every frame. * * @param {Function} callback * @param {Object} scope * @return {String} */ addFrameCallback: function(callback, scope) { var id = 'frameCallback' + (this.frameCallbackId++); scope = scope || this; if (Ext.isString(callback)) { callback = scope[callback]; } Ext.draw.Animator.frameCallbacks[id] = { fn: callback, scope: scope }; return id; }, /** * Unregister a recursive callback. * @param {String} id */ removeFrameCallback: function(id) { delete Ext.draw.Animator.frameCallbacks[id]; if (this.idle()) { this.extinguish(); } }, /** * @private */ fireFrameCallbacks: function() { var callbacks = this.frameCallbacks, id, fn, cb; for (id in callbacks) { cb = callbacks[id]; fn = cb.fn; if (Ext.isString(fn)) { fn = cb.scope[fn]; } fn.call(cb.scope); if (callbacks[id] && cb.once) { this.scheduled = Math.max(--this.scheduled, 0); delete callbacks[id]; } } }, handleFrame: function() { var me = this; me.step(me.animationTime()); me.fireFrameCallbacks(); if (me.idle()) { me.extinguish(); } }, ignite: function() { if (!this.running) { this.running = true; Ext.AnimationQueue.start(this.handleFrame, this); Ext.draw.Draw.beginUpdateIOS(); } }, extinguish: function() { this.running = false; Ext.AnimationQueue.stop(this.handleFrame, this); Ext.draw.Draw.endUpdateIOS(); }});