/* * 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. */ // namespace: this.createjs = this.createjs||{}; (function() { /** * The MovieClip class associates a TweenJS Timeline with an EaselJS {{#crossLink "Container"}}{{/crossLink}}. It allows * you to create objects which encapsulate timeline animations, state changes, and synched actions. Due to the * complexities inherent in correctly setting up a MovieClip, it is largely intended for tool output and is not included * in the main EaselJS library. * * Currently MovieClip only works properly if it is tick based (as opposed to time based) though some concessions have * been made to support time-based timelines in the future. * *

Example

* This example animates two shapes back and forth. The grey shape starts on the left, but we jump to a mid-point in * the animation using {{#crossLink "MovieClip/gotoAndPlay"}}{{/crossLink}}. * * var stage = new createjs.Stage("canvas"); * createjs.Ticker.addEventListener("tick", stage); * * var mc = new createjs.MovieClip(null, 0, true, {start:20}); * stage.addChild(mc); * * var child1 = new createjs.Shape( * new createjs.Graphics().beginFill("#999999") * .drawCircle(30,30,30)); * var child2 = new createjs.Shape( * new createjs.Graphics().beginFill("#5a9cfb") * .drawCircle(30,30,30)); * * mc.timeline.addTween( * createjs.Tween.get(child1) * .to({x:0}).to({x:60}, 50).to({x:0}, 50)); * mc.timeline.addTween( * createjs.Tween.get(child2) * .to({x:60}).to({x:0}, 50).to({x:60}, 50)); * * mc.gotoAndPlay("start"); * * It is recommended to use tween.to() to animate and set properties (use no duration to have it set * immediately), and the tween.wait() method to create delays between animations. Note that using the * tween.set() method to affect properties will likely not provide the desired result. * * @class MovieClip * @main MovieClip * @extends Container * @constructor * @param {String} [mode=independent] Initial value for the mode property. One of MovieClip.INDEPENDENT, * MovieClip.SINGLE_FRAME, or MovieClip.SYNCHED. The default is MovieClip.INDEPENDENT. * @param {Number} [startPosition=0] Initial value for the startPosition property. * @param {Boolean} [loop=true] Initial value for the loop property. The default is true. * @param {Object} [labels=null] A hash of labels to pass to the timeline instance associated with this MovieClip. * Labels only need to be passed if they need to be used. **/ var MovieClip = function(mode, startPosition, loop, labels) { this.initialize(mode, startPosition, loop, labels); } var p = MovieClip.prototype = new createjs.Container(); /** * Read-only. 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" **/ MovieClip.INDEPENDENT = "independent"; /** * Read-only. The MovieClip will only display a single frame (as determined by the startPosition property). * @property SINGLE_FRAME * @static * @type String * @default "single" **/ MovieClip.SINGLE_FRAME = "single"; /** * Read-only. The MovieClip will be advanced only when it's parent advances and will be synched to the position of * the parent MovieClip. * @property SYNCHED * @static * @type String * @default "synched" **/ MovieClip.SYNCHED = "synched"; // public properties: /** * 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 **/ p.mode; /** * 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 */ p.startPosition = 0; /** * Indicates whether this MovieClip should loop when it reaches the end of its timeline. * @property loop * @type Boolean * @default true */ p.loop = true; /** * Read-Only. The current frame of the movieclip. * @property currentFrame * @type Number */ p.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 TweenJS Tween * instances to the timeline. * *

Example

* 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 tweenInstance.to() method. Note that using Tween.set 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 */ p.timeline = null; /** * If true, the MovieClip's position will not advance when ticked. * @property paused * @type Boolean * @default false */ p.paused = false; /** * If true, actions in this MovieClip's tweens will be run when the playhead advances. * @property actionsEnabled * @type Boolean * @default true */ p.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. *

* 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 */ p.autoReset = true; // private properties: /** * @property _synchOffset * @type Number * @default 0 * @private */ p._synchOffset = 0; /** * @property _prevPos * @type Number * @default -1 * @private */ p._prevPos = -1; // TODO: evaluate using a ._reset Boolean prop instead of -1. /** * @property _prevPosition * @type Number * @default 0 * @private */ p._prevPosition = 0; /** * List of display objects that are actively being managed by the MovieClip. * @property _managed * @type Object * @private */ p._managed; // constructor: /** * @property DisplayObject_initialize * @type Function * @private **/ p.Container_initialize = p.initialize; /** * Initialization method called by the constructor. * @method initialize * @protected **/ p.initialize = function(mode, startPosition, loop, labels) { this.mode = mode||MovieClip.INDEPENDENT; this.startPosition = startPosition || 0; this.loop = loop; props = {paused:true, position:startPosition, useTicks:true}; this.Container_initialize(); this.timeline = new createjs.Timeline(null, labels, props); this._managed = {}; } // public methods: /** * Returns true or false indicating whether the display object would be visible if drawn to a canvas. * This does not account for whether it would be visible within the boundaries of the stage. * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method isVisible * @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas **/ p.isVisible = function() { // children are placed in draw, so we can't determine if we have content. return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0); }; /** * @property Container_draw * @type Function * @private **/ p.Container_draw = p.draw; /** * Draws the display object into the specified context ignoring it's visible, alpha, shadow, and transform. * Returns true if the draw was handled (useful for overriding functionality). * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method draw * @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. * @param {Boolean} ignoreCache Indicates whether the draw operation should ignore any current cache. * For example, used for drawing the cache (to prevent it from simply drawing an existing cache back * into itself). **/ p.draw = function(ctx, ignoreCache, _mtx) { // draw to cache first: if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; } this._updateTimeline(); this.Container_draw(ctx, ignoreCache, _mtx); } /** * 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 **/ 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 **/ p.gotoAndStop = function(positionOrLabel) { this.paused = true; this._goto(positionOrLabel); } /** * MovieClip instances cannot be cloned. * @method clone **/ p.clone = function() { // TODO: add support for this? Need to clone the Timeline & retarget tweens - pretty complex. throw("MovieClip cannot be cloned.") } /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[MovieClip (name="+ this.name +")]"; } // private methods: /** * @property Container__tick * @type Function * @private **/ p.Container__tick = p._tick; /** * @method _tick * @private **/ p._tick = function(params) { if (!this.paused && this.mode == MovieClip.INDEPENDENT) { this._prevPosition = (this._prevPos < 0) ? 0 : this._prevPosition+1; } this.Container__tick(params); } /** * @method _goto * @private **/ p._goto = function(positionOrLabel) { var pos = this.timeline.resolve(positionOrLabel); if (pos == null) { return; } // prevent _updateTimeline from overwriting the new position because of a reset: if (this._prevPos == -1) { this._prevPos = NaN; } this._prevPosition = pos; this._updateTimeline(); } /** * @method _reset * @private **/ p._reset = function() { this._prevPos = -1; this.currentFrame = 0; } /** * @method _updateTimeline * @private **/ p._updateTimeline = function() { var tl = this.timeline; var tweens = tl._tweens; var kids = this.children; var synched = this.mode != MovieClip.INDEPENDENT; tl.loop = this.loop==null?true:this.loop; // update timeline position, ignoring actions if this is a graphic. if (synched) { // TODO: this would be far more ideal if the _synchOffset was somehow provided by the parent, so that reparenting wouldn't cause problems and we can direct draw. Ditto for _off (though less important). tl.setPosition(this.startPosition + (this.mode==MovieClip.SINGLE_FRAME?0:this._synchOffset), createjs.Tween.NONE); } else { tl.setPosition(this._prevPos < 0 ? 0 : this._prevPosition, this.actionsEnabled ? null : createjs.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; } for (var i=tweens.length-1;i>=0;i--) { var tween = tweens[i]; var target = tween._target; if (target == this) { continue; } // TODO: this assumes this is the actions tween. Valid? var offset = tween._stepPosition; if (target instanceof createjs.DisplayObject) { // motion tween. this._addManagedChild(target, offset); } else { // state tween. this._setState(target.state, offset); } } 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 * @private **/ p._setState = function(state, offset) { if (!state) { return; } for (var i=0,l=state.length;iTweenJS to prevent the startPosition * property from tweening. * @private * @class MovieClipPlugin * @constructor **/ var MovieClipPlugin = function() { throw("MovieClipPlugin cannot be instantiated.") } /** * @method priority * @private **/ MovieClipPlugin.priority = 100; // very high priority, should run first /** * @method install * @private **/ MovieClipPlugin.install = function() { createjs.Tween.installPlugin(MovieClipPlugin, ["startPosition"]); } /** * @method init * @private **/ MovieClipPlugin.init = function(tween, prop, value) { return value; } /** * @method step * @private **/ MovieClipPlugin.step = function() { // unused. } /** * @method tween * @private **/ 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]); } MovieClipPlugin.install(); }());