/*
* Timeline
* Visit http://createjs.com/ for documentation, updates and examples.
*
* Copyright (c) 2010 gskinner.com, inc.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* @module TweenJS
*/
// namespace:
this.createjs = this.createjs||{};
(function() {
"use strict";
// constructor
/**
* The Timeline class synchronizes multiple tweens and allows them to be controlled as a group. Please note that if a
* timeline is looping, the tweens on it may appear to loop even if the "loop" property of the tween is false.
* @class Timeline
* @param {Array} tweens An array of Tweens to add to this timeline. See addTween for more info.
* @param {Object} labels An object defining labels for using {{#crossLink "Timeline/gotoAndPlay"}}{{/crossLink}}/{{#crossLink "Timeline/gotoAndStop"}}{{/crossLink}}.
* See {{#crossLink "Timeline/setLabels"}}{{/crossLink}}
* for details.
* @param {Object} props The configuration properties to apply to this tween instance (ex. `{loop:true}`). All properties
* default to false. Supported props are:<UL>
* <LI> loop: sets the loop property on this tween.</LI>
* <LI> useTicks: uses ticks for all durations instead of milliseconds.</LI>
* <LI> ignoreGlobalPause: sets the ignoreGlobalPause property on this tween.</LI>
* <LI> paused: indicates whether to start the tween paused.</LI>
* <LI> position: indicates the initial position for this timeline.</LI>
* <LI> onChange: specifies a listener to add for the {{#crossLink "Timeline/change:event"}}{{/crossLink}} event.</LI>
* </UL>
* @extends EventDispatcher
* @constructor
**/
function Timeline(tweens, labels, props) {
this.EventDispatcher_constructor();
// public properties:
/**
* Causes this timeline to continue playing when a global pause is active.
* @property ignoreGlobalPause
* @type Boolean
**/
this.ignoreGlobalPause = false;
/**
* Read-only property specifying the total duration of this timeline in milliseconds (or ticks if useTicks is true).
* This value is usually automatically updated as you modify the timeline. See updateDuration for more information.
* @property duration
* @type Number
**/
this.duration = 0;
/**
* If true, the timeline will loop when it reaches the end. Can be set via the props param.
* @property loop
* @type Boolean
**/
this.loop = false;
/**
* Read-only. The current normalized position of the timeline. This will always be a value between 0 and duration.
* Changing this property directly will have no effect.
* @property position
* @type Object
**/
this.position = null;
// private properties:
/**
* @property _paused
* @type Boolean
* @protected
**/
this._paused = false;
/**
* @property _tweens
* @type Array[Tween]
* @protected
**/
this._tweens = [];
/**
* @property _labels
* @type Object
* @protected
**/
this._labels = null;
/**
* @property _labelList
* @type Array[Object]
* @protected
**/
this._labelList = null;
/**
* @property _prevPosition
* @type Number
* @default 0
* @protected
**/
this._prevPosition = 0;
/**
* @property _prevPos
* @type Number
* @default -1
* @protected
**/
this._prevPos = -1;
/**
* @property _useTicks
* @type Boolean
* @default false
* @protected
**/
this._useTicks = false;
if (props) {
this._useTicks = props.useTicks;
this.loop = props.loop;
this.ignoreGlobalPause = props.ignoreGlobalPause;
props.onChange&&this.addEventListener("change", props.onChange);
}
if (tweens) { this.addTween.apply(this, tweens); }
this.setLabels(labels);
if (props&&props.paused) { this._paused=true; }
else { createjs.Tween._register(this,true); }
if (props&&props.position!=null) { this.setPosition(props.position, createjs.Tween.NONE); }
};
var p = createjs.extend(Timeline, createjs.EventDispatcher);
// TODO: deprecated
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
// events:
/**
* Called whenever the timeline's position changes.
* @event change
* @since 0.5.0
**/
// public methods:
/**
* Adds one or more tweens (or timelines) to this timeline. The tweens will be paused (to remove them from the normal ticking system)
* and managed by this timeline. Adding a tween to multiple timelines will result in unexpected behaviour.
* @method addTween
* @param tween The tween(s) to add. Accepts multiple arguments.
* @return Tween The first tween that was passed in.
**/
p.addTween = function(tween) {
var l = arguments.length;
if (l > 1) {
for (var i=0; i<l; i++) { this.addTween(arguments[i]); }
return arguments[0];
} else if (l == 0) { return null; }
this.removeTween(tween);
this._tweens.push(tween);
tween.setPaused(true);
tween._paused = false;
tween._useTicks = this._useTicks;
if (tween.duration > this.duration) { this.duration = tween.duration; }
if (this._prevPos >= 0) { tween.setPosition(this._prevPos, createjs.Tween.NONE); }
return tween;
};
/**
* Removes one or more tweens from this timeline.
* @method removeTween
* @param tween The tween(s) to remove. Accepts multiple arguments.
* @return Boolean Returns true if all of the tweens were successfully removed.
**/
p.removeTween = function(tween) {
var l = arguments.length;
if (l > 1) {
var good = true;
for (var i=0; i<l; i++) { good = good && this.removeTween(arguments[i]); }
return good;
} else if (l == 0) { return false; }
var tweens = this._tweens;
var i = tweens.length;
while (i--) {
if (tweens[i] == tween) {
tweens.splice(i, 1);
if (tween.duration >= this.duration) { this.updateDuration(); }
return true;
}
}
return false;
};
/**
* Adds a label that can be used with {{#crossLink "Timeline/gotoAndPlay"}}{{/crossLink}}/{{#crossLink "Timeline/gotoAndStop"}}{{/crossLink}}.
* @method addLabel
* @param {String} label The label name.
* @param {Number} position The position this label represents.
**/
p.addLabel = function(label, position) {
this._labels[label] = position;
var list = this._labelList;
if (list) {
for (var i= 0,l=list.length; i<l; i++) { if (position < list[i].position) { break; } }
list.splice(i, 0, {label:label, position:position});
}
};
/**
* Defines labels for use with gotoAndPlay/Stop. Overwrites any previously set labels.
* @method setLabels
* @param {Object} o An object defining labels for using gotoAndPlay/Stop in the form `{labelName:time}` where time is in
* milliseconds (or ticks if `useTicks` is true).
**/
p.setLabels = function(o) {
this._labels = o ? o : {};
};
/**
* Returns a sorted list of the labels defined on this timeline.
* @method getLabels
* @return {Array[Object]} A sorted array of objects with label and position properties.
**/
p.getLabels = function() {
var list = this._labelList;
if (!list) {
list = this._labelList = [];
var labels = this._labels;
for (var n in labels) {
list.push({label:n, position:labels[n]});
}
list.sort(function (a,b) { return a.position- b.position; });
}
return list;
};
/**
* Returns the name of the label on or immediately before the current position. For example, given a timeline with
* two labels, "first" on frame index 4, and "second" on frame 8, getCurrentLabel would return:<UL>
* <LI>null if the current position is 2.</LI>
* <LI>"first" if the current position is 4.</LI>
* <LI>"first" if the current position is 7.</LI>
* <LI>"second" if the current position is 15.</LI></UL>
* @method getCurrentLabel
* @return {String} The name of the current label or null if there is no label
**/
p.getCurrentLabel = function() {
var labels = this.getLabels();
var pos = this.position;
var l = labels.length;
if (l) {
for (var i = 0; i<l; i++) { if (pos < labels[i].position) { break; } }
return (i==0) ? null : labels[i-1].label;
}
return null;
};
/**
* Unpauses this timeline and jumps to the specified position or label.
* @method gotoAndPlay
* @param {String|Number} positionOrLabel The position in milliseconds (or ticks if `useTicks` is true) or label to jump to.
**/
p.gotoAndPlay = function(positionOrLabel) {
this.setPaused(false);
this._goto(positionOrLabel);
};
/**
* Pauses this timeline and jumps to the specified position or label.
* @method gotoAndStop
* @param {String|Number} positionOrLabel The position in milliseconds (or ticks if `useTicks` is true) or label to jump to.
**/
p.gotoAndStop = function(positionOrLabel) {
this.setPaused(true);
this._goto(positionOrLabel);
};
/**
* Advances the timeline to the specified position.
* @method setPosition
* @param {Number} value The position to seek to in milliseconds (or ticks if `useTicks` is true).
* @param {Number} [actionsMode] parameter specifying how actions are handled. See the Tween {{#crossLink "Tween/setPosition"}}{{/crossLink}}
* method for more details.
* @return {Boolean} Returns true if the timeline is complete (ie. the full timeline has run & loop is false).
**/
p.setPosition = function(value, actionsMode) {
if (value < 0) { value = 0; }
var t = this.loop ? value%this.duration : value;
var end = !this.loop && value >= this.duration;
if (t == this._prevPos) { return end; }
this._prevPosition = value;
this.position = this._prevPos = t; // in case an action changes the current frame.
for (var i=0, l=this._tweens.length; i<l; i++) {
this._tweens[i].setPosition(t, actionsMode);
if (t != this._prevPos) { return false; } // an action changed this timeline's position.
}
if (end) { this.setPaused(true); }
this.dispatchEvent("change");
return end;
};
/**
* Pauses or plays this timeline.
* @method setPaused
* @param {Boolean} value Indicates whether the tween should be paused (true) or played (false).
**/
p.setPaused = function(value) {
this._paused = !!value;
createjs.Tween._register(this, !value);
};
/**
* Recalculates the duration of the timeline.
* The duration is automatically updated when tweens are added or removed, but this method is useful
* if you modify a tween after it was added to the timeline.
* @method updateDuration
**/
p.updateDuration = function() {
this.duration = 0;
for (var i=0,l=this._tweens.length; i<l; i++) {
var tween = this._tweens[i];
if (tween.duration > this.duration) { this.duration = tween.duration; }
}
};
/**
* Advances this timeline by the specified amount of time in milliseconds (or ticks if useTicks is true).
* This is normally called automatically by the Tween engine (via Tween.tick), but is exposed for advanced uses.
* @method tick
* @param {Number} delta The time to advance in milliseconds (or ticks if useTicks is true).
**/
p.tick = function(delta) {
this.setPosition(this._prevPosition+delta);
};
/**
* If a numeric position is passed, it is returned unchanged. If a string is passed, the position of the
* corresponding frame label will be returned, or null if a matching label is not defined.
* @method resolve
* @param {String|Number} positionOrLabel A numeric position value or label string.
**/
p.resolve = function(positionOrLabel) {
var pos = Number(positionOrLabel);
if (isNaN(pos)) { pos = this._labels[positionOrLabel]; }
return pos;
};
/**
* Returns a string representation of this object.
* @method toString
* @return {String} a string representation of the instance.
**/
p.toString = function() {
return "[Timeline]";
};
/**
* @method clone
* @protected
**/
p.clone = function() {
throw("Timeline can not be cloned.")
};
// private methods:
/**
* @method _goto
* @protected
**/
p._goto = function(positionOrLabel) {
var pos = this.resolve(positionOrLabel);
if (pos != null) { this.setPosition(pos); }
};
createjs.Timeline = createjs.promote(Timeline, "EventDispatcher");
}());