/**
* @module Pixi Flash
* @namespace pixiflash
*/
(function(undefined)
{
var Container = PIXI.Container,
DisplayObject = pixiflash.DisplayObject,
Timeline = createjs.Timeline,
Tween = createjs.Tween,
SharedTicker = PIXI.ticker.shared;
//*** Note: the vast majority of the code here is from EaselJS's MovieClip class.
/*
* MovieClip
* 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.
*/
/**
* The class to emulate createjs.MovieClip, requires TweenJS
* @class MovieClip
* @extends PIXI.Container
*/
var MovieClip = function(mode, startPosition, loop, labels)
{
if(!MovieClip.inited) MovieClip.init(); // static init
Container.call(this);
DisplayObject.call(this);
this.tickChildren = true;
/**
* Controls how this MovieClip advances its time. Must be one of 0 (INDEPENDENT), 1 (SINGLE_FRAME), or 2 (SYNCHED).
* See each constant for a description of the behaviour.
* @property mode
* @type String
* @default null
**/
this.mode = mode||MovieClip.INDEPENDENT;
/**
* Specifies what the first frame to play in this movieclip, or the only frame to display if mode is SINGLE_FRAME.
* @property startPosition
* @type Number
* @default 0
*/
this.startPosition = startPosition || 0;
/**
* Indicates whether this MovieClip should loop when it reaches the end of its timeline.
* @property loop
* @type Boolean
* @default true
*/
this.loop = loop;
/**
* The current frame of the movieclip.
* @property currentFrame
* @type Number
* @default 0
* @readonly
*/
this.currentFrame = 0;
/**
* The TweenJS Timeline that is associated with this MovieClip. This is created automatically when the MovieClip
* instance is initialized. Animations are created by adding <a href="http://tweenjs.com">TweenJS</a> Tween
* instances to the timeline.
*
* <h4>Example</h4>
*
* var tween = createjs.Tween.get(target).to({x:0}).to({x:100}, 30);
* var mc = new createjs.MovieClip();
* mc.timeline.addTween(tween);
*
* Elements can be added and removed from the timeline by toggling an "_off" property
* using the <code>tweenInstance.to()</code> method. Note that using <code>Tween.set</code> is not recommended to
* create MovieClip animations. The following example will toggle the target off on frame 0, and then back on for
* frame 1. You can use the "visible" property to achieve the same effect.
*
* var tween = createjs.Tween.get(target).to({_off:false})
* .wait(1).to({_off:true})
* .wait(1).to({_off:false});
*
* @property timeline
* @type Timeline
* @default null
*/
this.timeline = new Timeline(null, labels, {paused:true, position:startPosition, useTicks:true});
/**
* If true, the MovieClip's position will not advance when ticked.
* @property paused
* @type Boolean
* @default false
*/
this.paused = false;
/**
* If true, actions in this MovieClip's tweens will be run when the playhead advances.
* @property actionsEnabled
* @type Boolean
* @default true
*/
this.actionsEnabled = true;
/**
* If true, the MovieClip will automatically be reset to its first frame whenever the timeline adds
* it back onto the display list. This only applies to MovieClip instances with mode=INDEPENDENT.
* <br><br>
* For example, if you had a character animation with a "body" child MovieClip instance
* with different costumes on each frame, you could set body.autoReset = false, so that
* you can manually change the frame it is on, without worrying that it will be reset
* automatically.
* @property autoReset
* @type Boolean
* @default true
*/
this.autoReset = true;
/**
* @property _synchOffset
* @type Number
* @default 0
* @private
*/
this._synchOffset = 0;
/**
* @property _prevPos
* @type Number
* @default -1
* @private
*/
this._prevPos = -1; // TODO: evaluate using a ._reset Boolean prop instead of -1.
/**
* @property _prevPosition
* @type Number
* @default 0
* @private
*/
this._prevPosition = 0;
/**
* Note - changed from default: When the MovieClip is framerate independent, this is the time
* elapsed from frame 0 in seconds.
* @property _t
* @type Number
* @default 0
* @private
*/
this._t = 0;
/**
* By default MovieClip instances advance one frame per tick. Specifying a framerate for the MovieClip
* will cause it to advance based on elapsed time between ticks as appropriate to maintain the target
* framerate.
*
* @property _framerate
* @type {Number}
* @default 0
**/
this._framerate = 0;
/**
* When the MovieClip is framerate independent, this is the total time in seconds for the animation.
* @property _duration
* @type Number
* @default 0
* @private
*/
this._duration = 0;
/**
* List of display objects that are actively being managed by the MovieClip.
* @property _managed
* @type Object
* @private
*/
this._managed = {};
//add a listener for the first time the object is added, to get around
//using new instances for prototypes that the CreateJS exporting does.
this.once("added", function()
{
this._tickListener = this._tickListener.bind(this);
this._onAdded();
this._onAdded = this._onAdded.bind(this);
this._onRemoved = this._onRemoved.bind(this);
this.on("added", this._onAdded);
this.on("removed", this._onRemoved);
}.bind(this));
};
MovieClip.inited = false;
// static methods:
MovieClip.init = function() {
if (MovieClip.inited) { return; }
// plugins introduce some overhead to Tween, so we only install this if an MC is instantiated.
MovieClipPlugin.install();
MovieClip.inited = true;
};
/**
* The MovieClip will advance independently of its parent, even if its parent is paused.
* This is the default mode.
* @property INDEPENDENT
* @static
* @type String
* @default "independent"
* @readonly
**/
MovieClip.INDEPENDENT = "independent";
/**
* The MovieClip will only display a single frame (as determined by the startPosition property).
* @property SINGLE_FRAME
* @static
* @type String
* @default "single"
* @readonly
**/
MovieClip.SINGLE_FRAME = "single";
/**
* The MovieClip will be advanced only when its parent advances and will be synched to the position of
* the parent MovieClip.
* @property SYNCHED
* @static
* @type String
* @default "synched"
* @readonly
**/
MovieClip.SYNCHED = "synched";
var p = MovieClip.prototype = Object.create(Container.prototype);
DisplayObject.mixin(p);
//constructor for backwards/Flash exporting compatibility
p.initialize = MovieClip;
p._onAdded = function()
{
if(!this.parent._isPixiFlash)
{
SharedTicker.add(this._tickListener);
}
};
p._tickListener = function(tickerDeltaTime)
{
var ms = tickerDeltaTime / SharedTicker.speed / PIXI.TARGET_FPMS;
this._tick(ms);
};
p._onRemoved = function()
{
if(this._tickListener)
SharedTicker.remove(this._tickListener);
};
/**
* Use the {{#crossLink "MovieClip/labels:property"}}{{/crossLink}} property instead.
* @method getLabels
* @return {Array}
* @deprecated
**/
p.getLabels = function() {
return this.timeline.getLabels();
};
/**
* Use the {{#crossLink "MovieClip/currentLabel:property"}}{{/crossLink}} property instead.
* @method getCurrentLabel
* @return {String}
* @deprecated
**/
p.getCurrentLabel = function() {
this._updateTimeline();
return this.timeline.getCurrentLabel();
};
/**
* Returns an array of objects with label and position (aka frame) properties, sorted by position.
* Shortcut to TweenJS: Timeline.getLabels();
* @property labels
* @type {Array}
* @readonly
**/
/**
* Returns the name of the label on or immediately before the current frame. See TweenJS: Timeline.getCurrentLabel()
* for more information.
* @property currentLabel
* @type {String}
* @readonly
**/
try {
Object.defineProperties(p, {
labels: { get: p.getLabels },
currentLabel: { get: p.getCurrentLabel }
});
} catch (e) {}
/**
* When the MovieClip is framerate independent, this is the time elapsed from frame 0 in seconds.
* @property elapsedTime
* @type Number
* @default 0
* @public
*/
Object.defineProperty(p, 'elapsedTime', {
get: function() {
return this._t;
},
set: function(value) {
this._t = value;
}
});
/**
* By default MovieClip instances advance one frame per tick. Specifying a framerate for the MovieClip
* will cause it to advance based on elapsed time between ticks as appropriate to maintain the target
* framerate.
*
* For example, if a MovieClip with a framerate of 10 is placed on a Stage being updated at 40fps, then the MovieClip will
* advance roughly one frame every 4 ticks. This will not be exact, because the time between each tick will
* vary slightly between frames.
*
* This feature is dependent on the tick event object (or an object with an appropriate "delta" property) being
* passed into {{#crossLink "Stage/update"}}{{/crossLink}}.
* @property framerate
* @type {Number}
* @default 0
**/
Object.defineProperty(p, 'framerate', {
get: function() {
return this._framerate;
},
set: function(value) {
if(value > 0)
{
this._framerate = value;
this._duration = value ? this.timeline.duration / value : 0;
}
else
this._framerate = this._duration = 0;
}
});
/**
* Sets paused to false.
* @method play
**/
p.play = function() {
this.paused = false;
};
/**
* Sets paused to true.
* @method stop
**/
p.stop = function() {
this.paused = true;
};
/**
* Advances this movie clip to the specified position or label and sets paused to false.
* @method gotoAndPlay
* @param {String|Number} positionOrLabel The animation name or frame number to go to.
**/
p.gotoAndPlay = function(positionOrLabel) {
this.paused = false;
this._goto(positionOrLabel);
};
/**
* Advances this movie clip to the specified position or label and sets paused to true.
* @method gotoAndStop
* @param {String|Number} positionOrLabel The animation or frame name to go to.
**/
p.gotoAndStop = function(positionOrLabel) {
this.paused = true;
this._goto(positionOrLabel);
};
/**
* Advances the playhead. This occurs automatically each tick by default.
* @param [time] {Number} The amount of time in ms to advance by. Only applicable if framerate is set.
* @method advance
*/
p.advance = function(time) {
// TODO: should we worry at all about clips who change their own modes via frame scripts?
var independent = MovieClip.INDEPENDENT;
if (this.mode != independent) { return; }
if(!this._framerate)
{
var o=this, fps = o._framerate;
while ((o = o.parent) && !fps) {
if (o.mode == independent) { fps = o._framerate; }
}
this.framerate = fps;
}
if(!this.paused)
{
if(this._framerate > 0)
{
if(time)
this._t += time * 0.001;//milliseconds -> seconds
if(this._t > this._duration)
this._t = this.timeline.loop ? this._t - this._duration : this._duration;
//add a tiny amount to account for potential floating point errors
this._prevPosition = Math.floor(this._t * this._framerate + 0.00000001);
if(this._prevPosition > this.timeline.duration)
this._prevPosition = this.timeline.duration;
}
else
this._prevPosition = (this._prevPos < 0) ? 0 : this._prevPosition+1;
//Timeline is always updated in the tick function for PixiFlash MovieClips,
//to replace EaselJS's timeline updating in draw().
//this._updateTimeline();
}
};
/**
* @method _tick
* @param {Number} delta Time elapsed since the previous tick, in milliseconds.
* function.
* @protected
**/
p._tick = function(delta) {
if(this.tickEnabled)
this.advance(delta);
this._updateTimeline();
this.Container__tick(delta);
};
p.Container__tick = function(delta) {
if (this.tickChildren) {
for (var i=this.children.length-1; i>=0; i--) {
var child = this.children[i];
if (child.tickEnabled && child._tick) { child._tick(delta); }
else if(child.tickChildren && child.Container__tick)
{
child.Container__tick(delta);
}
}
}
};
/**
* @method _goto
* @param {String|Number} positionOrLabel The animation name or frame number to go to.
* @protected
**/
p._goto = function(positionOrLabel) {
var pos = this.timeline.resolve(positionOrLabel);
if (pos === null || pos === undefined) { return; }
// prevent _updateTimeline from overwriting the new position because of a reset:
if (this._prevPos == -1) { this._prevPos = NaN; }
this._prevPosition = pos;
//update the elapsed time if a time based movieclip
if(this._framerate > 0)
this._t = pos / this._framerate;
else
this._t = 0;
this._updateTimeline();
};
/**
* @method _reset
* @private
**/
p._reset = function() {
this._prevPos = -1;
this._t = 0;
this.currentFrame = 0;
};
/**
* @method _updateTimeline
* @protected
**/
p._updateTimeline = function() {
var tl = this.timeline;
var synched = this.mode != MovieClip.INDEPENDENT;
tl.loop = (this.loop==null) ? true : this.loop; // jshint ignore:line
// update timeline position, ignoring actions if this is a graphic.
if (synched) {
tl.setPosition(this.startPosition + (this.mode==MovieClip.SINGLE_FRAME?0:this._synchOffset), Tween.NONE);
} else {
tl.setPosition(this._prevPos < 0 ? 0 : this._prevPosition, this.actionsEnabled ? null : Tween.NONE);
}
this._prevPosition = tl._prevPosition;
if (this._prevPos == tl._prevPos) { return; }
this.currentFrame = this._prevPos = tl._prevPos;
for (var n in this._managed) { this._managed[n] = 1; }
var tweens = tl._tweens;
for (var i=0, l=tweens.length; i<l; i++) {
var tween = tweens[i];
var target = tween._target;
if (target == this || tween.passive) { continue; } // TODO: this assumes actions tween has this as the target. Valid?
var offset = tween._stepPosition;
//Containers, Bitmaps(Sprites), and MovieClips(also Containers) all inherit from
//Container for PIXI
if (target instanceof Container) {
// motion tween.
this._addManagedChild(target, offset);
} else {
// state tween.
this._setState(target.state, offset);
}
}
var kids = this.children;
for (i=kids.length-1; i>=0; i--) {
var id = kids[i].id;
if (this._managed[id] == 1) {
this.removeChildAt(i);
delete(this._managed[id]);
}
}
};
/**
* @method _setState
* @param {Array} state
* @param {Number} offset
* @protected
**/
p._setState = function(state, offset) {
if (!state) { return; }
for (var i=state.length-1;i>=0;i--) {
var o = state[i];
var target = o.t;
var props = o.p;
for (var n in props) { target[n] = props[n]; }
this._addManagedChild(target, offset);
}
};
/**
* Adds a child to the timeline, and sets it up as a managed child.
* @method _addManagedChild
* @param {MovieClip} child The child MovieClip to manage
* @param {Number} offset
* @private
**/
p._addManagedChild = function(child, offset) {
if (child._off) { return; }
this.addChildAt(child,0);
if (child instanceof MovieClip) {
child._synchOffset = offset;
child._updateTimeline();
// TODO: this does not precisely match Flash. Flash loses track of the clip if it is renamed or removed from the timeline, which causes it to reset.
if (child.mode == MovieClip.INDEPENDENT && child.autoReset && !this._managed[child.id]) { child._reset(); }
}
this._managed[child.id] = 2;
};
p.__Container_destroy = p.destroy;
p.destroy = function(destroyChildren)
{
if(this._tickListener)
{
SharedTicker.remove(this._tickListener);
this._tickListener = null;
}
this.__Container_destroy(destroyChildren);
};
pixiflash.MovieClip = MovieClip;
/**
* This plugin works with <a href="http://tweenjs.com" target="_blank">TweenJS</a> to prevent the startPosition
* property from tweening.
* @private
* @class MovieClipPlugin
* @constructor
**/
function MovieClipPlugin() {
throw("MovieClipPlugin cannot be instantiated.");
}
/**
* @method priority
* @private
**/
MovieClipPlugin.priority = 100; // very high priority, should run first
/**
* @method install
* @private
**/
MovieClipPlugin.install = function() {
Tween.installPlugin(MovieClipPlugin, ["startPosition"]);
};
/**
* @method init
* @param {Tween} tween
* @param {String} prop
* @param {String|Number|Boolean} value
* @private
**/
MovieClipPlugin.init = function(tween, prop, value) {
return value;
};
/**
* @method step
* @private
**/
MovieClipPlugin.step = function() {
// unused.
};
/**
* @method tween
* @param {Tween} tween
* @param {String} prop
* @param {String | Number | Boolean} value
* @param {Array} startValues
* @param {Array} endValues
* @param {Number} ratio
* @param {Object} wait
* @param {Object} end
* @return {*}
*/
MovieClipPlugin.tween = function(tween, prop, value, startValues, endValues, ratio, wait, end) {
if (!(tween.target instanceof MovieClip)) { return value; }
return (ratio == 1 ? endValues[prop] : startValues[prop]);
};
}());