1 /** 2 * enchant.js v0.8.3 3 * http://enchantjs.com 4 * 5 * Copyright UEI Corporation 6 * Released under the MIT license. 7 */ 8 9 (function(window, undefined) { 10 11 // ECMA-262 5th edition Functions 12 if (typeof Object.defineProperty !== 'function') { 13 Object.defineProperty = function(obj, prop, desc) { 14 if ('value' in desc) { 15 obj[prop] = desc.value; 16 } 17 if ('get' in desc) { 18 obj.__defineGetter__(prop, desc.get); 19 } 20 if ('set' in desc) { 21 obj.__defineSetter__(prop, desc.set); 22 } 23 return obj; 24 }; 25 } 26 if (typeof Object.defineProperties !== 'function') { 27 Object.defineProperties = function(obj, descs) { 28 for (var prop in descs) { 29 if (descs.hasOwnProperty(prop)) { 30 Object.defineProperty(obj, prop, descs[prop]); 31 } 32 } 33 return obj; 34 }; 35 } 36 if (typeof Object.create !== 'function') { 37 Object.create = function(prototype, descs) { 38 function F() { 39 } 40 41 F.prototype = prototype; 42 var obj = new F(); 43 if (descs != null) { 44 Object.defineProperties(obj, descs); 45 } 46 return obj; 47 }; 48 } 49 if (typeof Object.getPrototypeOf !== 'function') { 50 Object.getPrototypeOf = function(obj) { 51 return obj.__proto__; 52 }; 53 } 54 55 if (typeof Function.prototype.bind !== 'function') { 56 Function.prototype.bind = function(thisObject) { 57 var func = this; 58 var args = Array.prototype.slice.call(arguments, 1); 59 var Nop = function() { 60 }; 61 var bound = function() { 62 var a = args.concat(Array.prototype.slice.call(arguments)); 63 return func.apply( 64 this instanceof Nop ? this : thisObject || window, a); 65 }; 66 Nop.prototype = func.prototype; 67 bound.prototype = new Nop(); 68 return bound; 69 }; 70 } 71 72 window.getTime = (function() { 73 var origin; 74 if (window.performance && window.performance.now) { 75 origin = Date.now(); 76 return function() { 77 return origin + window.performance.now(); 78 }; 79 } else if (window.performance && window.performance.webkitNow) { 80 origin = Date.now(); 81 return function() { 82 return origin + window.performance.webkitNow(); 83 }; 84 } else { 85 return Date.now; 86 } 87 }()); 88 89 // define requestAnimationFrame 90 window.requestAnimationFrame = 91 window.requestAnimationFrame || 92 window.mozRequestAnimationFrame || 93 window.webkitRequestAnimationFrame || 94 window.msRequestAnimationFrame || 95 (function() { 96 var lastTime = window.getTime(); 97 var frame = 1000 / 60; 98 return function(func) { 99 var _id = setTimeout(function() { 100 lastTime = window.getTime(); 101 func(lastTime); 102 }, Math.max(0, lastTime + frame - window.getTime())); 103 return _id; 104 }; 105 }()); 106 107 /** 108 * Export the library classes globally. 109 * 110 * When no arguments are given, all classes defined in enchant.js as well as all classes defined in 111 * plugins will be exported. When more than one argument is given, by default only classes defined 112 * in enchant.js will be exported. When you wish to export plugin classes you must explicitly deliver 113 * the plugin identifiers as arguments. 114 * 115 * @example 116 * enchant(); // All classes will be exported. 117 * enchant(''); // Only classes in enchant.js will be exported. 118 * enchant('ui'); // enchant.js classes and ui.enchant.js classes will be exported. 119 * 120 * @param {...String} [modules] Export module. Multiple designations possible. 121 * @function 122 * @global 123 * @name enchant 124 */ 125 var enchant = function(modules) { 126 if (modules != null) { 127 if (!(modules instanceof Array)) { 128 modules = Array.prototype.slice.call(arguments); 129 } 130 modules = modules.filter(function(module) { 131 return [module].join(); 132 }); 133 } 134 (function include(module, prefix) { 135 var submodules = [], 136 i, len; 137 for (var prop in module) { 138 if (module.hasOwnProperty(prop)) { 139 if (typeof module[prop] === 'function') { 140 window[prop] = module[prop]; 141 } else if (typeof module[prop] === 'object' && module[prop] !== null && Object.getPrototypeOf(module[prop]) === Object.prototype) { 142 if (modules == null) { 143 submodules.push(prop); 144 } else { 145 i = modules.indexOf(prefix + prop); 146 if (i !== -1) { 147 submodules.push(prop); 148 modules.splice(i, 1); 149 } 150 } 151 } 152 } 153 } 154 155 for (i = 0, len = submodules.length; i < len; i++) { 156 include(module[submodules[i]], prefix + submodules[i] + '.'); 157 } 158 }(enchant, '')); 159 160 // issue 185 161 if (enchant.Class.getInheritanceTree(window.Game).length <= enchant.Class.getInheritanceTree(window.Core).length) { 162 window.Game = window.Core; 163 } 164 165 if (modules != null && modules.length) { 166 throw new Error('Cannot load module: ' + modules.join(', ')); 167 } 168 }; 169 170 // export enchant 171 window.enchant = enchant; 172 173 window.addEventListener("message", function(msg, origin) { 174 try { 175 var data = JSON.parse(msg.data); 176 if (data.type === "event") { 177 enchant.Core.instance.dispatchEvent(new enchant.Event(data.value)); 178 } else if (data.type === "debug") { 179 switch (data.value) { 180 case "start": 181 enchant.Core.instance.start(); 182 break; 183 case "pause": 184 enchant.Core.instance.pause(); 185 break; 186 case "resume": 187 enchant.Core.instance.resume(); 188 break; 189 case "tick": 190 enchant.Core.instance._tick(); 191 break; 192 default: 193 break; 194 } 195 } 196 } catch (e) { 197 // ignore 198 } 199 }, false); 200 201 /** 202 * @name enchant.Class 203 * @class 204 * A Class representing a class which supports inheritance. 205 * @param {Function} [superclass] The class from which the 206 * new class will inherit the class definition. 207 * @param {*} [definition] Class definition. 208 * @constructor 209 */ 210 enchant.Class = function(superclass, definition) { 211 return enchant.Class.create(superclass, definition); 212 }; 213 214 /** 215 * Creates a class. 216 * 217 * When defining a class that extends from another class, 218 * the constructor of the other class will be used by default. 219 * Even if you override this constructor, you must still call it 220 * to ensure that the class is initialized correctly. 221 * 222 * @example 223 * // Creates a Ball class. 224 * var Ball = Class.create({ 225 * 226 * // Ball's constructor 227 * initialize: function(radius) { 228 * // ... code ... 229 * }, 230 * 231 * // Defines a fall method that doesn't take any arguments. 232 * fall: function() { 233 * // ... code ... 234 * } 235 * }); 236 * 237 * // Creates a Ball class that extends from "Sprite" 238 * var Ball = Class.create(Sprite); 239 * 240 * // Creates a Ball class that extends from "Sprite" 241 * var Ball = Class.create(Sprite, { 242 * 243 * // Overwrite Sprite's constructor 244 * initialize: function(radius) { 245 * 246 * // Call Sprite's constructor. 247 * Sprite.call(this, radius * 2, radius * 2); 248 * 249 * this.image = core.assets['ball.gif']; 250 * } 251 * }); 252 * 253 * @param {Function} [superclass] The class from which the 254 * new class will inherit the class definition. 255 * @param {*} [definition] Class definition. 256 * @static 257 */ 258 enchant.Class.create = function(superclass, definition) { 259 if (superclass == null && definition) { 260 throw new Error("superclass is undefined (enchant.Class.create)"); 261 } else if (superclass == null) { 262 throw new Error("definition is undefined (enchant.Class.create)"); 263 } 264 265 if (arguments.length === 0) { 266 return enchant.Class.create(Object, definition); 267 } else if (arguments.length === 1 && typeof arguments[0] !== 'function') { 268 return enchant.Class.create(Object, arguments[0]); 269 } 270 271 for (var prop in definition) { 272 if (definition.hasOwnProperty(prop)) { 273 if (typeof definition[prop] === 'object' && definition[prop] !== null && Object.getPrototypeOf(definition[prop]) === Object.prototype) { 274 if (!('enumerable' in definition[prop])) { 275 definition[prop].enumerable = true; 276 } 277 } else { 278 definition[prop] = { value: definition[prop], enumerable: true, writable: true }; 279 } 280 } 281 } 282 var Constructor = function() { 283 if (this instanceof Constructor) { 284 Constructor.prototype.initialize.apply(this, arguments); 285 } else { 286 return new Constructor(); 287 } 288 }; 289 Constructor.prototype = Object.create(superclass.prototype, definition); 290 Constructor.prototype.constructor = Constructor; 291 if (Constructor.prototype.initialize == null) { 292 Constructor.prototype.initialize = function() { 293 superclass.apply(this, arguments); 294 }; 295 } 296 297 var tree = this.getInheritanceTree(superclass); 298 for (var i = 0, l = tree.length; i < l; i++) { 299 if (typeof tree[i]._inherited === 'function') { 300 tree[i]._inherited(Constructor); 301 break; 302 } 303 } 304 305 return Constructor; 306 }; 307 308 /** 309 * Get the inheritance tree of this class. 310 * @param {Function} Constructor 311 * @return {Function[]} Parent's constructor 312 */ 313 enchant.Class.getInheritanceTree = function(Constructor) { 314 var ret = []; 315 var C = Constructor; 316 var proto = C.prototype; 317 while (C !== Object) { 318 ret.push(C); 319 proto = Object.getPrototypeOf(proto); 320 C = proto.constructor; 321 } 322 return ret; 323 }; 324 325 /** 326 * @namespace 327 * enchant.js environment variables. 328 * Execution settings can be changed by modifying these before calling new Core(). 329 */ 330 enchant.ENV = { 331 /** 332 * Version of enchant.js 333 * @type String 334 */ 335 VERSION: '0.8.3', 336 /** 337 * Identifier of the current browser. 338 * @type String 339 */ 340 BROWSER: (function(ua) { 341 if (/Eagle/.test(ua)) { 342 return 'eagle'; 343 } else if (/Opera/.test(ua)) { 344 return 'opera'; 345 } else if (/MSIE|Trident/.test(ua)) { 346 return 'ie'; 347 } else if (/Chrome/.test(ua)) { 348 return 'chrome'; 349 } else if (/(?:Macintosh|Windows).*AppleWebKit/.test(ua)) { 350 return 'safari'; 351 } else if (/(?:iPhone|iPad|iPod).*AppleWebKit/.test(ua)) { 352 return 'mobilesafari'; 353 } else if (/Firefox/.test(ua)) { 354 return 'firefox'; 355 } else if (/Android/.test(ua)) { 356 return 'android'; 357 } else { 358 return ''; 359 } 360 }(navigator.userAgent)), 361 /** 362 * The CSS vendor prefix of the current browser. 363 * @type String 364 */ 365 VENDOR_PREFIX: (function() { 366 var ua = navigator.userAgent; 367 if (ua.indexOf('Opera') !== -1) { 368 return 'O'; 369 } else if (/MSIE|Trident/.test(ua)) { 370 return 'ms'; 371 } else if (ua.indexOf('WebKit') !== -1) { 372 return 'webkit'; 373 } else if (navigator.product === 'Gecko') { 374 return 'Moz'; 375 } else { 376 return ''; 377 } 378 }()), 379 /** 380 * Determines if the current browser supports touch. 381 * True, if touch is enabled. 382 * @type Boolean 383 */ 384 TOUCH_ENABLED: (function() { 385 var div = document.createElement('div'); 386 div.setAttribute('ontouchstart', 'return'); 387 return typeof div.ontouchstart === 'function'; 388 }()), 389 /** 390 * Determines if the current browser is an iPhone with a retina display. 391 * True, if this display is a retina display. 392 * @type Boolean 393 */ 394 RETINA_DISPLAY: (function() { 395 if (navigator.userAgent.indexOf('iPhone') !== -1 && window.devicePixelRatio === 2) { 396 var viewport = document.querySelector('meta[name="viewport"]'); 397 if (viewport == null) { 398 viewport = document.createElement('meta'); 399 document.head.appendChild(viewport); 400 } 401 viewport.setAttribute('content', 'width=640'); 402 return true; 403 } else { 404 return false; 405 } 406 }()), 407 /** 408 * Determines if for current browser Flash should be used to play 409 * sound instead of the native audio class. 410 * True, if flash should be used. 411 * @type Boolean 412 */ 413 USE_FLASH_SOUND: (function() { 414 var ua = navigator.userAgent; 415 var vendor = navigator.vendor || ""; 416 // non-local access, not on mobile mobile device, not on safari 417 return (location.href.indexOf('http') === 0 && ua.indexOf('Mobile') === -1 && vendor.indexOf('Apple') !== -1); 418 }()), 419 /** 420 * If click/touch event occure for these tags the setPreventDefault() method will not be called. 421 * @type String[] 422 */ 423 USE_DEFAULT_EVENT_TAGS: ['input', 'textarea', 'select', 'area'], 424 /** 425 * Method names of CanvasRenderingContext2D that will be defined as Surface method. 426 * @type String[] 427 */ 428 CANVAS_DRAWING_METHODS: [ 429 'putImageData', 'drawImage', 'drawFocusRing', 'fill', 'stroke', 430 'clearRect', 'fillRect', 'strokeRect', 'fillText', 'strokeText' 431 ], 432 /** 433 * Keybind Table. 434 * You can use 'left', 'up', 'right', 'down' for preset event. 435 * @example 436 * enchant.ENV.KEY_BIND_TABLE = { 437 * 37: 'left', 438 * 38: 'up', 439 * 39: 'right', 440 * 40: 'down', 441 * 32: 'a', //-> use 'space' key as 'a button' 442 * }; 443 * @type Object 444 */ 445 KEY_BIND_TABLE: { 446 37: 'left', 447 38: 'up', 448 39: 'right', 449 40: 'down' 450 }, 451 /** 452 * If keydown event occure for these keycodes the setPreventDefault() method will be called. 453 * @type Number[] 454 */ 455 PREVENT_DEFAULT_KEY_CODES: [37, 38, 39, 40], 456 /** 457 * Determines if Sound is enabled on Mobile Safari. 458 * @type Boolean 459 */ 460 SOUND_ENABLED_ON_MOBILE_SAFARI: true, 461 /** 462 * Determines if "touch to start" scene is enabled. 463 * It is necessary on Mobile Safari because WebAudio Sound is 464 * muted by browser until play any sound in touch event handler. 465 * If set it to false, you should control this behavior manually. 466 * @type Boolean 467 */ 468 USE_TOUCH_TO_START_SCENE: true, 469 /** 470 * Determines if WebAudioAPI is enabled. (true: use WebAudioAPI instead of Audio element if possible) 471 * @type Boolean 472 */ 473 USE_WEBAUDIO: (function() { 474 return location.protocol !== 'file:'; 475 }()), 476 /** 477 * Determines if animation feature is enabled. (true: Timeline instance will be generated in new Node) 478 * @type Boolean 479 */ 480 USE_ANIMATION: true, 481 /** 482 * Specifies range of the touch detection. 483 * The detection area will be (COLOR_DETECTION_LEVEL * 2 + 1)px square. 484 * @type Boolean 485 */ 486 COLOR_DETECTION_LEVEL: 2 487 }; 488 489 /** 490 * @scope enchant.Event.prototype 491 */ 492 enchant.Event = enchant.Class.create({ 493 /** 494 * @name enchant.Event 495 * @class 496 * A class for an independent implementation of events similar to DOM Events. 497 * Does not include phase concepts. 498 * @param {String} type Event type. 499 * @constructs 500 */ 501 initialize: function(type) { 502 /** 503 * The type of the event. 504 * @type String 505 */ 506 this.type = type; 507 /** 508 * The target of the event. 509 * @type * 510 */ 511 this.target = null; 512 /** 513 * The x-coordinate of the event's occurrence. 514 * @type Number 515 */ 516 this.x = 0; 517 /** 518 * The y-coordinate of the event's occurrence. 519 * @type Number 520 */ 521 this.y = 0; 522 /** 523 * The x-coordinate of the event's occurrence relative to the object 524 * which issued the event. 525 * @type Number 526 */ 527 this.localX = 0; 528 /** 529 * The y-coordinate of the event's occurrence relative to the object 530 * which issued the event. 531 * @type Number 532 */ 533 this.localY = 0; 534 }, 535 _initPosition: function(pageX, pageY) { 536 var core = enchant.Core.instance; 537 this.x = this.localX = (pageX - core._pageX) / core.scale; 538 this.y = this.localY = (pageY - core._pageY) / core.scale; 539 } 540 }); 541 542 /** 543 * An event dispatched once the core has finished loading. 544 * 545 * When preloading images, it is necessary to wait until preloading is complete 546 * before starting the game. 547 * Issued by: {@link enchant.Core} 548 * 549 * @example 550 * var core = new Core(320, 320); 551 * core.preload('player.gif'); 552 * core.onload = function() { 553 * ... // Describes initial core processing 554 * }; 555 * core.start(); 556 * @type String 557 */ 558 enchant.Event.LOAD = 'load'; 559 560 /** 561 * An event dispatched when an error occurs. 562 * Issued by: {@link enchant.Core}, {@link enchant.Surface}, {@link enchant.WebAudioSound}, {@link enchant.DOMSound} 563 */ 564 enchant.Event.ERROR = 'error'; 565 566 /** 567 * An event dispatched when the display size is changed. 568 * Issued by: {@link enchant.Core}, {@link enchant.Scene} 569 @type String 570 */ 571 enchant.Event.CORE_RESIZE = 'coreresize'; 572 573 /** 574 * An event dispatched while the core is loading. 575 * Dispatched each time an image is preloaded. 576 * Issued by: {@link enchant.LoadingScene} 577 * @type String 578 */ 579 enchant.Event.PROGRESS = 'progress'; 580 581 /** 582 * An event which is occurring when a new frame is beeing processed. 583 * Issued object: {@link enchant.Core}, {@link enchant.Node} 584 * @type String 585 */ 586 enchant.Event.ENTER_FRAME = 'enterframe'; 587 588 /** 589 * An event dispatched at the end of processing a new frame. 590 * Issued by: {@link enchant.Core}, {@link enchant.Node} 591 * @type String 592 */ 593 enchant.Event.EXIT_FRAME = 'exitframe'; 594 595 /** 596 * An event dispatched when a Scene begins. 597 * Issued by: {@link enchant.Scene} 598 * @type String 599 */ 600 enchant.Event.ENTER = 'enter'; 601 602 /** 603 * An event dispatched when a Scene ends. 604 * Issued by: {@link enchant.Scene} 605 * @type String 606 */ 607 enchant.Event.EXIT = 'exit'; 608 609 /** 610 * An event dispatched when a Child is added to a Node. 611 * Issued by: {@link enchant.Group}, {@link enchant.Scene} 612 * @type String 613 */ 614 enchant.Event.CHILD_ADDED = 'childadded'; 615 616 /** 617 * An event dispatched when a Node is added to a Group. 618 * Issued by: {@link enchant.Node} 619 * @type String 620 */ 621 enchant.Event.ADDED = 'added'; 622 623 /** 624 * An event dispatched when a Node is added to a Scene. 625 * Issued by: {@link enchant.Node} 626 * @type String 627 */ 628 enchant.Event.ADDED_TO_SCENE = 'addedtoscene'; 629 630 /** 631 * An event dispatched when a Child is removed from a Node. 632 * Issued by: {@link enchant.Group}, {@link enchant.Scene} 633 * @type String 634 * @type String 635 */ 636 enchant.Event.CHILD_REMOVED = 'childremoved'; 637 638 /** 639 * An event dispatched when a Node is deleted from a Group. 640 * Issued by: {@link enchant.Node} 641 * @type String 642 */ 643 enchant.Event.REMOVED = 'removed'; 644 645 /** 646 * An event dispatched when a Node is deleted from a Scene. 647 * Issued by: {@link enchant.Node} 648 * @type String 649 */ 650 enchant.Event.REMOVED_FROM_SCENE = 'removedfromscene'; 651 652 /** 653 * An event dispatched when a touch event intersecting a Node begins. 654 * A mouse event counts as a touch event. Issued by: {@link enchant.Node} 655 * @type String 656 */ 657 enchant.Event.TOUCH_START = 'touchstart'; 658 659 /** 660 * An event dispatched when a touch event intersecting the Node has been moved. 661 * A mouse event counts as a touch event. Issued by: {@link enchant.Node} 662 * @type String 663 */ 664 enchant.Event.TOUCH_MOVE = 'touchmove'; 665 666 /** 667 * An event dispatched when a touch event intersecting the Node ends. 668 * A mouse event counts as a touch event. Issued by: {@link enchant.Node} 669 * @type String 670 */ 671 enchant.Event.TOUCH_END = 'touchend'; 672 673 /** 674 * An event dispatched when an Entity is rendered. 675 * Issued by: {@link enchant.Entity} 676 * @type String 677 */ 678 enchant.Event.RENDER = 'render'; 679 680 /** 681 * An event dispatched when a button is pressed. 682 * Issued by: {@link enchant.Core}, {@link enchant.Scene} 683 * @type String 684 */ 685 enchant.Event.INPUT_START = 'inputstart'; 686 687 /** 688 * An event dispatched when button inputs change. 689 * Issued by: {@link enchant.Core}, {@link enchant.Scene} 690 * @type String 691 */ 692 enchant.Event.INPUT_CHANGE = 'inputchange'; 693 694 /** 695 * An event dispatched when button input ends. 696 * Issued by: {@link enchant.Core}, {@link enchant.Scene} 697 * @type String 698 */ 699 enchant.Event.INPUT_END = 'inputend'; 700 701 /** 702 * An internal event which is occurring when a input changes. 703 * Issued object: {@link enchant.InputSource} 704 * @type String 705 */ 706 enchant.Event.INPUT_STATE_CHANGED = 'inputstatechanged'; 707 708 /** 709 * An event dispatched when the 'left' button is pressed. 710 * Issued by: {@link enchant.Core}, {@link enchant.Scene} 711 * @type String 712 */ 713 enchant.Event.LEFT_BUTTON_DOWN = 'leftbuttondown'; 714 715 /** 716 * An event dispatched when the 'left' button is released. 717 * Issued by: {@link enchant.Core}, {@link enchant.Scene} 718 * @type String 719 */ 720 enchant.Event.LEFT_BUTTON_UP = 'leftbuttonup'; 721 722 /** 723 * An event dispatched when the 'right' button is pressed. 724 * Issued by: {@link enchant.Core}, {@link enchant.Scene} 725 * @type String 726 */ 727 enchant.Event.RIGHT_BUTTON_DOWN = 'rightbuttondown'; 728 729 /** 730 * An event dispatched when the 'right' button is released. 731 * Issued by: {@link enchant.Core}, {@link enchant.Scene} 732 * @type String 733 */ 734 enchant.Event.RIGHT_BUTTON_UP = 'rightbuttonup'; 735 736 /** 737 * An event dispatched when the 'up' button is pressed. 738 * Issued by: {@link enchant.Core}, {@link enchant.Scene} 739 * @type String 740 */ 741 enchant.Event.UP_BUTTON_DOWN = 'upbuttondown'; 742 743 /** 744 * An event dispatched when the 'up' button is released. 745 * Issued by: {@link enchant.Core}, {@link enchant.Scene} 746 * @type String 747 */ 748 enchant.Event.UP_BUTTON_UP = 'upbuttonup'; 749 750 /** 751 * An event dispatched when the 'down' button is pressed. 752 * Issued by: {@link enchant.Core}, {@link enchant.Scene} 753 * @type String 754 */ 755 enchant.Event.DOWN_BUTTON_DOWN = 'downbuttondown'; 756 757 /** 758 * An event dispatched when the 'down' button is released. 759 * Issued by: {@link enchant.Core}, {@link enchant.Scene} 760 * @type String 761 */ 762 enchant.Event.DOWN_BUTTON_UP = 'downbuttonup'; 763 764 /** 765 * An event dispatched when the 'a' button is pressed. 766 * Issued by: {@link enchant.Core}, {@link enchant.Scene} 767 * @type String 768 */ 769 enchant.Event.A_BUTTON_DOWN = 'abuttondown'; 770 771 /** 772 * An event dispatched when the 'a' button is released. 773 * Issued by: {@link enchant.Core}, {@link enchant.Scene} 774 * @type String 775 */ 776 enchant.Event.A_BUTTON_UP = 'abuttonup'; 777 778 /** 779 * An event dispatched when the 'b' button is pressed. 780 * Issued by: {@link enchant.Core}, {@link enchant.Scene} 781 * @type String 782 */ 783 enchant.Event.B_BUTTON_DOWN = 'bbuttondown'; 784 785 /** 786 * An event dispatched when the 'b' button is released. 787 * Issued by: {@link enchant.Core}, {@link enchant.Scene} 788 * @type String 789 */ 790 enchant.Event.B_BUTTON_UP = 'bbuttonup'; 791 792 /** 793 * An event dispatched when an Action is added to a Timeline. 794 * When looped, an Action is removed from the Timeline and added back into it. 795 * @type String 796 */ 797 enchant.Event.ADDED_TO_TIMELINE = "addedtotimeline"; 798 799 /** 800 * An event dispatched when an Action is removed from a Timeline. 801 * When looped, an Action is removed from the timeline and added back into it. 802 * @type String 803 */ 804 enchant.Event.REMOVED_FROM_TIMELINE = "removedfromtimeline"; 805 806 /** 807 * An event dispatched when an Action begins. 808 * @type String 809 */ 810 enchant.Event.ACTION_START = "actionstart"; 811 812 /** 813 * An event dispatched when an Action finishes. 814 * @type String 815 */ 816 enchant.Event.ACTION_END = "actionend"; 817 818 /** 819 * An event dispatched when an Action has gone through one frame. 820 * @type String 821 */ 822 enchant.Event.ACTION_TICK = "actiontick"; 823 824 /** 825 * An event dispatched to the Timeline when an Action is added. 826 * @type String 827 */ 828 enchant.Event.ACTION_ADDED = "actionadded"; 829 830 /** 831 * An event dispatched to the Timeline when an Action is removed. 832 * @type String 833 */ 834 enchant.Event.ACTION_REMOVED = "actionremoved"; 835 836 /** 837 * An event dispatched when an animation finishes, meaning null element was encountered 838 * Issued by: {@link enchant.Sprite} 839 * @type String 840 */ 841 enchant.Event.ANIMATION_END = "animationend"; 842 843 /** 844 * @scope enchant.EventTarget.prototype 845 */ 846 enchant.EventTarget = enchant.Class.create({ 847 /** 848 * @name enchant.EventTarget 849 * @class 850 * A class for implementation of events similar to DOM Events. 851 * However, it does not include the concept of phases. 852 * @constructs 853 */ 854 initialize: function() { 855 this._listeners = {}; 856 }, 857 /** 858 * Add a new event listener which will be executed when the event 859 * is dispatched. 860 * @param {String} type Type of the events. 861 * @param {Function(e:enchant.Event)} listener Event listener to be added. 862 */ 863 addEventListener: function(type, listener) { 864 var listeners = this._listeners[type]; 865 if (listeners == null) { 866 this._listeners[type] = [listener]; 867 } else if (listeners.indexOf(listener) === -1) { 868 listeners.unshift(listener); 869 870 } 871 }, 872 /** 873 * Synonym for addEventListener. 874 * @param {String} type Type of the events. 875 * @param {Function(e:enchant.Event)} listener Event listener to be added. 876 * @see enchant.EventTarget#addEventListener 877 */ 878 on: function() { 879 this.addEventListener.apply(this, arguments); 880 }, 881 /** 882 * Delete an event listener. 883 * @param {String} [type] Type of the events. 884 * @param {Function(e:enchant.Event)} listener Event listener to be deleted. 885 */ 886 removeEventListener: function(type, listener) { 887 var listeners = this._listeners[type]; 888 if (listeners != null) { 889 var i = listeners.indexOf(listener); 890 if (i !== -1) { 891 listeners.splice(i, 1); 892 } 893 } 894 }, 895 /** 896 * Clear all defined event listeners of a given type. 897 * If no type is given, all listeners will be removed. 898 * @param {String} type Type of the events. 899 */ 900 clearEventListener: function(type) { 901 if (type != null) { 902 delete this._listeners[type]; 903 } else { 904 this._listeners = {}; 905 } 906 }, 907 /** 908 * Issue an event. 909 * @param {enchant.Event} e Event to be issued. 910 */ 911 dispatchEvent: function(e) { 912 e.target = this; 913 e.localX = e.x - this._offsetX; 914 e.localY = e.y - this._offsetY; 915 if (this['on' + e.type] != null){ 916 this['on' + e.type](e); 917 } 918 var listeners = this._listeners[e.type]; 919 if (listeners != null) { 920 listeners = listeners.slice(); 921 for (var i = 0, len = listeners.length; i < len; i++) { 922 listeners[i].call(this, e); 923 } 924 } 925 } 926 }); 927 928 (function() { 929 var core; 930 /** 931 * @scope enchant.Core.prototype 932 */ 933 enchant.Core = enchant.Class.create(enchant.EventTarget, { 934 /** 935 * @name enchant.Core 936 * @class 937 * A class for controlling the core’s main loop and scenes. 938 * 939 * There can be only one instance at a time. When the 940 * constructor is executed while an instance exists, the 941 * existing instance will be overwritten. The existing instance 942 * can be accessed from {@link enchant.Core.instance}. 943 * 944 * @param {Number} [width=320] The width of the core viewport. 945 * @param {Number} [height=320] The height of the core viewport. 946 * @constructs 947 * @extends enchant.EventTarget 948 */ 949 initialize: function(width, height) { 950 if (window.document.body === null) { 951 // @TODO postpone initialization after window.onload 952 throw new Error("document.body is null. Please excute 'new Core()' in window.onload."); 953 } 954 955 enchant.EventTarget.call(this); 956 var initial = true; 957 if (core) { 958 initial = false; 959 core.stop(); 960 } 961 core = enchant.Core.instance = this; 962 963 this._calledTime = 0; 964 this._mousedownID = 0; 965 this._surfaceID = 0; 966 this._soundID = 0; 967 968 this._scenes = []; 969 970 width = width || 320; 971 height = height || 320; 972 973 var stage = document.getElementById('enchant-stage'); 974 var scale, sWidth, sHeight; 975 if (!stage) { 976 stage = document.createElement('div'); 977 stage.id = 'enchant-stage'; 978 stage.style.position = 'absolute'; 979 980 if (document.body.firstChild) { 981 document.body.insertBefore(stage, document.body.firstChild); 982 } else { 983 document.body.appendChild(stage); 984 } 985 scale = Math.min( 986 window.innerWidth / width, 987 window.innerHeight / height 988 ); 989 this._pageX = stage.getBoundingClientRect().left; 990 this._pageY = stage.getBoundingClientRect().top; 991 } else { 992 var style = window.getComputedStyle(stage); 993 sWidth = parseInt(style.width, 10); 994 sHeight = parseInt(style.height, 10); 995 if (sWidth && sHeight) { 996 scale = Math.min( 997 sWidth / width, 998 sHeight / height 999 ); 1000 } else { 1001 scale = 1; 1002 } 1003 while (stage.firstChild) { 1004 stage.removeChild(stage.firstChild); 1005 } 1006 stage.style.position = 'relative'; 1007 1008 var bounding = stage.getBoundingClientRect(); 1009 this._pageX = Math.round(window.scrollX || window.pageXOffset + bounding.left); 1010 this._pageY = Math.round(window.scrollY || window.pageYOffset + bounding.top); 1011 } 1012 stage.style.fontSize = '12px'; 1013 stage.style.webkitTextSizeAdjust = 'none'; 1014 stage.style.webkitTapHighlightColor = 'rgba(0, 0, 0, 0)'; 1015 this._element = stage; 1016 1017 this.addEventListener('coreresize', this._oncoreresize); 1018 1019 this._width = width; 1020 this._height = height; 1021 this.scale = scale; 1022 1023 /** 1024 * The frame rate of the core. 1025 * @type Number 1026 */ 1027 this.fps = 30; 1028 /** 1029 * The number of frames processed since the core was started. 1030 * @type Number 1031 */ 1032 this.frame = 0; 1033 /** 1034 * Indicates whether or not the core can be executed. 1035 * @type Boolean 1036 */ 1037 this.ready = false; 1038 /** 1039 * Indicates whether or not the core is currently running. 1040 * @type Boolean 1041 */ 1042 this.running = false; 1043 /** 1044 * Object which stores loaded assets using their paths as keys. 1045 * @type Object 1046 */ 1047 this.assets = {}; 1048 var assets = this._assets = []; 1049 (function detectAssets(module) { 1050 if (module.assets) { 1051 enchant.Core.instance.preload(module.assets); 1052 } 1053 for (var prop in module) { 1054 if (module.hasOwnProperty(prop)) { 1055 if (typeof module[prop] === 'object' && module[prop] !== null && Object.getPrototypeOf(module[prop]) === Object.prototype) { 1056 detectAssets(module[prop]); 1057 } 1058 } 1059 } 1060 }(enchant)); 1061 1062 /** 1063 * The Scene which is currently displayed. This Scene is on top of the Scene stack. 1064 * @type enchant.Scene 1065 */ 1066 this.currentScene = null; 1067 /** 1068 * The root Scene. The Scene at the bottom of the Scene stack. 1069 * @type enchant.Scene 1070 */ 1071 this.rootScene = new enchant.Scene(); 1072 this.pushScene(this.rootScene); 1073 /** 1074 * The Scene to be displayed during loading. 1075 * @type enchant.Scene 1076 */ 1077 this.loadingScene = new enchant.LoadingScene(); 1078 1079 /** 1080 [/lang:ja] 1081 * Indicates whether or not {@link enchant.Core#start} has been called. 1082 [/lang] 1083 * @type Boolean 1084 * @private 1085 */ 1086 this._activated = false; 1087 1088 this._offsetX = 0; 1089 this._offsetY = 0; 1090 1091 /** 1092 * Object that saves the current input state for the core. 1093 * @type Object 1094 */ 1095 this.input = {}; 1096 1097 this.keyboardInputManager = new enchant.KeyboardInputManager(window.document, this.input); 1098 this.keyboardInputManager.addBroadcastTarget(this); 1099 this._keybind = this.keyboardInputManager._binds; 1100 1101 if (!enchant.ENV.KEY_BIND_TABLE) { 1102 enchant.ENV.KEY_BIND_TABLE = {}; 1103 } 1104 1105 for (var prop in enchant.ENV.KEY_BIND_TABLE) { 1106 this.keybind(prop, enchant.ENV.KEY_BIND_TABLE[prop]); 1107 } 1108 1109 if (initial) { 1110 stage = enchant.Core.instance._element; 1111 var evt; 1112 document.addEventListener('keydown', function(e) { 1113 core.dispatchEvent(new enchant.Event('keydown')); 1114 if (enchant.ENV.PREVENT_DEFAULT_KEY_CODES.indexOf(e.keyCode) !== -1) { 1115 e.preventDefault(); 1116 e.stopPropagation(); 1117 } 1118 }, true); 1119 1120 if (enchant.ENV.TOUCH_ENABLED) { 1121 stage.addEventListener('touchstart', function(e) { 1122 var tagName = (e.target.tagName).toLowerCase(); 1123 if (enchant.ENV.USE_DEFAULT_EVENT_TAGS.indexOf(tagName) === -1) { 1124 e.preventDefault(); 1125 if (!core.running) { 1126 e.stopPropagation(); 1127 } 1128 } 1129 }, true); 1130 stage.addEventListener('touchmove', function(e) { 1131 var tagName = (e.target.tagName).toLowerCase(); 1132 if (enchant.ENV.USE_DEFAULT_EVENT_TAGS.indexOf(tagName) === -1) { 1133 e.preventDefault(); 1134 if (!core.running) { 1135 e.stopPropagation(); 1136 } 1137 } 1138 }, true); 1139 stage.addEventListener('touchend', function(e) { 1140 var tagName = (e.target.tagName).toLowerCase(); 1141 if (enchant.ENV.USE_DEFAULT_EVENT_TAGS.indexOf(tagName) === -1) { 1142 e.preventDefault(); 1143 if (!core.running) { 1144 e.stopPropagation(); 1145 } 1146 } 1147 }, true); 1148 } 1149 stage.addEventListener('mousedown', function(e) { 1150 var tagName = (e.target.tagName).toLowerCase(); 1151 if (enchant.ENV.USE_DEFAULT_EVENT_TAGS.indexOf(tagName) === -1) { 1152 e.preventDefault(); 1153 core._mousedownID++; 1154 if (!core.running) { 1155 e.stopPropagation(); 1156 } 1157 } 1158 }, true); 1159 stage.addEventListener('mousemove', function(e) { 1160 var tagName = (e.target.tagName).toLowerCase(); 1161 if (enchant.ENV.USE_DEFAULT_EVENT_TAGS.indexOf(tagName) === -1) { 1162 e.preventDefault(); 1163 if (!core.running) { 1164 e.stopPropagation(); 1165 } 1166 } 1167 }, true); 1168 stage.addEventListener('mouseup', function(e) { 1169 var tagName = (e.target.tagName).toLowerCase(); 1170 if (enchant.ENV.USE_DEFAULT_EVENT_TAGS.indexOf(tagName) === -1) { 1171 e.preventDefault(); 1172 if (!core.running) { 1173 e.stopPropagation(); 1174 } 1175 } 1176 }, true); 1177 core._touchEventTarget = {}; 1178 if (enchant.ENV.TOUCH_ENABLED) { 1179 stage.addEventListener('touchstart', function(e) { 1180 var core = enchant.Core.instance; 1181 var evt = new enchant.Event(enchant.Event.TOUCH_START); 1182 var touches = e.changedTouches; 1183 var touch, target; 1184 for (var i = 0, l = touches.length; i < l; i++) { 1185 touch = touches[i]; 1186 evt._initPosition(touch.pageX, touch.pageY); 1187 target = core.currentScene._determineEventTarget(evt); 1188 core._touchEventTarget[touch.identifier] = target; 1189 target.dispatchEvent(evt); 1190 } 1191 }, false); 1192 stage.addEventListener('touchmove', function(e) { 1193 var core = enchant.Core.instance; 1194 var evt = new enchant.Event(enchant.Event.TOUCH_MOVE); 1195 var touches = e.changedTouches; 1196 var touch, target; 1197 for (var i = 0, l = touches.length; i < l; i++) { 1198 touch = touches[i]; 1199 target = core._touchEventTarget[touch.identifier]; 1200 if (target) { 1201 evt._initPosition(touch.pageX, touch.pageY); 1202 target.dispatchEvent(evt); 1203 } 1204 } 1205 }, false); 1206 stage.addEventListener('touchend', function(e) { 1207 var core = enchant.Core.instance; 1208 var evt = new enchant.Event(enchant.Event.TOUCH_END); 1209 var touches = e.changedTouches; 1210 var touch, target; 1211 for (var i = 0, l = touches.length; i < l; i++) { 1212 touch = touches[i]; 1213 target = core._touchEventTarget[touch.identifier]; 1214 if (target) { 1215 evt._initPosition(touch.pageX, touch.pageY); 1216 target.dispatchEvent(evt); 1217 delete core._touchEventTarget[touch.identifier]; 1218 } 1219 } 1220 }, false); 1221 } 1222 stage.addEventListener('mousedown', function(e) { 1223 var core = enchant.Core.instance; 1224 var evt = new enchant.Event(enchant.Event.TOUCH_START); 1225 evt._initPosition(e.pageX, e.pageY); 1226 var target = core.currentScene._determineEventTarget(evt); 1227 core._touchEventTarget[core._mousedownID] = target; 1228 target.dispatchEvent(evt); 1229 }, false); 1230 stage.addEventListener('mousemove', function(e) { 1231 var core = enchant.Core.instance; 1232 var evt = new enchant.Event(enchant.Event.TOUCH_MOVE); 1233 evt._initPosition(e.pageX, e.pageY); 1234 var target = core._touchEventTarget[core._mousedownID]; 1235 if (target) { 1236 target.dispatchEvent(evt); 1237 } 1238 }, false); 1239 stage.addEventListener('mouseup', function(e) { 1240 var core = enchant.Core.instance; 1241 var evt = new enchant.Event(enchant.Event.TOUCH_END); 1242 evt._initPosition(e.pageX, e.pageY); 1243 var target = core._touchEventTarget[core._mousedownID]; 1244 if (target) { 1245 target.dispatchEvent(evt); 1246 } 1247 delete core._touchEventTarget[core._mousedownID]; 1248 }, false); 1249 } 1250 }, 1251 /** 1252 * The width of the core screen. 1253 * @type Number 1254 */ 1255 width: { 1256 get: function() { 1257 return this._width; 1258 }, 1259 set: function(w) { 1260 this._width = w; 1261 this._dispatchCoreResizeEvent(); 1262 } 1263 }, 1264 /** 1265 * The height of the core screen. 1266 * @type Number 1267 */ 1268 height: { 1269 get: function() { 1270 return this._height; 1271 }, 1272 set: function(h) { 1273 this._height = h; 1274 this._dispatchCoreResizeEvent(); 1275 } 1276 }, 1277 /** 1278 * The scaling of the core rendering. 1279 * @type Number 1280 */ 1281 scale: { 1282 get: function() { 1283 return this._scale; 1284 }, 1285 set: function(s) { 1286 this._scale = s; 1287 this._dispatchCoreResizeEvent(); 1288 } 1289 }, 1290 _dispatchCoreResizeEvent: function() { 1291 var e = new enchant.Event('coreresize'); 1292 e.width = this._width; 1293 e.height = this._height; 1294 e.scale = this._scale; 1295 this.dispatchEvent(e); 1296 }, 1297 _oncoreresize: function(e) { 1298 this._element.style.width = Math.floor(this._width * this._scale) + 'px'; 1299 this._element.style.height = Math.floor(this._height * this._scale) + 'px'; 1300 var scene; 1301 for (var i = 0, l = this._scenes.length; i < l; i++) { 1302 scene = this._scenes[i]; 1303 scene.dispatchEvent(e); 1304 } 1305 }, 1306 /** 1307 * File preloader. 1308 * 1309 * Loads the files specified in the parameters when 1310 * {@link enchant.Core#start} is called. 1311 * When all files are loaded, a {@link enchant.Event.LOAD} 1312 * event is dispatched from the Core object. Depending on the 1313 * type of each file, different objects will be created and 1314 * stored in {@link enchant.Core#assets} Variable. 1315 * 1316 * When an image file is loaded, a {@link enchant.Surface} is 1317 * created. If a sound file is loaded, an {@link enchant.Sound} 1318 * object is created. Any other file type will be accessible 1319 * as a string. 1320 * 1321 * In addition, because this Surface object is created with 1322 * {@link enchant.Surface.load}, it is not possible to 1323 * manipulate the image directly. 1324 * Refer to the {@link enchant.Surface.load} documentation. 1325 * 1326 * @example 1327 * core.preload('player.gif'); 1328 * core.onload = function() { 1329 * var sprite = new Sprite(32, 32); 1330 * sprite.image = core.assets['player.gif']; // Access via path 1331 * ... 1332 * }; 1333 * core.start(); 1334 * 1335 * @param {...String|String[]} assets Path of images to be preloaded. 1336 * Multiple settings possible. 1337 * @return {enchant.Core} this 1338 */ 1339 preload: function(assets) { 1340 var a; 1341 if (!(assets instanceof Array)) { 1342 if (typeof assets === 'object') { 1343 a = []; 1344 for (var name in assets) { 1345 if (assets.hasOwnProperty(name)) { 1346 a.push([ assets[name], name ]); 1347 } 1348 } 1349 assets = a; 1350 } else { 1351 assets = Array.prototype.slice.call(arguments); 1352 } 1353 } 1354 Array.prototype.push.apply(this._assets, assets); 1355 return this; 1356 }, 1357 /** 1358 * Loads a file. 1359 * 1360 * @param {String} src File path of the resource to be loaded. 1361 * @param {String} [alias] Name you want to designate for the resource to be loaded. 1362 * @param {Function} [callback] Function to be called if the file loads successfully. 1363 * @param {Function} [onerror] Function to be called if the file fails to load. 1364 * @return {enchant.Deferred} 1365 */ 1366 load: function(src, alias, callback, onerror) { 1367 var assetName; 1368 if (typeof arguments[1] === 'string') { 1369 assetName = alias; 1370 callback = callback || function() {}; 1371 onerror = onerror || function() {}; 1372 } else { 1373 assetName = src; 1374 var tempCallback = callback; 1375 callback = arguments[1] || function() {}; 1376 onerror = tempCallback || function() {}; 1377 } 1378 1379 var ext = enchant.Core.findExt(src); 1380 1381 return enchant.Deferred.next(function() { 1382 var d = new enchant.Deferred(); 1383 var _callback = function(e) { 1384 d.call(e); 1385 callback.call(this, e); 1386 }; 1387 var _onerror = function(e) { 1388 d.fail(e); 1389 onerror.call(this, e); 1390 }; 1391 if (enchant.Core._loadFuncs[ext]) { 1392 enchant.Core.instance.assets[assetName] = enchant.Core._loadFuncs[ext](src, ext, _callback, _onerror); 1393 } else { 1394 var req = new XMLHttpRequest(); 1395 req.open('GET', src, true); 1396 req.onreadystatechange = function() { 1397 if (req.readyState === 4) { 1398 if (req.status !== 200 && req.status !== 0) { 1399 // throw new Error(req.status + ': ' + 'Cannot load an asset: ' + src); 1400 var e = new enchant.Event('error'); 1401 e.message = req.status + ': ' + 'Cannot load an asset: ' + src; 1402 _onerror.call(enchant.Core.instance, e); 1403 } 1404 1405 var type = req.getResponseHeader('Content-Type') || ''; 1406 if (type.match(/^image/)) { 1407 core.assets[assetName] = enchant.Surface.load(src, _callback, _onerror); 1408 } else if (type.match(/^audio/)) { 1409 core.assets[assetName] = enchant.Sound.load(src, type, _callback, _onerror); 1410 } else { 1411 core.assets[assetName] = req.responseText; 1412 _callback.call(enchant.Core.instance, new enchant.Event('load')); 1413 } 1414 } 1415 }; 1416 req.send(null); 1417 } 1418 return d; 1419 }); 1420 }, 1421 /** 1422 * Start the core. 1423 * 1424 * Sets the framerate of the {@link enchant.Core#currentScene} 1425 * according to the value stored in {@link enchant.core#fps}. If 1426 * there are images to preload, loading will begin and the 1427 * loading screen will be displayed. 1428 * @return {enchant.Deferred} 1429 */ 1430 start: function(deferred) { 1431 var onloadTimeSetter = function() { 1432 this.frame = 0; 1433 this.removeEventListener('load', onloadTimeSetter); 1434 }; 1435 this.addEventListener('load', onloadTimeSetter); 1436 1437 this.currentTime = window.getTime(); 1438 this.running = true; 1439 this.ready = true; 1440 1441 if (!this._activated) { 1442 this._activated = true; 1443 if (enchant.ENV.BROWSER === 'mobilesafari' && 1444 enchant.ENV.USE_WEBAUDIO && 1445 enchant.ENV.USE_TOUCH_TO_START_SCENE) { 1446 var d = new enchant.Deferred(); 1447 var scene = this._createTouchToStartScene(); 1448 scene.addEventListener(enchant.Event.TOUCH_START, function waitTouch() { 1449 this.removeEventListener(enchant.Event.TOUCH_START, waitTouch); 1450 var a = new enchant.WebAudioSound(); 1451 a.buffer = enchant.WebAudioSound.audioContext.createBuffer(1, 1, 48000); 1452 a.play(); 1453 core.removeScene(scene); 1454 core.start(d); 1455 }, false); 1456 core.pushScene(scene); 1457 return d; 1458 } 1459 } 1460 1461 this._requestNextFrame(0); 1462 1463 var ret = this._requestPreload() 1464 .next(function() { 1465 enchant.Core.instance.loadingScene.dispatchEvent(new enchant.Event(enchant.Event.LOAD)); 1466 }); 1467 1468 if (deferred) { 1469 ret.next(function(arg) { 1470 deferred.call(arg); 1471 }) 1472 .error(function(arg) { 1473 deferred.fail(arg); 1474 }); 1475 } 1476 1477 return ret; 1478 }, 1479 _requestPreload: function() { 1480 var o = {}; 1481 var loaded = 0, 1482 len = 0, 1483 loadFunc = function() { 1484 var e = new enchant.Event('progress'); 1485 e.loaded = ++loaded; 1486 e.total = len; 1487 core.loadingScene.dispatchEvent(e); 1488 }; 1489 this._assets 1490 .reverse() 1491 .forEach(function(asset) { 1492 var src, name; 1493 if (asset instanceof Array) { 1494 src = asset[0]; 1495 name = asset[1]; 1496 } else { 1497 src = name = asset; 1498 } 1499 if (!o[name]) { 1500 o[name] = this.load(src, name, loadFunc); 1501 len++; 1502 } 1503 }, this); 1504 1505 this.pushScene(this.loadingScene); 1506 return enchant.Deferred.parallel(o); 1507 }, 1508 _createTouchToStartScene: function() { 1509 var label = new enchant.Label('Touch to Start'), 1510 size = Math.round(core.width / 10), 1511 scene = new enchant.Scene(); 1512 1513 label.color = '#fff'; 1514 label.font = (size - 1) + 'px bold Helvetica,Arial,sans-serif'; 1515 label.textAlign = 'center'; 1516 label.width = core.width; 1517 label.height = label._boundHeight; 1518 label.y = (core.height - label.height) / 2; 1519 1520 scene.backgroundColor = '#000'; 1521 scene.addChild(label); 1522 1523 return scene; 1524 }, 1525 /** 1526 * Start application in debug mode. 1527 * 1528 * Core debug mode can be turned on even if the 1529 * {@link enchant.Core#_debug} flag is already set to true. 1530 * @return {enchant.Deferred} 1531 */ 1532 debug: function() { 1533 this._debug = true; 1534 return this.start(); 1535 }, 1536 actualFps: { 1537 get: function() { 1538 return this._actualFps || this.fps; 1539 } 1540 }, 1541 /** 1542 * Requests the next frame. 1543 * @param {Number} delay Amount of time to delay before calling requestAnimationFrame. 1544 * @private 1545 */ 1546 _requestNextFrame: function(delay) { 1547 if (!this.ready) { 1548 return; 1549 } 1550 if (this.fps >= 60 || delay <= 16) { 1551 this._calledTime = window.getTime(); 1552 window.requestAnimationFrame(this._callTick); 1553 } else { 1554 setTimeout(function() { 1555 var core = enchant.Core.instance; 1556 core._calledTime = window.getTime(); 1557 window.requestAnimationFrame(core._callTick); 1558 }, Math.max(0, delay)); 1559 } 1560 }, 1561 /** 1562 * Calls {@link enchant.Core#_tick}. 1563 * @param {Number} time 1564 * @private 1565 */ 1566 _callTick: function(time) { 1567 enchant.Core.instance._tick(time); 1568 }, 1569 _tick: function(time) { 1570 var e = new enchant.Event('enterframe'); 1571 var now = window.getTime(); 1572 var elapsed = e.elapsed = now - this.currentTime; 1573 this.currentTime = now; 1574 1575 this._actualFps = elapsed > 0 ? (1000 / elapsed) : 0; 1576 1577 var nodes = this.currentScene.childNodes.slice(); 1578 var push = Array.prototype.push; 1579 while (nodes.length) { 1580 var node = nodes.pop(); 1581 node.age++; 1582 node.dispatchEvent(e); 1583 if (node.childNodes) { 1584 push.apply(nodes, node.childNodes); 1585 } 1586 } 1587 1588 this.currentScene.age++; 1589 this.currentScene.dispatchEvent(e); 1590 this.dispatchEvent(e); 1591 1592 this.dispatchEvent(new enchant.Event('exitframe')); 1593 this.frame++; 1594 now = window.getTime(); 1595 1596 this._requestNextFrame(1000 / this.fps - (now - this._calledTime)); 1597 }, 1598 getTime: function() { 1599 return window.getTime(); 1600 }, 1601 /** 1602 * Stops the core. 1603 * 1604 * The frame will not be updated, and player input will not be accepted anymore. 1605 * Core can be restarted using {@link enchant.Core#resume}. 1606 */ 1607 stop: function() { 1608 this.ready = false; 1609 this.running = false; 1610 }, 1611 /** 1612 * Stops the core. 1613 * 1614 * The frame will not be updated, and player input will not be accepted anymore. 1615 * Core can be started again using {@link enchant.Core#resume}. 1616 */ 1617 pause: function() { 1618 this.ready = false; 1619 }, 1620 /** 1621 * Resumes core operations. 1622 */ 1623 resume: function() { 1624 if (this.ready) { 1625 return; 1626 } 1627 this.currentTime = window.getTime(); 1628 this.ready = true; 1629 this.running = true; 1630 this._requestNextFrame(0); 1631 }, 1632 1633 /** 1634 * Switches to a new Scene. 1635 * 1636 * Scenes are controlled using a stack, with the top scene on 1637 * the stack being the one displayed. 1638 * When {@link enchant.Core#pushScene} is executed, the Scene is 1639 * placed top of the stack. Frames will be only updated for the 1640 * Scene which is on the top of the stack. 1641 * 1642 * @param {enchant.Scene} scene The new scene to display. 1643 * @return {enchant.Scene} The new Scene. 1644 */ 1645 pushScene: function(scene) { 1646 this._element.appendChild(scene._element); 1647 if (this.currentScene) { 1648 this.currentScene.dispatchEvent(new enchant.Event('exit')); 1649 } 1650 this.currentScene = scene; 1651 this.currentScene.dispatchEvent(new enchant.Event('enter')); 1652 return this._scenes.push(scene); 1653 }, 1654 /** 1655 * Ends the current Scene and returns to the previous Scene. 1656 * 1657 * Scenes are controlled using a stack, with the top scene on 1658 * the stack being the one displayed. 1659 * When {@link enchant.Core#popScene} is executed, the Scene at 1660 * the top of the stack is removed and returned. 1661 * 1662 * @return {enchant.Scene} Removed Scene. 1663 */ 1664 popScene: function() { 1665 if (this.currentScene === this.rootScene) { 1666 return this.currentScene; 1667 } 1668 this._element.removeChild(this.currentScene._element); 1669 this.currentScene.dispatchEvent(new enchant.Event('exit')); 1670 this.currentScene = this._scenes[this._scenes.length - 2]; 1671 this.currentScene.dispatchEvent(new enchant.Event('enter')); 1672 return this._scenes.pop(); 1673 }, 1674 /** 1675 * Overwrites the current Scene with a new Scene. 1676 * 1677 * Executes {@link enchant.Core#popScene} and {@link enchant.Core#pushScene} 1678 * one after another to replace the current scene with the new scene. 1679 * 1680 * @param {enchant.Scene} scene The new scene with which to replace the current scene. 1681 * @return {enchant.Scene} The new Scene. 1682 */ 1683 replaceScene: function(scene) { 1684 this.popScene(); 1685 return this.pushScene(scene); 1686 }, 1687 /** 1688 * Removes a Scene from the Scene stack. 1689 * 1690 * If the scene passed in as a parameter is not the current 1691 * scene, the stack will be searched for the given scene. 1692 * If the given scene does not exist anywhere in the stack, 1693 * this method returns null. 1694 * 1695 * @param {enchant.Scene} scene Scene to be removed. 1696 * @return {enchant.Scene} The deleted Scene. 1697 */ 1698 removeScene: function(scene) { 1699 if (this.currentScene === scene) { 1700 return this.popScene(); 1701 } else { 1702 var i = this._scenes.indexOf(scene); 1703 if (i !== -1) { 1704 this._scenes.splice(i, 1); 1705 this._element.removeChild(scene._element); 1706 return scene; 1707 } else { 1708 return null; 1709 } 1710 } 1711 }, 1712 _buttonListener: function(e) { 1713 this.currentScene.dispatchEvent(e); 1714 }, 1715 /** 1716 * Bind a key code to an enchant.js button. 1717 * 1718 * Binds the given key code to the given enchant.js button 1719 * ('left', 'right', 'up', 'down', 'a', 'b'). 1720 * 1721 * @param {Number} key Key code for the button to be bound. 1722 * @param {String} button An enchant.js button. 1723 * @return {enchant.Core} this 1724 */ 1725 keybind: function(key, button) { 1726 this.keyboardInputManager.keybind(key, button); 1727 this.addEventListener(button + 'buttondown', this._buttonListener); 1728 this.addEventListener(button + 'buttonup', this._buttonListener); 1729 return this; 1730 }, 1731 /** 1732 * Delete the key binding for the given key. 1733 * 1734 * @param {Number} key Key code whose binding is to be deleted. 1735 * @return {enchant.Core} this 1736 */ 1737 keyunbind: function(key) { 1738 var button = this._keybind[key]; 1739 this.keyboardInputManager.keyunbind(key); 1740 this.removeEventListener(button + 'buttondown', this._buttonListener); 1741 this.removeEventListener(button + 'buttonup', this._buttonListener); 1742 return this; 1743 }, 1744 changeButtonState: function(button, bool) { 1745 this.keyboardInputManager.changeState(button, bool); 1746 }, 1747 /** 1748 * Get the core time (not actual) elapsed since {@link enchant.Core#start} was called. 1749 * @return {Number} Time elapsed (in seconds). 1750 */ 1751 getElapsedTime: function() { 1752 return this.frame / this.fps; 1753 } 1754 }); 1755 1756 /** 1757 * Functions for loading assets of the corresponding file type. 1758 * The loading functions must take the file path, extension and 1759 * callback function as arguments, then return the appropriate 1760 * class instance. 1761 * @static 1762 * @private 1763 * @type Object 1764 */ 1765 enchant.Core._loadFuncs = {}; 1766 enchant.Core._loadFuncs['jpg'] = 1767 enchant.Core._loadFuncs['jpeg'] = 1768 enchant.Core._loadFuncs['gif'] = 1769 enchant.Core._loadFuncs['png'] = 1770 enchant.Core._loadFuncs['bmp'] = function(src, ext, callback, onerror) { 1771 return enchant.Surface.load(src, callback, onerror); 1772 }; 1773 enchant.Core._loadFuncs['mp3'] = 1774 enchant.Core._loadFuncs['aac'] = 1775 enchant.Core._loadFuncs['m4a'] = 1776 enchant.Core._loadFuncs['wav'] = 1777 enchant.Core._loadFuncs['ogg'] = function(src, ext, callback, onerror) { 1778 return enchant.Sound.load(src, 'audio/' + ext, callback, onerror); 1779 }; 1780 1781 /** 1782 * Get the file extension from a path. 1783 * @param {String} path file path. 1784 * @return {*} 1785 */ 1786 enchant.Core.findExt = function(path) { 1787 var matched = path.match(/\.\w+$/); 1788 if (matched && matched.length > 0) { 1789 return matched[0].slice(1).toLowerCase(); 1790 } 1791 1792 // for data URI 1793 if (path.indexOf('data:') === 0) { 1794 return path.split(/[\/;]/)[1].toLowerCase(); 1795 } 1796 return null; 1797 }; 1798 1799 /** 1800 * The current Core instance. 1801 * @type enchant.Core 1802 * @static 1803 */ 1804 enchant.Core.instance = null; 1805 }()); 1806 1807 /** 1808 * @name enchant.Game 1809 * @class 1810 * enchant.Game is moved to {@link enchant.Core} from v0.6 1811 * @deprecated 1812 */ 1813 enchant.Game = enchant.Core; 1814 1815 /** 1816 * @scope enchant.InputManager.prototype 1817 */ 1818 enchant.InputManager = enchant.Class.create(enchant.EventTarget, { 1819 /** 1820 * @name enchant.InputManager 1821 * @class 1822 * Class for managing input. 1823 * @param {*} valueStore object that store input state. 1824 * @param {*} [source=this] source that will be added to event object. 1825 * @constructs 1826 * @extends enchant.EventTarget 1827 */ 1828 initialize: function(valueStore, source) { 1829 enchant.EventTarget.call(this); 1830 1831 /** 1832 * Array that store event target. 1833 * @type enchant.EventTarget[] 1834 */ 1835 this.broadcastTarget = []; 1836 /** 1837 * Object that store input state. 1838 * @type Object 1839 */ 1840 this.valueStore = valueStore; 1841 /** 1842 * source that will be added to event object. 1843 * @type Object 1844 */ 1845 this.source = source || this; 1846 1847 this._binds = {}; 1848 1849 this._stateHandler = function(e) { 1850 var id = e.source.identifier; 1851 var name = this._binds[id]; 1852 this.changeState(name, e.data); 1853 }.bind(this); 1854 }, 1855 /** 1856 * Name specified input. 1857 * Input can be watched by flag or event. 1858 * @param {enchant.InputSource} inputSource input source. 1859 * @param {String} name input name. 1860 */ 1861 bind: function(inputSource, name) { 1862 inputSource.addEventListener(enchant.Event.INPUT_STATE_CHANGED, this._stateHandler); 1863 this._binds[inputSource.identifier] = name; 1864 }, 1865 /** 1866 * Remove binded name. 1867 * @param {enchant.InputSource} inputSource input source. 1868 */ 1869 unbind: function(inputSource) { 1870 inputSource.removeEventListener(enchant.Event.INPUT_STATE_CHANGED, this._stateHandler); 1871 delete this._binds[inputSource.identifier]; 1872 }, 1873 /** 1874 * Add event target. 1875 * @param {enchant.EventTarget} eventTarget broadcast target. 1876 */ 1877 addBroadcastTarget: function(eventTarget) { 1878 var i = this.broadcastTarget.indexOf(eventTarget); 1879 if (i === -1) { 1880 this.broadcastTarget.push(eventTarget); 1881 } 1882 }, 1883 /** 1884 * Remove event target. 1885 * @param {enchant.EventTarget} eventTarget broadcast target. 1886 */ 1887 removeBroadcastTarget: function(eventTarget) { 1888 var i = this.broadcastTarget.indexOf(eventTarget); 1889 if (i !== -1) { 1890 this.broadcastTarget.splice(i, 1); 1891 } 1892 }, 1893 /** 1894 * Dispatch event to {@link enchant.InputManager#broadcastTarget}. 1895 * @param {enchant.Event} e event. 1896 */ 1897 broadcastEvent: function(e) { 1898 var target = this.broadcastTarget; 1899 for (var i = 0, l = target.length; i < l; i++) { 1900 target[i].dispatchEvent(e); 1901 } 1902 }, 1903 /** 1904 * Change state of input. 1905 * @param {String} name input name. 1906 * @param {*} data input state. 1907 */ 1908 changeState: function(name, data) { 1909 } 1910 }); 1911 1912 /** 1913 * @scope enchant.InputSource.prototype 1914 */ 1915 enchant.InputSource = enchant.Class.create(enchant.EventTarget, { 1916 /** 1917 * @name enchant.InputSource 1918 * @class 1919 * Class that wrap input. 1920 * @param {String} identifier identifier of InputSource. 1921 * @constructs 1922 * @extends enchant.EventTarget 1923 */ 1924 initialize: function(identifier) { 1925 enchant.EventTarget.call(this); 1926 /** 1927 * identifier of InputSource. 1928 * @type String 1929 */ 1930 this.identifier = identifier; 1931 }, 1932 /** 1933 * Notify state change by event. 1934 * @param {*} data state. 1935 */ 1936 notifyStateChange: function(data) { 1937 var e = new enchant.Event(enchant.Event.INPUT_STATE_CHANGED); 1938 e.data = data; 1939 e.source = this; 1940 this.dispatchEvent(e); 1941 } 1942 }); 1943 1944 /** 1945 * @scope enchant.BinaryInputManager.prototype 1946 */ 1947 enchant.BinaryInputManager = enchant.Class.create(enchant.InputManager, { 1948 /** 1949 * @name enchant.BinaryInputManager 1950 * @class 1951 * Class for managing input. 1952 * @param {*} flagStore object that store input flag. 1953 * @param {String} activeEventNameSuffix event name suffix. 1954 * @param {String} inactiveEventNameSuffix event name suffix. 1955 * @param {*} [source=this] source that will be added to event object. 1956 * @constructs 1957 * @extends enchant.InputManager 1958 */ 1959 initialize: function(flagStore, activeEventNameSuffix, inactiveEventNameSuffix, source) { 1960 enchant.InputManager.call(this, flagStore, source); 1961 /** 1962 * The number of active inputs. 1963 * @type Number 1964 */ 1965 this.activeInputsNum = 0; 1966 /** 1967 * event name suffix that dispatched by BinaryInputManager. 1968 * @type String 1969 */ 1970 this.activeEventNameSuffix = activeEventNameSuffix; 1971 /** 1972 * event name suffix that dispatched by BinaryInputManager. 1973 * @type String 1974 */ 1975 this.inactiveEventNameSuffix = inactiveEventNameSuffix; 1976 }, 1977 /** 1978 * Name specified input. 1979 * @param {enchant.BinaryInputSource} inputSource input source. 1980 * @param {String} name input name. 1981 * @see enchant.InputManager#bind 1982 */ 1983 bind: function(binaryInputSource, name) { 1984 enchant.InputManager.prototype.bind.call(this, binaryInputSource, name); 1985 this.valueStore[name] = false; 1986 }, 1987 /** 1988 * Remove binded name. 1989 * @param {enchant.BinaryInputSource} inputSource input source. 1990 * @see enchant.InputManager#unbind 1991 */ 1992 unbind: function(binaryInputSource) { 1993 var name = this._binds[binaryInputSource.identifier]; 1994 enchant.InputManager.prototype.unbind.call(this, binaryInputSource); 1995 delete this.valueStore[name]; 1996 }, 1997 /** 1998 * Change state of input. 1999 * @param {String} name input name. 2000 * @param {Boolean} bool input state. 2001 */ 2002 changeState: function(name, bool) { 2003 if (bool) { 2004 this._down(name); 2005 } else { 2006 this._up(name); 2007 } 2008 }, 2009 _down: function(name) { 2010 var inputEvent; 2011 if (!this.valueStore[name]) { 2012 this.valueStore[name] = true; 2013 inputEvent = new enchant.Event((this.activeInputsNum++) ? 'inputchange' : 'inputstart'); 2014 inputEvent.source = this.source; 2015 this.broadcastEvent(inputEvent); 2016 } 2017 var downEvent = new enchant.Event(name + this.activeEventNameSuffix); 2018 downEvent.source = this.source; 2019 this.broadcastEvent(downEvent); 2020 }, 2021 _up: function(name) { 2022 var inputEvent; 2023 if (this.valueStore[name]) { 2024 this.valueStore[name] = false; 2025 inputEvent = new enchant.Event((--this.activeInputsNum) ? 'inputchange' : 'inputend'); 2026 inputEvent.source = this.source; 2027 this.broadcastEvent(inputEvent); 2028 } 2029 var upEvent = new enchant.Event(name + this.inactiveEventNameSuffix); 2030 upEvent.source = this.source; 2031 this.broadcastEvent(upEvent); 2032 } 2033 }); 2034 2035 /** 2036 * @scope enchant.BinaryInputSource.prototype 2037 */ 2038 enchant.BinaryInputSource = enchant.Class.create(enchant.InputSource, { 2039 /** 2040 * @name enchant.BinaryInputSource 2041 * @class 2042 * Class that wrap binary input. 2043 * @param {String} identifier identifier of BinaryInputSource. 2044 * @constructs 2045 * @extends enchant.InputSource 2046 */ 2047 initialize: function(identifier) { 2048 enchant.InputSource.call(this, identifier); 2049 } 2050 }); 2051 2052 /** 2053 * @scope enchant.KeyboardInputManager.prototype 2054 */ 2055 enchant.KeyboardInputManager = enchant.Class.create(enchant.BinaryInputManager, { 2056 /** 2057 * @name enchant.KeyboardInputManager 2058 * @class 2059 * Class that manage keyboard input. 2060 * @param {HTMLElement} dom element that will be watched. 2061 * @param {*} flagStore object that store input flag. 2062 * @constructs 2063 * @extends enchant.BinaryInputManager 2064 */ 2065 initialize: function(domElement, flagStore) { 2066 enchant.BinaryInputManager.call(this, flagStore, 'buttondown', 'buttonup'); 2067 this._attachDOMEvent(domElement, 'keydown', true); 2068 this._attachDOMEvent(domElement, 'keyup', false); 2069 }, 2070 /** 2071 * Call {@link enchant.BinaryInputManager#bind} with BinaryInputSource equivalent of key code. 2072 * @param {Number} keyCode key code. 2073 * @param {String} name input name. 2074 */ 2075 keybind: function(keyCode, name) { 2076 this.bind(enchant.KeyboardInputSource.getByKeyCode('' + keyCode), name); 2077 }, 2078 /** 2079 * Call {@link enchant.BinaryInputManager#unbind} with BinaryInputSource equivalent of key code. 2080 * @param {Number} keyCode key code. 2081 */ 2082 keyunbind: function(keyCode) { 2083 this.unbind(enchant.KeyboardInputSource.getByKeyCode('' + keyCode)); 2084 }, 2085 _attachDOMEvent: function(domElement, eventType, state) { 2086 domElement.addEventListener(eventType, function(e) { 2087 var core = enchant.Core.instance; 2088 if (!core || !core.running) { 2089 return; 2090 } 2091 var code = e.keyCode; 2092 var source = enchant.KeyboardInputSource._instances[code]; 2093 if (source) { 2094 source.notifyStateChange(state); 2095 } 2096 }, true); 2097 } 2098 }); 2099 2100 /** 2101 * @scope enchant.KeyboardInputSource.prototype 2102 */ 2103 enchant.KeyboardInputSource = enchant.Class.create(enchant.BinaryInputSource, { 2104 /** 2105 * @name enchant.KeyboardInputSource 2106 * @class 2107 * @param {String} keyCode key code of BinaryInputSource. 2108 * @constructs 2109 * @extends enchant.BinaryInputSource 2110 */ 2111 initialize: function(keyCode) { 2112 enchant.BinaryInputSource.call(this, keyCode); 2113 } 2114 }); 2115 /** 2116 * @private 2117 */ 2118 enchant.KeyboardInputSource._instances = {}; 2119 /** 2120 * @static 2121 * Get the instance by key code. 2122 * @param {Number} keyCode key code. 2123 * @return {enchant.KeyboardInputSource} instance. 2124 */ 2125 enchant.KeyboardInputSource.getByKeyCode = function(keyCode) { 2126 if (!this._instances[keyCode]) { 2127 this._instances[keyCode] = new enchant.KeyboardInputSource(keyCode); 2128 } 2129 return this._instances[keyCode]; 2130 }; 2131 2132 /** 2133 * @scope enchant.Node.prototype 2134 */ 2135 enchant.Node = enchant.Class.create(enchant.EventTarget, { 2136 /** 2137 * @name enchant.Node 2138 * @class 2139 * Base class for objects in the display tree which is rooted at a Scene. 2140 * Not to be used directly. 2141 * @constructs 2142 * @extends enchant.EventTarget 2143 */ 2144 initialize: function() { 2145 enchant.EventTarget.call(this); 2146 2147 this._dirty = false; 2148 2149 this._matrix = [ 1, 0, 0, 1, 0, 0 ]; 2150 2151 this._x = 0; 2152 this._y = 0; 2153 this._offsetX = 0; 2154 this._offsetY = 0; 2155 2156 /** 2157 * The age (frames) of this node which will be increased before this node receives {@link enchant.Event.ENTER_FRAME} event. 2158 * @type Number 2159 */ 2160 this.age = 0; 2161 2162 /** 2163 * Parent Node of this Node. 2164 * @type enchant.Group 2165 */ 2166 this.parentNode = null; 2167 /** 2168 * Scene to which Node belongs. 2169 * @type enchant.Scene 2170 */ 2171 this.scene = null; 2172 2173 this.addEventListener('touchstart', function(e) { 2174 if (this.parentNode) { 2175 this.parentNode.dispatchEvent(e); 2176 } 2177 }); 2178 this.addEventListener('touchmove', function(e) { 2179 if (this.parentNode) { 2180 this.parentNode.dispatchEvent(e); 2181 } 2182 }); 2183 this.addEventListener('touchend', function(e) { 2184 if (this.parentNode) { 2185 this.parentNode.dispatchEvent(e); 2186 } 2187 }); 2188 2189 // Nodeが生成される際に, tl プロパティに Timeline オブジェクトを追加している. 2190 if (enchant.ENV.USE_ANIMATION) { 2191 this.tl = new enchant.Timeline(this); 2192 } 2193 }, 2194 /** 2195 * Move the Node to the given target location. 2196 * @param {Number} x Target x coordinates. 2197 * @param {Number} y Target y coordinates. 2198 */ 2199 moveTo: function(x, y) { 2200 this.x = x; 2201 this.y = y; 2202 }, 2203 /** 2204 * Move the Node relative to its current position. 2205 * @param {Number} x x axis movement distance. 2206 * @param {Number} y y axis movement distance. 2207 */ 2208 moveBy: function(x, y) { 2209 this.x += x; 2210 this.y += y; 2211 }, 2212 /** 2213 * x coordinates of the Node. 2214 * @type Number 2215 */ 2216 x: { 2217 get: function() { 2218 return this._x; 2219 }, 2220 set: function(x) { 2221 if(this._x !== x) { 2222 this._x = x; 2223 this._dirty = true; 2224 } 2225 } 2226 }, 2227 /** 2228 * y coordinates of the Node. 2229 * @type Number 2230 */ 2231 y: { 2232 get: function() { 2233 return this._y; 2234 }, 2235 set: function(y) { 2236 if(this._y !== y) { 2237 this._y = y; 2238 this._dirty = true; 2239 } 2240 } 2241 }, 2242 _updateCoordinate: function() { 2243 var node = this; 2244 var tree = [ node ]; 2245 var parent = node.parentNode; 2246 var scene = this.scene; 2247 while (parent && node._dirty) { 2248 tree.unshift(parent); 2249 node = node.parentNode; 2250 parent = node.parentNode; 2251 } 2252 var matrix = enchant.Matrix.instance; 2253 var stack = matrix.stack; 2254 var mat = []; 2255 var newmat, ox, oy; 2256 stack.push(tree[0]._matrix); 2257 for (var i = 1, l = tree.length; i < l; i++) { 2258 node = tree[i]; 2259 newmat = []; 2260 matrix.makeTransformMatrix(node, mat); 2261 matrix.multiply(stack[stack.length - 1], mat, newmat); 2262 node._matrix = newmat; 2263 stack.push(newmat); 2264 ox = (typeof node._originX === 'number') ? node._originX : node._width / 2 || 0; 2265 oy = (typeof node._originY === 'number') ? node._originY : node._height / 2 || 0; 2266 var vec = [ ox, oy ]; 2267 matrix.multiplyVec(newmat, vec, vec); 2268 node._offsetX = vec[0] - ox; 2269 node._offsetY = vec[1] - oy; 2270 node._dirty = false; 2271 } 2272 matrix.reset(); 2273 }, 2274 remove: function() { 2275 if (this.parentNode) { 2276 this.parentNode.removeChild(this); 2277 } 2278 if (this.childNodes) { 2279 var childNodes = this.childNodes.slice(); 2280 for(var i = childNodes.length-1; i >= 0; i--) { 2281 childNodes[i].remove(); 2282 } 2283 } 2284 2285 this.clearEventListener(); 2286 } 2287 }); 2288 2289 var _intersectBetweenClassAndInstance = function(Class, instance) { 2290 var ret = []; 2291 var c; 2292 for (var i = 0, l = Class.collection.length; i < l; i++) { 2293 c = Class.collection[i]; 2294 if (instance._intersectOne(c)) { 2295 ret.push(c); 2296 } 2297 } 2298 return ret; 2299 }; 2300 2301 var _intersectBetweenClassAndClass = function(Class1, Class2) { 2302 var ret = []; 2303 var c1, c2; 2304 for (var i = 0, l = Class1.collection.length; i < l; i++) { 2305 c1 = Class1.collection[i]; 2306 for (var j = 0, ll = Class2.collection.length; j < ll; j++) { 2307 c2 = Class2.collection[j]; 2308 if (c1._intersectOne(c2)) { 2309 ret.push([ c1, c2 ]); 2310 } 2311 } 2312 } 2313 return ret; 2314 }; 2315 2316 var _intersectStrictBetweenClassAndInstance = function(Class, instance) { 2317 var ret = []; 2318 var c; 2319 for (var i = 0, l = Class.collection.length; i < l; i++) { 2320 c = Class.collection[i]; 2321 if (instance._intersectStrictOne(c)) { 2322 ret.push(c); 2323 } 2324 } 2325 return ret; 2326 }; 2327 2328 var _intersectStrictBetweenClassAndClass = function(Class1, Class2) { 2329 var ret = []; 2330 var c1, c2; 2331 for (var i = 0, l = Class1.collection.length; i < l; i++) { 2332 c1 = Class1.collection[i]; 2333 for (var j = 0, ll = Class2.collection.length; j < ll; j++) { 2334 c2 = Class2.collection[j]; 2335 if (c1._intersectStrictOne(c2)) { 2336 ret.push([ c1, c2 ]); 2337 } 2338 } 2339 } 2340 return ret; 2341 }; 2342 2343 var _staticIntersect = function(other) { 2344 if (other instanceof enchant.Entity) { 2345 return _intersectBetweenClassAndInstance(this, other); 2346 } else if (typeof other === 'function' && other.collection) { 2347 return _intersectBetweenClassAndClass(this, other); 2348 } 2349 return false; 2350 }; 2351 2352 var _staticIntersectStrict = function(other) { 2353 if (other instanceof enchant.Entity) { 2354 return _intersectStrictBetweenClassAndInstance(this, other); 2355 } else if (typeof other === 'function' && other.collection) { 2356 return _intersectStrictBetweenClassAndClass(this, other); 2357 } 2358 return false; 2359 }; 2360 2361 var _nodePrototypeClearEventListener = enchant.Node.prototype.clearEventListener; 2362 2363 /** 2364 * @scope enchant.Entity.prototype 2365 */ 2366 enchant.Entity = enchant.Class.create(enchant.Node, { 2367 /** 2368 * @name enchant.Entity 2369 * @class 2370 * A class with objects displayed as DOM elements. Not to be used directly. 2371 * @constructs 2372 * @extends enchant.Node 2373 */ 2374 initialize: function() { 2375 var core = enchant.Core.instance; 2376 enchant.Node.call(this); 2377 2378 this._rotation = 0; 2379 this._scaleX = 1; 2380 this._scaleY = 1; 2381 2382 this._touchEnabled = true; 2383 this._clipping = false; 2384 2385 this._originX = null; 2386 this._originY = null; 2387 2388 this._width = 0; 2389 this._height = 0; 2390 this._backgroundColor = null; 2391 this._debugColor = '#0000ff'; 2392 this._opacity = 1; 2393 this._visible = true; 2394 this._buttonMode = null; 2395 2396 this._style = {}; 2397 this.__styleStatus = {}; 2398 2399 this._isContainedInCollection = false; 2400 2401 /** 2402 * @type String 2403 */ 2404 this.compositeOperation = null; 2405 2406 /** 2407 * Defines this Entity as a button. 2408 * When touched or clicked the corresponding button event is dispatched. 2409 * Valid buttonModes are: left, right, up, down, a, b. 2410 * @type String 2411 */ 2412 this.buttonMode = null; 2413 /** 2414 * Indicates if this Entity is being clicked. 2415 * Only works when {@link enchant.Entity.buttonMode} is set. 2416 * @type Boolean 2417 */ 2418 this.buttonPressed = false; 2419 this.addEventListener('touchstart', function() { 2420 if (!this.buttonMode) { 2421 return; 2422 } 2423 this.buttonPressed = true; 2424 this.dispatchEvent(new enchant.Event(this.buttonMode + 'buttondown')); 2425 core.changeButtonState(this.buttonMode, true); 2426 }); 2427 this.addEventListener('touchend', function() { 2428 if (!this.buttonMode) { 2429 return; 2430 } 2431 this.buttonPressed = false; 2432 this.dispatchEvent(new enchant.Event(this.buttonMode + 'buttonup')); 2433 core.changeButtonState(this.buttonMode, false); 2434 }); 2435 2436 this.enableCollection(); 2437 }, 2438 /** 2439 * The width of the Entity. 2440 * @type Number 2441 */ 2442 width: { 2443 get: function() { 2444 return this._width; 2445 }, 2446 set: function(width) { 2447 if(this._width !== width) { 2448 this._width = width; 2449 this._dirty = true; 2450 } 2451 } 2452 }, 2453 /** 2454 * The height of the Entity. 2455 * @type Number 2456 */ 2457 height: { 2458 get: function() { 2459 return this._height; 2460 }, 2461 set: function(height) { 2462 if(this._height !== height) { 2463 this._height = height; 2464 this._dirty = true; 2465 } 2466 } 2467 }, 2468 /** 2469 * The Entity background color. 2470 * Must be provided in the same format as the CSS 'color' property. 2471 * @type String 2472 */ 2473 backgroundColor: { 2474 get: function() { 2475 return this._backgroundColor; 2476 }, 2477 set: function(color) { 2478 this._backgroundColor = color; 2479 } 2480 }, 2481 /** 2482 * The Entity debug color. 2483 * Must be provided in the same format as the CSS 'color' property. 2484 * @type String 2485 */ 2486 debugColor: { 2487 get: function() { 2488 return this._debugColor; 2489 }, 2490 set: function(color) { 2491 this._debugColor = color; 2492 } 2493 }, 2494 /** 2495 * The transparency of this entity. 2496 * Defines the transparency level from 0 to 1 2497 * (0 is completely transparent, 1 is completely opaque). 2498 * @type Number 2499 */ 2500 opacity: { 2501 get: function() { 2502 return this._opacity; 2503 }, 2504 set: function(opacity) { 2505 this._opacity = parseFloat(opacity); 2506 } 2507 }, 2508 /** 2509 * Indicates whether or not to display this Entity. 2510 * @type Boolean 2511 */ 2512 visible: { 2513 get: function() { 2514 return this._visible; 2515 }, 2516 set: function(visible) { 2517 this._visible = visible; 2518 } 2519 }, 2520 /** 2521 * Indicates whether or not this Entity can be touched. 2522 * @type Boolean 2523 */ 2524 touchEnabled: { 2525 get: function() { 2526 return this._touchEnabled; 2527 }, 2528 set: function(enabled) { 2529 this._touchEnabled = enabled; 2530 if (enabled) { 2531 this._style.pointerEvents = 'all'; 2532 } else { 2533 this._style.pointerEvents = 'none'; 2534 } 2535 } 2536 }, 2537 /** 2538 * Performs a collision detection based on whether or not the bounding rectangles are intersecting. 2539 * @param {*} other An object like Entity, with the properties x, y, width, height, which are used for the 2540 * collision detection. 2541 * @return {Boolean} True, if a collision was detected. 2542 */ 2543 intersect: function(other) { 2544 if (other instanceof enchant.Entity) { 2545 return this._intersectOne(other); 2546 } else if (typeof other === 'function' && other.collection) { 2547 return _intersectBetweenClassAndInstance(other, this); 2548 } 2549 return false; 2550 }, 2551 _intersectOne: function(other) { 2552 if (this._dirty) { 2553 this._updateCoordinate(); 2554 } if (other._dirty) { 2555 other._updateCoordinate(); 2556 } 2557 return this._offsetX < other._offsetX + other.width && other._offsetX < this._offsetX + this.width && 2558 this._offsetY < other._offsetY + other.height && other._offsetY < this._offsetY + this.height; 2559 }, 2560 intersectStrict: function(other) { 2561 if (other instanceof enchant.Entity) { 2562 return this._intersectStrictOne(other); 2563 } else if (typeof other === 'function' && other.collection) { 2564 return _intersectStrictBetweenClassAndInstance(other, this); 2565 } 2566 return false; 2567 }, 2568 _intersectStrictOne: function(other) { 2569 if (this._dirty) { 2570 this._updateCoordinate(); 2571 } if (other._dirty) { 2572 other._updateCoordinate(); 2573 } 2574 var rect1 = this.getOrientedBoundingRect(), 2575 rect2 = other.getOrientedBoundingRect(), 2576 lt1 = rect1.leftTop, rt1 = rect1.rightTop, 2577 lb1 = rect1.leftBottom, rb1 = rect1.rightBottom, 2578 lt2 = rect2.leftTop, rt2 = rect2.rightTop, 2579 lb2 = rect2.leftBottom, rb2 = rect2.rightBottom, 2580 ltx1 = lt1[0], lty1 = lt1[1], rtx1 = rt1[0], rty1 = rt1[1], 2581 lbx1 = lb1[0], lby1 = lb1[1], rbx1 = rb1[0], rby1 = rb1[1], 2582 ltx2 = lt2[0], lty2 = lt2[1], rtx2 = rt2[0], rty2 = rt2[1], 2583 lbx2 = lb2[0], lby2 = lb2[1], rbx2 = rb2[0], rby2 = rb2[1], 2584 t1 = [ rtx1 - ltx1, rty1 - lty1 ], 2585 r1 = [ rbx1 - rtx1, rby1 - rty1 ], 2586 b1 = [ lbx1 - rbx1, lby1 - rby1 ], 2587 l1 = [ ltx1 - lbx1, lty1 - lby1 ], 2588 t2 = [ rtx2 - ltx2, rty2 - lty2 ], 2589 r2 = [ rbx2 - rtx2, rby2 - rty2 ], 2590 b2 = [ lbx2 - rbx2, lby2 - rby2 ], 2591 l2 = [ ltx2 - lbx2, lty2 - lby2 ], 2592 cx1 = (ltx1 + rtx1 + lbx1 + rbx1) >> 2, 2593 cy1 = (lty1 + rty1 + lby1 + rby1) >> 2, 2594 cx2 = (ltx2 + rtx2 + lbx2 + rbx2) >> 2, 2595 cy2 = (lty2 + rty2 + lby2 + rby2) >> 2, 2596 i, j, poss1, poss2, dirs1, dirs2, pos1, pos2, dir1, dir2, 2597 px1, py1, px2, py2, dx1, dy1, dx2, dy2, vx, vy, c, c1, c2; 2598 if (t1[0] * (cy2 - lty1) - t1[1] * (cx2 - ltx1) > 0 && 2599 r1[0] * (cy2 - rty1) - r1[1] * (cx2 - rtx1) > 0 && 2600 b1[0] * (cy2 - rby1) - b1[1] * (cx2 - rbx1) > 0 && 2601 l1[0] * (cy2 - lby1) - l1[1] * (cx2 - lbx1) > 0) { 2602 return true; 2603 } else if (t2[0] * (cy1 - lty2) - t2[1] * (cx1 - ltx2) > 0 && 2604 r2[0] * (cy1 - rty2) - r2[1] * (cx1 - rtx2) > 0 && 2605 b2[0] * (cy1 - rby2) - b2[1] * (cx1 - rbx2) > 0 && 2606 l2[0] * (cy1 - lby2) - l2[1] * (cx1 - lbx2) > 0) { 2607 return true; 2608 } else { 2609 poss1 = [ lt1, rt1, rb1, lb1 ]; 2610 poss2 = [ lt2, rt2, rb2, lb2 ]; 2611 dirs1 = [ t1, r1, b1, l1 ]; 2612 dirs2 = [ t2, r2, b2, l2 ]; 2613 for (i = 0; i < 4; i++) { 2614 pos1 = poss1[i]; 2615 px1 = pos1[0]; py1 = pos1[1]; 2616 dir1 = dirs1[i]; 2617 dx1 = dir1[0]; dy1 = dir1[1]; 2618 for (j = 0; j < 4; j++) { 2619 pos2 = poss2[j]; 2620 px2 = pos2[0]; py2 = pos2[1]; 2621 dir2 = dirs2[j]; 2622 dx2 = dir2[0]; dy2 = dir2[1]; 2623 c = dx1 * dy2 - dy1 * dx2; 2624 if (c !== 0) { 2625 vx = px2 - px1; 2626 vy = py2 - py1; 2627 c1 = (vx * dy1 - vy * dx1) / c; 2628 c2 = (vx * dy2 - vy * dx2) / c; 2629 if (0 < c1 && c1 < 1 && 0 < c2 && c2 < 1) { 2630 return true; 2631 } 2632 } 2633 } 2634 } 2635 return false; 2636 } 2637 }, 2638 /** 2639 * Performs a collision detection based on distance from the Entity's central point. 2640 * @param {*} other An object like Entity, with properties x, y, width, height, which are used for the 2641 * collision detection. 2642 * @param {Number} [distance] The greatest distance to be considered for a collision. 2643 * The default distance is the average of both objects width and height. 2644 * @return {Boolean} True, if a collision was detected. 2645 */ 2646 within: function(other, distance) { 2647 if (this._dirty) { 2648 this._updateCoordinate(); 2649 } if (other._dirty) { 2650 other._updateCoordinate(); 2651 } 2652 if (distance == null) { 2653 distance = (this.width + this.height + other.width + other.height) / 4; 2654 } 2655 var _; 2656 return (_ = this._offsetX - other._offsetX + (this.width - other.width) / 2) * _ + 2657 (_ = this._offsetY - other._offsetY + (this.height - other.height) / 2) * _ < distance * distance; 2658 }, 2659 /** 2660 * Enlarges or shrinks this Entity. 2661 * @param {Number} x Scaling factor on the x axis. 2662 * @param {Number} [y] Scaling factor on the y axis. 2663 */ 2664 scale: function(x, y) { 2665 this._scaleX *= x; 2666 this._scaleY *= (y != null) ? y : x; 2667 this._dirty = true; 2668 }, 2669 /** 2670 * Rotate this Entity. 2671 * @param {Number} deg Rotation angle (degree). 2672 */ 2673 rotate: function(deg) { 2674 this.rotation += deg; 2675 }, 2676 /** 2677 * Scaling factor on the x axis of this Entity. 2678 * @type Number 2679 */ 2680 scaleX: { 2681 get: function() { 2682 return this._scaleX; 2683 }, 2684 set: function(scaleX) { 2685 if(this._scaleX !== scaleX) { 2686 this._scaleX = scaleX; 2687 this._dirty = true; 2688 } 2689 } 2690 }, 2691 /** 2692 * Scaling factor on the y axis of this Entity. 2693 * @type Number 2694 */ 2695 scaleY: { 2696 get: function() { 2697 return this._scaleY; 2698 }, 2699 set: function(scaleY) { 2700 if(this._scaleY !== scaleY) { 2701 this._scaleY = scaleY; 2702 this._dirty = true; 2703 } 2704 } 2705 }, 2706 /** 2707 * Entity rotation angle (degree). 2708 * @type Number 2709 */ 2710 rotation: { 2711 get: function() { 2712 return this._rotation; 2713 }, 2714 set: function(rotation) { 2715 if(this._rotation !== rotation) { 2716 this._rotation = rotation; 2717 this._dirty = true; 2718 } 2719 } 2720 }, 2721 /** 2722 * The point of origin used for rotation and scaling. 2723 * @type Number 2724 */ 2725 originX: { 2726 get: function() { 2727 return this._originX; 2728 }, 2729 set: function(originX) { 2730 if(this._originX !== originX) { 2731 this._originX = originX; 2732 this._dirty = true; 2733 } 2734 } 2735 }, 2736 /** 2737 * The point of origin used for rotation and scaling. 2738 * @type Number 2739 */ 2740 originY: { 2741 get: function() { 2742 return this._originY; 2743 }, 2744 set: function(originY) { 2745 if(this._originY !== originY) { 2746 this._originY = originY; 2747 this._dirty = true; 2748 } 2749 } 2750 }, 2751 /** 2752 */ 2753 enableCollection: function() { 2754 this.addEventListener('addedtoscene', this._addSelfToCollection); 2755 this.addEventListener('removedfromscene', this._removeSelfFromCollection); 2756 if (this.scene) { 2757 this._addSelfToCollection(); 2758 } 2759 }, 2760 /** 2761 */ 2762 disableCollection: function() { 2763 this.removeEventListener('addedtoscene', this._addSelfToCollection); 2764 this.removeEventListener('removedfromscene', this._removeSelfFromCollection); 2765 if (this.scene) { 2766 this._removeSelfFromCollection(); 2767 } 2768 }, 2769 /**#nocode+*/ 2770 clearEventListener: function() { 2771 _nodePrototypeClearEventListener.apply(this,arguments); 2772 if (this.scene) { 2773 this._removeSelfFromCollection(); 2774 } 2775 }, 2776 /**#nocode-*/ 2777 _addSelfToCollection: function() { 2778 if (this._isContainedInCollection) { 2779 return; 2780 } 2781 2782 var Constructor = this.getConstructor(); 2783 Constructor._collectionTarget.forEach(function(C) { 2784 C.collection.push(this); 2785 }, this); 2786 2787 this._isContainedInCollection = true; 2788 }, 2789 _removeSelfFromCollection: function() { 2790 if (!this._isContainedInCollection) { 2791 return; 2792 } 2793 2794 var Constructor = this.getConstructor(); 2795 Constructor._collectionTarget.forEach(function(C) { 2796 var i = C.collection.indexOf(this); 2797 if (i !== -1) { 2798 C.collection.splice(i, 1); 2799 } 2800 }, this); 2801 2802 this._isContainedInCollection = false; 2803 }, 2804 getBoundingRect: function() { 2805 var w = this.width || 0; 2806 var h = this.height || 0; 2807 var mat = this._matrix; 2808 var m11w = mat[0] * w, m12w = mat[1] * w, 2809 m21h = mat[2] * h, m22h = mat[3] * h, 2810 mdx = mat[4], mdy = mat[5]; 2811 var xw = [ mdx, m11w + mdx, m21h + mdx, m11w + m21h + mdx ].sort(function(a, b) { return a - b; }); 2812 var yh = [ mdy, m12w + mdy, m22h + mdy, m12w + m22h + mdy ].sort(function(a, b) { return a - b; }); 2813 2814 return { 2815 left: xw[0], 2816 top: yh[0], 2817 width: xw[3] - xw[0], 2818 height: yh[3] - yh[0] 2819 }; 2820 }, 2821 getOrientedBoundingRect: function() { 2822 var w = this.width || 0; 2823 var h = this.height || 0; 2824 var mat = this._matrix; 2825 var m11w = mat[0] * w, m12w = mat[1] * w, 2826 m21h = mat[2] * h, m22h = mat[3] * h, 2827 mdx = mat[4], mdy = mat[5]; 2828 2829 return { 2830 leftTop: [ mdx, mdy ], 2831 rightTop: [ m11w + mdx, m12w + mdy ], 2832 leftBottom: [ m21h + mdx, m22h + mdy ], 2833 rightBottom: [ m11w + m21h + mdx, m12w + m22h + mdy ] 2834 }; 2835 }, 2836 getConstructor: function() { 2837 return Object.getPrototypeOf(this).constructor; 2838 } 2839 }); 2840 2841 var _collectizeConstructor = function(Constructor) { 2842 if (Constructor._collective) { 2843 return; 2844 } 2845 var rel = enchant.Class.getInheritanceTree(Constructor); 2846 var i = rel.indexOf(enchant.Entity); 2847 if (i !== -1) { 2848 Constructor._collectionTarget = rel.splice(0, i + 1); 2849 } else { 2850 Constructor._collectionTarget = []; 2851 } 2852 Constructor.intersect = _staticIntersect; 2853 Constructor.intersectStrict = _staticIntersectStrict; 2854 Constructor.collection = []; 2855 Constructor._collective = true; 2856 }; 2857 2858 _collectizeConstructor(enchant.Entity); 2859 2860 enchant.Entity._inherited = function(subclass) { 2861 _collectizeConstructor(subclass); 2862 }; 2863 2864 /** 2865 * @scope enchant.Sprite.prototype 2866 */ 2867 enchant.Sprite = enchant.Class.create(enchant.Entity, { 2868 /** 2869 * @name enchant.Sprite 2870 * @class 2871 * Class which can display images. 2872 * @param {Number} width Sprite width. 2873 * @param {Number} height Sprite height. 2874 * 2875 * @example 2876 * var bear = new Sprite(32, 32); 2877 * bear.image = core.assets['chara1.gif']; 2878 * 2879 * @constructs 2880 * @extends enchant.Entity 2881 */ 2882 initialize: function(width, height) { 2883 enchant.Entity.call(this); 2884 2885 this.width = width; 2886 this.height = height; 2887 this._image = null; 2888 this._debugColor = '#ff0000'; 2889 this._frameLeft = 0; 2890 this._frameTop = 0; 2891 this._frame = 0; 2892 this._frameSequence = null; 2893 }, 2894 /** 2895 * Image displayed in the Sprite. 2896 * @type enchant.Surface 2897 */ 2898 image: { 2899 get: function() { 2900 return this._image; 2901 }, 2902 set: function(image) { 2903 if (image === undefined) { 2904 throw new Error('Assigned value on Sprite.image is undefined. Please double-check image path, and check if the image you want to use is preload before use.'); 2905 } 2906 if (image === this._image) { 2907 return; 2908 } 2909 this._image = image; 2910 this._computeFramePosition(); 2911 } 2912 }, 2913 /** 2914 * Index of the frame to be displayed. 2915 * Frames with the same width and height as Sprite will be arrayed from upper left corner of the 2916 * {@link enchant.Sprite#image} image. When a sequence of numbers is provided, the displayed frame 2917 * will switch automatically. At the end of the array the sequence will restart. By setting 2918 * a value within the sequence to null, the frame switching is stopped. 2919 * 2920 * @example 2921 * var sprite = new Sprite(32, 32); 2922 * sprite.frame = [0, 1, 0, 2] 2923 * //-> 0, 1, 0, 2, 0, 1, 0, 2,.. 2924 * sprite.frame = [0, 1, 0, 2, null] 2925 * //-> 0, 1, 0, 2, (2, 2,.. :stop) 2926 * 2927 * @type Number|Array 2928 */ 2929 frame: { 2930 get: function() { 2931 return this._frame; 2932 }, 2933 set: function(frame) { 2934 if (((this._frameSequence == null) && (this._frame === frame)) || (this._deepCompareToPreviousFrame(frame))) { 2935 return; 2936 } 2937 if (frame instanceof Array) { 2938 this._frameSequence = frame; 2939 } else { 2940 this._frameSequence = null; 2941 this._frame = frame; 2942 this._computeFramePosition(); 2943 } 2944 } 2945 }, 2946 _frameSequence: { 2947 get: function() { 2948 return this.__frameSequence; 2949 }, 2950 set: function(frameSequence) { 2951 if(frameSequence && !this.__frameSequence) { 2952 this.addEventListener(enchant.Event.ENTER_FRAME, this._rotateFrameSequence); 2953 } else if(!frameSequence && this.__frameSequence) { 2954 this.removeEventListener(enchant.Event.ENTER_FRAME, this._rotateFrameSequence); 2955 } 2956 if(frameSequence) { 2957 this.__frameSequence = frameSequence.slice(); 2958 this._originalFrameSequence = frameSequence.slice(); 2959 this._rotateFrameSequence(); 2960 } else { 2961 this.__frameSequence = null; 2962 this._originalFrameSequence = null; 2963 } 2964 } 2965 }, 2966 /** 2967 * If we are setting the same frame Array as animation, 2968 * just continue animating. 2969 * @private 2970 */ 2971 _deepCompareToPreviousFrame: function(frameArray) { 2972 if (frameArray === this._originalFrameSequence) { 2973 return true; 2974 } 2975 if (frameArray == null || this._originalFrameSequence == null) { 2976 return false; 2977 } 2978 if (!(frameArray instanceof Array)) { 2979 return false; 2980 } 2981 if (frameArray.length !== this._originalFrameSequence.length) { 2982 return false; 2983 } 2984 for (var i = 0; i < frameArray.length; ++i) { 2985 if (frameArray[i] !== this._originalFrameSequence[i]){ 2986 return false; 2987 } 2988 } 2989 return true; 2990 }, 2991 /** 2992 * 0 <= frame 2993 * @private 2994 */ 2995 _computeFramePosition: function() { 2996 var image = this._image; 2997 var row; 2998 if (image != null) { 2999 row = image.width / this._width | 0; 3000 this._frameLeft = (this._frame % row | 0) * this._width; 3001 this._frameTop = (this._frame / row | 0) * this._height % image.height; 3002 } 3003 }, 3004 _rotateFrameSequence: function() { 3005 var frameSequence = this._frameSequence; 3006 if (frameSequence && frameSequence.length !== 0) { 3007 var nextFrame = frameSequence.shift(); 3008 if (nextFrame === null) { 3009 this._frameSequence = null; 3010 this.dispatchEvent(new enchant.Event(enchant.Event.ANIMATION_END)); 3011 } else { 3012 this._frame = nextFrame; 3013 this._computeFramePosition(); 3014 frameSequence.push(nextFrame); 3015 } 3016 } 3017 }, 3018 /**#nocode+*/ 3019 width: { 3020 get: function() { 3021 return this._width; 3022 }, 3023 set: function(width) { 3024 this._width = width; 3025 this._computeFramePosition(); 3026 this._dirty = true; 3027 } 3028 }, 3029 height: { 3030 get: function() { 3031 return this._height; 3032 }, 3033 set: function(height) { 3034 this._height = height; 3035 this._computeFramePosition(); 3036 this._dirty = true; 3037 } 3038 }, 3039 /**#nocode-*/ 3040 cvsRender: function(ctx) { 3041 var image = this._image, 3042 w = this._width, h = this._height, 3043 iw, ih, elem, sx, sy, sw, sh; 3044 if (image && w !== 0 && h !== 0) { 3045 iw = image.width; 3046 ih = image.height; 3047 if (iw < w || ih < h) { 3048 ctx.fillStyle = enchant.Surface._getPattern(image); 3049 ctx.fillRect(0, 0, w, h); 3050 } else { 3051 elem = image._element; 3052 sx = this._frameLeft; 3053 sy = Math.min(this._frameTop, ih - h); 3054 // IE9 doesn't allow for negative or 0 widths/heights when drawing on the CANVAS element 3055 sw = Math.max(0.01, Math.min(iw - sx, w)); 3056 sh = Math.max(0.01, Math.min(ih - sy, h)); 3057 ctx.drawImage(elem, sx, sy, sw, sh, 0, 0, w, h); 3058 } 3059 } 3060 }, 3061 domRender: (function() { 3062 if (enchant.ENV.VENDOR_PREFIX === 'ms') { 3063 return function(element) { 3064 if (this._image) { 3065 if (this._image._css) { 3066 this._style['background-image'] = this._image._css; 3067 this._style['background-position'] = 3068 -this._frameLeft + 'px ' + 3069 -this._frameTop + 'px'; 3070 } else if (this._image._element) { 3071 } 3072 } 3073 }; 3074 } else { 3075 return function(element) { 3076 if (this._image) { 3077 if (this._image._css) { 3078 this._style['background-image'] = this._image._css; 3079 this._style['background-position'] = 3080 -this._frameLeft + 'px ' + 3081 -this._frameTop + 'px'; 3082 } else if (this._image._element) { 3083 } 3084 } 3085 }; 3086 } 3087 }()) 3088 }); 3089 3090 /** 3091 * @scope enchant.Label.prototype 3092 */ 3093 enchant.Label = enchant.Class.create(enchant.Entity, { 3094 /** 3095 * @name enchant.Label 3096 * @class 3097 * A class for Label object. 3098 * @constructs 3099 * @extends enchant.Entity 3100 */ 3101 initialize: function(text) { 3102 enchant.Entity.call(this); 3103 3104 this.text = text || ''; 3105 this.width = 300; 3106 this.font = '14px serif'; 3107 this.textAlign = 'left'; 3108 3109 this._debugColor = '#ff0000'; 3110 }, 3111 /**#nocode+*/ 3112 width: { 3113 get: function() { 3114 return this._width; 3115 }, 3116 set: function(width) { 3117 this._width = width; 3118 this._dirty = true; 3119 // issue #164 3120 this.updateBoundArea(); 3121 } 3122 }, 3123 /**#nocode-*/ 3124 /** 3125 * Text to be displayed. 3126 * @type String 3127 */ 3128 text: { 3129 get: function() { 3130 return this._text; 3131 }, 3132 set: function(text) { 3133 text = '' + text; 3134 if(this._text === text) { 3135 return; 3136 } 3137 this._text = text; 3138 text = text.replace(/<br ?\/?>/gi, '<br/>'); 3139 this._splitText = text.split('<br/>'); 3140 this.updateBoundArea(); 3141 for (var i = 0, l = this._splitText.length; i < l; i++) { 3142 text = this._splitText[i]; 3143 var metrics = this.getMetrics(text); 3144 this._splitText[i] = {}; 3145 this._splitText[i].text = text; 3146 this._splitText[i].height = metrics.height; 3147 this._splitText[i].width = metrics.width; 3148 } 3149 } 3150 }, 3151 /** 3152 * Specifies horizontal alignment of text. 3153 * Can be set according to the format of the CSS 'text-align' property. 3154 * @type String 3155 */ 3156 textAlign: { 3157 get: function() { 3158 return this._style['text-align']; 3159 }, 3160 set: function(textAlign) { 3161 this._style['text-align'] = textAlign; 3162 this.updateBoundArea(); 3163 } 3164 }, 3165 /** 3166 * Font settings. 3167 * Can be set according to the format of the CSS 'font' property. 3168 * @type String 3169 */ 3170 font: { 3171 get: function() { 3172 return this._style.font; 3173 }, 3174 set: function(font) { 3175 this._style.font = font; 3176 this.updateBoundArea(); 3177 } 3178 }, 3179 /** 3180 * Text color settings. 3181 * Can be set according to the format of the CSS 'color' property. 3182 * @type String 3183 */ 3184 color: { 3185 get: function() { 3186 return this._style.color; 3187 }, 3188 set: function(color) { 3189 this._style.color = color; 3190 } 3191 }, 3192 cvsRender: function(ctx) { 3193 var x, y = 0; 3194 var labelWidth = this.width; 3195 var charWidth, amount, line, text, c, buf, increase, length; 3196 var bufWidth; 3197 if (this._splitText) { 3198 ctx.textBaseline = 'top'; 3199 ctx.font = this.font; 3200 ctx.fillStyle = this.color || '#000000'; 3201 charWidth = ctx.measureText(' ').width; 3202 amount = labelWidth / charWidth; 3203 for (var i = 0, l = this._splitText.length; i < l; i++) { 3204 line = this._splitText[i]; 3205 text = line.text; 3206 c = 0; 3207 while (text.length > c + amount || ctx.measureText(text.slice(c, c + amount)).width > labelWidth) { 3208 buf = ''; 3209 increase = amount; 3210 length = 0; 3211 while (increase > 0) { 3212 if (ctx.measureText(buf).width < labelWidth) { 3213 length += increase; 3214 buf = text.slice(c, c + length); 3215 } else { 3216 length -= increase; 3217 buf = text.slice(c, c + length); 3218 } 3219 increase = increase / 2 | 0; 3220 } 3221 ctx.fillText(buf, 0, y); 3222 y += line.height - 1; 3223 c += length; 3224 } 3225 buf = text.slice(c, c + text.length); 3226 if (this.textAlign === 'right') { 3227 x = labelWidth - ctx.measureText(buf).width; 3228 } else if (this.textAlign === 'center') { 3229 x = (labelWidth - ctx.measureText(buf).width) / 2; 3230 } else { 3231 x = 0; 3232 } 3233 ctx.fillText(buf, x, y); 3234 y += line.height - 1; 3235 } 3236 } 3237 }, 3238 domRender: function(element) { 3239 if (element.innerHTML !== this._text) { 3240 element.innerHTML = this._text; 3241 } 3242 }, 3243 detectRender: function(ctx) { 3244 ctx.fillRect(this._boundOffset, 0, this._boundWidth, this._boundHeight); 3245 }, 3246 updateBoundArea: function() { 3247 var metrics = this.getMetrics(); 3248 this._boundWidth = metrics.width; 3249 this._boundHeight = metrics.height; 3250 if (this.textAlign === 'right') { 3251 this._boundOffset = this.width - this._boundWidth; 3252 } else if (this.textAlign === 'center') { 3253 this._boundOffset = (this.width - this._boundWidth) / 2; 3254 } else { 3255 this._boundOffset = 0; 3256 } 3257 }, 3258 getMetrics: function(text) { 3259 var ret = {}; 3260 var div, width, height; 3261 if (document.body) { 3262 div = document.createElement('div'); 3263 for (var prop in this._style) { 3264 if(prop !== 'width' && prop !== 'height') { 3265 div.style[prop] = this._style[prop]; 3266 } 3267 } 3268 text = text || this._text; 3269 div.innerHTML = text.replace(/ /g, ' '); 3270 div.style.whiteSpace = 'noWrap'; 3271 div.style.lineHeight = 1; 3272 document.body.appendChild(div); 3273 var computedStyle = getComputedStyle(div); 3274 ret.height = parseInt(computedStyle.height, 10) + 1; 3275 div.style.position = 'absolute'; 3276 ret.width = parseInt(computedStyle.width, 10) + 1; 3277 document.body.removeChild(div); 3278 } else { 3279 ret.width = this.width; 3280 ret.height = this.height; 3281 } 3282 return ret; 3283 } 3284 }); 3285 3286 /** 3287 * @scope enchant.Map.prototype 3288 */ 3289 enchant.Map = enchant.Class.create(enchant.Entity, { 3290 /** 3291 * @name enchant.Map 3292 * @class 3293 * A class to create and display maps from a tile set. 3294 * @param {Number} tileWidth Tile width. 3295 * @param {Number} tileHeight Tile height. 3296 * @constructs 3297 * @extends enchant.Entity 3298 */ 3299 initialize: function(tileWidth, tileHeight) { 3300 var core = enchant.Core.instance; 3301 3302 enchant.Entity.call(this); 3303 3304 var surface = new enchant.Surface(core.width, core.height); 3305 this._surface = surface; 3306 var canvas = surface._element; 3307 canvas.style.position = 'absolute'; 3308 if (enchant.ENV.RETINA_DISPLAY && core.scale === 2) { 3309 canvas.width = core.width * 2; 3310 canvas.height = core.height * 2; 3311 this._style.webkitTransformOrigin = '0 0'; 3312 this._style.webkitTransform = 'scale(0.5)'; 3313 } else { 3314 canvas.width = core.width; 3315 canvas.height = core.height; 3316 } 3317 this._context = canvas.getContext('2d'); 3318 3319 this._tileWidth = tileWidth || 0; 3320 this._tileHeight = tileHeight || 0; 3321 this._image = null; 3322 this._data = [ 3323 [ 3324 [] 3325 ] 3326 ]; 3327 this._dirty = false; 3328 this._tight = false; 3329 3330 this.touchEnabled = false; 3331 3332 /** 3333 * Two dimensional array to store if collision detection should be performed for a tile. 3334 * @type Number[][] 3335 */ 3336 this.collisionData = null; 3337 3338 this._listeners['render'] = null; 3339 this.addEventListener('render', function() { 3340 if(this._dirty) { 3341 this._previousOffsetX = this._previousOffsetY = null; 3342 } 3343 }); 3344 }, 3345 /** 3346 * Set map data. 3347 * Sets the tile data, whereas the data (two-dimensional array with indizes starting from 0) 3348 * is mapped on the image starting from the upper left corner. 3349 * When more than one map data array is set, they are displayed in reverse order. 3350 * @param {...Number[][]} data Two-dimensional array of tile indizes. Multiple designations possible. 3351 */ 3352 loadData: function(data) { 3353 this._data = Array.prototype.slice.apply(arguments); 3354 this._dirty = true; 3355 3356 this._tight = false; 3357 for (var i = 0, len = this._data.length; i < len; i++) { 3358 var c = 0; 3359 data = this._data[i]; 3360 for (var y = 0, l = data.length; y < l; y++) { 3361 for (var x = 0, ll = data[y].length; x < ll; x++) { 3362 if (data[y][x] >= 0) { 3363 c++; 3364 } 3365 } 3366 } 3367 if (c / (data.length * data[0].length) > 0.2) { 3368 this._tight = true; 3369 break; 3370 } 3371 } 3372 }, 3373 /** 3374 * Checks what tile is present at the given position. 3375 * @param {Number} x x coordinates of the point on the map. 3376 * @param {Number} y y coordinates of the point on the map. 3377 * @return {*} The tile data for the given position. 3378 */ 3379 checkTile: function(x, y) { 3380 if (x < 0 || this.width <= x || y < 0 || this.height <= y) { 3381 return false; 3382 } 3383 var width = this._image.width; 3384 var height = this._image.height; 3385 var tileWidth = this._tileWidth || width; 3386 var tileHeight = this._tileHeight || height; 3387 x = x / tileWidth | 0; 3388 y = y / tileHeight | 0; 3389 // return this._data[y][x]; 3390 var data = this._data[0]; 3391 return data[y][x]; 3392 }, 3393 /** 3394 * Judges whether or not obstacles are on top of Map. 3395 * @param {Number} x x coordinates of detection spot on map. 3396 * @param {Number} y y coordinates of detection spot on map. 3397 * @return {Boolean} True, if there are obstacles. 3398 */ 3399 hitTest: function(x, y) { 3400 if (x < 0 || this.width <= x || y < 0 || this.height <= y) { 3401 return false; 3402 } 3403 var width = this._image.width; 3404 var height = this._image.height; 3405 var tileWidth = this._tileWidth || width; 3406 var tileHeight = this._tileHeight || height; 3407 x = x / tileWidth | 0; 3408 y = y / tileHeight | 0; 3409 if (this.collisionData != null) { 3410 return this.collisionData[y] && !!this.collisionData[y][x]; 3411 } else { 3412 for (var i = 0, len = this._data.length; i < len; i++) { 3413 var data = this._data[i]; 3414 var n; 3415 if (data[y] != null && (n = data[y][x]) != null && 3416 0 <= n && n < (width / tileWidth | 0) * (height / tileHeight | 0)) { 3417 return true; 3418 } 3419 } 3420 return false; 3421 } 3422 }, 3423 /** 3424 * Image with which the tile set is displayed on the map. 3425 * @type enchant.Surface 3426 */ 3427 image: { 3428 get: function() { 3429 return this._image; 3430 }, 3431 set: function(image) { 3432 var core = enchant.Core.instance; 3433 3434 this._image = image; 3435 if (enchant.ENV.RETINA_DISPLAY && core.scale === 2) { 3436 var img = new enchant.Surface(image.width * 2, image.height * 2); 3437 var tileWidth = this._tileWidth || image.width; 3438 var tileHeight = this._tileHeight || image.height; 3439 var row = image.width / tileWidth | 0; 3440 var col = image.height / tileHeight | 0; 3441 for (var y = 0; y < col; y++) { 3442 for (var x = 0; x < row; x++) { 3443 img.draw(image, x * tileWidth, y * tileHeight, tileWidth, tileHeight, 3444 x * tileWidth * 2, y * tileHeight * 2, tileWidth * 2, tileHeight * 2); 3445 } 3446 } 3447 this._doubledImage = img; 3448 } 3449 this._dirty = true; 3450 } 3451 }, 3452 /** 3453 * Map tile width. 3454 * @type Number 3455 */ 3456 tileWidth: { 3457 get: function() { 3458 return this._tileWidth; 3459 }, 3460 set: function(tileWidth) { 3461 if(this._tileWidth !== tileWidth) { 3462 this._tileWidth = tileWidth; 3463 this._dirty = true; 3464 } 3465 } 3466 }, 3467 /** 3468 * Map tile height. 3469 * @type Number 3470 */ 3471 tileHeight: { 3472 get: function() { 3473 return this._tileHeight; 3474 }, 3475 set: function(tileHeight) { 3476 if(this._tileHeight !== tileHeight) { 3477 this._tileHeight = tileHeight; 3478 this._dirty = true; 3479 } 3480 } 3481 }, 3482 /** 3483 * @private 3484 */ 3485 width: { 3486 get: function() { 3487 return this._tileWidth * this._data[0][0].length; 3488 } 3489 }, 3490 /** 3491 * @private 3492 */ 3493 height: { 3494 get: function() { 3495 return this._tileHeight * this._data[0].length; 3496 } 3497 }, 3498 /** 3499 * @private 3500 */ 3501 redraw: function(x, y, width, height) { 3502 if (this._image == null) { 3503 return; 3504 } 3505 3506 var image, tileWidth, tileHeight, dx, dy; 3507 if (this._doubledImage) { 3508 image = this._doubledImage; 3509 tileWidth = this._tileWidth * 2; 3510 tileHeight = this._tileHeight * 2; 3511 dx = -this._offsetX * 2; 3512 dy = -this._offsetY * 2; 3513 x *= 2; 3514 y *= 2; 3515 width *= 2; 3516 height *= 2; 3517 } else { 3518 image = this._image; 3519 tileWidth = this._tileWidth; 3520 tileHeight = this._tileHeight; 3521 dx = -this._offsetX; 3522 dy = -this._offsetY; 3523 } 3524 var row = image.width / tileWidth | 0; 3525 var col = image.height / tileHeight | 0; 3526 var left = Math.max((x + dx) / tileWidth | 0, 0); 3527 var top = Math.max((y + dy) / tileHeight | 0, 0); 3528 var right = Math.ceil((x + dx + width) / tileWidth); 3529 var bottom = Math.ceil((y + dy + height) / tileHeight); 3530 3531 var source = image._element; 3532 var context = this._context; 3533 var canvas = context.canvas; 3534 context.clearRect(x, y, width, height); 3535 for (var i = 0, len = this._data.length; i < len; i++) { 3536 var data = this._data[i]; 3537 var r = Math.min(right, data[0].length); 3538 var b = Math.min(bottom, data.length); 3539 for (y = top; y < b; y++) { 3540 for (x = left; x < r; x++) { 3541 var n = data[y][x]; 3542 if (0 <= n && n < row * col) { 3543 var sx = (n % row) * tileWidth; 3544 var sy = (n / row | 0) * tileHeight; 3545 context.drawImage(source, sx, sy, tileWidth, tileHeight, 3546 x * tileWidth - dx, y * tileHeight - dy, tileWidth, tileHeight); 3547 } 3548 } 3549 } 3550 } 3551 }, 3552 /** 3553 * @private 3554 */ 3555 updateBuffer: function() { 3556 if (this._visible === undefined || this._visible) { 3557 var core = enchant.Core.instance; 3558 if (this._dirty || this._previousOffsetX == null) { 3559 this.redraw(0, 0, core.width, core.height); 3560 } else if (this._offsetX !== this._previousOffsetX || 3561 this._offsetY !== this._previousOffsetY) { 3562 if (this._tight) { 3563 var x = -this._offsetX; 3564 var y = -this._offsetY; 3565 var px = -this._previousOffsetX; 3566 var py = -this._previousOffsetY; 3567 var w1 = x - px + core.width; 3568 var w2 = px - x + core.width; 3569 var h1 = y - py + core.height; 3570 var h2 = py - y + core.height; 3571 if (w1 > this._tileWidth && w2 > this._tileWidth && 3572 h1 > this._tileHeight && h2 > this._tileHeight) { 3573 var sx, sy, dx, dy, sw, sh; 3574 if (w1 < w2) { 3575 sx = 0; 3576 dx = px - x; 3577 sw = w1; 3578 } else { 3579 sx = x - px; 3580 dx = 0; 3581 sw = w2; 3582 } 3583 if (h1 < h2) { 3584 sy = 0; 3585 dy = py - y; 3586 sh = h1; 3587 } else { 3588 sy = y - py; 3589 dy = 0; 3590 sh = h2; 3591 } 3592 3593 if (core._buffer == null) { 3594 core._buffer = document.createElement('canvas'); 3595 core._buffer.width = this._context.canvas.width; 3596 core._buffer.height = this._context.canvas.height; 3597 } 3598 var context = core._buffer.getContext('2d'); 3599 if (this._doubledImage) { 3600 context.clearRect(0, 0, sw * 2, sh * 2); 3601 context.drawImage(this._context.canvas, 3602 sx * 2, sy * 2, sw * 2, sh * 2, 0, 0, sw * 2, sh * 2); 3603 context = this._context; 3604 context.clearRect(dx * 2, dy * 2, sw * 2, sh * 2); 3605 context.drawImage(core._buffer, 3606 0, 0, sw * 2, sh * 2, dx * 2, dy * 2, sw * 2, sh * 2); 3607 } else { 3608 context.clearRect(0, 0, sw, sh); 3609 context.drawImage(this._context.canvas, 3610 sx, sy, sw, sh, 0, 0, sw, sh); 3611 context = this._context; 3612 context.clearRect(dx, dy, sw, sh); 3613 context.drawImage(core._buffer, 3614 0, 0, sw, sh, dx, dy, sw, sh); 3615 } 3616 3617 if (dx === 0) { 3618 this.redraw(sw, 0, core.width - sw, core.height); 3619 } else { 3620 this.redraw(0, 0, core.width - sw, core.height); 3621 } 3622 if (dy === 0) { 3623 this.redraw(0, sh, core.width, core.height - sh); 3624 } else { 3625 this.redraw(0, 0, core.width, core.height - sh); 3626 } 3627 } else { 3628 this.redraw(0, 0, core.width, core.height); 3629 } 3630 } else { 3631 this.redraw(0, 0, core.width, core.height); 3632 } 3633 } 3634 this._previousOffsetX = this._offsetX; 3635 this._previousOffsetY = this._offsetY; 3636 } 3637 }, 3638 cvsRender: function(ctx) { 3639 if (this.width !== 0 && this.height !== 0) { 3640 var core = enchant.Core.instance; 3641 this.updateBuffer(); 3642 ctx.save(); 3643 ctx.setTransform(1, 0, 0, 1, 0, 0); 3644 var cvs = this._context.canvas; 3645 ctx.drawImage(cvs, 0, 0, core.width, core.height); 3646 ctx.restore(); 3647 } 3648 }, 3649 domRender: function(element) { 3650 if (this._image) { 3651 this.updateBuffer(); 3652 this._style['background-image'] = this._surface._css; 3653 // bad performance 3654 this._style[enchant.ENV.VENDOR_PREFIX + 'Transform'] = 'matrix(1, 0, 0, 1, 0, 0)'; 3655 } 3656 } 3657 }); 3658 3659 3660 /** 3661 * @scope enchant.Group.prototype 3662 */ 3663 enchant.Group = enchant.Class.create(enchant.Node, { 3664 /** 3665 * @name enchant.Group 3666 * @class 3667 * A class that can hold multiple {@link enchant.Node}. 3668 * 3669 * @example 3670 * var stage = new Group(); 3671 * stage.addChild(player); 3672 * stage.addChild(enemy); 3673 * stage.addChild(map); 3674 * stage.addEventListener('enterframe', function() { 3675 * // Moves the entire frame in according to the player's coordinates. 3676 * if (this.x > 64 - player.x) { 3677 * this.x = 64 - player.x; 3678 * } 3679 * }); 3680 * @constructs 3681 * @extends enchant.Node 3682 */ 3683 initialize: function() { 3684 /** 3685 * Child Nodes. 3686 * @type enchant.Node[] 3687 */ 3688 this.childNodes = []; 3689 3690 enchant.Node.call(this); 3691 3692 this._rotation = 0; 3693 this._scaleX = 1; 3694 this._scaleY = 1; 3695 3696 this._originX = null; 3697 this._originY = null; 3698 3699 this.__dirty = false; 3700 3701 [enchant.Event.ADDED_TO_SCENE, enchant.Event.REMOVED_FROM_SCENE] 3702 .forEach(function(event) { 3703 this.addEventListener(event, function(e) { 3704 this.childNodes.forEach(function(child) { 3705 child.scene = this.scene; 3706 child.dispatchEvent(e); 3707 }, this); 3708 }); 3709 }, this); 3710 }, 3711 /** 3712 * Adds a Node to the Group. 3713 * @param {enchant.Node} node Node to be added. 3714 */ 3715 addChild: function(node) { 3716 if (node.parentNode) { 3717 node.parentNode.removeChild(node); 3718 } 3719 this.childNodes.push(node); 3720 node.parentNode = this; 3721 var childAdded = new enchant.Event('childadded'); 3722 childAdded.node = node; 3723 childAdded.next = null; 3724 this.dispatchEvent(childAdded); 3725 node.dispatchEvent(new enchant.Event('added')); 3726 if (this.scene) { 3727 node.scene = this.scene; 3728 var addedToScene = new enchant.Event('addedtoscene'); 3729 node.dispatchEvent(addedToScene); 3730 } 3731 }, 3732 /** 3733 * Incorporates Node into Group. 3734 * @param {enchant.Node} node Node to be incorporated. 3735 * @param {enchant.Node} reference Node in position before insertion. 3736 */ 3737 insertBefore: function(node, reference) { 3738 if (node.parentNode) { 3739 node.parentNode.removeChild(node); 3740 } 3741 var i = this.childNodes.indexOf(reference); 3742 if (i !== -1) { 3743 this.childNodes.splice(i, 0, node); 3744 node.parentNode = this; 3745 var childAdded = new enchant.Event('childadded'); 3746 childAdded.node = node; 3747 childAdded.next = reference; 3748 this.dispatchEvent(childAdded); 3749 node.dispatchEvent(new enchant.Event('added')); 3750 if (this.scene) { 3751 node.scene = this.scene; 3752 var addedToScene = new enchant.Event('addedtoscene'); 3753 node.dispatchEvent(addedToScene); 3754 } 3755 } else { 3756 this.addChild(node); 3757 } 3758 }, 3759 /** 3760 * Remove a Node from the Group. 3761 * @param {enchant.Node} node Node to be deleted. 3762 */ 3763 removeChild: function(node) { 3764 var i; 3765 if ((i = this.childNodes.indexOf(node)) !== -1) { 3766 this.childNodes.splice(i, 1); 3767 node.parentNode = null; 3768 var childRemoved = new enchant.Event('childremoved'); 3769 childRemoved.node = node; 3770 this.dispatchEvent(childRemoved); 3771 node.dispatchEvent(new enchant.Event('removed')); 3772 if (this.scene) { 3773 node.scene = null; 3774 var removedFromScene = new enchant.Event('removedfromscene'); 3775 node.dispatchEvent(removedFromScene); 3776 } 3777 } 3778 }, 3779 /** 3780 * The Node which is the first child. 3781 * @type enchant.Node 3782 */ 3783 firstChild: { 3784 get: function() { 3785 return this.childNodes[0]; 3786 } 3787 }, 3788 /** 3789 * The Node which is the last child. 3790 * @type enchant.Node 3791 */ 3792 lastChild: { 3793 get: function() { 3794 return this.childNodes[this.childNodes.length - 1]; 3795 } 3796 }, 3797 /** 3798 * Group rotation angle (degree). 3799 * @type Number 3800 */ 3801 rotation: { 3802 get: function() { 3803 return this._rotation; 3804 }, 3805 set: function(rotation) { 3806 if(this._rotation !== rotation) { 3807 this._rotation = rotation; 3808 this._dirty = true; 3809 } 3810 } 3811 }, 3812 /** 3813 * Scaling factor on the x axis of the Group. 3814 * @type Number 3815 * @see enchant.Group#originX 3816 * @see enchant.Group#originY 3817 */ 3818 scaleX: { 3819 get: function() { 3820 return this._scaleX; 3821 }, 3822 set: function(scale) { 3823 if(this._scaleX !== scale) { 3824 this._scaleX = scale; 3825 this._dirty = true; 3826 } 3827 } 3828 }, 3829 /** 3830 * Scaling factor on the y axis of the Group. 3831 * @type Number 3832 * @see enchant.Group#originX 3833 * @see enchant.Group#originY 3834 */ 3835 scaleY: { 3836 get: function() { 3837 return this._scaleY; 3838 }, 3839 set: function(scale) { 3840 if(this._scaleY !== scale) { 3841 this._scaleY = scale; 3842 this._dirty = true; 3843 } 3844 } 3845 }, 3846 /** 3847 * origin point of rotation, scaling 3848 * @type Number 3849 */ 3850 originX: { 3851 get: function() { 3852 return this._originX; 3853 }, 3854 set: function(originX) { 3855 if(this._originX !== originX) { 3856 this._originX = originX; 3857 this._dirty = true; 3858 } 3859 } 3860 }, 3861 /** 3862 * origin point of rotation, scaling 3863 * @type Number 3864 */ 3865 originY: { 3866 get: function() { 3867 return this._originY; 3868 }, 3869 set: function(originY) { 3870 if(this._originY !== originY) { 3871 this._originY = originY; 3872 this._dirty = true; 3873 } 3874 } 3875 }, 3876 /**#nocode+*/ 3877 _dirty: { 3878 get: function() { 3879 return this.__dirty; 3880 }, 3881 set: function(dirty) { 3882 dirty = !!dirty; 3883 this.__dirty = dirty; 3884 if (dirty) { 3885 for (var i = 0, l = this.childNodes.length; i < l; i++) { 3886 this.childNodes[i]._dirty = true; 3887 } 3888 } 3889 } 3890 } 3891 /**#nocode-*/ 3892 }); 3893 3894 enchant.Matrix = enchant.Class.create({ 3895 initialize: function() { 3896 this.reset(); 3897 }, 3898 reset: function() { 3899 this.stack = []; 3900 this.stack.push([ 1, 0, 0, 1, 0, 0 ]); 3901 }, 3902 makeTransformMatrix: function(node, dest) { 3903 var x = node._x; 3904 var y = node._y; 3905 var width = node.width || 0; 3906 var height = node.height || 0; 3907 var rotation = node._rotation || 0; 3908 var scaleX = (typeof node._scaleX === 'number') ? node._scaleX : 1; 3909 var scaleY = (typeof node._scaleY === 'number') ? node._scaleY : 1; 3910 var theta = rotation * Math.PI / 180; 3911 var tmpcos = Math.cos(theta); 3912 var tmpsin = Math.sin(theta); 3913 var w = (typeof node._originX === 'number') ? node._originX : width / 2; 3914 var h = (typeof node._originY === 'number') ? node._originY : height / 2; 3915 var a = scaleX * tmpcos; 3916 var b = scaleX * tmpsin; 3917 var c = scaleY * tmpsin; 3918 var d = scaleY * tmpcos; 3919 dest[0] = a; 3920 dest[1] = b; 3921 dest[2] = -c; 3922 dest[3] = d; 3923 dest[4] = (-a * w + c * h + x + w); 3924 dest[5] = (-b * w - d * h + y + h); 3925 }, 3926 multiply: function(m1, m2, dest) { 3927 var a11 = m1[0], a21 = m1[2], adx = m1[4], 3928 a12 = m1[1], a22 = m1[3], ady = m1[5]; 3929 var b11 = m2[0], b21 = m2[2], bdx = m2[4], 3930 b12 = m2[1], b22 = m2[3], bdy = m2[5]; 3931 3932 dest[0] = a11 * b11 + a21 * b12; 3933 dest[1] = a12 * b11 + a22 * b12; 3934 dest[2] = a11 * b21 + a21 * b22; 3935 dest[3] = a12 * b21 + a22 * b22; 3936 dest[4] = a11 * bdx + a21 * bdy + adx; 3937 dest[5] = a12 * bdx + a22 * bdy + ady; 3938 }, 3939 multiplyVec: function(mat, vec, dest) { 3940 var x = vec[0], y = vec[1]; 3941 var m11 = mat[0], m21 = mat[2], mdx = mat[4], 3942 m12 = mat[1], m22 = mat[3], mdy = mat[5]; 3943 dest[0] = m11 * x + m21 * y + mdx; 3944 dest[1] = m12 * x + m22 * y + mdy; 3945 } 3946 }); 3947 enchant.Matrix.instance = new enchant.Matrix(); 3948 3949 enchant.DetectColorManager = enchant.Class.create({ 3950 initialize: function(reso, max) { 3951 this.reference = []; 3952 this.colorResolution = reso || 16; 3953 this.max = max || 1; 3954 this.capacity = Math.pow(this.colorResolution, 3); 3955 for (var i = 1, l = this.capacity; i < l; i++) { 3956 this.reference[i] = null; 3957 } 3958 }, 3959 attachDetectColor: function(sprite) { 3960 var i = this.reference.indexOf(null); 3961 if (i === -1) { 3962 i = 1; 3963 } 3964 this.reference[i] = sprite; 3965 return this._getColor(i); 3966 }, 3967 detachDetectColor: function(sprite) { 3968 var i = this.reference.indexOf(sprite); 3969 if (i !== -1) { 3970 this.reference[i] = null; 3971 } 3972 }, 3973 _getColor: function(n) { 3974 var C = this.colorResolution; 3975 var d = C / this.max; 3976 return [ 3977 parseInt((n / C / C) % C, 10) / d, 3978 parseInt((n / C) % C, 10) / d, 3979 parseInt(n % C, 10) / d, 3980 1.0 3981 ]; 3982 }, 3983 _decodeDetectColor: function(color, i) { 3984 i = i || 0; 3985 var C = this.colorResolution; 3986 return ~~(color[i] * C * C * C / 256) + 3987 ~~(color[i + 1] * C * C / 256) + 3988 ~~(color[i + 2] * C / 256); 3989 }, 3990 getSpriteByColor: function(color) { 3991 return this.reference[this._decodeDetectColor(color)]; 3992 }, 3993 getSpriteByColors: function(rgba) { 3994 var i, l, id, result, 3995 score = 0, 3996 found = {}; 3997 3998 for (i = 0, l = rgba.length; i < l; i+= 4) { 3999 id = this._decodeDetectColor(rgba, i); 4000 found[id] = (found[id] || 0) + 1; 4001 } 4002 for (id in found) { 4003 if (found[id] > score) { 4004 score = found[id]; 4005 result = id; 4006 } 4007 } 4008 4009 return this.reference[result]; 4010 } 4011 }); 4012 4013 enchant.DomManager = enchant.Class.create({ 4014 initialize: function(node, elementDefinition) { 4015 var core = enchant.Core.instance; 4016 this.layer = null; 4017 this.targetNode = node; 4018 if (typeof elementDefinition === 'string') { 4019 this.element = document.createElement(elementDefinition); 4020 } else if (elementDefinition instanceof HTMLElement) { 4021 this.element = elementDefinition; 4022 } 4023 this.style = this.element.style; 4024 this.style.position = 'absolute'; 4025 this.style[enchant.ENV.VENDOR_PREFIX + 'TransformOrigin'] = '0px 0px'; 4026 if (core._debug) { 4027 this.style.border = '1px solid blue'; 4028 this.style.margin = '-1px'; 4029 } 4030 4031 var manager = this; 4032 this._setDomTarget = function() { 4033 manager.layer._touchEventTarget = manager.targetNode; 4034 }; 4035 this._attachEvent(); 4036 }, 4037 getDomElement: function() { 4038 return this.element; 4039 }, 4040 getDomElementAsNext: function() { 4041 return this.element; 4042 }, 4043 getNextManager: function(manager) { 4044 var i = this.targetNode.parentNode.childNodes.indexOf(manager.targetNode); 4045 if (i !== this.targetNode.parentNode.childNodes.length - 1) { 4046 return this.targetNode.parentNode.childNodes[i + 1]._domManager; 4047 } else { 4048 return null; 4049 } 4050 }, 4051 addManager: function(childManager, nextManager) { 4052 var nextElement; 4053 if (nextManager) { 4054 nextElement = nextManager.getDomElementAsNext(); 4055 } 4056 var element = childManager.getDomElement(); 4057 if (element instanceof Array) { 4058 element.forEach(function(child) { 4059 if (nextElement) { 4060 this.element.insertBefore(child, nextElement); 4061 } else { 4062 this.element.appendChild(child); 4063 } 4064 }, this); 4065 } else { 4066 if (nextElement) { 4067 this.element.insertBefore(element, nextElement); 4068 } else { 4069 this.element.appendChild(element); 4070 } 4071 } 4072 this.setLayer(this.layer); 4073 }, 4074 removeManager: function(childManager) { 4075 if (childManager instanceof enchant.DomlessManager) { 4076 childManager._domRef.forEach(function(element) { 4077 this.element.removeChild(element); 4078 }, this); 4079 } else { 4080 this.element.removeChild(childManager.element); 4081 } 4082 this.setLayer(this.layer); 4083 }, 4084 setLayer: function(layer) { 4085 this.layer = layer; 4086 var node = this.targetNode; 4087 var manager; 4088 if (node.childNodes) { 4089 for (var i = 0, l = node.childNodes.length; i < l; i++) { 4090 manager = node.childNodes[i]._domManager; 4091 if (manager) { 4092 manager.setLayer(layer); 4093 } 4094 } 4095 } 4096 }, 4097 render: function(inheritMat) { 4098 var node = this.targetNode; 4099 var matrix = enchant.Matrix.instance; 4100 var stack = matrix.stack; 4101 var dest = []; 4102 matrix.makeTransformMatrix(node, dest); 4103 matrix.multiply(stack[stack.length - 1], dest, dest); 4104 matrix.multiply(inheritMat, dest, inheritMat); 4105 node._matrix = inheritMat; 4106 var ox = (typeof node._originX === 'number') ? node._originX : node.width / 2 || 0; 4107 var oy = (typeof node._originY === 'number') ? node._originY : node.height / 2 || 0; 4108 var vec = [ ox, oy ]; 4109 matrix.multiplyVec(dest, vec, vec); 4110 4111 node._offsetX = vec[0] - ox; 4112 node._offsetY = vec[1] - oy; 4113 if(node.parentNode && !(node.parentNode instanceof enchant.Group)) { 4114 node._offsetX += node.parentNode._offsetX; 4115 node._offsetY += node.parentNode._offsetY; 4116 } 4117 if (node._dirty) { 4118 this.style[enchant.ENV.VENDOR_PREFIX + 'Transform'] = 'matrix(' + 4119 dest[0].toFixed(10) + ',' + 4120 dest[1].toFixed(10) + ',' + 4121 dest[2].toFixed(10) + ',' + 4122 dest[3].toFixed(10) + ',' + 4123 dest[4].toFixed(10) + ',' + 4124 dest[5].toFixed(10) + 4125 ')'; 4126 } 4127 this.domRender(); 4128 }, 4129 domRender: function() { 4130 var node = this.targetNode; 4131 if(!node._style) { 4132 node._style = {}; 4133 } 4134 if(!node.__styleStatus) { 4135 node.__styleStatus = {}; 4136 } 4137 if (node.width !== null) { 4138 node._style.width = node.width + 'px'; 4139 } 4140 if (node.height !== null) { 4141 node._style.height = node.height + 'px'; 4142 } 4143 node._style.opacity = node._opacity; 4144 node._style['background-color'] = node._backgroundColor; 4145 if (typeof node._visible !== 'undefined') { 4146 node._style.display = node._visible ? 'block' : 'none'; 4147 } 4148 if (typeof node.domRender === 'function') { 4149 node.domRender(this.element); 4150 } 4151 var value; 4152 for (var prop in node._style) { 4153 value = node._style[prop]; 4154 if(node.__styleStatus[prop] !== value && value != null) { 4155 this.style.setProperty(prop, '' + value); 4156 node.__styleStatus[prop] = value; 4157 } 4158 } 4159 }, 4160 _attachEvent: function() { 4161 if (enchant.ENV.TOUCH_ENABLED) { 4162 this.element.addEventListener('touchstart', this._setDomTarget, true); 4163 } 4164 this.element.addEventListener('mousedown', this._setDomTarget, true); 4165 }, 4166 _detachEvent: function() { 4167 if (enchant.ENV.TOUCH_ENABLED) { 4168 this.element.removeEventListener('touchstart', this._setDomTarget, true); 4169 } 4170 this.element.removeEventListener('mousedown', this._setDomTarget, true); 4171 }, 4172 remove: function() { 4173 this._detachEvent(); 4174 this.element = this.style = this.targetNode = null; 4175 } 4176 }); 4177 4178 enchant.DomlessManager = enchant.Class.create({ 4179 initialize: function(node) { 4180 this._domRef = []; 4181 this.targetNode = node; 4182 }, 4183 _register: function(element, nextElement) { 4184 var i = this._domRef.indexOf(nextElement); 4185 if (element instanceof Array) { 4186 if (i === -1) { 4187 Array.prototype.push.apply(this._domRef, element); 4188 } else { 4189 Array.prototype.splice.apply(this._domRef, [i, 0].concat(element)); 4190 } 4191 } else { 4192 if (i === -1) { 4193 this._domRef.push(element); 4194 } else { 4195 this._domRef.splice(i, 0, element); 4196 } 4197 } 4198 }, 4199 getNextManager: function(manager) { 4200 var i = this.targetNode.parentNode.childNodes.indexOf(manager.targetNode); 4201 if (i !== this.targetNode.parentNode.childNodes.length - 1) { 4202 return this.targetNode.parentNode.childNodes[i + 1]._domManager; 4203 } else { 4204 return null; 4205 } 4206 }, 4207 getDomElement: function() { 4208 var ret = []; 4209 this.targetNode.childNodes.forEach(function(child) { 4210 ret = ret.concat(child._domManager.getDomElement()); 4211 }); 4212 return ret; 4213 }, 4214 getDomElementAsNext: function() { 4215 if (this._domRef.length) { 4216 return this._domRef[0]; 4217 } else { 4218 var nextManager = this.getNextManager(this); 4219 if (nextManager) { 4220 return nextManager.element; 4221 } else { 4222 return null; 4223 } 4224 } 4225 }, 4226 addManager: function(childManager, nextManager) { 4227 var parentNode = this.targetNode.parentNode; 4228 if (parentNode) { 4229 if (nextManager === null) { 4230 nextManager = this.getNextManager(this); 4231 } 4232 if (parentNode instanceof enchant.Scene) { 4233 parentNode._layers.Dom._domManager.addManager(childManager, nextManager); 4234 } else { 4235 parentNode._domManager.addManager(childManager, nextManager); 4236 } 4237 } 4238 var nextElement = nextManager ? nextManager.getDomElementAsNext() : null; 4239 this._register(childManager.getDomElement(), nextElement); 4240 this.setLayer(this.layer); 4241 }, 4242 removeManager: function(childManager) { 4243 var dom; 4244 var i = this._domRef.indexOf(childManager.element); 4245 if (i !== -1) { 4246 dom = this._domRef[i]; 4247 dom.parentNode.removeChild(dom); 4248 this._domRef.splice(i, 1); 4249 } 4250 this.setLayer(this.layer); 4251 }, 4252 setLayer: function(layer) { 4253 this.layer = layer; 4254 var node = this.targetNode; 4255 var manager; 4256 if (node.childNodes) { 4257 for (var i = 0, l = node.childNodes.length; i < l; i++) { 4258 manager = node.childNodes[i]._domManager; 4259 if (manager) { 4260 manager.setLayer(layer); 4261 } 4262 } 4263 } 4264 }, 4265 render: function(inheritMat) { 4266 var matrix = enchant.Matrix.instance; 4267 var stack = matrix.stack; 4268 var node = this.targetNode; 4269 var dest = []; 4270 matrix.makeTransformMatrix(node, dest); 4271 matrix.multiply(stack[stack.length - 1], dest, dest); 4272 matrix.multiply(inheritMat, dest, inheritMat); 4273 node._matrix = inheritMat; 4274 var ox = (typeof node._originX === 'number') ? node._originX : node.width / 2 || 0; 4275 var oy = (typeof node._originY === 'number') ? node._originY : node.height / 2 || 0; 4276 var vec = [ ox, oy ]; 4277 matrix.multiplyVec(dest, vec, vec); 4278 node._offsetX = vec[0] - ox; 4279 node._offsetY = vec[1] - oy; 4280 stack.push(dest); 4281 }, 4282 remove: function() { 4283 this._domRef = []; 4284 this.targetNode = null; 4285 } 4286 }); 4287 4288 enchant.DomLayer = enchant.Class.create(enchant.Group, { 4289 initialize: function() { 4290 var core = enchant.Core.instance; 4291 enchant.Group.call(this); 4292 4293 this._touchEventTarget = null; 4294 4295 this._element = document.createElement('div'); 4296 this._element.style.position = 'absolute'; 4297 4298 this._domManager = new enchant.DomManager(this, this._element); 4299 this._domManager.layer = this; 4300 4301 this.width = core.width; 4302 this.height = core.height; 4303 4304 var touch = [ 4305 enchant.Event.TOUCH_START, 4306 enchant.Event.TOUCH_MOVE, 4307 enchant.Event.TOUCH_END 4308 ]; 4309 4310 touch.forEach(function(type) { 4311 this.addEventListener(type, function(e) { 4312 if (this._scene) { 4313 this._scene.dispatchEvent(e); 4314 } 4315 }); 4316 }, this); 4317 4318 var __onchildadded = function(e) { 4319 var child = e.node; 4320 var next = e.next; 4321 var self = e.target; 4322 var nextManager = next ? next._domManager : null; 4323 enchant.DomLayer._attachDomManager(child, __onchildadded, __onchildremoved); 4324 self._domManager.addManager(child._domManager, nextManager); 4325 var render = new enchant.Event(enchant.Event.RENDER); 4326 child._dirty = true; 4327 self._domManager.layer._rendering(child, render); 4328 }; 4329 4330 var __onchildremoved = function(e) { 4331 var child = e.node; 4332 var self = e.target; 4333 self._domManager.removeManager(child._domManager); 4334 enchant.DomLayer._detachDomManager(child, __onchildadded, __onchildremoved); 4335 }; 4336 4337 this.addEventListener('childremoved', __onchildremoved); 4338 this.addEventListener('childadded', __onchildadded); 4339 4340 }, 4341 width: { 4342 get: function() { 4343 return this._width; 4344 }, 4345 set: function(width) { 4346 this._width = width; 4347 this._element.style.width = width + 'px'; 4348 } 4349 }, 4350 height: { 4351 get: function() { 4352 return this._height; 4353 }, 4354 set: function(height) { 4355 this._height = height; 4356 this._element.style.height = height + 'px'; 4357 } 4358 }, 4359 addChild: function(node) { 4360 this.childNodes.push(node); 4361 node.parentNode = this; 4362 var childAdded = new enchant.Event('childadded'); 4363 childAdded.node = node; 4364 childAdded.next = null; 4365 this.dispatchEvent(childAdded); 4366 node.dispatchEvent(new enchant.Event('added')); 4367 if (this.scene) { 4368 node.scene = this.scene; 4369 var addedToScene = new enchant.Event('addedtoscene'); 4370 node.dispatchEvent(addedToScene); 4371 } 4372 }, 4373 insertBefore: function(node, reference) { 4374 var i = this.childNodes.indexOf(reference); 4375 if (i !== -1) { 4376 this.childNodes.splice(i, 0, node); 4377 node.parentNode = this; 4378 var childAdded = new enchant.Event('childadded'); 4379 childAdded.node = node; 4380 childAdded.next = reference; 4381 this.dispatchEvent(childAdded); 4382 node.dispatchEvent(new enchant.Event('added')); 4383 if (this.scene) { 4384 node.scene = this.scene; 4385 var addedToScene = new enchant.Event('addedtoscene'); 4386 node.dispatchEvent(addedToScene); 4387 } 4388 } else { 4389 this.addChild(node); 4390 } 4391 }, 4392 _startRendering: function() { 4393 this.addEventListener('exitframe', this._onexitframe); 4394 this._onexitframe(); 4395 }, 4396 _stopRendering: function() { 4397 this.removeEventListener('exitframe', this._onexitframe); 4398 this._onexitframe(); 4399 }, 4400 _onexitframe: function() { 4401 this._rendering(this, new enchant.Event(enchant.Event.RENDER)); 4402 }, 4403 _rendering: function(node, e, inheritMat) { 4404 var child; 4405 if (!inheritMat) { 4406 inheritMat = [ 1, 0, 0, 1, 0, 0 ]; 4407 } 4408 node.dispatchEvent(e); 4409 node._domManager.render(inheritMat); 4410 if (node.childNodes) { 4411 for (var i = 0, l = node.childNodes.length; i < l; i++) { 4412 child = node.childNodes[i]; 4413 this._rendering(child, e, inheritMat.slice()); 4414 } 4415 } 4416 if (node._domManager instanceof enchant.DomlessManager) { 4417 enchant.Matrix.instance.stack.pop(); 4418 } 4419 node._dirty = false; 4420 }, 4421 _determineEventTarget: function() { 4422 var target = this._touchEventTarget; 4423 this._touchEventTarget = null; 4424 return (target === this) ? null : target; 4425 } 4426 }); 4427 4428 enchant.DomLayer._attachDomManager = function(node, onchildadded, onchildremoved) { 4429 var child; 4430 if (!node._domManager) { 4431 node.addEventListener('childadded', onchildadded); 4432 node.addEventListener('childremoved', onchildremoved); 4433 if (node instanceof enchant.Group) { 4434 node._domManager = new enchant.DomlessManager(node); 4435 } else { 4436 if (node._element) { 4437 node._domManager = new enchant.DomManager(node, node._element); 4438 } else { 4439 node._domManager = new enchant.DomManager(node, 'div'); 4440 } 4441 } 4442 } 4443 if (node.childNodes) { 4444 for (var i = 0, l = node.childNodes.length; i < l; i++) { 4445 child = node.childNodes[i]; 4446 enchant.DomLayer._attachDomManager(child, onchildadded, onchildremoved); 4447 node._domManager.addManager(child._domManager, null); 4448 } 4449 } 4450 }; 4451 4452 enchant.DomLayer._detachDomManager = function(node, onchildadded, onchildremoved) { 4453 var child; 4454 node.removeEventListener('childadded', onchildadded); 4455 node.removeEventListener('childremoved', onchildremoved); 4456 if (node.childNodes) { 4457 for (var i = 0, l = node.childNodes.length; i < l; i++) { 4458 child = node.childNodes[i]; 4459 node._domManager.removeManager(child._domManager, null); 4460 enchant.DomLayer._detachDomManager(child, onchildadded, onchildremoved); 4461 } 4462 } 4463 node._domManager.remove(); 4464 delete node._domManager; 4465 }; 4466 4467 /** 4468 * @scope enchant.CanvasLayer.prototype 4469 */ 4470 enchant.CanvasLayer = enchant.Class.create(enchant.Group, { 4471 /** 4472 * @name enchant.CanvasLayer 4473 * @class 4474 * Class that uses the HTML Canvas for rendering. 4475 * The rendering of children will be replaced by the Canvas rendering. 4476 * @constructs 4477 * @extends enchant.Group 4478 */ 4479 initialize: function() { 4480 var core = enchant.Core.instance; 4481 4482 enchant.Group.call(this); 4483 4484 this._cvsCache = { 4485 matrix: [1, 0, 0, 1, 0, 0], 4486 detectColor: '#000000' 4487 }; 4488 this._cvsCache.layer = this; 4489 4490 this._element = document.createElement('canvas'); 4491 this._element.style.position = 'absolute'; 4492 // issue 179 4493 this._element.style.left = this._element.style.top = '0px'; 4494 4495 this._detect = document.createElement('canvas'); 4496 this._detect.style.position = 'absolute'; 4497 this._lastDetected = 0; 4498 4499 this.context = this._element.getContext('2d'); 4500 this._dctx = this._detect.getContext('2d'); 4501 this._setImageSmoothingEnable(); 4502 4503 this._colorManager = new enchant.DetectColorManager(16, 256); 4504 4505 this.width = core.width; 4506 this.height = core.height; 4507 4508 var touch = [ 4509 enchant.Event.TOUCH_START, 4510 enchant.Event.TOUCH_MOVE, 4511 enchant.Event.TOUCH_END 4512 ]; 4513 4514 touch.forEach(function(type) { 4515 this.addEventListener(type, function(e) { 4516 if (this._scene) { 4517 this._scene.dispatchEvent(e); 4518 } 4519 }); 4520 }, this); 4521 4522 var __onchildadded = function(e) { 4523 var child = e.node; 4524 var self = e.target; 4525 var layer; 4526 if (self instanceof enchant.CanvasLayer) { 4527 layer = self._scene._layers.Canvas; 4528 } else { 4529 layer = self.scene._layers.Canvas; 4530 } 4531 enchant.CanvasLayer._attachCache(child, layer, __onchildadded, __onchildremoved); 4532 var render = new enchant.Event(enchant.Event.RENDER); 4533 if (self._dirty) { 4534 self._updateCoordinate(); 4535 } 4536 child._dirty = true; 4537 enchant.Matrix.instance.stack.push(self._matrix); 4538 enchant.CanvasRenderer.instance.render(layer.context, child, render); 4539 enchant.Matrix.instance.stack.pop(self._matrix); 4540 }; 4541 4542 var __onchildremoved = function(e) { 4543 var child = e.node; 4544 var self = e.target; 4545 var layer; 4546 if (self instanceof enchant.CanvasLayer) { 4547 layer = self._scene._layers.Canvas; 4548 } else { 4549 layer = self.scene._layers.Canvas; 4550 } 4551 enchant.CanvasLayer._detachCache(child, layer, __onchildadded, __onchildremoved); 4552 }; 4553 4554 this.addEventListener('childremoved', __onchildremoved); 4555 this.addEventListener('childadded', __onchildadded); 4556 4557 }, 4558 /** 4559 * The width of the CanvasLayer. 4560 * @type Number 4561 */ 4562 width: { 4563 get: function() { 4564 return this._width; 4565 }, 4566 set: function(width) { 4567 this._width = width; 4568 this._element.width = this._detect.width = width; 4569 this._setImageSmoothingEnable(); 4570 } 4571 }, 4572 /** 4573 * The height of the CanvasLayer. 4574 * @type Number 4575 */ 4576 height: { 4577 get: function() { 4578 return this._height; 4579 }, 4580 set: function(height) { 4581 this._height = height; 4582 this._element.height = this._detect.height = height; 4583 this._setImageSmoothingEnable(); 4584 } 4585 }, 4586 addChild: function(node) { 4587 this.childNodes.push(node); 4588 node.parentNode = this; 4589 var childAdded = new enchant.Event('childadded'); 4590 childAdded.node = node; 4591 childAdded.next = null; 4592 this.dispatchEvent(childAdded); 4593 node.dispatchEvent(new enchant.Event('added')); 4594 }, 4595 insertBefore: function(node, reference) { 4596 var i = this.childNodes.indexOf(reference); 4597 if (i !== -1) { 4598 this.childNodes.splice(i, 0, node); 4599 node.parentNode = this; 4600 var childAdded = new enchant.Event('childadded'); 4601 childAdded.node = node; 4602 childAdded.next = reference; 4603 this.dispatchEvent(childAdded); 4604 node.dispatchEvent(new enchant.Event('added')); 4605 } else { 4606 this.addChild(node); 4607 } 4608 }, 4609 /** 4610 * @private 4611 */ 4612 _startRendering: function() { 4613 this.addEventListener('exitframe', this._onexitframe); 4614 this._onexitframe(); 4615 }, 4616 /** 4617 * @private 4618 */ 4619 _stopRendering: function() { 4620 this.removeEventListener('exitframe', this._onexitframe); 4621 this._onexitframe(); 4622 }, 4623 _onexitframe: function() { 4624 var core = enchant.Core.instance; 4625 var ctx = this.context; 4626 ctx.clearRect(0, 0, core.width, core.height); 4627 var render = new enchant.Event(enchant.Event.RENDER); 4628 enchant.CanvasRenderer.instance.render(ctx, this, render); 4629 }, 4630 _determineEventTarget: function(e) { 4631 return this._getEntityByPosition(e.x, e.y); 4632 }, 4633 _getEntityByPosition: function(x, y) { 4634 var core = enchant.Core.instance; 4635 var ctx = this._dctx; 4636 if (this._lastDetected < core.frame) { 4637 ctx.clearRect(0, 0, this.width, this.height); 4638 enchant.CanvasRenderer.instance.detectRender(ctx, this); 4639 this._lastDetected = core.frame; 4640 } 4641 var extra = enchant.ENV.COLOR_DETECTION_LEVEL - 1; 4642 var rgba = ctx.getImageData(x - extra, y - extra, 1 + extra * 2, 1 + extra * 2).data; 4643 return this._colorManager.getSpriteByColors(rgba); 4644 }, 4645 _setImageSmoothingEnable: function() { 4646 this._dctx.imageSmoothingEnabled = 4647 this._dctx.msImageSmoothingEnabled = 4648 this._dctx.mozImageSmoothingEnabled = 4649 this._dctx.webkitImageSmoothingEnabled = false; 4650 } 4651 }); 4652 4653 enchant.CanvasLayer._attachCache = function(node, layer, onchildadded, onchildremoved) { 4654 var child; 4655 if (!node._cvsCache) { 4656 node._cvsCache = {}; 4657 node._cvsCache.matrix = [ 1, 0, 0, 1, 0, 0 ]; 4658 node._cvsCache.detectColor = 'rgba(' + layer._colorManager.attachDetectColor(node) + ')'; 4659 node.addEventListener('childadded', onchildadded); 4660 node.addEventListener('childremoved', onchildremoved); 4661 } 4662 if (node.childNodes) { 4663 for (var i = 0, l = node.childNodes.length; i < l; i++) { 4664 child = node.childNodes[i]; 4665 enchant.CanvasLayer._attachCache(child, layer, onchildadded, onchildremoved); 4666 } 4667 } 4668 }; 4669 4670 enchant.CanvasLayer._detachCache = function(node, layer, onchildadded, onchildremoved) { 4671 var child; 4672 if (node._cvsCache) { 4673 layer._colorManager.detachDetectColor(node); 4674 node.removeEventListener('childadded', onchildadded); 4675 node.removeEventListener('childremoved', onchildremoved); 4676 delete node._cvsCache; 4677 } 4678 if (node.childNodes) { 4679 for (var i = 0, l = node.childNodes.length; i < l; i++) { 4680 child = node.childNodes[i]; 4681 enchant.CanvasLayer._detachCache(child, layer, onchildadded, onchildremoved); 4682 } 4683 } 4684 }; 4685 4686 enchant.CanvasRenderer = enchant.Class.create({ 4687 render: function(ctx, node, e) { 4688 var width, height, child; 4689 ctx.save(); 4690 node.dispatchEvent(e); 4691 // transform 4692 this.transform(ctx, node); 4693 if (typeof node._visible === 'undefined' || node._visible) { 4694 width = node.width; 4695 height = node.height; 4696 // composite 4697 if (node.compositeOperation) { 4698 ctx.globalCompositeOperation = node.compositeOperation; 4699 } 4700 ctx.globalAlpha = (typeof node._opacity === 'number') ? node._opacity : 1.0; 4701 // render 4702 if (node._backgroundColor) { 4703 ctx.fillStyle = node._backgroundColor; 4704 ctx.fillRect(0, 0, width, height); 4705 } 4706 4707 if (node.cvsRender) { 4708 node.cvsRender(ctx); 4709 } 4710 4711 if (enchant.Core.instance._debug && node._debugColor) { 4712 ctx.strokeStyle = node._debugColor; 4713 ctx.strokeRect(0, 0, width, height); 4714 } 4715 if (node._clipping) { 4716 ctx.beginPath(); 4717 ctx.rect(0, 0, width, height); 4718 ctx.clip(); 4719 } 4720 if (node.childNodes) { 4721 for (var i = 0, l = node.childNodes.length; i < l; i++) { 4722 child = node.childNodes[i]; 4723 this.render(ctx, child, e); 4724 } 4725 } 4726 } 4727 ctx.restore(); 4728 enchant.Matrix.instance.stack.pop(); 4729 }, 4730 detectRender: function(ctx, node) { 4731 var width, height, child; 4732 if (typeof node._visible === 'undefined' || node._visible) { 4733 width = node.width; 4734 height = node.height; 4735 ctx.save(); 4736 this.transform(ctx, node); 4737 ctx.fillStyle = node._cvsCache.detectColor; 4738 if (node._touchEnabled) { 4739 if (node.detectRender) { 4740 node.detectRender(ctx); 4741 } else { 4742 ctx.fillRect(0, 0, width, height); 4743 } 4744 } 4745 if (node._clipping) { 4746 ctx.beginPath(); 4747 ctx.rect(0, 0, width, height); 4748 ctx.clip(); 4749 } 4750 if (node.childNodes) { 4751 for (var i = 0, l = node.childNodes.length; i < l; i++) { 4752 child = node.childNodes[i]; 4753 this.detectRender(ctx, child); 4754 } 4755 } 4756 ctx.restore(); 4757 enchant.Matrix.instance.stack.pop(); 4758 } 4759 }, 4760 transform: function(ctx, node) { 4761 var matrix = enchant.Matrix.instance; 4762 var stack = matrix.stack; 4763 var newmat, ox, oy, vec; 4764 if (node._dirty) { 4765 matrix.makeTransformMatrix(node, node._cvsCache.matrix); 4766 newmat = []; 4767 matrix.multiply(stack[stack.length - 1], node._cvsCache.matrix, newmat); 4768 node._matrix = newmat; 4769 ox = (typeof node._originX === 'number') ? node._originX : node._width / 2 || 0; 4770 oy = (typeof node._originY === 'number') ? node._originY : node._height / 2 || 0; 4771 vec = [ ox, oy ]; 4772 matrix.multiplyVec(newmat, vec, vec); 4773 node._offsetX = vec[0] - ox; 4774 node._offsetY = vec[1] - oy; 4775 node._dirty = false; 4776 } else { 4777 newmat = node._matrix; 4778 } 4779 stack.push(newmat); 4780 ctx.setTransform.apply(ctx, newmat); 4781 } 4782 }); 4783 enchant.CanvasRenderer.instance = new enchant.CanvasRenderer(); 4784 4785 /** 4786 * @scope enchant.Scene.prototype 4787 */ 4788 enchant.Scene = enchant.Class.create(enchant.Group, { 4789 /** 4790 * @name enchant.Scene 4791 * @class 4792 * Class that becomes the root of the display object tree. 4793 * Child {@link Entity} objects are distributed to the Scene layer according to the drawing method. 4794 * The DOM of each Scene layer has a ({@link enchant.DOMLayer} and {@link enchant.CanvasLayer}) and is drawn using the Canvas. 4795 * Scenes are drawn in the order that they are added. 4796 * 4797 * @example 4798 * var scene = new Scene(); 4799 * scene.addChild(player); 4800 * scene.addChild(enemy); 4801 * core.pushScene(scene); 4802 * 4803 * @constructs 4804 * @extends enchant.Group 4805 */ 4806 initialize: function() { 4807 var core = enchant.Core.instance; 4808 4809 // Call initialize method of enchant.Group 4810 enchant.Group.call(this); 4811 4812 // All nodes (entities, groups, scenes) have reference to the scene that it belongs to. 4813 this.scene = this; 4814 4815 this._backgroundColor = null; 4816 4817 // Create div tag which possesses its layers 4818 this._element = document.createElement('div'); 4819 this._element.style.position = 'absolute'; 4820 this._element.style.overflow = 'hidden'; 4821 this._element.style[enchant.ENV.VENDOR_PREFIX + 'TransformOrigin'] = '0 0'; 4822 4823 this._layers = {}; 4824 this._layerPriority = []; 4825 4826 this.addEventListener(enchant.Event.CHILD_ADDED, this._onchildadded); 4827 this.addEventListener(enchant.Event.CHILD_REMOVED, this._onchildremoved); 4828 this.addEventListener(enchant.Event.ENTER, this._onenter); 4829 this.addEventListener(enchant.Event.EXIT, this._onexit); 4830 4831 var that = this; 4832 this._dispatchExitframe = function() { 4833 var layer; 4834 for (var prop in that._layers) { 4835 layer = that._layers[prop]; 4836 layer.dispatchEvent(new enchant.Event(enchant.Event.EXIT_FRAME)); 4837 } 4838 }; 4839 4840 this.addEventListener(enchant.Event.CORE_RESIZE, this._oncoreresize); 4841 4842 this._oncoreresize(core); 4843 }, 4844 /**#nocode+*/ 4845 x: { 4846 get: function() { 4847 return this._x; 4848 }, 4849 set: function(x) { 4850 this._x = x; 4851 for (var type in this._layers) { 4852 this._layers[type].x = x; 4853 } 4854 } 4855 }, 4856 y: { 4857 get: function() { 4858 return this._y; 4859 }, 4860 set: function(y) { 4861 this._y = y; 4862 for (var type in this._layers) { 4863 this._layers[type].y = y; 4864 } 4865 } 4866 }, 4867 width: { 4868 get: function() { 4869 return this._width; 4870 }, 4871 set: function(width) { 4872 this._width = width; 4873 for (var type in this._layers) { 4874 this._layers[type].width = width; 4875 } 4876 } 4877 }, 4878 height: { 4879 get: function() { 4880 return this._height; 4881 }, 4882 set: function(height) { 4883 this._height = height; 4884 for (var type in this._layers) { 4885 this._layers[type].height = height; 4886 } 4887 } 4888 }, 4889 rotation: { 4890 get: function() { 4891 return this._rotation; 4892 }, 4893 set: function(rotation) { 4894 this._rotation = rotation; 4895 for (var type in this._layers) { 4896 this._layers[type].rotation = rotation; 4897 } 4898 } 4899 }, 4900 scaleX: { 4901 get: function() { 4902 return this._scaleX; 4903 }, 4904 set: function(scaleX) { 4905 this._scaleX = scaleX; 4906 for (var type in this._layers) { 4907 this._layers[type].scaleX = scaleX; 4908 } 4909 } 4910 }, 4911 scaleY: { 4912 get: function() { 4913 return this._scaleY; 4914 }, 4915 set: function(scaleY) { 4916 this._scaleY = scaleY; 4917 for (var type in this._layers) { 4918 this._layers[type].scaleY = scaleY; 4919 } 4920 } 4921 }, 4922 backgroundColor: { 4923 get: function() { 4924 return this._backgroundColor; 4925 }, 4926 set: function(color) { 4927 this._backgroundColor = this._element.style.backgroundColor = color; 4928 } 4929 }, 4930 remove: function() { 4931 this.clearEventListener(); 4932 4933 while (this.childNodes.length > 0) { 4934 this.childNodes[0].remove(); 4935 } 4936 4937 return enchant.Core.instance.removeScene(this); 4938 }, 4939 /**#nocode-*/ 4940 _oncoreresize: function(e) { 4941 this._element.style.width = e.width + 'px'; 4942 this.width = e.width; 4943 this._element.style.height = e.height + 'px'; 4944 this.height = e.height; 4945 this._element.style[enchant.ENV.VENDOR_PREFIX + 'Transform'] = 'scale(' + e.scale + ')'; 4946 4947 for (var type in this._layers) { 4948 this._layers[type].dispatchEvent(e); 4949 } 4950 }, 4951 addLayer: function(type, i) { 4952 var core = enchant.Core.instance; 4953 if (this._layers[type]) { 4954 return; 4955 } 4956 var layer = new enchant[type + 'Layer'](); 4957 if (core.currentScene === this) { 4958 layer._startRendering(); 4959 } 4960 this._layers[type] = layer; 4961 var element = layer._element; 4962 if (typeof i === 'number') { 4963 var nextSibling = this._element.childNodes[i]; 4964 if (nextSibling) { 4965 this._element.insertBefore(element, nextSibling); 4966 } else { 4967 this._element.appendChild(element); 4968 } 4969 this._layerPriority.splice(i, 0, type); 4970 } else { 4971 this._element.appendChild(element); 4972 this._layerPriority.push(type); 4973 } 4974 layer._scene = this; 4975 }, 4976 _determineEventTarget: function(e) { 4977 var layer, target; 4978 for (var i = this._layerPriority.length - 1; i >= 0; i--) { 4979 layer = this._layers[this._layerPriority[i]]; 4980 target = layer._determineEventTarget(e); 4981 if (target) { 4982 break; 4983 } 4984 } 4985 if (!target) { 4986 target = this; 4987 } 4988 return target; 4989 }, 4990 _onchildadded: function(e) { 4991 var child = e.node; 4992 var next = e.next; 4993 var target, i; 4994 if (child._element) { 4995 target = 'Dom'; 4996 i = 1; 4997 } else { 4998 target = 'Canvas'; 4999 i = 0; 5000 } 5001 if (!this._layers[target]) { 5002 this.addLayer(target, i); 5003 } 5004 child._layer = this._layers[target]; 5005 this._layers[target].insertBefore(child, next); 5006 child.parentNode = this; 5007 }, 5008 _onchildremoved: function(e) { 5009 var child = e.node; 5010 child._layer.removeChild(child); 5011 child._layer = null; 5012 }, 5013 _onenter: function() { 5014 for (var type in this._layers) { 5015 this._layers[type]._startRendering(); 5016 } 5017 enchant.Core.instance.addEventListener('exitframe', this._dispatchExitframe); 5018 }, 5019 _onexit: function() { 5020 for (var type in this._layers) { 5021 this._layers[type]._stopRendering(); 5022 } 5023 enchant.Core.instance.removeEventListener('exitframe', this._dispatchExitframe); 5024 } 5025 }); 5026 5027 /** 5028 * @scope enchant.LoadingScene.prototype 5029 */ 5030 enchant.LoadingScene = enchant.Class.create(enchant.Scene, { 5031 /** 5032 * @name enchant.LoadingScene 5033 * @class 5034 * Default loading scene. If you want to use your own loading animation, overwrite (don't inherit) this class. 5035 * Referred from enchant.Core in default, as `new enchant.LoadingScene` etc. 5036 * 5037 * @example 5038 * enchant.LoadingScene = enchant.Class.create(enchant.Scene, { 5039 * initialize: function() { 5040 * enchant.Scene.call(this); 5041 * this.backgroundColor = 'red'; 5042 * // ... 5043 * this.addEventListener('progress', function(e) { 5044 * progress = e.loaded / e.total; 5045 * }); 5046 * this.addEventListener('enterframe', function() { 5047 * // animation 5048 * }); 5049 * } 5050 * }); 5051 * @constructs 5052 * @extends enchant.Scene 5053 */ 5054 initialize: function() { 5055 enchant.Scene.call(this); 5056 this.backgroundColor = '#000'; 5057 var barWidth = this.width * 0.4 | 0; 5058 var barHeight = this.width * 0.05 | 0; 5059 var border = barWidth * 0.03 | 0; 5060 var bar = new enchant.Sprite(barWidth, barHeight); 5061 bar.disableCollection(); 5062 bar.x = (this.width - barWidth) / 2; 5063 bar.y = (this.height - barHeight) / 2; 5064 var image = new enchant.Surface(barWidth, barHeight); 5065 image.context.fillStyle = '#fff'; 5066 image.context.fillRect(0, 0, barWidth, barHeight); 5067 image.context.fillStyle = '#000'; 5068 image.context.fillRect(border, border, barWidth - border * 2, barHeight - border * 2); 5069 bar.image = image; 5070 var progress = 0, _progress = 0; 5071 this.addEventListener('progress', function(e) { 5072 // avoid #167 https://github.com/wise9/enchant.js/issues/177 5073 progress = e.loaded / e.total * 1.0; 5074 }); 5075 bar.addEventListener('enterframe', function() { 5076 _progress *= 0.9; 5077 _progress += progress * 0.1; 5078 image.context.fillStyle = '#fff'; 5079 image.context.fillRect(border, 0, (barWidth - border * 2) * _progress, barHeight); 5080 }); 5081 this.addChild(bar); 5082 this.addEventListener('load', function(e) { 5083 var core = enchant.Core.instance; 5084 core.removeScene(core.loadingScene); 5085 core.dispatchEvent(e); 5086 }); 5087 } 5088 }); 5089 5090 /** 5091 * @scope enchant.CanvasScene.prototype 5092 */ 5093 enchant.CanvasScene = enchant.Class.create(enchant.Scene, { 5094 /** 5095 * @name enchant.CanvasScene 5096 * @class 5097 * Scene to draw by the Canvas all of the children. 5098 * @constructs 5099 * @extends enchant.Scene 5100 */ 5101 initialize: function() { 5102 enchant.Scene.call(this); 5103 this.addLayer('Canvas'); 5104 }, 5105 _determineEventTarget: function(e) { 5106 var target = this._layers.Canvas._determineEventTarget(e); 5107 if (!target) { 5108 target = this; 5109 } 5110 return target; 5111 }, 5112 _onchildadded: function(e) { 5113 var child = e.node; 5114 var next = e.next; 5115 child._layer = this._layers.Canvas; 5116 this._layers.Canvas.insertBefore(child, next); 5117 }, 5118 _onenter: function() { 5119 this._layers.Canvas._startRendering(); 5120 enchant.Core.instance.addEventListener('exitframe', this._dispatchExitframe); 5121 }, 5122 _onexit: function() { 5123 this._layers.Canvas._stopRendering(); 5124 enchant.Core.instance.removeEventListener('exitframe', this._dispatchExitframe); 5125 } 5126 }); 5127 5128 /** 5129 * @scope enchant.DOMScene.prototype 5130 */ 5131 enchant.DOMScene = enchant.Class.create(enchant.Scene, { 5132 /** 5133 * @name enchant.DOMScene 5134 * @class 5135 * Scene to draw by the DOM all of the children. 5136 * @constructs 5137 * @extends enchant.Scene 5138 */ 5139 initialize: function() { 5140 enchant.Scene.call(this); 5141 this.addLayer('Dom'); 5142 }, 5143 _determineEventTarget: function(e) { 5144 var target = this._layers.Dom._determineEventTarget(e); 5145 if (!target) { 5146 target = this; 5147 } 5148 return target; 5149 }, 5150 _onchildadded: function(e) { 5151 var child = e.node; 5152 var next = e.next; 5153 child._layer = this._layers.Dom; 5154 this._layers.Dom.insertBefore(child, next); 5155 }, 5156 _onenter: function() { 5157 this._layers.Dom._startRendering(); 5158 enchant.Core.instance.addEventListener('exitframe', this._dispatchExitframe); 5159 }, 5160 _onexit: function() { 5161 this._layers.Dom._stopRendering(); 5162 enchant.Core.instance.removeEventListener('exitframe', this._dispatchExitframe); 5163 } 5164 }); 5165 5166 /** 5167 * @scope enchant.Surface.prototype 5168 */ 5169 enchant.Surface = enchant.Class.create(enchant.EventTarget, { 5170 /** 5171 * @name enchant.Surface 5172 * @class 5173 * Class that wraps canvas elements. 5174 * 5175 * Can be used to set the {@link enchant.Sprite} and {@link enchant.Map}'s image properties to be displayed. 5176 * If you wish to access Canvas API use the {@link enchant.Surface#context} property. 5177 * 5178 * @example 5179 * // Creates Sprite that displays a circle. 5180 * var ball = new Sprite(50, 50); 5181 * var surface = new Surface(50, 50); 5182 * surface.context.beginPath(); 5183 * surface.context.arc(25, 25, 25, 0, Math.PI*2, true); 5184 * surface.context.fill(); 5185 * ball.image = surface; 5186 * 5187 * @param {Number} width Surface width. 5188 * @param {Number} height Surface height. 5189 * @constructs 5190 * @extends enchant.EventTarget 5191 */ 5192 initialize: function(width, height) { 5193 enchant.EventTarget.call(this); 5194 5195 var core = enchant.Core.instance; 5196 5197 /** 5198 * Surface width. 5199 * @type Number 5200 */ 5201 this.width = Math.ceil(width); 5202 /** 5203 * Surface height. 5204 * @type Number 5205 */ 5206 this.height = Math.ceil(height); 5207 /** 5208 * Surface drawing context. 5209 * @type CanvasRenderingContext2D 5210 */ 5211 this.context = null; 5212 5213 var id = 'enchant-surface' + core._surfaceID++; 5214 if (document.getCSSCanvasContext) { 5215 this.context = document.getCSSCanvasContext('2d', id, width, height); 5216 this._element = this.context.canvas; 5217 this._css = '-webkit-canvas(' + id + ')'; 5218 var context = this.context; 5219 } else if (document.mozSetImageElement) { 5220 this._element = document.createElement('canvas'); 5221 this._element.width = width; 5222 this._element.height = height; 5223 this._css = '-moz-element(#' + id + ')'; 5224 this.context = this._element.getContext('2d'); 5225 document.mozSetImageElement(id, this._element); 5226 } else { 5227 this._element = document.createElement('canvas'); 5228 this._element.width = width; 5229 this._element.height = height; 5230 this._element.style.position = 'absolute'; 5231 this.context = this._element.getContext('2d'); 5232 5233 enchant.ENV.CANVAS_DRAWING_METHODS.forEach(function(name) { 5234 var method = this.context[name]; 5235 this.context[name] = function() { 5236 method.apply(this, arguments); 5237 this._dirty = true; 5238 }; 5239 }, this); 5240 } 5241 }, 5242 /** 5243 * Returns 1 pixel from the Surface. 5244 * @param {Number} x The pixel's x coordinates. 5245 * @param {Number} y The pixel's y coordinates. 5246 * @return {Number[]} An array that holds pixel information in [r, g, b, a] format. 5247 */ 5248 getPixel: function(x, y) { 5249 return this.context.getImageData(x, y, 1, 1).data; 5250 }, 5251 /** 5252 * Sets one pixel within the surface. 5253 * @param {Number} x The pixel's x coordinates. 5254 * @param {Number} y The pixel's y coordinates. 5255 * @param {Number} r The pixel's red level. 5256 * @param {Number} g The pixel's green level. 5257 * @param {Number} b The pixel's blue level. 5258 * @param {Number} a The pixel's transparency. 5259 */ 5260 setPixel: function(x, y, r, g, b, a) { 5261 var pixel = this.context.createImageData(1, 1); 5262 pixel.data[0] = r; 5263 pixel.data[1] = g; 5264 pixel.data[2] = b; 5265 pixel.data[3] = a; 5266 this.context.putImageData(pixel, x, y); 5267 }, 5268 /** 5269 * Clears all Surface pixels and makes the pixels transparent. 5270 */ 5271 clear: function() { 5272 this.context.clearRect(0, 0, this.width, this.height); 5273 }, 5274 /** 5275 * Draws the content of the given Surface onto this surface. 5276 * 5277 * Wraps Canvas API drawImage and if multiple arguments are given, 5278 * these are getting applied to the Canvas drawImage method. 5279 * 5280 * @example 5281 * var src = core.assets['src.gif']; 5282 * var dst = new Surface(100, 100); 5283 * dst.draw(src); // Draws source at (0, 0) 5284 * dst.draw(src, 50, 50); // Draws source at (50, 50) 5285 * // Draws just 30 horizontal and vertical pixels of source at (50, 50) 5286 * dst.draw(src, 50, 50, 30, 30); 5287 * // Takes the image content in src starting at (10,10) with a (Width, Height) of (40,40), 5288 * // scales it and draws it in this surface at (50, 50) with a (Width, Height) of (30,30). 5289 * dst.draw(src, 10, 10, 40, 40, 50, 50, 30, 30); 5290 * 5291 * @param {enchant.Surface} image Surface used in drawing. 5292 */ 5293 draw: function(image) { 5294 image = image._element; 5295 if (arguments.length === 1) { 5296 this.context.drawImage(image, 0, 0); 5297 } else { 5298 var args = arguments; 5299 args[0] = image; 5300 this.context.drawImage.apply(this.context, args); 5301 } 5302 }, 5303 /** 5304 * Copies Surface. 5305 * @return {enchant.Surface} The copied Surface. 5306 */ 5307 clone: function() { 5308 var clone = new enchant.Surface(this.width, this.height); 5309 clone.draw(this); 5310 return clone; 5311 }, 5312 /** 5313 * Creates a data URI scheme from this Surface. 5314 * @return {String} The data URI scheme that identifies this Surface and 5315 * can be used to include this Surface into a dom tree. 5316 */ 5317 toDataURL: function() { 5318 var src = this._element.src; 5319 if (src) { 5320 if (src.slice(0, 5) === 'data:') { 5321 return src; 5322 } else { 5323 return this.clone().toDataURL(); 5324 } 5325 } else { 5326 return this._element.toDataURL(); 5327 } 5328 } 5329 }); 5330 5331 /** 5332 * Loads an image and creates a Surface object out of it. 5333 * 5334 * It is not possible to access properties or methods of the {@link enchant.Surface#context}, or to call methods using the Canvas API - 5335 * like {@link enchant.Surface#draw}, {@link enchant.Surface#clear}, {@link enchant.Surface#getPixel}, {@link enchant.Surface#setPixel}.. - 5336 * of the wrapped image created with this method. 5337 * However, it is possible to use this surface to draw it to another surface using the {@link enchant.Surface#draw} method. 5338 * The resulting surface can then be manipulated. (when loading images in a cross-origin resource sharing environment, 5339 * pixel acquisition and other image manipulation might be limited). 5340 * 5341 * @param {String} src The file path of the image to be loaded. 5342 * @param {Function} callback on load callback. 5343 * @param {Function} [onerror] on error callback. 5344 * @static 5345 * @return {enchant.Surface} Surface 5346 */ 5347 enchant.Surface.load = function(src, callback, onerror) { 5348 var image = new Image(); 5349 var surface = Object.create(enchant.Surface.prototype, { 5350 context: { value: null }, 5351 _css: { value: 'url(' + src + ')' }, 5352 _element: { value: image } 5353 }); 5354 enchant.EventTarget.call(surface); 5355 onerror = onerror || function() {}; 5356 surface.addEventListener('load', callback); 5357 surface.addEventListener('error', onerror); 5358 image.onerror = function() { 5359 var e = new enchant.Event(enchant.Event.ERROR); 5360 e.message = 'Cannot load an asset: ' + image.src; 5361 enchant.Core.instance.dispatchEvent(e); 5362 surface.dispatchEvent(e); 5363 }; 5364 image.onload = function() { 5365 surface.width = image.width; 5366 surface.height = image.height; 5367 surface.dispatchEvent(new enchant.Event('load')); 5368 }; 5369 image.src = src; 5370 return surface; 5371 }; 5372 enchant.Surface._staticCanvas2DContext = document.createElement('canvas').getContext('2d'); 5373 5374 enchant.Surface._getPattern = function(surface, force) { 5375 if (!surface._pattern || force) { 5376 surface._pattern = this._staticCanvas2DContext.createPattern(surface._element, 'repeat'); 5377 } 5378 return surface._pattern; 5379 }; 5380 5381 if (window.Deferred) { 5382 enchant.Deferred = window.Deferred; 5383 } else { 5384 /** 5385 * @scope enchant.Deferred.prototype 5386 */ 5387 enchant.Deferred = enchant.Class.create({ 5388 /** 5389 * @name enchant.Deferred 5390 * @class 5391 * <br/> 5392 * See: <a href="http://cho45.stfuawsc.com/jsdeferred/"> 5393 * http://cho45.stfuawsc.com/jsdeferred/</a> 5394 * 5395 * @example 5396 * enchant.Deferred 5397 * .next(function() { 5398 * return 42; 5399 * }) 5400 * .next(function(n) { 5401 * console.log(n); // 42 5402 * }) 5403 * .next(function() { 5404 * return core.load('img.png'); // wait loading 5405 * }) 5406 * .next(function() { 5407 * var img = core.assets['img.png']; 5408 * console.log(img instanceof enchant.Surface); // true 5409 * throw new Error('!!!'); 5410 * }) 5411 * .next(function() { 5412 * // skip 5413 * }) 5414 * .error(function(err) { 5415 * console.log(err.message); // !!! 5416 * }); 5417 * 5418 * @constructs 5419 */ 5420 initialize: function() { 5421 this._succ = this._fail = this._next = this._id = null; 5422 this._tail = this; 5423 }, 5424 /** 5425 * @param {Function} func 5426 */ 5427 next: function(func) { 5428 var q = new enchant.Deferred(); 5429 q._succ = func; 5430 return this._add(q); 5431 }, 5432 /** 5433 * @param {Function} func 5434 */ 5435 error: function(func) { 5436 var q = new enchant.Deferred(); 5437 q._fail = func; 5438 return this._add(q); 5439 }, 5440 _add: function(queue) { 5441 this._tail._next = queue; 5442 this._tail = queue; 5443 return this; 5444 }, 5445 /** 5446 * @param {*} arg 5447 */ 5448 call: function(arg) { 5449 var received; 5450 var queue = this; 5451 while (queue && !queue._succ) { 5452 queue = queue._next; 5453 } 5454 if (!(queue instanceof enchant.Deferred)) { 5455 return; 5456 } 5457 try { 5458 received = queue._succ(arg); 5459 } catch (e) { 5460 return queue.fail(e); 5461 } 5462 if (received instanceof enchant.Deferred) { 5463 enchant.Deferred._insert(queue, received); 5464 } else if (queue._next instanceof enchant.Deferred) { 5465 queue._next.call(received); 5466 } 5467 }, 5468 /** 5469 * @param {*} arg 5470 */ 5471 fail: function(arg) { 5472 var result, err, 5473 queue = this; 5474 while (queue && !queue._fail) { 5475 queue = queue._next; 5476 } 5477 if (queue instanceof enchant.Deferred) { 5478 result = queue._fail(arg); 5479 queue.call(result); 5480 } else if (arg instanceof Error) { 5481 throw arg; 5482 } else { 5483 err = new Error('failed in Deferred'); 5484 err.arg = arg; 5485 throw err; 5486 } 5487 } 5488 }); 5489 enchant.Deferred._insert = function(queue, ins) { 5490 if (queue._next instanceof enchant.Deferred) { 5491 ins._tail._next = queue._next; 5492 } 5493 queue._next = ins; 5494 }; 5495 /** 5496 * @param {Function} func 5497 * @return {enchant.Deferred} 5498 * @static 5499 */ 5500 enchant.Deferred.next = function(func) { 5501 var q = new enchant.Deferred().next(func); 5502 q._id = setTimeout(function() { q.call(); }, 0); 5503 return q; 5504 }; 5505 /** 5506 * @param {Object|enchant.Deferred[]} arg 5507 * @return {enchant.Deferred} 5508 * 5509 * @example 5510 * // array 5511 * enchant.Deferred 5512 * .parallel([ 5513 * enchant.Deferred.next(function() { 5514 * return 24; 5515 * }), 5516 * enchant.Deferred.next(function() { 5517 * return 42; 5518 * }) 5519 * ]) 5520 * .next(function(arg) { 5521 * console.log(arg); // [ 24, 42 ] 5522 * }); 5523 * // object 5524 * enchant.Deferred 5525 * .parallel({ 5526 * foo: enchant.Deferred.next(function() { 5527 * return 24; 5528 * }), 5529 * bar: enchant.Deferred.next(function() { 5530 * return 42; 5531 * }) 5532 * }) 5533 * .next(function(arg) { 5534 * console.log(arg.foo); // 24 5535 * console.log(arg.bar); // 42 5536 * }); 5537 * 5538 * @static 5539 */ 5540 enchant.Deferred.parallel = function(arg) { 5541 var q = new enchant.Deferred(); 5542 q._id = setTimeout(function() { q.call(); }, 0); 5543 var progress = 0; 5544 var ret = (arg instanceof Array) ? [] : {}; 5545 var p = new enchant.Deferred(); 5546 for (var prop in arg) { 5547 if (arg.hasOwnProperty(prop)) { 5548 progress++; 5549 /*jshint loopfunc:true */ 5550 (function(queue, name) { 5551 queue.next(function(arg) { 5552 progress--; 5553 ret[name] = arg; 5554 if (progress <= 0) { 5555 p.call(ret); 5556 } 5557 }) 5558 .error(function(err) { p.fail(err); }); 5559 if (typeof queue._id === 'number') { 5560 clearTimeout(queue._id); 5561 } 5562 queue._id = setTimeout(function() { queue.call(); }, 0); 5563 }(arg[prop], prop)); 5564 } 5565 } 5566 if (!progress) { 5567 p._id = setTimeout(function() { p.call(ret); }, 0); 5568 } 5569 return q.next(function() { return p; }); 5570 }; 5571 } 5572 5573 /** 5574 * @scope enchant.DOMSound.prototype 5575 */ 5576 enchant.DOMSound = enchant.Class.create(enchant.EventTarget, { 5577 /** 5578 * @name enchant.DOMSound 5579 * @class 5580 * Class to wrap audio elements. 5581 * 5582 * Safari, Chrome, Firefox, Opera, and IE all play MP3 files 5583 * (Firefox and Opera play via Flash). WAVE files can be played on 5584 * Safari, Chrome, Firefox, and Opera. When the browser is not compatible with 5585 * the used codec the file will not play. 5586 * 5587 * Instances are created not via constructor but via {@link enchant.DOMSound.load}. 5588 * @constructs 5589 * @extends enchant.EventTarget 5590 */ 5591 initialize: function() { 5592 enchant.EventTarget.call(this); 5593 /** 5594 * Sound file duration (seconds). 5595 * @type Number 5596 */ 5597 this.duration = 0; 5598 throw new Error("Illegal Constructor"); 5599 }, 5600 /** 5601 * Begin playing. 5602 */ 5603 play: function() { 5604 if (this._element) { 5605 this._element.play(); 5606 } 5607 }, 5608 /** 5609 * Pause playback. 5610 */ 5611 pause: function() { 5612 if (this._element) { 5613 this._element.pause(); 5614 } 5615 }, 5616 /** 5617 * Stop playing. 5618 */ 5619 stop: function() { 5620 this.pause(); 5621 this.currentTime = 0; 5622 }, 5623 /** 5624 * Create a copy of this Sound object. 5625 * @return {enchant.DOMSound} Copied Sound. 5626 */ 5627 clone: function() { 5628 var clone; 5629 if (this._element instanceof Audio) { 5630 clone = Object.create(enchant.DOMSound.prototype, { 5631 _element: { value: this._element.cloneNode(false) }, 5632 duration: { value: this.duration } 5633 }); 5634 } else if (enchant.ENV.USE_FLASH_SOUND) { 5635 return this; 5636 } else { 5637 clone = Object.create(enchant.DOMSound.prototype); 5638 } 5639 enchant.EventTarget.call(clone); 5640 return clone; 5641 }, 5642 /** 5643 * Current playback position (seconds). 5644 * @type Number 5645 */ 5646 currentTime: { 5647 get: function() { 5648 return this._element ? this._element.currentTime : 0; 5649 }, 5650 set: function(time) { 5651 if (this._element) { 5652 this._element.currentTime = time; 5653 } 5654 } 5655 }, 5656 /** 5657 * Volume. 0 (muted) ~ 1 (full volume). 5658 * @type Number 5659 */ 5660 volume: { 5661 get: function() { 5662 return this._element ? this._element.volume : 1; 5663 }, 5664 set: function(volume) { 5665 if (this._element) { 5666 this._element.volume = volume; 5667 } 5668 } 5669 } 5670 }); 5671 5672 /** 5673 * Loads an audio file and creates DOMSound object. 5674 * @param {String} src Path of the audio file to be loaded. 5675 * @param {String} [type] MIME Type of the audio file. 5676 * @param {Function} [callback] on load callback. 5677 * @param {Function} [onerror] on error callback. 5678 * @return {enchant.DOMSound} DOMSound 5679 * @static 5680 */ 5681 enchant.DOMSound.load = function(src, type, callback, onerror) { 5682 if (type == null) { 5683 var ext = enchant.Core.findExt(src); 5684 if (ext) { 5685 type = 'audio/' + ext; 5686 } else { 5687 type = ''; 5688 } 5689 } 5690 type = type.replace('mp3', 'mpeg').replace('m4a', 'mp4'); 5691 callback = callback || function() {}; 5692 onerror = onerror || function() {}; 5693 5694 var sound = Object.create(enchant.DOMSound.prototype); 5695 enchant.EventTarget.call(sound); 5696 sound.addEventListener('load', callback); 5697 sound.addEventListener('error', onerror); 5698 var audio = new Audio(); 5699 if (!enchant.ENV.SOUND_ENABLED_ON_MOBILE_SAFARI && 5700 enchant.ENV.VENDOR_PREFIX === 'webkit' && enchant.ENV.TOUCH_ENABLED) { 5701 window.setTimeout(function() { 5702 sound.dispatchEvent(new enchant.Event('load')); 5703 }, 0); 5704 } else { 5705 if (!enchant.ENV.USE_FLASH_SOUND && audio.canPlayType(type)) { 5706 audio.addEventListener('canplaythrough', function canplay() { 5707 sound.duration = audio.duration; 5708 sound.dispatchEvent(new enchant.Event('load')); 5709 audio.removeEventListener('canplaythrough', canplay); 5710 }, false); 5711 audio.src = src; 5712 audio.load(); 5713 audio.autoplay = false; 5714 audio.onerror = function() { 5715 var e = new enchant.Event(enchant.Event.ERROR); 5716 e.message = 'Cannot load an asset: ' + audio.src; 5717 enchant.Core.instance.dispatchEvent(e); 5718 sound.dispatchEvent(e); 5719 }; 5720 sound._element = audio; 5721 } else if (type === 'audio/mpeg') { 5722 var embed = document.createElement('embed'); 5723 var id = 'enchant-audio' + enchant.Core.instance._soundID++; 5724 embed.width = embed.height = 1; 5725 embed.name = id; 5726 embed.src = 'sound.swf?id=' + id + '&src=' + src; 5727 embed.allowscriptaccess = 'always'; 5728 embed.style.position = 'absolute'; 5729 embed.style.left = '-1px'; 5730 sound.addEventListener('load', function() { 5731 Object.defineProperties(embed, { 5732 currentTime: { 5733 get: function() { 5734 return embed.getCurrentTime(); 5735 }, 5736 set: function(time) { 5737 embed.setCurrentTime(time); 5738 } 5739 }, 5740 volume: { 5741 get: function() { 5742 return embed.getVolume(); 5743 }, 5744 set: function(volume) { 5745 embed.setVolume(volume); 5746 } 5747 } 5748 }); 5749 sound._element = embed; 5750 sound.duration = embed.getDuration(); 5751 }); 5752 enchant.Core.instance._element.appendChild(embed); 5753 enchant.DOMSound[id] = sound; 5754 } else { 5755 window.setTimeout(function() { 5756 sound.dispatchEvent(new enchant.Event('load')); 5757 }, 0); 5758 } 5759 } 5760 return sound; 5761 }; 5762 5763 window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext || window.oAudioContext; 5764 5765 /** 5766 * @scope enchant.WebAudioSound.prototype 5767 */ 5768 enchant.WebAudioSound = enchant.Class.create(enchant.EventTarget, { 5769 /** 5770 * @name enchant.WebAudioSound 5771 * @class 5772 * Sound wrapper class for Web Audio API (supported on some webkit-based browsers) 5773 * @constructs 5774 * @extends enchant.EventTarget 5775 */ 5776 initialize: function() { 5777 if (!window.AudioContext) { 5778 throw new Error("This browser does not support WebAudio API."); 5779 } 5780 enchant.EventTarget.call(this); 5781 if (!enchant.WebAudioSound.audioContext) { 5782 enchant.WebAudioSound.audioContext = new window.AudioContext(); 5783 enchant.WebAudioSound.destination = enchant.WebAudioSound.audioContext.destination; 5784 } 5785 this.context = enchant.WebAudioSound.audioContext; 5786 this.src = this.context.createBufferSource(); 5787 this.buffer = null; 5788 this._volume = 1; 5789 this._currentTime = 0; 5790 this._state = 0; 5791 this.connectTarget = enchant.WebAudioSound.destination; 5792 }, 5793 /** 5794 * Begin playing. 5795 * @param {Boolean} [dup=false] If true, Object plays new sound while keeps last sound. 5796 */ 5797 play: function(dup) { 5798 if (this._state === 1 && !dup) { 5799 this.src.disconnect(); 5800 } 5801 if (this._state !== 2) { 5802 this._currentTime = 0; 5803 } 5804 var offset = this._currentTime; 5805 var actx = this.context; 5806 this.src = actx.createBufferSource(); 5807 if (actx.createGain != null) { 5808 this._gain = actx.createGain(); 5809 } else { 5810 this._gain = actx.createGainNode(); 5811 } 5812 this.src.buffer = this.buffer; 5813 this._gain.gain.value = this._volume; 5814 5815 this.src.connect(this._gain); 5816 this._gain.connect(this.connectTarget); 5817 if (this.src.start != null) { 5818 this.src.start(0, offset, this.buffer.duration - offset - 1.192e-7); 5819 } else { 5820 this.src.noteGrainOn(0, offset, this.buffer.duration - offset - 1.192e-7); 5821 } 5822 this._startTime = actx.currentTime - this._currentTime; 5823 this._state = 1; 5824 }, 5825 /** 5826 * Pause playback. 5827 */ 5828 pause: function() { 5829 var currentTime = this.currentTime; 5830 if (currentTime === this.duration) { 5831 return; 5832 } 5833 if (this.src.stop != null) { 5834 this.src.stop(0); 5835 } else { 5836 this.src.noteOff(0); 5837 } 5838 this._currentTime = currentTime; 5839 this._state = 2; 5840 }, 5841 /** 5842 * Stop playing. 5843 */ 5844 stop: function() { 5845 if (this.src.stop != null) { 5846 this.src.stop(0); 5847 } else { 5848 this.src.noteOff(0); 5849 } 5850 this._state = 0; 5851 }, 5852 /** 5853 * Create a copy of this Sound object. 5854 * @return {enchant.WebAudioSound} Copied Sound. 5855 */ 5856 clone: function() { 5857 var sound = new enchant.WebAudioSound(); 5858 sound.buffer = this.buffer; 5859 return sound; 5860 }, 5861 /** 5862 * Sound file duration (seconds). 5863 * @type Number 5864 */ 5865 duration: { 5866 get: function() { 5867 if (this.buffer) { 5868 return this.buffer.duration; 5869 } else { 5870 return 0; 5871 } 5872 } 5873 }, 5874 /** 5875 * Volume. 0 (muted) ~ 1 (full volume). 5876 * @type Number 5877 */ 5878 volume: { 5879 get: function() { 5880 return this._volume; 5881 }, 5882 set: function(volume) { 5883 volume = Math.max(0, Math.min(1, volume)); 5884 this._volume = volume; 5885 if (this.src) { 5886 this._gain.gain.value = volume; 5887 } 5888 } 5889 }, 5890 /** 5891 * Current playback position (seconds). 5892 * @type Number 5893 */ 5894 currentTime: { 5895 get: function() { 5896 return Math.max(0, Math.min(this.duration, this.src.context.currentTime - this._startTime)); 5897 }, 5898 set: function(time) { 5899 this._currentTime = time; 5900 if (this._state !== 2) { 5901 this.play(false); 5902 } 5903 } 5904 } 5905 }); 5906 5907 /** 5908 * Loads an audio file and creates WebAudioSound object. 5909 * @param {String} src Path of the audio file to be loaded. 5910 * @param {String} [type] MIME Type of the audio file. 5911 * @param {Function} [callback] on load callback. 5912 * @param {Function} [onerror] on error callback. 5913 * @return {enchant.WebAudioSound} WebAudioSound 5914 * @static 5915 */ 5916 enchant.WebAudioSound.load = function(src, type, callback, onerror) { 5917 var canPlay = (new Audio()).canPlayType(type); 5918 var sound = new enchant.WebAudioSound(); 5919 callback = callback || function() {}; 5920 onerror = onerror || function() {}; 5921 sound.addEventListener(enchant.Event.LOAD, callback); 5922 sound.addEventListener(enchant.Event.ERROR, onerror); 5923 function dispatchErrorEvent() { 5924 var e = new enchant.Event(enchant.Event.ERROR); 5925 e.message = 'Cannot load an asset: ' + src; 5926 enchant.Core.instance.dispatchEvent(e); 5927 sound.dispatchEvent(e); 5928 } 5929 var actx, xhr; 5930 if (canPlay === 'maybe' || canPlay === 'probably') { 5931 actx = enchant.WebAudioSound.audioContext; 5932 xhr = new XMLHttpRequest(); 5933 xhr.open('GET', src, true); 5934 xhr.responseType = 'arraybuffer'; 5935 xhr.onload = function() { 5936 actx.decodeAudioData(xhr.response, function(buffer) { 5937 sound.buffer = buffer; 5938 sound.dispatchEvent(new enchant.Event(enchant.Event.LOAD)); 5939 }, dispatchErrorEvent); 5940 }; 5941 xhr.onerror = dispatchErrorEvent; 5942 xhr.send(null); 5943 } else { 5944 setTimeout(dispatchErrorEvent, 50); 5945 } 5946 return sound; 5947 }; 5948 5949 enchant.Sound = window.AudioContext && enchant.ENV.USE_WEBAUDIO ? enchant.WebAudioSound : enchant.DOMSound; 5950 5951 /* 5952 * ============================================================================================ 5953 * Easing Equations v2.0 5954 * September 1, 2003 5955 * (c) 2003 Robert Penner, all rights reserved. 5956 * This work is subject to the terms in http://www.robertpenner.com/easing_terms_of_use.html. 5957 * ============================================================================================ 5958 */ 5959 5960 /** 5961 * @namespace 5962 * JavaScript translation of Robert Penner's "Easing Equations" library which is widely used in ActionScript. 5963 * 5964 * @param [t] the current time 5965 * @param [b] the property's initial value 5966 * @param [c] how much the value should change 5967 * @param [d] how much time should elapse before value is changed 5968 * 5969 * @return {Number} 5970 * <br/> 5971 * See: <a href="http://www.robertpenner.com/easing/"> 5972 * http://www.robertpenner.com/easing/</a> 5973 * <br/> 5974 * See: <a href="http://www.robertpenner.com/easing/penner_chapter7_tweening.pdf"> 5975 * http://www.robertpenner.com/easing/penner_chapter7_tweening.pdf</a> 5976 */ 5977 enchant.Easing = { 5978 LINEAR: function(t, b, c, d) { 5979 return c * t / d + b; 5980 }, 5981 5982 SWING: function(t, b, c, d) { 5983 return c * (0.5 - Math.cos(((t / d) * Math.PI)) / 2) + b; 5984 }, 5985 5986 // *** quad 5987 QUAD_EASEIN: function(t, b, c, d) { 5988 return c * (t /= d) * t + b; 5989 }, 5990 5991 QUAD_EASEOUT: function(t, b, c, d) { 5992 return -c * (t /= d) * (t - 2) + b; 5993 }, 5994 5995 QUAD_EASEINOUT: function(t, b, c, d) { 5996 if ((t /= d / 2) < 1) { 5997 return c / 2 * t * t + b; 5998 } 5999 return -c / 2 * ((--t) * (t - 2) - 1) + b; 6000 }, 6001 6002 // *** cubic 6003 CUBIC_EASEIN: function(t, b, c, d) { 6004 return c * (t /= d) * t * t + b; 6005 }, 6006 6007 CUBIC_EASEOUT: function(t, b, c, d) { 6008 return c * ((t = t / d - 1) * t * t + 1) + b; 6009 }, 6010 6011 CUBIC_EASEINOUT: function(t, b, c, d) { 6012 if ((t /= d / 2) < 1) { 6013 return c / 2 * t * t * t + b; 6014 } 6015 return c / 2 * ((t -= 2) * t * t + 2) + b; 6016 }, 6017 6018 // *** quart 6019 QUART_EASEIN: function(t, b, c, d) { 6020 return c * (t /= d) * t * t * t + b; 6021 }, 6022 6023 QUART_EASEOUT: function(t, b, c, d) { 6024 return -c * ((t = t / d - 1) * t * t * t - 1) + b; 6025 }, 6026 6027 QUART_EASEINOUT: function(t, b, c, d) { 6028 if ((t /= d / 2) < 1) { 6029 return c / 2 * t * t * t * t + b; 6030 } 6031 return -c / 2 * ((t -= 2) * t * t * t - 2) + b; 6032 }, 6033 6034 // *** quint 6035 QUINT_EASEIN: function(t, b, c, d) { 6036 return c * (t /= d) * t * t * t * t + b; 6037 }, 6038 6039 QUINT_EASEOUT: function(t, b, c, d) { 6040 return c * ((t = t / d - 1) * t * t * t * t + 1) + b; 6041 }, 6042 6043 QUINT_EASEINOUT: function(t, b, c, d) { 6044 if ((t /= d / 2) < 1) { 6045 return c / 2 * t * t * t * t * t + b; 6046 } 6047 return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; 6048 }, 6049 6050 // *** sin 6051 SIN_EASEIN: function(t, b, c, d) { 6052 return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; 6053 }, 6054 6055 SIN_EASEOUT: function(t, b, c, d) { 6056 return c * Math.sin(t / d * (Math.PI / 2)) + b; 6057 }, 6058 6059 SIN_EASEINOUT: function(t, b, c, d) { 6060 return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; 6061 }, 6062 6063 // *** circ 6064 CIRC_EASEIN: function(t, b, c, d) { 6065 return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; 6066 }, 6067 6068 CIRC_EASEOUT: function(t, b, c, d) { 6069 return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; 6070 }, 6071 6072 CIRC_EASEINOUT: function(t, b, c, d) { 6073 if ((t /= d / 2) < 1) { 6074 return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; 6075 } 6076 return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; 6077 }, 6078 6079 // *** elastic 6080 ELASTIC_EASEIN: function(t, b, c, d, a, p) { 6081 if (t === 0) { 6082 return b; 6083 } 6084 if ((t /= d) === 1) { 6085 return b + c; 6086 } 6087 6088 if (!p) { 6089 p = d * 0.3; 6090 } 6091 6092 var s; 6093 if (!a || a < Math.abs(c)) { 6094 a = c; 6095 s = p / 4; 6096 } else { 6097 s = p / (2 * Math.PI) * Math.asin(c / a); 6098 } 6099 return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; 6100 }, 6101 6102 ELASTIC_EASEOUT: function(t, b, c, d, a, p) { 6103 if (t === 0) { 6104 return b; 6105 } 6106 if ((t /= d) === 1) { 6107 return b + c; 6108 } 6109 if (!p) { 6110 p = d * 0.3; 6111 } 6112 var s; 6113 if (!a || a < Math.abs(c)) { 6114 a = c; 6115 s = p / 4; 6116 } else { 6117 s = p / (2 * Math.PI) * Math.asin(c / a); 6118 } 6119 return (a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b); 6120 }, 6121 6122 ELASTIC_EASEINOUT: function(t, b, c, d, a, p) { 6123 if (t === 0) { 6124 return b; 6125 } 6126 if ((t /= d / 2) === 2) { 6127 return b + c; 6128 } 6129 if (!p) { 6130 p = d * (0.3 * 1.5); 6131 } 6132 var s; 6133 if (!a || a < Math.abs(c)) { 6134 a = c; 6135 s = p / 4; 6136 } else { 6137 s = p / (2 * Math.PI) * Math.asin(c / a); 6138 } 6139 if (t < 1) { 6140 return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; 6141 } 6142 return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b; 6143 }, 6144 6145 // *** bounce 6146 BOUNCE_EASEOUT: function(t, b, c, d) { 6147 if ((t /= d) < (1 / 2.75)) { 6148 return c * (7.5625 * t * t) + b; 6149 } else if (t < (2 / 2.75)) { 6150 return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; 6151 } else if (t < (2.5 / 2.75)) { 6152 return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; 6153 } else { 6154 return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; 6155 } 6156 }, 6157 6158 BOUNCE_EASEIN: function(t, b, c, d) { 6159 return c - enchant.Easing.BOUNCE_EASEOUT(d - t, 0, c, d) + b; 6160 }, 6161 6162 BOUNCE_EASEINOUT: function(t, b, c, d) { 6163 if (t < d / 2) { 6164 return enchant.Easing.BOUNCE_EASEIN(t * 2, 0, c, d) * 0.5 + b; 6165 } else { 6166 return enchant.Easing.BOUNCE_EASEOUT(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; 6167 } 6168 6169 }, 6170 6171 // *** back 6172 BACK_EASEIN: function(t, b, c, d, s) { 6173 if (s === undefined) { 6174 s = 1.70158; 6175 } 6176 return c * (t /= d) * t * ((s + 1) * t - s) + b; 6177 }, 6178 6179 BACK_EASEOUT: function(t, b, c, d, s) { 6180 if (s === undefined) { 6181 s = 1.70158; 6182 } 6183 return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; 6184 }, 6185 6186 BACK_EASEINOUT: function(t, b, c, d, s) { 6187 if (s === undefined) { 6188 s = 1.70158; 6189 } 6190 if ((t /= d / 2) < 1) { 6191 return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; 6192 } 6193 return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; 6194 }, 6195 6196 // *** expo 6197 EXPO_EASEIN: function(t, b, c, d) { 6198 return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; 6199 }, 6200 6201 EXPO_EASEOUT: function(t, b, c, d) { 6202 return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; 6203 }, 6204 6205 EXPO_EASEINOUT: function(t, b, c, d) { 6206 if (t === 0) { 6207 return b; 6208 } 6209 if (t === d) { 6210 return b + c; 6211 } 6212 if ((t /= d / 2) < 1) { 6213 return c / 2 * Math.pow(2, 10 * (t - 1)) + b; 6214 } 6215 return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; 6216 } 6217 }; 6218 6219 /** 6220 * @scope enchant.ActionEventTarget.prototype 6221 */ 6222 enchant.ActionEventTarget = enchant.Class.create(enchant.EventTarget, { 6223 /** 6224 * @name enchant.ActionEventTarget 6225 * @class 6226 * EventTarget which can change the context of event listeners. 6227 * @constructs 6228 * @extends enchant.EventTarget 6229 */ 6230 initialize: function() { 6231 enchant.EventTarget.apply(this, arguments); 6232 }, 6233 dispatchEvent: function(e) { 6234 var target = this.node ? this.node : this; 6235 6236 e.target = target; 6237 e.localX = e.x - target._offsetX; 6238 e.localY = e.y - target._offsetY; 6239 6240 if (this['on' + e.type] != null) { 6241 this['on' + e.type].call(target, e); 6242 } 6243 var listeners = this._listeners[e.type]; 6244 if (listeners != null) { 6245 listeners = listeners.slice(); 6246 for (var i = 0, len = listeners.length; i < len; i++) { 6247 listeners[i].call(target, e); 6248 } 6249 } 6250 } 6251 }); 6252 6253 /** 6254 * @scope enchant.Timeline.prototype 6255 */ 6256 enchant.Timeline = enchant.Class.create(enchant.EventTarget, { 6257 /** 6258 * @name enchant.Timeline 6259 * @class 6260 * Time-line class. 6261 * Class for managing the action. 6262 * 6263 * For one node to manipulate the timeline of one must correspond. 6264 * Time-line class has a method to add a variety of actions to himself, 6265 * entities can be animated and various operations by using these briefly. 6266 * You can choose time based and frame based(default) animation. 6267 * @param {enchant.Node} node target node. 6268 * @constructs 6269 * @extends enchant.EventTarget 6270 */ 6271 initialize: function(node) { 6272 enchant.EventTarget.call(this); 6273 this.node = node; 6274 this.queue = []; 6275 this.paused = false; 6276 this.looped = false; 6277 this.isFrameBased = true; 6278 this._parallel = null; 6279 this._activated = false; 6280 this.addEventListener(enchant.Event.ENTER_FRAME, this._onenterframe); 6281 6282 var tl = this; 6283 this._nodeEventListener = function(e) { 6284 tl.dispatchEvent(e); 6285 }; 6286 }, 6287 /** 6288 * @private 6289 */ 6290 _deactivateTimeline: function() { 6291 if (this._activated) { 6292 this._activated = false; 6293 this.node.removeEventListener('enterframe', this._nodeEventListener); 6294 } 6295 }, 6296 /** 6297 * @private 6298 */ 6299 _activateTimeline: function() { 6300 if (!this._activated && !this.paused) { 6301 this.node.addEventListener("enterframe", this._nodeEventListener); 6302 this._activated = true; 6303 } 6304 }, 6305 /** 6306 * @private 6307 */ 6308 _onenterframe: function(evt) { 6309 if (this.paused) { 6310 return; 6311 } 6312 6313 this.tick(this.isFrameBased ? 1 : evt.elapsed); 6314 }, 6315 /** 6316 */ 6317 setFrameBased: function() { 6318 this.isFrameBased = true; 6319 }, 6320 /** 6321 */ 6322 setTimeBased: function() { 6323 this.isFrameBased = false; 6324 }, 6325 /** 6326 */ 6327 next: function(remainingTime) { 6328 var e, action = this.queue.shift(); 6329 6330 if (action) { 6331 e = new enchant.Event("actionend"); 6332 e.timeline = this; 6333 action.dispatchEvent(e); 6334 6335 e = new enchant.Event("removedfromtimeline"); 6336 e.timeline = this; 6337 action.dispatchEvent(e); 6338 6339 if (this.looped) { 6340 this.add(action); 6341 } 6342 } 6343 6344 if (this.queue.length === 0) { 6345 this._deactivateTimeline(); 6346 return; 6347 } 6348 6349 if (remainingTime > 0 || (this.queue[0] && this.queue[0].time === 0)) { 6350 var event = new enchant.Event("actiontick"); 6351 event.elapsed = remainingTime; 6352 event.timeline = this; 6353 this.queue[0].dispatchEvent(event); 6354 } 6355 }, 6356 /** 6357 * @param {Number} elapsed 6358 */ 6359 tick: function(elapsed) { 6360 if (this.queue.length > 0) { 6361 var action = this.queue[0]; 6362 if (action.frame === 0) { 6363 var f; 6364 f = new enchant.Event("actionstart"); 6365 f.timeline = this; 6366 action.dispatchEvent(f); 6367 } 6368 6369 var e = new enchant.Event("actiontick"); 6370 e.timeline = this; 6371 e.elapsed = elapsed; 6372 action.dispatchEvent(e); 6373 } 6374 }, 6375 /** 6376 * @param {enchant.Action} action 6377 * @return {enchant.Timeline} 6378 */ 6379 add: function(action) { 6380 this._activateTimeline(); 6381 if (this._parallel) { 6382 this._parallel.actions.push(action); 6383 this._parallel = null; 6384 } else { 6385 this.queue.push(action); 6386 } 6387 action.frame = 0; 6388 6389 var e = new enchant.Event("addedtotimeline"); 6390 e.timeline = this; 6391 action.dispatchEvent(e); 6392 6393 e = new enchant.Event("actionadded"); 6394 e.action = action; 6395 this.dispatchEvent(e); 6396 6397 return this; 6398 }, 6399 /** 6400 * @param {Object} params 6401 * @return {enchant.Timeline} 6402 */ 6403 action: function(params) { 6404 return this.add(new enchant.Action(params)); 6405 }, 6406 /** 6407 * @param {Object} params 6408 * @return {enchant.Timeline} 6409 */ 6410 tween: function(params) { 6411 return this.add(new enchant.Tween(params)); 6412 }, 6413 /** 6414 * @return {enchant.Timeline} 6415 */ 6416 clear: function() { 6417 var e = new enchant.Event("removedfromtimeline"); 6418 e.timeline = this; 6419 6420 for (var i = 0, len = this.queue.length; i < len; i++) { 6421 this.queue[i].dispatchEvent(e); 6422 } 6423 this.queue = []; 6424 this._deactivateTimeline(); 6425 return this; 6426 }, 6427 /** 6428 * @param {Number} frames 6429 * @return {enchant.Timeline} 6430 */ 6431 skip: function(frames) { 6432 var event = new enchant.Event("enterframe"); 6433 if (this.isFrameBased) { 6434 event.elapsed = 1; 6435 } else { 6436 event.elapsed = frames; 6437 frames = 1; 6438 } 6439 while (frames--) { 6440 this.dispatchEvent(event); 6441 } 6442 return this; 6443 }, 6444 /** 6445 * @return {enchant.Timeline} 6446 */ 6447 pause: function() { 6448 if (!this.paused) { 6449 this.paused = true; 6450 this._deactivateTimeline(); 6451 } 6452 return this; 6453 }, 6454 /** 6455 * @return {enchant.Timeline} 6456 */ 6457 resume: function() { 6458 if (this.paused) { 6459 this.paused = false; 6460 this._activateTimeline(); 6461 } 6462 return this; 6463 }, 6464 /** 6465 * @return {enchant.Timeline} 6466 */ 6467 loop: function() { 6468 this.looped = true; 6469 return this; 6470 }, 6471 /** 6472 * @return {enchant.Timeline} 6473 */ 6474 unloop: function() { 6475 this.looped = false; 6476 return this; 6477 }, 6478 /** 6479 * @param {Number} time 6480 * @return {enchant.Timeline} 6481 */ 6482 delay: function(time) { 6483 return this.action({ 6484 time: time 6485 }); 6486 }, 6487 /** 6488 * @ignore 6489 * @param {Number} time 6490 */ 6491 wait: function(time) { 6492 // reserved 6493 return this; 6494 }, 6495 /** 6496 * @param {Function} func 6497 * @return {enchant.Timeline} 6498 */ 6499 then: function(func) { 6500 return this.action({ 6501 onactiontick: function(evt) { 6502 func.call(this); 6503 }, 6504 // if time is 0, next action will be immediately executed 6505 time: 0 6506 }); 6507 }, 6508 /** 6509 * @param {Function} func 6510 * @return {enchant.Timeline} 6511 */ 6512 exec: function(func) { 6513 return this.then(func); 6514 }, 6515 /** 6516 * @param {Object} cue 6517 * @return {enchant.Timeline} 6518 */ 6519 cue: function(cue) { 6520 var ptr = 0; 6521 for (var frame in cue) { 6522 if (cue.hasOwnProperty(frame)) { 6523 this.delay(frame - ptr); 6524 this.then(cue[frame]); 6525 ptr = frame; 6526 } 6527 } 6528 return this; 6529 }, 6530 /** 6531 * @param {Function} func 6532 * @param {Number} time 6533 * @return {enchant.Timeline} 6534 */ 6535 repeat: function(func, time) { 6536 return this.action({ 6537 onactiontick: function(evt) { 6538 func.call(this); 6539 }, 6540 time: time 6541 }); 6542 }, 6543 /** 6544 * @return {enchant.Timeline} 6545 */ 6546 and: function() { 6547 var last = this.queue.pop(); 6548 if (last instanceof enchant.ParallelAction) { 6549 this._parallel = last; 6550 this.queue.push(last); 6551 } else { 6552 var parallel = new enchant.ParallelAction(); 6553 parallel.actions.push(last); 6554 this.queue.push(parallel); 6555 this._parallel = parallel; 6556 } 6557 return this; 6558 }, 6559 /** 6560 * @ignore 6561 */ 6562 or: function() { 6563 return this; 6564 }, 6565 /** 6566 * @ignore 6567 */ 6568 doAll: function(children) { 6569 return this; 6570 }, 6571 /** 6572 * @ignore 6573 */ 6574 waitAll: function() { 6575 return this; 6576 }, 6577 /** 6578 * @param {Function} func 6579 * @return {enchant.Timeline} 6580 */ 6581 waitUntil: function(func) { 6582 return this.action({ 6583 onactiontick: function(evt) { 6584 if (func.call(this)) { 6585 evt.timeline.next(); 6586 } 6587 } 6588 }); 6589 }, 6590 /** 6591 * @param {Number} opacity 6592 * @param {Number} time 6593 * @param {Function} [easing=enchant.Easing.LINEAR] 6594 * @return {enchant.Timeline} 6595 */ 6596 fadeTo: function(opacity, time, easing) { 6597 return this.tween({ 6598 opacity: opacity, 6599 time: time, 6600 easing: easing 6601 }); 6602 }, 6603 /** 6604 * @param {Number} time 6605 * @param {Function} [easing=enchant.Easing.LINEAR] 6606 * @return {enchant.Timeline} 6607 */ 6608 fadeIn: function(time, easing) { 6609 return this.fadeTo(1, time, easing); 6610 }, 6611 /** 6612 * @param {Number} time 6613 * @param {Function} [easing=enchant.Easing.LINEAR] 6614 * @return {enchant.Timeline} 6615 */ 6616 fadeOut: function(time, easing) { 6617 return this.fadeTo(0, time, easing); 6618 }, 6619 /** 6620 * @param {Number} x 6621 * @param {Number} y 6622 * @param {Number} time 6623 * @param {Function} [easing=enchant.Easing.LINEAR] 6624 * @return {enchant.Timeline} 6625 */ 6626 moveTo: function(x, y, time, easing) { 6627 return this.tween({ 6628 x: x, 6629 y: y, 6630 time: time, 6631 easing: easing 6632 }); 6633 }, 6634 /** 6635 * @param {Number} x 6636 * @param {Number} time 6637 * @param {Function} [easing=enchant.Easing.LINEAR] 6638 * @return {enchant.Timeline} 6639 */ 6640 moveX: function(x, time, easing) { 6641 return this.tween({ 6642 x: x, 6643 time: time, 6644 easing: easing 6645 }); 6646 }, 6647 /** 6648 * @param {Number} y 6649 * @param {Number} time 6650 * @param {Function} [easing=enchant.Easing.LINEAR] 6651 * @return {enchant.Timeline} 6652 */ 6653 moveY: function(y, time, easing) { 6654 return this.tween({ 6655 y: y, 6656 time: time, 6657 easing: easing 6658 }); 6659 }, 6660 /** 6661 * @param {Number} x 6662 * @param {Number} y 6663 * @param {Number} time 6664 * @param {Function} [easing=enchant.Easing.LINEAR] 6665 * @return {enchant.Timeline} 6666 */ 6667 moveBy: function(x, y, time, easing) { 6668 return this.tween({ 6669 x: function() { 6670 return this.x + x; 6671 }, 6672 y: function() { 6673 return this.y + y; 6674 }, 6675 time: time, 6676 easing: easing 6677 }); 6678 }, 6679 /** 6680 * @return {enchant.Timeline} 6681 */ 6682 hide: function() { 6683 return this.then(function() { 6684 this.opacity = 0; 6685 }); 6686 }, 6687 /** 6688 * @return {enchant.Timeline} 6689 */ 6690 show: function() { 6691 return this.then(function() { 6692 this.opacity = 1; 6693 }); 6694 }, 6695 /** 6696 * @return {enchant.Timeline} 6697 */ 6698 removeFromScene: function() { 6699 return this.then(function() { 6700 this.parentNode.removeChild(this); 6701 }); 6702 }, 6703 /** 6704 * @param {Number} scaleX 6705 * @param {Number} [scaleY] 6706 * @param {Number} time 6707 * @param {Function} [easing=enchant.Easing.LINEAR] 6708 * @return {enchant.Timeline} 6709 */ 6710 scaleTo: function(scale, time, easing) { 6711 var scaleX, scaleY; 6712 6713 if (typeof easing === "number") { 6714 scaleX = arguments[0]; 6715 scaleY = arguments[1]; 6716 time = arguments[2]; 6717 easing = arguments[3]; 6718 } else { 6719 scaleX = scaleY = scale; 6720 } 6721 6722 return this.tween({ 6723 scaleX: scaleX, 6724 scaleY: scaleY, 6725 time: time, 6726 easing: easing 6727 }); 6728 }, 6729 /** 6730 * @param {Number} scaleX 6731 * @param {Number} [scaleY] 6732 * @param {Number} time 6733 * @param {Function} [easing=enchant.Easing.LINEAR] 6734 * @return {enchant.Timeline} 6735 */ 6736 scaleBy: function(scale, time, easing) { 6737 var scaleX, scaleY; 6738 6739 if (typeof easing === "number") { 6740 scaleX = arguments[0]; 6741 scaleY = arguments[1]; 6742 time = arguments[2]; 6743 easing = arguments[3]; 6744 } else { 6745 scaleX = scaleY = scale; 6746 } 6747 6748 return this.tween({ 6749 scaleX: function() { 6750 return this.scaleX * scaleX; 6751 }, 6752 scaleY: function() { 6753 return this.scaleY * scaleY; 6754 }, 6755 time: time, 6756 easing: easing 6757 }); 6758 }, 6759 /** 6760 * @param {Number} deg 6761 * @param {Number} time 6762 * @param {Function} [easing=enchant.Easing.LINEAR] 6763 * @return {enchant.Timeline} 6764 */ 6765 rotateTo: function(deg, time, easing) { 6766 return this.tween({ 6767 rotation: deg, 6768 time: time, 6769 easing: easing 6770 }); 6771 }, 6772 /** 6773 * @param {Number} deg 6774 * @param {Number} time 6775 * @param {Function} [easing=enchant.Easing.LINEAR] 6776 * @return {enchant.Timeline} 6777 */ 6778 rotateBy: function(deg, time, easing) { 6779 return this.tween({ 6780 rotation: function() { 6781 return this.rotation + deg; 6782 }, 6783 time: time, 6784 easing: easing 6785 }); 6786 } 6787 }); 6788 6789 /** 6790 * @scope enchant.Action.prototype 6791 */ 6792 enchant.Action = enchant.Class.create(enchant.ActionEventTarget, { 6793 /** 6794 * @name enchant.Action 6795 * @class 6796 * Actions are units that make up the timeline. 6797 * It is a unit used to specify the action you want to perform. 6798 * 6799 * Actions that have been added to the timeline are performed in sequential order. 6800 * The transition from one action to the next occurs automatically 6801 * after the number of frames specified by the time parameter have elapsed. 6802 * 6803 * An actionstart event is fired when the action has started. 6804 * An actionend event is fired when the action has stopped. 6805 * For each frame that elapses, an actiontick event is fired. 6806 * 6807 * You can specify a listener for these events to perform specific events when they occur. 6808 * 6809 * @param {Object} param 6810 * @param {Number} [param.time] The number of frames that the action will persist. For an infinite number set this to null. 6811 * @param {Function} [param.onactionstart] Event listener for when the action is initiated. 6812 * @param {Function} [param.onactiontick] Event listener for when the action has passed one frame. 6813 * @param {Function} [param.onactionend] Event listener for when the action is finished. 6814 * @constructs 6815 * @extends enchant.ActionEventTarget 6816 */ 6817 initialize: function(param) { 6818 enchant.ActionEventTarget.call(this); 6819 this.time = null; 6820 this.frame = 0; 6821 for (var key in param) { 6822 if (param.hasOwnProperty(key)) { 6823 if (param[key] != null) { 6824 this[key] = param[key]; 6825 } 6826 } 6827 } 6828 var action = this; 6829 6830 this.timeline = null; 6831 this.node = null; 6832 6833 this.addEventListener(enchant.Event.ADDED_TO_TIMELINE, function(evt) { 6834 action.timeline = evt.timeline; 6835 action.node = evt.timeline.node; 6836 action.frame = 0; 6837 }); 6838 6839 this.addEventListener(enchant.Event.REMOVED_FROM_TIMELINE, function() { 6840 action.timeline = null; 6841 action.node = null; 6842 action.frame = 0; 6843 }); 6844 6845 this.addEventListener(enchant.Event.ACTION_TICK, function(evt) { 6846 var remaining = action.time - (action.frame + evt.elapsed); 6847 if (action.time != null && remaining <= 0) { 6848 action.frame = action.time; 6849 evt.timeline.next(-remaining); 6850 } else { 6851 action.frame += evt.elapsed; 6852 } 6853 }); 6854 6855 } 6856 }); 6857 6858 /** 6859 * @scope enchant.ParallelAction.prototype 6860 */ 6861 enchant.ParallelAction = enchant.Class.create(enchant.Action, { 6862 /** 6863 * @name enchant.ParallelAction 6864 * @class 6865 * Actions to be executed in parallel. 6866 * It's possible to have more than one child action. 6867 * @constructs 6868 * @extends enchant.Action 6869 */ 6870 initialize: function(param) { 6871 enchant.Action.call(this, param); 6872 /** 6873 * Children Actions. 6874 * @type enchant.Action[] 6875 */ 6876 this.actions = []; 6877 /** 6878 * Removed actions. 6879 * @type enchant.Action[] 6880 */ 6881 this.endedActions = []; 6882 var that = this; 6883 6884 this.addEventListener(enchant.Event.ACTION_START, function(evt) { 6885 for (var i = 0, len = that.actions.length; i < len; i++) { 6886 that.actions[i].dispatchEvent(evt); 6887 } 6888 }); 6889 6890 this.addEventListener(enchant.Event.ACTION_TICK, function(evt) { 6891 var i, len, timeline = { 6892 next: function(remaining) { 6893 var action = that.actions[i]; 6894 that.actions.splice(i--, 1); 6895 len = that.actions.length; 6896 that.endedActions.push(action); 6897 6898 var e = new enchant.Event("actionend"); 6899 e.timeline = this; 6900 action.dispatchEvent(e); 6901 6902 e = new enchant.Event("removedfromtimeline"); 6903 e.timeline = this; 6904 action.dispatchEvent(e); 6905 } 6906 }; 6907 6908 var e = new enchant.Event("actiontick"); 6909 e.timeline = timeline; 6910 e.elapsed = evt.elapsed; 6911 for (i = 0, len = that.actions.length; i < len; i++) { 6912 that.actions[i].dispatchEvent(e); 6913 } 6914 6915 if (that.actions.length === 0) { 6916 evt.timeline.next(); 6917 } 6918 }); 6919 6920 this.addEventListener(enchant.Event.ADDED_TO_TIMELINE, function(evt) { 6921 for (var i = 0, len = that.actions.length; i < len; i++) { 6922 that.actions[i].dispatchEvent(evt); 6923 } 6924 }); 6925 6926 this.addEventListener(enchant.Event.REMOVED_FROM_TIMELINE, function() { 6927 that.actions = that.endedActions; 6928 that.endedActions = []; 6929 }); 6930 6931 } 6932 }); 6933 6934 /** 6935 * @scope enchant.Tween.prototype 6936 */ 6937 enchant.Tween = enchant.Class.create(enchant.Action, { 6938 /** 6939 * @name enchant.Tween 6940 * @class 6941 * @param {Object} params 6942 * @param {Number} params.time 6943 * @param {Function} [params.easing=enchant.Easing.LINEAR] 6944 * @constructs 6945 * @extends enchant.Action 6946 */ 6947 initialize: function(params) { 6948 var origin = {}; 6949 var target = {}; 6950 enchant.Action.call(this, params); 6951 6952 if (this.easing == null) { 6953 this.easing = enchant.Easing.LINEAR; 6954 } 6955 6956 var tween = this; 6957 this.addEventListener(enchant.Event.ACTION_START, function() { 6958 // excepted property 6959 var excepted = ["frame", "time", "callback", "onactiontick", "onactionstart", "onactionend"]; 6960 for (var prop in params) { 6961 if (params.hasOwnProperty(prop)) { 6962 // if function is used instead of numerical value, evaluate it 6963 var target_val; 6964 if (typeof params[prop] === "function") { 6965 target_val = params[prop].call(tween.node); 6966 } else { 6967 target_val = params[prop]; 6968 } 6969 6970 if (excepted.indexOf(prop) === -1) { 6971 origin[prop] = tween.node[prop]; 6972 target[prop] = target_val; 6973 } 6974 } 6975 } 6976 }); 6977 6978 this.addEventListener(enchant.Event.ACTION_TICK, function(evt) { 6979 // if time is 0, set property to target value immediately 6980 var ratio = tween.time === 0 ? 1 : tween.easing(Math.min(tween.time,tween.frame + evt.elapsed), 0, 1, tween.time) - tween.easing(tween.frame, 0, 1, tween.time); 6981 6982 for (var prop in target){ 6983 if (target.hasOwnProperty(prop)) { 6984 if (typeof this[prop] === "undefined"){ 6985 continue; 6986 } 6987 tween.node[prop] += (target[prop] - origin[prop]) * ratio; 6988 if (Math.abs(tween.node[prop]) < 10e-8){ 6989 tween.node[prop] = 0; 6990 } 6991 } 6992 } 6993 }); 6994 } 6995 }); 6996 6997 }(window)); 6998