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