websteroids/js/vendor/enchant.js
Zachary D. Rowitsch fbf1af5e2b rocket that rotates
2013-10-28 00:38:19 -04:00

6415 lines
200 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* enchant.js v0.7.1
* http://enchantjs.com
*
* Copyright Ubiquitous Entertainment Inc.
* Released under the MIT license.
*/
(function(window, undefined){
/**
* ECMA-262 5th edition Functions
*/
if (typeof Object.defineProperty !== 'function') {
Object.defineProperty = function(obj, prop, desc) {
if ('value' in desc) {
obj[prop] = desc.value;
}
if ('get' in desc) {
obj.__defineGetter__(prop, desc.get);
}
if ('set' in desc) {
obj.__defineSetter__(prop, desc.set);
}
return obj;
};
}
if (typeof Object.defineProperties !== 'function') {
Object.defineProperties = function(obj, descs) {
for (var prop in descs) {
if (descs.hasOwnProperty(prop)) {
Object.defineProperty(obj, prop, descs[prop]);
}
}
return obj;
};
}
if (typeof Object.create !== 'function') {
Object.create = function(prototype, descs) {
function F() {
}
F.prototype = prototype;
var obj = new F();
if (descs != null) {
Object.defineProperties(obj, descs);
}
return obj;
};
}
if (typeof Object.getPrototypeOf !== 'function') {
Object.getPrototypeOf = function(obj) {
return obj.__proto__;
};
}
if (typeof Function.prototype.bind !== 'function') {
Function.prototype.bind = function(thisObject) {
var func = this;
var args = Array.prototype.slice.call(arguments, 1);
var Nop = function() {
};
var bound = function() {
var a = args.concat(Array.prototype.slice.call(arguments));
return func.apply(
this instanceof Nop ? this : thisObject || window, a);
};
Nop.prototype = func.prototype;
bound.prototype = new Nop();
return bound;
};
}
window.getTime = (function() {
var origin;
if (window.performance && window.performance.now) {
origin = Date.now();
return function() {
return origin + window.performance.now();
};
} else if (window.performance && window.performance.webkitNow) {
origin = Date.now();
return function() {
return origin + window.performance.webkitNow();
};
} else {
return Date.now;
}
}());
/**
* define requestAnimationFrame
*/
window.requestAnimationFrame =
window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
(function() {
var lastTime = window.getTime();
var frame = 1000 / 60;
return function(func) {
var _id = setTimeout(function() {
lastTime = window.getTime();
func(lastTime);
}, Math.max(0, lastTime + frame - window.getTime()));
return _id;
};
}());
/**
* Export the library classes globally.
*
* When no arguments are given, all classes defined in enchant.js as well as all classes defined in
* plugins will be exported. When more than one argument is given, by default only classes defined
* in enchant.js will be exported. When you wish to export plugin classes you must explicitly deliver
* the plugin identifiers as arguments.
*
* @example
* enchant(); // All classes will be exported.
* enchant(''); // Only classes in enchant.js will be exported.
* enchant('ui'); // enchant.js classes and ui.enchant.js classes will be exported.
*
* @param {...String} [modules] Export module. Multiple designations possible.
* @global
* @type {Object}
* @name enchant
*/
var enchant = function(modules) {
if (modules != null) {
if (!(modules instanceof Array)) {
modules = Array.prototype.slice.call(arguments);
}
modules = modules.filter(function(module) {
return [module].join();
});
}
(function include(module, prefix) {
var submodules = [],
i, len;
for (var prop in module) {
if (module.hasOwnProperty(prop)) {
if (typeof module[prop] === 'function') {
window[prop] = module[prop];
} else if (typeof module[prop] === 'object' && module[prop] !== null && Object.getPrototypeOf(module[prop]) === Object.prototype) {
if (modules == null) {
submodules.push(prop);
} else {
i = modules.indexOf(prefix + prop);
if (i !== -1) {
submodules.push(prop);
modules.splice(i, 1);
}
}
}
}
}
for (i = 0, len = submodules.length; i < len; i++) {
include(module[submodules[i]], prefix + submodules[i] + '.');
}
}(enchant, ''));
// issue 185
if (enchant.Class.getInheritanceTree(window.Game).length <= enchant.Class.getInheritanceTree(window.Core).length) {
window.Game = window.Core;
}
if (modules != null && modules.length) {
throw new Error('Cannot load module: ' + modules.join(', '));
}
};
/**
* export enchant
*/
window.enchant = enchant;
window.addEventListener("message", function(msg, origin) {
try {
var data = JSON.parse(msg.data);
if (data.type === "event") {
enchant.Core.instance.dispatchEvent(new enchant.Event(data.value));
} else if (data.type === "debug") {
switch (data.value) {
case "start":
enchant.Core.instance.start();
break;
case "pause":
enchant.Core.instance.pause();
break;
case "resume":
enchant.Core.instance.resume();
break;
case "tick":
enchant.Core.instance._tick();
break;
default:
break;
}
}
} catch (e) {
// ignore
}
}, false);
/**
* @name enchant.Class
* @class
* A Class representing a class which supports inheritance.
*
* @param {Function} [superclass] The class from which the
* new class will inherit the class definition.
* @param {*} definition Class definition.
* @constructor
*/
enchant.Class = function(superclass, definition) {
return enchant.Class.create(superclass, definition);
};
/**
* Create a class.
*
* When defining classes that inherit from other classes, the previous class is used as a base with
* the superclass's constructor as default. When overriding the default constructor, it is necessary
* to explicitly call the previous constructor to ensure a correct class initialization.
*
* @example
* var Ball = Class.create({ // Creates independent class.
* initialize: function(radius) { ... }, // Method definition.
* fall: function() { ... }
* });
*
* var Ball = Class.create(Sprite); // Creates a class inheriting from "Sprite"
* var Ball = Class.create(Sprite, { // Creates a class inheriting "Sprite"
* initialize: function(radius) { // Overwrites constructor
* Sprite.call(this, radius*2, radius*2); // Applies previous constructor.
* this.image = core.assets['ball.gif'];
* }
* });
*
* @param {Function} [superclass] The class from which the
* new class will inherit the class definition.
* @param {*} [definition] Class definition.
* @static
*/
enchant.Class.create = function(superclass, definition) {
if (superclass == null && definition){
throw new Error("superclass is undefined (enchant.Class.create)");
}else if(superclass == null){
throw new Error("definition is undefined (enchant.Class.create)");
}
if (arguments.length === 0) {
return enchant.Class.create(Object, definition);
} else if (arguments.length === 1 && typeof arguments[0] !== 'function') {
return enchant.Class.create(Object, arguments[0]);
}
for (var prop in definition) {
if (definition.hasOwnProperty(prop)) {
if (typeof definition[prop] === 'object' && definition[prop] !== null && Object.getPrototypeOf(definition[prop]) === Object.prototype) {
if (!('enumerable' in definition[prop])) {
definition[prop].enumerable = true;
}
} else {
definition[prop] = { value: definition[prop], enumerable: true, writable: true };
}
}
}
var Constructor = function() {
if (this instanceof Constructor) {
Constructor.prototype.initialize.apply(this, arguments);
} else {
return new Constructor();
}
};
Constructor.prototype = Object.create(superclass.prototype, definition);
Constructor.prototype.constructor = Constructor;
if (Constructor.prototype.initialize == null) {
Constructor.prototype.initialize = function() {
superclass.apply(this, arguments);
};
}
var tree = this.getInheritanceTree(superclass);
for (var i = 0, l = tree.length; i < l; i++) {
if (typeof tree[i]._inherited === 'function') {
tree[i]._inherited(Constructor);
break;
}
}
return Constructor;
};
/**
* Get the inheritance tree of this class.
* @param {ConstructorFunction}
* @return {...ConstructorFunction}
*/
enchant.Class.getInheritanceTree = function(Constructor) {
var ret = [];
var C = Constructor;
var proto = C.prototype;
while (C !== Object) {
ret.push(C);
proto = Object.getPrototypeOf(proto);
C = proto.constructor;
}
return ret;
};
/**
* @namespace
* Environment variable.
* @type {Object}
*/
enchant.ENV = {
/**
* Version of enchant.js
* @type {String}
*/
VERSION: "0.6.1",
/**
* The CSS vendor prefix of the current browser.
* @type {String}
*/
VENDOR_PREFIX: (function() {
var ua = navigator.userAgent;
if (ua.indexOf('Opera') !== -1) {
return 'O';
} else if (ua.indexOf('MSIE') !== -1) {
return 'ms';
} else if (ua.indexOf('WebKit') !== -1) {
return 'webkit';
} else if (navigator.product === 'Gecko') {
return 'Moz';
} else {
return '';
}
}()),
/**
* Determines if the current browser supports touch.
* @type {Boolean} True, if touch is enabled.
*/
TOUCH_ENABLED: (function() {
var div = document.createElement('div');
div.setAttribute('ontouchstart', 'return');
return typeof div.ontouchstart === 'function';
}()),
/**
* Determines if the current browser is an iPhone with a retina display.
* @return {Boolean} True, if this display is a retina display
*/
RETINA_DISPLAY: (function() {
if (navigator.userAgent.indexOf('iPhone') !== -1 && window.devicePixelRatio === 2) {
var viewport = document.querySelector('meta[name="viewport"]');
if (viewport == null) {
viewport = document.createElement('meta');
document.head.appendChild(viewport);
}
viewport.setAttribute('content', 'width=640');
return true;
} else {
return false;
}
}()),
/**
* Determines if for current browser Flash should be used to play
* sound instead of the native audio class.
* @type {Boolean} True, if flash should be used.
*/
USE_FLASH_SOUND: (function() {
var ua = navigator.userAgent;
var vendor = navigator.vendor || "";
// non-local access, not on mobile mobile device, not on safari
return (location.href.indexOf('http') === 0 && ua.indexOf('Mobile') === -1 && vendor.indexOf('Apple') !== -1);
}()),
/**
* If click/touch event occure for these tags the setPreventDefault() method will not be called.
*/
USE_DEFAULT_EVENT_TAGS: ['input', 'textarea', 'select', 'area'],
CANVAS_DRAWING_METHODS: [
'putImageData', 'drawImage', 'drawFocusRing', 'fill', 'stroke',
'clearRect', 'fillRect', 'strokeRect', 'fillText', 'strokeText'
],
/**
* Keybind Table.
* You can use 'left', 'up', 'right', 'down', 'a', 'b' for preset event.
* @example
* enchant.ENV.KEY_BIND_TABLE = {
* 37: 'left',
* 38: 'up',
* 39: 'right',
* 40: 'down',
* 32: 'a', //-> use 'space' key as 'a button'
* }
*/
KEY_BIND_TABLE: {
37: 'left',
38: 'up',
39: 'right',
40: 'down'
},
PREVENT_DEFAULT_KEY_CODES: [37, 38, 39, 40, 32],
/**
* @type {Boolean}
*/
SOUND_ENABLED_ON_MOBILE_SAFARI: false,
/**
* Determines if WebAudioAPI is enabled. (true: use WebAudioAPI instead of Audio element if possible)
*/
USE_WEBAUDIO: (function(){
return location.protocol !== 'file:';
}()),
/**
* Determines if animation feature is enabled. (true: Timeline instance will be generated in new Node)
*/
USE_ANIMATION: true
};
/**
* @scope enchant.Event.prototype
*/
enchant.Event = enchant.Class.create({
/**
* @name enchant.Event
* @class
* A class for an independent implementation of events
* similar to DOM Events.
* However, it does not include phase concept.
* @param {String} type Event type.
* @constructs
*/
initialize: function(type) {
/**
* The type of the event.
* @type {String}
*/
this.type = type;
/**
* The target of the event.
* @type {*}
*/
this.target = null;
/**
* The x coordinate of the events occurrence.
* @type {Number}
*/
this.x = 0;
/**
* The y coordinate of the events occurrence.
* @type {Number}
*/
this.y = 0;
/**
* The event occurrences local coordinate systems x coordinates.
* @type {Number}
*/
this.localX = 0;
/**
* The event occurrences local coordinate systems y coordinates.
* @type {Number}
*/
this.localY = 0;
},
_initPosition: function(pageX, pageY) {
var core = enchant.Core.instance;
this.x = this.localX = (pageX - core._pageX) / core.scale;
this.y = this.localY = (pageY - core._pageY) / core.scale;
}
});
/**
* An event dispatched upon completion of core loading.
*
* It is necessary to wait for loading to finish and to do initial processing when preloading images.
* Issued object: {@link enchant.Core}
*
* @example
* var core = new Core(320, 320);
* core.preload('player.gif');
* core.onload = function() {
* ... // Describes initial core processing
* };
* core.start();
*
* @type {String}
*/
enchant.Event.LOAD = 'load';
/**
* Events which are occurring when error is occured.
* Issued object: {@link enchant.Core}, {@link enchant.Surface}, {@link enchant.WebAudioSound}, {@link enchant.DOMSound}
*/
enchant.Event.ERROR = 'error';
/**
* Events which are occurring when display size is changed.
* Issued object: {@link enchant.Core}, {@link enchant.Scene}
@type {String}
*/
enchant.Event.CORE_RESIZE = 'coreresize';
/**
* Events which are occurring during core loading.
* Dispatched each time preloaded image is loaded. Issued object: {@link enchant.LoadingScene}
* @type {String}
*/
enchant.Event.PROGRESS = 'progress';
/**
* An event which is occurring when a new frame is beeing processed.
* Issued object: {@link enchant.Core}, {@link enchant.Node}
* @type {String}
*/
enchant.Event.ENTER_FRAME = 'enterframe';
/**
* An event which is occurring when the frame processing is about to end.
* Issued object: {@link enchant.Core}
* @type {String}
*/
enchant.Event.EXIT_FRAME = 'exitframe';
/**
* Events occurring during Scene beginning.
* Issued object: {@link enchant.Scene}
* @type {String}
*/
enchant.Event.ENTER = 'enter';
/**
* Events occurring during Scene end.
* Issued object: {@link enchant.Scene}
* @type {String}
*/
enchant.Event.EXIT = 'exit';
/**
* An event which is occurring when a Child is getting added to a Node.
* Issued object: {@link enchant.Group}, {@link enchant.Scene}
* @type {String}
*/
enchant.Event.CHILD_ADDED = 'childadded';
/**
* An event which is occurring when the Node is added to a Group.
* Issued object: {@link enchant.Node}
* @type {String}
*/
enchant.Event.ADDED = 'added';
/**
* An event which is occurring when the Node is added to a Scene.
* Issued object: {@link enchant.Node}
* @type {String}
*/
enchant.Event.ADDED_TO_SCENE = 'addedtoscene';
/**
* An event which is occurring when a Child is removed from a Node.
* Issued object: {@link enchant.Group}, {@link enchant.Scene}
* @type {String}
* @type {String}
*/
enchant.Event.CHILD_REMOVED = 'childremoved';
/**
* An event which is occurring when the Node is deleted from a Group.
* Issued object: {@link enchant.Node}
* @type {String}
*/
enchant.Event.REMOVED = 'removed';
/**
* An event which is occurring when the Node is deleted from a Scene.
* Issued object: {@link enchant.Node}
* @type {String}
*/
enchant.Event.REMOVED_FROM_SCENE = 'removedfromscene';
/**
* An event occurring when a touch related to the Node has begun.
* A click is also treated as touch. Issued object: {@link enchant.Node}
* @type {String}
*/
enchant.Event.TOUCH_START = 'touchstart';
/**
* An event occurring when a touch related to the Node has been moved.
* A click is also treated as touch. Issued object: {@link enchant.Node}
* @type {String}
*/
enchant.Event.TOUCH_MOVE = 'touchmove';
/**
* An event which is occurring when a touch related to the Node has ended.
* A Click is also treated as touch. Issued object: enchant.Node
* @type {String}
*/
enchant.Event.TOUCH_END = 'touchend';
/**
* An event which is occurring when an Entity is rendered.
* Issued object: {@link enchant.Entity}
* @type {String}
*/
enchant.Event.RENDER = 'render';
/**
* An event which is occurring when a button is pressed.
* Issued object: {@link enchant.Core}, {@link enchant.Scene}
* @type {String}
*/
enchant.Event.INPUT_START = 'inputstart';
/**
* An event which is occurring when a button input changes.
* Issued object: {@link enchant.Core}, {@link enchant.Scene}
* @type {String}
*/
enchant.Event.INPUT_CHANGE = 'inputchange';
/**
* An event which is occurring when a button input ends.
* Issued object: {@link enchant.Core}, {@link enchant.Scene}
* @type {String}
*/
enchant.Event.INPUT_END = 'inputend';
/**
* An event which is occurring when the left button is pressed.
* Issued object: {@link enchant.Core}, {@link enchant.Scene}
* @type {String}
*/
enchant.Event.LEFT_BUTTON_DOWN = 'leftbuttondown';
/**
* An event which is occurring when the left button is released.
* Issued object: {@link enchant.Core}, {@link enchant.Scene}
* @type {String}
*/
enchant.Event.LEFT_BUTTON_UP = 'leftbuttonup';
/**
* An event which is occurring when the right button is pressed.
* Issued object: {@link enchant.Core}, {@link enchant.Scene}
* @type {String}
*/
enchant.Event.RIGHT_BUTTON_DOWN = 'rightbuttondown';
/**
* An event which is occurring when the right button is released.
* Issued object: {@link enchant.Core}, {@link enchant.Scene}
* @type {String}
*/
enchant.Event.RIGHT_BUTTON_UP = 'rightbuttonup';
/**
* An event which is occurring when the up button is pressed.
* Issued object: {@link enchant.Core}, {@link enchant.Scene}
* @type {String}
*/
enchant.Event.UP_BUTTON_DOWN = 'upbuttondown';
/**
* An event which is occurring when the up button is released.
* Issued object: {@link enchant.Core}, {@link enchant.Scene}
* @type {String}
*/
enchant.Event.UP_BUTTON_UP = 'upbuttonup';
/**
* An event which is occurring when the down button is pressed.
* Issued object: {@link enchant.Core}, {@link enchant.Scene}
* @type {String}
*/
enchant.Event.DOWN_BUTTON_DOWN = 'downbuttondown';
/**
* An event which is occurring when the down button is released.
* Issued object: {@link enchant.Core}, {@link enchant.Scene}
* @type {String}
*/
enchant.Event.DOWN_BUTTON_UP = 'downbuttonup';
/**
* An event which is occurring when the a button is pressed.
* Issued object: {@link enchant.Core}, {@link enchant.Scene}
* @type {String}
*/
enchant.Event.A_BUTTON_DOWN = 'abuttondown';
/**
* An event which is occurring when the a button is released.
* Issued object: {@link enchant.Core}, {@link enchant.Scene}
* @type {String}
*/
enchant.Event.A_BUTTON_UP = 'abuttonup';
/**
* An event which is occurring when the b button is pressed.
* Issued object: {@link enchant.Core}, {@link enchant.Scene}
* @type {String}
*/
enchant.Event.B_BUTTON_DOWN = 'bbuttondown';
/**
* An event which is occurring when the b button is released.
* Issued object: {@link enchant.Core}, {@link enchant.Scene}
* @type {String}
*/
enchant.Event.B_BUTTON_UP = 'bbuttonup';
/**
*/
enchant.Event.ADDED_TO_TIMELINE = "addedtotimeline";
/**
* @type {String}
*/
enchant.Event.REMOVED_FROM_TIMELINE = "removedfromtimeline";
/**
* @type {String}
*/
enchant.Event.ACTION_START = "actionstart";
/**
* @type {String}
*/
enchant.Event.ACTION_END = "actionend";
/**
* @type {String}
*/
enchant.Event.ACTION_TICK = "actiontick";
/**
* @type {String}
*/
enchant.Event.ACTION_ADDED = "actionadded";
/**
* @type {String}
*/
enchant.Event.ACTION_REMOVED = "actionremoved";
/**
* @scope enchant.EventTarget.prototype
*/
enchant.EventTarget = enchant.Class.create({
/**
* @name enchant.EventTarget
* @class
* A class for an independent implementation of events
* similar to DOM Events.
* However, it does not include the phase concept.
* @extends {enchant.Event}
* @constructs
*/
initialize: function() {
this._listeners = {};
},
/**
* Add a new event listener which will be executed when the event
* is being dispatched.
* @param {String} type Type of the events.
* @param {function(e:enchant.Event)} listener Event listener to be added.
*/
addEventListener: function(type, listener) {
var listeners = this._listeners[type];
if (listeners == null) {
this._listeners[type] = [listener];
} else if (listeners.indexOf(listener) === -1) {
listeners.unshift(listener);
}
},
/**
* Synonym for addEventListener
* @see {enchant.EventTarget#addEventListener}
* @param {String} type Type of the events.
* @param {function(e:enchant.Event)} listener Event listener to be added.
*/
on: function() {
this.addEventListener.apply(this, arguments);
},
/**
* Delete an event listener.
* @param {String} type Type of the events.
* @param {function(e:enchant.Event)} listener Event listener to be deleted.
*/
removeEventListener: function(type, listener) {
var listeners = this._listeners[type];
if (listeners != null) {
var i = listeners.indexOf(listener);
if (i !== -1) {
listeners.splice(i, 1);
}
}
},
/**
* Clear all defined event listener for a given type.
* If no type is given, all listener will be removed.
* @param [String] type Type of the events.
*/
clearEventListener: function(type) {
if (type != null) {
delete this._listeners[type];
} else {
this._listeners = {};
}
},
/**
* Issue an event.
* @param {enchant.Event} e Event to be issued.
*/
dispatchEvent: function(e) {
e.target = this;
e.localX = e.x - this._offsetX;
e.localY = e.y - this._offsetY;
if (this['on' + e.type] != null){
this['on' + e.type](e);
}
var listeners = this._listeners[e.type];
if (listeners != null) {
listeners = listeners.slice();
for (var i = 0, len = listeners.length; i < len; i++) {
listeners[i].call(this, e);
}
}
}
});
/**
* @scope enchant.Core.prototype
*/
(function() {
var core;
/**
* @scope enchant.Core.prototype
*/
enchant.Core = enchant.Class.create(enchant.EventTarget, {
/**
* @name enchant.Core
* @class
* A class which is controlling the cores main loop and scenes.
*
* There can be only one instance at a time, when the constructor is executed
* with an instance present, the existing instance will be overwritten. The existing instance
* can be accessed from {@link enchant.Core.instance}.
*
* @param {Number} width The width of the core screen.
* @param {Number} height The height of the core screen.
* @constructs
* @extends enchant.EventTarget
*/
initialize: function(width, height) {
if (window.document.body === null) {
// @TODO postpone initialization after window.onload
throw new Error("document.body is null. Please excute 'new Core()' in window.onload.");
}
enchant.EventTarget.call(this);
var initial = true;
if (core) {
initial = false;
core.stop();
}
core = enchant.Core.instance = this;
this._calledTime = 0;
this._mousedownID = 0;
this._surfaceID = 0;
this._soundID = 0;
this._scenes = [];
width = width || 320;
height = height || 320;
var stage = document.getElementById('enchant-stage');
var scale, sWidth, sHeight;
if (!stage) {
stage = document.createElement('div');
stage.id = 'enchant-stage';
stage.style.position = 'absolute';
if (document.body.firstChild) {
document.body.insertBefore(stage, document.body.firstChild);
} else {
document.body.appendChild(stage);
}
scale = Math.min(
window.innerWidth / width,
window.innerHeight / height
);
this._pageX = 0;
this._pageY = 0;
} else {
var style = window.getComputedStyle(stage);
sWidth = parseInt(style.width, 10);
sHeight = parseInt(style.height, 10);
if (sWidth && sHeight) {
scale = Math.min(
sWidth / width,
sHeight / height
);
} else {
scale = 1;
}
while (stage.firstChild) {
stage.removeChild(stage.firstChild);
}
stage.style.position = 'relative';
var bounding = stage.getBoundingClientRect();
this._pageX = Math.round(window.scrollX || window.pageXOffset + bounding.left);
this._pageY = Math.round(window.scrollY || window.pageYOffset + bounding.top);
}
stage.style.fontSize = '12px';
stage.style.webkitTextSizeAdjust = 'none';
stage.style.webkitTapHighlightColor = 'rgba(0, 0, 0, 0)';
this._element = stage;
this.addEventListener('coreresize', this._oncoreresize);
this._width = width;
this._height = height;
this.scale = scale;
/**
* The frame rate of the core.
* @type {Number}
*/
this.fps = 30;
/**
* The amount of frames since the core was started.
* @type {Number}
*/
this.frame = 0;
/**
* Indicates if the core can be executed.
* @type {Boolean}
*/
this.ready = false;
/**
* Indicates if the core is currently executed.
* @type {Boolean}
*/
this.running = false;
/**
* Object which stores loaded objects with the path as key.
* @type {Object.<String, Surface>}
*/
this.assets = {};
var assets = this._assets = [];
(function detectAssets(module) {
if (module.assets) {
enchant.Core.instance.preload(module.assets);
}
for (var prop in module) {
if (module.hasOwnProperty(prop)) {
if (typeof module[prop] === 'object' && module[prop] !== null && Object.getPrototypeOf(module[prop]) === Object.prototype) {
detectAssets(module[prop]);
}
}
}
}(enchant));
/**
* The Scene which is currently displayed. This Scene is on top of Scene stack.
* @type {enchant.Scene}
*/
this.currentScene = null;
/**
* The root Scene. The Scene at bottom of Scene stack.
* @type {enchant.Scene}
*/
this.rootScene = new enchant.Scene();
this.pushScene(this.rootScene);
/**
* The Scene which is getting displayed during loading.
* @type {enchant.Scene}
*/
this.loadingScene = new enchant.LoadingScene();
/**
* @type {Boolean}
* @private
*/
this._activated = false;
this._offsetX = 0;
this._offsetY = 0;
/**
* Object that saves the current input state for the core.
* @type {Object.<String, Boolean>}
*/
this.input = {};
if (!enchant.ENV.KEY_BIND_TABLE) {
enchant.ENV.KEY_BIND_TABLE = {};
}
this._keybind = enchant.ENV.KEY_BIND_TABLE;
this.pressedKeysNum = 0;
this._internalButtondownListeners = {};
this._internalButtonupListeners = {};
for (var prop in this._keybind) {
this.keybind(prop, this._keybind[prop]);
}
if (initial) {
stage = enchant.Core.instance._element;
var evt;
document.addEventListener('keydown', function(e) {
core.dispatchEvent(new enchant.Event('keydown'));
if (enchant.ENV.PREVENT_DEFAULT_KEY_CODES.indexOf(e.keyCode) !== -1) {
e.preventDefault();
e.stopPropagation();
}
if (!core.running) {
return;
}
var button = core._keybind[e.keyCode];
if (button) {
evt = new enchant.Event(button + 'buttondown');
core.dispatchEvent(evt);
}
}, true);
document.addEventListener('keyup', function(e) {
if (!core.running) {
return;
}
var button = core._keybind[e.keyCode];
if (button) {
evt = new enchant.Event(button + 'buttonup');
core.dispatchEvent(evt);
}
}, true);
if (enchant.ENV.TOUCH_ENABLED) {
stage.addEventListener('touchstart', function(e) {
var tagName = (e.target.tagName).toLowerCase();
if (enchant.ENV.USE_DEFAULT_EVENT_TAGS.indexOf(tagName) === -1) {
e.preventDefault();
if (!core.running) {
e.stopPropagation();
}
}
}, true);
stage.addEventListener('touchmove', function(e) {
var tagName = (e.target.tagName).toLowerCase();
if (enchant.ENV.USE_DEFAULT_EVENT_TAGS.indexOf(tagName) === -1) {
e.preventDefault();
if (!core.running) {
e.stopPropagation();
}
}
}, true);
stage.addEventListener('touchend', function(e) {
var tagName = (e.target.tagName).toLowerCase();
if (enchant.ENV.USE_DEFAULT_EVENT_TAGS.indexOf(tagName) === -1) {
e.preventDefault();
if (!core.running) {
e.stopPropagation();
}
}
}, true);
}
stage.addEventListener('mousedown', function(e) {
var tagName = (e.target.tagName).toLowerCase();
if (enchant.ENV.USE_DEFAULT_EVENT_TAGS.indexOf(tagName) === -1) {
e.preventDefault();
core._mousedownID++;
if (!core.running) {
e.stopPropagation();
}
}
}, true);
stage.addEventListener('mousemove', function(e) {
var tagName = (e.target.tagName).toLowerCase();
if (enchant.ENV.USE_DEFAULT_EVENT_TAGS.indexOf(tagName) === -1) {
e.preventDefault();
if (!core.running) {
e.stopPropagation();
}
}
}, true);
stage.addEventListener('mouseup', function(e) {
var tagName = (e.target.tagName).toLowerCase();
if (enchant.ENV.USE_DEFAULT_EVENT_TAGS.indexOf(tagName) === -1) {
e.preventDefault();
if (!core.running) {
e.stopPropagation();
}
}
}, true);
core._touchEventTarget = {};
if (enchant.ENV.TOUCH_ENABLED) {
stage.addEventListener('touchstart', function(e) {
var core = enchant.Core.instance;
var evt = new enchant.Event(enchant.Event.TOUCH_START);
var touches = e.changedTouches;
var touch, target;
for (var i = 0, l = touches.length; i < l; i++) {
touch = touches[i];
evt._initPosition(touch.pageX, touch.pageY);
target = core.currentScene._determineEventTarget(evt);
core._touchEventTarget[touch.identifier] = target;
target.dispatchEvent(evt);
}
}, false);
stage.addEventListener('touchmove', function(e) {
var core = enchant.Core.instance;
var evt = new enchant.Event(enchant.Event.TOUCH_MOVE);
var touches = e.changedTouches;
var touch, target;
for (var i = 0, l = touches.length; i < l; i++) {
touch = touches[i];
target = core._touchEventTarget[touch.identifier];
if (target) {
evt._initPosition(touch.pageX, touch.pageY);
target.dispatchEvent(evt);
}
}
}, false);
stage.addEventListener('touchend', function(e) {
var core = enchant.Core.instance;
var evt = new enchant.Event(enchant.Event.TOUCH_END);
var touches = e.changedTouches;
var touch, target;
for (var i = 0, l = touches.length; i < l; i++) {
touch = touches[i];
target = core._touchEventTarget[touch.identifier];
if (target) {
evt._initPosition(touch.pageX, touch.pageY);
target.dispatchEvent(evt);
delete core._touchEventTarget[touch.identifier];
}
}
}, false);
}
stage.addEventListener('mousedown', function(e) {
var core = enchant.Core.instance;
var evt = new enchant.Event(enchant.Event.TOUCH_START);
evt._initPosition(e.pageX, e.pageY);
var target = core.currentScene._determineEventTarget(evt);
core._touchEventTarget[core._mousedownID] = target;
target.dispatchEvent(evt);
}, false);
stage.addEventListener('mousemove', function(e) {
var core = enchant.Core.instance;
var evt = new enchant.Event(enchant.Event.TOUCH_MOVE);
evt._initPosition(e.pageX, e.pageY);
var target = core._touchEventTarget[core._mousedownID];
if (target) {
target.dispatchEvent(evt);
}
}, false);
stage.addEventListener('mouseup', function(e) {
var core = enchant.Core.instance;
var evt = new enchant.Event(enchant.Event.TOUCH_END);
evt._initPosition(e.pageX, e.pageY);
var target = core._touchEventTarget[core._mousedownID];
if (target) {
target.dispatchEvent(evt);
}
delete core._touchEventTarget[core._mousedownID];
}, false);
}
},
/**
* The width of the core screen.
* @type {Number}
*/
width: {
get: function() {
return this._width;
},
set: function(w) {
this._width = w;
this._dispatchCoreResizeEvent();
}
},
/**
* The height of the core screen.
* @type {Number}
*/
height: {
get: function() {
return this._height;
},
set: function(h) {
this._height = h;
this._dispatchCoreResizeEvent();
}
},
/**
* The scaling of the core rendering.
* @type {Number}
*/
scale: {
get: function() {
return this._scale;
},
set: function(s) {
this._scale = s;
this._dispatchCoreResizeEvent();
}
},
_dispatchCoreResizeEvent: function() {
var e = new enchant.Event('coreresize');
e.width = this._width;
e.height = this._height;
e.scale = this._scale;
this.dispatchEvent(e);
},
_oncoreresize: function(e) {
this._element.style.width = Math.floor(this._width * this._scale) + 'px';
this._element.style.height = Math.floor(this._height * this._scale) + 'px';
var scene;
for (var i = 0, l = this._scenes.length; i < l; i++) {
scene = this._scenes[i];
scene.dispatchEvent(e);
}
},
/**
* Performs a file preload.
*
* Sets files which are to be preloaded. When {@link enchant.Core#start} is called the
* actual loading takes place. When all files are loaded, a {@link enchant.Event.LOAD} event
* is dispatched from the Core object. Depending on the type of the file different objects will be
* created and stored in {@link enchant.Core#assets} Variable.
* When an image file is loaded, an {@link enchant.Surface} is created. If a sound file is loaded, an
* {@link enchant.Sound} object is created. Otherwise it will be accessible as a string.
*
* In addition, because this Surface object used made with {@link enchant.Surface.load},
* direct object manipulation is not possible. Refer to the items of {@link enchant.Surface.load}
*
* @example
* core.preload('player.gif');
* core.onload = function() {
* var sprite = new Sprite(32, 32);
* sprite.image = core.assets['player.gif']; // Access via path
* ...
* };
* core.start();
*
* @param {...String} assets Path of images to be preloaded. Multiple settings possible.
* @return {enchant.Core} this
*/
preload: function(assets) {
var a;
if (!(assets instanceof Array)) {
if (typeof assets === 'object') {
a = [];
for (var name in assets) {
if (assets.hasOwnProperty(name)) {
a.push([ assets[name], name ]);
}
}
assets = a;
} else {
assets = Array.prototype.slice.call(arguments);
}
}
Array.prototype.push.apply(this._assets, assets);
return this;
},
/**
* Loads a file.
*
* @param {String} asset File path of the resource to be loaded.
* @param {String} asset name of the resource to be loaded.
* @param {Function} [callback] Function called up when file loading is finished.
* @param {Function} [callback] Function called up when file loading is failed.
*/
load: function(src, alias, callback, onerror) {
var assetName, offset;
if (typeof arguments[1] === 'string') {
assetName = alias;
offset = 1;
} else {
assetName = src;
offset = 0;
}
callback = arguments[1 + offset] || function() {};
onerror = arguments[2 + offset] || function() {};
var ext = enchant.Core.findExt(src);
return enchant.Deferred.next(function() {
var d = new enchant.Deferred();
var _callback = function(e) {
d.call(e);
callback.call(this, e);
};
var _onerror = function(e) {
d.fail(e);
onerror.call(this, e);
};
if (enchant.Core._loadFuncs[ext]) {
enchant.Core.instance.assets[assetName] = enchant.Core._loadFuncs[ext](src, ext, _callback, _onerror);
} else {
var req = new XMLHttpRequest();
req.open('GET', src, true);
req.onreadystatechange = function() {
if (req.readyState === 4) {
if (req.status !== 200 && req.status !== 0) {
// throw new Error(req.status + ': ' + 'Cannot load an asset: ' + src);
var e = new enchant.Event('error');
e.message = req.status + ': ' + 'Cannot load an asset: ' + src;
_onerror.call(enchant.Core.instance, e);
}
var type = req.getResponseHeader('Content-Type') || '';
if (type.match(/^image/)) {
core.assets[assetName] = enchant.Surface.load(src, _callback, _onerror);
} else if (type.match(/^audio/)) {
core.assets[assetName] = enchant.Sound.load(src, type, _callback, _onerror);
} else {
core.assets[assetName] = req.responseText;
_callback.call(enchant.Core.instance, new enchant.Event('laod'));
}
}
};
req.send(null);
}
return d;
});
},
/**
* Start the core.
*
* Obeying the frame rate set in {@link enchant.Core#fps}, the frame in
* {@link enchant.Core#currentScene} will be updated. If images to preload are present,
* loading will begin and the loading screen will be displayed.
* @return {enchant.Deferred} Deferred
*/
start: function(deferred) {
var onloadTimeSetter = function() {
this.frame = 0;
this.removeEventListener('load', onloadTimeSetter);
};
this.addEventListener('load', onloadTimeSetter);
this.currentTime = window.getTime();
this.running = true;
this.ready = true;
if (!this._activated) {
this._activated = true;
if (enchant.ENV.SOUND_ENABLED_ON_MOBILE_SAFARI && !core._touched &&
(navigator.userAgent.indexOf('iPhone OS') !== -1 ||
navigator.userAgent.indexOf('iPad') !== -1)) {
var d = new enchant.Deferred();
var scene = new enchant.Scene();
scene.backgroundColor = '#000';
var size = Math.round(core.width / 10);
var sprite = new enchant.Sprite(core.width, size);
sprite.y = (core.height - size) / 2;
sprite.image = new enchant.Surface(core.width, size);
sprite.image.context.fillStyle = '#fff';
sprite.image.context.font = (size - 1) + 'px bold Helvetica,Arial,sans-serif';
var width = sprite.image.context.measureText('Touch to Start').width;
sprite.image.context.fillText('Touch to Start', (core.width - width) / 2, size - 1);
scene.addChild(sprite);
document.addEventListener('mousedown', function waitTouch() {
document.removeEventListener('mousedown', waitTouch);
core._touched = true;
core.removeScene(scene);
core.start(d);
}, false);
core.pushScene(scene);
return d;
}
}
this._requestNextFrame(0);
var ret = this._requestPreload()
.next(function() {
enchant.Core.instance.loadingScene.dispatchEvent(new enchant.Event(enchant.Event.LOAD));
});
if (deferred) {
ret.next(function(arg) {
deferred.call(arg);
})
.error(function(arg) {
deferred.fail(arg);
});
}
return ret;
},
_requestPreload: function() {
var o = {};
var loaded = 0,
len = 0,
loadFunc = function() {
var e = new enchant.Event('progress');
e.loaded = ++loaded;
e.total = len;
core.loadingScene.dispatchEvent(e);
};
this._assets
.reverse()
.forEach(function(asset) {
var src, name;
if (asset instanceof Array) {
src = asset[0];
name = asset[1];
} else {
src = name = asset;
}
if (!o[name]) {
o[name] = this.load(src, name, loadFunc);
len++;
}
}, this);
this.pushScene(this.loadingScene);
return enchant.Deferred.parallel(o);
},
/**
* Begin core debug mode.
*
* Core debug mode can be set to on even if enchant.Core.instance._debug
* flag is already set to true.
* @return {enchant.Deferred} Deferred
*/
debug: function() {
this._debug = true;
return this.start();
},
actualFps: {
get: function() {
return this._actualFps || this.fps;
}
},
/**
* @private
*/
_requestNextFrame: function(delay) {
if (!this.ready) {
return;
}
if (this.fps >= 60 || delay <= 16) {
this._calledTime = window.getTime();
window.requestAnimationFrame(this._callTick);
} else {
setTimeout(function() {
var core = enchant.Core.instance;
core._calledTime = window.getTime();
window.requestAnimationFrame(core._callTick);
}, Math.max(0, delay));
}
},
/**
* @private
*/
_callTick: function(time) {
enchant.Core.instance._tick(time);
},
_tick: function(time) {
var e = new enchant.Event('enterframe');
var now = window.getTime();
var elapsed = e.elapsed = now - this.currentTime;
this._actualFps = elapsed > 0 ? (1000 / elapsed) : 0;
var nodes = this.currentScene.childNodes.slice();
var push = Array.prototype.push;
while (nodes.length) {
var node = nodes.pop();
node.age++;
node.dispatchEvent(e);
if (node.childNodes) {
push.apply(nodes, node.childNodes);
}
}
this.currentScene.age++;
this.currentScene.dispatchEvent(e);
this.dispatchEvent(e);
this.dispatchEvent(new enchant.Event('exitframe'));
this.frame++;
now = window.getTime();
this.currentTime = now;
this._requestNextFrame(1000 / this.fps - (now - this._calledTime));
},
getTime: function() {
return window.getTime();
},
/**
* Stops the core.
*
* The frame will not be updated, and player input will not be accepted anymore.
* Core can be restarted using {@link enchant.Core#start}.
*/
stop: function() {
this.ready = false;
this.running = false;
},
/**
* Stops the core.
*
* The frame will not be updated, and player input will not be accepted anymore.
* Core can be started again using {@link enchant.Core#start}.
*/
pause: function() {
this.ready = false;
},
/**
* Resumes the core.
*/
resume: function() {
if (this.ready) {
return;
}
this.currentTime = window.getTime();
this.ready = true;
this.running = true;
this._requestNextFrame(0);
},
/**
* Switch to a new Scene.
*
* Scenes are controlled using a stack, and the display order also obeys that stack order.
* When {@link enchant.Core#pushScene} is executed, the Scene can be brought to the top of stack.
* Frames will be updated in the Scene which is on the top of the stack.
*
* @param {enchant.Scene} scene The new scene to be switched to.
* @return {enchant.Scene} The new Scene.
*/
pushScene: function(scene) {
this._element.appendChild(scene._element);
if (this.currentScene) {
this.currentScene.dispatchEvent(new enchant.Event('exit'));
}
this.currentScene = scene;
this.currentScene.dispatchEvent(new enchant.Event('enter'));
return this._scenes.push(scene);
},
/**
* Ends the current Scene, return to the previous Scene.
*
* Scenes are controlled using a stack, and the display order also obeys that stack order.
* When {@link enchant.Core#popScene} is executed, the Scene at the top of the stack
* will be removed and returned.
*
* @return {enchant.Scene} Ended Scene.
*/
popScene: function() {
if (this.currentScene === this.rootScene) {
return this.currentScene;
}
this._element.removeChild(this.currentScene._element);
this.currentScene.dispatchEvent(new enchant.Event('exit'));
this.currentScene = this._scenes[this._scenes.length - 2];
this.currentScene.dispatchEvent(new enchant.Event('enter'));
return this._scenes.pop();
},
/**
* Overwrites the current Scene with a new Scene.
*
* {@link enchant.Core#popScene}, {@link enchant.Core#pushScene} are executed after
* each other to replace to current scene with the new scene.
*
* @param {enchant.Scene} scene The new scene which will replace the previous scene.
* @return {enchant.Scene} The new Scene.
*/
replaceScene: function(scene) {
this.popScene();
return this.pushScene(scene);
},
/**
* Removes a Scene.
*
* Removes a Scene from the Scene stack.
*
* @param {enchant.Scene} scene Scene to be removed.
* @return {enchant.Scene} The deleted Scene.
*/
removeScene: function(scene) {
if (this.currentScene === scene) {
return this.popScene();
} else {
var i = this._scenes.indexOf(scene);
if (i !== -1) {
this._scenes.splice(i, 1);
this._element.removeChild(scene._element);
return scene;
} else {
return null;
}
}
},
/**
* Set a key binding.
*
* @param {Number} key Key code for the button which will be bound.
* @param {String} button The enchant.js button (left, right, up, down, a, b).
* @return {enchant.Core} this
*/
keybind: function(key, button) {
this._keybind[key] = button;
var onxbuttondown = function(e) {
var inputEvent;
if (!this.input[button]) {
this.input[button] = true;
inputEvent = new enchant.Event((this.pressedKeysNum++) ? 'inputchange' : 'inputstart');
this.dispatchEvent(inputEvent);
this.currentScene.dispatchEvent(inputEvent);
}
this.currentScene.dispatchEvent(e);
};
var onxbuttonup = function(e) {
var inputEvent;
if (this.input[button]) {
this.input[button] = false;
inputEvent = new enchant.Event((--this.pressedKeysNum) ? 'inputchange' : 'inputend');
this.dispatchEvent(inputEvent);
this.currentScene.dispatchEvent(inputEvent);
}
this.currentScene.dispatchEvent(e);
};
this.addEventListener(button + 'buttondown', onxbuttondown);
this.addEventListener(button + 'buttonup', onxbuttonup);
this._internalButtondownListeners[key] = onxbuttondown;
this._internalButtonupListeners[key] = onxbuttonup;
return this;
},
/**
* Delete a key binding.
*
* @param {Number} key Key code that want to delete.
* @return {enchant.Core} this
*/
keyunbind: function(key) {
if (!this._keybind[key]) {
return this;
}
var buttondowns = this._internalButtondownListeners;
var buttonups = this._internalButtonupListeners;
this.removeEventListener(key + 'buttondown', buttondowns);
this.removeEventListener(key + 'buttonup', buttonups);
delete buttondowns[key];
delete buttonups[key];
delete this._keybind[key];
return this;
},
/**
* Get the elapsed core time (not actual) from when core.start was called.
* @return {Number} The elapsed time (seconds)
*/
getElapsedTime: function() {
return this.frame / this.fps;
}
});
/**
* @static
* @private
* @type {Object.<String, Function>}
*/
enchant.Core._loadFuncs = {};
enchant.Core._loadFuncs['jpg'] =
enchant.Core._loadFuncs['jpeg'] =
enchant.Core._loadFuncs['gif'] =
enchant.Core._loadFuncs['png'] =
enchant.Core._loadFuncs['bmp'] = function(src, ext, callback, onerror) {
return enchant.Surface.load(src, callback, onerror);
};
enchant.Core._loadFuncs['mp3'] =
enchant.Core._loadFuncs['aac'] =
enchant.Core._loadFuncs['m4a'] =
enchant.Core._loadFuncs['wav'] =
enchant.Core._loadFuncs['ogg'] = function(src, ext, callback, onerror) {
return enchant.Sound.load(src, 'audio/' + ext, callback, onerror);
};
/**
* Get the file extension from a path
* @param path
* @return {*}
*/
enchant.Core.findExt = function(path) {
var matched = path.match(/\.\w+$/);
if (matched && matched.length > 0) {
return matched[0].slice(1).toLowerCase();
}
// for data URI
if (path.indexOf('data:') === 0) {
return path.split(/[\/;]/)[1].toLowerCase();
}
return null;
};
/**
* The Current Core instance.
* @type {enchant.Core}
* @static
*/
enchant.Core.instance = null;
}());
/**
* enchant.Core is moved to enchant.Core from v0.6
* @type {*}
*/
enchant.Game = enchant.Core;
/**
* @scope enchant.Node.prototype
*/
enchant.Node = enchant.Class.create(enchant.EventTarget, {
/**
* @name enchant.Node
* @class
* Base class for objects in the display tree which is rooted at a Scene.
* Not to be used directly.
* @constructs
* @extends enchant.EventTarget
*/
initialize: function() {
enchant.EventTarget.call(this);
this._dirty = false;
this._matrix = [ 1, 0, 0, 1, 0, 0 ];
this._x = 0;
this._y = 0;
this._offsetX = 0;
this._offsetY = 0;
/**
* The age (frames) of this node which will be increased before this node receives {@link enchant.Event.ENTER_FRAME} event.
* @type {Number}
*/
this.age = 0;
/**
* Parent Node of this Node.
* @type {enchant.Group}
*/
this.parentNode = null;
/**
* Scene to which Node belongs.
* @type {enchant.Scene}
*/
this.scene = null;
this.addEventListener('touchstart', function(e) {
if (this.parentNode) {
this.parentNode.dispatchEvent(e);
}
});
this.addEventListener('touchmove', function(e) {
if (this.parentNode) {
this.parentNode.dispatchEvent(e);
}
});
this.addEventListener('touchend', function(e) {
if (this.parentNode) {
this.parentNode.dispatchEvent(e);
}
});
/**
*/
if(enchant.ENV.USE_ANIMATION){
var tl = this.tl = new enchant.Timeline(this);
}
},
/**
* Move the Node to the given target location.
* @param {Number} x Target x coordinates.
* @param {Number} y Target y coordinates.
*/
moveTo: function(x, y) {
this._x = x;
this._y = y;
this._dirty = true;
},
/**
* Move the Node relative to its current position.
* @param {Number} x x axis movement distance.
* @param {Number} y y axis movement distance.
*/
moveBy: function(x, y) {
this._x += x;
this._y += y;
this._dirty = true;
},
/**
* x coordinates of the Node.
* @type {Number}
*/
x: {
get: function() {
return this._x;
},
set: function(x) {
this._x = x;
this._dirty = true;
}
},
/**
* y coordinates of the Node.
* @type {Number}
*/
y: {
get: function() {
return this._y;
},
set: function(y) {
this._y = y;
this._dirty = true;
}
},
_updateCoordinate: function() {
var node = this;
var tree = [ node ];
var parent = node.parentNode;
var scene = this.scene;
while (parent && node._dirty) {
tree.unshift(parent);
node = node.parentNode;
parent = node.parentNode;
}
var matrix = enchant.Matrix.instance;
var stack = matrix.stack;
var mat = [];
var newmat, ox, oy;
stack.push(tree[0]._matrix);
for (var i = 1, l = tree.length; i < l; i++) {
node = tree[i];
newmat = [];
matrix.makeTransformMatrix(node, mat);
matrix.multiply(stack[stack.length - 1], mat, newmat);
node._matrix = newmat;
stack.push(newmat);
ox = (typeof node._originX === 'number') ? node._originX : node._width / 2 || 0;
oy = (typeof node._originY === 'number') ? node._originY : node._height / 2 || 0;
var vec = [ ox, oy ];
matrix.multiplyVec(newmat, vec, vec);
node._offsetX = vec[0] - ox;
node._offsetY = vec[1] - oy;
node._dirty = false;
}
matrix.reset();
},
remove: function() {
if (this._listener) {
this.clearEventListener();
}
if (this.parentNode) {
this.parentNode.removeChild(this);
}
}
});
var _intersectBetweenClassAndInstance = function(Class, instance) {
var ret = [];
var c;
for (var i = 0, l = Class.collection.length; i < l; i++) {
c = Class.collection[i];
if (instance._intersectOne(c)) {
ret.push(c);
}
}
return ret;
};
var _intersectBetweenClassAndClass = function(Class1, Class2) {
var ret = [];
var c1, c2;
for (var i = 0, l = Class1.collection.length; i < l; i++) {
c1 = Class1.collection[i];
for (var j = 0, ll = Class2.collection.length; j < ll; j++) {
c2 = Class2.collection[j];
if (c1._intersectOne(c2)) {
ret.push([ c1, c2 ]);
}
}
}
return ret;
};
var _intersectStrictBetweenClassAndInstance = function(Class, instance) {
var ret = [];
var c;
for (var i = 0, l = Class.collection.length; i < l; i++) {
c = Class.collection[i];
if (instance._intersectStrictOne(c)) {
ret.push(c);
}
}
return ret;
};
var _intersectStrictBetweenClassAndClass = function(Class1, Class2) {
var ret = [];
var c1, c2;
for (var i = 0, l = Class1.collection.length; i < l; i++) {
c1 = Class1.collection[i];
for (var j = 0, ll = Class2.collection.length; j < ll; j++) {
c2 = Class2.collection[j];
if (c1._intersectStrictOne(c2)) {
ret.push([ c1, c2 ]);
}
}
}
return ret;
};
var _staticIntersect = function(other) {
if (other instanceof enchant.Entity) {
return _intersectBetweenClassAndInstance(this, other);
} else if (typeof other === 'function' && other.collection) {
return _intersectBetweenClassAndClass(this, other);
}
return false;
};
var _staticIntersectStrict = function(other) {
if (other instanceof enchant.Entity) {
return _intersectStrictBetweenClassAndInstance(this, other);
} else if (typeof other === 'function' && other.collection) {
return _intersectStrictBetweenClassAndClass(this, other);
}
return false;
};
/**
* @scope enchant.Entity.prototype
*/
enchant.Entity = enchant.Class.create(enchant.Node, {
/**
* @name enchant.Entity
* @class
* A class with objects displayed as DOM elements. Not to be used directly.
* @constructs
* @extends enchant.Node
*/
initialize: function() {
var core = enchant.Core.instance;
enchant.Node.call(this);
this._rotation = 0;
this._scaleX = 1;
this._scaleY = 1;
this._touchEnabled = true;
this._clipping = false;
this._originX = null;
this._originY = null;
this._width = 0;
this._height = 0;
this._backgroundColor = null;
this._debugColor = '#0000ff';
this._opacity = 1;
this._visible = true;
this._buttonMode = null;
this._style = {};
this.__styleStatus = {};
/**
*/
this.compositeOperation = null;
/**
* Defines this Entity as a button.
* When touched or clicked the corresponding button event is dispatched.
* Valid buttonModes are: left, right, up, down, a, b.
* @type {String}
*/
this.buttonMode = null;
/**
* Indicates if this Entity is being clicked.
* Only works when {@link enchant.Entity.buttonMode} is set.
* @type {Boolean}
*/
this.buttonPressed = false;
this.addEventListener('touchstart', function() {
if (!this.buttonMode) {
return;
}
this.buttonPressed = true;
var e = new enchant.Event(this.buttonMode + 'buttondown');
this.dispatchEvent(e);
core.dispatchEvent(e);
});
this.addEventListener('touchend', function() {
if (!this.buttonMode) {
return;
}
this.buttonPressed = false;
var e = new enchant.Event(this.buttonMode + 'buttonup');
this.dispatchEvent(e);
core.dispatchEvent(e);
});
this.enableCollection();
},
/**
* The width of the Entity.
* @type {Number}
*/
width: {
get: function() {
return this._width;
},
set: function(width) {
this._width = width;
this._dirty = true;
}
},
/**
* The height of the Entity.
* @type {Number}
*/
height: {
get: function() {
return this._height;
},
set: function(height) {
this._height = height;
this._dirty = true;
}
},
/**
* The Entity background color.
* Must be provided in the same format as the CSS 'color' property.
* @type {String}
*/
backgroundColor: {
get: function() {
return this._backgroundColor;
},
set: function(color) {
this._backgroundColor = color;
}
},
/**
* The Entity debug color.
* Must be provided in the same format as the CSS 'color' property.
* @type {String}
*/
debugColor: {
get: function() {
return this._debugColor;
},
set: function(color) {
this._debugColor = color;
}
},
/**
* The transparency of this entity.
* Defines the transparency level from 0 to 1
* (0 is completely transparent, 1 is completely opaque).
* @type {Number}
*/
opacity: {
get: function() {
return this._opacity;
},
set: function(opacity) {
this._opacity = parseFloat(opacity);
}
},
/**
* Indicates whether or not to display this Entity.
* @type {Boolean}
*/
visible: {
get: function() {
return this._visible;
},
set: function(visible) {
this._visible = visible;
}
},
/**
* Indicates whether or not this Entity can be touched.
* @type {Boolean}
*/
touchEnabled: {
get: function() {
return this._touchEnabled;
},
set: function(enabled) {
this._touchEnabled = enabled;
if (enabled) {
this._style.pointerEvents = 'all';
} else {
this._style.pointerEvents = 'none';
}
}
},
/**
* Performs a collision detection based on whether or not the bounding rectangles are intersecting.
* @param {*} other An object like Entity, with the properties x, y, width, height, which are used for the
* collision detection.
* @return {Boolean} True, if a collision was detected.
*/
intersect: function(other) {
if (other instanceof enchant.Entity) {
return this._intersectOne(other);
} else if (typeof other === 'function' && other.collection) {
return _intersectBetweenClassAndInstance(other, this);
}
return false;
},
_intersectOne: function(other) {
if (this._dirty) {
this._updateCoordinate();
} if (other._dirty) {
other._updateCoordinate();
}
return this._offsetX < other._offsetX + other.width && other._offsetX < this._offsetX + this.width &&
this._offsetY < other._offsetY + other.height && other._offsetY < this._offsetY + this.height;
},
intersectStrict: function(other) {
if (other instanceof enchant.Entity) {
return this._intersectStrictOne(other);
} else if (typeof other === 'function' && other.collection) {
return _intersectStrictBetweenClassAndInstance(other, this);
}
return false;
},
_intersectStrictOne: function(other) {
if (this._dirty) {
this._updateCoordinate();
} if (other._dirty) {
other._updateCoordinate();
}
var rect1 = this.getOrientedBoundingRect(),
rect2 = other.getOrientedBoundingRect(),
lt1 = rect1.leftTop, rt1 = rect1.rightTop,
lb1 = rect1.leftBottom, rb1 = rect1.rightBottom,
lt2 = rect2.leftTop, rt2 = rect2.rightTop,
lb2 = rect2.leftBottom, rb2 = rect2.rightBottom,
ltx1 = lt1[0], lty1 = lt1[1], rtx1 = rt1[0], rty1 = rt1[1],
lbx1 = lb1[0], lby1 = lb1[1], rbx1 = rb1[0], rby1 = rb1[1],
ltx2 = lt2[0], lty2 = lt2[1], rtx2 = rt2[0], rty2 = rt2[1],
lbx2 = lb2[0], lby2 = lb2[1], rbx2 = rb2[0], rby2 = rb2[1],
t1 = [ rtx1 - ltx1, rty1 - lty1 ],
r1 = [ rbx1 - rtx1, rby1 - rty1 ],
b1 = [ lbx1 - rbx1, lby1 - rby1 ],
l1 = [ ltx1 - lbx1, lty1 - lby1 ],
t2 = [ rtx2 - ltx2, rty2 - lty2 ],
r2 = [ rbx2 - rtx2, rby2 - rty2 ],
b2 = [ lbx2 - rbx2, lby2 - rby2 ],
l2 = [ ltx2 - lbx2, lty2 - lby2 ],
cx1 = (ltx1 + rtx1 + lbx1 + rbx1) >> 2,
cy1 = (lty1 + rty1 + lby1 + rby1) >> 2,
cx2 = (ltx2 + rtx2 + lbx2 + rbx2) >> 2,
cy2 = (lty2 + rty2 + lby2 + rby2) >> 2,
i, j, poss1, poss2, dirs1, dirs2, pos1, pos2, dir1, dir2,
px1, py1, px2, py2, dx1, dy1, dx2, dy2, vx, vy, c, c1, c2;
if (t1[0] * (cy2 - lty1) - t1[1] * (cx2 - ltx1) > 0 &&
r1[0] * (cy2 - rty1) - r1[1] * (cx2 - rtx1) > 0 &&
b1[0] * (cy2 - rby1) - b1[1] * (cx2 - rbx1) > 0 &&
l1[0] * (cy2 - lby1) - l1[1] * (cx2 - lbx1) > 0) {
return true;
} else if (t2[0] * (cy1 - lty2) - t2[1] * (cx1 - ltx2) > 0 &&
r2[0] * (cy1 - rty2) - r2[1] * (cx1 - rtx2) > 0 &&
b2[0] * (cy1 - rby2) - b2[1] * (cx1 - rbx2) > 0 &&
l2[0] * (cy1 - lby2) - l2[1] * (cx1 - lbx2) > 0) {
return true;
} else {
poss1 = [ lt1, rt1, rb1, lb1 ];
poss2 = [ lt2, rt2, rb2, lb2 ];
dirs1 = [ t1, r1, b1, l1 ];
dirs2 = [ t2, r2, b2, l2 ];
for (i = 0; i < 4; i++) {
pos1 = poss1[i];
px1 = pos1[0]; py1 = pos1[1];
dir1 = dirs1[i];
dx1 = dir1[0]; dy1 = dir1[1];
for (j = 0; j < 4; j++) {
pos2 = poss2[j];
px2 = pos2[0]; py2 = pos2[1];
dir2 = dirs2[j];
dx2 = dir2[0]; dy2 = dir2[1];
c = dx1 * dy2 - dy1 * dx2;
if (c !== 0) {
vx = px2 - px1;
vy = py2 - py1;
c1 = (vx * dy1 - vy * dx1) / c;
c2 = (vx * dy2 - vy * dx2) / c;
if (0 < c1 && c1 < 1 && 0 < c2 && c2 < 1) {
return true;
}
}
}
}
return false;
}
},
/**
* Performs a collision detection based on distance from the Entity's central point.
* @param {*} other An object like Entity, with properties x, y, width, height, which are used for the
* collision detection.
* @param {Number} [distance] The greatest distance to be considered for a collision.
* The default distance is the average of both objects width and height.
* @return {Boolean} True, if a collision was detected.
*/
within: function(other, distance) {
if (this._dirty) {
this._updateCoordinate();
} if (other._dirty) {
other._updateCoordinate();
}
if (distance == null) {
distance = (this.width + this.height + other.width + other.height) / 4;
}
var _;
return (_ = this._offsetX - other._offsetX + (this.width - other.width) / 2) * _ +
(_ = this._offsetY - other._offsetY + (this.height - other.height) / 2) * _ < distance * distance;
}, /**
* Enlarges or shrinks this Sprite.
* @param {Number} x Scaling factor on the x axis.
* @param {Number} [y] Scaling factor on the y axis.
*/
scale: function(x, y) {
this._scaleX *= x;
this._scaleY *= (y != null) ? y : x;
this._dirty = true;
},
/**
* Rotate this Sprite.
* @param {Number} deg Rotation angle (degree).
*/
rotate: function(deg) {
this._rotation += deg;
this._dirty = true;
},
/**
* Scaling factor on the x axis of this Sprite.
* @type {Number}
*/
scaleX: {
get: function() {
return this._scaleX;
},
set: function(scaleX) {
this._scaleX = scaleX;
this._dirty = true;
}
},
/**
* Scaling factor on the y axis of this Sprite.
* @type {Number}
*/
scaleY: {
get: function() {
return this._scaleY;
},
set: function(scaleY) {
this._scaleY = scaleY;
this._dirty = true;
}
},
/**
* Sprite rotation angle (degree).
* @type {Number}
*/
rotation: {
get: function() {
return this._rotation;
},
set: function(rotation) {
this._rotation = rotation;
this._dirty = true;
}
},
/**
* The point of origin used for rotation and scaling.
* @type {Number}
*/
originX: {
get: function() {
return this._originX;
},
set: function(originX) {
this._originX = originX;
this._dirty = true;
}
},
/**
* The point of origin used for rotation and scaling.
* @type {Number}
*/
originY: {
get: function() {
return this._originY;
},
set: function(originY) {
this._originY = originY;
this._dirty = true;
}
},
/**
*/
enableCollection: function() {
this.addEventListener('addedtoscene', this._addSelfToCollection);
this.addEventListener('removedfromscene', this._removeSelfFromCollection);
if (this.scene) {
this._addSelfToCollection();
}
},
/**
*/
disableCollection: function() {
this.removeEventListener('addedtoscene', this._addSelfToCollection);
this.removeEventListener('removedfromscene', this._removeSelfFromCollection);
if (this.scene) {
this._removeSelfFromCollection();
}
},
_addSelfToCollection: function() {
var Constructor = this.getConstructor();
Constructor._collectionTarget.forEach(function(C) {
C.collection.push(this);
}, this);
},
_removeSelfFromCollection: function() {
var Constructor = this.getConstructor();
Constructor._collectionTarget.forEach(function(C) {
var i = C.collection.indexOf(this);
if (i !== -1) {
C.collection.splice(i, 1);
}
}, this);
},
getBoundingRect: function() {
var w = this.width || 0;
var h = this.height || 0;
var mat = this._matrix;
var m11w = mat[0] * w, m12w = mat[1] * w,
m21h = mat[2] * h, m22h = mat[3] * h,
mdx = mat[4], mdy = mat[5];
var xw = [ mdx, m11w + mdx, m21h + mdx, m11w + m21h + mdx ].sort(function(a, b) { return a - b; });
var yh = [ mdy, m12w + mdy, m22h + mdy, m12w + m22h + mdy ].sort(function(a, b) { return a - b; });
return {
left: xw[0],
top: yh[0],
width: xw[3] - xw[0],
height: yh[3] - yh[0]
};
},
getOrientedBoundingRect: function() {
var w = this.width || 0;
var h = this.height || 0;
var mat = this._matrix;
var m11w = mat[0] * w, m12w = mat[1] * w,
m21h = mat[2] * h, m22h = mat[3] * h,
mdx = mat[4], mdy = mat[5];
return {
leftTop: [ mdx, mdy ],
rightTop: [ m11w + mdx, m12w + mdy ],
leftBottom: [ m21h + mdx, m22h + mdy ],
rightBottom: [ m11w + m21h + mdx, m12w + m22h + mdy ]
};
},
getConstructor: function() {
return Object.getPrototypeOf(this).constructor;
}
});
var _collectizeConstructor = function(Constructor) {
if (Constructor._collective) {
return;
}
var rel = enchant.Class.getInheritanceTree(Constructor);
var i = rel.indexOf(enchant.Entity);
if (i !== -1) {
Constructor._collectionTarget = rel.splice(0, i + 1);
} else {
Constructor._collectionTarget = [];
}
Constructor.intersect = _staticIntersect;
Constructor.intersectStrict = _staticIntersectStrict;
Constructor.collection = [];
Constructor._collective = true;
};
_collectizeConstructor(enchant.Entity);
enchant.Entity._inherited = function(subclass) {
_collectizeConstructor(subclass);
};
/**
* @scope enchant.Sprite.prototype
*/
enchant.Sprite = enchant.Class.create(enchant.Entity, {
/**
* @name enchant.Sprite
* @class
* Class which can display images.
*
* @param {Number} [width] Sprite width.
* @param {Number} [height] Sprite height.
* @example
* var bear = new Sprite(32, 32);
* bear.image = core.assets['chara1.gif'];
*
* @constructs
* @extends enchant.Entity
*/
initialize: function(width, height) {
enchant.Entity.call(this);
this.width = width;
this.height = height;
this._image = null;
this._debugColor = '#ff0000';
this._frameLeft = 0;
this._frameTop = 0;
this._frame = 0;
this._frameSequence = [];
/**
*/
this.addEventListener('enterframe', function() {
if (this._frameSequence.length !== 0) {
var nextFrame = this._frameSequence.shift();
if (nextFrame === null) {
this._frameSequence = [];
} else {
this._setFrame(nextFrame);
this._frameSequence.push(nextFrame);
}
}
});
},
/**
* Image displayed in the Sprite.
* @type {enchant.Surface}
*/
image: {
get: function() {
return this._image;
},
set: function(image) {
if (image === undefined) {
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.');
}
if (image === this._image) {
return;
}
this._image = image;
this._setFrame(this._frame);
}
},
/**
* Indizes of the frames to be displayed.
* Frames with same width and height as Sprite will be arrayed from upper left corner of the
* {@link enchant.Sprite#image} image. When a sequence of numbers is provided, the displayed frame
* will switch automatically. At the end of the array the sequence will restart. By setting
* a value within the sequence to null, the frame switching is stopped.
* @example
* var sprite = new Sprite(32, 32);
* sprite.frame = [0, 1, 0, 2]
* //-> 0, 1, 0, 2, 0, 1, 0, 2,..
* sprite.frame = [0, 1, 0, 2, null]
* //-> 0, 1, 0, 2, (2, 2,.. :stop)
*
* @type {Number|Array}
*/
frame: {
get: function() {
return this._frame;
},
set: function(frame) {
if(this._frame === frame) {
return;
}
if (frame instanceof Array) {
var frameSequence = frame;
var nextFrame = frameSequence.shift();
this._setFrame(nextFrame);
frameSequence.push(nextFrame);
this._frameSequence = frameSequence;
} else {
this._setFrame(frame);
this._frameSequence = [];
this._frame = frame;
}
}
},
/**
* 0 <= frame
* @param frame
* @private
*/
_setFrame: function(frame) {
var image = this._image;
var row, col;
if (image != null) {
this._frame = frame;
row = image.width / this._width | 0;
this._frameLeft = (frame % row | 0) * this._width;
this._frameTop = (frame / row | 0) * this._height % image.height;
}
},
/**
* width of Sprite
* @type {Number}
*/
width: {
get: function() {
return this._width;
},
set: function(width) {
this._width = width;
this._setFrame(this._frame);
this._dirty = true;
}
},
/**
* height of Sprite
* @type {Number}
*/
height: {
get: function() {
return this._height;
},
set: function(height) {
this._height = height;
this._setFrame(this._frame);
this._dirty = true;
}
},
cvsRender: function(ctx) {
var image = this._image,
w = this._width, h = this._height,
iw, ih, elem, sx, sy, sw, sh;
if (image && w !== 0 && h !== 0) {
iw = image.width, ih = image.height;
if (iw < w || ih < h) {
ctx.fillStyle = enchant.Surface._getPattern(image);
ctx.fillRect(0, 0, w, h);
} else {
elem = image._element;
sx = this._frameLeft;
sy = Math.min(this._frameTop, ih - h);
// IE9 doesn't allow for negative or 0 widths/heights when drawing on the CANVAS element
sw = Math.max(0.01, Math.min(iw - sx, w));
sh = Math.max(0.01, Math.min(ih - sy, h));
ctx.drawImage(elem, sx, sy, sw, sh, 0, 0, w, h);
}
}
},
domRender: (function() {
if (enchant.ENV.VENDOR_PREFIX === 'ms') {
return function(element) {
if (this._image) {
if (this._image._css) {
this._style['background-image'] = this._image._css;
this._style['background-position'] =
-this._frameLeft + 'px ' +
-this._frameTop + 'px';
} else if (this._image._element) {
}
}
};
} else {
return function(element) {
if (this._image) {
if (this._image._css) {
this._style['background-image'] = this._image._css;
this._style['background-position'] =
-this._frameLeft + 'px ' +
-this._frameTop + 'px';
} else if (this._image._element) {
}
}
};
}
}())
});
/**
* @scope enchant.Label.prototype
*/
enchant.Label = enchant.Class.create(enchant.Entity, {
/**
* @name enchant.Label
* @class
* A class for Label object.
* @constructs
* @extends enchant.Entity
*/
initialize: function(text) {
enchant.Entity.call(this);
this.text = text || '';
this.width = 300;
this.font = '14px serif';
this.textAlign = 'left';
this._debugColor = '#ff0000';
},
width: {
get: function() {
return this._width;
},
set: function(width) {
this._width = width;
this._dirty = true;
// issue #164
this.updateBoundArea();
}
},
/**
* Text to be displayed.
* @type {String}
*/
text: {
get: function() {
return this._text;
},
set: function(text) {
text = '' + text;
if(this._text === text) {
return;
}
this._text = text;
text = text.replace(/<(br|BR) ?\/?>/g, '<br/>');
this._splitText = text.split('<br/>');
this.updateBoundArea();
for (var i = 0, l = this._splitText.length; i < l; i++) {
text = this._splitText[i];
var metrics = this.getMetrics(text);
this._splitText[i] = {};
this._splitText[i].text = text;
this._splitText[i].height = metrics.height;
}
}
},
/**
* Specifies horizontal alignment of text.
* Can be set according to the format of the CSS 'text-align' property.
* @type {String}
*/
textAlign: {
get: function() {
return this._style['text-align'];
},
set: function(textAlign) {
this._style['text-align'] = textAlign;
this.updateBoundArea();
}
},
/**
* Font settings.
* Can be set according to the format of the CSS 'font' property.
* @type {String}
*/
font: {
get: function() {
return this._style.font;
},
set: function(font) {
this._style.font = font;
this.updateBoundArea();
}
},
/**
* Text color settings.
* Can be set according to the format of the CSS 'color' property.
* @type {String}
*/
color: {
get: function() {
return this._style.color;
},
set: function(color) {
this._style.color = color;
}
},
cvsRender: function(ctx) {
var x, y = 0;
var labelWidth = this.width;
var charWidth, amount, line, text, c, buf, increase, length;
var bufWidth;
if (this._splitText) {
ctx.textBaseline = 'top';
ctx.font = this.font;
ctx.fillStyle = this.color || '#000000';
charWidth = ctx.measureText(' ').width;
amount = labelWidth / charWidth;
for (var i = 0, l = this._splitText.length; i < l; i++) {
line = this._splitText[i];
text = line.text;
c = 0;
while (text.length > c + amount || ctx.measureText(text.slice(c, c + amount)).width > labelWidth) {
buf = '';
increase = amount;
length = 0;
while (increase > 0) {
if (ctx.measureText(buf).width < labelWidth) {
length += increase;
buf = text.slice(c, c + length);
} else {
length -= increase;
buf = text.slice(c, c + length);
}
increase = increase / 2 | 0;
}
ctx.fillText(buf, 0, y);
y += line.height - 1;
c += length;
}
buf = text.slice(c, c + text.length);
if (this.textAlign === 'right') {
x = labelWidth - ctx.measureText(buf).width;
} else if (this.textAlign === 'center') {
x = (labelWidth - ctx.measureText(buf).width) / 2;
} else {
x = 0;
}
ctx.fillText(buf, x, y);
y += line.height - 1;
}
}
},
domRender: function(element) {
if (element.innerHTML !== this._text) {
element.innerHTML = this._text;
}
},
detectRender: function(ctx) {
ctx.fillRect(this._boundOffset, 0, this._boundWidth, this._boundHeight);
},
updateBoundArea: function() {
var metrics = this.getMetrics();
this._boundWidth = metrics.width;
this._boundHeight = metrics.height;
if (this.textAlign === 'right') {
this._boundOffset = this.width - this._boundWidth;
} else if (this.textAlign === 'center') {
this._boundOffset = (this.width - this._boundWidth) / 2;
} else {
this._boundOffset = 0;
}
},
getMetrics: function(text) {
var ret = {};
var div, width, height;
if (document.body) {
div = document.createElement('div');
for (var prop in this._style) {
if(prop !== 'width' && prop !== 'height') {
div.style[prop] = this._style[prop];
}
}
text = text || this._text;
div.innerHTML = text.replace(/ /g, '&nbsp;');
div.style.whiteSpace = 'noWrap';
div.style.lineHeight = 1;
document.body.appendChild(div);
ret.height = parseInt(getComputedStyle(div).height, 10) + 1;
div.style.position = 'absolute';
ret.width = parseInt(getComputedStyle(div).width, 10) + 1;
document.body.removeChild(div);
} else {
ret.width = this.width;
ret.height = this.height;
}
return ret;
}
});
/**
* @scope enchant.Map.prototype
*/
enchant.Map = enchant.Class.create(enchant.Entity, {
/**
* @name enchant.Map
* @class
* A class to create and display maps from a tile set.
*
* @param {Number} tileWidth Tile width.
* @param {Number} tileHeight Tile height.
* @constructs
* @extends enchant.Entity
*/
initialize: function(tileWidth, tileHeight) {
var core = enchant.Core.instance;
enchant.Entity.call(this);
var surface = new enchant.Surface(core.width, core.height);
this._surface = surface;
var canvas = surface._element;
canvas.style.position = 'absolute';
if (enchant.ENV.RETINA_DISPLAY && core.scale === 2) {
canvas.width = core.width * 2;
canvas.height = core.height * 2;
this._style.webkitTransformOrigin = '0 0';
this._style.webkitTransform = 'scale(0.5)';
} else {
canvas.width = core.width;
canvas.height = core.height;
}
this._context = canvas.getContext('2d');
this._tileWidth = tileWidth || 0;
this._tileHeight = tileHeight || 0;
this._image = null;
this._data = [
[
[]
]
];
this._dirty = false;
this._tight = false;
this.touchEnabled = false;
/**
* Two dimensional array to store if collision detection should be performed for a tile.
* @type {Array.<Array.<Number>>}
*/
this.collisionData = null;
this._listeners['render'] = null;
this.addEventListener('render', function() {
if (this._dirty || this._previousOffsetX == null) {
this.redraw(0, 0, core.width, core.height);
} else if (this._offsetX !== this._previousOffsetX ||
this._offsetY !== this._previousOffsetY) {
if (this._tight) {
var x = -this._offsetX;
var y = -this._offsetY;
var px = -this._previousOffsetX;
var py = -this._previousOffsetY;
var w1 = x - px + core.width;
var w2 = px - x + core.width;
var h1 = y - py + core.height;
var h2 = py - y + core.height;
if (w1 > this._tileWidth && w2 > this._tileWidth &&
h1 > this._tileHeight && h2 > this._tileHeight) {
var sx, sy, dx, dy, sw, sh;
if (w1 < w2) {
sx = 0;
dx = px - x;
sw = w1;
} else {
sx = x - px;
dx = 0;
sw = w2;
}
if (h1 < h2) {
sy = 0;
dy = py - y;
sh = h1;
} else {
sy = y - py;
dy = 0;
sh = h2;
}
if (core._buffer == null) {
core._buffer = document.createElement('canvas');
core._buffer.width = this._context.canvas.width;
core._buffer.height = this._context.canvas.height;
}
var context = core._buffer.getContext('2d');
if (this._doubledImage) {
context.clearRect(0, 0, sw * 2, sh * 2);
context.drawImage(this._context.canvas,
sx * 2, sy * 2, sw * 2, sh * 2, 0, 0, sw * 2, sh * 2);
context = this._context;
context.clearRect(dx * 2, dy * 2, sw * 2, sh * 2);
context.drawImage(core._buffer,
0, 0, sw * 2, sh * 2, dx * 2, dy * 2, sw * 2, sh * 2);
} else {
context.clearRect(0, 0, sw, sh);
context.drawImage(this._context.canvas,
sx, sy, sw, sh, 0, 0, sw, sh);
context = this._context;
context.clearRect(dx, dy, sw, sh);
context.drawImage(core._buffer,
0, 0, sw, sh, dx, dy, sw, sh);
}
if (dx === 0) {
this.redraw(sw, 0, core.width - sw, core.height);
} else {
this.redraw(0, 0, core.width - sw, core.height);
}
if (dy === 0) {
this.redraw(0, sh, core.width, core.height - sh);
} else {
this.redraw(0, 0, core.width, core.height - sh);
}
} else {
this.redraw(0, 0, core.width, core.height);
}
} else {
this.redraw(0, 0, core.width, core.height);
}
}
this._previousOffsetX = this._offsetX;
this._previousOffsetY = this._offsetY;
});
},
/**
* Set map data.
* Sets the tile data, whereas the data (two-dimensional array with indizes starting from 0)
* is mapped on the image starting from the upper left corner.
* When more than one map data array is set, they are displayed in reverse order.
* @param {...Array<Array.<Number>>} data Two-dimensional array of tile indizes. Multiple designations possible.
*/
loadData: function(data) {
this._data = Array.prototype.slice.apply(arguments);
this._dirty = true;
this._tight = false;
for (var i = 0, len = this._data.length; i < len; i++) {
var c = 0;
data = this._data[i];
for (var y = 0, l = data.length; y < l; y++) {
for (var x = 0, ll = data[y].length; x < ll; x++) {
if (data[y][x] >= 0) {
c++;
}
}
}
if (c / (data.length * data[0].length) > 0.2) {
this._tight = true;
break;
}
}
},
/**
* Checks what tile is present at the given position.
* @param {Number} x x coordinates of the point on the map.
* @param {Number} y y coordinates of the point on the map.
* @return {*} The tile data for the given position.
*/
checkTile: function(x, y) {
if (x < 0 || this.width <= x || y < 0 || this.height <= y) {
return false;
}
var width = this._image.width;
var height = this._image.height;
var tileWidth = this._tileWidth || width;
var tileHeight = this._tileHeight || height;
x = x / tileWidth | 0;
y = y / tileHeight | 0;
// return this._data[y][x];
var data = this._data[0];
return data[y][x];
},
/**
* Judges whether or not obstacles are on top of Map.
* @param {Number} x x coordinates of detection spot on map.
* @param {Number} y y coordinates of detection spot on map.
* @return {Boolean} True, if there are obstacles.
*/
hitTest: function(x, y) {
if (x < 0 || this.width <= x || y < 0 || this.height <= y) {
return false;
}
var width = this._image.width;
var height = this._image.height;
var tileWidth = this._tileWidth || width;
var tileHeight = this._tileHeight || height;
x = x / tileWidth | 0;
y = y / tileHeight | 0;
if (this.collisionData != null) {
return this.collisionData[y] && !!this.collisionData[y][x];
} else {
for (var i = 0, len = this._data.length; i < len; i++) {
var data = this._data[i];
var n;
if (data[y] != null && (n = data[y][x]) != null &&
0 <= n && n < (width / tileWidth | 0) * (height / tileHeight | 0)) {
return true;
}
}
return false;
}
},
/**
* Image with which the tile set is displayed on the map.
* @type {enchant.Surface}
*/
image: {
get: function() {
return this._image;
},
set: function(image) {
var core = enchant.Core.instance;
this._image = image;
if (enchant.ENV.RETINA_DISPLAY && core.scale === 2) {
var img = new enchant.Surface(image.width * 2, image.height * 2);
var tileWidth = this._tileWidth || image.width;
var tileHeight = this._tileHeight || image.height;
var row = image.width / tileWidth | 0;
var col = image.height / tileHeight | 0;
for (var y = 0; y < col; y++) {
for (var x = 0; x < row; x++) {
img.draw(image, x * tileWidth, y * tileHeight, tileWidth, tileHeight,
x * tileWidth * 2, y * tileHeight * 2, tileWidth * 2, tileHeight * 2);
}
}
this._doubledImage = img;
}
this._dirty = true;
}
},
/**
* Map tile width.
* @type {Number}
*/
tileWidth: {
get: function() {
return this._tileWidth;
},
set: function(tileWidth) {
this._tileWidth = tileWidth;
this._dirty = true;
}
},
/**
* Map tile height.
* @type {Number}
*/
tileHeight: {
get: function() {
return this._tileHeight;
},
set: function(tileHeight) {
this._tileHeight = tileHeight;
this._dirty = true;
}
},
/**
* @private
*/
width: {
get: function() {
return this._tileWidth * this._data[0][0].length;
}
},
/**
* @private
*/
height: {
get: function() {
return this._tileHeight * this._data[0].length;
}
},
/**
* @private
*/
redraw: function(x, y, width, height) {
if (this._image == null) {
return;
}
var image, tileWidth, tileHeight, dx, dy;
if (this._doubledImage) {
image = this._doubledImage;
tileWidth = this._tileWidth * 2;
tileHeight = this._tileHeight * 2;
dx = -this._offsetX * 2;
dy = -this._offsetY * 2;
x *= 2;
y *= 2;
width *= 2;
height *= 2;
} else {
image = this._image;
tileWidth = this._tileWidth;
tileHeight = this._tileHeight;
dx = -this._offsetX;
dy = -this._offsetY;
}
var row = image.width / tileWidth | 0;
var col = image.height / tileHeight | 0;
var left = Math.max((x + dx) / tileWidth | 0, 0);
var top = Math.max((y + dy) / tileHeight | 0, 0);
var right = Math.ceil((x + dx + width) / tileWidth);
var bottom = Math.ceil((y + dy + height) / tileHeight);
var source = image._element;
var context = this._context;
var canvas = context.canvas;
context.clearRect(x, y, width, height);
for (var i = 0, len = this._data.length; i < len; i++) {
var data = this._data[i];
var r = Math.min(right, data[0].length);
var b = Math.min(bottom, data.length);
for (y = top; y < b; y++) {
for (x = left; x < r; x++) {
var n = data[y][x];
if (0 <= n && n < row * col) {
var sx = (n % row) * tileWidth;
var sy = (n / row | 0) * tileHeight;
context.drawImage(source, sx, sy, tileWidth, tileHeight,
x * tileWidth - dx, y * tileHeight - dy, tileWidth, tileHeight);
}
}
}
}
},
cvsRender: function(ctx) {
var core = enchant.Core.instance;
if (this.width !== 0 && this.height !== 0) {
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
var cvs = this._context.canvas;
ctx.drawImage(cvs, 0, 0, core.width, core.height);
ctx.restore();
}
},
domRender: function(element) {
if (this._image) {
this._style['background-image'] = this._surface._css;
// bad performance
this._style[enchant.ENV.VENDOR_PREFIX + 'Transform'] = 'matrix(1, 0, 0, 1, 0, 0)';
}
}
});
/**
* @scope enchant.Group.prototype
*/
enchant.Group = enchant.Class.create(enchant.Node, {
/**
* @name enchant.Group
* @class
* A class that can hold multiple {@link enchant.Node}.
*
* @example
* var stage = new Group();
* stage.addChild(player);
* stage.addChild(enemy);
* stage.addChild(map);
* stage.addEventListener('enterframe', function() {
* // Moves the entire frame in according to the player's coordinates.
* if (this.x > 64 - player.x) {
* this.x = 64 - player.x;
* }
* });
*
* @constructs
* @extends enchant.Node
*/
initialize: function() {
/**
* Child Nodes.
* @type {Array.<enchant.Node>}
*/
this.childNodes = [];
enchant.Node.call(this);
this._rotation = 0;
this._scaleX = 1;
this._scaleY = 1;
this._originX = null;
this._originY = null;
this.__dirty = false;
[enchant.Event.ADDED_TO_SCENE, enchant.Event.REMOVED_FROM_SCENE]
.forEach(function(event) {
this.addEventListener(event, function(e) {
this.childNodes.forEach(function(child) {
child.scene = this.scene;
child.dispatchEvent(e);
}, this);
});
}, this);
},
/**
* Adds a Node to the Group.
* @param {enchant.Node} node Node to be added.
*/
addChild: function(node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
this.childNodes.push(node);
node.parentNode = this;
var childAdded = new enchant.Event('childadded');
childAdded.node = node;
childAdded.next = null;
this.dispatchEvent(childAdded);
node.dispatchEvent(new enchant.Event('added'));
if (this.scene) {
node.scene = this.scene;
var addedToScene = new enchant.Event('addedtoscene');
node.dispatchEvent(addedToScene);
}
},
/**
* Incorporates Node into Group.
* @param {enchant.Node} node Node to be incorporated.
* @param {enchant.Node} reference Node in position before insertion.
*/
insertBefore: function(node, reference) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
var i = this.childNodes.indexOf(reference);
if (i !== -1) {
this.childNodes.splice(i, 0, node);
node.parentNode = this;
var childAdded = new enchant.Event('childadded');
childAdded.node = node;
childAdded.next = reference;
this.dispatchEvent(childAdded);
node.dispatchEvent(new enchant.Event('added'));
if (this.scene) {
node.scene = this.scene;
var addedToScene = new enchant.Event('addedtoscene');
node.dispatchEvent(addedToScene);
}
} else {
this.addChild(node);
}
},
/**
* Remove a Node from the Group.
* @param {enchant.Node} node Node to be deleted.
*/
removeChild: function(node) {
var i;
if ((i = this.childNodes.indexOf(node)) !== -1) {
this.childNodes.splice(i, 1);
node.parentNode = null;
var childRemoved = new enchant.Event('childremoved');
childRemoved.node = node;
this.dispatchEvent(childRemoved);
node.dispatchEvent(new enchant.Event('removed'));
if (this.scene) {
node.scene = null;
var removedFromScene = new enchant.Event('removedfromscene');
node.dispatchEvent(removedFromScene);
}
}
},
/**
* The Node which is the first child.
* @type {enchant.Node}
*/
firstChild: {
get: function() {
return this.childNodes[0];
}
},
/**
* The Node which is the last child.
* @type {enchant.Node}
*/
lastChild: {
get: function() {
return this.childNodes[this.childNodes.length - 1];
}
},
/**
* Group rotation angle (degree).
* @type {Number}
*/
rotation: {
get: function() {
return this._rotation;
},
set: function(rotation) {
this._rotation = rotation;
this._dirty = true;
}
},
/**
* Scaling factor on the x axis of the Group.
* @type {Number}
* @see enchant.CanvasGroup.originX
* @see enchant.CanvasGroup.originY
*/
scaleX: {
get: function() {
return this._scaleX;
},
set: function(scale) {
this._scaleX = scale;
this._dirty = true;
}
},
/**
* Scaling factor on the y axis of the Group.
* @type {Number}
* @see enchant.CanvasGroup.originX
* @see enchant.CanvasGroup.originY
*/
scaleY: {
get: function() {
return this._scaleY;
},
set: function(scale) {
this._scaleY = scale;
this._dirty = true;
}
},
/**
* origin point of rotation, scaling
* @type {Number}
*/
originX: {
get: function() {
return this._originX;
},
set: function(originX) {
this._originX = originX;
this._dirty = true;
}
},
/**
* origin point of rotation, scaling
* @type {Number}
*/
originY: {
get: function() {
return this._originY;
},
set: function(originY) {
this._originY = originY;
this._dirty = true;
}
},
_dirty: {
get: function() {
return this.__dirty;
},
set: function(dirty) {
dirty = !!dirty;
this.__dirty = dirty;
if (dirty) {
for (var i = 0, l = this.childNodes.length; i < l; i++) {
this.childNodes[i]._dirty = true;
}
}
}
}
});
enchant.Matrix = enchant.Class.create({
initialize: function() {
this.reset();
},
reset: function() {
this.stack = [];
this.stack.push([ 1, 0, 0, 1, 0, 0 ]);
},
makeTransformMatrix: function(node, dest) {
var x = node._x;
var y = node._y;
var width = node.width || 0;
var height = node.height || 0;
var rotation = node._rotation || 0;
var scaleX = (typeof node._scaleX === 'number') ? node._scaleX : 1;
var scaleY = (typeof node._scaleY === 'number') ? node._scaleY : 1;
var theta = rotation * Math.PI / 180;
var tmpcos = Math.cos(theta);
var tmpsin = Math.sin(theta);
var w = (typeof node._originX === 'number') ? node._originX : width / 2;
var h = (typeof node._originY === 'number') ? node._originY : height / 2;
var a = scaleX * tmpcos;
var b = scaleX * tmpsin;
var c = scaleY * tmpsin;
var d = scaleY * tmpcos;
dest[0] = a;
dest[1] = b;
dest[2] = -c;
dest[3] = d;
dest[4] = (-a * w + c * h + x + w);
dest[5] = (-b * w - d * h + y + h);
},
multiply: function(m1, m2, dest) {
var a11 = m1[0], a21 = m1[2], adx = m1[4],
a12 = m1[1], a22 = m1[3], ady = m1[5];
var b11 = m2[0], b21 = m2[2], bdx = m2[4],
b12 = m2[1], b22 = m2[3], bdy = m2[5];
dest[0] = a11 * b11 + a21 * b12;
dest[1] = a12 * b11 + a22 * b12;
dest[2] = a11 * b21 + a21 * b22;
dest[3] = a12 * b21 + a22 * b22;
dest[4] = a11 * bdx + a21 * bdy + adx;
dest[5] = a12 * bdx + a22 * bdy + ady;
},
multiplyVec: function(mat, vec, dest) {
var x = vec[0], y = vec[1];
var m11 = mat[0], m21 = mat[2], mdx = mat[4],
m12 = mat[1], m22 = mat[3], mdy = mat[5];
dest[0] = m11 * x + m21 * y + mdx;
dest[1] = m12 * x + m22 * y + mdy;
}
});
enchant.Matrix.instance = new enchant.Matrix();
enchant.DetectColorManager = enchant.Class.create({
initialize: function(reso, max) {
this.reference = [];
this.colorResolution = reso || 16;
this.max = max || 1;
this.capacity = Math.pow(this.colorResolution, 3);
for (var i = 1, l = this.capacity; i < l; i++) {
this.reference[i] = null;
}
},
attachDetectColor: function(sprite) {
var i = this.reference.indexOf(null);
if (i === -1) {
i = 1;
}
this.reference[i] = sprite;
return this._getColor(i);
},
detachDetectColor: function(sprite) {
var i = this.reference.indexOf(sprite);
if (i !== -1) {
this.reference[i] = null;
}
},
_getColor: function(n) {
var C = this.colorResolution;
var d = C / this.max;
return [
parseInt((n / C / C) % C, 10) / d,
parseInt((n / C) % C, 10) / d,
parseInt(n % C, 10) / d,
1.0
];
},
_decodeDetectColor: function(color) {
var C = this.colorResolution;
return ~~(color[0] * C * C * C / 256) +
~~(color[1] * C * C / 256) +
~~(color[2] * C / 256);
},
getSpriteByColor: function(color) {
return this.reference[this._decodeDetectColor(color)];
}
});
enchant.DomManager = enchant.Class.create({
initialize: function(node, elementDefinition) {
var core = enchant.Core.instance;
this.layer = null;
this.targetNode = node;
if (typeof elementDefinition === 'string') {
this.element = document.createElement(elementDefinition);
} else if (elementDefinition instanceof HTMLElement) {
this.element = elementDefinition;
}
this.style = this.element.style;
this.style.position = 'absolute';
this.style[enchant.ENV.VENDOR_PREFIX + 'TransformOrigin'] = '0px 0px';
if (core._debug) {
this.style.border = '1px solid blue';
this.style.margin = '-1px';
}
var manager = this;
this._setDomTarget = function() {
manager.layer._touchEventTarget = manager.targetNode;
};
this._attachEvent();
},
getDomElement: function() {
return this.element;
},
getDomElementAsNext: function() {
return this.element;
},
getNextManager: function(manager) {
var i = this.targetNode.parentNode.childNodes.indexOf(manager.targetNode);
if (i !== this.targetNode.parentNode.childNodes.length - 1) {
return this.targetNode.parentNode.childNodes[i + 1]._domManager;
} else {
return null;
}
},
addManager: function(childManager, nextManager) {
var nextElement;
if (nextManager) {
nextElement = nextManager.getDomElementAsNext();
}
var element = childManager.getDomElement();
if (element instanceof Array) {
element.forEach(function(child) {
if (nextElement) {
this.element.insertBefore(child, nextElement);
} else {
this.element.appendChild(child);
}
}, this);
} else {
if (nextElement) {
this.element.insertBefore(element, nextElement);
} else {
this.element.appendChild(element);
}
}
this.setLayer(this.layer);
},
removeManager: function(childManager) {
if (childManager instanceof enchant.DomlessManager) {
childManager._domRef.forEach(function(element) {
this.element.removeChild(element);
}, this);
} else {
this.element.removeChild(childManager.element);
}
this.setLayer(this.layer);
},
setLayer: function(layer) {
this.layer = layer;
var node = this.targetNode;
var manager;
if (node.childNodes) {
for (var i = 0, l = node.childNodes.length; i < l; i++) {
manager = node.childNodes[i]._domManager;
if (manager) {
manager.setLayer(layer);
}
}
}
},
render: function(inheritMat) {
var node = this.targetNode;
var matrix = enchant.Matrix.instance;
var stack = matrix.stack;
var dest = [];
matrix.makeTransformMatrix(node, dest);
matrix.multiply(stack[stack.length - 1], dest, dest);
matrix.multiply(inheritMat, dest, inheritMat);
node._matrix = inheritMat;
var ox = (typeof node._originX === 'number') ? node._originX : node.width / 2 || 0;
var oy = (typeof node._originY === 'number') ? node._originY : node.height / 2 || 0;
var vec = [ ox, oy ];
matrix.multiplyVec(dest, vec, vec);
node._offsetX = vec[0] - ox;
node._offsetY = vec[1] - oy;
if(node.parentNode && !(node.parentNode instanceof enchant.Group)) {
node._offsetX += node.parentNode._offsetX;
node._offsetY += node.parentNode._offsetY;
}
if (node._dirty) {
this.style[enchant.ENV.VENDOR_PREFIX + 'Transform'] = 'matrix(' +
dest[0].toFixed(10) + ',' +
dest[1].toFixed(10) + ',' +
dest[2].toFixed(10) + ',' +
dest[3].toFixed(10) + ',' +
dest[4].toFixed(10) + ',' +
dest[5].toFixed(10) +
')';
}
this.domRender();
},
domRender: function() {
var node = this.targetNode;
if(!node._style) {
node._style = {};
}
if(!node.__styleStatus) {
node.__styleStatus = {};
}
if (node.width !== null) {
node._style.width = node.width + 'px';
}
if (node.height !== null) {
node._style.height = node.height + 'px';
}
node._style.opacity = node._opacity;
node._style['background-color'] = node._backgroundColor;
if (typeof node._visible !== 'undefined') {
node._style.display = node._visible ? 'block' : 'none';
}
if (typeof node.domRender === 'function') {
node.domRender(this.element);
}
var value;
for (var prop in node._style) {
value = node._style[prop];
if(node.__styleStatus[prop] !== value && value != null) {
this.style.setProperty(prop, '' + value);
node.__styleStatus[prop] = value;
}
}
},
_attachEvent: function() {
if (enchant.ENV.TOUCH_ENABLED) {
this.element.addEventListener('touchstart', this._setDomTarget, true);
}
this.element.addEventListener('mousedown', this._setDomTarget, true);
},
_detachEvent: function() {
if (enchant.ENV.TOUCH_ENABLED) {
this.element.removeEventListener('touchstart', this._setDomTarget, true);
}
this.element.removeEventListener('mousedown', this._setDomTarget, true);
},
remove: function() {
this._detachEvent();
this.element = this.style = this.targetNode = null;
}
});
enchant.DomlessManager = enchant.Class.create({
initialize: function(node) {
this._domRef = [];
this.targetNode = node;
},
_register: function(element, nextElement) {
var i = this._domRef.indexOf(nextElement);
var childNodes;
if (element instanceof Array) {
if (i === -1) {
Array.prototype.push.apply(this._domRef, element);
} else {
Array.prototype.splice.apply(this._domRef, [i, 0].concat(element));
}
} else {
if (i === -1) {
this._domRef.push(element);
} else {
this._domRef.splice(i, 0, element);
}
}
},
getNextManager: function(manager) {
var i = this.targetNode.parentNode.childNodes.indexOf(manager.targetNode);
if (i !== this.targetNode.parentNode.childNodes.length - 1) {
return this.targetNode.parentNode.childNodes[i + 1]._domManager;
} else {
return null;
}
},
getDomElement: function() {
var ret = [];
this.targetNode.childNodes.forEach(function(child) {
ret = ret.concat(child._domManager.getDomElement());
});
return ret;
},
getDomElementAsNext: function() {
if (this._domRef.length) {
return this._domRef[0];
} else {
var nextManager = this.getNextManager(this);
if (nextManager) {
return nextManager.element;
} else {
return null;
}
}
},
addManager: function(childManager, nextManager) {
var parentNode = this.targetNode.parentNode;
if (parentNode) {
if (nextManager === null) {
nextManager = this.getNextManager(this);
}
if (parentNode instanceof enchant.Scene) {
parentNode._layers.Dom._domManager.addManager(childManager, nextManager);
} else {
parentNode._domManager.addManager(childManager, nextManager);
}
}
var nextElement = nextManager ? nextManager.getDomElementAsNext() : null;
this._register(childManager.getDomElement(), nextElement);
this.setLayer(this.layer);
},
removeManager: function(childManager) {
var dom;
var i = this._domRef.indexOf(childManager.element);
if (i !== -1) {
dom = this._domRef[i];
dom.parentNode.removeChild(dom);
this._domRef.splice(i, 1);
}
this.setLayer(this.layer);
},
setLayer: function(layer) {
this.layer = layer;
var node = this.targetNode;
var manager;
if (node.childNodes) {
for (var i = 0, l = node.childNodes.length; i < l; i++) {
manager = node.childNodes[i]._domManager;
if (manager) {
manager.setLayer(layer);
}
}
}
},
render: function(inheritMat) {
var matrix = enchant.Matrix.instance;
var stack = matrix.stack;
var node = this.targetNode;
var dest = [];
matrix.makeTransformMatrix(node, dest);
matrix.multiply(stack[stack.length - 1], dest, dest);
matrix.multiply(inheritMat, dest, inheritMat);
node._matrix = inheritMat;
var ox = (typeof node._originX === 'number') ? node._originX : node.width / 2 || 0;
var oy = (typeof node._originY === 'number') ? node._originY : node.height / 2 || 0;
var vec = [ ox, oy ];
matrix.multiplyVec(dest, vec, vec);
node._offsetX = vec[0] - ox;
node._offsetY = vec[1] - oy;
stack.push(dest);
},
remove: function() {
this._domRef = [];
this.targetNode = null;
}
});
enchant.DomLayer = enchant.Class.create(enchant.Group, {
initialize: function() {
var core = enchant.Core.instance;
enchant.Group.call(this);
this._touchEventTarget = null;
this._element = document.createElement('div');
this._element.style.position = 'absolute';
this._domManager = new enchant.DomManager(this, this._element);
this._domManager.layer = this;
this.width = core.width;
this.height = core.height;
var touch = [
enchant.Event.TOUCH_START,
enchant.Event.TOUCH_MOVE,
enchant.Event.TOUCH_END
];
touch.forEach(function(type) {
this.addEventListener(type, function(e) {
if (this._scene) {
this._scene.dispatchEvent(e);
}
});
}, this);
var __onchildadded = function(e) {
var child = e.node;
var next = e.next;
var self = e.target;
var nextManager = next ? next._domManager : null;
enchant.DomLayer._attachDomManager(child, __onchildadded, __onchildremoved);
self._domManager.addManager(child._domManager, nextManager);
var render = new enchant.Event(enchant.Event.RENDER);
child._dirty = true;
self._domManager.layer._rendering(child, render);
};
var __onchildremoved = function(e) {
var child = e.node;
var self = e.target;
self._domManager.removeManager(child._domManager);
enchant.DomLayer._detachDomManager(child, __onchildadded, __onchildremoved);
};
this.addEventListener('childremoved', __onchildremoved);
this.addEventListener('childadded', __onchildadded);
},
width: {
get: function() {
return this._width;
},
set: function(width) {
this._width = width;
this._element.style.width = width + 'px';
}
},
height: {
get: function() {
return this._height;
},
set: function(height) {
this._height = height;
this._element.style.height = height + 'px';
}
},
addChild: function(node) {
this.childNodes.push(node);
node.parentNode = this;
var childAdded = new enchant.Event('childadded');
childAdded.node = node;
childAdded.next = null;
this.dispatchEvent(childAdded);
node.dispatchEvent(new enchant.Event('added'));
if (this.scene) {
node.scene = this.scene;
var addedToScene = new enchant.Event('addedtoscene');
node.dispatchEvent(addedToScene);
}
},
insertBefore: function(node, reference) {
var i = this.childNodes.indexOf(reference);
if (i !== -1) {
this.childNodes.splice(i, 0, node);
node.parentNode = this;
var childAdded = new enchant.Event('childadded');
childAdded.node = node;
childAdded.next = reference;
this.dispatchEvent(childAdded);
node.dispatchEvent(new enchant.Event('added'));
if (this.scene) {
node.scene = this.scene;
var addedToScene = new enchant.Event('addedtoscene');
node.dispatchEvent(addedToScene);
}
} else {
this.addChild(node);
}
},
_startRendering: function() {
this.addEventListener('exitframe', this._onexitframe);
this._onexitframe();
},
_stopRendering: function() {
this.removeEventListener('exitframe', this._onexitframe);
this._onexitframe();
},
_onexitframe: function() {
this._rendering(this, new enchant.Event(enchant.Event.RENDER));
},
_rendering: function(node, e, inheritMat) {
var child;
if (!inheritMat) {
inheritMat = [ 1, 0, 0, 1, 0, 0 ];
}
node.dispatchEvent(e);
node._domManager.render(inheritMat);
if (node.childNodes) {
for (var i = 0, l = node.childNodes.length; i < l; i++) {
child = node.childNodes[i];
this._rendering(child, e, inheritMat.slice());
}
}
if (node._domManager instanceof enchant.DomlessManager) {
enchant.Matrix.instance.stack.pop();
}
node._dirty = false;
},
_determineEventTarget: function() {
var target = this._touchEventTarget;
this._touchEventTarget = null;
return (target === this) ? null : target;
}
});
enchant.DomLayer._attachDomManager = function(node, onchildadded, onchildremoved) {
var child;
if (!node._domManager) {
node.addEventListener('childadded', onchildadded);
node.addEventListener('childremoved', onchildremoved);
if (node instanceof enchant.Group) {
node._domManager = new enchant.DomlessManager(node);
} else {
if (node._element) {
node._domManager = new enchant.DomManager(node, node._element);
} else {
node._domManager = new enchant.DomManager(node, 'div');
}
}
}
if (node.childNodes) {
for (var i = 0, l = node.childNodes.length; i < l; i++) {
child = node.childNodes[i];
enchant.DomLayer._attachDomManager(child, onchildadded, onchildremoved);
node._domManager.addManager(child._domManager, null);
}
}
};
enchant.DomLayer._detachDomManager = function(node, onchildadded, onchildremoved) {
var child;
node.removeEventListener('childadded', onchildadded);
node.removeEventListener('childremoved', onchildremoved);
if (node.childNodes) {
for (var i = 0, l = node.childNodes.length; i < l; i++) {
child = node.childNodes[i];
node._domManager.removeManager(child._domManager, null);
enchant.DomLayer._detachDomManager(child, onchildadded, onchildremoved);
}
}
node._domManager.remove();
delete node._domManager;
};
/**
* @scope enchant.CanvasLayer.prototype
*/
enchant.CanvasLayer = enchant.Class.create(enchant.Group, {
/**
* @name enchant.CanvasLayer
* @class
* A class which is using HTML Canvas for the rendering.
* The rendering of children will be replaced by the Canvas rendering.
* @constructs
*/
initialize: function() {
var core = enchant.Core.instance;
enchant.Group.call(this);
this._cvsCache = {
matrix: [1, 0, 0, 1, 0, 0],
detectColor: '#000000'
};
this._cvsCache.layer = this;
this._element = document.createElement('canvas');
this._element.style.position = 'absolute';
// issue 179
this._element.style.left = this._element.style.top = '0px';
this._detect = document.createElement('canvas');
this._detect.style.position = 'absolute';
this._lastDetected = 0;
this.context = this._element.getContext('2d');
this._dctx = this._detect.getContext('2d');
this._colorManager = new enchant.DetectColorManager(16, 256);
this.width = core.width;
this.height = core.height;
var touch = [
enchant.Event.TOUCH_START,
enchant.Event.TOUCH_MOVE,
enchant.Event.TOUCH_END
];
touch.forEach(function(type) {
this.addEventListener(type, function(e) {
if (this._scene) {
this._scene.dispatchEvent(e);
}
});
}, this);
var __onchildadded = function(e) {
var child = e.node;
var self = e.target;
var layer;
if (self instanceof enchant.CanvasLayer) {
layer = self._scene._layers.Canvas;
} else {
layer = self.scene._layers.Canvas;
}
enchant.CanvasLayer._attachCache(child, layer, __onchildadded, __onchildremoved);
var render = new enchant.Event(enchant.Event.RENDER);
if (self._dirty) {
self._updateCoordinate();
}
child._dirty = true;
enchant.Matrix.instance.stack.push(self._matrix);
enchant.CanvasRenderer.instance.render(layer.context, child, render);
enchant.Matrix.instance.stack.pop(self._matrix);
};
var __onchildremoved = function(e) {
var child = e.node;
var self = e.target;
var layer;
if (self instanceof enchant.CanvasLayer) {
layer = self._scene._layers.Canvas;
} else {
layer = self.scene._layers.Canvas;
}
enchant.CanvasLayer._detachCache(child, layer, __onchildadded, __onchildremoved);
};
this.addEventListener('childremoved', __onchildremoved);
this.addEventListener('childadded', __onchildadded);
},
width: {
get: function() {
return this._width;
},
set: function(width) {
this._width = width;
this._element.width = this._detect.width = width;
}
},
height: {
get: function() {
return this._height;
},
set: function(height) {
this._height = height;
this._element.height = this._detect.height = height;
}
},
addChild: function(node) {
this.childNodes.push(node);
node.parentNode = this;
var childAdded = new enchant.Event('childadded');
childAdded.node = node;
childAdded.next = null;
this.dispatchEvent(childAdded);
node.dispatchEvent(new enchant.Event('added'));
if (this.scene) {
node.scene = this.scene;
var addedToScene = new enchant.Event('addedtoscene');
node.dispatchEvent(addedToScene);
}
},
insertBefore: function(node, reference) {
var i = this.childNodes.indexOf(reference);
if (i !== -1) {
this.childNodes.splice(i, 0, node);
node.parentNode = this;
var childAdded = new enchant.Event('childadded');
childAdded.node = node;
childAdded.next = reference;
this.dispatchEvent(childAdded);
node.dispatchEvent(new enchant.Event('added'));
if (this.scene) {
node.scene = this.scene;
var addedToScene = new enchant.Event('addedtoscene');
node.dispatchEvent(addedToScene);
}
} else {
this.addChild(node);
}
},
/**
* @private
*/
_startRendering: function() {
this.addEventListener('exitframe', this._onexitframe);
this._onexitframe(new enchant.Event(enchant.Event.RENDER));
},
/**
* @private
*/
_stopRendering: function() {
this.removeEventListener('render', this._onexitframe);
this._onexitframe(new enchant.Event(enchant.Event.RENDER));
},
_onexitframe: function() {
var core = enchant.Core.instance;
var ctx = this.context;
ctx.clearRect(0, 0, core.width, core.height);
var render = new enchant.Event(enchant.Event.RENDER);
enchant.CanvasRenderer.instance.render(ctx, this, render);
},
_determineEventTarget: function(e) {
return this._getEntityByPosition(e.x, e.y);
},
_getEntityByPosition: function(x, y) {
var core = enchant.Core.instance;
var ctx = this._dctx;
if (this._lastDetected < core.frame) {
ctx.clearRect(0, 0, this.width, this.height);
enchant.CanvasRenderer.instance.detectRender(ctx, this);
this._lastDetected = core.frame;
}
var color = ctx.getImageData(x, y, 1, 1).data;
return this._colorManager.getSpriteByColor(color);
}
});
enchant.CanvasLayer._attachCache = function(node, layer, onchildadded, onchildremoved) {
var child;
if (!node._cvsCache) {
node._cvsCache = {};
node._cvsCache.matrix = [ 1, 0, 0, 1, 0, 0 ];
node._cvsCache.detectColor = 'rgba(' + layer._colorManager.attachDetectColor(node) + ')';
node.addEventListener('childadded', onchildadded);
node.addEventListener('childremoved', onchildremoved);
}
if (node.childNodes) {
for (var i = 0, l = node.childNodes.length; i < l; i++) {
child = node.childNodes[i];
enchant.CanvasLayer._attachCache(child, layer, onchildadded, onchildremoved);
}
}
};
enchant.CanvasLayer._detachCache = function(node, layer, onchildadded, onchildremoved) {
var child;
if (node._cvsCache) {
layer._colorManager.detachDetectColor(node);
node.removeEventListener('childadded', onchildadded);
node.removeEventListener('childremoved', onchildremoved);
delete node._cvsCache;
}
if (node.childNodes) {
for (var i = 0, l = node.childNodes.length; i < l; i++) {
child = node.childNodes[i];
enchant.CanvasLayer._detachCache(child, layer, onchildadded, onchildremoved);
}
}
};
enchant.CanvasRenderer = enchant.Class.create({
render: function(ctx, node, e) {
var width, height, child;
ctx.save();
node.dispatchEvent(e);
// transform
this.transform(ctx, node);
if (typeof node._visible === 'undefined' || node._visible) {
width = node.width;
height = node.height;
// composite
if (node.compositeOperation) {
ctx.globalCompositeOperation = node.compositeOperation;
}
ctx.globalAlpha = (typeof node._opacity === 'number') ? node._opacity : 1.0;
// render
if (node._backgroundColor) {
ctx.fillStyle = node._backgroundColor;
ctx.fillRect(0, 0, width, height);
}
if (node.cvsRender) {
node.cvsRender(ctx);
}
if (enchant.Core.instance._debug && node._debugColor) {
ctx.strokeStyle = node._debugColor;
ctx.strokeRect(0, 0, width, height);
}
if (node._clipping) {
ctx.beginPath();
ctx.rect(0, 0, width, height);
ctx.clip();
}
if (node.childNodes) {
for (var i = 0, l = node.childNodes.length; i < l; i++) {
child = node.childNodes[i];
this.render(ctx, child, e);
}
}
}
ctx.restore();
enchant.Matrix.instance.stack.pop();
},
detectRender: function(ctx, node) {
var width, height, child;
if (typeof node._visible === 'undefined' || node._visible) {
width = node.width;
height = node.height;
ctx.save();
this.transform(ctx, node);
ctx.fillStyle = node._cvsCache.detectColor;
if (node._touchEnabled) {
if (node.detectRender) {
node.detectRender(ctx);
} else {
ctx.fillRect(0, 0, width, height);
}
}
if (node._clipping) {
ctx.beginPath();
ctx.rect(0, 0, width, height);
ctx.clip();
}
if (node.childNodes) {
for (var i = 0, l = node.childNodes.length; i < l; i++) {
child = node.childNodes[i];
this.detectRender(ctx, child);
}
}
ctx.restore();
enchant.Matrix.instance.stack.pop();
}
},
transform: function(ctx, node) {
var matrix = enchant.Matrix.instance;
var stack = matrix.stack;
var newmat, ox, oy, vec;
if (node._dirty) {
matrix.makeTransformMatrix(node, node._cvsCache.matrix);
newmat = [];
matrix.multiply(stack[stack.length - 1], node._cvsCache.matrix, newmat);
node._matrix = newmat;
ox = (typeof node._originX === 'number') ? node._originX : node._width / 2 || 0;
oy = (typeof node._originY === 'number') ? node._originY : node._height / 2 || 0;
vec = [ ox, oy ];
matrix.multiplyVec(newmat, vec, vec);
node._offsetX = vec[0] - ox;
node._offsetY = vec[1] - oy;
node._dirty = false;
} else {
newmat = node._matrix;
}
stack.push(newmat);
ctx.setTransform.apply(ctx, newmat);
}
});
enchant.CanvasRenderer.instance = new enchant.CanvasRenderer();
/**
* @scope enchant.Scene.prototype
* @type {*}
*/
enchant.Scene = enchant.Class.create(enchant.Group, {
/**
* @name enchant.Scene
* @class
* A Class that becomes the root of the display object tree.
*
* @example
* var scene = new Scene();
* scene.addChild(player);
* scene.addChild(enemy);
* core.pushScene(scene);
*
* @constructs
* @extends enchant.Group
*/
initialize: function() {
var core = enchant.Core.instance;
// Call initialize method of enchant.Group
enchant.Group.call(this);
// All nodes (entities, groups, scenes) have reference to the scene that it belongs to.
this.scene = this;
this._backgroundColor = null;
// Create div tag which possesses its layers
this._element = document.createElement('div');
this._element.style.position = 'absolute';
this._element.style.overflow = 'hidden';
this._element.style[enchant.ENV.VENDOR_PREFIX + 'TransformOrigin'] = '0 0';
this._layers = {};
this._layerPriority = [];
this.addEventListener(enchant.Event.CHILD_ADDED, this._onchildadded);
this.addEventListener(enchant.Event.CHILD_REMOVED, this._onchildremoved);
this.addEventListener(enchant.Event.ENTER, this._onenter);
this.addEventListener(enchant.Event.EXIT, this._onexit);
var that = this;
this._dispatchExitframe = function() {
var layer;
for (var prop in that._layers) {
layer = that._layers[prop];
layer.dispatchEvent(new enchant.Event(enchant.Event.EXIT_FRAME));
}
};
this.addEventListener(enchant.Event.CORE_RESIZE, this._oncoreresize);
this._oncoreresize(core);
},
x: {
get: function() {
return this._x;
},
set: function(x) {
this._x = x;
for (var type in this._layers) {
this._layers[type].x = x;
}
}
},
y: {
get: function() {
return this._y;
},
set: function(y) {
this._y = y;
for (var type in this._layers) {
this._layers[type].y = y;
}
}
},
width: {
get: function() {
return this._width;
},
set: function(width) {
this._width = width;
for (var type in this._layers) {
this._layers[type].width = width;
}
}
},
height: {
get: function() {
return this._height;
},
set: function(height) {
this._height = height;
for (var type in this._layers) {
this._layers[type].height = height;
}
}
},
rotation: {
get: function() {
return this._rotation;
},
set: function(rotation) {
this._rotation = rotation;
for (var type in this._layers) {
this._layers[type].rotation = rotation;
}
}
},
scaleX: {
get: function() {
return this._scaleX;
},
set: function(scaleX) {
this._scaleX = scaleX;
for (var type in this._layers) {
this._layers[type].scaleX = scaleX;
}
}
},
scaleY: {
get: function() {
return this._scaleY;
},
set: function(scaleY) {
this._scaleY = scaleY;
for (var type in this._layers) {
this._layers[type].scaleY = scaleY;
}
}
},
backgroundColor: {
get: function() {
return this._backgroundColor;
},
set: function(color) {
this._backgroundColor = this._element.style.backgroundColor = color;
}
},
_oncoreresize: function(e) {
this._element.style.width = e.width + 'px';
this.width = e.width;
this._element.style.height = e.height + 'px';
this.height = e.height;
this._element.style[enchant.ENV.VENDOR_PREFIX + 'Transform'] = 'scale(' + e.scale + ')';
for (var type in this._layers) {
this._layers[type].dispatchEvent(e);
}
},
addLayer: function(type, i) {
var core = enchant.Core.instance;
if (this._layers[type]) {
return;
}
var layer = new enchant[type + 'Layer']();
if (core.currentScene === this) {
layer._startRendering();
}
this._layers[type] = layer;
var element = layer._element;
if (typeof i === 'number') {
var nextSibling = this._element.childNodes[i];
if (nextSibling) {
this._element.insertBefore(element, nextSibling);
} else {
this._element.appendChild(element);
}
this._layerPriority.splice(i, 0, type);
} else {
this._element.appendChild(element);
this._layerPriority.push(type);
}
layer._scene = this;
},
_determineEventTarget: function(e) {
var layer, target;
for (var i = this._layerPriority.length - 1; i >= 0; i--) {
layer = this._layers[this._layerPriority[i]];
target = layer._determineEventTarget(e);
if (target) {
break;
}
}
if (!target) {
target = this;
}
return target;
},
_onchildadded: function(e) {
var child = e.node;
var next = e.next;
var target, i;
if (child._element) {
target = 'Dom';
i = 1;
} else {
target = 'Canvas';
i = 0;
}
if (!this._layers[target]) {
this.addLayer(target, i);
}
child._layer = this._layers[target];
this._layers[target].insertBefore(child, next);
child.parentNode = this;
},
_onchildremoved: function(e) {
var child = e.node;
child._layer.removeChild(child);
child._layer = null;
},
_onenter: function() {
for (var type in this._layers) {
this._layers[type]._startRendering();
}
enchant.Core.instance.addEventListener('exitframe', this._dispatchExitframe);
},
_onexit: function() {
for (var type in this._layers) {
this._layers[type]._stopRendering();
}
enchant.Core.instance.removeEventListener('exitframe', this._dispatchExitframe);
}
});
/**
* @scope enchant.LoadingScene.prototype
*/
enchant.LoadingScene = enchant.Class.create(enchant.Scene, {
/**
* @name enchant.LoadingScene.
* @class
* @constructs
* @extends enchant.Scene
* Default loading scene. If you want to use your own loading animation, overwrite (don't inherit) this class.
* Referred from enchant.Core in default, as `new enchant.LoadingScene` etc.
*
* @usage
* enchant.LoadingScene = enchant.Class.create(enchant.Scene, {
* initialize: function(){
* enchant.Scene.call(this);
* this.backgroundColor = 'red';
* // ...
* this.addEventListener('progress', function(e){
* progress = e.loaded / e.total;
* });
* this.addEventListener('enterframe', function(){
* // animation
* });
* }
* });
*/
initialize: function() {
enchant.Scene.call(this);
this.backgroundColor = '#000';
var barWidth = this.width * 0.4 | 0;
var barHeight = this.width * 0.05 | 0;
var border = barWidth * 0.03 | 0;
var bar = new enchant.Sprite(barWidth, barHeight);
bar.disableCollection();
bar.x = (this.width - barWidth) / 2;
bar.y = (this.height - barHeight) / 2;
var image = new enchant.Surface(barWidth, barHeight);
image.context.fillStyle = '#fff';
image.context.fillRect(0, 0, barWidth, barHeight);
image.context.fillStyle = '#000';
image.context.fillRect(border, border, barWidth - border * 2, barHeight - border * 2);
bar.image = image;
var progress = 0, _progress = 0;
this.addEventListener('progress', function(e) {
// avoid #167 https://github.com/wise9/enchant.js/issues/177
progress = e.loaded / e.total * 1.0;
});
bar.addEventListener('enterframe', function() {
_progress *= 0.9;
_progress += progress * 0.1;
image.context.fillStyle = '#fff';
image.context.fillRect(border, 0, (barWidth - border * 2) * _progress, barHeight);
});
this.addChild(bar);
this.addEventListener('load', function(e) {
var core = enchant.Core.instance;
core.removeScene(core.loadingScene);
core.dispatchEvent(e);
});
}
});
/**
* @scope enchant.CanvasScene.prototype
* @type {*}
*/
enchant.CanvasScene = enchant.Class.create(enchant.Scene, {
initialize: function() {
enchant.Scene.call(this);
this.addLayer('Canvas');
},
_determineEventTarget: function(e) {
var target = this._layers.Canvas._determineEventTarget(e);
if (!target) {
target = this;
}
return target;
},
_onchildadded: function(e) {
var child = e.node;
var next = e.next;
child._layer = this._layers.Canvas;
this._layers.Canvas.insertBefore(child, next);
},
_onenter: function() {
this._layers.Canvas._startRendering();
enchant.Core.instance.addEventListener('exitframe', this._dispatchExitframe);
},
_onexit: function() {
this._layers.Canvas._stopRendering();
enchant.Core.instance.removeEventListener('exitframe', this._dispatchExitframe);
}
});
/**
* @scope enchant.CanvasScene.prototype
* @type {*}
*/
enchant.DOMScene = enchant.Class.create(enchant.Scene, {
initialize: function() {
enchant.Scene.call(this);
this.addLayer('Dom');
},
_determineEventTarget: function(e) {
var target = this._layers.Dom._determineEventTarget(e);
if (!target) {
target = this;
}
return target;
},
_onchildadded: function(e) {
var child = e.node;
var next = e.next;
child._layer = this._layers.Dom;
this._layers.Dom.insertBefore(child, next);
},
_onenter: function() {
this._layers.Dom._startRendering();
enchant.Core.instance.addEventListener('exitframe', this._dispatchExitframe);
},
_onexit: function() {
this._layers.Dom._stopRendering();
enchant.Core.instance.removeEventListener('exitframe', this._dispatchExitframe);
}
});
/**
* @scope enchant.Surface.prototype
*/
enchant.Surface = enchant.Class.create(enchant.EventTarget, {
/**
* @name enchant.Surface
* @class
* Class that wraps canvas elements.
*
* Can be used to set the {@link enchant.Sprite} and {@link enchant.Map}'s image properties to be displayed.
* If you wish to access Canvas API use the {@link enchant.Surface#context} property.
*
* @example
* // Creates Sprite that displays a circle.
* var ball = new Sprite(50, 50);
* var surface = new Surface(50, 50);
* surface.context.beginPath();
* surface.context.arc(25, 25, 25, 0, Math.PI*2, true);
* surface.context.fill();
* ball.image = surface;
*
* @param {Number} width Surface width.
* @param {Number} height Surface height.
* @constructs
*/
initialize: function(width, height) {
enchant.EventTarget.call(this);
var core = enchant.Core.instance;
/**
* Surface width.
* @type {Number}
*/
this.width = width;
/**
* Surface height.
* @type {Number}
*/
this.height = height;
/**
* Surface drawing context.
* @type {CanvasRenderingContext2D}
*/
this.context = null;
var id = 'enchant-surface' + core._surfaceID++;
if (document.getCSSCanvasContext) {
this.context = document.getCSSCanvasContext('2d', id, width, height);
this._element = this.context.canvas;
this._css = '-webkit-canvas(' + id + ')';
var context = this.context;
} else if (document.mozSetImageElement) {
this._element = document.createElement('canvas');
this._element.width = width;
this._element.height = height;
this._css = '-moz-element(#' + id + ')';
this.context = this._element.getContext('2d');
document.mozSetImageElement(id, this._element);
} else {
this._element = document.createElement('canvas');
this._element.width = width;
this._element.height = height;
this._element.style.position = 'absolute';
this.context = this._element.getContext('2d');
enchant.ENV.CANVAS_DRAWING_METHODS.forEach(function(name) {
var method = this.context[name];
this.context[name] = function() {
method.apply(this, arguments);
this._dirty = true;
};
}, this);
}
},
/**
* Returns 1 pixel from the Surface.
* @param {Number} x The pixel's x coordinates.
* @param {Number} y The pixel's y coordinates.
* @return {Array.<Number>} An array that holds pixel information in [r, g, b, a] format.
*/
getPixel: function(x, y) {
return this.context.getImageData(x, y, 1, 1).data;
},
/**
* Sets one pixel within the surface.
* @param {Number} x The pixel's x coordinates.
* @param {Number} y The pixel's y coordinates.
* @param {Number} r The pixel's red level.
* @param {Number} g The pixel's green level.
* @param {Number} b The pixel's blue level.
* @param {Number} a The pixel's transparency.
*/
setPixel: function(x, y, r, g, b, a) {
var pixel = this.context.createImageData(1, 1);
pixel.data[0] = r;
pixel.data[1] = g;
pixel.data[2] = b;
pixel.data[3] = a;
this.context.putImageData(pixel, x, y);
},
/**
* Clears all Surface pixels and makes the pixels transparent.
*/
clear: function() {
this.context.clearRect(0, 0, this.width, this.height);
},
/**
* Draws the content of the given Surface onto this surface.
*
* Wraps Canvas API drawImage and if multiple arguments are given,
* these are getting applied to the Canvas drawImage method.
*
* @example
* var src = core.assets['src.gif'];
* var dst = new Surface(100, 100);
* dst.draw(src); // Draws source at (0, 0)
* dst.draw(src, 50, 50); // Draws source at (50, 50)
* // Draws just 30 horizontal and vertical pixels of source at (50, 50)
* dst.draw(src, 50, 50, 30, 30);
* // Takes the image content in src starting at (10,10) with a (Width, Height) of (40,40),
* // scales it and draws it in this surface at (50, 50) with a (Width, Height) of (30,30).
* dst.draw(src, 10, 10, 40, 40, 50, 50, 30, 30);
*
* @param {enchant.Surface} image Surface used in drawing.
*/
draw: function(image) {
image = image._element;
if (arguments.length === 1) {
this.context.drawImage(image, 0, 0);
} else {
var args = arguments;
args[0] = image;
this.context.drawImage.apply(this.context, args);
}
},
/**
* Copies Surface.
* @return {enchant.Surface} The copied Surface.
*/
clone: function() {
var clone = new enchant.Surface(this.width, this.height);
clone.draw(this);
return clone;
},
/**
* Creates a data URI scheme from this Surface.
* @return {String} The data URI scheme that identifies this Surface and
* can be used to include this Surface into a dom tree.
*/
toDataURL: function() {
var src = this._element.src;
if (src) {
if (src.slice(0, 5) === 'data:') {
return src;
} else {
return this.clone().toDataURL();
}
} else {
return this._element.toDataURL();
}
}
});
/**
* Loads an image and creates a Surface object out of it.
*
* It is not possible to access properties or methods of the {@link enchant.Surface#context}, or to call methods using the Canvas API -
* like {@link enchant.Surface#draw}, {@link enchant.Surface#clear}, {@link enchant.Surface#getPixel}, {@link enchant.Surface#setPixel}.. -
* of the wrapped image created with this method.
* However, it is possible to use this surface to draw it to another surface using the {@link enchant.Surface#draw} method.
* The resulting surface can then be manipulated. (when loading images in a cross-origin resource sharing environment,
* pixel acquisition and other image manipulation might be limited).
*
* @param {String} src The file path of the image to be loaded.
* @param {Function} callback on load callback.
* @param {Function} [onerror] on error callback.
* @static
* @return {enchant.Surface} Surface
*/
enchant.Surface.load = function(src, callback, onerror) {
var image = new Image();
var surface = Object.create(enchant.Surface.prototype, {
context: { value: null },
_css: { value: 'url(' + src + ')' },
_element: { value: image }
});
enchant.EventTarget.call(surface);
onerror = onerror || function() {};
surface.addEventListener('load', callback);
surface.addEventListener('error', onerror);
image.onerror = function() {
var e = new enchant.Event(enchant.Event.ERROR);
e.message = 'Cannot load an asset: ' + image.src;
enchant.Core.instance.dispatchEvent(e);
surface.dispatchEvent(e);
};
image.onload = function() {
surface.width = image.width;
surface.height = image.height;
surface.dispatchEvent(new enchant.Event('load'));
};
image.src = src;
return surface;
};
enchant.Surface._staticCanvas2DContext = document.createElement('canvas').getContext('2d');
enchant.Surface._getPattern = function(surface, force) {
if (!surface._pattern || force) {
surface._pattern = this._staticCanvas2DContext.createPattern(surface._element, 'repeat');
}
return surface._pattern;
};
if (window.Deferred) {
enchant.Deferred = window.Deferred;
} else {
enchant.Deferred = enchant.Class.create({
initialize: function() {
this._succ = this._fail = this._next = this._id = null;
this._tail = this;
},
next: function(func) {
var q = new enchant.Deferred();
q._succ = func;
return this._add(q);
},
error: function(func) {
var q = new enchant.Deferred();
q._fail = func;
return this._add(q);
},
_add: function(queue) {
this._tail._next = queue;
this._tail = queue;
return this;
},
call: function(arg) {
var received;
var queue = this;
while (queue && !queue._succ) {
queue = queue._next;
}
if (!(queue instanceof enchant.Deferred)) {
return;
}
try {
received = queue._succ(arg);
} catch (e) {
return queue.fail(e);
}
if (received instanceof enchant.Deferred) {
enchant.Deferred._insert(queue, received);
} else if (queue._next instanceof enchant.Deferred) {
queue._next.call(received);
}
},
fail: function(arg) {
var result, err,
queue = this;
while (queue && !queue._fail) {
queue = queue._next;
}
if (queue instanceof enchant.Deferred) {
result = queue._fail(arg);
queue.call(result);
} else if (arg instanceof Error) {
throw arg;
} else {
err = new Error('failed in Deferred');
err.arg = arg;
throw err;
}
}
});
enchant.Deferred._insert = function(queue, ins) {
if (queue._next instanceof enchant.Deferred) {
ins._next = queue._next;
}
queue._next = ins;
};
enchant.Deferred.next = function(func) {
var q = new enchant.Deferred().next(func);
q._id = setTimeout(function() { q.call(); }, 0);
return q;
};
enchant.Deferred.parallel = function(arg) {
var q = new enchant.Deferred();
q._id = setTimeout(function() { q.call(); }, 0);
var progress = 0;
var ret = (arg instanceof Array) ? [] : {};
var p = new enchant.Deferred();
for (var prop in arg) {
if (arg.hasOwnProperty(prop)) {
progress++;
/*jshint loopfunc:true */
(function(queue, name) {
queue.next(function(arg) {
progress--;
ret[name] = arg;
if (progress <= 0) {
p.call(ret);
}
})
.error(function(err) { p.fail(err); });
if (typeof queue._id === 'number') {
clearTimeout(queue._id);
}
queue._id = setTimeout(function() { queue.call(); }, 0);
}(arg[prop], prop));
}
}
if (!progress) {
p._id = setTimeout(function() { p.call(ret); }, 0);
}
return q.next(function() { return p; });
};
}
/**
* @scope enchant.DOMSound.prototype
* @type {*}
*/
enchant.DOMSound = enchant.Class.create(enchant.EventTarget, {
/**
* @name enchant.DOMSound
* @class
* Class to wrap audio elements.
*
* Safari, Chrome, Firefox, Opera, and IE all play MP3 files
* (Firefox and Opera play via Flash). WAVE files can be played on
* Safari, Chrome, Firefox, and Opera. When the browser is not compatible with
* the used codec the file will not play.
*
* Instances are created not via constructor but via {@link enchant.DOMSound.load}.
* @constructs
*/
initialize: function() {
enchant.EventTarget.call(this);
/**
* Sound file duration (seconds).
* @type {Number}
*/
this.duration = 0;
throw new Error("Illegal Constructor");
},
/**
* Begin playing.
*/
play: function() {
if (this._element) {
this._element.play();
}
},
/**
* Pause playback.
*/
pause: function() {
if (this._element) {
this._element.pause();
}
},
/**
* Stop playing.
*/
stop: function() {
this.pause();
this.currentTime = 0;
},
/**
* Create a copy of this Sound object.
* @return {enchant.DOMSound} Copied Sound.
*/
clone: function() {
var clone;
if (this._element instanceof Audio) {
clone = Object.create(enchant.DOMSound.prototype, {
_element: { value: this._element.cloneNode(false) },
duration: { value: this.duration }
});
} else if (enchant.ENV.USE_FLASH_SOUND) {
return this;
} else {
clone = Object.create(enchant.DOMSound.prototype);
}
enchant.EventTarget.call(clone);
return clone;
},
/**
* Current playback position (seconds).
* @type {Number}
*/
currentTime: {
get: function() {
return this._element ? this._element.currentTime : 0;
},
set: function(time) {
if (this._element) {
this._element.currentTime = time;
}
}
},
/**
* Volume. 0 (muted) 1 (full volume).
* @type {Number}
*/
volume: {
get: function() {
return this._element ? this._element.volume : 1;
},
set: function(volume) {
if (this._element) {
this._element.volume = volume;
}
}
}
});
/**
* Loads an audio file and creates Sound object.
*
* @param {String} src Path of the audio file to be loaded.
* @param {String} [type] MIME Type of the audio file.
* @param {Function} callback on load callback.
* @param {Function} [onerror] on error callback.
* @return {enchant.DOMSound} DOMSound
* @static
*/
enchant.DOMSound.load = function(src, type, callback, onerror) {
if (type == null) {
var ext = enchant.Core.findExt(src);
if (ext) {
type = 'audio/' + ext;
} else {
type = '';
}
}
type = type.replace('mp3', 'mpeg').replace('m4a', 'mp4');
onerror = onerror || function() {};
var sound = Object.create(enchant.DOMSound.prototype);
enchant.EventTarget.call(sound);
sound.addEventListener('load', callback);
sound.addEventListener('error', onerror);
var audio = new Audio();
if (!enchant.ENV.SOUND_ENABLED_ON_MOBILE_SAFARI &&
enchant.ENV.VENDOR_PREFIX === 'webkit' && enchant.ENV.TOUCH_ENABLED) {
window.setTimeout(function() {
sound.dispatchEvent(new enchant.Event('load'));
}, 0);
} else {
if (!enchant.ENV.USE_FLASH_SOUND && audio.canPlayType(type)) {
audio.addEventListener('canplaythrough', function() {
sound.duration = audio.duration;
sound.dispatchEvent(new enchant.Event('load'));
}, false);
audio.src = src;
audio.load();
audio.autoplay = false;
audio.onerror = function() {
var e = new enchant.Event(enchant.Event.ERROR);
e.message = 'Cannot load an asset: ' + audio.src;
enchant.Core.instance.dispatchEvent(e);
sound.dispatchEvent(e);
};
sound._element = audio;
} else if (type === 'audio/mpeg') {
var embed = document.createElement('embed');
var id = 'enchant-audio' + enchant.Core.instance._soundID++;
embed.width = embed.height = 1;
embed.name = id;
embed.src = 'sound.swf?id=' + id + '&src=' + src;
embed.allowscriptaccess = 'always';
embed.style.position = 'absolute';
embed.style.left = '-1px';
sound.addEventListener('load', function() {
Object.defineProperties(embed, {
currentTime: {
get: function() {
return embed.getCurrentTime();
},
set: function(time) {
embed.setCurrentTime(time);
}
},
volume: {
get: function() {
return embed.getVolume();
},
set: function(volume) {
embed.setVolume(volume);
}
}
});
sound._element = embed;
sound.duration = embed.getDuration();
});
enchant.Core.instance._element.appendChild(embed);
enchant.DOMSound[id] = sound;
} else {
window.setTimeout(function() {
sound.dispatchEvent(new enchant.Event('load'));
}, 0);
}
}
return sound;
};
window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext || window.oAudioContext;
/**
* @scope enchant.WebAudioSound.prototype
* @type {*}
*/
enchant.WebAudioSound = enchant.Class.create(enchant.EventTarget, {
/**
* @name enchant.WebAudioSound
* @class
* Sound wrapper class for Web Audio API (supported on some webkit-based browsers)
*
* @constructs
*/
initialize: function() {
if(!window.webkitAudioContext){
throw new Error("This browser does not support WebAudio API.");
}
var actx = enchant.WebAudioSound.audioContext;
enchant.EventTarget.call(this);
this.src = actx.createBufferSource();
this.buffer = null;
this._volume = 1;
this._currentTime = 0;
this._state = 0;
this.connectTarget = enchant.WebAudioSound.destination;
},
play: function(dup) {
var actx = enchant.WebAudioSound.audioContext;
if (this._state === 2) {
this.src.connect(this.connectTarget);
} else {
if (this._state === 1 && !dup) {
this.src.disconnect(this.connectTarget);
}
this.src = actx.createBufferSource();
this.src.buffer = this.buffer;
this.src.gain.value = this._volume;
this.src.connect(this.connectTarget);
this.src.noteOn(0);
}
this._state = 1;
},
pause: function() {
var actx = enchant.WebAudioSound.audioContext;
this.src.disconnect(this.connectTarget);
this._state = 2;
},
stop: function() {
this.src.noteOff(0);
this._state = 0;
},
clone: function() {
var sound = new enchant.WebAudioSound();
sound.buffer = this.buffer;
return sound;
},
dulation: {
get: function() {
if (this.buffer) {
return this.buffer.dulation;
} else {
return 0;
}
}
},
volume: {
get: function() {
return this._volume;
},
set: function(volume) {
volume = Math.max(0, Math.min(1, volume));
this._volume = volume;
if (this.src) {
this.src.gain.value = volume;
}
}
},
currentTime: {
get: function() {
window.console.log('currentTime is not allowed');
return this._currentTime;
},
set: function(time) {
window.console.log('currentTime is not allowed');
this._currentTime = time;
}
}
});
enchant.WebAudioSound.load = function(src, type, callback, onerror) {
var canPlay = (new Audio()).canPlayType(type);
var sound = new enchant.WebAudioSound();
onerror = onerror || function() {};
sound.addEventListener(enchant.Event.LOAD, callback);
sound.addEventListener(enchant.Event.ERROR, onerror);
var e = new enchant.Event(enchant.Event.ERROR);
e.message = 'Cannot load an asset: ' + src;
var actx, xhr;
if (canPlay === 'maybe' || canPlay === 'probably') {
actx = enchant.WebAudioSound.audioContext;
xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.open('GET', src, true);
xhr.onload = function() {
actx.decodeAudioData(
xhr.response,
function(buffer) {
sound.buffer = buffer;
sound.dispatchEvent(new enchant.Event(enchant.Event.LOAD));
},
function(error) {
enchant.Core.instance.dispatchEvent(e);
sound.dispatchEvent(e);
}
);
};
xhr.send(null);
} else {
setTimeout(function() {
sound.dispatchEvent(e);
}, 50);
}
return sound;
};
if(window.AudioContext){
enchant.WebAudioSound.audioContext = new window.AudioContext();
enchant.WebAudioSound.destination = enchant.WebAudioSound.audioContext.destination;
}
/* jshint newcap: false */
enchant.Sound = window.AudioContext && enchant.ENV.USE_WEBAUDIO ? enchant.WebAudioSound : enchant.DOMSound;
/**
* ============================================================================================
* Easing Equations v2.0
* September 1, 2003
* (c) 2003 Robert Penner, all rights reserved.
* This work is subject to the terms in http://www.robertpenner.com/easing_terms_of_use.html.
* ============================================================================================
*/
/**
* Easing function library, from "Easing Equations" by Robert Penner.
* @type {Object}
* @namespace
* {@link enchant.Tween} クラスで用いるイージング関数のライブラリ名前空間.
*/
enchant.Easing = {
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
LINEAR: function(t, b, c, d) {
return c * t / d + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
SWING: function(t, b, c, d) {
return c * (0.5 - Math.cos(((t / d) * Math.PI)) / 2) + b;
},
// quad
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
QUAD_EASEIN: function(t, b, c, d) {
return c * (t /= d) * t + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
QUAD_EASEOUT: function(t, b, c, d) {
return -c * (t /= d) * (t - 2) + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
QUAD_EASEINOUT: function(t, b, c, d) {
if ((t /= d / 2) < 1) {
return c / 2 * t * t + b;
}
return -c / 2 * ((--t) * (t - 2) - 1) + b;
},
// cubic
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
CUBIC_EASEIN: function(t, b, c, d) {
return c * (t /= d) * t * t + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
CUBIC_EASEOUT: function(t, b, c, d) {
return c * ((t = t / d - 1) * t * t + 1) + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
CUBIC_EASEINOUT: function(t, b, c, d) {
if ((t /= d / 2) < 1) {
return c / 2 * t * t * t + b;
}
return c / 2 * ((t -= 2) * t * t + 2) + b;
},
// quart
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
QUART_EASEIN: function(t, b, c, d) {
return c * (t /= d) * t * t * t + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
QUART_EASEOUT: function(t, b, c, d) {
return -c * ((t = t / d - 1) * t * t * t - 1) + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
QUART_EASEINOUT: function(t, b, c, d) {
if ((t /= d / 2) < 1) {
return c / 2 * t * t * t * t + b;
}
return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
},
// quint
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
QUINT_EASEIN: function(t, b, c, d) {
return c * (t /= d) * t * t * t * t + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
QUINT_EASEOUT: function(t, b, c, d) {
return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
QUINT_EASEINOUT: function(t, b, c, d) {
if ((t /= d / 2) < 1) {
return c / 2 * t * t * t * t * t + b;
}
return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
},
//sin
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
SIN_EASEIN: function(t, b, c, d) {
return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
SIN_EASEOUT: function(t, b, c, d) {
return c * Math.sin(t / d * (Math.PI / 2)) + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
SIN_EASEINOUT: function(t, b, c, d) {
return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
},
// circ
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
CIRC_EASEIN: function(t, b, c, d) {
return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
CIRC_EASEOUT: function(t, b, c, d) {
return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
CIRC_EASEINOUT: function(t, b, c, d) {
if ((t /= d / 2) < 1) {
return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
}
return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
},
// elastic
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
ELASTIC_EASEIN: function(t, b, c, d, a, p) {
if (t === 0) {
return b;
}
if ((t /= d) === 1) {
return b + c;
}
if (!p) {
p = d * 0.3;
}
var s;
if (!a || a < Math.abs(c)) {
a = c;
s = p / 4;
} else {
s = p / (2 * Math.PI) * Math.asin(c / a);
}
return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
ELASTIC_EASEOUT: function(t, b, c, d, a, p) {
if (t === 0) {
return b;
}
if ((t /= d) === 1) {
return b + c;
}
if (!p) {
p = d * 0.3;
}
var s;
if (!a || a < Math.abs(c)) {
a = c;
s = p / 4;
} else {
s = p / (2 * Math.PI) * Math.asin(c / a);
}
return (a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b);
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
ELASTIC_EASEINOUT: function(t, b, c, d, a, p) {
if (t === 0) {
return b;
}
if ((t /= d / 2) === 2) {
return b + c;
}
if (!p) {
p = d * (0.3 * 1.5);
}
var s;
if (!a || a < Math.abs(c)) {
a = c;
s = p / 4;
} else {
s = p / (2 * Math.PI) * Math.asin(c / a);
}
if (t < 1) {
return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
}
return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b;
},
// bounce
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
BOUNCE_EASEOUT: function(t, b, c, d) {
if ((t /= d) < (1 / 2.75)) {
return c * (7.5625 * t * t) + b;
} else if (t < (2 / 2.75)) {
return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
} else if (t < (2.5 / 2.75)) {
return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
} else {
return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
}
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
BOUNCE_EASEIN: function(t, b, c, d) {
return c - enchant.Easing.BOUNCE_EASEOUT(d - t, 0, c, d) + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
BOUNCE_EASEINOUT: function(t, b, c, d) {
if (t < d / 2) {
return enchant.Easing.BOUNCE_EASEIN(t * 2, 0, c, d) * 0.5 + b;
} else {
return enchant.Easing.BOUNCE_EASEOUT(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
}
},
// back
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
BACK_EASEIN: function(t, b, c, d, s) {
if (s === undefined) {
s = 1.70158;
}
return c * (t /= d) * t * ((s + 1) * t - s) + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
BACK_EASEOUT: function(t, b, c, d, s) {
if (s === undefined) {
s = 1.70158;
}
return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
BACK_EASEINOUT: function(t, b, c, d, s) {
if (s === undefined) {
s = 1.70158;
}
if ((t /= d / 2) < 1) {
return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
}
return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
},
// expo
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
EXPO_EASEIN: function(t, b, c, d) {
return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
EXPO_EASEOUT: function(t, b, c, d) {
return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
},
/**
* @param t
* @param b
* @param c
* @param d
* @return {Number}
*/
EXPO_EASEINOUT: function(t, b, c, d) {
if (t === 0) {
return b;
}
if (t === d) {
return b + c;
}
if ((t /= d / 2) < 1) {
return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
}
return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
}
};
/**
* Easing Equations v2.0
*/
/**
* @scope enchant.ActionEventTarget.prototype
* @type {*}
*/
enchant.ActionEventTarget = enchant.Class.create(enchant.EventTarget, {
/**
* @name enchant.ActionEventTarget
* @class
* EventTarget which can change the context of event listeners
* @constructs
* @extends enchant.EventTarget
*/
initialize: function() {
enchant.EventTarget.apply(this, arguments);
},
/**
* Issue event.
* @param {enchant.Event} e Event issued.
*/
dispatchEvent: function(e) {
var target;
if (this.node) {
target = this.node;
e.target = target;
e.localX = e.x - target._offsetX;
e.localY = e.y - target._offsetY;
} else {
this.node = null;
}
if (this['on' + e.type] != null) {
this['on' + e.type].call(target, e);
}
var listeners = this._listeners[e.type];
if (listeners != null) {
listeners = listeners.slice();
for (var i = 0, len = listeners.length; i < len; i++) {
listeners[i].call(target, e);
}
}
}
});
/**
* @scope enchant.Timeline.prototype
*/
enchant.Timeline = enchant.Class.create(enchant.EventTarget, {
/**
* @name enchant.Timeline
* @class
* Time-line class.
* Class for managing the action.
* For one node to manipulate the timeline of one must correspond.
*
     * Reading a tl.enchant.js, all classes (Group, Scene, Entity, Label, Sprite) of the Node class that inherits
     * Tlthe property, an instance of the Timeline class is generated.
     * Time-line class has a method to add a variety of actions to himself,
     * entities can be animated and various operations by using these briefly.
     * You can choose time based and frame based(default) animation.
*
* @param node target node
* @param [unitialized] if this param is true, when add method called in the first time,
* enchant.Event.ENTER_FRAME event listener will be added to node (for reducing unused event listeners)
* @constructs
*/
initialize: function(node) {
enchant.EventTarget.call(this);
this.node = node;
this.queue = [];
this.paused = false;
this.looped = false;
this.isFrameBased = true;
this._parallel = null;
this._activated = false;
this.addEventListener(enchant.Event.ENTER_FRAME, this.tick);
},
/**
* @private
*/
_deactivateTimeline: function() {
if (this._activated) {
this._activated = false;
this.node.removeEventListener('enterframe', this._nodeEventListener);
}
},
/**
* @private
*/
_activateTimeline: function() {
if (!this._activated && !this.paused) {
this.node.addEventListener("enterframe", this._nodeEventListener);
this._activated = true;
}
},
/**
*/
setFrameBased: function() {
this.isFrameBased = true;
},
/**
*/
setTimeBased: function() {
this.isFrameBased = false;
},
/**
*/
next: function(remainingTime) {
var e, action = this.queue.shift();
e = new enchant.Event("actionend");
e.timeline = this;
action.dispatchEvent(e);
if (this.queue.length === 0) {
this._activated = false;
this.node.removeEventListener('enterframe', this._nodeEventListener);
return;
}
if (this.looped) {
e = new enchant.Event("removedfromtimeline");
e.timeline = this;
action.dispatchEvent(e);
action.frame = 0;
this.add(action);
} else {
// remove after dispatching removedfromtimeline event
e = new enchant.Event("removedfromtimeline");
e.timeline = this;
action.dispatchEvent(e);
}
if (remainingTime > 0 || (this.queue[0] && this.queue[0].time === 0)) {
var event = new enchant.Event("enterframe");
event.elapsed = remainingTime;
this.dispatchEvent(event);
}
},
/**
*/
tick: function(enterFrameEvent) {
if (this.paused) {
return;
}
if (this.queue.length > 0) {
var action = this.queue[0];
if (action.frame === 0) {
var f;
f = new enchant.Event("actionstart");
f.timeline = this;
action.dispatchEvent(f);
}
var e = new enchant.Event("actiontick");
e.timeline = this;
if (this.isFrameBased) {
e.elapsed = 1;
} else {
e.elapsed = enterFrameEvent.elapsed;
}
action.dispatchEvent(e);
}
},
add: function(action) {
if (!this._activated) {
var tl = this;
this._nodeEventListener = function(e) {
tl.dispatchEvent(e);
};
this.node.addEventListener("enterframe", this._nodeEventListener);
this._activated = true;
}
if (this._parallel) {
this._parallel.actions.push(action);
this._parallel = null;
} else {
this.queue.push(action);
}
action.frame = 0;
var e = new enchant.Event("addedtotimeline");
e.timeline = this;
action.dispatchEvent(e);
e = new enchant.Event("actionadded");
e.action = action;
this.dispatchEvent(e);
return this;
},
/**
*/
action: function(params) {
return this.add(new enchant.Action(params));
},
/**
*/
tween: function(params) {
return this.add(new enchant.Tween(params));
},
/**
*/
clear: function() {
var e = new enchant.Event("removedfromtimeline");
e.timeline = this;
for (var i = 0, len = this.queue.length; i < len; i++) {
this.queue[i].dispatchEvent(e);
}
this.queue = [];
this._deactivateTimeline();
return this;
},
/**
*/
skip: function(frames) {
var event = new enchant.Event("enterframe");
if (this.isFrameBased) {
event.elapsed = 1;
} else {
event.elapsed = frames;
frames = 1;
}
while (frames--) {
this.dispatchEvent(event);
}
return this;
},
/**
*/
pause: function() {
if (!this.paused) {
this.paused = true;
this._deactivateTimeline();
}
return this;
},
/**
*/
resume: function() {
if (this.paused) {
this.paused = false;
this._activateTimeline();
}
return this;
},
/**
*/
loop: function() {
this.looped = true;
return this;
},
/**
*/
unloop: function() {
this.looped = false;
return this;
},
/**
*/
delay: function(time) {
this.add(new enchant.Action({
time: time
}));
return this;
},
/**
*/
wait: function(time) {
// reserved
return this;
},
/**
*/
then: function(func) {
var timeline = this;
this.add(new enchant.Action({
onactiontick: function(evt) {
func.call(timeline.node);
},
// if time is 0, next action will be immediately executed
time: 0
}));
return this;
},
/**
*/
exec: function(func) {
this.then(func);
},
/**
*/
cue: function(cue) {
var ptr = 0;
for (var frame in cue) {
if (cue.hasOwnProperty(frame)) {
this.delay(frame - ptr);
this.then(cue[frame]);
ptr = frame;
}
}
},
/**
*/
repeat: function(func, time) {
this.add(new enchant.Action({
onactiontick: function(evt) {
func.call(this);
},
time: time
}));
return this;
},
/**
*/
and: function() {
var last = this.queue.pop();
if (last instanceof enchant.ParallelAction) {
this._parallel = last;
this.queue.push(last);
} else {
var parallel = new enchant.ParallelAction();
parallel.actions.push(last);
this.queue.push(parallel);
this._parallel = parallel;
}
return this;
},
/**
* @ignore
*/
or: function() {
return this;
},
/**
* @ignore
*/
doAll: function(children) {
return this;
},
/**
* @ignore
*/
waitAll: function() {
return this;
},
/**
*/
waitUntil: function(func) {
var timeline = this;
this.add(new enchant.Action({
onactionstart: func,
onactiontick: function(evt) {
if (func.call(this)) {
timeline.next();
}
}
}));
return this;
},
/**
*/
fadeTo: function(opacity, time, easing) {
this.tween({
opacity: opacity,
time: time,
easing: easing
});
return this;
},
/**
*/
fadeIn: function(time, easing) {
return this.fadeTo(1, time, easing);
},
/**
*/
fadeOut: function(time, easing) {
return this.fadeTo(0, time, easing);
},
/**
*/
moveTo: function(x, y, time, easing) {
return this.tween({
x: x,
y: y,
time: time,
easing: easing
});
},
/**
*/
moveX: function(x, time, easing) {
return this.tween({
x: x,
time: time,
easing: easing
});
},
/**
*/
moveY: function(y, time, easing) {
return this.tween({
y: y,
time: time,
easing: easing
});
},
/**
*/
moveBy: function(x, y, time, easing) {
return this.tween({
x: function() {
return this.x + x;
},
y: function() {
return this.y + y;
},
time: time,
easing: easing
});
},
/**
*/
hide: function() {
return this.then(function() {
this.opacity = 0;
});
},
/**
*/
show: function() {
return this.then(function() {
this.opacity = 1;
});
},
/**
*/
removeFromScene: function() {
return this.then(function() {
this.scene.removeChild(this);
});
},
/**
*/
scaleTo: function(scale, time, easing) {
if (typeof easing === "number") {
return this.tween({
scaleX: arguments[0],
scaleY: arguments[1],
time: arguments[2],
easing: arguments[3]
});
}
return this.tween({
scaleX: scale,
scaleY: scale,
time: time,
easing: easing
});
},
/**
*/
scaleBy: function(scale, time, easing) {
if (typeof easing === "number") {
return this.tween({
scaleX: function() {
return this.scaleX * arguments[0];
},
scaleY: function() {
return this.scaleY * arguments[1];
},
time: arguments[2],
easing: arguments[3]
});
}
return this.tween({
scaleX: function() {
return this.scaleX * scale;
},
scaleY: function() {
return this.scaleY * scale;
},
time: time,
easing: easing
});
},
/**
*/
rotateTo: function(deg, time, easing) {
return this.tween({
rotation: deg,
time: time,
easing: easing
});
},
/**
*/
rotateBy: function(deg, time, easing) {
return this.tween({
rotation: function() {
return this.rotation + deg;
},
time: time,
easing: easing
});
}
});
/**
* @scope enchant.Action.prototype
* @type {*}
*/
enchant.Action = enchant.Class.create(enchant.ActionEventTarget, {
/**
* @name enchant.Action
* @class
* Action class.
     * Actions are units that make up the time line,
     * It is a unit used to specify the action you want to perform.
     * Action has been added to the time line is performed in order.
     *
     * Actionstart, actiontick event is fired when the action is started and stopped,
     * When one frame has elapsed actiontick event is also issued.
     * Specify the action you want to perform as a listener for these events.
    * The transition to the next action automatically the number of frames that are specified in the time has elapsed.
*
* @constructs
* @param param
* @config {integer} [time] The number of frames that will last action. infinite length is specified null
* @config {function} [onactionstart] Event listener for when the action is initiated
* @config {function} [onactiontick] Event listener for when the action has passed one frame
* @config {function} [onactionend] Event listener for when the action is finished
* @constructs
*/
initialize: function(param) {
enchant.ActionEventTarget.call(this);
this.time = null;
this.frame = 0;
for (var key in param) {
if (param.hasOwnProperty(key)) {
if (param[key] != null) {
this[key] = param[key];
}
}
}
var action = this;
this.timeline = null;
this.node = null;
this.addEventListener(enchant.Event.ADDED_TO_TIMELINE, function(evt) {
action.timeline = evt.timeline;
action.node = evt.timeline.node;
action.frame = 0;
});
this.addEventListener(enchant.Event.REMOVED_FROM_TIMELINE, function() {
action.timeline = null;
action.node = null;
action.frame = 0;
});
this.addEventListener(enchant.Event.ACTION_TICK, function(evt) {
var remaining = action.time - (action.frame + evt.elapsed);
if (action.time != null && remaining <= 0) {
action.frame = action.time;
evt.timeline.next(-remaining);
} else {
action.frame += evt.elapsed;
}
});
}
});
/**
* @scope enchant.ParallelAction.prototype
*/
enchant.ParallelAction = enchant.Class.create(enchant.Action, {
/**
* @name enchant.ParallelAction
* @class
* @constructs
* @extends enchant.Action
*/
initialize: function(param) {
enchant.Action.call(this, param);
var timeline = this.timeline;
var node = this.node;
/**
* Children Actions
*/
this.actions = [];
/**
* Removed actions
*/
this.endedActions = [];
var that = this;
this.addEventListener(enchant.Event.ACTION_START, function(evt) {
for (var i = 0, len = that.actions.length; i < len; i++) {
that.actions[i].dispatchEvent(evt);
}
});
this.addEventListener(enchant.Event.ACTION_TICK, function(evt) {
var i, len, timeline = {
next: function(remaining) {
var action = that.actions[i];
that.actions.splice(i--, 1);
len = that.actions.length;
that.endedActions.push(action);
var e = new enchant.Event("actionend");
e.timeline = this;
action.dispatchEvent(e);
e = new enchant.Event("removedfromtimeline");
e.timeline = this;
action.dispatchEvent(e);
}
};
var e = new enchant.Event("actiontick");
e.timeline = timeline;
e.elapsed = evt.elapsed;
for (i = 0, len = that.actions.length; i < len; i++) {
that.actions[i].dispatchEvent(e);
}
if (that.actions.length === 0) {
evt.timeline.next();
}
});
this.addEventListener(enchant.Event.ADDED_TO_TIMELINE, function(evt) {
for (var i = 0, len = that.actions.length; i < len; i++) {
that.actions[i].dispatchEvent(evt);
}
});
this.addEventListener(enchant.Event.REMOVED_FROM_TIMELINE, function() {
this.actions = this.endedActions;
this.endedActions = [];
});
}
});
/**
* @scope enchant.Tween.prototype
*/
enchant.Tween = enchant.Class.create(enchant.Action, {
/**
* @name enchant.Tween
* @class
*/
initialize: function(params) {
var origin = {};
var target = {};
enchant.Action.call(this, params);
if (this.easing == null) {
// linear
this.easing = function(t, b, c, d) {
return c * t / d + b;
};
}
var tween = this;
this.addEventListener(enchant.Event.ACTION_START, function() {
// excepted property
var excepted = ["frame", "time", "callback", "onactiontick", "onactionstart", "onactionend"];
for (var prop in params) {
if (params.hasOwnProperty(prop)) {
// if function is used instead of numerical value, evaluate it
var target_val;
if (typeof params[prop] === "function") {
target_val = params[prop].call(tween.node);
} else {
target_val = params[prop];
}
if (excepted.indexOf(prop) === -1) {
origin[prop] = tween.node[prop];
target[prop] = target_val;
}
}
}
});
this.addEventListener(enchant.Event.ACTION_TICK, function(evt) {
// if time is 0, set property to target value immediately
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);
for (var prop in target){
if (target.hasOwnProperty(prop)) {
if (typeof this[prop] === "undefined"){
continue;
}
tween.node[prop] += (target[prop] - origin[prop]) * ratio;
if (Math.abs(tween.node[prop]) < 10e-8){
tween.node[prop] = 0;
}
}
}
});
}
});
/**
*
*/
}(window));