/**
* @class Ext.fx.Manager
* Animation Manager which keeps track of all current animations and manages them on a frame by frame basis.
* @singleton
*/
Ext.define('Ext.fx.Manager', {
/* Begin Definitions */
singleton: true,
requires: ['Ext.util.MixedCollection'],
/* End Definitions */
constructor: function() {
this.items = new Ext.util.MixedCollection();
},
/**
* @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,
createTarget: function(target) {
var useCSS3 = !this.forceJS && Ext.supports.Transitions;
this.useCSS3 = useCSS3;
// dom id
if (typeof target == 'string') {
target = Ext.get(target);
}
// dom element
else if (target.tagName) {
target = Ext.get(target);
}
if (Ext.isObject(target)) {
// Element
if (target.dom) {
return Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
}
// Element Composite
else if (target.isComposite) {
return Ext.create('Ext.fx.target.' + 'CompositeElement' + (useCSS3 ? 'CSS' : ''), target);
}
// Draw Sprite
else if (target.isSprite) {
return Ext.create('Ext.fx.target.Sprite', target);
}
// Draw Sprite Composite
else if (target.isSpriteGroup) {
return Ext.create('Ext.fx.target.SpriteGroup', target);
}
// Component
else if (target.isComponent) {
return Ext.create('Ext.fx.target.Component', target);
}
// Component Compositishtypethingy (When ComponentQuery is complete)
//else if (target.isComponent) {
// return new Ext.fx.target['Component' + (useCSS3 ? 'CSS' : '')](target);
//}
// Target
else if (target.isAnimTarget) {
return target;
}
else {
return null;
}
}
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;
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.TaskMgr.start(task);
}
},
/**
* Remove an Anim from the manager. This is done automatically when an Anim ends.
* @param {Ext.fx.Anim} anim
*/
removeAnim: function(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.TaskMgr.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;
},
/**
* @private
* Runner function being called each frame
*/
runner: function() {
var items = this.items;
this.targetData = {};
this.targetArr = {};
// Single timestamp for all animations this interval
this.timestamp = new Date();
// Start any items not current running
items.filterBy(this.startingFilter).each(this.startAnim, this);
// Build the new attributes to be applied for all targets in this frame
items.filterBy(this.runningFilter).each(this.runAnim, this);
// Apply all the pending changes to their targets
this.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) {
var targetId = anim.target.getId(),
useCSS3 = this.useCSS3 && anim.target.type == 'element',
timestamp = this.timestamp - anim.startTime,
target,
o;
this.collectTargetData(anim, timestamp, 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(this.targetData[targetId], true);
// Add the end frame data
this.targetData[targetId] = [];
this.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 (timestamp >= anim.duration) {
this.applyPendingAttrs();
delete this.targetData[targetId];
delete this.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, timestamp, 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(timestamp));
targetData.push(data);
},
/**
* @private
* Apply all pending attribute changes to their targets
*/
applyPendingAttrs: function() {
var targetData = this.targetData,
targetArr = this.targetArr,
targetId;
for (targetId in targetData) {
if (targetData.hasOwnProperty(targetId)) {
targetArr[targetId].setAttr(targetData[targetId], false);
}
}
}
});