X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/fx/Manager.js diff --git a/src/fx/Manager.js b/src/fx/Manager.js new file mode 100644 index 00000000..f228c810 --- /dev/null +++ b/src/fx/Manager.js @@ -0,0 +1,305 @@ +/** + * @class Ext.fx.Manager + * Animation Manager which keeps track of all current animations and manages them on a frame by frame basis. + * @private + * @singleton + */ + +Ext.define('Ext.fx.Manager', { + + /* Begin Definitions */ + + singleton: true, + + requires: ['Ext.util.MixedCollection', + 'Ext.fx.target.Element', + 'Ext.fx.target.CompositeElement', + 'Ext.fx.target.Sprite', + 'Ext.fx.target.CompositeSprite', + 'Ext.fx.target.Component'], + + mixins: { + queue: 'Ext.fx.Queue' + }, + + /* End Definitions */ + + constructor: function() { + this.items = Ext.create('Ext.util.MixedCollection'); + this.mixins.queue.constructor.call(this); + + // this.requestAnimFrame = (function() { + // var raf = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // window.oRequestAnimationFrame || + // window.msRequestAnimationFrame; + // if (raf) { + // return function(callback, element) { + // raf(callback); + // }; + // } + // else { + // return function(callback, element) { + // window.setTimeout(callback, Ext.fx.Manager.interval); + // }; + // } + // })(); + }, + + /** + * @cfg {Number} interval Default interval in miliseconds to calculate each frame. Defaults to 16ms (~60fps) + */ + interval: 16, + + /** + * @cfg {Boolean} forceJS Turn off to not use CSS3 transitions when they are available + */ + forceJS: true, + + // @private Target factory + createTarget: function(target) { + var me = this, + useCSS3 = !me.forceJS && Ext.supports.Transitions, + targetObj; + + me.useCSS3 = useCSS3; + + // dom id + if (Ext.isString(target)) { + target = Ext.get(target); + } + // dom element + if (target && target.tagName) { + target = Ext.get(target); + targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target); + me.targets.add(targetObj); + return targetObj; + } + if (Ext.isObject(target)) { + // Element + if (target.dom) { + targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target); + } + // Element Composite + else if (target.isComposite) { + targetObj = Ext.create('Ext.fx.target.' + 'CompositeElement' + (useCSS3 ? 'CSS' : ''), target); + } + // Draw Sprite + else if (target.isSprite) { + targetObj = Ext.create('Ext.fx.target.Sprite', target); + } + // Draw Sprite Composite + else if (target.isCompositeSprite) { + targetObj = Ext.create('Ext.fx.target.CompositeSprite', target); + } + // Component + else if (target.isComponent) { + targetObj = Ext.create('Ext.fx.target.Component', target); + } + else if (target.isAnimTarget) { + return target; + } + else { + return null; + } + me.targets.add(targetObj); + return targetObj; + } + else { + return null; + } + }, + + /** + * Add an Anim to the manager. This is done automatically when an Anim instance is created. + * @param {Ext.fx.Anim} anim + */ + addAnim: function(anim) { + var items = this.items, + task = this.task; + // var me = this, + // items = me.items, + // cb = function() { + // if (items.length) { + // me.task = true; + // me.runner(); + // me.requestAnimFrame(cb); + // } + // else { + // me.task = false; + // } + // }; + + items.add(anim); + + // Start the timer if not already running + if (!task && items.length) { + task = this.task = { + run: this.runner, + interval: this.interval, + scope: this + }; + Ext.TaskManager.start(task); + } + + // //Start the timer if not already running + // if (!me.task && items.length) { + // me.requestAnimFrame(cb); + // } + }, + + /** + * Remove an Anim from the manager. This is done automatically when an Anim ends. + * @param {Ext.fx.Anim} anim + */ + removeAnim: function(anim) { + // this.items.remove(anim); + var items = this.items, + task = this.task; + items.remove(anim); + // Stop the timer if there are no more managed Anims + if (task && !items.length) { + Ext.TaskManager.stop(task); + delete this.task; + } + }, + + /** + * @private + * Filter function to determine which animations need to be started + */ + startingFilter: function(o) { + return o.paused === false && o.running === false && o.iterations > 0; + }, + + /** + * @private + * Filter function to determine which animations are still running + */ + runningFilter: function(o) { + return o.paused === false && o.running === true && o.isAnimator !== true; + }, + + /** + * @private + * Runner function being called each frame + */ + runner: function() { + var me = this, + items = me.items; + + me.targetData = {}; + me.targetArr = {}; + + // Single timestamp for all animations this interval + me.timestamp = new Date(); + + // Start any items not current running + items.filterBy(me.startingFilter).each(me.startAnim, me); + + // Build the new attributes to be applied for all targets in this frame + items.filterBy(me.runningFilter).each(me.runAnim, me); + + // Apply all the pending changes to their targets + me.applyPendingAttrs(); + }, + + /** + * @private + * Start the individual animation (initialization) + */ + startAnim: function(anim) { + anim.start(this.timestamp); + }, + + /** + * @private + * Run the individual animation for this frame + */ + runAnim: function(anim) { + if (!anim) { + return; + } + var me = this, + targetId = anim.target.getId(), + useCSS3 = me.useCSS3 && anim.target.type == 'element', + elapsedTime = me.timestamp - anim.startTime, + target, o; + + this.collectTargetData(anim, elapsedTime, useCSS3); + + // For CSS3 animation, we need to immediately set the first frame's attributes without any transition + // to get a good initial state, then add the transition properties and set the final attributes. + if (useCSS3) { + // Flush the collected attributes, without transition + anim.target.setAttr(me.targetData[targetId], true); + + // Add the end frame data + me.targetData[targetId] = []; + me.collectTargetData(anim, anim.duration, useCSS3); + + // Pause the animation so runAnim doesn't keep getting called + anim.paused = true; + + target = anim.target.target; + // We only want to attach an event on the last element in a composite + if (anim.target.isComposite) { + target = anim.target.target.last(); + } + + // Listen for the transitionend event + o = {}; + o[Ext.supports.CSS3TransitionEnd] = anim.lastFrame; + o.scope = anim; + o.single = true; + target.on(o); + } + // For JS animation, trigger the lastFrame handler if this is the final frame + else if (elapsedTime >= anim.duration) { + me.applyPendingAttrs(true); + delete me.targetData[targetId]; + delete me.targetArr[targetId]; + anim.lastFrame(); + } + }, + + /** + * Collect target attributes for the given Anim object at the given timestamp + * @param {Ext.fx.Anim} anim The Anim instance + * @param {Number} timestamp Time after the anim's start time + */ + collectTargetData: function(anim, elapsedTime, useCSS3) { + var targetId = anim.target.getId(), + targetData = this.targetData[targetId], + data; + + if (!targetData) { + targetData = this.targetData[targetId] = []; + this.targetArr[targetId] = anim.target; + } + + data = { + duration: anim.duration, + easing: (useCSS3 && anim.reverse) ? anim.easingFn.reverse().toCSS3() : anim.easing, + attrs: {} + }; + Ext.apply(data.attrs, anim.runAnim(elapsedTime)); + targetData.push(data); + }, + + /** + * @private + * Apply all pending attribute changes to their targets + */ + applyPendingAttrs: function(isLastFrame) { + var targetData = this.targetData, + targetArr = this.targetArr, + targetId; + for (targetId in targetData) { + if (targetData.hasOwnProperty(targetId)) { + targetArr[targetId].setAttr(targetData[targetId], false, isLastFrame); + } + } + } +});