1 /**
  2  * The MIT License (MIT)
  3  *
  4  * Copyright (c) 2016 DeNA Co., Ltd.
  5  *
  6  * Permission is hereby granted, free of charge, to any person obtaining a copy
  7  * of this software and associated documentation files (the "Software"), to deal
  8  * in the Software without restriction, including without limitation the rights
  9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10  * copies of the Software, and to permit persons to whom the Software is
 11  * furnished to do so, subject to the following conditions:
 12  *
 13  * The above copyright notice and this permission notice shall be included in
 14  * all copies or substantial portions of the Software.
 15  *
 16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 22  * SOFTWARE.
 23  */
 24 
 25 /// <reference path="base.js"/>
 26 /// <reference path="container.js"/>
 27 /// <reference path="tween_target.js"/>
 28 /// <reference path="tween_object.js"/>
 29 
 30 /**
 31  * A class that associates a createjs.Timeline object with a createjs.Container
 32  * object.
 33  * @param {string=} opt_mode
 34  * @param {number=} opt_position
 35  * @param {boolean=} opt_loop
 36  * @param {Object.<string,number>=} opt_labels
 37  * @extends {createjs.Container}
 38  * @constructor
 39  */
 40 createjs.MovieClip = function(opt_mode, opt_position, opt_loop, opt_labels) {
 41   createjs.Container.call(this);
 42 
 43   /**
 44    * The first frame to play in this clip. (Its default value is used for
 45    * preventing an infinite loop and it must be 0 for now.)
 46    * @type {number}
 47    * @private
 48    */
 49   this.startPosition_ = opt_position || 0;
 50 
 51   /**
 52    * Whether this clip loops when it reaches its end.
 53    * @type {boolean}
 54    * @private
 55    */
 56   this.loop_ = (opt_loop == null) ? true : !!opt_loop;
 57 
 58   /**
 59    * The label set, i.e. a mapping table from a string to a frame number.
 60    * @type {Object.<string,number>}
 61    * @private
 62    */
 63   this.labels_ = opt_labels || {};
 64 
 65   /**
 66    * The timeline object for this clip.
 67    * @type {createjs.MovieClip.Timeline}
 68    * @private
 69    */
 70   this.timeline_ = new createjs.MovieClip.Timeline(this);
 71 
 72   // Initialize the play mode of the tweens to be added to this clip.
 73   this.setPlayMode(createjs.MovieClip.getMode_(opt_mode));
 74 };
 75 createjs.inherits('MovieClip', createjs.MovieClip, createjs.Container);
 76 
 77 /**
 78  * An inner class used by the 'createjs.MovieClip.getLabels()' method.
 79  * @param {string} label
 80  * @param {number} position
 81  * @constructor
 82  */
 83 createjs.MovieClip.Label = function(label, position) {
 84   /**
 85    * @const {string}
 86    * @private
 87    */
 88   this['label'] = label;
 89 
 90   /**
 91    * @const {number}
 92    * @private
 93    */
 94   this['position'] = position;
 95 };
 96 
 97 /**
 98  * Returns a sorted list of the labels defined on this timeline.
 99  * @param {createjs.MovieClip.Label} a
100  * @param {createjs.MovieClip.Label} b
101  * @return {number}
102  * @private
103  */
104 createjs.MovieClip.Label.sortFunction_ = function(a, b) {
105   /// <param type="createjs.MovieClip.Label" name="a"/>
106   /// <param type="createjs.MovieClip.Label" name="b"/>
107   /// <returns type="number"/>
108   return a['position'] - b['position'];
109 };
110 
111 /**
112  * An inner class that synchronizes multiple createjs.Tween objects and controls
113  * them.
114  * @param {createjs.MovieClip} clip
115  * @constructor
116  */
117 createjs.MovieClip.Timeline = function(clip) {
118   /**
119    * @type {createjs.MovieClip}
120    * @private
121    */
122   this.clip_ = clip;
123 };
124 
125 /**
126  * Returns the duration of this timeline.
127  * @return {number}
128  */
129 createjs.MovieClip.Timeline.prototype.getDuration = function() {
130   /// <returns type="number"/>
131   return this.clip_.getDuration();
132 };
133 
134 /**
135  * Returns the current position of this timeline.
136  * @return {number}
137  */
138 createjs.MovieClip.Timeline.prototype.getPosition = function() {
139   /// <returns type="number"/>
140   return this.clip_.getPosition();
141 };
142 
143 /**
144  * Adds one or more tweens (or timelines) to this timeline.
145  * @param {...createjs.TweenObject} var_args
146  * @return {createjs.TweenObject}
147  * @private
148  */
149 createjs.MovieClip.Timeline.prototype.addTween = function(var_args) {
150   /// <param type="createjs.TweenObject" name="var_args"/>
151   /// <returns type="createjs.TweenObject"/>
152   var args = arguments;
153   var length = args.length;
154   if (length == 0) {
155     return null;
156   }
157   for (var i = 0; i < length; ++i) {
158     this.clip_.addTween(args[i]);
159   }
160   return args[0];
161 };
162 
163 /**
164  * Removes one or more tweens from this timeline.
165  * @param {...createjs.TweenObject} var_args
166  * @return {boolean}
167  * @private
168  */
169 createjs.MovieClip.Timeline.prototype.removeTween = function(var_args) {
170   /// <param type="createjs.TweenObject" name="var_args"/>
171   /// <returns type="boolean"/>
172   var removed = false;
173   var args = arguments;
174   var length = args.length;
175   for (var i = 0; i < length; ++i) {
176     var tween = /** @type {createjs.TweenObject} */ (args[i]);
177     if (this.clip_.removeTween(tween)) {
178       removed = true;
179     }
180   }
181   return removed;
182 };
183 
184 /**
185  * Adds a label that can be used with the gotoAndPlay() method.
186  * @param {string} label
187  * @param {number} position
188  * @private
189  */
190 createjs.MovieClip.Timeline.prototype.addLabel = function(label, position) {
191   /// <param type="string" name="label"/>
192   /// <param type="number" name="position"/>
193   this.clip_.addLabel(label, position);
194 };
195 
196 /**
197  * Sets labels for this timeline.
198  * @param {Object.<string,number>=} opt_labels
199  * @private
200  */
201 createjs.MovieClip.Timeline.prototype.setLabels = function(opt_labels) {
202   /// <param type="Object" name="labels"/>
203   this.clip_.setLabels(opt_labels || {});
204 };
205 
206 /**
207  * Returns the sorted list of the labels added to this timeline.
208  * @return {Array.<createjs.MovieClip.Label>}
209  * @private
210  */
211 createjs.MovieClip.Timeline.prototype.getLabels = function() {
212   /// <returns type="Array" elementType="createjs.MovieClip.Label"/>
213   return this.clip_.getLabels();
214 };
215   
216 /**
217  * Returns the name of the label on or immediately before the current position.
218  * @return {string}
219  * @private
220  */
221 createjs.MovieClip.Timeline.prototype.getCurrentLabel = function() {
222   /// <returns type="string"/>
223   return this.clip_.getCurrentLabel();
224 };
225 
226 /**
227  * Pauses or plays this timeline.
228  * @param {boolean} value
229  */
230 createjs.MovieClip.Timeline.prototype.setPaused = function(value) {
231   /// <param type="boolean" name="value"/>
232   this.clip_.setPaused(!!value);
233 };
234 
235 /**
236  * Starts playing this timeline from the specified position.
237  * @param {string|number} value
238  */
239 createjs.MovieClip.Timeline.prototype.gotoAndPlay = function(value) {
240   /// <signature>
241   ///   <param type="string" name="label"/>
242   /// </signature>
243   /// <signature>
244   ///   <param type="number" name="frame"/>
245   /// </signature>
246   this.clip_.gotoAndPlay(value);
247 };
248 
249 /**
250  * Stops playing this timeline and jumps to the specified position.
251  * @param {string|number} value
252  */
253 createjs.MovieClip.Timeline.prototype.gotoAndStop = function(value) {
254   /// <signature>
255   ///   <param type="string" name="label"/>
256   /// </signature>
257   /// <signature>
258   ///   <param type="number" name="frame"/>
259   /// </signature>
260   this.clip_.gotoAndStop(value);
261 };
262 
263 // Add getters for applications that access internal variables.
264 Object.defineProperties(createjs.MovieClip.Timeline.prototype, {
265   'duration': {
266     get: createjs.MovieClip.Timeline.prototype.getDuration
267   },
268   'position': {
269     get: createjs.MovieClip.Timeline.prototype.getPosition
270   }
271 });
272 
273 // Export the createjs.Timeline object to the global namespace.
274 createjs.exportObject('createjs.MovieClip.Timeline',
275                       createjs.MovieClip.Timeline, {
276   // createjs.Timeline methods.
277   'addTween': createjs.MovieClip.Timeline.prototype.addTween,
278   'removeTween': createjs.MovieClip.Timeline.prototype.removeTween,
279   'addLabel': createjs.MovieClip.Timeline.prototype.addLabel,
280   'setLabels': createjs.MovieClip.Timeline.prototype.setLabels,
281   'getLabels': createjs.MovieClip.Timeline.prototype.getLabels,
282   'getCurrentLabel': createjs.MovieClip.Timeline.prototype.getCurrentLabel,
283   'setPaused': createjs.MovieClip.Timeline.prototype.setPaused,
284   'gotoAndPlay': createjs.MovieClip.Timeline.prototype.gotoAndPlay,
285   'gotoAndStop': createjs.MovieClip.Timeline.prototype.gotoAndStop
286 });
287 
288 /**
289  * Returns a mode ID for the specified mode name.
290  * @param {string|undefined} mode
291  * @return {number}
292  * @private
293  */
294 createjs.MovieClip.getMode_ = function(mode) {
295   /// <param type="string" name="mode"/>
296   /// <returns type="number"/>
297   if (mode == 'single') {
298     return createjs.TweenTarget.PlayMode.SINGLE;
299   } else if (mode == 'synched') {
300     return createjs.TweenTarget.PlayMode.SYNCHED;
301   }
302   return createjs.TweenTarget.PlayMode.INDEPENDENT;
303 };
304 
305 /**
306  * The total duration of this clip in milliseconds.
307  * @type {number}
308  * @private
309  */
310 createjs.MovieClip.prototype.duration_ = 0;
311 
312 /**
313  * A list of labels used by the getLabels() method.
314  * @type {Array.<createjs.MovieClip.Label>}
315  * @private
316  */
317 createjs.MovieClip.prototype.labelList_ = null;
318 
319 /**
320  * Target objects of the tweens added to this clip.
321  * @type {Array.<createjs.TweenTarget>}
322  * @private
323  */
324 createjs.MovieClip.prototype.targets_ = null;
325 
326 /**
327  * Whether this clip needs to update properties of all its tweens.
328  * @type {boolean}
329  * @private
330  */
331 createjs.MovieClip.prototype.updated_ = false;
332 
333 /**
334  * Sets the positions of all tweens attached to this clip.
335  * @param {number} startPosition
336  * @private
337  */
338 createjs.MovieClip.prototype.setStartPosition_ = function(startPosition) {
339   /// <param type="number" name="startPosition"/>
340   // Flash somehow generates code that has 'startPosition' properties with their
341   // values 'undefined' (i.e. '{ startPosition: undefined }'). CreateJS treats
342   // these 'undefined' values as 0 and this code emulates it.
343   startPosition = startPosition || 0;
344   createjs.assert(createjs.isNumber(startPosition));
345   // Truncate the frame position to an integral number for consistency with a
346   // frame position used by tweens and change the positions of its tweens only
347   // when the truncated position is not equal to the current position. (Changing
348   // the position of a tween resets the parameters of its target and over-writes
349   // the other parameters being changed by the tween.)
350   startPosition = createjs.truncate(startPosition);
351   if (this.startPosition_ != startPosition) {
352     this.startPosition_ = startPosition;
353     this.updated_ = true;
354   }
355 };
356 
357 /**
358  * Sets whether to loop all tweens attached to this clip.
359  * @param {boolean} loop
360  * @private
361  */
362 createjs.MovieClip.prototype.setLoop_ = function(loop) {
363   /// <param type="boolean" name="loop"/>
364 
365   // Flash somehow generates code that has 'loop' properties with their values
366   // 'undefined' (i.e. '{ loop: undefined }'). CreateJS it treats these
367   // 'undefined' values as true and this code emulates it.
368   if (loop == null) {
369     loop = true;
370   }
371   createjs.assert(createjs.isBoolean(loop));
372   if (this.loop_ != loop) {
373     this.loop_ = loop;
374     this.updated_ = true;
375   }
376 };
377 
378 /**
379  * Sets the play mode of all tweens attached to this clip.
380  * @param {string} value
381  * @param {createjs.TweenTarget} proxy
382  * @private
383  */
384 createjs.MovieClip.prototype.setMode_ = function(value, proxy) {
385   /// <param type="string" name="value"/>
386   /// <param type="createjs.TweenTarget" name="proxy"/>
387   var mode = createjs.MovieClip.getMode_(value);
388   if (this.getPlayMode() != mode) {
389     this.setPlayMode(mode);
390     if (proxy) {
391       proxy.synchronize(this, mode == createjs.TweenTarget.PlayMode.SYNCHED);
392     }
393     this.updated_ = true;
394   }
395 };
396 
397 /**
398  * Moves the play offset to the specified frame number or the beginning of the
399  * specified animation.
400  * @param {boolean} paused
401  * @param {string|number} value
402  * @private
403  */
404 createjs.MovieClip.prototype.goto_ = function(paused, value) {
405   /// <signature>
406   ///   <param type="boolean" name="paused"/>
407   ///   <param type="string" name="label"/>
408   /// </signature>
409   /// <signature>
410   ///   <param type="boolean" name="paused"/>
411   ///   <param type="number" name="frame"/>
412   /// </signature>
413   this.setPaused(paused);
414   var position = createjs.parseFloat(value);
415   if (createjs.isNaN(position)) {
416     position = this.labels_[createjs.getString(value)];
417     if (position == null) {
418       return;
419     }
420   }
421   // Changes the position of all tweens attached to this clip only when its
422   // position is not equal to the current one.
423   if (paused || position != this.getCurrentFrame()) {
424     this.setTweenPosition(position);
425   }
426 };
427 
428 /**
429  * Returns the createjs.Timeline object associated with this object.
430  * @return {createjs.MovieClip.Timeline}
431  * @const
432  */
433 createjs.MovieClip.prototype.getTimeline = function() {
434   /// <returns type="createjs.MovieClip.Timeline"/>
435   return this.timeline_;
436 };
437 
438 /**
439  * Returns the duration of this clip.
440  * @return {number}
441  * @const
442  */
443 createjs.MovieClip.prototype.getDuration = function() {
444   /// <returns type="number"/>
445   return this.duration_;
446 };
447 
448 /**
449  * Returns the current position of this clip.
450  * @return {number}
451  * @const
452  */
453 createjs.MovieClip.prototype.getPosition = function() {
454   /// <returns type="number"/>
455   return this.getCurrentFrame();
456 };
457 
458 /**
459  * Pauses or plays this timeline.
460  * @param {boolean} value
461  * @const
462  */
463 createjs.MovieClip.prototype.setPaused = function(value) {
464   /// <param type="boolean" name="value"/>
465   /// <returns type="createjs.TweenObject"/>
466   var paused = !!value;
467   var time = createjs.Ticker.getRunTime();
468   if (paused) {
469     this.stopTweens(time);
470   } else {
471     this.playTweens(time);
472   }
473 };
474 
475 /**
476  * Starts playing this clip.
477  * @const
478  */
479 createjs.MovieClip.prototype.play = function() {
480   this.setPaused(false);
481 };
482   
483 /**
484  * Stops playing this clip.
485  * @const
486  */
487 createjs.MovieClip.prototype.stop = function() {
488   this.setPaused(true);
489 };
490   
491 /**
492  * Moves the play offset of this clip to the specified position or label and
493  * starts playing it.
494  * @param {string|number} value
495  * @const
496  */
497 createjs.MovieClip.prototype.gotoAndPlay = function(value) {
498   /// <signature>
499   ///   <param type="string" name="label"/>
500   /// </signature>
501   /// <signature>
502   ///   <param type="number" name="frame"/>
503   /// </signature>
504   this.goto_(false, value);
505 };
506   
507 /**
508  * Moves the play offset of this clip to the specified position or label and
509  * stops playing it.
510  * @param {string|number} value
511  * @const
512  */
513 createjs.MovieClip.prototype.gotoAndStop = function(value) {
514   /// <signature>
515   ///   <param type="string" name="label"/>
516   /// </signature>
517   /// <signature>
518   ///   <param type="number" name="frame"/>
519   /// </signature>
520   this.goto_(true, value);
521 };
522   
523 /**
524  * Adds a tween to this clip.
525  * @param {createjs.TweenObject} tween
526  */
527 createjs.MovieClip.prototype.addTween = function(tween) {
528   /// <returns type="createjs.TweenObject" name="tween"/>
529   if (!this.targets_) {
530     this.targets_ = [];
531   }
532   var duration = tween.setProxy(this, this.targets_);
533   if (this.duration_ < duration) {
534     this.duration_ = duration;
535   }
536   var single = this.getPlayMode() == createjs.TweenTarget.PlayMode.SINGLE;
537   tween.setProperties(this.loop_, this.startPosition_, single);
538 };
539 
540 /**
541  * Removes a tween from this clip.
542  * @param {createjs.TweenObject} tween
543  */
544 createjs.MovieClip.prototype.removeTween = function(tween) {
545   /// <returns type="boolean"/>
546   createjs.notImplemented();
547 };
548 
549 /**
550  * Adds a label that can be used with the gotoAndPlay() method.
551  * @param {string} label
552  * @param {number} position
553  */
554 createjs.MovieClip.prototype.addLabel = function(label, position) {
555   /// <param type="string" name="label"/>
556   /// <param type="number" name="position"/>
557   this.labels_[label] = position;
558   this.labelList_ = null;
559 };
560 
561 /**
562  * Sets labels for this timeline.
563  * @param {Object.<string,number>} labels
564  */
565 createjs.MovieClip.prototype.setLabels = function(labels) {
566   /// <param type="Object" name="labels"/>
567   this.labels_ = labels;
568   this.labelList_ = null;
569 };
570 
571 /**
572  * Returns the sorted list of the labels added to this timeline.
573  * @return {Array.<createjs.MovieClip.Label>}
574  */
575 createjs.MovieClip.prototype.getLabels = function() {
576   /// <returns type="Array" elementType="createjs.MovieClip.Label"/>
577   if (!this.labelList_) {
578     var list = [];
579     for (var label in this.labels_) {
580       var position = this.labels_[label];
581       list.push(new createjs.MovieClip.Label(label, position));
582     }
583     list.sort(createjs.MovieClip.Label.sortFunction_);
584     this.labelList_ = list;
585   }
586   return this.labelList_;
587 };
588 
589 /**
590  * Returns the name of the label on or immediately before the current position.
591  * @return {string}
592  */
593 createjs.MovieClip.prototype.getCurrentLabel = function() {
594   /// <returns type="string"/>
595   var labels = this.getLabels();
596   var length = labels.length;
597   var label = labels[0];
598   var position = this.getCurrentFrame();
599   if (length == 0 || position < label['position']) {
600     return '';
601   }
602   for (var i = 1; i < length; ++i) {
603     label = labels[i];
604     if (position < label['position']) {
605       break;
606     }
607   }
608   return label['label'];
609 };
610 
611 /** @override */
612 createjs.MovieClip.prototype.removeAllChildren = function(opt_destroy) {
613   this.resetTweens();
614   this.timeline_ = null;
615   this.labels_ = null;
616   this.labelList_ = null;
617   createjs.MovieClip.superClass_.removeAllChildren.call(this, opt_destroy);
618 };
619 
620 /** @override */
621 createjs.MovieClip.prototype.updateTweens = function(time) {
622   /// <param type="number" name="time"/>
623   // Update the tween properties before updating tweens when another tween
624   // changes the properties of this clip to prevent updating each tween twice.
625   if (this.updated_) {
626     var single = this.getPlayMode() == createjs.TweenTarget.PlayMode.SINGLE;
627     this.setTweenProperties(this.loop_, this.startPosition_, single);
628     this.updated_ = false;
629   }
630   createjs.MovieClip.superClass_.updateTweens.call(this, time);
631   if (this.targets_) {
632     // Add the targets of the tweens (except masks) belonging to this clip. A
633     // clip may have a tween that changes a mask. This clip does not have to
634     // render masks and it filters them out. (Applications may override the
635     // exported 'addChild()' method and this 'addChild()' call should use the
636     // exported one.)
637     var targets = this.targets_;
638     for (var i = 0; i < targets.length; ++i) {
639       if (!targets[i].getOwners()) {
640         this['addChild'](targets[i]);
641       }
642     }
643     this.targets_ = null;
644   }
645 };
646 
647 /** @override */
648 createjs.MovieClip.prototype.getSetters = function() {
649   /// <return type="Object" elementType="createjs.TweenTarget.Setter"/>
650   var MODES = ['independent', 'single', 'synched'];
651   var setters = createjs.MovieClip.superClass_.getSetters.call(this);
652   setters['startPosition'].setPosition(this.startPosition_);
653   setters['loop'].setLoop(this.loop_);
654   setters['mode'].setString(MODES[this.getPlayMode()]);
655   return setters;
656 };
657 
658 // Add setters to allow tweens to change this object.
659 createjs.TweenTarget.Property.addSetters({
660   'startPosition': createjs.MovieClip.prototype.setStartPosition_,
661   'loop': createjs.MovieClip.prototype.setLoop_,
662   'mode': createjs.MovieClip.prototype.setMode_
663 });
664 
665 // Add getters for applications to read internal variables.
666 Object.defineProperties(createjs.MovieClip.prototype, {
667   'currentFrame': {
668     get: createjs.MovieClip.prototype.getPosition
669   },
670   'timeline': {
671     get: createjs.MovieClip.prototype.getTimeline
672   }
673 });
674 
675 // Export the createjs.MovieClip object to the global namespace.
676 createjs.exportObject('createjs.MovieClip', createjs.MovieClip, {
677   // createjs.MovieClip methods.
678   'play': createjs.MovieClip.prototype.play,
679   'stop': createjs.MovieClip.prototype.stop,
680   'gotoAndPlay': createjs.MovieClip.prototype.gotoAndPlay,
681   'gotoAndStop': createjs.MovieClip.prototype.gotoAndStop,
682   'getLabels': createjs.MovieClip.prototype.getLabels,
683   'getCurrentLabel': createjs.MovieClip.prototype.getCurrentLabel
684 });
685