import Base from './core/Base';
import vendor from './core/vendor';
import Animator from './animation/Animator';
/**
* Animation is global timeline that schedule all clips. each frame animation will set the time of clips to current and update the states of clips
* @constructor clay.Timeline
* @extends clay.core.Base
*
* @example
* var animation = new clay.Timeline();
* var node = new clay.Node();
* animation.animate(node.position)
* .when(1000, {
* x: 500,
* y: 500
* })
* .when(2000, {
* x: 100,
* y: 100
* })
* .when(3000, {
* z: 10
* })
* .start('spline');
*/
var Timeline = Base.extend(function () {
return /** @lends clay.Timeline# */{
/**
* stage is an object with render method, each frame if there exists any animating clips, stage.render will be called
* @type {Object}
*/
stage: null,
_clips: [],
_running: false,
_time: 0,
_paused: false,
_pausedTime: 0
};
},
/** @lends clay.Timeline.prototype */
{
/**
* Add animator
* @param {clay.animate.Animator} animator
*/
addAnimator: function (animator) {
animator.animation = this;
var clips = animator.getClips();
for (var i = 0; i < clips.length; i++) {
this.addClip(clips[i]);
}
},
/**
* @param {clay.animation.Clip} clip
*/
addClip: function (clip) {
if (this._clips.indexOf(clip) < 0) {
this._clips.push(clip);
}
},
/**
* @param {clay.animation.Clip} clip
*/
removeClip: function (clip) {
var idx = this._clips.indexOf(clip);
if (idx >= 0) {
this._clips.splice(idx, 1);
}
},
/**
* Remove animator
* @param {clay.animate.Animator} animator
*/
removeAnimator: function (animator) {
var clips = animator.getClips();
for (var i = 0; i < clips.length; i++) {
this.removeClip(clips[i]);
}
animator.animation = null;
},
_update: function () {
var time = Date.now() - this._pausedTime;
var delta = time - this._time;
var clips = this._clips;
var len = clips.length;
var deferredEvents = [];
var deferredClips = [];
for (var i = 0; i < len; i++) {
var clip = clips[i];
var e = clip.step(time, delta, false);
// Throw out the events need to be called after
// stage.render, like finish
if (e) {
deferredEvents.push(e);
deferredClips.push(clip);
}
}
// Remove the finished clip
for (var i = 0; i < len;) {
if (clips[i]._needsRemove) {
clips[i] = clips[len-1];
clips.pop();
len--;
} else {
i++;
}
}
len = deferredEvents.length;
for (var i = 0; i < len; i++) {
deferredClips[i].fire(deferredEvents[i]);
}
this._time = time;
this.trigger('frame', delta);
if (this.stage && this.stage.render) {
this.stage.render();
}
},
/**
* Start running animation
*/
start: function () {
var self = this;
this._running = true;
this._time = Date.now();
this._pausedTime = 0;
var requestAnimationFrame = vendor.requestAnimationFrame;
function step() {
if (self._running) {
requestAnimationFrame(step);
if (!self._paused) {
self._update();
}
}
}
requestAnimationFrame(step);
},
/**
* Stop running animation
*/
stop: function () {
this._running = false;
},
/**
* Pause
*/
pause: function () {
if (!this._paused) {
this._pauseStart = Date.now();
this._paused = true;
}
},
/**
* Resume
*/
resume: function () {
if (this._paused) {
this._pausedTime += Date.now() - this._pauseStart;
this._paused = false;
}
},
/**
* Remove all clips
*/
removeClipsAll: function () {
this._clips = [];
},
/**
* Create an animator
* @param {Object} target
* @param {Object} [options]
* @param {boolean} [options.loop]
* @param {Function} [options.getter]
* @param {Function} [options.setter]
* @param {Function} [options.interpolater]
* @return {clay.animation.Animator}
*/
animate: function (target, options) {
options = options || {};
var animator = new Animator(
target,
options.loop,
options.getter,
options.setter,
options.interpolater
);
animator.animation = this;
return animator;
}
});
export default Timeline;