You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
29954 lines
946 KiB
29954 lines
946 KiB
/*!
|
|
* CreateJS
|
|
* Visit http://createjs.com/ for documentation, updates and examples.
|
|
*
|
|
* Copyright (c) 2010 gskinner.com, inc.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
const createjs = {};
|
|
export default createjs;
|
|
|
|
//##############################################################################
|
|
// extend.js
|
|
//##############################################################################
|
|
|
|
/**
|
|
* @class Utility Methods
|
|
*/
|
|
|
|
/**
|
|
* Sets up the prototype chain and constructor property for a new class.
|
|
*
|
|
* This should be called right after creating the class constructor.
|
|
*
|
|
* function MySubClass() {}
|
|
* createjs.extend(MySubClass, MySuperClass);
|
|
* MySubClass.prototype.doSomething = function() { }
|
|
*
|
|
* var foo = new MySubClass();
|
|
* console.log(foo instanceof MySuperClass); // true
|
|
* console.log(foo.prototype.constructor === MySubClass); // true
|
|
*
|
|
* @method extend
|
|
* @param {Function} subclass The subclass.
|
|
* @param {Function} superclass The superclass to extend.
|
|
* @return {Function} Returns the subclass's new prototype.
|
|
*/
|
|
createjs.extend = function (subclass, superclass) {
|
|
"use strict";
|
|
|
|
function o() {
|
|
this.constructor = subclass;
|
|
}
|
|
o.prototype = superclass.prototype;
|
|
return (subclass.prototype = new o());
|
|
};
|
|
|
|
//##############################################################################
|
|
// promote.js
|
|
//##############################################################################
|
|
|
|
/**
|
|
* @class Utility Methods
|
|
*/
|
|
|
|
/**
|
|
* Promotes any methods on the super class that were overridden, by creating an alias in the format `prefix_methodName`.
|
|
* It is recommended to use the super class's name as the prefix.
|
|
* An alias to the super class's constructor is always added in the format `prefix_constructor`.
|
|
* This allows the subclass to call super class methods without using `function.call`, providing better performance.
|
|
*
|
|
* For example, if `MySubClass` extends `MySuperClass`, and both define a `draw` method, then calling `promote(MySubClass, "MySuperClass")`
|
|
* would add a `MySuperClass_constructor` method to MySubClass and promote the `draw` method on `MySuperClass` to the
|
|
* prototype of `MySubClass` as `MySuperClass_draw`.
|
|
*
|
|
* This should be called after the class's prototype is fully defined.
|
|
*
|
|
* function ClassA(name) {
|
|
* this.name = name;
|
|
* }
|
|
* ClassA.prototype.greet = function() {
|
|
* return "Hello "+this.name;
|
|
* }
|
|
*
|
|
* function ClassB(name, punctuation) {
|
|
* this.ClassA_constructor(name);
|
|
* this.punctuation = punctuation;
|
|
* }
|
|
* createjs.extend(ClassB, ClassA);
|
|
* ClassB.prototype.greet = function() {
|
|
* return this.ClassA_greet()+this.punctuation;
|
|
* }
|
|
* createjs.promote(ClassB, "ClassA");
|
|
*
|
|
* var foo = new ClassB("World", "!?!");
|
|
* console.log(foo.greet()); // Hello World!?!
|
|
*
|
|
* @method promote
|
|
* @param {Function} subclass The class to promote super class methods on.
|
|
* @param {String} prefix The prefix to add to the promoted method names. Usually the name of the superclass.
|
|
* @return {Function} Returns the subclass.
|
|
*/
|
|
createjs.promote = function (subclass, prefix) {
|
|
"use strict";
|
|
|
|
var subP = subclass.prototype,
|
|
supP =
|
|
(Object.getPrototypeOf && Object.getPrototypeOf(subP)) || subP.__proto__;
|
|
if (supP) {
|
|
subP[(prefix += "_") + "constructor"] = supP.constructor; // constructor is not always innumerable
|
|
for (var n in supP) {
|
|
if (subP.hasOwnProperty(n) && typeof supP[n] == "function") {
|
|
subP[prefix + n] = supP[n];
|
|
}
|
|
}
|
|
}
|
|
return subclass;
|
|
};
|
|
|
|
//##############################################################################
|
|
// indexOf.js
|
|
//##############################################################################
|
|
|
|
/**
|
|
* @class Utility Methods
|
|
*/
|
|
|
|
/**
|
|
* Finds the first occurrence of a specified value searchElement in the passed in array, and returns the index of
|
|
* that value. Returns -1 if value is not found.
|
|
*
|
|
* var i = createjs.indexOf(myArray, myElementToFind);
|
|
*
|
|
* @method indexOf
|
|
* @param {Array} array Array to search for searchElement
|
|
* @param searchElement Element to find in array.
|
|
* @return {Number} The first index of searchElement in array.
|
|
*/
|
|
createjs.indexOf = function (array, searchElement) {
|
|
"use strict";
|
|
|
|
for (var i = 0, l = array.length; i < l; i++) {
|
|
if (searchElement === array[i]) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
|
|
//##############################################################################
|
|
// Event.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Contains properties and methods shared by all events for use with
|
|
* {{#crossLink "EventDispatcher"}}{{/crossLink}}.
|
|
*
|
|
* Note that Event objects are often reused, so you should never
|
|
* rely on an event object's state outside of the call stack it was received in.
|
|
* @class Event
|
|
* @param {String} type The event type.
|
|
* @param {Boolean} bubbles Indicates whether the event will bubble through the display list.
|
|
* @param {Boolean} cancelable Indicates whether the default behaviour of this event can be cancelled.
|
|
* @constructor
|
|
**/
|
|
function Event(type, bubbles, cancelable) {
|
|
// public properties:
|
|
/**
|
|
* The type of event.
|
|
* @property type
|
|
* @type String
|
|
**/
|
|
this.type = type;
|
|
|
|
/**
|
|
* The object that generated an event.
|
|
* @property target
|
|
* @type Object
|
|
* @default null
|
|
* @readonly
|
|
*/
|
|
this.target = null;
|
|
|
|
/**
|
|
* The current target that a bubbling event is being dispatched from. For non-bubbling events, this will
|
|
* always be the same as target. For example, if childObj.parent = parentObj, and a bubbling event
|
|
* is generated from childObj, then a listener on parentObj would receive the event with
|
|
* target=childObj (the original target) and currentTarget=parentObj (where the listener was added).
|
|
* @property currentTarget
|
|
* @type Object
|
|
* @default null
|
|
* @readonly
|
|
*/
|
|
this.currentTarget = null;
|
|
|
|
/**
|
|
* For bubbling events, this indicates the current event phase:<OL>
|
|
* <LI> capture phase: starting from the top parent to the target</LI>
|
|
* <LI> at target phase: currently being dispatched from the target</LI>
|
|
* <LI> bubbling phase: from the target to the top parent</LI>
|
|
* </OL>
|
|
* @property eventPhase
|
|
* @type Number
|
|
* @default 0
|
|
* @readonly
|
|
*/
|
|
this.eventPhase = 0;
|
|
|
|
/**
|
|
* Indicates whether the event will bubble through the display list.
|
|
* @property bubbles
|
|
* @type Boolean
|
|
* @default false
|
|
* @readonly
|
|
*/
|
|
this.bubbles = !!bubbles;
|
|
|
|
/**
|
|
* Indicates whether the default behaviour of this event can be cancelled via
|
|
* {{#crossLink "Event/preventDefault"}}{{/crossLink}}. This is set via the Event constructor.
|
|
* @property cancelable
|
|
* @type Boolean
|
|
* @default false
|
|
* @readonly
|
|
*/
|
|
this.cancelable = !!cancelable;
|
|
|
|
/**
|
|
* The epoch time at which this event was created.
|
|
* @property timeStamp
|
|
* @type Number
|
|
* @default 0
|
|
* @readonly
|
|
*/
|
|
this.timeStamp = new Date().getTime();
|
|
|
|
/**
|
|
* Indicates if {{#crossLink "Event/preventDefault"}}{{/crossLink}} has been called
|
|
* on this event.
|
|
* @property defaultPrevented
|
|
* @type Boolean
|
|
* @default false
|
|
* @readonly
|
|
*/
|
|
this.defaultPrevented = false;
|
|
|
|
/**
|
|
* Indicates if {{#crossLink "Event/stopPropagation"}}{{/crossLink}} or
|
|
* {{#crossLink "Event/stopImmediatePropagation"}}{{/crossLink}} has been called on this event.
|
|
* @property propagationStopped
|
|
* @type Boolean
|
|
* @default false
|
|
* @readonly
|
|
*/
|
|
this.propagationStopped = false;
|
|
|
|
/**
|
|
* Indicates if {{#crossLink "Event/stopImmediatePropagation"}}{{/crossLink}} has been called
|
|
* on this event.
|
|
* @property immediatePropagationStopped
|
|
* @type Boolean
|
|
* @default false
|
|
* @readonly
|
|
*/
|
|
this.immediatePropagationStopped = false;
|
|
|
|
/**
|
|
* Indicates if {{#crossLink "Event/remove"}}{{/crossLink}} has been called on this event.
|
|
* @property removed
|
|
* @type Boolean
|
|
* @default false
|
|
* @readonly
|
|
*/
|
|
this.removed = false;
|
|
}
|
|
var p = Event.prototype;
|
|
|
|
/**
|
|
* <strong>REMOVED</strong>. Removed in favor of using `MySuperClass_constructor`.
|
|
* See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}}
|
|
* for details.
|
|
*
|
|
* There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance.
|
|
*
|
|
* @method initialize
|
|
* @protected
|
|
* @deprecated
|
|
*/
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is.
|
|
|
|
// public methods:
|
|
/**
|
|
* Sets {{#crossLink "Event/defaultPrevented"}}{{/crossLink}} to true if the event is cancelable.
|
|
* Mirrors the DOM level 2 event standard. In general, cancelable events that have `preventDefault()` called will
|
|
* cancel the default behaviour associated with the event.
|
|
* @method preventDefault
|
|
**/
|
|
p.preventDefault = function () {
|
|
this.defaultPrevented = this.cancelable && true;
|
|
};
|
|
|
|
/**
|
|
* Sets {{#crossLink "Event/propagationStopped"}}{{/crossLink}} to true.
|
|
* Mirrors the DOM event standard.
|
|
* @method stopPropagation
|
|
**/
|
|
p.stopPropagation = function () {
|
|
this.propagationStopped = true;
|
|
};
|
|
|
|
/**
|
|
* Sets {{#crossLink "Event/propagationStopped"}}{{/crossLink}} and
|
|
* {{#crossLink "Event/immediatePropagationStopped"}}{{/crossLink}} to true.
|
|
* Mirrors the DOM event standard.
|
|
* @method stopImmediatePropagation
|
|
**/
|
|
p.stopImmediatePropagation = function () {
|
|
this.immediatePropagationStopped = this.propagationStopped = true;
|
|
};
|
|
|
|
/**
|
|
* Causes the active listener to be removed via removeEventListener();
|
|
*
|
|
* myBtn.addEventListener("click", function(evt) {
|
|
* // do stuff...
|
|
* evt.remove(); // removes this listener.
|
|
* });
|
|
*
|
|
* @method remove
|
|
**/
|
|
p.remove = function () {
|
|
this.removed = true;
|
|
};
|
|
|
|
/**
|
|
* Returns a clone of the Event instance.
|
|
* @method clone
|
|
* @return {Event} a clone of the Event instance.
|
|
**/
|
|
p.clone = function () {
|
|
return new Event(this.type, this.bubbles, this.cancelable);
|
|
};
|
|
|
|
/**
|
|
* Provides a chainable shortcut method for setting a number of properties on the instance.
|
|
*
|
|
* @method set
|
|
* @param {Object} props A generic object containing properties to copy to the instance.
|
|
* @return {Event} Returns the instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
*/
|
|
p.set = function (props) {
|
|
for (var n in props) {
|
|
this[n] = props[n];
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[Event (type=" + this.type + ")]";
|
|
};
|
|
|
|
createjs.Event = Event;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// EventDispatcher.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* EventDispatcher provides methods for managing queues of event listeners and dispatching events.
|
|
*
|
|
* You can either extend EventDispatcher or mix its methods into an existing prototype or instance by using the
|
|
* EventDispatcher {{#crossLink "EventDispatcher/initialize"}}{{/crossLink}} method.
|
|
*
|
|
* Together with the CreateJS Event class, EventDispatcher provides an extended event model that is based on the
|
|
* DOM Level 2 event model, including addEventListener, removeEventListener, and dispatchEvent. It supports
|
|
* bubbling / capture, preventDefault, stopPropagation, stopImmediatePropagation, and handleEvent.
|
|
*
|
|
* EventDispatcher also exposes a {{#crossLink "EventDispatcher/on"}}{{/crossLink}} method, which makes it easier
|
|
* to create scoped listeners, listeners that only run once, and listeners with associated arbitrary data. The
|
|
* {{#crossLink "EventDispatcher/off"}}{{/crossLink}} method is merely an alias to
|
|
* {{#crossLink "EventDispatcher/removeEventListener"}}{{/crossLink}}.
|
|
*
|
|
* Another addition to the DOM Level 2 model is the {{#crossLink "EventDispatcher/removeAllEventListeners"}}{{/crossLink}}
|
|
* method, which can be used to listeners for all events, or listeners for a specific event. The Event object also
|
|
* includes a {{#crossLink "Event/remove"}}{{/crossLink}} method which removes the active listener.
|
|
*
|
|
* <h4>Example</h4>
|
|
* Add EventDispatcher capabilities to the "MyClass" class.
|
|
*
|
|
* EventDispatcher.initialize(MyClass.prototype);
|
|
*
|
|
* Add an event (see {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}}).
|
|
*
|
|
* instance.addEventListener("eventName", handlerMethod);
|
|
* function handlerMethod(event) {
|
|
* console.log(event.target + " Was Clicked");
|
|
* }
|
|
*
|
|
* <b>Maintaining proper scope</b><br />
|
|
* Scope (ie. "this") can be be a challenge with events. Using the {{#crossLink "EventDispatcher/on"}}{{/crossLink}}
|
|
* method to subscribe to events simplifies this.
|
|
*
|
|
* instance.addEventListener("click", function(event) {
|
|
* console.log(instance == this); // false, scope is ambiguous.
|
|
* });
|
|
*
|
|
* instance.on("click", function(event) {
|
|
* console.log(instance == this); // true, "on" uses dispatcher scope by default.
|
|
* });
|
|
*
|
|
* If you want to use addEventListener instead, you may want to use function.bind() or a similar proxy to manage
|
|
* scope.
|
|
*
|
|
* <b>Browser support</b>
|
|
* The event model in CreateJS can be used separately from the suite in any project, however the inheritance model
|
|
* requires modern browsers (IE9+).
|
|
*
|
|
*
|
|
* @class EventDispatcher
|
|
* @constructor
|
|
**/
|
|
function EventDispatcher() {
|
|
// private properties:
|
|
/**
|
|
* @protected
|
|
* @property _listeners
|
|
* @type Object
|
|
**/
|
|
this._listeners = null;
|
|
|
|
/**
|
|
* @protected
|
|
* @property _captureListeners
|
|
* @type Object
|
|
**/
|
|
this._captureListeners = null;
|
|
}
|
|
var p = EventDispatcher.prototype;
|
|
|
|
/**
|
|
* <strong>REMOVED</strong>. Removed in favor of using `MySuperClass_constructor`.
|
|
* See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}}
|
|
* for details.
|
|
*
|
|
* There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance.
|
|
*
|
|
* @method initialize
|
|
* @protected
|
|
* @deprecated
|
|
*/
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is.
|
|
|
|
// static public methods:
|
|
/**
|
|
* Static initializer to mix EventDispatcher methods into a target object or prototype.
|
|
*
|
|
* EventDispatcher.initialize(MyClass.prototype); // add to the prototype of the class
|
|
* EventDispatcher.initialize(myObject); // add to a specific instance
|
|
*
|
|
* @method initialize
|
|
* @static
|
|
* @param {Object} target The target object to inject EventDispatcher methods into. This can be an instance or a
|
|
* prototype.
|
|
**/
|
|
EventDispatcher.initialize = function (target) {
|
|
target.addEventListener = p.addEventListener;
|
|
target.on = p.on;
|
|
target.removeEventListener = target.off = p.removeEventListener;
|
|
target.removeAllEventListeners = p.removeAllEventListeners;
|
|
target.hasEventListener = p.hasEventListener;
|
|
target.dispatchEvent = p.dispatchEvent;
|
|
target._dispatchEvent = p._dispatchEvent;
|
|
target.willTrigger = p.willTrigger;
|
|
};
|
|
|
|
// public methods:
|
|
/**
|
|
* Adds the specified event listener. Note that adding multiple listeners to the same function will result in
|
|
* multiple callbacks getting fired.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* displayObject.addEventListener("click", handleClick);
|
|
* function handleClick(event) {
|
|
* // Click happened.
|
|
* }
|
|
*
|
|
* @method addEventListener
|
|
* @param {String} type The string type of the event.
|
|
* @param {Function | Object} listener An object with a handleEvent method, or a function that will be called when
|
|
* the event is dispatched.
|
|
* @param {Boolean} [useCapture] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase.
|
|
* @return {Function | Object} Returns the listener for chaining or assignment.
|
|
**/
|
|
p.addEventListener = function (type, listener, useCapture) {
|
|
var listeners;
|
|
if (useCapture) {
|
|
listeners = this._captureListeners = this._captureListeners || {};
|
|
} else {
|
|
listeners = this._listeners = this._listeners || {};
|
|
}
|
|
var arr = listeners[type];
|
|
if (arr) {
|
|
this.removeEventListener(type, listener, useCapture);
|
|
}
|
|
arr = listeners[type]; // remove may have deleted the array
|
|
if (!arr) {
|
|
listeners[type] = [listener];
|
|
} else {
|
|
arr.push(listener);
|
|
}
|
|
return listener;
|
|
};
|
|
|
|
/**
|
|
* A shortcut method for using addEventListener that makes it easier to specify an execution scope, have a listener
|
|
* only run once, associate arbitrary data with the listener, and remove the listener.
|
|
*
|
|
* This method works by creating an anonymous wrapper function and subscribing it with addEventListener.
|
|
* The wrapper function is returned for use with `removeEventListener` (or `off`).
|
|
*
|
|
* <b>IMPORTANT:</b> To remove a listener added with `on`, you must pass in the returned wrapper function as the listener, or use
|
|
* {{#crossLink "Event/remove"}}{{/crossLink}}. Likewise, each time you call `on` a NEW wrapper function is subscribed, so multiple calls
|
|
* to `on` with the same params will create multiple listeners.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var listener = myBtn.on("click", handleClick, null, false, {count:3});
|
|
* function handleClick(evt, data) {
|
|
* data.count -= 1;
|
|
* console.log(this == myBtn); // true - scope defaults to the dispatcher
|
|
* if (data.count == 0) {
|
|
* alert("clicked 3 times!");
|
|
* myBtn.off("click", listener);
|
|
* // alternately: evt.remove();
|
|
* }
|
|
* }
|
|
*
|
|
* @method on
|
|
* @param {String} type The string type of the event.
|
|
* @param {Function | Object} listener An object with a handleEvent method, or a function that will be called when
|
|
* the event is dispatched.
|
|
* @param {Object} [scope] The scope to execute the listener in. Defaults to the dispatcher/currentTarget for function listeners, and to the listener itself for object listeners (ie. using handleEvent).
|
|
* @param {Boolean} [once=false] If true, the listener will remove itself after the first time it is triggered.
|
|
* @param {*} [data] Arbitrary data that will be included as the second parameter when the listener is called.
|
|
* @param {Boolean} [useCapture=false] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase.
|
|
* @return {Function} Returns the anonymous function that was created and assigned as the listener. This is needed to remove the listener later using .removeEventListener.
|
|
**/
|
|
p.on = function (type, listener, scope, once, data, useCapture) {
|
|
if (listener.handleEvent) {
|
|
scope = scope || listener;
|
|
listener = listener.handleEvent;
|
|
}
|
|
scope = scope || this;
|
|
return this.addEventListener(
|
|
type,
|
|
function (evt) {
|
|
listener.call(scope, evt, data);
|
|
once && evt.remove();
|
|
},
|
|
useCapture
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Removes the specified event listener.
|
|
*
|
|
* <b>Important Note:</b> that you must pass the exact function reference used when the event was added. If a proxy
|
|
* function, or function closure is used as the callback, the proxy/closure reference must be used - a new proxy or
|
|
* closure will not work.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* displayObject.removeEventListener("click", handleClick);
|
|
*
|
|
* @method removeEventListener
|
|
* @param {String} type The string type of the event.
|
|
* @param {Function | Object} listener The listener function or object.
|
|
* @param {Boolean} [useCapture] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase.
|
|
**/
|
|
p.removeEventListener = function (type, listener, useCapture) {
|
|
var listeners = useCapture ? this._captureListeners : this._listeners;
|
|
if (!listeners) {
|
|
return;
|
|
}
|
|
var arr = listeners[type];
|
|
if (!arr) {
|
|
return;
|
|
}
|
|
for (var i = 0, l = arr.length; i < l; i++) {
|
|
if (arr[i] == listener) {
|
|
if (l == 1) {
|
|
delete listeners[type];
|
|
} // allows for faster checks.
|
|
else {
|
|
arr.splice(i, 1);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* A shortcut to the removeEventListener method, with the same parameters and return value. This is a companion to the
|
|
* .on method.
|
|
*
|
|
* <b>IMPORTANT:</b> To remove a listener added with `on`, you must pass in the returned wrapper function as the listener. See
|
|
* {{#crossLink "EventDispatcher/on"}}{{/crossLink}} for an example.
|
|
*
|
|
* @method off
|
|
* @param {String} type The string type of the event.
|
|
* @param {Function | Object} listener The listener function or object.
|
|
* @param {Boolean} [useCapture] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase.
|
|
**/
|
|
p.off = p.removeEventListener;
|
|
|
|
/**
|
|
* Removes all listeners for the specified type, or all listeners of all types.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* // Remove all listeners
|
|
* displayObject.removeAllEventListeners();
|
|
*
|
|
* // Remove all click listeners
|
|
* displayObject.removeAllEventListeners("click");
|
|
*
|
|
* @method removeAllEventListeners
|
|
* @param {String} [type] The string type of the event. If omitted, all listeners for all types will be removed.
|
|
**/
|
|
p.removeAllEventListeners = function (type) {
|
|
if (!type) {
|
|
this._listeners = this._captureListeners = null;
|
|
} else {
|
|
if (this._listeners) {
|
|
delete this._listeners[type];
|
|
}
|
|
if (this._captureListeners) {
|
|
delete this._captureListeners[type];
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Dispatches the specified event to all listeners.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* // Use a string event
|
|
* this.dispatchEvent("complete");
|
|
*
|
|
* // Use an Event instance
|
|
* var event = new createjs.Event("progress");
|
|
* this.dispatchEvent(event);
|
|
*
|
|
* @method dispatchEvent
|
|
* @param {Object | String | Event} eventObj An object with a "type" property, or a string type.
|
|
* While a generic object will work, it is recommended to use a CreateJS Event instance. If a string is used,
|
|
* dispatchEvent will construct an Event instance if necessary with the specified type. This latter approach can
|
|
* be used to avoid event object instantiation for non-bubbling events that may not have any listeners.
|
|
* @param {Boolean} [bubbles] Specifies the `bubbles` value when a string was passed to eventObj.
|
|
* @param {Boolean} [cancelable] Specifies the `cancelable` value when a string was passed to eventObj.
|
|
* @return {Boolean} Returns false if `preventDefault()` was called on a cancelable event, true otherwise.
|
|
**/
|
|
p.dispatchEvent = function (eventObj, bubbles, cancelable) {
|
|
if (typeof eventObj == "string") {
|
|
// skip everything if there's no listeners and it doesn't bubble:
|
|
var listeners = this._listeners;
|
|
if (!bubbles && (!listeners || !listeners[eventObj])) {
|
|
return true;
|
|
}
|
|
eventObj = new createjs.Event(eventObj, bubbles, cancelable);
|
|
} else if (eventObj.target && eventObj.clone) {
|
|
// redispatching an active event object, so clone it:
|
|
eventObj = eventObj.clone();
|
|
}
|
|
|
|
// TODO: it would be nice to eliminate this. Maybe in favour of evtObj instanceof Event? Or !!evtObj.createEvent
|
|
try {
|
|
eventObj.target = this;
|
|
} catch (e) {} // try/catch allows redispatching of native events
|
|
|
|
if (!eventObj.bubbles || !this.parent) {
|
|
this._dispatchEvent(eventObj, 2);
|
|
} else {
|
|
var top = this,
|
|
list = [top];
|
|
while (top.parent) {
|
|
list.push((top = top.parent));
|
|
}
|
|
var i,
|
|
l = list.length;
|
|
|
|
// capture & atTarget
|
|
for (i = l - 1; i >= 0 && !eventObj.propagationStopped; i--) {
|
|
list[i]._dispatchEvent(eventObj, 1 + (i == 0));
|
|
}
|
|
// bubbling
|
|
for (i = 1; i < l && !eventObj.propagationStopped; i++) {
|
|
list[i]._dispatchEvent(eventObj, 3);
|
|
}
|
|
}
|
|
return !eventObj.defaultPrevented;
|
|
};
|
|
|
|
/**
|
|
* Indicates whether there is at least one listener for the specified event type.
|
|
* @method hasEventListener
|
|
* @param {String} type The string type of the event.
|
|
* @return {Boolean} Returns true if there is at least one listener for the specified event.
|
|
**/
|
|
p.hasEventListener = function (type) {
|
|
var listeners = this._listeners,
|
|
captureListeners = this._captureListeners;
|
|
return !!(
|
|
(listeners && listeners[type]) ||
|
|
(captureListeners && captureListeners[type])
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Indicates whether there is at least one listener for the specified event type on this object or any of its
|
|
* ancestors (parent, parent's parent, etc). A return value of true indicates that if a bubbling event of the
|
|
* specified type is dispatched from this object, it will trigger at least one listener.
|
|
*
|
|
* This is similar to {{#crossLink "EventDispatcher/hasEventListener"}}{{/crossLink}}, but it searches the entire
|
|
* event flow for a listener, not just this object.
|
|
* @method willTrigger
|
|
* @param {String} type The string type of the event.
|
|
* @return {Boolean} Returns `true` if there is at least one listener for the specified event.
|
|
**/
|
|
p.willTrigger = function (type) {
|
|
var o = this;
|
|
while (o) {
|
|
if (o.hasEventListener(type)) {
|
|
return true;
|
|
}
|
|
o = o.parent;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[EventDispatcher]";
|
|
};
|
|
|
|
// private methods:
|
|
/**
|
|
* @method _dispatchEvent
|
|
* @param {Object | String | Event} eventObj
|
|
* @param {Object} eventPhase
|
|
* @protected
|
|
**/
|
|
p._dispatchEvent = function (eventObj, eventPhase) {
|
|
var l,
|
|
listeners = eventPhase == 1 ? this._captureListeners : this._listeners;
|
|
if (eventObj && listeners) {
|
|
var arr = listeners[eventObj.type];
|
|
if (!arr || !(l = arr.length)) {
|
|
return;
|
|
}
|
|
try {
|
|
eventObj.currentTarget = this;
|
|
} catch (e) {}
|
|
try {
|
|
eventObj.eventPhase = eventPhase;
|
|
} catch (e) {}
|
|
eventObj.removed = false;
|
|
|
|
arr = arr.slice(); // to avoid issues with items being removed or added during the dispatch
|
|
for (var i = 0; i < l && !eventObj.immediatePropagationStopped; i++) {
|
|
var o = arr[i];
|
|
if (o.handleEvent) {
|
|
o.handleEvent(eventObj);
|
|
} else {
|
|
o(eventObj);
|
|
}
|
|
if (eventObj.removed) {
|
|
this.off(eventObj.type, o, eventPhase == 1);
|
|
eventObj.removed = false;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
createjs.EventDispatcher = EventDispatcher;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Ticker.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* The Ticker provides a centralized tick or heartbeat broadcast at a set interval. Listeners can subscribe to the tick
|
|
* event to be notified when a set time interval has elapsed.
|
|
*
|
|
* Note that the interval that the tick event is called is a target interval, and may be broadcast at a slower interval
|
|
* when under high CPU load. The Ticker class uses a static interface (ex. `Ticker.framerate = 30;`) and
|
|
* can not be instantiated.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* createjs.Ticker.addEventListener("tick", handleTick);
|
|
* function handleTick(event) {
|
|
* // Actions carried out each tick (aka frame)
|
|
* if (!event.paused) {
|
|
* // Actions carried out when the Ticker is not paused.
|
|
* }
|
|
* }
|
|
*
|
|
* @class Ticker
|
|
* @uses EventDispatcher
|
|
* @static
|
|
**/
|
|
function Ticker() {
|
|
throw "Ticker cannot be instantiated.";
|
|
}
|
|
|
|
// constants:
|
|
/**
|
|
* In this mode, Ticker uses the requestAnimationFrame API, but attempts to synch the ticks to target framerate. It
|
|
* uses a simple heuristic that compares the time of the RAF return to the target time for the current frame and
|
|
* dispatches the tick when the time is within a certain threshold.
|
|
*
|
|
* This mode has a higher variance for time between frames than {{#crossLink "Ticker/TIMEOUT:property"}}{{/crossLink}},
|
|
* but does not require that content be time based as with {{#crossLink "Ticker/RAF:property"}}{{/crossLink}} while
|
|
* gaining the benefits of that API (screen synch, background throttling).
|
|
*
|
|
* Variance is usually lowest for framerates that are a divisor of the RAF frequency. This is usually 60, so
|
|
* framerates of 10, 12, 15, 20, and 30 work well.
|
|
*
|
|
* Falls back to {{#crossLink "Ticker/TIMEOUT:property"}}{{/crossLink}} if the requestAnimationFrame API is not
|
|
* supported.
|
|
* @property RAF_SYNCHED
|
|
* @static
|
|
* @type {String}
|
|
* @default "synched"
|
|
* @readonly
|
|
**/
|
|
Ticker.RAF_SYNCHED = "synched";
|
|
|
|
/**
|
|
* In this mode, Ticker passes through the requestAnimationFrame heartbeat, ignoring the target framerate completely.
|
|
* Because requestAnimationFrame frequency is not deterministic, any content using this mode should be time based.
|
|
* You can leverage {{#crossLink "Ticker/getTime"}}{{/crossLink}} and the {{#crossLink "Ticker/tick:event"}}{{/crossLink}}
|
|
* event object's "delta" properties to make this easier.
|
|
*
|
|
* Falls back on {{#crossLink "Ticker/TIMEOUT:property"}}{{/crossLink}} if the requestAnimationFrame API is not
|
|
* supported.
|
|
* @property RAF
|
|
* @static
|
|
* @type {String}
|
|
* @default "raf"
|
|
* @readonly
|
|
**/
|
|
Ticker.RAF = "raf";
|
|
|
|
/**
|
|
* In this mode, Ticker uses the setTimeout API. This provides predictable, adaptive frame timing, but does not
|
|
* provide the benefits of requestAnimationFrame (screen synch, background throttling).
|
|
* @property TIMEOUT
|
|
* @static
|
|
* @type {String}
|
|
* @default "timeout"
|
|
* @readonly
|
|
**/
|
|
Ticker.TIMEOUT = "timeout";
|
|
|
|
// static events:
|
|
/**
|
|
* Dispatched each tick. The event will be dispatched to each listener even when the Ticker has been paused using
|
|
* {{#crossLink "Ticker/setPaused"}}{{/crossLink}}.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* createjs.Ticker.addEventListener("tick", handleTick);
|
|
* function handleTick(event) {
|
|
* console.log("Paused:", event.paused, event.delta);
|
|
* }
|
|
*
|
|
* @event tick
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @param {Boolean} paused Indicates whether the ticker is currently paused.
|
|
* @param {Number} delta The time elapsed in ms since the last tick.
|
|
* @param {Number} time The total time in ms since Ticker was initialized.
|
|
* @param {Number} runTime The total time in ms that Ticker was not paused since it was initialized. For example,
|
|
* you could determine the amount of time that the Ticker has been paused since initialization with `time-runTime`.
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
// public static properties:
|
|
/**
|
|
* Deprecated in favour of {{#crossLink "Ticker/timingMode"}}{{/crossLink}}, and will be removed in a future version. If true, timingMode will
|
|
* use {{#crossLink "Ticker/RAF_SYNCHED"}}{{/crossLink}} by default.
|
|
* @deprecated Deprecated in favour of {{#crossLink "Ticker/timingMode"}}{{/crossLink}}.
|
|
* @property useRAF
|
|
* @static
|
|
* @type {Boolean}
|
|
* @default false
|
|
**/
|
|
Ticker.useRAF = false;
|
|
|
|
/**
|
|
* Specifies the timing api (setTimeout or requestAnimationFrame) and mode to use. See
|
|
* {{#crossLink "Ticker/TIMEOUT"}}{{/crossLink}}, {{#crossLink "Ticker/RAF"}}{{/crossLink}}, and
|
|
* {{#crossLink "Ticker/RAF_SYNCHED"}}{{/crossLink}} for mode details.
|
|
* @property timingMode
|
|
* @static
|
|
* @type {String}
|
|
* @default Ticker.TIMEOUT
|
|
**/
|
|
Ticker.timingMode = null;
|
|
|
|
/**
|
|
* Specifies a maximum value for the delta property in the tick event object. This is useful when building time
|
|
* based animations and systems to prevent issues caused by large time gaps caused by background tabs, system sleep,
|
|
* alert dialogs, or other blocking routines. Double the expected frame duration is often an effective value
|
|
* (ex. maxDelta=50 when running at 40fps).
|
|
*
|
|
* This does not impact any other values (ex. time, runTime, etc), so you may experience issues if you enable maxDelta
|
|
* when using both delta and other values.
|
|
*
|
|
* If 0, there is no maximum.
|
|
* @property maxDelta
|
|
* @static
|
|
* @type {number}
|
|
* @default 0
|
|
*/
|
|
Ticker.maxDelta = 0;
|
|
|
|
/**
|
|
* When the ticker is paused, all listeners will still receive a tick event, but the <code>paused</code> property
|
|
* of the event will be `true`. Also, while paused the `runTime` will not increase. See {{#crossLink "Ticker/tick:event"}}{{/crossLink}},
|
|
* {{#crossLink "Ticker/getTime"}}{{/crossLink}}, and {{#crossLink "Ticker/getEventTime"}}{{/crossLink}} for more
|
|
* info.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* createjs.Ticker.addEventListener("tick", handleTick);
|
|
* createjs.Ticker.paused = true;
|
|
* function handleTick(event) {
|
|
* console.log(event.paused,
|
|
* createjs.Ticker.getTime(false),
|
|
* createjs.Ticker.getTime(true));
|
|
* }
|
|
*
|
|
* @property paused
|
|
* @static
|
|
* @type {Boolean}
|
|
* @default false
|
|
**/
|
|
Ticker.paused = false;
|
|
|
|
// mix-ins:
|
|
// EventDispatcher methods:
|
|
Ticker.removeEventListener = null;
|
|
Ticker.removeAllEventListeners = null;
|
|
Ticker.dispatchEvent = null;
|
|
Ticker.hasEventListener = null;
|
|
Ticker._listeners = null;
|
|
createjs.EventDispatcher.initialize(Ticker); // inject EventDispatcher methods.
|
|
Ticker._addEventListener = Ticker.addEventListener;
|
|
Ticker.addEventListener = function () {
|
|
!Ticker._inited && Ticker.init();
|
|
return Ticker._addEventListener.apply(Ticker, arguments);
|
|
};
|
|
|
|
// private static properties:
|
|
/**
|
|
* @property _inited
|
|
* @static
|
|
* @type {Boolean}
|
|
* @protected
|
|
**/
|
|
Ticker._inited = false;
|
|
|
|
/**
|
|
* @property _startTime
|
|
* @static
|
|
* @type {Number}
|
|
* @protected
|
|
**/
|
|
Ticker._startTime = 0;
|
|
|
|
/**
|
|
* @property _pausedTime
|
|
* @static
|
|
* @type {Number}
|
|
* @protected
|
|
**/
|
|
Ticker._pausedTime = 0;
|
|
|
|
/**
|
|
* The number of ticks that have passed
|
|
* @property _ticks
|
|
* @static
|
|
* @type {Number}
|
|
* @protected
|
|
**/
|
|
Ticker._ticks = 0;
|
|
|
|
/**
|
|
* The number of ticks that have passed while Ticker has been paused
|
|
* @property _pausedTicks
|
|
* @static
|
|
* @type {Number}
|
|
* @protected
|
|
**/
|
|
Ticker._pausedTicks = 0;
|
|
|
|
/**
|
|
* @property _interval
|
|
* @static
|
|
* @type {Number}
|
|
* @protected
|
|
**/
|
|
Ticker._interval = 50;
|
|
|
|
/**
|
|
* @property _lastTime
|
|
* @static
|
|
* @type {Number}
|
|
* @protected
|
|
**/
|
|
Ticker._lastTime = 0;
|
|
|
|
/**
|
|
* @property _times
|
|
* @static
|
|
* @type {Array}
|
|
* @protected
|
|
**/
|
|
Ticker._times = null;
|
|
|
|
/**
|
|
* @property _tickTimes
|
|
* @static
|
|
* @type {Array}
|
|
* @protected
|
|
**/
|
|
Ticker._tickTimes = null;
|
|
|
|
/**
|
|
* Stores the timeout or requestAnimationFrame id.
|
|
* @property _timerId
|
|
* @static
|
|
* @type {Number}
|
|
* @protected
|
|
**/
|
|
Ticker._timerId = null;
|
|
|
|
/**
|
|
* True if currently using requestAnimationFrame, false if using setTimeout. This may be different than timingMode
|
|
* if that property changed and a tick hasn't fired.
|
|
* @property _raf
|
|
* @static
|
|
* @type {Boolean}
|
|
* @protected
|
|
**/
|
|
Ticker._raf = true;
|
|
|
|
// static getter / setters:
|
|
/**
|
|
* Use the {{#crossLink "Ticker/interval:property"}}{{/crossLink}} property instead.
|
|
* @method setInterval
|
|
* @static
|
|
* @param {Number} interval
|
|
* @deprecated
|
|
**/
|
|
Ticker.setInterval = function (interval) {
|
|
Ticker._interval = interval;
|
|
if (!Ticker._inited) {
|
|
return;
|
|
}
|
|
Ticker._setupTick();
|
|
};
|
|
|
|
/**
|
|
* Use the {{#crossLink "Ticker/interval:property"}}{{/crossLink}} property instead.
|
|
* @method getInterval
|
|
* @static
|
|
* @return {Number}
|
|
* @deprecated
|
|
**/
|
|
Ticker.getInterval = function () {
|
|
return Ticker._interval;
|
|
};
|
|
|
|
/**
|
|
* Use the {{#crossLink "Ticker/framerate:property"}}{{/crossLink}} property instead.
|
|
* @method setFPS
|
|
* @static
|
|
* @param {Number} value
|
|
* @deprecated
|
|
**/
|
|
Ticker.setFPS = function (value) {
|
|
Ticker.setInterval(1000 / value);
|
|
};
|
|
|
|
/**
|
|
* Use the {{#crossLink "Ticker/framerate:property"}}{{/crossLink}} property instead.
|
|
* @method getFPS
|
|
* @static
|
|
* @return {Number}
|
|
* @deprecated
|
|
**/
|
|
Ticker.getFPS = function () {
|
|
return 1000 / Ticker._interval;
|
|
};
|
|
|
|
/**
|
|
* Indicates the target time (in milliseconds) between ticks. Default is 50 (20 FPS).
|
|
* Note that actual time between ticks may be more than specified depending on CPU load.
|
|
* This property is ignored if the ticker is using the `RAF` timing mode.
|
|
* @property interval
|
|
* @static
|
|
* @type {Number}
|
|
**/
|
|
|
|
/**
|
|
* Indicates the target frame rate in frames per second (FPS). Effectively just a shortcut to `interval`, where
|
|
* `framerate == 1000/interval`.
|
|
* @property framerate
|
|
* @static
|
|
* @type {Number}
|
|
**/
|
|
try {
|
|
Object.defineProperties(Ticker, {
|
|
interval: { get: Ticker.getInterval, set: Ticker.setInterval },
|
|
framerate: { get: Ticker.getFPS, set: Ticker.setFPS },
|
|
});
|
|
} catch (e) {
|
|
console.log(e);
|
|
}
|
|
|
|
// public static methods:
|
|
/**
|
|
* Starts the tick. This is called automatically when the first listener is added.
|
|
* @method init
|
|
* @static
|
|
**/
|
|
Ticker.init = function () {
|
|
if (Ticker._inited) {
|
|
return;
|
|
}
|
|
Ticker._inited = true;
|
|
Ticker._times = [];
|
|
Ticker._tickTimes = [];
|
|
Ticker._startTime = Ticker._getTime();
|
|
Ticker._times.push((Ticker._lastTime = 0));
|
|
Ticker.interval = Ticker._interval;
|
|
};
|
|
|
|
/**
|
|
* Stops the Ticker and removes all listeners. Use init() to restart the Ticker.
|
|
* @method reset
|
|
* @static
|
|
**/
|
|
Ticker.reset = function () {
|
|
if (Ticker._raf) {
|
|
var f =
|
|
window.cancelAnimationFrame ||
|
|
window.webkitCancelAnimationFrame ||
|
|
window.mozCancelAnimationFrame ||
|
|
window.oCancelAnimationFrame ||
|
|
window.msCancelAnimationFrame;
|
|
f && f(Ticker._timerId);
|
|
} else {
|
|
clearTimeout(Ticker._timerId);
|
|
}
|
|
Ticker.removeAllEventListeners("tick");
|
|
Ticker._timerId = Ticker._times = Ticker._tickTimes = null;
|
|
Ticker._startTime = Ticker._lastTime = Ticker._ticks = 0;
|
|
Ticker._inited = false;
|
|
};
|
|
|
|
/**
|
|
* Returns the average time spent within a tick. This can vary significantly from the value provided by getMeasuredFPS
|
|
* because it only measures the time spent within the tick execution stack.
|
|
*
|
|
* Example 1: With a target FPS of 20, getMeasuredFPS() returns 20fps, which indicates an average of 50ms between
|
|
* the end of one tick and the end of the next. However, getMeasuredTickTime() returns 15ms. This indicates that
|
|
* there may be up to 35ms of "idle" time between the end of one tick and the start of the next.
|
|
*
|
|
* Example 2: With a target FPS of 30, getFPS() returns 10fps, which indicates an average of 100ms between the end of
|
|
* one tick and the end of the next. However, getMeasuredTickTime() returns 20ms. This would indicate that something
|
|
* other than the tick is using ~80ms (another script, DOM rendering, etc).
|
|
* @method getMeasuredTickTime
|
|
* @static
|
|
* @param {Number} [ticks] The number of previous ticks over which to measure the average time spent in a tick.
|
|
* Defaults to the number of ticks per second. To get only the last tick's time, pass in 1.
|
|
* @return {Number} The average time spent in a tick in milliseconds.
|
|
**/
|
|
Ticker.getMeasuredTickTime = function (ticks) {
|
|
var ttl = 0,
|
|
times = Ticker._tickTimes;
|
|
if (!times || times.length < 1) {
|
|
return -1;
|
|
}
|
|
|
|
// by default, calculate average for the past ~1 second:
|
|
ticks = Math.min(times.length, ticks || Ticker.getFPS() | 0);
|
|
for (var i = 0; i < ticks; i++) {
|
|
ttl += times[i];
|
|
}
|
|
return ttl / ticks;
|
|
};
|
|
|
|
/**
|
|
* Returns the actual frames / ticks per second.
|
|
* @method getMeasuredFPS
|
|
* @static
|
|
* @param {Number} [ticks] The number of previous ticks over which to measure the actual frames / ticks per second.
|
|
* Defaults to the number of ticks per second.
|
|
* @return {Number} The actual frames / ticks per second. Depending on performance, this may differ
|
|
* from the target frames per second.
|
|
**/
|
|
Ticker.getMeasuredFPS = function (ticks) {
|
|
var times = Ticker._times;
|
|
if (!times || times.length < 2) {
|
|
return -1;
|
|
}
|
|
|
|
// by default, calculate fps for the past ~1 second:
|
|
ticks = Math.min(times.length - 1, ticks || Ticker.getFPS() | 0);
|
|
return 1000 / ((times[0] - times[ticks]) / ticks);
|
|
};
|
|
|
|
/**
|
|
* Use the {{#crossLink "Ticker/paused:property"}}{{/crossLink}} property instead.
|
|
* @method setPaused
|
|
* @static
|
|
* @param {Boolean} value
|
|
* @deprecated
|
|
**/
|
|
Ticker.setPaused = function (value) {
|
|
// TODO: deprecated.
|
|
Ticker.paused = value;
|
|
};
|
|
|
|
/**
|
|
* Use the {{#crossLink "Ticker/paused:property"}}{{/crossLink}} property instead.
|
|
* @method getPaused
|
|
* @static
|
|
* @return {Boolean}
|
|
* @deprecated
|
|
**/
|
|
Ticker.getPaused = function () {
|
|
// TODO: deprecated.
|
|
return Ticker.paused;
|
|
};
|
|
|
|
/**
|
|
* Returns the number of milliseconds that have elapsed since Ticker was initialized via {{#crossLink "Ticker/init"}}.
|
|
* Returns -1 if Ticker has not been initialized. For example, you could use
|
|
* this in a time synchronized animation to determine the exact amount of time that has elapsed.
|
|
* @method getTime
|
|
* @static
|
|
* @param {Boolean} [runTime=false] If true only time elapsed while Ticker was not paused will be returned.
|
|
* If false, the value returned will be total time elapsed since the first tick event listener was added.
|
|
* @return {Number} Number of milliseconds that have elapsed since Ticker was initialized or -1.
|
|
**/
|
|
Ticker.getTime = function (runTime) {
|
|
return Ticker._startTime
|
|
? Ticker._getTime() - (runTime ? Ticker._pausedTime : 0)
|
|
: -1;
|
|
};
|
|
|
|
/**
|
|
* Similar to the {{#crossLink "Ticker/getTime"}}{{/crossLink}} method, but returns the time on the most recent {{#crossLink "Ticker/tick:event"}}{{/crossLink}}
|
|
* event object.
|
|
* @method getEventTime
|
|
* @static
|
|
* @param runTime {Boolean} [runTime=false] If true, the runTime property will be returned instead of time.
|
|
* @returns {number} The time or runTime property from the most recent tick event or -1.
|
|
*/
|
|
Ticker.getEventTime = function (runTime) {
|
|
return Ticker._startTime
|
|
? (Ticker._lastTime || Ticker._startTime) -
|
|
(runTime ? Ticker._pausedTime : 0)
|
|
: -1;
|
|
};
|
|
|
|
/**
|
|
* Returns the number of ticks that have been broadcast by Ticker.
|
|
* @method getTicks
|
|
* @static
|
|
* @param {Boolean} pauseable Indicates whether to include ticks that would have been broadcast
|
|
* while Ticker was paused. If true only tick events broadcast while Ticker is not paused will be returned.
|
|
* If false, tick events that would have been broadcast while Ticker was paused will be included in the return
|
|
* value. The default value is false.
|
|
* @return {Number} of ticks that have been broadcast.
|
|
**/
|
|
Ticker.getTicks = function (pauseable) {
|
|
return Ticker._ticks - (pauseable ? Ticker._pausedTicks : 0);
|
|
};
|
|
|
|
// private static methods:
|
|
/**
|
|
* @method _handleSynch
|
|
* @static
|
|
* @protected
|
|
**/
|
|
Ticker._handleSynch = function () {
|
|
Ticker._timerId = null;
|
|
Ticker._setupTick();
|
|
|
|
// run if enough time has elapsed, with a little bit of flexibility to be early:
|
|
if (Ticker._getTime() - Ticker._lastTime >= (Ticker._interval - 1) * 0.97) {
|
|
Ticker._tick();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _handleRAF
|
|
* @static
|
|
* @protected
|
|
**/
|
|
Ticker._handleRAF = function () {
|
|
Ticker._timerId = null;
|
|
Ticker._setupTick();
|
|
Ticker._tick();
|
|
};
|
|
|
|
/**
|
|
* @method _handleTimeout
|
|
* @static
|
|
* @protected
|
|
**/
|
|
Ticker._handleTimeout = function () {
|
|
Ticker._timerId = null;
|
|
Ticker._setupTick();
|
|
Ticker._tick();
|
|
};
|
|
|
|
/**
|
|
* @method _setupTick
|
|
* @static
|
|
* @protected
|
|
**/
|
|
Ticker._setupTick = function () {
|
|
if (Ticker._timerId != null) {
|
|
return;
|
|
} // avoid duplicates
|
|
|
|
var mode = Ticker.timingMode || (Ticker.useRAF && Ticker.RAF_SYNCHED);
|
|
if (mode == Ticker.RAF_SYNCHED || mode == Ticker.RAF) {
|
|
var f =
|
|
window.requestAnimationFrame ||
|
|
window.webkitRequestAnimationFrame ||
|
|
window.mozRequestAnimationFrame ||
|
|
window.oRequestAnimationFrame ||
|
|
window.msRequestAnimationFrame;
|
|
if (f) {
|
|
Ticker._timerId = f(
|
|
mode == Ticker.RAF ? Ticker._handleRAF : Ticker._handleSynch
|
|
);
|
|
Ticker._raf = true;
|
|
return;
|
|
}
|
|
}
|
|
Ticker._raf = false;
|
|
Ticker._timerId = setTimeout(Ticker._handleTimeout, Ticker._interval);
|
|
};
|
|
|
|
/**
|
|
* @method _tick
|
|
* @static
|
|
* @protected
|
|
**/
|
|
Ticker._tick = function () {
|
|
var paused = Ticker.paused;
|
|
var time = Ticker._getTime();
|
|
var elapsedTime = time - Ticker._lastTime;
|
|
Ticker._lastTime = time;
|
|
Ticker._ticks++;
|
|
|
|
if (paused) {
|
|
Ticker._pausedTicks++;
|
|
Ticker._pausedTime += elapsedTime;
|
|
}
|
|
|
|
if (Ticker.hasEventListener("tick")) {
|
|
var event = new createjs.Event("tick");
|
|
var maxDelta = Ticker.maxDelta;
|
|
event.delta = maxDelta && elapsedTime > maxDelta ? maxDelta : elapsedTime;
|
|
event.paused = paused;
|
|
event.time = time;
|
|
event.runTime = time - Ticker._pausedTime;
|
|
Ticker.dispatchEvent(event);
|
|
}
|
|
|
|
Ticker._tickTimes.unshift(Ticker._getTime() - time);
|
|
while (Ticker._tickTimes.length > 100) {
|
|
Ticker._tickTimes.pop();
|
|
}
|
|
|
|
Ticker._times.unshift(time);
|
|
while (Ticker._times.length > 100) {
|
|
Ticker._times.pop();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _getTime
|
|
* @static
|
|
* @protected
|
|
**/
|
|
var now =
|
|
window.performance &&
|
|
(performance.now ||
|
|
performance.mozNow ||
|
|
performance.msNow ||
|
|
performance.oNow ||
|
|
performance.webkitNow);
|
|
Ticker._getTime = function () {
|
|
return (
|
|
((now && now.call(performance)) || new Date().getTime()) -
|
|
Ticker._startTime
|
|
);
|
|
};
|
|
|
|
createjs.Ticker = Ticker;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// UID.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Global utility for generating sequential unique ID numbers. The UID class uses a static interface (ex. <code>UID.get()</code>)
|
|
* and should not be instantiated.
|
|
* @class UID
|
|
* @static
|
|
**/
|
|
function UID() {
|
|
throw "UID cannot be instantiated";
|
|
}
|
|
|
|
// private static properties:
|
|
/**
|
|
* @property _nextID
|
|
* @type Number
|
|
* @protected
|
|
**/
|
|
UID._nextID = 0;
|
|
|
|
// public static methods:
|
|
/**
|
|
* Returns the next unique id.
|
|
* @method get
|
|
* @return {Number} The next unique id
|
|
* @static
|
|
**/
|
|
UID.get = function () {
|
|
return UID._nextID++;
|
|
};
|
|
|
|
createjs.UID = UID;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// MouseEvent.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Passed as the parameter to all mouse/pointer/touch related events. For a listing of mouse events and their properties,
|
|
* see the {{#crossLink "DisplayObject"}}{{/crossLink}} and {{#crossLink "Stage"}}{{/crossLink}} event listings.
|
|
* @class MouseEvent
|
|
* @param {String} type The event type.
|
|
* @param {Boolean} bubbles Indicates whether the event will bubble through the display list.
|
|
* @param {Boolean} cancelable Indicates whether the default behaviour of this event can be cancelled.
|
|
* @param {Number} stageX The normalized x position relative to the stage.
|
|
* @param {Number} stageY The normalized y position relative to the stage.
|
|
* @param {MouseEvent} nativeEvent The native DOM event related to this mouse event.
|
|
* @param {Number} pointerID The unique id for the pointer.
|
|
* @param {Boolean} primary Indicates whether this is the primary pointer in a multitouch environment.
|
|
* @param {Number} rawX The raw x position relative to the stage.
|
|
* @param {Number} rawY The raw y position relative to the stage.
|
|
* @param {DisplayObject} relatedTarget The secondary target for the event.
|
|
* @extends Event
|
|
* @constructor
|
|
**/
|
|
function MouseEvent(
|
|
type,
|
|
bubbles,
|
|
cancelable,
|
|
stageX,
|
|
stageY,
|
|
nativeEvent,
|
|
pointerID,
|
|
primary,
|
|
rawX,
|
|
rawY,
|
|
relatedTarget
|
|
) {
|
|
this.Event_constructor(type, bubbles, cancelable);
|
|
|
|
// public properties:
|
|
/**
|
|
* The normalized x position on the stage. This will always be within the range 0 to stage width.
|
|
* @property stageX
|
|
* @type Number
|
|
*/
|
|
this.stageX = stageX;
|
|
|
|
/**
|
|
* The normalized y position on the stage. This will always be within the range 0 to stage height.
|
|
* @property stageY
|
|
* @type Number
|
|
**/
|
|
this.stageY = stageY;
|
|
|
|
/**
|
|
* The raw x position relative to the stage. Normally this will be the same as the stageX value, unless
|
|
* stage.mouseMoveOutside is true and the pointer is outside of the stage bounds.
|
|
* @property rawX
|
|
* @type Number
|
|
*/
|
|
this.rawX = rawX == null ? stageX : rawX;
|
|
|
|
/**
|
|
* The raw y position relative to the stage. Normally this will be the same as the stageY value, unless
|
|
* stage.mouseMoveOutside is true and the pointer is outside of the stage bounds.
|
|
* @property rawY
|
|
* @type Number
|
|
*/
|
|
this.rawY = rawY == null ? stageY : rawY;
|
|
|
|
/**
|
|
* The native MouseEvent generated by the browser. The properties and API for this
|
|
* event may differ between browsers. This property will be null if the
|
|
* EaselJS property was not directly generated from a native MouseEvent.
|
|
* @property nativeEvent
|
|
* @type HtmlMouseEvent
|
|
* @default null
|
|
**/
|
|
this.nativeEvent = nativeEvent;
|
|
|
|
/**
|
|
* The unique id for the pointer (touch point or cursor). This will be either -1 for the mouse, or the system
|
|
* supplied id value.
|
|
* @property pointerID
|
|
* @type {Number}
|
|
*/
|
|
this.pointerID = pointerID;
|
|
|
|
/**
|
|
* Indicates whether this is the primary pointer in a multitouch environment. This will always be true for the mouse.
|
|
* For touch pointers, the first pointer in the current stack will be considered the primary pointer.
|
|
* @property primary
|
|
* @type {Boolean}
|
|
*/
|
|
this.primary = !!primary;
|
|
|
|
/**
|
|
* The secondary target for the event, if applicable. This is used for mouseout/rollout
|
|
* events to indicate the object that the mouse entered from, mouseover/rollover for the object the mouse exited,
|
|
* and stagemousedown/stagemouseup events for the object that was the under the cursor, if any.
|
|
*
|
|
* Only valid interaction targets will be returned (ie. objects with mouse listeners or a cursor set).
|
|
* @property relatedTarget
|
|
* @type {DisplayObject}
|
|
*/
|
|
this.relatedTarget = relatedTarget;
|
|
}
|
|
var p = createjs.extend(MouseEvent, createjs.Event);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// getter / setters:
|
|
/**
|
|
* Returns the x position of the mouse in the local coordinate system of the current target (ie. the dispatcher).
|
|
* @property localX
|
|
* @type {Number}
|
|
* @readonly
|
|
*/
|
|
p._get_localX = function () {
|
|
return this.currentTarget.globalToLocal(this.rawX, this.rawY).x;
|
|
};
|
|
|
|
/**
|
|
* Returns the y position of the mouse in the local coordinate system of the current target (ie. the dispatcher).
|
|
* @property localY
|
|
* @type {Number}
|
|
* @readonly
|
|
*/
|
|
p._get_localY = function () {
|
|
return this.currentTarget.globalToLocal(this.rawX, this.rawY).y;
|
|
};
|
|
|
|
/**
|
|
* Indicates whether the event was generated by a touch input (versus a mouse input).
|
|
* @property isTouch
|
|
* @type {Boolean}
|
|
* @readonly
|
|
*/
|
|
p._get_isTouch = function () {
|
|
return this.pointerID !== -1;
|
|
};
|
|
|
|
try {
|
|
Object.defineProperties(p, {
|
|
localX: { get: p._get_localX },
|
|
localY: { get: p._get_localY },
|
|
isTouch: { get: p._get_isTouch },
|
|
});
|
|
} catch (e) {} // TODO: use Log
|
|
|
|
// public methods:
|
|
/**
|
|
* Returns a clone of the MouseEvent instance.
|
|
* @method clone
|
|
* @return {MouseEvent} a clone of the MouseEvent instance.
|
|
**/
|
|
p.clone = function () {
|
|
return new MouseEvent(
|
|
this.type,
|
|
this.bubbles,
|
|
this.cancelable,
|
|
this.stageX,
|
|
this.stageY,
|
|
this.nativeEvent,
|
|
this.pointerID,
|
|
this.primary,
|
|
this.rawX,
|
|
this.rawY
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return (
|
|
"[MouseEvent (type=" +
|
|
this.type +
|
|
" stageX=" +
|
|
this.stageX +
|
|
" stageY=" +
|
|
this.stageY +
|
|
")]"
|
|
);
|
|
};
|
|
|
|
createjs.MouseEvent = createjs.promote(MouseEvent, "Event");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Matrix2D.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Represents an affine transformation matrix, and provides tools for constructing and concatenating matrices.
|
|
*
|
|
* This matrix can be visualized as:
|
|
*
|
|
* [ a c tx
|
|
* b d ty
|
|
* 0 0 1 ]
|
|
*
|
|
* Note the locations of b and c.
|
|
*
|
|
* @class Matrix2D
|
|
* @param {Number} [a=1] Specifies the a property for the new matrix.
|
|
* @param {Number} [b=0] Specifies the b property for the new matrix.
|
|
* @param {Number} [c=0] Specifies the c property for the new matrix.
|
|
* @param {Number} [d=1] Specifies the d property for the new matrix.
|
|
* @param {Number} [tx=0] Specifies the tx property for the new matrix.
|
|
* @param {Number} [ty=0] Specifies the ty property for the new matrix.
|
|
* @constructor
|
|
**/
|
|
function Matrix2D(a, b, c, d, tx, ty) {
|
|
this.setValues(a, b, c, d, tx, ty);
|
|
|
|
// public properties:
|
|
// assigned in the setValues method.
|
|
/**
|
|
* Position (0, 0) in a 3x3 affine transformation matrix.
|
|
* @property a
|
|
* @type Number
|
|
**/
|
|
|
|
/**
|
|
* Position (0, 1) in a 3x3 affine transformation matrix.
|
|
* @property b
|
|
* @type Number
|
|
**/
|
|
|
|
/**
|
|
* Position (1, 0) in a 3x3 affine transformation matrix.
|
|
* @property c
|
|
* @type Number
|
|
**/
|
|
|
|
/**
|
|
* Position (1, 1) in a 3x3 affine transformation matrix.
|
|
* @property d
|
|
* @type Number
|
|
**/
|
|
|
|
/**
|
|
* Position (2, 0) in a 3x3 affine transformation matrix.
|
|
* @property tx
|
|
* @type Number
|
|
**/
|
|
|
|
/**
|
|
* Position (2, 1) in a 3x3 affine transformation matrix.
|
|
* @property ty
|
|
* @type Number
|
|
**/
|
|
}
|
|
var p = Matrix2D.prototype;
|
|
|
|
/**
|
|
* <strong>REMOVED</strong>. Removed in favor of using `MySuperClass_constructor`.
|
|
* See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}}
|
|
* for details.
|
|
*
|
|
* There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance.
|
|
*
|
|
* @method initialize
|
|
* @protected
|
|
* @deprecated
|
|
*/
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is.
|
|
|
|
// constants:
|
|
/**
|
|
* Multiplier for converting degrees to radians. Used internally by Matrix2D.
|
|
* @property DEG_TO_RAD
|
|
* @static
|
|
* @final
|
|
* @type Number
|
|
* @readonly
|
|
**/
|
|
Matrix2D.DEG_TO_RAD = Math.PI / 180;
|
|
|
|
// static public properties:
|
|
/**
|
|
* An identity matrix, representing a null transformation.
|
|
* @property identity
|
|
* @static
|
|
* @type Matrix2D
|
|
* @readonly
|
|
**/
|
|
Matrix2D.identity = null; // set at bottom of class definition.
|
|
|
|
// public methods:
|
|
/**
|
|
* Sets the specified values on this instance.
|
|
* @method setValues
|
|
* @param {Number} [a=1] Specifies the a property for the new matrix.
|
|
* @param {Number} [b=0] Specifies the b property for the new matrix.
|
|
* @param {Number} [c=0] Specifies the c property for the new matrix.
|
|
* @param {Number} [d=1] Specifies the d property for the new matrix.
|
|
* @param {Number} [tx=0] Specifies the tx property for the new matrix.
|
|
* @param {Number} [ty=0] Specifies the ty property for the new matrix.
|
|
* @return {Matrix2D} This instance. Useful for chaining method calls.
|
|
*/
|
|
p.setValues = function (a, b, c, d, tx, ty) {
|
|
// don't forget to update docs in the constructor if these change:
|
|
this.a = a == null ? 1 : a;
|
|
this.b = b || 0;
|
|
this.c = c || 0;
|
|
this.d = d == null ? 1 : d;
|
|
this.tx = tx || 0;
|
|
this.ty = ty || 0;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Appends the specified matrix properties to this matrix. All parameters are required.
|
|
* This is the equivalent of multiplying `(this matrix) * (specified matrix)`.
|
|
* @method append
|
|
* @param {Number} a
|
|
* @param {Number} b
|
|
* @param {Number} c
|
|
* @param {Number} d
|
|
* @param {Number} tx
|
|
* @param {Number} ty
|
|
* @return {Matrix2D} This matrix. Useful for chaining method calls.
|
|
**/
|
|
p.append = function (a, b, c, d, tx, ty) {
|
|
var a1 = this.a;
|
|
var b1 = this.b;
|
|
var c1 = this.c;
|
|
var d1 = this.d;
|
|
if (a != 1 || b != 0 || c != 0 || d != 1) {
|
|
this.a = a1 * a + c1 * b;
|
|
this.b = b1 * a + d1 * b;
|
|
this.c = a1 * c + c1 * d;
|
|
this.d = b1 * c + d1 * d;
|
|
}
|
|
this.tx = a1 * tx + c1 * ty + this.tx;
|
|
this.ty = b1 * tx + d1 * ty + this.ty;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Prepends the specified matrix properties to this matrix.
|
|
* This is the equivalent of multiplying `(specified matrix) * (this matrix)`.
|
|
* All parameters are required.
|
|
* @method prepend
|
|
* @param {Number} a
|
|
* @param {Number} b
|
|
* @param {Number} c
|
|
* @param {Number} d
|
|
* @param {Number} tx
|
|
* @param {Number} ty
|
|
* @return {Matrix2D} This matrix. Useful for chaining method calls.
|
|
**/
|
|
p.prepend = function (a, b, c, d, tx, ty) {
|
|
var a1 = this.a;
|
|
var c1 = this.c;
|
|
var tx1 = this.tx;
|
|
|
|
this.a = a * a1 + c * this.b;
|
|
this.b = b * a1 + d * this.b;
|
|
this.c = a * c1 + c * this.d;
|
|
this.d = b * c1 + d * this.d;
|
|
this.tx = a * tx1 + c * this.ty + tx;
|
|
this.ty = b * tx1 + d * this.ty + ty;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Appends the specified matrix to this matrix.
|
|
* This is the equivalent of multiplying `(this matrix) * (specified matrix)`.
|
|
* @method appendMatrix
|
|
* @param {Matrix2D} matrix
|
|
* @return {Matrix2D} This matrix. Useful for chaining method calls.
|
|
**/
|
|
p.appendMatrix = function (matrix) {
|
|
return this.append(
|
|
matrix.a,
|
|
matrix.b,
|
|
matrix.c,
|
|
matrix.d,
|
|
matrix.tx,
|
|
matrix.ty
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Prepends the specified matrix to this matrix.
|
|
* This is the equivalent of multiplying `(specified matrix) * (this matrix)`.
|
|
* For example, you could calculate the combined transformation for a child object using:
|
|
*
|
|
* var o = myDisplayObject;
|
|
* var mtx = o.getMatrix();
|
|
* while (o = o.parent) {
|
|
* // prepend each parent's transformation in turn:
|
|
* o.prependMatrix(o.getMatrix());
|
|
* }
|
|
* @method prependMatrix
|
|
* @param {Matrix2D} matrix
|
|
* @return {Matrix2D} This matrix. Useful for chaining method calls.
|
|
**/
|
|
p.prependMatrix = function (matrix) {
|
|
return this.prepend(
|
|
matrix.a,
|
|
matrix.b,
|
|
matrix.c,
|
|
matrix.d,
|
|
matrix.tx,
|
|
matrix.ty
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Generates matrix properties from the specified display object transform properties, and appends them to this matrix.
|
|
* For example, you can use this to generate a matrix representing the transformations of a display object:
|
|
*
|
|
* var mtx = new createjs.Matrix2D();
|
|
* mtx.appendTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation);
|
|
* @method appendTransform
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} scaleX
|
|
* @param {Number} scaleY
|
|
* @param {Number} rotation
|
|
* @param {Number} skewX
|
|
* @param {Number} skewY
|
|
* @param {Number} regX Optional.
|
|
* @param {Number} regY Optional.
|
|
* @return {Matrix2D} This matrix. Useful for chaining method calls.
|
|
**/
|
|
p.appendTransform = function (
|
|
x,
|
|
y,
|
|
scaleX,
|
|
scaleY,
|
|
rotation,
|
|
skewX,
|
|
skewY,
|
|
regX,
|
|
regY
|
|
) {
|
|
if (rotation % 360) {
|
|
var r = rotation * Matrix2D.DEG_TO_RAD;
|
|
var cos = Math.cos(r);
|
|
var sin = Math.sin(r);
|
|
} else {
|
|
cos = 1;
|
|
sin = 0;
|
|
}
|
|
|
|
if (skewX || skewY) {
|
|
// TODO: can this be combined into a single append operation?
|
|
skewX *= Matrix2D.DEG_TO_RAD;
|
|
skewY *= Matrix2D.DEG_TO_RAD;
|
|
this.append(
|
|
Math.cos(skewY),
|
|
Math.sin(skewY),
|
|
-Math.sin(skewX),
|
|
Math.cos(skewX),
|
|
x,
|
|
y
|
|
);
|
|
this.append(
|
|
cos * scaleX,
|
|
sin * scaleX,
|
|
-sin * scaleY,
|
|
cos * scaleY,
|
|
0,
|
|
0
|
|
);
|
|
} else {
|
|
this.append(
|
|
cos * scaleX,
|
|
sin * scaleX,
|
|
-sin * scaleY,
|
|
cos * scaleY,
|
|
x,
|
|
y
|
|
);
|
|
}
|
|
|
|
if (regX || regY) {
|
|
// append the registration offset:
|
|
this.tx -= regX * this.a + regY * this.c;
|
|
this.ty -= regX * this.b + regY * this.d;
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Generates matrix properties from the specified display object transform properties, and prepends them to this matrix.
|
|
* For example, you could calculate the combined transformation for a child object using:
|
|
*
|
|
* var o = myDisplayObject;
|
|
* var mtx = new createjs.Matrix2D();
|
|
* do {
|
|
* // prepend each parent's transformation in turn:
|
|
* mtx.prependTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation, o.skewX, o.skewY, o.regX, o.regY);
|
|
* } while (o = o.parent);
|
|
*
|
|
* Note that the above example would not account for {{#crossLink "DisplayObject/transformMatrix:property"}}{{/crossLink}}
|
|
* values. See {{#crossLink "Matrix2D/prependMatrix"}}{{/crossLink}} for an example that does.
|
|
* @method prependTransform
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} scaleX
|
|
* @param {Number} scaleY
|
|
* @param {Number} rotation
|
|
* @param {Number} skewX
|
|
* @param {Number} skewY
|
|
* @param {Number} regX Optional.
|
|
* @param {Number} regY Optional.
|
|
* @return {Matrix2D} This matrix. Useful for chaining method calls.
|
|
**/
|
|
p.prependTransform = function (
|
|
x,
|
|
y,
|
|
scaleX,
|
|
scaleY,
|
|
rotation,
|
|
skewX,
|
|
skewY,
|
|
regX,
|
|
regY
|
|
) {
|
|
if (rotation % 360) {
|
|
var r = rotation * Matrix2D.DEG_TO_RAD;
|
|
var cos = Math.cos(r);
|
|
var sin = Math.sin(r);
|
|
} else {
|
|
cos = 1;
|
|
sin = 0;
|
|
}
|
|
|
|
if (regX || regY) {
|
|
// prepend the registration offset:
|
|
this.tx -= regX;
|
|
this.ty -= regY;
|
|
}
|
|
if (skewX || skewY) {
|
|
// TODO: can this be combined into a single prepend operation?
|
|
skewX *= Matrix2D.DEG_TO_RAD;
|
|
skewY *= Matrix2D.DEG_TO_RAD;
|
|
this.prepend(
|
|
cos * scaleX,
|
|
sin * scaleX,
|
|
-sin * scaleY,
|
|
cos * scaleY,
|
|
0,
|
|
0
|
|
);
|
|
this.prepend(
|
|
Math.cos(skewY),
|
|
Math.sin(skewY),
|
|
-Math.sin(skewX),
|
|
Math.cos(skewX),
|
|
x,
|
|
y
|
|
);
|
|
} else {
|
|
this.prepend(
|
|
cos * scaleX,
|
|
sin * scaleX,
|
|
-sin * scaleY,
|
|
cos * scaleY,
|
|
x,
|
|
y
|
|
);
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Applies a clockwise rotation transformation to the matrix.
|
|
* @method rotate
|
|
* @param {Number} angle The angle to rotate by, in degrees. To use a value in radians, multiply it by `180/Math.PI`.
|
|
* @return {Matrix2D} This matrix. Useful for chaining method calls.
|
|
**/
|
|
p.rotate = function (angle) {
|
|
angle = angle * Matrix2D.DEG_TO_RAD;
|
|
var cos = Math.cos(angle);
|
|
var sin = Math.sin(angle);
|
|
|
|
var a1 = this.a;
|
|
var b1 = this.b;
|
|
|
|
this.a = a1 * cos + this.c * sin;
|
|
this.b = b1 * cos + this.d * sin;
|
|
this.c = -a1 * sin + this.c * cos;
|
|
this.d = -b1 * sin + this.d * cos;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Applies a skew transformation to the matrix.
|
|
* @method skew
|
|
* @param {Number} skewX The amount to skew horizontally in degrees. To use a value in radians, multiply it by `180/Math.PI`.
|
|
* @param {Number} skewY The amount to skew vertically in degrees.
|
|
* @return {Matrix2D} This matrix. Useful for chaining method calls.
|
|
*/
|
|
p.skew = function (skewX, skewY) {
|
|
skewX = skewX * Matrix2D.DEG_TO_RAD;
|
|
skewY = skewY * Matrix2D.DEG_TO_RAD;
|
|
this.append(
|
|
Math.cos(skewY),
|
|
Math.sin(skewY),
|
|
-Math.sin(skewX),
|
|
Math.cos(skewX),
|
|
0,
|
|
0
|
|
);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Applies a scale transformation to the matrix.
|
|
* @method scale
|
|
* @param {Number} x The amount to scale horizontally. E.G. a value of 2 will double the size in the X direction, and 0.5 will halve it.
|
|
* @param {Number} y The amount to scale vertically.
|
|
* @return {Matrix2D} This matrix. Useful for chaining method calls.
|
|
**/
|
|
p.scale = function (x, y) {
|
|
this.a *= x;
|
|
this.b *= x;
|
|
this.c *= y;
|
|
this.d *= y;
|
|
//this.tx *= x;
|
|
//this.ty *= y;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Translates the matrix on the x and y axes.
|
|
* @method translate
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @return {Matrix2D} This matrix. Useful for chaining method calls.
|
|
**/
|
|
p.translate = function (x, y) {
|
|
this.tx += this.a * x + this.c * y;
|
|
this.ty += this.b * x + this.d * y;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Sets the properties of the matrix to those of an identity matrix (one that applies a null transformation).
|
|
* @method identity
|
|
* @return {Matrix2D} This matrix. Useful for chaining method calls.
|
|
**/
|
|
p.identity = function () {
|
|
this.a = this.d = 1;
|
|
this.b = this.c = this.tx = this.ty = 0;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Inverts the matrix, causing it to perform the opposite transformation.
|
|
* @method invert
|
|
* @return {Matrix2D} This matrix. Useful for chaining method calls.
|
|
**/
|
|
p.invert = function () {
|
|
var a1 = this.a;
|
|
var b1 = this.b;
|
|
var c1 = this.c;
|
|
var d1 = this.d;
|
|
var tx1 = this.tx;
|
|
var n = a1 * d1 - b1 * c1;
|
|
|
|
this.a = d1 / n;
|
|
this.b = -b1 / n;
|
|
this.c = -c1 / n;
|
|
this.d = a1 / n;
|
|
this.tx = (c1 * this.ty - d1 * tx1) / n;
|
|
this.ty = -(a1 * this.ty - b1 * tx1) / n;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Returns true if the matrix is an identity matrix.
|
|
* @method isIdentity
|
|
* @return {Boolean}
|
|
**/
|
|
p.isIdentity = function () {
|
|
return (
|
|
this.tx === 0 &&
|
|
this.ty === 0 &&
|
|
this.a === 1 &&
|
|
this.b === 0 &&
|
|
this.c === 0 &&
|
|
this.d === 1
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Returns true if this matrix is equal to the specified matrix (all property values are equal).
|
|
* @method equals
|
|
* @param {Matrix2D} matrix The matrix to compare.
|
|
* @return {Boolean}
|
|
**/
|
|
p.equals = function (matrix) {
|
|
return (
|
|
this.tx === matrix.tx &&
|
|
this.ty === matrix.ty &&
|
|
this.a === matrix.a &&
|
|
this.b === matrix.b &&
|
|
this.c === matrix.c &&
|
|
this.d === matrix.d
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Transforms a point according to this matrix.
|
|
* @method transformPoint
|
|
* @param {Number} x The x component of the point to transform.
|
|
* @param {Number} y The y component of the point to transform.
|
|
* @param {Point | Object} [pt] An object to copy the result into. If omitted a generic object with x/y properties will be returned.
|
|
* @return {Point} This matrix. Useful for chaining method calls.
|
|
**/
|
|
p.transformPoint = function (x, y, pt) {
|
|
pt = pt || {};
|
|
pt.x = x * this.a + y * this.c + this.tx;
|
|
pt.y = x * this.b + y * this.d + this.ty;
|
|
return pt;
|
|
};
|
|
|
|
/**
|
|
* Decomposes the matrix into transform properties (x, y, scaleX, scaleY, and rotation). Note that these values
|
|
* may not match the transform properties you used to generate the matrix, though they will produce the same visual
|
|
* results.
|
|
* @method decompose
|
|
* @param {Object} target The object to apply the transform properties to. If null, then a new object will be returned.
|
|
* @return {Object} The target, or a new generic object with the transform properties applied.
|
|
*/
|
|
p.decompose = function (target) {
|
|
// TODO: it would be nice to be able to solve for whether the matrix can be decomposed into only scale/rotation even when scale is negative
|
|
if (target == null) {
|
|
target = {};
|
|
}
|
|
target.x = this.tx;
|
|
target.y = this.ty;
|
|
target.scaleX = Math.sqrt(this.a * this.a + this.b * this.b);
|
|
target.scaleY = Math.sqrt(this.c * this.c + this.d * this.d);
|
|
|
|
var skewX = Math.atan2(-this.c, this.d);
|
|
var skewY = Math.atan2(this.b, this.a);
|
|
|
|
var delta = Math.abs(1 - skewX / skewY);
|
|
if (delta < 0.00001) {
|
|
// effectively identical, can use rotation:
|
|
target.rotation = skewY / Matrix2D.DEG_TO_RAD;
|
|
if (this.a < 0 && this.d >= 0) {
|
|
target.rotation += target.rotation <= 0 ? 180 : -180;
|
|
}
|
|
target.skewX = target.skewY = 0;
|
|
} else {
|
|
target.skewX = skewX / Matrix2D.DEG_TO_RAD;
|
|
target.skewY = skewY / Matrix2D.DEG_TO_RAD;
|
|
}
|
|
return target;
|
|
};
|
|
|
|
/**
|
|
* Copies all properties from the specified matrix to this matrix.
|
|
* @method copy
|
|
* @param {Matrix2D} matrix The matrix to copy properties from.
|
|
* @return {Matrix2D} This matrix. Useful for chaining method calls.
|
|
*/
|
|
p.copy = function (matrix) {
|
|
return this.setValues(
|
|
matrix.a,
|
|
matrix.b,
|
|
matrix.c,
|
|
matrix.d,
|
|
matrix.tx,
|
|
matrix.ty
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Returns a clone of the Matrix2D instance.
|
|
* @method clone
|
|
* @return {Matrix2D} a clone of the Matrix2D instance.
|
|
**/
|
|
p.clone = function () {
|
|
return new Matrix2D(this.a, this.b, this.c, this.d, this.tx, this.ty);
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return (
|
|
"[Matrix2D (a=" +
|
|
this.a +
|
|
" b=" +
|
|
this.b +
|
|
" c=" +
|
|
this.c +
|
|
" d=" +
|
|
this.d +
|
|
" tx=" +
|
|
this.tx +
|
|
" ty=" +
|
|
this.ty +
|
|
")]"
|
|
);
|
|
};
|
|
|
|
// this has to be populated after the class is defined:
|
|
Matrix2D.identity = new Matrix2D();
|
|
|
|
createjs.Matrix2D = Matrix2D;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// DisplayProps.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* Used for calculating and encapsulating display related properties.
|
|
* @class DisplayProps
|
|
* @param {Number} [visible=true] Visible value.
|
|
* @param {Number} [alpha=1] Alpha value.
|
|
* @param {Number} [shadow=null] A Shadow instance or null.
|
|
* @param {Number} [compositeOperation=null] A compositeOperation value or null.
|
|
* @param {Number} [matrix] A transformation matrix. Defaults to a new identity matrix.
|
|
* @constructor
|
|
**/
|
|
function DisplayProps(visible, alpha, shadow, compositeOperation, matrix) {
|
|
this.setValues(visible, alpha, shadow, compositeOperation, matrix);
|
|
|
|
// public properties:
|
|
// assigned in the setValues method.
|
|
/**
|
|
* Property representing the alpha that will be applied to a display object.
|
|
* @property alpha
|
|
* @type Number
|
|
**/
|
|
|
|
/**
|
|
* Property representing the shadow that will be applied to a display object.
|
|
* @property shadow
|
|
* @type Shadow
|
|
**/
|
|
|
|
/**
|
|
* Property representing the compositeOperation that will be applied to a display object.
|
|
* You can find a list of valid composite operations at:
|
|
* <a href="https://developer.mozilla.org/en/Canvas_tutorial/Compositing">https://developer.mozilla.org/en/Canvas_tutorial/Compositing</a>
|
|
* @property compositeOperation
|
|
* @type String
|
|
**/
|
|
|
|
/**
|
|
* Property representing the value for visible that will be applied to a display object.
|
|
* @property visible
|
|
* @type Boolean
|
|
**/
|
|
|
|
/**
|
|
* The transformation matrix that will be applied to a display object.
|
|
* @property matrix
|
|
* @type Matrix2D
|
|
**/
|
|
}
|
|
var p = DisplayProps.prototype;
|
|
|
|
// initialization:
|
|
/**
|
|
* Reinitializes the instance with the specified values.
|
|
* @method setValues
|
|
* @param {Number} [visible=true] Visible value.
|
|
* @param {Number} [alpha=1] Alpha value.
|
|
* @param {Number} [shadow=null] A Shadow instance or null.
|
|
* @param {Number} [compositeOperation=null] A compositeOperation value or null.
|
|
* @param {Number} [matrix] A transformation matrix. Defaults to an identity matrix.
|
|
* @return {DisplayProps} This instance. Useful for chaining method calls.
|
|
* @chainable
|
|
*/
|
|
p.setValues = function (visible, alpha, shadow, compositeOperation, matrix) {
|
|
this.visible = visible == null ? true : !!visible;
|
|
this.alpha = alpha == null ? 1 : alpha;
|
|
this.shadow = shadow;
|
|
this.compositeOperation = compositeOperation;
|
|
this.matrix =
|
|
matrix ||
|
|
(this.matrix && this.matrix.identity()) ||
|
|
new createjs.Matrix2D();
|
|
return this;
|
|
};
|
|
|
|
// public methods:
|
|
/**
|
|
* Appends the specified display properties. This is generally used to apply a child's properties its parent's.
|
|
* @method append
|
|
* @param {Boolean} visible desired visible value
|
|
* @param {Number} alpha desired alpha value
|
|
* @param {Shadow} shadow desired shadow value
|
|
* @param {String} compositeOperation desired composite operation value
|
|
* @param {Matrix2D} [matrix] a Matrix2D instance
|
|
* @return {DisplayProps} This instance. Useful for chaining method calls.
|
|
* @chainable
|
|
*/
|
|
p.append = function (visible, alpha, shadow, compositeOperation, matrix) {
|
|
this.alpha *= alpha;
|
|
this.shadow = shadow || this.shadow;
|
|
this.compositeOperation = compositeOperation || this.compositeOperation;
|
|
this.visible = this.visible && visible;
|
|
matrix && this.matrix.appendMatrix(matrix);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Prepends the specified display properties. This is generally used to apply a parent's properties to a child's.
|
|
* For example, to get the combined display properties that would be applied to a child, you could use:
|
|
*
|
|
* var o = myDisplayObject;
|
|
* var props = new createjs.DisplayProps();
|
|
* do {
|
|
* // prepend each parent's props in turn:
|
|
* props.prepend(o.visible, o.alpha, o.shadow, o.compositeOperation, o.getMatrix());
|
|
* } while (o = o.parent);
|
|
*
|
|
* @method prepend
|
|
* @param {Boolean} visible desired visible value
|
|
* @param {Number} alpha desired alpha value
|
|
* @param {Shadow} shadow desired shadow value
|
|
* @param {String} compositeOperation desired composite operation value
|
|
* @param {Matrix2D} [matrix] a Matrix2D instance
|
|
* @return {DisplayProps} This instance. Useful for chaining method calls.
|
|
* @chainable
|
|
*/
|
|
p.prepend = function (visible, alpha, shadow, compositeOperation, matrix) {
|
|
this.alpha *= alpha;
|
|
this.shadow = this.shadow || shadow;
|
|
this.compositeOperation = this.compositeOperation || compositeOperation;
|
|
this.visible = this.visible && visible;
|
|
matrix && this.matrix.prependMatrix(matrix);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Resets this instance and its matrix to default values.
|
|
* @method identity
|
|
* @return {DisplayProps} This instance. Useful for chaining method calls.
|
|
* @chainable
|
|
*/
|
|
p.identity = function () {
|
|
this.visible = true;
|
|
this.alpha = 1;
|
|
this.shadow = this.compositeOperation = null;
|
|
this.matrix.identity();
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Returns a clone of the DisplayProps instance. Clones the associated matrix.
|
|
* @method clone
|
|
* @return {DisplayProps} a clone of the DisplayProps instance.
|
|
**/
|
|
p.clone = function () {
|
|
return new DisplayProps(
|
|
this.alpha,
|
|
this.shadow,
|
|
this.compositeOperation,
|
|
this.visible,
|
|
this.matrix.clone()
|
|
);
|
|
};
|
|
|
|
// private methods:
|
|
|
|
createjs.DisplayProps = DisplayProps;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Point.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Represents a point on a 2 dimensional x / y coordinate system.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var point = new createjs.Point(0, 100);
|
|
*
|
|
* @class Point
|
|
* @param {Number} [x=0] X position.
|
|
* @param {Number} [y=0] Y position.
|
|
* @constructor
|
|
**/
|
|
function Point(x, y) {
|
|
this.setValues(x, y);
|
|
|
|
// public properties:
|
|
// assigned in the setValues method.
|
|
/**
|
|
* X position.
|
|
* @property x
|
|
* @type Number
|
|
**/
|
|
|
|
/**
|
|
* Y position.
|
|
* @property y
|
|
* @type Number
|
|
**/
|
|
}
|
|
var p = Point.prototype;
|
|
|
|
/**
|
|
* <strong>REMOVED</strong>. Removed in favor of using `MySuperClass_constructor`.
|
|
* See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}}
|
|
* for details.
|
|
*
|
|
* There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance.
|
|
*
|
|
* @method initialize
|
|
* @protected
|
|
* @deprecated
|
|
*/
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is.
|
|
|
|
// public methods:
|
|
/**
|
|
* Sets the specified values on this instance.
|
|
* @method setValues
|
|
* @param {Number} [x=0] X position.
|
|
* @param {Number} [y=0] Y position.
|
|
* @return {Point} This instance. Useful for chaining method calls.
|
|
* @chainable
|
|
*/
|
|
p.setValues = function (x, y) {
|
|
this.x = x || 0;
|
|
this.y = y || 0;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Copies all properties from the specified point to this point.
|
|
* @method copy
|
|
* @param {Point} point The point to copy properties from.
|
|
* @return {Point} This point. Useful for chaining method calls.
|
|
* @chainable
|
|
*/
|
|
p.copy = function (point) {
|
|
this.x = point.x;
|
|
this.y = point.y;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Returns a clone of the Point instance.
|
|
* @method clone
|
|
* @return {Point} a clone of the Point instance.
|
|
**/
|
|
p.clone = function () {
|
|
return new Point(this.x, this.y);
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[Point (x=" + this.x + " y=" + this.y + ")]";
|
|
};
|
|
|
|
createjs.Point = Point;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Rectangle.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Represents a rectangle as defined by the points (x, y) and (x+width, y+height).
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var rect = new createjs.Rectangle(0, 0, 100, 100);
|
|
*
|
|
* @class Rectangle
|
|
* @param {Number} [x=0] X position.
|
|
* @param {Number} [y=0] Y position.
|
|
* @param {Number} [width=0] The width of the Rectangle.
|
|
* @param {Number} [height=0] The height of the Rectangle.
|
|
* @constructor
|
|
**/
|
|
function Rectangle(x, y, width, height) {
|
|
this.setValues(x, y, width, height);
|
|
|
|
// public properties:
|
|
// assigned in the setValues method.
|
|
/**
|
|
* X position.
|
|
* @property x
|
|
* @type Number
|
|
**/
|
|
|
|
/**
|
|
* Y position.
|
|
* @property y
|
|
* @type Number
|
|
**/
|
|
|
|
/**
|
|
* Width.
|
|
* @property width
|
|
* @type Number
|
|
**/
|
|
|
|
/**
|
|
* Height.
|
|
* @property height
|
|
* @type Number
|
|
**/
|
|
}
|
|
var p = Rectangle.prototype;
|
|
|
|
/**
|
|
* <strong>REMOVED</strong>. Removed in favor of using `MySuperClass_constructor`.
|
|
* See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}}
|
|
* for details.
|
|
*
|
|
* There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance.
|
|
*
|
|
* @method initialize
|
|
* @protected
|
|
* @deprecated
|
|
*/
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is.
|
|
|
|
// public methods:
|
|
/**
|
|
* Sets the specified values on this instance.
|
|
* @method setValues
|
|
* @param {Number} [x=0] X position.
|
|
* @param {Number} [y=0] Y position.
|
|
* @param {Number} [width=0] The width of the Rectangle.
|
|
* @param {Number} [height=0] The height of the Rectangle.
|
|
* @return {Rectangle} This instance. Useful for chaining method calls.
|
|
* @chainable
|
|
*/
|
|
p.setValues = function (x, y, width, height) {
|
|
// don't forget to update docs in the constructor if these change:
|
|
this.x = x || 0;
|
|
this.y = y || 0;
|
|
this.width = width || 0;
|
|
this.height = height || 0;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Extends the rectangle's bounds to include the described point or rectangle.
|
|
* @method extend
|
|
* @param {Number} x X position of the point or rectangle.
|
|
* @param {Number} y Y position of the point or rectangle.
|
|
* @param {Number} [width=0] The width of the rectangle.
|
|
* @param {Number} [height=0] The height of the rectangle.
|
|
* @return {Rectangle} This instance. Useful for chaining method calls.
|
|
* @chainable
|
|
*/
|
|
p.extend = function (x, y, width, height) {
|
|
width = width || 0;
|
|
height = height || 0;
|
|
if (x + width > this.x + this.width) {
|
|
this.width = x + width - this.x;
|
|
}
|
|
if (y + height > this.y + this.height) {
|
|
this.height = y + height - this.y;
|
|
}
|
|
if (x < this.x) {
|
|
this.width += this.x - x;
|
|
this.x = x;
|
|
}
|
|
if (y < this.y) {
|
|
this.height += this.y - y;
|
|
this.y = y;
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Adds the specified padding to the rectangle's bounds.
|
|
* @method pad
|
|
* @param {Number} top
|
|
* @param {Number} left
|
|
* @param {Number} right
|
|
* @param {Number} bottom
|
|
* @return {Rectangle} This instance. Useful for chaining method calls.
|
|
* @chainable
|
|
*/
|
|
p.pad = function (top, left, bottom, right) {
|
|
this.x -= left;
|
|
this.y -= top;
|
|
this.width += left + right;
|
|
this.height += top + bottom;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Copies all properties from the specified rectangle to this rectangle.
|
|
* @method copy
|
|
* @param {Rectangle} rectangle The rectangle to copy properties from.
|
|
* @return {Rectangle} This rectangle. Useful for chaining method calls.
|
|
* @chainable
|
|
*/
|
|
p.copy = function (rectangle) {
|
|
return this.setValues(
|
|
rectangle.x,
|
|
rectangle.y,
|
|
rectangle.width,
|
|
rectangle.height
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Returns true if this rectangle fully encloses the described point or rectangle.
|
|
* @method contains
|
|
* @param {Number} x X position of the point or rectangle.
|
|
* @param {Number} y Y position of the point or rectangle.
|
|
* @param {Number} [width=0] The width of the rectangle.
|
|
* @param {Number} [height=0] The height of the rectangle.
|
|
* @return {Boolean} True if the described point or rectangle is contained within this rectangle.
|
|
*/
|
|
p.contains = function (x, y, width, height) {
|
|
width = width || 0;
|
|
height = height || 0;
|
|
return (
|
|
x >= this.x &&
|
|
x + width <= this.x + this.width &&
|
|
y >= this.y &&
|
|
y + height <= this.y + this.height
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Returns a new rectangle which contains this rectangle and the specified rectangle.
|
|
* @method union
|
|
* @param {Rectangle} rect The rectangle to calculate a union with.
|
|
* @return {Rectangle} A new rectangle describing the union.
|
|
*/
|
|
p.union = function (rect) {
|
|
return this.clone().extend(rect.x, rect.y, rect.width, rect.height);
|
|
};
|
|
|
|
/**
|
|
* Returns a new rectangle which describes the intersection (overlap) of this rectangle and the specified rectangle,
|
|
* or null if they do not intersect.
|
|
* @method intersection
|
|
* @param {Rectangle} rect The rectangle to calculate an intersection with.
|
|
* @return {Rectangle} A new rectangle describing the intersection or null.
|
|
*/
|
|
p.intersection = function (rect) {
|
|
var x1 = rect.x,
|
|
y1 = rect.y,
|
|
x2 = x1 + rect.width,
|
|
y2 = y1 + rect.height;
|
|
if (this.x > x1) {
|
|
x1 = this.x;
|
|
}
|
|
if (this.y > y1) {
|
|
y1 = this.y;
|
|
}
|
|
if (this.x + this.width < x2) {
|
|
x2 = this.x + this.width;
|
|
}
|
|
if (this.y + this.height < y2) {
|
|
y2 = this.y + this.height;
|
|
}
|
|
return x2 <= x1 || y2 <= y1
|
|
? null
|
|
: new Rectangle(x1, y1, x2 - x1, y2 - y1);
|
|
};
|
|
|
|
/**
|
|
* Returns true if the specified rectangle intersects (has any overlap) with this rectangle.
|
|
* @method intersects
|
|
* @param {Rectangle} rect The rectangle to compare.
|
|
* @return {Boolean} True if the rectangles intersect.
|
|
*/
|
|
p.intersects = function (rect) {
|
|
return (
|
|
rect.x <= this.x + this.width &&
|
|
this.x <= rect.x + rect.width &&
|
|
rect.y <= this.y + this.height &&
|
|
this.y <= rect.y + rect.height
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Returns true if the width or height are equal or less than 0.
|
|
* @method isEmpty
|
|
* @return {Boolean} True if the rectangle is empty.
|
|
*/
|
|
p.isEmpty = function () {
|
|
return this.width <= 0 || this.height <= 0;
|
|
};
|
|
|
|
/**
|
|
* Returns a clone of the Rectangle instance.
|
|
* @method clone
|
|
* @return {Rectangle} a clone of the Rectangle instance.
|
|
**/
|
|
p.clone = function () {
|
|
return new Rectangle(this.x, this.y, this.width, this.height);
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return (
|
|
"[Rectangle (x=" +
|
|
this.x +
|
|
" y=" +
|
|
this.y +
|
|
" width=" +
|
|
this.width +
|
|
" height=" +
|
|
this.height +
|
|
")]"
|
|
);
|
|
};
|
|
|
|
createjs.Rectangle = Rectangle;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// ButtonHelper.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* The ButtonHelper is a helper class to create interactive buttons from {{#crossLink "MovieClip"}}{{/crossLink}} or
|
|
* {{#crossLink "Sprite"}}{{/crossLink}} instances. This class will intercept mouse events from an object, and
|
|
* automatically call {{#crossLink "Sprite/gotoAndStop"}}{{/crossLink}} or {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}},
|
|
* to the respective animation labels, add a pointer cursor, and allows the user to define a hit state frame.
|
|
*
|
|
* The ButtonHelper instance does not need to be added to the stage, but a reference should be maintained to prevent
|
|
* garbage collection.
|
|
*
|
|
* Note that over states will not work unless you call {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var helper = new createjs.ButtonHelper(myInstance, "out", "over", "down", false, myInstance, "hit");
|
|
* myInstance.addEventListener("click", handleClick);
|
|
* function handleClick(event) {
|
|
* // Click Happened.
|
|
* }
|
|
*
|
|
* @class ButtonHelper
|
|
* @param {Sprite|MovieClip} target The instance to manage.
|
|
* @param {String} [outLabel="out"] The label or animation to go to when the user rolls out of the button.
|
|
* @param {String} [overLabel="over"] The label or animation to go to when the user rolls over the button.
|
|
* @param {String} [downLabel="down"] The label or animation to go to when the user presses the button.
|
|
* @param {Boolean} [play=false] If the helper should call "gotoAndPlay" or "gotoAndStop" on the button when changing
|
|
* states.
|
|
* @param {DisplayObject} [hitArea] An optional item to use as the hit state for the button. If this is not defined,
|
|
* then the button's visible states will be used instead. Note that the same instance as the "target" argument can be
|
|
* used for the hitState.
|
|
* @param {String} [hitLabel] The label or animation on the hitArea instance that defines the hitArea bounds. If this is
|
|
* null, then the default state of the hitArea will be used. *
|
|
* @constructor
|
|
*/
|
|
function ButtonHelper(
|
|
target,
|
|
outLabel,
|
|
overLabel,
|
|
downLabel,
|
|
play,
|
|
hitArea,
|
|
hitLabel
|
|
) {
|
|
if (!target.addEventListener) {
|
|
return;
|
|
}
|
|
|
|
// public properties:
|
|
/**
|
|
* The target for this button helper.
|
|
* @property target
|
|
* @type MovieClip | Sprite
|
|
* @readonly
|
|
**/
|
|
this.target = target;
|
|
|
|
/**
|
|
* The label name or frame number to display when the user mouses out of the target. Defaults to "over".
|
|
* @property overLabel
|
|
* @type String | Number
|
|
**/
|
|
this.overLabel = overLabel == null ? "over" : overLabel;
|
|
|
|
/**
|
|
* The label name or frame number to display when the user mouses over the target. Defaults to "out".
|
|
* @property outLabel
|
|
* @type String | Number
|
|
**/
|
|
this.outLabel = outLabel == null ? "out" : outLabel;
|
|
|
|
/**
|
|
* The label name or frame number to display when the user presses on the target. Defaults to "down".
|
|
* @property downLabel
|
|
* @type String | Number
|
|
**/
|
|
this.downLabel = downLabel == null ? "down" : downLabel;
|
|
|
|
/**
|
|
* If true, then ButtonHelper will call gotoAndPlay, if false, it will use gotoAndStop. Default is false.
|
|
* @property play
|
|
* @default false
|
|
* @type Boolean
|
|
**/
|
|
this.play = play;
|
|
|
|
// private properties
|
|
/**
|
|
* @property _isPressed
|
|
* @type Boolean
|
|
* @protected
|
|
**/
|
|
this._isPressed = false;
|
|
|
|
/**
|
|
* @property _isOver
|
|
* @type Boolean
|
|
* @protected
|
|
**/
|
|
this._isOver = false;
|
|
|
|
/**
|
|
* @property _enabled
|
|
* @type Boolean
|
|
* @protected
|
|
**/
|
|
this._enabled = false;
|
|
|
|
// setup:
|
|
target.mouseChildren = false; // prevents issues when children are removed from the display list when state changes.
|
|
this.enabled = true;
|
|
this.handleEvent({});
|
|
if (hitArea) {
|
|
if (hitLabel) {
|
|
hitArea.actionsEnabled = false;
|
|
hitArea.gotoAndStop && hitArea.gotoAndStop(hitLabel);
|
|
}
|
|
target.hitArea = hitArea;
|
|
}
|
|
}
|
|
var p = ButtonHelper.prototype;
|
|
|
|
/**
|
|
* <strong>REMOVED</strong>. Removed in favor of using `MySuperClass_constructor`.
|
|
* See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}}
|
|
* for details.
|
|
*
|
|
* There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance.
|
|
*
|
|
* @method initialize
|
|
* @protected
|
|
* @deprecated
|
|
*/
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is.
|
|
|
|
// getter / setters:
|
|
/**
|
|
* Use the {{#crossLink "ButtonHelper/enabled:property"}}{{/crossLink}} property instead.
|
|
* @method setEnabled
|
|
* @param {Boolean} value
|
|
* @deprecated
|
|
**/
|
|
p.setEnabled = function (value) {
|
|
// TODO: deprecated.
|
|
if (value == this._enabled) {
|
|
return;
|
|
}
|
|
var o = this.target;
|
|
this._enabled = value;
|
|
if (value) {
|
|
o.cursor = "pointer";
|
|
o.addEventListener("rollover", this);
|
|
o.addEventListener("rollout", this);
|
|
o.addEventListener("mousedown", this);
|
|
o.addEventListener("pressup", this);
|
|
if (o._reset) {
|
|
o.__reset = o._reset;
|
|
o._reset = this._reset;
|
|
}
|
|
} else {
|
|
o.cursor = null;
|
|
o.removeEventListener("rollover", this);
|
|
o.removeEventListener("rollout", this);
|
|
o.removeEventListener("mousedown", this);
|
|
o.removeEventListener("pressup", this);
|
|
if (o.__reset) {
|
|
o._reset = o.__reset;
|
|
delete o.__reset;
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* Use the {{#crossLink "ButtonHelper/enabled:property"}}{{/crossLink}} property instead.
|
|
* @method getEnabled
|
|
* @return {Boolean}
|
|
* @deprecated
|
|
**/
|
|
p.getEnabled = function () {
|
|
return this._enabled;
|
|
};
|
|
|
|
/**
|
|
* Enables or disables the button functionality on the target.
|
|
* @property enabled
|
|
* @type {Boolean}
|
|
**/
|
|
try {
|
|
Object.defineProperties(p, {
|
|
enabled: { get: p.getEnabled, set: p.setEnabled },
|
|
});
|
|
} catch (e) {} // TODO: use Log
|
|
|
|
// public methods:
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[ButtonHelper]";
|
|
};
|
|
|
|
// private methods:
|
|
/**
|
|
* @method handleEvent
|
|
* @param {Object} evt The mouse event to handle.
|
|
* @protected
|
|
**/
|
|
p.handleEvent = function (evt) {
|
|
var label,
|
|
t = this.target,
|
|
type = evt.type;
|
|
if (type == "mousedown") {
|
|
this._isPressed = true;
|
|
label = this.downLabel;
|
|
} else if (type == "pressup") {
|
|
this._isPressed = false;
|
|
label = this._isOver ? this.overLabel : this.outLabel;
|
|
} else if (type == "rollover") {
|
|
this._isOver = true;
|
|
label = this._isPressed ? this.downLabel : this.overLabel;
|
|
} else {
|
|
// rollout and default
|
|
this._isOver = false;
|
|
label = this._isPressed ? this.overLabel : this.outLabel;
|
|
}
|
|
if (this.play) {
|
|
t.gotoAndPlay && t.gotoAndPlay(label);
|
|
} else {
|
|
t.gotoAndStop && t.gotoAndStop(label);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Injected into target. Preserves the paused state through a reset.
|
|
* @method _reset
|
|
* @protected
|
|
**/
|
|
p._reset = function () {
|
|
// TODO: explore better ways to handle this issue. This is hacky & disrupts object signatures.
|
|
var p = this.paused;
|
|
this.__reset();
|
|
this.paused = p;
|
|
};
|
|
|
|
createjs.ButtonHelper = ButtonHelper;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Shadow.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* This class encapsulates the properties required to define a shadow to apply to a {{#crossLink "DisplayObject"}}{{/crossLink}}
|
|
* via its <code>shadow</code> property.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* myImage.shadow = new createjs.Shadow("#000000", 5, 5, 10);
|
|
*
|
|
* @class Shadow
|
|
* @constructor
|
|
* @param {String} color The color of the shadow. This can be any valid CSS color value.
|
|
* @param {Number} offsetX The x offset of the shadow in pixels.
|
|
* @param {Number} offsetY The y offset of the shadow in pixels.
|
|
* @param {Number} blur The size of the blurring effect.
|
|
**/
|
|
function Shadow(color, offsetX, offsetY, blur) {
|
|
// public properties:
|
|
/**
|
|
* The color of the shadow. This can be any valid CSS color value.
|
|
* @property color
|
|
* @type String
|
|
* @default null
|
|
*/
|
|
this.color = color || "black";
|
|
|
|
/** The x offset of the shadow.
|
|
* @property offsetX
|
|
* @type Number
|
|
* @default 0
|
|
*/
|
|
this.offsetX = offsetX || 0;
|
|
|
|
/** The y offset of the shadow.
|
|
* @property offsetY
|
|
* @type Number
|
|
* @default 0
|
|
*/
|
|
this.offsetY = offsetY || 0;
|
|
|
|
/** The blur of the shadow.
|
|
* @property blur
|
|
* @type Number
|
|
* @default 0
|
|
*/
|
|
this.blur = blur || 0;
|
|
}
|
|
var p = Shadow.prototype;
|
|
|
|
/**
|
|
* <strong>REMOVED</strong>. Removed in favor of using `MySuperClass_constructor`.
|
|
* See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}}
|
|
* for details.
|
|
*
|
|
* There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance.
|
|
*
|
|
* @method initialize
|
|
* @protected
|
|
* @deprecated
|
|
*/
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is.
|
|
|
|
// static public properties:
|
|
/**
|
|
* An identity shadow object (all properties are set to 0).
|
|
* @property identity
|
|
* @type Shadow
|
|
* @static
|
|
* @final
|
|
* @readonly
|
|
**/
|
|
Shadow.identity = new Shadow("transparent", 0, 0, 0);
|
|
|
|
// public methods:
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[Shadow]";
|
|
};
|
|
|
|
/**
|
|
* Returns a clone of this Shadow instance.
|
|
* @method clone
|
|
* @return {Shadow} A clone of the current Shadow instance.
|
|
**/
|
|
p.clone = function () {
|
|
return new Shadow(this.color, this.offsetX, this.offsetY, this.blur);
|
|
};
|
|
|
|
createjs.Shadow = Shadow;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// SpriteSheet.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Encapsulates the properties and methods associated with a sprite sheet. A sprite sheet is a series of images (usually
|
|
* animation frames) combined into a larger image (or images). For example, an animation consisting of eight 100x100
|
|
* images could be combined into a single 400x200 sprite sheet (4 frames across by 2 high).
|
|
*
|
|
* The data passed to the SpriteSheet constructor defines:
|
|
* <ol>
|
|
* <li> The source image or images to use.</li>
|
|
* <li> The positions of individual image frames.</li>
|
|
* <li> Sequences of frames that form named animations. Optional.</li>
|
|
* <li> The target playback framerate. Optional.</li>
|
|
* </ol>
|
|
* <h3>SpriteSheet Format</h3>
|
|
* SpriteSheets are an object with two required properties (`images` and `frames`), and two optional properties
|
|
* (`framerate` and `animations`). This makes them easy to define in javascript code, or in JSON.
|
|
*
|
|
* <h4>images</h4>
|
|
* An array of source images. Images can be either an HTMlimage
|
|
* instance, or a uri to an image. The former is recommended to control preloading.
|
|
*
|
|
* images: [image1, "path/to/image2.png"],
|
|
*
|
|
* <h4>frames</h4>
|
|
* Defines the individual frames. There are two supported formats for frame data:
|
|
* When all of the frames are the same size (in a grid), use an object with `width`, `height`, `regX`, `regY`,
|
|
* and `count` properties.
|
|
*
|
|
* <ul>
|
|
* <li>`width` & `height` are required and specify the dimensions of the frames</li>
|
|
* <li>`regX` & `regY` indicate the registration point or "origin" of the frames</li>
|
|
* <li>`spacing` indicate the spacing between frames</li>
|
|
* <li>`margin` specify the margin around the image(s)</li>
|
|
* <li>`count` allows you to specify the total number of frames in the spritesheet; if omitted, this will
|
|
* be calculated based on the dimensions of the source images and the frames. Frames will be assigned
|
|
* indexes based on their position in the source images (left to right, top to bottom).</li>
|
|
* </ul>
|
|
*
|
|
* frames: {width:64, height:64, count:20, regX: 32, regY:64, spacing:0, margin:0}
|
|
*
|
|
* If the frames are of different sizes, use an array of frame definitions. Each definition is itself an array
|
|
* with 4 required and 3 optional entries, in the order:
|
|
*
|
|
* <ul>
|
|
* <li>The first four, `x`, `y`, `width`, and `height` are required and define the frame rectangle.</li>
|
|
* <li>The fifth, `imageIndex`, specifies the index of the source image (defaults to 0)</li>
|
|
* <li>The last two, `regX` and `regY` specify the registration point of the frame</li>
|
|
* </ul>
|
|
*
|
|
* frames: [
|
|
* // x, y, width, height, imageIndex*, regX*, regY*
|
|
* [64, 0, 96, 64],
|
|
* [0, 0, 64, 64, 1, 32, 32]
|
|
* // etc.
|
|
* ]
|
|
*
|
|
* <h4>animations</h4>
|
|
* Optional. An object defining sequences of frames to play as named animations. Each property corresponds to an
|
|
* animation of the same name. Each animation must specify the frames to play, and may
|
|
* also include a relative playback `speed` (ex. 2 would playback at double speed, 0.5 at half), and
|
|
* the name of the `next` animation to sequence to after it completes.
|
|
*
|
|
* There are three formats supported for defining the frames in an animation, which can be mixed and matched as appropriate:
|
|
* <ol>
|
|
* <li>for a single frame animation, you can simply specify the frame index
|
|
*
|
|
* animations: {
|
|
* sit: 7
|
|
* }
|
|
*
|
|
* </li>
|
|
* <li>
|
|
* for an animation of consecutive frames, you can use an array with two required, and two optional entries
|
|
* in the order: `start`, `end`, `next`, and `speed`. This will play the frames from start to end inclusive.
|
|
*
|
|
* animations: {
|
|
* // start, end, next*, speed*
|
|
* run: [0, 8],
|
|
* jump: [9, 12, "run", 2]
|
|
* }
|
|
*
|
|
* </li>
|
|
* <li>
|
|
* for non-consecutive frames, you can use an object with a `frames` property defining an array of frame
|
|
* indexes to play in order. The object can also specify `next` and `speed` properties.
|
|
*
|
|
* animations: {
|
|
* walk: {
|
|
* frames: [1,2,3,3,2,1]
|
|
* },
|
|
* shoot: {
|
|
* frames: [1,4,5,6],
|
|
* next: "walk",
|
|
* speed: 0.5
|
|
* }
|
|
* }
|
|
*
|
|
* </li>
|
|
* </ol>
|
|
* <strong>Note:</strong> the `speed` property was added in EaselJS 0.7.0. Earlier versions had a `frequency`
|
|
* property instead, which was the inverse of `speed`. For example, a value of "4" would be 1/4 normal speed in
|
|
* earlier versions, but is 4x normal speed in EaselJS 0.7.0+.
|
|
*
|
|
* <h4>framerate</h4>
|
|
* Optional. Indicates the default framerate to play this spritesheet at in frames per second. See
|
|
* {{#crossLink "SpriteSheet/framerate:property"}}{{/crossLink}} for more information.
|
|
*
|
|
* framerate: 20
|
|
*
|
|
* Note that the Sprite framerate will only work if the stage update method is provided with the {{#crossLink "Ticker/tick:event"}}{{/crossLink}}
|
|
* event generated by the {{#crossLink "Ticker"}}{{/crossLink}}.
|
|
*
|
|
* createjs.Ticker.on("tick", handleTick);
|
|
* function handleTick(event) {
|
|
* stage.update(event);
|
|
* }
|
|
*
|
|
* <h3>Example</h3>
|
|
* To define a simple sprite sheet, with a single image "sprites.jpg" arranged in a regular 50x50 grid with three
|
|
* animations: "stand" showing the first frame, "run" looping frame 1-5 inclusive, and "jump" playing frame 6-8 and
|
|
* sequencing back to run.
|
|
*
|
|
* var data = {
|
|
* images: ["sprites.jpg"],
|
|
* frames: {width:50, height:50},
|
|
* animations: {
|
|
* stand:0,
|
|
* run:[1,5],
|
|
* jump:[6,8,"run"]
|
|
* }
|
|
* };
|
|
* var spriteSheet = new createjs.SpriteSheet(data);
|
|
* var animation = new createjs.Sprite(spriteSheet, "run");
|
|
*
|
|
* <h3>Generating SpriteSheet Images</h3>
|
|
* Spritesheets can be created manually by combining images in PhotoShop, and specifying the frame size or
|
|
* coordinates manually, however there are a number of tools that facilitate this.
|
|
* <ul>
|
|
* <li>Exporting SpriteSheets or HTML5 content from Flash Pro supports the EaselJS SpriteSheet format.</li>
|
|
* <li>The popular <a href="https://www.codeandweb.com/texturepacker/easeljs" target="_blank">Texture Packer</a> has
|
|
* EaselJS support.
|
|
* <li>SWF animations in Flash can be exported to SpriteSheets using <a href="http://createjs.com/zoe" target="_blank"></a></li>
|
|
* </ul>
|
|
*
|
|
* <h3>Cross Origin Issues</h3>
|
|
* <strong>Warning:</strong> Images loaded cross-origin will throw cross-origin security errors when interacted with
|
|
* using:
|
|
* <ul>
|
|
* <li>a mouse</li>
|
|
* <li>methods such as {{#crossLink "Container/getObjectUnderPoint"}}{{/crossLink}}</li>
|
|
* <li>Filters (see {{#crossLink "Filter"}}{{/crossLink}})</li>
|
|
* <li>caching (see {{#crossLink "DisplayObject/cache"}}{{/crossLink}})</li>
|
|
* </ul>
|
|
* You can get around this by setting `crossOrigin` property on your images before passing them to EaselJS, or
|
|
* setting the `crossOrigin` property on PreloadJS' LoadQueue or LoadItems.
|
|
*
|
|
* var image = new Image();
|
|
* img.crossOrigin="Anonymous";
|
|
* img.src = "http://server-with-CORS-support.com/path/to/image.jpg";
|
|
*
|
|
* If you pass string paths to SpriteSheets, they will not work cross-origin. The server that stores the image must
|
|
* support cross-origin requests, or this will not work. For more information, check out
|
|
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS" target="_blank">CORS overview on MDN</a>.
|
|
*
|
|
* @class SpriteSheet
|
|
* @constructor
|
|
* @param {Object} data An object describing the SpriteSheet data.
|
|
* @extends EventDispatcher
|
|
**/
|
|
function SpriteSheet(data) {
|
|
this.EventDispatcher_constructor();
|
|
|
|
// public properties:
|
|
/**
|
|
* Indicates whether all images are finished loading.
|
|
* @property complete
|
|
* @type Boolean
|
|
* @readonly
|
|
**/
|
|
this.complete = true;
|
|
|
|
/**
|
|
* Specifies the framerate to use by default for Sprite instances using the SpriteSheet. See the Sprite class
|
|
* {{#crossLink "Sprite/framerate:property"}}{{/crossLink}} for more information.
|
|
* @property framerate
|
|
* @type Number
|
|
**/
|
|
this.framerate = 0;
|
|
|
|
// private properties:
|
|
/**
|
|
* @property _animations
|
|
* @protected
|
|
* @type Array
|
|
**/
|
|
this._animations = null;
|
|
|
|
/**
|
|
* @property _frames
|
|
* @protected
|
|
* @type Array
|
|
**/
|
|
this._frames = null;
|
|
|
|
/**
|
|
* @property _images
|
|
* @protected
|
|
* @type Array
|
|
**/
|
|
this._images = null;
|
|
|
|
/**
|
|
* @property _data
|
|
* @protected
|
|
* @type Object
|
|
**/
|
|
this._data = null;
|
|
|
|
/**
|
|
* @property _loadCount
|
|
* @protected
|
|
* @type Number
|
|
**/
|
|
this._loadCount = 0;
|
|
|
|
// only used for simple frame defs:
|
|
/**
|
|
* @property _frameHeight
|
|
* @protected
|
|
* @type Number
|
|
**/
|
|
this._frameHeight = 0;
|
|
|
|
/**
|
|
* @property _frameWidth
|
|
* @protected
|
|
* @type Number
|
|
**/
|
|
this._frameWidth = 0;
|
|
|
|
/**
|
|
* @property _numFrames
|
|
* @protected
|
|
* @type Number
|
|
**/
|
|
this._numFrames = 0;
|
|
|
|
/**
|
|
* @property _regX
|
|
* @protected
|
|
* @type Number
|
|
**/
|
|
this._regX = 0;
|
|
|
|
/**
|
|
* @property _regY
|
|
* @protected
|
|
* @type Number
|
|
**/
|
|
this._regY = 0;
|
|
|
|
/**
|
|
* @property _spacing
|
|
* @protected
|
|
* @type Number
|
|
**/
|
|
this._spacing = 0;
|
|
|
|
/**
|
|
* @property _margin
|
|
* @protected
|
|
* @type Number
|
|
**/
|
|
this._margin = 0;
|
|
|
|
// setup:
|
|
this._parseData(data);
|
|
}
|
|
var p = createjs.extend(SpriteSheet, createjs.EventDispatcher);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// events:
|
|
/**
|
|
* Dispatched when all images are loaded. Note that this only fires if the images
|
|
* were not fully loaded when the sprite sheet was initialized. You should check the complete property
|
|
* to prior to adding a listener. Ex.
|
|
*
|
|
* var sheet = new createjs.SpriteSheet(data);
|
|
* if (!sheet.complete) {
|
|
* // not preloaded, listen for the complete event:
|
|
* sheet.addEventListener("complete", handler);
|
|
* }
|
|
*
|
|
* @event complete
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
/**
|
|
* Dispatched when getFrame is called with a valid frame index. This is primarily intended for use by {{#crossLink "SpriteSheetBuilder"}}{{/crossLink}}
|
|
* when doing on-demand rendering.
|
|
* @event getframe
|
|
* @param {Number} index The frame index.
|
|
* @param {Object} frame The frame object that getFrame will return.
|
|
*/
|
|
|
|
/**
|
|
* Dispatched when an image encounters an error. A SpriteSheet will dispatch an error event for each image that
|
|
* encounters an error, and will still dispatch a {{#crossLink "SpriteSheet/complete:event"}}{{/crossLink}}
|
|
* event once all images are finished processing, even if an error is encountered.
|
|
* @event error
|
|
* @param {String} src The source of the image that failed to load.
|
|
* @since 0.8.2
|
|
*/
|
|
|
|
// getter / setters:
|
|
/**
|
|
* Use the {{#crossLink "SpriteSheet/animations:property"}}{{/crossLink}} property instead.
|
|
* @method getAnimations
|
|
* @return {Array}
|
|
* @deprecated
|
|
**/
|
|
p.getAnimations = function () {
|
|
return this._animations.slice();
|
|
};
|
|
|
|
/**
|
|
* Returns an array of all available animation names available on this sprite sheet as strings.
|
|
* @property animations
|
|
* @type {Array}
|
|
* @readonly
|
|
**/
|
|
try {
|
|
Object.defineProperties(p, {
|
|
animations: { get: p.getAnimations },
|
|
});
|
|
} catch (e) {}
|
|
|
|
// public methods:
|
|
/**
|
|
* Returns the total number of frames in the specified animation, or in the whole sprite
|
|
* sheet if the animation param is omitted. Returns 0 if the spritesheet relies on calculated frame counts, and
|
|
* the images have not been fully loaded.
|
|
* @method getNumFrames
|
|
* @param {String} animation The name of the animation to get a frame count for.
|
|
* @return {Number} The number of frames in the animation, or in the entire sprite sheet if the animation param is omitted.
|
|
*/
|
|
p.getNumFrames = function (animation) {
|
|
if (animation == null) {
|
|
return this._frames ? this._frames.length : this._numFrames || 0;
|
|
} else {
|
|
var data = this._data[animation];
|
|
if (data == null) {
|
|
return 0;
|
|
} else {
|
|
return data.frames.length;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns an object defining the specified animation. The returned object contains:<UL>
|
|
* <li>frames: an array of the frame ids in the animation</li>
|
|
* <li>speed: the playback speed for this animation</li>
|
|
* <li>name: the name of the animation</li>
|
|
* <li>next: the default animation to play next. If the animation loops, the name and next property will be the
|
|
* same.</li>
|
|
* </UL>
|
|
* @method getAnimation
|
|
* @param {String} name The name of the animation to get.
|
|
* @return {Object} a generic object with frames, speed, name, and next properties.
|
|
**/
|
|
p.getAnimation = function (name) {
|
|
return this._data[name];
|
|
};
|
|
|
|
/**
|
|
* Returns an object specifying the image and source rect of the specified frame. The returned object has:<UL>
|
|
* <li>an image property holding a reference to the image object in which the frame is found</li>
|
|
* <li>a rect property containing a Rectangle instance which defines the boundaries for the frame within that
|
|
* image.</li>
|
|
* <li> A regX and regY property corresponding to the regX/Y values for the frame.
|
|
* </UL>
|
|
* @method getFrame
|
|
* @param {Number} frameIndex The index of the frame.
|
|
* @return {Object} a generic object with image and rect properties. Returns null if the frame does not exist.
|
|
**/
|
|
p.getFrame = function (frameIndex) {
|
|
var frame;
|
|
if (this._frames && (frame = this._frames[frameIndex])) {
|
|
return frame;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Returns a {{#crossLink "Rectangle"}}{{/crossLink}} instance defining the bounds of the specified frame relative
|
|
* to the origin. For example, a 90 x 70 frame with a regX of 50 and a regY of 40 would return:
|
|
*
|
|
* [x=-50, y=-40, width=90, height=70]
|
|
*
|
|
* @method getFrameBounds
|
|
* @param {Number} frameIndex The index of the frame.
|
|
* @param {Rectangle} [rectangle] A Rectangle instance to copy the values into. By default a new instance is created.
|
|
* @return {Rectangle} A Rectangle instance. Returns null if the frame does not exist, or the image is not fully loaded.
|
|
**/
|
|
p.getFrameBounds = function (frameIndex, rectangle) {
|
|
var frame = this.getFrame(frameIndex);
|
|
return frame
|
|
? (rectangle || new createjs.Rectangle()).setValues(
|
|
-frame.regX,
|
|
-frame.regY,
|
|
frame.rect.width,
|
|
frame.rect.height
|
|
)
|
|
: null;
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[SpriteSheet]";
|
|
};
|
|
|
|
/**
|
|
* SpriteSheet cannot be cloned. A SpriteSheet can be shared by multiple Sprite instances without cloning it.
|
|
* @method clone
|
|
**/
|
|
p.clone = function () {
|
|
throw "SpriteSheet cannot be cloned.";
|
|
};
|
|
|
|
// private methods:
|
|
/**
|
|
* @method _parseData
|
|
* @param {Object} data An object describing the SpriteSheet data.
|
|
* @protected
|
|
**/
|
|
p._parseData = function (data) {
|
|
var i, l, o, a;
|
|
if (data == null) {
|
|
return;
|
|
}
|
|
|
|
this.framerate = data.framerate || 0;
|
|
|
|
// parse images:
|
|
if (data.images && (l = data.images.length) > 0) {
|
|
a = this._images = [];
|
|
for (i = 0; i < l; i++) {
|
|
var img = data.images[i];
|
|
if (typeof img == "string") {
|
|
var src = img;
|
|
img = document.createElement("img");
|
|
img.src = src;
|
|
}
|
|
a.push(img);
|
|
if (!img.getContext && !img.naturalWidth) {
|
|
this._loadCount++;
|
|
this.complete = false;
|
|
(function (o, src) {
|
|
img.onload = function () {
|
|
o._handleImageLoad(src);
|
|
};
|
|
})(this, src);
|
|
(function (o, src) {
|
|
img.onerror = function () {
|
|
o._handleImageError(src);
|
|
};
|
|
})(this, src);
|
|
}
|
|
}
|
|
}
|
|
|
|
// parse frames:
|
|
if (data.frames == null) {
|
|
// nothing
|
|
} else if (Array.isArray(data.frames)) {
|
|
this._frames = [];
|
|
a = data.frames;
|
|
for (i = 0, l = a.length; i < l; i++) {
|
|
var arr = a[i];
|
|
this._frames.push({
|
|
image: this._images[arr[4] ? arr[4] : 0],
|
|
rect: new createjs.Rectangle(arr[0], arr[1], arr[2], arr[3]),
|
|
regX: arr[5] || 0,
|
|
regY: arr[6] || 0,
|
|
});
|
|
}
|
|
} else {
|
|
o = data.frames;
|
|
this._frameWidth = o.width;
|
|
this._frameHeight = o.height;
|
|
this._regX = o.regX || 0;
|
|
this._regY = o.regY || 0;
|
|
this._spacing = o.spacing || 0;
|
|
this._margin = o.margin || 0;
|
|
this._numFrames = o.count;
|
|
if (this._loadCount == 0) {
|
|
this._calculateFrames();
|
|
}
|
|
}
|
|
|
|
// parse animations:
|
|
this._animations = [];
|
|
if ((o = data.animations) != null) {
|
|
this._data = {};
|
|
var name;
|
|
for (name in o) {
|
|
var anim = { name: name };
|
|
var obj = o[name];
|
|
if (typeof obj == "number") {
|
|
// single frame
|
|
a = anim.frames = [obj];
|
|
} else if (Array.isArray(obj)) {
|
|
// simple
|
|
if (obj.length == 1) {
|
|
anim.frames = [obj[0]];
|
|
} else {
|
|
anim.speed = obj[3];
|
|
anim.next = obj[2];
|
|
a = anim.frames = [];
|
|
for (i = obj[0]; i <= obj[1]; i++) {
|
|
a.push(i);
|
|
}
|
|
}
|
|
} else {
|
|
// complex
|
|
anim.speed = obj.speed;
|
|
anim.next = obj.next;
|
|
var frames = obj.frames;
|
|
a = anim.frames =
|
|
typeof frames == "number" ? [frames] : frames.slice(0);
|
|
}
|
|
if (anim.next === true || anim.next === undefined) {
|
|
anim.next = name;
|
|
} // loop
|
|
if (anim.next === false || (a.length < 2 && anim.next == name)) {
|
|
anim.next = null;
|
|
} // stop
|
|
if (!anim.speed) {
|
|
anim.speed = 1;
|
|
}
|
|
this._animations.push(name);
|
|
this._data[name] = anim;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _handleImageLoad
|
|
* @protected
|
|
**/
|
|
p._handleImageLoad = function (src) {
|
|
if (--this._loadCount == 0) {
|
|
this._calculateFrames();
|
|
this.complete = true;
|
|
this.dispatchEvent("complete");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _handleImageError
|
|
* @protected
|
|
*/
|
|
p._handleImageError = function (src) {
|
|
var errorEvent = new createjs.Event("error");
|
|
errorEvent.src = src;
|
|
this.dispatchEvent(errorEvent);
|
|
|
|
// Complete is still dispatched.
|
|
if (--this._loadCount == 0) {
|
|
this.dispatchEvent("complete");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _calculateFrames
|
|
* @protected
|
|
**/
|
|
p._calculateFrames = function () {
|
|
if (this._frames || this._frameWidth == 0) {
|
|
return;
|
|
}
|
|
|
|
this._frames = [];
|
|
|
|
var maxFrames = this._numFrames || 100000; // if we go over this, something is wrong.
|
|
var frameCount = 0,
|
|
frameWidth = this._frameWidth,
|
|
frameHeight = this._frameHeight;
|
|
var spacing = this._spacing,
|
|
margin = this._margin;
|
|
|
|
imgLoop: for (var i = 0, imgs = this._images; i < imgs.length; i++) {
|
|
var img = imgs[i],
|
|
imgW = img.width,
|
|
imgH = img.height;
|
|
|
|
var y = margin;
|
|
while (y <= imgH - margin - frameHeight) {
|
|
var x = margin;
|
|
while (x <= imgW - margin - frameWidth) {
|
|
if (frameCount >= maxFrames) {
|
|
break imgLoop;
|
|
}
|
|
frameCount++;
|
|
this._frames.push({
|
|
image: img,
|
|
rect: new createjs.Rectangle(x, y, frameWidth, frameHeight),
|
|
regX: this._regX,
|
|
regY: this._regY,
|
|
});
|
|
x += frameWidth + spacing;
|
|
}
|
|
y += frameHeight + spacing;
|
|
}
|
|
}
|
|
this._numFrames = frameCount;
|
|
};
|
|
|
|
createjs.SpriteSheet = createjs.promote(SpriteSheet, "EventDispatcher");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Graphics.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* The Graphics class exposes an easy to use API for generating vector drawing instructions and drawing them to a
|
|
* specified context. Note that you can use Graphics without any dependency on the EaselJS framework by calling {{#crossLink "Graphics/draw"}}{{/crossLink}}
|
|
* directly, or it can be used with the {{#crossLink "Shape"}}{{/crossLink}} object to draw vector graphics within the
|
|
* context of an EaselJS display list.
|
|
*
|
|
* There are two approaches to working with Graphics object: calling methods on a Graphics instance (the "Graphics API"), or
|
|
* instantiating Graphics command objects and adding them to the graphics queue via {{#crossLink "Graphics/append"}}{{/crossLink}}.
|
|
* The former abstracts the latter, simplifying beginning and ending paths, fills, and strokes.
|
|
*
|
|
* var g = new createjs.Graphics();
|
|
* g.setStrokeStyle(1);
|
|
* g.beginStroke("#000000");
|
|
* g.beginFill("red");
|
|
* g.drawCircle(0,0,30);
|
|
*
|
|
* All drawing methods in Graphics return the Graphics instance, so they can be chained together. For example,
|
|
* the following line of code would generate the instructions to draw a rectangle with a red stroke and blue fill:
|
|
*
|
|
* myGraphics.beginStroke("red").beginFill("blue").drawRect(20, 20, 100, 50);
|
|
*
|
|
* Each graphics API call generates a command object (see below). The last command to be created can be accessed via
|
|
* {{#crossLink "Graphics/command:property"}}{{/crossLink}}:
|
|
*
|
|
* var fillCommand = myGraphics.beginFill("red").command;
|
|
* // ... later, update the fill style/color:
|
|
* fillCommand.style = "blue";
|
|
* // or change it to a bitmap fill:
|
|
* fillCommand.bitmap(myImage);
|
|
*
|
|
* For more direct control of rendering, you can instantiate and append command objects to the graphics queue directly. In this case, you
|
|
* need to manage path creation manually, and ensure that fill/stroke is applied to a defined path:
|
|
*
|
|
* // start a new path. Graphics.beginCmd is a reusable BeginPath instance:
|
|
* myGraphics.append(createjs.Graphics.beginCmd);
|
|
* // we need to define the path before applying the fill:
|
|
* var circle = new createjs.Graphics.Circle(0,0,30);
|
|
* myGraphics.append(circle);
|
|
* // fill the path we just defined:
|
|
* var fill = new createjs.Graphics.Fill("red");
|
|
* myGraphics.append(fill);
|
|
*
|
|
* These approaches can be used together, for example to insert a custom command:
|
|
*
|
|
* myGraphics.beginFill("red");
|
|
* var customCommand = new CustomSpiralCommand(etc);
|
|
* myGraphics.append(customCommand);
|
|
* myGraphics.beginFill("blue");
|
|
* myGraphics.drawCircle(0, 0, 30);
|
|
*
|
|
* See {{#crossLink "Graphics/append"}}{{/crossLink}} for more info on creating custom commands.
|
|
*
|
|
* <h4>Tiny API</h4>
|
|
* The Graphics class also includes a "tiny API", which is one or two-letter methods that are shortcuts for all of the
|
|
* Graphics methods. These methods are great for creating compact instructions, and is used by the Toolkit for CreateJS
|
|
* to generate readable code. All tiny methods are marked as protected, so you can view them by enabling protected
|
|
* descriptions in the docs.
|
|
*
|
|
* <table>
|
|
* <tr><td><b>Tiny</b></td><td><b>Method</b></td><td><b>Tiny</b></td><td><b>Method</b></td></tr>
|
|
* <tr><td>mt</td><td>{{#crossLink "Graphics/moveTo"}}{{/crossLink}} </td>
|
|
* <td>lt</td> <td>{{#crossLink "Graphics/lineTo"}}{{/crossLink}}</td></tr>
|
|
* <tr><td>a/at</td><td>{{#crossLink "Graphics/arc"}}{{/crossLink}} / {{#crossLink "Graphics/arcTo"}}{{/crossLink}} </td>
|
|
* <td>bt</td><td>{{#crossLink "Graphics/bezierCurveTo"}}{{/crossLink}} </td></tr>
|
|
* <tr><td>qt</td><td>{{#crossLink "Graphics/quadraticCurveTo"}}{{/crossLink}} (also curveTo)</td>
|
|
* <td>r</td><td>{{#crossLink "Graphics/rect"}}{{/crossLink}} </td></tr>
|
|
* <tr><td>cp</td><td>{{#crossLink "Graphics/closePath"}}{{/crossLink}} </td>
|
|
* <td>c</td><td>{{#crossLink "Graphics/clear"}}{{/crossLink}} </td></tr>
|
|
* <tr><td>f</td><td>{{#crossLink "Graphics/beginFill"}}{{/crossLink}} </td>
|
|
* <td>lf</td><td>{{#crossLink "Graphics/beginLinearGradientFill"}}{{/crossLink}} </td></tr>
|
|
* <tr><td>rf</td><td>{{#crossLink "Graphics/beginRadialGradientFill"}}{{/crossLink}} </td>
|
|
* <td>bf</td><td>{{#crossLink "Graphics/beginBitmapFill"}}{{/crossLink}} </td></tr>
|
|
* <tr><td>ef</td><td>{{#crossLink "Graphics/endFill"}}{{/crossLink}} </td>
|
|
* <td>ss / sd</td><td>{{#crossLink "Graphics/setStrokeStyle"}}{{/crossLink}} / {{#crossLink "Graphics/setStrokeDash"}}{{/crossLink}} </td></tr>
|
|
* <tr><td>s</td><td>{{#crossLink "Graphics/beginStroke"}}{{/crossLink}} </td>
|
|
* <td>ls</td><td>{{#crossLink "Graphics/beginLinearGradientStroke"}}{{/crossLink}} </td></tr>
|
|
* <tr><td>rs</td><td>{{#crossLink "Graphics/beginRadialGradientStroke"}}{{/crossLink}} </td>
|
|
* <td>bs</td><td>{{#crossLink "Graphics/beginBitmapStroke"}}{{/crossLink}} </td></tr>
|
|
* <tr><td>es</td><td>{{#crossLink "Graphics/endStroke"}}{{/crossLink}} </td>
|
|
* <td>dr</td><td>{{#crossLink "Graphics/drawRect"}}{{/crossLink}} </td></tr>
|
|
* <tr><td>rr</td><td>{{#crossLink "Graphics/drawRoundRect"}}{{/crossLink}} </td>
|
|
* <td>rc</td><td>{{#crossLink "Graphics/drawRoundRectComplex"}}{{/crossLink}} </td></tr>
|
|
* <tr><td>dc</td><td>{{#crossLink "Graphics/drawCircle"}}{{/crossLink}} </td>
|
|
* <td>de</td><td>{{#crossLink "Graphics/drawEllipse"}}{{/crossLink}} </td></tr>
|
|
* <tr><td>dp</td><td>{{#crossLink "Graphics/drawPolyStar"}}{{/crossLink}} </td>
|
|
* <td>p</td><td>{{#crossLink "Graphics/decodePath"}}{{/crossLink}} </td></tr>
|
|
* </table>
|
|
*
|
|
* Here is the above example, using the tiny API instead.
|
|
*
|
|
* myGraphics.s("red").f("blue").r(20, 20, 100, 50);
|
|
*
|
|
* @class Graphics
|
|
* @constructor
|
|
**/
|
|
function Graphics() {
|
|
// public properties
|
|
/**
|
|
* Holds a reference to the last command that was created or appended. For example, you could retain a reference
|
|
* to a Fill command in order to dynamically update the color later by using:
|
|
*
|
|
* var myFill = myGraphics.beginFill("red").command;
|
|
* // update color later:
|
|
* myFill.style = "yellow";
|
|
*
|
|
* @property command
|
|
* @type Object
|
|
**/
|
|
this.command = null;
|
|
|
|
// private properties
|
|
/**
|
|
* @property _stroke
|
|
* @protected
|
|
* @type {Stroke}
|
|
**/
|
|
this._stroke = null;
|
|
|
|
/**
|
|
* @property _strokeStyle
|
|
* @protected
|
|
* @type {StrokeStyle}
|
|
**/
|
|
this._strokeStyle = null;
|
|
|
|
/**
|
|
* @property _oldStrokeStyle
|
|
* @protected
|
|
* @type {StrokeStyle}
|
|
**/
|
|
this._oldStrokeStyle = null;
|
|
|
|
/**
|
|
* @property _strokeDash
|
|
* @protected
|
|
* @type {StrokeDash}
|
|
**/
|
|
this._strokeDash = null;
|
|
|
|
/**
|
|
* @property _oldStrokeDash
|
|
* @protected
|
|
* @type {StrokeDash}
|
|
**/
|
|
this._oldStrokeDash = null;
|
|
|
|
/**
|
|
* @property _strokeIgnoreScale
|
|
* @protected
|
|
* @type Boolean
|
|
**/
|
|
this._strokeIgnoreScale = false;
|
|
|
|
/**
|
|
* @property _fill
|
|
* @protected
|
|
* @type {Fill}
|
|
**/
|
|
this._fill = null;
|
|
|
|
/**
|
|
* @property _instructions
|
|
* @protected
|
|
* @type {Array}
|
|
**/
|
|
this._instructions = [];
|
|
|
|
/**
|
|
* Indicates the last instruction index that was committed.
|
|
* @property _commitIndex
|
|
* @protected
|
|
* @type {Number}
|
|
**/
|
|
this._commitIndex = 0;
|
|
|
|
/**
|
|
* Uncommitted instructions.
|
|
* @property _activeInstructions
|
|
* @protected
|
|
* @type {Array}
|
|
**/
|
|
this._activeInstructions = [];
|
|
|
|
/**
|
|
* This indicates that there have been changes to the activeInstruction list since the last updateInstructions call.
|
|
* @property _dirty
|
|
* @protected
|
|
* @type {Boolean}
|
|
* @default false
|
|
**/
|
|
this._dirty = false;
|
|
|
|
/**
|
|
* Index to draw from if a store operation has happened.
|
|
* @property _storeIndex
|
|
* @protected
|
|
* @type {Number}
|
|
* @default 0
|
|
**/
|
|
this._storeIndex = 0;
|
|
|
|
// setup:
|
|
this.clear();
|
|
}
|
|
var p = Graphics.prototype;
|
|
var G = Graphics; // shortcut
|
|
|
|
/**
|
|
* <strong>REMOVED</strong>. Removed in favor of using `MySuperClass_constructor`.
|
|
* See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}}
|
|
* for details.
|
|
*
|
|
* There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance.
|
|
*
|
|
* @method initialize
|
|
* @protected
|
|
* @deprecated
|
|
*/
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is.
|
|
|
|
// static public methods:
|
|
/**
|
|
* Returns a CSS compatible color string based on the specified RGB numeric color values in the format
|
|
* "rgba(255,255,255,1.0)", or if alpha is null then in the format "rgb(255,255,255)". For example,
|
|
*
|
|
* createjs.Graphics.getRGB(50, 100, 150, 0.5);
|
|
* // Returns "rgba(50,100,150,0.5)"
|
|
*
|
|
* It also supports passing a single hex color value as the first param, and an optional alpha value as the second
|
|
* param. For example,
|
|
*
|
|
* createjs.Graphics.getRGB(0xFF00FF, 0.2);
|
|
* // Returns "rgba(255,0,255,0.2)"
|
|
*
|
|
* @method getRGB
|
|
* @static
|
|
* @param {Number} r The red component for the color, between 0 and 0xFF (255).
|
|
* @param {Number} g The green component for the color, between 0 and 0xFF (255).
|
|
* @param {Number} b The blue component for the color, between 0 and 0xFF (255).
|
|
* @param {Number} [alpha] The alpha component for the color where 0 is fully transparent and 1 is fully opaque.
|
|
* @return {String} A CSS compatible color string based on the specified RGB numeric color values in the format
|
|
* "rgba(255,255,255,1.0)", or if alpha is null then in the format "rgb(255,255,255)".
|
|
**/
|
|
Graphics.getRGB = function (r, g, b, alpha) {
|
|
if (r != null && b == null) {
|
|
alpha = g;
|
|
b = r & 0xff;
|
|
g = (r >> 8) & 0xff;
|
|
r = (r >> 16) & 0xff;
|
|
}
|
|
if (alpha == null) {
|
|
return "rgb(" + r + "," + g + "," + b + ")";
|
|
} else {
|
|
return "rgba(" + r + "," + g + "," + b + "," + alpha + ")";
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns a CSS compatible color string based on the specified HSL numeric color values in the format "hsla(360,100,100,1.0)",
|
|
* or if alpha is null then in the format "hsl(360,100,100)".
|
|
*
|
|
* createjs.Graphics.getHSL(150, 100, 70);
|
|
* // Returns "hsl(150,100,70)"
|
|
*
|
|
* @method getHSL
|
|
* @static
|
|
* @param {Number} hue The hue component for the color, between 0 and 360.
|
|
* @param {Number} saturation The saturation component for the color, between 0 and 100.
|
|
* @param {Number} lightness The lightness component for the color, between 0 and 100.
|
|
* @param {Number} [alpha] The alpha component for the color where 0 is fully transparent and 1 is fully opaque.
|
|
* @return {String} A CSS compatible color string based on the specified HSL numeric color values in the format
|
|
* "hsla(360,100,100,1.0)", or if alpha is null then in the format "hsl(360,100,100)".
|
|
**/
|
|
Graphics.getHSL = function (hue, saturation, lightness, alpha) {
|
|
if (alpha == null) {
|
|
return "hsl(" + (hue % 360) + "," + saturation + "%," + lightness + "%)";
|
|
} else {
|
|
return (
|
|
"hsla(" +
|
|
(hue % 360) +
|
|
"," +
|
|
saturation +
|
|
"%," +
|
|
lightness +
|
|
"%," +
|
|
alpha +
|
|
")"
|
|
);
|
|
}
|
|
};
|
|
|
|
// static properties:
|
|
/**
|
|
* A reusable instance of {{#crossLink "Graphics/BeginPath"}}{{/crossLink}} to avoid
|
|
* unnecessary instantiation.
|
|
* @property beginCmd
|
|
* @type {Graphics.BeginPath}
|
|
* @static
|
|
**/
|
|
// defined at the bottom of this file.
|
|
|
|
/**
|
|
* Map of Base64 characters to values. Used by {{#crossLink "Graphics/decodePath"}}{{/crossLink}}.
|
|
* @property BASE_64
|
|
* @static
|
|
* @final
|
|
* @readonly
|
|
* @type {Object}
|
|
**/
|
|
Graphics.BASE_64 = {
|
|
A: 0,
|
|
B: 1,
|
|
C: 2,
|
|
D: 3,
|
|
E: 4,
|
|
F: 5,
|
|
G: 6,
|
|
H: 7,
|
|
I: 8,
|
|
J: 9,
|
|
K: 10,
|
|
L: 11,
|
|
M: 12,
|
|
N: 13,
|
|
O: 14,
|
|
P: 15,
|
|
Q: 16,
|
|
R: 17,
|
|
S: 18,
|
|
T: 19,
|
|
U: 20,
|
|
V: 21,
|
|
W: 22,
|
|
X: 23,
|
|
Y: 24,
|
|
Z: 25,
|
|
a: 26,
|
|
b: 27,
|
|
c: 28,
|
|
d: 29,
|
|
e: 30,
|
|
f: 31,
|
|
g: 32,
|
|
h: 33,
|
|
i: 34,
|
|
j: 35,
|
|
k: 36,
|
|
l: 37,
|
|
m: 38,
|
|
n: 39,
|
|
o: 40,
|
|
p: 41,
|
|
q: 42,
|
|
r: 43,
|
|
s: 44,
|
|
t: 45,
|
|
u: 46,
|
|
v: 47,
|
|
w: 48,
|
|
x: 49,
|
|
y: 50,
|
|
z: 51,
|
|
0: 52,
|
|
1: 53,
|
|
2: 54,
|
|
3: 55,
|
|
4: 56,
|
|
5: 57,
|
|
6: 58,
|
|
7: 59,
|
|
8: 60,
|
|
9: 61,
|
|
"+": 62,
|
|
"/": 63,
|
|
};
|
|
|
|
/**
|
|
* Maps numeric values for the caps parameter of {{#crossLink "Graphics/setStrokeStyle"}}{{/crossLink}} to
|
|
* corresponding string values. This is primarily for use with the tiny API. The mappings are as follows: 0 to
|
|
* "butt", 1 to "round", and 2 to "square".
|
|
* For example, to set the line caps to "square":
|
|
*
|
|
* myGraphics.ss(16, 2);
|
|
*
|
|
* @property STROKE_CAPS_MAP
|
|
* @static
|
|
* @final
|
|
* @readonly
|
|
* @type {Array}
|
|
**/
|
|
Graphics.STROKE_CAPS_MAP = ["butt", "round", "square"];
|
|
|
|
/**
|
|
* Maps numeric values for the joints parameter of {{#crossLink "Graphics/setStrokeStyle"}}{{/crossLink}} to
|
|
* corresponding string values. This is primarily for use with the tiny API. The mappings are as follows: 0 to
|
|
* "miter", 1 to "round", and 2 to "bevel".
|
|
* For example, to set the line joints to "bevel":
|
|
*
|
|
* myGraphics.ss(16, 0, 2);
|
|
*
|
|
* @property STROKE_JOINTS_MAP
|
|
* @static
|
|
* @final
|
|
* @readonly
|
|
* @type {Array}
|
|
**/
|
|
Graphics.STROKE_JOINTS_MAP = ["miter", "round", "bevel"];
|
|
|
|
/**
|
|
* @property _ctx
|
|
* @static
|
|
* @protected
|
|
* @type {CanvasRenderingContext2D}
|
|
**/
|
|
var canvas = createjs.createCanvas
|
|
? createjs.createCanvas()
|
|
: document.createElement("canvas");
|
|
if (canvas.getContext) {
|
|
Graphics._ctx = canvas.getContext("2d");
|
|
canvas.width = canvas.height = 1;
|
|
}
|
|
|
|
// getter / setters:
|
|
/**
|
|
* Use the {{#crossLink "Graphics/instructions:property"}}{{/crossLink}} property instead.
|
|
* @method getInstructions
|
|
* @return {Array}
|
|
* @deprecated
|
|
**/
|
|
p.getInstructions = function () {
|
|
this._updateInstructions();
|
|
return this._instructions;
|
|
};
|
|
|
|
/**
|
|
* Returns the graphics instructions array. Each entry is a graphics command object (ex. Graphics.Fill, Graphics.Rect)
|
|
* Modifying the returned array directly is not recommended, and is likely to result in unexpected behaviour.
|
|
*
|
|
* This property is mainly intended for introspection of the instructions (ex. for graphics export).
|
|
* @property instructions
|
|
* @type {Array}
|
|
* @readonly
|
|
**/
|
|
try {
|
|
Object.defineProperties(p, {
|
|
instructions: { get: p.getInstructions },
|
|
});
|
|
} catch (e) {}
|
|
|
|
// public methods:
|
|
/**
|
|
* Returns true if this Graphics instance has no drawing commands.
|
|
* @method isEmpty
|
|
* @return {Boolean} Returns true if this Graphics instance has no drawing commands.
|
|
**/
|
|
p.isEmpty = function () {
|
|
return !(this._instructions.length || this._activeInstructions.length);
|
|
};
|
|
|
|
/**
|
|
* Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform.
|
|
* Returns true if the draw was handled (useful for overriding functionality).
|
|
*
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method draw
|
|
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into.
|
|
* @param {Object} data Optional data that is passed to graphics command exec methods. When called from a Shape instance, the shape passes itself as the data parameter. This can be used by custom graphic commands to insert contextual data.
|
|
**/
|
|
p.draw = function (ctx, data) {
|
|
this._updateInstructions();
|
|
var instr = this._instructions;
|
|
for (var i = this._storeIndex, l = instr.length; i < l; i++) {
|
|
instr[i].exec(ctx, data);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Draws only the path described for this Graphics instance, skipping any non-path instructions, including fill and
|
|
* stroke descriptions. Used for <code>DisplayObject.mask</code> to draw the clipping path, for example.
|
|
*
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method drawAsPath
|
|
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into.
|
|
**/
|
|
p.drawAsPath = function (ctx) {
|
|
this._updateInstructions();
|
|
var instr,
|
|
instrs = this._instructions;
|
|
for (var i = this._storeIndex, l = instrs.length; i < l; i++) {
|
|
// the first command is always a beginPath command.
|
|
if ((instr = instrs[i]).path !== false) {
|
|
instr.exec(ctx);
|
|
}
|
|
}
|
|
};
|
|
|
|
// public methods that map directly to context 2D calls:
|
|
/**
|
|
* Moves the drawing point to the specified position. A tiny API method "mt" also exists.
|
|
* @method moveTo
|
|
* @param {Number} x The x coordinate the drawing point should move to.
|
|
* @param {Number} y The y coordinate the drawing point should move to.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls).
|
|
* @chainable
|
|
**/
|
|
p.moveTo = function (x, y) {
|
|
return this.append(new G.MoveTo(x, y), true);
|
|
};
|
|
|
|
/**
|
|
* Draws a line from the current drawing point to the specified position, which become the new current drawing
|
|
* point. Note that you *must* call {{#crossLink "Graphics/moveTo"}}{{/crossLink}} before the first `lineTo()`.
|
|
* A tiny API method "lt" also exists.
|
|
*
|
|
* For detailed information, read the
|
|
* <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#complex-shapes-(paths)">
|
|
* whatwg spec</a>.
|
|
* @method lineTo
|
|
* @param {Number} x The x coordinate the drawing point should draw to.
|
|
* @param {Number} y The y coordinate the drawing point should draw to.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.lineTo = function (x, y) {
|
|
return this.append(new G.LineTo(x, y));
|
|
};
|
|
|
|
/**
|
|
* Draws an arc with the specified control points and radius. For detailed information, read the
|
|
* <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-arcto">
|
|
* whatwg spec</a>. A tiny API method "at" also exists.
|
|
* @method arcTo
|
|
* @param {Number} x1
|
|
* @param {Number} y1
|
|
* @param {Number} x2
|
|
* @param {Number} y2
|
|
* @param {Number} radius
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.arcTo = function (x1, y1, x2, y2, radius) {
|
|
return this.append(new G.ArcTo(x1, y1, x2, y2, radius));
|
|
};
|
|
|
|
/**
|
|
* Draws an arc defined by the radius, startAngle and endAngle arguments, centered at the position (x, y). For
|
|
* example, to draw a full circle with a radius of 20 centered at (100, 100):
|
|
*
|
|
* arc(100, 100, 20, 0, Math.PI*2);
|
|
*
|
|
* For detailed information, read the
|
|
* <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-arc">whatwg spec</a>.
|
|
* A tiny API method "a" also exists.
|
|
* @method arc
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} radius
|
|
* @param {Number} startAngle Measured in radians.
|
|
* @param {Number} endAngle Measured in radians.
|
|
* @param {Boolean} anticlockwise
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.arc = function (x, y, radius, startAngle, endAngle, anticlockwise) {
|
|
return this.append(
|
|
new G.Arc(x, y, radius, startAngle, endAngle, anticlockwise)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Draws a quadratic curve from the current drawing point to (x, y) using the control point (cpx, cpy). For detailed
|
|
* information, read the <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-quadraticcurveto">
|
|
* whatwg spec</a>. A tiny API method "qt" also exists.
|
|
* @method quadraticCurveTo
|
|
* @param {Number} cpx
|
|
* @param {Number} cpy
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.quadraticCurveTo = function (cpx, cpy, x, y) {
|
|
return this.append(new G.QuadraticCurveTo(cpx, cpy, x, y));
|
|
};
|
|
|
|
/**
|
|
* Draws a bezier curve from the current drawing point to (x, y) using the control points (cp1x, cp1y) and (cp2x,
|
|
* cp2y). For detailed information, read the
|
|
* <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-beziercurveto">
|
|
* whatwg spec</a>. A tiny API method "bt" also exists.
|
|
* @method bezierCurveTo
|
|
* @param {Number} cp1x
|
|
* @param {Number} cp1y
|
|
* @param {Number} cp2x
|
|
* @param {Number} cp2y
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) {
|
|
return this.append(new G.BezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y));
|
|
};
|
|
|
|
/**
|
|
* Draws a rectangle at (x, y) with the specified width and height using the current fill and/or stroke.
|
|
* For detailed information, read the
|
|
* <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-rect">
|
|
* whatwg spec</a>. A tiny API method "r" also exists.
|
|
* @method rect
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} w Width of the rectangle
|
|
* @param {Number} h Height of the rectangle
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.rect = function (x, y, w, h) {
|
|
return this.append(new G.Rect(x, y, w, h));
|
|
};
|
|
|
|
/**
|
|
* Closes the current path, effectively drawing a line from the current drawing point to the first drawing point specified
|
|
* since the fill or stroke was last set. A tiny API method "cp" also exists.
|
|
* @method closePath
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.closePath = function () {
|
|
return this._activeInstructions.length
|
|
? this.append(new G.ClosePath())
|
|
: this;
|
|
};
|
|
|
|
// public methods that roughly map to Flash graphics APIs:
|
|
/**
|
|
* Clears all drawing instructions, effectively resetting this Graphics instance. Any line and fill styles will need
|
|
* to be redefined to draw shapes following a clear call. A tiny API method "c" also exists.
|
|
* @method clear
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.clear = function () {
|
|
this._instructions.length = this._activeInstructions.length = this._commitIndex = 0;
|
|
this._strokeStyle = this._oldStrokeStyle = this._stroke = this._fill = this._strokeDash = this._oldStrokeDash = null;
|
|
this._dirty = this._strokeIgnoreScale = false;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Begins a fill with the specified color. This ends the current sub-path. A tiny API method "f" also exists.
|
|
* @method beginFill
|
|
* @param {String} color A CSS compatible color value (ex. "red", "#FF0000", or "rgba(255,0,0,0.5)"). Setting to
|
|
* null will result in no fill.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.beginFill = function (color) {
|
|
return this._setFill(color ? new G.Fill(color) : null);
|
|
};
|
|
|
|
/**
|
|
* Begins a linear gradient fill defined by the line (x0, y0) to (x1, y1). This ends the current sub-path. For
|
|
* example, the following code defines a black to white vertical gradient ranging from 20px to 120px, and draws a
|
|
* square to display it:
|
|
*
|
|
* myGraphics.beginLinearGradientFill(["#000","#FFF"], [0, 1], 0, 20, 0, 120).drawRect(20, 20, 120, 120);
|
|
*
|
|
* A tiny API method "lf" also exists.
|
|
* @method beginLinearGradientFill
|
|
* @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define a gradient
|
|
* drawing from red to blue.
|
|
* @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, 0.9] would draw
|
|
* the first color to 10% then interpolating to the second color at 90%.
|
|
* @param {Number} x0 The position of the first point defining the line that defines the gradient direction and size.
|
|
* @param {Number} y0 The position of the first point defining the line that defines the gradient direction and size.
|
|
* @param {Number} x1 The position of the second point defining the line that defines the gradient direction and size.
|
|
* @param {Number} y1 The position of the second point defining the line that defines the gradient direction and size.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.beginLinearGradientFill = function (colors, ratios, x0, y0, x1, y1) {
|
|
return this._setFill(
|
|
new G.Fill().linearGradient(colors, ratios, x0, y0, x1, y1)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Begins a radial gradient fill. This ends the current sub-path. For example, the following code defines a red to
|
|
* blue radial gradient centered at (100, 100), with a radius of 50, and draws a circle to display it:
|
|
*
|
|
* myGraphics.beginRadialGradientFill(["#F00","#00F"], [0, 1], 100, 100, 0, 100, 100, 50).drawCircle(100, 100, 50);
|
|
*
|
|
* A tiny API method "rf" also exists.
|
|
* @method beginRadialGradientFill
|
|
* @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define
|
|
* a gradient drawing from red to blue.
|
|
* @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1,
|
|
* 0.9] would draw the first color to 10% then interpolating to the second color at 90%.
|
|
* @param {Number} x0 Center position of the inner circle that defines the gradient.
|
|
* @param {Number} y0 Center position of the inner circle that defines the gradient.
|
|
* @param {Number} r0 Radius of the inner circle that defines the gradient.
|
|
* @param {Number} x1 Center position of the outer circle that defines the gradient.
|
|
* @param {Number} y1 Center position of the outer circle that defines the gradient.
|
|
* @param {Number} r1 Radius of the outer circle that defines the gradient.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.beginRadialGradientFill = function (
|
|
colors,
|
|
ratios,
|
|
x0,
|
|
y0,
|
|
r0,
|
|
x1,
|
|
y1,
|
|
r1
|
|
) {
|
|
return this._setFill(
|
|
new G.Fill().radialGradient(colors, ratios, x0, y0, r0, x1, y1, r1)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Begins a pattern fill using the specified image. This ends the current sub-path. A tiny API method "bf" also
|
|
* exists.
|
|
* @method beginBitmapFill
|
|
* @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement} image The Image, Canvas, or Video object to use
|
|
* as the pattern. Must be loaded prior to creating a bitmap fill, or the fill will be empty.
|
|
* @param {String} repetition Optional. Indicates whether to repeat the image in the fill area. One of "repeat",
|
|
* "repeat-x", "repeat-y", or "no-repeat". Defaults to "repeat". Note that Firefox does not support "repeat-x" or
|
|
* "repeat-y" (latest tests were in FF 20.0), and will default to "repeat".
|
|
* @param {Matrix2D} matrix Optional. Specifies a transformation matrix for the bitmap fill. This transformation
|
|
* will be applied relative to the parent transform.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.beginBitmapFill = function (image, repetition, matrix) {
|
|
return this._setFill(new G.Fill(null, matrix).bitmap(image, repetition));
|
|
};
|
|
|
|
/**
|
|
* Ends the current sub-path, and begins a new one with no fill. Functionally identical to <code>beginFill(null)</code>.
|
|
* A tiny API method "ef" also exists.
|
|
* @method endFill
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.endFill = function () {
|
|
return this.beginFill();
|
|
};
|
|
|
|
/**
|
|
* Sets the stroke style. Like all drawing methods, this can be chained, so you can define
|
|
* the stroke style and color in a single line of code like so:
|
|
*
|
|
* myGraphics.setStrokeStyle(8,"round").beginStroke("#F00");
|
|
*
|
|
* A tiny API method "ss" also exists.
|
|
* @method setStrokeStyle
|
|
* @param {Number} thickness The width of the stroke.
|
|
* @param {String | Number} [caps=0] Indicates the type of caps to use at the end of lines. One of butt,
|
|
* round, or square. Defaults to "butt". Also accepts the values 0 (butt), 1 (round), and 2 (square) for use with
|
|
* the tiny API.
|
|
* @param {String | Number} [joints=0] Specifies the type of joints that should be used where two lines meet.
|
|
* One of bevel, round, or miter. Defaults to "miter". Also accepts the values 0 (miter), 1 (round), and 2 (bevel)
|
|
* for use with the tiny API.
|
|
* @param {Number} [miterLimit=10] If joints is set to "miter", then you can specify a miter limit ratio which
|
|
* controls at what point a mitered joint will be clipped.
|
|
* @param {Boolean} [ignoreScale=false] If true, the stroke will be drawn at the specified thickness regardless
|
|
* of active transformations.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.setStrokeStyle = function (
|
|
thickness,
|
|
caps,
|
|
joints,
|
|
miterLimit,
|
|
ignoreScale
|
|
) {
|
|
this._updateInstructions(true);
|
|
this._strokeStyle = this.command = new G.StrokeStyle(
|
|
thickness,
|
|
caps,
|
|
joints,
|
|
miterLimit,
|
|
ignoreScale
|
|
);
|
|
|
|
// ignoreScale lives on Stroke, not StrokeStyle, so we do a little trickery:
|
|
if (this._stroke) {
|
|
this._stroke.ignoreScale = ignoreScale;
|
|
}
|
|
this._strokeIgnoreScale = ignoreScale;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Sets or clears the stroke dash pattern.
|
|
*
|
|
* myGraphics.setStrokeDash([20, 10], 0);
|
|
*
|
|
* A tiny API method `sd` also exists.
|
|
* @method setStrokeDash
|
|
* @param {Array} [segments] An array specifying the dash pattern, alternating between line and gap.
|
|
* For example, `[20,10]` would create a pattern of 20 pixel lines with 10 pixel gaps between them.
|
|
* Passing null or an empty array will clear the existing stroke dash.
|
|
* @param {Number} [offset=0] The offset of the dash pattern. For example, you could increment this value to create a "marching ants" effect.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.setStrokeDash = function (segments, offset) {
|
|
this._updateInstructions(true);
|
|
this._strokeDash = this.command = new G.StrokeDash(segments, offset);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Begins a stroke with the specified color. This ends the current sub-path. A tiny API method "s" also exists.
|
|
* @method beginStroke
|
|
* @param {String} color A CSS compatible color value (ex. "#FF0000", "red", or "rgba(255,0,0,0.5)"). Setting to
|
|
* null will result in no stroke.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.beginStroke = function (color) {
|
|
return this._setStroke(color ? new G.Stroke(color) : null);
|
|
};
|
|
|
|
/**
|
|
* Begins a linear gradient stroke defined by the line (x0, y0) to (x1, y1). This ends the current sub-path. For
|
|
* example, the following code defines a black to white vertical gradient ranging from 20px to 120px, and draws a
|
|
* square to display it:
|
|
*
|
|
* myGraphics.setStrokeStyle(10).
|
|
* beginLinearGradientStroke(["#000","#FFF"], [0, 1], 0, 20, 0, 120).drawRect(20, 20, 120, 120);
|
|
*
|
|
* A tiny API method "ls" also exists.
|
|
* @method beginLinearGradientStroke
|
|
* @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define
|
|
* a gradient drawing from red to blue.
|
|
* @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1,
|
|
* 0.9] would draw the first color to 10% then interpolating to the second color at 90%.
|
|
* @param {Number} x0 The position of the first point defining the line that defines the gradient direction and size.
|
|
* @param {Number} y0 The position of the first point defining the line that defines the gradient direction and size.
|
|
* @param {Number} x1 The position of the second point defining the line that defines the gradient direction and size.
|
|
* @param {Number} y1 The position of the second point defining the line that defines the gradient direction and size.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.beginLinearGradientStroke = function (colors, ratios, x0, y0, x1, y1) {
|
|
return this._setStroke(
|
|
new G.Stroke().linearGradient(colors, ratios, x0, y0, x1, y1)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Begins a radial gradient stroke. This ends the current sub-path. For example, the following code defines a red to
|
|
* blue radial gradient centered at (100, 100), with a radius of 50, and draws a rectangle to display it:
|
|
*
|
|
* myGraphics.setStrokeStyle(10)
|
|
* .beginRadialGradientStroke(["#F00","#00F"], [0, 1], 100, 100, 0, 100, 100, 50)
|
|
* .drawRect(50, 90, 150, 110);
|
|
*
|
|
* A tiny API method "rs" also exists.
|
|
* @method beginRadialGradientStroke
|
|
* @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define
|
|
* a gradient drawing from red to blue.
|
|
* @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1,
|
|
* 0.9] would draw the first color to 10% then interpolating to the second color at 90%, then draw the second color
|
|
* to 100%.
|
|
* @param {Number} x0 Center position of the inner circle that defines the gradient.
|
|
* @param {Number} y0 Center position of the inner circle that defines the gradient.
|
|
* @param {Number} r0 Radius of the inner circle that defines the gradient.
|
|
* @param {Number} x1 Center position of the outer circle that defines the gradient.
|
|
* @param {Number} y1 Center position of the outer circle that defines the gradient.
|
|
* @param {Number} r1 Radius of the outer circle that defines the gradient.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.beginRadialGradientStroke = function (
|
|
colors,
|
|
ratios,
|
|
x0,
|
|
y0,
|
|
r0,
|
|
x1,
|
|
y1,
|
|
r1
|
|
) {
|
|
return this._setStroke(
|
|
new G.Stroke().radialGradient(colors, ratios, x0, y0, r0, x1, y1, r1)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Begins a pattern fill using the specified image. This ends the current sub-path. Note that unlike bitmap fills,
|
|
* strokes do not currently support a matrix parameter due to limitations in the canvas API. A tiny API method "bs"
|
|
* also exists.
|
|
* @method beginBitmapStroke
|
|
* @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement} image The Image, Canvas, or Video object to use
|
|
* as the pattern. Must be loaded prior to creating a bitmap fill, or the fill will be empty.
|
|
* @param {String} [repetition=repeat] Optional. Indicates whether to repeat the image in the fill area. One of
|
|
* "repeat", "repeat-x", "repeat-y", or "no-repeat". Defaults to "repeat".
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.beginBitmapStroke = function (image, repetition) {
|
|
// NOTE: matrix is not supported for stroke because transforms on strokes also affect the drawn stroke width.
|
|
return this._setStroke(new G.Stroke().bitmap(image, repetition));
|
|
};
|
|
|
|
/**
|
|
* Ends the current sub-path, and begins a new one with no stroke. Functionally identical to <code>beginStroke(null)</code>.
|
|
* A tiny API method "es" also exists.
|
|
* @method endStroke
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.endStroke = function () {
|
|
return this.beginStroke();
|
|
};
|
|
|
|
/**
|
|
* Maps the familiar ActionScript <code>curveTo()</code> method to the functionally similar {{#crossLink "Graphics/quadraticCurveTo"}}{{/crossLink}}
|
|
* method.
|
|
* @method quadraticCurveTo
|
|
* @param {Number} cpx
|
|
* @param {Number} cpy
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.curveTo = p.quadraticCurveTo;
|
|
|
|
/**
|
|
*
|
|
* Maps the familiar ActionScript <code>drawRect()</code> method to the functionally similar {{#crossLink "Graphics/rect"}}{{/crossLink}}
|
|
* method.
|
|
* @method drawRect
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} w Width of the rectangle
|
|
* @param {Number} h Height of the rectangle
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.drawRect = p.rect;
|
|
|
|
/**
|
|
* Draws a rounded rectangle with all corners with the specified radius.
|
|
* @method drawRoundRect
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} w
|
|
* @param {Number} h
|
|
* @param {Number} radius Corner radius.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.drawRoundRect = function (x, y, w, h, radius) {
|
|
return this.drawRoundRectComplex(
|
|
x,
|
|
y,
|
|
w,
|
|
h,
|
|
radius,
|
|
radius,
|
|
radius,
|
|
radius
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Draws a rounded rectangle with different corner radii. Supports positive and negative corner radii. A tiny API
|
|
* method "rc" also exists.
|
|
* @method drawRoundRectComplex
|
|
* @param {Number} x The horizontal coordinate to draw the round rect.
|
|
* @param {Number} y The vertical coordinate to draw the round rect.
|
|
* @param {Number} w The width of the round rect.
|
|
* @param {Number} h The height of the round rect.
|
|
* @param {Number} radiusTL Top left corner radius.
|
|
* @param {Number} radiusTR Top right corner radius.
|
|
* @param {Number} radiusBR Bottom right corner radius.
|
|
* @param {Number} radiusBL Bottom left corner radius.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.drawRoundRectComplex = function (
|
|
x,
|
|
y,
|
|
w,
|
|
h,
|
|
radiusTL,
|
|
radiusTR,
|
|
radiusBR,
|
|
radiusBL
|
|
) {
|
|
return this.append(
|
|
new G.RoundRect(x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Draws a circle with the specified radius at (x, y).
|
|
*
|
|
* var g = new createjs.Graphics();
|
|
* g.setStrokeStyle(1);
|
|
* g.beginStroke(createjs.Graphics.getRGB(0,0,0));
|
|
* g.beginFill(createjs.Graphics.getRGB(255,0,0));
|
|
* g.drawCircle(0,0,3);
|
|
*
|
|
* var s = new createjs.Shape(g);
|
|
* s.x = 100;
|
|
* s.y = 100;
|
|
*
|
|
* stage.addChild(s);
|
|
* stage.update();
|
|
*
|
|
* A tiny API method "dc" also exists.
|
|
* @method drawCircle
|
|
* @param {Number} x x coordinate center point of circle.
|
|
* @param {Number} y y coordinate center point of circle.
|
|
* @param {Number} radius Radius of circle.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.drawCircle = function (x, y, radius) {
|
|
return this.append(new G.Circle(x, y, radius));
|
|
};
|
|
|
|
/**
|
|
* Draws an ellipse (oval) with a specified width (w) and height (h). Similar to {{#crossLink "Graphics/drawCircle"}}{{/crossLink}},
|
|
* except the width and height can be different. A tiny API method "de" also exists.
|
|
* @method drawEllipse
|
|
* @param {Number} x The left coordinate point of the ellipse. Note that this is different from {{#crossLink "Graphics/drawCircle"}}{{/crossLink}}
|
|
* which draws from center.
|
|
* @param {Number} y The top coordinate point of the ellipse. Note that this is different from {{#crossLink "Graphics/drawCircle"}}{{/crossLink}}
|
|
* which draws from the center.
|
|
* @param {Number} w The height (horizontal diameter) of the ellipse. The horizontal radius will be half of this
|
|
* number.
|
|
* @param {Number} h The width (vertical diameter) of the ellipse. The vertical radius will be half of this number.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.drawEllipse = function (x, y, w, h) {
|
|
return this.append(new G.Ellipse(x, y, w, h));
|
|
};
|
|
|
|
/**
|
|
* Draws a star if pointSize is greater than 0, or a regular polygon if pointSize is 0 with the specified number of
|
|
* points. For example, the following code will draw a familiar 5 pointed star shape centered at 100, 100 and with a
|
|
* radius of 50:
|
|
*
|
|
* myGraphics.beginFill("#FF0").drawPolyStar(100, 100, 50, 5, 0.6, -90);
|
|
* // Note: -90 makes the first point vertical
|
|
*
|
|
* A tiny API method "dp" also exists.
|
|
*
|
|
* @method drawPolyStar
|
|
* @param {Number} x Position of the center of the shape.
|
|
* @param {Number} y Position of the center of the shape.
|
|
* @param {Number} radius The outer radius of the shape.
|
|
* @param {Number} sides The number of points on the star or sides on the polygon.
|
|
* @param {Number} pointSize The depth or "pointy-ness" of the star points. A pointSize of 0 will draw a regular
|
|
* polygon (no points), a pointSize of 1 will draw nothing because the points are infinitely pointy.
|
|
* @param {Number} angle The angle of the first point / corner. For example a value of 0 will draw the first point
|
|
* directly to the right of the center.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.drawPolyStar = function (x, y, radius, sides, pointSize, angle) {
|
|
return this.append(new G.PolyStar(x, y, radius, sides, pointSize, angle));
|
|
};
|
|
|
|
// TODO: deprecated.
|
|
/**
|
|
* Removed in favour of using custom command objects with {{#crossLink "Graphics/append"}}{{/crossLink}}.
|
|
* @method inject
|
|
* @deprecated
|
|
**/
|
|
|
|
/**
|
|
* Appends a graphics command object to the graphics queue. Command objects expose an "exec" method
|
|
* that accepts two parameters: the Context2D to operate on, and an arbitrary data object passed into
|
|
* {{#crossLink "Graphics/draw"}}{{/crossLink}}. The latter will usually be the Shape instance that called draw.
|
|
*
|
|
* This method is used internally by Graphics methods, such as drawCircle, but can also be used directly to insert
|
|
* built-in or custom graphics commands. For example:
|
|
*
|
|
* // attach data to our shape, so we can access it during the draw:
|
|
* myShape.color = "red";
|
|
*
|
|
* // append a Circle command object:
|
|
* myShape.graphics.append(new createjs.Graphics.Circle(50, 50, 30));
|
|
*
|
|
* // append a custom command object with an exec method that sets the fill style
|
|
* // based on the shape's data, and then fills the circle.
|
|
* myShape.graphics.append({exec:function(ctx, shape) {
|
|
* ctx.fillStyle = shape.color;
|
|
* ctx.fill();
|
|
* }});
|
|
*
|
|
* @method append
|
|
* @param {Object} command A graphics command object exposing an "exec" method.
|
|
* @param {boolean} clean The clean param is primarily for internal use. A value of true indicates that a command does not generate a path that should be stroked or filled.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.append = function (command, clean) {
|
|
this._activeInstructions.push(command);
|
|
this.command = command;
|
|
if (!clean) {
|
|
this._dirty = true;
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Decodes a compact encoded path string into a series of draw instructions.
|
|
* This format is not intended to be human readable, and is meant for use by authoring tools.
|
|
* The format uses a base64 character set, with each character representing 6 bits, to define a series of draw
|
|
* commands.
|
|
*
|
|
* Each command is comprised of a single "header" character followed by a variable number of alternating x and y
|
|
* position values. Reading the header bits from left to right (most to least significant): bits 1 to 3 specify the
|
|
* type of operation (0-moveTo, 1-lineTo, 2-quadraticCurveTo, 3-bezierCurveTo, 4-closePath, 5-7 unused). Bit 4
|
|
* indicates whether position values use 12 bits (2 characters) or 18 bits (3 characters), with a one indicating the
|
|
* latter. Bits 5 and 6 are currently unused.
|
|
*
|
|
* Following the header is a series of 0 (closePath), 2 (moveTo, lineTo), 4 (quadraticCurveTo), or 6 (bezierCurveTo)
|
|
* parameters. These parameters are alternating x/y positions represented by 2 or 3 characters (as indicated by the
|
|
* 4th bit in the command char). These characters consist of a 1 bit sign (1 is negative, 0 is positive), followed
|
|
* by an 11 (2 char) or 17 (3 char) bit integer value. All position values are in tenths of a pixel. Except in the
|
|
* case of move operations which are absolute, this value is a delta from the previous x or y position (as
|
|
* appropriate).
|
|
*
|
|
* For example, the string "A3cAAMAu4AAA" represents a line starting at -150,0 and ending at 150,0.
|
|
* <br />A - bits 000000. First 3 bits (000) indicate a moveTo operation. 4th bit (0) indicates 2 chars per
|
|
* parameter.
|
|
* <br />n0 - 110111011100. Absolute x position of -150.0px. First bit indicates a negative value, remaining bits
|
|
* indicate 1500 tenths of a pixel.
|
|
* <br />AA - 000000000000. Absolute y position of 0.
|
|
* <br />I - 001100. First 3 bits (001) indicate a lineTo operation. 4th bit (1) indicates 3 chars per parameter.
|
|
* <br />Au4 - 000000101110111000. An x delta of 300.0px, which is added to the previous x value of -150.0px to
|
|
* provide an absolute position of +150.0px.
|
|
* <br />AAA - 000000000000000000. A y delta value of 0.
|
|
*
|
|
* A tiny API method "p" also exists.
|
|
* @method decodePath
|
|
* @param {String} str The path string to decode.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.decodePath = function (str) {
|
|
var instructions = [
|
|
this.moveTo,
|
|
this.lineTo,
|
|
this.quadraticCurveTo,
|
|
this.bezierCurveTo,
|
|
this.closePath,
|
|
];
|
|
var paramCount = [2, 2, 4, 6, 0];
|
|
var i = 0,
|
|
l = str.length;
|
|
var params = [];
|
|
var x = 0,
|
|
y = 0;
|
|
var base64 = Graphics.BASE_64;
|
|
|
|
while (i < l) {
|
|
var c = str.charAt(i);
|
|
var n = base64[c];
|
|
var fi = n >> 3; // highest order bits 1-3 code for operation.
|
|
var f = instructions[fi];
|
|
// check that we have a valid instruction & that the unused bits are empty:
|
|
if (!f || n & 3) {
|
|
throw "bad path data (@" + i + "): " + c;
|
|
}
|
|
var pl = paramCount[fi];
|
|
if (!fi) {
|
|
x = y = 0;
|
|
} // move operations reset the position.
|
|
params.length = 0;
|
|
i++;
|
|
var charCount = ((n >> 2) & 1) + 2; // 4th header bit indicates number size for this operation.
|
|
for (var p = 0; p < pl; p++) {
|
|
var num = base64[str.charAt(i)];
|
|
var sign = num >> 5 ? -1 : 1;
|
|
num = ((num & 31) << 6) | base64[str.charAt(i + 1)];
|
|
if (charCount == 3) {
|
|
num = (num << 6) | base64[str.charAt(i + 2)];
|
|
}
|
|
num = (sign * num) / 10;
|
|
if (p % 2) {
|
|
x = num += x;
|
|
} else {
|
|
y = num += y;
|
|
}
|
|
params[p] = num;
|
|
i += charCount;
|
|
}
|
|
f.apply(this, params);
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Stores all graphics commands so they won't be executed in future draws. Calling store() a second time adds to
|
|
* the existing store. This also affects `drawAsPath()`.
|
|
*
|
|
* This is useful in cases where you are creating vector graphics in an iterative manner (ex. generative art), so
|
|
* that only new graphics need to be drawn (which can provide huge performance benefits), but you wish to retain all
|
|
* of the vector instructions for later use (ex. scaling, modifying, or exporting).
|
|
*
|
|
* Note that calling store() will force the active path (if any) to be ended in a manner similar to changing
|
|
* the fill or stroke.
|
|
*
|
|
* For example, consider a application where the user draws lines with the mouse. As each line segment (or collection of
|
|
* segments) are added to a Shape, it can be rasterized using {{#crossLink "DisplayObject/updateCache"}}{{/crossLink}},
|
|
* and then stored, so that it can be redrawn at a different scale when the application is resized, or exported to SVG.
|
|
*
|
|
* // set up cache:
|
|
* myShape.cache(0,0,500,500,scale);
|
|
*
|
|
* // when the user drags, draw a new line:
|
|
* myShape.graphics.moveTo(oldX,oldY).lineTo(newX,newY);
|
|
* // then draw it into the existing cache:
|
|
* myShape.updateCache("source-over");
|
|
* // store the new line, so it isn't redrawn next time:
|
|
* myShape.store();
|
|
*
|
|
* // then, when the window resizes, we can re-render at a different scale:
|
|
* // first, unstore all our lines:
|
|
* myShape.unstore();
|
|
* // then cache using the new scale:
|
|
* myShape.cache(0,0,500,500,newScale);
|
|
* // finally, store the existing commands again:
|
|
* myShape.store();
|
|
*
|
|
* @method store
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.store = function () {
|
|
this._updateInstructions(true);
|
|
this._storeIndex = this._instructions.length;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Unstores any graphics commands that were previously stored using {{#crossLink "Graphics/store"}}{{/crossLink}}
|
|
* so that they will be executed in subsequent draw calls.
|
|
*
|
|
* @method unstore
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.unstore = function () {
|
|
this._storeIndex = 0;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Returns a clone of this Graphics instance. Note that the individual command objects are not cloned.
|
|
* @method clone
|
|
* @return {Graphics} A clone of the current Graphics instance.
|
|
**/
|
|
p.clone = function () {
|
|
var o = new Graphics();
|
|
o.command = this.command;
|
|
o._stroke = this._stroke;
|
|
o._strokeStyle = this._strokeStyle;
|
|
o._strokeDash = this._strokeDash;
|
|
o._strokeIgnoreScale = this._strokeIgnoreScale;
|
|
o._fill = this._fill;
|
|
o._instructions = this._instructions.slice();
|
|
o._commitIndex = this._commitIndex;
|
|
o._activeInstructions = this._activeInstructions.slice();
|
|
o._dirty = this._dirty;
|
|
o._storeIndex = this._storeIndex;
|
|
return o;
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[Graphics]";
|
|
};
|
|
|
|
// tiny API:
|
|
/**
|
|
* Shortcut to moveTo.
|
|
* @method mt
|
|
* @param {Number} x The x coordinate the drawing point should move to.
|
|
* @param {Number} y The y coordinate the drawing point should move to.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls).
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.mt = p.moveTo;
|
|
|
|
/**
|
|
* Shortcut to lineTo.
|
|
* @method lt
|
|
* @param {Number} x The x coordinate the drawing point should draw to.
|
|
* @param {Number} y The y coordinate the drawing point should draw to.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.lt = p.lineTo;
|
|
|
|
/**
|
|
* Shortcut to arcTo.
|
|
* @method at
|
|
* @param {Number} x1
|
|
* @param {Number} y1
|
|
* @param {Number} x2
|
|
* @param {Number} y2
|
|
* @param {Number} radius
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.at = p.arcTo;
|
|
|
|
/**
|
|
* Shortcut to bezierCurveTo.
|
|
* @method bt
|
|
* @param {Number} cp1x
|
|
* @param {Number} cp1y
|
|
* @param {Number} cp2x
|
|
* @param {Number} cp2y
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.bt = p.bezierCurveTo;
|
|
|
|
/**
|
|
* Shortcut to quadraticCurveTo / curveTo.
|
|
* @method qt
|
|
* @param {Number} cpx
|
|
* @param {Number} cpy
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @protected
|
|
* @chainable
|
|
**/
|
|
p.qt = p.quadraticCurveTo;
|
|
|
|
/**
|
|
* Shortcut to arc.
|
|
* @method a
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} radius
|
|
* @param {Number} startAngle Measured in radians.
|
|
* @param {Number} endAngle Measured in radians.
|
|
* @param {Boolean} anticlockwise
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @protected
|
|
* @chainable
|
|
**/
|
|
p.a = p.arc;
|
|
|
|
/**
|
|
* Shortcut to rect.
|
|
* @method r
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} w Width of the rectangle
|
|
* @param {Number} h Height of the rectangle
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.r = p.rect;
|
|
|
|
/**
|
|
* Shortcut to closePath.
|
|
* @method cp
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.cp = p.closePath;
|
|
|
|
/**
|
|
* Shortcut to clear.
|
|
* @method c
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.c = p.clear;
|
|
|
|
/**
|
|
* Shortcut to beginFill.
|
|
* @method f
|
|
* @param {String} color A CSS compatible color value (ex. "red", "#FF0000", or "rgba(255,0,0,0.5)"). Setting to
|
|
* null will result in no fill.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.f = p.beginFill;
|
|
|
|
/**
|
|
* Shortcut to beginLinearGradientFill.
|
|
* @method lf
|
|
* @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define a gradient
|
|
* drawing from red to blue.
|
|
* @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, 0.9] would draw
|
|
* the first color to 10% then interpolating to the second color at 90%.
|
|
* @param {Number} x0 The position of the first point defining the line that defines the gradient direction and size.
|
|
* @param {Number} y0 The position of the first point defining the line that defines the gradient direction and size.
|
|
* @param {Number} x1 The position of the second point defining the line that defines the gradient direction and size.
|
|
* @param {Number} y1 The position of the second point defining the line that defines the gradient direction and size.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.lf = p.beginLinearGradientFill;
|
|
|
|
/**
|
|
* Shortcut to beginRadialGradientFill.
|
|
* @method rf
|
|
* @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define
|
|
* a gradient drawing from red to blue.
|
|
* @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1,
|
|
* 0.9] would draw the first color to 10% then interpolating to the second color at 90%.
|
|
* @param {Number} x0 Center position of the inner circle that defines the gradient.
|
|
* @param {Number} y0 Center position of the inner circle that defines the gradient.
|
|
* @param {Number} r0 Radius of the inner circle that defines the gradient.
|
|
* @param {Number} x1 Center position of the outer circle that defines the gradient.
|
|
* @param {Number} y1 Center position of the outer circle that defines the gradient.
|
|
* @param {Number} r1 Radius of the outer circle that defines the gradient.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.rf = p.beginRadialGradientFill;
|
|
|
|
/**
|
|
* Shortcut to beginBitmapFill.
|
|
* @method bf
|
|
* @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement} image The Image, Canvas, or Video object to use
|
|
* as the pattern.
|
|
* @param {String} repetition Optional. Indicates whether to repeat the image in the fill area. One of "repeat",
|
|
* "repeat-x", "repeat-y", or "no-repeat". Defaults to "repeat". Note that Firefox does not support "repeat-x" or
|
|
* "repeat-y" (latest tests were in FF 20.0), and will default to "repeat".
|
|
* @param {Matrix2D} matrix Optional. Specifies a transformation matrix for the bitmap fill. This transformation
|
|
* will be applied relative to the parent transform.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.bf = p.beginBitmapFill;
|
|
|
|
/**
|
|
* Shortcut to endFill.
|
|
* @method ef
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.ef = p.endFill;
|
|
|
|
/**
|
|
* Shortcut to setStrokeStyle.
|
|
* @method ss
|
|
* @param {Number} thickness The width of the stroke.
|
|
* @param {String | Number} [caps=0] Indicates the type of caps to use at the end of lines. One of butt,
|
|
* round, or square. Defaults to "butt". Also accepts the values 0 (butt), 1 (round), and 2 (square) for use with
|
|
* the tiny API.
|
|
* @param {String | Number} [joints=0] Specifies the type of joints that should be used where two lines meet.
|
|
* One of bevel, round, or miter. Defaults to "miter". Also accepts the values 0 (miter), 1 (round), and 2 (bevel)
|
|
* for use with the tiny API.
|
|
* @param {Number} [miterLimit=10] If joints is set to "miter", then you can specify a miter limit ratio which
|
|
* controls at what point a mitered joint will be clipped.
|
|
* @param {Boolean} [ignoreScale=false] If true, the stroke will be drawn at the specified thickness regardless
|
|
* of active transformations.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.ss = p.setStrokeStyle;
|
|
|
|
/**
|
|
* Shortcut to setStrokeDash.
|
|
* @method sd
|
|
* @param {Array} [segments] An array specifying the dash pattern, alternating between line and gap.
|
|
* For example, [20,10] would create a pattern of 20 pixel lines with 10 pixel gaps between them.
|
|
* Passing null or an empty array will clear any existing dash.
|
|
* @param {Number} [offset=0] The offset of the dash pattern. For example, you could increment this value to create a "marching ants" effect.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.sd = p.setStrokeDash;
|
|
|
|
/**
|
|
* Shortcut to beginStroke.
|
|
* @method s
|
|
* @param {String} color A CSS compatible color value (ex. "#FF0000", "red", or "rgba(255,0,0,0.5)"). Setting to
|
|
* null will result in no stroke.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.s = p.beginStroke;
|
|
|
|
/**
|
|
* Shortcut to beginLinearGradientStroke.
|
|
* @method ls
|
|
* @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define
|
|
* a gradient drawing from red to blue.
|
|
* @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1,
|
|
* 0.9] would draw the first color to 10% then interpolating to the second color at 90%.
|
|
* @param {Number} x0 The position of the first point defining the line that defines the gradient direction and size.
|
|
* @param {Number} y0 The position of the first point defining the line that defines the gradient direction and size.
|
|
* @param {Number} x1 The position of the second point defining the line that defines the gradient direction and size.
|
|
* @param {Number} y1 The position of the second point defining the line that defines the gradient direction and size.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.ls = p.beginLinearGradientStroke;
|
|
|
|
/**
|
|
* Shortcut to beginRadialGradientStroke.
|
|
* @method rs
|
|
* @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define
|
|
* a gradient drawing from red to blue.
|
|
* @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1,
|
|
* 0.9] would draw the first color to 10% then interpolating to the second color at 90%, then draw the second color
|
|
* to 100%.
|
|
* @param {Number} x0 Center position of the inner circle that defines the gradient.
|
|
* @param {Number} y0 Center position of the inner circle that defines the gradient.
|
|
* @param {Number} r0 Radius of the inner circle that defines the gradient.
|
|
* @param {Number} x1 Center position of the outer circle that defines the gradient.
|
|
* @param {Number} y1 Center position of the outer circle that defines the gradient.
|
|
* @param {Number} r1 Radius of the outer circle that defines the gradient.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.rs = p.beginRadialGradientStroke;
|
|
|
|
/**
|
|
* Shortcut to beginBitmapStroke.
|
|
* @method bs
|
|
* @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement} image The Image, Canvas, or Video object to use
|
|
* as the pattern.
|
|
* @param {String} [repetition=repeat] Optional. Indicates whether to repeat the image in the fill area. One of
|
|
* "repeat", "repeat-x", "repeat-y", or "no-repeat". Defaults to "repeat".
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.bs = p.beginBitmapStroke;
|
|
|
|
/**
|
|
* Shortcut to endStroke.
|
|
* @method es
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.es = p.endStroke;
|
|
|
|
/**
|
|
* Shortcut to drawRect.
|
|
* @method dr
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} w Width of the rectangle
|
|
* @param {Number} h Height of the rectangle
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.dr = p.drawRect;
|
|
|
|
/**
|
|
* Shortcut to drawRoundRect.
|
|
* @method rr
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} w
|
|
* @param {Number} h
|
|
* @param {Number} radius Corner radius.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.rr = p.drawRoundRect;
|
|
|
|
/**
|
|
* Shortcut to drawRoundRectComplex.
|
|
* @method rc
|
|
* @param {Number} x The horizontal coordinate to draw the round rect.
|
|
* @param {Number} y The vertical coordinate to draw the round rect.
|
|
* @param {Number} w The width of the round rect.
|
|
* @param {Number} h The height of the round rect.
|
|
* @param {Number} radiusTL Top left corner radius.
|
|
* @param {Number} radiusTR Top right corner radius.
|
|
* @param {Number} radiusBR Bottom right corner radius.
|
|
* @param {Number} radiusBL Bottom left corner radius.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.rc = p.drawRoundRectComplex;
|
|
|
|
/**
|
|
* Shortcut to drawCircle.
|
|
* @method dc
|
|
* @param {Number} x x coordinate center point of circle.
|
|
* @param {Number} y y coordinate center point of circle.
|
|
* @param {Number} radius Radius of circle.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.dc = p.drawCircle;
|
|
|
|
/**
|
|
* Shortcut to drawEllipse.
|
|
* @method de
|
|
* @param {Number} x The left coordinate point of the ellipse. Note that this is different from {{#crossLink "Graphics/drawCircle"}}{{/crossLink}}
|
|
* which draws from center.
|
|
* @param {Number} y The top coordinate point of the ellipse. Note that this is different from {{#crossLink "Graphics/drawCircle"}}{{/crossLink}}
|
|
* which draws from the center.
|
|
* @param {Number} w The height (horizontal diameter) of the ellipse. The horizontal radius will be half of this
|
|
* number.
|
|
* @param {Number} h The width (vertical diameter) of the ellipse. The vertical radius will be half of this number.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.de = p.drawEllipse;
|
|
|
|
/**
|
|
* Shortcut to drawPolyStar.
|
|
* @method dp
|
|
* @param {Number} x Position of the center of the shape.
|
|
* @param {Number} y Position of the center of the shape.
|
|
* @param {Number} radius The outer radius of the shape.
|
|
* @param {Number} sides The number of points on the star or sides on the polygon.
|
|
* @param {Number} pointSize The depth or "pointy-ness" of the star points. A pointSize of 0 will draw a regular
|
|
* polygon (no points), a pointSize of 1 will draw nothing because the points are infinitely pointy.
|
|
* @param {Number} angle The angle of the first point / corner. For example a value of 0 will draw the first point
|
|
* directly to the right of the center.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.dp = p.drawPolyStar;
|
|
|
|
/**
|
|
* Shortcut to decodePath.
|
|
* @method p
|
|
* @param {String} str The path string to decode.
|
|
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
* @protected
|
|
**/
|
|
p.p = p.decodePath;
|
|
|
|
// private methods:
|
|
/**
|
|
* @method _updateInstructions
|
|
* @param commit
|
|
* @protected
|
|
**/
|
|
p._updateInstructions = function (commit) {
|
|
var instr = this._instructions,
|
|
active = this._activeInstructions,
|
|
commitIndex = this._commitIndex;
|
|
|
|
if (this._dirty && active.length) {
|
|
instr.length = commitIndex; // remove old, uncommitted commands
|
|
instr.push(Graphics.beginCmd);
|
|
|
|
var l = active.length,
|
|
ll = instr.length;
|
|
instr.length = ll + l;
|
|
for (var i = 0; i < l; i++) {
|
|
instr[i + ll] = active[i];
|
|
}
|
|
|
|
if (this._fill) {
|
|
instr.push(this._fill);
|
|
}
|
|
if (this._stroke) {
|
|
// doesn't need to be re-applied if it hasn't changed.
|
|
if (this._strokeDash !== this._oldStrokeDash) {
|
|
this._oldStrokeDash = this._strokeDash;
|
|
instr.push(this._strokeDash);
|
|
}
|
|
if (this._strokeStyle !== this._oldStrokeStyle) {
|
|
this._oldStrokeStyle = this._strokeStyle;
|
|
instr.push(this._strokeStyle);
|
|
}
|
|
instr.push(this._stroke);
|
|
}
|
|
|
|
this._dirty = false;
|
|
}
|
|
|
|
if (commit) {
|
|
active.length = 0;
|
|
this._commitIndex = instr.length;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _setFill
|
|
* @param fill
|
|
* @protected
|
|
**/
|
|
p._setFill = function (fill) {
|
|
this._updateInstructions(true);
|
|
this.command = this._fill = fill;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* @method _setStroke
|
|
* @param stroke
|
|
* @protected
|
|
**/
|
|
p._setStroke = function (stroke) {
|
|
this._updateInstructions(true);
|
|
if ((this.command = this._stroke = stroke)) {
|
|
stroke.ignoreScale = this._strokeIgnoreScale;
|
|
}
|
|
return this;
|
|
};
|
|
|
|
// Command Objects:
|
|
/**
|
|
* @namespace Graphics
|
|
*/
|
|
/**
|
|
* Graphics command object. See {{#crossLink "Graphics/lineTo"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. See {{#crossLink "Graphics"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class LineTo
|
|
* @constructor
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
**/
|
|
/**
|
|
* @property x
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property y
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* Execute the Graphics command in the provided Canvas context.
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx The canvas rendering context
|
|
*/
|
|
(G.LineTo = function (x, y) {
|
|
this.x = x;
|
|
this.y = y;
|
|
}).prototype.exec = function (ctx) {
|
|
ctx.lineTo(this.x, this.y);
|
|
};
|
|
|
|
/**
|
|
* Graphics command object. See {{#crossLink "Graphics/moveTo"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class MoveTo
|
|
* @constructor
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
**/
|
|
/**
|
|
* @property x
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property y
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx
|
|
*/
|
|
(G.MoveTo = function (x, y) {
|
|
this.x = x;
|
|
this.y = y;
|
|
}).prototype.exec = function (ctx) {
|
|
ctx.moveTo(this.x, this.y);
|
|
};
|
|
|
|
/**
|
|
* Graphics command object. See {{#crossLink "Graphics/arcTo"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class ArcTo
|
|
* @constructor
|
|
* @param {Number} x1
|
|
* @param {Number} y1
|
|
* @param {Number} x2
|
|
* @param {Number} y2
|
|
* @param {Number} radius
|
|
**/
|
|
/**
|
|
* @property x1
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property y1
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property x2
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property y2
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property radius
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* Execute the Graphics command in the provided Canvas context.
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx The canvas rendering context
|
|
*/
|
|
(G.ArcTo = function (x1, y1, x2, y2, radius) {
|
|
this.x1 = x1;
|
|
this.y1 = y1;
|
|
this.x2 = x2;
|
|
this.y2 = y2;
|
|
this.radius = radius;
|
|
}).prototype.exec = function (ctx) {
|
|
ctx.arcTo(this.x1, this.y1, this.x2, this.y2, this.radius);
|
|
};
|
|
|
|
/**
|
|
* Graphics command object. See {{#crossLink "Graphics/arc"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class Arc
|
|
* @constructor
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} radius
|
|
* @param {Number} startAngle
|
|
* @param {Number} endAngle
|
|
* @param {Number} anticlockwise
|
|
**/
|
|
/**
|
|
* @property x
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property y
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property radius
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property startAngle
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property endAngle
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property anticlockwise
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* Execute the Graphics command in the provided Canvas context.
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx The canvas rendering context
|
|
*/
|
|
(G.Arc = function (x, y, radius, startAngle, endAngle, anticlockwise) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.radius = radius;
|
|
this.startAngle = startAngle;
|
|
this.endAngle = endAngle;
|
|
this.anticlockwise = !!anticlockwise;
|
|
}).prototype.exec = function (ctx) {
|
|
ctx.arc(
|
|
this.x,
|
|
this.y,
|
|
this.radius,
|
|
this.startAngle,
|
|
this.endAngle,
|
|
this.anticlockwise
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Graphics command object. See {{#crossLink "Graphics/quadraticCurveTo"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class QuadraticCurveTo
|
|
* @constructor
|
|
* @param {Number} cpx
|
|
* @param {Number} cpy
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
**/
|
|
/**
|
|
* @property cpx
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property cpy
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property x
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property y
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* Execute the Graphics command in the provided Canvas context.
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx The canvas rendering context
|
|
*/
|
|
(G.QuadraticCurveTo = function (cpx, cpy, x, y) {
|
|
this.cpx = cpx;
|
|
this.cpy = cpy;
|
|
this.x = x;
|
|
this.y = y;
|
|
}).prototype.exec = function (ctx) {
|
|
ctx.quadraticCurveTo(this.cpx, this.cpy, this.x, this.y);
|
|
};
|
|
|
|
/**
|
|
* Graphics command object. See {{#crossLink "Graphics/bezierCurveTo"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class BezierCurveTo
|
|
* @constructor
|
|
* @param {Number} cp1x
|
|
* @param {Number} cp1y
|
|
* @param {Number} cp2x
|
|
* @param {Number} cp2y
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
**/
|
|
/**
|
|
* @property cp1x
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property cp1y
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property cp2x
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property cp2y
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property x
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property y
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* Execute the Graphics command in the provided Canvas context.
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx The canvas rendering context
|
|
*/
|
|
(G.BezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) {
|
|
this.cp1x = cp1x;
|
|
this.cp1y = cp1y;
|
|
this.cp2x = cp2x;
|
|
this.cp2y = cp2y;
|
|
this.x = x;
|
|
this.y = y;
|
|
}).prototype.exec = function (ctx) {
|
|
ctx.bezierCurveTo(
|
|
this.cp1x,
|
|
this.cp1y,
|
|
this.cp2x,
|
|
this.cp2y,
|
|
this.x,
|
|
this.y
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Graphics command object. See {{#crossLink "Graphics/rect"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class Rect
|
|
* @constructor
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} w
|
|
* @param {Number} h
|
|
**/
|
|
/**
|
|
* @property x
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property y
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property w
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property h
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* Execute the Graphics command in the provided Canvas context.
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx The canvas rendering context
|
|
*/
|
|
(G.Rect = function (x, y, w, h) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.w = w;
|
|
this.h = h;
|
|
}).prototype.exec = function (ctx) {
|
|
ctx.rect(this.x, this.y, this.w, this.h);
|
|
};
|
|
|
|
/**
|
|
* Graphics command object. See {{#crossLink "Graphics/closePath"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class ClosePath
|
|
* @constructor
|
|
**/
|
|
/**
|
|
* Execute the Graphics command in the provided Canvas context.
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx The canvas rendering context
|
|
*/
|
|
(G.ClosePath = function () {}).prototype.exec = function (ctx) {
|
|
ctx.closePath();
|
|
};
|
|
|
|
/**
|
|
* Graphics command object to begin a new path. See {{#crossLink "Graphics"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class BeginPath
|
|
* @constructor
|
|
**/
|
|
/**
|
|
* Execute the Graphics command in the provided Canvas context.
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx The canvas rendering context
|
|
*/
|
|
(G.BeginPath = function () {}).prototype.exec = function (ctx) {
|
|
ctx.beginPath();
|
|
};
|
|
|
|
/**
|
|
* Graphics command object. See {{#crossLink "Graphics/beginFill"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class Fill
|
|
* @constructor
|
|
* @param {Object} style A valid Context2D fillStyle.
|
|
* @param {Matrix2D} matrix
|
|
**/
|
|
/**
|
|
* A valid Context2D fillStyle.
|
|
* @property style
|
|
* @type Object
|
|
*/
|
|
/**
|
|
* @property matrix
|
|
* @type Matrix2D
|
|
*/
|
|
/**
|
|
* Execute the Graphics command in the provided Canvas context.
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx The canvas rendering context
|
|
*/
|
|
p = (G.Fill = function (style, matrix) {
|
|
this.style = style;
|
|
this.matrix = matrix;
|
|
}).prototype;
|
|
p.exec = function (ctx) {
|
|
if (!this.style) {
|
|
return;
|
|
}
|
|
ctx.fillStyle = this.style;
|
|
var mtx = this.matrix;
|
|
if (mtx) {
|
|
ctx.save();
|
|
ctx.transform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx, mtx.ty);
|
|
}
|
|
ctx.fill();
|
|
if (mtx) {
|
|
ctx.restore();
|
|
}
|
|
};
|
|
/**
|
|
* Creates a linear gradient style and assigns it to {{#crossLink "Fill/style:property"}}{{/crossLink}}.
|
|
* See {{#crossLink "Graphics/beginLinearGradientFill"}}{{/crossLink}} for more information.
|
|
* @method linearGradient
|
|
* @param {Array} colors
|
|
*
|
|
* @param {Array} ratios
|
|
* @param {Number} x0
|
|
* @param {Number} y0
|
|
* @param {Number} x1
|
|
* @param {Number} y1
|
|
* @return {Fill} Returns this Fill object for chaining or assignment.
|
|
*/
|
|
p.linearGradient = function (colors, ratios, x0, y0, x1, y1) {
|
|
var o = (this.style = Graphics._ctx.createLinearGradient(x0, y0, x1, y1));
|
|
for (var i = 0, l = colors.length; i < l; i++) {
|
|
o.addColorStop(ratios[i], colors[i]);
|
|
}
|
|
o.props = {
|
|
colors: colors,
|
|
ratios: ratios,
|
|
x0: x0,
|
|
y0: y0,
|
|
x1: x1,
|
|
y1: y1,
|
|
type: "linear",
|
|
};
|
|
return this;
|
|
};
|
|
/**
|
|
* Creates a radial gradient style and assigns it to {{#crossLink "Fill/style:property"}}{{/crossLink}}.
|
|
* See {{#crossLink "Graphics/beginRadialGradientFill"}}{{/crossLink}} for more information.
|
|
* @method radialGradient
|
|
* @param {Array} colors
|
|
* @param {Array} ratios
|
|
* @param {Number} x0
|
|
* @param {Number} y0
|
|
* @param {Number} r0
|
|
* @param {Number} x1
|
|
* @param {Number} y1
|
|
* @param {Number} r1
|
|
* @return {Fill} Returns this Fill object for chaining or assignment.
|
|
*/
|
|
p.radialGradient = function (colors, ratios, x0, y0, r0, x1, y1, r1) {
|
|
var o = (this.style = Graphics._ctx.createRadialGradient(
|
|
x0,
|
|
y0,
|
|
r0,
|
|
x1,
|
|
y1,
|
|
r1
|
|
));
|
|
for (var i = 0, l = colors.length; i < l; i++) {
|
|
o.addColorStop(ratios[i], colors[i]);
|
|
}
|
|
o.props = {
|
|
colors: colors,
|
|
ratios: ratios,
|
|
x0: x0,
|
|
y0: y0,
|
|
r0: r0,
|
|
x1: x1,
|
|
y1: y1,
|
|
r1: r1,
|
|
type: "radial",
|
|
};
|
|
return this;
|
|
};
|
|
/**
|
|
* Creates a bitmap fill style and assigns it to the {{#crossLink "Fill/style:property"}}{{/crossLink}}.
|
|
* See {{#crossLink "Graphics/beginBitmapFill"}}{{/crossLink}} for more information.
|
|
* @method bitmap
|
|
* @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement} image Must be loaded prior to creating a bitmap fill, or the fill will be empty.
|
|
* @param {String} [repetition] One of: repeat, repeat-x, repeat-y, or no-repeat.
|
|
* @return {Fill} Returns this Fill object for chaining or assignment.
|
|
*/
|
|
p.bitmap = function (image, repetition) {
|
|
if (image.naturalWidth || image.getContext || image.readyState >= 2) {
|
|
var o = (this.style = Graphics._ctx.createPattern(
|
|
image,
|
|
repetition || ""
|
|
));
|
|
o.props = { image: image, repetition: repetition, type: "bitmap" };
|
|
}
|
|
return this;
|
|
};
|
|
p.path = false;
|
|
|
|
/**
|
|
* Graphics command object. See {{#crossLink "Graphics/beginStroke"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class Stroke
|
|
* @constructor
|
|
* @param {Object} style A valid Context2D fillStyle.
|
|
* @param {Boolean} ignoreScale
|
|
**/
|
|
/**
|
|
* A valid Context2D strokeStyle.
|
|
* @property style
|
|
* @type Object
|
|
*/
|
|
/**
|
|
* @property ignoreScale
|
|
* @type Boolean
|
|
*/
|
|
/**
|
|
* Execute the Graphics command in the provided Canvas context.
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx The canvas rendering context
|
|
*/
|
|
p = (G.Stroke = function (style, ignoreScale) {
|
|
this.style = style;
|
|
this.ignoreScale = ignoreScale;
|
|
}).prototype;
|
|
p.exec = function (ctx) {
|
|
if (!this.style) {
|
|
return;
|
|
}
|
|
ctx.strokeStyle = this.style;
|
|
if (this.ignoreScale) {
|
|
ctx.save();
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
}
|
|
ctx.stroke();
|
|
if (this.ignoreScale) {
|
|
ctx.restore();
|
|
}
|
|
};
|
|
/**
|
|
* Creates a linear gradient style and assigns it to {{#crossLink "Stroke/style:property"}}{{/crossLink}}.
|
|
* See {{#crossLink "Graphics/beginLinearGradientStroke"}}{{/crossLink}} for more information.
|
|
* @method linearGradient
|
|
* @param {Array} colors
|
|
* @param {Array} ratios
|
|
* @param {Number} x0
|
|
* @param {Number} y0
|
|
* @param {Number} x1
|
|
* @param {Number} y1
|
|
* @return {Fill} Returns this Stroke object for chaining or assignment.
|
|
*/
|
|
p.linearGradient = G.Fill.prototype.linearGradient;
|
|
/**
|
|
* Creates a radial gradient style and assigns it to {{#crossLink "Stroke/style:property"}}{{/crossLink}}.
|
|
* See {{#crossLink "Graphics/beginRadialGradientStroke"}}{{/crossLink}} for more information.
|
|
* @method radialGradient
|
|
* @param {Array} colors
|
|
* @param {Array} ratios
|
|
* @param {Number} x0
|
|
* @param {Number} y0
|
|
* @param {Number} r0
|
|
* @param {Number} x1
|
|
* @param {Number} y1
|
|
* @param {Number} r1
|
|
* @return {Fill} Returns this Stroke object for chaining or assignment.
|
|
*/
|
|
p.radialGradient = G.Fill.prototype.radialGradient;
|
|
/**
|
|
* Creates a bitmap fill style and assigns it to {{#crossLink "Stroke/style:property"}}{{/crossLink}}.
|
|
* See {{#crossLink "Graphics/beginBitmapStroke"}}{{/crossLink}} for more information.
|
|
* @method bitmap
|
|
* @param {HTMLImageElement} image
|
|
* @param {String} [repetition] One of: repeat, repeat-x, repeat-y, or no-repeat.
|
|
* @return {Fill} Returns this Stroke object for chaining or assignment.
|
|
*/
|
|
p.bitmap = G.Fill.prototype.bitmap;
|
|
p.path = false;
|
|
|
|
/**
|
|
* Graphics command object. See {{#crossLink "Graphics/setStrokeStyle"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class StrokeStyle
|
|
* @constructor
|
|
* @param {Number} width
|
|
* @param {String} [caps=butt]
|
|
* @param {String} [joints=miter]
|
|
* @param {Number} [miterLimit=10]
|
|
* @param {Boolean} [ignoreScale=false]
|
|
**/
|
|
/**
|
|
* @property width
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* One of: butt, round, square
|
|
* @property caps
|
|
* @type String
|
|
*/
|
|
/**
|
|
* One of: round, bevel, miter
|
|
* @property joints
|
|
* @type String
|
|
*/
|
|
/**
|
|
* @property miterLimit
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* Execute the Graphics command in the provided Canvas context.
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx The canvas rendering context
|
|
*/
|
|
p = (G.StrokeStyle = function (width, caps, joints, miterLimit, ignoreScale) {
|
|
this.width = width;
|
|
this.caps = caps;
|
|
this.joints = joints;
|
|
this.miterLimit = miterLimit;
|
|
this.ignoreScale = ignoreScale;
|
|
}).prototype;
|
|
p.exec = function (ctx) {
|
|
ctx.lineWidth = this.width == null ? "1" : this.width;
|
|
ctx.lineCap =
|
|
this.caps == null
|
|
? "butt"
|
|
: isNaN(this.caps)
|
|
? this.caps
|
|
: Graphics.STROKE_CAPS_MAP[this.caps];
|
|
ctx.lineJoin =
|
|
this.joints == null
|
|
? "miter"
|
|
: isNaN(this.joints)
|
|
? this.joints
|
|
: Graphics.STROKE_JOINTS_MAP[this.joints];
|
|
ctx.miterLimit = this.miterLimit == null ? "10" : this.miterLimit;
|
|
ctx.ignoreScale = this.ignoreScale == null ? false : this.ignoreScale;
|
|
};
|
|
p.path = false;
|
|
|
|
/**
|
|
* Graphics command object. See {{#crossLink "Graphics/setStrokeDash"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class StrokeDash
|
|
* @constructor
|
|
* @param {Array} [segments]
|
|
* @param {Number} [offset=0]
|
|
**/
|
|
/**
|
|
* @property segments
|
|
* @type Array
|
|
*/
|
|
/**
|
|
* @property offset
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* Execute the Graphics command in the provided Canvas context.
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx The canvas rendering context
|
|
*/
|
|
(G.StrokeDash = function (segments, offset) {
|
|
this.segments = segments;
|
|
this.offset = offset || 0;
|
|
}).prototype.exec = function (ctx) {
|
|
if (ctx.setLineDash) {
|
|
// feature detection.
|
|
ctx.setLineDash(this.segments || G.StrokeDash.EMPTY_SEGMENTS); // instead of [] to reduce churn.
|
|
ctx.lineDashOffset = this.offset || 0;
|
|
}
|
|
};
|
|
/**
|
|
* The default value for segments (ie. no dash).
|
|
* @property EMPTY_SEGMENTS
|
|
* @static
|
|
* @final
|
|
* @readonly
|
|
* @protected
|
|
* @type {Array}
|
|
**/
|
|
G.StrokeDash.EMPTY_SEGMENTS = [];
|
|
|
|
/**
|
|
* Graphics command object. See {{#crossLink "Graphics/drawRoundRectComplex"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class RoundRect
|
|
* @constructor
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} w
|
|
* @param {Number} h
|
|
* @param {Number} radiusTL
|
|
* @param {Number} radiusTR
|
|
* @param {Number} radiusBR
|
|
* @param {Number} radiusBL
|
|
**/
|
|
/**
|
|
* @property x
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property y
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property w
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property h
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property radiusTL
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property radiusTR
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property radiusBR
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property radiusBL
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* Execute the Graphics command in the provided Canvas context.
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx The canvas rendering context
|
|
*/
|
|
(G.RoundRect = function (x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.w = w;
|
|
this.h = h;
|
|
this.radiusTL = radiusTL;
|
|
this.radiusTR = radiusTR;
|
|
this.radiusBR = radiusBR;
|
|
this.radiusBL = radiusBL;
|
|
}).prototype.exec = function (ctx) {
|
|
var max = (w < h ? w : h) / 2;
|
|
var mTL = 0,
|
|
mTR = 0,
|
|
mBR = 0,
|
|
mBL = 0;
|
|
var x = this.x,
|
|
y = this.y,
|
|
w = this.w,
|
|
h = this.h;
|
|
var rTL = this.radiusTL,
|
|
rTR = this.radiusTR,
|
|
rBR = this.radiusBR,
|
|
rBL = this.radiusBL;
|
|
|
|
if (rTL < 0) {
|
|
rTL *= mTL = -1;
|
|
}
|
|
if (rTL > max) {
|
|
rTL = max;
|
|
}
|
|
if (rTR < 0) {
|
|
rTR *= mTR = -1;
|
|
}
|
|
if (rTR > max) {
|
|
rTR = max;
|
|
}
|
|
if (rBR < 0) {
|
|
rBR *= mBR = -1;
|
|
}
|
|
if (rBR > max) {
|
|
rBR = max;
|
|
}
|
|
if (rBL < 0) {
|
|
rBL *= mBL = -1;
|
|
}
|
|
if (rBL > max) {
|
|
rBL = max;
|
|
}
|
|
|
|
ctx.moveTo(x + w - rTR, y);
|
|
ctx.arcTo(x + w + rTR * mTR, y - rTR * mTR, x + w, y + rTR, rTR);
|
|
ctx.lineTo(x + w, y + h - rBR);
|
|
ctx.arcTo(x + w + rBR * mBR, y + h + rBR * mBR, x + w - rBR, y + h, rBR);
|
|
ctx.lineTo(x + rBL, y + h);
|
|
ctx.arcTo(x - rBL * mBL, y + h + rBL * mBL, x, y + h - rBL, rBL);
|
|
ctx.lineTo(x, y + rTL);
|
|
ctx.arcTo(x - rTL * mTL, y - rTL * mTL, x + rTL, y, rTL);
|
|
ctx.closePath();
|
|
};
|
|
|
|
/**
|
|
* Graphics command object. See {{#crossLink "Graphics/drawCircle"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class Circle
|
|
* @constructor
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} radius
|
|
**/
|
|
/**
|
|
* @property x
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property y
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property radius
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* Execute the Graphics command in the provided Canvas context.
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx The canvas rendering context
|
|
*/
|
|
(G.Circle = function (x, y, radius) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.radius = radius;
|
|
}).prototype.exec = function (ctx) {
|
|
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
|
};
|
|
|
|
/**
|
|
* Graphics command object. See {{#crossLink "Graphics/drawEllipse"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class Ellipse
|
|
* @constructor
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} w
|
|
* @param {Number} h
|
|
**/
|
|
/**
|
|
* @property x
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property y
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property w
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property h
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* Execute the Graphics command in the provided Canvas context.
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx The canvas rendering context
|
|
*/
|
|
(G.Ellipse = function (x, y, w, h) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.w = w;
|
|
this.h = h;
|
|
}).prototype.exec = function (ctx) {
|
|
var x = this.x,
|
|
y = this.y;
|
|
var w = this.w,
|
|
h = this.h;
|
|
|
|
var k = 0.5522848;
|
|
var ox = (w / 2) * k;
|
|
var oy = (h / 2) * k;
|
|
var xe = x + w;
|
|
var ye = y + h;
|
|
var xm = x + w / 2;
|
|
var ym = y + h / 2;
|
|
|
|
ctx.moveTo(x, ym);
|
|
ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
|
|
ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
|
|
ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
|
|
ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
|
|
};
|
|
|
|
/**
|
|
* Graphics command object. See {{#crossLink "Graphics/drawPolyStar"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information.
|
|
* @class PolyStar
|
|
* @constructor
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} radius
|
|
* @param {Number} sides
|
|
* @param {Number} pointSize
|
|
* @param {Number} angle
|
|
**/
|
|
/**
|
|
* @property x
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property y
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property radius
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property sides
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property pointSize
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* @property angle
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* Execute the Graphics command in the provided Canvas context.
|
|
* @method exec
|
|
* @param {CanvasRenderingContext2D} ctx The canvas rendering context
|
|
*/
|
|
(G.PolyStar = function (x, y, radius, sides, pointSize, angle) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.radius = radius;
|
|
this.sides = sides;
|
|
this.pointSize = pointSize;
|
|
this.angle = angle;
|
|
}).prototype.exec = function (ctx) {
|
|
var x = this.x,
|
|
y = this.y;
|
|
var radius = this.radius;
|
|
var angle = ((this.angle || 0) / 180) * Math.PI;
|
|
var sides = this.sides;
|
|
var ps = 1 - (this.pointSize || 0);
|
|
var a = Math.PI / sides;
|
|
|
|
ctx.moveTo(x + Math.cos(angle) * radius, y + Math.sin(angle) * radius);
|
|
for (var i = 0; i < sides; i++) {
|
|
angle += a;
|
|
if (ps != 1) {
|
|
ctx.lineTo(
|
|
x + Math.cos(angle) * radius * ps,
|
|
y + Math.sin(angle) * radius * ps
|
|
);
|
|
}
|
|
angle += a;
|
|
ctx.lineTo(x + Math.cos(angle) * radius, y + Math.sin(angle) * radius);
|
|
}
|
|
ctx.closePath();
|
|
};
|
|
|
|
// docced above.
|
|
Graphics.beginCmd = new G.BeginPath(); // so we don't have to instantiate multiple instances.
|
|
|
|
createjs.Graphics = Graphics;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// DisplayObject.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* DisplayObject is an abstract class that should not be constructed directly. Instead construct subclasses such as
|
|
* {{#crossLink "Container"}}{{/crossLink}}, {{#crossLink "Bitmap"}}{{/crossLink}}, and {{#crossLink "Shape"}}{{/crossLink}}.
|
|
* DisplayObject is the base class for all display classes in the EaselJS library. It defines the core properties and
|
|
* methods that are shared between all display objects, such as transformation properties (x, y, scaleX, scaleY, etc),
|
|
* caching, and mouse handlers.
|
|
* @class DisplayObject
|
|
* @extends EventDispatcher
|
|
* @constructor
|
|
**/
|
|
function DisplayObject() {
|
|
this.EventDispatcher_constructor();
|
|
|
|
// public properties:
|
|
/**
|
|
* The alpha (transparency) for this display object. 0 is fully transparent, 1 is fully opaque.
|
|
* @property alpha
|
|
* @type {Number}
|
|
* @default 1
|
|
**/
|
|
this.alpha = 1;
|
|
|
|
/**
|
|
* If a cache is active, this returns the canvas that holds the cached version of this display object. See {{#crossLink "cache"}}{{/crossLink}}
|
|
* for more information.
|
|
* @property cacheCanvas
|
|
* @type {HTMLCanvasElement | Object}
|
|
* @default null
|
|
* @readonly
|
|
**/
|
|
this.cacheCanvas = null;
|
|
|
|
/**
|
|
* Returns an ID number that uniquely identifies the current cache for this display object. This can be used to
|
|
* determine if the cache has changed since a previous check.
|
|
* @property cacheID
|
|
* @type {Number}
|
|
* @default 0
|
|
*/
|
|
this.cacheID = 0;
|
|
|
|
/**
|
|
* Unique ID for this display object. Makes display objects easier for some uses.
|
|
* @property id
|
|
* @type {Number}
|
|
* @default -1
|
|
**/
|
|
this.id = createjs.UID.get();
|
|
|
|
/**
|
|
* Indicates whether to include this object when running mouse interactions. Setting this to `false` for children
|
|
* of a {{#crossLink "Container"}}{{/crossLink}} will cause events on the Container to not fire when that child is
|
|
* clicked. Setting this property to `false` does not prevent the {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}}
|
|
* method from returning the child.
|
|
*
|
|
* <strong>Note:</strong> In EaselJS 0.7.0, the mouseEnabled property will not work properly with nested Containers. Please
|
|
* check out the latest NEXT version in <a href="https://github.com/CreateJS/EaselJS/tree/master/lib">GitHub</a> for an updated version with this issue resolved. The fix will be
|
|
* provided in the next release of EaselJS.
|
|
* @property mouseEnabled
|
|
* @type {Boolean}
|
|
* @default true
|
|
**/
|
|
this.mouseEnabled = true;
|
|
|
|
/**
|
|
* If false, the tick will not run on this display object (or its children). This can provide some performance benefits.
|
|
* In addition to preventing the "tick" event from being dispatched, it will also prevent tick related updates
|
|
* on some display objects (ex. Sprite & MovieClip frame advancing, DOMElement visibility handling).
|
|
* @property tickEnabled
|
|
* @type Boolean
|
|
* @default true
|
|
**/
|
|
this.tickEnabled = true;
|
|
|
|
/**
|
|
* An optional name for this display object. Included in {{#crossLink "DisplayObject/toString"}}{{/crossLink}} . Useful for
|
|
* debugging.
|
|
* @property name
|
|
* @type {String}
|
|
* @default null
|
|
**/
|
|
this.name = null;
|
|
|
|
/**
|
|
* A reference to the {{#crossLink "Container"}}{{/crossLink}} or {{#crossLink "Stage"}}{{/crossLink}} object that
|
|
* contains this display object, or null if it has not been added
|
|
* to one.
|
|
* @property parent
|
|
* @final
|
|
* @type {Container}
|
|
* @default null
|
|
* @readonly
|
|
**/
|
|
this.parent = null;
|
|
|
|
/**
|
|
* The left offset for this display object's registration point. For example, to make a 100x100px Bitmap rotate
|
|
* around its center, you would set regX and {{#crossLink "DisplayObject/regY:property"}}{{/crossLink}} to 50.
|
|
* @property regX
|
|
* @type {Number}
|
|
* @default 0
|
|
**/
|
|
this.regX = 0;
|
|
|
|
/**
|
|
* The y offset for this display object's registration point. For example, to make a 100x100px Bitmap rotate around
|
|
* its center, you would set {{#crossLink "DisplayObject/regX:property"}}{{/crossLink}} and regY to 50.
|
|
* @property regY
|
|
* @type {Number}
|
|
* @default 0
|
|
**/
|
|
this.regY = 0;
|
|
|
|
/**
|
|
* The rotation in degrees for this display object.
|
|
* @property rotation
|
|
* @type {Number}
|
|
* @default 0
|
|
**/
|
|
this.rotation = 0;
|
|
|
|
/**
|
|
* The factor to stretch this display object horizontally. For example, setting scaleX to 2 will stretch the display
|
|
* object to twice its nominal width. To horizontally flip an object, set the scale to a negative number.
|
|
* @property scaleX
|
|
* @type {Number}
|
|
* @default 1
|
|
**/
|
|
this.scaleX = 1;
|
|
|
|
/**
|
|
* The factor to stretch this display object vertically. For example, setting scaleY to 0.5 will stretch the display
|
|
* object to half its nominal height. To vertically flip an object, set the scale to a negative number.
|
|
* @property scaleY
|
|
* @type {Number}
|
|
* @default 1
|
|
**/
|
|
this.scaleY = 1;
|
|
|
|
/**
|
|
* The factor to skew this display object horizontally.
|
|
* @property skewX
|
|
* @type {Number}
|
|
* @default 0
|
|
**/
|
|
this.skewX = 0;
|
|
|
|
/**
|
|
* The factor to skew this display object vertically.
|
|
* @property skewY
|
|
* @type {Number}
|
|
* @default 0
|
|
**/
|
|
this.skewY = 0;
|
|
|
|
/**
|
|
* A shadow object that defines the shadow to render on this display object. Set to `null` to remove a shadow. If
|
|
* null, this property is inherited from the parent container.
|
|
* @property shadow
|
|
* @type {Shadow}
|
|
* @default null
|
|
**/
|
|
this.shadow = null;
|
|
|
|
/**
|
|
* Indicates whether this display object should be rendered to the canvas and included when running the Stage
|
|
* {{#crossLink "Stage/getObjectsUnderPoint"}}{{/crossLink}} method.
|
|
* @property visible
|
|
* @type {Boolean}
|
|
* @default true
|
|
**/
|
|
this.visible = true;
|
|
|
|
/**
|
|
* The x (horizontal) position of the display object, relative to its parent.
|
|
* @property x
|
|
* @type {Number}
|
|
* @default 0
|
|
**/
|
|
this.x = 0;
|
|
|
|
/** The y (vertical) position of the display object, relative to its parent.
|
|
* @property y
|
|
* @type {Number}
|
|
* @default 0
|
|
**/
|
|
this.y = 0;
|
|
|
|
/**
|
|
* If set, defines the transformation for this display object, overriding all other transformation properties
|
|
* (x, y, rotation, scale, skew).
|
|
* @property transformMatrix
|
|
* @type {Matrix2D}
|
|
* @default null
|
|
**/
|
|
this.transformMatrix = null;
|
|
|
|
/**
|
|
* The composite operation indicates how the pixels of this display object will be composited with the elements
|
|
* behind it. If `null`, this property is inherited from the parent container. For more information, read the
|
|
* <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#compositing">
|
|
* whatwg spec on compositing</a>.
|
|
* @property compositeOperation
|
|
* @type {String}
|
|
* @default null
|
|
**/
|
|
this.compositeOperation = null;
|
|
|
|
/**
|
|
* Indicates whether the display object should be drawn to a whole pixel when
|
|
* {{#crossLink "Stage/snapToPixelEnabled"}}{{/crossLink}} is true. To enable/disable snapping on whole
|
|
* categories of display objects, set this value on the prototype (Ex. Text.prototype.snapToPixel = true).
|
|
* @property snapToPixel
|
|
* @type {Boolean}
|
|
* @default true
|
|
**/
|
|
this.snapToPixel = true;
|
|
|
|
/**
|
|
* An array of Filter objects to apply to this display object. Filters are only applied / updated when {{#crossLink "cache"}}{{/crossLink}}
|
|
* or {{#crossLink "updateCache"}}{{/crossLink}} is called on the display object, and only apply to the area that is
|
|
* cached.
|
|
* @property filters
|
|
* @type {Array}
|
|
* @default null
|
|
**/
|
|
this.filters = null;
|
|
|
|
/**
|
|
* A Shape instance that defines a vector mask (clipping path) for this display object. The shape's transformation
|
|
* will be applied relative to the display object's parent coordinates (as if it were a child of the parent).
|
|
* @property mask
|
|
* @type {Shape}
|
|
* @default null
|
|
*/
|
|
this.mask = null;
|
|
|
|
/**
|
|
* A display object that will be tested when checking mouse interactions or testing {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}}.
|
|
* The hit area will have its transformation applied relative to this display object's coordinate space (as though
|
|
* the hit test object were a child of this display object and relative to its regX/Y). The hitArea will be tested
|
|
* using only its own `alpha` value regardless of the alpha value on the target display object, or the target's
|
|
* ancestors (parents).
|
|
*
|
|
* If set on a {{#crossLink "Container"}}{{/crossLink}}, children of the Container will not receive mouse events.
|
|
* This is similar to setting {{#crossLink "mouseChildren"}}{{/crossLink}} to false.
|
|
*
|
|
* Note that hitArea is NOT currently used by the `hitTest()` method, nor is it supported for {{#crossLink "Stage"}}{{/crossLink}}.
|
|
* @property hitArea
|
|
* @type {DisplayObject}
|
|
* @default null
|
|
*/
|
|
this.hitArea = null;
|
|
|
|
/**
|
|
* A CSS cursor (ex. "pointer", "help", "text", etc) that will be displayed when the user hovers over this display
|
|
* object. You must enable mouseover events using the {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}} method to
|
|
* use this property. Setting a non-null cursor on a Container will override the cursor set on its descendants.
|
|
* @property cursor
|
|
* @type {String}
|
|
* @default null
|
|
*/
|
|
this.cursor = null;
|
|
|
|
// private properties:
|
|
/**
|
|
* @property _cacheOffsetX
|
|
* @protected
|
|
* @type {Number}
|
|
* @default 0
|
|
**/
|
|
this._cacheOffsetX = 0;
|
|
|
|
/**
|
|
* @property _cacheOffsetY
|
|
* @protected
|
|
* @type {Number}
|
|
* @default 0
|
|
**/
|
|
this._cacheOffsetY = 0;
|
|
|
|
/**
|
|
* @property _filterOffsetX
|
|
* @protected
|
|
* @type {Number}
|
|
* @default 0
|
|
**/
|
|
this._filterOffsetX = 0;
|
|
|
|
/**
|
|
* @property _filterOffsetY
|
|
* @protected
|
|
* @type {Number}
|
|
* @default 0
|
|
**/
|
|
this._filterOffsetY = 0;
|
|
|
|
/**
|
|
* @property _cacheScale
|
|
* @protected
|
|
* @type {Number}
|
|
* @default 1
|
|
**/
|
|
this._cacheScale = 1;
|
|
|
|
/**
|
|
* @property _cacheDataURLID
|
|
* @protected
|
|
* @type {Number}
|
|
* @default 0
|
|
*/
|
|
this._cacheDataURLID = 0;
|
|
|
|
/**
|
|
* @property _cacheDataURL
|
|
* @protected
|
|
* @type {String}
|
|
* @default null
|
|
*/
|
|
this._cacheDataURL = null;
|
|
|
|
/**
|
|
* @property _props
|
|
* @protected
|
|
* @type {DisplayObject}
|
|
* @default null
|
|
**/
|
|
this._props = new createjs.DisplayProps();
|
|
|
|
/**
|
|
* @property _rectangle
|
|
* @protected
|
|
* @type {Rectangle}
|
|
* @default null
|
|
**/
|
|
this._rectangle = new createjs.Rectangle();
|
|
|
|
/**
|
|
* @property _bounds
|
|
* @protected
|
|
* @type {Rectangle}
|
|
* @default null
|
|
**/
|
|
this._bounds = null;
|
|
}
|
|
var p = createjs.extend(DisplayObject, createjs.EventDispatcher);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// static properties:
|
|
/**
|
|
* Listing of mouse event names. Used in _hasMouseEventListener.
|
|
* @property _MOUSE_EVENTS
|
|
* @protected
|
|
* @static
|
|
* @type {Array}
|
|
**/
|
|
DisplayObject._MOUSE_EVENTS = [
|
|
"click",
|
|
"dblclick",
|
|
"mousedown",
|
|
"mouseout",
|
|
"mouseover",
|
|
"pressmove",
|
|
"pressup",
|
|
"rollout",
|
|
"rollover",
|
|
];
|
|
|
|
/**
|
|
* Suppresses errors generated when using features like hitTest, mouse events, and {{#crossLink "getObjectsUnderPoint"}}{{/crossLink}}
|
|
* with cross domain content.
|
|
* @property suppressCrossDomainErrors
|
|
* @static
|
|
* @type {Boolean}
|
|
* @default false
|
|
**/
|
|
DisplayObject.suppressCrossDomainErrors = false;
|
|
|
|
/**
|
|
* @property _snapToPixelEnabled
|
|
* @protected
|
|
* @static
|
|
* @type {Boolean}
|
|
* @default false
|
|
**/
|
|
DisplayObject._snapToPixelEnabled = false; // stage.snapToPixelEnabled is temporarily copied here during a draw to provide global access.
|
|
|
|
/**
|
|
* @property _hitTestCanvas
|
|
* @type {HTMLCanvasElement | Object}
|
|
* @static
|
|
* @protected
|
|
**/
|
|
/**
|
|
* @property _hitTestContext
|
|
* @type {CanvasRenderingContext2D}
|
|
* @static
|
|
* @protected
|
|
**/
|
|
var canvas = createjs.createCanvas
|
|
? createjs.createCanvas()
|
|
: document.createElement("canvas"); // prevent errors on load in browsers without canvas.
|
|
if (canvas.getContext) {
|
|
DisplayObject._hitTestCanvas = canvas;
|
|
DisplayObject._hitTestContext = canvas.getContext("2d");
|
|
canvas.width = canvas.height = 1;
|
|
}
|
|
|
|
/**
|
|
* @property _nextCacheID
|
|
* @type {Number}
|
|
* @static
|
|
* @protected
|
|
**/
|
|
DisplayObject._nextCacheID = 1;
|
|
|
|
// events:
|
|
/**
|
|
* Dispatched when the user presses their left mouse button over the display object. See the
|
|
* {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties.
|
|
* @event mousedown
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
/**
|
|
* Dispatched when the user presses their left mouse button and then releases it while over the display object.
|
|
* See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties.
|
|
* @event click
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
/**
|
|
* Dispatched when the user double clicks their left mouse button over this display object.
|
|
* See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties.
|
|
* @event dblclick
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
/**
|
|
* Dispatched when the user's mouse enters this display object. This event must be enabled using
|
|
* {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}. See also {{#crossLink "DisplayObject/rollover:event"}}{{/crossLink}}.
|
|
* See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties.
|
|
* @event mouseover
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
/**
|
|
* Dispatched when the user's mouse leaves this display object. This event must be enabled using
|
|
* {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}. See also {{#crossLink "DisplayObject/rollout:event"}}{{/crossLink}}.
|
|
* See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties.
|
|
* @event mouseout
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
/**
|
|
* This event is similar to {{#crossLink "DisplayObject/mouseover:event"}}{{/crossLink}}, with the following
|
|
* differences: it does not bubble, and it considers {{#crossLink "Container"}}{{/crossLink}} instances as an
|
|
* aggregate of their content.
|
|
*
|
|
* For example, myContainer contains two overlapping children: shapeA and shapeB. The user moves their mouse over
|
|
* shapeA and then directly on to shapeB. With a listener for {{#crossLink "mouseover:event"}}{{/crossLink}} on
|
|
* myContainer, two events would be received, each targeting a child element:<OL>
|
|
* <LI>when the mouse enters shapeA (target=shapeA)</LI>
|
|
* <LI>when the mouse enters shapeB (target=shapeB)</LI>
|
|
* </OL>
|
|
* However, with a listener for "rollover" instead, only a single event is received when the mouse first enters
|
|
* the aggregate myContainer content (target=myContainer).
|
|
*
|
|
* This event must be enabled using {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}.
|
|
* See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties.
|
|
* @event rollover
|
|
* @since 0.7.0
|
|
*/
|
|
|
|
/**
|
|
* This event is similar to {{#crossLink "DisplayObject/mouseout:event"}}{{/crossLink}}, with the following
|
|
* differences: it does not bubble, and it considers {{#crossLink "Container"}}{{/crossLink}} instances as an
|
|
* aggregate of their content.
|
|
*
|
|
* For example, myContainer contains two overlapping children: shapeA and shapeB. The user moves their mouse over
|
|
* shapeA, then directly on to shapeB, then off both. With a listener for {{#crossLink "mouseout:event"}}{{/crossLink}}
|
|
* on myContainer, two events would be received, each targeting a child element:<OL>
|
|
* <LI>when the mouse leaves shapeA (target=shapeA)</LI>
|
|
* <LI>when the mouse leaves shapeB (target=shapeB)</LI>
|
|
* </OL>
|
|
* However, with a listener for "rollout" instead, only a single event is received when the mouse leaves
|
|
* the aggregate myContainer content (target=myContainer).
|
|
*
|
|
* This event must be enabled using {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}.
|
|
* See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties.
|
|
* @event rollout
|
|
* @since 0.7.0
|
|
*/
|
|
|
|
/**
|
|
* After a {{#crossLink "DisplayObject/mousedown:event"}}{{/crossLink}} occurs on a display object, a pressmove
|
|
* event will be generated on that object whenever the mouse moves until the mouse press is released. This can be
|
|
* useful for dragging and similar operations.
|
|
* @event pressmove
|
|
* @since 0.7.0
|
|
*/
|
|
|
|
/**
|
|
* After a {{#crossLink "DisplayObject/mousedown:event"}}{{/crossLink}} occurs on a display object, a pressup event
|
|
* will be generated on that object when that mouse press is released. This can be useful for dragging and similar
|
|
* operations.
|
|
* @event pressup
|
|
* @since 0.7.0
|
|
*/
|
|
|
|
/**
|
|
* Dispatched when the display object is added to a parent container.
|
|
* @event added
|
|
*/
|
|
|
|
/**
|
|
* Dispatched when the display object is removed from its parent container.
|
|
* @event removed
|
|
*/
|
|
|
|
/**
|
|
* Dispatched on each display object on a stage whenever the stage updates. This occurs immediately before the
|
|
* rendering (draw) pass. When {{#crossLink "Stage/update"}}{{/crossLink}} is called, first all display objects on
|
|
* the stage dispatch the tick event, then all of the display objects are drawn to stage. Children will have their
|
|
* {{#crossLink "tick:event"}}{{/crossLink}} event dispatched in order of their depth prior to the event being
|
|
* dispatched on their parent.
|
|
* @event tick
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @param {Array} params An array containing any arguments that were passed to the Stage.update() method. For
|
|
* example if you called stage.update("hello"), then the params would be ["hello"].
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
// getter / setters:
|
|
/**
|
|
* Use the {{#crossLink "DisplayObject/stage:property"}}{{/crossLink}} property instead.
|
|
* @method getStage
|
|
* @return {Stage}
|
|
* @deprecated
|
|
**/
|
|
p.getStage = function () {
|
|
// uses dynamic access to avoid circular dependencies;
|
|
var o = this,
|
|
_Stage = createjs["Stage"];
|
|
while (o.parent) {
|
|
o = o.parent;
|
|
}
|
|
if (o instanceof _Stage) {
|
|
return o;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Returns the Stage instance that this display object will be rendered on, or null if it has not been added to one.
|
|
* @property stage
|
|
* @type {Stage}
|
|
* @readonly
|
|
**/
|
|
try {
|
|
Object.defineProperties(p, {
|
|
stage: { get: p.getStage },
|
|
});
|
|
} catch (e) {}
|
|
|
|
// public methods:
|
|
/**
|
|
* Returns true or false indicating whether the display object would be visible if drawn to a canvas.
|
|
* This does not account for whether it would be visible within the boundaries of the stage.
|
|
*
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method isVisible
|
|
* @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas
|
|
**/
|
|
p.isVisible = function () {
|
|
return !!(
|
|
this.visible &&
|
|
this.alpha > 0 &&
|
|
this.scaleX != 0 &&
|
|
this.scaleY != 0
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform.
|
|
* Returns <code>true</code> if the draw was handled (useful for overriding functionality).
|
|
*
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method draw
|
|
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into.
|
|
* @param {Boolean} [ignoreCache=false] Indicates whether the draw operation should ignore any current cache. For example,
|
|
* used for drawing the cache (to prevent it from simply drawing an existing cache back into itself).
|
|
* @return {Boolean}
|
|
**/
|
|
p.draw = function (ctx, ignoreCache) {
|
|
var cacheCanvas = this.cacheCanvas;
|
|
if (ignoreCache || !cacheCanvas) {
|
|
return false;
|
|
}
|
|
var scale = this._cacheScale;
|
|
ctx.drawImage(
|
|
cacheCanvas,
|
|
this._cacheOffsetX + this._filterOffsetX,
|
|
this._cacheOffsetY + this._filterOffsetY,
|
|
cacheCanvas.width / scale,
|
|
cacheCanvas.height / scale
|
|
);
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Applies this display object's transformation, alpha, globalCompositeOperation, clipping path (mask), and shadow
|
|
* to the specified context. This is typically called prior to {{#crossLink "DisplayObject/draw"}}{{/crossLink}}.
|
|
* @method updateContext
|
|
* @param {CanvasRenderingContext2D} ctx The canvas 2D to update.
|
|
**/
|
|
p.updateContext = function (ctx) {
|
|
var o = this,
|
|
mask = o.mask,
|
|
mtx = o._props.matrix;
|
|
|
|
if (mask && mask.graphics && !mask.graphics.isEmpty()) {
|
|
mask.getMatrix(mtx);
|
|
ctx.transform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx, mtx.ty);
|
|
|
|
mask.graphics.drawAsPath(ctx);
|
|
ctx.clip();
|
|
|
|
mtx.invert();
|
|
ctx.transform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx, mtx.ty);
|
|
}
|
|
|
|
this.getMatrix(mtx);
|
|
var tx = mtx.tx,
|
|
ty = mtx.ty;
|
|
if (DisplayObject._snapToPixelEnabled && o.snapToPixel) {
|
|
tx = (tx + (tx < 0 ? -0.5 : 0.5)) | 0;
|
|
ty = (ty + (ty < 0 ? -0.5 : 0.5)) | 0;
|
|
}
|
|
ctx.transform(mtx.a, mtx.b, mtx.c, mtx.d, tx, ty);
|
|
ctx.globalAlpha *= o.alpha;
|
|
if (o.compositeOperation) {
|
|
ctx.globalCompositeOperation = o.compositeOperation;
|
|
}
|
|
if (o.shadow) {
|
|
this._applyShadow(ctx, o.shadow);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Draws the display object into a new canvas, which is then used for subsequent draws. For complex content
|
|
* that does not change frequently (ex. a Container with many children that do not move, or a complex vector Shape),
|
|
* this can provide for much faster rendering because the content does not need to be re-rendered each tick. The
|
|
* cached display object can be moved, rotated, faded, etc freely, however if its content changes, you must
|
|
* manually update the cache by calling <code>updateCache()</code> or <code>cache()</code> again. You must specify
|
|
* the cache area via the x, y, w, and h parameters. This defines the rectangle that will be rendered and cached
|
|
* using this display object's coordinates.
|
|
*
|
|
* <h4>Example</h4>
|
|
* For example if you defined a Shape that drew a circle at 0, 0 with a radius of 25:
|
|
*
|
|
* var shape = new createjs.Shape();
|
|
* shape.graphics.beginFill("#ff0000").drawCircle(0, 0, 25);
|
|
* myShape.cache(-25, -25, 50, 50);
|
|
*
|
|
* Note that filters need to be defined <em>before</em> the cache is applied. Check out the {{#crossLink "Filter"}}{{/crossLink}}
|
|
* class for more information. Some filters (ex. BlurFilter) will not work as expected in conjunction with the scale param.
|
|
*
|
|
* Usually, the resulting cacheCanvas will have the dimensions width*scale by height*scale, however some filters (ex. BlurFilter)
|
|
* will add padding to the canvas dimensions.
|
|
*
|
|
* @method cache
|
|
* @param {Number} x The x coordinate origin for the cache region.
|
|
* @param {Number} y The y coordinate origin for the cache region.
|
|
* @param {Number} width The width of the cache region.
|
|
* @param {Number} height The height of the cache region.
|
|
* @param {Number} [scale=1] The scale at which the cache will be created. For example, if you cache a vector shape using
|
|
* myShape.cache(0,0,100,100,2) then the resulting cacheCanvas will be 200x200 px. This lets you scale and rotate
|
|
* cached elements with greater fidelity. Default is 1.
|
|
**/
|
|
p.cache = function (x, y, width, height, scale) {
|
|
// draw to canvas.
|
|
scale = scale || 1;
|
|
if (!this.cacheCanvas) {
|
|
this.cacheCanvas = createjs.createCanvas
|
|
? createjs.createCanvas()
|
|
: document.createElement("canvas");
|
|
}
|
|
this._cacheWidth = width;
|
|
this._cacheHeight = height;
|
|
this._cacheOffsetX = x;
|
|
this._cacheOffsetY = y;
|
|
this._cacheScale = scale;
|
|
this.updateCache();
|
|
};
|
|
|
|
/**
|
|
* Redraws the display object to its cache. Calling updateCache without an active cache will throw an error.
|
|
* If compositeOperation is null the current cache will be cleared prior to drawing. Otherwise the display object
|
|
* will be drawn over the existing cache using the specified compositeOperation.
|
|
*
|
|
* <h4>Example</h4>
|
|
* Clear the current graphics of a cached shape, draw some new instructions, and then update the cache. The new line
|
|
* will be drawn on top of the old one.
|
|
*
|
|
* // Not shown: Creating the shape, and caching it.
|
|
* shapeInstance.clear();
|
|
* shapeInstance.setStrokeStyle(3).beginStroke("#ff0000").moveTo(100, 100).lineTo(200,200);
|
|
* shapeInstance.updateCache();
|
|
*
|
|
* @method updateCache
|
|
* @param {String} compositeOperation The compositeOperation to use, or null to clear the cache and redraw it.
|
|
* <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#compositing">
|
|
* whatwg spec on compositing</a>.
|
|
**/
|
|
p.updateCache = function (compositeOperation) {
|
|
var cacheCanvas = this.cacheCanvas;
|
|
if (!cacheCanvas) {
|
|
throw "cache() must be called before updateCache()";
|
|
}
|
|
var scale = this._cacheScale,
|
|
offX = this._cacheOffsetX * scale,
|
|
offY = this._cacheOffsetY * scale;
|
|
var w = this._cacheWidth,
|
|
h = this._cacheHeight,
|
|
ctx = cacheCanvas.getContext("2d");
|
|
|
|
var fBounds = this._getFilterBounds();
|
|
offX += this._filterOffsetX = fBounds.x;
|
|
offY += this._filterOffsetY = fBounds.y;
|
|
|
|
w = Math.ceil(w * scale) + fBounds.width;
|
|
h = Math.ceil(h * scale) + fBounds.height;
|
|
if (w != cacheCanvas.width || h != cacheCanvas.height) {
|
|
// TODO: it would be nice to preserve the content if there is a compositeOperation.
|
|
cacheCanvas.width = w;
|
|
cacheCanvas.height = h;
|
|
} else if (!compositeOperation) {
|
|
ctx.clearRect(0, 0, w + 1, h + 1);
|
|
}
|
|
|
|
ctx.save();
|
|
ctx.globalCompositeOperation = compositeOperation;
|
|
ctx.setTransform(scale, 0, 0, scale, -offX, -offY);
|
|
this.draw(ctx, true);
|
|
// TODO: filters and cache scale don't play well together at present.
|
|
this._applyFilters();
|
|
ctx.restore();
|
|
this.cacheID = DisplayObject._nextCacheID++;
|
|
};
|
|
|
|
/**
|
|
* Clears the current cache. See {{#crossLink "DisplayObject/cache"}}{{/crossLink}} for more information.
|
|
* @method uncache
|
|
**/
|
|
p.uncache = function () {
|
|
this._cacheDataURL = this.cacheCanvas = null;
|
|
this.cacheID = this._cacheOffsetX = this._cacheOffsetY = this._filterOffsetX = this._filterOffsetY = 0;
|
|
this._cacheScale = 1;
|
|
};
|
|
|
|
/**
|
|
* Returns a data URL for the cache, or null if this display object is not cached.
|
|
* Uses cacheID to ensure a new data URL is not generated if the cache has not changed.
|
|
* @method getCacheDataURL
|
|
* @return {String} The image data url for the cache.
|
|
**/
|
|
p.getCacheDataURL = function () {
|
|
if (!this.cacheCanvas) {
|
|
return null;
|
|
}
|
|
if (this.cacheID != this._cacheDataURLID) {
|
|
this._cacheDataURL = this.cacheCanvas.toDataURL();
|
|
}
|
|
return this._cacheDataURL;
|
|
};
|
|
|
|
/**
|
|
* Transforms the specified x and y position from the coordinate space of the display object
|
|
* to the global (stage) coordinate space. For example, this could be used to position an HTML label
|
|
* over a specific point on a nested display object. Returns a Point instance with x and y properties
|
|
* correlating to the transformed coordinates on the stage.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* displayObject.x = 300;
|
|
* displayObject.y = 200;
|
|
* stage.addChild(displayObject);
|
|
* var point = displayObject.localToGlobal(100, 100);
|
|
* // Results in x=400, y=300
|
|
*
|
|
* @method localToGlobal
|
|
* @param {Number} x The x position in the source display object to transform.
|
|
* @param {Number} y The y position in the source display object to transform.
|
|
* @param {Point | Object} [pt] An object to copy the result into. If omitted a new Point object with x/y properties will be returned.
|
|
* @return {Point} A Point instance with x and y properties correlating to the transformed coordinates
|
|
* on the stage.
|
|
**/
|
|
p.localToGlobal = function (x, y, pt) {
|
|
return this.getConcatenatedMatrix(this._props.matrix).transformPoint(
|
|
x,
|
|
y,
|
|
pt || new createjs.Point()
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Transforms the specified x and y position from the global (stage) coordinate space to the
|
|
* coordinate space of the display object. For example, this could be used to determine
|
|
* the current mouse position within the display object. Returns a Point instance with x and y properties
|
|
* correlating to the transformed position in the display object's coordinate space.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* displayObject.x = 300;
|
|
* displayObject.y = 200;
|
|
* stage.addChild(displayObject);
|
|
* var point = displayObject.globalToLocal(100, 100);
|
|
* // Results in x=-200, y=-100
|
|
*
|
|
* @method globalToLocal
|
|
* @param {Number} x The x position on the stage to transform.
|
|
* @param {Number} y The y position on the stage to transform.
|
|
* @param {Point | Object} [pt] An object to copy the result into. If omitted a new Point object with x/y properties will be returned.
|
|
* @return {Point} A Point instance with x and y properties correlating to the transformed position in the
|
|
* display object's coordinate space.
|
|
**/
|
|
p.globalToLocal = function (x, y, pt) {
|
|
return this.getConcatenatedMatrix(this._props.matrix)
|
|
.invert()
|
|
.transformPoint(x, y, pt || new createjs.Point());
|
|
};
|
|
|
|
/**
|
|
* Transforms the specified x and y position from the coordinate space of this display object to the coordinate
|
|
* space of the target display object. Returns a Point instance with x and y properties correlating to the
|
|
* transformed position in the target's coordinate space. Effectively the same as using the following code with
|
|
* {{#crossLink "DisplayObject/localToGlobal"}}{{/crossLink}} and {{#crossLink "DisplayObject/globalToLocal"}}{{/crossLink}}.
|
|
*
|
|
* var pt = this.localToGlobal(x, y);
|
|
* pt = target.globalToLocal(pt.x, pt.y);
|
|
*
|
|
* @method localToLocal
|
|
* @param {Number} x The x position in the source display object to transform.
|
|
* @param {Number} y The y position on the source display object to transform.
|
|
* @param {DisplayObject} target The target display object to which the coordinates will be transformed.
|
|
* @param {Point | Object} [pt] An object to copy the result into. If omitted a new Point object with x/y properties will be returned.
|
|
* @return {Point} Returns a Point instance with x and y properties correlating to the transformed position
|
|
* in the target's coordinate space.
|
|
**/
|
|
p.localToLocal = function (x, y, target, pt) {
|
|
pt = this.localToGlobal(x, y, pt);
|
|
return target.globalToLocal(pt.x, pt.y, pt);
|
|
};
|
|
|
|
/**
|
|
* Shortcut method to quickly set the transform properties on the display object. All parameters are optional.
|
|
* Omitted parameters will have the default value set.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* displayObject.setTransform(100, 100, 2, 2);
|
|
*
|
|
* @method setTransform
|
|
* @param {Number} [x=0] The horizontal translation (x position) in pixels
|
|
* @param {Number} [y=0] The vertical translation (y position) in pixels
|
|
* @param {Number} [scaleX=1] The horizontal scale, as a percentage of 1
|
|
* @param {Number} [scaleY=1] the vertical scale, as a percentage of 1
|
|
* @param {Number} [rotation=0] The rotation, in degrees
|
|
* @param {Number} [skewX=0] The horizontal skew factor
|
|
* @param {Number} [skewY=0] The vertical skew factor
|
|
* @param {Number} [regX=0] The horizontal registration point in pixels
|
|
* @param {Number} [regY=0] The vertical registration point in pixels
|
|
* @return {DisplayObject} Returns this instance. Useful for chaining commands.
|
|
* @chainable
|
|
*/
|
|
p.setTransform = function (
|
|
x,
|
|
y,
|
|
scaleX,
|
|
scaleY,
|
|
rotation,
|
|
skewX,
|
|
skewY,
|
|
regX,
|
|
regY
|
|
) {
|
|
this.x = x || 0;
|
|
this.y = y || 0;
|
|
this.scaleX = scaleX == null ? 1 : scaleX;
|
|
this.scaleY = scaleY == null ? 1 : scaleY;
|
|
this.rotation = rotation || 0;
|
|
this.skewX = skewX || 0;
|
|
this.skewY = skewY || 0;
|
|
this.regX = regX || 0;
|
|
this.regY = regY || 0;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Returns a matrix based on this object's current transform.
|
|
* @method getMatrix
|
|
* @param {Matrix2D} matrix Optional. A Matrix2D object to populate with the calculated values. If null, a new
|
|
* Matrix object is returned.
|
|
* @return {Matrix2D} A matrix representing this display object's transform.
|
|
**/
|
|
p.getMatrix = function (matrix) {
|
|
var o = this,
|
|
mtx = (matrix && matrix.identity()) || new createjs.Matrix2D();
|
|
return o.transformMatrix
|
|
? mtx.copy(o.transformMatrix)
|
|
: mtx.appendTransform(
|
|
o.x,
|
|
o.y,
|
|
o.scaleX,
|
|
o.scaleY,
|
|
o.rotation,
|
|
o.skewX,
|
|
o.skewY,
|
|
o.regX,
|
|
o.regY
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Generates a Matrix2D object representing the combined transform of the display object and all of its
|
|
* parent Containers up to the highest level ancestor (usually the {{#crossLink "Stage"}}{{/crossLink}}). This can
|
|
* be used to transform positions between coordinate spaces, such as with {{#crossLink "DisplayObject/localToGlobal"}}{{/crossLink}}
|
|
* and {{#crossLink "DisplayObject/globalToLocal"}}{{/crossLink}}.
|
|
* @method getConcatenatedMatrix
|
|
* @param {Matrix2D} [matrix] A {{#crossLink "Matrix2D"}}{{/crossLink}} object to populate with the calculated values.
|
|
* If null, a new Matrix2D object is returned.
|
|
* @return {Matrix2D} The combined matrix.
|
|
**/
|
|
p.getConcatenatedMatrix = function (matrix) {
|
|
var o = this,
|
|
mtx = this.getMatrix(matrix);
|
|
while ((o = o.parent)) {
|
|
mtx.prependMatrix(o.getMatrix(o._props.matrix));
|
|
}
|
|
return mtx;
|
|
};
|
|
|
|
/**
|
|
* Generates a DisplayProps object representing the combined display properties of the object and all of its
|
|
* parent Containers up to the highest level ancestor (usually the {{#crossLink "Stage"}}{{/crossLink}}).
|
|
* @method getConcatenatedDisplayProps
|
|
* @param {DisplayProps} [props] A {{#crossLink "DisplayProps"}}{{/crossLink}} object to populate with the calculated values.
|
|
* If null, a new DisplayProps object is returned.
|
|
* @return {DisplayProps} The combined display properties.
|
|
**/
|
|
p.getConcatenatedDisplayProps = function (props) {
|
|
props = props ? props.identity() : new createjs.DisplayProps();
|
|
var o = this,
|
|
mtx = o.getMatrix(props.matrix);
|
|
do {
|
|
props.prepend(o.visible, o.alpha, o.shadow, o.compositeOperation);
|
|
|
|
// we do this to avoid problems with the matrix being used for both operations when o._props.matrix is passed in as the props param.
|
|
// this could be simplified (ie. just done as part of the prepend above) if we switched to using a pool.
|
|
if (o != this) {
|
|
mtx.prependMatrix(o.getMatrix(o._props.matrix));
|
|
}
|
|
} while ((o = o.parent));
|
|
return props;
|
|
};
|
|
|
|
/**
|
|
* Tests whether the display object intersects the specified point in <em>local</em> coordinates (ie. draws a pixel
|
|
* with alpha > 0 at the specified position). This ignores the alpha, shadow, hitArea, mask, and compositeOperation
|
|
* of the display object.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var myShape = new createjs.Shape();
|
|
* myShape.graphics.beginFill("red").drawRect(100, 100, 20, 50);
|
|
*
|
|
* console.log(myShape.hitTest(10,10); // false
|
|
* console.log(myShape.hitTest(110, 25); // true
|
|
*
|
|
* Note that to use Stage coordinates (such as {{#crossLink "Stage/mouseX:property"}}{{/crossLink}}), they must
|
|
* first be converted to local coordinates:
|
|
*
|
|
* stage.addEventListener("stagemousedown", handleMouseDown);
|
|
* function handleMouseDown(event) {
|
|
* var p = myShape.globalToLocal(stage.mouseX, stage.mouseY);
|
|
* var hit = myShape.hitTest(p.x, p.y);
|
|
* }
|
|
*
|
|
* Shape-to-shape collision is not currently supported by EaselJS.
|
|
*
|
|
* @method hitTest
|
|
* @param {Number} x The x position to check in the display object's local coordinates.
|
|
* @param {Number} y The y position to check in the display object's local coordinates.
|
|
* @return {Boolean} A Boolean indicating whether a visible portion of the DisplayObject intersect the specified
|
|
* local Point.
|
|
*/
|
|
p.hitTest = function (x, y) {
|
|
var ctx = DisplayObject._hitTestContext;
|
|
ctx.setTransform(1, 0, 0, 1, -x, -y);
|
|
this.draw(ctx);
|
|
|
|
var hit = this._testHit(ctx);
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
ctx.clearRect(0, 0, 2, 2);
|
|
return hit;
|
|
};
|
|
|
|
/**
|
|
* Provides a chainable shortcut method for setting a number of properties on the instance.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var myGraphics = new createjs.Graphics().beginFill("#ff0000").drawCircle(0, 0, 25);
|
|
* var shape = stage.addChild(new createjs.Shape()).set({graphics:myGraphics, x:100, y:100, alpha:0.5});
|
|
*
|
|
* @method set
|
|
* @param {Object} props A generic object containing properties to copy to the DisplayObject instance.
|
|
* @return {DisplayObject} Returns the instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
*/
|
|
p.set = function (props) {
|
|
for (var n in props) {
|
|
this[n] = props[n];
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Returns a rectangle representing this object's bounds in its local coordinate system (ie. with no transformation).
|
|
* Objects that have been cached will return the bounds of the cache.
|
|
*
|
|
* Not all display objects can calculate their own bounds (ex. Shape). For these objects, you can use
|
|
* {{#crossLink "DisplayObject/setBounds"}}{{/crossLink}} so that they are included when calculating Container
|
|
* bounds.
|
|
*
|
|
* <table>
|
|
* <tr><td><b>All</b></td><td>
|
|
* All display objects support setting bounds manually using setBounds(). Likewise, display objects that
|
|
* have been cached using cache() will return the bounds of their cache. Manual and cache bounds will override
|
|
* the automatic calculations listed below.
|
|
* </td></tr>
|
|
* <tr><td><b>Bitmap</b></td><td>
|
|
* Returns the width and height of the sourceRect (if specified) or image, extending from (x=0,y=0).
|
|
* </td></tr>
|
|
* <tr><td><b>Sprite</b></td><td>
|
|
* Returns the bounds of the current frame. May have non-zero x/y if a frame registration point was specified
|
|
* in the spritesheet data. See also {{#crossLink "SpriteSheet/getFrameBounds"}}{{/crossLink}}
|
|
* </td></tr>
|
|
* <tr><td><b>Container</b></td><td>
|
|
* Returns the aggregate (combined) bounds of all children that return a non-null value from getBounds().
|
|
* </td></tr>
|
|
* <tr><td><b>Shape</b></td><td>
|
|
* Does not currently support automatic bounds calculations. Use setBounds() to manually define bounds.
|
|
* </td></tr>
|
|
* <tr><td><b>Text</b></td><td>
|
|
* Returns approximate bounds. Horizontal values (x/width) are quite accurate, but vertical values (y/height) are
|
|
* not, especially when using textBaseline values other than "top".
|
|
* </td></tr>
|
|
* <tr><td><b>BitmapText</b></td><td>
|
|
* Returns approximate bounds. Values will be more accurate if spritesheet frame registration points are close
|
|
* to (x=0,y=0).
|
|
* </td></tr>
|
|
* </table>
|
|
*
|
|
* Bounds can be expensive to calculate for some objects (ex. text, or containers with many children), and
|
|
* are recalculated each time you call getBounds(). You can prevent recalculation on static objects by setting the
|
|
* bounds explicitly:
|
|
*
|
|
* var bounds = obj.getBounds();
|
|
* obj.setBounds(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
* // getBounds will now use the set values, instead of recalculating
|
|
*
|
|
* To reduce memory impact, the returned Rectangle instance may be reused internally; clone the instance or copy its
|
|
* values if you need to retain it.
|
|
*
|
|
* var myBounds = obj.getBounds().clone();
|
|
* // OR:
|
|
* myRect.copy(obj.getBounds());
|
|
*
|
|
* @method getBounds
|
|
* @return {Rectangle} A Rectangle instance representing the bounds, or null if bounds are not available for this
|
|
* object.
|
|
**/
|
|
p.getBounds = function () {
|
|
if (this._bounds) {
|
|
return this._rectangle.copy(this._bounds);
|
|
}
|
|
var cacheCanvas = this.cacheCanvas;
|
|
if (cacheCanvas) {
|
|
var scale = this._cacheScale;
|
|
return this._rectangle.setValues(
|
|
this._cacheOffsetX,
|
|
this._cacheOffsetY,
|
|
cacheCanvas.width / scale,
|
|
cacheCanvas.height / scale
|
|
);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Returns a rectangle representing this object's bounds in its parent's coordinate system (ie. with transformations applied).
|
|
* Objects that have been cached will return the transformed bounds of the cache.
|
|
*
|
|
* Not all display objects can calculate their own bounds (ex. Shape). For these objects, you can use
|
|
* {{#crossLink "DisplayObject/setBounds"}}{{/crossLink}} so that they are included when calculating Container
|
|
* bounds.
|
|
*
|
|
* To reduce memory impact, the returned Rectangle instance may be reused internally; clone the instance or copy its
|
|
* values if you need to retain it.
|
|
*
|
|
* Container instances calculate aggregate bounds for all children that return bounds via getBounds.
|
|
* @method getTransformedBounds
|
|
* @return {Rectangle} A Rectangle instance representing the bounds, or null if bounds are not available for this object.
|
|
**/
|
|
p.getTransformedBounds = function () {
|
|
return this._getBounds();
|
|
};
|
|
|
|
/**
|
|
* Allows you to manually specify the bounds of an object that either cannot calculate their own bounds (ex. Shape &
|
|
* Text) for future reference, or so the object can be included in Container bounds. Manually set bounds will always
|
|
* override calculated bounds.
|
|
*
|
|
* The bounds should be specified in the object's local (untransformed) coordinates. For example, a Shape instance
|
|
* with a 25px radius circle centered at 0,0 would have bounds of (-25, -25, 50, 50).
|
|
* @method setBounds
|
|
* @param {Number} x The x origin of the bounds. Pass null to remove the manual bounds.
|
|
* @param {Number} y The y origin of the bounds.
|
|
* @param {Number} width The width of the bounds.
|
|
* @param {Number} height The height of the bounds.
|
|
**/
|
|
p.setBounds = function (x, y, width, height) {
|
|
if (x == null) {
|
|
this._bounds = x;
|
|
}
|
|
this._bounds = (this._bounds || new createjs.Rectangle()).setValues(
|
|
x,
|
|
y,
|
|
width,
|
|
height
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Returns a clone of this DisplayObject. Some properties that are specific to this instance's current context are
|
|
* reverted to their defaults (for example .parent). Caches are not maintained across clones, and some elements
|
|
* are copied by reference (masks, individual filter instances, hit area)
|
|
* @method clone
|
|
* @return {DisplayObject} A clone of the current DisplayObject instance.
|
|
**/
|
|
p.clone = function () {
|
|
return this._cloneProps(new DisplayObject());
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[DisplayObject (name=" + this.name + ")]";
|
|
};
|
|
|
|
// private methods:
|
|
// separated so it can be used more easily in subclasses:
|
|
/**
|
|
* @method _cloneProps
|
|
* @param {DisplayObject} o The DisplayObject instance which will have properties from the current DisplayObject
|
|
* instance copied into.
|
|
* @return {DisplayObject} o
|
|
* @protected
|
|
**/
|
|
p._cloneProps = function (o) {
|
|
o.alpha = this.alpha;
|
|
o.mouseEnabled = this.mouseEnabled;
|
|
o.tickEnabled = this.tickEnabled;
|
|
o.name = this.name;
|
|
o.regX = this.regX;
|
|
o.regY = this.regY;
|
|
o.rotation = this.rotation;
|
|
o.scaleX = this.scaleX;
|
|
o.scaleY = this.scaleY;
|
|
o.shadow = this.shadow;
|
|
o.skewX = this.skewX;
|
|
o.skewY = this.skewY;
|
|
o.visible = this.visible;
|
|
o.x = this.x;
|
|
o.y = this.y;
|
|
o.compositeOperation = this.compositeOperation;
|
|
o.snapToPixel = this.snapToPixel;
|
|
o.filters = this.filters == null ? null : this.filters.slice(0);
|
|
o.mask = this.mask;
|
|
o.hitArea = this.hitArea;
|
|
o.cursor = this.cursor;
|
|
o._bounds = this._bounds;
|
|
return o;
|
|
};
|
|
|
|
/**
|
|
* @method _applyShadow
|
|
* @protected
|
|
* @param {CanvasRenderingContext2D} ctx
|
|
* @param {Shadow} shadow
|
|
**/
|
|
p._applyShadow = function (ctx, shadow) {
|
|
shadow = shadow || Shadow.identity;
|
|
ctx.shadowColor = shadow.color;
|
|
ctx.shadowOffsetX = shadow.offsetX;
|
|
ctx.shadowOffsetY = shadow.offsetY;
|
|
ctx.shadowBlur = shadow.blur;
|
|
};
|
|
|
|
/**
|
|
* @method _tick
|
|
* @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs.
|
|
* @protected
|
|
**/
|
|
p._tick = function (evtObj) {
|
|
// because tick can be really performance sensitive, check for listeners before calling dispatchEvent.
|
|
var ls = this._listeners;
|
|
if (ls && ls["tick"]) {
|
|
// reset & reuse the event object to avoid construction / GC costs:
|
|
evtObj.target = null;
|
|
evtObj.propagationStopped = evtObj.immediatePropagationStopped = false;
|
|
this.dispatchEvent(evtObj);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _testHit
|
|
* @protected
|
|
* @param {CanvasRenderingContext2D} ctx
|
|
* @return {Boolean}
|
|
**/
|
|
p._testHit = function (ctx) {
|
|
try {
|
|
var hit = ctx.getImageData(0, 0, 1, 1).data[3] > 1;
|
|
} catch (e) {
|
|
if (!DisplayObject.suppressCrossDomainErrors) {
|
|
throw "An error has occurred. This is most likely due to security restrictions on reading canvas pixel data with local or cross-domain images.";
|
|
}
|
|
}
|
|
return hit;
|
|
};
|
|
|
|
/**
|
|
* @method _applyFilters
|
|
* @protected
|
|
**/
|
|
p._applyFilters = function () {
|
|
if (!this.filters || this.filters.length == 0 || !this.cacheCanvas) {
|
|
return;
|
|
}
|
|
var l = this.filters.length;
|
|
var ctx = this.cacheCanvas.getContext("2d");
|
|
var w = this.cacheCanvas.width;
|
|
var h = this.cacheCanvas.height;
|
|
for (var i = 0; i < l; i++) {
|
|
this.filters[i].applyFilter(ctx, 0, 0, w, h);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _getFilterBounds
|
|
* @return {Rectangle}
|
|
* @protected
|
|
**/
|
|
p._getFilterBounds = function (rect) {
|
|
var l,
|
|
filters = this.filters,
|
|
bounds = this._rectangle.setValues(0, 0, 0, 0);
|
|
if (!filters || !(l = filters.length)) {
|
|
return bounds;
|
|
}
|
|
|
|
for (var i = 0; i < l; i++) {
|
|
var f = this.filters[i];
|
|
f.getBounds && f.getBounds(bounds);
|
|
}
|
|
return bounds;
|
|
};
|
|
|
|
/**
|
|
* @method _getBounds
|
|
* @param {Matrix2D} matrix
|
|
* @param {Boolean} ignoreTransform If true, does not apply this object's transform.
|
|
* @return {Rectangle}
|
|
* @protected
|
|
**/
|
|
p._getBounds = function (matrix, ignoreTransform) {
|
|
return this._transformBounds(this.getBounds(), matrix, ignoreTransform);
|
|
};
|
|
|
|
/**
|
|
* @method _transformBounds
|
|
* @param {Rectangle} bounds
|
|
* @param {Matrix2D} matrix
|
|
* @param {Boolean} ignoreTransform
|
|
* @return {Rectangle}
|
|
* @protected
|
|
**/
|
|
p._transformBounds = function (bounds, matrix, ignoreTransform) {
|
|
if (!bounds) {
|
|
return bounds;
|
|
}
|
|
var x = bounds.x,
|
|
y = bounds.y,
|
|
width = bounds.width,
|
|
height = bounds.height,
|
|
mtx = this._props.matrix;
|
|
mtx = ignoreTransform ? mtx.identity() : this.getMatrix(mtx);
|
|
|
|
if (x || y) {
|
|
mtx.appendTransform(0, 0, 1, 1, 0, 0, 0, -x, -y);
|
|
} // TODO: simplify this.
|
|
if (matrix) {
|
|
mtx.prependMatrix(matrix);
|
|
}
|
|
|
|
var x_a = width * mtx.a,
|
|
x_b = width * mtx.b;
|
|
var y_c = height * mtx.c,
|
|
y_d = height * mtx.d;
|
|
var tx = mtx.tx,
|
|
ty = mtx.ty;
|
|
|
|
var minX = tx,
|
|
maxX = tx,
|
|
minY = ty,
|
|
maxY = ty;
|
|
|
|
if ((x = x_a + tx) < minX) {
|
|
minX = x;
|
|
} else if (x > maxX) {
|
|
maxX = x;
|
|
}
|
|
if ((x = x_a + y_c + tx) < minX) {
|
|
minX = x;
|
|
} else if (x > maxX) {
|
|
maxX = x;
|
|
}
|
|
if ((x = y_c + tx) < minX) {
|
|
minX = x;
|
|
} else if (x > maxX) {
|
|
maxX = x;
|
|
}
|
|
|
|
if ((y = x_b + ty) < minY) {
|
|
minY = y;
|
|
} else if (y > maxY) {
|
|
maxY = y;
|
|
}
|
|
if ((y = x_b + y_d + ty) < minY) {
|
|
minY = y;
|
|
} else if (y > maxY) {
|
|
maxY = y;
|
|
}
|
|
if ((y = y_d + ty) < minY) {
|
|
minY = y;
|
|
} else if (y > maxY) {
|
|
maxY = y;
|
|
}
|
|
|
|
return bounds.setValues(minX, minY, maxX - minX, maxY - minY);
|
|
};
|
|
|
|
/**
|
|
* Indicates whether the display object has any mouse event listeners or a cursor.
|
|
* @method _isMouseOpaque
|
|
* @return {Boolean}
|
|
* @protected
|
|
**/
|
|
p._hasMouseEventListener = function () {
|
|
var evts = DisplayObject._MOUSE_EVENTS;
|
|
for (var i = 0, l = evts.length; i < l; i++) {
|
|
if (this.hasEventListener(evts[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
return !!this.cursor;
|
|
};
|
|
|
|
createjs.DisplayObject = createjs.promote(DisplayObject, "EventDispatcher");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Container.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* A Container is a nestable display list that allows you to work with compound display elements. For example you could
|
|
* group arm, leg, torso and head {{#crossLink "Bitmap"}}{{/crossLink}} instances together into a Person Container, and
|
|
* transform them as a group, while still being able to move the individual parts relative to each other. Children of
|
|
* containers have their <code>transform</code> and <code>alpha</code> properties concatenated with their parent
|
|
* Container.
|
|
*
|
|
* For example, a {{#crossLink "Shape"}}{{/crossLink}} with x=100 and alpha=0.5, placed in a Container with <code>x=50</code>
|
|
* and <code>alpha=0.7</code> will be rendered to the canvas at <code>x=150</code> and <code>alpha=0.35</code>.
|
|
* Containers have some overhead, so you generally shouldn't create a Container to hold a single child.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var container = new createjs.Container();
|
|
* container.addChild(bitmapInstance, shapeInstance);
|
|
* container.x = 100;
|
|
*
|
|
* @class Container
|
|
* @extends DisplayObject
|
|
* @constructor
|
|
**/
|
|
function Container() {
|
|
this.DisplayObject_constructor();
|
|
|
|
// public properties:
|
|
/**
|
|
* The array of children in the display list. You should usually use the child management methods such as
|
|
* {{#crossLink "Container/addChild"}}{{/crossLink}}, {{#crossLink "Container/removeChild"}}{{/crossLink}},
|
|
* {{#crossLink "Container/swapChildren"}}{{/crossLink}}, etc, rather than accessing this directly, but it is
|
|
* included for advanced uses.
|
|
* @property children
|
|
* @type Array
|
|
* @default null
|
|
**/
|
|
this.children = [];
|
|
|
|
/**
|
|
* Indicates whether the children of this container are independently enabled for mouse/pointer interaction.
|
|
* If false, the children will be aggregated under the container - for example, a click on a child shape would
|
|
* trigger a click event on the container.
|
|
* @property mouseChildren
|
|
* @type Boolean
|
|
* @default true
|
|
**/
|
|
this.mouseChildren = true;
|
|
|
|
/**
|
|
* If false, the tick will not be propagated to children of this Container. This can provide some performance benefits.
|
|
* In addition to preventing the "tick" event from being dispatched, it will also prevent tick related updates
|
|
* on some display objects (ex. Sprite & MovieClip frame advancing, DOMElement visibility handling).
|
|
* @property tickChildren
|
|
* @type Boolean
|
|
* @default true
|
|
**/
|
|
this.tickChildren = true;
|
|
}
|
|
var p = createjs.extend(Container, createjs.DisplayObject);
|
|
|
|
// getter / setters:
|
|
/**
|
|
* Use the {{#crossLink "Container/numChildren:property"}}{{/crossLink}} property instead.
|
|
* @method getNumChildren
|
|
* @return {Number}
|
|
* @deprecated
|
|
**/
|
|
p.getNumChildren = function () {
|
|
return this.children.length;
|
|
};
|
|
|
|
/**
|
|
* Returns the number of children in the container.
|
|
* @property numChildren
|
|
* @type {Number}
|
|
* @readonly
|
|
**/
|
|
try {
|
|
Object.defineProperties(p, {
|
|
numChildren: { get: p.getNumChildren },
|
|
});
|
|
} catch (e) {}
|
|
|
|
// public methods:
|
|
/**
|
|
* Constructor alias for backwards compatibility. This method will be removed in future versions.
|
|
* Subclasses should be updated to use {{#crossLink "Utility Methods/extends"}}{{/crossLink}}.
|
|
* @method initialize
|
|
* @deprecated in favour of `createjs.promote()`
|
|
**/
|
|
p.initialize = Container; // TODO: deprecated.
|
|
|
|
/**
|
|
* Returns true or false indicating whether the display object would be visible if drawn to a canvas.
|
|
* This does not account for whether it would be visible within the boundaries of the stage.
|
|
*
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method isVisible
|
|
* @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas
|
|
**/
|
|
p.isVisible = function () {
|
|
var hasContent = this.cacheCanvas || this.children.length;
|
|
return !!(
|
|
this.visible &&
|
|
this.alpha > 0 &&
|
|
this.scaleX != 0 &&
|
|
this.scaleY != 0 &&
|
|
hasContent
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform.
|
|
* Returns true if the draw was handled (useful for overriding functionality).
|
|
*
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method draw
|
|
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into.
|
|
* @param {Boolean} [ignoreCache=false] Indicates whether the draw operation should ignore any current cache.
|
|
* For example, used for drawing the cache (to prevent it from simply drawing an existing cache back
|
|
* into itself).
|
|
**/
|
|
p.draw = function (ctx, ignoreCache) {
|
|
if (this.DisplayObject_draw(ctx, ignoreCache)) {
|
|
return true;
|
|
}
|
|
|
|
// this ensures we don't have issues with display list changes that occur during a draw:
|
|
var list = this.children.slice();
|
|
for (var i = 0, l = list.length; i < l; i++) {
|
|
var child = list[i];
|
|
if (!child.isVisible()) {
|
|
continue;
|
|
}
|
|
|
|
// draw the child:
|
|
ctx.save();
|
|
child.updateContext(ctx);
|
|
child.draw(ctx);
|
|
ctx.restore();
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Adds a child to the top of the display list.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* container.addChild(bitmapInstance);
|
|
*
|
|
* You can also add multiple children at once:
|
|
*
|
|
* container.addChild(bitmapInstance, shapeInstance, textInstance);
|
|
*
|
|
* @method addChild
|
|
* @param {DisplayObject} child The display object to add.
|
|
* @return {DisplayObject} The child that was added, or the last child if multiple children were added.
|
|
**/
|
|
p.addChild = function (child) {
|
|
if (child == null) {
|
|
return child;
|
|
}
|
|
var l = arguments.length;
|
|
if (l > 1) {
|
|
for (var i = 0; i < l; i++) {
|
|
this.addChild(arguments[i]);
|
|
}
|
|
return arguments[l - 1];
|
|
}
|
|
if (child.parent) {
|
|
child.parent.removeChild(child);
|
|
}
|
|
child.parent = this;
|
|
this.children.push(child);
|
|
child.dispatchEvent("added");
|
|
return child;
|
|
};
|
|
|
|
/**
|
|
* Adds a child to the display list at the specified index, bumping children at equal or greater indexes up one, and
|
|
* setting its parent to this Container.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* addChildAt(child1, index);
|
|
*
|
|
* You can also add multiple children, such as:
|
|
*
|
|
* addChildAt(child1, child2, ..., index);
|
|
*
|
|
* The index must be between 0 and numChildren. For example, to add myShape under otherShape in the display list,
|
|
* you could use:
|
|
*
|
|
* container.addChildAt(myShape, container.getChildIndex(otherShape));
|
|
*
|
|
* This would also bump otherShape's index up by one. Fails silently if the index is out of range.
|
|
*
|
|
* @method addChildAt
|
|
* @param {DisplayObject} child The display object to add.
|
|
* @param {Number} index The index to add the child at.
|
|
* @return {DisplayObject} Returns the last child that was added, or the last child if multiple children were added.
|
|
**/
|
|
p.addChildAt = function (child, index) {
|
|
var l = arguments.length;
|
|
var indx = arguments[l - 1]; // can't use the same name as the index param or it replaces arguments[1]
|
|
if (indx < 0 || indx > this.children.length) {
|
|
return arguments[l - 2];
|
|
}
|
|
if (l > 2) {
|
|
for (var i = 0; i < l - 1; i++) {
|
|
this.addChildAt(arguments[i], indx + i);
|
|
}
|
|
return arguments[l - 2];
|
|
}
|
|
if (child.parent) {
|
|
child.parent.removeChild(child);
|
|
}
|
|
child.parent = this;
|
|
this.children.splice(index, 0, child);
|
|
child.dispatchEvent("added");
|
|
return child;
|
|
};
|
|
|
|
/**
|
|
* Removes the specified child from the display list. Note that it is faster to use removeChildAt() if the index is
|
|
* already known.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* container.removeChild(child);
|
|
*
|
|
* You can also remove multiple children:
|
|
*
|
|
* removeChild(child1, child2, ...);
|
|
*
|
|
* Returns true if the child (or children) was removed, or false if it was not in the display list.
|
|
* @method removeChild
|
|
* @param {DisplayObject} child The child to remove.
|
|
* @return {Boolean} true if the child (or children) was removed, or false if it was not in the display list.
|
|
**/
|
|
p.removeChild = function (child) {
|
|
var l = arguments.length;
|
|
if (l > 1) {
|
|
var good = true;
|
|
for (var i = 0; i < l; i++) {
|
|
good = good && this.removeChild(arguments[i]);
|
|
}
|
|
return good;
|
|
}
|
|
return this.removeChildAt(createjs.indexOf(this.children, child));
|
|
};
|
|
|
|
/**
|
|
* Removes the child at the specified index from the display list, and sets its parent to null.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* container.removeChildAt(2);
|
|
*
|
|
* You can also remove multiple children:
|
|
*
|
|
* container.removeChild(2, 7, ...)
|
|
*
|
|
* Returns true if the child (or children) was removed, or false if any index was out of range.
|
|
* @method removeChildAt
|
|
* @param {Number} index The index of the child to remove.
|
|
* @return {Boolean} true if the child (or children) was removed, or false if any index was out of range.
|
|
**/
|
|
p.removeChildAt = function (index) {
|
|
var l = arguments.length;
|
|
if (l > 1) {
|
|
var a = [];
|
|
for (var i = 0; i < l; i++) {
|
|
a[i] = arguments[i];
|
|
}
|
|
a.sort(function (a, b) {
|
|
return b - a;
|
|
});
|
|
var good = true;
|
|
for (var i = 0; i < l; i++) {
|
|
good = good && this.removeChildAt(a[i]);
|
|
}
|
|
return good;
|
|
}
|
|
if (index < 0 || index > this.children.length - 1) {
|
|
return false;
|
|
}
|
|
var child = this.children[index];
|
|
if (child) {
|
|
child.parent = null;
|
|
}
|
|
this.children.splice(index, 1);
|
|
child.dispatchEvent("removed");
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Removes all children from the display list.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* container.removeAllChildren();
|
|
*
|
|
* @method removeAllChildren
|
|
**/
|
|
p.removeAllChildren = function () {
|
|
var kids = this.children;
|
|
while (kids.length) {
|
|
this.removeChildAt(0);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns the child at the specified index.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* container.getChildAt(2);
|
|
*
|
|
* @method getChildAt
|
|
* @param {Number} index The index of the child to return.
|
|
* @return {DisplayObject} The child at the specified index. Returns null if there is no child at the index.
|
|
**/
|
|
p.getChildAt = function (index) {
|
|
return this.children[index];
|
|
};
|
|
|
|
/**
|
|
* Returns the child with the specified name.
|
|
* @method getChildByName
|
|
* @param {String} name The name of the child to return.
|
|
* @return {DisplayObject} The child with the specified name.
|
|
**/
|
|
p.getChildByName = function (name) {
|
|
var kids = this.children;
|
|
for (var i = 0, l = kids.length; i < l; i++) {
|
|
if (kids[i].name == name) {
|
|
return kids[i];
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Performs an array sort operation on the child list.
|
|
*
|
|
* <h4>Example: Display children with a higher y in front.</h4>
|
|
*
|
|
* var sortFunction = function(obj1, obj2, options) {
|
|
* if (obj1.y > obj2.y) { return 1; }
|
|
* if (obj1.y < obj2.y) { return -1; }
|
|
* return 0;
|
|
* }
|
|
* container.sortChildren(sortFunction);
|
|
*
|
|
* @method sortChildren
|
|
* @param {Function} sortFunction the function to use to sort the child list. See JavaScript's <code>Array.sort</code>
|
|
* documentation for details.
|
|
**/
|
|
p.sortChildren = function (sortFunction) {
|
|
this.children.sort(sortFunction);
|
|
};
|
|
|
|
/**
|
|
* Returns the index of the specified child in the display list, or -1 if it is not in the display list.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var index = container.getChildIndex(child);
|
|
*
|
|
* @method getChildIndex
|
|
* @param {DisplayObject} child The child to return the index of.
|
|
* @return {Number} The index of the specified child. -1 if the child is not found.
|
|
**/
|
|
p.getChildIndex = function (child) {
|
|
return createjs.indexOf(this.children, child);
|
|
};
|
|
|
|
/**
|
|
* Swaps the children at the specified indexes. Fails silently if either index is out of range.
|
|
* @method swapChildrenAt
|
|
* @param {Number} index1
|
|
* @param {Number} index2
|
|
**/
|
|
p.swapChildrenAt = function (index1, index2) {
|
|
var kids = this.children;
|
|
var o1 = kids[index1];
|
|
var o2 = kids[index2];
|
|
if (!o1 || !o2) {
|
|
return;
|
|
}
|
|
kids[index1] = o2;
|
|
kids[index2] = o1;
|
|
};
|
|
|
|
/**
|
|
* Swaps the specified children's depth in the display list. Fails silently if either child is not a child of this
|
|
* Container.
|
|
* @method swapChildren
|
|
* @param {DisplayObject} child1
|
|
* @param {DisplayObject} child2
|
|
**/
|
|
p.swapChildren = function (child1, child2) {
|
|
var kids = this.children;
|
|
var index1, index2;
|
|
for (var i = 0, l = kids.length; i < l; i++) {
|
|
if (kids[i] == child1) {
|
|
index1 = i;
|
|
}
|
|
if (kids[i] == child2) {
|
|
index2 = i;
|
|
}
|
|
if (index1 != null && index2 != null) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == l) {
|
|
return;
|
|
} // TODO: throw error?
|
|
kids[index1] = child2;
|
|
kids[index2] = child1;
|
|
};
|
|
|
|
/**
|
|
* Changes the depth of the specified child. Fails silently if the child is not a child of this container, or the index is out of range.
|
|
* @param {DisplayObject} child
|
|
* @param {Number} index
|
|
* @method setChildIndex
|
|
**/
|
|
p.setChildIndex = function (child, index) {
|
|
var kids = this.children,
|
|
l = kids.length;
|
|
if (child.parent != this || index < 0 || index >= l) {
|
|
return;
|
|
}
|
|
for (var i = 0; i < l; i++) {
|
|
if (kids[i] == child) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == l || i == index) {
|
|
return;
|
|
}
|
|
kids.splice(i, 1);
|
|
kids.splice(index, 0, child);
|
|
};
|
|
|
|
/**
|
|
* Returns true if the specified display object either is this container or is a descendent (child, grandchild, etc)
|
|
* of this container.
|
|
* @method contains
|
|
* @param {DisplayObject} child The DisplayObject to be checked.
|
|
* @return {Boolean} true if the specified display object either is this container or is a descendent.
|
|
**/
|
|
p.contains = function (child) {
|
|
while (child) {
|
|
if (child == this) {
|
|
return true;
|
|
}
|
|
child = child.parent;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Tests whether the display object intersects the specified local point (ie. draws a pixel with alpha > 0 at the
|
|
* specified position). This ignores the alpha, shadow and compositeOperation of the display object, and all
|
|
* transform properties including regX/Y.
|
|
* @method hitTest
|
|
* @param {Number} x The x position to check in the display object's local coordinates.
|
|
* @param {Number} y The y position to check in the display object's local coordinates.
|
|
* @return {Boolean} A Boolean indicating whether there is a visible section of a DisplayObject that overlaps the specified
|
|
* coordinates.
|
|
**/
|
|
p.hitTest = function (x, y) {
|
|
// TODO: optimize to use the fast cache check where possible.
|
|
return this.getObjectUnderPoint(x, y) != null;
|
|
};
|
|
|
|
/**
|
|
* Returns an array of all display objects under the specified coordinates that are in this container's display
|
|
* list. This routine ignores any display objects with {{#crossLink "DisplayObject/mouseEnabled:property"}}{{/crossLink}}
|
|
* set to `false`. The array will be sorted in order of visual depth, with the top-most display object at index 0.
|
|
* This uses shape based hit detection, and can be an expensive operation to run, so it is best to use it carefully.
|
|
* For example, if testing for objects under the mouse, test on tick (instead of on {{#crossLink "DisplayObject/mousemove:event"}}{{/crossLink}}),
|
|
* and only if the mouse's position has changed.
|
|
*
|
|
* <ul>
|
|
* <li>By default (mode=0) this method evaluates all display objects.</li>
|
|
* <li>By setting the `mode` parameter to `1`, the {{#crossLink "DisplayObject/mouseEnabled:property"}}{{/crossLink}}
|
|
* and {{#crossLink "mouseChildren:property"}}{{/crossLink}} properties will be respected.</li>
|
|
* <li>Setting the `mode` to `2` additionally excludes display objects that do not have active mouse event
|
|
* listeners or a {{#crossLink "DisplayObject:cursor:property"}}{{/crossLink}} property. That is, only objects
|
|
* that would normally intercept mouse interaction will be included. This can significantly improve performance
|
|
* in some cases by reducing the number of display objects that need to be tested.</li>
|
|
* </li>
|
|
*
|
|
* This method accounts for both {{#crossLink "DisplayObject/hitArea:property"}}{{/crossLink}} and {{#crossLink "DisplayObject/mask:property"}}{{/crossLink}}.
|
|
* @method getObjectsUnderPoint
|
|
* @param {Number} x The x position in the container to test.
|
|
* @param {Number} y The y position in the container to test.
|
|
* @param {Number} [mode=0] The mode to use to determine which display objects to include. 0-all, 1-respect mouseEnabled/mouseChildren, 2-only mouse opaque objects.
|
|
* @return {Array} An Array of DisplayObjects under the specified coordinates.
|
|
**/
|
|
p.getObjectsUnderPoint = function (x, y, mode) {
|
|
var arr = [];
|
|
var pt = this.localToGlobal(x, y);
|
|
this._getObjectsUnderPoint(pt.x, pt.y, arr, mode > 0, mode == 1);
|
|
return arr;
|
|
};
|
|
|
|
/**
|
|
* Similar to {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}}, but returns only the top-most display
|
|
* object. This runs significantly faster than <code>getObjectsUnderPoint()</code>, but is still potentially an expensive
|
|
* operation. See {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}} for more information.
|
|
* @method getObjectUnderPoint
|
|
* @param {Number} x The x position in the container to test.
|
|
* @param {Number} y The y position in the container to test.
|
|
* @param {Number} mode The mode to use to determine which display objects to include. 0-all, 1-respect mouseEnabled/mouseChildren, 2-only mouse opaque objects.
|
|
* @return {DisplayObject} The top-most display object under the specified coordinates.
|
|
**/
|
|
p.getObjectUnderPoint = function (x, y, mode) {
|
|
var pt = this.localToGlobal(x, y);
|
|
return this._getObjectsUnderPoint(pt.x, pt.y, null, mode > 0, mode == 1);
|
|
};
|
|
|
|
/**
|
|
* Docced in superclass.
|
|
*/
|
|
p.getBounds = function () {
|
|
return this._getBounds(null, true);
|
|
};
|
|
|
|
/**
|
|
* Docced in superclass.
|
|
*/
|
|
p.getTransformedBounds = function () {
|
|
return this._getBounds();
|
|
};
|
|
|
|
/**
|
|
* Returns a clone of this Container. Some properties that are specific to this instance's current context are
|
|
* reverted to their defaults (for example .parent).
|
|
* @method clone
|
|
* @param {Boolean} [recursive=false] If true, all of the descendants of this container will be cloned recursively. If false, the
|
|
* properties of the container will be cloned, but the new instance will not have any children.
|
|
* @return {Container} A clone of the current Container instance.
|
|
**/
|
|
p.clone = function (recursive) {
|
|
var o = this._cloneProps(new Container());
|
|
if (recursive) {
|
|
this._cloneChildren(o);
|
|
}
|
|
return o;
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[Container (name=" + this.name + ")]";
|
|
};
|
|
|
|
// private methods:
|
|
/**
|
|
* @method _tick
|
|
* @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs.
|
|
* @protected
|
|
**/
|
|
p._tick = function (evtObj) {
|
|
if (this.tickChildren) {
|
|
for (var i = this.children.length - 1; i >= 0; i--) {
|
|
var child = this.children[i];
|
|
if (child.tickEnabled && child._tick) {
|
|
child._tick(evtObj);
|
|
}
|
|
}
|
|
}
|
|
this.DisplayObject__tick(evtObj);
|
|
};
|
|
|
|
/**
|
|
* Recursively clones all children of this container, and adds them to the target container.
|
|
* @method cloneChildren
|
|
* @protected
|
|
* @param {Container} o The target container.
|
|
**/
|
|
p._cloneChildren = function (o) {
|
|
if (o.children.length) {
|
|
o.removeAllChildren();
|
|
}
|
|
var arr = o.children;
|
|
for (var i = 0, l = this.children.length; i < l; i++) {
|
|
var clone = this.children[i].clone(true);
|
|
clone.parent = o;
|
|
arr.push(clone);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _getObjectsUnderPoint
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Array} arr
|
|
* @param {Boolean} mouse If true, it will respect mouse interaction properties like mouseEnabled, mouseChildren, and active listeners.
|
|
* @param {Boolean} activeListener If true, there is an active mouse event listener on a parent object.
|
|
* @param {Number} currentDepth Indicates the current depth of the search.
|
|
* @return {DisplayObject}
|
|
* @protected
|
|
**/
|
|
p._getObjectsUnderPoint = function (
|
|
x,
|
|
y,
|
|
arr,
|
|
mouse,
|
|
activeListener,
|
|
currentDepth
|
|
) {
|
|
currentDepth = currentDepth || 0;
|
|
if (!currentDepth && !this._testMask(this, x, y)) {
|
|
return null;
|
|
}
|
|
var mtx,
|
|
ctx = createjs.DisplayObject._hitTestContext;
|
|
activeListener = activeListener || (mouse && this._hasMouseEventListener());
|
|
|
|
// draw children one at a time, and check if we get a hit:
|
|
var children = this.children,
|
|
l = children.length;
|
|
for (var i = l - 1; i >= 0; i--) {
|
|
var child = children[i];
|
|
var hitArea = child.hitArea;
|
|
if (
|
|
!child.visible ||
|
|
(!hitArea && !child.isVisible()) ||
|
|
(mouse && !child.mouseEnabled)
|
|
) {
|
|
continue;
|
|
}
|
|
if (!hitArea && !this._testMask(child, x, y)) {
|
|
continue;
|
|
}
|
|
|
|
// if a child container has a hitArea then we only need to check its hitArea, so we can treat it as a normal DO:
|
|
if (!hitArea && child instanceof Container) {
|
|
var result = child._getObjectsUnderPoint(
|
|
x,
|
|
y,
|
|
arr,
|
|
mouse,
|
|
activeListener,
|
|
currentDepth + 1
|
|
);
|
|
if (!arr && result) {
|
|
return mouse && !this.mouseChildren ? this : result;
|
|
}
|
|
} else {
|
|
if (mouse && !activeListener && !child._hasMouseEventListener()) {
|
|
continue;
|
|
}
|
|
|
|
// TODO: can we pass displayProps forward, to avoid having to calculate this backwards every time? It's kind of a mixed bag. When we're only hunting for DOs with event listeners, it may not make sense.
|
|
var props = child.getConcatenatedDisplayProps(child._props);
|
|
mtx = props.matrix;
|
|
|
|
if (hitArea) {
|
|
mtx.appendMatrix(hitArea.getMatrix(hitArea._props.matrix));
|
|
props.alpha = hitArea.alpha;
|
|
}
|
|
|
|
ctx.globalAlpha = props.alpha;
|
|
ctx.setTransform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx - x, mtx.ty - y);
|
|
(hitArea || child).draw(ctx);
|
|
if (!this._testHit(ctx)) {
|
|
continue;
|
|
}
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
ctx.clearRect(0, 0, 2, 2);
|
|
if (arr) {
|
|
arr.push(child);
|
|
} else {
|
|
return mouse && !this.mouseChildren ? this : child;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* @method _testMask
|
|
* @param {DisplayObject} target
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @return {Boolean} Indicates whether the x/y is within the masked region.
|
|
* @protected
|
|
**/
|
|
p._testMask = function (target, x, y) {
|
|
var mask = target.mask;
|
|
if (!mask || !mask.graphics || mask.graphics.isEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
var mtx = this._props.matrix,
|
|
parent = target.parent;
|
|
mtx = parent ? parent.getConcatenatedMatrix(mtx) : mtx.identity();
|
|
mtx = mask.getMatrix(mask._props.matrix).prependMatrix(mtx);
|
|
|
|
var ctx = createjs.DisplayObject._hitTestContext;
|
|
ctx.setTransform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx - x, mtx.ty - y);
|
|
|
|
// draw the mask as a solid fill:
|
|
mask.graphics.drawAsPath(ctx);
|
|
ctx.fillStyle = "#000";
|
|
ctx.fill();
|
|
|
|
if (!this._testHit(ctx)) {
|
|
return false;
|
|
}
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
ctx.clearRect(0, 0, 2, 2);
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* @method _getBounds
|
|
* @param {Matrix2D} matrix
|
|
* @param {Boolean} ignoreTransform If true, does not apply this object's transform.
|
|
* @return {Rectangle}
|
|
* @protected
|
|
**/
|
|
p._getBounds = function (matrix, ignoreTransform) {
|
|
var bounds = this.DisplayObject_getBounds();
|
|
if (bounds) {
|
|
return this._transformBounds(bounds, matrix, ignoreTransform);
|
|
}
|
|
|
|
var mtx = this._props.matrix;
|
|
mtx = ignoreTransform ? mtx.identity() : this.getMatrix(mtx);
|
|
if (matrix) {
|
|
mtx.prependMatrix(matrix);
|
|
}
|
|
|
|
var l = this.children.length,
|
|
rect = null;
|
|
for (var i = 0; i < l; i++) {
|
|
var child = this.children[i];
|
|
if (!child.visible || !(bounds = child._getBounds(mtx))) {
|
|
continue;
|
|
}
|
|
if (rect) {
|
|
rect.extend(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
} else {
|
|
rect = bounds.clone();
|
|
}
|
|
}
|
|
return rect;
|
|
};
|
|
|
|
createjs.Container = createjs.promote(Container, "DisplayObject");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Stage.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* A stage is the root level {{#crossLink "Container"}}{{/crossLink}} for a display list. Each time its {{#crossLink "Stage/tick"}}{{/crossLink}}
|
|
* method is called, it will render its display list to its target canvas.
|
|
*
|
|
* <h4>Example</h4>
|
|
* This example creates a stage, adds a child to it, then uses {{#crossLink "Ticker"}}{{/crossLink}} to update the child
|
|
* and redraw the stage using {{#crossLink "Stage/update"}}{{/crossLink}}.
|
|
*
|
|
* var stage = new createjs.Stage("canvasElementId");
|
|
* var image = new createjs.Bitmap("imagePath.png");
|
|
* stage.addChild(image);
|
|
* createjs.Ticker.addEventListener("tick", handleTick);
|
|
* function handleTick(event) {
|
|
* image.x += 10;
|
|
* stage.update();
|
|
* }
|
|
*
|
|
* @class Stage
|
|
* @extends Container
|
|
* @constructor
|
|
* @param {HTMLCanvasElement | String | Object} canvas A canvas object that the Stage will render to, or the string id
|
|
* of a canvas object in the current document.
|
|
**/
|
|
function Stage(canvas) {
|
|
this.Container_constructor();
|
|
|
|
// public properties:
|
|
/**
|
|
* Indicates whether the stage should automatically clear the canvas before each render. You can set this to <code>false</code>
|
|
* to manually control clearing (for generative art, or when pointing multiple stages at the same canvas for
|
|
* example).
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var stage = new createjs.Stage("canvasId");
|
|
* stage.autoClear = false;
|
|
*
|
|
* @property autoClear
|
|
* @type Boolean
|
|
* @default true
|
|
**/
|
|
this.autoClear = true;
|
|
|
|
/**
|
|
* The canvas the stage will render to. Multiple stages can share a single canvas, but you must disable autoClear for all but the
|
|
* first stage that will be ticked (or they will clear each other's render).
|
|
*
|
|
* When changing the canvas property you must disable the events on the old canvas, and enable events on the
|
|
* new canvas or mouse events will not work as expected. For example:
|
|
*
|
|
* myStage.enableDOMEvents(false);
|
|
* myStage.canvas = anotherCanvas;
|
|
* myStage.enableDOMEvents(true);
|
|
*
|
|
* @property canvas
|
|
* @type HTMLCanvasElement | Object
|
|
**/
|
|
this.canvas =
|
|
typeof canvas == "string" ? document.getElementById(canvas) : canvas;
|
|
|
|
/**
|
|
* The current mouse X position on the canvas. If the mouse leaves the canvas, this will indicate the most recent
|
|
* position over the canvas, and mouseInBounds will be set to false.
|
|
* @property mouseX
|
|
* @type Number
|
|
* @readonly
|
|
**/
|
|
this.mouseX = 0;
|
|
|
|
/**
|
|
* The current mouse Y position on the canvas. If the mouse leaves the canvas, this will indicate the most recent
|
|
* position over the canvas, and mouseInBounds will be set to false.
|
|
* @property mouseY
|
|
* @type Number
|
|
* @readonly
|
|
**/
|
|
this.mouseY = 0;
|
|
|
|
/**
|
|
* Specifies the area of the stage to affect when calling update. This can be use to selectively
|
|
* re-draw specific regions of the canvas. If null, the whole canvas area is drawn.
|
|
* @property drawRect
|
|
* @type {Rectangle}
|
|
*/
|
|
this.drawRect = null;
|
|
|
|
/**
|
|
* Indicates whether display objects should be rendered on whole pixels. You can set the
|
|
* {{#crossLink "DisplayObject/snapToPixel"}}{{/crossLink}} property of
|
|
* display objects to false to enable/disable this behaviour on a per instance basis.
|
|
* @property snapToPixelEnabled
|
|
* @type Boolean
|
|
* @default false
|
|
**/
|
|
this.snapToPixelEnabled = false;
|
|
|
|
/**
|
|
* Indicates whether the mouse is currently within the bounds of the canvas.
|
|
* @property mouseInBounds
|
|
* @type Boolean
|
|
* @default false
|
|
**/
|
|
this.mouseInBounds = false;
|
|
|
|
/**
|
|
* If true, tick callbacks will be called on all display objects on the stage prior to rendering to the canvas.
|
|
* @property tickOnUpdate
|
|
* @type Boolean
|
|
* @default true
|
|
**/
|
|
this.tickOnUpdate = true;
|
|
|
|
/**
|
|
* If true, mouse move events will continue to be called when the mouse leaves the target canvas. See
|
|
* {{#crossLink "Stage/mouseInBounds:property"}}{{/crossLink}}, and {{#crossLink "MouseEvent"}}{{/crossLink}}
|
|
* x/y/rawX/rawY.
|
|
* @property mouseMoveOutside
|
|
* @type Boolean
|
|
* @default false
|
|
**/
|
|
this.mouseMoveOutside = false;
|
|
|
|
/**
|
|
* Prevents selection of other elements in the html page if the user clicks and drags, or double clicks on the canvas.
|
|
* This works by calling `preventDefault()` on any mousedown events (or touch equivalent) originating on the canvas.
|
|
* @property preventSelection
|
|
* @type Boolean
|
|
* @default true
|
|
**/
|
|
this.preventSelection = true;
|
|
|
|
/**
|
|
* The hitArea property is not supported for Stage.
|
|
* @property hitArea
|
|
* @type {DisplayObject}
|
|
* @default null
|
|
*/
|
|
|
|
// private properties:
|
|
/**
|
|
* Holds objects with data for each active pointer id. Each object has the following properties:
|
|
* x, y, event, target, overTarget, overX, overY, inBounds, posEvtObj (native event that last updated position)
|
|
* @property _pointerData
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
this._pointerData = {};
|
|
|
|
/**
|
|
* Number of active pointers.
|
|
* @property _pointerCount
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
this._pointerCount = 0;
|
|
|
|
/**
|
|
* The ID of the primary pointer.
|
|
* @property _primaryPointerID
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
this._primaryPointerID = null;
|
|
|
|
/**
|
|
* @property _mouseOverIntervalID
|
|
* @protected
|
|
* @type Number
|
|
**/
|
|
this._mouseOverIntervalID = null;
|
|
|
|
/**
|
|
* @property _nextStage
|
|
* @protected
|
|
* @type Stage
|
|
**/
|
|
this._nextStage = null;
|
|
|
|
/**
|
|
* @property _prevStage
|
|
* @protected
|
|
* @type Stage
|
|
**/
|
|
this._prevStage = null;
|
|
|
|
// initialize:
|
|
this.enableDOMEvents(true);
|
|
}
|
|
var p = createjs.extend(Stage, createjs.Container);
|
|
|
|
/**
|
|
* <strong>REMOVED</strong>. Removed in favor of using `MySuperClass_constructor`.
|
|
* See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}}
|
|
* for details.
|
|
*
|
|
* There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance.
|
|
*
|
|
* @method initialize
|
|
* @protected
|
|
* @deprecated
|
|
*/
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is.
|
|
|
|
// events:
|
|
/**
|
|
* Dispatched when the user moves the mouse over the canvas.
|
|
* See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties.
|
|
* @event stagemousemove
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
/**
|
|
* Dispatched when the user presses their left mouse button on the canvas. See the {{#crossLink "MouseEvent"}}{{/crossLink}}
|
|
* class for a listing of event properties.
|
|
* @event stagemousedown
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
/**
|
|
* Dispatched when the user the user presses somewhere on the stage, then releases the mouse button anywhere that the page can detect it (this varies slightly between browsers).
|
|
* You can use {{#crossLink "Stage/mouseInBounds:property"}}{{/crossLink}} to check whether the mouse is currently within the stage bounds.
|
|
* See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties.
|
|
* @event stagemouseup
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
/**
|
|
* Dispatched when the mouse moves from within the canvas area (mouseInBounds == true) to outside it (mouseInBounds == false).
|
|
* This is currently only dispatched for mouse input (not touch). See the {{#crossLink "MouseEvent"}}{{/crossLink}}
|
|
* class for a listing of event properties.
|
|
* @event mouseleave
|
|
* @since 0.7.0
|
|
*/
|
|
|
|
/**
|
|
* Dispatched when the mouse moves into the canvas area (mouseInBounds == false) from outside it (mouseInBounds == true).
|
|
* This is currently only dispatched for mouse input (not touch). See the {{#crossLink "MouseEvent"}}{{/crossLink}}
|
|
* class for a listing of event properties.
|
|
* @event mouseenter
|
|
* @since 0.7.0
|
|
*/
|
|
|
|
/**
|
|
* Dispatched each update immediately before the tick event is propagated through the display list.
|
|
* You can call preventDefault on the event object to cancel propagating the tick event.
|
|
* @event tickstart
|
|
* @since 0.7.0
|
|
*/
|
|
|
|
/**
|
|
* Dispatched each update immediately after the tick event is propagated through the display list. Does not fire if
|
|
* tickOnUpdate is false. Precedes the "drawstart" event.
|
|
* @event tickend
|
|
* @since 0.7.0
|
|
*/
|
|
|
|
/**
|
|
* Dispatched each update immediately before the canvas is cleared and the display list is drawn to it.
|
|
* You can call preventDefault on the event object to cancel the draw.
|
|
* @event drawstart
|
|
* @since 0.7.0
|
|
*/
|
|
|
|
/**
|
|
* Dispatched each update immediately after the display list is drawn to the canvas and the canvas context is restored.
|
|
* @event drawend
|
|
* @since 0.7.0
|
|
*/
|
|
|
|
// getter / setters:
|
|
/**
|
|
* Specifies a target stage that will have mouse / touch interactions relayed to it after this stage handles them.
|
|
* This can be useful in cases where you have multiple layered canvases and want user interactions
|
|
* events to pass through. For example, this would relay mouse events from topStage to bottomStage:
|
|
*
|
|
* topStage.nextStage = bottomStage;
|
|
*
|
|
* To disable relaying, set nextStage to null.
|
|
*
|
|
* MouseOver, MouseOut, RollOver, and RollOut interactions are also passed through using the mouse over settings
|
|
* of the top-most stage, but are only processed if the target stage has mouse over interactions enabled.
|
|
* Considerations when using roll over in relay targets:<OL>
|
|
* <LI> The top-most (first) stage must have mouse over interactions enabled (via enableMouseOver)</LI>
|
|
* <LI> All stages that wish to participate in mouse over interaction must enable them via enableMouseOver</LI>
|
|
* <LI> All relay targets will share the frequency value of the top-most stage</LI>
|
|
* </OL>
|
|
* To illustrate, in this example the targetStage would process mouse over interactions at 10hz (despite passing
|
|
* 30 as it's desired frequency):
|
|
* topStage.nextStage = targetStage;
|
|
* topStage.enableMouseOver(10);
|
|
* targetStage.enableMouseOver(30);
|
|
*
|
|
* If the target stage's canvas is completely covered by this stage's canvas, you may also want to disable its
|
|
* DOM events using:
|
|
*
|
|
* targetStage.enableDOMEvents(false);
|
|
*
|
|
* @property nextStage
|
|
* @type {Stage}
|
|
**/
|
|
p._get_nextStage = function () {
|
|
return this._nextStage;
|
|
};
|
|
p._set_nextStage = function (value) {
|
|
if (this._nextStage) {
|
|
this._nextStage._prevStage = null;
|
|
}
|
|
if (value) {
|
|
value._prevStage = this;
|
|
}
|
|
this._nextStage = value;
|
|
};
|
|
|
|
try {
|
|
Object.defineProperties(p, {
|
|
nextStage: { get: p._get_nextStage, set: p._set_nextStage },
|
|
});
|
|
} catch (e) {} // TODO: use Log
|
|
|
|
// public methods:
|
|
/**
|
|
* Each time the update method is called, the stage will call {{#crossLink "Stage/tick"}}{{/crossLink}}
|
|
* unless {{#crossLink "Stage/tickOnUpdate:property"}}{{/crossLink}} is set to false,
|
|
* and then render the display list to the canvas.
|
|
*
|
|
* @method update
|
|
* @param {Object} [props] Props object to pass to `tick()`. Should usually be a {{#crossLink "Ticker"}}{{/crossLink}} event object, or similar object with a delta property.
|
|
**/
|
|
p.update = function (props) {
|
|
if (!this.canvas) {
|
|
return;
|
|
}
|
|
if (this.tickOnUpdate) {
|
|
this.tick(props);
|
|
}
|
|
if (this.dispatchEvent("drawstart", false, true) === false) {
|
|
return;
|
|
}
|
|
createjs.DisplayObject._snapToPixelEnabled = this.snapToPixelEnabled;
|
|
var r = this.drawRect,
|
|
ctx = this.canvas.getContext("2d");
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
if (this.autoClear) {
|
|
if (r) {
|
|
ctx.clearRect(r.x, r.y, r.width, r.height);
|
|
} else {
|
|
ctx.clearRect(0, 0, this.canvas.width + 1, this.canvas.height + 1);
|
|
}
|
|
}
|
|
ctx.save();
|
|
if (this.drawRect) {
|
|
ctx.beginPath();
|
|
ctx.rect(r.x, r.y, r.width, r.height);
|
|
ctx.clip();
|
|
}
|
|
this.updateContext(ctx);
|
|
this.draw(ctx, false);
|
|
ctx.restore();
|
|
this.dispatchEvent("drawend");
|
|
};
|
|
|
|
/**
|
|
* Propagates a tick event through the display list. This is automatically called by {{#crossLink "Stage/update"}}{{/crossLink}}
|
|
* unless {{#crossLink "Stage/tickOnUpdate:property"}}{{/crossLink}} is set to false.
|
|
*
|
|
* If a props object is passed to `tick()`, then all of its properties will be copied to the event object that is
|
|
* propagated to listeners.
|
|
*
|
|
* Some time-based features in EaselJS (for example {{#crossLink "Sprite/framerate"}}{{/crossLink}} require that
|
|
* a {{#crossLink "Ticker/tick:event"}}{{/crossLink}} event object (or equivalent object with a delta property) be
|
|
* passed as the `props` parameter to `tick()`. For example:
|
|
*
|
|
* Ticker.on("tick", handleTick);
|
|
* function handleTick(evtObj) {
|
|
* // clone the event object from Ticker, and add some custom data to it:
|
|
* var evt = evtObj.clone().set({greeting:"hello", name:"world"});
|
|
*
|
|
* // pass it to stage.update():
|
|
* myStage.update(evt); // subsequently calls tick() with the same param
|
|
* }
|
|
*
|
|
* // ...
|
|
* myDisplayObject.on("tick", handleDisplayObjectTick);
|
|
* function handleDisplayObjectTick(evt) {
|
|
* console.log(evt.delta); // the delta property from the Ticker tick event object
|
|
* console.log(evt.greeting, evt.name); // custom data: "hello world"
|
|
* }
|
|
*
|
|
* @method tick
|
|
* @param {Object} [props] An object with properties that should be copied to the event object. Should usually be a Ticker event object, or similar object with a delta property.
|
|
**/
|
|
p.tick = function (props) {
|
|
if (
|
|
!this.tickEnabled ||
|
|
this.dispatchEvent("tickstart", false, true) === false
|
|
) {
|
|
return;
|
|
}
|
|
var evtObj = new createjs.Event("tick");
|
|
if (props) {
|
|
for (var n in props) {
|
|
if (props.hasOwnProperty(n)) {
|
|
evtObj[n] = props[n];
|
|
}
|
|
}
|
|
}
|
|
this._tick(evtObj);
|
|
this.dispatchEvent("tickend");
|
|
};
|
|
|
|
/**
|
|
* Default event handler that calls the Stage {{#crossLink "Stage/update"}}{{/crossLink}} method when a {{#crossLink "DisplayObject/tick:event"}}{{/crossLink}}
|
|
* event is received. This allows you to register a Stage instance as a event listener on {{#crossLink "Ticker"}}{{/crossLink}}
|
|
* directly, using:
|
|
*
|
|
* Ticker.addEventListener("tick", myStage");
|
|
*
|
|
* Note that if you subscribe to ticks using this pattern, then the tick event object will be passed through to
|
|
* display object tick handlers, instead of <code>delta</code> and <code>paused</code> parameters.
|
|
* @property handleEvent
|
|
* @type Function
|
|
**/
|
|
p.handleEvent = function (evt) {
|
|
if (evt.type == "tick") {
|
|
this.update(evt);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Clears the target canvas. Useful if {{#crossLink "Stage/autoClear:property"}}{{/crossLink}} is set to `false`.
|
|
* @method clear
|
|
**/
|
|
p.clear = function () {
|
|
if (!this.canvas) {
|
|
return;
|
|
}
|
|
var ctx = this.canvas.getContext("2d");
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
ctx.clearRect(0, 0, this.canvas.width + 1, this.canvas.height + 1);
|
|
};
|
|
|
|
/**
|
|
* Returns a data url that contains a Base64-encoded image of the contents of the stage. The returned data url can
|
|
* be specified as the src value of an image element.
|
|
* @method toDataURL
|
|
* @param {String} [backgroundColor] The background color to be used for the generated image. Any valid CSS color
|
|
* value is allowed. The default value is a transparent background.
|
|
* @param {String} [mimeType="image/png"] The MIME type of the image format to be create. The default is "image/png". If an unknown MIME type
|
|
* is passed in, or if the browser does not support the specified MIME type, the default value will be used.
|
|
* @return {String} a Base64 encoded image.
|
|
**/
|
|
p.toDataURL = function (backgroundColor, mimeType) {
|
|
var data,
|
|
ctx = this.canvas.getContext("2d"),
|
|
w = this.canvas.width,
|
|
h = this.canvas.height;
|
|
|
|
if (backgroundColor) {
|
|
data = ctx.getImageData(0, 0, w, h);
|
|
var compositeOperation = ctx.globalCompositeOperation;
|
|
ctx.globalCompositeOperation = "destination-over";
|
|
|
|
ctx.fillStyle = backgroundColor;
|
|
ctx.fillRect(0, 0, w, h);
|
|
}
|
|
|
|
var dataURL = this.canvas.toDataURL(mimeType || "image/png");
|
|
|
|
if (backgroundColor) {
|
|
ctx.putImageData(data, 0, 0);
|
|
ctx.globalCompositeOperation = compositeOperation;
|
|
}
|
|
|
|
return dataURL;
|
|
};
|
|
|
|
/**
|
|
* Enables or disables (by passing a frequency of 0) mouse over ({{#crossLink "DisplayObject/mouseover:event"}}{{/crossLink}}
|
|
* and {{#crossLink "DisplayObject/mouseout:event"}}{{/crossLink}}) and roll over events ({{#crossLink "DisplayObject/rollover:event"}}{{/crossLink}}
|
|
* and {{#crossLink "DisplayObject/rollout:event"}}{{/crossLink}}) for this stage's display list. These events can
|
|
* be expensive to generate, so they are disabled by default. The frequency of the events can be controlled
|
|
* independently of mouse move events via the optional `frequency` parameter.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var stage = new createjs.Stage("canvasId");
|
|
* stage.enableMouseOver(10); // 10 updates per second
|
|
*
|
|
* @method enableMouseOver
|
|
* @param {Number} [frequency=20] Optional param specifying the maximum number of times per second to broadcast
|
|
* mouse over/out events. Set to 0 to disable mouse over events completely. Maximum is 50. A lower frequency is less
|
|
* responsive, but uses less CPU.
|
|
**/
|
|
p.enableMouseOver = function (frequency) {
|
|
if (this._mouseOverIntervalID) {
|
|
clearInterval(this._mouseOverIntervalID);
|
|
this._mouseOverIntervalID = null;
|
|
if (frequency == 0) {
|
|
this._testMouseOver(true);
|
|
}
|
|
}
|
|
if (frequency == null) {
|
|
frequency = 20;
|
|
} else if (frequency <= 0) {
|
|
return;
|
|
}
|
|
var o = this;
|
|
this._mouseOverIntervalID = setInterval(function () {
|
|
o._testMouseOver();
|
|
}, 1000 / Math.min(50, frequency));
|
|
};
|
|
|
|
/**
|
|
* Enables or disables the event listeners that stage adds to DOM elements (window, document and canvas). It is good
|
|
* practice to disable events when disposing of a Stage instance, otherwise the stage will continue to receive
|
|
* events from the page.
|
|
*
|
|
* When changing the canvas property you must disable the events on the old canvas, and enable events on the
|
|
* new canvas or mouse events will not work as expected. For example:
|
|
*
|
|
* myStage.enableDOMEvents(false);
|
|
* myStage.canvas = anotherCanvas;
|
|
* myStage.enableDOMEvents(true);
|
|
*
|
|
* @method enableDOMEvents
|
|
* @param {Boolean} [enable=true] Indicates whether to enable or disable the events. Default is true.
|
|
**/
|
|
p.enableDOMEvents = function (enable) {
|
|
if (enable == null) {
|
|
enable = true;
|
|
}
|
|
var n,
|
|
o,
|
|
ls = this._eventListeners;
|
|
if (!enable && ls) {
|
|
for (n in ls) {
|
|
o = ls[n];
|
|
o.t.removeEventListener(n, o.f, false);
|
|
}
|
|
this._eventListeners = null;
|
|
} else if (enable && !ls && this.canvas) {
|
|
var t = window.addEventListener ? window : document;
|
|
var _this = this;
|
|
ls = this._eventListeners = {};
|
|
ls["mouseup"] = {
|
|
t: t,
|
|
f: function (e) {
|
|
_this._handleMouseUp(e);
|
|
},
|
|
};
|
|
ls["mousemove"] = {
|
|
t: t,
|
|
f: function (e) {
|
|
_this._handleMouseMove(e);
|
|
},
|
|
};
|
|
ls["dblclick"] = {
|
|
t: this.canvas,
|
|
f: function (e) {
|
|
_this._handleDoubleClick(e);
|
|
},
|
|
};
|
|
ls["mousedown"] = {
|
|
t: this.canvas,
|
|
f: function (e) {
|
|
_this._handleMouseDown(e);
|
|
},
|
|
};
|
|
|
|
for (n in ls) {
|
|
o = ls[n];
|
|
o.t.addEventListener(n, o.f, false);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Stage instances cannot be cloned.
|
|
* @method clone
|
|
**/
|
|
p.clone = function () {
|
|
throw "Stage cannot be cloned.";
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[Stage (name=" + this.name + ")]";
|
|
};
|
|
|
|
// private methods:
|
|
/**
|
|
* @method _getElementRect
|
|
* @protected
|
|
* @param {HTMLElement} e
|
|
**/
|
|
p._getElementRect = function (e) {
|
|
var bounds;
|
|
try {
|
|
bounds = e.getBoundingClientRect();
|
|
} catch (err) {
|
|
// this can fail on disconnected DOM elements in IE9
|
|
bounds = {
|
|
top: e.offsetTop,
|
|
left: e.offsetLeft,
|
|
width: e.offsetWidth,
|
|
height: e.offsetHeight,
|
|
};
|
|
}
|
|
|
|
var offX =
|
|
(window.pageXOffset || document.scrollLeft || 0) -
|
|
(document.clientLeft || document.body.clientLeft || 0);
|
|
var offY =
|
|
(window.pageYOffset || document.scrollTop || 0) -
|
|
(document.clientTop || document.body.clientTop || 0);
|
|
|
|
var styles = window.getComputedStyle
|
|
? getComputedStyle(e, null)
|
|
: e.currentStyle; // IE <9 compatibility.
|
|
var padL = parseInt(styles.paddingLeft) + parseInt(styles.borderLeftWidth);
|
|
var padT = parseInt(styles.paddingTop) + parseInt(styles.borderTopWidth);
|
|
var padR =
|
|
parseInt(styles.paddingRight) + parseInt(styles.borderRightWidth);
|
|
var padB =
|
|
parseInt(styles.paddingBottom) + parseInt(styles.borderBottomWidth);
|
|
|
|
// note: in some browsers bounds properties are read only.
|
|
return {
|
|
left: bounds.left + offX + padL,
|
|
right: bounds.right + offX - padR,
|
|
top: bounds.top + offY + padT,
|
|
bottom: bounds.bottom + offY - padB,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @method _getPointerData
|
|
* @protected
|
|
* @param {Number} id
|
|
**/
|
|
p._getPointerData = function (id) {
|
|
var data = this._pointerData[id];
|
|
if (!data) {
|
|
data = this._pointerData[id] = { x: 0, y: 0 };
|
|
}
|
|
return data;
|
|
};
|
|
|
|
/**
|
|
* @method _handleMouseMove
|
|
* @protected
|
|
* @param {MouseEvent} e
|
|
**/
|
|
p._handleMouseMove = function (e) {
|
|
if (!e) {
|
|
e = window.event;
|
|
}
|
|
this._handlePointerMove(-1, e, e.pageX, e.pageY);
|
|
};
|
|
|
|
/**
|
|
* @method _handlePointerMove
|
|
* @protected
|
|
* @param {Number} id
|
|
* @param {Event} e
|
|
* @param {Number} pageX
|
|
* @param {Number} pageY
|
|
* @param {Stage} owner Indicates that the event has already been captured & handled by the indicated stage.
|
|
**/
|
|
p._handlePointerMove = function (id, e, pageX, pageY, owner) {
|
|
if (this._prevStage && owner === undefined) {
|
|
return;
|
|
} // redundant listener.
|
|
if (!this.canvas) {
|
|
return;
|
|
}
|
|
var nextStage = this._nextStage,
|
|
o = this._getPointerData(id);
|
|
|
|
var inBounds = o.inBounds;
|
|
this._updatePointerPosition(id, e, pageX, pageY);
|
|
if (inBounds || o.inBounds || this.mouseMoveOutside) {
|
|
if (id === -1 && o.inBounds == !inBounds) {
|
|
this._dispatchMouseEvent(
|
|
this,
|
|
inBounds ? "mouseleave" : "mouseenter",
|
|
false,
|
|
id,
|
|
o,
|
|
e
|
|
);
|
|
}
|
|
|
|
this._dispatchMouseEvent(this, "stagemousemove", false, id, o, e);
|
|
this._dispatchMouseEvent(o.target, "pressmove", true, id, o, e);
|
|
}
|
|
|
|
nextStage && nextStage._handlePointerMove(id, e, pageX, pageY, null);
|
|
};
|
|
|
|
/**
|
|
* @method _updatePointerPosition
|
|
* @protected
|
|
* @param {Number} id
|
|
* @param {Event} e
|
|
* @param {Number} pageX
|
|
* @param {Number} pageY
|
|
**/
|
|
p._updatePointerPosition = function (id, e, pageX, pageY) {
|
|
var rect = this._getElementRect(this.canvas);
|
|
pageX -= rect.left;
|
|
pageY -= rect.top;
|
|
|
|
var w = this.canvas.width;
|
|
var h = this.canvas.height;
|
|
pageX /= (rect.right - rect.left) / w;
|
|
pageY /= (rect.bottom - rect.top) / h;
|
|
var o = this._getPointerData(id);
|
|
if (
|
|
(o.inBounds =
|
|
pageX >= 0 && pageY >= 0 && pageX <= w - 1 && pageY <= h - 1)
|
|
) {
|
|
o.x = pageX;
|
|
o.y = pageY;
|
|
} else if (this.mouseMoveOutside) {
|
|
o.x = pageX < 0 ? 0 : pageX > w - 1 ? w - 1 : pageX;
|
|
o.y = pageY < 0 ? 0 : pageY > h - 1 ? h - 1 : pageY;
|
|
}
|
|
|
|
o.posEvtObj = e;
|
|
o.rawX = pageX;
|
|
o.rawY = pageY;
|
|
|
|
if (id === this._primaryPointerID || id === -1) {
|
|
this.mouseX = o.x;
|
|
this.mouseY = o.y;
|
|
this.mouseInBounds = o.inBounds;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _handleMouseUp
|
|
* @protected
|
|
* @param {MouseEvent} e
|
|
**/
|
|
p._handleMouseUp = function (e) {
|
|
this._handlePointerUp(-1, e, false);
|
|
};
|
|
|
|
/**
|
|
* @method _handlePointerUp
|
|
* @protected
|
|
* @param {Number} id
|
|
* @param {Event} e
|
|
* @param {Boolean} clear
|
|
* @param {Stage} owner Indicates that the event has already been captured & handled by the indicated stage.
|
|
**/
|
|
p._handlePointerUp = function (id, e, clear, owner) {
|
|
var nextStage = this._nextStage,
|
|
o = this._getPointerData(id);
|
|
if (this._prevStage && owner === undefined) {
|
|
return;
|
|
} // redundant listener.
|
|
|
|
var target = null,
|
|
oTarget = o.target;
|
|
if (!owner && (oTarget || nextStage)) {
|
|
target = this._getObjectsUnderPoint(o.x, o.y, null, true);
|
|
}
|
|
|
|
if (o.down) {
|
|
this._dispatchMouseEvent(this, "stagemouseup", false, id, o, e, target);
|
|
o.down = false;
|
|
}
|
|
|
|
if (target == oTarget) {
|
|
this._dispatchMouseEvent(oTarget, "click", true, id, o, e);
|
|
}
|
|
this._dispatchMouseEvent(oTarget, "pressup", true, id, o, e);
|
|
|
|
if (clear) {
|
|
if (id == this._primaryPointerID) {
|
|
this._primaryPointerID = null;
|
|
}
|
|
delete this._pointerData[id];
|
|
} else {
|
|
o.target = null;
|
|
}
|
|
|
|
nextStage &&
|
|
nextStage._handlePointerUp(id, e, clear, owner || (target && this));
|
|
};
|
|
|
|
/**
|
|
* @method _handleMouseDown
|
|
* @protected
|
|
* @param {MouseEvent} e
|
|
**/
|
|
p._handleMouseDown = function (e) {
|
|
this._handlePointerDown(-1, e, e.pageX, e.pageY);
|
|
};
|
|
|
|
/**
|
|
* @method _handlePointerDown
|
|
* @protected
|
|
* @param {Number} id
|
|
* @param {Event} e
|
|
* @param {Number} pageX
|
|
* @param {Number} pageY
|
|
* @param {Stage} owner Indicates that the event has already been captured & handled by the indicated stage.
|
|
**/
|
|
p._handlePointerDown = function (id, e, pageX, pageY, owner) {
|
|
if (this.preventSelection) {
|
|
e.preventDefault();
|
|
}
|
|
if (this._primaryPointerID == null || id === -1) {
|
|
this._primaryPointerID = id;
|
|
} // mouse always takes over.
|
|
|
|
if (pageY != null) {
|
|
this._updatePointerPosition(id, e, pageX, pageY);
|
|
}
|
|
var target = null,
|
|
nextStage = this._nextStage,
|
|
o = this._getPointerData(id);
|
|
if (!owner) {
|
|
target = o.target = this._getObjectsUnderPoint(o.x, o.y, null, true);
|
|
}
|
|
|
|
if (o.inBounds) {
|
|
this._dispatchMouseEvent(this, "stagemousedown", false, id, o, e, target);
|
|
o.down = true;
|
|
}
|
|
this._dispatchMouseEvent(target, "mousedown", true, id, o, e);
|
|
|
|
nextStage &&
|
|
nextStage._handlePointerDown(
|
|
id,
|
|
e,
|
|
pageX,
|
|
pageY,
|
|
owner || (target && this)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* @method _testMouseOver
|
|
* @param {Boolean} clear If true, clears the mouseover / rollover (ie. no target)
|
|
* @param {Stage} owner Indicates that the event has already been captured & handled by the indicated stage.
|
|
* @param {Stage} eventTarget The stage that the cursor is actively over.
|
|
* @protected
|
|
**/
|
|
p._testMouseOver = function (clear, owner, eventTarget) {
|
|
if (this._prevStage && owner === undefined) {
|
|
return;
|
|
} // redundant listener.
|
|
|
|
var nextStage = this._nextStage;
|
|
if (!this._mouseOverIntervalID) {
|
|
// not enabled for mouseover, but should still relay the event.
|
|
nextStage && nextStage._testMouseOver(clear, owner, eventTarget);
|
|
return;
|
|
}
|
|
var o = this._getPointerData(-1);
|
|
// only update if the mouse position has changed. This provides a lot of optimization, but has some trade-offs.
|
|
if (
|
|
!o ||
|
|
(!clear &&
|
|
this.mouseX == this._mouseOverX &&
|
|
this.mouseY == this._mouseOverY &&
|
|
this.mouseInBounds)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
var e = o.posEvtObj;
|
|
var isEventTarget = eventTarget || (e && e.target == this.canvas);
|
|
var target = null,
|
|
common = -1,
|
|
cursor = "",
|
|
t,
|
|
i,
|
|
l;
|
|
|
|
if (!owner && (clear || (this.mouseInBounds && isEventTarget))) {
|
|
target = this._getObjectsUnderPoint(this.mouseX, this.mouseY, null, true);
|
|
this._mouseOverX = this.mouseX;
|
|
this._mouseOverY = this.mouseY;
|
|
}
|
|
|
|
var oldList = this._mouseOverTarget || [];
|
|
var oldTarget = oldList[oldList.length - 1];
|
|
var list = (this._mouseOverTarget = []);
|
|
|
|
// generate ancestor list and check for cursor:
|
|
t = target;
|
|
while (t) {
|
|
list.unshift(t);
|
|
if (!cursor) {
|
|
cursor = t.cursor;
|
|
}
|
|
t = t.parent;
|
|
}
|
|
this.canvas.style.cursor = cursor;
|
|
if (!owner && eventTarget) {
|
|
eventTarget.canvas.style.cursor = cursor;
|
|
}
|
|
|
|
// find common ancestor:
|
|
for (i = 0, l = list.length; i < l; i++) {
|
|
if (list[i] != oldList[i]) {
|
|
break;
|
|
}
|
|
common = i;
|
|
}
|
|
|
|
if (oldTarget != target) {
|
|
this._dispatchMouseEvent(oldTarget, "mouseout", true, -1, o, e, target);
|
|
}
|
|
|
|
for (i = oldList.length - 1; i > common; i--) {
|
|
this._dispatchMouseEvent(oldList[i], "rollout", false, -1, o, e, target);
|
|
}
|
|
|
|
for (i = list.length - 1; i > common; i--) {
|
|
this._dispatchMouseEvent(list[i], "rollover", false, -1, o, e, oldTarget);
|
|
}
|
|
|
|
if (oldTarget != target) {
|
|
this._dispatchMouseEvent(target, "mouseover", true, -1, o, e, oldTarget);
|
|
}
|
|
|
|
nextStage &&
|
|
nextStage._testMouseOver(
|
|
clear,
|
|
owner || (target && this),
|
|
eventTarget || (isEventTarget && this)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* @method _handleDoubleClick
|
|
* @protected
|
|
* @param {MouseEvent} e
|
|
* @param {Stage} owner Indicates that the event has already been captured & handled by the indicated stage.
|
|
**/
|
|
p._handleDoubleClick = function (e, owner) {
|
|
var target = null,
|
|
nextStage = this._nextStage,
|
|
o = this._getPointerData(-1);
|
|
if (!owner) {
|
|
target = this._getObjectsUnderPoint(o.x, o.y, null, true);
|
|
this._dispatchMouseEvent(target, "dblclick", true, -1, o, e);
|
|
}
|
|
nextStage && nextStage._handleDoubleClick(e, owner || (target && this));
|
|
};
|
|
|
|
/**
|
|
* @method _dispatchMouseEvent
|
|
* @protected
|
|
* @param {DisplayObject} target
|
|
* @param {String} type
|
|
* @param {Boolean} bubbles
|
|
* @param {Number} pointerId
|
|
* @param {Object} o
|
|
* @param {MouseEvent} [nativeEvent]
|
|
* @param {DisplayObject} [relatedTarget]
|
|
**/
|
|
p._dispatchMouseEvent = function (
|
|
target,
|
|
type,
|
|
bubbles,
|
|
pointerId,
|
|
o,
|
|
nativeEvent,
|
|
relatedTarget
|
|
) {
|
|
// TODO: might be worth either reusing MouseEvent instances, or adding a willTrigger method to avoid GC.
|
|
if (!target || (!bubbles && !target.hasEventListener(type))) {
|
|
return;
|
|
}
|
|
/*
|
|
// TODO: account for stage transformations?
|
|
this._mtx = this.getConcatenatedMatrix(this._mtx).invert();
|
|
var pt = this._mtx.transformPoint(o.x, o.y);
|
|
var evt = new createjs.MouseEvent(type, bubbles, false, pt.x, pt.y, nativeEvent, pointerId, pointerId==this._primaryPointerID || pointerId==-1, o.rawX, o.rawY);
|
|
*/
|
|
var evt = new createjs.MouseEvent(
|
|
type,
|
|
bubbles,
|
|
false,
|
|
o.x,
|
|
o.y,
|
|
nativeEvent,
|
|
pointerId,
|
|
pointerId === this._primaryPointerID || pointerId === -1,
|
|
o.rawX,
|
|
o.rawY,
|
|
relatedTarget
|
|
);
|
|
target.dispatchEvent(evt);
|
|
};
|
|
|
|
createjs.Stage = createjs.promote(Stage, "Container");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Bitmap.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
/**
|
|
* A Bitmap represents an Image, Canvas, or Video in the display list. A Bitmap can be instantiated using an existing
|
|
* HTML element, or a string.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var bitmap = new createjs.Bitmap("imagePath.jpg");
|
|
*
|
|
* <strong>Notes:</strong>
|
|
* <ol>
|
|
* <li>When a string path or image tag that is not yet loaded is used, the stage may need to be redrawn before it
|
|
* will be displayed.</li>
|
|
* <li>Bitmaps with an SVG source currently will not respect an alpha value other than 0 or 1. To get around this,
|
|
* the Bitmap can be cached.</li>
|
|
* <li>Bitmaps with an SVG source will taint the canvas with cross-origin data, which prevents interactivity. This
|
|
* happens in all browsers except recent Firefox builds.</li>
|
|
* <li>Images loaded cross-origin will throw cross-origin security errors when interacted with using a mouse, using
|
|
* methods such as `getObjectUnderPoint`, or using filters, or caching. You can get around this by setting
|
|
* `crossOrigin` flags on your images before passing them to EaselJS, eg: `img.crossOrigin="Anonymous";`</li>
|
|
* </ol>
|
|
*
|
|
* @class Bitmap
|
|
* @extends DisplayObject
|
|
* @constructor
|
|
* @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | String} imageOrUri The source object or URI to an image to
|
|
* display. This can be either an Image, Canvas, or Video object, or a string URI to an image file to load and use.
|
|
* If it is a URI, a new Image object will be constructed and assigned to the .image property.
|
|
**/
|
|
function Bitmap(imageOrUri) {
|
|
this.DisplayObject_constructor();
|
|
|
|
// public properties:
|
|
/**
|
|
* The image to render. This can be an Image, a Canvas, or a Video. Not all browsers (especially
|
|
* mobile browsers) support drawing video to a canvas.
|
|
* @property image
|
|
* @type HTMLImageElement | HTMLCanvasElement | HTMLVideoElement
|
|
**/
|
|
if (typeof imageOrUri == "string") {
|
|
this.image = document.createElement("img");
|
|
this.image.src = imageOrUri;
|
|
} else {
|
|
this.image = imageOrUri;
|
|
}
|
|
|
|
/**
|
|
* Specifies an area of the source image to draw. If omitted, the whole image will be drawn.
|
|
* Note that video sources must have a width / height set to work correctly with `sourceRect`.
|
|
* @property sourceRect
|
|
* @type Rectangle
|
|
* @default null
|
|
*/
|
|
this.sourceRect = null;
|
|
}
|
|
var p = createjs.extend(Bitmap, createjs.DisplayObject);
|
|
|
|
// public methods:
|
|
/**
|
|
* Constructor alias for backwards compatibility. This method will be removed in future versions.
|
|
* Subclasses should be updated to use {{#crossLink "Utility Methods/extends"}}{{/crossLink}}.
|
|
* @method initialize
|
|
* @deprecated in favour of `createjs.promote()`
|
|
**/
|
|
p.initialize = Bitmap; // TODO: deprecated.
|
|
|
|
/**
|
|
* Returns true or false indicating whether the display object would be visible if drawn to a canvas.
|
|
* This does not account for whether it would be visible within the boundaries of the stage.
|
|
*
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method isVisible
|
|
* @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas
|
|
**/
|
|
p.isVisible = function () {
|
|
var image = this.image;
|
|
var hasContent =
|
|
this.cacheCanvas ||
|
|
(image &&
|
|
(image.naturalWidth || image.getContext || image.readyState >= 2));
|
|
return !!(
|
|
this.visible &&
|
|
this.alpha > 0 &&
|
|
this.scaleX != 0 &&
|
|
this.scaleY != 0 &&
|
|
hasContent
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform.
|
|
* Returns true if the draw was handled (useful for overriding functionality).
|
|
*
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method draw
|
|
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into.
|
|
* @param {Boolean} [ignoreCache=false] Indicates whether the draw operation should ignore any current cache.
|
|
* For example, used for drawing the cache (to prevent it from simply drawing an existing cache back
|
|
* into itself).
|
|
* @return {Boolean}
|
|
**/
|
|
p.draw = function (ctx, ignoreCache) {
|
|
if (this.DisplayObject_draw(ctx, ignoreCache) || !this.image) {
|
|
return true;
|
|
}
|
|
var img = this.image,
|
|
rect = this.sourceRect;
|
|
if (rect) {
|
|
// some browsers choke on out of bound values, so we'll fix them:
|
|
var x1 = rect.x,
|
|
y1 = rect.y,
|
|
x2 = x1 + rect.width,
|
|
y2 = y1 + rect.height,
|
|
x = 0,
|
|
y = 0,
|
|
w = img.width,
|
|
h = img.height;
|
|
if (x1 < 0) {
|
|
x -= x1;
|
|
x1 = 0;
|
|
}
|
|
if (x2 > w) {
|
|
x2 = w;
|
|
}
|
|
if (y1 < 0) {
|
|
y -= y1;
|
|
y1 = 0;
|
|
}
|
|
if (y2 > h) {
|
|
y2 = h;
|
|
}
|
|
ctx.drawImage(img, x1, y1, x2 - x1, y2 - y1, x, y, x2 - x1, y2 - y1);
|
|
} else {
|
|
ctx.drawImage(img, 0, 0);
|
|
}
|
|
return true;
|
|
};
|
|
|
|
//Note, the doc sections below document using the specified APIs (from DisplayObject) from
|
|
//Bitmap. This is why they have no method implementations.
|
|
|
|
/**
|
|
* Because the content of a Bitmap is already in a simple format, cache is unnecessary for Bitmap instances.
|
|
* You should <b>not</b> cache Bitmap instances as it can degrade performance.
|
|
*
|
|
* <strong>However: If you want to use a filter on a Bitmap, you <em>MUST</em> cache it, or it will not work.</strong>
|
|
* To see the API for caching, please visit the DisplayObject {{#crossLink "DisplayObject/cache"}}{{/crossLink}}
|
|
* method.
|
|
* @method cache
|
|
**/
|
|
|
|
/**
|
|
* Because the content of a Bitmap is already in a simple format, cache is unnecessary for Bitmap instances.
|
|
* You should <b>not</b> cache Bitmap instances as it can degrade performance.
|
|
*
|
|
* <strong>However: If you want to use a filter on a Bitmap, you <em>MUST</em> cache it, or it will not work.</strong>
|
|
* To see the API for caching, please visit the DisplayObject {{#crossLink "DisplayObject/cache"}}{{/crossLink}}
|
|
* method.
|
|
* @method updateCache
|
|
**/
|
|
|
|
/**
|
|
* Because the content of a Bitmap is already in a simple format, cache is unnecessary for Bitmap instances.
|
|
* You should <b>not</b> cache Bitmap instances as it can degrade performance.
|
|
*
|
|
* <strong>However: If you want to use a filter on a Bitmap, you <em>MUST</em> cache it, or it will not work.</strong>
|
|
* To see the API for caching, please visit the DisplayObject {{#crossLink "DisplayObject/cache"}}{{/crossLink}}
|
|
* method.
|
|
* @method uncache
|
|
**/
|
|
|
|
/**
|
|
* Docced in superclass.
|
|
*/
|
|
p.getBounds = function () {
|
|
var rect = this.DisplayObject_getBounds();
|
|
if (rect) {
|
|
return rect;
|
|
}
|
|
var image = this.image,
|
|
o = this.sourceRect || image;
|
|
var hasContent =
|
|
image &&
|
|
(image.naturalWidth || image.getContext || image.readyState >= 2);
|
|
return hasContent
|
|
? this._rectangle.setValues(0, 0, o.width, o.height)
|
|
: null;
|
|
};
|
|
|
|
/**
|
|
* Returns a clone of the Bitmap instance.
|
|
* @method clone
|
|
* @return {Bitmap} a clone of the Bitmap instance.
|
|
**/
|
|
p.clone = function () {
|
|
var o = new Bitmap(this.image);
|
|
if (this.sourceRect) {
|
|
o.sourceRect = this.sourceRect.clone();
|
|
}
|
|
this._cloneProps(o);
|
|
return o;
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[Bitmap (name=" + this.name + ")]";
|
|
};
|
|
|
|
createjs.Bitmap = createjs.promote(Bitmap, "DisplayObject");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Sprite.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Displays a frame or sequence of frames (ie. an animation) from a SpriteSheet instance. A sprite sheet is a series of
|
|
* images (usually animation frames) combined into a single image. For example, an animation consisting of 8 100x100
|
|
* images could be combined into a 400x200 sprite sheet (4 frames across by 2 high). You can display individual frames,
|
|
* play frames as an animation, and even sequence animations together.
|
|
*
|
|
* See the {{#crossLink "SpriteSheet"}}{{/crossLink}} class for more information on setting up frames and animations.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var instance = new createjs.Sprite(spriteSheet);
|
|
* instance.gotoAndStop("frameName");
|
|
*
|
|
* Until {{#crossLink "Sprite/gotoAndStop"}}{{/crossLink}} or {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}} is called,
|
|
* only the first defined frame defined in the sprite sheet will be displayed.
|
|
*
|
|
* @class Sprite
|
|
* @extends DisplayObject
|
|
* @constructor
|
|
* @param {SpriteSheet} spriteSheet The SpriteSheet instance to play back. This includes the source image(s), frame
|
|
* dimensions, and frame data. See {{#crossLink "SpriteSheet"}}{{/crossLink}} for more information.
|
|
* @param {String|Number} [frameOrAnimation] The frame number or animation to play initially.
|
|
**/
|
|
function Sprite(spriteSheet, frameOrAnimation) {
|
|
this.DisplayObject_constructor();
|
|
|
|
// public properties:
|
|
/**
|
|
* The frame index that will be drawn when draw is called. Note that with some {{#crossLink "SpriteSheet"}}{{/crossLink}}
|
|
* definitions, this will advance non-sequentially. This will always be an integer value.
|
|
* @property currentFrame
|
|
* @type {Number}
|
|
* @default 0
|
|
* @readonly
|
|
**/
|
|
this.currentFrame = 0;
|
|
|
|
/**
|
|
* Returns the name of the currently playing animation.
|
|
* @property currentAnimation
|
|
* @type {String}
|
|
* @final
|
|
* @readonly
|
|
**/
|
|
this.currentAnimation = null;
|
|
|
|
/**
|
|
* Prevents the animation from advancing each tick automatically. For example, you could create a sprite
|
|
* sheet of icons, set paused to true, and display the appropriate icon by setting <code>currentFrame</code>.
|
|
* @property paused
|
|
* @type {Boolean}
|
|
* @default false
|
|
**/
|
|
this.paused = true;
|
|
|
|
/**
|
|
* The SpriteSheet instance to play back. This includes the source image, frame dimensions, and frame
|
|
* data. See {{#crossLink "SpriteSheet"}}{{/crossLink}} for more information.
|
|
* @property spriteSheet
|
|
* @type {SpriteSheet}
|
|
* @readonly
|
|
**/
|
|
this.spriteSheet = spriteSheet;
|
|
|
|
/**
|
|
* Specifies the current frame index within the currently playing animation. When playing normally, this will increase
|
|
* from 0 to n-1, where n is the number of frames in the current animation.
|
|
*
|
|
* This could be a non-integer value if
|
|
* using time-based playback (see {{#crossLink "Sprite/framerate"}}{{/crossLink}}, or if the animation's speed is
|
|
* not an integer.
|
|
* @property currentAnimationFrame
|
|
* @type {Number}
|
|
* @default 0
|
|
**/
|
|
this.currentAnimationFrame = 0;
|
|
|
|
/**
|
|
* By default Sprite instances advance one frame per tick. Specifying a framerate for the Sprite (or its related
|
|
* SpriteSheet) will cause it to advance based on elapsed time between ticks as appropriate to maintain the target
|
|
* framerate.
|
|
*
|
|
* For example, if a Sprite with a framerate of 10 is placed on a Stage being updated at 40fps, then the Sprite will
|
|
* advance roughly one frame every 4 ticks. This will not be exact, because the time between each tick will
|
|
* vary slightly between frames.
|
|
*
|
|
* This feature is dependent on the tick event object (or an object with an appropriate "delta" property) being
|
|
* passed into {{#crossLink "Stage/update"}}{{/crossLink}}.
|
|
* @property framerate
|
|
* @type {Number}
|
|
* @default 0
|
|
**/
|
|
this.framerate = 0;
|
|
|
|
// private properties:
|
|
/**
|
|
* Current animation object.
|
|
* @property _animation
|
|
* @protected
|
|
* @type {Object}
|
|
* @default null
|
|
**/
|
|
this._animation = null;
|
|
|
|
/**
|
|
* Current frame index.
|
|
* @property _currentFrame
|
|
* @protected
|
|
* @type {Number}
|
|
* @default null
|
|
**/
|
|
this._currentFrame = null;
|
|
|
|
/**
|
|
* Skips the next auto advance. Used by gotoAndPlay to avoid immediately jumping to the next frame
|
|
* @property _skipAdvance
|
|
* @protected
|
|
* @type {Boolean}
|
|
* @default false
|
|
**/
|
|
this._skipAdvance = false;
|
|
|
|
if (frameOrAnimation != null) {
|
|
this.gotoAndPlay(frameOrAnimation);
|
|
}
|
|
}
|
|
var p = createjs.extend(Sprite, createjs.DisplayObject);
|
|
|
|
/**
|
|
* Constructor alias for backwards compatibility. This method will be removed in future versions.
|
|
* Subclasses should be updated to use {{#crossLink "Utility Methods/extends"}}{{/crossLink}}.
|
|
* @method initialize
|
|
* @deprecated in favour of `createjs.promote()`
|
|
**/
|
|
p.initialize = Sprite; // TODO: Deprecated. This is for backwards support of FlashCC spritesheet export.
|
|
|
|
// events:
|
|
/**
|
|
* Dispatched when an animation reaches its ends.
|
|
* @event animationend
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @param {String} name The name of the animation that just ended.
|
|
* @param {String} next The name of the next animation that will be played, or null. This will be the same as name if the animation is looping.
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
/**
|
|
* Dispatched any time the current frame changes. For example, this could be due to automatic advancement on a tick,
|
|
* or calling gotoAndPlay() or gotoAndStop().
|
|
* @event change
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
*/
|
|
|
|
// public methods:
|
|
/**
|
|
* Returns true or false indicating whether the display object would be visible if drawn to a canvas.
|
|
* This does not account for whether it would be visible within the boundaries of the stage.
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method isVisible
|
|
* @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas
|
|
**/
|
|
p.isVisible = function () {
|
|
var hasContent = this.cacheCanvas || this.spriteSheet.complete;
|
|
return !!(
|
|
this.visible &&
|
|
this.alpha > 0 &&
|
|
this.scaleX != 0 &&
|
|
this.scaleY != 0 &&
|
|
hasContent
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform.
|
|
* Returns true if the draw was handled (useful for overriding functionality).
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method draw
|
|
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into.
|
|
* @param {Boolean} ignoreCache Indicates whether the draw operation should ignore any current cache.
|
|
* For example, used for drawing the cache (to prevent it from simply drawing an existing cache back
|
|
* into itself).
|
|
**/
|
|
p.draw = function (ctx, ignoreCache) {
|
|
if (this.DisplayObject_draw(ctx, ignoreCache)) {
|
|
return true;
|
|
}
|
|
this._normalizeFrame();
|
|
var o = this.spriteSheet.getFrame(this._currentFrame | 0);
|
|
if (!o) {
|
|
return false;
|
|
}
|
|
var rect = o.rect;
|
|
if (rect.width && rect.height) {
|
|
ctx.drawImage(
|
|
o.image,
|
|
rect.x,
|
|
rect.y,
|
|
rect.width,
|
|
rect.height,
|
|
-o.regX,
|
|
-o.regY,
|
|
rect.width,
|
|
rect.height
|
|
);
|
|
}
|
|
return true;
|
|
};
|
|
|
|
//Note, the doc sections below document using the specified APIs (from DisplayObject) from
|
|
//Bitmap. This is why they have no method implementations.
|
|
|
|
/**
|
|
* Because the content of a Sprite is already in a raster format, cache is unnecessary for Sprite instances.
|
|
* You should not cache Sprite instances as it can degrade performance.
|
|
* @method cache
|
|
**/
|
|
|
|
/**
|
|
* Because the content of a Sprite is already in a raster format, cache is unnecessary for Sprite instances.
|
|
* You should not cache Sprite instances as it can degrade performance.
|
|
* @method updateCache
|
|
**/
|
|
|
|
/**
|
|
* Because the content of a Sprite is already in a raster format, cache is unnecessary for Sprite instances.
|
|
* You should not cache Sprite instances as it can degrade performance.
|
|
* @method uncache
|
|
**/
|
|
|
|
/**
|
|
* Play (unpause) the current animation. The Sprite will be paused if either {{#crossLink "Sprite/stop"}}{{/crossLink}}
|
|
* or {{#crossLink "Sprite/gotoAndStop"}}{{/crossLink}} is called. Single frame animations will remain
|
|
* unchanged.
|
|
* @method play
|
|
**/
|
|
p.play = function () {
|
|
this.paused = false;
|
|
};
|
|
|
|
/**
|
|
* Stop playing a running animation. The Sprite will be playing if {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}}
|
|
* is called. Note that calling {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}} or {{#crossLink "Sprite/play"}}{{/crossLink}}
|
|
* will resume playback.
|
|
* @method stop
|
|
**/
|
|
p.stop = function () {
|
|
this.paused = true;
|
|
};
|
|
|
|
/**
|
|
* Sets paused to false and plays the specified animation name, named frame, or frame number.
|
|
* @method gotoAndPlay
|
|
* @param {String|Number} frameOrAnimation The frame number or animation name that the playhead should move to
|
|
* and begin playing.
|
|
**/
|
|
p.gotoAndPlay = function (frameOrAnimation) {
|
|
this.paused = false;
|
|
this._skipAdvance = true;
|
|
this._goto(frameOrAnimation);
|
|
};
|
|
|
|
/**
|
|
* Sets paused to true and seeks to the specified animation name, named frame, or frame number.
|
|
* @method gotoAndStop
|
|
* @param {String|Number} frameOrAnimation The frame number or animation name that the playhead should move to
|
|
* and stop.
|
|
**/
|
|
p.gotoAndStop = function (frameOrAnimation) {
|
|
this.paused = true;
|
|
this._goto(frameOrAnimation);
|
|
};
|
|
|
|
/**
|
|
* Advances the playhead. This occurs automatically each tick by default.
|
|
* @param [time] {Number} The amount of time in ms to advance by. Only applicable if framerate is set on the Sprite
|
|
* or its SpriteSheet.
|
|
* @method advance
|
|
*/
|
|
p.advance = function (time) {
|
|
var fps = this.framerate || this.spriteSheet.framerate;
|
|
var t = fps && time != null ? time / (1000 / fps) : 1;
|
|
this._normalizeFrame(t);
|
|
};
|
|
|
|
/**
|
|
* Returns a {{#crossLink "Rectangle"}}{{/crossLink}} instance defining the bounds of the current frame relative to
|
|
* the origin. For example, a 90 x 70 frame with <code>regX=50</code> and <code>regY=40</code> would return a
|
|
* rectangle with [x=-50, y=-40, width=90, height=70]. This ignores transformations on the display object.
|
|
*
|
|
* Also see the SpriteSheet {{#crossLink "SpriteSheet/getFrameBounds"}}{{/crossLink}} method.
|
|
* @method getBounds
|
|
* @return {Rectangle} A Rectangle instance. Returns null if the frame does not exist, or the image is not fully
|
|
* loaded.
|
|
**/
|
|
p.getBounds = function () {
|
|
// TODO: should this normalizeFrame?
|
|
return (
|
|
this.DisplayObject_getBounds() ||
|
|
this.spriteSheet.getFrameBounds(this.currentFrame, this._rectangle)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Returns a clone of the Sprite instance. Note that the same SpriteSheet is shared between cloned
|
|
* instances.
|
|
* @method clone
|
|
* @return {Sprite} a clone of the Sprite instance.
|
|
**/
|
|
p.clone = function () {
|
|
return this._cloneProps(new Sprite(this.spriteSheet));
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[Sprite (name=" + this.name + ")]";
|
|
};
|
|
|
|
// private methods:
|
|
/**
|
|
* @method _cloneProps
|
|
* @param {Sprite} o
|
|
* @return {Sprite} o
|
|
* @protected
|
|
**/
|
|
p._cloneProps = function (o) {
|
|
this.DisplayObject__cloneProps(o);
|
|
o.currentFrame = this.currentFrame;
|
|
o.currentAnimation = this.currentAnimation;
|
|
o.paused = this.paused;
|
|
o.currentAnimationFrame = this.currentAnimationFrame;
|
|
o.framerate = this.framerate;
|
|
|
|
o._animation = this._animation;
|
|
o._currentFrame = this._currentFrame;
|
|
o._skipAdvance = this._skipAdvance;
|
|
return o;
|
|
};
|
|
|
|
/**
|
|
* Advances the <code>currentFrame</code> if paused is not true. This is called automatically when the {{#crossLink "Stage"}}{{/crossLink}}
|
|
* ticks.
|
|
* @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs.
|
|
* @protected
|
|
* @method _tick
|
|
**/
|
|
p._tick = function (evtObj) {
|
|
if (!this.paused) {
|
|
if (!this._skipAdvance) {
|
|
this.advance(evtObj && evtObj.delta);
|
|
}
|
|
this._skipAdvance = false;
|
|
}
|
|
this.DisplayObject__tick(evtObj);
|
|
};
|
|
|
|
/**
|
|
* Normalizes the current frame, advancing animations and dispatching callbacks as appropriate.
|
|
* @protected
|
|
* @method _normalizeFrame
|
|
**/
|
|
p._normalizeFrame = function (frameDelta) {
|
|
frameDelta = frameDelta || 0;
|
|
var animation = this._animation;
|
|
var paused = this.paused;
|
|
var frame = this._currentFrame;
|
|
var l;
|
|
|
|
if (animation) {
|
|
var speed = animation.speed || 1;
|
|
var animFrame = this.currentAnimationFrame;
|
|
l = animation.frames.length;
|
|
if (animFrame + frameDelta * speed >= l) {
|
|
var next = animation.next;
|
|
if (this._dispatchAnimationEnd(animation, frame, paused, next, l - 1)) {
|
|
// something changed in the event stack, so we shouldn't make any more changes here.
|
|
return;
|
|
} else if (next) {
|
|
// sequence. Automatically calls _normalizeFrame again with the remaining frames.
|
|
return this._goto(next, frameDelta - (l - animFrame) / speed);
|
|
} else {
|
|
// end.
|
|
this.paused = true;
|
|
animFrame = animation.frames.length - 1;
|
|
}
|
|
} else {
|
|
animFrame += frameDelta * speed;
|
|
}
|
|
this.currentAnimationFrame = animFrame;
|
|
this._currentFrame = animation.frames[animFrame | 0];
|
|
} else {
|
|
frame = this._currentFrame += frameDelta;
|
|
l = this.spriteSheet.getNumFrames();
|
|
if (frame >= l && l > 0) {
|
|
if (!this._dispatchAnimationEnd(animation, frame, paused, l - 1)) {
|
|
// looped.
|
|
if ((this._currentFrame -= l) >= l) {
|
|
return this._normalizeFrame();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
frame = this._currentFrame | 0;
|
|
if (this.currentFrame != frame) {
|
|
this.currentFrame = frame;
|
|
this.dispatchEvent("change");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Dispatches the "animationend" event. Returns true if a handler changed the animation (ex. calling {{#crossLink "Sprite/stop"}}{{/crossLink}},
|
|
* {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}}, etc.)
|
|
* @property _dispatchAnimationEnd
|
|
* @private
|
|
* @type {Function}
|
|
**/
|
|
p._dispatchAnimationEnd = function (animation, frame, paused, next, end) {
|
|
var name = animation ? animation.name : null;
|
|
if (this.hasEventListener("animationend")) {
|
|
var evt = new createjs.Event("animationend");
|
|
evt.name = name;
|
|
evt.next = next;
|
|
this.dispatchEvent(evt);
|
|
}
|
|
// did the animation get changed in the event stack?:
|
|
var changed = this._animation != animation || this._currentFrame != frame;
|
|
// if the animation hasn't changed, but the sprite was paused, then we want to stick to the last frame:
|
|
if (!changed && !paused && this.paused) {
|
|
this.currentAnimationFrame = end;
|
|
changed = true;
|
|
}
|
|
return changed;
|
|
};
|
|
|
|
/**
|
|
* Moves the playhead to the specified frame number or animation.
|
|
* @method _goto
|
|
* @param {String|Number} frameOrAnimation The frame number or animation that the playhead should move to.
|
|
* @param {Boolean} [frame] The frame of the animation to go to. Defaults to 0.
|
|
* @protected
|
|
**/
|
|
p._goto = function (frameOrAnimation, frame) {
|
|
this.currentAnimationFrame = 0;
|
|
if (isNaN(frameOrAnimation)) {
|
|
var data = this.spriteSheet.getAnimation(frameOrAnimation);
|
|
if (data) {
|
|
this._animation = data;
|
|
this.currentAnimation = frameOrAnimation;
|
|
this._normalizeFrame(frame);
|
|
}
|
|
} else {
|
|
this.currentAnimation = this._animation = null;
|
|
this._currentFrame = frameOrAnimation;
|
|
this._normalizeFrame();
|
|
}
|
|
};
|
|
|
|
createjs.Sprite = createjs.promote(Sprite, "DisplayObject");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Shape.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* A Shape allows you to display vector art in the display list. It composites a {{#crossLink "Graphics"}}{{/crossLink}}
|
|
* instance which exposes all of the vector drawing methods. The Graphics instance can be shared between multiple Shape
|
|
* instances to display the same vector graphics with different positions or transforms.
|
|
*
|
|
* If the vector art will not
|
|
* change between draws, you may want to use the {{#crossLink "DisplayObject/cache"}}{{/crossLink}} method to reduce the
|
|
* rendering cost.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var graphics = new createjs.Graphics().beginFill("#ff0000").drawRect(0, 0, 100, 100);
|
|
* var shape = new createjs.Shape(graphics);
|
|
*
|
|
* //Alternatively use can also use the graphics property of the Shape class to renderer the same as above.
|
|
* var shape = new createjs.Shape();
|
|
* shape.graphics.beginFill("#ff0000").drawRect(0, 0, 100, 100);
|
|
*
|
|
* @class Shape
|
|
* @extends DisplayObject
|
|
* @constructor
|
|
* @param {Graphics} graphics Optional. The graphics instance to display. If null, a new Graphics instance will be created.
|
|
**/
|
|
function Shape(graphics) {
|
|
this.DisplayObject_constructor();
|
|
|
|
// public properties:
|
|
/**
|
|
* The graphics instance to display.
|
|
* @property graphics
|
|
* @type Graphics
|
|
**/
|
|
this.graphics = graphics ? graphics : new createjs.Graphics();
|
|
}
|
|
var p = createjs.extend(Shape, createjs.DisplayObject);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// public methods:
|
|
/**
|
|
* Returns true or false indicating whether the Shape would be visible if drawn to a canvas.
|
|
* This does not account for whether it would be visible within the boundaries of the stage.
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method isVisible
|
|
* @return {Boolean} Boolean indicating whether the Shape would be visible if drawn to a canvas
|
|
**/
|
|
p.isVisible = function () {
|
|
var hasContent =
|
|
this.cacheCanvas || (this.graphics && !this.graphics.isEmpty());
|
|
return !!(
|
|
this.visible &&
|
|
this.alpha > 0 &&
|
|
this.scaleX != 0 &&
|
|
this.scaleY != 0 &&
|
|
hasContent
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Draws the Shape into the specified context ignoring its visible, alpha, shadow, and transform. Returns true if
|
|
* the draw was handled (useful for overriding functionality).
|
|
*
|
|
* <i>NOTE: This method is mainly for internal use, though it may be useful for advanced uses.</i>
|
|
* @method draw
|
|
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into.
|
|
* @param {Boolean} [ignoreCache=false] Indicates whether the draw operation should ignore any current cache. For example,
|
|
* used for drawing the cache (to prevent it from simply drawing an existing cache back into itself).
|
|
* @return {Boolean}
|
|
**/
|
|
p.draw = function (ctx, ignoreCache) {
|
|
if (this.DisplayObject_draw(ctx, ignoreCache)) {
|
|
return true;
|
|
}
|
|
this.graphics.draw(ctx, this);
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Returns a clone of this Shape. Some properties that are specific to this instance's current context are reverted to
|
|
* their defaults (for example .parent).
|
|
* @method clone
|
|
* @param {Boolean} recursive If true, this Shape's {{#crossLink "Graphics"}}{{/crossLink}} instance will also be
|
|
* cloned. If false, the Graphics instance will be shared with the new Shape.
|
|
**/
|
|
p.clone = function (recursive) {
|
|
var g = recursive && this.graphics ? this.graphics.clone() : this.graphics;
|
|
return this._cloneProps(new Shape(g));
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[Shape (name=" + this.name + ")]";
|
|
};
|
|
|
|
createjs.Shape = createjs.promote(Shape, "DisplayObject");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Text.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Display one or more lines of dynamic text (not user editable) in the display list. Line wrapping support (using the
|
|
* lineWidth) is very basic, wrapping on spaces and tabs only. Note that as an alternative to Text, you can position HTML
|
|
* text above or below the canvas relative to items in the display list using the {{#crossLink "DisplayObject/localToGlobal"}}{{/crossLink}}
|
|
* method, or using {{#crossLink "DOMElement"}}{{/crossLink}}.
|
|
*
|
|
* <b>Please note that Text does not support HTML text, and can only display one font style at a time.</b> To use
|
|
* multiple font styles, you will need to create multiple text instances, and position them manually.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var text = new createjs.Text("Hello World", "20px Arial", "#ff7700");
|
|
* text.x = 100;
|
|
* text.textBaseline = "alphabetic";
|
|
*
|
|
* CreateJS Text supports web fonts (the same rules as Canvas). The font must be loaded and supported by the browser
|
|
* before it can be displayed.
|
|
*
|
|
* <strong>Note:</strong> Text can be expensive to generate, so cache instances where possible. Be aware that not all
|
|
* browsers will render Text exactly the same.
|
|
* @class Text
|
|
* @extends DisplayObject
|
|
* @constructor
|
|
* @param {String} [text] The text to display.
|
|
* @param {String} [font] The font style to use. Any valid value for the CSS font attribute is acceptable (ex. "bold
|
|
* 36px Arial").
|
|
* @param {String} [color] The color to draw the text in. Any valid value for the CSS color attribute is acceptable (ex.
|
|
* "#F00", "red", or "#FF0000").
|
|
**/
|
|
function Text(text, font, color) {
|
|
this.DisplayObject_constructor();
|
|
|
|
// public properties:
|
|
/**
|
|
* The text to display.
|
|
* @property text
|
|
* @type String
|
|
**/
|
|
this.text = text;
|
|
|
|
/**
|
|
* The font style to use. Any valid value for the CSS font attribute is acceptable (ex. "bold 36px Arial").
|
|
* @property font
|
|
* @type String
|
|
**/
|
|
this.font = font;
|
|
|
|
/**
|
|
* The color to draw the text in. Any valid value for the CSS color attribute is acceptable (ex. "#F00"). Default is "#000".
|
|
* It will also accept valid canvas fillStyle values.
|
|
* @property color
|
|
* @type String
|
|
**/
|
|
this.color = color;
|
|
|
|
/**
|
|
* The horizontal text alignment. Any of "start", "end", "left", "right", and "center". For detailed
|
|
* information view the
|
|
* <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-styles">
|
|
* whatwg spec</a>. Default is "left".
|
|
* @property textAlign
|
|
* @type String
|
|
**/
|
|
this.textAlign = "left";
|
|
|
|
/**
|
|
* The vertical alignment point on the font. Any of "top", "hanging", "middle", "alphabetic", "ideographic", or
|
|
* "bottom". For detailed information view the <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-styles">
|
|
* whatwg spec</a>. Default is "top".
|
|
* @property textBaseline
|
|
* @type String
|
|
*/
|
|
this.textBaseline = "top";
|
|
|
|
/**
|
|
* The maximum width to draw the text. If maxWidth is specified (not null), the text will be condensed or
|
|
* shrunk to make it fit in this width. For detailed information view the
|
|
* <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-styles">
|
|
* whatwg spec</a>.
|
|
* @property maxWidth
|
|
* @type Number
|
|
*/
|
|
this.maxWidth = null;
|
|
|
|
/**
|
|
* If greater than 0, the text will be drawn as a stroke (outline) of the specified width.
|
|
* @property outline
|
|
* @type Number
|
|
**/
|
|
this.outline = 0;
|
|
|
|
/**
|
|
* Indicates the line height (vertical distance between baselines) for multi-line text. If null or 0,
|
|
* the value of getMeasuredLineHeight is used.
|
|
* @property lineHeight
|
|
* @type Number
|
|
**/
|
|
this.lineHeight = 0;
|
|
|
|
/**
|
|
* Indicates the maximum width for a line of text before it is wrapped to multiple lines. If null,
|
|
* the text will not be wrapped.
|
|
* @property lineWidth
|
|
* @type Number
|
|
**/
|
|
this.lineWidth = null;
|
|
}
|
|
var p = createjs.extend(Text, createjs.DisplayObject);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// static properties:
|
|
/**
|
|
* @property _workingContext
|
|
* @type CanvasRenderingContext2D
|
|
* @private
|
|
**/
|
|
var canvas = createjs.createCanvas
|
|
? createjs.createCanvas()
|
|
: document.createElement("canvas");
|
|
if (canvas.getContext) {
|
|
Text._workingContext = canvas.getContext("2d");
|
|
canvas.width = canvas.height = 1;
|
|
}
|
|
|
|
// constants:
|
|
/**
|
|
* Lookup table for the ratio to offset bounds x calculations based on the textAlign property.
|
|
* @property H_OFFSETS
|
|
* @type Object
|
|
* @protected
|
|
* @static
|
|
**/
|
|
Text.H_OFFSETS = { start: 0, left: 0, center: -0.5, end: -1, right: -1 };
|
|
|
|
/**
|
|
* Lookup table for the ratio to offset bounds y calculations based on the textBaseline property.
|
|
* @property H_OFFSETS
|
|
* @type Object
|
|
* @protected
|
|
* @static
|
|
**/
|
|
Text.V_OFFSETS = {
|
|
top: 0,
|
|
hanging: -0.01,
|
|
middle: -0.4,
|
|
alphabetic: -0.8,
|
|
ideographic: -0.85,
|
|
bottom: -1,
|
|
};
|
|
|
|
// public methods:
|
|
/**
|
|
* Returns true or false indicating whether the display object would be visible if drawn to a canvas.
|
|
* This does not account for whether it would be visible within the boundaries of the stage.
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method isVisible
|
|
* @return {Boolean} Whether the display object would be visible if drawn to a canvas
|
|
**/
|
|
p.isVisible = function () {
|
|
var hasContent =
|
|
this.cacheCanvas || (this.text != null && this.text !== "");
|
|
return !!(
|
|
this.visible &&
|
|
this.alpha > 0 &&
|
|
this.scaleX != 0 &&
|
|
this.scaleY != 0 &&
|
|
hasContent
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Draws the Text into the specified context ignoring its visible, alpha, shadow, and transform.
|
|
* Returns true if the draw was handled (useful for overriding functionality).
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method draw
|
|
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into.
|
|
* @param {Boolean} ignoreCache Indicates whether the draw operation should ignore any current cache.
|
|
* For example, used for drawing the cache (to prevent it from simply drawing an existing cache back
|
|
* into itself).
|
|
**/
|
|
p.draw = function (ctx, ignoreCache) {
|
|
if (this.DisplayObject_draw(ctx, ignoreCache)) {
|
|
return true;
|
|
}
|
|
|
|
var col = this.color || "#000";
|
|
if (this.outline) {
|
|
ctx.strokeStyle = col;
|
|
ctx.lineWidth = this.outline * 1;
|
|
} else {
|
|
ctx.fillStyle = col;
|
|
}
|
|
|
|
this._drawText(this._prepContext(ctx));
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Returns the measured, untransformed width of the text without wrapping. Use getBounds for a more robust value.
|
|
* @method getMeasuredWidth
|
|
* @return {Number} The measured, untransformed width of the text.
|
|
**/
|
|
p.getMeasuredWidth = function () {
|
|
return this._getMeasuredWidth(this.text);
|
|
};
|
|
|
|
/**
|
|
* Returns an approximate line height of the text, ignoring the lineHeight property. This is based on the measured
|
|
* width of a "M" character multiplied by 1.2, which provides an approximate line height for most fonts.
|
|
* @method getMeasuredLineHeight
|
|
* @return {Number} an approximate line height of the text, ignoring the lineHeight property. This is
|
|
* based on the measured width of a "M" character multiplied by 1.2, which approximates em for most fonts.
|
|
**/
|
|
p.getMeasuredLineHeight = function () {
|
|
return this._getMeasuredWidth("M") * 1.2;
|
|
};
|
|
|
|
/**
|
|
* Returns the approximate height of multi-line text by multiplying the number of lines against either the
|
|
* <code>lineHeight</code> (if specified) or {{#crossLink "Text/getMeasuredLineHeight"}}{{/crossLink}}. Note that
|
|
* this operation requires the text flowing logic to run, which has an associated CPU cost.
|
|
* @method getMeasuredHeight
|
|
* @return {Number} The approximate height of the untransformed multi-line text.
|
|
**/
|
|
p.getMeasuredHeight = function () {
|
|
return this._drawText(null, {}).height;
|
|
};
|
|
|
|
/**
|
|
* Docced in superclass.
|
|
*/
|
|
p.getBounds = function () {
|
|
var rect = this.DisplayObject_getBounds();
|
|
if (rect) {
|
|
return rect;
|
|
}
|
|
if (this.text == null || this.text === "") {
|
|
return null;
|
|
}
|
|
var o = this._drawText(null, {});
|
|
var w = this.maxWidth && this.maxWidth < o.width ? this.maxWidth : o.width;
|
|
var x = w * Text.H_OFFSETS[this.textAlign || "left"];
|
|
var lineHeight = this.lineHeight || this.getMeasuredLineHeight();
|
|
var y = lineHeight * Text.V_OFFSETS[this.textBaseline || "top"];
|
|
return this._rectangle.setValues(x, y, w, o.height);
|
|
};
|
|
|
|
/**
|
|
* Returns an object with width, height, and lines properties. The width and height are the visual width and height
|
|
* of the drawn text. The lines property contains an array of strings, one for
|
|
* each line of text that will be drawn, accounting for line breaks and wrapping. These strings have trailing
|
|
* whitespace removed.
|
|
* @method getMetrics
|
|
* @return {Object} An object with width, height, and lines properties.
|
|
**/
|
|
p.getMetrics = function () {
|
|
var o = { lines: [] };
|
|
o.lineHeight = this.lineHeight || this.getMeasuredLineHeight();
|
|
o.vOffset = o.lineHeight * Text.V_OFFSETS[this.textBaseline || "top"];
|
|
return this._drawText(null, o, o.lines);
|
|
};
|
|
|
|
/**
|
|
* Returns a clone of the Text instance.
|
|
* @method clone
|
|
* @return {Text} a clone of the Text instance.
|
|
**/
|
|
p.clone = function () {
|
|
return this._cloneProps(new Text(this.text, this.font, this.color));
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return (
|
|
"[Text (text=" +
|
|
(this.text.length > 20 ? this.text.substr(0, 17) + "..." : this.text) +
|
|
")]"
|
|
);
|
|
};
|
|
|
|
// private methods:
|
|
/**
|
|
* @method _cloneProps
|
|
* @param {Text} o
|
|
* @protected
|
|
* @return {Text} o
|
|
**/
|
|
p._cloneProps = function (o) {
|
|
this.DisplayObject__cloneProps(o);
|
|
o.textAlign = this.textAlign;
|
|
o.textBaseline = this.textBaseline;
|
|
o.maxWidth = this.maxWidth;
|
|
o.outline = this.outline;
|
|
o.lineHeight = this.lineHeight;
|
|
o.lineWidth = this.lineWidth;
|
|
return o;
|
|
};
|
|
|
|
/**
|
|
* @method _getWorkingContext
|
|
* @param {CanvasRenderingContext2D} ctx
|
|
* @return {CanvasRenderingContext2D}
|
|
* @protected
|
|
**/
|
|
p._prepContext = function (ctx) {
|
|
ctx.font = this.font || "10px sans-serif";
|
|
ctx.textAlign = this.textAlign || "left";
|
|
ctx.textBaseline = this.textBaseline || "top";
|
|
return ctx;
|
|
};
|
|
|
|
/**
|
|
* Draws multiline text.
|
|
* @method _drawText
|
|
* @param {CanvasRenderingContext2D} ctx
|
|
* @param {Object} o
|
|
* @param {Array} lines
|
|
* @return {Object}
|
|
* @protected
|
|
**/
|
|
p._drawText = function (ctx, o, lines) {
|
|
var paint = !!ctx;
|
|
if (!paint) {
|
|
ctx = Text._workingContext;
|
|
ctx.save();
|
|
this._prepContext(ctx);
|
|
}
|
|
var lineHeight = this.lineHeight || this.getMeasuredLineHeight();
|
|
|
|
var maxW = 0,
|
|
count = 0;
|
|
var hardLines = String(this.text).split(/(?:\r\n|\r|\n)/);
|
|
for (var i = 0, l = hardLines.length; i < l; i++) {
|
|
var str = hardLines[i];
|
|
var w = null;
|
|
|
|
if (
|
|
this.lineWidth != null &&
|
|
(w = ctx.measureText(str).width) > this.lineWidth
|
|
) {
|
|
// text wrapping:
|
|
var words = str.split(/(\s)/);
|
|
str = words[0];
|
|
w = ctx.measureText(str).width;
|
|
|
|
for (var j = 1, jl = words.length; j < jl; j += 2) {
|
|
// Line needs to wrap:
|
|
var wordW = ctx.measureText(words[j] + words[j + 1]).width;
|
|
if (w + wordW > this.lineWidth) {
|
|
if (paint) {
|
|
this._drawTextLine(ctx, str, count * lineHeight);
|
|
}
|
|
if (lines) {
|
|
lines.push(str);
|
|
}
|
|
if (w > maxW) {
|
|
maxW = w;
|
|
}
|
|
str = words[j + 1];
|
|
w = ctx.measureText(str).width;
|
|
count++;
|
|
} else {
|
|
str += words[j] + words[j + 1];
|
|
w += wordW;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (paint) {
|
|
this._drawTextLine(ctx, str, count * lineHeight);
|
|
}
|
|
if (lines) {
|
|
lines.push(str);
|
|
}
|
|
if (o && w == null) {
|
|
w = ctx.measureText(str).width;
|
|
}
|
|
if (w > maxW) {
|
|
maxW = w;
|
|
}
|
|
count++;
|
|
}
|
|
|
|
if (o) {
|
|
o.width = maxW;
|
|
o.height = count * lineHeight;
|
|
}
|
|
if (!paint) {
|
|
ctx.restore();
|
|
}
|
|
return o;
|
|
};
|
|
|
|
/**
|
|
* @method _drawTextLine
|
|
* @param {CanvasRenderingContext2D} ctx
|
|
* @param {String} text
|
|
* @param {Number} y
|
|
* @protected
|
|
**/
|
|
p._drawTextLine = function (ctx, text, y) {
|
|
// Chrome 17 will fail to draw the text if the last param is included but null, so we feed it a large value instead:
|
|
if (this.outline) {
|
|
ctx.strokeText(text, 0, y, this.maxWidth || 0xffff);
|
|
} else {
|
|
ctx.fillText(text, 0, y, this.maxWidth || 0xffff);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _getMeasuredWidth
|
|
* @param {String} text
|
|
* @protected
|
|
**/
|
|
p._getMeasuredWidth = function (text) {
|
|
var ctx = Text._workingContext;
|
|
ctx.save();
|
|
var w = this._prepContext(ctx).measureText(text).width;
|
|
ctx.restore();
|
|
return w;
|
|
};
|
|
|
|
createjs.Text = createjs.promote(Text, "DisplayObject");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// BitmapText.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Displays text using bitmap glyphs defined in a sprite sheet. Multi-line text is supported
|
|
* using new line characters, but automatic wrapping is not supported. See the
|
|
* {{#crossLink "BitmapText/spriteSheet:property"}}{{/crossLink}}
|
|
* property for more information on defining glyphs.
|
|
*
|
|
* <strong>Important:</strong> BitmapText extends Container, but is not designed to be used as one.
|
|
* As such, methods like addChild and removeChild are disabled.
|
|
* @class BitmapText
|
|
* @extends DisplayObject
|
|
* @param {String} [text=""] The text to display.
|
|
* @param {SpriteSheet} [spriteSheet=null] The spritesheet that defines the character glyphs.
|
|
* @constructor
|
|
**/
|
|
function BitmapText(text, spriteSheet) {
|
|
this.Container_constructor();
|
|
|
|
// public properties:
|
|
/**
|
|
* The text to display.
|
|
* @property text
|
|
* @type String
|
|
* @default ""
|
|
**/
|
|
this.text = text || "";
|
|
|
|
/**
|
|
* A SpriteSheet instance that defines the glyphs for this bitmap text. Each glyph/character
|
|
* should have a single frame animation defined in the sprite sheet named the same as
|
|
* corresponding character. For example, the following animation definition:
|
|
*
|
|
* "A": {frames: [0]}
|
|
*
|
|
* would indicate that the frame at index 0 of the spritesheet should be drawn for the "A" character. The short form
|
|
* is also acceptable:
|
|
*
|
|
* "A": 0
|
|
*
|
|
* Note that if a character in the text is not found in the sprite sheet, it will also
|
|
* try to use the alternate case (upper or lower).
|
|
*
|
|
* See SpriteSheet for more information on defining sprite sheet data.
|
|
* @property spriteSheet
|
|
* @type SpriteSheet
|
|
* @default null
|
|
**/
|
|
this.spriteSheet = spriteSheet;
|
|
|
|
/**
|
|
* The height of each line of text. If 0, then it will use a line height calculated
|
|
* by checking for the height of the "1", "T", or "L" character (in that order). If
|
|
* those characters are not defined, it will use the height of the first frame of the
|
|
* sprite sheet.
|
|
* @property lineHeight
|
|
* @type Number
|
|
* @default 0
|
|
**/
|
|
this.lineHeight = 0;
|
|
|
|
/**
|
|
* This spacing (in pixels) will be added after each character in the output.
|
|
* @property letterSpacing
|
|
* @type Number
|
|
* @default 0
|
|
**/
|
|
this.letterSpacing = 0;
|
|
|
|
/**
|
|
* If a space character is not defined in the sprite sheet, then empty pixels equal to
|
|
* spaceWidth will be inserted instead. If 0, then it will use a value calculated
|
|
* by checking for the width of the "1", "l", "E", or "A" character (in that order). If
|
|
* those characters are not defined, it will use the width of the first frame of the
|
|
* sprite sheet.
|
|
* @property spaceWidth
|
|
* @type Number
|
|
* @default 0
|
|
**/
|
|
this.spaceWidth = 0;
|
|
|
|
// private properties:
|
|
/**
|
|
* @property _oldProps
|
|
* @type Object
|
|
* @protected
|
|
**/
|
|
this._oldProps = {
|
|
text: 0,
|
|
spriteSheet: 0,
|
|
lineHeight: 0,
|
|
letterSpacing: 0,
|
|
spaceWidth: 0,
|
|
};
|
|
}
|
|
var p = createjs.extend(BitmapText, createjs.Container);
|
|
|
|
/**
|
|
* <strong>REMOVED</strong>. Removed in favor of using `MySuperClass_constructor`.
|
|
* See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}}
|
|
* for details.
|
|
*
|
|
* There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance.
|
|
*
|
|
* @method initialize
|
|
* @protected
|
|
* @deprecated
|
|
*/
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is.
|
|
|
|
// static properties:
|
|
/**
|
|
* BitmapText uses Sprite instances to draw text. To reduce the creation and destruction of instances (and thus garbage collection), it maintains
|
|
* an internal object pool of sprite instances to reuse. Increasing this value can cause more sprites to be
|
|
* retained, slightly increasing memory use, but reducing instantiation.
|
|
* @property maxPoolSize
|
|
* @type Number
|
|
* @static
|
|
* @default 100
|
|
**/
|
|
BitmapText.maxPoolSize = 100;
|
|
|
|
/**
|
|
* Sprite object pool.
|
|
* @type {Array}
|
|
* @static
|
|
* @private
|
|
*/
|
|
BitmapText._spritePool = [];
|
|
|
|
// public methods:
|
|
/**
|
|
* Docced in superclass.
|
|
**/
|
|
p.draw = function (ctx, ignoreCache) {
|
|
if (this.DisplayObject_draw(ctx, ignoreCache)) {
|
|
return;
|
|
}
|
|
this._updateText();
|
|
this.Container_draw(ctx, ignoreCache);
|
|
};
|
|
|
|
/**
|
|
* Docced in superclass.
|
|
**/
|
|
p.getBounds = function () {
|
|
this._updateText();
|
|
return this.Container_getBounds();
|
|
};
|
|
|
|
/**
|
|
* Returns true or false indicating whether the display object would be visible if drawn to a canvas.
|
|
* This does not account for whether it would be visible within the boundaries of the stage.
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method isVisible
|
|
* @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas
|
|
**/
|
|
p.isVisible = function () {
|
|
var hasContent =
|
|
this.cacheCanvas ||
|
|
(this.spriteSheet && this.spriteSheet.complete && this.text);
|
|
return !!(
|
|
this.visible &&
|
|
this.alpha > 0 &&
|
|
this.scaleX !== 0 &&
|
|
this.scaleY !== 0 &&
|
|
hasContent
|
|
);
|
|
};
|
|
|
|
p.clone = function () {
|
|
return this._cloneProps(new BitmapText(this.text, this.spriteSheet));
|
|
};
|
|
|
|
/**
|
|
* <strong>Disabled in BitmapText.</strong>
|
|
* @method addChild
|
|
**/
|
|
/**
|
|
* <strong>Disabled in BitmapText.</strong>
|
|
* @method addChildAt
|
|
**/
|
|
/**
|
|
* <strong>Disabled in BitmapText.</strong>
|
|
* @method removeChild
|
|
**/
|
|
/**
|
|
* <strong>Disabled in BitmapText.</strong>
|
|
* @method removeChildAt
|
|
**/
|
|
/**
|
|
* <strong>Disabled in BitmapText.</strong>
|
|
* @method removeAllChildren
|
|
**/
|
|
p.addChild = p.addChildAt = p.removeChild = p.removeChildAt = p.removeAllChildren = function () {};
|
|
|
|
// private methods:
|
|
/**
|
|
* @method _cloneProps
|
|
* @param {BitmapText} o
|
|
* @return {BitmapText} o
|
|
* @protected
|
|
**/
|
|
p._cloneProps = function (o) {
|
|
this.Container__cloneProps(o);
|
|
o.lineHeight = this.lineHeight;
|
|
o.letterSpacing = this.letterSpacing;
|
|
o.spaceWidth = this.spaceWidth;
|
|
return o;
|
|
};
|
|
|
|
/**
|
|
* @method _getFrameIndex
|
|
* @param {String} character
|
|
* @param {SpriteSheet} spriteSheet
|
|
* @return {Number}
|
|
* @protected
|
|
**/
|
|
p._getFrameIndex = function (character, spriteSheet) {
|
|
var c,
|
|
o = spriteSheet.getAnimation(character);
|
|
if (!o) {
|
|
character != (c = character.toUpperCase()) ||
|
|
character != (c = character.toLowerCase()) ||
|
|
(c = null);
|
|
if (c) {
|
|
o = spriteSheet.getAnimation(c);
|
|
}
|
|
}
|
|
return o && o.frames[0];
|
|
};
|
|
|
|
/**
|
|
* @method _getFrame
|
|
* @param {String} character
|
|
* @param {SpriteSheet} spriteSheet
|
|
* @return {Object}
|
|
* @protected
|
|
**/
|
|
p._getFrame = function (character, spriteSheet) {
|
|
var index = this._getFrameIndex(character, spriteSheet);
|
|
return index == null ? index : spriteSheet.getFrame(index);
|
|
};
|
|
|
|
/**
|
|
* @method _getLineHeight
|
|
* @param {SpriteSheet} ss
|
|
* @return {Number}
|
|
* @protected
|
|
**/
|
|
p._getLineHeight = function (ss) {
|
|
var frame =
|
|
this._getFrame("1", ss) ||
|
|
this._getFrame("T", ss) ||
|
|
this._getFrame("L", ss) ||
|
|
ss.getFrame(0);
|
|
return frame ? frame.rect.height : 1;
|
|
};
|
|
/**
|
|
* @method _getSpaceWidth
|
|
* @param {SpriteSheet} ss
|
|
* @return {Number}
|
|
* @protected
|
|
**/
|
|
p._getSpaceWidth = function (ss) {
|
|
var frame =
|
|
this._getFrame("1", ss) ||
|
|
this._getFrame("l", ss) ||
|
|
this._getFrame("e", ss) ||
|
|
this._getFrame("a", ss) ||
|
|
ss.getFrame(0);
|
|
return frame ? frame.rect.width : 1;
|
|
};
|
|
|
|
/**
|
|
* @method _drawText
|
|
* @protected
|
|
**/
|
|
p._updateText = function () {
|
|
var x = 0,
|
|
y = 0,
|
|
o = this._oldProps,
|
|
change = false,
|
|
spaceW = this.spaceWidth,
|
|
lineH = this.lineHeight,
|
|
ss = this.spriteSheet;
|
|
var pool = BitmapText._spritePool,
|
|
kids = this.children,
|
|
childIndex = 0,
|
|
numKids = kids.length,
|
|
sprite;
|
|
|
|
for (var n in o) {
|
|
if (o[n] != this[n]) {
|
|
o[n] = this[n];
|
|
change = true;
|
|
}
|
|
}
|
|
if (!change) {
|
|
return;
|
|
}
|
|
|
|
var hasSpace = !!this._getFrame(" ", ss);
|
|
if (!hasSpace && !spaceW) {
|
|
spaceW = this._getSpaceWidth(ss);
|
|
}
|
|
if (!lineH) {
|
|
lineH = this._getLineHeight(ss);
|
|
}
|
|
|
|
for (var i = 0, l = this.text.length; i < l; i++) {
|
|
var character = this.text.charAt(i);
|
|
if (character == " " && !hasSpace) {
|
|
x += spaceW;
|
|
continue;
|
|
} else if (character == "\n" || character == "\r") {
|
|
if (character == "\r" && this.text.charAt(i + 1) == "\n") {
|
|
i++;
|
|
} // crlf
|
|
x = 0;
|
|
y += lineH;
|
|
continue;
|
|
}
|
|
|
|
var index = this._getFrameIndex(character, ss);
|
|
if (index == null) {
|
|
continue;
|
|
}
|
|
|
|
if (childIndex < numKids) {
|
|
sprite = kids[childIndex];
|
|
} else {
|
|
kids.push((sprite = pool.length ? pool.pop() : new createjs.Sprite()));
|
|
sprite.parent = this;
|
|
numKids++;
|
|
}
|
|
sprite.spriteSheet = ss;
|
|
sprite.gotoAndStop(index);
|
|
sprite.x = x;
|
|
sprite.y = y;
|
|
childIndex++;
|
|
|
|
x += sprite.getBounds().width + this.letterSpacing;
|
|
}
|
|
while (numKids > childIndex) {
|
|
// faster than removeChild.
|
|
pool.push((sprite = kids.pop()));
|
|
sprite.parent = null;
|
|
numKids--;
|
|
}
|
|
if (pool.length > BitmapText.maxPoolSize) {
|
|
pool.length = BitmapText.maxPoolSize;
|
|
}
|
|
};
|
|
|
|
createjs.BitmapText = createjs.promote(BitmapText, "Container");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// MovieClip.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* The MovieClip class associates a TweenJS Timeline with an EaselJS {{#crossLink "Container"}}{{/crossLink}}. It allows
|
|
* you to create objects which encapsulate timeline animations, state changes, and synched actions. Due to the
|
|
* complexities inherent in correctly setting up a MovieClip, it is largely intended for tool output and is not included
|
|
* in the main EaselJS library.
|
|
*
|
|
* Currently MovieClip only works properly if it is tick based (as opposed to time based) though some concessions have
|
|
* been made to support time-based timelines in the future.
|
|
*
|
|
* <h4>Example</h4>
|
|
* This example animates two shapes back and forth. The grey shape starts on the left, but we jump to a mid-point in
|
|
* the animation using {{#crossLink "MovieClip/gotoAndPlay"}}{{/crossLink}}.
|
|
*
|
|
* var stage = new createjs.Stage("canvas");
|
|
* createjs.Ticker.addEventListener("tick", stage);
|
|
*
|
|
* var mc = new createjs.MovieClip(null, 0, true, {start:20});
|
|
* stage.addChild(mc);
|
|
*
|
|
* var child1 = new createjs.Shape(
|
|
* new createjs.Graphics().beginFill("#999999")
|
|
* .drawCircle(30,30,30));
|
|
* var child2 = new createjs.Shape(
|
|
* new createjs.Graphics().beginFill("#5a9cfb")
|
|
* .drawCircle(30,30,30));
|
|
*
|
|
* mc.timeline.addTween(
|
|
* createjs.Tween.get(child1)
|
|
* .to({x:0}).to({x:60}, 50).to({x:0}, 50));
|
|
* mc.timeline.addTween(
|
|
* createjs.Tween.get(child2)
|
|
* .to({x:60}).to({x:0}, 50).to({x:60}, 50));
|
|
*
|
|
* mc.gotoAndPlay("start");
|
|
*
|
|
* It is recommended to use <code>tween.to()</code> to animate and set properties (use no duration to have it set
|
|
* immediately), and the <code>tween.wait()</code> method to create delays between animations. Note that using the
|
|
* <code>tween.set()</code> method to affect properties will likely not provide the desired result.
|
|
*
|
|
* @class MovieClip
|
|
* @main MovieClip
|
|
* @extends Container
|
|
* @constructor
|
|
* @param {String} [mode=independent] Initial value for the mode property. One of {{#crossLink "MovieClip/INDEPENDENT:property"}}{{/crossLink}},
|
|
* {{#crossLink "MovieClip/SINGLE_FRAME:property"}}{{/crossLink}}, or {{#crossLink "MovieClip/SYNCHED:property"}}{{/crossLink}}.
|
|
* The default is {{#crossLink "MovieClip/INDEPENDENT:property"}}{{/crossLink}}.
|
|
* @param {Number} [startPosition=0] Initial value for the {{#crossLink "MovieClip/startPosition:property"}}{{/crossLink}}
|
|
* property.
|
|
* @param {Boolean} [loop=true] Initial value for the {{#crossLink "MovieClip/loop:property"}}{{/crossLink}}
|
|
* property. The default is `true`.
|
|
* @param {Object} [labels=null] A hash of labels to pass to the {{#crossLink "MovieClip/timeline:property"}}{{/crossLink}}
|
|
* instance associated with this MovieClip. Labels only need to be passed if they need to be used.
|
|
**/
|
|
function MovieClip(mode, startPosition, loop, labels) {
|
|
this.Container_constructor();
|
|
!MovieClip.inited && MovieClip.init(); // static init
|
|
|
|
// public properties:
|
|
/**
|
|
* Controls how this MovieClip advances its time. Must be one of 0 (INDEPENDENT), 1 (SINGLE_FRAME), or 2 (SYNCHED).
|
|
* See each constant for a description of the behaviour.
|
|
* @property mode
|
|
* @type String
|
|
* @default null
|
|
**/
|
|
this.mode = mode || MovieClip.INDEPENDENT;
|
|
|
|
/**
|
|
* Specifies what the first frame to play in this movieclip, or the only frame to display if mode is SINGLE_FRAME.
|
|
* @property startPosition
|
|
* @type Number
|
|
* @default 0
|
|
*/
|
|
this.startPosition = startPosition || 0;
|
|
|
|
/**
|
|
* Indicates whether this MovieClip should loop when it reaches the end of its timeline.
|
|
* @property loop
|
|
* @type Boolean
|
|
* @default true
|
|
*/
|
|
this.loop = loop;
|
|
|
|
/**
|
|
* The current frame of the movieclip.
|
|
* @property currentFrame
|
|
* @type Number
|
|
* @default 0
|
|
* @readonly
|
|
*/
|
|
this.currentFrame = 0;
|
|
|
|
/**
|
|
* The TweenJS Timeline that is associated with this MovieClip. This is created automatically when the MovieClip
|
|
* instance is initialized. Animations are created by adding <a href="http://tweenjs.com">TweenJS</a> Tween
|
|
* instances to the timeline.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var tween = createjs.Tween.get(target).to({x:0}).to({x:100}, 30);
|
|
* var mc = new createjs.MovieClip();
|
|
* mc.timeline.addTween(tween);
|
|
*
|
|
* Elements can be added and removed from the timeline by toggling an "_off" property
|
|
* using the <code>tweenInstance.to()</code> method. Note that using <code>Tween.set</code> is not recommended to
|
|
* create MovieClip animations. The following example will toggle the target off on frame 0, and then back on for
|
|
* frame 1. You can use the "visible" property to achieve the same effect.
|
|
*
|
|
* var tween = createjs.Tween.get(target).to({_off:false})
|
|
* .wait(1).to({_off:true})
|
|
* .wait(1).to({_off:false});
|
|
*
|
|
* @property timeline
|
|
* @type Timeline
|
|
* @default null
|
|
*/
|
|
this.timeline = new createjs.Timeline(null, labels, {
|
|
paused: true,
|
|
position: startPosition,
|
|
useTicks: true,
|
|
});
|
|
|
|
/**
|
|
* If true, the MovieClip's position will not advance when ticked.
|
|
* @property paused
|
|
* @type Boolean
|
|
* @default false
|
|
*/
|
|
this.paused = false;
|
|
|
|
/**
|
|
* If true, actions in this MovieClip's tweens will be run when the playhead advances.
|
|
* @property actionsEnabled
|
|
* @type Boolean
|
|
* @default true
|
|
*/
|
|
this.actionsEnabled = true;
|
|
|
|
/**
|
|
* If true, the MovieClip will automatically be reset to its first frame whenever the timeline adds
|
|
* it back onto the display list. This only applies to MovieClip instances with mode=INDEPENDENT.
|
|
* <br><br>
|
|
* For example, if you had a character animation with a "body" child MovieClip instance
|
|
* with different costumes on each frame, you could set body.autoReset = false, so that
|
|
* you can manually change the frame it is on, without worrying that it will be reset
|
|
* automatically.
|
|
* @property autoReset
|
|
* @type Boolean
|
|
* @default true
|
|
*/
|
|
this.autoReset = true;
|
|
|
|
/**
|
|
* An array of bounds for each frame in the MovieClip. This is mainly intended for tool output.
|
|
* @property frameBounds
|
|
* @type Array
|
|
* @default null
|
|
*/
|
|
this.frameBounds = this.frameBounds || null; // TODO: Deprecated. This is for backwards support of FlashCC
|
|
|
|
/**
|
|
* By default MovieClip instances advance one frame per tick. Specifying a framerate for the MovieClip
|
|
* will cause it to advance based on elapsed time between ticks as appropriate to maintain the target
|
|
* framerate.
|
|
*
|
|
* For example, if a MovieClip with a framerate of 10 is placed on a Stage being updated at 40fps, then the MovieClip will
|
|
* advance roughly one frame every 4 ticks. This will not be exact, because the time between each tick will
|
|
* vary slightly between frames.
|
|
*
|
|
* This feature is dependent on the tick event object (or an object with an appropriate "delta" property) being
|
|
* passed into {{#crossLink "Stage/update"}}{{/crossLink}}.
|
|
* @property framerate
|
|
* @type {Number}
|
|
* @default null
|
|
**/
|
|
this.framerate = null;
|
|
|
|
// private properties:
|
|
/**
|
|
* @property _synchOffset
|
|
* @type Number
|
|
* @default 0
|
|
* @private
|
|
*/
|
|
this._synchOffset = 0;
|
|
|
|
/**
|
|
* @property _prevPos
|
|
* @type Number
|
|
* @default -1
|
|
* @private
|
|
*/
|
|
this._prevPos = -1; // TODO: evaluate using a ._reset Boolean prop instead of -1.
|
|
|
|
/**
|
|
* @property _prevPosition
|
|
* @type Number
|
|
* @default 0
|
|
* @private
|
|
*/
|
|
this._prevPosition = 0;
|
|
|
|
/**
|
|
* The time remaining from the previous tick, only applicable when .framerate is set.
|
|
* @property _t
|
|
* @type Number
|
|
* @private
|
|
*/
|
|
this._t = 0;
|
|
|
|
/**
|
|
* List of display objects that are actively being managed by the MovieClip.
|
|
* @property _managed
|
|
* @type Object
|
|
* @private
|
|
*/
|
|
this._managed = {};
|
|
}
|
|
var p = createjs.extend(MovieClip, createjs.Container);
|
|
|
|
// constants:
|
|
/**
|
|
* The MovieClip will advance independently of its parent, even if its parent is paused.
|
|
* This is the default mode.
|
|
* @property INDEPENDENT
|
|
* @static
|
|
* @type String
|
|
* @default "independent"
|
|
* @readonly
|
|
**/
|
|
MovieClip.INDEPENDENT = "independent";
|
|
|
|
/**
|
|
* The MovieClip will only display a single frame (as determined by the startPosition property).
|
|
* @property SINGLE_FRAME
|
|
* @static
|
|
* @type String
|
|
* @default "single"
|
|
* @readonly
|
|
**/
|
|
MovieClip.SINGLE_FRAME = "single";
|
|
|
|
/**
|
|
* The MovieClip will be advanced only when its parent advances and will be synched to the position of
|
|
* the parent MovieClip.
|
|
* @property SYNCHED
|
|
* @static
|
|
* @type String
|
|
* @default "synched"
|
|
* @readonly
|
|
**/
|
|
MovieClip.SYNCHED = "synched";
|
|
|
|
// static properties:
|
|
MovieClip.inited = false;
|
|
|
|
// static methods:
|
|
MovieClip.init = function () {
|
|
if (MovieClip.inited) {
|
|
return;
|
|
}
|
|
// plugins introduce some overhead to Tween, so we only install this if an MC is instantiated.
|
|
MovieClipPlugin.install();
|
|
MovieClip.inited = true;
|
|
};
|
|
|
|
// getter / setters:
|
|
/**
|
|
* Use the {{#crossLink "MovieClip/labels:property"}}{{/crossLink}} property instead.
|
|
* @method getLabels
|
|
* @return {Array}
|
|
* @deprecated
|
|
**/
|
|
p.getLabels = function () {
|
|
return this.timeline.getLabels();
|
|
};
|
|
|
|
/**
|
|
* Use the {{#crossLink "MovieClip/currentLabel:property"}}{{/crossLink}} property instead.
|
|
* @method getCurrentLabel
|
|
* @return {String}
|
|
* @deprecated
|
|
**/
|
|
p.getCurrentLabel = function () {
|
|
this._updateTimeline();
|
|
return this.timeline.getCurrentLabel();
|
|
};
|
|
|
|
/**
|
|
* Use the {{#crossLink "MovieClip/duration:property"}}{{/crossLink}} property instead.
|
|
* @method getDuration
|
|
* @return {Number}
|
|
* @protected
|
|
**/
|
|
p.getDuration = function () {
|
|
return this.timeline.duration;
|
|
};
|
|
|
|
/**
|
|
* Returns an array of objects with label and position (aka frame) properties, sorted by position.
|
|
* Shortcut to TweenJS: Timeline.getLabels();
|
|
* @property labels
|
|
* @type {Array}
|
|
* @readonly
|
|
**/
|
|
|
|
/**
|
|
* Returns the name of the label on or immediately before the current frame. See TweenJS: Timeline.getCurrentLabel()
|
|
* for more information.
|
|
* @property currentLabel
|
|
* @type {String}
|
|
* @readonly
|
|
**/
|
|
|
|
/**
|
|
* Returns the duration of this MovieClip in seconds or ticks. Identical to {{#crossLink "MovieClip/duration:property"}}{{/crossLink}}
|
|
* and provided for Flash API compatibility.
|
|
* @property totalFrames
|
|
* @type {Number}
|
|
* @readonly
|
|
**/
|
|
|
|
/**
|
|
* Returns the duration of this MovieClip in seconds or ticks.
|
|
* @property duration
|
|
* @type {Number}
|
|
* @readonly
|
|
**/
|
|
try {
|
|
Object.defineProperties(p, {
|
|
labels: { get: p.getLabels },
|
|
currentLabel: { get: p.getCurrentLabel },
|
|
totalFrames: { get: p.getDuration },
|
|
duration: { get: p.getDuration },
|
|
});
|
|
} catch (e) {}
|
|
|
|
// public methods:
|
|
/**
|
|
* Constructor alias for backwards compatibility. This method will be removed in future versions.
|
|
* Subclasses should be updated to use {{#crossLink "Utility Methods/extends"}}{{/crossLink}}.
|
|
* @method initialize
|
|
* @deprecated in favour of `createjs.promote()`
|
|
**/
|
|
p.initialize = MovieClip; // TODO: Deprecated. This is for backwards support of FlashCC
|
|
|
|
/**
|
|
* Returns true or false indicating whether the display object would be visible if drawn to a canvas.
|
|
* This does not account for whether it would be visible within the boundaries of the stage.
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method isVisible
|
|
* @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas
|
|
**/
|
|
p.isVisible = function () {
|
|
// children are placed in draw, so we can't determine if we have content.
|
|
return !!(
|
|
this.visible &&
|
|
this.alpha > 0 &&
|
|
this.scaleX != 0 &&
|
|
this.scaleY != 0
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform.
|
|
* Returns true if the draw was handled (useful for overriding functionality).
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method draw
|
|
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into.
|
|
* @param {Boolean} ignoreCache Indicates whether the draw operation should ignore any current cache.
|
|
* For example, used for drawing the cache (to prevent it from simply drawing an existing cache back
|
|
* into itself).
|
|
**/
|
|
p.draw = function (ctx, ignoreCache) {
|
|
// draw to cache first:
|
|
if (this.DisplayObject_draw(ctx, ignoreCache)) {
|
|
return true;
|
|
}
|
|
this._updateTimeline();
|
|
this.Container_draw(ctx, ignoreCache);
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Sets paused to false.
|
|
* @method play
|
|
**/
|
|
p.play = function () {
|
|
this.paused = false;
|
|
};
|
|
|
|
/**
|
|
* Sets paused to true.
|
|
* @method stop
|
|
**/
|
|
p.stop = function () {
|
|
this.paused = true;
|
|
};
|
|
|
|
/**
|
|
* Advances this movie clip to the specified position or label and sets paused to false.
|
|
* @method gotoAndPlay
|
|
* @param {String|Number} positionOrLabel The animation name or frame number to go to.
|
|
**/
|
|
p.gotoAndPlay = function (positionOrLabel) {
|
|
this.paused = false;
|
|
this._goto(positionOrLabel);
|
|
};
|
|
|
|
/**
|
|
* Advances this movie clip to the specified position or label and sets paused to true.
|
|
* @method gotoAndStop
|
|
* @param {String|Number} positionOrLabel The animation or frame name to go to.
|
|
**/
|
|
p.gotoAndStop = function (positionOrLabel) {
|
|
this.paused = true;
|
|
this._goto(positionOrLabel);
|
|
};
|
|
|
|
/**
|
|
* Advances the playhead. This occurs automatically each tick by default.
|
|
* @param [time] {Number} The amount of time in ms to advance by. Only applicable if framerate is set.
|
|
* @method advance
|
|
*/
|
|
p.advance = function (time) {
|
|
// TODO: should we worry at all about clips who change their own modes via frame scripts?
|
|
var independent = MovieClip.INDEPENDENT;
|
|
if (this.mode != independent) {
|
|
return;
|
|
}
|
|
|
|
var o = this,
|
|
fps = o.framerate;
|
|
while ((o = o.parent) && fps == null) {
|
|
if (o.mode == independent) {
|
|
fps = o._framerate;
|
|
}
|
|
}
|
|
this._framerate = fps;
|
|
|
|
var t =
|
|
fps != null && fps != -1 && time != null
|
|
? time / (1000 / fps) + this._t
|
|
: 1;
|
|
var frames = t | 0;
|
|
this._t = t - frames; // leftover time
|
|
|
|
while (!this.paused && frames--) {
|
|
this._prevPosition = this._prevPos < 0 ? 0 : this._prevPosition + 1;
|
|
this._updateTimeline();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* MovieClip instances cannot be cloned.
|
|
* @method clone
|
|
**/
|
|
p.clone = function () {
|
|
// TODO: add support for this? Need to clone the Timeline & retarget tweens - pretty complex.
|
|
throw "MovieClip cannot be cloned.";
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[MovieClip (name=" + this.name + ")]";
|
|
};
|
|
|
|
// private methods:
|
|
/**
|
|
* @method _tick
|
|
* @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs.
|
|
* function.
|
|
* @protected
|
|
**/
|
|
p._tick = function (evtObj) {
|
|
this.advance(evtObj && evtObj.delta);
|
|
this.Container__tick(evtObj);
|
|
};
|
|
|
|
/**
|
|
* @method _goto
|
|
* @param {String|Number} positionOrLabel The animation name or frame number to go to.
|
|
* @protected
|
|
**/
|
|
p._goto = function (positionOrLabel) {
|
|
var pos = this.timeline.resolve(positionOrLabel);
|
|
if (pos == null) {
|
|
return;
|
|
}
|
|
// prevent _updateTimeline from overwriting the new position because of a reset:
|
|
if (this._prevPos == -1) {
|
|
this._prevPos = NaN;
|
|
}
|
|
this._prevPosition = pos;
|
|
this._t = 0;
|
|
this._updateTimeline();
|
|
};
|
|
|
|
/**
|
|
* @method _reset
|
|
* @private
|
|
**/
|
|
p._reset = function () {
|
|
this._prevPos = -1;
|
|
this._t = this.currentFrame = 0;
|
|
this.paused = false;
|
|
};
|
|
|
|
/**
|
|
* @method _updateTimeline
|
|
* @protected
|
|
**/
|
|
p._updateTimeline = function () {
|
|
var tl = this.timeline;
|
|
var synched = this.mode != MovieClip.INDEPENDENT;
|
|
tl.loop = this.loop == null ? true : this.loop;
|
|
|
|
var pos = synched
|
|
? this.startPosition +
|
|
(this.mode == MovieClip.SINGLE_FRAME ? 0 : this._synchOffset)
|
|
: this._prevPos < 0
|
|
? 0
|
|
: this._prevPosition;
|
|
var mode = synched || !this.actionsEnabled ? createjs.Tween.NONE : null;
|
|
|
|
// pre-assign currentFrame so it is available to frame scripts:
|
|
this.currentFrame = tl._calcPosition(pos);
|
|
|
|
// update timeline position, ignoring actions if this is a graphic.
|
|
tl.setPosition(pos, mode);
|
|
|
|
this._prevPosition = tl._prevPosition;
|
|
if (this._prevPos == tl._prevPos) {
|
|
return;
|
|
}
|
|
this.currentFrame = this._prevPos = tl._prevPos;
|
|
|
|
for (var n in this._managed) {
|
|
this._managed[n] = 1;
|
|
}
|
|
|
|
var tweens = tl._tweens;
|
|
for (var i = 0, l = tweens.length; i < l; i++) {
|
|
var tween = tweens[i];
|
|
var target = tween._target;
|
|
if (target == this || tween.passive) {
|
|
continue;
|
|
} // TODO: this assumes actions tween has this as the target. Valid?
|
|
var offset = tween._stepPosition;
|
|
|
|
if (target instanceof createjs.DisplayObject) {
|
|
// motion tween.
|
|
this._addManagedChild(target, offset);
|
|
} else {
|
|
// state tween.
|
|
this._setState(target.state, offset);
|
|
}
|
|
}
|
|
|
|
var kids = this.children;
|
|
for (i = kids.length - 1; i >= 0; i--) {
|
|
var id = kids[i].id;
|
|
if (this._managed[id] == 1) {
|
|
this.removeChildAt(i);
|
|
delete this._managed[id];
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _setState
|
|
* @param {Array} state
|
|
* @param {Number} offset
|
|
* @protected
|
|
**/
|
|
p._setState = function (state, offset) {
|
|
if (!state) {
|
|
return;
|
|
}
|
|
for (var i = state.length - 1; i >= 0; i--) {
|
|
var o = state[i];
|
|
var target = o.t;
|
|
var props = o.p;
|
|
for (var n in props) {
|
|
target[n] = props[n];
|
|
}
|
|
this._addManagedChild(target, offset);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Adds a child to the timeline, and sets it up as a managed child.
|
|
* @method _addManagedChild
|
|
* @param {MovieClip} child The child MovieClip to manage
|
|
* @param {Number} offset
|
|
* @private
|
|
**/
|
|
p._addManagedChild = function (child, offset) {
|
|
if (child._off) {
|
|
return;
|
|
}
|
|
this.addChildAt(child, 0);
|
|
|
|
if (child instanceof MovieClip) {
|
|
child._synchOffset = offset;
|
|
// TODO: this does not precisely match Flash. Flash loses track of the clip if it is renamed or removed from the timeline, which causes it to reset.
|
|
if (
|
|
child.mode == MovieClip.INDEPENDENT &&
|
|
child.autoReset &&
|
|
!this._managed[child.id]
|
|
) {
|
|
child._reset();
|
|
}
|
|
}
|
|
this._managed[child.id] = 2;
|
|
};
|
|
|
|
/**
|
|
* @method _getBounds
|
|
* @param {Matrix2D} matrix
|
|
* @param {Boolean} ignoreTransform
|
|
* @return {Rectangle}
|
|
* @protected
|
|
**/
|
|
p._getBounds = function (matrix, ignoreTransform) {
|
|
var bounds = this.DisplayObject_getBounds();
|
|
if (!bounds) {
|
|
this._updateTimeline();
|
|
if (this.frameBounds) {
|
|
bounds = this._rectangle.copy(this.frameBounds[this.currentFrame]);
|
|
}
|
|
}
|
|
if (bounds) {
|
|
return this._transformBounds(bounds, matrix, ignoreTransform);
|
|
}
|
|
return this.Container__getBounds(matrix, ignoreTransform);
|
|
};
|
|
|
|
createjs.MovieClip = createjs.promote(MovieClip, "Container");
|
|
|
|
// MovieClipPlugin for TweenJS:
|
|
/**
|
|
* This plugin works with <a href="http://tweenjs.com" target="_blank">TweenJS</a> to prevent the startPosition
|
|
* property from tweening.
|
|
* @private
|
|
* @class MovieClipPlugin
|
|
* @constructor
|
|
**/
|
|
function MovieClipPlugin() {
|
|
throw "MovieClipPlugin cannot be instantiated.";
|
|
}
|
|
|
|
/**
|
|
* @method priority
|
|
* @private
|
|
**/
|
|
MovieClipPlugin.priority = 100; // very high priority, should run first
|
|
|
|
/**
|
|
* @method install
|
|
* @private
|
|
**/
|
|
MovieClipPlugin.install = function () {
|
|
createjs.Tween.installPlugin(MovieClipPlugin, ["startPosition"]);
|
|
};
|
|
|
|
/**
|
|
* @method init
|
|
* @param {Tween} tween
|
|
* @param {String} prop
|
|
* @param {String|Number|Boolean} value
|
|
* @private
|
|
**/
|
|
MovieClipPlugin.init = function (tween, prop, value) {
|
|
return value;
|
|
};
|
|
|
|
/**
|
|
* @method step
|
|
* @private
|
|
**/
|
|
MovieClipPlugin.step = function () {
|
|
// unused.
|
|
};
|
|
|
|
/**
|
|
* @method tween
|
|
* @param {Tween} tween
|
|
* @param {String} prop
|
|
* @param {String | Number | Boolean} value
|
|
* @param {Array} startValues
|
|
* @param {Array} endValues
|
|
* @param {Number} ratio
|
|
* @param {Object} wait
|
|
* @param {Object} end
|
|
* @return {*}
|
|
*/
|
|
MovieClipPlugin.tween = function (
|
|
tween,
|
|
prop,
|
|
value,
|
|
startValues,
|
|
endValues,
|
|
ratio,
|
|
wait,
|
|
end
|
|
) {
|
|
if (!(tween.target instanceof MovieClip)) {
|
|
return value;
|
|
}
|
|
return ratio == 1 ? endValues[prop] : startValues[prop];
|
|
};
|
|
})();
|
|
|
|
//##############################################################################
|
|
// SpriteSheetUtils.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* The SpriteSheetUtils class is a collection of static methods for working with {{#crossLink "SpriteSheet"}}{{/crossLink}}s.
|
|
* A sprite sheet is a series of images (usually animation frames) combined into a single image on a regular grid. For
|
|
* example, an animation consisting of 8 100x100 images could be combined into a 400x200 sprite sheet (4 frames across
|
|
* by 2 high). The SpriteSheetUtils class uses a static interface and should not be instantiated.
|
|
* @class SpriteSheetUtils
|
|
* @static
|
|
**/
|
|
function SpriteSheetUtils() {
|
|
throw "SpriteSheetUtils cannot be instantiated";
|
|
}
|
|
|
|
// private static properties:
|
|
/**
|
|
* @property _workingCanvas
|
|
* @static
|
|
* @type HTMLCanvasElement | Object
|
|
* @protected
|
|
*/
|
|
/**
|
|
* @property _workingContext
|
|
* @static
|
|
* @type CanvasRenderingContext2D
|
|
* @protected
|
|
*/
|
|
var canvas = createjs.createCanvas
|
|
? createjs.createCanvas()
|
|
: document.createElement("canvas");
|
|
if (canvas.getContext) {
|
|
SpriteSheetUtils._workingCanvas = canvas;
|
|
SpriteSheetUtils._workingContext = canvas.getContext("2d");
|
|
canvas.width = canvas.height = 1;
|
|
}
|
|
|
|
// public static methods:
|
|
/**
|
|
* <b>This is an experimental method, and may be buggy. Please report issues.</b><br/><br/>
|
|
* Extends the existing sprite sheet by flipping the original frames horizontally, vertically, or both,
|
|
* and adding appropriate animation & frame data. The flipped animations will have a suffix added to their names
|
|
* (_h, _v, _hv as appropriate). Make sure the sprite sheet images are fully loaded before using this method.
|
|
* <br/><br/>
|
|
* For example:<br/>
|
|
* SpriteSheetUtils.addFlippedFrames(mySpriteSheet, true, true);
|
|
* The above would add frames that are flipped horizontally AND frames that are flipped vertically.
|
|
* <br/><br/>
|
|
* Note that you can also flip any display object by setting its scaleX or scaleY to a negative value. On some
|
|
* browsers (especially those without hardware accelerated canvas) this can result in slightly degraded performance,
|
|
* which is why addFlippedFrames is available.
|
|
* @method addFlippedFrames
|
|
* @static
|
|
* @param {SpriteSheet} spriteSheet
|
|
* @param {Boolean} horizontal If true, horizontally flipped frames will be added.
|
|
* @param {Boolean} vertical If true, vertically flipped frames will be added.
|
|
* @param {Boolean} both If true, frames that are flipped both horizontally and vertically will be added.
|
|
* @deprecated Modern browsers perform better when flipping via a transform (ex. scaleX=-1) rendering this obsolete.
|
|
**/
|
|
SpriteSheetUtils.addFlippedFrames = function (
|
|
spriteSheet,
|
|
horizontal,
|
|
vertical,
|
|
both
|
|
) {
|
|
if (!horizontal && !vertical && !both) {
|
|
return;
|
|
}
|
|
|
|
var count = 0;
|
|
if (horizontal) {
|
|
SpriteSheetUtils._flip(spriteSheet, ++count, true, false);
|
|
}
|
|
if (vertical) {
|
|
SpriteSheetUtils._flip(spriteSheet, ++count, false, true);
|
|
}
|
|
if (both) {
|
|
SpriteSheetUtils._flip(spriteSheet, ++count, true, true);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns a single frame of the specified sprite sheet as a new PNG image. An example of when this may be useful is
|
|
* to use a spritesheet frame as the source for a bitmap fill.
|
|
*
|
|
* <strong>WARNING:</strong> In almost all cases it is better to display a single frame using a {{#crossLink "Sprite"}}{{/crossLink}}
|
|
* with a {{#crossLink "Sprite/gotoAndStop"}}{{/crossLink}} call than it is to slice out a frame using this
|
|
* method and display it with a Bitmap instance. You can also crop an image using the {{#crossLink "Bitmap/sourceRect"}}{{/crossLink}}
|
|
* property of {{#crossLink "Bitmap"}}{{/crossLink}}.
|
|
*
|
|
* The extractFrame method may cause cross-domain warnings since it accesses pixels directly on the canvas.
|
|
* @method extractFrame
|
|
* @static
|
|
* @param {SpriteSheet} spriteSheet The SpriteSheet instance to extract a frame from.
|
|
* @param {Number|String} frameOrAnimation The frame number or animation name to extract. If an animation
|
|
* name is specified, only the first frame of the animation will be extracted.
|
|
* @return {HTMLImageElement} a single frame of the specified sprite sheet as a new PNG image.
|
|
*/
|
|
SpriteSheetUtils.extractFrame = function (spriteSheet, frameOrAnimation) {
|
|
if (isNaN(frameOrAnimation)) {
|
|
frameOrAnimation = spriteSheet.getAnimation(frameOrAnimation).frames[0];
|
|
}
|
|
var data = spriteSheet.getFrame(frameOrAnimation);
|
|
if (!data) {
|
|
return null;
|
|
}
|
|
var r = data.rect;
|
|
var canvas = SpriteSheetUtils._workingCanvas;
|
|
canvas.width = r.width;
|
|
canvas.height = r.height;
|
|
SpriteSheetUtils._workingContext.drawImage(
|
|
data.image,
|
|
r.x,
|
|
r.y,
|
|
r.width,
|
|
r.height,
|
|
0,
|
|
0,
|
|
r.width,
|
|
r.height
|
|
);
|
|
var img = document.createElement("img");
|
|
img.src = canvas.toDataURL("image/png");
|
|
return img;
|
|
};
|
|
|
|
/**
|
|
* Merges the rgb channels of one image with the alpha channel of another. This can be used to combine a compressed
|
|
* JPEG image containing color data with a PNG32 monochromatic image containing alpha data. With certain types of
|
|
* images (those with detail that lend itself to JPEG compression) this can provide significant file size savings
|
|
* versus a single RGBA PNG32. This method is very fast (generally on the order of 1-2 ms to run).
|
|
* @method mergeAlpha
|
|
* @static
|
|
* @param {HTMLImageElement} rbgImage The image (or canvas) containing the RGB channels to use.
|
|
* @param {HTMLImageElement} alphaImage The image (or canvas) containing the alpha channel to use.
|
|
* @param {HTMLCanvasElement} canvas Optional. If specified, this canvas will be used and returned. If not, a new canvas will be created.
|
|
* @return {HTMLCanvasElement} A canvas with the combined image data. This can be used as a source for Bitmap or SpriteSheet.
|
|
* @deprecated Tools such as ImageAlpha generally provide better results. This will be moved to sandbox in the future.
|
|
*/
|
|
SpriteSheetUtils.mergeAlpha = function (rgbImage, alphaImage, canvas) {
|
|
if (!canvas) {
|
|
canvas = createjs.createCanvas
|
|
? createjs.createCanvas()
|
|
: document.createElement("canvas");
|
|
}
|
|
canvas.width = Math.max(alphaImage.width, rgbImage.width);
|
|
canvas.height = Math.max(alphaImage.height, rgbImage.height);
|
|
var ctx = canvas.getContext("2d");
|
|
ctx.save();
|
|
ctx.drawImage(rgbImage, 0, 0);
|
|
ctx.globalCompositeOperation = "destination-in";
|
|
ctx.drawImage(alphaImage, 0, 0);
|
|
ctx.restore();
|
|
return canvas;
|
|
};
|
|
|
|
// private static methods:
|
|
SpriteSheetUtils._flip = function (spriteSheet, count, h, v) {
|
|
var imgs = spriteSheet._images;
|
|
var canvas = SpriteSheetUtils._workingCanvas;
|
|
var ctx = SpriteSheetUtils._workingContext;
|
|
var il = imgs.length / count;
|
|
for (var i = 0; i < il; i++) {
|
|
var src = imgs[i];
|
|
src.__tmp = i; // a bit hacky, but faster than doing indexOf below.
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
ctx.clearRect(0, 0, canvas.width + 1, canvas.height + 1);
|
|
canvas.width = src.width;
|
|
canvas.height = src.height;
|
|
ctx.setTransform(
|
|
h ? -1 : 1,
|
|
0,
|
|
0,
|
|
v ? -1 : 1,
|
|
h ? src.width : 0,
|
|
v ? src.height : 0
|
|
);
|
|
ctx.drawImage(src, 0, 0);
|
|
var img = document.createElement("img");
|
|
img.src = canvas.toDataURL("image/png");
|
|
// work around a strange bug in Safari:
|
|
img.width = src.width;
|
|
img.height = src.height;
|
|
imgs.push(img);
|
|
}
|
|
|
|
var frames = spriteSheet._frames;
|
|
var fl = frames.length / count;
|
|
for (i = 0; i < fl; i++) {
|
|
src = frames[i];
|
|
var rect = src.rect.clone();
|
|
img = imgs[src.image.__tmp + il * count];
|
|
|
|
var frame = { image: img, rect: rect, regX: src.regX, regY: src.regY };
|
|
if (h) {
|
|
rect.x = img.width - rect.x - rect.width; // update rect
|
|
frame.regX = rect.width - src.regX; // update registration point
|
|
}
|
|
if (v) {
|
|
rect.y = img.height - rect.y - rect.height; // update rect
|
|
frame.regY = rect.height - src.regY; // update registration point
|
|
}
|
|
frames.push(frame);
|
|
}
|
|
|
|
var sfx = "_" + (h ? "h" : "") + (v ? "v" : "");
|
|
var names = spriteSheet._animations;
|
|
var data = spriteSheet._data;
|
|
var al = names.length / count;
|
|
for (i = 0; i < al; i++) {
|
|
var name = names[i];
|
|
src = data[name];
|
|
var anim = {
|
|
name: name + sfx,
|
|
speed: src.speed,
|
|
next: src.next,
|
|
frames: [],
|
|
};
|
|
if (src.next) {
|
|
anim.next += sfx;
|
|
}
|
|
frames = src.frames;
|
|
for (var j = 0, l = frames.length; j < l; j++) {
|
|
anim.frames.push(frames[j] + fl * count);
|
|
}
|
|
data[anim.name] = anim;
|
|
names.push(anim.name);
|
|
}
|
|
};
|
|
|
|
createjs.SpriteSheetUtils = SpriteSheetUtils;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// SpriteSheetBuilder.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* The SpriteSheetBuilder allows you to generate {{#crossLink "SpriteSheet"}}{{/crossLink}} instances at run time
|
|
* from any display object. This can allow you to maintain your assets as vector graphics (for low file size), and
|
|
* render them at run time as SpriteSheets for better performance.
|
|
*
|
|
* SpriteSheets can be built either synchronously, or asynchronously, so that large SpriteSheets can be generated
|
|
* without locking the UI.
|
|
*
|
|
* Note that the "images" used in the generated SpriteSheet are actually canvas elements, and that they will be
|
|
* sized to the nearest power of 2 up to the value of {{#crossLink "SpriteSheetBuilder/maxWidth:property"}}{{/crossLink}}
|
|
* or {{#crossLink "SpriteSheetBuilder/maxHeight:property"}}{{/crossLink}}.
|
|
* @class SpriteSheetBuilder
|
|
* @param {Number} [framerate=0] The {{#crossLink "SpriteSheet/framerate:property"}}{{/crossLink}} of
|
|
* {{#crossLink "SpriteSheet"}}{{/crossLink}} instances that are created.
|
|
* @extends EventDispatcher
|
|
* @constructor
|
|
**/
|
|
function SpriteSheetBuilder(framerate) {
|
|
this.EventDispatcher_constructor();
|
|
|
|
// public properties:
|
|
/**
|
|
* The maximum width for the images (not individual frames) in the generated SpriteSheet. It is recommended to
|
|
* use a power of 2 for this value (ex. 1024, 2048, 4096). If the frames cannot all fit within the max
|
|
* dimensions, then additional images will be created as needed.
|
|
* @property maxWidth
|
|
* @type Number
|
|
* @default 2048
|
|
*/
|
|
this.maxWidth = 2048;
|
|
|
|
/**
|
|
* The maximum height for the images (not individual frames) in the generated SpriteSheet. It is recommended to
|
|
* use a power of 2 for this value (ex. 1024, 2048, 4096). If the frames cannot all fit within the max
|
|
* dimensions, then additional images will be created as needed.
|
|
* @property maxHeight
|
|
* @type Number
|
|
* @default 2048
|
|
**/
|
|
this.maxHeight = 2048;
|
|
|
|
/**
|
|
* The SpriteSheet that was generated. This will be null before a build is completed successfully.
|
|
* @property spriteSheet
|
|
* @type SpriteSheet
|
|
**/
|
|
this.spriteSheet = null;
|
|
|
|
/**
|
|
* The scale to apply when drawing all frames to the SpriteSheet. This is multiplied against any scale specified
|
|
* in the addFrame call. This can be used, for example, to generate a SpriteSheet at run time that is tailored
|
|
* to the a specific device resolution (ex. tablet vs mobile).
|
|
* @property scale
|
|
* @type Number
|
|
* @default 1
|
|
**/
|
|
this.scale = 1;
|
|
|
|
/**
|
|
* The padding to use between frames. This is helpful to preserve antialiasing on drawn vector content.
|
|
* @property padding
|
|
* @type Number
|
|
* @default 1
|
|
**/
|
|
this.padding = 1;
|
|
|
|
/**
|
|
* A number from 0.01 to 0.99 that indicates what percentage of time the builder can use. This can be
|
|
* thought of as the number of seconds per second the builder will use. For example, with a timeSlice value of 0.3,
|
|
* the builder will run 20 times per second, using approximately 15ms per build (30% of available time, or 0.3s per second).
|
|
* Defaults to 0.3.
|
|
* @property timeSlice
|
|
* @type Number
|
|
* @default 0.3
|
|
**/
|
|
this.timeSlice = 0.3;
|
|
|
|
/**
|
|
* A value between 0 and 1 that indicates the progress of a build, or -1 if a build has not
|
|
* been initiated.
|
|
* @property progress
|
|
* @type Number
|
|
* @default -1
|
|
* @readonly
|
|
*/
|
|
this.progress = -1;
|
|
|
|
/**
|
|
* A {{#crossLink "SpriteSheet/framerate:property"}}{{/crossLink}} value that will be passed to new {{#crossLink "SpriteSheet"}}{{/crossLink}} instances that are
|
|
* created. If no framerate is specified (or it is 0), then SpriteSheets will use the {{#crossLink "Ticker"}}{{/crossLink}}
|
|
* framerate.
|
|
* @property framerate
|
|
* @type Number
|
|
* @default 0
|
|
*/
|
|
this.framerate = framerate || 0;
|
|
|
|
// private properties:
|
|
/**
|
|
* @property _frames
|
|
* @protected
|
|
* @type Array
|
|
**/
|
|
this._frames = [];
|
|
|
|
/**
|
|
* @property _animations
|
|
* @protected
|
|
* @type Array
|
|
**/
|
|
this._animations = {};
|
|
|
|
/**
|
|
* @property _data
|
|
* @protected
|
|
* @type Array
|
|
**/
|
|
this._data = null;
|
|
|
|
/**
|
|
* @property _nextFrameIndex
|
|
* @protected
|
|
* @type Number
|
|
**/
|
|
this._nextFrameIndex = 0;
|
|
|
|
/**
|
|
* @property _index
|
|
* @protected
|
|
* @type Number
|
|
**/
|
|
this._index = 0;
|
|
|
|
/**
|
|
* @property _timerID
|
|
* @protected
|
|
* @type Number
|
|
**/
|
|
this._timerID = null;
|
|
|
|
/**
|
|
* @property _scale
|
|
* @protected
|
|
* @type Number
|
|
**/
|
|
this._scale = 1;
|
|
}
|
|
var p = createjs.extend(SpriteSheetBuilder, createjs.EventDispatcher);
|
|
|
|
/**
|
|
* <strong>REMOVED</strong>. Removed in favor of using `MySuperClass_constructor`.
|
|
* See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}}
|
|
* for details.
|
|
*
|
|
* There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance.
|
|
*
|
|
* @method initialize
|
|
* @protected
|
|
* @deprecated
|
|
*/
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is.
|
|
|
|
// constants:
|
|
SpriteSheetBuilder.ERR_DIMENSIONS =
|
|
"frame dimensions exceed max spritesheet dimensions";
|
|
SpriteSheetBuilder.ERR_RUNNING = "a build is already running";
|
|
|
|
// events:
|
|
/**
|
|
* Dispatched when a build completes.
|
|
* @event complete
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
/**
|
|
* Dispatched when an asynchronous build has progress.
|
|
* @event progress
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @param {Number} progress The current progress value (0-1).
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
// public methods:
|
|
/**
|
|
* Adds a frame to the {{#crossLink "SpriteSheet"}}{{/crossLink}}. Note that the frame will not be drawn until you
|
|
* call {{#crossLink "SpriteSheetBuilder/build"}}{{/crossLink}} method. The optional setup params allow you to have
|
|
* a function run immediately before the draw occurs. For example, this allows you to add a single source multiple
|
|
* times, but manipulate it or its children to change it to generate different frames.
|
|
*
|
|
* Note that the source's transformations (x, y, scale, rotate, alpha) will be ignored, except for regX/Y. To apply
|
|
* transforms to a source object and have them captured in the SpriteSheet, simply place it into a {{#crossLink "Container"}}{{/crossLink}}
|
|
* and pass in the Container as the source.
|
|
* @method addFrame
|
|
* @param {DisplayObject} source The source {{#crossLink "DisplayObject"}}{{/crossLink}} to draw as the frame.
|
|
* @param {Rectangle} [sourceRect] A {{#crossLink "Rectangle"}}{{/crossLink}} defining the portion of the
|
|
* source to draw to the frame. If not specified, it will look for a `getBounds` method, bounds property, or
|
|
* `nominalBounds` property on the source to use. If one is not found, the frame will be skipped.
|
|
* @param {Number} [scale=1] Optional. The scale to draw this frame at. Default is 1.
|
|
* @param {Function} [setupFunction] A function to call immediately before drawing this frame. It will be called with two parameters: the source, and setupData.
|
|
* @param {Object} [setupData] Arbitrary setup data to pass to setupFunction as the second parameter.
|
|
* @return {Number} The index of the frame that was just added, or null if a sourceRect could not be determined.
|
|
**/
|
|
p.addFrame = function (source, sourceRect, scale, setupFunction, setupData) {
|
|
if (this._data) {
|
|
throw SpriteSheetBuilder.ERR_RUNNING;
|
|
}
|
|
var rect = sourceRect || source.bounds || source.nominalBounds;
|
|
if (!rect && source.getBounds) {
|
|
rect = source.getBounds();
|
|
}
|
|
if (!rect) {
|
|
return null;
|
|
}
|
|
scale = scale || 1;
|
|
return (
|
|
this._frames.push({
|
|
source: source,
|
|
sourceRect: rect,
|
|
scale: scale,
|
|
funct: setupFunction,
|
|
data: setupData,
|
|
index: this._frames.length,
|
|
height: rect.height * scale,
|
|
}) - 1
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Adds an animation that will be included in the created {{#crossLink "SpriteSheet"}}{{/crossLink}}.
|
|
* @method addAnimation
|
|
* @param {String} name The name for the animation.
|
|
* @param {Array} frames An array of frame indexes that comprise the animation. Ex. [3,6,5] would describe an animation
|
|
* that played frame indexes 3, 6, and 5 in that order.
|
|
* @param {String} [next] Specifies the name of the animation to continue to after this animation ends. You can
|
|
* also pass false to have the animation stop when it ends. By default it will loop to the start of the same animation.
|
|
* @param {Number} [speed] Specifies a frame advance speed for this animation. For example, a value of 0.5 would
|
|
* cause the animation to advance every second tick. Note that earlier versions used `frequency` instead, which had
|
|
* the opposite effect.
|
|
**/
|
|
p.addAnimation = function (name, frames, next, speed) {
|
|
if (this._data) {
|
|
throw SpriteSheetBuilder.ERR_RUNNING;
|
|
}
|
|
this._animations[name] = { frames: frames, next: next, speed: speed };
|
|
};
|
|
|
|
/**
|
|
* This will take a {{#crossLink "MovieClip"}}{{/crossLink}} instance, and add its frames and labels to this
|
|
* builder. Labels will be added as an animation running from the label index to the next label. For example, if
|
|
* there is a label named "foo" at frame 0 and a label named "bar" at frame 10, in a MovieClip with 15 frames, it
|
|
* will add an animation named "foo" that runs from frame index 0 to 9, and an animation named "bar" that runs from
|
|
* frame index 10 to 14.
|
|
*
|
|
* Note that this will iterate through the full MovieClip with {{#crossLink "MovieClip/actionsEnabled:property"}}{{/crossLink}}
|
|
* set to `false`, ending on the last frame.
|
|
* @method addMovieClip
|
|
* @param {MovieClip} source The source MovieClip instance to add to the SpriteSheet.
|
|
* @param {Rectangle} [sourceRect] A {{#crossLink "Rectangle"}}{{/crossLink}} defining the portion of the source to
|
|
* draw to the frame. If not specified, it will look for a {{#crossLink "DisplayObject/getBounds"}}{{/crossLink}}
|
|
* method, `frameBounds` Array, `bounds` property, or `nominalBounds` property on the source to use. If one is not
|
|
* found, the MovieClip will be skipped.
|
|
* @param {Number} [scale=1] The scale to draw the movie clip at.
|
|
* @param {Function} [setupFunction] A function to call immediately before drawing each frame. It will be called
|
|
* with three parameters: the source, setupData, and the frame index.
|
|
* @param {Object} [setupData] Arbitrary setup data to pass to setupFunction as the second parameter.
|
|
* @param {Function} [labelFunction] This method will be called for each MovieClip label that is added with four
|
|
* parameters: the label name, the source MovieClip instance, the starting frame index (in the movieclip timeline)
|
|
* and the end index. It must return a new name for the label/animation, or `false` to exclude the label.
|
|
**/
|
|
p.addMovieClip = function (
|
|
source,
|
|
sourceRect,
|
|
scale,
|
|
setupFunction,
|
|
setupData,
|
|
labelFunction
|
|
) {
|
|
if (this._data) {
|
|
throw SpriteSheetBuilder.ERR_RUNNING;
|
|
}
|
|
var rects = source.frameBounds;
|
|
var rect = sourceRect || source.bounds || source.nominalBounds;
|
|
if (!rect && source.getBounds) {
|
|
rect = source.getBounds();
|
|
}
|
|
if (!rect && !rects) {
|
|
return;
|
|
}
|
|
|
|
var i,
|
|
l,
|
|
baseFrameIndex = this._frames.length;
|
|
var duration = source.timeline.duration;
|
|
for (i = 0; i < duration; i++) {
|
|
var r = rects && rects[i] ? rects[i] : rect;
|
|
this.addFrame(source, r, scale, this._setupMovieClipFrame, {
|
|
i: i,
|
|
f: setupFunction,
|
|
d: setupData,
|
|
});
|
|
}
|
|
var labels = source.timeline._labels;
|
|
var lbls = [];
|
|
for (var n in labels) {
|
|
lbls.push({ index: labels[n], label: n });
|
|
}
|
|
if (lbls.length) {
|
|
lbls.sort(function (a, b) {
|
|
return a.index - b.index;
|
|
});
|
|
for (i = 0, l = lbls.length; i < l; i++) {
|
|
var label = lbls[i].label;
|
|
var start = baseFrameIndex + lbls[i].index;
|
|
var end = baseFrameIndex + (i == l - 1 ? duration : lbls[i + 1].index);
|
|
var frames = [];
|
|
for (var j = start; j < end; j++) {
|
|
frames.push(j);
|
|
}
|
|
if (labelFunction) {
|
|
label = labelFunction(label, source, start, end);
|
|
if (!label) {
|
|
continue;
|
|
}
|
|
}
|
|
this.addAnimation(label, frames, true); // for now, this loops all animations.
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Builds a {{#crossLink "SpriteSheet"}}{{/crossLink}} instance based on the current frames.
|
|
* @method build
|
|
* @return {SpriteSheet} The created SpriteSheet instance, or null if a build is already running or an error
|
|
* occurred.
|
|
**/
|
|
p.build = function () {
|
|
if (this._data) {
|
|
throw SpriteSheetBuilder.ERR_RUNNING;
|
|
}
|
|
this._startBuild();
|
|
while (this._drawNext()) {}
|
|
this._endBuild();
|
|
return this.spriteSheet;
|
|
};
|
|
|
|
/**
|
|
* Asynchronously builds a {{#crossLink "SpriteSheet"}}{{/crossLink}} instance based on the current frames. It will
|
|
* run 20 times per second, using an amount of time defined by `timeSlice`. When it is complete it will call the
|
|
* specified callback.
|
|
* @method buildAsync
|
|
* @param {Number} [timeSlice] Sets the timeSlice property on this instance.
|
|
**/
|
|
p.buildAsync = function (timeSlice) {
|
|
if (this._data) {
|
|
throw SpriteSheetBuilder.ERR_RUNNING;
|
|
}
|
|
this.timeSlice = timeSlice;
|
|
this._startBuild();
|
|
var _this = this;
|
|
this._timerID = setTimeout(function () {
|
|
_this._run();
|
|
}, 50 - Math.max(0.01, Math.min(0.99, this.timeSlice || 0.3)) * 50);
|
|
};
|
|
|
|
/**
|
|
* Stops the current asynchronous build.
|
|
* @method stopAsync
|
|
**/
|
|
p.stopAsync = function () {
|
|
clearTimeout(this._timerID);
|
|
this._data = null;
|
|
};
|
|
|
|
/**
|
|
* SpriteSheetBuilder instances cannot be cloned.
|
|
* @method clone
|
|
**/
|
|
p.clone = function () {
|
|
throw "SpriteSheetBuilder cannot be cloned.";
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[SpriteSheetBuilder]";
|
|
};
|
|
|
|
// private methods:
|
|
/**
|
|
* @method _startBuild
|
|
* @protected
|
|
**/
|
|
p._startBuild = function () {
|
|
var pad = this.padding || 0;
|
|
this.progress = 0;
|
|
this.spriteSheet = null;
|
|
this._index = 0;
|
|
this._scale = this.scale;
|
|
var dataFrames = [];
|
|
this._data = {
|
|
images: [],
|
|
frames: dataFrames,
|
|
framerate: this.framerate,
|
|
animations: this._animations, // TODO: should we "clone" _animations in case someone adds more animations after a build?
|
|
};
|
|
|
|
var frames = this._frames.slice();
|
|
frames.sort(function (a, b) {
|
|
return a.height <= b.height ? -1 : 1;
|
|
});
|
|
|
|
if (frames[frames.length - 1].height + pad * 2 > this.maxHeight) {
|
|
throw SpriteSheetBuilder.ERR_DIMENSIONS;
|
|
}
|
|
var y = 0,
|
|
x = 0;
|
|
var img = 0;
|
|
while (frames.length) {
|
|
var o = this._fillRow(frames, y, img, dataFrames, pad);
|
|
if (o.w > x) {
|
|
x = o.w;
|
|
}
|
|
y += o.h;
|
|
if (!o.h || !frames.length) {
|
|
var canvas = createjs.createCanvas
|
|
? createjs.createCanvas()
|
|
: document.createElement("canvas");
|
|
canvas.width = this._getSize(x, this.maxWidth);
|
|
canvas.height = this._getSize(y, this.maxHeight);
|
|
this._data.images[img] = canvas;
|
|
if (!o.h) {
|
|
x = y = 0;
|
|
img++;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _setupMovieClipFrame
|
|
* @protected
|
|
* @return {Number} The width & height of the row.
|
|
**/
|
|
p._setupMovieClipFrame = function (source, data) {
|
|
var ae = source.actionsEnabled;
|
|
source.actionsEnabled = false;
|
|
source.gotoAndStop(data.i);
|
|
source.actionsEnabled = ae;
|
|
data.f && data.f(source, data.d, data.i);
|
|
};
|
|
|
|
/**
|
|
* @method _getSize
|
|
* @protected
|
|
* @return {Number} The width & height of the row.
|
|
**/
|
|
p._getSize = function (size, max) {
|
|
var pow = 4;
|
|
while (Math.pow(2, ++pow) < size) {}
|
|
return Math.min(max, Math.pow(2, pow));
|
|
};
|
|
|
|
/**
|
|
* @method _fillRow
|
|
* @param {Array} frames
|
|
* @param {Number} y
|
|
* @param {HTMLImageElement} img
|
|
* @param {Object} dataFrames
|
|
* @param {Number} pad
|
|
* @protected
|
|
* @return {Number} The width & height of the row.
|
|
**/
|
|
p._fillRow = function (frames, y, img, dataFrames, pad) {
|
|
var w = this.maxWidth;
|
|
var maxH = this.maxHeight;
|
|
y += pad;
|
|
var h = maxH - y;
|
|
var x = pad;
|
|
var height = 0;
|
|
for (var i = frames.length - 1; i >= 0; i--) {
|
|
var frame = frames[i];
|
|
var sc = this._scale * frame.scale;
|
|
var rect = frame.sourceRect;
|
|
var source = frame.source;
|
|
var rx = Math.floor(sc * rect.x - pad);
|
|
var ry = Math.floor(sc * rect.y - pad);
|
|
var rh = Math.ceil(sc * rect.height + pad * 2);
|
|
var rw = Math.ceil(sc * rect.width + pad * 2);
|
|
if (rw > w) {
|
|
throw SpriteSheetBuilder.ERR_DIMENSIONS;
|
|
}
|
|
if (rh > h || x + rw > w) {
|
|
continue;
|
|
}
|
|
frame.img = img;
|
|
frame.rect = new createjs.Rectangle(x, y, rw, rh);
|
|
height = height || rh;
|
|
frames.splice(i, 1);
|
|
dataFrames[frame.index] = [
|
|
x,
|
|
y,
|
|
rw,
|
|
rh,
|
|
img,
|
|
Math.round(-rx + sc * source.regX - pad),
|
|
Math.round(-ry + sc * source.regY - pad),
|
|
];
|
|
x += rw;
|
|
}
|
|
return { w: x, h: height };
|
|
};
|
|
|
|
/**
|
|
* @method _endBuild
|
|
* @protected
|
|
**/
|
|
p._endBuild = function () {
|
|
this.spriteSheet = new createjs.SpriteSheet(this._data);
|
|
this._data = null;
|
|
this.progress = 1;
|
|
this.dispatchEvent("complete");
|
|
};
|
|
|
|
/**
|
|
* @method _run
|
|
* @protected
|
|
**/
|
|
p._run = function () {
|
|
var ts = Math.max(0.01, Math.min(0.99, this.timeSlice || 0.3)) * 50;
|
|
var t = new Date().getTime() + ts;
|
|
var complete = false;
|
|
while (t > new Date().getTime()) {
|
|
if (!this._drawNext()) {
|
|
complete = true;
|
|
break;
|
|
}
|
|
}
|
|
if (complete) {
|
|
this._endBuild();
|
|
} else {
|
|
var _this = this;
|
|
this._timerID = setTimeout(function () {
|
|
_this._run();
|
|
}, 50 - ts);
|
|
}
|
|
var p = (this.progress = this._index / this._frames.length);
|
|
if (this.hasEventListener("progress")) {
|
|
var evt = new createjs.Event("progress");
|
|
evt.progress = p;
|
|
this.dispatchEvent(evt);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _drawNext
|
|
* @protected
|
|
* @return Boolean Returns false if this is the last draw.
|
|
**/
|
|
p._drawNext = function () {
|
|
var frame = this._frames[this._index];
|
|
var sc = frame.scale * this._scale;
|
|
var rect = frame.rect;
|
|
var sourceRect = frame.sourceRect;
|
|
var canvas = this._data.images[frame.img];
|
|
var ctx = canvas.getContext("2d");
|
|
frame.funct && frame.funct(frame.source, frame.data);
|
|
ctx.save();
|
|
ctx.beginPath();
|
|
ctx.rect(rect.x, rect.y, rect.width, rect.height);
|
|
ctx.clip();
|
|
ctx.translate(
|
|
Math.ceil(rect.x - sourceRect.x * sc),
|
|
Math.ceil(rect.y - sourceRect.y * sc)
|
|
);
|
|
ctx.scale(sc, sc);
|
|
frame.source.draw(ctx); // display object will draw itself.
|
|
ctx.restore();
|
|
return ++this._index < this._frames.length;
|
|
};
|
|
|
|
createjs.SpriteSheetBuilder = createjs.promote(
|
|
SpriteSheetBuilder,
|
|
"EventDispatcher"
|
|
);
|
|
})();
|
|
|
|
//##############################################################################
|
|
// DOMElement.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* <b>This class is still experimental, and more advanced use is likely to be buggy. Please report bugs.</b>
|
|
*
|
|
* A DOMElement allows you to associate a HTMLElement with the display list. It will be transformed
|
|
* within the DOM as though it is child of the {{#crossLink "Container"}}{{/crossLink}} it is added to. However, it is
|
|
* not rendered to canvas, and as such will retain whatever z-index it has relative to the canvas (ie. it will be
|
|
* drawn in front of or behind the canvas).
|
|
*
|
|
* The position of a DOMElement is relative to their parent node in the DOM. It is recommended that
|
|
* the DOM Object be added to a div that also contains the canvas so that they share the same position
|
|
* on the page.
|
|
*
|
|
* DOMElement is useful for positioning HTML elements over top of canvas content, and for elements
|
|
* that you want to display outside the bounds of the canvas. For example, a tooltip with rich HTML
|
|
* content.
|
|
*
|
|
* <h4>Mouse Interaction</h4>
|
|
*
|
|
* DOMElement instances are not full EaselJS display objects, and do not participate in EaselJS mouse
|
|
* events or support methods like hitTest. To get mouse events from a DOMElement, you must instead add handlers to
|
|
* the htmlElement (note, this does not support EventDispatcher)
|
|
*
|
|
* var domElement = new createjs.DOMElement(htmlElement);
|
|
* domElement.htmlElement.onclick = function() {
|
|
* console.log("clicked");
|
|
* }
|
|
*
|
|
* @class DOMElement
|
|
* @extends DisplayObject
|
|
* @constructor
|
|
* @param {HTMLElement} htmlElement A reference or id for the DOM element to manage.
|
|
*/
|
|
function DOMElement(htmlElement) {
|
|
this.DisplayObject_constructor();
|
|
|
|
if (typeof htmlElement == "string") {
|
|
htmlElement = document.getElementById(htmlElement);
|
|
}
|
|
this.mouseEnabled = false;
|
|
|
|
var style = htmlElement.style;
|
|
style.position = "absolute";
|
|
style.transformOrigin = style.WebkitTransformOrigin = style.msTransformOrigin = style.MozTransformOrigin = style.OTransformOrigin =
|
|
"0% 0%";
|
|
|
|
// public properties:
|
|
/**
|
|
* The DOM object to manage.
|
|
* @property htmlElement
|
|
* @type HTMLElement
|
|
*/
|
|
this.htmlElement = htmlElement;
|
|
|
|
// private properties:
|
|
/**
|
|
* @property _oldMtx
|
|
* @type Matrix2D
|
|
* @protected
|
|
*/
|
|
this._oldProps = null;
|
|
}
|
|
var p = createjs.extend(DOMElement, createjs.DisplayObject);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// public methods:
|
|
/**
|
|
* Returns true or false indicating whether the display object would be visible if drawn to a canvas.
|
|
* This does not account for whether it would be visible within the boundaries of the stage.
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method isVisible
|
|
* @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas
|
|
*/
|
|
p.isVisible = function () {
|
|
return this.htmlElement != null;
|
|
};
|
|
|
|
/**
|
|
* Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform.
|
|
* Returns true if the draw was handled (useful for overriding functionality).
|
|
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
|
|
* @method draw
|
|
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into.
|
|
* @param {Boolean} ignoreCache Indicates whether the draw operation should ignore any current cache.
|
|
* For example, used for drawing the cache (to prevent it from simply drawing an existing cache back
|
|
* into itself).
|
|
* @return {Boolean}
|
|
*/
|
|
p.draw = function (ctx, ignoreCache) {
|
|
// this relies on the _tick method because draw isn't called if the parent is not visible.
|
|
// the actual update happens in _handleDrawEnd
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Not applicable to DOMElement.
|
|
* @method cache
|
|
*/
|
|
p.cache = function () {};
|
|
|
|
/**
|
|
* Not applicable to DOMElement.
|
|
* @method uncache
|
|
*/
|
|
p.uncache = function () {};
|
|
|
|
/**
|
|
* Not applicable to DOMElement.
|
|
* @method updateCache
|
|
*/
|
|
p.updateCache = function () {};
|
|
|
|
/**
|
|
* Not applicable to DOMElement.
|
|
* @method hitTest
|
|
*/
|
|
p.hitTest = function () {};
|
|
|
|
/**
|
|
* Not applicable to DOMElement.
|
|
* @method localToGlobal
|
|
*/
|
|
p.localToGlobal = function () {};
|
|
|
|
/**
|
|
* Not applicable to DOMElement.
|
|
* @method globalToLocal
|
|
*/
|
|
p.globalToLocal = function () {};
|
|
|
|
/**
|
|
* Not applicable to DOMElement.
|
|
* @method localToLocal
|
|
*/
|
|
p.localToLocal = function () {};
|
|
|
|
/**
|
|
* DOMElement cannot be cloned. Throws an error.
|
|
* @method clone
|
|
*/
|
|
p.clone = function () {
|
|
throw "DOMElement cannot be cloned.";
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
*/
|
|
p.toString = function () {
|
|
return "[DOMElement (name=" + this.name + ")]";
|
|
};
|
|
|
|
/**
|
|
* Interaction events should be added to `htmlElement`, and not the DOMElement instance, since DOMElement instances
|
|
* are not full EaselJS display objects and do not participate in EaselJS mouse events.
|
|
* @event click
|
|
*/
|
|
|
|
/**
|
|
* Interaction events should be added to `htmlElement`, and not the DOMElement instance, since DOMElement instances
|
|
* are not full EaselJS display objects and do not participate in EaselJS mouse events.
|
|
* @event dblClick
|
|
*/
|
|
|
|
/**
|
|
* Interaction events should be added to `htmlElement`, and not the DOMElement instance, since DOMElement instances
|
|
* are not full EaselJS display objects and do not participate in EaselJS mouse events.
|
|
* @event mousedown
|
|
*/
|
|
|
|
/**
|
|
* The HTMLElement can listen for the mouseover event, not the DOMElement instance.
|
|
* Since DOMElement instances are not full EaselJS display objects and do not participate in EaselJS mouse events.
|
|
* @event mouseover
|
|
*/
|
|
|
|
/**
|
|
* Not applicable to DOMElement.
|
|
* @event tick
|
|
*/
|
|
|
|
// private methods:
|
|
/**
|
|
* @method _tick
|
|
* @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs.
|
|
* function.
|
|
* @protected
|
|
*/
|
|
p._tick = function (evtObj) {
|
|
var stage = this.getStage();
|
|
stage && stage.on("drawend", this._handleDrawEnd, this, true);
|
|
this.DisplayObject__tick(evtObj);
|
|
};
|
|
|
|
/**
|
|
* @method _handleDrawEnd
|
|
* @param {Event} evt
|
|
* @protected
|
|
*/
|
|
p._handleDrawEnd = function (evt) {
|
|
var o = this.htmlElement;
|
|
if (!o) {
|
|
return;
|
|
}
|
|
var style = o.style;
|
|
|
|
var props = this.getConcatenatedDisplayProps(this._props),
|
|
mtx = props.matrix;
|
|
|
|
var visibility = props.visible ? "visible" : "hidden";
|
|
if (visibility != style.visibility) {
|
|
style.visibility = visibility;
|
|
}
|
|
if (!props.visible) {
|
|
return;
|
|
}
|
|
|
|
var oldProps = this._oldProps,
|
|
oldMtx = oldProps && oldProps.matrix;
|
|
var n = 10000; // precision
|
|
|
|
if (!oldMtx || !oldMtx.equals(mtx)) {
|
|
var str =
|
|
"matrix(" +
|
|
((mtx.a * n) | 0) / n +
|
|
"," +
|
|
((mtx.b * n) | 0) / n +
|
|
"," +
|
|
((mtx.c * n) | 0) / n +
|
|
"," +
|
|
((mtx.d * n) | 0) / n +
|
|
"," +
|
|
((mtx.tx + 0.5) | 0);
|
|
style.transform = style.WebkitTransform = style.OTransform = style.msTransform =
|
|
str + "," + ((mtx.ty + 0.5) | 0) + ")";
|
|
style.MozTransform = str + "px," + ((mtx.ty + 0.5) | 0) + "px)";
|
|
if (!oldProps) {
|
|
oldProps = this._oldProps = new createjs.DisplayProps(true, NaN);
|
|
}
|
|
oldProps.matrix.copy(mtx);
|
|
}
|
|
|
|
if (oldProps.alpha != props.alpha) {
|
|
style.opacity = "" + ((props.alpha * n) | 0) / n;
|
|
oldProps.alpha = props.alpha;
|
|
}
|
|
};
|
|
|
|
createjs.DOMElement = createjs.promote(DOMElement, "DisplayObject");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Filter.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Base class that all filters should inherit from. Filters need to be applied to objects that have been cached using
|
|
* the {{#crossLink "DisplayObject/cache"}}{{/crossLink}} method. If an object changes, please cache it again, or use
|
|
* {{#crossLink "DisplayObject/updateCache"}}{{/crossLink}}. Note that the filters must be applied before caching.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* myInstance.filters = [
|
|
* new createjs.ColorFilter(0, 0, 0, 1, 255, 0, 0),
|
|
* new createjs.BlurFilter(5, 5, 10)
|
|
* ];
|
|
* myInstance.cache(0,0, 100, 100);
|
|
*
|
|
* Note that each filter can implement a {{#crossLink "Filter/getBounds"}}{{/crossLink}} method, which returns the
|
|
* margins that need to be applied in order to fully display the filter. For example, the {{#crossLink "BlurFilter"}}{{/crossLink}}
|
|
* will cause an object to feather outwards, resulting in a margin around the shape.
|
|
*
|
|
* <h4>EaselJS Filters</h4>
|
|
* EaselJS comes with a number of pre-built filters:
|
|
* <ul><li>{{#crossLink "AlphaMapFilter"}}{{/crossLink}} : Map a greyscale image to the alpha channel of a display object</li>
|
|
* <li>{{#crossLink "AlphaMaskFilter"}}{{/crossLink}}: Map an image's alpha channel to the alpha channel of a display object</li>
|
|
* <li>{{#crossLink "BlurFilter"}}{{/crossLink}}: Apply vertical and horizontal blur to a display object</li>
|
|
* <li>{{#crossLink "ColorFilter"}}{{/crossLink}}: Color transform a display object</li>
|
|
* <li>{{#crossLink "ColorMatrixFilter"}}{{/crossLink}}: Transform an image using a {{#crossLink "ColorMatrix"}}{{/crossLink}}</li>
|
|
* </ul>
|
|
*
|
|
* @class Filter
|
|
* @constructor
|
|
**/
|
|
function Filter() {}
|
|
var p = Filter.prototype;
|
|
|
|
/**
|
|
* <strong>REMOVED</strong>. Removed in favor of using `MySuperClass_constructor`.
|
|
* See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}}
|
|
* for details.
|
|
*
|
|
* There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance.
|
|
*
|
|
* @method initialize
|
|
* @protected
|
|
* @deprecated
|
|
*/
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is.
|
|
|
|
// public methods:
|
|
/**
|
|
* Provides padding values for this filter. That is, how much the filter will extend the visual bounds of an object it is applied to.
|
|
* @method getBounds
|
|
* @param {Rectangle} [rect] If specified, the provided Rectangle instance will be expanded by the padding amounts and returned.
|
|
* @return {Rectangle} If a `rect` param was provided, it is returned. If not, either a new rectangle with the padding values, or null if no padding is required for this filter.
|
|
**/
|
|
p.getBounds = function (rect) {
|
|
return rect;
|
|
};
|
|
|
|
/**
|
|
* Applies the filter to the specified context.
|
|
* @method applyFilter
|
|
* @param {CanvasRenderingContext2D} ctx The 2D context to use as the source.
|
|
* @param {Number} x The x position to use for the source rect.
|
|
* @param {Number} y The y position to use for the source rect.
|
|
* @param {Number} width The width to use for the source rect.
|
|
* @param {Number} height The height to use for the source rect.
|
|
* @param {CanvasRenderingContext2D} [targetCtx] The 2D context to draw the result to. Defaults to the context passed to ctx.
|
|
* @param {Number} [targetX] The x position to draw the result to. Defaults to the value passed to x.
|
|
* @param {Number} [targetY] The y position to draw the result to. Defaults to the value passed to y.
|
|
* @return {Boolean} If the filter was applied successfully.
|
|
**/
|
|
p.applyFilter = function (
|
|
ctx,
|
|
x,
|
|
y,
|
|
width,
|
|
height,
|
|
targetCtx,
|
|
targetX,
|
|
targetY
|
|
) {
|
|
// this is the default behaviour because most filters access pixel data. It is overridden when not needed.
|
|
targetCtx = targetCtx || ctx;
|
|
if (targetX == null) {
|
|
targetX = x;
|
|
}
|
|
if (targetY == null) {
|
|
targetY = y;
|
|
}
|
|
try {
|
|
var imageData = ctx.getImageData(x, y, width, height);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
if (this._applyFilter(imageData)) {
|
|
targetCtx.putImageData(imageData, targetX, targetY);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[Filter]";
|
|
};
|
|
|
|
/**
|
|
* Returns a clone of this Filter instance.
|
|
* @method clone
|
|
* @return {Filter} A clone of the current Filter instance.
|
|
**/
|
|
p.clone = function () {
|
|
return new Filter();
|
|
};
|
|
|
|
// private methods:
|
|
/**
|
|
* @method _applyFilter
|
|
* @param {ImageData} imageData Target ImageData instance.
|
|
* @return {Boolean}
|
|
**/
|
|
p._applyFilter = function (imageData) {
|
|
return true;
|
|
};
|
|
|
|
createjs.Filter = Filter;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// BlurFilter.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Applies a box blur to DisplayObjects. Note that this filter is fairly CPU intensive, particularly if the quality is
|
|
* set higher than 1.
|
|
*
|
|
* <h4>Example</h4>
|
|
* This example creates a red circle, and then applies a 5 pixel blur to it. It uses the {{#crossLink "Filter/getBounds"}}{{/crossLink}}
|
|
* method to account for the spread that the blur causes.
|
|
*
|
|
* var shape = new createjs.Shape().set({x:100,y:100});
|
|
* shape.graphics.beginFill("#ff0000").drawCircle(0,0,50);
|
|
*
|
|
* var blurFilter = new createjs.BlurFilter(5, 5, 1);
|
|
* shape.filters = [blurFilter];
|
|
* var bounds = blurFilter.getBounds();
|
|
*
|
|
* shape.cache(-50+bounds.x, -50+bounds.y, 100+bounds.width, 100+bounds.height);
|
|
*
|
|
* See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters.
|
|
* @class BlurFilter
|
|
* @extends Filter
|
|
* @constructor
|
|
* @param {Number} [blurX=0] The horizontal blur radius in pixels.
|
|
* @param {Number} [blurY=0] The vertical blur radius in pixels.
|
|
* @param {Number} [quality=1] The number of blur iterations.
|
|
**/
|
|
function BlurFilter(blurX, blurY, quality) {
|
|
if (isNaN(blurX) || blurX < 0) blurX = 0;
|
|
if (isNaN(blurY) || blurY < 0) blurY = 0;
|
|
if (isNaN(quality) || quality < 1) quality = 1;
|
|
|
|
// public properties:
|
|
/**
|
|
* Horizontal blur radius in pixels
|
|
* @property blurX
|
|
* @default 0
|
|
* @type Number
|
|
**/
|
|
this.blurX = blurX | 0;
|
|
|
|
/**
|
|
* Vertical blur radius in pixels
|
|
* @property blurY
|
|
* @default 0
|
|
* @type Number
|
|
**/
|
|
this.blurY = blurY | 0;
|
|
|
|
/**
|
|
* Number of blur iterations. For example, a value of 1 will produce a rough blur. A value of 2 will produce a
|
|
* smoother blur, but take twice as long to run.
|
|
* @property quality
|
|
* @default 1
|
|
* @type Number
|
|
**/
|
|
this.quality = quality | 0;
|
|
}
|
|
var p = createjs.extend(BlurFilter, createjs.Filter);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// constants:
|
|
/**
|
|
* Array of multiply values for blur calculations.
|
|
* @property MUL_TABLE
|
|
* @type Array
|
|
* @protected
|
|
* @static
|
|
**/
|
|
BlurFilter.MUL_TABLE = [
|
|
1,
|
|
171,
|
|
205,
|
|
293,
|
|
57,
|
|
373,
|
|
79,
|
|
137,
|
|
241,
|
|
27,
|
|
391,
|
|
357,
|
|
41,
|
|
19,
|
|
283,
|
|
265,
|
|
497,
|
|
469,
|
|
443,
|
|
421,
|
|
25,
|
|
191,
|
|
365,
|
|
349,
|
|
335,
|
|
161,
|
|
155,
|
|
149,
|
|
9,
|
|
278,
|
|
269,
|
|
261,
|
|
505,
|
|
245,
|
|
475,
|
|
231,
|
|
449,
|
|
437,
|
|
213,
|
|
415,
|
|
405,
|
|
395,
|
|
193,
|
|
377,
|
|
369,
|
|
361,
|
|
353,
|
|
345,
|
|
169,
|
|
331,
|
|
325,
|
|
319,
|
|
313,
|
|
307,
|
|
301,
|
|
37,
|
|
145,
|
|
285,
|
|
281,
|
|
69,
|
|
271,
|
|
267,
|
|
263,
|
|
259,
|
|
509,
|
|
501,
|
|
493,
|
|
243,
|
|
479,
|
|
118,
|
|
465,
|
|
459,
|
|
113,
|
|
446,
|
|
55,
|
|
435,
|
|
429,
|
|
423,
|
|
209,
|
|
413,
|
|
51,
|
|
403,
|
|
199,
|
|
393,
|
|
97,
|
|
3,
|
|
379,
|
|
375,
|
|
371,
|
|
367,
|
|
363,
|
|
359,
|
|
355,
|
|
351,
|
|
347,
|
|
43,
|
|
85,
|
|
337,
|
|
333,
|
|
165,
|
|
327,
|
|
323,
|
|
5,
|
|
317,
|
|
157,
|
|
311,
|
|
77,
|
|
305,
|
|
303,
|
|
75,
|
|
297,
|
|
294,
|
|
73,
|
|
289,
|
|
287,
|
|
71,
|
|
141,
|
|
279,
|
|
277,
|
|
275,
|
|
68,
|
|
135,
|
|
67,
|
|
133,
|
|
33,
|
|
262,
|
|
260,
|
|
129,
|
|
511,
|
|
507,
|
|
503,
|
|
499,
|
|
495,
|
|
491,
|
|
61,
|
|
121,
|
|
481,
|
|
477,
|
|
237,
|
|
235,
|
|
467,
|
|
232,
|
|
115,
|
|
457,
|
|
227,
|
|
451,
|
|
7,
|
|
445,
|
|
221,
|
|
439,
|
|
218,
|
|
433,
|
|
215,
|
|
427,
|
|
425,
|
|
211,
|
|
419,
|
|
417,
|
|
207,
|
|
411,
|
|
409,
|
|
203,
|
|
202,
|
|
401,
|
|
399,
|
|
396,
|
|
197,
|
|
49,
|
|
389,
|
|
387,
|
|
385,
|
|
383,
|
|
95,
|
|
189,
|
|
47,
|
|
187,
|
|
93,
|
|
185,
|
|
23,
|
|
183,
|
|
91,
|
|
181,
|
|
45,
|
|
179,
|
|
89,
|
|
177,
|
|
11,
|
|
175,
|
|
87,
|
|
173,
|
|
345,
|
|
343,
|
|
341,
|
|
339,
|
|
337,
|
|
21,
|
|
167,
|
|
83,
|
|
331,
|
|
329,
|
|
327,
|
|
163,
|
|
81,
|
|
323,
|
|
321,
|
|
319,
|
|
159,
|
|
79,
|
|
315,
|
|
313,
|
|
39,
|
|
155,
|
|
309,
|
|
307,
|
|
153,
|
|
305,
|
|
303,
|
|
151,
|
|
75,
|
|
299,
|
|
149,
|
|
37,
|
|
295,
|
|
147,
|
|
73,
|
|
291,
|
|
145,
|
|
289,
|
|
287,
|
|
143,
|
|
285,
|
|
71,
|
|
141,
|
|
281,
|
|
35,
|
|
279,
|
|
139,
|
|
69,
|
|
275,
|
|
137,
|
|
273,
|
|
17,
|
|
271,
|
|
135,
|
|
269,
|
|
267,
|
|
133,
|
|
265,
|
|
33,
|
|
263,
|
|
131,
|
|
261,
|
|
130,
|
|
259,
|
|
129,
|
|
257,
|
|
1,
|
|
];
|
|
|
|
/**
|
|
* Array of shift values for blur calculations.
|
|
* @property SHG_TABLE
|
|
* @type Array
|
|
* @protected
|
|
* @static
|
|
**/
|
|
BlurFilter.SHG_TABLE = [
|
|
0,
|
|
9,
|
|
10,
|
|
11,
|
|
9,
|
|
12,
|
|
10,
|
|
11,
|
|
12,
|
|
9,
|
|
13,
|
|
13,
|
|
10,
|
|
9,
|
|
13,
|
|
13,
|
|
14,
|
|
14,
|
|
14,
|
|
14,
|
|
10,
|
|
13,
|
|
14,
|
|
14,
|
|
14,
|
|
13,
|
|
13,
|
|
13,
|
|
9,
|
|
14,
|
|
14,
|
|
14,
|
|
15,
|
|
14,
|
|
15,
|
|
14,
|
|
15,
|
|
15,
|
|
14,
|
|
15,
|
|
15,
|
|
15,
|
|
14,
|
|
15,
|
|
15,
|
|
15,
|
|
15,
|
|
15,
|
|
14,
|
|
15,
|
|
15,
|
|
15,
|
|
15,
|
|
15,
|
|
15,
|
|
12,
|
|
14,
|
|
15,
|
|
15,
|
|
13,
|
|
15,
|
|
15,
|
|
15,
|
|
15,
|
|
16,
|
|
16,
|
|
16,
|
|
15,
|
|
16,
|
|
14,
|
|
16,
|
|
16,
|
|
14,
|
|
16,
|
|
13,
|
|
16,
|
|
16,
|
|
16,
|
|
15,
|
|
16,
|
|
13,
|
|
16,
|
|
15,
|
|
16,
|
|
14,
|
|
9,
|
|
16,
|
|
16,
|
|
16,
|
|
16,
|
|
16,
|
|
16,
|
|
16,
|
|
16,
|
|
16,
|
|
13,
|
|
14,
|
|
16,
|
|
16,
|
|
15,
|
|
16,
|
|
16,
|
|
10,
|
|
16,
|
|
15,
|
|
16,
|
|
14,
|
|
16,
|
|
16,
|
|
14,
|
|
16,
|
|
16,
|
|
14,
|
|
16,
|
|
16,
|
|
14,
|
|
15,
|
|
16,
|
|
16,
|
|
16,
|
|
14,
|
|
15,
|
|
14,
|
|
15,
|
|
13,
|
|
16,
|
|
16,
|
|
15,
|
|
17,
|
|
17,
|
|
17,
|
|
17,
|
|
17,
|
|
17,
|
|
14,
|
|
15,
|
|
17,
|
|
17,
|
|
16,
|
|
16,
|
|
17,
|
|
16,
|
|
15,
|
|
17,
|
|
16,
|
|
17,
|
|
11,
|
|
17,
|
|
16,
|
|
17,
|
|
16,
|
|
17,
|
|
16,
|
|
17,
|
|
17,
|
|
16,
|
|
17,
|
|
17,
|
|
16,
|
|
17,
|
|
17,
|
|
16,
|
|
16,
|
|
17,
|
|
17,
|
|
17,
|
|
16,
|
|
14,
|
|
17,
|
|
17,
|
|
17,
|
|
17,
|
|
15,
|
|
16,
|
|
14,
|
|
16,
|
|
15,
|
|
16,
|
|
13,
|
|
16,
|
|
15,
|
|
16,
|
|
14,
|
|
16,
|
|
15,
|
|
16,
|
|
12,
|
|
16,
|
|
15,
|
|
16,
|
|
17,
|
|
17,
|
|
17,
|
|
17,
|
|
17,
|
|
13,
|
|
16,
|
|
15,
|
|
17,
|
|
17,
|
|
17,
|
|
16,
|
|
15,
|
|
17,
|
|
17,
|
|
17,
|
|
16,
|
|
15,
|
|
17,
|
|
17,
|
|
14,
|
|
16,
|
|
17,
|
|
17,
|
|
16,
|
|
17,
|
|
17,
|
|
16,
|
|
15,
|
|
17,
|
|
16,
|
|
14,
|
|
17,
|
|
16,
|
|
15,
|
|
17,
|
|
16,
|
|
17,
|
|
17,
|
|
16,
|
|
17,
|
|
15,
|
|
16,
|
|
17,
|
|
14,
|
|
17,
|
|
16,
|
|
15,
|
|
17,
|
|
16,
|
|
17,
|
|
13,
|
|
17,
|
|
16,
|
|
17,
|
|
17,
|
|
16,
|
|
17,
|
|
14,
|
|
17,
|
|
16,
|
|
17,
|
|
16,
|
|
17,
|
|
16,
|
|
17,
|
|
9,
|
|
];
|
|
|
|
// public methods:
|
|
/** docced in super class **/
|
|
p.getBounds = function (rect) {
|
|
var x = this.blurX | 0,
|
|
y = this.blurY | 0;
|
|
if (x <= 0 && y <= 0) {
|
|
return rect;
|
|
}
|
|
var q = Math.pow(this.quality, 0.2);
|
|
return (rect || new createjs.Rectangle()).pad(
|
|
x * q + 1,
|
|
y * q + 1,
|
|
x * q + 1,
|
|
y * q + 1
|
|
);
|
|
};
|
|
|
|
/** docced in super class **/
|
|
p.clone = function () {
|
|
return new BlurFilter(this.blurX, this.blurY, this.quality);
|
|
};
|
|
|
|
/** docced in super class **/
|
|
p.toString = function () {
|
|
return "[BlurFilter]";
|
|
};
|
|
|
|
// private methods:
|
|
|
|
/** docced in super class **/
|
|
p._applyFilter = function (imageData) {
|
|
var radiusX = this.blurX >> 1;
|
|
if (isNaN(radiusX) || radiusX < 0) return false;
|
|
var radiusY = this.blurY >> 1;
|
|
if (isNaN(radiusY) || radiusY < 0) return false;
|
|
if (radiusX == 0 && radiusY == 0) return false;
|
|
|
|
var iterations = this.quality;
|
|
if (isNaN(iterations) || iterations < 1) iterations = 1;
|
|
iterations |= 0;
|
|
if (iterations > 3) iterations = 3;
|
|
if (iterations < 1) iterations = 1;
|
|
|
|
var px = imageData.data;
|
|
var x = 0,
|
|
y = 0,
|
|
i = 0,
|
|
p = 0,
|
|
yp = 0,
|
|
yi = 0,
|
|
yw = 0,
|
|
r = 0,
|
|
g = 0,
|
|
b = 0,
|
|
a = 0,
|
|
pr = 0,
|
|
pg = 0,
|
|
pb = 0,
|
|
pa = 0;
|
|
|
|
var divx = (radiusX + radiusX + 1) | 0;
|
|
var divy = (radiusY + radiusY + 1) | 0;
|
|
var w = imageData.width | 0;
|
|
var h = imageData.height | 0;
|
|
|
|
var w1 = (w - 1) | 0;
|
|
var h1 = (h - 1) | 0;
|
|
var rxp1 = (radiusX + 1) | 0;
|
|
var ryp1 = (radiusY + 1) | 0;
|
|
|
|
var ssx = { r: 0, b: 0, g: 0, a: 0 };
|
|
var sx = ssx;
|
|
for (i = 1; i < divx; i++) {
|
|
sx = sx.n = { r: 0, b: 0, g: 0, a: 0 };
|
|
}
|
|
sx.n = ssx;
|
|
|
|
var ssy = { r: 0, b: 0, g: 0, a: 0 };
|
|
var sy = ssy;
|
|
for (i = 1; i < divy; i++) {
|
|
sy = sy.n = { r: 0, b: 0, g: 0, a: 0 };
|
|
}
|
|
sy.n = ssy;
|
|
|
|
var si = null;
|
|
|
|
var mtx = BlurFilter.MUL_TABLE[radiusX] | 0;
|
|
var stx = BlurFilter.SHG_TABLE[radiusX] | 0;
|
|
var mty = BlurFilter.MUL_TABLE[radiusY] | 0;
|
|
var sty = BlurFilter.SHG_TABLE[radiusY] | 0;
|
|
|
|
while (iterations-- > 0) {
|
|
yw = yi = 0;
|
|
var ms = mtx;
|
|
var ss = stx;
|
|
for (y = h; --y > -1; ) {
|
|
r = rxp1 * (pr = px[yi | 0]);
|
|
g = rxp1 * (pg = px[(yi + 1) | 0]);
|
|
b = rxp1 * (pb = px[(yi + 2) | 0]);
|
|
a = rxp1 * (pa = px[(yi + 3) | 0]);
|
|
|
|
sx = ssx;
|
|
|
|
for (i = rxp1; --i > -1; ) {
|
|
sx.r = pr;
|
|
sx.g = pg;
|
|
sx.b = pb;
|
|
sx.a = pa;
|
|
sx = sx.n;
|
|
}
|
|
|
|
for (i = 1; i < rxp1; i++) {
|
|
p = (yi + ((w1 < i ? w1 : i) << 2)) | 0;
|
|
r += sx.r = px[p];
|
|
g += sx.g = px[p + 1];
|
|
b += sx.b = px[p + 2];
|
|
a += sx.a = px[p + 3];
|
|
|
|
sx = sx.n;
|
|
}
|
|
|
|
si = ssx;
|
|
for (x = 0; x < w; x++) {
|
|
px[yi++] = (r * ms) >>> ss;
|
|
px[yi++] = (g * ms) >>> ss;
|
|
px[yi++] = (b * ms) >>> ss;
|
|
px[yi++] = (a * ms) >>> ss;
|
|
|
|
p = (yw + ((p = x + radiusX + 1) < w1 ? p : w1)) << 2;
|
|
|
|
r -= si.r - (si.r = px[p]);
|
|
g -= si.g - (si.g = px[p + 1]);
|
|
b -= si.b - (si.b = px[p + 2]);
|
|
a -= si.a - (si.a = px[p + 3]);
|
|
|
|
si = si.n;
|
|
}
|
|
yw += w;
|
|
}
|
|
|
|
ms = mty;
|
|
ss = sty;
|
|
for (x = 0; x < w; x++) {
|
|
yi = (x << 2) | 0;
|
|
|
|
r = (ryp1 * (pr = px[yi])) | 0;
|
|
g = (ryp1 * (pg = px[(yi + 1) | 0])) | 0;
|
|
b = (ryp1 * (pb = px[(yi + 2) | 0])) | 0;
|
|
a = (ryp1 * (pa = px[(yi + 3) | 0])) | 0;
|
|
|
|
sy = ssy;
|
|
for (i = 0; i < ryp1; i++) {
|
|
sy.r = pr;
|
|
sy.g = pg;
|
|
sy.b = pb;
|
|
sy.a = pa;
|
|
sy = sy.n;
|
|
}
|
|
|
|
yp = w;
|
|
|
|
for (i = 1; i <= radiusY; i++) {
|
|
yi = (yp + x) << 2;
|
|
|
|
r += sy.r = px[yi];
|
|
g += sy.g = px[yi + 1];
|
|
b += sy.b = px[yi + 2];
|
|
a += sy.a = px[yi + 3];
|
|
|
|
sy = sy.n;
|
|
|
|
if (i < h1) {
|
|
yp += w;
|
|
}
|
|
}
|
|
|
|
yi = x;
|
|
si = ssy;
|
|
if (iterations > 0) {
|
|
for (y = 0; y < h; y++) {
|
|
p = yi << 2;
|
|
px[p + 3] = pa = (a * ms) >>> ss;
|
|
if (pa > 0) {
|
|
px[p] = (r * ms) >>> ss;
|
|
px[p + 1] = (g * ms) >>> ss;
|
|
px[p + 2] = (b * ms) >>> ss;
|
|
} else {
|
|
px[p] = px[p + 1] = px[p + 2] = 0;
|
|
}
|
|
|
|
p = (x + ((p = y + ryp1) < h1 ? p : h1) * w) << 2;
|
|
|
|
r -= si.r - (si.r = px[p]);
|
|
g -= si.g - (si.g = px[p + 1]);
|
|
b -= si.b - (si.b = px[p + 2]);
|
|
a -= si.a - (si.a = px[p + 3]);
|
|
|
|
si = si.n;
|
|
|
|
yi += w;
|
|
}
|
|
} else {
|
|
for (y = 0; y < h; y++) {
|
|
p = yi << 2;
|
|
px[p + 3] = pa = (a * ms) >>> ss;
|
|
if (pa > 0) {
|
|
pa = 255 / pa;
|
|
px[p] = ((r * ms) >>> ss) * pa;
|
|
px[p + 1] = ((g * ms) >>> ss) * pa;
|
|
px[p + 2] = ((b * ms) >>> ss) * pa;
|
|
} else {
|
|
px[p] = px[p + 1] = px[p + 2] = 0;
|
|
}
|
|
|
|
p = (x + ((p = y + ryp1) < h1 ? p : h1) * w) << 2;
|
|
|
|
r -= si.r - (si.r = px[p]);
|
|
g -= si.g - (si.g = px[p + 1]);
|
|
b -= si.b - (si.b = px[p + 2]);
|
|
a -= si.a - (si.a = px[p + 3]);
|
|
|
|
si = si.n;
|
|
|
|
yi += w;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
createjs.BlurFilter = createjs.promote(BlurFilter, "Filter");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// AlphaMapFilter.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Applies a greyscale alpha map image (or canvas) to the target, such that the alpha channel of the result will
|
|
* be copied from the red channel of the map, and the RGB channels will be copied from the target.
|
|
*
|
|
* Generally, it is recommended that you use {{#crossLink "AlphaMaskFilter"}}{{/crossLink}}, because it has much
|
|
* better performance.
|
|
*
|
|
* <h4>Example</h4>
|
|
* This example draws a red->blue box, caches it, and then uses the cache canvas as an alpha map on a 100x100 image.
|
|
*
|
|
* var box = new createjs.Shape();
|
|
* box.graphics.beginLinearGradientFill(["#ff0000", "#0000ff"], [0, 1], 0, 0, 0, 100)
|
|
* box.graphics.drawRect(0, 0, 100, 100);
|
|
* box.cache(0, 0, 100, 100);
|
|
*
|
|
* var bmp = new createjs.Bitmap("path/to/image.jpg");
|
|
* bmp.filters = [
|
|
* new createjs.AlphaMapFilter(box.cacheCanvas)
|
|
* ];
|
|
* bmp.cache(0, 0, 100, 100);
|
|
* stage.addChild(bmp);
|
|
*
|
|
* See {{#crossLink "Filter"}}{{/crossLink}} for more information on applying filters.
|
|
* @class AlphaMapFilter
|
|
* @extends Filter
|
|
* @constructor
|
|
* @param {HTMLImageElement|HTMLCanvasElement} alphaMap The greyscale image (or canvas) to use as the alpha value for the
|
|
* result. This should be exactly the same dimensions as the target.
|
|
**/
|
|
function AlphaMapFilter(alphaMap) {
|
|
// public properties:
|
|
/**
|
|
* The greyscale image (or canvas) to use as the alpha value for the result. This should be exactly the same
|
|
* dimensions as the target.
|
|
* @property alphaMap
|
|
* @type HTMLImageElement|HTMLCanvasElement
|
|
**/
|
|
this.alphaMap = alphaMap;
|
|
|
|
// private properties:
|
|
/**
|
|
* @property _alphaMap
|
|
* @protected
|
|
* @type HTMLImageElement|HTMLCanvasElement
|
|
**/
|
|
this._alphaMap = null;
|
|
|
|
/**
|
|
* @property _mapData
|
|
* @protected
|
|
* @type Uint8ClampedArray
|
|
**/
|
|
this._mapData = null;
|
|
}
|
|
var p = createjs.extend(AlphaMapFilter, createjs.Filter);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// public methods:
|
|
/** docced in super class **/
|
|
p.clone = function () {
|
|
var o = new AlphaMapFilter(this.alphaMap);
|
|
o._alphaMap = this._alphaMap;
|
|
o._mapData = this._mapData;
|
|
return o;
|
|
};
|
|
|
|
/** docced in super class **/
|
|
p.toString = function () {
|
|
return "[AlphaMapFilter]";
|
|
};
|
|
|
|
// private methods:
|
|
/** docced in super class **/
|
|
p._applyFilter = function (imageData) {
|
|
if (!this.alphaMap) {
|
|
return true;
|
|
}
|
|
if (!this._prepAlphaMap()) {
|
|
return false;
|
|
}
|
|
|
|
// TODO: update to support scenarios where the target has different dimensions.
|
|
var data = imageData.data;
|
|
var map = this._mapData;
|
|
for (var i = 0, l = data.length; i < l; i += 4) {
|
|
data[i + 3] = map[i] || 0;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* @method _prepAlphaMap
|
|
* @protected
|
|
**/
|
|
p._prepAlphaMap = function () {
|
|
if (!this.alphaMap) {
|
|
return false;
|
|
}
|
|
if (this.alphaMap == this._alphaMap && this._mapData) {
|
|
return true;
|
|
}
|
|
|
|
this._mapData = null;
|
|
var map = (this._alphaMap = this.alphaMap);
|
|
var canvas = map;
|
|
var ctx;
|
|
if (map instanceof HTMLCanvasElement) {
|
|
ctx = canvas.getContext("2d");
|
|
} else {
|
|
canvas = createjs.createCanvas
|
|
? createjs.createCanvas()
|
|
: document.createElement("canvas");
|
|
canvas.width = map.width;
|
|
canvas.height = map.height;
|
|
ctx = canvas.getContext("2d");
|
|
ctx.drawImage(map, 0, 0);
|
|
}
|
|
|
|
try {
|
|
var imgData = ctx.getImageData(0, 0, map.width, map.height);
|
|
} catch (e) {
|
|
//if (!this.suppressCrossDomainErrors) throw new Error("unable to access local image data: " + e);
|
|
return false;
|
|
}
|
|
|
|
this._mapData = imgData.data;
|
|
return true;
|
|
};
|
|
|
|
createjs.AlphaMapFilter = createjs.promote(AlphaMapFilter, "Filter");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// AlphaMaskFilter.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Applies the alpha from the mask image (or canvas) to the target, such that the alpha channel of the result will
|
|
* be derived from the mask, and the RGB channels will be copied from the target. This can be used, for example, to
|
|
* apply an alpha mask to a display object. This can also be used to combine a JPG compressed RGB image with a PNG32
|
|
* alpha mask, which can result in a much smaller file size than a single PNG32 containing ARGB.
|
|
*
|
|
* <b>IMPORTANT NOTE: This filter currently does not support the targetCtx, or targetX/Y parameters correctly.</b>
|
|
*
|
|
* <h4>Example</h4>
|
|
* This example draws a gradient box, then caches it and uses the "cacheCanvas" as the alpha mask on a 100x100 image.
|
|
*
|
|
* var box = new createjs.Shape();
|
|
* box.graphics.beginLinearGradientFill(["#000000", "rgba(0, 0, 0, 0)"], [0, 1], 0, 0, 100, 100)
|
|
* box.graphics.drawRect(0, 0, 100, 100);
|
|
* box.cache(0, 0, 100, 100);
|
|
*
|
|
* var bmp = new createjs.Bitmap("path/to/image.jpg");
|
|
* bmp.filters = [
|
|
* new createjs.AlphaMaskFilter(box.cacheCanvas)
|
|
* ];
|
|
* bmp.cache(0, 0, 100, 100);
|
|
*
|
|
* See {{#crossLink "Filter"}}{{/crossLink}} for more information on applying filters.
|
|
* @class AlphaMaskFilter
|
|
* @extends Filter
|
|
* @constructor
|
|
* @param {HTMLImageElement|HTMLCanvasElement} mask
|
|
**/
|
|
function AlphaMaskFilter(mask) {
|
|
// public properties:
|
|
/**
|
|
* The image (or canvas) to use as the mask.
|
|
* @property mask
|
|
* @type HTMLImageElement|HTMLCanvasElement
|
|
**/
|
|
this.mask = mask;
|
|
}
|
|
var p = createjs.extend(AlphaMaskFilter, createjs.Filter);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// public methods:
|
|
/**
|
|
* Applies the filter to the specified context.
|
|
*
|
|
* <strong>IMPORTANT NOTE: This filter currently does not support the targetCtx, or targetX/Y parameters
|
|
* correctly.</strong>
|
|
* @method applyFilter
|
|
* @param {CanvasRenderingContext2D} ctx The 2D context to use as the source.
|
|
* @param {Number} x The x position to use for the source rect.
|
|
* @param {Number} y The y position to use for the source rect.
|
|
* @param {Number} width The width to use for the source rect.
|
|
* @param {Number} height The height to use for the source rect.
|
|
* @param {CanvasRenderingContext2D} [targetCtx] NOT SUPPORTED IN THIS FILTER. The 2D context to draw the result to. Defaults to the context passed to ctx.
|
|
* @param {Number} [targetX] NOT SUPPORTED IN THIS FILTER. The x position to draw the result to. Defaults to the value passed to x.
|
|
* @param {Number} [targetY] NOT SUPPORTED IN THIS FILTER. The y position to draw the result to. Defaults to the value passed to y.
|
|
* @return {Boolean} If the filter was applied successfully.
|
|
**/
|
|
p.applyFilter = function (
|
|
ctx,
|
|
x,
|
|
y,
|
|
width,
|
|
height,
|
|
targetCtx,
|
|
targetX,
|
|
targetY
|
|
) {
|
|
if (!this.mask) {
|
|
return true;
|
|
}
|
|
targetCtx = targetCtx || ctx;
|
|
if (targetX == null) {
|
|
targetX = x;
|
|
}
|
|
if (targetY == null) {
|
|
targetY = y;
|
|
}
|
|
|
|
targetCtx.save();
|
|
if (ctx != targetCtx) {
|
|
// TODO: support targetCtx and targetX/Y
|
|
// clearRect, then draw the ctx in?
|
|
return false;
|
|
}
|
|
|
|
targetCtx.globalCompositeOperation = "destination-in";
|
|
targetCtx.drawImage(this.mask, targetX, targetY);
|
|
targetCtx.restore();
|
|
return true;
|
|
};
|
|
|
|
/** docced in super class **/
|
|
p.clone = function () {
|
|
return new AlphaMaskFilter(this.mask);
|
|
};
|
|
|
|
/** docced in super class **/
|
|
p.toString = function () {
|
|
return "[AlphaMaskFilter]";
|
|
};
|
|
|
|
createjs.AlphaMaskFilter = createjs.promote(AlphaMaskFilter, "Filter");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// ColorFilter.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Applies a color transform to DisplayObjects.
|
|
*
|
|
* <h4>Example</h4>
|
|
* This example draws a red circle, and then transforms it to Blue. This is accomplished by multiplying all the channels
|
|
* to 0 (except alpha, which is set to 1), and then adding 255 to the blue channel.
|
|
*
|
|
* var shape = new createjs.Shape().set({x:100,y:100});
|
|
* shape.graphics.beginFill("#ff0000").drawCircle(0,0,50);
|
|
*
|
|
* shape.filters = [
|
|
* new createjs.ColorFilter(0,0,0,1, 0,0,255,0)
|
|
* ];
|
|
* shape.cache(-50, -50, 100, 100);
|
|
*
|
|
* See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters.
|
|
* @class ColorFilter
|
|
* @param {Number} [redMultiplier=1] The amount to multiply against the red channel. This is a range between 0 and 1.
|
|
* @param {Number} [greenMultiplier=1] The amount to multiply against the green channel. This is a range between 0 and 1.
|
|
* @param {Number} [blueMultiplier=1] The amount to multiply against the blue channel. This is a range between 0 and 1.
|
|
* @param {Number} [alphaMultiplier=1] The amount to multiply against the alpha channel. This is a range between 0 and 1.
|
|
* @param {Number} [redOffset=0] The amount to add to the red channel after it has been multiplied. This is a range
|
|
* between -255 and 255.
|
|
* @param {Number} [greenOffset=0] The amount to add to the green channel after it has been multiplied. This is a range
|
|
* between -255 and 255.
|
|
* @param {Number} [blueOffset=0] The amount to add to the blue channel after it has been multiplied. This is a range
|
|
* between -255 and 255.
|
|
* @param {Number} [alphaOffset=0] The amount to add to the alpha channel after it has been multiplied. This is a range
|
|
* between -255 and 255.
|
|
* @constructor
|
|
* @extends Filter
|
|
**/
|
|
function ColorFilter(
|
|
redMultiplier,
|
|
greenMultiplier,
|
|
blueMultiplier,
|
|
alphaMultiplier,
|
|
redOffset,
|
|
greenOffset,
|
|
blueOffset,
|
|
alphaOffset
|
|
) {
|
|
// public properties:
|
|
/**
|
|
* Red channel multiplier.
|
|
* @property redMultiplier
|
|
* @type Number
|
|
**/
|
|
this.redMultiplier = redMultiplier != null ? redMultiplier : 1;
|
|
|
|
/**
|
|
* Green channel multiplier.
|
|
* @property greenMultiplier
|
|
* @type Number
|
|
**/
|
|
this.greenMultiplier = greenMultiplier != null ? greenMultiplier : 1;
|
|
|
|
/**
|
|
* Blue channel multiplier.
|
|
* @property blueMultiplier
|
|
* @type Number
|
|
**/
|
|
this.blueMultiplier = blueMultiplier != null ? blueMultiplier : 1;
|
|
|
|
/**
|
|
* Alpha channel multiplier.
|
|
* @property alphaMultiplier
|
|
* @type Number
|
|
**/
|
|
this.alphaMultiplier = alphaMultiplier != null ? alphaMultiplier : 1;
|
|
|
|
/**
|
|
* Red channel offset (added to value).
|
|
* @property redOffset
|
|
* @type Number
|
|
**/
|
|
this.redOffset = redOffset || 0;
|
|
|
|
/**
|
|
* Green channel offset (added to value).
|
|
* @property greenOffset
|
|
* @type Number
|
|
**/
|
|
this.greenOffset = greenOffset || 0;
|
|
|
|
/**
|
|
* Blue channel offset (added to value).
|
|
* @property blueOffset
|
|
* @type Number
|
|
**/
|
|
this.blueOffset = blueOffset || 0;
|
|
|
|
/**
|
|
* Alpha channel offset (added to value).
|
|
* @property alphaOffset
|
|
* @type Number
|
|
**/
|
|
this.alphaOffset = alphaOffset || 0;
|
|
}
|
|
var p = createjs.extend(ColorFilter, createjs.Filter);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// public methods:
|
|
/** docced in super class **/
|
|
p.toString = function () {
|
|
return "[ColorFilter]";
|
|
};
|
|
|
|
/** docced in super class **/
|
|
p.clone = function () {
|
|
return new ColorFilter(
|
|
this.redMultiplier,
|
|
this.greenMultiplier,
|
|
this.blueMultiplier,
|
|
this.alphaMultiplier,
|
|
this.redOffset,
|
|
this.greenOffset,
|
|
this.blueOffset,
|
|
this.alphaOffset
|
|
);
|
|
};
|
|
|
|
// private methods:
|
|
/** docced in super class **/
|
|
p._applyFilter = function (imageData) {
|
|
var data = imageData.data;
|
|
var l = data.length;
|
|
for (var i = 0; i < l; i += 4) {
|
|
data[i] = data[i] * this.redMultiplier + this.redOffset;
|
|
data[i + 1] = data[i + 1] * this.greenMultiplier + this.greenOffset;
|
|
data[i + 2] = data[i + 2] * this.blueMultiplier + this.blueOffset;
|
|
data[i + 3] = data[i + 3] * this.alphaMultiplier + this.alphaOffset;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
createjs.ColorFilter = createjs.promote(ColorFilter, "Filter");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// ColorMatrix.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Provides helper functions for assembling a matrix for use with the {{#crossLink "ColorMatrixFilter"}}{{/crossLink}}.
|
|
* Most methods return the instance to facilitate chained calls.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* myColorMatrix.adjustHue(20).adjustBrightness(50);
|
|
*
|
|
* See {{#crossLink "Filter"}}{{/crossLink}} for an example of how to apply filters, or {{#crossLink "ColorMatrixFilter"}}{{/crossLink}}
|
|
* for an example of how to use ColorMatrix to change a DisplayObject's color.
|
|
* @class ColorMatrix
|
|
* @param {Number} brightness
|
|
* @param {Number} contrast
|
|
* @param {Number} saturation
|
|
* @param {Number} hue
|
|
* @constructor
|
|
**/
|
|
function ColorMatrix(brightness, contrast, saturation, hue) {
|
|
this.setColor(brightness, contrast, saturation, hue);
|
|
}
|
|
var p = ColorMatrix.prototype;
|
|
|
|
/**
|
|
* <strong>REMOVED</strong>. Removed in favor of using `MySuperClass_constructor`.
|
|
* See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}}
|
|
* for details.
|
|
*
|
|
* There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance.
|
|
*
|
|
* @method initialize
|
|
* @protected
|
|
* @deprecated
|
|
*/
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is.
|
|
|
|
// constants:
|
|
/**
|
|
* Array of delta values for contrast calculations.
|
|
* @property DELTA_INDEX
|
|
* @type Array
|
|
* @protected
|
|
* @static
|
|
**/
|
|
ColorMatrix.DELTA_INDEX = [
|
|
0,
|
|
0.01,
|
|
0.02,
|
|
0.04,
|
|
0.05,
|
|
0.06,
|
|
0.07,
|
|
0.08,
|
|
0.1,
|
|
0.11,
|
|
0.12,
|
|
0.14,
|
|
0.15,
|
|
0.16,
|
|
0.17,
|
|
0.18,
|
|
0.2,
|
|
0.21,
|
|
0.22,
|
|
0.24,
|
|
0.25,
|
|
0.27,
|
|
0.28,
|
|
0.3,
|
|
0.32,
|
|
0.34,
|
|
0.36,
|
|
0.38,
|
|
0.4,
|
|
0.42,
|
|
0.44,
|
|
0.46,
|
|
0.48,
|
|
0.5,
|
|
0.53,
|
|
0.56,
|
|
0.59,
|
|
0.62,
|
|
0.65,
|
|
0.68,
|
|
0.71,
|
|
0.74,
|
|
0.77,
|
|
0.8,
|
|
0.83,
|
|
0.86,
|
|
0.89,
|
|
0.92,
|
|
0.95,
|
|
0.98,
|
|
1.0,
|
|
1.06,
|
|
1.12,
|
|
1.18,
|
|
1.24,
|
|
1.3,
|
|
1.36,
|
|
1.42,
|
|
1.48,
|
|
1.54,
|
|
1.6,
|
|
1.66,
|
|
1.72,
|
|
1.78,
|
|
1.84,
|
|
1.9,
|
|
1.96,
|
|
2.0,
|
|
2.12,
|
|
2.25,
|
|
2.37,
|
|
2.5,
|
|
2.62,
|
|
2.75,
|
|
2.87,
|
|
3.0,
|
|
3.2,
|
|
3.4,
|
|
3.6,
|
|
3.8,
|
|
4.0,
|
|
4.3,
|
|
4.7,
|
|
4.9,
|
|
5.0,
|
|
5.5,
|
|
6.0,
|
|
6.5,
|
|
6.8,
|
|
7.0,
|
|
7.3,
|
|
7.5,
|
|
7.8,
|
|
8.0,
|
|
8.4,
|
|
8.7,
|
|
9.0,
|
|
9.4,
|
|
9.6,
|
|
9.8,
|
|
10.0,
|
|
];
|
|
|
|
/**
|
|
* Identity matrix values.
|
|
* @property IDENTITY_MATRIX
|
|
* @type Array
|
|
* @protected
|
|
* @static
|
|
**/
|
|
ColorMatrix.IDENTITY_MATRIX = [
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
];
|
|
|
|
/**
|
|
* The constant length of a color matrix.
|
|
* @property LENGTH
|
|
* @type Number
|
|
* @protected
|
|
* @static
|
|
**/
|
|
ColorMatrix.LENGTH = ColorMatrix.IDENTITY_MATRIX.length;
|
|
|
|
// public methods:
|
|
/**
|
|
* Resets the instance with the specified values.
|
|
* @method setColor
|
|
* @param {Number} brightness
|
|
* @param {Number} contrast
|
|
* @param {Number} saturation
|
|
* @param {Number} hue
|
|
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
*/
|
|
p.setColor = function (brightness, contrast, saturation, hue) {
|
|
return this.reset().adjustColor(brightness, contrast, saturation, hue);
|
|
};
|
|
|
|
/**
|
|
* Resets the matrix to identity values.
|
|
* @method reset
|
|
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
*/
|
|
p.reset = function () {
|
|
return this.copy(ColorMatrix.IDENTITY_MATRIX);
|
|
};
|
|
|
|
/**
|
|
* Shortcut method to adjust brightness, contrast, saturation and hue.
|
|
* Equivalent to calling adjustHue(hue), adjustContrast(contrast),
|
|
* adjustBrightness(brightness), adjustSaturation(saturation), in that order.
|
|
* @method adjustColor
|
|
* @param {Number} brightness
|
|
* @param {Number} contrast
|
|
* @param {Number} saturation
|
|
* @param {Number} hue
|
|
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.adjustColor = function (brightness, contrast, saturation, hue) {
|
|
this.adjustHue(hue);
|
|
this.adjustContrast(contrast);
|
|
this.adjustBrightness(brightness);
|
|
return this.adjustSaturation(saturation);
|
|
};
|
|
|
|
/**
|
|
* Adjusts the brightness of pixel color by adding the specified value to the red, green and blue channels.
|
|
* Positive values will make the image brighter, negative values will make it darker.
|
|
* @method adjustBrightness
|
|
* @param {Number} value A value between -255 & 255 that will be added to the RGB channels.
|
|
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.adjustBrightness = function (value) {
|
|
if (value == 0 || isNaN(value)) {
|
|
return this;
|
|
}
|
|
value = this._cleanValue(value, 255);
|
|
this._multiplyMatrix([
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
value,
|
|
0,
|
|
1,
|
|
0,
|
|
0,
|
|
value,
|
|
0,
|
|
0,
|
|
1,
|
|
0,
|
|
value,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
]);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Adjusts the contrast of pixel color.
|
|
* Positive values will increase contrast, negative values will decrease contrast.
|
|
* @method adjustContrast
|
|
* @param {Number} value A value between -100 & 100.
|
|
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.adjustContrast = function (value) {
|
|
if (value == 0 || isNaN(value)) {
|
|
return this;
|
|
}
|
|
value = this._cleanValue(value, 100);
|
|
var x;
|
|
if (value < 0) {
|
|
x = 127 + (value / 100) * 127;
|
|
} else {
|
|
x = value % 1;
|
|
if (x == 0) {
|
|
x = ColorMatrix.DELTA_INDEX[value];
|
|
} else {
|
|
x =
|
|
ColorMatrix.DELTA_INDEX[value << 0] * (1 - x) +
|
|
ColorMatrix.DELTA_INDEX[(value << 0) + 1] * x; // use linear interpolation for more granularity.
|
|
}
|
|
x = x * 127 + 127;
|
|
}
|
|
this._multiplyMatrix([
|
|
x / 127,
|
|
0,
|
|
0,
|
|
0,
|
|
0.5 * (127 - x),
|
|
0,
|
|
x / 127,
|
|
0,
|
|
0,
|
|
0.5 * (127 - x),
|
|
0,
|
|
0,
|
|
x / 127,
|
|
0,
|
|
0.5 * (127 - x),
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
]);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Adjusts the color saturation of the pixel.
|
|
* Positive values will increase saturation, negative values will decrease saturation (trend towards greyscale).
|
|
* @method adjustSaturation
|
|
* @param {Number} value A value between -100 & 100.
|
|
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.adjustSaturation = function (value) {
|
|
if (value == 0 || isNaN(value)) {
|
|
return this;
|
|
}
|
|
value = this._cleanValue(value, 100);
|
|
var x = 1 + (value > 0 ? (3 * value) / 100 : value / 100);
|
|
var lumR = 0.3086;
|
|
var lumG = 0.6094;
|
|
var lumB = 0.082;
|
|
this._multiplyMatrix([
|
|
lumR * (1 - x) + x,
|
|
lumG * (1 - x),
|
|
lumB * (1 - x),
|
|
0,
|
|
0,
|
|
lumR * (1 - x),
|
|
lumG * (1 - x) + x,
|
|
lumB * (1 - x),
|
|
0,
|
|
0,
|
|
lumR * (1 - x),
|
|
lumG * (1 - x),
|
|
lumB * (1 - x) + x,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
]);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Adjusts the hue of the pixel color.
|
|
* @method adjustHue
|
|
* @param {Number} value A value between -180 & 180.
|
|
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.adjustHue = function (value) {
|
|
if (value == 0 || isNaN(value)) {
|
|
return this;
|
|
}
|
|
value = (this._cleanValue(value, 180) / 180) * Math.PI;
|
|
var cosVal = Math.cos(value);
|
|
var sinVal = Math.sin(value);
|
|
var lumR = 0.213;
|
|
var lumG = 0.715;
|
|
var lumB = 0.072;
|
|
this._multiplyMatrix([
|
|
lumR + cosVal * (1 - lumR) + sinVal * -lumR,
|
|
lumG + cosVal * -lumG + sinVal * -lumG,
|
|
lumB + cosVal * -lumB + sinVal * (1 - lumB),
|
|
0,
|
|
0,
|
|
lumR + cosVal * -lumR + sinVal * 0.143,
|
|
lumG + cosVal * (1 - lumG) + sinVal * 0.14,
|
|
lumB + cosVal * -lumB + sinVal * -0.283,
|
|
0,
|
|
0,
|
|
lumR + cosVal * -lumR + sinVal * -(1 - lumR),
|
|
lumG + cosVal * -lumG + sinVal * lumG,
|
|
lumB + cosVal * (1 - lumB) + sinVal * lumB,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
]);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Concatenates (multiplies) the specified matrix with this one.
|
|
* @method concat
|
|
* @param {Array} matrix An array or ColorMatrix instance.
|
|
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.concat = function (matrix) {
|
|
matrix = this._fixMatrix(matrix);
|
|
if (matrix.length != ColorMatrix.LENGTH) {
|
|
return this;
|
|
}
|
|
this._multiplyMatrix(matrix);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Returns a clone of this ColorMatrix.
|
|
* @method clone
|
|
* @return {ColorMatrix} A clone of this ColorMatrix.
|
|
**/
|
|
p.clone = function () {
|
|
return new ColorMatrix().copy(this);
|
|
};
|
|
|
|
/**
|
|
* Return a length 25 (5x5) array instance containing this matrix's values.
|
|
* @method toArray
|
|
* @return {Array} An array holding this matrix's values.
|
|
**/
|
|
p.toArray = function () {
|
|
var arr = [];
|
|
for (var i = 0, l = ColorMatrix.LENGTH; i < l; i++) {
|
|
arr[i] = this[i];
|
|
}
|
|
return arr;
|
|
};
|
|
|
|
/**
|
|
* Copy the specified matrix's values to this matrix.
|
|
* @method copy
|
|
* @param {Array} matrix An array or ColorMatrix instance.
|
|
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.)
|
|
* @chainable
|
|
**/
|
|
p.copy = function (matrix) {
|
|
var l = ColorMatrix.LENGTH;
|
|
for (var i = 0; i < l; i++) {
|
|
this[i] = matrix[i];
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[ColorMatrix]";
|
|
};
|
|
|
|
// private methods:
|
|
/**
|
|
* @method _multiplyMatrix
|
|
* @param {Array} matrix
|
|
* @protected
|
|
**/
|
|
p._multiplyMatrix = function (matrix) {
|
|
var i,
|
|
j,
|
|
k,
|
|
col = [];
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
for (j = 0; j < 5; j++) {
|
|
col[j] = this[j + i * 5];
|
|
}
|
|
for (j = 0; j < 5; j++) {
|
|
var val = 0;
|
|
for (k = 0; k < 5; k++) {
|
|
val += matrix[j + k * 5] * col[k];
|
|
}
|
|
this[j + i * 5] = val;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Make sure values are within the specified range, hue has a limit of 180, brightness is 255, others are 100.
|
|
* @method _cleanValue
|
|
* @param {Number} value The raw number
|
|
* @param {Number} limit The maximum that the number can be. The minimum is the limit * -1.
|
|
* @protected
|
|
**/
|
|
p._cleanValue = function (value, limit) {
|
|
return Math.min(limit, Math.max(-limit, value));
|
|
};
|
|
|
|
/**
|
|
* Makes sure matrixes are 5x5 (25 long).
|
|
* @method _fixMatrix
|
|
* @param {Array} matrix
|
|
* @protected
|
|
**/
|
|
p._fixMatrix = function (matrix) {
|
|
if (matrix instanceof ColorMatrix) {
|
|
matrix = matrix.toArray();
|
|
}
|
|
if (matrix.length < ColorMatrix.LENGTH) {
|
|
matrix = matrix
|
|
.slice(0, matrix.length)
|
|
.concat(
|
|
ColorMatrix.IDENTITY_MATRIX.slice(matrix.length, ColorMatrix.LENGTH)
|
|
);
|
|
} else if (matrix.length > ColorMatrix.LENGTH) {
|
|
matrix = matrix.slice(0, ColorMatrix.LENGTH);
|
|
}
|
|
return matrix;
|
|
};
|
|
|
|
createjs.ColorMatrix = ColorMatrix;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// ColorMatrixFilter.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Allows you to carry out complex color operations such as modifying saturation, brightness, or inverting. See the
|
|
* {{#crossLink "ColorMatrix"}}{{/crossLink}} for more information on changing colors. For an easier color transform,
|
|
* consider the {{#crossLink "ColorFilter"}}{{/crossLink}}.
|
|
*
|
|
* <h4>Example</h4>
|
|
* This example creates a red circle, inverts its hue, and then saturates it to brighten it up.
|
|
*
|
|
* var shape = new createjs.Shape().set({x:100,y:100});
|
|
* shape.graphics.beginFill("#ff0000").drawCircle(0,0,50);
|
|
*
|
|
* var matrix = new createjs.ColorMatrix().adjustHue(180).adjustSaturation(100);
|
|
* shape.filters = [
|
|
* new createjs.ColorMatrixFilter(matrix)
|
|
* ];
|
|
*
|
|
* shape.cache(-50, -50, 100, 100);
|
|
*
|
|
* See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters.
|
|
* @class ColorMatrixFilter
|
|
* @constructor
|
|
* @extends Filter
|
|
* @param {Array | ColorMatrix} matrix A 4x5 matrix describing the color operation to perform. See also the {{#crossLink "ColorMatrix"}}{{/crossLink}}
|
|
* class.
|
|
**/
|
|
function ColorMatrixFilter(matrix) {
|
|
// public properties:
|
|
/**
|
|
* A 4x5 matrix describing the color operation to perform. See also the {{#crossLink "ColorMatrix"}}{{/crossLink}}
|
|
* @property matrix
|
|
* @type Array | ColorMatrix
|
|
**/
|
|
this.matrix = matrix;
|
|
}
|
|
var p = createjs.extend(ColorMatrixFilter, createjs.Filter);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// public methods:
|
|
/** docced in super class **/
|
|
p.toString = function () {
|
|
return "[ColorMatrixFilter]";
|
|
};
|
|
|
|
/** docced in super class **/
|
|
p.clone = function () {
|
|
return new ColorMatrixFilter(this.matrix);
|
|
};
|
|
|
|
// private methods:
|
|
/** docced in super class **/
|
|
p._applyFilter = function (imageData) {
|
|
var data = imageData.data;
|
|
var l = data.length;
|
|
var r, g, b, a;
|
|
var mtx = this.matrix;
|
|
var m0 = mtx[0],
|
|
m1 = mtx[1],
|
|
m2 = mtx[2],
|
|
m3 = mtx[3],
|
|
m4 = mtx[4];
|
|
var m5 = mtx[5],
|
|
m6 = mtx[6],
|
|
m7 = mtx[7],
|
|
m8 = mtx[8],
|
|
m9 = mtx[9];
|
|
var m10 = mtx[10],
|
|
m11 = mtx[11],
|
|
m12 = mtx[12],
|
|
m13 = mtx[13],
|
|
m14 = mtx[14];
|
|
var m15 = mtx[15],
|
|
m16 = mtx[16],
|
|
m17 = mtx[17],
|
|
m18 = mtx[18],
|
|
m19 = mtx[19];
|
|
|
|
for (var i = 0; i < l; i += 4) {
|
|
r = data[i];
|
|
g = data[i + 1];
|
|
b = data[i + 2];
|
|
a = data[i + 3];
|
|
data[i] = r * m0 + g * m1 + b * m2 + a * m3 + m4; // red
|
|
data[i + 1] = r * m5 + g * m6 + b * m7 + a * m8 + m9; // green
|
|
data[i + 2] = r * m10 + g * m11 + b * m12 + a * m13 + m14; // blue
|
|
data[i + 3] = r * m15 + g * m16 + b * m17 + a * m18 + m19; // alpha
|
|
}
|
|
return true;
|
|
};
|
|
|
|
createjs.ColorMatrixFilter = createjs.promote(ColorMatrixFilter, "Filter");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Touch.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* Global utility for working with multi-touch enabled devices in EaselJS. Currently supports W3C Touch API (iOS and
|
|
* modern Android browser) and the Pointer API (IE), including ms-prefixed events in IE10, and unprefixed in IE11.
|
|
*
|
|
* Ensure that you {{#crossLink "Touch/disable"}}{{/crossLink}} touch when cleaning up your application. You do not have
|
|
* to check if touch is supported to enable it, as it will fail gracefully if it is not supported.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var stage = new createjs.Stage("canvasId");
|
|
* createjs.Touch.enable(stage);
|
|
*
|
|
* <strong>Note:</strong> It is important to disable Touch on a stage that you are no longer using:
|
|
*
|
|
* createjs.Touch.disable(stage);
|
|
*
|
|
* @class Touch
|
|
* @static
|
|
**/
|
|
function Touch() {
|
|
throw "Touch cannot be instantiated";
|
|
}
|
|
|
|
// public static methods:
|
|
/**
|
|
* Returns `true` if touch is supported in the current browser.
|
|
* @method isSupported
|
|
* @return {Boolean} Indicates whether touch is supported in the current browser.
|
|
* @static
|
|
**/
|
|
Touch.isSupported = function () {
|
|
return !!(
|
|
"ontouchstart" in window || // iOS & Android
|
|
(window.navigator["msPointerEnabled"] &&
|
|
window.navigator["msMaxTouchPoints"] > 0) || // IE10
|
|
(window.navigator["pointerEnabled"] &&
|
|
window.navigator["maxTouchPoints"] > 0)
|
|
); // IE11+
|
|
};
|
|
|
|
/**
|
|
* Enables touch interaction for the specified EaselJS {{#crossLink "Stage"}}{{/crossLink}}. Currently supports iOS
|
|
* (and compatible browsers, such as modern Android browsers), and IE10/11. Supports both single touch and
|
|
* multi-touch modes. Extends the EaselJS {{#crossLink "MouseEvent"}}{{/crossLink}} model, but without support for
|
|
* double click or over/out events. See the MouseEvent {{#crossLink "MouseEvent/pointerId:property"}}{{/crossLink}}
|
|
* for more information.
|
|
* @method enable
|
|
* @param {Stage} stage The {{#crossLink "Stage"}}{{/crossLink}} to enable touch on.
|
|
* @param {Boolean} [singleTouch=false] If `true`, only a single touch will be active at a time.
|
|
* @param {Boolean} [allowDefault=false] If `true`, then default gesture actions (ex. scrolling, zooming) will be
|
|
* allowed when the user is interacting with the target canvas.
|
|
* @return {Boolean} Returns `true` if touch was successfully enabled on the target stage.
|
|
* @static
|
|
**/
|
|
Touch.enable = function (stage, singleTouch, allowDefault) {
|
|
if (!stage || !stage.canvas || !Touch.isSupported()) {
|
|
return false;
|
|
}
|
|
if (stage.__touch) {
|
|
return true;
|
|
}
|
|
|
|
// inject required properties on stage:
|
|
stage.__touch = {
|
|
pointers: {},
|
|
multitouch: !singleTouch,
|
|
preventDefault: !allowDefault,
|
|
count: 0,
|
|
};
|
|
|
|
// note that in the future we may need to disable the standard mouse event model before adding
|
|
// these to prevent duplicate calls. It doesn't seem to be an issue with iOS devices though.
|
|
if ("ontouchstart" in window) {
|
|
Touch._IOS_enable(stage);
|
|
} else if (
|
|
window.navigator["msPointerEnabled"] ||
|
|
window.navigator["pointerEnabled"]
|
|
) {
|
|
Touch._IE_enable(stage);
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Removes all listeners that were set up when calling `Touch.enable()` on a stage.
|
|
* @method disable
|
|
* @param {Stage} stage The {{#crossLink "Stage"}}{{/crossLink}} to disable touch on.
|
|
* @static
|
|
**/
|
|
Touch.disable = function (stage) {
|
|
if (!stage) {
|
|
return;
|
|
}
|
|
if ("ontouchstart" in window) {
|
|
Touch._IOS_disable(stage);
|
|
} else if (
|
|
window.navigator["msPointerEnabled"] ||
|
|
window.navigator["pointerEnabled"]
|
|
) {
|
|
Touch._IE_disable(stage);
|
|
}
|
|
|
|
delete stage.__touch;
|
|
};
|
|
|
|
// Private static methods:
|
|
/**
|
|
* @method _IOS_enable
|
|
* @protected
|
|
* @param {Stage} stage
|
|
* @static
|
|
**/
|
|
Touch._IOS_enable = function (stage) {
|
|
var canvas = stage.canvas;
|
|
var f = (stage.__touch.f = function (e) {
|
|
Touch._IOS_handleEvent(stage, e);
|
|
});
|
|
canvas.addEventListener("touchstart", f, false);
|
|
canvas.addEventListener("touchmove", f, false);
|
|
canvas.addEventListener("touchend", f, false);
|
|
canvas.addEventListener("touchcancel", f, false);
|
|
};
|
|
|
|
/**
|
|
* @method _IOS_disable
|
|
* @protected
|
|
* @param {Stage} stage
|
|
* @static
|
|
**/
|
|
Touch._IOS_disable = function (stage) {
|
|
var canvas = stage.canvas;
|
|
if (!canvas) {
|
|
return;
|
|
}
|
|
var f = stage.__touch.f;
|
|
canvas.removeEventListener("touchstart", f, false);
|
|
canvas.removeEventListener("touchmove", f, false);
|
|
canvas.removeEventListener("touchend", f, false);
|
|
canvas.removeEventListener("touchcancel", f, false);
|
|
};
|
|
|
|
/**
|
|
* @method _IOS_handleEvent
|
|
* @param {Stage} stage
|
|
* @param {Object} e The event to handle
|
|
* @protected
|
|
* @static
|
|
**/
|
|
Touch._IOS_handleEvent = function (stage, e) {
|
|
if (!stage) {
|
|
return;
|
|
}
|
|
if (stage.__touch.preventDefault) {
|
|
e.preventDefault && e.preventDefault();
|
|
}
|
|
var touches = e.changedTouches;
|
|
var type = e.type;
|
|
for (var i = 0, l = touches.length; i < l; i++) {
|
|
var touch = touches[i];
|
|
var id = touch.identifier;
|
|
if (touch.target != stage.canvas) {
|
|
continue;
|
|
}
|
|
|
|
if (type == "touchstart") {
|
|
this._handleStart(stage, id, e, touch.pageX, touch.pageY);
|
|
} else if (type == "touchmove") {
|
|
this._handleMove(stage, id, e, touch.pageX, touch.pageY);
|
|
} else if (type == "touchend" || type == "touchcancel") {
|
|
this._handleEnd(stage, id, e);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _IE_enable
|
|
* @protected
|
|
* @param {Stage} stage
|
|
* @static
|
|
**/
|
|
Touch._IE_enable = function (stage) {
|
|
var canvas = stage.canvas;
|
|
var f = (stage.__touch.f = function (e) {
|
|
Touch._IE_handleEvent(stage, e);
|
|
});
|
|
|
|
if (window.navigator["pointerEnabled"] === undefined) {
|
|
canvas.addEventListener("MSPointerDown", f, false);
|
|
window.addEventListener("MSPointerMove", f, false);
|
|
window.addEventListener("MSPointerUp", f, false);
|
|
window.addEventListener("MSPointerCancel", f, false);
|
|
if (stage.__touch.preventDefault) {
|
|
canvas.style.msTouchAction = "none";
|
|
}
|
|
} else {
|
|
canvas.addEventListener("pointerdown", f, false);
|
|
window.addEventListener("pointermove", f, false);
|
|
window.addEventListener("pointerup", f, false);
|
|
window.addEventListener("pointercancel", f, false);
|
|
if (stage.__touch.preventDefault) {
|
|
canvas.style.touchAction = "none";
|
|
}
|
|
}
|
|
stage.__touch.activeIDs = {};
|
|
};
|
|
|
|
/**
|
|
* @method _IE_disable
|
|
* @protected
|
|
* @param {Stage} stage
|
|
* @static
|
|
**/
|
|
Touch._IE_disable = function (stage) {
|
|
var f = stage.__touch.f;
|
|
|
|
if (window.navigator["pointerEnabled"] === undefined) {
|
|
window.removeEventListener("MSPointerMove", f, false);
|
|
window.removeEventListener("MSPointerUp", f, false);
|
|
window.removeEventListener("MSPointerCancel", f, false);
|
|
if (stage.canvas) {
|
|
stage.canvas.removeEventListener("MSPointerDown", f, false);
|
|
}
|
|
} else {
|
|
window.removeEventListener("pointermove", f, false);
|
|
window.removeEventListener("pointerup", f, false);
|
|
window.removeEventListener("pointercancel", f, false);
|
|
if (stage.canvas) {
|
|
stage.canvas.removeEventListener("pointerdown", f, false);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _IE_handleEvent
|
|
* @param {Stage} stage
|
|
* @param {Object} e The event to handle.
|
|
* @protected
|
|
* @static
|
|
**/
|
|
Touch._IE_handleEvent = function (stage, e) {
|
|
if (!stage) {
|
|
return;
|
|
}
|
|
if (stage.__touch.preventDefault) {
|
|
e.preventDefault && e.preventDefault();
|
|
}
|
|
var type = e.type;
|
|
var id = e.pointerId;
|
|
var ids = stage.__touch.activeIDs;
|
|
|
|
if (type == "MSPointerDown" || type == "pointerdown") {
|
|
if (e.srcElement != stage.canvas) {
|
|
return;
|
|
}
|
|
ids[id] = true;
|
|
this._handleStart(stage, id, e, e.pageX, e.pageY);
|
|
} else if (ids[id]) {
|
|
// it's an id we're watching
|
|
if (type == "MSPointerMove" || type == "pointermove") {
|
|
this._handleMove(stage, id, e, e.pageX, e.pageY);
|
|
} else if (
|
|
type == "MSPointerUp" ||
|
|
type == "MSPointerCancel" ||
|
|
type == "pointerup" ||
|
|
type == "pointercancel"
|
|
) {
|
|
delete ids[id];
|
|
this._handleEnd(stage, id, e);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _handleStart
|
|
* @param {Stage} stage
|
|
* @param {String|Number} id
|
|
* @param {Object} e
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @protected
|
|
**/
|
|
Touch._handleStart = function (stage, id, e, x, y) {
|
|
var props = stage.__touch;
|
|
if (!props.multitouch && props.count) {
|
|
return;
|
|
}
|
|
var ids = props.pointers;
|
|
if (ids[id]) {
|
|
return;
|
|
}
|
|
ids[id] = true;
|
|
props.count++;
|
|
stage._handlePointerDown(id, e, x, y);
|
|
};
|
|
|
|
/**
|
|
* @method _handleMove
|
|
* @param {Stage} stage
|
|
* @param {String|Number} id
|
|
* @param {Object} e
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @protected
|
|
**/
|
|
Touch._handleMove = function (stage, id, e, x, y) {
|
|
if (!stage.__touch.pointers[id]) {
|
|
return;
|
|
}
|
|
stage._handlePointerMove(id, e, x, y);
|
|
};
|
|
|
|
/**
|
|
* @method _handleEnd
|
|
* @param {Stage} stage
|
|
* @param {String|Number} id
|
|
* @param {Object} e
|
|
* @protected
|
|
**/
|
|
Touch._handleEnd = function (stage, id, e) {
|
|
// TODO: cancel should be handled differently for proper UI (ex. an up would trigger a click, a cancel would more closely resemble an out).
|
|
var props = stage.__touch;
|
|
var ids = props.pointers;
|
|
if (!ids[id]) {
|
|
return;
|
|
}
|
|
props.count--;
|
|
stage._handlePointerUp(id, e, true);
|
|
delete ids[id];
|
|
};
|
|
|
|
createjs.Touch = Touch;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// version.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* Static class holding library specific information such as the version and buildDate of
|
|
* the library.
|
|
* @class EaselJS
|
|
**/
|
|
var s = (createjs.EaselJS = createjs.EaselJS || {});
|
|
|
|
/**
|
|
* The version string for this release.
|
|
* @property version
|
|
* @type String
|
|
* @static
|
|
**/
|
|
s.version = /*=version*/ "0.8.2"; // injected by build process
|
|
|
|
/**
|
|
* The build date for this release in UTC format.
|
|
* @property buildDate
|
|
* @type String
|
|
* @static
|
|
**/
|
|
s.buildDate = /*=date*/ "Thu, 26 Nov 2015 20:44:34 GMT"; // injected by build process
|
|
})();
|
|
|
|
//##############################################################################
|
|
// version.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* Static class holding library specific information such as the version and buildDate of the library.
|
|
* @class PreloadJS
|
|
**/
|
|
var s = (createjs.PreloadJS = createjs.PreloadJS || {});
|
|
|
|
/**
|
|
* The version string for this release.
|
|
* @property version
|
|
* @type {String}
|
|
* @static
|
|
**/
|
|
s.version = /*=version*/ "0.6.2"; // injected by build process
|
|
|
|
/**
|
|
* The build date for this release in UTC format.
|
|
* @property buildDate
|
|
* @type {String}
|
|
* @static
|
|
**/
|
|
s.buildDate = /*=date*/ "Thu, 26 Nov 2015 20:44:31 GMT"; // injected by build process
|
|
})();
|
|
|
|
//##############################################################################
|
|
// proxy.js
|
|
//##############################################################################
|
|
|
|
/**
|
|
* Various utilities that the CreateJS Suite uses. Utilities are created as separate files, and will be available on the
|
|
* createjs namespace directly.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* myObject.addEventListener("change", createjs.proxy(myMethod, scope));
|
|
*
|
|
* @class Utility Methods
|
|
* @main Utility Methods
|
|
*/
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* A function proxy for methods. By default, JavaScript methods do not maintain scope, so passing a method as a
|
|
* callback will result in the method getting called in the scope of the caller. Using a proxy ensures that the
|
|
* method gets called in the correct scope.
|
|
*
|
|
* Additional arguments can be passed that will be applied to the function when it is called.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* myObject.addEventListener("event", createjs.proxy(myHandler, this, arg1, arg2));
|
|
*
|
|
* function myHandler(arg1, arg2) {
|
|
* // This gets called when myObject.myCallback is executed.
|
|
* }
|
|
*
|
|
* @method proxy
|
|
* @param {Function} method The function to call
|
|
* @param {Object} scope The scope to call the method name on
|
|
* @param {mixed} [arg] * Arguments that are appended to the callback for additional params.
|
|
* @public
|
|
* @static
|
|
*/
|
|
createjs.proxy = function (method, scope) {
|
|
var aArgs = Array.prototype.slice.call(arguments, 2);
|
|
return function () {
|
|
return method.apply(
|
|
scope,
|
|
Array.prototype.slice.call(arguments, 0).concat(aArgs)
|
|
);
|
|
};
|
|
};
|
|
})();
|
|
|
|
//##############################################################################
|
|
// ErrorEvent.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* A general error {{#crossLink "Event"}}{{/crossLink}}, that describes an error that occurred, as well as any details.
|
|
* @class ErrorEvent
|
|
* @param {String} [title] The error title
|
|
* @param {String} [message] The error description
|
|
* @param {Object} [data] Additional error data
|
|
* @constructor
|
|
*/
|
|
function ErrorEvent(title, message, data) {
|
|
this.Event_constructor("error");
|
|
|
|
/**
|
|
* The short error title, which indicates the type of error that occurred.
|
|
* @property title
|
|
* @type String
|
|
*/
|
|
this.title = title;
|
|
|
|
/**
|
|
* The verbose error message, containing details about the error.
|
|
* @property message
|
|
* @type String
|
|
*/
|
|
this.message = message;
|
|
|
|
/**
|
|
* Additional data attached to an error.
|
|
* @property data
|
|
* @type {Object}
|
|
*/
|
|
this.data = data;
|
|
}
|
|
|
|
var p = createjs.extend(ErrorEvent, createjs.Event);
|
|
|
|
p.clone = function () {
|
|
return new createjs.ErrorEvent(this.title, this.message, this.data);
|
|
};
|
|
|
|
createjs.ErrorEvent = createjs.promote(ErrorEvent, "Event");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// ProgressEvent.js
|
|
//##############################################################################
|
|
|
|
(function (scope) {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* A CreateJS {{#crossLink "Event"}}{{/crossLink}} that is dispatched when progress changes.
|
|
* @class ProgressEvent
|
|
* @param {Number} loaded The amount that has been loaded. This can be any number relative to the total.
|
|
* @param {Number} [total=1] The total amount that will load. This will default to 1, so if the `loaded` value is
|
|
* a percentage (between 0 and 1), it can be omitted.
|
|
* @todo Consider having this event be a "fileprogress" event as well
|
|
* @constructor
|
|
*/
|
|
function ProgressEvent(loaded, total) {
|
|
this.Event_constructor("progress");
|
|
|
|
/**
|
|
* The amount that has been loaded (out of a total amount)
|
|
* @property loaded
|
|
* @type {Number}
|
|
*/
|
|
this.loaded = loaded;
|
|
|
|
/**
|
|
* The total "size" of the load.
|
|
* @property total
|
|
* @type {Number}
|
|
* @default 1
|
|
*/
|
|
this.total = total == null ? 1 : total;
|
|
|
|
/**
|
|
* The percentage (out of 1) that the load has been completed. This is calculated using `loaded/total`.
|
|
* @property progress
|
|
* @type {Number}
|
|
* @default 0
|
|
*/
|
|
this.progress = total == 0 ? 0 : this.loaded / this.total;
|
|
}
|
|
|
|
var p = createjs.extend(ProgressEvent, createjs.Event);
|
|
|
|
/**
|
|
* Returns a clone of the ProgressEvent instance.
|
|
* @method clone
|
|
* @return {ProgressEvent} a clone of the Event instance.
|
|
**/
|
|
p.clone = function () {
|
|
return new createjs.ProgressEvent(this.loaded, this.total);
|
|
};
|
|
|
|
createjs.ProgressEvent = createjs.promote(ProgressEvent, "Event");
|
|
})(window);
|
|
|
|
//##############################################################################
|
|
// json3.js
|
|
//##############################################################################
|
|
|
|
/*! JSON v3.3.2 | http://bestiejs.github.io/json3 | Copyright 2012-2014, Kit Cambridge | http://kit.mit-license.org */
|
|
(function () {
|
|
// Detect the `define` function exposed by asynchronous module loaders. The
|
|
// strict `define` check is necessary for compatibility with `r.js`.
|
|
var isLoader = typeof define === "function" && define.amd;
|
|
|
|
// A set of types used to distinguish objects from primitives.
|
|
var objectTypes = {
|
|
function: true,
|
|
object: true,
|
|
};
|
|
|
|
// Detect the `exports` object exposed by CommonJS implementations.
|
|
var freeExports =
|
|
objectTypes[typeof exports] && exports && !exports.nodeType && exports;
|
|
|
|
// Use the `global` object exposed by Node (including Browserify via
|
|
// `insert-module-globals`), Narwhal, and Ringo as the default context,
|
|
// and the `window` object in browsers. Rhino exports a `global` function
|
|
// instead.
|
|
var root = (objectTypes[typeof window] && window) || this,
|
|
freeGlobal =
|
|
freeExports &&
|
|
objectTypes[typeof module] &&
|
|
module &&
|
|
!module.nodeType &&
|
|
typeof global == "object" &&
|
|
global;
|
|
|
|
if (
|
|
freeGlobal &&
|
|
(freeGlobal["global"] === freeGlobal ||
|
|
freeGlobal["window"] === freeGlobal ||
|
|
freeGlobal["self"] === freeGlobal)
|
|
) {
|
|
root = freeGlobal;
|
|
}
|
|
|
|
// Public: Initializes JSON 3 using the given `context` object, attaching the
|
|
// `stringify` and `parse` functions to the specified `exports` object.
|
|
function runInContext(context, exports) {
|
|
context || (context = root["Object"]());
|
|
exports || (exports = root["Object"]());
|
|
|
|
// Native constructor aliases.
|
|
var Number = context["Number"] || root["Number"],
|
|
String = context["String"] || root["String"],
|
|
Object = context["Object"] || root["Object"],
|
|
Date = context["Date"] || root["Date"],
|
|
SyntaxError = context["SyntaxError"] || root["SyntaxError"],
|
|
TypeError = context["TypeError"] || root["TypeError"],
|
|
Math = context["Math"] || root["Math"],
|
|
nativeJSON = context["JSON"] || root["JSON"];
|
|
|
|
// Delegate to the native `stringify` and `parse` implementations.
|
|
if (typeof nativeJSON == "object" && nativeJSON) {
|
|
exports.stringify = nativeJSON.stringify;
|
|
exports.parse = nativeJSON.parse;
|
|
}
|
|
|
|
// Convenience aliases.
|
|
var objectProto = Object.prototype,
|
|
getClass = objectProto.toString,
|
|
isProperty,
|
|
forEach,
|
|
undef;
|
|
|
|
// Test the `Date#getUTC*` methods. Based on work by @Yaffle.
|
|
var isExtended = new Date(-3509827334573292);
|
|
try {
|
|
// The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical
|
|
// results for certain dates in Opera >= 10.53.
|
|
isExtended =
|
|
isExtended.getUTCFullYear() == -109252 &&
|
|
isExtended.getUTCMonth() === 0 &&
|
|
isExtended.getUTCDate() === 1 &&
|
|
// Safari < 2.0.2 stores the internal millisecond time value correctly,
|
|
// but clips the values returned by the date methods to the range of
|
|
// signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]).
|
|
isExtended.getUTCHours() == 10 &&
|
|
isExtended.getUTCMinutes() == 37 &&
|
|
isExtended.getUTCSeconds() == 6 &&
|
|
isExtended.getUTCMilliseconds() == 708;
|
|
} catch (exception) {}
|
|
|
|
// Internal: Determines whether the native `JSON.stringify` and `parse`
|
|
// implementations are spec-compliant. Based on work by Ken Snyder.
|
|
function has(name) {
|
|
if (has[name] !== undef) {
|
|
// Return cached feature test result.
|
|
return has[name];
|
|
}
|
|
var isSupported;
|
|
if (name == "bug-string-char-index") {
|
|
// IE <= 7 doesn't support accessing string characters using square
|
|
// bracket notation. IE 8 only supports this for primitives.
|
|
isSupported = "a"[0] != "a";
|
|
} else if (name == "json") {
|
|
// Indicates whether both `JSON.stringify` and `JSON.parse` are
|
|
// supported.
|
|
isSupported = has("json-stringify") && has("json-parse");
|
|
} else {
|
|
var value,
|
|
serialized = '{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}';
|
|
// Test `JSON.stringify`.
|
|
if (name == "json-stringify") {
|
|
var stringify = exports.stringify,
|
|
stringifySupported = typeof stringify == "function" && isExtended;
|
|
if (stringifySupported) {
|
|
// A test function object with a custom `toJSON` method.
|
|
(value = function () {
|
|
return 1;
|
|
}).toJSON = value;
|
|
try {
|
|
stringifySupported =
|
|
// Firefox 3.1b1 and b2 serialize string, number, and boolean
|
|
// primitives as object literals.
|
|
stringify(0) === "0" &&
|
|
// FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object
|
|
// literals.
|
|
stringify(new Number()) === "0" &&
|
|
stringify(new String()) == '""' &&
|
|
// FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or
|
|
// does not define a canonical JSON representation (this applies to
|
|
// objects with `toJSON` properties as well, *unless* they are nested
|
|
// within an object or array).
|
|
stringify(getClass) === undef &&
|
|
// IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and
|
|
// FF 3.1b3 pass this test.
|
|
stringify(undef) === undef &&
|
|
// Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s,
|
|
// respectively, if the value is omitted entirely.
|
|
stringify() === undef &&
|
|
// FF 3.1b1, 2 throw an error if the given value is not a number,
|
|
// string, array, object, Boolean, or `null` literal. This applies to
|
|
// objects with custom `toJSON` methods as well, unless they are nested
|
|
// inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON`
|
|
// methods entirely.
|
|
stringify(value) === "1" &&
|
|
stringify([value]) == "[1]" &&
|
|
// Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of
|
|
// `"[null]"`.
|
|
stringify([undef]) == "[null]" &&
|
|
// YUI 3.0.0b1 fails to serialize `null` literals.
|
|
stringify(null) == "null" &&
|
|
// FF 3.1b1, 2 halts serialization if an array contains a function:
|
|
// `[1, true, getClass, 1]` serializes as "[1,true,],". FF 3.1b3
|
|
// elides non-JSON values from objects and arrays, unless they
|
|
// define custom `toJSON` methods.
|
|
stringify([undef, getClass, null]) == "[null,null,null]" &&
|
|
// Simple serialization test. FF 3.1b1 uses Unicode escape sequences
|
|
// where character escape codes are expected (e.g., `\b` => `\u0008`).
|
|
stringify({
|
|
a: [value, true, false, null, "\x00\b\n\f\r\t"],
|
|
}) == serialized &&
|
|
// FF 3.1b1 and b2 ignore the `filter` and `width` arguments.
|
|
stringify(null, value) === "1" &&
|
|
stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" &&
|
|
// JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly
|
|
// serialize extended years.
|
|
stringify(new Date(-8.64e15)) ==
|
|
'"-271821-04-20T00:00:00.000Z"' &&
|
|
// The milliseconds are optional in ES 5, but required in 5.1.
|
|
stringify(new Date(8.64e15)) ==
|
|
'"+275760-09-13T00:00:00.000Z"' &&
|
|
// Firefox <= 11.0 incorrectly serializes years prior to 0 as negative
|
|
// four-digit years instead of six-digit years. Credits: @Yaffle.
|
|
stringify(new Date(-621987552e5)) ==
|
|
'"-000001-01-01T00:00:00.000Z"' &&
|
|
// Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond
|
|
// values less than 1000. Credits: @Yaffle.
|
|
stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"';
|
|
} catch (exception) {
|
|
stringifySupported = false;
|
|
}
|
|
}
|
|
isSupported = stringifySupported;
|
|
}
|
|
// Test `JSON.parse`.
|
|
if (name == "json-parse") {
|
|
var parse = exports.parse;
|
|
if (typeof parse == "function") {
|
|
try {
|
|
// FF 3.1b1, b2 will throw an exception if a bare literal is provided.
|
|
// Conforming implementations should also coerce the initial argument to
|
|
// a string prior to parsing.
|
|
if (parse("0") === 0 && !parse(false)) {
|
|
// Simple parsing test.
|
|
value = parse(serialized);
|
|
var parseSupported =
|
|
value["a"].length == 5 && value["a"][0] === 1;
|
|
if (parseSupported) {
|
|
try {
|
|
// Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings.
|
|
parseSupported = !parse('"\t"');
|
|
} catch (exception) {}
|
|
if (parseSupported) {
|
|
try {
|
|
// FF 4.0 and 4.0.1 allow leading `+` signs and leading
|
|
// decimal points. FF 4.0, 4.0.1, and IE 9-10 also allow
|
|
// certain octal literals.
|
|
parseSupported = parse("01") !== 1;
|
|
} catch (exception) {}
|
|
}
|
|
if (parseSupported) {
|
|
try {
|
|
// FF 4.0, 4.0.1, and Rhino 1.7R3-R4 allow trailing decimal
|
|
// points. These environments, along with FF 3.1b1 and 2,
|
|
// also allow trailing commas in JSON objects and arrays.
|
|
parseSupported = parse("1.") !== 1;
|
|
} catch (exception) {}
|
|
}
|
|
}
|
|
}
|
|
} catch (exception) {
|
|
parseSupported = false;
|
|
}
|
|
}
|
|
isSupported = parseSupported;
|
|
}
|
|
}
|
|
return (has[name] = !!isSupported);
|
|
}
|
|
|
|
if (!has("json")) {
|
|
// Common `[[Class]]` name aliases.
|
|
var functionClass = "[object Function]",
|
|
dateClass = "[object Date]",
|
|
numberClass = "[object Number]",
|
|
stringClass = "[object String]",
|
|
arrayClass = "[object Array]",
|
|
booleanClass = "[object Boolean]";
|
|
|
|
// Detect incomplete support for accessing string characters by index.
|
|
var charIndexBuggy = has("bug-string-char-index");
|
|
|
|
// Define additional utility methods if the `Date` methods are buggy.
|
|
if (!isExtended) {
|
|
var floor = Math.floor;
|
|
// A mapping between the months of the year and the number of days between
|
|
// January 1st and the first of the respective month.
|
|
var Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
|
|
// Internal: Calculates the number of days between the Unix epoch and the
|
|
// first day of the given month.
|
|
var getDay = function (year, month) {
|
|
return (
|
|
Months[month] +
|
|
365 * (year - 1970) +
|
|
floor((year - 1969 + (month = +(month > 1))) / 4) -
|
|
floor((year - 1901 + month) / 100) +
|
|
floor((year - 1601 + month) / 400)
|
|
);
|
|
};
|
|
}
|
|
|
|
// Internal: Determines if a property is a direct property of the given
|
|
// object. Delegates to the native `Object#hasOwnProperty` method.
|
|
if (!(isProperty = objectProto.hasOwnProperty)) {
|
|
isProperty = function (property) {
|
|
var members = {},
|
|
constructor;
|
|
if (
|
|
((members.__proto__ = null),
|
|
(members.__proto__ = {
|
|
// The *proto* property cannot be set multiple times in recent
|
|
// versions of Firefox and SeaMonkey.
|
|
toString: 1,
|
|
}),
|
|
members).toString != getClass
|
|
) {
|
|
// Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but
|
|
// supports the mutable *proto* property.
|
|
isProperty = function (property) {
|
|
// Capture and break the object's prototype chain (see section 8.6.2
|
|
// of the ES 5.1 spec). The parenthesized expression prevents an
|
|
// unsafe transformation by the Closure Compiler.
|
|
var original = this.__proto__,
|
|
result = property in ((this.__proto__ = null), this);
|
|
// Restore the original prototype chain.
|
|
this.__proto__ = original;
|
|
return result;
|
|
};
|
|
} else {
|
|
// Capture a reference to the top-level `Object` constructor.
|
|
constructor = members.constructor;
|
|
// Use the `constructor` property to simulate `Object#hasOwnProperty` in
|
|
// other environments.
|
|
isProperty = function (property) {
|
|
var parent = (this.constructor || constructor).prototype;
|
|
return (
|
|
property in this &&
|
|
!(property in parent && this[property] === parent[property])
|
|
);
|
|
};
|
|
}
|
|
members = null;
|
|
return isProperty.call(this, property);
|
|
};
|
|
}
|
|
|
|
// Internal: Normalizes the `for...in` iteration algorithm across
|
|
// environments. Each enumerated key is yielded to a `callback` function.
|
|
forEach = function (object, callback) {
|
|
var size = 0,
|
|
Properties,
|
|
members,
|
|
property;
|
|
|
|
// Tests for bugs in the current environment's `for...in` algorithm. The
|
|
// `valueOf` property inherits the non-enumerable flag from
|
|
// `Object.prototype` in older versions of IE, Netscape, and Mozilla.
|
|
(Properties = function () {
|
|
this.valueOf = 0;
|
|
}).prototype.valueOf = 0;
|
|
|
|
// Iterate over a new instance of the `Properties` class.
|
|
members = new Properties();
|
|
for (property in members) {
|
|
// Ignore all properties inherited from `Object.prototype`.
|
|
if (isProperty.call(members, property)) {
|
|
size++;
|
|
}
|
|
}
|
|
Properties = members = null;
|
|
|
|
// Normalize the iteration algorithm.
|
|
if (!size) {
|
|
// A list of non-enumerable properties inherited from `Object.prototype`.
|
|
members = [
|
|
"valueOf",
|
|
"toString",
|
|
"toLocaleString",
|
|
"propertyIsEnumerable",
|
|
"isPrototypeOf",
|
|
"hasOwnProperty",
|
|
"constructor",
|
|
];
|
|
// IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable
|
|
// properties.
|
|
forEach = function (object, callback) {
|
|
var isFunction = getClass.call(object) == functionClass,
|
|
property,
|
|
length;
|
|
var hasProperty =
|
|
(!isFunction &&
|
|
typeof object.constructor != "function" &&
|
|
objectTypes[typeof object.hasOwnProperty] &&
|
|
object.hasOwnProperty) ||
|
|
isProperty;
|
|
for (property in object) {
|
|
// Gecko <= 1.0 enumerates the `prototype` property of functions under
|
|
// certain conditions; IE does not.
|
|
if (
|
|
!(isFunction && property == "prototype") &&
|
|
hasProperty.call(object, property)
|
|
) {
|
|
callback(property);
|
|
}
|
|
}
|
|
// Manually invoke the callback for each non-enumerable property.
|
|
for (
|
|
length = members.length;
|
|
(property = members[--length]);
|
|
hasProperty.call(object, property) && callback(property)
|
|
);
|
|
};
|
|
} else if (size == 2) {
|
|
// Safari <= 2.0.4 enumerates shadowed properties twice.
|
|
forEach = function (object, callback) {
|
|
// Create a set of iterated properties.
|
|
var members = {},
|
|
isFunction = getClass.call(object) == functionClass,
|
|
property;
|
|
for (property in object) {
|
|
// Store each property name to prevent double enumeration. The
|
|
// `prototype` property of functions is not enumerated due to cross-
|
|
// environment inconsistencies.
|
|
if (
|
|
!(isFunction && property == "prototype") &&
|
|
!isProperty.call(members, property) &&
|
|
(members[property] = 1) &&
|
|
isProperty.call(object, property)
|
|
) {
|
|
callback(property);
|
|
}
|
|
}
|
|
};
|
|
} else {
|
|
// No bugs detected; use the standard `for...in` algorithm.
|
|
forEach = function (object, callback) {
|
|
var isFunction = getClass.call(object) == functionClass,
|
|
property,
|
|
isConstructor;
|
|
for (property in object) {
|
|
if (
|
|
!(isFunction && property == "prototype") &&
|
|
isProperty.call(object, property) &&
|
|
!(isConstructor = property === "constructor")
|
|
) {
|
|
callback(property);
|
|
}
|
|
}
|
|
// Manually invoke the callback for the `constructor` property due to
|
|
// cross-environment inconsistencies.
|
|
if (
|
|
isConstructor ||
|
|
isProperty.call(object, (property = "constructor"))
|
|
) {
|
|
callback(property);
|
|
}
|
|
};
|
|
}
|
|
return forEach(object, callback);
|
|
};
|
|
|
|
// Public: Serializes a JavaScript `value` as a JSON string. The optional
|
|
// `filter` argument may specify either a function that alters how object and
|
|
// array members are serialized, or an array of strings and numbers that
|
|
// indicates which properties should be serialized. The optional `width`
|
|
// argument may be either a string or number that specifies the indentation
|
|
// level of the output.
|
|
if (!has("json-stringify")) {
|
|
// Internal: A map of control characters and their escaped equivalents.
|
|
var Escapes = {
|
|
92: "\\\\",
|
|
34: '\\"',
|
|
8: "\\b",
|
|
12: "\\f",
|
|
10: "\\n",
|
|
13: "\\r",
|
|
9: "\\t",
|
|
};
|
|
|
|
// Internal: Converts `value` into a zero-padded string such that its
|
|
// length is at least equal to `width`. The `width` must be <= 6.
|
|
var leadingZeroes = "000000";
|
|
var toPaddedString = function (width, value) {
|
|
// The `|| 0` expression is necessary to work around a bug in
|
|
// Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`.
|
|
return (leadingZeroes + (value || 0)).slice(-width);
|
|
};
|
|
|
|
// Internal: Double-quotes a string `value`, replacing all ASCII control
|
|
// characters (characters with code unit values between 0 and 31) with
|
|
// their escaped equivalents. This is an implementation of the
|
|
// `Quote(value)` operation defined in ES 5.1 section 15.12.3.
|
|
var unicodePrefix = "\\u00";
|
|
var quote = function (value) {
|
|
var result = '"',
|
|
index = 0,
|
|
length = value.length,
|
|
useCharIndex = !charIndexBuggy || length > 10;
|
|
var symbols =
|
|
useCharIndex && (charIndexBuggy ? value.split("") : value);
|
|
for (; index < length; index++) {
|
|
var charCode = value.charCodeAt(index);
|
|
// If the character is a control character, append its Unicode or
|
|
// shorthand escape sequence; otherwise, append the character as-is.
|
|
switch (charCode) {
|
|
case 8:
|
|
case 9:
|
|
case 10:
|
|
case 12:
|
|
case 13:
|
|
case 34:
|
|
case 92:
|
|
result += Escapes[charCode];
|
|
break;
|
|
default:
|
|
if (charCode < 32) {
|
|
result +=
|
|
unicodePrefix + toPaddedString(2, charCode.toString(16));
|
|
break;
|
|
}
|
|
result += useCharIndex ? symbols[index] : value.charAt(index);
|
|
}
|
|
}
|
|
return result + '"';
|
|
};
|
|
|
|
// Internal: Recursively serializes an object. Implements the
|
|
// `Str(key, holder)`, `JO(value)`, and `JA(value)` operations.
|
|
var serialize = function (
|
|
property,
|
|
object,
|
|
callback,
|
|
properties,
|
|
whitespace,
|
|
indentation,
|
|
stack
|
|
) {
|
|
var value,
|
|
className,
|
|
year,
|
|
month,
|
|
date,
|
|
time,
|
|
hours,
|
|
minutes,
|
|
seconds,
|
|
milliseconds,
|
|
results,
|
|
element,
|
|
index,
|
|
length,
|
|
prefix,
|
|
result;
|
|
try {
|
|
// Necessary for host object support.
|
|
value = object[property];
|
|
} catch (exception) {}
|
|
if (typeof value == "object" && value) {
|
|
className = getClass.call(value);
|
|
if (className == dateClass && !isProperty.call(value, "toJSON")) {
|
|
if (value > -1 / 0 && value < 1 / 0) {
|
|
// Dates are serialized according to the `Date#toJSON` method
|
|
// specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15
|
|
// for the ISO 8601 date time string format.
|
|
if (getDay) {
|
|
// Manually compute the year, month, date, hours, minutes,
|
|
// seconds, and milliseconds if the `getUTC*` methods are
|
|
// buggy. Adapted from @Yaffle's `date-shim` project.
|
|
date = floor(value / 864e5);
|
|
for (
|
|
year = floor(date / 365.2425) + 1970 - 1;
|
|
getDay(year + 1, 0) <= date;
|
|
year++
|
|
);
|
|
for (
|
|
month = floor((date - getDay(year, 0)) / 30.42);
|
|
getDay(year, month + 1) <= date;
|
|
month++
|
|
);
|
|
date = 1 + date - getDay(year, month);
|
|
// The `time` value specifies the time within the day (see ES
|
|
// 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used
|
|
// to compute `A modulo B`, as the `%` operator does not
|
|
// correspond to the `modulo` operation for negative numbers.
|
|
time = ((value % 864e5) + 864e5) % 864e5;
|
|
// The hours, minutes, seconds, and milliseconds are obtained by
|
|
// decomposing the time within the day. See section 15.9.1.10.
|
|
hours = floor(time / 36e5) % 24;
|
|
minutes = floor(time / 6e4) % 60;
|
|
seconds = floor(time / 1e3) % 60;
|
|
milliseconds = time % 1e3;
|
|
} else {
|
|
year = value.getUTCFullYear();
|
|
month = value.getUTCMonth();
|
|
date = value.getUTCDate();
|
|
hours = value.getUTCHours();
|
|
minutes = value.getUTCMinutes();
|
|
seconds = value.getUTCSeconds();
|
|
milliseconds = value.getUTCMilliseconds();
|
|
}
|
|
// Serialize extended years correctly.
|
|
value =
|
|
(year <= 0 || year >= 1e4
|
|
? (year < 0 ? "-" : "+") +
|
|
toPaddedString(6, year < 0 ? -year : year)
|
|
: toPaddedString(4, year)) +
|
|
"-" +
|
|
toPaddedString(2, month + 1) +
|
|
"-" +
|
|
toPaddedString(2, date) +
|
|
// Months, dates, hours, minutes, and seconds should have two
|
|
// digits; milliseconds should have three.
|
|
"T" +
|
|
toPaddedString(2, hours) +
|
|
":" +
|
|
toPaddedString(2, minutes) +
|
|
":" +
|
|
toPaddedString(2, seconds) +
|
|
// Milliseconds are optional in ES 5.0, but required in 5.1.
|
|
"." +
|
|
toPaddedString(3, milliseconds) +
|
|
"Z";
|
|
} else {
|
|
value = null;
|
|
}
|
|
} else if (
|
|
typeof value.toJSON == "function" &&
|
|
((className != numberClass &&
|
|
className != stringClass &&
|
|
className != arrayClass) ||
|
|
isProperty.call(value, "toJSON"))
|
|
) {
|
|
// Prototype <= 1.6.1 adds non-standard `toJSON` methods to the
|
|
// `Number`, `String`, `Date`, and `Array` prototypes. JSON 3
|
|
// ignores all `toJSON` methods on these objects unless they are
|
|
// defined directly on an instance.
|
|
value = value.toJSON(property);
|
|
}
|
|
}
|
|
if (callback) {
|
|
// If a replacement function was provided, call it to obtain the value
|
|
// for serialization.
|
|
value = callback.call(object, property, value);
|
|
}
|
|
if (value === null) {
|
|
return "null";
|
|
}
|
|
className = getClass.call(value);
|
|
if (className == booleanClass) {
|
|
// Booleans are represented literally.
|
|
return "" + value;
|
|
} else if (className == numberClass) {
|
|
// JSON numbers must be finite. `Infinity` and `NaN` are serialized as
|
|
// `"null"`.
|
|
return value > -1 / 0 && value < 1 / 0 ? "" + value : "null";
|
|
} else if (className == stringClass) {
|
|
// Strings are double-quoted and escaped.
|
|
return quote("" + value);
|
|
}
|
|
// Recursively serialize objects and arrays.
|
|
if (typeof value == "object") {
|
|
// Check for cyclic structures. This is a linear search; performance
|
|
// is inversely proportional to the number of unique nested objects.
|
|
for (length = stack.length; length--; ) {
|
|
if (stack[length] === value) {
|
|
// Cyclic structures cannot be serialized by `JSON.stringify`.
|
|
throw TypeError();
|
|
}
|
|
}
|
|
// Add the object to the stack of traversed objects.
|
|
stack.push(value);
|
|
results = [];
|
|
// Save the current indentation level and indent one additional level.
|
|
prefix = indentation;
|
|
indentation += whitespace;
|
|
if (className == arrayClass) {
|
|
// Recursively serialize array elements.
|
|
for (index = 0, length = value.length; index < length; index++) {
|
|
element = serialize(
|
|
index,
|
|
value,
|
|
callback,
|
|
properties,
|
|
whitespace,
|
|
indentation,
|
|
stack
|
|
);
|
|
results.push(element === undef ? "null" : element);
|
|
}
|
|
result = results.length
|
|
? whitespace
|
|
? "[\n" +
|
|
indentation +
|
|
results.join(",\n" + indentation) +
|
|
"\n" +
|
|
prefix +
|
|
"]"
|
|
: "[" + results.join(",") + "]"
|
|
: "[]";
|
|
} else {
|
|
// Recursively serialize object members. Members are selected from
|
|
// either a user-specified list of property names, or the object
|
|
// itself.
|
|
forEach(properties || value, function (property) {
|
|
var element = serialize(
|
|
property,
|
|
value,
|
|
callback,
|
|
properties,
|
|
whitespace,
|
|
indentation,
|
|
stack
|
|
);
|
|
if (element !== undef) {
|
|
// According to ES 5.1 section 15.12.3: "If `gap` {whitespace}
|
|
// is not the empty string, let `member` {quote(property) + ":"}
|
|
// be the concatenation of `member` and the `space` character."
|
|
// The "`space` character" refers to the literal space
|
|
// character, not the `space` {width} argument provided to
|
|
// `JSON.stringify`.
|
|
results.push(
|
|
quote(property) + ":" + (whitespace ? " " : "") + element
|
|
);
|
|
}
|
|
});
|
|
result = results.length
|
|
? whitespace
|
|
? "{\n" +
|
|
indentation +
|
|
results.join(",\n" + indentation) +
|
|
"\n" +
|
|
prefix +
|
|
"}"
|
|
: "{" + results.join(",") + "}"
|
|
: "{}";
|
|
}
|
|
// Remove the object from the traversed object stack.
|
|
stack.pop();
|
|
return result;
|
|
}
|
|
};
|
|
|
|
// Public: `JSON.stringify`. See ES 5.1 section 15.12.3.
|
|
exports.stringify = function (source, filter, width) {
|
|
var whitespace, callback, properties, className;
|
|
if (objectTypes[typeof filter] && filter) {
|
|
if ((className = getClass.call(filter)) == functionClass) {
|
|
callback = filter;
|
|
} else if (className == arrayClass) {
|
|
// Convert the property names array into a makeshift set.
|
|
properties = {};
|
|
for (
|
|
var index = 0, length = filter.length, value;
|
|
index < length;
|
|
value = filter[index++],
|
|
((className = getClass.call(value)),
|
|
className == stringClass || className == numberClass) &&
|
|
(properties[value] = 1)
|
|
);
|
|
}
|
|
}
|
|
if (width) {
|
|
if ((className = getClass.call(width)) == numberClass) {
|
|
// Convert the `width` to an integer and create a string containing
|
|
// `width` number of space characters.
|
|
if ((width -= width % 1) > 0) {
|
|
for (
|
|
whitespace = "", width > 10 && (width = 10);
|
|
whitespace.length < width;
|
|
whitespace += " "
|
|
);
|
|
}
|
|
} else if (className == stringClass) {
|
|
whitespace = width.length <= 10 ? width : width.slice(0, 10);
|
|
}
|
|
}
|
|
// Opera <= 7.54u2 discards the values associated with empty string keys
|
|
// (`""`) only if they are used directly within an object member list
|
|
// (e.g., `!("" in { "": 1})`).
|
|
return serialize(
|
|
"",
|
|
((value = {}), (value[""] = source), value),
|
|
callback,
|
|
properties,
|
|
whitespace,
|
|
"",
|
|
[]
|
|
);
|
|
};
|
|
}
|
|
|
|
// Public: Parses a JSON source string.
|
|
if (!has("json-parse")) {
|
|
var fromCharCode = String.fromCharCode;
|
|
|
|
// Internal: A map of escaped control characters and their unescaped
|
|
// equivalents.
|
|
var Unescapes = {
|
|
92: "\\",
|
|
34: '"',
|
|
47: "/",
|
|
98: "\b",
|
|
116: "\t",
|
|
110: "\n",
|
|
102: "\f",
|
|
114: "\r",
|
|
};
|
|
|
|
// Internal: Stores the parser state.
|
|
var Index, Source;
|
|
|
|
// Internal: Resets the parser state and throws a `SyntaxError`.
|
|
var abort = function () {
|
|
Index = Source = null;
|
|
throw SyntaxError();
|
|
};
|
|
|
|
// Internal: Returns the next token, or `"$"` if the parser has reached
|
|
// the end of the source string. A token may be a string, number, `null`
|
|
// literal, or Boolean literal.
|
|
var lex = function () {
|
|
var source = Source,
|
|
length = source.length,
|
|
value,
|
|
begin,
|
|
position,
|
|
isSigned,
|
|
charCode;
|
|
while (Index < length) {
|
|
charCode = source.charCodeAt(Index);
|
|
switch (charCode) {
|
|
case 9:
|
|
case 10:
|
|
case 13:
|
|
case 32:
|
|
// Skip whitespace tokens, including tabs, carriage returns, line
|
|
// feeds, and space characters.
|
|
Index++;
|
|
break;
|
|
case 123:
|
|
case 125:
|
|
case 91:
|
|
case 93:
|
|
case 58:
|
|
case 44:
|
|
// Parse a punctuator token (`{`, `}`, `[`, `]`, `:`, or `,`) at
|
|
// the current position.
|
|
value = charIndexBuggy ? source.charAt(Index) : source[Index];
|
|
Index++;
|
|
return value;
|
|
case 34:
|
|
// `"` delimits a JSON string; advance to the next character and
|
|
// begin parsing the string. String tokens are prefixed with the
|
|
// sentinel `@` character to distinguish them from punctuators and
|
|
// end-of-string tokens.
|
|
for (value = "@", Index++; Index < length; ) {
|
|
charCode = source.charCodeAt(Index);
|
|
if (charCode < 32) {
|
|
// Unescaped ASCII control characters (those with a code unit
|
|
// less than the space character) are not permitted.
|
|
abort();
|
|
} else if (charCode == 92) {
|
|
// A reverse solidus (`\`) marks the beginning of an escaped
|
|
// control character (including `"`, `\`, and `/`) or Unicode
|
|
// escape sequence.
|
|
charCode = source.charCodeAt(++Index);
|
|
switch (charCode) {
|
|
case 92:
|
|
case 34:
|
|
case 47:
|
|
case 98:
|
|
case 116:
|
|
case 110:
|
|
case 102:
|
|
case 114:
|
|
// Revive escaped control characters.
|
|
value += Unescapes[charCode];
|
|
Index++;
|
|
break;
|
|
case 117:
|
|
// `\u` marks the beginning of a Unicode escape sequence.
|
|
// Advance to the first character and validate the
|
|
// four-digit code point.
|
|
begin = ++Index;
|
|
for (position = Index + 4; Index < position; Index++) {
|
|
charCode = source.charCodeAt(Index);
|
|
// A valid sequence comprises four hexdigits (case-
|
|
// insensitive) that form a single hexadecimal value.
|
|
if (
|
|
!(
|
|
(charCode >= 48 && charCode <= 57) ||
|
|
(charCode >= 97 && charCode <= 102) ||
|
|
(charCode >= 65 && charCode <= 70)
|
|
)
|
|
) {
|
|
// Invalid Unicode escape sequence.
|
|
abort();
|
|
}
|
|
}
|
|
// Revive the escaped character.
|
|
value += fromCharCode(
|
|
"0x" + source.slice(begin, Index)
|
|
);
|
|
break;
|
|
default:
|
|
// Invalid escape sequence.
|
|
abort();
|
|
}
|
|
} else {
|
|
if (charCode == 34) {
|
|
// An unescaped double-quote character marks the end of the
|
|
// string.
|
|
break;
|
|
}
|
|
charCode = source.charCodeAt(Index);
|
|
begin = Index;
|
|
// Optimize for the common case where a string is valid.
|
|
while (charCode >= 32 && charCode != 92 && charCode != 34) {
|
|
charCode = source.charCodeAt(++Index);
|
|
}
|
|
// Append the string as-is.
|
|
value += source.slice(begin, Index);
|
|
}
|
|
}
|
|
if (source.charCodeAt(Index) == 34) {
|
|
// Advance to the next character and return the revived string.
|
|
Index++;
|
|
return value;
|
|
}
|
|
// Unterminated string.
|
|
abort();
|
|
default:
|
|
// Parse numbers and literals.
|
|
begin = Index;
|
|
// Advance past the negative sign, if one is specified.
|
|
if (charCode == 45) {
|
|
isSigned = true;
|
|
charCode = source.charCodeAt(++Index);
|
|
}
|
|
// Parse an integer or floating-point value.
|
|
if (charCode >= 48 && charCode <= 57) {
|
|
// Leading zeroes are interpreted as octal literals.
|
|
if (
|
|
charCode == 48 &&
|
|
((charCode = source.charCodeAt(Index + 1)),
|
|
charCode >= 48 && charCode <= 57)
|
|
) {
|
|
// Illegal octal literal.
|
|
abort();
|
|
}
|
|
isSigned = false;
|
|
// Parse the integer component.
|
|
for (
|
|
;
|
|
Index < length &&
|
|
((charCode = source.charCodeAt(Index)),
|
|
charCode >= 48 && charCode <= 57);
|
|
Index++
|
|
);
|
|
// Floats cannot contain a leading decimal point; however, this
|
|
// case is already accounted for by the parser.
|
|
if (source.charCodeAt(Index) == 46) {
|
|
position = ++Index;
|
|
// Parse the decimal component.
|
|
for (
|
|
;
|
|
position < length &&
|
|
((charCode = source.charCodeAt(position)),
|
|
charCode >= 48 && charCode <= 57);
|
|
position++
|
|
);
|
|
if (position == Index) {
|
|
// Illegal trailing decimal.
|
|
abort();
|
|
}
|
|
Index = position;
|
|
}
|
|
// Parse exponents. The `e` denoting the exponent is
|
|
// case-insensitive.
|
|
charCode = source.charCodeAt(Index);
|
|
if (charCode == 101 || charCode == 69) {
|
|
charCode = source.charCodeAt(++Index);
|
|
// Skip past the sign following the exponent, if one is
|
|
// specified.
|
|
if (charCode == 43 || charCode == 45) {
|
|
Index++;
|
|
}
|
|
// Parse the exponential component.
|
|
for (
|
|
position = Index;
|
|
position < length &&
|
|
((charCode = source.charCodeAt(position)),
|
|
charCode >= 48 && charCode <= 57);
|
|
position++
|
|
);
|
|
if (position == Index) {
|
|
// Illegal empty exponent.
|
|
abort();
|
|
}
|
|
Index = position;
|
|
}
|
|
// Coerce the parsed value to a JavaScript number.
|
|
return +source.slice(begin, Index);
|
|
}
|
|
// A negative sign may only precede numbers.
|
|
if (isSigned) {
|
|
abort();
|
|
}
|
|
// `true`, `false`, and `null` literals.
|
|
if (source.slice(Index, Index + 4) == "true") {
|
|
Index += 4;
|
|
return true;
|
|
} else if (source.slice(Index, Index + 5) == "false") {
|
|
Index += 5;
|
|
return false;
|
|
} else if (source.slice(Index, Index + 4) == "null") {
|
|
Index += 4;
|
|
return null;
|
|
}
|
|
// Unrecognized token.
|
|
abort();
|
|
}
|
|
}
|
|
// Return the sentinel `$` character if the parser has reached the end
|
|
// of the source string.
|
|
return "$";
|
|
};
|
|
|
|
// Internal: Parses a JSON `value` token.
|
|
var get = function (value) {
|
|
var results, hasMembers;
|
|
if (value == "$") {
|
|
// Unexpected end of input.
|
|
abort();
|
|
}
|
|
if (typeof value == "string") {
|
|
if ((charIndexBuggy ? value.charAt(0) : value[0]) == "@") {
|
|
// Remove the sentinel `@` character.
|
|
return value.slice(1);
|
|
}
|
|
// Parse object and array literals.
|
|
if (value == "[") {
|
|
// Parses a JSON array, returning a new JavaScript array.
|
|
results = [];
|
|
for (; ; hasMembers || (hasMembers = true)) {
|
|
value = lex();
|
|
// A closing square bracket marks the end of the array literal.
|
|
if (value == "]") {
|
|
break;
|
|
}
|
|
// If the array literal contains elements, the current token
|
|
// should be a comma separating the previous element from the
|
|
// next.
|
|
if (hasMembers) {
|
|
if (value == ",") {
|
|
value = lex();
|
|
if (value == "]") {
|
|
// Unexpected trailing `,` in array literal.
|
|
abort();
|
|
}
|
|
} else {
|
|
// A `,` must separate each array element.
|
|
abort();
|
|
}
|
|
}
|
|
// Elisions and leading commas are not permitted.
|
|
if (value == ",") {
|
|
abort();
|
|
}
|
|
results.push(get(value));
|
|
}
|
|
return results;
|
|
} else if (value == "{") {
|
|
// Parses a JSON object, returning a new JavaScript object.
|
|
results = {};
|
|
for (; ; hasMembers || (hasMembers = true)) {
|
|
value = lex();
|
|
// A closing curly brace marks the end of the object literal.
|
|
if (value == "}") {
|
|
break;
|
|
}
|
|
// If the object literal contains members, the current token
|
|
// should be a comma separator.
|
|
if (hasMembers) {
|
|
if (value == ",") {
|
|
value = lex();
|
|
if (value == "}") {
|
|
// Unexpected trailing `,` in object literal.
|
|
abort();
|
|
}
|
|
} else {
|
|
// A `,` must separate each object member.
|
|
abort();
|
|
}
|
|
}
|
|
// Leading commas are not permitted, object property names must be
|
|
// double-quoted strings, and a `:` must separate each property
|
|
// name and value.
|
|
if (
|
|
value == "," ||
|
|
typeof value != "string" ||
|
|
(charIndexBuggy ? value.charAt(0) : value[0]) != "@" ||
|
|
lex() != ":"
|
|
) {
|
|
abort();
|
|
}
|
|
results[value.slice(1)] = get(lex());
|
|
}
|
|
return results;
|
|
}
|
|
// Unexpected token encountered.
|
|
abort();
|
|
}
|
|
return value;
|
|
};
|
|
|
|
// Internal: Updates a traversed object member.
|
|
var update = function (source, property, callback) {
|
|
var element = walk(source, property, callback);
|
|
if (element === undef) {
|
|
delete source[property];
|
|
} else {
|
|
source[property] = element;
|
|
}
|
|
};
|
|
|
|
// Internal: Recursively traverses a parsed JSON object, invoking the
|
|
// `callback` function for each value. This is an implementation of the
|
|
// `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2.
|
|
var walk = function (source, property, callback) {
|
|
var value = source[property],
|
|
length;
|
|
if (typeof value == "object" && value) {
|
|
// `forEach` can't be used to traverse an array in Opera <= 8.54
|
|
// because its `Object#hasOwnProperty` implementation returns `false`
|
|
// for array indices (e.g., `![1, 2, 3].hasOwnProperty("0")`).
|
|
if (getClass.call(value) == arrayClass) {
|
|
for (length = value.length; length--; ) {
|
|
update(value, length, callback);
|
|
}
|
|
} else {
|
|
forEach(value, function (property) {
|
|
update(value, property, callback);
|
|
});
|
|
}
|
|
}
|
|
return callback.call(source, property, value);
|
|
};
|
|
|
|
// Public: `JSON.parse`. See ES 5.1 section 15.12.2.
|
|
exports.parse = function (source, callback) {
|
|
var result, value;
|
|
Index = 0;
|
|
Source = "" + source;
|
|
result = get(lex());
|
|
// If a JSON string contains multiple tokens, it is invalid.
|
|
if (lex() != "$") {
|
|
abort();
|
|
}
|
|
// Reset the parser state.
|
|
Index = Source = null;
|
|
return callback && getClass.call(callback) == functionClass
|
|
? walk(((value = {}), (value[""] = result), value), "", callback)
|
|
: result;
|
|
};
|
|
}
|
|
}
|
|
|
|
exports["runInContext"] = runInContext;
|
|
return exports;
|
|
}
|
|
|
|
if (freeExports && !isLoader) {
|
|
// Export for CommonJS environments.
|
|
runInContext(root, freeExports);
|
|
} else {
|
|
// Export for web browsers and JavaScript engines.
|
|
var nativeJSON = root.JSON,
|
|
previousJSON = root["JSON3"],
|
|
isRestored = false;
|
|
|
|
var JSON3 = runInContext(
|
|
root,
|
|
(root["JSON3"] = {
|
|
// Public: Restores the original value of the global `JSON` object and
|
|
// returns a reference to the `JSON3` object.
|
|
noConflict: function () {
|
|
if (!isRestored) {
|
|
isRestored = true;
|
|
root.JSON = nativeJSON;
|
|
root["JSON3"] = previousJSON;
|
|
nativeJSON = previousJSON = null;
|
|
}
|
|
return JSON3;
|
|
},
|
|
})
|
|
);
|
|
|
|
root.JSON = {
|
|
parse: JSON3.parse,
|
|
stringify: JSON3.stringify,
|
|
};
|
|
}
|
|
|
|
// Export for asynchronous module loaders.
|
|
if (isLoader) {
|
|
define(function () {
|
|
return JSON3;
|
|
});
|
|
}
|
|
}.call(this));
|
|
|
|
//##############################################################################
|
|
// DomUtils.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
/**
|
|
* A few utilities for interacting with the dom.
|
|
* @class DomUtils
|
|
*/
|
|
var s = {};
|
|
|
|
s.appendToHead = function (el) {
|
|
s.getHead().appendChild(el);
|
|
};
|
|
|
|
s.getHead = function () {
|
|
return document.head || document.getElementsByTagName("head")[0];
|
|
};
|
|
|
|
s.getBody = function () {
|
|
return document.body || document.getElementsByTagName("body")[0];
|
|
};
|
|
|
|
createjs.DomUtils = s;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// DataUtils.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
/**
|
|
* A few data utilities for formatting different data types.
|
|
* @class DataUtils
|
|
*/
|
|
var s = {};
|
|
|
|
// static methods
|
|
/**
|
|
* Parse XML using the DOM. This is required when preloading XML or SVG.
|
|
* @method parseXML
|
|
* @param {String} text The raw text or XML that is loaded by XHR.
|
|
* @param {String} type The mime type of the XML. Use "text/xml" for XML, and "image/svg+xml" for SVG parsing.
|
|
* @return {XML} An XML document
|
|
* @static
|
|
*/
|
|
s.parseXML = function (text, type) {
|
|
var xml = null;
|
|
// CocoonJS does not support XML parsing with either method.
|
|
|
|
// Most browsers will use DOMParser
|
|
// IE fails on certain SVG files, so we have a fallback below.
|
|
try {
|
|
if (window.DOMParser) {
|
|
var parser = new DOMParser();
|
|
xml = parser.parseFromString(text, type);
|
|
}
|
|
} catch (e) {}
|
|
|
|
// Fallback for IE support.
|
|
if (!xml) {
|
|
try {
|
|
xml = new ActiveXObject("Microsoft.XMLDOM");
|
|
xml.async = false;
|
|
xml.loadXML(text);
|
|
} catch (e) {
|
|
xml = null;
|
|
}
|
|
}
|
|
|
|
return xml;
|
|
};
|
|
|
|
/**
|
|
* Parse a string into an Object.
|
|
* @method parseJSON
|
|
* @param {String} value The loaded JSON string
|
|
* @returns {Object} A JavaScript object.
|
|
*/
|
|
s.parseJSON = function (value) {
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(value);
|
|
} catch (e) {
|
|
// TODO; Handle this with a custom error?
|
|
throw e;
|
|
}
|
|
};
|
|
|
|
createjs.DataUtils = s;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// LoadItem.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* All loaders accept an item containing the properties defined in this class. If a raw object is passed instead,
|
|
* it will not be affected, but it must contain at least a {{#crossLink "src:property"}}{{/crossLink}} property. A
|
|
* string path or HTML tag is also acceptable, but it will be automatically converted to a LoadItem using the
|
|
* {{#crossLink "create"}}{{/crossLink}} method by {{#crossLink "AbstractLoader"}}{{/crossLink}}
|
|
* @class LoadItem
|
|
* @constructor
|
|
* @since 0.6.0
|
|
*/
|
|
function LoadItem() {
|
|
/**
|
|
* The source of the file that is being loaded. This property is <b>required</b>. The source can either be a
|
|
* string (recommended), or an HTML tag.
|
|
* This can also be an object, but in that case it has to include a type and be handled by a plugin.
|
|
* @property src
|
|
* @type {String}
|
|
* @default null
|
|
*/
|
|
this.src = null;
|
|
|
|
/**
|
|
* The type file that is being loaded. The type of the file is usually inferred by the extension, but can also
|
|
* be set manually. This is helpful in cases where a file does not have an extension.
|
|
* @property type
|
|
* @type {String}
|
|
* @default null
|
|
*/
|
|
this.type = null;
|
|
|
|
/**
|
|
* A string identifier which can be used to reference the loaded object. If none is provided, this will be
|
|
* automatically set to the {{#crossLink "src:property"}}{{/crossLink}}.
|
|
* @property id
|
|
* @type {String}
|
|
* @default null
|
|
*/
|
|
this.id = null;
|
|
|
|
/**
|
|
* Determines if a manifest will maintain the order of this item, in relation to other items in the manifest
|
|
* that have also set the `maintainOrder` property to `true`. This only applies when the max connections has
|
|
* been set above 1 (using {{#crossLink "LoadQueue/setMaxConnections"}}{{/crossLink}}). Everything with this
|
|
* property set to `false` will finish as it is loaded. Ordered items are combined with script tags loading in
|
|
* order when {{#crossLink "LoadQueue/maintainScriptOrder:property"}}{{/crossLink}} is set to `true`.
|
|
* @property maintainOrder
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
this.maintainOrder = false;
|
|
|
|
/**
|
|
* A callback used by JSONP requests that defines what global method to call when the JSONP content is loaded.
|
|
* @property callback
|
|
* @type {String}
|
|
* @default null
|
|
*/
|
|
this.callback = null;
|
|
|
|
/**
|
|
* An arbitrary data object, which is included with the loaded object.
|
|
* @property data
|
|
* @type {Object}
|
|
* @default null
|
|
*/
|
|
this.data = null;
|
|
|
|
/**
|
|
* The request method used for HTTP calls. Both {{#crossLink "AbstractLoader/GET:property"}}{{/crossLink}} or
|
|
* {{#crossLink "AbstractLoader/POST:property"}}{{/crossLink}} request types are supported, and are defined as
|
|
* constants on {{#crossLink "AbstractLoader"}}{{/crossLink}}.
|
|
* @property method
|
|
* @type {String}
|
|
* @default get
|
|
*/
|
|
this.method = createjs.LoadItem.GET;
|
|
|
|
/**
|
|
* An object hash of name/value pairs to send to the server.
|
|
* @property values
|
|
* @type {Object}
|
|
* @default null
|
|
*/
|
|
this.values = null;
|
|
|
|
/**
|
|
* An object hash of headers to attach to an XHR request. PreloadJS will automatically attach some default
|
|
* headers when required, including "Origin", "Content-Type", and "X-Requested-With". You may override the
|
|
* default headers by including them in your headers object.
|
|
* @property headers
|
|
* @type {Object}
|
|
* @default null
|
|
*/
|
|
this.headers = null;
|
|
|
|
/**
|
|
* Enable credentials for XHR requests.
|
|
* @property withCredentials
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
this.withCredentials = false;
|
|
|
|
/**
|
|
* Set the mime type of XHR-based requests. This is automatically set to "text/plain; charset=utf-8" for text
|
|
* based files (json, xml, text, css, js).
|
|
* @property mimeType
|
|
* @type {String}
|
|
* @default null
|
|
*/
|
|
this.mimeType = null;
|
|
|
|
/**
|
|
* Sets the crossOrigin attribute for CORS-enabled images loading cross-domain.
|
|
* @property crossOrigin
|
|
* @type {boolean}
|
|
* @default Anonymous
|
|
*/
|
|
this.crossOrigin = null;
|
|
|
|
/**
|
|
* The duration in milliseconds to wait before a request times out. This only applies to tag-based and and XHR
|
|
* (level one) loading, as XHR (level 2) provides its own timeout event.
|
|
* @property loadTimeout
|
|
* @type {Number}
|
|
* @default 8000 (8 seconds)
|
|
*/
|
|
this.loadTimeout = s.LOAD_TIMEOUT_DEFAULT;
|
|
}
|
|
|
|
var p = (LoadItem.prototype = {});
|
|
var s = LoadItem;
|
|
|
|
/**
|
|
* Default duration in milliseconds to wait before a request times out. This only applies to tag-based and and XHR
|
|
* (level one) loading, as XHR (level 2) provides its own timeout event.
|
|
* @property LOAD_TIMEOUT_DEFAULT
|
|
* @type {number}
|
|
* @static
|
|
*/
|
|
s.LOAD_TIMEOUT_DEFAULT = 8000;
|
|
|
|
/**
|
|
* Create a LoadItem.
|
|
* <ul>
|
|
* <li>String-based items are converted to a LoadItem with a populated {{#crossLink "src:property"}}{{/crossLink}}.</li>
|
|
* <li>LoadItem instances are returned as-is</li>
|
|
* <li>Objects are returned with any needed properties added</li>
|
|
* </ul>
|
|
* @method create
|
|
* @param {LoadItem|String|Object} value The load item value
|
|
* @returns {LoadItem|Object}
|
|
* @static
|
|
*/
|
|
s.create = function (value) {
|
|
if (typeof value == "string") {
|
|
var item = new LoadItem();
|
|
item.src = value;
|
|
return item;
|
|
} else if (value instanceof s) {
|
|
return value;
|
|
} else if (value instanceof Object && value.src) {
|
|
if (value.loadTimeout == null) {
|
|
value.loadTimeout = s.LOAD_TIMEOUT_DEFAULT;
|
|
}
|
|
return value;
|
|
} else {
|
|
throw new Error("Type not recognized.");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Provides a chainable shortcut method for setting a number of properties on the instance.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var loadItem = new createjs.LoadItem().set({src:"image.png", maintainOrder:true});
|
|
*
|
|
* @method set
|
|
* @param {Object} props A generic object containing properties to copy to the LoadItem instance.
|
|
* @return {LoadItem} Returns the instance the method is called on (useful for chaining calls.)
|
|
*/
|
|
p.set = function (props) {
|
|
for (var n in props) {
|
|
this[n] = props[n];
|
|
}
|
|
return this;
|
|
};
|
|
|
|
createjs.LoadItem = s;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// RequestUtils.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
/**
|
|
* Utilities that assist with parsing load items, and determining file types, etc.
|
|
* @class RequestUtils
|
|
*/
|
|
var s = {};
|
|
|
|
/**
|
|
* The Regular Expression used to test file URLS for an absolute path.
|
|
* @property ABSOLUTE_PATH
|
|
* @type {RegExp}
|
|
* @static
|
|
*/
|
|
s.ABSOLUTE_PATT = /^(?:\w+:)?\/{2}/i;
|
|
|
|
/**
|
|
* The Regular Expression used to test file URLS for a relative path.
|
|
* @property RELATIVE_PATH
|
|
* @type {RegExp}
|
|
* @static
|
|
*/
|
|
s.RELATIVE_PATT = /^[./]*?\//i;
|
|
|
|
/**
|
|
* The Regular Expression used to test file URLS for an extension. Note that URIs must already have the query string
|
|
* removed.
|
|
* @property EXTENSION_PATT
|
|
* @type {RegExp}
|
|
* @static
|
|
*/
|
|
s.EXTENSION_PATT = /\/?[^/]+\.(\w{1,5})$/i;
|
|
|
|
/**
|
|
* Parse a file path to determine the information we need to work with it. Currently, PreloadJS needs to know:
|
|
* <ul>
|
|
* <li>If the path is absolute. Absolute paths start with a protocol (such as `http://`, `file://`, or
|
|
* `//networkPath`)</li>
|
|
* <li>If the path is relative. Relative paths start with `../` or `/path` (or similar)</li>
|
|
* <li>The file extension. This is determined by the filename with an extension. Query strings are dropped, and
|
|
* the file path is expected to follow the format `name.ext`.</li>
|
|
* </ul>
|
|
* @method parseURI
|
|
* @param {String} path
|
|
* @returns {Object} An Object with an `absolute` and `relative` Boolean values, as well as an optional 'extension`
|
|
* property, which is the lowercase extension.
|
|
* @static
|
|
*/
|
|
s.parseURI = function (path) {
|
|
var info = { absolute: false, relative: false };
|
|
if (path == null) {
|
|
return info;
|
|
}
|
|
|
|
// Drop the query string
|
|
var queryIndex = path.indexOf("?");
|
|
if (queryIndex > -1) {
|
|
path = path.substr(0, queryIndex);
|
|
}
|
|
|
|
// Absolute
|
|
var match;
|
|
if (s.ABSOLUTE_PATT.test(path)) {
|
|
info.absolute = true;
|
|
|
|
// Relative
|
|
} else if (s.RELATIVE_PATT.test(path)) {
|
|
info.relative = true;
|
|
}
|
|
|
|
// Extension
|
|
if ((match = path.match(s.EXTENSION_PATT))) {
|
|
info.extension = match[1].toLowerCase();
|
|
}
|
|
return info;
|
|
};
|
|
|
|
/**
|
|
* Formats an object into a query string for either a POST or GET request.
|
|
* @method formatQueryString
|
|
* @param {Object} data The data to convert to a query string.
|
|
* @param {Array} [query] Existing name/value pairs to append on to this query.
|
|
* @static
|
|
*/
|
|
s.formatQueryString = function (data, query) {
|
|
if (data == null) {
|
|
throw new Error("You must specify data.");
|
|
}
|
|
var params = [];
|
|
for (var n in data) {
|
|
params.push(n + "=" + escape(data[n]));
|
|
}
|
|
if (query) {
|
|
params = params.concat(query);
|
|
}
|
|
return params.join("&");
|
|
};
|
|
|
|
/**
|
|
* A utility method that builds a file path using a source and a data object, and formats it into a new path.
|
|
* @method buildPath
|
|
* @param {String} src The source path to add values to.
|
|
* @param {Object} [data] Object used to append values to this request as a query string. Existing parameters on the
|
|
* path will be preserved.
|
|
* @returns {string} A formatted string that contains the path and the supplied parameters.
|
|
* @static
|
|
*/
|
|
s.buildPath = function (src, data) {
|
|
if (data == null) {
|
|
return src;
|
|
}
|
|
|
|
var query = [];
|
|
var idx = src.indexOf("?");
|
|
|
|
if (idx != -1) {
|
|
var q = src.slice(idx + 1);
|
|
query = query.concat(q.split("&"));
|
|
}
|
|
|
|
if (idx != -1) {
|
|
return src.slice(0, idx) + "?" + this.formatQueryString(data, query);
|
|
} else {
|
|
return src + "?" + this.formatQueryString(data, query);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method isCrossDomain
|
|
* @param {LoadItem|Object} item A load item with a `src` property.
|
|
* @return {Boolean} If the load item is loading from a different domain than the current location.
|
|
* @static
|
|
*/
|
|
s.isCrossDomain = function (item) {
|
|
var target = document.createElement("a");
|
|
target.href = item.src;
|
|
|
|
var host = document.createElement("a");
|
|
host.href = location.href;
|
|
|
|
var crossdomain =
|
|
target.hostname != "" &&
|
|
(target.port != host.port ||
|
|
target.protocol != host.protocol ||
|
|
target.hostname != host.hostname);
|
|
return crossdomain;
|
|
};
|
|
|
|
/**
|
|
* @method isLocal
|
|
* @param {LoadItem|Object} item A load item with a `src` property
|
|
* @return {Boolean} If the load item is loading from the "file:" protocol. Assume that the host must be local as
|
|
* well.
|
|
* @static
|
|
*/
|
|
s.isLocal = function (item) {
|
|
var target = document.createElement("a");
|
|
target.href = item.src;
|
|
return target.hostname == "" && target.protocol == "file:";
|
|
};
|
|
|
|
/**
|
|
* Determine if a specific type should be loaded as a binary file. Currently, only images and items marked
|
|
* specifically as "binary" are loaded as binary. Note that audio is <b>not</b> a binary type, as we can not play
|
|
* back using an audio tag if it is loaded as binary. Plugins can change the item type to binary to ensure they get
|
|
* a binary result to work with. Binary files are loaded using XHR2. Types are defined as static constants on
|
|
* {{#crossLink "AbstractLoader"}}{{/crossLink}}.
|
|
* @method isBinary
|
|
* @param {String} type The item type.
|
|
* @return {Boolean} If the specified type is binary.
|
|
* @static
|
|
*/
|
|
s.isBinary = function (type) {
|
|
switch (type) {
|
|
case createjs.AbstractLoader.IMAGE:
|
|
case createjs.AbstractLoader.BINARY:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Check if item is a valid HTMLImageElement
|
|
* @method isImageTag
|
|
* @param {Object} item
|
|
* @returns {Boolean}
|
|
* @static
|
|
*/
|
|
s.isImageTag = function (item) {
|
|
return item instanceof HTMLImageElement;
|
|
};
|
|
|
|
/**
|
|
* Check if item is a valid HTMLAudioElement
|
|
* @method isAudioTag
|
|
* @param {Object} item
|
|
* @returns {Boolean}
|
|
* @static
|
|
*/
|
|
s.isAudioTag = function (item) {
|
|
if (window.HTMLAudioElement) {
|
|
return item instanceof HTMLAudioElement;
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Check if item is a valid HTMLVideoElement
|
|
* @method isVideoTag
|
|
* @param {Object} item
|
|
* @returns {Boolean}
|
|
* @static
|
|
*/
|
|
s.isVideoTag = function (item) {
|
|
if (window.HTMLVideoElement) {
|
|
return item instanceof HTMLVideoElement;
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Determine if a specific type is a text-based asset, and should be loaded as UTF-8.
|
|
* @method isText
|
|
* @param {String} type The item type.
|
|
* @return {Boolean} If the specified type is text.
|
|
* @static
|
|
*/
|
|
s.isText = function (type) {
|
|
switch (type) {
|
|
case createjs.AbstractLoader.TEXT:
|
|
case createjs.AbstractLoader.JSON:
|
|
case createjs.AbstractLoader.MANIFEST:
|
|
case createjs.AbstractLoader.XML:
|
|
case createjs.AbstractLoader.CSS:
|
|
case createjs.AbstractLoader.SVG:
|
|
case createjs.AbstractLoader.JAVASCRIPT:
|
|
case createjs.AbstractLoader.SPRITESHEET:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Determine the type of the object using common extensions. Note that the type can be passed in with the load item
|
|
* if it is an unusual extension.
|
|
* @method getTypeByExtension
|
|
* @param {String} extension The file extension to use to determine the load type.
|
|
* @return {String} The determined load type (for example, <code>AbstractLoader.IMAGE</code>). Will return `null` if
|
|
* the type can not be determined by the extension.
|
|
* @static
|
|
*/
|
|
s.getTypeByExtension = function (extension) {
|
|
if (extension == null) {
|
|
return createjs.AbstractLoader.TEXT;
|
|
}
|
|
|
|
switch (extension.toLowerCase()) {
|
|
case "jpeg":
|
|
case "jpg":
|
|
case "gif":
|
|
case "png":
|
|
case "webp":
|
|
case "bmp":
|
|
return createjs.AbstractLoader.IMAGE;
|
|
case "ogg":
|
|
case "mp3":
|
|
case "webm":
|
|
return createjs.AbstractLoader.SOUND;
|
|
case "mp4":
|
|
case "webm":
|
|
case "ts":
|
|
return createjs.AbstractLoader.VIDEO;
|
|
case "json":
|
|
return createjs.AbstractLoader.JSON;
|
|
case "xml":
|
|
return createjs.AbstractLoader.XML;
|
|
case "css":
|
|
return createjs.AbstractLoader.CSS;
|
|
case "js":
|
|
return createjs.AbstractLoader.JAVASCRIPT;
|
|
case "svg":
|
|
return createjs.AbstractLoader.SVG;
|
|
default:
|
|
return createjs.AbstractLoader.TEXT;
|
|
}
|
|
};
|
|
|
|
createjs.RequestUtils = s;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// AbstractLoader.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* The base loader, which defines all the generic methods, properties, and events. All loaders extend this class,
|
|
* including the {{#crossLink "LoadQueue"}}{{/crossLink}}.
|
|
* @class AbstractLoader
|
|
* @param {LoadItem|object|string} loadItem The item to be loaded.
|
|
* @param {Boolean} [preferXHR] Determines if the LoadItem should <em>try</em> and load using XHR, or take a
|
|
* tag-based approach, which can be better in cross-domain situations. Not all loaders can load using one or the
|
|
* other, so this is a suggested directive.
|
|
* @param {String} [type] The type of loader. Loader types are defined as constants on the AbstractLoader class,
|
|
* such as {{#crossLink "IMAGE:property"}}{{/crossLink}}, {{#crossLink "CSS:property"}}{{/crossLink}}, etc.
|
|
* @extends EventDispatcher
|
|
*/
|
|
function AbstractLoader(loadItem, preferXHR, type) {
|
|
this.EventDispatcher_constructor();
|
|
|
|
// public properties
|
|
/**
|
|
* If the loader has completed loading. This provides a quick check, but also ensures that the different approaches
|
|
* used for loading do not pile up resulting in more than one `complete` {{#crossLink "Event"}}{{/crossLink}}.
|
|
* @property loaded
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
this.loaded = false;
|
|
|
|
/**
|
|
* Determine if the loader was canceled. Canceled loads will not fire complete events. Note that this property
|
|
* is readonly, so {{#crossLink "LoadQueue"}}{{/crossLink}} queues should be closed using {{#crossLink "LoadQueue/close"}}{{/crossLink}}
|
|
* instead.
|
|
* @property canceled
|
|
* @type {Boolean}
|
|
* @default false
|
|
* @readonly
|
|
*/
|
|
this.canceled = false;
|
|
|
|
/**
|
|
* The current load progress (percentage) for this item. This will be a number between 0 and 1.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var queue = new createjs.LoadQueue();
|
|
* queue.loadFile("largeImage.png");
|
|
* queue.on("progress", function() {
|
|
* console.log("Progress:", queue.progress, event.progress);
|
|
* });
|
|
*
|
|
* @property progress
|
|
* @type {Number}
|
|
* @default 0
|
|
*/
|
|
this.progress = 0;
|
|
|
|
/**
|
|
* The type of item this loader will load. See {{#crossLink "AbstractLoader"}}{{/crossLink}} for a full list of
|
|
* supported types.
|
|
* @property type
|
|
* @type {String}
|
|
*/
|
|
this.type = type;
|
|
|
|
/**
|
|
* A formatter function that converts the loaded raw result into the final result. For example, the JSONLoader
|
|
* converts a string of text into a JavaScript object. Not all loaders have a resultFormatter, and this property
|
|
* can be overridden to provide custom formatting.
|
|
*
|
|
* Optionally, a resultFormatter can return a callback function in cases where the formatting needs to be
|
|
* asynchronous, such as creating a new image. The callback function is passed 2 parameters, which are callbacks
|
|
* to handle success and error conditions in the resultFormatter. Note that the resultFormatter method is
|
|
* called in the current scope, as well as the success and error callbacks.
|
|
*
|
|
* <h4>Example asynchronous resultFormatter</h4>
|
|
*
|
|
* function _formatResult(loader) {
|
|
* return function(success, error) {
|
|
* if (errorCondition) { error(errorDetailEvent); }
|
|
* success(result);
|
|
* }
|
|
* }
|
|
* @property resultFormatter
|
|
* @type {Function}
|
|
* @default null
|
|
*/
|
|
this.resultFormatter = null;
|
|
|
|
// protected properties
|
|
/**
|
|
* The {{#crossLink "LoadItem"}}{{/crossLink}} this loader represents. Note that this is null in a {{#crossLink "LoadQueue"}}{{/crossLink}},
|
|
* but will be available on loaders such as {{#crossLink "XMLLoader"}}{{/crossLink}} and {{#crossLink "ImageLoader"}}{{/crossLink}}.
|
|
* @property _item
|
|
* @type {LoadItem|Object}
|
|
* @private
|
|
*/
|
|
if (loadItem) {
|
|
this._item = createjs.LoadItem.create(loadItem);
|
|
} else {
|
|
this._item = null;
|
|
}
|
|
|
|
/**
|
|
* Whether the loader will try and load content using XHR (true) or HTML tags (false).
|
|
* @property _preferXHR
|
|
* @type {Boolean}
|
|
* @private
|
|
*/
|
|
this._preferXHR = preferXHR;
|
|
|
|
/**
|
|
* The loaded result after it is formatted by an optional {{#crossLink "resultFormatter"}}{{/crossLink}}. For
|
|
* items that are not formatted, this will be the same as the {{#crossLink "_rawResult:property"}}{{/crossLink}}.
|
|
* The result is accessed using the {{#crossLink "getResult"}}{{/crossLink}} method.
|
|
* @property _result
|
|
* @type {Object|String}
|
|
* @private
|
|
*/
|
|
this._result = null;
|
|
|
|
/**
|
|
* The loaded result before it is formatted. The rawResult is accessed using the {{#crossLink "getResult"}}{{/crossLink}}
|
|
* method, and passing `true`.
|
|
* @property _rawResult
|
|
* @type {Object|String}
|
|
* @private
|
|
*/
|
|
this._rawResult = null;
|
|
|
|
/**
|
|
* A list of items that loaders load behind the scenes. This does not include the main item the loader is
|
|
* responsible for loading. Examples of loaders that have sub-items include the {{#crossLink "SpriteSheetLoader"}}{{/crossLink}} and
|
|
* {{#crossLink "ManifestLoader"}}{{/crossLink}}.
|
|
* @property _loadItems
|
|
* @type {null}
|
|
* @protected
|
|
*/
|
|
this._loadedItems = null;
|
|
|
|
/**
|
|
* The attribute the items loaded using tags use for the source.
|
|
* @type {string}
|
|
* @default null
|
|
* @private
|
|
*/
|
|
this._tagSrcAttribute = null;
|
|
|
|
/**
|
|
* An HTML tag (or similar) that a loader may use to load HTML content, such as images, scripts, etc.
|
|
* @property _tag
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
this._tag = null;
|
|
}
|
|
|
|
var p = createjs.extend(AbstractLoader, createjs.EventDispatcher);
|
|
var s = AbstractLoader;
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
/**
|
|
* Defines a POST request, use for a method value when loading data.
|
|
* @property POST
|
|
* @type {string}
|
|
* @default post
|
|
* @static
|
|
*/
|
|
s.POST = "POST";
|
|
|
|
/**
|
|
* Defines a GET request, use for a method value when loading data.
|
|
* @property GET
|
|
* @type {string}
|
|
* @default get
|
|
* @static
|
|
*/
|
|
s.GET = "GET";
|
|
|
|
/**
|
|
* The preload type for generic binary types. Note that images are loaded as binary files when using XHR.
|
|
* @property BINARY
|
|
* @type {String}
|
|
* @default binary
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s.BINARY = "binary";
|
|
|
|
/**
|
|
* The preload type for css files. CSS files are loaded using a <link> when loaded with XHR, or a
|
|
* <style> tag when loaded with tags.
|
|
* @property CSS
|
|
* @type {String}
|
|
* @default css
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s.CSS = "css";
|
|
|
|
/**
|
|
* The preload type for image files, usually png, gif, or jpg/jpeg. Images are loaded into an <image> tag.
|
|
* @property IMAGE
|
|
* @type {String}
|
|
* @default image
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s.IMAGE = "image";
|
|
|
|
/**
|
|
* The preload type for javascript files, usually with the "js" file extension. JavaScript files are loaded into a
|
|
* <script> tag.
|
|
*
|
|
* Since version 0.4.1+, due to how tag-loaded scripts work, all JavaScript files are automatically injected into
|
|
* the body of the document to maintain parity between XHR and tag-loaded scripts. In version 0.4.0 and earlier,
|
|
* only tag-loaded scripts are injected.
|
|
* @property JAVASCRIPT
|
|
* @type {String}
|
|
* @default javascript
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s.JAVASCRIPT = "javascript";
|
|
|
|
/**
|
|
* The preload type for json files, usually with the "json" file extension. JSON data is loaded and parsed into a
|
|
* JavaScript object. Note that if a `callback` is present on the load item, the file will be loaded with JSONP,
|
|
* no matter what the {{#crossLink "LoadQueue/preferXHR:property"}}{{/crossLink}} property is set to, and the JSON
|
|
* must contain a matching wrapper function.
|
|
* @property JSON
|
|
* @type {String}
|
|
* @default json
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s.JSON = "json";
|
|
|
|
/**
|
|
* The preload type for jsonp files, usually with the "json" file extension. JSON data is loaded and parsed into a
|
|
* JavaScript object. You are required to pass a callback parameter that matches the function wrapper in the JSON.
|
|
* Note that JSONP will always be used if there is a callback present, no matter what the {{#crossLink "LoadQueue/preferXHR:property"}}{{/crossLink}}
|
|
* property is set to.
|
|
* @property JSONP
|
|
* @type {String}
|
|
* @default jsonp
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s.JSONP = "jsonp";
|
|
|
|
/**
|
|
* The preload type for json-based manifest files, usually with the "json" file extension. The JSON data is loaded
|
|
* and parsed into a JavaScript object. PreloadJS will then look for a "manifest" property in the JSON, which is an
|
|
* Array of files to load, following the same format as the {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}}
|
|
* method. If a "callback" is specified on the manifest object, then it will be loaded using JSONP instead,
|
|
* regardless of what the {{#crossLink "LoadQueue/preferXHR:property"}}{{/crossLink}} property is set to.
|
|
* @property MANIFEST
|
|
* @type {String}
|
|
* @default manifest
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s.MANIFEST = "manifest";
|
|
|
|
/**
|
|
* The preload type for sound files, usually mp3, ogg, or wav. When loading via tags, audio is loaded into an
|
|
* <audio> tag.
|
|
* @property SOUND
|
|
* @type {String}
|
|
* @default sound
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s.SOUND = "sound";
|
|
|
|
/**
|
|
* The preload type for video files, usually mp4, ts, or ogg. When loading via tags, video is loaded into an
|
|
* <video> tag.
|
|
* @property VIDEO
|
|
* @type {String}
|
|
* @default video
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s.VIDEO = "video";
|
|
|
|
/**
|
|
* The preload type for SpriteSheet files. SpriteSheet files are JSON files that contain string image paths.
|
|
* @property SPRITESHEET
|
|
* @type {String}
|
|
* @default spritesheet
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s.SPRITESHEET = "spritesheet";
|
|
|
|
/**
|
|
* The preload type for SVG files.
|
|
* @property SVG
|
|
* @type {String}
|
|
* @default svg
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s.SVG = "svg";
|
|
|
|
/**
|
|
* The preload type for text files, which is also the default file type if the type can not be determined. Text is
|
|
* loaded as raw text.
|
|
* @property TEXT
|
|
* @type {String}
|
|
* @default text
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s.TEXT = "text";
|
|
|
|
/**
|
|
* The preload type for xml files. XML is loaded into an XML document.
|
|
* @property XML
|
|
* @type {String}
|
|
* @default xml
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s.XML = "xml";
|
|
|
|
// Events
|
|
/**
|
|
* The {{#crossLink "ProgressEvent"}}{{/crossLink}} that is fired when the overall progress changes. Prior to
|
|
* version 0.6.0, this was just a regular {{#crossLink "Event"}}{{/crossLink}}.
|
|
* @event progress
|
|
* @since 0.3.0
|
|
*/
|
|
|
|
/**
|
|
* The {{#crossLink "Event"}}{{/crossLink}} that is fired when a load starts.
|
|
* @event loadstart
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @since 0.3.1
|
|
*/
|
|
|
|
/**
|
|
* The {{#crossLink "Event"}}{{/crossLink}} that is fired when the entire queue has been loaded.
|
|
* @event complete
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @since 0.3.0
|
|
*/
|
|
|
|
/**
|
|
* The {{#crossLink "ErrorEvent"}}{{/crossLink}} that is fired when the loader encounters an error. If the error was
|
|
* encountered by a file, the event will contain the item that caused the error. Prior to version 0.6.0, this was
|
|
* just a regular {{#crossLink "Event"}}{{/crossLink}}.
|
|
* @event error
|
|
* @since 0.3.0
|
|
*/
|
|
|
|
/**
|
|
* The {{#crossLink "Event"}}{{/crossLink}} that is fired when the loader encounters an internal file load error.
|
|
* This enables loaders to maintain internal queues, and surface file load errors.
|
|
* @event fileerror
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The even type ("fileerror")
|
|
* @param {LoadItem|object} The item that encountered the error
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
/**
|
|
* The {{#crossLink "Event"}}{{/crossLink}} that is fired when a loader internally loads a file. This enables
|
|
* loaders such as {{#crossLink "ManifestLoader"}}{{/crossLink}} to maintain internal {{#crossLink "LoadQueue"}}{{/crossLink}}s
|
|
* and notify when they have loaded a file. The {{#crossLink "LoadQueue"}}{{/crossLink}} class dispatches a
|
|
* slightly different {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} event.
|
|
* @event fileload
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type ("fileload")
|
|
* @param {Object} item The file item which was specified in the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}}
|
|
* or {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} call. If only a string path or tag was specified, the
|
|
* object will contain that value as a `src` property.
|
|
* @param {Object} result The HTML tag or parsed result of the loaded item.
|
|
* @param {Object} rawResult The unprocessed result, usually the raw text or binary data before it is converted
|
|
* to a usable object.
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
/**
|
|
* The {{#crossLink "Event"}}{{/crossLink}} that is fired after the internal request is created, but before a load.
|
|
* This allows updates to the loader for specific loading needs, such as binary or XHR image loading.
|
|
* @event initialize
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type ("initialize")
|
|
* @param {AbstractLoader} loader The loader that has been initialized.
|
|
*/
|
|
|
|
/**
|
|
* Get a reference to the manifest item that is loaded by this loader. In some cases this will be the value that was
|
|
* passed into {{#crossLink "LoadQueue"}}{{/crossLink}} using {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} or
|
|
* {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}}. However if only a String path was passed in, then it will
|
|
* be a {{#crossLink "LoadItem"}}{{/crossLink}}.
|
|
* @method getItem
|
|
* @return {Object} The manifest item that this loader is responsible for loading.
|
|
* @since 0.6.0
|
|
*/
|
|
p.getItem = function () {
|
|
return this._item;
|
|
};
|
|
|
|
/**
|
|
* Get a reference to the content that was loaded by the loader (only available after the {{#crossLink "complete:event"}}{{/crossLink}}
|
|
* event is dispatched.
|
|
* @method getResult
|
|
* @param {Boolean} [raw=false] Determines if the returned result will be the formatted content, or the raw loaded
|
|
* data (if it exists).
|
|
* @return {Object}
|
|
* @since 0.6.0
|
|
*/
|
|
p.getResult = function (raw) {
|
|
return raw ? this._rawResult : this._result;
|
|
};
|
|
|
|
/**
|
|
* Return the `tag` this object creates or uses for loading.
|
|
* @method getTag
|
|
* @return {Object} The tag instance
|
|
* @since 0.6.0
|
|
*/
|
|
p.getTag = function () {
|
|
return this._tag;
|
|
};
|
|
|
|
/**
|
|
* Set the `tag` this item uses for loading.
|
|
* @method setTag
|
|
* @param {Object} tag The tag instance
|
|
* @since 0.6.0
|
|
*/
|
|
p.setTag = function (tag) {
|
|
this._tag = tag;
|
|
};
|
|
|
|
/**
|
|
* Begin loading the item. This method is required when using a loader by itself.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var queue = new createjs.LoadQueue();
|
|
* queue.on("complete", handleComplete);
|
|
* queue.loadManifest(fileArray, false); // Note the 2nd argument that tells the queue not to start loading yet
|
|
* queue.load();
|
|
*
|
|
* @method load
|
|
*/
|
|
p.load = function () {
|
|
this._createRequest();
|
|
|
|
this._request.on("complete", this, this);
|
|
this._request.on("progress", this, this);
|
|
this._request.on("loadStart", this, this);
|
|
this._request.on("abort", this, this);
|
|
this._request.on("timeout", this, this);
|
|
this._request.on("error", this, this);
|
|
|
|
var evt = new createjs.Event("initialize");
|
|
evt.loader = this._request;
|
|
this.dispatchEvent(evt);
|
|
|
|
this._request.load();
|
|
};
|
|
|
|
/**
|
|
* Close the the item. This will stop any open requests (although downloads using HTML tags may still continue in
|
|
* the background), but events will not longer be dispatched.
|
|
* @method cancel
|
|
*/
|
|
p.cancel = function () {
|
|
this.canceled = true;
|
|
this.destroy();
|
|
};
|
|
|
|
/**
|
|
* Clean up the loader.
|
|
* @method destroy
|
|
*/
|
|
p.destroy = function () {
|
|
if (this._request) {
|
|
this._request.removeAllEventListeners();
|
|
this._request.destroy();
|
|
}
|
|
|
|
this._request = null;
|
|
|
|
this._item = null;
|
|
this._rawResult = null;
|
|
this._result = null;
|
|
|
|
this._loadItems = null;
|
|
|
|
this.removeAllEventListeners();
|
|
};
|
|
|
|
/**
|
|
* Get any items loaded internally by the loader. The enables loaders such as {{#crossLink "ManifestLoader"}}{{/crossLink}}
|
|
* to expose items it loads internally.
|
|
* @method getLoadedItems
|
|
* @return {Array} A list of the items loaded by the loader.
|
|
* @since 0.6.0
|
|
*/
|
|
p.getLoadedItems = function () {
|
|
return this._loadedItems;
|
|
};
|
|
|
|
// Private methods
|
|
/**
|
|
* Create an internal request used for loading. By default, an {{#crossLink "XHRRequest"}}{{/crossLink}} or
|
|
* {{#crossLink "TagRequest"}}{{/crossLink}} is created, depending on the value of {{#crossLink "preferXHR:property"}}{{/crossLink}}.
|
|
* Other loaders may override this to use different request types, such as {{#crossLink "ManifestLoader"}}{{/crossLink}},
|
|
* which uses {{#crossLink "JSONLoader"}}{{/crossLink}} or {{#crossLink "JSONPLoader"}}{{/crossLink}} under the hood.
|
|
* @method _createRequest
|
|
* @protected
|
|
*/
|
|
p._createRequest = function () {
|
|
if (!this._preferXHR) {
|
|
this._request = new createjs.TagRequest(
|
|
this._item,
|
|
this._tag || this._createTag(),
|
|
this._tagSrcAttribute
|
|
);
|
|
} else {
|
|
this._request = new createjs.XHRRequest(this._item);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create the HTML tag used for loading. This method does nothing by default, and needs to be implemented
|
|
* by loaders that require tag loading.
|
|
* @method _createTag
|
|
* @param {String} src The tag source
|
|
* @return {HTMLElement} The tag that was created
|
|
* @protected
|
|
*/
|
|
p._createTag = function (src) {
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Dispatch a loadstart {{#crossLink "Event"}}{{/crossLink}}. Please see the {{#crossLink "AbstractLoader/loadstart:event"}}{{/crossLink}}
|
|
* event for details on the event payload.
|
|
* @method _sendLoadStart
|
|
* @protected
|
|
*/
|
|
p._sendLoadStart = function () {
|
|
if (this._isCanceled()) {
|
|
return;
|
|
}
|
|
this.dispatchEvent("loadstart");
|
|
};
|
|
|
|
/**
|
|
* Dispatch a {{#crossLink "ProgressEvent"}}{{/crossLink}}.
|
|
* @method _sendProgress
|
|
* @param {Number | Object} value The progress of the loaded item, or an object containing <code>loaded</code>
|
|
* and <code>total</code> properties.
|
|
* @protected
|
|
*/
|
|
p._sendProgress = function (value) {
|
|
if (this._isCanceled()) {
|
|
return;
|
|
}
|
|
var event = null;
|
|
if (typeof value == "number") {
|
|
this.progress = value;
|
|
event = new createjs.ProgressEvent(this.progress);
|
|
} else {
|
|
event = value;
|
|
this.progress = value.loaded / value.total;
|
|
event.progress = this.progress;
|
|
if (isNaN(this.progress) || this.progress == Infinity) {
|
|
this.progress = 0;
|
|
}
|
|
}
|
|
this.hasEventListener("progress") && this.dispatchEvent(event);
|
|
};
|
|
|
|
/**
|
|
* Dispatch a complete {{#crossLink "Event"}}{{/crossLink}}. Please see the {{#crossLink "AbstractLoader/complete:event"}}{{/crossLink}} event
|
|
* @method _sendComplete
|
|
* @protected
|
|
*/
|
|
p._sendComplete = function () {
|
|
if (this._isCanceled()) {
|
|
return;
|
|
}
|
|
|
|
this.loaded = true;
|
|
|
|
var event = new createjs.Event("complete");
|
|
event.rawResult = this._rawResult;
|
|
|
|
if (this._result != null) {
|
|
event.result = this._result;
|
|
}
|
|
|
|
this.dispatchEvent(event);
|
|
};
|
|
|
|
/**
|
|
* Dispatch an error {{#crossLink "Event"}}{{/crossLink}}. Please see the {{#crossLink "AbstractLoader/error:event"}}{{/crossLink}}
|
|
* event for details on the event payload.
|
|
* @method _sendError
|
|
* @param {ErrorEvent} event The event object containing specific error properties.
|
|
* @protected
|
|
*/
|
|
p._sendError = function (event) {
|
|
if (this._isCanceled() || !this.hasEventListener("error")) {
|
|
return;
|
|
}
|
|
if (event == null) {
|
|
event = new createjs.ErrorEvent("PRELOAD_ERROR_EMPTY"); // TODO: Populate error
|
|
}
|
|
this.dispatchEvent(event);
|
|
};
|
|
|
|
/**
|
|
* Determine if the load has been canceled. This is important to ensure that method calls or asynchronous events
|
|
* do not cause issues after the queue has been cleaned up.
|
|
* @method _isCanceled
|
|
* @return {Boolean} If the loader has been canceled.
|
|
* @protected
|
|
*/
|
|
p._isCanceled = function () {
|
|
if (window.createjs == null || this.canceled) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* A custom result formatter function, which is called just before a request dispatches its complete event. Most
|
|
* loader types already have an internal formatter, but this can be user-overridden for custom formatting. The
|
|
* formatted result will be available on Loaders using {{#crossLink "getResult"}}{{/crossLink}}, and passing `true`.
|
|
* @property resultFormatter
|
|
* @type Function
|
|
* @return {Object} The formatted result
|
|
* @since 0.6.0
|
|
*/
|
|
p.resultFormatter = null;
|
|
|
|
/**
|
|
* Handle events from internal requests. By default, loaders will handle, and redispatch the necessary events, but
|
|
* this method can be overridden for custom behaviours.
|
|
* @method handleEvent
|
|
* @param {Event} event The event that the internal request dispatches.
|
|
* @protected
|
|
* @since 0.6.0
|
|
*/
|
|
p.handleEvent = function (event) {
|
|
switch (event.type) {
|
|
case "complete":
|
|
this._rawResult = event.target._response;
|
|
var result = this.resultFormatter && this.resultFormatter(this);
|
|
if (result instanceof Function) {
|
|
result.call(
|
|
this,
|
|
createjs.proxy(this._resultFormatSuccess, this),
|
|
createjs.proxy(this._resultFormatFailed, this)
|
|
);
|
|
} else {
|
|
this._result = result || this._rawResult;
|
|
this._sendComplete();
|
|
}
|
|
break;
|
|
case "progress":
|
|
this._sendProgress(event);
|
|
break;
|
|
case "error":
|
|
this._sendError(event);
|
|
break;
|
|
case "loadstart":
|
|
this._sendLoadStart();
|
|
break;
|
|
case "abort":
|
|
case "timeout":
|
|
if (!this._isCanceled()) {
|
|
this.dispatchEvent(
|
|
new createjs.ErrorEvent(
|
|
"PRELOAD_" + event.type.toUpperCase() + "_ERROR"
|
|
)
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The "success" callback passed to {{#crossLink "AbstractLoader/resultFormatter"}}{{/crossLink}} asynchronous
|
|
* functions.
|
|
* @method _resultFormatSuccess
|
|
* @param {Object} result The formatted result
|
|
* @private
|
|
*/
|
|
p._resultFormatSuccess = function (result) {
|
|
this._result = result;
|
|
this._sendComplete();
|
|
};
|
|
|
|
/**
|
|
* The "error" callback passed to {{#crossLink "AbstractLoader/resultFormatter"}}{{/crossLink}} asynchronous
|
|
* functions.
|
|
* @method _resultFormatSuccess
|
|
* @param {Object} error The error event
|
|
* @private
|
|
*/
|
|
p._resultFormatFailed = function (event) {
|
|
this._sendError(event);
|
|
};
|
|
|
|
/**
|
|
* @method buildPath
|
|
* @protected
|
|
* @deprecated Use the {{#crossLink "RequestUtils"}}{{/crossLink}} method {{#crossLink "RequestUtils/buildPath"}}{{/crossLink}}
|
|
* instead.
|
|
*/
|
|
p.buildPath = function (src, data) {
|
|
return createjs.RequestUtils.buildPath(src, data);
|
|
};
|
|
|
|
/**
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
*/
|
|
p.toString = function () {
|
|
return "[PreloadJS AbstractLoader]";
|
|
};
|
|
|
|
createjs.AbstractLoader = createjs.promote(AbstractLoader, "EventDispatcher");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// AbstractMediaLoader.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* The AbstractMediaLoader is a base class that handles some of the shared methods and properties of loaders that
|
|
* handle HTML media elements, such as Video and Audio.
|
|
* @class AbstractMediaLoader
|
|
* @param {LoadItem|Object} loadItem
|
|
* @param {Boolean} preferXHR
|
|
* @param {String} type The type of media to load. Usually "video" or "audio".
|
|
* @extends AbstractLoader
|
|
* @constructor
|
|
*/
|
|
function AbstractMediaLoader(loadItem, preferXHR, type) {
|
|
this.AbstractLoader_constructor(loadItem, preferXHR, type);
|
|
|
|
// public properties
|
|
this.resultFormatter = this._formatResult;
|
|
|
|
// protected properties
|
|
this._tagSrcAttribute = "src";
|
|
|
|
this.on("initialize", this._updateXHR, this);
|
|
}
|
|
|
|
var p = createjs.extend(AbstractMediaLoader, createjs.AbstractLoader);
|
|
|
|
// static properties
|
|
// public methods
|
|
p.load = function () {
|
|
// TagRequest will handle most of this, but Sound / Video need a few custom properties, so just handle them here.
|
|
if (!this._tag) {
|
|
this._tag = this._createTag(this._item.src);
|
|
}
|
|
|
|
this._tag.preload = "auto";
|
|
this._tag.load();
|
|
|
|
this.AbstractLoader_load();
|
|
};
|
|
|
|
// protected methods
|
|
/**
|
|
* Creates a new tag for loading if it doesn't exist yet.
|
|
* @method _createTag
|
|
* @private
|
|
*/
|
|
p._createTag = function () {};
|
|
|
|
p._createRequest = function () {
|
|
if (!this._preferXHR) {
|
|
this._request = new createjs.MediaTagRequest(
|
|
this._item,
|
|
this._tag || this._createTag(),
|
|
this._tagSrcAttribute
|
|
);
|
|
} else {
|
|
this._request = new createjs.XHRRequest(this._item);
|
|
}
|
|
};
|
|
|
|
// protected methods
|
|
/**
|
|
* Before the item loads, set its mimeType and responseType.
|
|
* @property _updateXHR
|
|
* @param {Event} event
|
|
* @private
|
|
*/
|
|
p._updateXHR = function (event) {
|
|
// Only exists for XHR
|
|
if (event.loader.setResponseType) {
|
|
event.loader.setResponseType("blob");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The result formatter for media files.
|
|
* @method _formatResult
|
|
* @param {AbstractLoader} loader
|
|
* @returns {HTMLVideoElement|HTMLAudioElement}
|
|
* @private
|
|
*/
|
|
p._formatResult = function (loader) {
|
|
this._tag.removeEventListener &&
|
|
this._tag.removeEventListener("canplaythrough", this._loadedHandler);
|
|
this._tag.onstalled = null;
|
|
if (this._preferXHR) {
|
|
var URL = window.URL || window.webkitURL;
|
|
var result = loader.getResult(true);
|
|
|
|
loader.getTag().src = URL.createObjectURL(result);
|
|
}
|
|
return loader.getTag();
|
|
};
|
|
|
|
createjs.AbstractMediaLoader = createjs.promote(
|
|
AbstractMediaLoader,
|
|
"AbstractLoader"
|
|
);
|
|
})();
|
|
|
|
//##############################################################################
|
|
// AbstractRequest.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* A base class for actual data requests, such as {{#crossLink "XHRRequest"}}{{/crossLink}}, {{#crossLink "TagRequest"}}{{/crossLink}},
|
|
* and {{#crossLink "MediaRequest"}}{{/crossLink}}. PreloadJS loaders will typically use a data loader under the
|
|
* hood to get data.
|
|
* @class AbstractRequest
|
|
* @param {LoadItem} item
|
|
* @constructor
|
|
*/
|
|
var AbstractRequest = function (item) {
|
|
this._item = item;
|
|
};
|
|
|
|
var p = createjs.extend(AbstractRequest, createjs.EventDispatcher);
|
|
|
|
// public methods
|
|
/**
|
|
* Begin a load.
|
|
* @method load
|
|
*/
|
|
p.load = function () {};
|
|
|
|
/**
|
|
* Clean up a request.
|
|
* @method destroy
|
|
*/
|
|
p.destroy = function () {};
|
|
|
|
/**
|
|
* Cancel an in-progress request.
|
|
* @method cancel
|
|
*/
|
|
p.cancel = function () {};
|
|
|
|
createjs.AbstractRequest = createjs.promote(
|
|
AbstractRequest,
|
|
"EventDispatcher"
|
|
);
|
|
})();
|
|
|
|
//##############################################################################
|
|
// TagRequest.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* An {{#crossLink "AbstractRequest"}}{{/crossLink}} that loads HTML tags, such as images and scripts.
|
|
* @class TagRequest
|
|
* @param {LoadItem} loadItem
|
|
* @param {HTMLElement} tag
|
|
* @param {String} srcAttribute The tag attribute that specifies the source, such as "src", "href", etc.
|
|
*/
|
|
function TagRequest(loadItem, tag, srcAttribute) {
|
|
this.AbstractRequest_constructor(loadItem);
|
|
|
|
// protected properties
|
|
/**
|
|
* The HTML tag instance that is used to load.
|
|
* @property _tag
|
|
* @type {HTMLElement}
|
|
* @protected
|
|
*/
|
|
this._tag = tag;
|
|
|
|
/**
|
|
* The tag attribute that specifies the source, such as "src", "href", etc.
|
|
* @property _tagSrcAttribute
|
|
* @type {String}
|
|
* @protected
|
|
*/
|
|
this._tagSrcAttribute = srcAttribute;
|
|
|
|
/**
|
|
* A method closure used for handling the tag load event.
|
|
* @property _loadedHandler
|
|
* @type {Function}
|
|
* @private
|
|
*/
|
|
this._loadedHandler = createjs.proxy(this._handleTagComplete, this);
|
|
|
|
/**
|
|
* Determines if the element was added to the DOM automatically by PreloadJS, so it can be cleaned up after.
|
|
* @property _addedToDOM
|
|
* @type {Boolean}
|
|
* @private
|
|
*/
|
|
this._addedToDOM = false;
|
|
|
|
/**
|
|
* Determines what the tags initial style.visibility was, so we can set it correctly after a load.
|
|
*
|
|
* @type {null}
|
|
* @private
|
|
*/
|
|
this._startTagVisibility = null;
|
|
}
|
|
|
|
var p = createjs.extend(TagRequest, createjs.AbstractRequest);
|
|
|
|
// public methods
|
|
p.load = function () {
|
|
this._tag.onload = createjs.proxy(this._handleTagComplete, this);
|
|
this._tag.onreadystatechange = createjs.proxy(
|
|
this._handleReadyStateChange,
|
|
this
|
|
);
|
|
this._tag.onerror = createjs.proxy(this._handleError, this);
|
|
|
|
var evt = new createjs.Event("initialize");
|
|
evt.loader = this._tag;
|
|
|
|
this.dispatchEvent(evt);
|
|
|
|
this._hideTag();
|
|
|
|
this._loadTimeout = setTimeout(
|
|
createjs.proxy(this._handleTimeout, this),
|
|
this._item.loadTimeout
|
|
);
|
|
|
|
this._tag[this._tagSrcAttribute] = this._item.src;
|
|
|
|
// wdg:: Append the tag AFTER setting the src, or SVG loading on iOS will fail.
|
|
if (this._tag.parentNode == null) {
|
|
window.document.body.appendChild(this._tag);
|
|
this._addedToDOM = true;
|
|
}
|
|
};
|
|
|
|
p.destroy = function () {
|
|
this._clean();
|
|
this._tag = null;
|
|
|
|
this.AbstractRequest_destroy();
|
|
};
|
|
|
|
// private methods
|
|
/**
|
|
* Handle the readyStateChange event from a tag. We need this in place of the `onload` callback (mainly SCRIPT
|
|
* and LINK tags), but other cases may exist.
|
|
* @method _handleReadyStateChange
|
|
* @private
|
|
*/
|
|
p._handleReadyStateChange = function () {
|
|
clearTimeout(this._loadTimeout);
|
|
// This is strictly for tags in browsers that do not support onload.
|
|
var tag = this._tag;
|
|
|
|
// Complete is for old IE support.
|
|
if (tag.readyState == "loaded" || tag.readyState == "complete") {
|
|
this._handleTagComplete();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle any error events from the tag.
|
|
* @method _handleError
|
|
* @protected
|
|
*/
|
|
p._handleError = function () {
|
|
this._clean();
|
|
this.dispatchEvent("error");
|
|
};
|
|
|
|
/**
|
|
* Handle the tag's onload callback.
|
|
* @method _handleTagComplete
|
|
* @private
|
|
*/
|
|
p._handleTagComplete = function () {
|
|
this._rawResult = this._tag;
|
|
this._result =
|
|
(this.resultFormatter && this.resultFormatter(this)) || this._rawResult;
|
|
|
|
this._clean();
|
|
this._showTag();
|
|
|
|
this.dispatchEvent("complete");
|
|
};
|
|
|
|
/**
|
|
* The tag request has not loaded within the time specified in loadTimeout.
|
|
* @method _handleError
|
|
* @param {Object} event The XHR error event.
|
|
* @private
|
|
*/
|
|
p._handleTimeout = function () {
|
|
this._clean();
|
|
this.dispatchEvent(new createjs.Event("timeout"));
|
|
};
|
|
|
|
/**
|
|
* Remove event listeners, but don't destroy the request object
|
|
* @method _clean
|
|
* @private
|
|
*/
|
|
p._clean = function () {
|
|
this._tag.onload = null;
|
|
this._tag.onreadystatechange = null;
|
|
this._tag.onerror = null;
|
|
if (this._addedToDOM && this._tag.parentNode != null) {
|
|
this._tag.parentNode.removeChild(this._tag);
|
|
}
|
|
clearTimeout(this._loadTimeout);
|
|
};
|
|
|
|
p._hideTag = function () {
|
|
this._startTagVisibility = this._tag.style.visibility;
|
|
this._tag.style.visibility = "hidden";
|
|
};
|
|
|
|
p._showTag = function () {
|
|
this._tag.style.visibility = this._startTagVisibility;
|
|
};
|
|
|
|
/**
|
|
* Handle a stalled audio event. The main place this happens is with HTMLAudio in Chrome when playing back audio
|
|
* that is already in a load, but not complete.
|
|
* @method _handleStalled
|
|
* @private
|
|
*/
|
|
p._handleStalled = function () {
|
|
//Ignore, let the timeout take care of it. Sometimes its not really stopped.
|
|
};
|
|
|
|
createjs.TagRequest = createjs.promote(TagRequest, "AbstractRequest");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// MediaTagRequest.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* An {{#crossLink "TagRequest"}}{{/crossLink}} that loads HTML tags for video and audio.
|
|
* @class MediaTagRequest
|
|
* @param {LoadItem} loadItem
|
|
* @param {HTMLAudioElement|HTMLVideoElement} tag
|
|
* @param {String} srcAttribute The tag attribute that specifies the source, such as "src", "href", etc.
|
|
* @constructor
|
|
*/
|
|
function MediaTagRequest(loadItem, tag, srcAttribute) {
|
|
this.AbstractRequest_constructor(loadItem);
|
|
|
|
// protected properties
|
|
this._tag = tag;
|
|
this._tagSrcAttribute = srcAttribute;
|
|
this._loadedHandler = createjs.proxy(this._handleTagComplete, this);
|
|
}
|
|
|
|
var p = createjs.extend(MediaTagRequest, createjs.TagRequest);
|
|
var s = MediaTagRequest;
|
|
|
|
// public methods
|
|
p.load = function () {
|
|
var sc = createjs.proxy(this._handleStalled, this);
|
|
this._stalledCallback = sc;
|
|
|
|
var pc = createjs.proxy(this._handleProgress, this);
|
|
this._handleProgress = pc;
|
|
|
|
this._tag.addEventListener("stalled", sc);
|
|
this._tag.addEventListener("progress", pc);
|
|
|
|
// This will tell us when audio is buffered enough to play through, but not when its loaded.
|
|
// The tag doesn't keep loading in Chrome once enough has buffered, and we have decided that behaviour is sufficient.
|
|
this._tag.addEventListener &&
|
|
this._tag.addEventListener("canplaythrough", this._loadedHandler, false); // canplaythrough callback doesn't work in Chrome, so we use an event.
|
|
|
|
this.TagRequest_load();
|
|
};
|
|
|
|
// private methods
|
|
p._handleReadyStateChange = function () {
|
|
clearTimeout(this._loadTimeout);
|
|
// This is strictly for tags in browsers that do not support onload.
|
|
var tag = this._tag;
|
|
|
|
// Complete is for old IE support.
|
|
if (tag.readyState == "loaded" || tag.readyState == "complete") {
|
|
this._handleTagComplete();
|
|
}
|
|
};
|
|
|
|
p._handleStalled = function () {
|
|
//Ignore, let the timeout take care of it. Sometimes its not really stopped.
|
|
};
|
|
|
|
/**
|
|
* An XHR request has reported progress.
|
|
* @method _handleProgress
|
|
* @param {Object} event The XHR progress event.
|
|
* @private
|
|
*/
|
|
p._handleProgress = function (event) {
|
|
if (!event || (event.loaded > 0 && event.total == 0)) {
|
|
return; // Sometimes we get no "total", so just ignore the progress event.
|
|
}
|
|
|
|
var newEvent = new createjs.ProgressEvent(event.loaded, event.total);
|
|
this.dispatchEvent(newEvent);
|
|
};
|
|
|
|
// protected methods
|
|
p._clean = function () {
|
|
this._tag.removeEventListener &&
|
|
this._tag.removeEventListener("canplaythrough", this._loadedHandler);
|
|
this._tag.removeEventListener("stalled", this._stalledCallback);
|
|
this._tag.removeEventListener("progress", this._progressCallback);
|
|
|
|
this.TagRequest__clean();
|
|
};
|
|
|
|
createjs.MediaTagRequest = createjs.promote(MediaTagRequest, "TagRequest");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// XHRRequest.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* A preloader that loads items using XHR requests, usually XMLHttpRequest. However XDomainRequests will be used
|
|
* for cross-domain requests if possible, and older versions of IE fall back on to ActiveX objects when necessary.
|
|
* XHR requests load the content as text or binary data, provide progress and consistent completion events, and
|
|
* can be canceled during load. Note that XHR is not supported in IE 6 or earlier, and is not recommended for
|
|
* cross-domain loading.
|
|
* @class XHRRequest
|
|
* @constructor
|
|
* @param {Object} item The object that defines the file to load. Please see the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}}
|
|
* for an overview of supported file properties.
|
|
* @extends AbstractLoader
|
|
*/
|
|
function XHRRequest(item) {
|
|
this.AbstractRequest_constructor(item);
|
|
|
|
// protected properties
|
|
/**
|
|
* A reference to the XHR request used to load the content.
|
|
* @property _request
|
|
* @type {XMLHttpRequest | XDomainRequest | ActiveX.XMLHTTP}
|
|
* @private
|
|
*/
|
|
this._request = null;
|
|
|
|
/**
|
|
* A manual load timeout that is used for browsers that do not support the onTimeout event on XHR (XHR level 1,
|
|
* typically IE9).
|
|
* @property _loadTimeout
|
|
* @type {Number}
|
|
* @private
|
|
*/
|
|
this._loadTimeout = null;
|
|
|
|
/**
|
|
* The browser's XHR (XMLHTTPRequest) version. Supported versions are 1 and 2. There is no official way to detect
|
|
* the version, so we use capabilities to make a best guess.
|
|
* @property _xhrLevel
|
|
* @type {Number}
|
|
* @default 1
|
|
* @private
|
|
*/
|
|
this._xhrLevel = 1;
|
|
|
|
/**
|
|
* The response of a loaded file. This is set because it is expensive to look up constantly. This property will be
|
|
* null until the file is loaded.
|
|
* @property _response
|
|
* @type {mixed}
|
|
* @private
|
|
*/
|
|
this._response = null;
|
|
|
|
/**
|
|
* The response of the loaded file before it is modified. In most cases, content is converted from raw text to
|
|
* an HTML tag or a formatted object which is set to the <code>result</code> property, but the developer may still
|
|
* want to access the raw content as it was loaded.
|
|
* @property _rawResponse
|
|
* @type {String|Object}
|
|
* @private
|
|
*/
|
|
this._rawResponse = null;
|
|
|
|
this._canceled = false;
|
|
|
|
// Setup our event handlers now.
|
|
this._handleLoadStartProxy = createjs.proxy(this._handleLoadStart, this);
|
|
this._handleProgressProxy = createjs.proxy(this._handleProgress, this);
|
|
this._handleAbortProxy = createjs.proxy(this._handleAbort, this);
|
|
this._handleErrorProxy = createjs.proxy(this._handleError, this);
|
|
this._handleTimeoutProxy = createjs.proxy(this._handleTimeout, this);
|
|
this._handleLoadProxy = createjs.proxy(this._handleLoad, this);
|
|
this._handleReadyStateChangeProxy = createjs.proxy(
|
|
this._handleReadyStateChange,
|
|
this
|
|
);
|
|
|
|
if (!this._createXHR(item)) {
|
|
//TODO: Throw error?
|
|
}
|
|
}
|
|
|
|
var p = createjs.extend(XHRRequest, createjs.AbstractRequest);
|
|
|
|
// static properties
|
|
/**
|
|
* A list of XMLHTTP object IDs to try when building an ActiveX object for XHR requests in earlier versions of IE.
|
|
* @property ACTIVEX_VERSIONS
|
|
* @type {Array}
|
|
* @since 0.4.2
|
|
* @private
|
|
*/
|
|
XHRRequest.ACTIVEX_VERSIONS = [
|
|
"Msxml2.XMLHTTP.6.0",
|
|
"Msxml2.XMLHTTP.5.0",
|
|
"Msxml2.XMLHTTP.4.0",
|
|
"MSXML2.XMLHTTP.3.0",
|
|
"MSXML2.XMLHTTP",
|
|
"Microsoft.XMLHTTP",
|
|
];
|
|
|
|
// Public methods
|
|
/**
|
|
* Look up the loaded result.
|
|
* @method getResult
|
|
* @param {Boolean} [raw=false] Return a raw result instead of a formatted result. This applies to content
|
|
* loaded via XHR such as scripts, XML, CSS, and Images. If there is no raw result, the formatted result will be
|
|
* returned instead.
|
|
* @return {Object} A result object containing the content that was loaded, such as:
|
|
* <ul>
|
|
* <li>An image tag (<image />) for images</li>
|
|
* <li>A script tag for JavaScript (<script />). Note that scripts loaded with tags may be added to the
|
|
* HTML head.</li>
|
|
* <li>A style tag for CSS (<style />)</li>
|
|
* <li>Raw text for TEXT</li>
|
|
* <li>A formatted JavaScript object defined by JSON</li>
|
|
* <li>An XML document</li>
|
|
* <li>An binary arraybuffer loaded by XHR</li>
|
|
* </ul>
|
|
* Note that if a raw result is requested, but not found, the result will be returned instead.
|
|
*/
|
|
p.getResult = function (raw) {
|
|
if (raw && this._rawResponse) {
|
|
return this._rawResponse;
|
|
}
|
|
return this._response;
|
|
};
|
|
|
|
// Overrides abstract method in AbstractRequest
|
|
p.cancel = function () {
|
|
this.canceled = true;
|
|
this._clean();
|
|
this._request.abort();
|
|
};
|
|
|
|
// Overrides abstract method in AbstractLoader
|
|
p.load = function () {
|
|
if (this._request == null) {
|
|
this._handleError();
|
|
return;
|
|
}
|
|
|
|
//Events
|
|
if (this._request.addEventListener != null) {
|
|
this._request.addEventListener(
|
|
"loadstart",
|
|
this._handleLoadStartProxy,
|
|
false
|
|
);
|
|
this._request.addEventListener(
|
|
"progress",
|
|
this._handleProgressProxy,
|
|
false
|
|
);
|
|
this._request.addEventListener("abort", this._handleAbortProxy, false);
|
|
this._request.addEventListener("error", this._handleErrorProxy, false);
|
|
this._request.addEventListener(
|
|
"timeout",
|
|
this._handleTimeoutProxy,
|
|
false
|
|
);
|
|
|
|
// Note: We don't get onload in all browsers (earlier FF and IE). onReadyStateChange handles these.
|
|
this._request.addEventListener("load", this._handleLoadProxy, false);
|
|
this._request.addEventListener(
|
|
"readystatechange",
|
|
this._handleReadyStateChangeProxy,
|
|
false
|
|
);
|
|
} else {
|
|
// IE9 support
|
|
this._request.onloadstart = this._handleLoadStartProxy;
|
|
this._request.onprogress = this._handleProgressProxy;
|
|
this._request.onabort = this._handleAbortProxy;
|
|
this._request.onerror = this._handleErrorProxy;
|
|
this._request.ontimeout = this._handleTimeoutProxy;
|
|
|
|
// Note: We don't get onload in all browsers (earlier FF and IE). onReadyStateChange handles these.
|
|
this._request.onload = this._handleLoadProxy;
|
|
this._request.onreadystatechange = this._handleReadyStateChangeProxy;
|
|
}
|
|
|
|
// Set up a timeout if we don't have XHR2
|
|
if (this._xhrLevel == 1) {
|
|
this._loadTimeout = setTimeout(
|
|
createjs.proxy(this._handleTimeout, this),
|
|
this._item.loadTimeout
|
|
);
|
|
}
|
|
|
|
// Sometimes we get back 404s immediately, particularly when there is a cross origin request. // note this does not catch in Chrome
|
|
try {
|
|
if (
|
|
!this._item.values ||
|
|
this._item.method == createjs.AbstractLoader.GET
|
|
) {
|
|
this._request.send();
|
|
} else if (this._item.method == createjs.AbstractLoader.POST) {
|
|
this._request.send(
|
|
createjs.RequestUtils.formatQueryString(this._item.values)
|
|
);
|
|
}
|
|
} catch (error) {
|
|
this.dispatchEvent(new createjs.ErrorEvent("XHR_SEND", null, error));
|
|
}
|
|
};
|
|
|
|
p.setResponseType = function (type) {
|
|
// Some old browsers doesn't support blob, so we convert arraybuffer to blob after response is downloaded
|
|
if (type === "blob") {
|
|
type = window.URL ? "blob" : "arraybuffer";
|
|
this._responseType = type;
|
|
}
|
|
this._request.responseType = type;
|
|
};
|
|
|
|
/**
|
|
* Get all the response headers from the XmlHttpRequest.
|
|
*
|
|
* <strong>From the docs:</strong> Return all the HTTP headers, excluding headers that are a case-insensitive match
|
|
* for Set-Cookie or Set-Cookie2, as a single string, with each header line separated by a U+000D CR U+000A LF pair,
|
|
* excluding the status line, and with each header name and header value separated by a U+003A COLON U+0020 SPACE
|
|
* pair.
|
|
* @method getAllResponseHeaders
|
|
* @return {String}
|
|
* @since 0.4.1
|
|
*/
|
|
p.getAllResponseHeaders = function () {
|
|
if (this._request.getAllResponseHeaders instanceof Function) {
|
|
return this._request.getAllResponseHeaders();
|
|
} else {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get a specific response header from the XmlHttpRequest.
|
|
*
|
|
* <strong>From the docs:</strong> Returns the header field value from the response of which the field name matches
|
|
* header, unless the field name is Set-Cookie or Set-Cookie2.
|
|
* @method getResponseHeader
|
|
* @param {String} header The header name to retrieve.
|
|
* @return {String}
|
|
* @since 0.4.1
|
|
*/
|
|
p.getResponseHeader = function (header) {
|
|
if (this._request.getResponseHeader instanceof Function) {
|
|
return this._request.getResponseHeader(header);
|
|
} else {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
// protected methods
|
|
/**
|
|
* The XHR request has reported progress.
|
|
* @method _handleProgress
|
|
* @param {Object} event The XHR progress event.
|
|
* @private
|
|
*/
|
|
p._handleProgress = function (event) {
|
|
if (!event || (event.loaded > 0 && event.total == 0)) {
|
|
return; // Sometimes we get no "total", so just ignore the progress event.
|
|
}
|
|
|
|
var newEvent = new createjs.ProgressEvent(event.loaded, event.total);
|
|
this.dispatchEvent(newEvent);
|
|
};
|
|
|
|
/**
|
|
* The XHR request has reported a load start.
|
|
* @method _handleLoadStart
|
|
* @param {Object} event The XHR loadStart event.
|
|
* @private
|
|
*/
|
|
p._handleLoadStart = function (event) {
|
|
clearTimeout(this._loadTimeout);
|
|
this.dispatchEvent("loadstart");
|
|
};
|
|
|
|
/**
|
|
* The XHR request has reported an abort event.
|
|
* @method handleAbort
|
|
* @param {Object} event The XHR abort event.
|
|
* @private
|
|
*/
|
|
p._handleAbort = function (event) {
|
|
this._clean();
|
|
this.dispatchEvent(new createjs.ErrorEvent("XHR_ABORTED", null, event));
|
|
};
|
|
|
|
/**
|
|
* The XHR request has reported an error event.
|
|
* @method _handleError
|
|
* @param {Object} event The XHR error event.
|
|
* @private
|
|
*/
|
|
p._handleError = function (event) {
|
|
this._clean();
|
|
this.dispatchEvent(new createjs.ErrorEvent(event.message));
|
|
};
|
|
|
|
/**
|
|
* The XHR request has reported a readyState change. Note that older browsers (IE 7 & 8) do not provide an onload
|
|
* event, so we must monitor the readyStateChange to determine if the file is loaded.
|
|
* @method _handleReadyStateChange
|
|
* @param {Object} event The XHR readyStateChange event.
|
|
* @private
|
|
*/
|
|
p._handleReadyStateChange = function (event) {
|
|
if (this._request.readyState == 4) {
|
|
this._handleLoad();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The XHR request has completed. This is called by the XHR request directly, or by a readyStateChange that has
|
|
* <code>request.readyState == 4</code>. Only the first call to this method will be processed.
|
|
* @method _handleLoad
|
|
* @param {Object} event The XHR load event.
|
|
* @private
|
|
*/
|
|
p._handleLoad = function (event) {
|
|
if (this.loaded) {
|
|
return;
|
|
}
|
|
this.loaded = true;
|
|
|
|
var error = this._checkError();
|
|
if (error) {
|
|
this._handleError(error);
|
|
return;
|
|
}
|
|
|
|
this._response = this._getResponse();
|
|
// Convert arraybuffer back to blob
|
|
if (this._responseType === "arraybuffer") {
|
|
try {
|
|
this._response = new Blob([this._response]);
|
|
} catch (e) {
|
|
// Fallback to use BlobBuilder if Blob constructor is not supported
|
|
// Tested on Android 2.3 ~ 4.2 and iOS5 safari
|
|
window.BlobBuilder =
|
|
window.BlobBuilder ||
|
|
window.WebKitBlobBuilder ||
|
|
window.MozBlobBuilder ||
|
|
window.MSBlobBuilder;
|
|
if (e.name === "TypeError" && window.BlobBuilder) {
|
|
var builder = new BlobBuilder();
|
|
builder.append(this._response);
|
|
this._response = builder.getBlob();
|
|
}
|
|
}
|
|
}
|
|
this._clean();
|
|
|
|
this.dispatchEvent(new createjs.Event("complete"));
|
|
};
|
|
|
|
/**
|
|
* The XHR request has timed out. This is called by the XHR request directly, or via a <code>setTimeout</code>
|
|
* callback.
|
|
* @method _handleTimeout
|
|
* @param {Object} [event] The XHR timeout event. This is occasionally null when called by the backup setTimeout.
|
|
* @private
|
|
*/
|
|
p._handleTimeout = function (event) {
|
|
this._clean();
|
|
|
|
this.dispatchEvent(new createjs.ErrorEvent("PRELOAD_TIMEOUT", null, event));
|
|
};
|
|
|
|
// Protected
|
|
/**
|
|
* Determine if there is an error in the current load. This checks the status of the request for problem codes. Note
|
|
* that this does not check for an actual response. Currently, it only checks for 404 or 0 error code.
|
|
* @method _checkError
|
|
* @return {int} If the request status returns an error code.
|
|
* @private
|
|
*/
|
|
p._checkError = function () {
|
|
//LM: Probably need additional handlers here, maybe 501
|
|
var status = parseInt(this._request.status);
|
|
|
|
switch (status) {
|
|
case 404: // Not Found
|
|
case 0: // Not Loaded
|
|
return new Error(status);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Validate the response. Different browsers have different approaches, some of which throw errors when accessed
|
|
* in other browsers. If there is no response, the <code>_response</code> property will remain null.
|
|
* @method _getResponse
|
|
* @private
|
|
*/
|
|
p._getResponse = function () {
|
|
if (this._response != null) {
|
|
return this._response;
|
|
}
|
|
|
|
if (this._request.response != null) {
|
|
return this._request.response;
|
|
}
|
|
|
|
// Android 2.2 uses .responseText
|
|
try {
|
|
if (this._request.responseText != null) {
|
|
return this._request.responseText;
|
|
}
|
|
} catch (e) {}
|
|
|
|
// When loading XML, IE9 does not return .response, instead it returns responseXML.xml
|
|
try {
|
|
if (this._request.responseXML != null) {
|
|
return this._request.responseXML;
|
|
}
|
|
} catch (e) {}
|
|
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Create an XHR request. Depending on a number of factors, we get totally different results.
|
|
* <ol><li>Some browsers get an <code>XDomainRequest</code> when loading cross-domain.</li>
|
|
* <li>XMLHttpRequest are created when available.</li>
|
|
* <li>ActiveX.XMLHTTP objects are used in older IE browsers.</li>
|
|
* <li>Text requests override the mime type if possible</li>
|
|
* <li>Origin headers are sent for crossdomain requests in some browsers.</li>
|
|
* <li>Binary loads set the response type to "arraybuffer"</li></ol>
|
|
* @method _createXHR
|
|
* @param {Object} item The requested item that is being loaded.
|
|
* @return {Boolean} If an XHR request or equivalent was successfully created.
|
|
* @private
|
|
*/
|
|
p._createXHR = function (item) {
|
|
// Check for cross-domain loads. We can't fully support them, but we can try.
|
|
var crossdomain = createjs.RequestUtils.isCrossDomain(item);
|
|
var headers = {};
|
|
|
|
// Create the request. Fallback to whatever support we have.
|
|
var req = null;
|
|
if (window.XMLHttpRequest) {
|
|
req = new XMLHttpRequest();
|
|
// This is 8 or 9, so use XDomainRequest instead.
|
|
if (
|
|
crossdomain &&
|
|
req.withCredentials === undefined &&
|
|
window.XDomainRequest
|
|
) {
|
|
req = new XDomainRequest();
|
|
}
|
|
} else {
|
|
// Old IE versions use a different approach
|
|
for (var i = 0, l = s.ACTIVEX_VERSIONS.length; i < l; i++) {
|
|
var axVersion = s.ACTIVEX_VERSIONS[i];
|
|
try {
|
|
req = new ActiveXObject(axVersion);
|
|
break;
|
|
} catch (e) {}
|
|
}
|
|
if (req == null) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Default to utf-8 for Text requests.
|
|
if (item.mimeType == null && createjs.RequestUtils.isText(item.type)) {
|
|
item.mimeType = "text/plain; charset=utf-8";
|
|
}
|
|
|
|
// IE9 doesn't support overrideMimeType(), so we need to check for it.
|
|
if (item.mimeType && req.overrideMimeType) {
|
|
req.overrideMimeType(item.mimeType);
|
|
}
|
|
|
|
// Determine the XHR level
|
|
this._xhrLevel = typeof req.responseType === "string" ? 2 : 1;
|
|
|
|
var src = null;
|
|
if (item.method == createjs.AbstractLoader.GET) {
|
|
src = createjs.RequestUtils.buildPath(item.src, item.values);
|
|
} else {
|
|
src = item.src;
|
|
}
|
|
|
|
// Open the request. Set cross-domain flags if it is supported (XHR level 1 only)
|
|
req.open(item.method || createjs.AbstractLoader.GET, src, true);
|
|
|
|
if (crossdomain && req instanceof XMLHttpRequest && this._xhrLevel == 1) {
|
|
headers["Origin"] = location.origin;
|
|
}
|
|
|
|
// To send data we need to set the Content-type header)
|
|
if (item.values && item.method == createjs.AbstractLoader.POST) {
|
|
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
}
|
|
|
|
if (!crossdomain && !headers["X-Requested-With"]) {
|
|
headers["X-Requested-With"] = "XMLHttpRequest";
|
|
}
|
|
|
|
if (item.headers) {
|
|
for (var n in item.headers) {
|
|
headers[n] = item.headers[n];
|
|
}
|
|
}
|
|
|
|
for (n in headers) {
|
|
req.setRequestHeader(n, headers[n]);
|
|
}
|
|
|
|
if (req instanceof XMLHttpRequest && item.withCredentials !== undefined) {
|
|
req.withCredentials = item.withCredentials;
|
|
}
|
|
|
|
this._request = req;
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* A request has completed (or failed or canceled), and needs to be disposed.
|
|
* @method _clean
|
|
* @private
|
|
*/
|
|
p._clean = function () {
|
|
clearTimeout(this._loadTimeout);
|
|
|
|
if (this._request.removeEventListener != null) {
|
|
this._request.removeEventListener(
|
|
"loadstart",
|
|
this._handleLoadStartProxy
|
|
);
|
|
this._request.removeEventListener("progress", this._handleProgressProxy);
|
|
this._request.removeEventListener("abort", this._handleAbortProxy);
|
|
this._request.removeEventListener("error", this._handleErrorProxy);
|
|
this._request.removeEventListener("timeout", this._handleTimeoutProxy);
|
|
this._request.removeEventListener("load", this._handleLoadProxy);
|
|
this._request.removeEventListener(
|
|
"readystatechange",
|
|
this._handleReadyStateChangeProxy
|
|
);
|
|
} else {
|
|
this._request.onloadstart = null;
|
|
this._request.onprogress = null;
|
|
this._request.onabort = null;
|
|
this._request.onerror = null;
|
|
this._request.ontimeout = null;
|
|
this._request.onload = null;
|
|
this._request.onreadystatechange = null;
|
|
}
|
|
};
|
|
|
|
p.toString = function () {
|
|
return "[PreloadJS XHRRequest]";
|
|
};
|
|
|
|
createjs.XHRRequest = createjs.promote(XHRRequest, "AbstractRequest");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// LoadQueue.js
|
|
//##############################################################################
|
|
|
|
/*
|
|
TODO: WINDOWS ISSUES
|
|
* No error for HTML audio in IE 678
|
|
* SVG no failure error in IE 67 (maybe 8) TAGS AND XHR
|
|
* No script complete handler in IE 67 TAGS (XHR is fine)
|
|
* No XML/JSON in IE6 TAGS
|
|
* Need to hide loading SVG in Opera TAGS
|
|
* No CSS onload/readystatechange in Safari or Android TAGS (requires rule checking)
|
|
* SVG no load or failure in Opera XHR
|
|
* Reported issues with IE7/8
|
|
*/
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* The LoadQueue class is the main API for preloading content. LoadQueue is a load manager, which can preload either
|
|
* a single file, or queue of files.
|
|
*
|
|
* <b>Creating a Queue</b><br />
|
|
* To use LoadQueue, create a LoadQueue instance. If you want to force tag loading where possible, set the preferXHR
|
|
* argument to false.
|
|
*
|
|
* var queue = new createjs.LoadQueue(true);
|
|
*
|
|
* <b>Listening for Events</b><br />
|
|
* Add any listeners you want to the queue. Since PreloadJS 0.3.0, the {{#crossLink "EventDispatcher"}}{{/crossLink}}
|
|
* lets you add as many listeners as you want for events. You can subscribe to the following events:<ul>
|
|
* <li>{{#crossLink "AbstractLoader/complete:event"}}{{/crossLink}}: fired when a queue completes loading all
|
|
* files</li>
|
|
* <li>{{#crossLink "AbstractLoader/error:event"}}{{/crossLink}}: fired when the queue encounters an error with
|
|
* any file.</li>
|
|
* <li>{{#crossLink "AbstractLoader/progress:event"}}{{/crossLink}}: Progress for the entire queue has
|
|
* changed.</li>
|
|
* <li>{{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}}: A single file has completed loading.</li>
|
|
* <li>{{#crossLink "LoadQueue/fileprogress:event"}}{{/crossLink}}: Progress for a single file has changes. Note
|
|
* that only files loaded with XHR (or possibly by plugins) will fire progress events other than 0 or 100%.</li>
|
|
* </ul>
|
|
*
|
|
* queue.on("fileload", handleFileLoad, this);
|
|
* queue.on("complete", handleComplete, this);
|
|
*
|
|
* <b>Adding files and manifests</b><br />
|
|
* Add files you want to load using {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} or add multiple files at a
|
|
* time using a list or a manifest definition using {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}}. Files are
|
|
* appended to the end of the active queue, so you can use these methods as many times as you like, whenever you
|
|
* like.
|
|
*
|
|
* queue.loadFile("filePath/file.jpg");
|
|
* queue.loadFile({id:"image", src:"filePath/file.jpg"});
|
|
* queue.loadManifest(["filePath/file.jpg", {id:"image", src:"filePath/file.jpg"}]);
|
|
*
|
|
* // Use an external manifest
|
|
* queue.loadManifest("path/to/manifest.json");
|
|
* queue.loadManifest({src:"manifest.json", type:"manifest"});
|
|
*
|
|
* If you pass `false` as the `loadNow` parameter, the queue will not kick of the load of the files, but it will not
|
|
* stop if it has already been started. Call the {{#crossLink "AbstractLoader/load"}}{{/crossLink}} method to begin
|
|
* a paused queue. Note that a paused queue will automatically resume when new files are added to it with a
|
|
* `loadNow` argument of `true`.
|
|
*
|
|
* queue.load();
|
|
*
|
|
* <b>File Types</b><br />
|
|
* The file type of a manifest item is auto-determined by the file extension. The pattern matching in PreloadJS
|
|
* should handle the majority of standard file and url formats, and works with common file extensions. If you have
|
|
* either a non-standard file extension, or are serving the file using a proxy script, then you can pass in a
|
|
* <code>type</code> property with any manifest item.
|
|
*
|
|
* queue.loadFile({src:"path/to/myFile.mp3x", type:createjs.AbstractLoader.SOUND});
|
|
*
|
|
* // Note that PreloadJS will not read a file extension from the query string
|
|
* queue.loadFile({src:"http://server.com/proxy?file=image.jpg", type:createjs.AbstractLoader.IMAGE});
|
|
*
|
|
* Supported types are defined on the {{#crossLink "AbstractLoader"}}{{/crossLink}} class, and include:
|
|
* <ul>
|
|
* <li>{{#crossLink "AbstractLoader/BINARY:property"}}{{/crossLink}}: Raw binary data via XHR</li>
|
|
* <li>{{#crossLink "AbstractLoader/CSS:property"}}{{/crossLink}}: CSS files</li>
|
|
* <li>{{#crossLink "AbstractLoader/IMAGE:property"}}{{/crossLink}}: Common image formats</li>
|
|
* <li>{{#crossLink "AbstractLoader/JAVASCRIPT:property"}}{{/crossLink}}: JavaScript files</li>
|
|
* <li>{{#crossLink "AbstractLoader/JSON:property"}}{{/crossLink}}: JSON data</li>
|
|
* <li>{{#crossLink "AbstractLoader/JSONP:property"}}{{/crossLink}}: JSON files cross-domain</li>
|
|
* <li>{{#crossLink "AbstractLoader/MANIFEST:property"}}{{/crossLink}}: A list of files to load in JSON format, see
|
|
* {{#crossLink "AbstractLoader/loadManifest"}}{{/crossLink}}</li>
|
|
* <li>{{#crossLink "AbstractLoader/SOUND:property"}}{{/crossLink}}: Audio file formats</li>
|
|
* <li>{{#crossLink "AbstractLoader/SPRITESHEET:property"}}{{/crossLink}}: JSON SpriteSheet definitions. This
|
|
* will also load sub-images, and provide a {{#crossLink "SpriteSheet"}}{{/crossLink}} instance.</li>
|
|
* <li>{{#crossLink "AbstractLoader/SVG:property"}}{{/crossLink}}: SVG files</li>
|
|
* <li>{{#crossLink "AbstractLoader/TEXT:property"}}{{/crossLink}}: Text files - XHR only</li>
|
|
* <li>{{#crossLink "AbstractLoader/VIDEO:property"}}{{/crossLink}}: Video objects</li>
|
|
* <li>{{#crossLink "AbstractLoader/XML:property"}}{{/crossLink}}: XML data</li>
|
|
* </ul>
|
|
*
|
|
* <em>Note: Loader types used to be defined on LoadQueue, but have been moved to AbstractLoader for better
|
|
* portability of loader classes, which can be used individually now. The properties on LoadQueue still exist, but
|
|
* are deprecated.</em>
|
|
*
|
|
* <b>Handling Results</b><br />
|
|
* When a file is finished downloading, a {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} event is
|
|
* dispatched. In an example above, there is an event listener snippet for fileload. Loaded files are usually a
|
|
* formatted object that can be used immediately, including:
|
|
* <ul>
|
|
* <li>Binary: The binary loaded result</li>
|
|
* <li>CSS: A <link /> tag</li>
|
|
* <li>Image: An <img /> tag</li>
|
|
* <li>JavaScript: A <script /> tag</li>
|
|
* <li>JSON/JSONP: A formatted JavaScript Object</li>
|
|
* <li>Manifest: A JavaScript object.
|
|
* <li>Sound: An <audio /> tag</a>
|
|
* <li>SpriteSheet: A {{#crossLink "SpriteSheet"}}{{/crossLink}} instance, containing loaded images.
|
|
* <li>SVG: An <object /> tag</li>
|
|
* <li>Text: Raw text</li>
|
|
* <li>Video: A Video DOM node</li>
|
|
* <li>XML: An XML DOM node</li>
|
|
* </ul>
|
|
*
|
|
* function handleFileLoad(event) {
|
|
* var item = event.item; // A reference to the item that was passed in to the LoadQueue
|
|
* var type = item.type;
|
|
*
|
|
* // Add any images to the page body.
|
|
* if (type == createjs.LoadQueue.IMAGE) {
|
|
* document.body.appendChild(event.result);
|
|
* }
|
|
* }
|
|
*
|
|
* At any time after the file has been loaded (usually after the queue has completed), any result can be looked up
|
|
* via its "id" using {{#crossLink "LoadQueue/getResult"}}{{/crossLink}}. If no id was provided, then the
|
|
* "src" or file path can be used instead, including the `path` defined by a manifest, but <strong>not including</strong>
|
|
* a base path defined on the LoadQueue. It is recommended to always pass an id if you want to look up content.
|
|
*
|
|
* var image = queue.getResult("image");
|
|
* document.body.appendChild(image);
|
|
*
|
|
* Raw loaded content can be accessed using the <code>rawResult</code> property of the {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}}
|
|
* event, or can be looked up using {{#crossLink "LoadQueue/getResult"}}{{/crossLink}}, passing `true` as the 2nd
|
|
* argument. This is only applicable for content that has been parsed for the browser, specifically: JavaScript,
|
|
* CSS, XML, SVG, and JSON objects, or anything loaded with XHR.
|
|
*
|
|
* var image = queue.getResult("image", true); // load the binary image data loaded with XHR.
|
|
*
|
|
* <b>Plugins</b><br />
|
|
* LoadQueue has a simple plugin architecture to help process and preload content. For example, to preload audio,
|
|
* make sure to install the <a href="http://soundjs.com">SoundJS</a> Sound class, which will help load HTML audio,
|
|
* Flash audio, and WebAudio files. This should be installed <strong>before</strong> loading any audio files.
|
|
*
|
|
* queue.installPlugin(createjs.Sound);
|
|
*
|
|
* <h4>Known Browser Issues</h4>
|
|
* <ul>
|
|
* <li>Browsers without audio support can not load audio files.</li>
|
|
* <li>Safari on Mac OS X can only play HTML audio if QuickTime is installed</li>
|
|
* <li>HTML Audio tags will only download until their <code>canPlayThrough</code> event is fired. Browsers other
|
|
* than Chrome will continue to download in the background.</li>
|
|
* <li>When loading scripts using tags, they are automatically added to the document.</li>
|
|
* <li>Scripts loaded via XHR may not be properly inspectable with browser tools.</li>
|
|
* <li>IE6 and IE7 (and some other browsers) may not be able to load XML, Text, or JSON, since they require
|
|
* XHR to work.</li>
|
|
* <li>Content loaded via tags will not show progress, and will continue to download in the background when
|
|
* canceled, although no events will be dispatched.</li>
|
|
* </ul>
|
|
*
|
|
* @class LoadQueue
|
|
* @param {Boolean} [preferXHR=true] Determines whether the preload instance will favor loading with XHR (XML HTTP
|
|
* Requests), or HTML tags. When this is `false`, the queue will use tag loading when possible, and fall back on XHR
|
|
* when necessary.
|
|
* @param {String} [basePath=""] A path that will be prepended on to the source parameter of all items in the queue
|
|
* before they are loaded. Sources beginning with a protocol such as `http://` or a relative path such as `../`
|
|
* will not receive a base path.
|
|
* @param {String|Boolean} [crossOrigin=""] An optional flag to support images loaded from a CORS-enabled server. To
|
|
* use it, set this value to `true`, which will default the crossOrigin property on images to "Anonymous". Any
|
|
* string value will be passed through, but only "" and "Anonymous" are recommended. <strong>Note: The crossOrigin
|
|
* parameter is deprecated. Use LoadItem.crossOrigin instead</strong>
|
|
*
|
|
* @constructor
|
|
* @extends AbstractLoader
|
|
*/
|
|
function LoadQueue(preferXHR, basePath, crossOrigin) {
|
|
this.AbstractLoader_constructor();
|
|
|
|
/**
|
|
* An array of the plugins registered using {{#crossLink "LoadQueue/installPlugin"}}{{/crossLink}}.
|
|
* @property _plugins
|
|
* @type {Array}
|
|
* @private
|
|
* @since 0.6.1
|
|
*/
|
|
this._plugins = [];
|
|
|
|
/**
|
|
* An object hash of callbacks that are fired for each file type before the file is loaded, giving plugins the
|
|
* ability to override properties of the load. Please see the {{#crossLink "LoadQueue/installPlugin"}}{{/crossLink}}
|
|
* method for more information.
|
|
* @property _typeCallbacks
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
this._typeCallbacks = {};
|
|
|
|
/**
|
|
* An object hash of callbacks that are fired for each file extension before the file is loaded, giving plugins the
|
|
* ability to override properties of the load. Please see the {{#crossLink "LoadQueue/installPlugin"}}{{/crossLink}}
|
|
* method for more information.
|
|
* @property _extensionCallbacks
|
|
* @type {null}
|
|
* @private
|
|
*/
|
|
this._extensionCallbacks = {};
|
|
|
|
/**
|
|
* The next preload queue to process when this one is complete. If an error is thrown in the current queue, and
|
|
* {{#crossLink "LoadQueue/stopOnError:property"}}{{/crossLink}} is `true`, the next queue will not be processed.
|
|
* @property next
|
|
* @type {LoadQueue}
|
|
* @default null
|
|
*/
|
|
this.next = null;
|
|
|
|
/**
|
|
* Ensure loaded scripts "complete" in the order they are specified. Loaded scripts are added to the document head
|
|
* once they are loaded. Scripts loaded via tags will load one-at-a-time when this property is `true`, whereas
|
|
* scripts loaded using XHR can load in any order, but will "finish" and be added to the document in the order
|
|
* specified.
|
|
*
|
|
* Any items can be set to load in order by setting the {{#crossLink "maintainOrder:property"}}{{/crossLink}}
|
|
* property on the load item, or by ensuring that only one connection can be open at a time using
|
|
* {{#crossLink "LoadQueue/setMaxConnections"}}{{/crossLink}}. Note that when the `maintainScriptOrder` property
|
|
* is set to `true`, scripts items are automatically set to `maintainOrder=true`, and changing the
|
|
* `maintainScriptOrder` to `false` during a load will not change items already in a queue.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var queue = new createjs.LoadQueue();
|
|
* queue.setMaxConnections(3); // Set a higher number to load multiple items at once
|
|
* queue.maintainScriptOrder = true; // Ensure scripts are loaded in order
|
|
* queue.loadManifest([
|
|
* "script1.js",
|
|
* "script2.js",
|
|
* "image.png", // Load any time
|
|
* {src: "image2.png", maintainOrder: true} // Will wait for script2.js
|
|
* "image3.png",
|
|
* "script3.js" // Will wait for image2.png before loading (or completing when loading with XHR)
|
|
* ]);
|
|
*
|
|
* @property maintainScriptOrder
|
|
* @type {Boolean}
|
|
* @default true
|
|
*/
|
|
this.maintainScriptOrder = true;
|
|
|
|
/**
|
|
* Determines if the LoadQueue will stop processing the current queue when an error is encountered.
|
|
* @property stopOnError
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
this.stopOnError = false;
|
|
|
|
/**
|
|
* The number of maximum open connections that a loadQueue tries to maintain. Please see
|
|
* {{#crossLink "LoadQueue/setMaxConnections"}}{{/crossLink}} for more information.
|
|
* @property _maxConnections
|
|
* @type {Number}
|
|
* @default 1
|
|
* @private
|
|
*/
|
|
this._maxConnections = 1;
|
|
|
|
/**
|
|
* An internal list of all the default Loaders that are included with PreloadJS. Before an item is loaded, the
|
|
* available loader list is iterated, in the order they are included, and as soon as a loader indicates it can
|
|
* handle the content, it will be selected. The default loader, ({{#crossLink "TextLoader"}}{{/crossLink}} is
|
|
* last in the list, so it will be used if no other match is found. Typically, loaders will match based on the
|
|
* {{#crossLink "LoadItem/type"}}{{/crossLink}}, which is automatically determined using the file extension of
|
|
* the {{#crossLink "LoadItem/src:property"}}{{/crossLink}}.
|
|
*
|
|
* Loaders can be removed from PreloadJS by simply not including them.
|
|
*
|
|
* Custom loaders installed using {{#crossLink "registerLoader"}}{{/crossLink}} will be prepended to this list
|
|
* so that they are checked first.
|
|
* @property _availableLoaders
|
|
* @type {Array}
|
|
* @private
|
|
* @since 0.6.0
|
|
*/
|
|
this._availableLoaders = [
|
|
createjs.ImageLoader,
|
|
createjs.JavaScriptLoader,
|
|
createjs.CSSLoader,
|
|
createjs.JSONLoader,
|
|
createjs.JSONPLoader,
|
|
createjs.SoundLoader,
|
|
createjs.ManifestLoader,
|
|
createjs.SpriteSheetLoader,
|
|
createjs.XMLLoader,
|
|
createjs.SVGLoader,
|
|
createjs.BinaryLoader,
|
|
createjs.VideoLoader,
|
|
createjs.TextLoader,
|
|
];
|
|
|
|
/**
|
|
* The number of built in loaders, so they can't be removed by {{#crossLink "unregisterLoader"}}{{/crossLink}.
|
|
* @property _defaultLoaderLength
|
|
* @type {Number}
|
|
* @private
|
|
* @since 0.6.0
|
|
*/
|
|
this._defaultLoaderLength = this._availableLoaders.length;
|
|
|
|
this.init(preferXHR, basePath, crossOrigin);
|
|
}
|
|
|
|
var p = createjs.extend(LoadQueue, createjs.AbstractLoader);
|
|
var s = LoadQueue;
|
|
|
|
/**
|
|
* <strong>REMOVED</strong>. Removed in favor of using `MySuperClass_constructor`.
|
|
* See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}}
|
|
* for details.
|
|
*
|
|
* There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance.
|
|
*
|
|
* @method initialize
|
|
* @protected
|
|
* @deprecated
|
|
*/
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is.
|
|
|
|
/**
|
|
* An internal initialization method, which is used for initial set up, but also to reset the LoadQueue.
|
|
* @method init
|
|
* @param preferXHR
|
|
* @param basePath
|
|
* @param crossOrigin
|
|
* @private
|
|
*/
|
|
p.init = function (preferXHR, basePath, crossOrigin) {
|
|
// public properties
|
|
/**
|
|
* @property useXHR
|
|
* @type {Boolean}
|
|
* @readonly
|
|
* @default true
|
|
* @deprecated Use preferXHR instead.
|
|
*/
|
|
this.useXHR = true;
|
|
|
|
/**
|
|
* Try and use XMLHttpRequest (XHR) when possible. Note that LoadQueue will default to tag loading or XHR
|
|
* loading depending on the requirements for a media type. For example, HTML audio can not be loaded with XHR,
|
|
* and plain text can not be loaded with tags, so it will default the the correct type instead of using the
|
|
* user-defined type.
|
|
* @type {Boolean}
|
|
* @default true
|
|
* @since 0.6.0
|
|
*/
|
|
this.preferXHR = true; //TODO: Get/Set
|
|
this._preferXHR = true;
|
|
this.setPreferXHR(preferXHR);
|
|
|
|
// protected properties
|
|
/**
|
|
* Whether the queue is currently paused or not.
|
|
* @property _paused
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
this._paused = false;
|
|
|
|
/**
|
|
* A path that will be prepended on to the item's {{#crossLink "LoadItem/src:property"}}{{/crossLink}}. The
|
|
* `_basePath` property will only be used if an item's source is relative, and does not include a protocol such
|
|
* as `http://`, or a relative path such as `../`.
|
|
* @property _basePath
|
|
* @type {String}
|
|
* @private
|
|
* @since 0.3.1
|
|
*/
|
|
this._basePath = basePath;
|
|
|
|
/**
|
|
* An optional flag to set on images that are loaded using PreloadJS, which enables CORS support. Images loaded
|
|
* cross-domain by servers that support CORS require the crossOrigin flag to be loaded and interacted with by
|
|
* a canvas. When loading locally, or with a server with no CORS support, this flag can cause other security issues,
|
|
* so it is recommended to only set it if you are sure the server supports it. Currently, supported values are ""
|
|
* and "Anonymous".
|
|
* @property _crossOrigin
|
|
* @type {String}
|
|
* @default ""
|
|
* @private
|
|
* @since 0.4.1
|
|
*/
|
|
this._crossOrigin = crossOrigin;
|
|
|
|
/**
|
|
* Determines if the loadStart event was dispatched already. This event is only fired one time, when the first
|
|
* file is requested.
|
|
* @property _loadStartWasDispatched
|
|
* @type {Boolean}
|
|
* @default false
|
|
* @private
|
|
*/
|
|
this._loadStartWasDispatched = false;
|
|
|
|
/**
|
|
* Determines if there is currently a script loading. This helps ensure that only a single script loads at once when
|
|
* using a script tag to do preloading.
|
|
* @property _currentlyLoadingScript
|
|
* @type {Boolean}
|
|
* @private
|
|
*/
|
|
this._currentlyLoadingScript = null;
|
|
|
|
/**
|
|
* An array containing the currently downloading files.
|
|
* @property _currentLoads
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this._currentLoads = [];
|
|
|
|
/**
|
|
* An array containing the queued items that have not yet started downloading.
|
|
* @property _loadQueue
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this._loadQueue = [];
|
|
|
|
/**
|
|
* An array containing downloads that have not completed, so that the LoadQueue can be properly reset.
|
|
* @property _loadQueueBackup
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this._loadQueueBackup = [];
|
|
|
|
/**
|
|
* An object hash of items that have finished downloading, indexed by the {{#crossLink "LoadItem"}}{{/crossLink}}
|
|
* id.
|
|
* @property _loadItemsById
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
this._loadItemsById = {};
|
|
|
|
/**
|
|
* An object hash of items that have finished downloading, indexed by {{#crossLink "LoadItem"}}{{/crossLink}}
|
|
* source.
|
|
* @property _loadItemsBySrc
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
this._loadItemsBySrc = {};
|
|
|
|
/**
|
|
* An object hash of loaded items, indexed by the ID of the {{#crossLink "LoadItem"}}{{/crossLink}}.
|
|
* @property _loadedResults
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
this._loadedResults = {};
|
|
|
|
/**
|
|
* An object hash of un-parsed loaded items, indexed by the ID of the {{#crossLink "LoadItem"}}{{/crossLink}}.
|
|
* @property _loadedRawResults
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
this._loadedRawResults = {};
|
|
|
|
/**
|
|
* The number of items that have been requested. This helps manage an overall progress without knowing how large
|
|
* the files are before they are downloaded. This does not include items inside of loaders such as the
|
|
* {{#crossLink "ManifestLoader"}}{{/crossLink}}.
|
|
* @property _numItems
|
|
* @type {Number}
|
|
* @default 0
|
|
* @private
|
|
*/
|
|
this._numItems = 0;
|
|
|
|
/**
|
|
* The number of items that have completed loaded. This helps manage an overall progress without knowing how large
|
|
* the files are before they are downloaded.
|
|
* @property _numItemsLoaded
|
|
* @type {Number}
|
|
* @default 0
|
|
* @private
|
|
*/
|
|
this._numItemsLoaded = 0;
|
|
|
|
/**
|
|
* A list of scripts in the order they were requested. This helps ensure that scripts are "completed" in the right
|
|
* order.
|
|
* @property _scriptOrder
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this._scriptOrder = [];
|
|
|
|
/**
|
|
* A list of scripts that have been loaded. Items are added to this list as <code>null</code> when they are
|
|
* requested, contain the loaded item if it has completed, but not been dispatched to the user, and <code>true</true>
|
|
* once they are complete and have been dispatched.
|
|
* @property _loadedScripts
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this._loadedScripts = [];
|
|
|
|
/**
|
|
* The last progress amount. This is used to suppress duplicate progress events.
|
|
* @property _lastProgress
|
|
* @type {Number}
|
|
* @private
|
|
* @since 0.6.0
|
|
*/
|
|
this._lastProgress = NaN;
|
|
};
|
|
|
|
// static properties
|
|
/**
|
|
* The time in milliseconds to assume a load has failed. An {{#crossLink "AbstractLoader/error:event"}}{{/crossLink}}
|
|
* event is dispatched if the timeout is reached before any data is received.
|
|
* @property loadTimeout
|
|
* @type {Number}
|
|
* @default 8000
|
|
* @static
|
|
* @since 0.4.1
|
|
* @deprecated In favour of {{#crossLink "LoadItem/LOAD_TIMEOUT_DEFAULT:property}}{{/crossLink}} property.
|
|
*/
|
|
s.loadTimeout = 8000;
|
|
|
|
/**
|
|
* The time in milliseconds to assume a load has failed.
|
|
* @property LOAD_TIMEOUT
|
|
* @type {Number}
|
|
* @default 0
|
|
* @deprecated in favor of the {{#crossLink "LoadQueue/loadTimeout:property"}}{{/crossLink}} property.
|
|
*/
|
|
s.LOAD_TIMEOUT = 0;
|
|
|
|
// Preload Types
|
|
/**
|
|
* @property BINARY
|
|
* @type {String}
|
|
* @default binary
|
|
* @static
|
|
* @deprecated Use the AbstractLoader {{#crossLink "AbstractLoader/BINARY:property"}}{{/crossLink}} instead.
|
|
*/
|
|
s.BINARY = createjs.AbstractLoader.BINARY;
|
|
|
|
/**
|
|
* @property CSS
|
|
* @type {String}
|
|
* @default css
|
|
* @static
|
|
* @deprecated Use the AbstractLoader {{#crossLink "AbstractLoader/CSS:property"}}{{/crossLink}} instead.
|
|
*/
|
|
s.CSS = createjs.AbstractLoader.CSS;
|
|
|
|
/**
|
|
* @property IMAGE
|
|
* @type {String}
|
|
* @default image
|
|
* @static
|
|
* @deprecated Use the AbstractLoader {{#crossLink "AbstractLoader/CSS:property"}}{{/crossLink}} instead.
|
|
*/
|
|
s.IMAGE = createjs.AbstractLoader.IMAGE;
|
|
|
|
/**
|
|
* @property JAVASCRIPT
|
|
* @type {String}
|
|
* @default javascript
|
|
* @static
|
|
* @deprecated Use the AbstractLoader {{#crossLink "AbstractLoader/JAVASCRIPT:property"}}{{/crossLink}} instead.
|
|
*/
|
|
s.JAVASCRIPT = createjs.AbstractLoader.JAVASCRIPT;
|
|
|
|
/**
|
|
* @property JSON
|
|
* @type {String}
|
|
* @default json
|
|
* @static
|
|
* @deprecated Use the AbstractLoader {{#crossLink "AbstractLoader/JSON:property"}}{{/crossLink}} instead.
|
|
*/
|
|
s.JSON = createjs.AbstractLoader.JSON;
|
|
|
|
/**
|
|
* @property JSONP
|
|
* @type {String}
|
|
* @default jsonp
|
|
* @static
|
|
* @deprecated Use the AbstractLoader {{#crossLink "AbstractLoader/JSONP:property"}}{{/crossLink}} instead.
|
|
*/
|
|
s.JSONP = createjs.AbstractLoader.JSONP;
|
|
|
|
/**
|
|
* @property MANIFEST
|
|
* @type {String}
|
|
* @default manifest
|
|
* @static
|
|
* @since 0.4.1
|
|
* @deprecated Use the AbstractLoader {{#crossLink "AbstractLoader/MANIFEST:property"}}{{/crossLink}} instead.
|
|
*/
|
|
s.MANIFEST = createjs.AbstractLoader.MANIFEST;
|
|
|
|
/**
|
|
* @property SOUND
|
|
* @type {String}
|
|
* @default sound
|
|
* @static
|
|
* @deprecated Use the AbstractLoader {{#crossLink "AbstractLoader/JAVASCRIPT:property"}}{{/crossLink}} instead.
|
|
*/
|
|
s.SOUND = createjs.AbstractLoader.SOUND;
|
|
|
|
/**
|
|
* @property VIDEO
|
|
* @type {String}
|
|
* @default video
|
|
* @static
|
|
* @deprecated Use the AbstractLoader {{#crossLink "AbstractLoader/JAVASCRIPT:property"}}{{/crossLink}} instead.
|
|
*/
|
|
s.VIDEO = createjs.AbstractLoader.VIDEO;
|
|
|
|
/**
|
|
* @property SVG
|
|
* @type {String}
|
|
* @default svg
|
|
* @static
|
|
* @deprecated Use the AbstractLoader {{#crossLink "AbstractLoader/SVG:property"}}{{/crossLink}} instead.
|
|
*/
|
|
s.SVG = createjs.AbstractLoader.SVG;
|
|
|
|
/**
|
|
* @property TEXT
|
|
* @type {String}
|
|
* @default text
|
|
* @static
|
|
* @deprecated Use the AbstractLoader {{#crossLink "AbstractLoader/TEXT:property"}}{{/crossLink}} instead.
|
|
*/
|
|
s.TEXT = createjs.AbstractLoader.TEXT;
|
|
|
|
/**
|
|
* @property XML
|
|
* @type {String}
|
|
* @default xml
|
|
* @static
|
|
* @deprecated Use the AbstractLoader {{#crossLink "AbstractLoader/XML:property"}}{{/crossLink}} instead.
|
|
*/
|
|
s.XML = createjs.AbstractLoader.XML;
|
|
|
|
/**
|
|
* @property POST
|
|
* @type {string}
|
|
* @deprecated Use the AbstractLoader {{#crossLink "AbstractLoader/POST:property"}}{{/crossLink}} instead.
|
|
*/
|
|
s.POST = createjs.AbstractLoader.POST;
|
|
|
|
/**
|
|
* @property GET
|
|
* @type {string}
|
|
* @deprecated Use the AbstractLoader {{#crossLink "AbstractLoader/GET:property"}}{{/crossLink}} instead.
|
|
*/
|
|
s.GET = createjs.AbstractLoader.GET;
|
|
|
|
// events
|
|
/**
|
|
* This event is fired when an individual file has loaded, and been processed.
|
|
* @event fileload
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @param {Object} item The file item which was specified in the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}}
|
|
* or {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} call. If only a string path or tag was specified, the
|
|
* object will contain that value as a `src` property.
|
|
* @param {Object} result The HTML tag or parsed result of the loaded item.
|
|
* @param {Object} rawResult The unprocessed result, usually the raw text or binary data before it is converted
|
|
* to a usable object.
|
|
* @since 0.3.0
|
|
*/
|
|
|
|
/**
|
|
* This {{#crossLink "ProgressEvent"}}{{/crossLink}} that is fired when an an individual file's progress changes.
|
|
* @event fileprogress
|
|
* @since 0.3.0
|
|
*/
|
|
|
|
/**
|
|
* This event is fired when an individual file starts to load.
|
|
* @event filestart
|
|
* @param {Object} The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @param {Object} item The file item which was specified in the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}}
|
|
* or {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} call. If only a string path or tag was specified, the
|
|
* object will contain that value as a property.
|
|
*/
|
|
|
|
/**
|
|
* Although it extends {{#crossLink "AbstractLoader"}}{{/crossLink}}, the `initialize` event is never fired from
|
|
* a LoadQueue instance.
|
|
* @event initialize
|
|
* @private
|
|
*/
|
|
|
|
// public methods
|
|
/**
|
|
* Register a custom loaders class. New loaders are given precedence over loaders added earlier and default loaders.
|
|
* It is recommended that loaders extend {{#crossLink "AbstractLoader"}}{{/crossLink}}. Loaders can only be added
|
|
* once, and will be prepended to the list of available loaders.
|
|
* @method registerLoader
|
|
* @param {Function|AbstractLoader} loader The AbstractLoader class to add.
|
|
* @since 0.6.0
|
|
*/
|
|
p.registerLoader = function (loader) {
|
|
if (!loader || !loader.canLoadItem) {
|
|
throw new Error("loader is of an incorrect type.");
|
|
} else if (this._availableLoaders.indexOf(loader) != -1) {
|
|
throw new Error("loader already exists."); //LM: Maybe just silently fail here
|
|
}
|
|
|
|
this._availableLoaders.unshift(loader);
|
|
};
|
|
|
|
/**
|
|
* Remove a custom loader added using {{#crossLink "registerLoader"}}{{/crossLink}}. Only custom loaders can be
|
|
* unregistered, the default loaders will always be available.
|
|
* @method unregisterLoader
|
|
* @param {Function|AbstractLoader} loader The AbstractLoader class to remove
|
|
*/
|
|
p.unregisterLoader = function (loader) {
|
|
var idx = this._availableLoaders.indexOf(loader);
|
|
if (idx != -1 && idx < this._defaultLoaderLength - 1) {
|
|
this._availableLoaders.splice(idx, 1);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method setUseXHR
|
|
* @param {Boolean} value The new useXHR value to set.
|
|
* @return {Boolean} The new useXHR value. If XHR is not supported by the browser, this will return false, even if
|
|
* the provided value argument was true.
|
|
* @since 0.3.0
|
|
* @deprecated use the {{#crossLink "LoadQueue/preferXHR:property"}}{{/crossLink}} property, or the
|
|
* {{#crossLink "LoadQueue/setUseXHR"}}{{/crossLink}} method instead.
|
|
*/
|
|
p.setUseXHR = function (value) {
|
|
return this.setPreferXHR(value);
|
|
};
|
|
|
|
/**
|
|
* Change the {{#crossLink "preferXHR:property"}}{{/crossLink}} value. Note that if this is set to `true`, it may
|
|
* fail, or be ignored depending on the browser's capabilities and the load type.
|
|
* @method setPreferXHR
|
|
* @param {Boolean} value
|
|
* @returns {Boolean} The value of {{#crossLink "preferXHR"}}{{/crossLink}} that was successfully set.
|
|
* @since 0.6.0
|
|
*/
|
|
p.setPreferXHR = function (value) {
|
|
// Determine if we can use XHR. XHR defaults to TRUE, but the browser may not support it.
|
|
//TODO: Should we be checking for the other XHR types? Might have to do a try/catch on the different types similar to createXHR.
|
|
this.preferXHR = value != false && window.XMLHttpRequest != null;
|
|
return this.preferXHR;
|
|
};
|
|
|
|
/**
|
|
* Stops all queued and loading items, and clears the queue. This also removes all internal references to loaded
|
|
* content, and allows the queue to be used again.
|
|
* @method removeAll
|
|
* @since 0.3.0
|
|
*/
|
|
p.removeAll = function () {
|
|
this.remove();
|
|
};
|
|
|
|
/**
|
|
* Stops an item from being loaded, and removes it from the queue. If nothing is passed, all items are removed.
|
|
* This also removes internal references to loaded item(s).
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* queue.loadManifest([
|
|
* {src:"test.png", id:"png"},
|
|
* {src:"test.jpg", id:"jpg"},
|
|
* {src:"test.mp3", id:"mp3"}
|
|
* ]);
|
|
* queue.remove("png"); // Single item by ID
|
|
* queue.remove("png", "test.jpg"); // Items as arguments. Mixed id and src.
|
|
* queue.remove(["test.png", "jpg"]); // Items in an Array. Mixed id and src.
|
|
*
|
|
* @method remove
|
|
* @param {String | Array} idsOrUrls* The id or ids to remove from this queue. You can pass an item, an array of
|
|
* items, or multiple items as arguments.
|
|
* @since 0.3.0
|
|
*/
|
|
p.remove = function (idsOrUrls) {
|
|
var args = null;
|
|
|
|
if (idsOrUrls && !Array.isArray(idsOrUrls)) {
|
|
args = [idsOrUrls];
|
|
} else if (idsOrUrls) {
|
|
args = idsOrUrls;
|
|
} else if (arguments.length > 0) {
|
|
return;
|
|
}
|
|
|
|
var itemsWereRemoved = false;
|
|
|
|
// Destroy everything
|
|
if (!args) {
|
|
this.close();
|
|
for (var n in this._loadItemsById) {
|
|
this._disposeItem(this._loadItemsById[n]);
|
|
}
|
|
this.init(this.preferXHR, this._basePath);
|
|
|
|
// Remove specific items
|
|
} else {
|
|
while (args.length) {
|
|
var item = args.pop();
|
|
var r = this.getResult(item);
|
|
|
|
//Remove from the main load Queue
|
|
for (i = this._loadQueue.length - 1; i >= 0; i--) {
|
|
loadItem = this._loadQueue[i].getItem();
|
|
if (loadItem.id == item || loadItem.src == item) {
|
|
this._loadQueue.splice(i, 1)[0].cancel();
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Remove from the backup queue
|
|
for (i = this._loadQueueBackup.length - 1; i >= 0; i--) {
|
|
loadItem = this._loadQueueBackup[i].getItem();
|
|
if (loadItem.id == item || loadItem.src == item) {
|
|
this._loadQueueBackup.splice(i, 1)[0].cancel();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (r) {
|
|
this._disposeItem(this.getItem(item));
|
|
} else {
|
|
for (var i = this._currentLoads.length - 1; i >= 0; i--) {
|
|
var loadItem = this._currentLoads[i].getItem();
|
|
if (loadItem.id == item || loadItem.src == item) {
|
|
this._currentLoads.splice(i, 1)[0].cancel();
|
|
itemsWereRemoved = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If this was called during a load, try to load the next item.
|
|
if (itemsWereRemoved) {
|
|
this._loadNext();
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Stops all open loads, destroys any loaded items, and resets the queue, so all items can
|
|
* be reloaded again by calling {{#crossLink "AbstractLoader/load"}}{{/crossLink}}. Items are not removed from the
|
|
* queue. To remove items use the {{#crossLink "LoadQueue/remove"}}{{/crossLink}} or
|
|
* {{#crossLink "LoadQueue/removeAll"}}{{/crossLink}} method.
|
|
* @method reset
|
|
* @since 0.3.0
|
|
*/
|
|
p.reset = function () {
|
|
this.close();
|
|
for (var n in this._loadItemsById) {
|
|
this._disposeItem(this._loadItemsById[n]);
|
|
}
|
|
|
|
//Reset the queue to its start state
|
|
var a = [];
|
|
for (var i = 0, l = this._loadQueueBackup.length; i < l; i++) {
|
|
a.push(this._loadQueueBackup[i].getItem());
|
|
}
|
|
|
|
this.loadManifest(a, false);
|
|
};
|
|
|
|
/**
|
|
* Register a plugin. Plugins can map to load types (sound, image, etc), or specific extensions (png, mp3, etc).
|
|
* Currently, only one plugin can exist per type/extension.
|
|
*
|
|
* When a plugin is installed, a <code>getPreloadHandlers()</code> method will be called on it. For more information
|
|
* on this method, check out the {{#crossLink "SamplePlugin/getPreloadHandlers"}}{{/crossLink}} method in the
|
|
* {{#crossLink "SamplePlugin"}}{{/crossLink}} class.
|
|
*
|
|
* Before a file is loaded, a matching plugin has an opportunity to modify the load. If a `callback` is returned
|
|
* from the {{#crossLink "SamplePlugin/getPreloadHandlers"}}{{/crossLink}} method, it will be invoked first, and its
|
|
* result may cancel or modify the item. The callback method can also return a `completeHandler` to be fired when
|
|
* the file is loaded, or a `tag` object, which will manage the actual download. For more information on these
|
|
* methods, check out the {{#crossLink "SamplePlugin/preloadHandler"}}{{/crossLink}} and {{#crossLink "SamplePlugin/fileLoadHandler"}}{{/crossLink}}
|
|
* methods on the {{#crossLink "SamplePlugin"}}{{/crossLink}}.
|
|
*
|
|
* @method installPlugin
|
|
* @param {Function} plugin The plugin class to install.
|
|
*/
|
|
p.installPlugin = function (plugin) {
|
|
if (plugin == null) {
|
|
return;
|
|
}
|
|
|
|
if (plugin.getPreloadHandlers != null) {
|
|
this._plugins.push(plugin);
|
|
var map = plugin.getPreloadHandlers();
|
|
map.scope = plugin;
|
|
|
|
if (map.types != null) {
|
|
for (var i = 0, l = map.types.length; i < l; i++) {
|
|
this._typeCallbacks[map.types[i]] = map;
|
|
}
|
|
}
|
|
|
|
if (map.extensions != null) {
|
|
for (i = 0, l = map.extensions.length; i < l; i++) {
|
|
this._extensionCallbacks[map.extensions[i]] = map;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Set the maximum number of concurrent connections. Note that browsers and servers may have a built-in maximum
|
|
* number of open connections, so any additional connections may remain in a pending state until the browser
|
|
* opens the connection. When loading scripts using tags, and when {{#crossLink "LoadQueue/maintainScriptOrder:property"}}{{/crossLink}}
|
|
* is `true`, only one script is loaded at a time due to browser limitations.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var queue = new createjs.LoadQueue();
|
|
* queue.setMaxConnections(10); // Allow 10 concurrent loads
|
|
*
|
|
* @method setMaxConnections
|
|
* @param {Number} value The number of concurrent loads to allow. By default, only a single connection per LoadQueue
|
|
* is open at any time.
|
|
*/
|
|
p.setMaxConnections = function (value) {
|
|
this._maxConnections = value;
|
|
if (!this._paused && this._loadQueue.length > 0) {
|
|
this._loadNext();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Load a single file. To add multiple files at once, use the {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}}
|
|
* method.
|
|
*
|
|
* Files are always appended to the current queue, so this method can be used multiple times to add files.
|
|
* To clear the queue first, use the {{#crossLink "AbstractLoader/close"}}{{/crossLink}} method.
|
|
* @method loadFile
|
|
* @param {LoadItem|Object|String} file The file object or path to load. A file can be either
|
|
* <ul>
|
|
* <li>A {{#crossLink "LoadItem"}}{{/crossLink}} instance</li>
|
|
* <li>An object containing properties defined by {{#crossLink "LoadItem"}}{{/crossLink}}</li>
|
|
* <li>OR A string path to a resource. Note that this kind of load item will be converted to a {{#crossLink "LoadItem"}}{{/crossLink}}
|
|
* in the background.</li>
|
|
* </ul>
|
|
* @param {Boolean} [loadNow=true] Kick off an immediate load (true) or wait for a load call (false). The default
|
|
* value is true. If the queue is paused using {{#crossLink "LoadQueue/setPaused"}}{{/crossLink}}, and the value is
|
|
* `true`, the queue will resume automatically.
|
|
* @param {String} [basePath] A base path that will be prepended to each file. The basePath argument overrides the
|
|
* path specified in the constructor. Note that if you load a manifest using a file of type {{#crossLink "AbstractLoader/MANIFEST:property"}}{{/crossLink}},
|
|
* its files will <strong>NOT</strong> use the basePath parameter. <strong>The basePath parameter is deprecated.</strong>
|
|
* This parameter will be removed in a future version. Please either use the `basePath` parameter in the LoadQueue
|
|
* constructor, or a `path` property in a manifest definition.
|
|
*/
|
|
p.loadFile = function (file, loadNow, basePath) {
|
|
if (file == null) {
|
|
var event = new createjs.ErrorEvent("PRELOAD_NO_FILE");
|
|
this._sendError(event);
|
|
return;
|
|
}
|
|
this._addItem(file, null, basePath);
|
|
|
|
if (loadNow !== false) {
|
|
this.setPaused(false);
|
|
} else {
|
|
this.setPaused(true);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Load an array of files. To load a single file, use the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} method.
|
|
* The files in the manifest are requested in the same order, but may complete in a different order if the max
|
|
* connections are set above 1 using {{#crossLink "LoadQueue/setMaxConnections"}}{{/crossLink}}. Scripts will load
|
|
* in the right order as long as {{#crossLink "LoadQueue/maintainScriptOrder"}}{{/crossLink}} is true (which is
|
|
* default).
|
|
*
|
|
* Files are always appended to the current queue, so this method can be used multiple times to add files.
|
|
* To clear the queue first, use the {{#crossLink "AbstractLoader/close"}}{{/crossLink}} method.
|
|
* @method loadManifest
|
|
* @param {Array|String|Object} manifest An list of files to load. The loadManifest call supports four types of
|
|
* manifests:
|
|
* <ol>
|
|
* <li>A string path, which points to a manifest file, which is a JSON file that contains a "manifest" property,
|
|
* which defines the list of files to load, and can optionally contain a "path" property, which will be
|
|
* prepended to each file in the list.</li>
|
|
* <li>An object which defines a "src", which is a JSON or JSONP file. A "callback" can be defined for JSONP
|
|
* file. The JSON/JSONP file should contain a "manifest" property, which defines the list of files to load,
|
|
* and can optionally contain a "path" property, which will be prepended to each file in the list.</li>
|
|
* <li>An object which contains a "manifest" property, which defines the list of files to load, and can
|
|
* optionally contain a "path" property, which will be prepended to each file in the list.</li>
|
|
* <li>An Array of files to load.</li>
|
|
* </ol>
|
|
*
|
|
* Each "file" in a manifest can be either:
|
|
* <ul>
|
|
* <li>A {{#crossLink "LoadItem"}}{{/crossLink}} instance</li>
|
|
* <li>An object containing properties defined by {{#crossLink "LoadItem"}}{{/crossLink}}</li>
|
|
* <li>OR A string path to a resource. Note that this kind of load item will be converted to a {{#crossLink "LoadItem"}}{{/crossLink}}
|
|
* in the background.</li>
|
|
* </ul>
|
|
*
|
|
* @param {Boolean} [loadNow=true] Kick off an immediate load (true) or wait for a load call (false). The default
|
|
* value is true. If the queue is paused using {{#crossLink "LoadQueue/setPaused"}}{{/crossLink}} and this value is
|
|
* `true`, the queue will resume automatically.
|
|
* @param {String} [basePath] A base path that will be prepended to each file. The basePath argument overrides the
|
|
* path specified in the constructor. Note that if you load a manifest using a file of type {{#crossLink "LoadQueue/MANIFEST:property"}}{{/crossLink}},
|
|
* its files will <strong>NOT</strong> use the basePath parameter. <strong>The basePath parameter is deprecated.</strong>
|
|
* This parameter will be removed in a future version. Please either use the `basePath` parameter in the LoadQueue
|
|
* constructor, or a `path` property in a manifest definition.
|
|
*/
|
|
p.loadManifest = function (manifest, loadNow, basePath) {
|
|
var fileList = null;
|
|
var path = null;
|
|
|
|
// Array-based list of items
|
|
if (Array.isArray(manifest)) {
|
|
if (manifest.length == 0) {
|
|
var event = new createjs.ErrorEvent("PRELOAD_MANIFEST_EMPTY");
|
|
this._sendError(event);
|
|
return;
|
|
}
|
|
fileList = manifest;
|
|
|
|
// String-based. Only file manifests can be specified this way. Any other types will cause an error when loaded.
|
|
} else if (typeof manifest === "string") {
|
|
fileList = [
|
|
{
|
|
src: manifest,
|
|
type: s.MANIFEST,
|
|
},
|
|
];
|
|
} else if (typeof manifest == "object") {
|
|
// An object that defines a manifest path
|
|
if (manifest.src !== undefined) {
|
|
if (manifest.type == null) {
|
|
manifest.type = s.MANIFEST;
|
|
} else if (manifest.type != s.MANIFEST) {
|
|
var event = new createjs.ErrorEvent("PRELOAD_MANIFEST_TYPE");
|
|
this._sendError(event);
|
|
}
|
|
fileList = [manifest];
|
|
|
|
// An object that defines a manifest
|
|
} else if (manifest.manifest !== undefined) {
|
|
fileList = manifest.manifest;
|
|
path = manifest.path;
|
|
}
|
|
|
|
// Unsupported. This will throw an error.
|
|
} else {
|
|
var event = new createjs.ErrorEvent("PRELOAD_MANIFEST_NULL");
|
|
this._sendError(event);
|
|
return;
|
|
}
|
|
|
|
for (var i = 0, l = fileList.length; i < l; i++) {
|
|
this._addItem(fileList[i], path, basePath);
|
|
}
|
|
|
|
if (loadNow !== false) {
|
|
this.setPaused(false);
|
|
} else {
|
|
this.setPaused(true);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Start a LoadQueue that was created, but not automatically started.
|
|
* @method load
|
|
*/
|
|
p.load = function () {
|
|
this.setPaused(false);
|
|
};
|
|
|
|
/**
|
|
* Look up a {{#crossLink "LoadItem"}}{{/crossLink}} using either the "id" or "src" that was specified when loading it. Note that if no "id" was
|
|
* supplied with the load item, the ID will be the "src", including a `path` property defined by a manifest. The
|
|
* `basePath` will not be part of the ID.
|
|
* @method getItem
|
|
* @param {String} value The <code>id</code> or <code>src</code> of the load item.
|
|
* @return {Object} The load item that was initially requested using {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}}
|
|
* or {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}}. This object is also returned via the {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}}
|
|
* event as the `item` parameter.
|
|
*/
|
|
p.getItem = function (value) {
|
|
return this._loadItemsById[value] || this._loadItemsBySrc[value];
|
|
};
|
|
|
|
/**
|
|
* Look up a loaded result using either the "id" or "src" that was specified when loading it. Note that if no "id"
|
|
* was supplied with the load item, the ID will be the "src", including a `path` property defined by a manifest. The
|
|
* `basePath` will not be part of the ID.
|
|
* @method getResult
|
|
* @param {String} value The <code>id</code> or <code>src</code> of the load item.
|
|
* @param {Boolean} [rawResult=false] Return a raw result instead of a formatted result. This applies to content
|
|
* loaded via XHR such as scripts, XML, CSS, and Images. If there is no raw result, the formatted result will be
|
|
* returned instead.
|
|
* @return {Object} A result object containing the content that was loaded, such as:
|
|
* <ul>
|
|
* <li>An image tag (<image />) for images</li>
|
|
* <li>A script tag for JavaScript (<script />). Note that scripts are automatically added to the HTML
|
|
* DOM.</li>
|
|
* <li>A style tag for CSS (<style /> or <link >)</li>
|
|
* <li>Raw text for TEXT</li>
|
|
* <li>A formatted JavaScript object defined by JSON</li>
|
|
* <li>An XML document</li>
|
|
* <li>A binary arraybuffer loaded by XHR</li>
|
|
* <li>An audio tag (<audio >) for HTML audio. Note that it is recommended to use SoundJS APIs to play
|
|
* loaded audio. Specifically, audio loaded by Flash and WebAudio will return a loader object using this method
|
|
* which can not be used to play audio back.</li>
|
|
* </ul>
|
|
* This object is also returned via the {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} event as the 'item`
|
|
* parameter. Note that if a raw result is requested, but not found, the result will be returned instead.
|
|
*/
|
|
p.getResult = function (value, rawResult) {
|
|
var item = this._loadItemsById[value] || this._loadItemsBySrc[value];
|
|
if (item == null) {
|
|
return null;
|
|
}
|
|
var id = item.id;
|
|
if (rawResult && this._loadedRawResults[id]) {
|
|
return this._loadedRawResults[id];
|
|
}
|
|
return this._loadedResults[id];
|
|
};
|
|
|
|
/**
|
|
* Generate an list of items loaded by this queue.
|
|
* @method getItems
|
|
* @param {Boolean} loaded Determines if only items that have been loaded should be returned. If false, in-progress
|
|
* and failed load items will also be included.
|
|
* @returns {Array} A list of objects that have been loaded. Each item includes the {{#crossLink "LoadItem"}}{{/crossLink}},
|
|
* result, and rawResult.
|
|
* @since 0.6.0
|
|
*/
|
|
p.getItems = function (loaded) {
|
|
var arr = [];
|
|
for (var n in this._loadItemsById) {
|
|
var item = this._loadItemsById[n];
|
|
var result = this.getResult(n);
|
|
if (loaded === true && result == null) {
|
|
continue;
|
|
}
|
|
arr.push({
|
|
item: item,
|
|
result: result,
|
|
rawResult: this.getResult(n, true),
|
|
});
|
|
}
|
|
return arr;
|
|
};
|
|
|
|
/**
|
|
* Pause or resume the current load. Active loads will not be cancelled, but the next items in the queue will not
|
|
* be processed when active loads complete. LoadQueues are not paused by default.
|
|
*
|
|
* Note that if new items are added to the queue using {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} or
|
|
* {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}}, a paused queue will be resumed, unless the `loadNow`
|
|
* argument is `false`.
|
|
* @method setPaused
|
|
* @param {Boolean} value Whether the queue should be paused or not.
|
|
*/
|
|
p.setPaused = function (value) {
|
|
this._paused = value;
|
|
if (!this._paused) {
|
|
this._loadNext();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Close the active queue. Closing a queue completely empties the queue, and prevents any remaining items from
|
|
* starting to download. Note that currently any active loads will remain open, and events may be processed.
|
|
*
|
|
* To stop and restart a queue, use the {{#crossLink "LoadQueue/setPaused"}}{{/crossLink}} method instead.
|
|
* @method close
|
|
*/
|
|
p.close = function () {
|
|
while (this._currentLoads.length) {
|
|
this._currentLoads.pop().cancel();
|
|
}
|
|
this._scriptOrder.length = 0;
|
|
this._loadedScripts.length = 0;
|
|
this.loadStartWasDispatched = false;
|
|
this._itemCount = 0;
|
|
this._lastProgress = NaN;
|
|
};
|
|
|
|
// protected methods
|
|
/**
|
|
* Add an item to the queue. Items are formatted into a usable object containing all the properties necessary to
|
|
* load the content. The load queue is populated with the loader instance that handles preloading, and not the load
|
|
* item that was passed in by the user. To look up the load item by id or src, use the {{#crossLink "LoadQueue.getItem"}}{{/crossLink}}
|
|
* method.
|
|
* @method _addItem
|
|
* @param {String|Object} value The item to add to the queue.
|
|
* @param {String} [path] An optional path prepended to the `src`. The path will only be prepended if the src is
|
|
* relative, and does not start with a protocol such as `http://`, or a path like `../`. If the LoadQueue was
|
|
* provided a {{#crossLink "_basePath"}}{{/crossLink}}, then it will optionally be prepended after.
|
|
* @param {String} [basePath] <strong>Deprecated</strong>An optional basePath passed into a {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}}
|
|
* or {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} call. This parameter will be removed in a future tagged
|
|
* version.
|
|
* @private
|
|
*/
|
|
p._addItem = function (value, path, basePath) {
|
|
var item = this._createLoadItem(value, path, basePath); // basePath and manifest path are added to the src.
|
|
if (item == null) {
|
|
return;
|
|
} // Sometimes plugins or types should be skipped.
|
|
var loader = this._createLoader(item);
|
|
if (loader != null) {
|
|
if ("plugins" in loader) {
|
|
loader.plugins = this._plugins;
|
|
}
|
|
item._loader = loader;
|
|
this._loadQueue.push(loader);
|
|
this._loadQueueBackup.push(loader);
|
|
|
|
this._numItems++;
|
|
this._updateProgress();
|
|
|
|
// Only worry about script order when using XHR to load scripts. Tags are only loading one at a time.
|
|
if (
|
|
(this.maintainScriptOrder &&
|
|
item.type == createjs.LoadQueue.JAVASCRIPT) ||
|
|
//&& loader instanceof createjs.XHRLoader //NOTE: Have to track all JS files this way
|
|
item.maintainOrder === true
|
|
) {
|
|
this._scriptOrder.push(item);
|
|
this._loadedScripts.push(null);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create a refined {{#crossLink "LoadItem"}}{{/crossLink}}, which contains all the required properties. The type of
|
|
* item is determined by browser support, requirements based on the file type, and developer settings. For example,
|
|
* XHR is only used for file types that support it in new browsers.
|
|
*
|
|
* Before the item is returned, any plugins registered to handle the type or extension will be fired, which may
|
|
* alter the load item.
|
|
* @method _createLoadItem
|
|
* @param {String | Object | HTMLAudioElement | HTMLImageElement} value The item that needs to be preloaded.
|
|
* @param {String} [path] A path to prepend to the item's source. Sources beginning with http:// or similar will
|
|
* not receive a path. Since PreloadJS 0.4.1, the src will be modified to include the `path` and {{#crossLink "LoadQueue/_basePath:property"}}{{/crossLink}}
|
|
* when it is added.
|
|
* @param {String} [basePath] <strong>Deprectated</strong> A base path to prepend to the items source in addition to
|
|
* the path argument.
|
|
* @return {Object} The loader instance that will be used.
|
|
* @private
|
|
*/
|
|
p._createLoadItem = function (value, path, basePath) {
|
|
var item = createjs.LoadItem.create(value);
|
|
if (item == null) {
|
|
return null;
|
|
}
|
|
|
|
var bp = ""; // Store the generated basePath
|
|
var useBasePath = basePath || this._basePath;
|
|
|
|
if (item.src instanceof Object) {
|
|
if (!item.type) {
|
|
return null;
|
|
} // the the src is an object, type is required to pass off to plugin
|
|
if (path) {
|
|
bp = path;
|
|
var pathMatch = createjs.RequestUtils.parseURI(path);
|
|
// Also append basePath
|
|
if (useBasePath != null && !pathMatch.absolute && !pathMatch.relative) {
|
|
bp = useBasePath + bp;
|
|
}
|
|
} else if (useBasePath != null) {
|
|
bp = useBasePath;
|
|
}
|
|
} else {
|
|
// Determine Extension, etc.
|
|
var match = createjs.RequestUtils.parseURI(item.src);
|
|
if (match.extension) {
|
|
item.ext = match.extension;
|
|
}
|
|
if (item.type == null) {
|
|
item.type = createjs.RequestUtils.getTypeByExtension(item.ext);
|
|
}
|
|
|
|
// Inject path & basePath
|
|
var autoId = item.src;
|
|
if (!match.absolute && !match.relative) {
|
|
if (path) {
|
|
bp = path;
|
|
var pathMatch = createjs.RequestUtils.parseURI(path);
|
|
autoId = path + autoId;
|
|
// Also append basePath
|
|
if (
|
|
useBasePath != null &&
|
|
!pathMatch.absolute &&
|
|
!pathMatch.relative
|
|
) {
|
|
bp = useBasePath + bp;
|
|
}
|
|
} else if (useBasePath != null) {
|
|
bp = useBasePath;
|
|
}
|
|
}
|
|
item.src = bp + item.src;
|
|
}
|
|
item.path = bp;
|
|
|
|
// If there's no id, set one now.
|
|
if (item.id === undefined || item.id === null || item.id === "") {
|
|
item.id = autoId;
|
|
}
|
|
|
|
// Give plugins a chance to modify the loadItem:
|
|
var customHandler =
|
|
this._typeCallbacks[item.type] || this._extensionCallbacks[item.ext];
|
|
if (customHandler) {
|
|
// Plugins are now passed both the full source, as well as a combined path+basePath (appropriately)
|
|
var result = customHandler.callback.call(customHandler.scope, item, this);
|
|
|
|
// The plugin will handle the load, or has canceled it. Ignore it.
|
|
if (result === false) {
|
|
return null;
|
|
|
|
// Load as normal:
|
|
} else if (result === true) {
|
|
// Do Nothing
|
|
// Result is a loader class:
|
|
} else if (result != null) {
|
|
item._loader = result;
|
|
}
|
|
|
|
// Update the extension in case the type changed:
|
|
match = createjs.RequestUtils.parseURI(item.src);
|
|
if (match.extension != null) {
|
|
item.ext = match.extension;
|
|
}
|
|
}
|
|
|
|
// Store the item for lookup. This also helps clean-up later.
|
|
this._loadItemsById[item.id] = item;
|
|
this._loadItemsBySrc[item.src] = item;
|
|
|
|
if (item.crossOrigin == null) {
|
|
item.crossOrigin = this._crossOrigin;
|
|
}
|
|
|
|
return item;
|
|
};
|
|
|
|
/**
|
|
* Create a loader for a load item.
|
|
* @method _createLoader
|
|
* @param {Object} item A formatted load item that can be used to generate a loader.
|
|
* @return {AbstractLoader} A loader that can be used to load content.
|
|
* @private
|
|
*/
|
|
p._createLoader = function (item) {
|
|
if (item._loader != null) {
|
|
// A plugin already specified a loader
|
|
return item._loader;
|
|
}
|
|
|
|
// Initially, try and use the provided/supported XHR mode:
|
|
var preferXHR = this.preferXHR;
|
|
|
|
for (var i = 0; i < this._availableLoaders.length; i++) {
|
|
var loader = this._availableLoaders[i];
|
|
if (loader && loader.canLoadItem(item)) {
|
|
return new loader(item, preferXHR);
|
|
}
|
|
}
|
|
|
|
// TODO: Log error (requires createjs.log)
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Load the next item in the queue. If the queue is empty (all items have been loaded), then the complete event
|
|
* is processed. The queue will "fill up" any empty slots, up to the max connection specified using
|
|
* {{#crossLink "LoadQueue.setMaxConnections"}}{{/crossLink}} method. The only exception is scripts that are loaded
|
|
* using tags, which have to be loaded one at a time to maintain load order.
|
|
* @method _loadNext
|
|
* @private
|
|
*/
|
|
p._loadNext = function () {
|
|
if (this._paused) {
|
|
return;
|
|
}
|
|
|
|
// Only dispatch loadstart event when the first file is loaded.
|
|
if (!this._loadStartWasDispatched) {
|
|
this._sendLoadStart();
|
|
this._loadStartWasDispatched = true;
|
|
}
|
|
|
|
// The queue has completed.
|
|
if (this._numItems == this._numItemsLoaded) {
|
|
this.loaded = true;
|
|
this._sendComplete();
|
|
|
|
// Load the next queue, if it has been defined.
|
|
if (this.next && this.next.load) {
|
|
this.next.load();
|
|
}
|
|
} else {
|
|
this.loaded = false;
|
|
}
|
|
|
|
// Must iterate forwards to load in the right order.
|
|
for (var i = 0; i < this._loadQueue.length; i++) {
|
|
if (this._currentLoads.length >= this._maxConnections) {
|
|
break;
|
|
}
|
|
var loader = this._loadQueue[i];
|
|
|
|
// Determine if we should be only loading one tag-script at a time:
|
|
// Note: maintainOrder items don't do anything here because we can hold onto their loaded value
|
|
if (!this._canStartLoad(loader)) {
|
|
continue;
|
|
}
|
|
this._loadQueue.splice(i, 1);
|
|
i--;
|
|
this._loadItem(loader);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Begin loading an item. Event listeners are not added to the loaders until the load starts.
|
|
* @method _loadItem
|
|
* @param {AbstractLoader} loader The loader instance to start. Currently, this will be an XHRLoader or TagLoader.
|
|
* @private
|
|
*/
|
|
p._loadItem = function (loader) {
|
|
loader.on("fileload", this._handleFileLoad, this);
|
|
loader.on("progress", this._handleProgress, this);
|
|
loader.on("complete", this._handleFileComplete, this);
|
|
loader.on("error", this._handleError, this);
|
|
loader.on("fileerror", this._handleFileError, this);
|
|
this._currentLoads.push(loader);
|
|
this._sendFileStart(loader.getItem());
|
|
loader.load();
|
|
};
|
|
|
|
/**
|
|
* The callback that is fired when a loader loads a file. This enables loaders like {{#crossLink "ManifestLoader"}}{{/crossLink}}
|
|
* to maintain internal queues, but for this queue to dispatch the {{#crossLink "fileload:event"}}{{/crossLink}}
|
|
* events.
|
|
* @param {Event} event The {{#crossLink "AbstractLoader/fileload:event"}}{{/crossLink}} event from the loader.
|
|
* @private
|
|
* @since 0.6.0
|
|
*/
|
|
p._handleFileLoad = function (event) {
|
|
event.target = null;
|
|
this.dispatchEvent(event);
|
|
};
|
|
|
|
/**
|
|
* The callback that is fired when a loader encounters an error from an internal file load operation. This enables
|
|
* loaders like M
|
|
* @param event
|
|
* @private
|
|
*/
|
|
p._handleFileError = function (event) {
|
|
var newEvent = new createjs.ErrorEvent("FILE_LOAD_ERROR", null, event.item);
|
|
this._sendError(newEvent);
|
|
};
|
|
|
|
/**
|
|
* The callback that is fired when a loader encounters an error. The queue will continue loading unless {{#crossLink "LoadQueue/stopOnError:property"}}{{/crossLink}}
|
|
* is set to `true`.
|
|
* @method _handleError
|
|
* @param {ErrorEvent} event The error event, containing relevant error information.
|
|
* @private
|
|
*/
|
|
p._handleError = function (event) {
|
|
var loader = event.target;
|
|
this._numItemsLoaded++;
|
|
|
|
this._finishOrderedItem(loader, true);
|
|
this._updateProgress();
|
|
|
|
var newEvent = new createjs.ErrorEvent(
|
|
"FILE_LOAD_ERROR",
|
|
null,
|
|
loader.getItem()
|
|
);
|
|
// TODO: Propagate actual error message.
|
|
|
|
this._sendError(newEvent);
|
|
|
|
if (!this.stopOnError) {
|
|
this._removeLoadItem(loader);
|
|
this._cleanLoadItem(loader);
|
|
this._loadNext();
|
|
} else {
|
|
this.setPaused(true);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* An item has finished loading. We can assume that it is totally loaded, has been parsed for immediate use, and
|
|
* is available as the "result" property on the load item. The raw text result for a parsed item (such as JSON, XML,
|
|
* CSS, JavaScript, etc) is available as the "rawResult" property, and can also be looked up using {{#crossLink "LoadQueue/getResult"}}{{/crossLink}}.
|
|
* @method _handleFileComplete
|
|
* @param {Event} event The event object from the loader.
|
|
* @private
|
|
*/
|
|
p._handleFileComplete = function (event) {
|
|
var loader = event.target;
|
|
var item = loader.getItem();
|
|
|
|
var result = loader.getResult();
|
|
this._loadedResults[item.id] = result;
|
|
var rawResult = loader.getResult(true);
|
|
if (rawResult != null && rawResult !== result) {
|
|
this._loadedRawResults[item.id] = rawResult;
|
|
}
|
|
|
|
this._saveLoadedItems(loader);
|
|
|
|
// Remove the load item
|
|
this._removeLoadItem(loader);
|
|
|
|
if (!this._finishOrderedItem(loader)) {
|
|
// The item was NOT managed, so process it now
|
|
this._processFinishedLoad(item, loader);
|
|
}
|
|
|
|
// Clean up the load item
|
|
this._cleanLoadItem(loader);
|
|
};
|
|
|
|
/**
|
|
* Some loaders might load additional content, other than the item they were passed (such as {{#crossLink "ManifestLoader"}}{{/crossLink}}).
|
|
* Any items exposed by the loader using {{#crossLink "AbstractLoader/getLoadItems"}}{{/crossLink}} are added to the
|
|
* LoadQueue's look-ups, including {{#crossLink "getItem"}}{{/crossLink}} and {{#crossLink "getResult"}}{{/crossLink}}
|
|
* methods.
|
|
* @method _saveLoadedItems
|
|
* @param {AbstractLoader} loader
|
|
* @protected
|
|
* @since 0.6.0
|
|
*/
|
|
p._saveLoadedItems = function (loader) {
|
|
// TODO: Not sure how to handle this. Would be nice to expose the items.
|
|
// Loaders may load sub-items. This adds them to this queue
|
|
var list = loader.getLoadedItems();
|
|
if (list === null) {
|
|
return;
|
|
}
|
|
|
|
for (var i = 0; i < list.length; i++) {
|
|
var item = list[i].item;
|
|
|
|
// Store item lookups
|
|
this._loadItemsBySrc[item.src] = item;
|
|
this._loadItemsById[item.id] = item;
|
|
|
|
// Store loaded content
|
|
this._loadedResults[item.id] = list[i].result;
|
|
this._loadedRawResults[item.id] = list[i].rawResult;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Flag an item as finished. If the item's order is being managed, then ensure that it is allowed to finish, and if
|
|
* so, trigger prior items to trigger as well.
|
|
* @method _finishOrderedItem
|
|
* @param {AbstractLoader} loader
|
|
* @param {Boolean} loadFailed
|
|
* @return {Boolean} If the item's order is being managed. This allows the caller to take an alternate
|
|
* behaviour if it is.
|
|
* @private
|
|
*/
|
|
p._finishOrderedItem = function (loader, loadFailed) {
|
|
var item = loader.getItem();
|
|
|
|
if (
|
|
(this.maintainScriptOrder &&
|
|
item.type == createjs.LoadQueue.JAVASCRIPT) ||
|
|
item.maintainOrder
|
|
) {
|
|
//TODO: Evaluate removal of the _currentlyLoadingScript
|
|
if (loader instanceof createjs.JavaScriptLoader) {
|
|
this._currentlyLoadingScript = false;
|
|
}
|
|
|
|
var index = createjs.indexOf(this._scriptOrder, item);
|
|
if (index == -1) {
|
|
return false;
|
|
} // This loader no longer exists
|
|
this._loadedScripts[index] = loadFailed === true ? true : item;
|
|
|
|
this._checkScriptLoadOrder();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Ensure the scripts load and dispatch in the correct order. When using XHR, scripts are stored in an array in the
|
|
* order they were added, but with a "null" value. When they are completed, the value is set to the load item,
|
|
* and then when they are processed and dispatched, the value is set to `true`. This method simply
|
|
* iterates the array, and ensures that any loaded items that are not preceded by a `null` value are
|
|
* dispatched.
|
|
* @method _checkScriptLoadOrder
|
|
* @private
|
|
*/
|
|
p._checkScriptLoadOrder = function () {
|
|
var l = this._loadedScripts.length;
|
|
|
|
for (var i = 0; i < l; i++) {
|
|
var item = this._loadedScripts[i];
|
|
if (item === null) {
|
|
break;
|
|
} // This is still loading. Do not process further.
|
|
if (item === true) {
|
|
continue;
|
|
} // This has completed, and been processed. Move on.
|
|
|
|
var loadItem = this._loadedResults[item.id];
|
|
if (item.type == createjs.LoadQueue.JAVASCRIPT) {
|
|
// Append script tags to the head automatically.
|
|
createjs.DomUtils.appendToHead(loadItem);
|
|
}
|
|
|
|
var loader = item._loader;
|
|
this._processFinishedLoad(item, loader);
|
|
this._loadedScripts[i] = true;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* A file has completed loading, and the LoadQueue can move on. This triggers the complete event, and kick-starts
|
|
* the next item.
|
|
* @method _processFinishedLoad
|
|
* @param {LoadItem|Object} item
|
|
* @param {AbstractLoader} loader
|
|
* @protected
|
|
*/
|
|
p._processFinishedLoad = function (item, loader) {
|
|
this._numItemsLoaded++;
|
|
|
|
// Since LoadQueue needs maintain order, we can't append scripts in the loader.
|
|
// So we do it here instead. Or in _checkScriptLoadOrder();
|
|
if (
|
|
!this.maintainScriptOrder &&
|
|
item.type == createjs.LoadQueue.JAVASCRIPT
|
|
) {
|
|
var tag = loader.getTag();
|
|
createjs.DomUtils.appendToHead(tag);
|
|
}
|
|
|
|
this._updateProgress();
|
|
this._sendFileComplete(item, loader);
|
|
this._loadNext();
|
|
};
|
|
|
|
/**
|
|
* Ensure items with `maintainOrder=true` that are before the specified item have loaded. This only applies to
|
|
* JavaScript items that are being loaded with a TagLoader, since they have to be loaded and completed <strong>before</strong>
|
|
* the script can even be started, since it exist in the DOM while loading.
|
|
* @method _canStartLoad
|
|
* @param {AbstractLoader} loader The loader for the item
|
|
* @return {Boolean} Whether the item can start a load or not.
|
|
* @private
|
|
*/
|
|
p._canStartLoad = function (loader) {
|
|
if (!this.maintainScriptOrder || loader.preferXHR) {
|
|
return true;
|
|
}
|
|
var item = loader.getItem();
|
|
if (item.type != createjs.LoadQueue.JAVASCRIPT) {
|
|
return true;
|
|
}
|
|
if (this._currentlyLoadingScript) {
|
|
return false;
|
|
}
|
|
|
|
var index = this._scriptOrder.indexOf(item);
|
|
var i = 0;
|
|
while (i < index) {
|
|
var checkItem = this._loadedScripts[i];
|
|
if (checkItem == null) {
|
|
return false;
|
|
}
|
|
i++;
|
|
}
|
|
this._currentlyLoadingScript = true;
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* A load item is completed or was canceled, and needs to be removed from the LoadQueue.
|
|
* @method _removeLoadItem
|
|
* @param {AbstractLoader} loader A loader instance to remove.
|
|
* @private
|
|
*/
|
|
p._removeLoadItem = function (loader) {
|
|
var l = this._currentLoads.length;
|
|
for (var i = 0; i < l; i++) {
|
|
if (this._currentLoads[i] == loader) {
|
|
this._currentLoads.splice(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Remove unneeded references from a loader.
|
|
*
|
|
* @param loader
|
|
* @private
|
|
*/
|
|
p._cleanLoadItem = function (loader) {
|
|
var item = loader.getItem();
|
|
if (item) {
|
|
delete item._loader;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* An item has dispatched progress. Propagate that progress, and update the LoadQueue's overall progress.
|
|
* @method _handleProgress
|
|
* @param {ProgressEvent} event The progress event from the item.
|
|
* @private
|
|
*/
|
|
p._handleProgress = function (event) {
|
|
var loader = event.target;
|
|
this._sendFileProgress(loader.getItem(), loader.progress);
|
|
this._updateProgress();
|
|
};
|
|
|
|
/**
|
|
* Overall progress has changed, so determine the new progress amount and dispatch it. This changes any time an
|
|
* item dispatches progress or completes. Note that since we don't always know the actual filesize of items before
|
|
* they are loaded. In this case, we define a "slot" for each item (1 item in 10 would get 10%), and then append
|
|
* loaded progress on top of the already-loaded items.
|
|
*
|
|
* For example, if 5/10 items have loaded, and item 6 is 20% loaded, the total progress would be:
|
|
* <ul>
|
|
* <li>5/10 of the items in the queue (50%)</li>
|
|
* <li>plus 20% of item 6's slot (2%)</li>
|
|
* <li>equals 52%</li>
|
|
* </ul>
|
|
* @method _updateProgress
|
|
* @private
|
|
*/
|
|
p._updateProgress = function () {
|
|
var loaded = this._numItemsLoaded / this._numItems; // Fully Loaded Progress
|
|
var remaining = this._numItems - this._numItemsLoaded;
|
|
if (remaining > 0) {
|
|
var chunk = 0;
|
|
for (var i = 0, l = this._currentLoads.length; i < l; i++) {
|
|
chunk += this._currentLoads[i].progress;
|
|
}
|
|
loaded += (chunk / remaining) * (remaining / this._numItems);
|
|
}
|
|
|
|
if (this._lastProgress != loaded) {
|
|
this._sendProgress(loaded);
|
|
this._lastProgress = loaded;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Clean out item results, to free them from memory. Mainly, the loaded item and results are cleared from internal
|
|
* hashes.
|
|
* @method _disposeItem
|
|
* @param {LoadItem|Object} item The item that was passed in for preloading.
|
|
* @private
|
|
*/
|
|
p._disposeItem = function (item) {
|
|
delete this._loadedResults[item.id];
|
|
delete this._loadedRawResults[item.id];
|
|
delete this._loadItemsById[item.id];
|
|
delete this._loadItemsBySrc[item.src];
|
|
};
|
|
|
|
/**
|
|
* Dispatch a "fileprogress" {{#crossLink "Event"}}{{/crossLink}}. Please see the LoadQueue {{#crossLink "LoadQueue/fileprogress:event"}}{{/crossLink}}
|
|
* event for details on the event payload.
|
|
* @method _sendFileProgress
|
|
* @param {LoadItem|Object} item The item that is being loaded.
|
|
* @param {Number} progress The amount the item has been loaded (between 0 and 1).
|
|
* @protected
|
|
*/
|
|
p._sendFileProgress = function (item, progress) {
|
|
if (this._isCanceled() || this._paused) {
|
|
return;
|
|
}
|
|
if (!this.hasEventListener("fileprogress")) {
|
|
return;
|
|
}
|
|
|
|
//LM: Rework ProgressEvent to support this?
|
|
var event = new createjs.Event("fileprogress");
|
|
event.progress = progress;
|
|
event.loaded = progress;
|
|
event.total = 1;
|
|
event.item = item;
|
|
|
|
this.dispatchEvent(event);
|
|
};
|
|
|
|
/**
|
|
* Dispatch a fileload {{#crossLink "Event"}}{{/crossLink}}. Please see the {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} event for
|
|
* details on the event payload.
|
|
* @method _sendFileComplete
|
|
* @param {LoadItemObject} item The item that is being loaded.
|
|
* @param {AbstractLoader} loader
|
|
* @protected
|
|
*/
|
|
p._sendFileComplete = function (item, loader) {
|
|
if (this._isCanceled() || this._paused) {
|
|
return;
|
|
}
|
|
|
|
var event = new createjs.Event("fileload");
|
|
event.loader = loader;
|
|
event.item = item;
|
|
event.result = this._loadedResults[item.id];
|
|
event.rawResult = this._loadedRawResults[item.id];
|
|
|
|
// This calls a handler specified on the actual load item. Currently, the SoundJS plugin uses this.
|
|
if (item.completeHandler) {
|
|
item.completeHandler(event);
|
|
}
|
|
|
|
this.hasEventListener("fileload") && this.dispatchEvent(event);
|
|
};
|
|
|
|
/**
|
|
* Dispatch a filestart {{#crossLink "Event"}}{{/crossLink}} immediately before a file starts to load. Please see
|
|
* the {{#crossLink "LoadQueue/filestart:event"}}{{/crossLink}} event for details on the event payload.
|
|
* @method _sendFileStart
|
|
* @param {LoadItem|Object} item The item that is being loaded.
|
|
* @protected
|
|
*/
|
|
p._sendFileStart = function (item) {
|
|
var event = new createjs.Event("filestart");
|
|
event.item = item;
|
|
this.hasEventListener("filestart") && this.dispatchEvent(event);
|
|
};
|
|
|
|
p.toString = function () {
|
|
return "[PreloadJS LoadQueue]";
|
|
};
|
|
|
|
createjs.LoadQueue = createjs.promote(LoadQueue, "AbstractLoader");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// TextLoader.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* A loader for Text files.
|
|
* @class TextLoader
|
|
* @param {LoadItem|Object} loadItem
|
|
* @extends AbstractLoader
|
|
* @constructor
|
|
*/
|
|
function TextLoader(loadItem) {
|
|
this.AbstractLoader_constructor(
|
|
loadItem,
|
|
true,
|
|
createjs.AbstractLoader.TEXT
|
|
);
|
|
}
|
|
|
|
var p = createjs.extend(TextLoader, createjs.AbstractLoader);
|
|
var s = TextLoader;
|
|
|
|
// static methods
|
|
/**
|
|
* Determines if the loader can load a specific item. This loader loads items that are of type {{#crossLink "AbstractLoader/TEXT:property"}}{{/crossLink}},
|
|
* but is also the default loader if a file type can not be determined.
|
|
* @method canLoadItem
|
|
* @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load.
|
|
* @returns {Boolean} Whether the loader can load the item.
|
|
* @static
|
|
*/
|
|
s.canLoadItem = function (item) {
|
|
return item.type == createjs.AbstractLoader.TEXT;
|
|
};
|
|
|
|
createjs.TextLoader = createjs.promote(TextLoader, "AbstractLoader");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// BinaryLoader.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* A loader for binary files. This is useful for loading web audio, or content that requires an ArrayBuffer.
|
|
* @class BinaryLoader
|
|
* @param {LoadItem|Object} loadItem
|
|
* @extends AbstractLoader
|
|
* @constructor
|
|
*/
|
|
function BinaryLoader(loadItem) {
|
|
this.AbstractLoader_constructor(
|
|
loadItem,
|
|
true,
|
|
createjs.AbstractLoader.BINARY
|
|
);
|
|
this.on("initialize", this._updateXHR, this);
|
|
}
|
|
|
|
var p = createjs.extend(BinaryLoader, createjs.AbstractLoader);
|
|
var s = BinaryLoader;
|
|
|
|
// static methods
|
|
/**
|
|
* Determines if the loader can load a specific item. This loader can only load items that are of type
|
|
* {{#crossLink "AbstractLoader/BINARY:property"}}{{/crossLink}}
|
|
* @method canLoadItem
|
|
* @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load.
|
|
* @returns {Boolean} Whether the loader can load the item.
|
|
* @static
|
|
*/
|
|
s.canLoadItem = function (item) {
|
|
return item.type == createjs.AbstractLoader.BINARY;
|
|
};
|
|
|
|
// private methods
|
|
/**
|
|
* Before the item loads, set the response type to "arraybuffer"
|
|
* @property _updateXHR
|
|
* @param {Event} event
|
|
* @private
|
|
*/
|
|
p._updateXHR = function (event) {
|
|
event.loader.setResponseType("arraybuffer");
|
|
};
|
|
|
|
createjs.BinaryLoader = createjs.promote(BinaryLoader, "AbstractLoader");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// CSSLoader.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* A loader for CSS files.
|
|
* @class CSSLoader
|
|
* @param {LoadItem|Object} loadItem
|
|
* @param {Boolean} preferXHR
|
|
* @extends AbstractLoader
|
|
* @constructor
|
|
*/
|
|
function CSSLoader(loadItem, preferXHR) {
|
|
this.AbstractLoader_constructor(
|
|
loadItem,
|
|
preferXHR,
|
|
createjs.AbstractLoader.CSS
|
|
);
|
|
|
|
// public properties
|
|
this.resultFormatter = this._formatResult;
|
|
|
|
// protected properties
|
|
this._tagSrcAttribute = "href";
|
|
|
|
if (preferXHR) {
|
|
this._tag = document.createElement("style");
|
|
} else {
|
|
this._tag = document.createElement("link");
|
|
}
|
|
|
|
this._tag.rel = "stylesheet";
|
|
this._tag.type = "text/css";
|
|
}
|
|
|
|
var p = createjs.extend(CSSLoader, createjs.AbstractLoader);
|
|
var s = CSSLoader;
|
|
|
|
// static methods
|
|
/**
|
|
* Determines if the loader can load a specific item. This loader can only load items that are of type
|
|
* {{#crossLink "AbstractLoader/CSS:property"}}{{/crossLink}}.
|
|
* @method canLoadItem
|
|
* @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load.
|
|
* @returns {Boolean} Whether the loader can load the item.
|
|
* @static
|
|
*/
|
|
s.canLoadItem = function (item) {
|
|
return item.type == createjs.AbstractLoader.CSS;
|
|
};
|
|
|
|
// protected methods
|
|
/**
|
|
* The result formatter for CSS files.
|
|
* @method _formatResult
|
|
* @param {AbstractLoader} loader
|
|
* @returns {HTMLLinkElement|HTMLStyleElement}
|
|
* @private
|
|
*/
|
|
p._formatResult = function (loader) {
|
|
if (this._preferXHR) {
|
|
var tag = loader.getTag();
|
|
|
|
if (tag.styleSheet) {
|
|
// IE
|
|
tag.styleSheet.cssText = loader.getResult(true);
|
|
} else {
|
|
var textNode = document.createTextNode(loader.getResult(true));
|
|
tag.appendChild(textNode);
|
|
}
|
|
} else {
|
|
tag = this._tag;
|
|
}
|
|
|
|
createjs.DomUtils.appendToHead(tag);
|
|
|
|
return tag;
|
|
};
|
|
|
|
createjs.CSSLoader = createjs.promote(CSSLoader, "AbstractLoader");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// ImageLoader.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* A loader for image files.
|
|
* @class ImageLoader
|
|
* @param {LoadItem|Object} loadItem
|
|
* @param {Boolean} preferXHR
|
|
* @extends AbstractLoader
|
|
* @constructor
|
|
*/
|
|
function ImageLoader(loadItem, preferXHR) {
|
|
this.AbstractLoader_constructor(
|
|
loadItem,
|
|
preferXHR,
|
|
createjs.AbstractLoader.IMAGE
|
|
);
|
|
|
|
// public properties
|
|
this.resultFormatter = this._formatResult;
|
|
|
|
// protected properties
|
|
this._tagSrcAttribute = "src";
|
|
|
|
// Check if the preload item is already a tag.
|
|
if (createjs.RequestUtils.isImageTag(loadItem)) {
|
|
this._tag = loadItem;
|
|
} else if (createjs.RequestUtils.isImageTag(loadItem.src)) {
|
|
this._tag = loadItem.src;
|
|
} else if (createjs.RequestUtils.isImageTag(loadItem.tag)) {
|
|
this._tag = loadItem.tag;
|
|
}
|
|
|
|
if (this._tag != null) {
|
|
this._preferXHR = false;
|
|
} else {
|
|
this._tag = document.createElement("img");
|
|
}
|
|
|
|
this.on("initialize", this._updateXHR, this);
|
|
}
|
|
|
|
var p = createjs.extend(ImageLoader, createjs.AbstractLoader);
|
|
var s = ImageLoader;
|
|
|
|
// static methods
|
|
/**
|
|
* Determines if the loader can load a specific item. This loader can only load items that are of type
|
|
* {{#crossLink "AbstractLoader/IMAGE:property"}}{{/crossLink}}.
|
|
* @method canLoadItem
|
|
* @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load.
|
|
* @returns {Boolean} Whether the loader can load the item.
|
|
* @static
|
|
*/
|
|
s.canLoadItem = function (item) {
|
|
return item.type == createjs.AbstractLoader.IMAGE;
|
|
};
|
|
|
|
// public methods
|
|
p.load = function () {
|
|
if (this._tag.src != "" && this._tag.complete) {
|
|
this._sendComplete();
|
|
return;
|
|
}
|
|
|
|
var crossOrigin = this._item.crossOrigin;
|
|
if (crossOrigin == true) {
|
|
crossOrigin = "Anonymous";
|
|
}
|
|
if (crossOrigin != null && !createjs.RequestUtils.isLocal(this._item.src)) {
|
|
this._tag.crossOrigin = crossOrigin;
|
|
}
|
|
|
|
this.AbstractLoader_load();
|
|
};
|
|
|
|
// protected methods
|
|
/**
|
|
* Before the item loads, set its mimeType and responseType.
|
|
* @property _updateXHR
|
|
* @param {Event} event
|
|
* @private
|
|
*/
|
|
p._updateXHR = function (event) {
|
|
event.loader.mimeType = "text/plain; charset=x-user-defined-binary";
|
|
|
|
// Only exists for XHR
|
|
if (event.loader.setResponseType) {
|
|
event.loader.setResponseType("blob");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The result formatter for Image files.
|
|
* @method _formatResult
|
|
* @param {AbstractLoader} loader
|
|
* @returns {HTMLImageElement}
|
|
* @private
|
|
*/
|
|
p._formatResult = function (loader) {
|
|
return this._formatImage;
|
|
};
|
|
|
|
/**
|
|
* The asynchronous image formatter function. This is required because images have
|
|
* a short delay before they are ready.
|
|
* @method _formatImage
|
|
* @param {Function} successCallback The method to call when the result has finished formatting
|
|
* @param {Function} errorCallback The method to call if an error occurs during formatting
|
|
* @private
|
|
*/
|
|
p._formatImage = function (successCallback, errorCallback) {
|
|
var tag = this._tag;
|
|
var URL = window.URL || window.webkitURL;
|
|
|
|
if (!this._preferXHR) {
|
|
//document.body.removeChild(tag);
|
|
} else if (URL) {
|
|
var objURL = URL.createObjectURL(this.getResult(true));
|
|
tag.src = objURL;
|
|
|
|
tag.addEventListener("load", this._cleanUpURL, false);
|
|
tag.addEventListener("error", this._cleanUpURL, false);
|
|
} else {
|
|
tag.src = this._item.src;
|
|
}
|
|
|
|
if (tag.complete) {
|
|
successCallback(tag);
|
|
} else {
|
|
tag.onload = createjs.proxy(function () {
|
|
successCallback(this._tag);
|
|
}, this);
|
|
|
|
tag.onerror = createjs.proxy(function () {
|
|
errorCallback(_this._tag);
|
|
}, this);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Clean up the ObjectURL, the tag is done with it. Note that this function is run
|
|
* as an event listener without a proxy/closure, as it doesn't require it - so do not
|
|
* include any functionality that requires scope without changing it.
|
|
* @method _cleanUpURL
|
|
* @param event
|
|
* @private
|
|
*/
|
|
p._cleanUpURL = function (event) {
|
|
var URL = window.URL || window.webkitURL;
|
|
URL.revokeObjectURL(event.target.src);
|
|
};
|
|
|
|
createjs.ImageLoader = createjs.promote(ImageLoader, "AbstractLoader");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// JavaScriptLoader.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* A loader for JavaScript files.
|
|
* @class JavaScriptLoader
|
|
* @param {LoadItem|Object} loadItem
|
|
* @param {Boolean} preferXHR
|
|
* @extends AbstractLoader
|
|
* @constructor
|
|
*/
|
|
function JavaScriptLoader(loadItem, preferXHR) {
|
|
this.AbstractLoader_constructor(
|
|
loadItem,
|
|
preferXHR,
|
|
createjs.AbstractLoader.JAVASCRIPT
|
|
);
|
|
|
|
// public properties
|
|
this.resultFormatter = this._formatResult;
|
|
|
|
// protected properties
|
|
this._tagSrcAttribute = "src";
|
|
this.setTag(document.createElement("script"));
|
|
}
|
|
|
|
var p = createjs.extend(JavaScriptLoader, createjs.AbstractLoader);
|
|
var s = JavaScriptLoader;
|
|
|
|
// static methods
|
|
/**
|
|
* Determines if the loader can load a specific item. This loader can only load items that are of type
|
|
* {{#crossLink "AbstractLoader/JAVASCRIPT:property"}}{{/crossLink}}
|
|
* @method canLoadItem
|
|
* @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load.
|
|
* @returns {Boolean} Whether the loader can load the item.
|
|
* @static
|
|
*/
|
|
s.canLoadItem = function (item) {
|
|
return item.type == createjs.AbstractLoader.JAVASCRIPT;
|
|
};
|
|
|
|
// protected methods
|
|
/**
|
|
* The result formatter for JavaScript files.
|
|
* @method _formatResult
|
|
* @param {AbstractLoader} loader
|
|
* @returns {HTMLLinkElement|HTMLStyleElement}
|
|
* @private
|
|
*/
|
|
p._formatResult = function (loader) {
|
|
var tag = loader.getTag();
|
|
if (this._preferXHR) {
|
|
tag.text = loader.getResult(true);
|
|
}
|
|
return tag;
|
|
};
|
|
|
|
createjs.JavaScriptLoader = createjs.promote(
|
|
JavaScriptLoader,
|
|
"AbstractLoader"
|
|
);
|
|
})();
|
|
|
|
//##############################################################################
|
|
// JSONLoader.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* A loader for JSON files. To load JSON cross-domain, use JSONP and the {{#crossLink "JSONPLoader"}}{{/crossLink}}
|
|
* instead. To load JSON-formatted manifests, use {{#crossLink "ManifestLoader"}}{{/crossLink}}, and to
|
|
* load EaselJS SpriteSheets, use {{#crossLink "SpriteSheetLoader"}}{{/crossLink}}.
|
|
* @class JSONLoader
|
|
* @param {LoadItem|Object} loadItem
|
|
* @extends AbstractLoader
|
|
* @constructor
|
|
*/
|
|
function JSONLoader(loadItem) {
|
|
this.AbstractLoader_constructor(
|
|
loadItem,
|
|
true,
|
|
createjs.AbstractLoader.JSON
|
|
);
|
|
|
|
// public properties
|
|
this.resultFormatter = this._formatResult;
|
|
}
|
|
|
|
var p = createjs.extend(JSONLoader, createjs.AbstractLoader);
|
|
var s = JSONLoader;
|
|
|
|
// static methods
|
|
/**
|
|
* Determines if the loader can load a specific item. This loader can only load items that are of type
|
|
* {{#crossLink "AbstractLoader/JSON:property"}}{{/crossLink}}.
|
|
* @method canLoadItem
|
|
* @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load.
|
|
* @returns {Boolean} Whether the loader can load the item.
|
|
* @static
|
|
*/
|
|
s.canLoadItem = function (item) {
|
|
return item.type == createjs.AbstractLoader.JSON;
|
|
};
|
|
|
|
// protected methods
|
|
/**
|
|
* The result formatter for JSON files.
|
|
* @method _formatResult
|
|
* @param {AbstractLoader} loader
|
|
* @returns {HTMLLinkElement|HTMLStyleElement}
|
|
* @private
|
|
*/
|
|
p._formatResult = function (loader) {
|
|
var json = null;
|
|
try {
|
|
json = createjs.DataUtils.parseJSON(loader.getResult(true));
|
|
} catch (e) {
|
|
var event = new createjs.ErrorEvent("JSON_FORMAT", null, e);
|
|
this._sendError(event);
|
|
return e;
|
|
}
|
|
|
|
return json;
|
|
};
|
|
|
|
createjs.JSONLoader = createjs.promote(JSONLoader, "AbstractLoader");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// JSONPLoader.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* A loader for JSONP files, which are JSON-formatted text files, wrapped in a callback. To load regular JSON
|
|
* without a callback use the {{#crossLink "JSONLoader"}}{{/crossLink}} instead. To load JSON-formatted manifests,
|
|
* use {{#crossLink "ManifestLoader"}}{{/crossLink}}, and to load EaselJS SpriteSheets, use
|
|
* {{#crossLink "SpriteSheetLoader"}}{{/crossLink}}.
|
|
*
|
|
* JSONP is a format that provides a solution for loading JSON files cross-domain <em>without</em> requiring CORS.
|
|
* JSONP files are loaded as JavaScript, and the "callback" is executed once they are loaded. The callback in the
|
|
* JSONP must match the callback passed to the loadItem.
|
|
*
|
|
* <h4>Example JSONP</h4>
|
|
*
|
|
* callbackName({
|
|
* "name": "value",
|
|
* "num": 3,
|
|
* "obj": { "bool":true }
|
|
* });
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var loadItem = {id:"json", type:"jsonp", src:"http://server.com/text.json", callback:"callbackName"}
|
|
* var queue = new createjs.LoadQueue();
|
|
* queue.on("complete", handleComplete);
|
|
* queue.loadItem(loadItem);
|
|
*
|
|
* function handleComplete(event) }
|
|
* var json = queue.getResult("json");
|
|
* console.log(json.obj.bool); // true
|
|
* }
|
|
*
|
|
* Note that JSONP files loaded concurrently require a <em>unique</em> callback. To ensure JSONP files are loaded
|
|
* in order, either use the {{#crossLink "LoadQueue/setMaxConnections"}}{{/crossLink}} method (set to 1),
|
|
* or set {{#crossLink "LoadItem/maintainOrder:property"}}{{/crossLink}} on items with the same callback.
|
|
*
|
|
* @class JSONPLoader
|
|
* @param {LoadItem|Object} loadItem
|
|
* @extends AbstractLoader
|
|
* @constructor
|
|
*/
|
|
function JSONPLoader(loadItem) {
|
|
this.AbstractLoader_constructor(
|
|
loadItem,
|
|
false,
|
|
createjs.AbstractLoader.JSONP
|
|
);
|
|
this.setTag(document.createElement("script"));
|
|
this.getTag().type = "text/javascript";
|
|
}
|
|
|
|
var p = createjs.extend(JSONPLoader, createjs.AbstractLoader);
|
|
var s = JSONPLoader;
|
|
|
|
// static methods
|
|
/**
|
|
* Determines if the loader can load a specific item. This loader can only load items that are of type
|
|
* {{#crossLink "AbstractLoader/JSONP:property"}}{{/crossLink}}.
|
|
* @method canLoadItem
|
|
* @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load.
|
|
* @returns {Boolean} Whether the loader can load the item.
|
|
* @static
|
|
*/
|
|
s.canLoadItem = function (item) {
|
|
return item.type == createjs.AbstractLoader.JSONP;
|
|
};
|
|
|
|
// public methods
|
|
p.cancel = function () {
|
|
this.AbstractLoader_cancel();
|
|
this._dispose();
|
|
};
|
|
|
|
/**
|
|
* Loads the JSONp file. Because of the unique loading needs of JSONp
|
|
* we don't use the AbstractLoader.load() method.
|
|
*
|
|
* @method load
|
|
*
|
|
*/
|
|
p.load = function () {
|
|
if (this._item.callback == null) {
|
|
throw new Error("callback is required for loading JSONP requests.");
|
|
}
|
|
|
|
// TODO: Look into creating our own iFrame to handle the load
|
|
// In the first attempt, FF did not get the result
|
|
// result instanceof Object did not work either
|
|
// so we would need to clone the result.
|
|
if (window[this._item.callback] != null) {
|
|
throw new Error(
|
|
"JSONP callback '" +
|
|
this._item.callback +
|
|
"' already exists on window. You need to specify a different callback or re-name the current one."
|
|
);
|
|
}
|
|
|
|
window[this._item.callback] = createjs.proxy(this._handleLoad, this);
|
|
window.document.body.appendChild(this._tag);
|
|
|
|
this._loadTimeout = setTimeout(
|
|
createjs.proxy(this._handleTimeout, this),
|
|
this._item.loadTimeout
|
|
);
|
|
|
|
// Load the tag
|
|
this._tag.src = this._item.src;
|
|
};
|
|
|
|
// private methods
|
|
/**
|
|
* Handle the JSONP callback, which is a public method defined on `window`.
|
|
* @method _handleLoad
|
|
* @param {Object} data The formatted JSON data.
|
|
* @private
|
|
*/
|
|
p._handleLoad = function (data) {
|
|
this._result = this._rawResult = data;
|
|
this._sendComplete();
|
|
|
|
this._dispose();
|
|
};
|
|
|
|
/**
|
|
* The tag request has not loaded within the time specfied in loadTimeout.
|
|
* @method _handleError
|
|
* @param {Object} event The XHR error event.
|
|
* @private
|
|
*/
|
|
p._handleTimeout = function () {
|
|
this._dispose();
|
|
this.dispatchEvent(new createjs.ErrorEvent("timeout"));
|
|
};
|
|
|
|
/**
|
|
* Clean up the JSONP load. This clears out the callback and script tag that this loader creates.
|
|
* @method _dispose
|
|
* @private
|
|
*/
|
|
p._dispose = function () {
|
|
window.document.body.removeChild(this._tag);
|
|
delete window[this._item.callback];
|
|
|
|
clearTimeout(this._loadTimeout);
|
|
};
|
|
|
|
createjs.JSONPLoader = createjs.promote(JSONPLoader, "AbstractLoader");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// ManifestLoader.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* A loader for JSON manifests. Items inside the manifest are loaded before the loader completes. To load manifests
|
|
* using JSONP, specify a {{#crossLink "LoadItem/callback:property"}}{{/crossLink}} as part of the
|
|
* {{#crossLink "LoadItem"}}{{/crossLink}}.
|
|
*
|
|
* The list of files in the manifest must be defined on the top-level JSON object in a `manifest` property. This
|
|
* example shows a sample manifest definition, as well as how to to include a sub-manifest.
|
|
*
|
|
* {
|
|
* "path": "assets/",
|
|
* "manifest": [
|
|
* "image.png",
|
|
* {"src": "image2.png", "id":"image2"},
|
|
* {"src": "sub-manifest.json", "type":"manifest", "callback":"jsonCallback"}
|
|
* ]
|
|
* }
|
|
*
|
|
* When a ManifestLoader has completed loading, the parent loader (usually a {{#crossLink "LoadQueue"}}{{/crossLink}},
|
|
* but could also be another ManifestLoader) will inherit all the loaded items, so you can access them directly.
|
|
*
|
|
* Note that the {{#crossLink "JSONLoader"}}{{/crossLink}} and {{#crossLink "JSONPLoader"}}{{/crossLink}} are
|
|
* higher priority loaders, so manifests <strong>must</strong> set the {{#crossLink "LoadItem"}}{{/crossLink}}
|
|
* {{#crossLink "LoadItem/type:property"}}{{/crossLink}} property to {{#crossLink "AbstractLoader/MANIFEST:property"}}{{/crossLink}}.
|
|
* @class ManifestLoader
|
|
* @param {LoadItem|Object} loadItem
|
|
* @extends AbstractLoader
|
|
* @constructor
|
|
*/
|
|
function ManifestLoader(loadItem) {
|
|
this.AbstractLoader_constructor(
|
|
loadItem,
|
|
null,
|
|
createjs.AbstractLoader.MANIFEST
|
|
);
|
|
|
|
// Public Properties
|
|
/**
|
|
* An array of the plugins registered using {{#crossLink "LoadQueue/installPlugin"}}{{/crossLink}},
|
|
* used to pass plugins to new LoadQueues that may be created.
|
|
* @property _plugins
|
|
* @type {Array}
|
|
* @private
|
|
* @since 0.6.1
|
|
*/
|
|
this.plugins = null;
|
|
|
|
// Protected Properties
|
|
/**
|
|
* An internal {{#crossLink "LoadQueue"}}{{/crossLink}} that loads the contents of the manifest.
|
|
* @property _manifestQueue
|
|
* @type {LoadQueue}
|
|
* @private
|
|
*/
|
|
this._manifestQueue = null;
|
|
}
|
|
|
|
var p = createjs.extend(ManifestLoader, createjs.AbstractLoader);
|
|
var s = ManifestLoader;
|
|
|
|
// static properties
|
|
/**
|
|
* The amount of progress that the manifest itself takes up.
|
|
* @property MANIFEST_PROGRESS
|
|
* @type {number}
|
|
* @default 0.25 (25%)
|
|
* @private
|
|
* @static
|
|
*/
|
|
s.MANIFEST_PROGRESS = 0.25;
|
|
|
|
// static methods
|
|
/**
|
|
* Determines if the loader can load a specific item. This loader can only load items that are of type
|
|
* {{#crossLink "AbstractLoader/MANIFEST:property"}}{{/crossLink}}
|
|
* @method canLoadItem
|
|
* @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load.
|
|
* @returns {Boolean} Whether the loader can load the item.
|
|
* @static
|
|
*/
|
|
s.canLoadItem = function (item) {
|
|
return item.type == createjs.AbstractLoader.MANIFEST;
|
|
};
|
|
|
|
// public methods
|
|
p.load = function () {
|
|
this.AbstractLoader_load();
|
|
};
|
|
|
|
// protected methods
|
|
p._createRequest = function () {
|
|
var callback = this._item.callback;
|
|
if (callback != null) {
|
|
this._request = new createjs.JSONPLoader(this._item);
|
|
} else {
|
|
this._request = new createjs.JSONLoader(this._item);
|
|
}
|
|
};
|
|
|
|
p.handleEvent = function (event) {
|
|
switch (event.type) {
|
|
case "complete":
|
|
this._rawResult = event.target.getResult(true);
|
|
this._result = event.target.getResult();
|
|
this._sendProgress(s.MANIFEST_PROGRESS);
|
|
this._loadManifest(this._result);
|
|
return;
|
|
case "progress":
|
|
event.loaded *= s.MANIFEST_PROGRESS;
|
|
this.progress = event.loaded / event.total;
|
|
if (isNaN(this.progress) || this.progress == Infinity) {
|
|
this.progress = 0;
|
|
}
|
|
this._sendProgress(event);
|
|
return;
|
|
}
|
|
this.AbstractLoader_handleEvent(event);
|
|
};
|
|
|
|
p.destroy = function () {
|
|
this.AbstractLoader_destroy();
|
|
this._manifestQueue.close();
|
|
};
|
|
|
|
/**
|
|
* Create and load the manifest items once the actual manifest has been loaded.
|
|
* @method _loadManifest
|
|
* @param {Object} json
|
|
* @private
|
|
*/
|
|
p._loadManifest = function (json) {
|
|
if (json && json.manifest) {
|
|
var queue = (this._manifestQueue = new createjs.LoadQueue());
|
|
queue.on("fileload", this._handleManifestFileLoad, this);
|
|
queue.on("progress", this._handleManifestProgress, this);
|
|
queue.on("complete", this._handleManifestComplete, this, true);
|
|
queue.on("error", this._handleManifestError, this, true);
|
|
for (var i = 0, l = this.plugins.length; i < l; i++) {
|
|
// conserve order of plugins
|
|
queue.installPlugin(this.plugins[i]);
|
|
}
|
|
queue.loadManifest(json);
|
|
} else {
|
|
this._sendComplete();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* An item from the {{#crossLink "_manifestQueue:property"}}{{/crossLink}} has completed.
|
|
* @method _handleManifestFileLoad
|
|
* @param {Event} event
|
|
* @private
|
|
*/
|
|
p._handleManifestFileLoad = function (event) {
|
|
event.target = null;
|
|
this.dispatchEvent(event);
|
|
};
|
|
|
|
/**
|
|
* The manifest has completed loading. This triggers the {{#crossLink "AbstractLoader/complete:event"}}{{/crossLink}}
|
|
* {{#crossLink "Event"}}{{/crossLink}} from the ManifestLoader.
|
|
* @method _handleManifestComplete
|
|
* @param {Event} event
|
|
* @private
|
|
*/
|
|
p._handleManifestComplete = function (event) {
|
|
this._loadedItems = this._manifestQueue.getItems(true);
|
|
this._sendComplete();
|
|
};
|
|
|
|
/**
|
|
* The manifest has reported progress.
|
|
* @method _handleManifestProgress
|
|
* @param {ProgressEvent} event
|
|
* @private
|
|
*/
|
|
p._handleManifestProgress = function (event) {
|
|
this.progress =
|
|
event.progress * (1 - s.MANIFEST_PROGRESS) + s.MANIFEST_PROGRESS;
|
|
this._sendProgress(this.progress);
|
|
};
|
|
|
|
/**
|
|
* The manifest has reported an error with one of the files.
|
|
* @method _handleManifestError
|
|
* @param {ErrorEvent} event
|
|
* @private
|
|
*/
|
|
p._handleManifestError = function (event) {
|
|
var newEvent = new createjs.Event("fileerror");
|
|
newEvent.item = event.data;
|
|
this.dispatchEvent(newEvent);
|
|
};
|
|
|
|
createjs.ManifestLoader = createjs.promote(ManifestLoader, "AbstractLoader");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// SoundLoader.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* A loader for HTML audio files. PreloadJS can not load WebAudio files, as a WebAudio context is required, which
|
|
* should be created by either a library playing the sound (such as <a href="http://soundjs.com">SoundJS</a>, or an
|
|
* external framework that handles audio playback. To load content that can be played by WebAudio, use the
|
|
* {{#crossLink "BinaryLoader"}}{{/crossLink}}, and handle the audio context decoding manually.
|
|
* @class SoundLoader
|
|
* @param {LoadItem|Object} loadItem
|
|
* @param {Boolean} preferXHR
|
|
* @extends AbstractMediaLoader
|
|
* @constructor
|
|
*/
|
|
function SoundLoader(loadItem, preferXHR) {
|
|
this.AbstractMediaLoader_constructor(
|
|
loadItem,
|
|
preferXHR,
|
|
createjs.AbstractLoader.SOUND
|
|
);
|
|
|
|
// protected properties
|
|
if (createjs.RequestUtils.isAudioTag(loadItem)) {
|
|
this._tag = loadItem;
|
|
} else if (createjs.RequestUtils.isAudioTag(loadItem.src)) {
|
|
this._tag = loadItem;
|
|
} else if (createjs.RequestUtils.isAudioTag(loadItem.tag)) {
|
|
this._tag = createjs.RequestUtils.isAudioTag(loadItem)
|
|
? loadItem
|
|
: loadItem.src;
|
|
}
|
|
|
|
if (this._tag != null) {
|
|
this._preferXHR = false;
|
|
}
|
|
}
|
|
|
|
var p = createjs.extend(SoundLoader, createjs.AbstractMediaLoader);
|
|
var s = SoundLoader;
|
|
|
|
// static methods
|
|
/**
|
|
* Determines if the loader can load a specific item. This loader can only load items that are of type
|
|
* {{#crossLink "AbstractLoader/SOUND:property"}}{{/crossLink}}.
|
|
* @method canLoadItem
|
|
* @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load.
|
|
* @returns {Boolean} Whether the loader can load the item.
|
|
* @static
|
|
*/
|
|
s.canLoadItem = function (item) {
|
|
return item.type == createjs.AbstractLoader.SOUND;
|
|
};
|
|
|
|
// protected methods
|
|
p._createTag = function (src) {
|
|
var tag = document.createElement("audio");
|
|
tag.autoplay = false;
|
|
tag.preload = "none";
|
|
|
|
//LM: Firefox fails when this the preload="none" for other tags, but it needs to be "none" to ensure PreloadJS works.
|
|
tag.src = src;
|
|
return tag;
|
|
};
|
|
|
|
createjs.SoundLoader = createjs.promote(SoundLoader, "AbstractMediaLoader");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// VideoLoader.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* A loader for video files.
|
|
* @class VideoLoader
|
|
* @param {LoadItem|Object} loadItem
|
|
* @param {Boolean} preferXHR
|
|
* @extends AbstractMediaLoader
|
|
* @constructor
|
|
*/
|
|
function VideoLoader(loadItem, preferXHR) {
|
|
this.AbstractMediaLoader_constructor(
|
|
loadItem,
|
|
preferXHR,
|
|
createjs.AbstractLoader.VIDEO
|
|
);
|
|
|
|
if (
|
|
createjs.RequestUtils.isVideoTag(loadItem) ||
|
|
createjs.RequestUtils.isVideoTag(loadItem.src)
|
|
) {
|
|
this.setTag(
|
|
createjs.RequestUtils.isVideoTag(loadItem) ? loadItem : loadItem.src
|
|
);
|
|
|
|
// We can't use XHR for a tag that's passed in.
|
|
this._preferXHR = false;
|
|
} else {
|
|
this.setTag(this._createTag());
|
|
}
|
|
}
|
|
|
|
var p = createjs.extend(VideoLoader, createjs.AbstractMediaLoader);
|
|
var s = VideoLoader;
|
|
|
|
/**
|
|
* Create a new video tag
|
|
*
|
|
* @returns {HTMLElement}
|
|
* @private
|
|
*/
|
|
p._createTag = function () {
|
|
return document.createElement("video");
|
|
};
|
|
|
|
// static methods
|
|
/**
|
|
* Determines if the loader can load a specific item. This loader can only load items that are of type
|
|
* {{#crossLink "AbstractLoader/VIDEO:property"}}{{/crossLink}}.
|
|
* @method canLoadItem
|
|
* @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load.
|
|
* @returns {Boolean} Whether the loader can load the item.
|
|
* @static
|
|
*/
|
|
s.canLoadItem = function (item) {
|
|
return item.type == createjs.AbstractLoader.VIDEO;
|
|
};
|
|
|
|
createjs.VideoLoader = createjs.promote(VideoLoader, "AbstractMediaLoader");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// SpriteSheetLoader.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* A loader for EaselJS SpriteSheets. Images inside the spritesheet definition are loaded before the loader
|
|
* completes. To load SpriteSheets using JSONP, specify a {{#crossLink "LoadItem/callback:property"}}{{/crossLink}}
|
|
* as part of the {{#crossLink "LoadItem"}}{{/crossLink}}. Note that the {{#crossLink "JSONLoader"}}{{/crossLink}}
|
|
* and {{#crossLink "JSONPLoader"}}{{/crossLink}} are higher priority loaders, so SpriteSheets <strong>must</strong>
|
|
* set the {{#crossLink "LoadItem"}}{{/crossLink}} {{#crossLink "LoadItem/type:property"}}{{/crossLink}} property
|
|
* to {{#crossLink "AbstractLoader/SPRITESHEET:property"}}{{/crossLink}}.
|
|
*
|
|
* The {{#crossLink "LoadItem"}}{{/crossLink}} {{#crossLink "LoadItem/crossOrigin:property"}}{{/crossLink}} as well
|
|
* as the {{#crossLink "LoadQueue's"}}{{/crossLink}} `basePath` argument and {{#crossLink "LoadQueue/_preferXHR"}}{{/crossLink}}
|
|
* property supplied to the {{#crossLink "LoadQueue"}}{{/crossLink}} are passed on to the sub-manifest that loads
|
|
* the SpriteSheet images.
|
|
*
|
|
* Note that the SpriteSheet JSON does not respect the {{#crossLink "LoadQueue/_preferXHR:property"}}{{/crossLink}}
|
|
* property, which should instead be determined by the presence of a {{#crossLink "LoadItem/callback:property"}}{{/crossLink}}
|
|
* property on the SpriteSheet load item. This is because the JSON loaded will have a different format depending on
|
|
* if it is loaded as JSON, so just changing `preferXHR` is not enough to change how it is loaded.
|
|
* @class SpriteSheetLoader
|
|
* @param {LoadItem|Object} loadItem
|
|
* @extends AbstractLoader
|
|
* @constructor
|
|
*/
|
|
function SpriteSheetLoader(loadItem, preferXHR) {
|
|
this.AbstractLoader_constructor(
|
|
loadItem,
|
|
preferXHR,
|
|
createjs.AbstractLoader.SPRITESHEET
|
|
);
|
|
|
|
// protected properties
|
|
/**
|
|
* An internal queue which loads the SpriteSheet's images.
|
|
* @method _manifestQueue
|
|
* @type {LoadQueue}
|
|
* @private
|
|
*/
|
|
this._manifestQueue = null;
|
|
}
|
|
|
|
var p = createjs.extend(SpriteSheetLoader, createjs.AbstractLoader);
|
|
var s = SpriteSheetLoader;
|
|
|
|
// static properties
|
|
/**
|
|
* The amount of progress that the manifest itself takes up.
|
|
* @property SPRITESHEET_PROGRESS
|
|
* @type {number}
|
|
* @default 0.25 (25%)
|
|
* @private
|
|
* @static
|
|
*/
|
|
s.SPRITESHEET_PROGRESS = 0.25;
|
|
|
|
// static methods
|
|
/**
|
|
* Determines if the loader can load a specific item. This loader can only load items that are of type
|
|
* {{#crossLink "AbstractLoader/SPRITESHEET:property"}}{{/crossLink}}
|
|
* @method canLoadItem
|
|
* @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load.
|
|
* @returns {Boolean} Whether the loader can load the item.
|
|
* @static
|
|
*/
|
|
s.canLoadItem = function (item) {
|
|
return item.type == createjs.AbstractLoader.SPRITESHEET;
|
|
};
|
|
|
|
// public methods
|
|
p.destroy = function () {
|
|
this.AbstractLoader_destroy;
|
|
this._manifestQueue.close();
|
|
};
|
|
|
|
// protected methods
|
|
p._createRequest = function () {
|
|
var callback = this._item.callback;
|
|
if (callback != null) {
|
|
this._request = new createjs.JSONPLoader(this._item);
|
|
} else {
|
|
this._request = new createjs.JSONLoader(this._item);
|
|
}
|
|
};
|
|
|
|
p.handleEvent = function (event) {
|
|
switch (event.type) {
|
|
case "complete":
|
|
this._rawResult = event.target.getResult(true);
|
|
this._result = event.target.getResult();
|
|
this._sendProgress(s.SPRITESHEET_PROGRESS);
|
|
this._loadManifest(this._result);
|
|
return;
|
|
case "progress":
|
|
event.loaded *= s.SPRITESHEET_PROGRESS;
|
|
this.progress = event.loaded / event.total;
|
|
if (isNaN(this.progress) || this.progress == Infinity) {
|
|
this.progress = 0;
|
|
}
|
|
this._sendProgress(event);
|
|
return;
|
|
}
|
|
this.AbstractLoader_handleEvent(event);
|
|
};
|
|
|
|
/**
|
|
* Create and load the images once the SpriteSheet JSON has been loaded.
|
|
* @method _loadManifest
|
|
* @param {Object} json
|
|
* @private
|
|
*/
|
|
p._loadManifest = function (json) {
|
|
if (json && json.images) {
|
|
var queue = (this._manifestQueue = new createjs.LoadQueue(
|
|
this._preferXHR,
|
|
this._item.path,
|
|
this._item.crossOrigin
|
|
));
|
|
queue.on("complete", this._handleManifestComplete, this, true);
|
|
queue.on("fileload", this._handleManifestFileLoad, this);
|
|
queue.on("progress", this._handleManifestProgress, this);
|
|
queue.on("error", this._handleManifestError, this, true);
|
|
queue.loadManifest(json.images);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* An item from the {{#crossLink "_manifestQueue:property"}}{{/crossLink}} has completed.
|
|
* @method _handleManifestFileLoad
|
|
* @param {Event} event
|
|
* @private
|
|
*/
|
|
p._handleManifestFileLoad = function (event) {
|
|
var image = event.result;
|
|
if (image != null) {
|
|
var images = this.getResult().images;
|
|
var pos = images.indexOf(event.item.src);
|
|
images[pos] = image;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The images have completed loading. This triggers the {{#crossLink "AbstractLoader/complete:event"}}{{/crossLink}}
|
|
* {{#crossLink "Event"}}{{/crossLink}} from the SpriteSheetLoader.
|
|
* @method _handleManifestComplete
|
|
* @param {Event} event
|
|
* @private
|
|
*/
|
|
p._handleManifestComplete = function (event) {
|
|
this._result = new createjs.SpriteSheet(this._result);
|
|
this._loadedItems = this._manifestQueue.getItems(true);
|
|
this._sendComplete();
|
|
};
|
|
|
|
/**
|
|
* The images {{#crossLink "LoadQueue"}}{{/crossLink}} has reported progress.
|
|
* @method _handleManifestProgress
|
|
* @param {ProgressEvent} event
|
|
* @private
|
|
*/
|
|
p._handleManifestProgress = function (event) {
|
|
this.progress =
|
|
event.progress * (1 - s.SPRITESHEET_PROGRESS) + s.SPRITESHEET_PROGRESS;
|
|
this._sendProgress(this.progress);
|
|
};
|
|
|
|
/**
|
|
* An image has reported an error.
|
|
* @method _handleManifestError
|
|
* @param {ErrorEvent} event
|
|
* @private
|
|
*/
|
|
p._handleManifestError = function (event) {
|
|
var newEvent = new createjs.Event("fileerror");
|
|
newEvent.item = event.data;
|
|
this.dispatchEvent(newEvent);
|
|
};
|
|
|
|
createjs.SpriteSheetLoader = createjs.promote(
|
|
SpriteSheetLoader,
|
|
"AbstractLoader"
|
|
);
|
|
})();
|
|
|
|
//##############################################################################
|
|
// SVGLoader.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* A loader for SVG files.
|
|
* @class SVGLoader
|
|
* @param {LoadItem|Object} loadItem
|
|
* @param {Boolean} preferXHR
|
|
* @extends AbstractLoader
|
|
* @constructor
|
|
*/
|
|
function SVGLoader(loadItem, preferXHR) {
|
|
this.AbstractLoader_constructor(
|
|
loadItem,
|
|
preferXHR,
|
|
createjs.AbstractLoader.SVG
|
|
);
|
|
|
|
// public properties
|
|
this.resultFormatter = this._formatResult;
|
|
|
|
// protected properties
|
|
this._tagSrcAttribute = "data";
|
|
|
|
if (preferXHR) {
|
|
this.setTag(document.createElement("svg"));
|
|
} else {
|
|
this.setTag(document.createElement("object"));
|
|
this.getTag().type = "image/svg+xml";
|
|
}
|
|
}
|
|
|
|
var p = createjs.extend(SVGLoader, createjs.AbstractLoader);
|
|
var s = SVGLoader;
|
|
|
|
// static methods
|
|
/**
|
|
* Determines if the loader can load a specific item. This loader can only load items that are of type
|
|
* {{#crossLink "AbstractLoader/SVG:property"}}{{/crossLink}}
|
|
* @method canLoadItem
|
|
* @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load.
|
|
* @returns {Boolean} Whether the loader can load the item.
|
|
* @static
|
|
*/
|
|
s.canLoadItem = function (item) {
|
|
return item.type == createjs.AbstractLoader.SVG;
|
|
};
|
|
|
|
// protected methods
|
|
/**
|
|
* The result formatter for SVG files.
|
|
* @method _formatResult
|
|
* @param {AbstractLoader} loader
|
|
* @returns {Object}
|
|
* @private
|
|
*/
|
|
p._formatResult = function (loader) {
|
|
// mime should be image/svg+xml, but Opera requires text/xml
|
|
var xml = createjs.DataUtils.parseXML(loader.getResult(true), "text/xml");
|
|
var tag = loader.getTag();
|
|
|
|
if (!this._preferXHR && document.body.contains(tag)) {
|
|
document.body.removeChild(tag);
|
|
}
|
|
|
|
if (xml.documentElement != null) {
|
|
tag.appendChild(xml.documentElement);
|
|
tag.style.visibility = "visible";
|
|
return tag;
|
|
} else {
|
|
// For browsers that don't support SVG, just give them the XML. (IE 9-8)
|
|
return xml;
|
|
}
|
|
};
|
|
|
|
createjs.SVGLoader = createjs.promote(SVGLoader, "AbstractLoader");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// XMLLoader.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* A loader for CSS files.
|
|
* @class XMLLoader
|
|
* @param {LoadItem|Object} loadItem
|
|
* @extends AbstractLoader
|
|
* @constructor
|
|
*/
|
|
function XMLLoader(loadItem) {
|
|
this.AbstractLoader_constructor(
|
|
loadItem,
|
|
true,
|
|
createjs.AbstractLoader.XML
|
|
);
|
|
|
|
// public properties
|
|
this.resultFormatter = this._formatResult;
|
|
}
|
|
|
|
var p = createjs.extend(XMLLoader, createjs.AbstractLoader);
|
|
var s = XMLLoader;
|
|
|
|
// static methods
|
|
/**
|
|
* Determines if the loader can load a specific item. This loader can only load items that are of type
|
|
* {{#crossLink "AbstractLoader/XML:property"}}{{/crossLink}}.
|
|
* @method canLoadItem
|
|
* @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load.
|
|
* @returns {Boolean} Whether the loader can load the item.
|
|
* @static
|
|
*/
|
|
s.canLoadItem = function (item) {
|
|
return item.type == createjs.AbstractLoader.XML;
|
|
};
|
|
|
|
// protected methods
|
|
/**
|
|
* The result formatter for XML files.
|
|
* @method _formatResult
|
|
* @param {AbstractLoader} loader
|
|
* @returns {XMLDocument}
|
|
* @private
|
|
*/
|
|
p._formatResult = function (loader) {
|
|
return createjs.DataUtils.parseXML(loader.getResult(true), "text/xml");
|
|
};
|
|
|
|
createjs.XMLLoader = createjs.promote(XMLLoader, "AbstractLoader");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// version.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
/**
|
|
* Static class holding library specific information such as the version and buildDate of the library.
|
|
* The SoundJS class has been renamed {{#crossLink "Sound"}}{{/crossLink}}. Please see {{#crossLink "Sound"}}{{/crossLink}}
|
|
* for information on using sound.
|
|
* @class SoundJS
|
|
**/
|
|
var s = (createjs.SoundJS = createjs.SoundJS || {});
|
|
|
|
/**
|
|
* The version string for this release.
|
|
* @property version
|
|
* @type String
|
|
* @static
|
|
**/
|
|
s.version = /*=version*/ "0.6.2"; // injected by build process
|
|
|
|
/**
|
|
* The build date for this release in UTC format.
|
|
* @property buildDate
|
|
* @type String
|
|
* @static
|
|
**/
|
|
s.buildDate = /*=date*/ "Thu, 26 Nov 2015 20:44:31 GMT"; // injected by build process
|
|
})();
|
|
|
|
//##############################################################################
|
|
// IndexOf.js
|
|
//##############################################################################
|
|
|
|
/**
|
|
* @class Utility Methods
|
|
*/
|
|
|
|
/**
|
|
* Finds the first occurrence of a specified value searchElement in the passed in array, and returns the index of
|
|
* that value. Returns -1 if value is not found.
|
|
*
|
|
* var i = createjs.indexOf(myArray, myElementToFind);
|
|
*
|
|
* @method indexOf
|
|
* @param {Array} array Array to search for searchElement
|
|
* @param searchElement Element to find in array.
|
|
* @return {Number} The first index of searchElement in array.
|
|
*/
|
|
createjs.indexOf = function (array, searchElement) {
|
|
"use strict";
|
|
|
|
for (var i = 0, l = array.length; i < l; i++) {
|
|
if (searchElement === array[i]) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
|
|
//##############################################################################
|
|
// Proxy.js
|
|
//##############################################################################
|
|
|
|
/**
|
|
* Various utilities that the CreateJS Suite uses. Utilities are created as separate files, and will be available on the
|
|
* createjs namespace directly.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* myObject.addEventListener("change", createjs.proxy(myMethod, scope));
|
|
*
|
|
* @class Utility Methods
|
|
* @main Utility Methods
|
|
*/
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* A function proxy for methods. By default, JavaScript methods do not maintain scope, so passing a method as a
|
|
* callback will result in the method getting called in the scope of the caller. Using a proxy ensures that the
|
|
* method gets called in the correct scope.
|
|
*
|
|
* Additional arguments can be passed that will be applied to the function when it is called.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* myObject.addEventListener("event", createjs.proxy(myHandler, this, arg1, arg2));
|
|
*
|
|
* function myHandler(arg1, arg2) {
|
|
* // This gets called when myObject.myCallback is executed.
|
|
* }
|
|
*
|
|
* @method proxy
|
|
* @param {Function} method The function to call
|
|
* @param {Object} scope The scope to call the method name on
|
|
* @param {mixed} [arg] * Arguments that are appended to the callback for additional params.
|
|
* @public
|
|
* @static
|
|
*/
|
|
createjs.proxy = function (method, scope) {
|
|
var aArgs = Array.prototype.slice.call(arguments, 2);
|
|
return function () {
|
|
return method.apply(
|
|
scope,
|
|
Array.prototype.slice.call(arguments, 0).concat(aArgs)
|
|
);
|
|
};
|
|
};
|
|
})();
|
|
|
|
//##############################################################################
|
|
// BrowserDetect.js
|
|
//##############################################################################
|
|
|
|
/**
|
|
* @class Utility Methods
|
|
*/
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* An object that determines the current browser, version, operating system, and other environment
|
|
* variables via user agent string.
|
|
*
|
|
* Used for audio because feature detection is unable to detect the many limitations of mobile devices.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* if (createjs.BrowserDetect.isIOS) { // do stuff }
|
|
*
|
|
* @property BrowserDetect
|
|
* @type {Object}
|
|
* @param {Boolean} isFirefox True if our browser is Firefox.
|
|
* @param {Boolean} isOpera True if our browser is opera.
|
|
* @param {Boolean} isChrome True if our browser is Chrome. Note that Chrome for Android returns true, but is a
|
|
* completely different browser with different abilities.
|
|
* @param {Boolean} isIOS True if our browser is safari for iOS devices (iPad, iPhone, and iPod).
|
|
* @param {Boolean} isAndroid True if our browser is Android.
|
|
* @param {Boolean} isBlackberry True if our browser is Blackberry.
|
|
* @constructor
|
|
* @static
|
|
*/
|
|
function BrowserDetect() {
|
|
throw "BrowserDetect cannot be instantiated";
|
|
}
|
|
|
|
var agent = (BrowserDetect.agent = window.navigator.userAgent);
|
|
BrowserDetect.isWindowPhone =
|
|
agent.indexOf("IEMobile") > -1 || agent.indexOf("Windows Phone") > -1;
|
|
BrowserDetect.isFirefox = agent.indexOf("Firefox") > -1;
|
|
BrowserDetect.isOpera = window.opera != null;
|
|
BrowserDetect.isChrome = agent.indexOf("Chrome") > -1; // NOTE that Chrome on Android returns true but is a completely different browser with different abilities
|
|
BrowserDetect.isIOS =
|
|
(agent.indexOf("iPod") > -1 ||
|
|
agent.indexOf("iPhone") > -1 ||
|
|
agent.indexOf("iPad") > -1) &&
|
|
!BrowserDetect.isWindowPhone;
|
|
BrowserDetect.isAndroid =
|
|
agent.indexOf("Android") > -1 && !BrowserDetect.isWindowPhone;
|
|
BrowserDetect.isBlackberry = agent.indexOf("Blackberry") > -1;
|
|
|
|
createjs.BrowserDetect = BrowserDetect;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// AudioSprite.js
|
|
//##############################################################################
|
|
|
|
// NOTE this is "Class" is purely to document audioSprite Setup and usage.
|
|
|
|
/**
|
|
* <strong>Note: AudioSprite is not a class, but its usage is easily lost in the documentation, so it has been called
|
|
* out here for quick reference.</strong>
|
|
*
|
|
* Audio sprites are much like CSS sprites or image sprite sheets: multiple audio assets grouped into a single file.
|
|
* Audio sprites work around limitations in certain browsers, where only a single sound can be loaded and played at a
|
|
* time. We recommend at least 300ms of silence between audio clips to deal with HTML audio tag inaccuracy, and to prevent
|
|
* accidentally playing bits of the neighbouring clips.
|
|
*
|
|
* <strong>Benefits of Audio Sprites:</strong>
|
|
* <ul>
|
|
* <li>More robust support for older browsers and devices that only allow a single audio instance, such as iOS 5.</li>
|
|
* <li>They provide a work around for the Internet Explorer 9 audio tag limit, which restricts how many different
|
|
* sounds that could be loaded at once.</li>
|
|
* <li>Faster loading by only requiring a single network request for several sounds, especially on mobile devices
|
|
* where the network round trip for each file can add significant latency.</li>
|
|
* </ul>
|
|
*
|
|
* <strong>Drawbacks of Audio Sprites</strong>
|
|
* <ul>
|
|
* <li>No guarantee of smooth looping when using HTML or Flash audio. If you have a track that needs to loop
|
|
* smoothly and you are supporting non-web audio browsers, do not use audio sprites for that sound if you can avoid
|
|
* it.</li>
|
|
* <li>No guarantee that HTML audio will play back immediately, especially the first time. In some browsers
|
|
* (Chrome!), HTML audio will only load enough to play through at the current download speed – so we rely on the
|
|
* `canplaythrough` event to determine if the audio is loaded. Since audio sprites must jump ahead to play specific
|
|
* sounds, the audio may not yet have downloaded fully.</li>
|
|
* <li>Audio sprites share the same core source, so if you have a sprite with 5 sounds and are limited to 2
|
|
* concurrently playing instances, you can only play 2 of the sounds at the same time.</li>
|
|
* </ul>
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* createjs.Sound.initializeDefaultPlugins();
|
|
* var assetsPath = "./assets/";
|
|
* var sounds = [{
|
|
* src:"MyAudioSprite.ogg", data: {
|
|
* audioSprite: [
|
|
* {id:"sound1", startTime:0, duration:500},
|
|
* {id:"sound2", startTime:1000, duration:400},
|
|
* {id:"sound3", startTime:1700, duration: 1000}
|
|
* ]}
|
|
* }
|
|
* ];
|
|
* createjs.Sound.alternateExtensions = ["mp3"];
|
|
* createjs.Sound.on("fileload", loadSound);
|
|
* createjs.Sound.registerSounds(sounds, assetsPath);
|
|
* // after load is complete
|
|
* createjs.Sound.play("sound2");
|
|
*
|
|
* You can also create audio sprites on the fly by setting the startTime and duration when creating an new AbstractSoundInstance.
|
|
*
|
|
* createjs.Sound.play("MyAudioSprite", {startTime: 1000, duration: 400});
|
|
*
|
|
* The excellent CreateJS community has created a tool to create audio sprites, available at
|
|
* <a href="https://github.com/tonistiigi/audiosprite" target="_blank">https://github.com/tonistiigi/audiosprite</a>,
|
|
* as well as a <a href="http://jsfiddle.net/bharat_battu/g8fFP/12/" target="_blank">jsfiddle</a> to convert the output
|
|
* to SoundJS format.
|
|
*
|
|
* @class AudioSprite
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
//##############################################################################
|
|
// PlayPropsConfig.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
/**
|
|
* A class to store the optional play properties passed in {{#crossLink "Sound/play"}}{{/crossLink}} and
|
|
* {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}} calls.
|
|
*
|
|
* Optional Play Properties Include:
|
|
* <ul>
|
|
* <li>interrupt - How to interrupt any currently playing instances of audio with the same source,
|
|
* if the maximum number of instances of the sound are already playing. Values are defined as <code>INTERRUPT_TYPE</code>
|
|
* constants on the Sound class, with the default defined by {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}.</li>
|
|
* <li>delay - The amount of time to delay the start of audio playback, in milliseconds.</li>
|
|
* <li>offset - The offset from the start of the audio to begin playback, in milliseconds.</li>
|
|
* <li>loop - How many times the audio loops when it reaches the end of playback. The default is 0 (no
|
|
* loops), and -1 can be used for infinite playback.</li>
|
|
* <li>volume - The volume of the sound, between 0 and 1. Note that the master volume is applied
|
|
* against the individual volume.</li>
|
|
* <li>pan - The left-right pan of the sound (if supported), between -1 (left) and 1 (right).</li>
|
|
* <li>startTime - To create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds.</li>
|
|
* <li>duration - To create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds.</li>
|
|
* </ul>
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var ppc = new createjs.PlayPropsConfig().set({interrupt: createjs.Sound.INTERRUPT_ANY, loop: -1, volume: 0.5})
|
|
* createjs.Sound.play("mySound", ppc);
|
|
* mySoundInstance.play(ppc);
|
|
*
|
|
* @class PlayPropsConfig
|
|
* @constructor
|
|
* @since 0.6.1
|
|
*/
|
|
// TODO think of a better name for this class
|
|
var PlayPropsConfig = function () {
|
|
// Public Properties
|
|
/**
|
|
* How to interrupt any currently playing instances of audio with the same source,
|
|
* if the maximum number of instances of the sound are already playing. Values are defined as
|
|
* <code>INTERRUPT_TYPE</code> constants on the Sound class, with the default defined by
|
|
* {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}.
|
|
* @property interrupt
|
|
* @type {string}
|
|
* @default null
|
|
*/
|
|
this.interrupt = null;
|
|
|
|
/**
|
|
* The amount of time to delay the start of audio playback, in milliseconds.
|
|
* @property delay
|
|
* @type {Number}
|
|
* @default null
|
|
*/
|
|
this.delay = null;
|
|
|
|
/**
|
|
* The offset from the start of the audio to begin playback, in milliseconds.
|
|
* @property offset
|
|
* @type {number}
|
|
* @default null
|
|
*/
|
|
this.offset = null;
|
|
|
|
/**
|
|
* How many times the audio loops when it reaches the end of playback. The default is 0 (no
|
|
* loops), and -1 can be used for infinite playback.
|
|
* @property loop
|
|
* @type {number}
|
|
* @default null
|
|
*/
|
|
this.loop = null;
|
|
|
|
/**
|
|
* The volume of the sound, between 0 and 1. Note that the master volume is applied
|
|
* against the individual volume.
|
|
* @property volume
|
|
* @type {number}
|
|
* @default null
|
|
*/
|
|
this.volume = null;
|
|
|
|
/**
|
|
* The left-right pan of the sound (if supported), between -1 (left) and 1 (right).
|
|
* @property pan
|
|
* @type {number}
|
|
* @default null
|
|
*/
|
|
this.pan = null;
|
|
|
|
/**
|
|
* Used to create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds.
|
|
* @property startTime
|
|
* @type {number}
|
|
* @default null
|
|
*/
|
|
this.startTime = null;
|
|
|
|
/**
|
|
* Used to create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds.
|
|
* @property duration
|
|
* @type {number}
|
|
* @default null
|
|
*/
|
|
this.duration = null;
|
|
};
|
|
var p = (PlayPropsConfig.prototype = {});
|
|
var s = PlayPropsConfig;
|
|
|
|
// Static Methods
|
|
/**
|
|
* Creates a PlayPropsConfig from another PlayPropsConfig or an Object.
|
|
*
|
|
* @method create
|
|
* @param {PlayPropsConfig|Object} value The play properties
|
|
* @returns {PlayPropsConfig}
|
|
* @static
|
|
*/
|
|
s.create = function (value) {
|
|
if (value instanceof s || value instanceof Object) {
|
|
var ppc = new createjs.PlayPropsConfig();
|
|
ppc.set(value);
|
|
return ppc;
|
|
} else {
|
|
throw new Error("Type not recognized.");
|
|
}
|
|
};
|
|
|
|
// Public Methods
|
|
/**
|
|
* Provides a chainable shortcut method for setting a number of properties on the instance.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var PlayPropsConfig = new createjs.PlayPropsConfig().set({loop:-1, volume:0.7});
|
|
*
|
|
* @method set
|
|
* @param {Object} props A generic object containing properties to copy to the PlayPropsConfig instance.
|
|
* @return {PlayPropsConfig} Returns the instance the method is called on (useful for chaining calls.)
|
|
*/
|
|
p.set = function (props) {
|
|
for (var n in props) {
|
|
this[n] = props[n];
|
|
}
|
|
return this;
|
|
};
|
|
|
|
p.toString = function () {
|
|
return "[PlayPropsConfig]";
|
|
};
|
|
|
|
createjs.PlayPropsConfig = s;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Sound.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* The Sound class is the public API for creating sounds, controlling the overall sound levels, and managing plugins.
|
|
* All Sound APIs on this class are static.
|
|
*
|
|
* <b>Registering and Preloading</b><br />
|
|
* Before you can play a sound, it <b>must</b> be registered. You can do this with {{#crossLink "Sound/registerSound"}}{{/crossLink}},
|
|
* or register multiple sounds using {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. If you don't register a
|
|
* sound prior to attempting to play it using {{#crossLink "Sound/play"}}{{/crossLink}} or create it using {{#crossLink "Sound/createInstance"}}{{/crossLink}},
|
|
* the sound source will be automatically registered but playback will fail as the source will not be ready. If you use
|
|
* <a href="http://preloadjs.com" target="_blank">PreloadJS</a>, registration is handled for you when the sound is
|
|
* preloaded. It is recommended to preload sounds either internally using the register functions or externally using
|
|
* PreloadJS so they are ready when you want to use them.
|
|
*
|
|
* <b>Playback</b><br />
|
|
* To play a sound once it's been registered and preloaded, use the {{#crossLink "Sound/play"}}{{/crossLink}} method.
|
|
* This method returns a {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} which can be paused, resumed, muted, etc.
|
|
* Please see the {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} documentation for more on the instance control APIs.
|
|
*
|
|
* <b>Plugins</b><br />
|
|
* By default, the {{#crossLink "WebAudioPlugin"}}{{/crossLink}} or the {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}
|
|
* are used (when available), although developers can change plugin priority or add new plugins (such as the
|
|
* provided {{#crossLink "FlashAudioPlugin"}}{{/crossLink}}). Please see the {{#crossLink "Sound"}}{{/crossLink}} API
|
|
* methods for more on the playback and plugin APIs. To install plugins, or specify a different plugin order, see
|
|
* {{#crossLink "Sound/installPlugins"}}{{/crossLink}}.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* createjs.FlashAudioPlugin.swfPath = "../src/soundjs/flashaudio";
|
|
* createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.FlashAudioPlugin]);
|
|
* createjs.Sound.alternateExtensions = ["mp3"];
|
|
* createjs.Sound.on("fileload", this.loadHandler, this);
|
|
* createjs.Sound.registerSound("path/to/mySound.ogg", "sound");
|
|
* function loadHandler(event) {
|
|
* // This is fired for each sound that is registered.
|
|
* var instance = createjs.Sound.play("sound"); // play using id. Could also use full source path or event.src.
|
|
* instance.on("complete", this.handleComplete, this);
|
|
* instance.volume = 0.5;
|
|
* }
|
|
*
|
|
* The maximum number of concurrently playing instances of the same sound can be specified in the "data" argument
|
|
* of {{#crossLink "Sound/registerSound"}}{{/crossLink}}. Note that if not specified, the active plugin will apply
|
|
* a default limit. Currently HTMLAudioPlugin sets a default limit of 2, while WebAudioPlugin and FlashAudioPlugin set a
|
|
* default limit of 100.
|
|
*
|
|
* createjs.Sound.registerSound("sound.mp3", "soundId", 4);
|
|
*
|
|
* Sound can be used as a plugin with PreloadJS to help preload audio properly. Audio preloaded with PreloadJS is
|
|
* automatically registered with the Sound class. When audio is not preloaded, Sound will do an automatic internal
|
|
* load. As a result, it may fail to play the first time play is called if the audio is not finished loading. Use
|
|
* the {{#crossLink "Sound/fileload:event"}}{{/crossLink}} event to determine when a sound has finished internally
|
|
* preloading. It is recommended that all audio is preloaded before it is played.
|
|
*
|
|
* var queue = new createjs.LoadQueue();
|
|
* queue.installPlugin(createjs.Sound);
|
|
*
|
|
* <b>Audio Sprites</b><br />
|
|
* SoundJS has added support for {{#crossLink "AudioSprite"}}{{/crossLink}}, available as of version 0.6.0.
|
|
* For those unfamiliar with audio sprites, they are much like CSS sprites or sprite sheets: multiple audio assets
|
|
* grouped into a single file.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var assetsPath = "./assets/";
|
|
* var sounds = [{
|
|
* src:"MyAudioSprite.ogg", data: {
|
|
* audioSprite: [
|
|
* {id:"sound1", startTime:0, duration:500},
|
|
* {id:"sound2", startTime:1000, duration:400},
|
|
* {id:"sound3", startTime:1700, duration: 1000}
|
|
* ]}
|
|
* }
|
|
* ];
|
|
* createjs.Sound.alternateExtensions = ["mp3"];
|
|
* createjs.Sound.on("fileload", loadSound);
|
|
* createjs.Sound.registerSounds(sounds, assetsPath);
|
|
* // after load is complete
|
|
* createjs.Sound.play("sound2");
|
|
*
|
|
* <b>Mobile Playback</b><br />
|
|
* Devices running iOS require the WebAudio context to be "unlocked" by playing at least one sound inside of a user-
|
|
* initiated event (such as touch/click). Earlier versions of SoundJS included a "MobileSafe" sample, but this is no
|
|
* longer necessary as of SoundJS 0.6.2.
|
|
* <ul>
|
|
* <li>
|
|
* In SoundJS 0.4.1 and above, you can either initialize plugins or use the {{#crossLink "WebAudioPlugin/playEmptySound"}}{{/crossLink}}
|
|
* method in the call stack of a user input event to manually unlock the audio context.
|
|
* </li>
|
|
* <li>
|
|
* In SoundJS 0.6.2 and above, SoundJS will automatically listen for the first document-level "mousedown"
|
|
* and "touchend" event, and unlock WebAudio. This will continue to check these events until the WebAudio
|
|
* context becomes "unlocked" (changes from "suspended" to "running")
|
|
* </li>
|
|
* <li>
|
|
* Both the "mousedown" and "touchend" events can be used to unlock audio in iOS9+, the "touchstart" event
|
|
* will work in iOS8 and below. The "touchend" event will only work in iOS9 when the gesture is interpreted
|
|
* as a "click", so if the user long-presses the button, it will no longer work.
|
|
* </li>
|
|
* <li>
|
|
* When using the <a href="http://www.createjs.com/docs/easeljs/classes/Touch.html">EaselJS Touch class</a>,
|
|
* the "mousedown" event will not fire when a canvas is clicked, since MouseEvents are prevented, to ensure
|
|
* only touch events fire. To get around this, you can either rely on "touchend", or:
|
|
* <ol>
|
|
* <li>Set the `allowDefault` property on the Touch class constructor to `true` (defaults to `false`).</li>
|
|
* <li>Set the `preventSelection` property on the EaselJS `Stage` to `false`.</li>
|
|
* </ol>
|
|
* These settings may change how your application behaves, and are not recommended.
|
|
* </li>
|
|
* </ul>
|
|
*
|
|
* <b>Loading Alternate Paths and Extension-less Files</b><br />
|
|
* SoundJS supports loading alternate paths and extension-less files by passing an object instead of a string for
|
|
* the `src` property, which is a hash using the format `{extension:"path", extension2:"path2"}`. These labels are
|
|
* how SoundJS determines if the browser will support the sound. This also enables multiple formats to live in
|
|
* different folders, or on CDNs, which often has completely different filenames for each file.
|
|
*
|
|
* Priority is determined by the property order (first property is tried first). This is supported by both internal loading
|
|
* and loading with PreloadJS.
|
|
*
|
|
* <em>Note: an id is required for playback.</em>
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var sounds = {path:"./audioPath/",
|
|
* manifest: [
|
|
* {id: "cool", src: {mp3:"mp3/awesome.mp3", ogg:"noExtensionOggFile"}}
|
|
* ]};
|
|
*
|
|
* createjs.Sound.alternateExtensions = ["mp3"];
|
|
* createjs.Sound.addEventListener("fileload", handleLoad);
|
|
* createjs.Sound.registerSounds(sounds);
|
|
*
|
|
* <h3>Known Browser and OS issues</h3>
|
|
* <b>IE 9 HTML Audio limitations</b><br />
|
|
* <ul><li>There is a delay in applying volume changes to tags that occurs once playback is started. So if you have
|
|
* muted all sounds, they will all play during this delay until the mute applies internally. This happens regardless of
|
|
* when or how you apply the volume change, as the tag seems to need to play to apply it.</li>
|
|
* <li>MP3 encoding will not always work for audio tags, particularly in Internet Explorer. We've found default
|
|
* encoding with 64kbps works.</li>
|
|
* <li>Occasionally very short samples will get cut off.</li>
|
|
* <li>There is a limit to how many audio tags you can load and play at once, which appears to be determined by
|
|
* hardware and browser settings. See {{#crossLink "HTMLAudioPlugin.MAX_INSTANCES"}}{{/crossLink}} for a safe
|
|
* estimate.</li></ul>
|
|
*
|
|
* <b>Firefox 25 Web Audio limitations</b>
|
|
* <ul><li>mp3 audio files do not load properly on all windows machines, reported
|
|
* <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=929969" target="_blank">here</a>. </br>
|
|
* For this reason it is recommended to pass another FF supported type (ie ogg) first until this bug is resolved, if
|
|
* possible.</li></ul>
|
|
|
|
* <b>Safari limitations</b><br />
|
|
* <ul><li>Safari requires Quicktime to be installed for audio playback.</li></ul>
|
|
*
|
|
* <b>iOS 6 Web Audio limitations</b><br />
|
|
* <ul><li>Sound is initially locked, and must be unlocked via a user-initiated event. Please see the section on
|
|
* Mobile Playback above.</li>
|
|
* <li>A bug exists that will distort un-cached web audio when a video element is present in the DOM that has audio
|
|
* at a different sampleRate.</li>
|
|
* </ul>
|
|
*
|
|
* <b>Android HTML Audio limitations</b><br />
|
|
* <ul><li>We have no control over audio volume. Only the user can set volume on their device.</li>
|
|
* <li>We can only play audio inside a user event (touch/click). This currently means you cannot loop sound or use
|
|
* a delay.</li></ul>
|
|
*
|
|
* <b>Web Audio and PreloadJS</b><br />
|
|
* <ul><li>Web Audio must be loaded through XHR, therefore when used with PreloadJS, tag loading is not possible.
|
|
* This means that tag loading can not be used to avoid cross domain issues.</li><ul>
|
|
*
|
|
* @class Sound
|
|
* @static
|
|
* @uses EventDispatcher
|
|
*/
|
|
function Sound() {
|
|
throw "Sound cannot be instantiated";
|
|
}
|
|
|
|
var s = Sound;
|
|
|
|
// Static Properties
|
|
/**
|
|
* The interrupt value to interrupt any currently playing instance with the same source, if the maximum number of
|
|
* instances of the sound are already playing.
|
|
* @property INTERRUPT_ANY
|
|
* @type {String}
|
|
* @default any
|
|
* @static
|
|
*/
|
|
s.INTERRUPT_ANY = "any";
|
|
|
|
/**
|
|
* The interrupt value to interrupt the earliest currently playing instance with the same source that progressed the
|
|
* least distance in the audio track, if the maximum number of instances of the sound are already playing.
|
|
* @property INTERRUPT_EARLY
|
|
* @type {String}
|
|
* @default early
|
|
* @static
|
|
*/
|
|
s.INTERRUPT_EARLY = "early";
|
|
|
|
/**
|
|
* The interrupt value to interrupt the currently playing instance with the same source that progressed the most
|
|
* distance in the audio track, if the maximum number of instances of the sound are already playing.
|
|
* @property INTERRUPT_LATE
|
|
* @type {String}
|
|
* @default late
|
|
* @static
|
|
*/
|
|
s.INTERRUPT_LATE = "late";
|
|
|
|
/**
|
|
* The interrupt value to not interrupt any currently playing instances with the same source, if the maximum number of
|
|
* instances of the sound are already playing.
|
|
* @property INTERRUPT_NONE
|
|
* @type {String}
|
|
* @default none
|
|
* @static
|
|
*/
|
|
s.INTERRUPT_NONE = "none";
|
|
|
|
/**
|
|
* Defines the playState of an instance that is still initializing.
|
|
* @property PLAY_INITED
|
|
* @type {String}
|
|
* @default playInited
|
|
* @static
|
|
*/
|
|
s.PLAY_INITED = "playInited";
|
|
|
|
/**
|
|
* Defines the playState of an instance that is currently playing or paused.
|
|
* @property PLAY_SUCCEEDED
|
|
* @type {String}
|
|
* @default playSucceeded
|
|
* @static
|
|
*/
|
|
s.PLAY_SUCCEEDED = "playSucceeded";
|
|
|
|
/**
|
|
* Defines the playState of an instance that was interrupted by another instance.
|
|
* @property PLAY_INTERRUPTED
|
|
* @type {String}
|
|
* @default playInterrupted
|
|
* @static
|
|
*/
|
|
s.PLAY_INTERRUPTED = "playInterrupted";
|
|
|
|
/**
|
|
* Defines the playState of an instance that completed playback.
|
|
* @property PLAY_FINISHED
|
|
* @type {String}
|
|
* @default playFinished
|
|
* @static
|
|
*/
|
|
s.PLAY_FINISHED = "playFinished";
|
|
|
|
/**
|
|
* Defines the playState of an instance that failed to play. This is usually caused by a lack of available channels
|
|
* when the interrupt mode was "INTERRUPT_NONE", the playback stalled, or the sound could not be found.
|
|
* @property PLAY_FAILED
|
|
* @type {String}
|
|
* @default playFailed
|
|
* @static
|
|
*/
|
|
s.PLAY_FAILED = "playFailed";
|
|
|
|
/**
|
|
* A list of the default supported extensions that Sound will <i>try</i> to play. Plugins will check if the browser
|
|
* can play these types, so modifying this list before a plugin is initialized will allow the plugins to try to
|
|
* support additional media types.
|
|
*
|
|
* NOTE this does not currently work for {{#crossLink "FlashAudioPlugin"}}{{/crossLink}}.
|
|
*
|
|
* More details on file formats can be found at <a href="http://en.wikipedia.org/wiki/Audio_file_format" target="_blank">http://en.wikipedia.org/wiki/Audio_file_format</a>.<br />
|
|
* A very detailed list of file formats can be found at <a href="http://www.fileinfo.com/filetypes/audio" target="_blank">http://www.fileinfo.com/filetypes/audio</a>.
|
|
* @property SUPPORTED_EXTENSIONS
|
|
* @type {Array[String]}
|
|
* @default ["mp3", "ogg", "opus", "mpeg", "wav", "m4a", "mp4", "aiff", "wma", "mid"]
|
|
* @since 0.4.0
|
|
* @static
|
|
*/
|
|
s.SUPPORTED_EXTENSIONS = [
|
|
"mp3",
|
|
"ogg",
|
|
"opus",
|
|
"mpeg",
|
|
"wav",
|
|
"m4a",
|
|
"mp4",
|
|
"aiff",
|
|
"wma",
|
|
"mid",
|
|
];
|
|
|
|
/**
|
|
* Some extensions use another type of extension support to play (one of them is a codex). This allows you to map
|
|
* that support so plugins can accurately determine if an extension is supported. Adding to this list can help
|
|
* plugins determine more accurately if an extension is supported.
|
|
*
|
|
* A useful list of extensions for each format can be found at <a href="http://html5doctor.com/html5-audio-the-state-of-play/" target="_blank">http://html5doctor.com/html5-audio-the-state-of-play/</a>.
|
|
* @property EXTENSION_MAP
|
|
* @type {Object}
|
|
* @since 0.4.0
|
|
* @default {m4a:"mp4"}
|
|
* @static
|
|
*/
|
|
s.EXTENSION_MAP = {
|
|
m4a: "mp4",
|
|
};
|
|
|
|
/**
|
|
* The RegExp pattern used to parse file URIs. This supports simple file names, as well as full domain URIs with
|
|
* query strings. The resulting match is: protocol:$1 domain:$2 path:$3 file:$4 extension:$5 query:$6.
|
|
* @property FILE_PATTERN
|
|
* @type {RegExp}
|
|
* @static
|
|
* @protected
|
|
*/
|
|
s.FILE_PATTERN = /^(?:(\w+:)\/{2}(\w+(?:\.\w+)*\/?))?([/.]*?(?:[^?]+)?\/)?((?:[^/?]+)\.(\w+))(?:\?(\S+)?)?$/;
|
|
|
|
// Class Public properties
|
|
/**
|
|
* Determines the default behavior for interrupting other currently playing instances with the same source, if the
|
|
* maximum number of instances of the sound are already playing. Currently the default is {{#crossLink "Sound/INTERRUPT_NONE:property"}}{{/crossLink}}
|
|
* but this can be set and will change playback behavior accordingly. This is only used when {{#crossLink "Sound/play"}}{{/crossLink}}
|
|
* is called without passing a value for interrupt.
|
|
* @property defaultInterruptBehavior
|
|
* @type {String}
|
|
* @default Sound.INTERRUPT_NONE, or "none"
|
|
* @static
|
|
* @since 0.4.0
|
|
*/
|
|
s.defaultInterruptBehavior = s.INTERRUPT_NONE; // OJR does s.INTERRUPT_ANY make more sense as default? Needs game dev testing to see which case makes more sense.
|
|
|
|
/**
|
|
* An array of extensions to attempt to use when loading sound, if the default is unsupported by the active plugin.
|
|
* These are applied in order, so if you try to Load Thunder.ogg in a browser that does not support ogg, and your
|
|
* extensions array is ["mp3", "m4a", "wav"] it will check mp3 support, then m4a, then wav. The audio files need
|
|
* to exist in the same location, as only the extension is altered.
|
|
*
|
|
* Note that regardless of which file is loaded, you can call {{#crossLink "Sound/createInstance"}}{{/crossLink}}
|
|
* and {{#crossLink "Sound/play"}}{{/crossLink}} using the same id or full source path passed for loading.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var sounds = [
|
|
* {src:"myPath/mySound.ogg", id:"example"},
|
|
* ];
|
|
* createjs.Sound.alternateExtensions = ["mp3"]; // now if ogg is not supported, SoundJS will try asset0.mp3
|
|
* createjs.Sound.on("fileload", handleLoad); // call handleLoad when each sound loads
|
|
* createjs.Sound.registerSounds(sounds, assetPath);
|
|
* // ...
|
|
* createjs.Sound.play("myPath/mySound.ogg"); // works regardless of what extension is supported. Note calling with ID is a better approach
|
|
*
|
|
* @property alternateExtensions
|
|
* @type {Array}
|
|
* @since 0.5.2
|
|
* @static
|
|
*/
|
|
s.alternateExtensions = [];
|
|
|
|
/**
|
|
* The currently active plugin. If this is null, then no plugin could be initialized. If no plugin was specified,
|
|
* Sound attempts to apply the default plugins: {{#crossLink "WebAudioPlugin"}}{{/crossLink}}, followed by
|
|
* {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}.
|
|
* @property activePlugin
|
|
* @type {Object}
|
|
* @static
|
|
*/
|
|
s.activePlugin = null;
|
|
|
|
// class getter / setter properties
|
|
/**
|
|
* Set the master volume of Sound. The master volume is multiplied against each sound's individual volume. For
|
|
* example, if master volume is 0.5 and a sound's volume is 0.5, the resulting volume is 0.25. To set individual
|
|
* sound volume, use AbstractSoundInstance {{#crossLink "AbstractSoundInstance/volume:property"}}{{/crossLink}} instead.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* createjs.Sound.volume = 0.5;
|
|
*
|
|
*
|
|
* @property volume
|
|
* @type {Number}
|
|
* @default 1
|
|
* @since 0.6.1
|
|
*/
|
|
s._masterVolume = 1;
|
|
Object.defineProperty(s, "volume", {
|
|
get: function () {
|
|
return this._masterVolume;
|
|
},
|
|
set: function (value) {
|
|
if (Number(value) == null) {
|
|
return false;
|
|
}
|
|
value = Math.max(0, Math.min(1, value));
|
|
s._masterVolume = value;
|
|
if (
|
|
!this.activePlugin ||
|
|
!this.activePlugin.setVolume ||
|
|
!this.activePlugin.setVolume(value)
|
|
) {
|
|
var instances = this._instances;
|
|
for (var i = 0, l = instances.length; i < l; i++) {
|
|
instances[i].setMasterVolume(value);
|
|
}
|
|
}
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Mute/Unmute all audio. Note that muted audio still plays at 0 volume. This global mute value is maintained
|
|
* separately and when set will override, but not change the mute property of individual instances. To mute an individual
|
|
* instance, use AbstractSoundInstance {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} instead.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* createjs.Sound.muted = true;
|
|
*
|
|
*
|
|
* @property muted
|
|
* @type {Boolean}
|
|
* @default false
|
|
* @since 0.6.1
|
|
*/
|
|
s._masterMute = false;
|
|
// OJR references to the methods were not working, so the code had to be duplicated here
|
|
Object.defineProperty(s, "muted", {
|
|
get: function () {
|
|
return this._masterMute;
|
|
},
|
|
set: function (value) {
|
|
if (value == null) {
|
|
return false;
|
|
}
|
|
|
|
this._masterMute = value;
|
|
if (
|
|
!this.activePlugin ||
|
|
!this.activePlugin.setMute ||
|
|
!this.activePlugin.setMute(value)
|
|
) {
|
|
var instances = this._instances;
|
|
for (var i = 0, l = instances.length; i < l; i++) {
|
|
instances[i].setMasterMute(value);
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Get the active plugins capabilities, which help determine if a plugin can be used in the current environment,
|
|
* or if the plugin supports a specific feature. Capabilities include:
|
|
* <ul>
|
|
* <li><b>panning:</b> If the plugin can pan audio from left to right</li>
|
|
* <li><b>volume;</b> If the plugin can control audio volume.</li>
|
|
* <li><b>tracks:</b> The maximum number of audio tracks that can be played back at a time. This will be -1
|
|
* if there is no known limit.</li>
|
|
* <br />An entry for each file type in {{#crossLink "Sound/SUPPORTED_EXTENSIONS:property"}}{{/crossLink}}:
|
|
* <li><b>mp3:</b> If MP3 audio is supported.</li>
|
|
* <li><b>ogg:</b> If OGG audio is supported.</li>
|
|
* <li><b>wav:</b> If WAV audio is supported.</li>
|
|
* <li><b>mpeg:</b> If MPEG audio is supported.</li>
|
|
* <li><b>m4a:</b> If M4A audio is supported.</li>
|
|
* <li><b>mp4:</b> If MP4 audio is supported.</li>
|
|
* <li><b>aiff:</b> If aiff audio is supported.</li>
|
|
* <li><b>wma:</b> If wma audio is supported.</li>
|
|
* <li><b>mid:</b> If mid audio is supported.</li>
|
|
* </ul>
|
|
*
|
|
* You can get a specific capability of the active plugin using standard object notation
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var mp3 = createjs.Sound.capabilities.mp3;
|
|
*
|
|
* Note this property is read only.
|
|
*
|
|
* @property capabilities
|
|
* @type {Object}
|
|
* @static
|
|
* @readOnly
|
|
* @since 0.6.1
|
|
*/
|
|
Object.defineProperty(s, "capabilities", {
|
|
get: function () {
|
|
if (s.activePlugin == null) {
|
|
return null;
|
|
}
|
|
return s.activePlugin._capabilities;
|
|
},
|
|
set: function (value) {
|
|
return false;
|
|
},
|
|
});
|
|
|
|
// Class Private properties
|
|
/**
|
|
* Determines if the plugins have been registered. If false, the first call to play() will instantiate the default
|
|
* plugins ({{#crossLink "WebAudioPlugin"}}{{/crossLink}}, followed by {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}).
|
|
* If plugins have been registered, but none are applicable, then sound playback will fail.
|
|
* @property _pluginsRegistered
|
|
* @type {Boolean}
|
|
* @default false
|
|
* @static
|
|
* @protected
|
|
*/
|
|
s._pluginsRegistered = false;
|
|
|
|
/**
|
|
* Used internally to assign unique IDs to each AbstractSoundInstance.
|
|
* @property _lastID
|
|
* @type {Number}
|
|
* @static
|
|
* @protected
|
|
*/
|
|
s._lastID = 0;
|
|
|
|
/**
|
|
* An array containing all currently playing instances. This allows Sound to control the volume, mute, and playback of
|
|
* all instances when using static APIs like {{#crossLink "Sound/stop"}}{{/crossLink}} and {{#crossLink "Sound/setVolume"}}{{/crossLink}}.
|
|
* When an instance has finished playback, it gets removed via the {{#crossLink "Sound/finishedPlaying"}}{{/crossLink}}
|
|
* method. If the user replays an instance, it gets added back in via the {{#crossLink "Sound/_beginPlaying"}}{{/crossLink}}
|
|
* method.
|
|
* @property _instances
|
|
* @type {Array}
|
|
* @protected
|
|
* @static
|
|
*/
|
|
s._instances = [];
|
|
|
|
/**
|
|
* An object hash storing objects with sound sources, startTime, and duration via there corresponding ID.
|
|
* @property _idHash
|
|
* @type {Object}
|
|
* @protected
|
|
* @static
|
|
*/
|
|
s._idHash = {};
|
|
|
|
/**
|
|
* An object hash that stores preloading sound sources via the parsed source that is passed to the plugin. Contains the
|
|
* source, id, and data that was passed in by the user. Parsed sources can contain multiple instances of source, id,
|
|
* and data.
|
|
* @property _preloadHash
|
|
* @type {Object}
|
|
* @protected
|
|
* @static
|
|
*/
|
|
s._preloadHash = {};
|
|
|
|
/**
|
|
* An object hash storing {{#crossLink "PlayPropsConfig"}}{{/crossLink}} via the parsed source that is passed as defaultPlayProps in
|
|
* {{#crossLink "Sound/registerSound"}}{{/crossLink}} and {{#crossLink "Sound/registerSounds"}}{{/crossLink}}.
|
|
* @property _defaultPlayPropsHash
|
|
* @type {Object}
|
|
* @protected
|
|
* @static
|
|
* @since 0.6.1
|
|
*/
|
|
s._defaultPlayPropsHash = {};
|
|
|
|
// EventDispatcher methods:
|
|
s.addEventListener = null;
|
|
s.removeEventListener = null;
|
|
s.removeAllEventListeners = null;
|
|
s.dispatchEvent = null;
|
|
s.hasEventListener = null;
|
|
s._listeners = null;
|
|
|
|
createjs.EventDispatcher.initialize(s); // inject EventDispatcher methods.
|
|
|
|
// Events
|
|
/**
|
|
* This event is fired when a file finishes loading internally. This event is fired for each loaded sound,
|
|
* so any handler methods should look up the <code>event.src</code> to handle a particular sound.
|
|
* @event fileload
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @param {String} src The source of the sound that was loaded.
|
|
* @param {String} [id] The id passed in when the sound was registered. If one was not provided, it will be null.
|
|
* @param {Number|Object} [data] Any additional data associated with the item. If not provided, it will be undefined.
|
|
* @since 0.4.1
|
|
*/
|
|
|
|
/**
|
|
* This event is fired when a file fails loading internally. This event is fired for each loaded sound,
|
|
* so any handler methods should look up the <code>event.src</code> to handle a particular sound.
|
|
* @event fileerror
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @param {String} src The source of the sound that was loaded.
|
|
* @param {String} [id] The id passed in when the sound was registered. If one was not provided, it will be null.
|
|
* @param {Number|Object} [data] Any additional data associated with the item. If not provided, it will be undefined.
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
// Class Public Methods
|
|
/**
|
|
* Get the preload rules to allow Sound to be used as a plugin by <a href="http://preloadjs.com" target="_blank">PreloadJS</a>.
|
|
* Any load calls that have the matching type or extension will fire the callback method, and use the resulting
|
|
* object, which is potentially modified by Sound. This helps when determining the correct path, as well as
|
|
* registering the audio instance(s) with Sound. This method should not be called, except by PreloadJS.
|
|
* @method getPreloadHandlers
|
|
* @return {Object} An object containing:
|
|
* <ul><li>callback: A preload callback that is fired when a file is added to PreloadJS, which provides
|
|
* Sound a mechanism to modify the load parameters, select the correct file format, register the sound, etc.</li>
|
|
* <li>types: A list of file types that are supported by Sound (currently supports "sound").</li>
|
|
* <li>extensions: A list of file extensions that are supported by Sound (see {{#crossLink "Sound/SUPPORTED_EXTENSIONS:property"}}{{/crossLink}}).</li></ul>
|
|
* @static
|
|
* @protected
|
|
*/
|
|
s.getPreloadHandlers = function () {
|
|
return {
|
|
callback: createjs.proxy(s.initLoad, s),
|
|
types: ["sound"],
|
|
extensions: s.SUPPORTED_EXTENSIONS,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Used to dispatch fileload events from internal loading.
|
|
* @method _handleLoadComplete
|
|
* @param event A loader event.
|
|
* @protected
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s._handleLoadComplete = function (event) {
|
|
var src = event.target.getItem().src;
|
|
if (!s._preloadHash[src]) {
|
|
return;
|
|
}
|
|
|
|
for (var i = 0, l = s._preloadHash[src].length; i < l; i++) {
|
|
var item = s._preloadHash[src][i];
|
|
s._preloadHash[src][i] = true;
|
|
|
|
if (!s.hasEventListener("fileload")) {
|
|
continue;
|
|
}
|
|
|
|
var event = new createjs.Event("fileload");
|
|
event.src = item.src;
|
|
event.id = item.id;
|
|
event.data = item.data;
|
|
event.sprite = item.sprite;
|
|
|
|
s.dispatchEvent(event);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Used to dispatch error events from internal preloading.
|
|
* @param event
|
|
* @protected
|
|
* @since 0.6.0
|
|
* @static
|
|
*/
|
|
s._handleLoadError = function (event) {
|
|
var src = event.target.getItem().src;
|
|
if (!s._preloadHash[src]) {
|
|
return;
|
|
}
|
|
|
|
for (var i = 0, l = s._preloadHash[src].length; i < l; i++) {
|
|
var item = s._preloadHash[src][i];
|
|
s._preloadHash[src][i] = false;
|
|
|
|
if (!s.hasEventListener("fileerror")) {
|
|
continue;
|
|
}
|
|
|
|
var event = new createjs.Event("fileerror");
|
|
event.src = item.src;
|
|
event.id = item.id;
|
|
event.data = item.data;
|
|
event.sprite = item.sprite;
|
|
|
|
s.dispatchEvent(event);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Used by {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} to register a Sound plugin.
|
|
*
|
|
* @method _registerPlugin
|
|
* @param {Object} plugin The plugin class to install.
|
|
* @return {Boolean} Whether the plugin was successfully initialized.
|
|
* @static
|
|
* @private
|
|
*/
|
|
s._registerPlugin = function (plugin) {
|
|
// Note: Each plugin is passed in as a class reference, but we store the activePlugin as an instance
|
|
if (plugin.isSupported()) {
|
|
s.activePlugin = new plugin();
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Register a list of Sound plugins, in order of precedence. To register a single plugin, pass a single element in the array.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* createjs.FlashAudioPlugin.swfPath = "../src/soundjs/flashaudio/";
|
|
* createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin, createjs.FlashAudioPlugin]);
|
|
*
|
|
* @method registerPlugins
|
|
* @param {Array} plugins An array of plugins classes to install.
|
|
* @return {Boolean} Whether a plugin was successfully initialized.
|
|
* @static
|
|
*/
|
|
s.registerPlugins = function (plugins) {
|
|
s._pluginsRegistered = true;
|
|
for (var i = 0, l = plugins.length; i < l; i++) {
|
|
if (s._registerPlugin(plugins[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Initialize the default plugins. This method is automatically called when any audio is played or registered before
|
|
* the user has manually registered plugins, and enables Sound to work without manual plugin setup. Currently, the
|
|
* default plugins are {{#crossLink "WebAudioPlugin"}}{{/crossLink}} followed by {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* if (!createjs.initializeDefaultPlugins()) { return; }
|
|
*
|
|
* @method initializeDefaultPlugins
|
|
* @returns {Boolean} True if a plugin was initialized, false otherwise.
|
|
* @since 0.4.0
|
|
* @static
|
|
*/
|
|
s.initializeDefaultPlugins = function () {
|
|
if (s.activePlugin != null) {
|
|
return true;
|
|
}
|
|
if (s._pluginsRegistered) {
|
|
return false;
|
|
}
|
|
if (
|
|
s.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin])
|
|
) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Determines if Sound has been initialized, and a plugin has been activated.
|
|
*
|
|
* <h4>Example</h4>
|
|
* This example sets up a Flash fallback, but only if there is no plugin specified yet.
|
|
*
|
|
* if (!createjs.Sound.isReady()) {
|
|
* createjs.FlashAudioPlugin.swfPath = "../src/soundjs/flashaudio/";
|
|
* createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin, createjs.FlashAudioPlugin]);
|
|
* }
|
|
*
|
|
* @method isReady
|
|
* @return {Boolean} If Sound has initialized a plugin.
|
|
* @static
|
|
*/
|
|
s.isReady = function () {
|
|
return s.activePlugin != null;
|
|
};
|
|
|
|
/**
|
|
* Deprecated, please use {{#crossLink "Sound/capabilities:property"}}{{/crossLink}} instead.
|
|
*
|
|
* @method getCapabilities
|
|
* @return {Object} An object containing the capabilities of the active plugin.
|
|
* @static
|
|
* @deprecated
|
|
*/
|
|
s.getCapabilities = function () {
|
|
if (s.activePlugin == null) {
|
|
return null;
|
|
}
|
|
return s.activePlugin._capabilities;
|
|
};
|
|
|
|
/**
|
|
* Deprecated, please use {{#crossLink "Sound/capabilities:property"}}{{/crossLink}} instead.
|
|
*
|
|
* @method getCapability
|
|
* @param {String} key The capability to retrieve
|
|
* @return {Number|Boolean} The value of the capability.
|
|
* @static
|
|
* @see getCapabilities
|
|
* @deprecated
|
|
*/
|
|
s.getCapability = function (key) {
|
|
if (s.activePlugin == null) {
|
|
return null;
|
|
}
|
|
return s.activePlugin._capabilities[key];
|
|
};
|
|
|
|
/**
|
|
* Process manifest items from <a href="http://preloadjs.com" target="_blank">PreloadJS</a>. This method is intended
|
|
* for usage by a plugin, and not for direct interaction.
|
|
* @method initLoad
|
|
* @param {Object} src The object to load.
|
|
* @return {Object|AbstractLoader} An instance of AbstractLoader.
|
|
* @protected
|
|
* @static
|
|
*/
|
|
s.initLoad = function (loadItem) {
|
|
return s._registerSound(loadItem);
|
|
};
|
|
|
|
/**
|
|
* Internal method for loading sounds. This should not be called directly.
|
|
*
|
|
* @method _registerSound
|
|
* @param {Object} src The object to load, containing src property and optionally containing id and data.
|
|
* @return {Object} An object with the modified values that were passed in, which defines the sound.
|
|
* Returns false if the source cannot be parsed or no plugins can be initialized.
|
|
* Returns true if the source is already loaded.
|
|
* @static
|
|
* @private
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
s._registerSound = function (loadItem) {
|
|
if (!s.initializeDefaultPlugins()) {
|
|
return false;
|
|
}
|
|
|
|
var details;
|
|
if (loadItem.src instanceof Object) {
|
|
details = s._parseSrc(loadItem.src);
|
|
details.src = loadItem.path + details.src;
|
|
} else {
|
|
details = s._parsePath(loadItem.src);
|
|
}
|
|
if (details == null) {
|
|
return false;
|
|
}
|
|
loadItem.src = details.src;
|
|
loadItem.type = "sound";
|
|
|
|
var data = loadItem.data;
|
|
var numChannels = null;
|
|
if (data != null) {
|
|
if (!isNaN(data.channels)) {
|
|
numChannels = parseInt(data.channels);
|
|
} else if (!isNaN(data)) {
|
|
numChannels = parseInt(data);
|
|
}
|
|
|
|
if (data.audioSprite) {
|
|
var sp;
|
|
for (var i = data.audioSprite.length; i--; ) {
|
|
sp = data.audioSprite[i];
|
|
s._idHash[sp.id] = {
|
|
src: loadItem.src,
|
|
startTime: parseInt(sp.startTime),
|
|
duration: parseInt(sp.duration),
|
|
};
|
|
|
|
if (sp.defaultPlayProps) {
|
|
s._defaultPlayPropsHash[sp.id] = createjs.PlayPropsConfig.create(
|
|
sp.defaultPlayProps
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (loadItem.id != null) {
|
|
s._idHash[loadItem.id] = { src: loadItem.src };
|
|
}
|
|
var loader = s.activePlugin.register(loadItem);
|
|
|
|
SoundChannel.create(loadItem.src, numChannels);
|
|
|
|
// return the number of instances to the user. This will also be returned in the load event.
|
|
if (data == null || !isNaN(data)) {
|
|
loadItem.data = numChannels || SoundChannel.maxPerChannel();
|
|
} else {
|
|
loadItem.data.channels = numChannels || SoundChannel.maxPerChannel();
|
|
}
|
|
|
|
if (loader.type) {
|
|
loadItem.type = loader.type;
|
|
}
|
|
|
|
if (loadItem.defaultPlayProps) {
|
|
s._defaultPlayPropsHash[loadItem.src] = createjs.PlayPropsConfig.create(
|
|
loadItem.defaultPlayProps
|
|
);
|
|
}
|
|
return loader;
|
|
};
|
|
|
|
/**
|
|
* Register an audio file for loading and future playback in Sound. This is automatically called when using
|
|
* <a href="http://preloadjs.com" target="_blank">PreloadJS</a>. It is recommended to register all sounds that
|
|
* need to be played back in order to properly prepare and preload them. Sound does internal preloading when required.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* createjs.Sound.alternateExtensions = ["mp3"];
|
|
* createjs.Sound.on("fileload", handleLoad); // add an event listener for when load is completed
|
|
* createjs.Sound.registerSound("myAudioPath/mySound.ogg", "myID", 3);
|
|
* createjs.Sound.registerSound({ogg:"path1/mySound.ogg", mp3:"path2/mySoundNoExtension"}, "myID", 3);
|
|
*
|
|
*
|
|
* @method registerSound
|
|
* @param {String | Object} src The source or an Object with a "src" property or an Object with multiple extension labeled src properties.
|
|
* @param {String} [id] An id specified by the user to play the sound later. Note id is required for when src is multiple extension labeled src properties.
|
|
* @param {Number | Object} [data] Data associated with the item. Sound uses the data parameter as the number of
|
|
* channels for an audio instance, however a "channels" property can be appended to the data object if it is used
|
|
* for other information. The audio channels will set a default based on plugin if no value is found.
|
|
* Sound also uses the data property to hold an {{#crossLink "AudioSprite"}}{{/crossLink}} array of objects in the following format {id, startTime, duration}.<br/>
|
|
* id used to play the sound later, in the same manner as a sound src with an id.<br/>
|
|
* startTime is the initial offset to start playback and loop from, in milliseconds.<br/>
|
|
* duration is the amount of time to play the clip for, in milliseconds.<br/>
|
|
* This allows Sound to support audio sprites that are played back by id.
|
|
* @param {string} basePath Set a path that will be prepended to src for loading.
|
|
* @param {Object | PlayPropsConfig} defaultPlayProps Optional Playback properties that will be set as the defaults on any new AbstractSoundInstance.
|
|
* See {{#crossLink "PlayPropsConfig"}}{{/crossLink}} for options.
|
|
* @return {Object} An object with the modified values that were passed in, which defines the sound.
|
|
* Returns false if the source cannot be parsed or no plugins can be initialized.
|
|
* Returns true if the source is already loaded.
|
|
* @static
|
|
* @since 0.4.0
|
|
*/
|
|
s.registerSound = function (src, id, data, basePath, defaultPlayProps) {
|
|
var loadItem = {
|
|
src: src,
|
|
id: id,
|
|
data: data,
|
|
defaultPlayProps: defaultPlayProps,
|
|
};
|
|
if (src instanceof Object && src.src) {
|
|
basePath = id;
|
|
loadItem = src;
|
|
}
|
|
loadItem = createjs.LoadItem.create(loadItem);
|
|
loadItem.path = basePath;
|
|
|
|
if (basePath != null && !(loadItem.src instanceof Object)) {
|
|
loadItem.src = basePath + src;
|
|
}
|
|
|
|
var loader = s._registerSound(loadItem);
|
|
if (!loader) {
|
|
return false;
|
|
}
|
|
|
|
if (!s._preloadHash[loadItem.src]) {
|
|
s._preloadHash[loadItem.src] = [];
|
|
}
|
|
s._preloadHash[loadItem.src].push(loadItem);
|
|
if (s._preloadHash[loadItem.src].length == 1) {
|
|
// OJR note this will disallow reloading a sound if loading fails or the source changes
|
|
loader.on("complete", createjs.proxy(this._handleLoadComplete, this));
|
|
loader.on("error", createjs.proxy(this._handleLoadError, this));
|
|
s.activePlugin.preload(loader);
|
|
} else {
|
|
if (s._preloadHash[loadItem.src][0] == true) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return loadItem;
|
|
};
|
|
|
|
/**
|
|
* Register an array of audio files for loading and future playback in Sound. It is recommended to register all
|
|
* sounds that need to be played back in order to properly prepare and preload them. Sound does internal preloading
|
|
* when required.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var assetPath = "./myAudioPath/";
|
|
* var sounds = [
|
|
* {src:"asset0.ogg", id:"example"},
|
|
* {src:"asset1.ogg", id:"1", data:6},
|
|
* {src:"asset2.mp3", id:"works"}
|
|
* {src:{mp3:"path1/asset3.mp3", ogg:"path2/asset3NoExtension}, id:"better"}
|
|
* ];
|
|
* createjs.Sound.alternateExtensions = ["mp3"]; // if the passed extension is not supported, try this extension
|
|
* createjs.Sound.on("fileload", handleLoad); // call handleLoad when each sound loads
|
|
* createjs.Sound.registerSounds(sounds, assetPath);
|
|
*
|
|
* @method registerSounds
|
|
* @param {Array} sounds An array of objects to load. Objects are expected to be in the format needed for
|
|
* {{#crossLink "Sound/registerSound"}}{{/crossLink}}: <code>{src:srcURI, id:ID, data:Data}</code>
|
|
* with "id" and "data" being optional.
|
|
* You can also pass an object with path and manifest properties, where path is a basePath and manifest is an array of objects to load.
|
|
* Note id is required if src is an object with extension labeled src properties.
|
|
* @param {string} basePath Set a path that will be prepended to each src when loading. When creating, playing, or removing
|
|
* audio that was loaded with a basePath by src, the basePath must be included.
|
|
* @return {Object} An array of objects with the modified values that were passed in, which defines each sound.
|
|
* Like registerSound, it will return false for any values when the source cannot be parsed or if no plugins can be initialized.
|
|
* Also, it will return true for any values when the source is already loaded.
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s.registerSounds = function (sounds, basePath) {
|
|
var returnValues = [];
|
|
if (sounds.path) {
|
|
if (!basePath) {
|
|
basePath = sounds.path;
|
|
} else {
|
|
basePath = basePath + sounds.path;
|
|
}
|
|
sounds = sounds.manifest;
|
|
// TODO document this feature
|
|
}
|
|
for (var i = 0, l = sounds.length; i < l; i++) {
|
|
returnValues[i] = createjs.Sound.registerSound(
|
|
sounds[i].src,
|
|
sounds[i].id,
|
|
sounds[i].data,
|
|
basePath,
|
|
sounds[i].defaultPlayProps
|
|
);
|
|
}
|
|
return returnValues;
|
|
};
|
|
|
|
/**
|
|
* Remove a sound that has been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or
|
|
* {{#crossLink "Sound/registerSounds"}}{{/crossLink}}.
|
|
* <br />Note this will stop playback on active instances playing this sound before deleting them.
|
|
* <br />Note if you passed in a basePath, you need to pass it or prepend it to the src here.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* createjs.Sound.removeSound("myID");
|
|
* createjs.Sound.removeSound("myAudioBasePath/mySound.ogg");
|
|
* createjs.Sound.removeSound("myPath/myOtherSound.mp3", "myBasePath/");
|
|
* createjs.Sound.removeSound({mp3:"musicNoExtension", ogg:"music.ogg"}, "myBasePath/");
|
|
*
|
|
* @method removeSound
|
|
* @param {String | Object} src The src or ID of the audio, or an Object with a "src" property, or an Object with multiple extension labeled src properties.
|
|
* @param {string} basePath Set a path that will be prepended to each src when removing.
|
|
* @return {Boolean} True if sound is successfully removed.
|
|
* @static
|
|
* @since 0.4.1
|
|
*/
|
|
s.removeSound = function (src, basePath) {
|
|
if (s.activePlugin == null) {
|
|
return false;
|
|
}
|
|
|
|
if (src instanceof Object && src.src) {
|
|
src = src.src;
|
|
}
|
|
|
|
var details;
|
|
if (src instanceof Object) {
|
|
details = s._parseSrc(src);
|
|
} else {
|
|
src = s._getSrcById(src).src;
|
|
details = s._parsePath(src);
|
|
}
|
|
if (details == null) {
|
|
return false;
|
|
}
|
|
src = details.src;
|
|
if (basePath != null) {
|
|
src = basePath + src;
|
|
}
|
|
|
|
for (var prop in s._idHash) {
|
|
if (s._idHash[prop].src == src) {
|
|
delete s._idHash[prop];
|
|
}
|
|
}
|
|
|
|
// clear from SoundChannel, which also stops and deletes all instances
|
|
SoundChannel.removeSrc(src);
|
|
|
|
delete s._preloadHash[src];
|
|
|
|
s.activePlugin.removeSound(src);
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Remove an array of audio files that have been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or
|
|
* {{#crossLink "Sound/registerSounds"}}{{/crossLink}}.
|
|
* <br />Note this will stop playback on active instances playing this audio before deleting them.
|
|
* <br />Note if you passed in a basePath, you need to pass it or prepend it to the src here.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* assetPath = "./myPath/";
|
|
* var sounds = [
|
|
* {src:"asset0.ogg", id:"example"},
|
|
* {src:"asset1.ogg", id:"1", data:6},
|
|
* {src:"asset2.mp3", id:"works"}
|
|
* ];
|
|
* createjs.Sound.removeSounds(sounds, assetPath);
|
|
*
|
|
* @method removeSounds
|
|
* @param {Array} sounds An array of objects to remove. Objects are expected to be in the format needed for
|
|
* {{#crossLink "Sound/removeSound"}}{{/crossLink}}: <code>{srcOrID:srcURIorID}</code>.
|
|
* You can also pass an object with path and manifest properties, where path is a basePath and manifest is an array of objects to remove.
|
|
* @param {string} basePath Set a path that will be prepended to each src when removing.
|
|
* @return {Object} An array of Boolean values representing if the sounds with the same array index were
|
|
* successfully removed.
|
|
* @static
|
|
* @since 0.4.1
|
|
*/
|
|
s.removeSounds = function (sounds, basePath) {
|
|
var returnValues = [];
|
|
if (sounds.path) {
|
|
if (!basePath) {
|
|
basePath = sounds.path;
|
|
} else {
|
|
basePath = basePath + sounds.path;
|
|
}
|
|
sounds = sounds.manifest;
|
|
}
|
|
for (var i = 0, l = sounds.length; i < l; i++) {
|
|
returnValues[i] = createjs.Sound.removeSound(sounds[i].src, basePath);
|
|
}
|
|
return returnValues;
|
|
};
|
|
|
|
/**
|
|
* Remove all sounds that have been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or
|
|
* {{#crossLink "Sound/registerSounds"}}{{/crossLink}}.
|
|
* <br />Note this will stop playback on all active sound instances before deleting them.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* createjs.Sound.removeAllSounds();
|
|
*
|
|
* @method removeAllSounds
|
|
* @static
|
|
* @since 0.4.1
|
|
*/
|
|
s.removeAllSounds = function () {
|
|
s._idHash = {};
|
|
s._preloadHash = {};
|
|
SoundChannel.removeAll();
|
|
if (s.activePlugin) {
|
|
s.activePlugin.removeAllSounds();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Check if a source has been loaded by internal preloaders. This is necessary to ensure that sounds that are
|
|
* not completed preloading will not kick off a new internal preload if they are played.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var mySound = "assetPath/asset0.ogg";
|
|
* if(createjs.Sound.loadComplete(mySound) {
|
|
* createjs.Sound.play(mySound);
|
|
* }
|
|
*
|
|
* @method loadComplete
|
|
* @param {String} src The src or id that is being loaded.
|
|
* @return {Boolean} If the src is already loaded.
|
|
* @since 0.4.0
|
|
* @static
|
|
*/
|
|
s.loadComplete = function (src) {
|
|
if (!s.isReady()) {
|
|
return false;
|
|
}
|
|
var details = s._parsePath(src);
|
|
if (details) {
|
|
src = s._getSrcById(details.src).src;
|
|
} else {
|
|
src = s._getSrcById(src).src;
|
|
}
|
|
if (s._preloadHash[src] == undefined) {
|
|
return false;
|
|
}
|
|
return s._preloadHash[src][0] == true; // src only loads once, so if it's true for the first it's true for all
|
|
};
|
|
|
|
/**
|
|
* Parse the path of a sound. Alternate extensions will be attempted in order if the
|
|
* current extension is not supported
|
|
* @method _parsePath
|
|
* @param {String} value The path to an audio source.
|
|
* @return {Object} A formatted object that can be registered with the {{#crossLink "Sound/activePlugin:property"}}{{/crossLink}}
|
|
* and returned to a preloader like <a href="http://preloadjs.com" target="_blank">PreloadJS</a>.
|
|
* @protected
|
|
* @static
|
|
*/
|
|
s._parsePath = function (value) {
|
|
if (typeof value != "string") {
|
|
value = value.toString();
|
|
}
|
|
|
|
var match = value.match(s.FILE_PATTERN);
|
|
if (match == null) {
|
|
return false;
|
|
}
|
|
|
|
var name = match[4];
|
|
var ext = match[5];
|
|
var c = s.capabilities;
|
|
var i = 0;
|
|
while (!c[ext]) {
|
|
ext = s.alternateExtensions[i++];
|
|
if (i > s.alternateExtensions.length) {
|
|
return null;
|
|
} // no extensions are supported
|
|
}
|
|
value = value.replace("." + match[5], "." + ext);
|
|
|
|
var ret = { name: name, src: value, extension: ext };
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Parse the path of a sound based on properties of src matching with supported extensions.
|
|
* Returns false if none of the properties are supported
|
|
* @method _parseSrc
|
|
* @param {Object} value The paths to an audio source, indexed by extension type.
|
|
* @return {Object} A formatted object that can be registered with the {{#crossLink "Sound/activePlugin:property"}}{{/crossLink}}
|
|
* and returned to a preloader like <a href="http://preloadjs.com" target="_blank">PreloadJS</a>.
|
|
* @protected
|
|
* @static
|
|
*/
|
|
s._parseSrc = function (value) {
|
|
var ret = { name: undefined, src: undefined, extension: undefined };
|
|
var c = s.capabilities;
|
|
|
|
for (var prop in value) {
|
|
if (value.hasOwnProperty(prop) && c[prop]) {
|
|
ret.src = value[prop];
|
|
ret.extension = prop;
|
|
break;
|
|
}
|
|
}
|
|
if (!ret.src) {
|
|
return false;
|
|
} // no matches
|
|
|
|
var i = ret.src.lastIndexOf("/");
|
|
if (i != -1) {
|
|
ret.name = ret.src.slice(i + 1);
|
|
} else {
|
|
ret.name = ret.src;
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
|
|
/* ---------------
|
|
Static API.
|
|
--------------- */
|
|
/**
|
|
* Play a sound and get a {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to control. If the sound fails to play, a
|
|
* AbstractSoundInstance will still be returned, and have a playState of {{#crossLink "Sound/PLAY_FAILED:property"}}{{/crossLink}}.
|
|
* Note that even on sounds with failed playback, you may still be able to call AbstractSoundInstance {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}},
|
|
* since the failure could be due to lack of available channels. If the src does not have a supported extension or
|
|
* if there is no available plugin, a default AbstractSoundInstance will be returned which will not play any audio, but will not generate errors.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* createjs.Sound.on("fileload", handleLoad);
|
|
* createjs.Sound.registerSound("myAudioPath/mySound.mp3", "myID", 3);
|
|
* function handleLoad(event) {
|
|
* createjs.Sound.play("myID");
|
|
* // store off AbstractSoundInstance for controlling
|
|
* var myInstance = createjs.Sound.play("myID", {interrupt: createjs.Sound.INTERRUPT_ANY, loop:-1});
|
|
* }
|
|
*
|
|
* NOTE to create an audio sprite that has not already been registered, both startTime and duration need to be set.
|
|
* This is only when creating a new audio sprite, not when playing using the id of an already registered audio sprite.
|
|
*
|
|
* <b>Parameters Deprecated</b><br />
|
|
* The parameters for this method are deprecated in favor of a single parameter that is an Object or {{#crossLink "PlayPropsConfig"}}{{/crossLink}}.
|
|
*
|
|
* @method play
|
|
* @param {String} src The src or ID of the audio.
|
|
* @param {String | Object} [interrupt="none"|options] <b>This parameter will be renamed playProps in the next release.</b><br />
|
|
* This parameter can be an instance of {{#crossLink "PlayPropsConfig"}}{{/crossLink}} or an Object that contains any or all optional properties by name,
|
|
* including: interrupt, delay, offset, loop, volume, pan, startTime, and duration (see the above code sample).
|
|
* <br /><strong>OR</strong><br />
|
|
* <b>Deprecated</b> How to interrupt any currently playing instances of audio with the same source,
|
|
* if the maximum number of instances of the sound are already playing. Values are defined as <code>INTERRUPT_TYPE</code>
|
|
* constants on the Sound class, with the default defined by {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}.
|
|
* @param {Number} [delay=0] <b>Deprecated</b> The amount of time to delay the start of audio playback, in milliseconds.
|
|
* @param {Number} [offset=0] <b>Deprecated</b> The offset from the start of the audio to begin playback, in milliseconds.
|
|
* @param {Number} [loop=0] <b>Deprecated</b> How many times the audio loops when it reaches the end of playback. The default is 0 (no
|
|
* loops), and -1 can be used for infinite playback.
|
|
* @param {Number} [volume=1] <b>Deprecated</b> The volume of the sound, between 0 and 1. Note that the master volume is applied
|
|
* against the individual volume.
|
|
* @param {Number} [pan=0] <b>Deprecated</b> The left-right pan of the sound (if supported), between -1 (left) and 1 (right).
|
|
* @param {Number} [startTime=null] <b>Deprecated</b> To create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds.
|
|
* @param {Number} [duration=null] <b>Deprecated</b> To create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds.
|
|
* @return {AbstractSoundInstance} A {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} that can be controlled after it is created.
|
|
* @static
|
|
*/
|
|
s.play = function (
|
|
src,
|
|
interrupt,
|
|
delay,
|
|
offset,
|
|
loop,
|
|
volume,
|
|
pan,
|
|
startTime,
|
|
duration
|
|
) {
|
|
var playProps;
|
|
if (
|
|
interrupt instanceof Object ||
|
|
interrupt instanceof createjs.PlayPropsConfig
|
|
) {
|
|
playProps = createjs.PlayPropsConfig.create(interrupt);
|
|
} else {
|
|
playProps = createjs.PlayPropsConfig.create({
|
|
interrupt: interrupt,
|
|
delay: delay,
|
|
offset: offset,
|
|
loop: loop,
|
|
volume: volume,
|
|
pan: pan,
|
|
startTime: startTime,
|
|
duration: duration,
|
|
});
|
|
}
|
|
var instance = s.createInstance(
|
|
src,
|
|
playProps.startTime,
|
|
playProps.duration
|
|
);
|
|
var ok = s._playInstance(instance, playProps);
|
|
if (!ok) {
|
|
instance._playFailed();
|
|
}
|
|
return instance;
|
|
};
|
|
|
|
/**
|
|
* Creates a {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} using the passed in src. If the src does not have a
|
|
* supported extension or if there is no available plugin, a default AbstractSoundInstance will be returned that can be
|
|
* called safely but does nothing.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var myInstance = null;
|
|
* createjs.Sound.on("fileload", handleLoad);
|
|
* createjs.Sound.registerSound("myAudioPath/mySound.mp3", "myID", 3);
|
|
* function handleLoad(event) {
|
|
* myInstance = createjs.Sound.createInstance("myID");
|
|
* // alternately we could call the following
|
|
* myInstance = createjs.Sound.createInstance("myAudioPath/mySound.mp3");
|
|
* }
|
|
*
|
|
* NOTE to create an audio sprite that has not already been registered, both startTime and duration need to be set.
|
|
* This is only when creating a new audio sprite, not when playing using the id of an already registered audio sprite.
|
|
*
|
|
* @method createInstance
|
|
* @param {String} src The src or ID of the audio.
|
|
* @param {Number} [startTime=null] To create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds.
|
|
* @param {Number} [duration=null] To create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds.
|
|
* @return {AbstractSoundInstance} A {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} that can be controlled after it is created.
|
|
* Unsupported extensions will return the default AbstractSoundInstance.
|
|
* @since 0.4.0
|
|
* @static
|
|
*/
|
|
s.createInstance = function (src, startTime, duration) {
|
|
if (!s.initializeDefaultPlugins()) {
|
|
return new createjs.DefaultSoundInstance(src, startTime, duration);
|
|
}
|
|
|
|
var defaultPlayProps = s._defaultPlayPropsHash[src]; // for audio sprites, which create and store defaults by id
|
|
src = s._getSrcById(src);
|
|
|
|
var details = s._parsePath(src.src);
|
|
|
|
var instance = null;
|
|
if (details != null && details.src != null) {
|
|
SoundChannel.create(details.src);
|
|
if (startTime == null) {
|
|
startTime = src.startTime;
|
|
}
|
|
instance = s.activePlugin.create(
|
|
details.src,
|
|
startTime,
|
|
duration || src.duration
|
|
);
|
|
|
|
defaultPlayProps =
|
|
defaultPlayProps || s._defaultPlayPropsHash[details.src];
|
|
if (defaultPlayProps) {
|
|
instance.applyPlayProps(defaultPlayProps);
|
|
}
|
|
} else {
|
|
instance = new createjs.DefaultSoundInstance(src, startTime, duration);
|
|
}
|
|
|
|
instance.uniqueId = s._lastID++;
|
|
|
|
return instance;
|
|
};
|
|
|
|
/**
|
|
* Stop all audio (global stop). Stopped audio is reset, and not paused. To play audio that has been stopped,
|
|
* call AbstractSoundInstance {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}}.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* createjs.Sound.stop();
|
|
*
|
|
* @method stop
|
|
* @static
|
|
*/
|
|
s.stop = function () {
|
|
var instances = this._instances;
|
|
for (var i = instances.length; i--; ) {
|
|
instances[i].stop(); // NOTE stop removes instance from this._instances
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Deprecated, please use {{#crossLink "Sound/volume:property"}}{{/crossLink}} instead.
|
|
*
|
|
* @method setVolume
|
|
* @param {Number} value The master volume value. The acceptable range is 0-1.
|
|
* @static
|
|
* @deprecated
|
|
*/
|
|
s.setVolume = function (value) {
|
|
if (Number(value) == null) {
|
|
return false;
|
|
}
|
|
value = Math.max(0, Math.min(1, value));
|
|
s._masterVolume = value;
|
|
if (
|
|
!this.activePlugin ||
|
|
!this.activePlugin.setVolume ||
|
|
!this.activePlugin.setVolume(value)
|
|
) {
|
|
var instances = this._instances;
|
|
for (var i = 0, l = instances.length; i < l; i++) {
|
|
instances[i].setMasterVolume(value);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Deprecated, please use {{#crossLink "Sound/volume:property"}}{{/crossLink}} instead.
|
|
*
|
|
* @method getVolume
|
|
* @return {Number} The master volume, in a range of 0-1.
|
|
* @static
|
|
* @deprecated
|
|
*/
|
|
s.getVolume = function () {
|
|
return this._masterVolume;
|
|
};
|
|
|
|
/**
|
|
* Deprecated, please use {{#crossLink "Sound/muted:property"}}{{/crossLink}} instead.
|
|
*
|
|
* @method setMute
|
|
* @param {Boolean} value Whether the audio should be muted or not.
|
|
* @return {Boolean} If the mute was set.
|
|
* @static
|
|
* @since 0.4.0
|
|
* @deprecated
|
|
*/
|
|
s.setMute = function (value) {
|
|
if (value == null) {
|
|
return false;
|
|
}
|
|
|
|
this._masterMute = value;
|
|
if (
|
|
!this.activePlugin ||
|
|
!this.activePlugin.setMute ||
|
|
!this.activePlugin.setMute(value)
|
|
) {
|
|
var instances = this._instances;
|
|
for (var i = 0, l = instances.length; i < l; i++) {
|
|
instances[i].setMasterMute(value);
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Deprecated, please use {{#crossLink "Sound/muted:property"}}{{/crossLink}} instead.
|
|
*
|
|
* @method getMute
|
|
* @return {Boolean} The mute value of Sound.
|
|
* @static
|
|
* @since 0.4.0
|
|
* @deprecated
|
|
*/
|
|
s.getMute = function () {
|
|
return this._masterMute;
|
|
};
|
|
|
|
/**
|
|
* Set the default playback properties for all new SoundInstances of the passed in src or ID.
|
|
* See {{#crossLink "PlayPropsConfig"}}{{/crossLink}} for available properties.
|
|
*
|
|
* @method setDefaultPlayProps
|
|
* @param {String} src The src or ID used to register the audio.
|
|
* @param {Object | PlayPropsConfig} playProps The playback properties you would like to set.
|
|
* @since 0.6.1
|
|
*/
|
|
s.setDefaultPlayProps = function (src, playProps) {
|
|
src = s._getSrcById(src);
|
|
s._defaultPlayPropsHash[
|
|
s._parsePath(src.src).src
|
|
] = createjs.PlayPropsConfig.create(playProps);
|
|
};
|
|
|
|
/**
|
|
* Get the default playback properties for the passed in src or ID. These properties are applied to all
|
|
* new SoundInstances. Returns null if default does not exist.
|
|
*
|
|
* @method getDefaultPlayProps
|
|
* @param {String} src The src or ID used to register the audio.
|
|
* @returns {PlayPropsConfig} returns an existing PlayPropsConfig or null if one does not exist
|
|
* @since 0.6.1
|
|
*/
|
|
s.getDefaultPlayProps = function (src) {
|
|
src = s._getSrcById(src);
|
|
return s._defaultPlayPropsHash[s._parsePath(src.src).src];
|
|
};
|
|
|
|
/* ---------------
|
|
Internal methods
|
|
--------------- */
|
|
/**
|
|
* Play an instance. This is called by the static API, as well as from plugins. This allows the core class to
|
|
* control delays.
|
|
* @method _playInstance
|
|
* @param {AbstractSoundInstance} instance The {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to start playing.
|
|
* @param {PlayPropsConfig} playProps A PlayPropsConfig object.
|
|
* @return {Boolean} If the sound can start playing. Sounds that fail immediately will return false. Sounds that
|
|
* have a delay will return true, but may still fail to play.
|
|
* @protected
|
|
* @static
|
|
*/
|
|
s._playInstance = function (instance, playProps) {
|
|
var defaultPlayProps = s._defaultPlayPropsHash[instance.src] || {};
|
|
if (playProps.interrupt == null) {
|
|
playProps.interrupt =
|
|
defaultPlayProps.interrupt || s.defaultInterruptBehavior;
|
|
}
|
|
if (playProps.delay == null) {
|
|
playProps.delay = defaultPlayProps.delay || 0;
|
|
}
|
|
if (playProps.offset == null) {
|
|
playProps.offset = instance.getPosition();
|
|
}
|
|
if (playProps.loop == null) {
|
|
playProps.loop = instance.loop;
|
|
}
|
|
if (playProps.volume == null) {
|
|
playProps.volume = instance.volume;
|
|
}
|
|
if (playProps.pan == null) {
|
|
playProps.pan = instance.pan;
|
|
}
|
|
|
|
if (playProps.delay == 0) {
|
|
var ok = s._beginPlaying(instance, playProps);
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
} else {
|
|
//Note that we can't pass arguments to proxy OR setTimeout (IE only), so just wrap the function call.
|
|
// OJR WebAudio may want to handle this differently, so it might make sense to move this functionality into the plugins in the future
|
|
var delayTimeoutId = setTimeout(function () {
|
|
s._beginPlaying(instance, playProps);
|
|
}, playProps.delay);
|
|
instance.delayTimeoutId = delayTimeoutId;
|
|
}
|
|
|
|
this._instances.push(instance);
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Begin playback. This is called immediately or after delay by {{#crossLink "Sound/playInstance"}}{{/crossLink}}.
|
|
* @method _beginPlaying
|
|
* @param {AbstractSoundInstance} instance A {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to begin playback.
|
|
* @param {PlayPropsConfig} playProps A PlayPropsConfig object.
|
|
* @return {Boolean} If the sound can start playing. If there are no available channels, or the instance fails to
|
|
* start, this will return false.
|
|
* @protected
|
|
* @static
|
|
*/
|
|
s._beginPlaying = function (instance, playProps) {
|
|
if (!SoundChannel.add(instance, playProps.interrupt)) {
|
|
return false;
|
|
}
|
|
var result = instance._beginPlaying(playProps);
|
|
if (!result) {
|
|
var index = createjs.indexOf(this._instances, instance);
|
|
if (index > -1) {
|
|
this._instances.splice(index, 1);
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Get the source of a sound via the ID passed in with a register call. If no ID is found the value is returned
|
|
* instead.
|
|
* @method _getSrcById
|
|
* @param {String} value The ID the sound was registered with.
|
|
* @return {String} The source of the sound if it has been registered with this ID or the value that was passed in.
|
|
* @protected
|
|
* @static
|
|
*/
|
|
s._getSrcById = function (value) {
|
|
return s._idHash[value] || { src: value };
|
|
};
|
|
|
|
/**
|
|
* A sound has completed playback, been interrupted, failed, or been stopped. This method removes the instance from
|
|
* Sound management. It will be added again, if the sound re-plays. Note that this method is called from the
|
|
* instances themselves.
|
|
* @method _playFinished
|
|
* @param {AbstractSoundInstance} instance The instance that finished playback.
|
|
* @protected
|
|
* @static
|
|
*/
|
|
s._playFinished = function (instance) {
|
|
SoundChannel.remove(instance);
|
|
var index = createjs.indexOf(this._instances, instance);
|
|
if (index > -1) {
|
|
this._instances.splice(index, 1);
|
|
} // OJR this will always be > -1, there is no way for an instance to exist without being added to this._instances
|
|
};
|
|
|
|
createjs.Sound = Sound;
|
|
|
|
/**
|
|
* An internal class that manages the number of active {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} instances for
|
|
* each sound type. This method is only used internally by the {{#crossLink "Sound"}}{{/crossLink}} class.
|
|
*
|
|
* The number of sounds is artificially limited by Sound in order to prevent over-saturation of a
|
|
* single sound, as well as to stay within hardware limitations, although the latter may disappear with better
|
|
* browser support.
|
|
*
|
|
* When a sound is played, this class ensures that there is an available instance, or interrupts an appropriate
|
|
* sound that is already playing.
|
|
* #class SoundChannel
|
|
* @param {String} src The source of the instances
|
|
* @param {Number} [max=1] The number of instances allowed
|
|
* @constructor
|
|
* @protected
|
|
*/
|
|
function SoundChannel(src, max) {
|
|
this.init(src, max);
|
|
}
|
|
|
|
/* ------------
|
|
Static API
|
|
------------ */
|
|
/**
|
|
* A hash of channel instances indexed by source.
|
|
* #property channels
|
|
* @type {Object}
|
|
* @static
|
|
*/
|
|
SoundChannel.channels = {};
|
|
|
|
/**
|
|
* Create a sound channel. Note that if the sound channel already exists, this will fail.
|
|
* #method create
|
|
* @param {String} src The source for the channel
|
|
* @param {Number} max The maximum amount this channel holds. The default is {{#crossLink "SoundChannel.maxDefault"}}{{/crossLink}}.
|
|
* @return {Boolean} If the channels were created.
|
|
* @static
|
|
*/
|
|
SoundChannel.create = function (src, max) {
|
|
var channel = SoundChannel.get(src);
|
|
if (channel == null) {
|
|
SoundChannel.channels[src] = new SoundChannel(src, max);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
/**
|
|
* Delete a sound channel, stop and delete all related instances. Note that if the sound channel does not exist, this will fail.
|
|
* #method remove
|
|
* @param {String} src The source for the channel
|
|
* @return {Boolean} If the channels were deleted.
|
|
* @static
|
|
*/
|
|
SoundChannel.removeSrc = function (src) {
|
|
var channel = SoundChannel.get(src);
|
|
if (channel == null) {
|
|
return false;
|
|
}
|
|
channel._removeAll(); // this stops and removes all active instances
|
|
delete SoundChannel.channels[src];
|
|
return true;
|
|
};
|
|
/**
|
|
* Delete all sound channels, stop and delete all related instances.
|
|
* #method removeAll
|
|
* @static
|
|
*/
|
|
SoundChannel.removeAll = function () {
|
|
for (var channel in SoundChannel.channels) {
|
|
SoundChannel.channels[channel]._removeAll(); // this stops and removes all active instances
|
|
}
|
|
SoundChannel.channels = {};
|
|
};
|
|
/**
|
|
* Add an instance to a sound channel.
|
|
* #method add
|
|
* @param {AbstractSoundInstance} instance The instance to add to the channel
|
|
* @param {String} interrupt The interrupt value to use. Please see the {{#crossLink "Sound/play"}}{{/crossLink}}
|
|
* for details on interrupt modes.
|
|
* @return {Boolean} The success of the method call. If the channel is full, it will return false.
|
|
* @static
|
|
*/
|
|
SoundChannel.add = function (instance, interrupt) {
|
|
var channel = SoundChannel.get(instance.src);
|
|
if (channel == null) {
|
|
return false;
|
|
}
|
|
return channel._add(instance, interrupt);
|
|
};
|
|
/**
|
|
* Remove an instance from the channel.
|
|
* #method remove
|
|
* @param {AbstractSoundInstance} instance The instance to remove from the channel
|
|
* @return The success of the method call. If there is no channel, it will return false.
|
|
* @static
|
|
*/
|
|
SoundChannel.remove = function (instance) {
|
|
var channel = SoundChannel.get(instance.src);
|
|
if (channel == null) {
|
|
return false;
|
|
}
|
|
channel._remove(instance);
|
|
return true;
|
|
};
|
|
/**
|
|
* Get the maximum number of sounds you can have in a channel.
|
|
* #method maxPerChannel
|
|
* @return {Number} The maximum number of sounds you can have in a channel.
|
|
*/
|
|
SoundChannel.maxPerChannel = function () {
|
|
return p.maxDefault;
|
|
};
|
|
/**
|
|
* Get a channel instance by its src.
|
|
* #method get
|
|
* @param {String} src The src to use to look up the channel
|
|
* @static
|
|
*/
|
|
SoundChannel.get = function (src) {
|
|
return SoundChannel.channels[src];
|
|
};
|
|
|
|
var p = SoundChannel.prototype;
|
|
p.constructor = SoundChannel;
|
|
|
|
/**
|
|
* <strong>REMOVED</strong>. Removed in favor of using `MySuperClass_constructor`.
|
|
* See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}}
|
|
* for details.
|
|
*
|
|
* There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance.
|
|
*
|
|
* @method initialize
|
|
* @protected
|
|
* @deprecated
|
|
*/
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is.
|
|
|
|
/**
|
|
* The source of the channel.
|
|
* #property src
|
|
* @type {String}
|
|
*/
|
|
p.src = null;
|
|
|
|
/**
|
|
* The maximum number of instances in this channel. -1 indicates no limit
|
|
* #property max
|
|
* @type {Number}
|
|
*/
|
|
p.max = null;
|
|
|
|
/**
|
|
* The default value to set for max, if it isn't passed in. Also used if -1 is passed.
|
|
* #property maxDefault
|
|
* @type {Number}
|
|
* @default 100
|
|
* @since 0.4.0
|
|
*/
|
|
p.maxDefault = 100;
|
|
|
|
/**
|
|
* The current number of active instances.
|
|
* #property length
|
|
* @type {Number}
|
|
*/
|
|
p.length = 0;
|
|
|
|
/**
|
|
* Initialize the channel.
|
|
* #method init
|
|
* @param {String} src The source of the channel
|
|
* @param {Number} max The maximum number of instances in the channel
|
|
* @protected
|
|
*/
|
|
p.init = function (src, max) {
|
|
this.src = src;
|
|
this.max = max || this.maxDefault;
|
|
if (this.max == -1) {
|
|
this.max = this.maxDefault;
|
|
}
|
|
this._instances = [];
|
|
};
|
|
|
|
/**
|
|
* Get an instance by index.
|
|
* #method get
|
|
* @param {Number} index The index to return.
|
|
* @return {AbstractSoundInstance} The AbstractSoundInstance at a specific instance.
|
|
*/
|
|
p._get = function (index) {
|
|
return this._instances[index];
|
|
};
|
|
|
|
/**
|
|
* Add a new instance to the channel.
|
|
* #method add
|
|
* @param {AbstractSoundInstance} instance The instance to add.
|
|
* @return {Boolean} The success of the method call. If the channel is full, it will return false.
|
|
*/
|
|
p._add = function (instance, interrupt) {
|
|
if (!this._getSlot(interrupt, instance)) {
|
|
return false;
|
|
}
|
|
this._instances.push(instance);
|
|
this.length++;
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Remove an instance from the channel, either when it has finished playing, or it has been interrupted.
|
|
* #method remove
|
|
* @param {AbstractSoundInstance} instance The instance to remove
|
|
* @return {Boolean} The success of the remove call. If the instance is not found in this channel, it will
|
|
* return false.
|
|
*/
|
|
p._remove = function (instance) {
|
|
var index = createjs.indexOf(this._instances, instance);
|
|
if (index == -1) {
|
|
return false;
|
|
}
|
|
this._instances.splice(index, 1);
|
|
this.length--;
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Stop playback and remove all instances from the channel. Usually in response to a delete call.
|
|
* #method removeAll
|
|
*/
|
|
p._removeAll = function () {
|
|
// Note that stop() removes the item from the list
|
|
for (var i = this.length - 1; i >= 0; i--) {
|
|
this._instances[i].stop();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get an available slot depending on interrupt value and if slots are available.
|
|
* #method getSlot
|
|
* @param {String} interrupt The interrupt value to use.
|
|
* @param {AbstractSoundInstance} instance The sound instance that will go in the channel if successful.
|
|
* @return {Boolean} Determines if there is an available slot. Depending on the interrupt mode, if there are no slots,
|
|
* an existing AbstractSoundInstance may be interrupted. If there are no slots, this method returns false.
|
|
*/
|
|
p._getSlot = function (interrupt, instance) {
|
|
var target, replacement;
|
|
|
|
if (interrupt != Sound.INTERRUPT_NONE) {
|
|
// First replacement candidate
|
|
replacement = this._get(0);
|
|
if (replacement == null) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (var i = 0, l = this.max; i < l; i++) {
|
|
target = this._get(i);
|
|
|
|
// Available Space
|
|
if (target == null) {
|
|
return true;
|
|
}
|
|
|
|
// Audio is complete or not playing
|
|
if (
|
|
target.playState == Sound.PLAY_FINISHED ||
|
|
target.playState == Sound.PLAY_INTERRUPTED ||
|
|
target.playState == Sound.PLAY_FAILED
|
|
) {
|
|
replacement = target;
|
|
break;
|
|
}
|
|
|
|
if (interrupt == Sound.INTERRUPT_NONE) {
|
|
continue;
|
|
}
|
|
|
|
// Audio is a better candidate than the current target, according to playhead
|
|
if (
|
|
(interrupt == Sound.INTERRUPT_EARLY &&
|
|
target.getPosition() < replacement.getPosition()) ||
|
|
(interrupt == Sound.INTERRUPT_LATE &&
|
|
target.getPosition() > replacement.getPosition())
|
|
) {
|
|
replacement = target;
|
|
}
|
|
}
|
|
|
|
if (replacement != null) {
|
|
replacement._interrupt();
|
|
this._remove(replacement);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
p.toString = function () {
|
|
return "[Sound SoundChannel]";
|
|
};
|
|
// do not add SoundChannel to namespace
|
|
})();
|
|
|
|
//##############################################################################
|
|
// AbstractSoundInstance.js
|
|
//##############################################################################
|
|
|
|
/**
|
|
* A AbstractSoundInstance is created when any calls to the Sound API method {{#crossLink "Sound/play"}}{{/crossLink}} or
|
|
* {{#crossLink "Sound/createInstance"}}{{/crossLink}} are made. The AbstractSoundInstance is returned by the active plugin
|
|
* for control by the user.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var myInstance = createjs.Sound.play("myAssetPath/mySrcFile.mp3");
|
|
*
|
|
* A number of additional parameters provide a quick way to determine how a sound is played. Please see the Sound
|
|
* API method {{#crossLink "Sound/play"}}{{/crossLink}} for a list of arguments.
|
|
*
|
|
* Once a AbstractSoundInstance is created, a reference can be stored that can be used to control the audio directly through
|
|
* the AbstractSoundInstance. If the reference is not stored, the AbstractSoundInstance will play out its audio (and any loops), and
|
|
* is then de-referenced from the {{#crossLink "Sound"}}{{/crossLink}} class so that it can be cleaned up. If audio
|
|
* playback has completed, a simple call to the {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}} instance method
|
|
* will rebuild the references the Sound class need to control it.
|
|
*
|
|
* var myInstance = createjs.Sound.play("myAssetPath/mySrcFile.mp3", {loop:2});
|
|
* myInstance.on("loop", handleLoop);
|
|
* function handleLoop(event) {
|
|
* myInstance.volume = myInstance.volume * 0.5;
|
|
* }
|
|
*
|
|
* Events are dispatched from the instance to notify when the sound has completed, looped, or when playback fails
|
|
*
|
|
* var myInstance = createjs.Sound.play("myAssetPath/mySrcFile.mp3");
|
|
* myInstance.on("complete", handleComplete);
|
|
* myInstance.on("loop", handleLoop);
|
|
* myInstance.on("failed", handleFailed);
|
|
*
|
|
*
|
|
* @class AbstractSoundInstance
|
|
* @param {String} src The path to and file name of the sound.
|
|
* @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds.
|
|
* @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds.
|
|
* @param {Object} playbackResource Any resource needed by plugin to support audio playback.
|
|
* @extends EventDispatcher
|
|
* @constructor
|
|
*/
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// Constructor:
|
|
var AbstractSoundInstance = function (
|
|
src,
|
|
startTime,
|
|
duration,
|
|
playbackResource
|
|
) {
|
|
this.EventDispatcher_constructor();
|
|
|
|
// public properties:
|
|
/**
|
|
* The source of the sound.
|
|
* @property src
|
|
* @type {String}
|
|
* @default null
|
|
*/
|
|
this.src = src;
|
|
|
|
/**
|
|
* The unique ID of the instance. This is set by {{#crossLink "Sound"}}{{/crossLink}}.
|
|
* @property uniqueId
|
|
* @type {String} | Number
|
|
* @default -1
|
|
*/
|
|
this.uniqueId = -1;
|
|
|
|
/**
|
|
* The play state of the sound. Play states are defined as constants on {{#crossLink "Sound"}}{{/crossLink}}.
|
|
* @property playState
|
|
* @type {String}
|
|
* @default null
|
|
*/
|
|
this.playState = null;
|
|
|
|
/**
|
|
* A Timeout created by {{#crossLink "Sound"}}{{/crossLink}} when this AbstractSoundInstance is played with a delay.
|
|
* This allows AbstractSoundInstance to remove the delay if stop, pause, or cleanup are called before playback begins.
|
|
* @property delayTimeoutId
|
|
* @type {timeoutVariable}
|
|
* @default null
|
|
* @protected
|
|
* @since 0.4.0
|
|
*/
|
|
this.delayTimeoutId = null;
|
|
// TODO consider moving delay into AbstractSoundInstance so it can be handled by plugins
|
|
|
|
// private properties
|
|
// Getter / Setter Properties
|
|
// OJR TODO find original reason that we didn't use defined functions. I think it was performance related
|
|
/**
|
|
* The volume of the sound, between 0 and 1.
|
|
*
|
|
* The actual output volume of a sound can be calculated using:
|
|
* <code>myInstance.volume * createjs.Sound.getVolume();</code>
|
|
*
|
|
* @property volume
|
|
* @type {Number}
|
|
* @default 1
|
|
*/
|
|
this._volume = 1;
|
|
Object.defineProperty(this, "volume", {
|
|
get: this.getVolume,
|
|
set: this.setVolume,
|
|
});
|
|
|
|
/**
|
|
* The pan of the sound, between -1 (left) and 1 (right). Note that pan is not supported by HTML Audio.
|
|
*
|
|
* <br />Note in WebAudioPlugin this only gives us the "x" value of what is actually 3D audio.
|
|
*
|
|
* @property pan
|
|
* @type {Number}
|
|
* @default 0
|
|
*/
|
|
this._pan = 0;
|
|
Object.defineProperty(this, "pan", {
|
|
get: this.getPan,
|
|
set: this.setPan,
|
|
});
|
|
|
|
/**
|
|
* Audio sprite property used to determine the starting offset.
|
|
* @property startTime
|
|
* @type {Number}
|
|
* @default 0
|
|
* @since 0.6.1
|
|
*/
|
|
this._startTime = Math.max(0, startTime || 0);
|
|
Object.defineProperty(this, "startTime", {
|
|
get: this.getStartTime,
|
|
set: this.setStartTime,
|
|
});
|
|
|
|
/**
|
|
* Sets or gets the length of the audio clip, value is in milliseconds.
|
|
*
|
|
* @property duration
|
|
* @type {Number}
|
|
* @default 0
|
|
* @since 0.6.0
|
|
*/
|
|
this._duration = Math.max(0, duration || 0);
|
|
Object.defineProperty(this, "duration", {
|
|
get: this.getDuration,
|
|
set: this.setDuration,
|
|
});
|
|
|
|
/**
|
|
* Object that holds plugin specific resource need for audio playback.
|
|
* This is set internally by the plugin. For example, WebAudioPlugin will set an array buffer,
|
|
* HTMLAudioPlugin will set a tag, FlashAudioPlugin will set a flash reference.
|
|
*
|
|
* @property playbackResource
|
|
* @type {Object}
|
|
* @default null
|
|
*/
|
|
this._playbackResource = null;
|
|
Object.defineProperty(this, "playbackResource", {
|
|
get: this.getPlaybackResource,
|
|
set: this.setPlaybackResource,
|
|
});
|
|
if (playbackResource !== false && playbackResource !== true) {
|
|
this.setPlaybackResource(playbackResource);
|
|
}
|
|
|
|
/**
|
|
* The position of the playhead in milliseconds. This can be set while a sound is playing, paused, or stopped.
|
|
*
|
|
* @property position
|
|
* @type {Number}
|
|
* @default 0
|
|
* @since 0.6.0
|
|
*/
|
|
this._position = 0;
|
|
Object.defineProperty(this, "position", {
|
|
get: this.getPosition,
|
|
set: this.setPosition,
|
|
});
|
|
|
|
/**
|
|
* The number of play loops remaining. Negative values will loop infinitely.
|
|
*
|
|
* @property loop
|
|
* @type {Number}
|
|
* @default 0
|
|
* @public
|
|
* @since 0.6.0
|
|
*/
|
|
this._loop = 0;
|
|
Object.defineProperty(this, "loop", {
|
|
get: this.getLoop,
|
|
set: this.setLoop,
|
|
});
|
|
|
|
/**
|
|
* Mutes or unmutes the current audio instance.
|
|
*
|
|
* @property muted
|
|
* @type {Boolean}
|
|
* @default false
|
|
* @since 0.6.0
|
|
*/
|
|
this._muted = false;
|
|
Object.defineProperty(this, "muted", {
|
|
get: this.getMuted,
|
|
set: this.setMuted,
|
|
});
|
|
|
|
/**
|
|
* Pauses or resumes the current audio instance.
|
|
*
|
|
* @property paused
|
|
* @type {Boolean}
|
|
*/
|
|
this._paused = false;
|
|
Object.defineProperty(this, "paused", {
|
|
get: this.getPaused,
|
|
set: this.setPaused,
|
|
});
|
|
|
|
// Events
|
|
/**
|
|
* The event that is fired when playback has started successfully.
|
|
* @event succeeded
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @since 0.4.0
|
|
*/
|
|
|
|
/**
|
|
* The event that is fired when playback is interrupted. This happens when another sound with the same
|
|
* src property is played using an interrupt value that causes this instance to stop playing.
|
|
* @event interrupted
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @since 0.4.0
|
|
*/
|
|
|
|
/**
|
|
* The event that is fired when playback has failed. This happens when there are too many channels with the same
|
|
* src property already playing (and the interrupt value doesn't cause an interrupt of another instance), or
|
|
* the sound could not be played, perhaps due to a 404 error.
|
|
* @event failed
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @since 0.4.0
|
|
*/
|
|
|
|
/**
|
|
* The event that is fired when a sound has completed playing but has loops remaining.
|
|
* @event loop
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @since 0.4.0
|
|
*/
|
|
|
|
/**
|
|
* The event that is fired when playback completes. This means that the sound has finished playing in its
|
|
* entirety, including its loop iterations.
|
|
* @event complete
|
|
* @param {Object} target The object that dispatched the event.
|
|
* @param {String} type The event type.
|
|
* @since 0.4.0
|
|
*/
|
|
};
|
|
|
|
var p = createjs.extend(AbstractSoundInstance, createjs.EventDispatcher);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// Public Methods:
|
|
/**
|
|
* Play an instance. This method is intended to be called on SoundInstances that already exist (created
|
|
* with the Sound API {{#crossLink "Sound/createInstance"}}{{/crossLink}} or {{#crossLink "Sound/play"}}{{/crossLink}}).
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* var myInstance = createjs.Sound.createInstance(mySrc);
|
|
* myInstance.play({interrupt:createjs.Sound.INTERRUPT_ANY, loop:2, pan:0.5});
|
|
*
|
|
* Note that if this sound is already playing, this call will still set the passed in parameters.
|
|
|
|
* <b>Parameters Deprecated</b><br />
|
|
* The parameters for this method are deprecated in favor of a single parameter that is an Object or {{#crossLink "PlayPropsConfig"}}{{/crossLink}}.
|
|
*
|
|
* @method play
|
|
* @param {String | Object} [interrupt="none"|options] <b>This parameter will be renamed playProps in the next release.</b><br />
|
|
* This parameter can be an instance of {{#crossLink "PlayPropsConfig"}}{{/crossLink}} or an Object that contains any or all optional properties by name,
|
|
* including: interrupt, delay, offset, loop, volume, pan, startTime, and duration (see the above code sample).
|
|
* <br /><strong>OR</strong><br />
|
|
* <b>Deprecated</b> How to interrupt any currently playing instances of audio with the same source,
|
|
* if the maximum number of instances of the sound are already playing. Values are defined as <code>INTERRUPT_TYPE</code>
|
|
* constants on the Sound class, with the default defined by {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}.
|
|
* @param {Number} [delay=0] <b>Deprecated</b> The amount of time to delay the start of audio playback, in milliseconds.
|
|
* @param {Number} [offset=0] <b>Deprecated</b> The offset from the start of the audio to begin playback, in milliseconds.
|
|
* @param {Number} [loop=0] <b>Deprecated</b> How many times the audio loops when it reaches the end of playback. The default is 0 (no
|
|
* loops), and -1 can be used for infinite playback.
|
|
* @param {Number} [volume=1] <b>Deprecated</b> The volume of the sound, between 0 and 1. Note that the master volume is applied
|
|
* against the individual volume.
|
|
* @param {Number} [pan=0] <b>Deprecated</b> The left-right pan of the sound (if supported), between -1 (left) and 1 (right).
|
|
* Note that pan is not supported for HTML Audio.
|
|
* @return {AbstractSoundInstance} A reference to itself, intended for chaining calls.
|
|
*/
|
|
p.play = function (interrupt, delay, offset, loop, volume, pan) {
|
|
var playProps;
|
|
if (
|
|
interrupt instanceof Object ||
|
|
interrupt instanceof createjs.PlayPropsConfig
|
|
) {
|
|
playProps = createjs.PlayPropsConfig.create(interrupt);
|
|
} else {
|
|
playProps = createjs.PlayPropsConfig.create({
|
|
interrupt: interrupt,
|
|
delay: delay,
|
|
offset: offset,
|
|
loop: loop,
|
|
volume: volume,
|
|
pan: pan,
|
|
});
|
|
}
|
|
|
|
if (this.playState == createjs.Sound.PLAY_SUCCEEDED) {
|
|
this.applyPlayProps(playProps);
|
|
if (this._paused) {
|
|
this.setPaused(false);
|
|
}
|
|
return;
|
|
}
|
|
this._cleanUp();
|
|
createjs.Sound._playInstance(this, playProps); // make this an event dispatch??
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Stop playback of the instance. Stopped sounds will reset their position to 0, and calls to {{#crossLink "AbstractSoundInstance/resume"}}{{/crossLink}}
|
|
* will fail. To start playback again, call {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}}.
|
|
*
|
|
* If you don't want to lose your position use yourSoundInstance.paused = true instead. {{#crossLink "AbstractSoundInstance/paused"}}{{/crossLink}}.
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* myInstance.stop();
|
|
*
|
|
* @method stop
|
|
* @return {AbstractSoundInstance} A reference to itself, intended for chaining calls.
|
|
*/
|
|
p.stop = function () {
|
|
this._position = 0;
|
|
this._paused = false;
|
|
this._handleStop();
|
|
this._cleanUp();
|
|
this.playState = createjs.Sound.PLAY_FINISHED;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Remove all external references and resources from AbstractSoundInstance. Note this is irreversible and AbstractSoundInstance will no longer work
|
|
* @method destroy
|
|
* @since 0.6.0
|
|
*/
|
|
p.destroy = function () {
|
|
this._cleanUp();
|
|
this.src = null;
|
|
this.playbackResource = null;
|
|
|
|
this.removeAllEventListeners();
|
|
};
|
|
|
|
/**
|
|
* Takes an PlayPropsConfig or Object with the same properties and sets them on this instance.
|
|
* @method applyPlayProps
|
|
* @param {PlayPropsConfig | Object} playProps A PlayPropsConfig or object containing the same properties.
|
|
* @since 0.6.1
|
|
* @return {AbstractSoundInstance} A reference to itself, intended for chaining calls.
|
|
*/
|
|
p.applyPlayProps = function (playProps) {
|
|
if (playProps.offset != null) {
|
|
this.setPosition(playProps.offset);
|
|
}
|
|
if (playProps.loop != null) {
|
|
this.setLoop(playProps.loop);
|
|
}
|
|
if (playProps.volume != null) {
|
|
this.setVolume(playProps.volume);
|
|
}
|
|
if (playProps.pan != null) {
|
|
this.setPan(playProps.pan);
|
|
}
|
|
if (playProps.startTime != null) {
|
|
this.setStartTime(playProps.startTime);
|
|
this.setDuration(playProps.duration);
|
|
}
|
|
return this;
|
|
};
|
|
|
|
p.toString = function () {
|
|
return "[AbstractSoundInstance]";
|
|
};
|
|
|
|
// get/set methods that allow support for IE8
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/paused:property"}}{{/crossLink}} directly as a property,
|
|
*
|
|
* @deprecated
|
|
* @method getPaused
|
|
* @returns {boolean} If the instance is currently paused
|
|
* @since 0.6.0
|
|
*/
|
|
p.getPaused = function () {
|
|
return this._paused;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/paused:property"}}{{/crossLink}} directly as a property
|
|
*
|
|
* @deprecated
|
|
* @method setPaused
|
|
* @param {boolean} value
|
|
* @since 0.6.0
|
|
* @return {AbstractSoundInstance} A reference to itself, intended for chaining calls.
|
|
*/
|
|
p.setPaused = function (value) {
|
|
if ((value !== true && value !== false) || this._paused == value) {
|
|
return;
|
|
}
|
|
if (value == true && this.playState != createjs.Sound.PLAY_SUCCEEDED) {
|
|
return;
|
|
}
|
|
this._paused = value;
|
|
if (value) {
|
|
this._pause();
|
|
} else {
|
|
this._resume();
|
|
}
|
|
clearTimeout(this.delayTimeoutId);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/volume:property"}}{{/crossLink}} directly as a property
|
|
*
|
|
* @deprecated
|
|
* @method setVolume
|
|
* @param {Number} value The volume to set, between 0 and 1.
|
|
* @return {AbstractSoundInstance} A reference to itself, intended for chaining calls.
|
|
*/
|
|
p.setVolume = function (value) {
|
|
if (value == this._volume) {
|
|
return this;
|
|
}
|
|
this._volume = Math.max(0, Math.min(1, value));
|
|
if (!this._muted) {
|
|
this._updateVolume();
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/volume:property"}}{{/crossLink}} directly as a property
|
|
*
|
|
* @deprecated
|
|
* @method getVolume
|
|
* @return {Number} The current volume of the sound instance.
|
|
*/
|
|
p.getVolume = function () {
|
|
return this._volume;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} directly as a property
|
|
*
|
|
* @deprecated
|
|
* @method setMuted
|
|
* @param {Boolean} value If the sound should be muted.
|
|
* @return {AbstractSoundInstance} A reference to itself, intended for chaining calls.
|
|
* @since 0.6.0
|
|
*/
|
|
p.setMuted = function (value) {
|
|
if (value !== true && value !== false) {
|
|
return;
|
|
}
|
|
this._muted = value;
|
|
this._updateVolume();
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} directly as a property
|
|
*
|
|
* @deprecated
|
|
* @method getMuted
|
|
* @return {Boolean} If the sound is muted.
|
|
* @since 0.6.0
|
|
*/
|
|
p.getMuted = function () {
|
|
return this._muted;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/pan:property"}}{{/crossLink}} directly as a property
|
|
*
|
|
* @deprecated
|
|
* @method setPan
|
|
* @param {Number} value The pan value, between -1 (left) and 1 (right).
|
|
* @return {AbstractSoundInstance} Returns reference to itself for chaining calls
|
|
*/
|
|
p.setPan = function (value) {
|
|
if (value == this._pan) {
|
|
return this;
|
|
}
|
|
this._pan = Math.max(-1, Math.min(1, value));
|
|
this._updatePan();
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/pan:property"}}{{/crossLink}} directly as a property
|
|
*
|
|
* @deprecated
|
|
* @method getPan
|
|
* @return {Number} The value of the pan, between -1 (left) and 1 (right).
|
|
*/
|
|
p.getPan = function () {
|
|
return this._pan;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/position:property"}}{{/crossLink}} directly as a property
|
|
*
|
|
* @deprecated
|
|
* @method getPosition
|
|
* @return {Number} The position of the playhead in the sound, in milliseconds.
|
|
*/
|
|
p.getPosition = function () {
|
|
if (!this._paused && this.playState == createjs.Sound.PLAY_SUCCEEDED) {
|
|
this._position = this._calculateCurrentPosition();
|
|
}
|
|
return this._position;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/position:property"}}{{/crossLink}} directly as a property
|
|
*
|
|
* @deprecated
|
|
* @method setPosition
|
|
* @param {Number} value The position to place the playhead, in milliseconds.
|
|
* @return {AbstractSoundInstance} Returns reference to itself for chaining calls
|
|
*/
|
|
p.setPosition = function (value) {
|
|
this._position = Math.max(0, value);
|
|
if (this.playState == createjs.Sound.PLAY_SUCCEEDED) {
|
|
this._updatePosition();
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/startTime:property"}}{{/crossLink}} directly as a property
|
|
*
|
|
* @deprecated
|
|
* @method getStartTime
|
|
* @return {Number} The startTime of the sound instance in milliseconds.
|
|
*/
|
|
p.getStartTime = function () {
|
|
return this._startTime;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/startTime:property"}}{{/crossLink}} directly as a property
|
|
*
|
|
* @deprecated
|
|
* @method setStartTime
|
|
* @param {number} value The new startTime time in milli seconds.
|
|
* @return {AbstractSoundInstance} Returns reference to itself for chaining calls
|
|
*/
|
|
p.setStartTime = function (value) {
|
|
if (value == this._startTime) {
|
|
return this;
|
|
}
|
|
this._startTime = Math.max(0, value || 0);
|
|
this._updateStartTime();
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/duration:property"}}{{/crossLink}} directly as a property
|
|
*
|
|
* @deprecated
|
|
* @method getDuration
|
|
* @return {Number} The duration of the sound instance in milliseconds.
|
|
*/
|
|
p.getDuration = function () {
|
|
return this._duration;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/duration:property"}}{{/crossLink}} directly as a property
|
|
*
|
|
* @deprecated
|
|
* @method setDuration
|
|
* @param {number} value The new duration time in milli seconds.
|
|
* @return {AbstractSoundInstance} Returns reference to itself for chaining calls
|
|
* @since 0.6.0
|
|
*/
|
|
p.setDuration = function (value) {
|
|
if (value == this._duration) {
|
|
return this;
|
|
}
|
|
this._duration = Math.max(0, value || 0);
|
|
this._updateDuration();
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/playbackResource:property"}}{{/crossLink}} directly as a property
|
|
*
|
|
* @deprecated
|
|
* @method setPlayback
|
|
* @param {Object} value The new playback resource.
|
|
* @return {AbstractSoundInstance} Returns reference to itself for chaining calls
|
|
* @since 0.6.0
|
|
**/
|
|
p.setPlaybackResource = function (value) {
|
|
this._playbackResource = value;
|
|
if (this._duration == 0) {
|
|
this._setDurationFromSource();
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/playbackResource:property"}}{{/crossLink}} directly as a property
|
|
*
|
|
* @deprecated
|
|
* @method setPlayback
|
|
* @param {Object} value The new playback resource.
|
|
* @return {Object} playback resource used for playing audio
|
|
* @since 0.6.0
|
|
**/
|
|
p.getPlaybackResource = function () {
|
|
return this._playbackResource;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/loop:property"}}{{/crossLink}} directly as a property
|
|
*
|
|
* @deprecated
|
|
* @method getLoop
|
|
* @return {number}
|
|
* @since 0.6.0
|
|
**/
|
|
p.getLoop = function () {
|
|
return this._loop;
|
|
};
|
|
|
|
/**
|
|
* DEPRECATED, please use {{#crossLink "AbstractSoundInstance/loop:property"}}{{/crossLink}} directly as a property,
|
|
*
|
|
* @deprecated
|
|
* @method setLoop
|
|
* @param {number} value The number of times to loop after play.
|
|
* @since 0.6.0
|
|
*/
|
|
p.setLoop = function (value) {
|
|
if (this._playbackResource != null) {
|
|
// remove looping
|
|
if (this._loop != 0 && value == 0) {
|
|
this._removeLooping(value);
|
|
}
|
|
// add looping
|
|
else if (this._loop == 0 && value != 0) {
|
|
this._addLooping(value);
|
|
}
|
|
}
|
|
this._loop = value;
|
|
};
|
|
|
|
// Private Methods:
|
|
/**
|
|
* A helper method that dispatches all events for AbstractSoundInstance.
|
|
* @method _sendEvent
|
|
* @param {String} type The event type
|
|
* @protected
|
|
*/
|
|
p._sendEvent = function (type) {
|
|
var event = new createjs.Event(type);
|
|
this.dispatchEvent(event);
|
|
};
|
|
|
|
/**
|
|
* Clean up the instance. Remove references and clean up any additional properties such as timers.
|
|
* @method _cleanUp
|
|
* @protected
|
|
*/
|
|
p._cleanUp = function () {
|
|
clearTimeout(this.delayTimeoutId); // clear timeout that plays delayed sound
|
|
this._handleCleanUp();
|
|
this._paused = false;
|
|
|
|
createjs.Sound._playFinished(this); // TODO change to an event
|
|
};
|
|
|
|
/**
|
|
* The sound has been interrupted.
|
|
* @method _interrupt
|
|
* @protected
|
|
*/
|
|
p._interrupt = function () {
|
|
this._cleanUp();
|
|
this.playState = createjs.Sound.PLAY_INTERRUPTED;
|
|
this._sendEvent("interrupted");
|
|
};
|
|
|
|
/**
|
|
* Called by the Sound class when the audio is ready to play (delay has completed). Starts sound playing if the
|
|
* src is loaded, otherwise playback will fail.
|
|
* @method _beginPlaying
|
|
* @param {PlayPropsConfig} playProps A PlayPropsConfig object.
|
|
* @return {Boolean} If playback succeeded.
|
|
* @protected
|
|
*/
|
|
// OJR FlashAudioSoundInstance overwrites
|
|
p._beginPlaying = function (playProps) {
|
|
this.setPosition(playProps.offset);
|
|
this.setLoop(playProps.loop);
|
|
this.setVolume(playProps.volume);
|
|
this.setPan(playProps.pan);
|
|
if (playProps.startTime != null) {
|
|
this.setStartTime(playProps.startTime);
|
|
this.setDuration(playProps.duration);
|
|
}
|
|
|
|
if (this._playbackResource != null && this._position < this._duration) {
|
|
this._paused = false;
|
|
this._handleSoundReady();
|
|
this.playState = createjs.Sound.PLAY_SUCCEEDED;
|
|
this._sendEvent("succeeded");
|
|
return true;
|
|
} else {
|
|
this._playFailed();
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Play has failed, which can happen for a variety of reasons.
|
|
* Cleans up instance and dispatches failed event
|
|
* @method _playFailed
|
|
* @private
|
|
*/
|
|
p._playFailed = function () {
|
|
this._cleanUp();
|
|
this.playState = createjs.Sound.PLAY_FAILED;
|
|
this._sendEvent("failed");
|
|
};
|
|
|
|
/**
|
|
* Audio has finished playing. Manually loop it if required.
|
|
* @method _handleSoundComplete
|
|
* @param event
|
|
* @protected
|
|
*/
|
|
p._handleSoundComplete = function (event) {
|
|
this._position = 0; // have to set this as it can be set by pause during playback
|
|
|
|
if (this._loop != 0) {
|
|
this._loop--; // NOTE this introduces a theoretical limit on loops = float max size x 2 - 1
|
|
this._handleLoop();
|
|
this._sendEvent("loop");
|
|
return;
|
|
}
|
|
|
|
this._cleanUp();
|
|
this.playState = createjs.Sound.PLAY_FINISHED;
|
|
this._sendEvent("complete");
|
|
};
|
|
|
|
// Plugin specific code
|
|
/**
|
|
* Handles starting playback when the sound is ready for playing.
|
|
* @method _handleSoundReady
|
|
* @protected
|
|
*/
|
|
p._handleSoundReady = function () {
|
|
// plugin specific code
|
|
};
|
|
|
|
/**
|
|
* Internal function used to update the volume based on the instance volume, master volume, instance mute value,
|
|
* and master mute value.
|
|
* @method _updateVolume
|
|
* @protected
|
|
*/
|
|
p._updateVolume = function () {
|
|
// plugin specific code
|
|
};
|
|
|
|
/**
|
|
* Internal function used to update the pan
|
|
* @method _updatePan
|
|
* @protected
|
|
* @since 0.6.0
|
|
*/
|
|
p._updatePan = function () {
|
|
// plugin specific code
|
|
};
|
|
|
|
/**
|
|
* Internal function used to update the startTime of the audio.
|
|
* @method _updateStartTime
|
|
* @protected
|
|
* @since 0.6.1
|
|
*/
|
|
p._updateStartTime = function () {
|
|
// plugin specific code
|
|
};
|
|
|
|
/**
|
|
* Internal function used to update the duration of the audio.
|
|
* @method _updateDuration
|
|
* @protected
|
|
* @since 0.6.0
|
|
*/
|
|
p._updateDuration = function () {
|
|
// plugin specific code
|
|
};
|
|
|
|
/**
|
|
* Internal function used to get the duration of the audio from the source we'll be playing.
|
|
* @method _updateDuration
|
|
* @protected
|
|
* @since 0.6.0
|
|
*/
|
|
p._setDurationFromSource = function () {
|
|
// plugin specific code
|
|
};
|
|
|
|
/**
|
|
* Internal function that calculates the current position of the playhead and sets this._position to that value
|
|
* @method _calculateCurrentPosition
|
|
* @protected
|
|
* @since 0.6.0
|
|
*/
|
|
p._calculateCurrentPosition = function () {
|
|
// plugin specific code that sets this.position
|
|
};
|
|
|
|
/**
|
|
* Internal function used to update the position of the playhead.
|
|
* @method _updatePosition
|
|
* @protected
|
|
* @since 0.6.0
|
|
*/
|
|
p._updatePosition = function () {
|
|
// plugin specific code
|
|
};
|
|
|
|
/**
|
|
* Internal function called when looping is removed during playback.
|
|
* @method _removeLooping
|
|
* @param {number} value The number of times to loop after play.
|
|
* @protected
|
|
* @since 0.6.0
|
|
*/
|
|
p._removeLooping = function (value) {
|
|
// plugin specific code
|
|
};
|
|
|
|
/**
|
|
* Internal function called when looping is added during playback.
|
|
* @method _addLooping
|
|
* @param {number} value The number of times to loop after play.
|
|
* @protected
|
|
* @since 0.6.0
|
|
*/
|
|
p._addLooping = function (value) {
|
|
// plugin specific code
|
|
};
|
|
|
|
/**
|
|
* Internal function called when pausing playback
|
|
* @method _pause
|
|
* @protected
|
|
* @since 0.6.0
|
|
*/
|
|
p._pause = function () {
|
|
// plugin specific code
|
|
};
|
|
|
|
/**
|
|
* Internal function called when resuming playback
|
|
* @method _resume
|
|
* @protected
|
|
* @since 0.6.0
|
|
*/
|
|
p._resume = function () {
|
|
// plugin specific code
|
|
};
|
|
|
|
/**
|
|
* Internal function called when stopping playback
|
|
* @method _handleStop
|
|
* @protected
|
|
* @since 0.6.0
|
|
*/
|
|
p._handleStop = function () {
|
|
// plugin specific code
|
|
};
|
|
|
|
/**
|
|
* Internal function called when AbstractSoundInstance is being cleaned up
|
|
* @method _handleCleanUp
|
|
* @protected
|
|
* @since 0.6.0
|
|
*/
|
|
p._handleCleanUp = function () {
|
|
// plugin specific code
|
|
};
|
|
|
|
/**
|
|
* Internal function called when AbstractSoundInstance has played to end and is looping
|
|
* @method _handleLoop
|
|
* @protected
|
|
* @since 0.6.0
|
|
*/
|
|
p._handleLoop = function () {
|
|
// plugin specific code
|
|
};
|
|
|
|
createjs.AbstractSoundInstance = createjs.promote(
|
|
AbstractSoundInstance,
|
|
"EventDispatcher"
|
|
);
|
|
createjs.DefaultSoundInstance = createjs.AbstractSoundInstance; // used when no plugin is supported
|
|
})();
|
|
|
|
//##############################################################################
|
|
// AbstractPlugin.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor:
|
|
/**
|
|
* A default plugin class used as a base for all other plugins.
|
|
* @class AbstractPlugin
|
|
* @constructor
|
|
* @since 0.6.0
|
|
*/
|
|
|
|
var AbstractPlugin = function () {
|
|
// private properties:
|
|
/**
|
|
* The capabilities of the plugin.
|
|
* method and is used internally.
|
|
* @property _capabilities
|
|
* @type {Object}
|
|
* @default null
|
|
* @protected
|
|
* @static
|
|
*/
|
|
this._capabilities = null;
|
|
|
|
/**
|
|
* Object hash indexed by the source URI of all created loaders, used to properly destroy them if sources are removed.
|
|
* @type {Object}
|
|
* @protected
|
|
*/
|
|
this._loaders = {};
|
|
|
|
/**
|
|
* Object hash indexed by the source URI of each file to indicate if an audio source has begun loading,
|
|
* is currently loading, or has completed loading. Can be used to store non boolean data after loading
|
|
* is complete (for example arrayBuffers for web audio).
|
|
* @property _audioSources
|
|
* @type {Object}
|
|
* @protected
|
|
*/
|
|
this._audioSources = {};
|
|
|
|
/**
|
|
* Object hash indexed by the source URI of all created SoundInstances, updates the playbackResource if it loads after they are created,
|
|
* and properly destroy them if sources are removed
|
|
* @type {Object}
|
|
* @protected
|
|
*/
|
|
this._soundInstances = {};
|
|
|
|
/**
|
|
* The internal master volume value of the plugin.
|
|
* @property _volume
|
|
* @type {Number}
|
|
* @default 1
|
|
* @protected
|
|
*/
|
|
this._volume = 1;
|
|
|
|
/**
|
|
* A reference to a loader class used by a plugin that must be set.
|
|
* @type {Object}
|
|
* @protected
|
|
*/
|
|
this._loaderClass;
|
|
|
|
/**
|
|
* A reference to an AbstractSoundInstance class used by a plugin that must be set.
|
|
* @type {Object}
|
|
* @protected;
|
|
*/
|
|
this._soundInstanceClass;
|
|
};
|
|
var p = AbstractPlugin.prototype;
|
|
|
|
/**
|
|
* <strong>REMOVED</strong>. Removed in favor of using `MySuperClass_constructor`.
|
|
* See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}}
|
|
* for details.
|
|
*
|
|
* There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance.
|
|
*
|
|
* @method initialize
|
|
* @protected
|
|
* @deprecated
|
|
*/
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is.
|
|
|
|
// Static Properties:
|
|
// NOTE THESE PROPERTIES NEED TO BE ADDED TO EACH PLUGIN
|
|
/**
|
|
* The capabilities of the plugin. This is generated via the _generateCapabilities method and is used internally.
|
|
* @property _capabilities
|
|
* @type {Object}
|
|
* @default null
|
|
* @protected
|
|
* @static
|
|
*/
|
|
AbstractPlugin._capabilities = null;
|
|
|
|
/**
|
|
* Determine if the plugin can be used in the current browser/OS.
|
|
* @method isSupported
|
|
* @return {Boolean} If the plugin can be initialized.
|
|
* @static
|
|
*/
|
|
AbstractPlugin.isSupported = function () {
|
|
return true;
|
|
};
|
|
|
|
// public methods:
|
|
/**
|
|
* Pre-register a sound for preloading and setup. This is called by {{#crossLink "Sound"}}{{/crossLink}}.
|
|
* Note all plugins provide a <code>Loader</code> instance, which <a href="http://preloadjs.com" target="_blank">PreloadJS</a>
|
|
* can use to assist with preloading.
|
|
* @method register
|
|
* @param {String} loadItem An Object containing the source of the audio
|
|
* Note that not every plugin will manage this value.
|
|
* @return {Object} A result object, containing a "tag" for preloading purposes.
|
|
*/
|
|
p.register = function (loadItem) {
|
|
var loader = this._loaders[loadItem.src];
|
|
if (loader && !loader.canceled) {
|
|
return this._loaders[loadItem.src];
|
|
} // already loading/loaded this, so don't load twice
|
|
// OJR potential issue that we won't be firing loaded event, might need to trigger if this is already loaded?
|
|
this._audioSources[loadItem.src] = true;
|
|
this._soundInstances[loadItem.src] = [];
|
|
loader = new this._loaderClass(loadItem);
|
|
loader.on("complete", this._handlePreloadComplete, this);
|
|
this._loaders[loadItem.src] = loader;
|
|
return loader;
|
|
};
|
|
|
|
// note sound calls register before calling preload
|
|
/**
|
|
* Internally preload a sound.
|
|
* @method preload
|
|
* @param {Loader} loader The sound URI to load.
|
|
*/
|
|
p.preload = function (loader) {
|
|
loader.on("error", this._handlePreloadError, this);
|
|
loader.load();
|
|
};
|
|
|
|
/**
|
|
* Checks if preloading has started for a specific source. If the source is found, we can assume it is loading,
|
|
* or has already finished loading.
|
|
* @method isPreloadStarted
|
|
* @param {String} src The sound URI to check.
|
|
* @return {Boolean}
|
|
*/
|
|
p.isPreloadStarted = function (src) {
|
|
return this._audioSources[src] != null;
|
|
};
|
|
|
|
/**
|
|
* Checks if preloading has finished for a specific source.
|
|
* @method isPreloadComplete
|
|
* @param {String} src The sound URI to load.
|
|
* @return {Boolean}
|
|
*/
|
|
p.isPreloadComplete = function (src) {
|
|
return !(
|
|
this._audioSources[src] == null || this._audioSources[src] == true
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Remove a sound added using {{#crossLink "WebAudioPlugin/register"}}{{/crossLink}}. Note this does not cancel a preload.
|
|
* @method removeSound
|
|
* @param {String} src The sound URI to unload.
|
|
*/
|
|
p.removeSound = function (src) {
|
|
if (!this._soundInstances[src]) {
|
|
return;
|
|
}
|
|
for (var i = this._soundInstances[src].length; i--; ) {
|
|
var item = this._soundInstances[src][i];
|
|
item.destroy();
|
|
}
|
|
delete this._soundInstances[src];
|
|
delete this._audioSources[src];
|
|
if (this._loaders[src]) {
|
|
this._loaders[src].destroy();
|
|
}
|
|
delete this._loaders[src];
|
|
};
|
|
|
|
/**
|
|
* Remove all sounds added using {{#crossLink "WebAudioPlugin/register"}}{{/crossLink}}. Note this does not cancel a preload.
|
|
* @method removeAllSounds
|
|
* @param {String} src The sound URI to unload.
|
|
*/
|
|
p.removeAllSounds = function () {
|
|
for (var key in this._audioSources) {
|
|
this.removeSound(key);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create a sound instance. If the sound has not been preloaded, it is internally preloaded here.
|
|
* @method create
|
|
* @param {String} src The sound source to use.
|
|
* @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds.
|
|
* @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds.
|
|
* @return {AbstractSoundInstance} A sound instance for playback and control.
|
|
*/
|
|
p.create = function (src, startTime, duration) {
|
|
if (!this.isPreloadStarted(src)) {
|
|
this.preload(this.register(src));
|
|
}
|
|
var si = new this._soundInstanceClass(
|
|
src,
|
|
startTime,
|
|
duration,
|
|
this._audioSources[src]
|
|
);
|
|
this._soundInstances[src].push(si);
|
|
return si;
|
|
};
|
|
|
|
// if a plugin does not support volume and mute, it should set these to null
|
|
/**
|
|
* Set the master volume of the plugin, which affects all SoundInstances.
|
|
* @method setVolume
|
|
* @param {Number} value The volume to set, between 0 and 1.
|
|
* @return {Boolean} If the plugin processes the setVolume call (true). The Sound class will affect all the
|
|
* instances manually otherwise.
|
|
*/
|
|
p.setVolume = function (value) {
|
|
this._volume = value;
|
|
this._updateVolume();
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Get the master volume of the plugin, which affects all SoundInstances.
|
|
* @method getVolume
|
|
* @return {Number} The volume level, between 0 and 1.
|
|
*/
|
|
p.getVolume = function () {
|
|
return this._volume;
|
|
};
|
|
|
|
/**
|
|
* Mute all sounds via the plugin.
|
|
* @method setMute
|
|
* @param {Boolean} value If all sound should be muted or not. Note that plugin-level muting just looks up
|
|
* the mute value of Sound {{#crossLink "Sound/getMute"}}{{/crossLink}}, so this property is not used here.
|
|
* @return {Boolean} If the mute call succeeds.
|
|
*/
|
|
p.setMute = function (value) {
|
|
this._updateVolume();
|
|
return true;
|
|
};
|
|
|
|
// plugins should overwrite this method
|
|
p.toString = function () {
|
|
return "[AbstractPlugin]";
|
|
};
|
|
|
|
// private methods:
|
|
/**
|
|
* Handles internal preload completion.
|
|
* @method _handlePreloadComplete
|
|
* @protected
|
|
*/
|
|
p._handlePreloadComplete = function (event) {
|
|
var src = event.target.getItem().src;
|
|
this._audioSources[src] = event.result;
|
|
for (var i = 0, l = this._soundInstances[src].length; i < l; i++) {
|
|
var item = this._soundInstances[src][i];
|
|
item.setPlaybackResource(this._audioSources[src]);
|
|
// ToDo consider adding play call here if playstate == playfailed
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handles internal preload erros
|
|
* @method _handlePreloadError
|
|
* @param event
|
|
* @protected
|
|
*/
|
|
p._handlePreloadError = function (event) {
|
|
//delete(this._audioSources[src]);
|
|
};
|
|
|
|
/**
|
|
* Set the gain value for master audio. Should not be called externally.
|
|
* @method _updateVolume
|
|
* @protected
|
|
*/
|
|
p._updateVolume = function () {
|
|
// Plugin Specific code
|
|
};
|
|
|
|
createjs.AbstractPlugin = AbstractPlugin;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// WebAudioLoader.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* Loader provides a mechanism to preload Web Audio content via PreloadJS or internally. Instances are returned to
|
|
* the preloader, and the load method is called when the asset needs to be requested.
|
|
*
|
|
* @class WebAudioLoader
|
|
* @param {String} loadItem The item to be loaded
|
|
* @extends XHRRequest
|
|
* @protected
|
|
*/
|
|
function Loader(loadItem) {
|
|
this.AbstractLoader_constructor(
|
|
loadItem,
|
|
true,
|
|
createjs.AbstractLoader.SOUND
|
|
);
|
|
}
|
|
var p = createjs.extend(Loader, createjs.AbstractLoader);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
/**
|
|
* web audio context required for decoding audio
|
|
* @property context
|
|
* @type {AudioContext}
|
|
* @static
|
|
*/
|
|
Loader.context = null;
|
|
|
|
// public methods
|
|
p.toString = function () {
|
|
return "[WebAudioLoader]";
|
|
};
|
|
|
|
// private methods
|
|
p._createRequest = function () {
|
|
this._request = new createjs.XHRRequest(this._item, false);
|
|
this._request.setResponseType("arraybuffer");
|
|
};
|
|
|
|
p._sendComplete = function (event) {
|
|
// OJR we leave this wrapped in Loader because we need to reference src and the handler only receives a single argument, the decodedAudio
|
|
Loader.context.decodeAudioData(
|
|
this._rawResult,
|
|
createjs.proxy(this._handleAudioDecoded, this),
|
|
createjs.proxy(this._sendError, this)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* The audio has been decoded.
|
|
* @method handleAudioDecoded
|
|
* @param decoded
|
|
* @protected
|
|
*/
|
|
p._handleAudioDecoded = function (decodedAudio) {
|
|
this._result = decodedAudio;
|
|
this.AbstractLoader__sendComplete();
|
|
};
|
|
|
|
createjs.WebAudioLoader = createjs.promote(Loader, "AbstractLoader");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// WebAudioSoundInstance.js
|
|
//##############################################################################
|
|
|
|
/**
|
|
* WebAudioSoundInstance extends the base api of {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} and is used by
|
|
* {{#crossLink "WebAudioPlugin"}}{{/crossLink}}.
|
|
*
|
|
* WebAudioSoundInstance exposes audioNodes for advanced users.
|
|
*
|
|
* @param {String} src The path to and file name of the sound.
|
|
* @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds.
|
|
* @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds.
|
|
* @param {Object} playbackResource Any resource needed by plugin to support audio playback.
|
|
* @class WebAudioSoundInstance
|
|
* @extends AbstractSoundInstance
|
|
* @constructor
|
|
*/
|
|
(function () {
|
|
"use strict";
|
|
|
|
function WebAudioSoundInstance(src, startTime, duration, playbackResource) {
|
|
this.AbstractSoundInstance_constructor(
|
|
src,
|
|
startTime,
|
|
duration,
|
|
playbackResource
|
|
);
|
|
|
|
// public properties
|
|
/**
|
|
* NOTE this is only intended for use by advanced users.
|
|
* <br />GainNode for controlling <code>WebAudioSoundInstance</code> volume. Connected to the {{#crossLink "WebAudioSoundInstance/destinationNode:property"}}{{/crossLink}}.
|
|
* @property gainNode
|
|
* @type {AudioGainNode}
|
|
* @since 0.4.0
|
|
*
|
|
*/
|
|
this.gainNode = s.context.createGain();
|
|
|
|
/**
|
|
* NOTE this is only intended for use by advanced users.
|
|
* <br />A panNode allowing left and right audio channel panning only. Connected to WebAudioSoundInstance {{#crossLink "WebAudioSoundInstance/gainNode:property"}}{{/crossLink}}.
|
|
* @property panNode
|
|
* @type {AudioPannerNode}
|
|
* @since 0.4.0
|
|
*/
|
|
this.panNode = s.context.createPanner();
|
|
this.panNode.panningModel = s._panningModel;
|
|
this.panNode.connect(this.gainNode);
|
|
this._updatePan();
|
|
|
|
/**
|
|
* NOTE this is only intended for use by advanced users.
|
|
* <br />sourceNode is the audio source. Connected to WebAudioSoundInstance {{#crossLink "WebAudioSoundInstance/panNode:property"}}{{/crossLink}}.
|
|
* @property sourceNode
|
|
* @type {AudioNode}
|
|
* @since 0.4.0
|
|
*
|
|
*/
|
|
this.sourceNode = null;
|
|
|
|
// private properties
|
|
/**
|
|
* Timeout that is created internally to handle sound playing to completion.
|
|
* Stored so we can remove it when stop, pause, or cleanup are called
|
|
* @property _soundCompleteTimeout
|
|
* @type {timeoutVariable}
|
|
* @default null
|
|
* @protected
|
|
* @since 0.4.0
|
|
*/
|
|
this._soundCompleteTimeout = null;
|
|
|
|
/**
|
|
* NOTE this is only intended for use by very advanced users.
|
|
* _sourceNodeNext is the audio source for the next loop, inserted in a look ahead approach to allow for smooth
|
|
* looping. Connected to {{#crossLink "WebAudioSoundInstance/gainNode:property"}}{{/crossLink}}.
|
|
* @property _sourceNodeNext
|
|
* @type {AudioNode}
|
|
* @default null
|
|
* @protected
|
|
* @since 0.4.1
|
|
*
|
|
*/
|
|
this._sourceNodeNext = null;
|
|
|
|
/**
|
|
* Time audio started playback, in seconds. Used to handle set position, get position, and resuming from paused.
|
|
* @property _playbackStartTime
|
|
* @type {Number}
|
|
* @default 0
|
|
* @protected
|
|
* @since 0.4.0
|
|
*/
|
|
this._playbackStartTime = 0;
|
|
|
|
// Proxies, make removing listeners easier.
|
|
this._endedHandler = createjs.proxy(this._handleSoundComplete, this);
|
|
}
|
|
var p = createjs.extend(
|
|
WebAudioSoundInstance,
|
|
createjs.AbstractSoundInstance
|
|
);
|
|
var s = WebAudioSoundInstance;
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
/**
|
|
* Note this is only intended for use by advanced users.
|
|
* <br />Audio context used to create nodes. This is and needs to be the same context used by {{#crossLink "WebAudioPlugin"}}{{/crossLink}}.
|
|
* @property context
|
|
* @type {AudioContext}
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s.context = null;
|
|
|
|
/**
|
|
* Note this is only intended for use by advanced users.
|
|
* <br />The scratch buffer that will be assigned to the buffer property of a source node on close.
|
|
* This is and should be the same scratch buffer referenced by {{#crossLink "WebAudioPlugin"}}{{/crossLink}}.
|
|
* @property _scratchBuffer
|
|
* @type {AudioBufferSourceNode}
|
|
* @static
|
|
*/
|
|
s._scratchBuffer = null;
|
|
|
|
/**
|
|
* Note this is only intended for use by advanced users.
|
|
* <br /> Audio node from WebAudioPlugin that sequences to <code>context.destination</code>
|
|
* @property destinationNode
|
|
* @type {AudioNode}
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s.destinationNode = null;
|
|
|
|
/**
|
|
* Value to set panning model to equal power for WebAudioSoundInstance. Can be "equalpower" or 0 depending on browser implementation.
|
|
* @property _panningModel
|
|
* @type {Number / String}
|
|
* @protected
|
|
* @static
|
|
* @since 0.6.0
|
|
*/
|
|
s._panningModel = "equalpower";
|
|
|
|
// Public methods
|
|
p.destroy = function () {
|
|
this.AbstractSoundInstance_destroy();
|
|
|
|
this.panNode.disconnect(0);
|
|
this.panNode = null;
|
|
this.gainNode.disconnect(0);
|
|
this.gainNode = null;
|
|
};
|
|
|
|
p.toString = function () {
|
|
return "[WebAudioSoundInstance]";
|
|
};
|
|
|
|
// Private Methods
|
|
p._updatePan = function () {
|
|
this.panNode.setPosition(this._pan, 0, -0.5);
|
|
// z need to be -0.5 otherwise the sound only plays in left, right, or center
|
|
};
|
|
|
|
p._removeLooping = function (value) {
|
|
this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext);
|
|
};
|
|
|
|
p._addLooping = function (value) {
|
|
if (this.playState != createjs.Sound.PLAY_SUCCEEDED) {
|
|
return;
|
|
}
|
|
this._sourceNodeNext = this._createAndPlayAudioNode(
|
|
this._playbackStartTime,
|
|
0
|
|
);
|
|
};
|
|
|
|
p._setDurationFromSource = function () {
|
|
this._duration = this.playbackResource.duration * 1000;
|
|
};
|
|
|
|
p._handleCleanUp = function () {
|
|
if (this.sourceNode && this.playState == createjs.Sound.PLAY_SUCCEEDED) {
|
|
this.sourceNode = this._cleanUpAudioNode(this.sourceNode);
|
|
this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext);
|
|
}
|
|
|
|
if (this.gainNode.numberOfOutputs != 0) {
|
|
this.gainNode.disconnect(0);
|
|
}
|
|
// OJR there appears to be a bug that this doesn't always work in webkit (Chrome and Safari). According to the documentation, this should work.
|
|
|
|
clearTimeout(this._soundCompleteTimeout);
|
|
|
|
this._playbackStartTime = 0; // This is used by getPosition
|
|
};
|
|
|
|
/**
|
|
* Turn off and disconnect an audioNode, then set reference to null to release it for garbage collection
|
|
* @method _cleanUpAudioNode
|
|
* @param audioNode
|
|
* @return {audioNode}
|
|
* @protected
|
|
* @since 0.4.1
|
|
*/
|
|
p._cleanUpAudioNode = function (audioNode) {
|
|
if (audioNode) {
|
|
audioNode.stop(0);
|
|
audioNode.disconnect(0);
|
|
// necessary to prevent leak on iOS Safari 7-9. will throw in almost all other
|
|
// browser implementations.
|
|
try {
|
|
audioNode.buffer = s._scratchBuffer;
|
|
} catch (e) {}
|
|
audioNode = null;
|
|
}
|
|
return audioNode;
|
|
};
|
|
|
|
p._handleSoundReady = function (event) {
|
|
this.gainNode.connect(s.destinationNode); // this line can cause a memory leak. Nodes need to be disconnected from the audioDestination or any sequence that leads to it.
|
|
|
|
var dur = this._duration * 0.001;
|
|
var pos = this._position * 0.001;
|
|
if (pos > dur) {
|
|
pos = dur;
|
|
}
|
|
this.sourceNode = this._createAndPlayAudioNode(
|
|
s.context.currentTime - dur,
|
|
pos
|
|
);
|
|
this._playbackStartTime = this.sourceNode.startTime - pos;
|
|
|
|
this._soundCompleteTimeout = setTimeout(
|
|
this._endedHandler,
|
|
(dur - pos) * 1000
|
|
);
|
|
|
|
if (this._loop != 0) {
|
|
this._sourceNodeNext = this._createAndPlayAudioNode(
|
|
this._playbackStartTime,
|
|
0
|
|
);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Creates an audio node using the current src and context, connects it to the gain node, and starts playback.
|
|
* @method _createAndPlayAudioNode
|
|
* @param {Number} startTime The time to add this to the web audio context, in seconds.
|
|
* @param {Number} offset The amount of time into the src audio to start playback, in seconds.
|
|
* @return {audioNode}
|
|
* @protected
|
|
* @since 0.4.1
|
|
*/
|
|
p._createAndPlayAudioNode = function (startTime, offset) {
|
|
var audioNode = s.context.createBufferSource();
|
|
audioNode.buffer = this.playbackResource;
|
|
audioNode.connect(this.panNode);
|
|
var dur = this._duration * 0.001;
|
|
audioNode.startTime = startTime + dur;
|
|
audioNode.start(
|
|
audioNode.startTime,
|
|
offset + this._startTime * 0.001,
|
|
dur - offset
|
|
);
|
|
return audioNode;
|
|
};
|
|
|
|
p._pause = function () {
|
|
this._position = (s.context.currentTime - this._playbackStartTime) * 1000; // * 1000 to give milliseconds, lets us restart at same point
|
|
this.sourceNode = this._cleanUpAudioNode(this.sourceNode);
|
|
this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext);
|
|
|
|
if (this.gainNode.numberOfOutputs != 0) {
|
|
this.gainNode.disconnect(0);
|
|
}
|
|
|
|
clearTimeout(this._soundCompleteTimeout);
|
|
};
|
|
|
|
p._resume = function () {
|
|
this._handleSoundReady();
|
|
};
|
|
|
|
/*
|
|
p._handleStop = function () {
|
|
// web audio does not need to do anything extra
|
|
};
|
|
*/
|
|
|
|
p._updateVolume = function () {
|
|
var newVolume = this._muted ? 0 : this._volume;
|
|
if (newVolume != this.gainNode.gain.value) {
|
|
this.gainNode.gain.value = newVolume;
|
|
}
|
|
};
|
|
|
|
p._calculateCurrentPosition = function () {
|
|
return (s.context.currentTime - this._playbackStartTime) * 1000; // pos in seconds * 1000 to give milliseconds
|
|
};
|
|
|
|
p._updatePosition = function () {
|
|
this.sourceNode = this._cleanUpAudioNode(this.sourceNode);
|
|
this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext);
|
|
clearTimeout(this._soundCompleteTimeout);
|
|
|
|
if (!this._paused) {
|
|
this._handleSoundReady();
|
|
}
|
|
};
|
|
|
|
// OJR we are using a look ahead approach to ensure smooth looping.
|
|
// We add _sourceNodeNext to the audio context so that it starts playing even if this callback is delayed.
|
|
// This technique is described here: http://www.html5rocks.com/en/tutorials/audio/scheduling/
|
|
// NOTE the cost of this is that our audio loop may not always match the loop event timing precisely.
|
|
p._handleLoop = function () {
|
|
this._cleanUpAudioNode(this.sourceNode);
|
|
this.sourceNode = this._sourceNodeNext;
|
|
this._playbackStartTime = this.sourceNode.startTime;
|
|
this._sourceNodeNext = this._createAndPlayAudioNode(
|
|
this._playbackStartTime,
|
|
0
|
|
);
|
|
this._soundCompleteTimeout = setTimeout(this._endedHandler, this._duration);
|
|
};
|
|
|
|
p._updateDuration = function () {
|
|
if (this.playState == createjs.Sound.PLAY_SUCCEEDED) {
|
|
this._pause();
|
|
this._resume();
|
|
}
|
|
};
|
|
|
|
createjs.WebAudioSoundInstance = createjs.promote(
|
|
WebAudioSoundInstance,
|
|
"AbstractSoundInstance"
|
|
);
|
|
})();
|
|
|
|
//##############################################################################
|
|
// WebAudioPlugin.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* Play sounds using Web Audio in the browser. The WebAudioPlugin is currently the default plugin, and will be used
|
|
* anywhere that it is supported. To change plugin priority, check out the Sound API
|
|
* {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} method.
|
|
|
|
* <h4>Known Browser and OS issues for Web Audio</h4>
|
|
* <b>Firefox 25</b>
|
|
* <li>
|
|
* mp3 audio files do not load properly on all windows machines, reported <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=929969" target="_blank">here</a>.
|
|
* <br />For this reason it is recommended to pass another FireFox-supported type (i.e. ogg) as the default
|
|
* extension, until this bug is resolved
|
|
* </li>
|
|
*
|
|
* <b>Webkit (Chrome and Safari)</b>
|
|
* <li>
|
|
* AudioNode.disconnect does not always seem to work. This can cause the file size to grow over time if you
|
|
* are playing a lot of audio files.
|
|
* </li>
|
|
*
|
|
* <b>iOS 6 limitations</b>
|
|
* <ul>
|
|
* <li>
|
|
* Sound is initially muted and will only unmute through play being called inside a user initiated event
|
|
* (touch/click). Please read the mobile playback notes in the the {{#crossLink "Sound"}}{{/crossLink}}
|
|
* class for a full overview of the limitations, and how to get around them.
|
|
* </li>
|
|
* <li>
|
|
* A bug exists that will distort un-cached audio when a video element is present in the DOM. You can avoid
|
|
* this bug by ensuring the audio and video audio share the same sample rate.
|
|
* </li>
|
|
* </ul>
|
|
* @class WebAudioPlugin
|
|
* @extends AbstractPlugin
|
|
* @constructor
|
|
* @since 0.4.0
|
|
*/
|
|
function WebAudioPlugin() {
|
|
this.AbstractPlugin_constructor();
|
|
|
|
// Private Properties
|
|
/**
|
|
* Value to set panning model to equal power for WebAudioSoundInstance. Can be "equalpower" or 0 depending on browser implementation.
|
|
* @property _panningModel
|
|
* @type {Number / String}
|
|
* @protected
|
|
*/
|
|
this._panningModel = s._panningModel;
|
|
|
|
/**
|
|
* The web audio context, which WebAudio uses to play audio. All nodes that interact with the WebAudioPlugin
|
|
* need to be created within this context.
|
|
* @property context
|
|
* @type {AudioContext}
|
|
*/
|
|
this.context = s.context;
|
|
|
|
/**
|
|
* A DynamicsCompressorNode, which is used to improve sound quality and prevent audio distortion.
|
|
* It is connected to <code>context.destination</code>.
|
|
*
|
|
* Can be accessed by advanced users through createjs.Sound.activePlugin.dynamicsCompressorNode.
|
|
* @property dynamicsCompressorNode
|
|
* @type {AudioNode}
|
|
*/
|
|
this.dynamicsCompressorNode = this.context.createDynamicsCompressor();
|
|
this.dynamicsCompressorNode.connect(this.context.destination);
|
|
|
|
/**
|
|
* A GainNode for controlling master volume. It is connected to {{#crossLink "WebAudioPlugin/dynamicsCompressorNode:property"}}{{/crossLink}}.
|
|
*
|
|
* Can be accessed by advanced users through createjs.Sound.activePlugin.gainNode.
|
|
* @property gainNode
|
|
* @type {AudioGainNode}
|
|
*/
|
|
this.gainNode = this.context.createGain();
|
|
this.gainNode.connect(this.dynamicsCompressorNode);
|
|
createjs.WebAudioSoundInstance.destinationNode = this.gainNode;
|
|
|
|
this._capabilities = s._capabilities;
|
|
|
|
this._loaderClass = createjs.WebAudioLoader;
|
|
this._soundInstanceClass = createjs.WebAudioSoundInstance;
|
|
|
|
this._addPropsToClasses();
|
|
}
|
|
var p = createjs.extend(WebAudioPlugin, createjs.AbstractPlugin);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// Static Properties
|
|
var s = WebAudioPlugin;
|
|
/**
|
|
* The capabilities of the plugin. This is generated via the {{#crossLink "WebAudioPlugin/_generateCapabilities:method"}}{{/crossLink}}
|
|
* method and is used internally.
|
|
* @property _capabilities
|
|
* @type {Object}
|
|
* @default null
|
|
* @protected
|
|
* @static
|
|
*/
|
|
s._capabilities = null;
|
|
|
|
/**
|
|
* Value to set panning model to equal power for WebAudioSoundInstance. Can be "equalpower" or 0 depending on browser implementation.
|
|
* @property _panningModel
|
|
* @type {Number / String}
|
|
* @protected
|
|
* @static
|
|
*/
|
|
s._panningModel = "equalpower";
|
|
|
|
/**
|
|
* The web audio context, which WebAudio uses to play audio. All nodes that interact with the WebAudioPlugin
|
|
* need to be created within this context.
|
|
*
|
|
* Advanced users can set this to an existing context, but <b>must</b> do so before they call
|
|
* {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} or {{#crossLink "Sound/initializeDefaultPlugins"}}{{/crossLink}}.
|
|
*
|
|
* @property context
|
|
* @type {AudioContext}
|
|
* @static
|
|
*/
|
|
s.context = null;
|
|
|
|
/**
|
|
* The scratch buffer that will be assigned to the buffer property of a source node on close.
|
|
* Works around an iOS Safari bug: https://github.com/CreateJS/SoundJS/issues/102
|
|
*
|
|
* Advanced users can set this to an existing source node, but <b>must</b> do so before they call
|
|
* {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} or {{#crossLink "Sound/initializeDefaultPlugins"}}{{/crossLink}}.
|
|
*
|
|
* @property _scratchBuffer
|
|
* @type {AudioBuffer}
|
|
* @protected
|
|
* @static
|
|
*/
|
|
s._scratchBuffer = null;
|
|
|
|
/**
|
|
* Indicated whether audio on iOS has been unlocked, which requires a touchend/mousedown event that plays an
|
|
* empty sound.
|
|
* @property _unlocked
|
|
* @type {boolean}
|
|
* @since 0.6.2
|
|
* @private
|
|
*/
|
|
s._unlocked = false;
|
|
|
|
// Static Public Methods
|
|
/**
|
|
* Determine if the plugin can be used in the current browser/OS.
|
|
* @method isSupported
|
|
* @return {Boolean} If the plugin can be initialized.
|
|
* @static
|
|
*/
|
|
s.isSupported = function () {
|
|
// check if this is some kind of mobile device, Web Audio works with local protocol under PhoneGap and it is unlikely someone is trying to run a local file
|
|
var isMobilePhoneGap =
|
|
createjs.BrowserDetect.isIOS ||
|
|
createjs.BrowserDetect.isAndroid ||
|
|
createjs.BrowserDetect.isBlackberry;
|
|
// OJR isMobile may be redundant with _isFileXHRSupported available. Consider removing.
|
|
if (
|
|
location.protocol == "file:" &&
|
|
!isMobilePhoneGap &&
|
|
!this._isFileXHRSupported()
|
|
) {
|
|
return false;
|
|
} // Web Audio requires XHR, which is not usually available locally
|
|
s._generateCapabilities();
|
|
if (s.context == null) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Plays an empty sound in the web audio context. This is used to enable web audio on iOS devices, as they
|
|
* require the first sound to be played inside of a user initiated event (touch/click). This is called when
|
|
* {{#crossLink "WebAudioPlugin"}}{{/crossLink}} is initialized (by Sound {{#crossLink "Sound/initializeDefaultPlugins"}}{{/crossLink}}
|
|
* for example).
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* function handleTouch(event) {
|
|
* createjs.WebAudioPlugin.playEmptySound();
|
|
* }
|
|
*
|
|
* @method playEmptySound
|
|
* @static
|
|
* @since 0.4.1
|
|
*/
|
|
s.playEmptySound = function () {
|
|
if (s.context == null) {
|
|
return;
|
|
}
|
|
var source = s.context.createBufferSource();
|
|
source.buffer = s._scratchBuffer;
|
|
source.connect(s.context.destination);
|
|
source.start(0, 0, 0);
|
|
};
|
|
|
|
// Static Private Methods
|
|
/**
|
|
* Determine if XHR is supported, which is necessary for web audio.
|
|
* @method _isFileXHRSupported
|
|
* @return {Boolean} If XHR is supported.
|
|
* @since 0.4.2
|
|
* @protected
|
|
* @static
|
|
*/
|
|
s._isFileXHRSupported = function () {
|
|
// it's much easier to detect when something goes wrong, so let's start optimistically
|
|
var supported = true;
|
|
|
|
var xhr = new XMLHttpRequest();
|
|
try {
|
|
xhr.open("GET", "WebAudioPluginTest.fail", false); // loading non-existant file triggers 404 only if it could load (synchronous call)
|
|
} catch (error) {
|
|
// catch errors in cases where the onerror is passed by
|
|
supported = false;
|
|
return supported;
|
|
}
|
|
xhr.onerror = function () {
|
|
supported = false;
|
|
}; // cause irrelevant
|
|
// with security turned off, we can get empty success results, which is actually a failed read (status code 0?)
|
|
xhr.onload = function () {
|
|
supported =
|
|
this.status == 404 ||
|
|
this.status == 200 ||
|
|
(this.status == 0 && this.response != "");
|
|
};
|
|
try {
|
|
xhr.send();
|
|
} catch (error) {
|
|
// catch errors in cases where the onerror is passed by
|
|
supported = false;
|
|
}
|
|
|
|
return supported;
|
|
};
|
|
|
|
/**
|
|
* Determine the capabilities of the plugin. Used internally. Please see the Sound API {{#crossLink "Sound/getCapabilities"}}{{/crossLink}}
|
|
* method for an overview of plugin capabilities.
|
|
* @method _generateCapabilities
|
|
* @static
|
|
* @protected
|
|
*/
|
|
s._generateCapabilities = function () {
|
|
if (s._capabilities != null) {
|
|
return;
|
|
}
|
|
// Web Audio can be in any formats supported by the audio element, from http://www.w3.org/TR/webaudio/#AudioContext-section
|
|
var t = document.createElement("audio");
|
|
if (t.canPlayType == null) {
|
|
return null;
|
|
}
|
|
|
|
if (s.context == null) {
|
|
if (window.AudioContext) {
|
|
s.context = new AudioContext();
|
|
} else if (window.webkitAudioContext) {
|
|
s.context = new webkitAudioContext();
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
if (s._scratchBuffer == null) {
|
|
s._scratchBuffer = s.context.createBuffer(1, 1, 22050);
|
|
}
|
|
|
|
s._compatibilitySetUp();
|
|
|
|
// Listen for document level clicks to unlock WebAudio on iOS. See the _unlock method.
|
|
if ("ontouchstart" in window && s.context.state != "running") {
|
|
s._unlock(); // When played inside of a touch event, this will enable audio on iOS immediately.
|
|
document.addEventListener("mousedown", s._unlock, true);
|
|
document.addEventListener("touchend", s._unlock, true);
|
|
}
|
|
|
|
s._capabilities = {
|
|
panning: true,
|
|
volume: true,
|
|
tracks: -1,
|
|
};
|
|
|
|
// determine which extensions our browser supports for this plugin by iterating through Sound.SUPPORTED_EXTENSIONS
|
|
var supportedExtensions = createjs.Sound.SUPPORTED_EXTENSIONS;
|
|
var extensionMap = createjs.Sound.EXTENSION_MAP;
|
|
for (var i = 0, l = supportedExtensions.length; i < l; i++) {
|
|
var ext = supportedExtensions[i];
|
|
var playType = extensionMap[ext] || ext;
|
|
s._capabilities[ext] =
|
|
(t.canPlayType("audio/" + ext) != "no" &&
|
|
t.canPlayType("audio/" + ext) != "") ||
|
|
(t.canPlayType("audio/" + playType) != "no" &&
|
|
t.canPlayType("audio/" + playType) != "");
|
|
} // OJR another way to do this might be canPlayType:"m4a", codex: mp4
|
|
|
|
// 0=no output, 1=mono, 2=stereo, 4=surround, 6=5.1 surround.
|
|
// See http://www.w3.org/TR/webaudio/#AudioChannelSplitter for more details on channels.
|
|
if (s.context.destination.numberOfChannels < 2) {
|
|
s._capabilities.panning = false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Set up compatibility if only deprecated web audio calls are supported.
|
|
* See http://www.w3.org/TR/webaudio/#DeprecationNotes
|
|
* Needed so we can support new browsers that don't support deprecated calls (Firefox) as well as old browsers that
|
|
* don't support new calls.
|
|
*
|
|
* @method _compatibilitySetUp
|
|
* @static
|
|
* @protected
|
|
* @since 0.4.2
|
|
*/
|
|
s._compatibilitySetUp = function () {
|
|
s._panningModel = "equalpower";
|
|
//assume that if one new call is supported, they all are
|
|
if (s.context.createGain) {
|
|
return;
|
|
}
|
|
|
|
// simple name change, functionality the same
|
|
s.context.createGain = s.context.createGainNode;
|
|
|
|
// source node, add to prototype
|
|
var audioNode = s.context.createBufferSource();
|
|
audioNode.__proto__.start = audioNode.__proto__.noteGrainOn; // note that noteGrainOn requires all 3 parameters
|
|
audioNode.__proto__.stop = audioNode.__proto__.noteOff;
|
|
|
|
// panningModel
|
|
s._panningModel = 0;
|
|
};
|
|
|
|
/**
|
|
* Try to unlock audio on iOS. This is triggered from either WebAudio plugin setup (which will work if inside of
|
|
* a `mousedown` or `touchend` event stack), or the first document touchend/mousedown event. If it fails (touchend
|
|
* will fail if the user presses for too long, indicating a scroll event instead of a click event.
|
|
*
|
|
* Note that earlier versions of iOS supported `touchstart` for this, but iOS9 removed this functionality. Adding
|
|
* a `touchstart` event to support older platforms may preclude a `mousedown` even from getting fired on iOS9, so we
|
|
* stick with `mousedown` and `touchend`.
|
|
* @method _unlock
|
|
* @since 0.6.2
|
|
* @private
|
|
*/
|
|
s._unlock = function () {
|
|
if (s._unlocked) {
|
|
return;
|
|
}
|
|
s.playEmptySound();
|
|
if (s.context.state == "running") {
|
|
document.removeEventListener("mousedown", s._unlock, true);
|
|
document.removeEventListener("touchend", s._unlock, true);
|
|
s._unlocked = true;
|
|
}
|
|
};
|
|
|
|
// Public Methods
|
|
p.toString = function () {
|
|
return "[WebAudioPlugin]";
|
|
};
|
|
|
|
// Private Methods
|
|
/**
|
|
* Set up needed properties on supported classes WebAudioSoundInstance and WebAudioLoader.
|
|
* @method _addPropsToClasses
|
|
* @static
|
|
* @protected
|
|
* @since 0.6.0
|
|
*/
|
|
p._addPropsToClasses = function () {
|
|
var c = this._soundInstanceClass;
|
|
c.context = this.context;
|
|
c._scratchBuffer = s._scratchBuffer;
|
|
c.destinationNode = this.gainNode;
|
|
c._panningModel = this._panningModel;
|
|
|
|
this._loaderClass.context = this.context;
|
|
};
|
|
|
|
/**
|
|
* Set the gain value for master audio. Should not be called externally.
|
|
* @method _updateVolume
|
|
* @protected
|
|
*/
|
|
p._updateVolume = function () {
|
|
var newVolume = createjs.Sound._masterMute ? 0 : this._volume;
|
|
if (newVolume != this.gainNode.gain.value) {
|
|
this.gainNode.gain.value = newVolume;
|
|
}
|
|
};
|
|
|
|
createjs.WebAudioPlugin = createjs.promote(WebAudioPlugin, "AbstractPlugin");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// HTMLAudioTagPool.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* HTMLAudioTagPool is an object pool for HTMLAudio tag instances.
|
|
* @class HTMLAudioTagPool
|
|
* @param {String} src The source of the channel.
|
|
* @protected
|
|
*/
|
|
function HTMLAudioTagPool() {
|
|
throw "HTMLAudioTagPool cannot be instantiated";
|
|
}
|
|
|
|
var s = HTMLAudioTagPool;
|
|
|
|
// Static Properties
|
|
/**
|
|
* A hash lookup of each base audio tag, indexed by the audio source.
|
|
* @property _tags
|
|
* @type {{}}
|
|
* @static
|
|
* @protected
|
|
*/
|
|
s._tags = {};
|
|
|
|
/**
|
|
* An object pool for html audio tags
|
|
* @property _tagPool
|
|
* @type {TagPool}
|
|
* @static
|
|
* @protected
|
|
*/
|
|
s._tagPool = new TagPool();
|
|
|
|
/**
|
|
* A hash lookup of if a base audio tag is available, indexed by the audio source
|
|
* @property _tagsUsed
|
|
* @type {{}}
|
|
* @protected
|
|
* @static
|
|
*/
|
|
s._tagUsed = {};
|
|
|
|
// Static Methods
|
|
/**
|
|
* Get an audio tag with the given source.
|
|
* @method get
|
|
* @param {String} src The source file used by the audio tag.
|
|
* @static
|
|
*/
|
|
s.get = function (src) {
|
|
var t = s._tags[src];
|
|
if (t == null) {
|
|
// create new base tag
|
|
t = s._tags[src] = s._tagPool.get();
|
|
t.src = src;
|
|
} else {
|
|
// get base or pool
|
|
if (s._tagUsed[src]) {
|
|
t = s._tagPool.get();
|
|
t.src = src;
|
|
} else {
|
|
s._tagUsed[src] = true;
|
|
}
|
|
}
|
|
return t;
|
|
};
|
|
|
|
/**
|
|
* Return an audio tag to the pool.
|
|
* @method set
|
|
* @param {String} src The source file used by the audio tag.
|
|
* @param {HTMLElement} tag Audio tag to set.
|
|
* @static
|
|
*/
|
|
s.set = function (src, tag) {
|
|
// check if this is base, if yes set boolean if not return to pool
|
|
if (tag == s._tags[src]) {
|
|
s._tagUsed[src] = false;
|
|
} else {
|
|
s._tagPool.set(tag);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Delete stored tag reference and return them to pool. Note that if the tag reference does not exist, this will fail.
|
|
* @method remove
|
|
* @param {String} src The source for the tag
|
|
* @return {Boolean} If the TagPool was deleted.
|
|
* @static
|
|
*/
|
|
s.remove = function (src) {
|
|
var tag = s._tags[src];
|
|
if (tag == null) {
|
|
return false;
|
|
}
|
|
s._tagPool.set(tag);
|
|
delete s._tags[src];
|
|
delete s._tagUsed[src];
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Gets the duration of the src audio in milliseconds
|
|
* @method getDuration
|
|
* @param {String} src The source file used by the audio tag.
|
|
* @return {Number} Duration of src in milliseconds
|
|
* @static
|
|
*/
|
|
s.getDuration = function (src) {
|
|
var t = s._tags[src];
|
|
if (t == null || !t.duration) {
|
|
return 0;
|
|
} // OJR duration is NaN if loading has not completed
|
|
return t.duration * 1000;
|
|
};
|
|
|
|
createjs.HTMLAudioTagPool = HTMLAudioTagPool;
|
|
|
|
// ************************************************************************************************************
|
|
/**
|
|
* The TagPool is an object pool for HTMLAudio tag instances.
|
|
* #class TagPool
|
|
* @param {String} src The source of the channel.
|
|
* @protected
|
|
*/
|
|
function TagPool(src) {
|
|
// Public Properties
|
|
/**
|
|
* A list of all available tags in the pool.
|
|
* #property tags
|
|
* @type {Array}
|
|
* @protected
|
|
*/
|
|
this._tags = [];
|
|
}
|
|
|
|
var p = TagPool.prototype;
|
|
p.constructor = TagPool;
|
|
|
|
// Public Methods
|
|
/**
|
|
* Get an HTMLAudioElement for immediate playback. This takes it out of the pool.
|
|
* #method get
|
|
* @return {HTMLAudioElement} An HTML audio tag.
|
|
*/
|
|
p.get = function () {
|
|
var tag;
|
|
if (this._tags.length == 0) {
|
|
tag = this._createTag();
|
|
} else {
|
|
tag = this._tags.pop();
|
|
}
|
|
if (tag.parentNode == null) {
|
|
document.body.appendChild(tag);
|
|
}
|
|
return tag;
|
|
};
|
|
|
|
/**
|
|
* Put an HTMLAudioElement back in the pool for use.
|
|
* #method set
|
|
* @param {HTMLAudioElement} tag HTML audio tag
|
|
*/
|
|
p.set = function (tag) {
|
|
// OJR this first step seems unnecessary
|
|
var index = createjs.indexOf(this._tags, tag);
|
|
if (index == -1) {
|
|
this._tags.src = null;
|
|
this._tags.push(tag);
|
|
}
|
|
};
|
|
|
|
p.toString = function () {
|
|
return "[TagPool]";
|
|
};
|
|
|
|
// Private Methods
|
|
/**
|
|
* Create an HTML audio tag.
|
|
* #method _createTag
|
|
* @param {String} src The source file to set for the audio tag.
|
|
* @return {HTMLElement} Returns an HTML audio tag.
|
|
* @protected
|
|
*/
|
|
p._createTag = function () {
|
|
var tag = document.createElement("audio");
|
|
tag.autoplay = false;
|
|
tag.preload = "none";
|
|
//LM: Firefox fails when this the preload="none" for other tags, but it needs to be "none" to ensure PreloadJS works.
|
|
return tag;
|
|
};
|
|
})();
|
|
|
|
//##############################################################################
|
|
// HTMLAudioSoundInstance.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* HTMLAudioSoundInstance extends the base api of {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} and is used by
|
|
* {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}.
|
|
*
|
|
* @param {String} src The path to and file name of the sound.
|
|
* @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds.
|
|
* @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds.
|
|
* @param {Object} playbackResource Any resource needed by plugin to support audio playback.
|
|
* @class HTMLAudioSoundInstance
|
|
* @extends AbstractSoundInstance
|
|
* @constructor
|
|
*/
|
|
function HTMLAudioSoundInstance(src, startTime, duration, playbackResource) {
|
|
this.AbstractSoundInstance_constructor(
|
|
src,
|
|
startTime,
|
|
duration,
|
|
playbackResource
|
|
);
|
|
|
|
// Private Properties
|
|
this._audioSpriteStopTime = null;
|
|
this._delayTimeoutId = null;
|
|
|
|
// Proxies, make removing listeners easier.
|
|
this._endedHandler = createjs.proxy(this._handleSoundComplete, this);
|
|
this._readyHandler = createjs.proxy(this._handleTagReady, this);
|
|
this._stalledHandler = createjs.proxy(this._playFailed, this);
|
|
this._audioSpriteEndHandler = createjs.proxy(
|
|
this._handleAudioSpriteLoop,
|
|
this
|
|
);
|
|
this._loopHandler = createjs.proxy(this._handleSoundComplete, this);
|
|
|
|
if (duration) {
|
|
this._audioSpriteStopTime = (startTime + duration) * 0.001;
|
|
} else {
|
|
this._duration = createjs.HTMLAudioTagPool.getDuration(this.src);
|
|
}
|
|
}
|
|
var p = createjs.extend(
|
|
HTMLAudioSoundInstance,
|
|
createjs.AbstractSoundInstance
|
|
);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// Public Methods
|
|
/**
|
|
* Called by {{#crossLink "Sound"}}{{/crossLink}} when plugin does not handle master volume.
|
|
* undoc'd because it is not meant to be used outside of Sound
|
|
* #method setMasterVolume
|
|
* @param value
|
|
*/
|
|
p.setMasterVolume = function (value) {
|
|
this._updateVolume();
|
|
};
|
|
|
|
/**
|
|
* Called by {{#crossLink "Sound"}}{{/crossLink}} when plugin does not handle master mute.
|
|
* undoc'd because it is not meant to be used outside of Sound
|
|
* #method setMasterMute
|
|
* @param value
|
|
*/
|
|
p.setMasterMute = function (isMuted) {
|
|
this._updateVolume();
|
|
};
|
|
|
|
p.toString = function () {
|
|
return "[HTMLAudioSoundInstance]";
|
|
};
|
|
|
|
//Private Methods
|
|
p._removeLooping = function () {
|
|
if (this._playbackResource == null) {
|
|
return;
|
|
}
|
|
this._playbackResource.loop = false;
|
|
this._playbackResource.removeEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_SEEKED,
|
|
this._loopHandler,
|
|
false
|
|
);
|
|
};
|
|
|
|
p._addLooping = function () {
|
|
if (this._playbackResource == null || this._audioSpriteStopTime) {
|
|
return;
|
|
}
|
|
this._playbackResource.addEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_SEEKED,
|
|
this._loopHandler,
|
|
false
|
|
);
|
|
this._playbackResource.loop = true;
|
|
};
|
|
|
|
p._handleCleanUp = function () {
|
|
var tag = this._playbackResource;
|
|
if (tag != null) {
|
|
tag.pause();
|
|
tag.loop = false;
|
|
tag.removeEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_ENDED,
|
|
this._endedHandler,
|
|
false
|
|
);
|
|
tag.removeEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_READY,
|
|
this._readyHandler,
|
|
false
|
|
);
|
|
tag.removeEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_STALLED,
|
|
this._stalledHandler,
|
|
false
|
|
);
|
|
tag.removeEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_SEEKED,
|
|
this._loopHandler,
|
|
false
|
|
);
|
|
tag.removeEventListener(
|
|
createjs.HTMLAudioPlugin._TIME_UPDATE,
|
|
this._audioSpriteEndHandler,
|
|
false
|
|
);
|
|
|
|
try {
|
|
tag.currentTime = this._startTime;
|
|
} catch (e) {} // Reset Position
|
|
createjs.HTMLAudioTagPool.set(this.src, tag);
|
|
this._playbackResource = null;
|
|
}
|
|
};
|
|
|
|
p._beginPlaying = function (playProps) {
|
|
this._playbackResource = createjs.HTMLAudioTagPool.get(this.src);
|
|
return this.AbstractSoundInstance__beginPlaying(playProps);
|
|
};
|
|
|
|
p._handleSoundReady = function (event) {
|
|
if (this._playbackResource.readyState !== 4) {
|
|
var tag = this._playbackResource;
|
|
tag.addEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_READY,
|
|
this._readyHandler,
|
|
false
|
|
);
|
|
tag.addEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_STALLED,
|
|
this._stalledHandler,
|
|
false
|
|
);
|
|
tag.preload = "auto"; // This is necessary for Firefox, as it won't ever "load" until this is set.
|
|
tag.load();
|
|
return;
|
|
}
|
|
|
|
this._updateVolume();
|
|
this._playbackResource.currentTime =
|
|
(this._startTime + this._position) * 0.001;
|
|
if (this._audioSpriteStopTime) {
|
|
this._playbackResource.addEventListener(
|
|
createjs.HTMLAudioPlugin._TIME_UPDATE,
|
|
this._audioSpriteEndHandler,
|
|
false
|
|
);
|
|
} else {
|
|
this._playbackResource.addEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_ENDED,
|
|
this._endedHandler,
|
|
false
|
|
);
|
|
if (this._loop != 0) {
|
|
this._playbackResource.addEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_SEEKED,
|
|
this._loopHandler,
|
|
false
|
|
);
|
|
this._playbackResource.loop = true;
|
|
}
|
|
}
|
|
|
|
this._playbackResource.play();
|
|
};
|
|
|
|
/**
|
|
* Used to handle when a tag is not ready for immediate playback when it is returned from the HTMLAudioTagPool.
|
|
* @method _handleTagReady
|
|
* @param event
|
|
* @protected
|
|
*/
|
|
p._handleTagReady = function (event) {
|
|
this._playbackResource.removeEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_READY,
|
|
this._readyHandler,
|
|
false
|
|
);
|
|
this._playbackResource.removeEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_STALLED,
|
|
this._stalledHandler,
|
|
false
|
|
);
|
|
|
|
this._handleSoundReady();
|
|
};
|
|
|
|
p._pause = function () {
|
|
this._playbackResource.pause();
|
|
};
|
|
|
|
p._resume = function () {
|
|
this._playbackResource.play();
|
|
};
|
|
|
|
p._updateVolume = function () {
|
|
if (this._playbackResource != null) {
|
|
var newVolume =
|
|
this._muted || createjs.Sound._masterMute
|
|
? 0
|
|
: this._volume * createjs.Sound._masterVolume;
|
|
if (newVolume != this._playbackResource.volume) {
|
|
this._playbackResource.volume = newVolume;
|
|
}
|
|
}
|
|
};
|
|
|
|
p._calculateCurrentPosition = function () {
|
|
return this._playbackResource.currentTime * 1000 - this._startTime;
|
|
};
|
|
|
|
p._updatePosition = function () {
|
|
this._playbackResource.removeEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_SEEKED,
|
|
this._loopHandler,
|
|
false
|
|
);
|
|
this._playbackResource.addEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_SEEKED,
|
|
this._handleSetPositionSeek,
|
|
false
|
|
);
|
|
try {
|
|
this._playbackResource.currentTime =
|
|
(this._position + this._startTime) * 0.001;
|
|
} catch (error) {
|
|
// Out of range
|
|
this._handleSetPositionSeek(null);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Used to enable setting position, as we need to wait for that seek to be done before we add back our loop handling seek listener
|
|
* @method _handleSetPositionSeek
|
|
* @param event
|
|
* @protected
|
|
*/
|
|
p._handleSetPositionSeek = function (event) {
|
|
if (this._playbackResource == null) {
|
|
return;
|
|
}
|
|
this._playbackResource.removeEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_SEEKED,
|
|
this._handleSetPositionSeek,
|
|
false
|
|
);
|
|
this._playbackResource.addEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_SEEKED,
|
|
this._loopHandler,
|
|
false
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Timer used to loop audio sprites.
|
|
* NOTE because of the inaccuracies in the timeupdate event (15 - 250ms) and in setting the tag to the desired timed
|
|
* (up to 300ms), it is strongly recommended not to loop audio sprites with HTML Audio if smooth looping is desired
|
|
*
|
|
* @method _handleAudioSpriteLoop
|
|
* @param event
|
|
* @private
|
|
*/
|
|
p._handleAudioSpriteLoop = function (event) {
|
|
if (this._playbackResource.currentTime <= this._audioSpriteStopTime) {
|
|
return;
|
|
}
|
|
this._playbackResource.pause();
|
|
if (this._loop == 0) {
|
|
this._handleSoundComplete(null);
|
|
} else {
|
|
this._position = 0;
|
|
this._loop--;
|
|
this._playbackResource.currentTime = this._startTime * 0.001;
|
|
if (!this._paused) {
|
|
this._playbackResource.play();
|
|
}
|
|
this._sendEvent("loop");
|
|
}
|
|
};
|
|
|
|
// NOTE with this approach audio will loop as reliably as the browser allows
|
|
// but we could end up sending the loop event after next loop playback begins
|
|
p._handleLoop = function (event) {
|
|
if (this._loop == 0) {
|
|
this._playbackResource.loop = false;
|
|
this._playbackResource.removeEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_SEEKED,
|
|
this._loopHandler,
|
|
false
|
|
);
|
|
}
|
|
};
|
|
|
|
p._updateStartTime = function () {
|
|
this._audioSpriteStopTime = (this._startTime + this._duration) * 0.001;
|
|
|
|
if (this.playState == createjs.Sound.PLAY_SUCCEEDED) {
|
|
this._playbackResource.removeEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_ENDED,
|
|
this._endedHandler,
|
|
false
|
|
);
|
|
this._playbackResource.addEventListener(
|
|
createjs.HTMLAudioPlugin._TIME_UPDATE,
|
|
this._audioSpriteEndHandler,
|
|
false
|
|
);
|
|
}
|
|
};
|
|
|
|
p._updateDuration = function () {
|
|
this._audioSpriteStopTime = (this._startTime + this._duration) * 0.001;
|
|
|
|
if (this.playState == createjs.Sound.PLAY_SUCCEEDED) {
|
|
this._playbackResource.removeEventListener(
|
|
createjs.HTMLAudioPlugin._AUDIO_ENDED,
|
|
this._endedHandler,
|
|
false
|
|
);
|
|
this._playbackResource.addEventListener(
|
|
createjs.HTMLAudioPlugin._TIME_UPDATE,
|
|
this._audioSpriteEndHandler,
|
|
false
|
|
);
|
|
}
|
|
};
|
|
|
|
p._setDurationFromSource = function () {
|
|
this._duration = createjs.HTMLAudioTagPool.getDuration(this.src);
|
|
this._playbackResource = null;
|
|
};
|
|
|
|
createjs.HTMLAudioSoundInstance = createjs.promote(
|
|
HTMLAudioSoundInstance,
|
|
"AbstractSoundInstance"
|
|
);
|
|
})();
|
|
|
|
//##############################################################################
|
|
// HTMLAudioPlugin.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* Play sounds using HTML <audio> tags in the browser. This plugin is the second priority plugin installed
|
|
* by default, after the {{#crossLink "WebAudioPlugin"}}{{/crossLink}}. For older browsers that do not support html
|
|
* audio, include and install the {{#crossLink "FlashAudioPlugin"}}{{/crossLink}}.
|
|
*
|
|
* <h4>Known Browser and OS issues for HTML Audio</h4>
|
|
* <b>All browsers</b><br />
|
|
* Testing has shown in all browsers there is a limit to how many audio tag instances you are allowed. If you exceed
|
|
* this limit, you can expect to see unpredictable results. Please use {{#crossLink "Sound.MAX_INSTANCES"}}{{/crossLink}} as
|
|
* a guide to how many total audio tags you can safely use in all browsers. This issue is primarily limited to IE9.
|
|
*
|
|
* <b>IE html limitations</b><br />
|
|
* <ul><li>There is a delay in applying volume changes to tags that occurs once playback is started. So if you have
|
|
* muted all sounds, they will all play during this delay until the mute applies internally. This happens regardless of
|
|
* when or how you apply the volume change, as the tag seems to need to play to apply it.</li>
|
|
* <li>MP3 encoding will not always work for audio tags if it's not default. We've found default encoding with
|
|
* 64kbps works.</li>
|
|
* <li>Occasionally very short samples will get cut off.</li>
|
|
* <li>There is a limit to how many audio tags you can load or play at once, which appears to be determined by
|
|
* hardware and browser settings. See {{#crossLink "HTMLAudioPlugin.MAX_INSTANCES"}}{{/crossLink}} for a safe estimate.
|
|
* Note that audio sprites can be used as a solution to this issue.</li></ul>
|
|
*
|
|
* <b>Safari limitations</b><br />
|
|
* <ul><li>Safari requires Quicktime to be installed for audio playback.</li></ul>
|
|
*
|
|
* <b>iOS 6 limitations</b><br />
|
|
* <ul><li>can only have one <audio> tag</li>
|
|
* <li>can not preload or autoplay the audio</li>
|
|
* <li>can not cache the audio</li>
|
|
* <li>can not play the audio except inside a user initiated event.</li>
|
|
* <li>Note it is recommended to use {{#crossLink "WebAudioPlugin"}}{{/crossLink}} for iOS (6+)</li>
|
|
* <li>audio sprites can be used to mitigate some of these issues and are strongly recommended on iOS</li>
|
|
* </ul>
|
|
*
|
|
* <b>Android Native Browser limitations</b><br />
|
|
* <ul><li>We have no control over audio volume. Only the user can set volume on their device.</li>
|
|
* <li>We can only play audio inside a user event (touch/click). This currently means you cannot loop sound or use a delay.</li></ul>
|
|
* <b> Android Chrome 26.0.1410.58 specific limitations</b><br />
|
|
* <ul> <li>Can only play 1 sound at a time.</li>
|
|
* <li>Sound is not cached.</li>
|
|
* <li>Sound can only be loaded in a user initiated touch/click event.</li>
|
|
* <li>There is a delay before a sound is played, presumably while the src is loaded.</li>
|
|
* </ul>
|
|
*
|
|
* See {{#crossLink "Sound"}}{{/crossLink}} for general notes on known issues.
|
|
*
|
|
* @class HTMLAudioPlugin
|
|
* @extends AbstractPlugin
|
|
* @constructor
|
|
*/
|
|
function HTMLAudioPlugin() {
|
|
this.AbstractPlugin_constructor();
|
|
|
|
// Public Properties
|
|
/**
|
|
* This is no longer needed as we are now using object pooling for tags.
|
|
*
|
|
* <b>NOTE this property only exists as a limitation of HTML audio.</b>
|
|
* @property defaultNumChannels
|
|
* @type {Number}
|
|
* @default 2
|
|
* @since 0.4.0
|
|
* @deprecated
|
|
*/
|
|
this.defaultNumChannels = 2;
|
|
|
|
this._capabilities = s._capabilities;
|
|
|
|
this._loaderClass = createjs.SoundLoader;
|
|
this._soundInstanceClass = createjs.HTMLAudioSoundInstance;
|
|
}
|
|
|
|
var p = createjs.extend(HTMLAudioPlugin, createjs.AbstractPlugin);
|
|
var s = HTMLAudioPlugin;
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// Static Properties
|
|
/**
|
|
* The maximum number of instances that can be loaded or played. This is a browser limitation, primarily limited to IE9.
|
|
* The actual number varies from browser to browser (and is largely hardware dependant), but this is a safe estimate.
|
|
* Audio sprites work around this limitation.
|
|
* @property MAX_INSTANCES
|
|
* @type {Number}
|
|
* @default 30
|
|
* @static
|
|
*/
|
|
s.MAX_INSTANCES = 30;
|
|
|
|
/**
|
|
* Event constant for the "canPlayThrough" event for cleaner code.
|
|
* @property _AUDIO_READY
|
|
* @type {String}
|
|
* @default canplaythrough
|
|
* @static
|
|
* @protected
|
|
*/
|
|
s._AUDIO_READY = "canplaythrough";
|
|
|
|
/**
|
|
* Event constant for the "ended" event for cleaner code.
|
|
* @property _AUDIO_ENDED
|
|
* @type {String}
|
|
* @default ended
|
|
* @static
|
|
* @protected
|
|
*/
|
|
s._AUDIO_ENDED = "ended";
|
|
|
|
/**
|
|
* Event constant for the "seeked" event for cleaner code. We utilize this event for maintaining loop events.
|
|
* @property _AUDIO_SEEKED
|
|
* @type {String}
|
|
* @default seeked
|
|
* @static
|
|
* @protected
|
|
*/
|
|
s._AUDIO_SEEKED = "seeked";
|
|
|
|
/**
|
|
* Event constant for the "stalled" event for cleaner code.
|
|
* @property _AUDIO_STALLED
|
|
* @type {String}
|
|
* @default stalled
|
|
* @static
|
|
* @protected
|
|
*/
|
|
s._AUDIO_STALLED = "stalled";
|
|
|
|
/**
|
|
* Event constant for the "timeupdate" event for cleaner code. Utilized for looping audio sprites.
|
|
* This event callsback ever 15 to 250ms and can be dropped by the browser for performance.
|
|
* @property _TIME_UPDATE
|
|
* @type {String}
|
|
* @default timeupdate
|
|
* @static
|
|
* @protected
|
|
*/
|
|
s._TIME_UPDATE = "timeupdate";
|
|
|
|
/**
|
|
* The capabilities of the plugin. This is generated via the {{#crossLink "HTMLAudioPlugin/_generateCapabilities"}}{{/crossLink}}
|
|
* method. Please see the Sound {{#crossLink "Sound/getCapabilities"}}{{/crossLink}} method for an overview of all
|
|
* of the available properties.
|
|
* @property _capabilities
|
|
* @type {Object}
|
|
* @protected
|
|
* @static
|
|
*/
|
|
s._capabilities = null;
|
|
|
|
// Static Methods
|
|
/**
|
|
* Determine if the plugin can be used in the current browser/OS. Note that HTML audio is available in most modern
|
|
* browsers, but is disabled in iOS because of its limitations.
|
|
* @method isSupported
|
|
* @return {Boolean} If the plugin can be initialized.
|
|
* @static
|
|
*/
|
|
s.isSupported = function () {
|
|
s._generateCapabilities();
|
|
return s._capabilities != null;
|
|
};
|
|
|
|
/**
|
|
* Determine the capabilities of the plugin. Used internally. Please see the Sound API {{#crossLink "Sound/getCapabilities"}}{{/crossLink}}
|
|
* method for an overview of plugin capabilities.
|
|
* @method _generateCapabilities
|
|
* @static
|
|
* @protected
|
|
*/
|
|
s._generateCapabilities = function () {
|
|
if (s._capabilities != null) {
|
|
return;
|
|
}
|
|
var t = document.createElement("audio");
|
|
if (t.canPlayType == null) {
|
|
return null;
|
|
}
|
|
|
|
s._capabilities = {
|
|
panning: false,
|
|
volume: true,
|
|
tracks: -1,
|
|
};
|
|
|
|
// determine which extensions our browser supports for this plugin by iterating through Sound.SUPPORTED_EXTENSIONS
|
|
var supportedExtensions = createjs.Sound.SUPPORTED_EXTENSIONS;
|
|
var extensionMap = createjs.Sound.EXTENSION_MAP;
|
|
for (var i = 0, l = supportedExtensions.length; i < l; i++) {
|
|
var ext = supportedExtensions[i];
|
|
var playType = extensionMap[ext] || ext;
|
|
s._capabilities[ext] =
|
|
(t.canPlayType("audio/" + ext) != "no" &&
|
|
t.canPlayType("audio/" + ext) != "") ||
|
|
(t.canPlayType("audio/" + playType) != "no" &&
|
|
t.canPlayType("audio/" + playType) != "");
|
|
} // OJR another way to do this might be canPlayType:"m4a", codex: mp4
|
|
};
|
|
|
|
// public methods
|
|
p.register = function (loadItem) {
|
|
var tag = createjs.HTMLAudioTagPool.get(loadItem.src);
|
|
var loader = this.AbstractPlugin_register(loadItem);
|
|
loader.setTag(tag);
|
|
|
|
return loader;
|
|
};
|
|
|
|
p.removeSound = function (src) {
|
|
this.AbstractPlugin_removeSound(src);
|
|
createjs.HTMLAudioTagPool.remove(src);
|
|
};
|
|
|
|
p.create = function (src, startTime, duration) {
|
|
var si = this.AbstractPlugin_create(src, startTime, duration);
|
|
si.setPlaybackResource(null);
|
|
return si;
|
|
};
|
|
|
|
p.toString = function () {
|
|
return "[HTMLAudioPlugin]";
|
|
};
|
|
|
|
// plugin does not support these
|
|
p.setVolume = p.getVolume = p.setMute = null;
|
|
|
|
createjs.HTMLAudioPlugin = createjs.promote(
|
|
HTMLAudioPlugin,
|
|
"AbstractPlugin"
|
|
);
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Tween.js
|
|
//##############################################################################
|
|
|
|
// TODO: possibly add a END actionsMode (only runs actions that == position)?
|
|
// TODO: evaluate a way to decouple paused from tick registration.
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* A Tween instance tweens properties for a single target. Instance methods can be chained for easy construction and sequencing:
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* target.alpha = 1;
|
|
* createjs.Tween.get(target)
|
|
* .wait(500)
|
|
* .to({alpha:0, visible:false}, 1000)
|
|
* .call(handleComplete);
|
|
* function handleComplete() {
|
|
* //Tween complete
|
|
* }
|
|
*
|
|
* Multiple tweens can point to the same instance, however if they affect the same properties there could be unexpected
|
|
* behaviour. To stop all tweens on an object, use {{#crossLink "Tween/removeTweens"}}{{/crossLink}} or pass `override:true`
|
|
* in the props argument.
|
|
*
|
|
* createjs.Tween.get(target, {override:true}).to({x:100});
|
|
*
|
|
* Subscribe to the {{#crossLink "Tween/change:event"}}{{/crossLink}} event to get notified when a property of the
|
|
* target is changed.
|
|
*
|
|
* createjs.Tween.get(target, {override:true}).to({x:100}).addEventListener("change", handleChange);
|
|
* function handleChange(event) {
|
|
* // The tween changed.
|
|
* }
|
|
*
|
|
* See the Tween {{#crossLink "Tween/get"}}{{/crossLink}} method for additional param documentation.
|
|
* @class Tween
|
|
* @param {Object} target The target object that will have its properties tweened.
|
|
* @param {Object} [props] The configuration properties to apply to this tween instance (ex. `{loop:true, paused:true}`.
|
|
* All properties default to false. Supported props are:<UL>
|
|
* <LI> loop: sets the loop property on this tween.</LI>
|
|
* <LI> useTicks: uses ticks for all durations instead of milliseconds.</LI>
|
|
* <LI> ignoreGlobalPause: sets the {{#crossLink "Tween/ignoreGlobalPause:property"}}{{/crossLink}} property on this tween.</LI>
|
|
* <LI> override: if true, `Tween.removeTweens(target)` will be called to remove any other tweens with the same target.
|
|
* <LI> paused: indicates whether to start the tween paused.</LI>
|
|
* <LI> position: indicates the initial position for this tween.</LI>
|
|
* <LI> onChange: specifies a listener for the "change" event.</LI>
|
|
* </UL>
|
|
* @param {Object} [pluginData] An object containing data for use by installed plugins. See individual
|
|
* plugins' documentation for details.
|
|
* @extends EventDispatcher
|
|
* @constructor
|
|
*/
|
|
function Tween(target, props, pluginData) {
|
|
// public properties:
|
|
/**
|
|
* Causes this tween to continue playing when a global pause is active. For example, if TweenJS is using {{#crossLink "Ticker"}}{{/crossLink}},
|
|
* then setting this to true (the default) will cause this tween to be paused when <code>Ticker.setPaused(true)</code>
|
|
* is called. See the Tween {{#crossLink "Tween/tick"}}{{/crossLink}} method for more info. Can be set via the props
|
|
* parameter.
|
|
* @property ignoreGlobalPause
|
|
* @type Boolean
|
|
* @default false
|
|
*/
|
|
this.ignoreGlobalPause = false;
|
|
|
|
/**
|
|
* If true, the tween will loop when it reaches the end. Can be set via the props param.
|
|
* @property loop
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
this.loop = false;
|
|
|
|
/**
|
|
* Specifies the total duration of this tween in milliseconds (or ticks if useTicks is true).
|
|
* This value is automatically updated as you modify the tween. Changing it directly could result in unexpected
|
|
* behaviour.
|
|
* @property duration
|
|
* @type {Number}
|
|
* @default 0
|
|
* @readonly
|
|
*/
|
|
this.duration = 0;
|
|
|
|
/**
|
|
* Allows you to specify data that will be used by installed plugins. Each plugin uses this differently, but in general
|
|
* you specify data by setting it to a property of pluginData with the same name as the plugin class.
|
|
* @example
|
|
* myTween.pluginData.PluginClassName = data;
|
|
* <br/>
|
|
* Also, most plugins support a property to enable or disable them. This is typically the plugin class name followed by "_enabled".<br/>
|
|
* @example
|
|
* myTween.pluginData.PluginClassName_enabled = false;<br/>
|
|
* <br/>
|
|
* Some plugins also store instance data in this object, usually in a property named _PluginClassName.
|
|
* See the documentation for individual plugins for more details.
|
|
* @property pluginData
|
|
* @type {Object}
|
|
*/
|
|
this.pluginData = pluginData || {};
|
|
|
|
/**
|
|
* The target of this tween. This is the object on which the tweened properties will be changed. Changing
|
|
* this property after the tween is created will not have any effect.
|
|
* @property target
|
|
* @type {Object}
|
|
* @readonly
|
|
*/
|
|
this.target = target;
|
|
|
|
/**
|
|
* The current normalized position of the tween. This will always be a value between 0 and duration.
|
|
* Changing this property directly will have no effect.
|
|
* @property position
|
|
* @type {Object}
|
|
* @readonly
|
|
*/
|
|
this.position = null;
|
|
|
|
/**
|
|
* Indicates the tween's current position is within a passive wait.
|
|
* @property passive
|
|
* @type {Boolean}
|
|
* @default false
|
|
* @readonly
|
|
**/
|
|
this.passive = false;
|
|
|
|
// private properties:
|
|
/**
|
|
* @property _paused
|
|
* @type {Boolean}
|
|
* @default false
|
|
* @protected
|
|
*/
|
|
this._paused = false;
|
|
|
|
/**
|
|
* @property _curQueueProps
|
|
* @type {Object}
|
|
* @protected
|
|
*/
|
|
this._curQueueProps = {};
|
|
|
|
/**
|
|
* @property _initQueueProps
|
|
* @type {Object}
|
|
* @protected
|
|
*/
|
|
this._initQueueProps = {};
|
|
|
|
/**
|
|
* @property _steps
|
|
* @type {Array}
|
|
* @protected
|
|
*/
|
|
this._steps = [];
|
|
|
|
/**
|
|
* @property _actions
|
|
* @type {Array}
|
|
* @protected
|
|
*/
|
|
this._actions = [];
|
|
|
|
/**
|
|
* Raw position.
|
|
* @property _prevPosition
|
|
* @type {Number}
|
|
* @default 0
|
|
* @protected
|
|
*/
|
|
this._prevPosition = 0;
|
|
|
|
/**
|
|
* The position within the current step.
|
|
* @property _stepPosition
|
|
* @type {Number}
|
|
* @default 0
|
|
* @protected
|
|
*/
|
|
this._stepPosition = 0; // this is needed by MovieClip.
|
|
|
|
/**
|
|
* Normalized position.
|
|
* @property _prevPos
|
|
* @type {Number}
|
|
* @default -1
|
|
* @protected
|
|
*/
|
|
this._prevPos = -1;
|
|
|
|
/**
|
|
* @property _target
|
|
* @type {Object}
|
|
* @protected
|
|
*/
|
|
this._target = target;
|
|
|
|
/**
|
|
* @property _useTicks
|
|
* @type {Boolean}
|
|
* @default false
|
|
* @protected
|
|
*/
|
|
this._useTicks = false;
|
|
|
|
/**
|
|
* @property _inited
|
|
* @type {boolean}
|
|
* @default false
|
|
* @protected
|
|
*/
|
|
this._inited = false;
|
|
|
|
/**
|
|
* Indicates whether the tween is currently registered with Tween.
|
|
* @property _registered
|
|
* @type {boolean}
|
|
* @default false
|
|
* @protected
|
|
*/
|
|
this._registered = false;
|
|
|
|
if (props) {
|
|
this._useTicks = props.useTicks;
|
|
this.ignoreGlobalPause = props.ignoreGlobalPause;
|
|
this.loop = props.loop;
|
|
props.onChange && this.addEventListener("change", props.onChange);
|
|
if (props.override) {
|
|
Tween.removeTweens(target);
|
|
}
|
|
}
|
|
if (props && props.paused) {
|
|
this._paused = true;
|
|
} else {
|
|
createjs.Tween._register(this, true);
|
|
}
|
|
if (props && props.position != null) {
|
|
this.setPosition(props.position, Tween.NONE);
|
|
}
|
|
}
|
|
|
|
var p = createjs.extend(Tween, createjs.EventDispatcher);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// static properties
|
|
/**
|
|
* Constant defining the none actionsMode for use with setPosition.
|
|
* @property NONE
|
|
* @type Number
|
|
* @default 0
|
|
* @static
|
|
*/
|
|
Tween.NONE = 0;
|
|
|
|
/**
|
|
* Constant defining the loop actionsMode for use with setPosition.
|
|
* @property LOOP
|
|
* @type Number
|
|
* @default 1
|
|
* @static
|
|
*/
|
|
Tween.LOOP = 1;
|
|
|
|
/**
|
|
* Constant defining the reverse actionsMode for use with setPosition.
|
|
* @property REVERSE
|
|
* @type Number
|
|
* @default 2
|
|
* @static
|
|
*/
|
|
Tween.REVERSE = 2;
|
|
|
|
/**
|
|
* Constant returned by plugins to tell the tween not to use default assignment.
|
|
* @property IGNORE
|
|
* @type Object
|
|
* @static
|
|
*/
|
|
Tween.IGNORE = {};
|
|
|
|
/**
|
|
* @property _listeners
|
|
* @type Array[Tween]
|
|
* @static
|
|
* @protected
|
|
*/
|
|
Tween._tweens = [];
|
|
|
|
/**
|
|
* @property _plugins
|
|
* @type Object
|
|
* @static
|
|
* @protected
|
|
*/
|
|
Tween._plugins = {};
|
|
|
|
// static methods
|
|
/**
|
|
* Returns a new tween instance. This is functionally identical to using "new Tween(...)", but looks cleaner
|
|
* with the chained syntax of TweenJS.
|
|
* <h4>Example</h4>
|
|
*
|
|
* var tween = createjs.Tween.get(target);
|
|
*
|
|
* @method get
|
|
* @param {Object} target The target object that will have its properties tweened.
|
|
* @param {Object} [props] The configuration properties to apply to this tween instance (ex. `{loop:true, paused:true}`).
|
|
* All properties default to `false`. Supported props are:
|
|
* <UL>
|
|
* <LI> loop: sets the loop property on this tween.</LI>
|
|
* <LI> useTicks: uses ticks for all durations instead of milliseconds.</LI>
|
|
* <LI> ignoreGlobalPause: sets the {{#crossLink "Tween/ignoreGlobalPause:property"}}{{/crossLink}} property on
|
|
* this tween.</LI>
|
|
* <LI> override: if true, `createjs.Tween.removeTweens(target)` will be called to remove any other tweens with
|
|
* the same target.
|
|
* <LI> paused: indicates whether to start the tween paused.</LI>
|
|
* <LI> position: indicates the initial position for this tween.</LI>
|
|
* <LI> onChange: specifies a listener for the {{#crossLink "Tween/change:event"}}{{/crossLink}} event.</LI>
|
|
* </UL>
|
|
* @param {Object} [pluginData] An object containing data for use by installed plugins. See individual plugins'
|
|
* documentation for details.
|
|
* @param {Boolean} [override=false] If true, any previous tweens on the same target will be removed. This is the
|
|
* same as calling `Tween.removeTweens(target)`.
|
|
* @return {Tween} A reference to the created tween. Additional chained tweens, method calls, or callbacks can be
|
|
* applied to the returned tween instance.
|
|
* @static
|
|
*/
|
|
Tween.get = function (target, props, pluginData, override) {
|
|
if (override) {
|
|
Tween.removeTweens(target);
|
|
}
|
|
return new Tween(target, props, pluginData);
|
|
};
|
|
|
|
/**
|
|
* Advances all tweens. This typically uses the {{#crossLink "Ticker"}}{{/crossLink}} class, but you can call it
|
|
* manually if you prefer to use your own "heartbeat" implementation.
|
|
* @method tick
|
|
* @param {Number} delta The change in time in milliseconds since the last tick. Required unless all tweens have
|
|
* `useTicks` set to true.
|
|
* @param {Boolean} paused Indicates whether a global pause is in effect. Tweens with {{#crossLink "Tween/ignoreGlobalPause:property"}}{{/crossLink}}
|
|
* will ignore this, but all others will pause if this is `true`.
|
|
* @static
|
|
*/
|
|
Tween.tick = function (delta, paused) {
|
|
var tweens = Tween._tweens.slice(); // to avoid race conditions.
|
|
for (var i = tweens.length - 1; i >= 0; i--) {
|
|
var tween = tweens[i];
|
|
if ((paused && !tween.ignoreGlobalPause) || tween._paused) {
|
|
continue;
|
|
}
|
|
tween.tick(tween._useTicks ? 1 : delta);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle events that result from Tween being used as an event handler. This is included to allow Tween to handle
|
|
* {{#crossLink "Ticker/tick:event"}}{{/crossLink}} events from the createjs {{#crossLink "Ticker"}}{{/crossLink}}.
|
|
* No other events are handled in Tween.
|
|
* @method handleEvent
|
|
* @param {Object} event An event object passed in by the {{#crossLink "EventDispatcher"}}{{/crossLink}}. Will
|
|
* usually be of type "tick".
|
|
* @private
|
|
* @static
|
|
* @since 0.4.2
|
|
*/
|
|
Tween.handleEvent = function (event) {
|
|
if (event.type == "tick") {
|
|
this.tick(event.delta, event.paused);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Removes all existing tweens for a target. This is called automatically by new tweens if the `override`
|
|
* property is `true`.
|
|
* @method removeTweens
|
|
* @param {Object} target The target object to remove existing tweens from.
|
|
* @static
|
|
*/
|
|
Tween.removeTweens = function (target) {
|
|
if (!target.tweenjs_count) {
|
|
return;
|
|
}
|
|
var tweens = Tween._tweens;
|
|
for (var i = tweens.length - 1; i >= 0; i--) {
|
|
var tween = tweens[i];
|
|
if (tween._target == target) {
|
|
tween._paused = true;
|
|
tweens.splice(i, 1);
|
|
}
|
|
}
|
|
target.tweenjs_count = 0;
|
|
};
|
|
|
|
/**
|
|
* Stop and remove all existing tweens.
|
|
* @method removeAllTweens
|
|
* @static
|
|
* @since 0.4.1
|
|
*/
|
|
Tween.removeAllTweens = function () {
|
|
var tweens = Tween._tweens;
|
|
for (var i = 0, l = tweens.length; i < l; i++) {
|
|
var tween = tweens[i];
|
|
tween._paused = true;
|
|
tween.target && (tween.target.tweenjs_count = 0);
|
|
}
|
|
tweens.length = 0;
|
|
};
|
|
|
|
/**
|
|
* Indicates whether there are any active tweens (and how many) on the target object (if specified) or in general.
|
|
* @method hasActiveTweens
|
|
* @param {Object} [target] The target to check for active tweens. If not specified, the return value will indicate
|
|
* if there are any active tweens on any target.
|
|
* @return {Boolean} If there are active tweens.
|
|
* @static
|
|
*/
|
|
Tween.hasActiveTweens = function (target) {
|
|
if (target) {
|
|
return target.tweenjs_count != null && !!target.tweenjs_count;
|
|
}
|
|
return Tween._tweens && !!Tween._tweens.length;
|
|
};
|
|
|
|
/**
|
|
* Installs a plugin, which can modify how certain properties are handled when tweened. See the {{#crossLink "CSSPlugin"}}{{/crossLink}}
|
|
* for an example of how to write TweenJS plugins.
|
|
* @method installPlugin
|
|
* @static
|
|
* @param {Object} plugin The plugin class to install
|
|
* @param {Array} properties An array of properties that the plugin will handle.
|
|
*/
|
|
Tween.installPlugin = function (plugin, properties) {
|
|
var priority = plugin.priority;
|
|
if (priority == null) {
|
|
plugin.priority = priority = 0;
|
|
}
|
|
for (var i = 0, l = properties.length, p = Tween._plugins; i < l; i++) {
|
|
var n = properties[i];
|
|
if (!p[n]) {
|
|
p[n] = [plugin];
|
|
} else {
|
|
var arr = p[n];
|
|
for (var j = 0, jl = arr.length; j < jl; j++) {
|
|
if (priority < arr[j].priority) {
|
|
break;
|
|
}
|
|
}
|
|
p[n].splice(j, 0, plugin);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Registers or unregisters a tween with the ticking system.
|
|
* @method _register
|
|
* @param {Tween} tween The tween instance to register or unregister.
|
|
* @param {Boolean} value If `true`, the tween is registered. If `false` the tween is unregistered.
|
|
* @static
|
|
* @protected
|
|
*/
|
|
Tween._register = function (tween, value) {
|
|
var target = tween._target;
|
|
var tweens = Tween._tweens;
|
|
if (value && !tween._registered) {
|
|
// TODO: this approach might fail if a dev is using sealed objects in ES5
|
|
if (target) {
|
|
target.tweenjs_count = target.tweenjs_count
|
|
? target.tweenjs_count + 1
|
|
: 1;
|
|
}
|
|
tweens.push(tween);
|
|
if (!Tween._inited && createjs.Ticker) {
|
|
createjs.Ticker.addEventListener("tick", Tween);
|
|
Tween._inited = true;
|
|
}
|
|
} else if (!value && tween._registered) {
|
|
if (target) {
|
|
target.tweenjs_count--;
|
|
}
|
|
var i = tweens.length;
|
|
while (i--) {
|
|
if (tweens[i] == tween) {
|
|
tweens.splice(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
tween._registered = value;
|
|
};
|
|
|
|
// events:
|
|
/**
|
|
* Called whenever the tween's position changes.
|
|
* @event change
|
|
* @since 0.4.0
|
|
**/
|
|
|
|
// public methods:
|
|
/**
|
|
* Queues a wait (essentially an empty tween).
|
|
* <h4>Example</h4>
|
|
*
|
|
* //This tween will wait 1s before alpha is faded to 0.
|
|
* createjs.Tween.get(target).wait(1000).to({alpha:0}, 1000);
|
|
*
|
|
* @method wait
|
|
* @param {Number} duration The duration of the wait in milliseconds (or in ticks if `useTicks` is true).
|
|
* @param {Boolean} [passive] Tween properties will not be updated during a passive wait. This
|
|
* is mostly useful for use with {{#crossLink "Timeline"}}{{/crossLink}} instances that contain multiple tweens
|
|
* affecting the same target at different times.
|
|
* @return {Tween} This tween instance (for chaining calls).
|
|
**/
|
|
p.wait = function (duration, passive) {
|
|
if (duration == null || duration <= 0) {
|
|
return this;
|
|
}
|
|
var o = this._cloneProps(this._curQueueProps);
|
|
return this._addStep({
|
|
d: duration,
|
|
p0: o,
|
|
e: this._linearEase,
|
|
p1: o,
|
|
v: passive,
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Queues a tween from the current values to the target properties. Set duration to 0 to jump to these value.
|
|
* Numeric properties will be tweened from their current value in the tween to the target value. Non-numeric
|
|
* properties will be set at the end of the specified duration.
|
|
* <h4>Example</h4>
|
|
*
|
|
* createjs.Tween.get(target).to({alpha:0}, 1000);
|
|
*
|
|
* @method to
|
|
* @param {Object} props An object specifying property target values for this tween (Ex. `{x:300}` would tween the x
|
|
* property of the target to 300).
|
|
* @param {Number} [duration=0] The duration of the wait in milliseconds (or in ticks if `useTicks` is true).
|
|
* @param {Function} [ease="linear"] The easing function to use for this tween. See the {{#crossLink "Ease"}}{{/crossLink}}
|
|
* class for a list of built-in ease functions.
|
|
* @return {Tween} This tween instance (for chaining calls).
|
|
*/
|
|
p.to = function (props, duration, ease) {
|
|
if (isNaN(duration) || duration < 0) {
|
|
duration = 0;
|
|
}
|
|
return this._addStep({
|
|
d: duration || 0,
|
|
p0: this._cloneProps(this._curQueueProps),
|
|
e: ease,
|
|
p1: this._cloneProps(this._appendQueueProps(props)),
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Queues an action to call the specified function.
|
|
* <h4>Example</h4>
|
|
*
|
|
* //would call myFunction() after 1 second.
|
|
* myTween.wait(1000).call(myFunction);
|
|
*
|
|
* @method call
|
|
* @param {Function} callback The function to call.
|
|
* @param {Array} [params]. The parameters to call the function with. If this is omitted, then the function
|
|
* will be called with a single param pointing to this tween.
|
|
* @param {Object} [scope]. The scope to call the function in. If omitted, it will be called in the target's
|
|
* scope.
|
|
* @return {Tween} This tween instance (for chaining calls).
|
|
*/
|
|
p.call = function (callback, params, scope) {
|
|
return this._addAction({
|
|
f: callback,
|
|
p: params ? params : [this],
|
|
o: scope ? scope : this._target,
|
|
});
|
|
};
|
|
|
|
// TODO: add clarification between this and a 0 duration .to:
|
|
/**
|
|
* Queues an action to set the specified props on the specified target. If target is null, it will use this tween's
|
|
* target.
|
|
* <h4>Example</h4>
|
|
*
|
|
* myTween.wait(1000).set({visible:false},foo);
|
|
*
|
|
* @method set
|
|
* @param {Object} props The properties to set (ex. `{visible:false}`).
|
|
* @param {Object} [target] The target to set the properties on. If omitted, they will be set on the tween's target.
|
|
* @return {Tween} This tween instance (for chaining calls).
|
|
*/
|
|
p.set = function (props, target) {
|
|
return this._addAction({
|
|
f: this._set,
|
|
o: this,
|
|
p: [props, target ? target : this._target],
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Queues an action to play (unpause) the specified tween. This enables you to sequence multiple tweens.
|
|
* <h4>Example</h4>
|
|
*
|
|
* myTween.to({x:100},500).play(otherTween);
|
|
*
|
|
* @method play
|
|
* @param {Tween} tween The tween to play.
|
|
* @return {Tween} This tween instance (for chaining calls).
|
|
*/
|
|
p.play = function (tween) {
|
|
if (!tween) {
|
|
tween = this;
|
|
}
|
|
return this.call(tween.setPaused, [false], tween);
|
|
};
|
|
|
|
/**
|
|
* Queues an action to pause the specified tween.
|
|
* @method pause
|
|
* @param {Tween} tween The tween to pause. If null, it pauses this tween.
|
|
* @return {Tween} This tween instance (for chaining calls)
|
|
*/
|
|
p.pause = function (tween) {
|
|
if (!tween) {
|
|
tween = this;
|
|
}
|
|
return this.call(tween.setPaused, [true], tween);
|
|
};
|
|
|
|
/**
|
|
* Advances the tween to a specified position.
|
|
* @method setPosition
|
|
* @param {Number} value The position to seek to in milliseconds (or ticks if useTicks is true).
|
|
* @param {Number} [actionsMode=1] Specifies how actions are handled (ie. call, set, play, pause):
|
|
* <ul>
|
|
* <li>{{#crossLink "Tween/NONE:property"}}{{/crossLink}} (0) - run no actions.</li>
|
|
* <li>{{#crossLink "Tween/LOOP:property"}}{{/crossLink}} (1) - if new position is less than old, then run all
|
|
* actions between old and duration, then all actions between 0 and new.</li>
|
|
* <li>{{#crossLink "Tween/REVERSE:property"}}{{/crossLink}} (2) - if new position is less than old, run all
|
|
* actions between them in reverse.</li>
|
|
* </ul>
|
|
* @return {Boolean} Returns `true` if the tween is complete (ie. the full tween has run & {{#crossLink "Tween/loop:property"}}{{/crossLink}}
|
|
* is `false`).
|
|
*/
|
|
p.setPosition = function (value, actionsMode) {
|
|
if (value < 0) {
|
|
value = 0;
|
|
}
|
|
if (actionsMode == null) {
|
|
actionsMode = 1;
|
|
}
|
|
|
|
// normalize position:
|
|
var t = value;
|
|
var end = false;
|
|
if (t >= this.duration) {
|
|
if (this.loop) {
|
|
t = t % this.duration;
|
|
} else {
|
|
t = this.duration;
|
|
end = true;
|
|
}
|
|
}
|
|
if (t == this._prevPos) {
|
|
return end;
|
|
}
|
|
|
|
var prevPos = this._prevPos;
|
|
this.position = this._prevPos = t; // set this in advance in case an action modifies position.
|
|
this._prevPosition = value;
|
|
|
|
// handle tweens:
|
|
if (this._target) {
|
|
if (end) {
|
|
// addresses problems with an ending zero length step.
|
|
this._updateTargetProps(null, 1);
|
|
} else if (this._steps.length > 0) {
|
|
// find our new tween index:
|
|
for (var i = 0, l = this._steps.length; i < l; i++) {
|
|
if (this._steps[i].t > t) {
|
|
break;
|
|
}
|
|
}
|
|
var step = this._steps[i - 1];
|
|
this._updateTargetProps(
|
|
step,
|
|
(this._stepPosition = t - step.t) / step.d
|
|
);
|
|
}
|
|
}
|
|
|
|
// run actions:
|
|
if (actionsMode != 0 && this._actions.length > 0) {
|
|
if (this._useTicks) {
|
|
// only run the actions we landed on.
|
|
this._runActions(t, t);
|
|
} else if (actionsMode == 1 && t < prevPos) {
|
|
if (prevPos != this.duration) {
|
|
this._runActions(prevPos, this.duration);
|
|
}
|
|
this._runActions(0, t, true);
|
|
} else {
|
|
this._runActions(prevPos, t);
|
|
}
|
|
}
|
|
|
|
if (end) {
|
|
this.setPaused(true);
|
|
}
|
|
|
|
this.dispatchEvent("change");
|
|
return end;
|
|
};
|
|
|
|
/**
|
|
* Advances this tween by the specified amount of time in milliseconds (or ticks if`useTicks` is `true`).
|
|
* This is normally called automatically by the Tween engine (via {{#crossLink "Tween/tick"}}{{/crossLink}}), but is
|
|
* exposed for advanced uses.
|
|
* @method tick
|
|
* @param {Number} delta The time to advance in milliseconds (or ticks if `useTicks` is `true`).
|
|
*/
|
|
p.tick = function (delta) {
|
|
if (this._paused) {
|
|
return;
|
|
}
|
|
this.setPosition(this._prevPosition + delta);
|
|
};
|
|
|
|
/**
|
|
* Pauses or plays this tween.
|
|
* @method setPaused
|
|
* @param {Boolean} [value=true] Indicates whether the tween should be paused (`true`) or played (`false`).
|
|
* @return {Tween} This tween instance (for chaining calls)
|
|
*/
|
|
p.setPaused = function (value) {
|
|
if (this._paused === !!value) {
|
|
return this;
|
|
}
|
|
this._paused = !!value;
|
|
Tween._register(this, !value);
|
|
return this;
|
|
};
|
|
|
|
// tiny api (primarily for tool output):
|
|
p.w = p.wait;
|
|
p.t = p.to;
|
|
p.c = p.call;
|
|
p.s = p.set;
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
*/
|
|
p.toString = function () {
|
|
return "[Tween]";
|
|
};
|
|
|
|
/**
|
|
* @method clone
|
|
* @protected
|
|
*/
|
|
p.clone = function () {
|
|
throw "Tween can not be cloned.";
|
|
};
|
|
|
|
// private methods:
|
|
/**
|
|
* @method _updateTargetProps
|
|
* @param {Object} step
|
|
* @param {Number} ratio
|
|
* @protected
|
|
*/
|
|
p._updateTargetProps = function (step, ratio) {
|
|
var p0, p1, v, v0, v1, arr;
|
|
if (!step && ratio == 1) {
|
|
// GDS: when does this run? Just at the very end? Shouldn't.
|
|
this.passive = false;
|
|
p0 = p1 = this._curQueueProps;
|
|
} else {
|
|
this.passive = !!step.v;
|
|
if (this.passive) {
|
|
return;
|
|
} // don't update props.
|
|
// apply ease to ratio.
|
|
if (step.e) {
|
|
ratio = step.e(ratio, 0, 1, 1);
|
|
}
|
|
p0 = step.p0;
|
|
p1 = step.p1;
|
|
}
|
|
|
|
for (var n in this._initQueueProps) {
|
|
if ((v0 = p0[n]) == null) {
|
|
p0[n] = v0 = this._initQueueProps[n];
|
|
}
|
|
if ((v1 = p1[n]) == null) {
|
|
p1[n] = v1 = v0;
|
|
}
|
|
if (v0 == v1 || ratio == 0 || ratio == 1 || typeof v0 != "number") {
|
|
// no interpolation - either at start, end, values don't change, or the value is non-numeric.
|
|
v = ratio == 1 ? v1 : v0;
|
|
} else {
|
|
v = v0 + (v1 - v0) * ratio;
|
|
}
|
|
|
|
var ignore = false;
|
|
if ((arr = Tween._plugins[n])) {
|
|
for (var i = 0, l = arr.length; i < l; i++) {
|
|
var v2 = arr[i].tween(
|
|
this,
|
|
n,
|
|
v,
|
|
p0,
|
|
p1,
|
|
ratio,
|
|
!!step && p0 == p1,
|
|
!step
|
|
);
|
|
if (v2 == Tween.IGNORE) {
|
|
ignore = true;
|
|
} else {
|
|
v = v2;
|
|
}
|
|
}
|
|
}
|
|
if (!ignore) {
|
|
this._target[n] = v;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _runActions
|
|
* @param {Number} startPos
|
|
* @param {Number} endPos
|
|
* @param {Boolean} includeStart
|
|
* @protected
|
|
*/
|
|
p._runActions = function (startPos, endPos, includeStart) {
|
|
var sPos = startPos;
|
|
var ePos = endPos;
|
|
var i = -1;
|
|
var j = this._actions.length;
|
|
var k = 1;
|
|
if (startPos > endPos) {
|
|
// running backwards, flip everything:
|
|
sPos = endPos;
|
|
ePos = startPos;
|
|
i = j;
|
|
j = k = -1;
|
|
}
|
|
while ((i += k) != j) {
|
|
var action = this._actions[i];
|
|
var pos = action.t;
|
|
if (
|
|
pos == ePos ||
|
|
(pos > sPos && pos < ePos) ||
|
|
(includeStart && pos == startPos)
|
|
) {
|
|
action.f.apply(action.o, action.p);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _appendQueueProps
|
|
* @param {Object} o
|
|
* @protected
|
|
*/
|
|
p._appendQueueProps = function (o) {
|
|
var arr, oldValue, i, l, injectProps;
|
|
for (var n in o) {
|
|
if (this._initQueueProps[n] === undefined) {
|
|
oldValue = this._target[n];
|
|
|
|
// init plugins:
|
|
if ((arr = Tween._plugins[n])) {
|
|
for (i = 0, l = arr.length; i < l; i++) {
|
|
oldValue = arr[i].init(this, n, oldValue);
|
|
}
|
|
}
|
|
this._initQueueProps[n] = this._curQueueProps[n] =
|
|
oldValue === undefined ? null : oldValue;
|
|
} else {
|
|
oldValue = this._curQueueProps[n];
|
|
}
|
|
}
|
|
|
|
for (var n in o) {
|
|
oldValue = this._curQueueProps[n];
|
|
if ((arr = Tween._plugins[n])) {
|
|
injectProps = injectProps || {};
|
|
for (i = 0, l = arr.length; i < l; i++) {
|
|
// TODO: remove the check for .step in the next version. It's here for backwards compatibility.
|
|
if (arr[i].step) {
|
|
arr[i].step(this, n, oldValue, o[n], injectProps);
|
|
}
|
|
}
|
|
}
|
|
this._curQueueProps[n] = o[n];
|
|
}
|
|
if (injectProps) {
|
|
this._appendQueueProps(injectProps);
|
|
}
|
|
return this._curQueueProps;
|
|
};
|
|
|
|
/**
|
|
* @method _cloneProps
|
|
* @param {Object} props
|
|
* @protected
|
|
*/
|
|
p._cloneProps = function (props) {
|
|
var o = {};
|
|
for (var n in props) {
|
|
o[n] = props[n];
|
|
}
|
|
return o;
|
|
};
|
|
|
|
/**
|
|
* @method _addStep
|
|
* @param {Object} o
|
|
* @protected
|
|
*/
|
|
p._addStep = function (o) {
|
|
if (o.d > 0) {
|
|
this._steps.push(o);
|
|
o.t = this.duration;
|
|
this.duration += o.d;
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* @method _addAction
|
|
* @param {Object} o
|
|
* @protected
|
|
*/
|
|
p._addAction = function (o) {
|
|
o.t = this.duration;
|
|
this._actions.push(o);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* @method _set
|
|
* @param {Object} props
|
|
* @param {Object} o
|
|
* @protected
|
|
*/
|
|
p._set = function (props, o) {
|
|
for (var n in props) {
|
|
o[n] = props[n];
|
|
}
|
|
};
|
|
|
|
createjs.Tween = createjs.promote(Tween, "EventDispatcher");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Timeline.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
// constructor
|
|
/**
|
|
* The Timeline class synchronizes multiple tweens and allows them to be controlled as a group. Please note that if a
|
|
* timeline is looping, the tweens on it may appear to loop even if the "loop" property of the tween is false.
|
|
* @class Timeline
|
|
* @param {Array} tweens An array of Tweens to add to this timeline. See {{#crossLink "Timeline/addTween"}}{{/crossLink}}
|
|
* for more info.
|
|
* @param {Object} labels An object defining labels for using {{#crossLink "Timeline/gotoAndPlay"}}{{/crossLink}}/{{#crossLink "Timeline/gotoAndStop"}}{{/crossLink}}.
|
|
* See {{#crossLink "Timeline/setLabels"}}{{/crossLink}}
|
|
* for details.
|
|
* @param {Object} props The configuration properties to apply to this tween instance (ex. `{loop:true}`). All properties
|
|
* default to false. Supported props are:<UL>
|
|
* <LI> loop: sets the loop property on this tween.</LI>
|
|
* <LI> useTicks: uses ticks for all durations instead of milliseconds.</LI>
|
|
* <LI> ignoreGlobalPause: sets the ignoreGlobalPause property on this tween.</LI>
|
|
* <LI> paused: indicates whether to start the tween paused.</LI>
|
|
* <LI> position: indicates the initial position for this timeline.</LI>
|
|
* <LI> onChange: specifies a listener to add for the {{#crossLink "Timeline/change:event"}}{{/crossLink}} event.</LI>
|
|
* </UL>
|
|
* @extends EventDispatcher
|
|
* @constructor
|
|
**/
|
|
function Timeline(tweens, labels, props) {
|
|
this.EventDispatcher_constructor();
|
|
|
|
// public properties:
|
|
/**
|
|
* Causes this timeline to continue playing when a global pause is active.
|
|
* @property ignoreGlobalPause
|
|
* @type Boolean
|
|
**/
|
|
this.ignoreGlobalPause = false;
|
|
|
|
/**
|
|
* The total duration of this timeline in milliseconds (or ticks if `useTicks `is `true`). This value is usually
|
|
* automatically updated as you modify the timeline. See {{#crossLink "Timeline/updateDuration"}}{{/crossLink}}
|
|
* for more information.
|
|
* @property duration
|
|
* @type Number
|
|
* @default 0
|
|
* @readonly
|
|
**/
|
|
this.duration = 0;
|
|
|
|
/**
|
|
* If true, the timeline will loop when it reaches the end. Can be set via the props param.
|
|
* @property loop
|
|
* @type Boolean
|
|
**/
|
|
this.loop = false;
|
|
|
|
/**
|
|
* The current normalized position of the timeline. This will always be a value between 0 and
|
|
* {{#crossLink "Timeline/duration:property"}}{{/crossLink}}.
|
|
* Changing this property directly will have no effect.
|
|
* @property position
|
|
* @type Object
|
|
* @readonly
|
|
**/
|
|
this.position = null;
|
|
|
|
// private properties:
|
|
/**
|
|
* @property _paused
|
|
* @type Boolean
|
|
* @protected
|
|
**/
|
|
this._paused = false;
|
|
|
|
/**
|
|
* @property _tweens
|
|
* @type Array[Tween]
|
|
* @protected
|
|
**/
|
|
this._tweens = [];
|
|
|
|
/**
|
|
* @property _labels
|
|
* @type Object
|
|
* @protected
|
|
**/
|
|
this._labels = null;
|
|
|
|
/**
|
|
* @property _labelList
|
|
* @type Array[Object]
|
|
* @protected
|
|
**/
|
|
this._labelList = null;
|
|
|
|
/**
|
|
* @property _prevPosition
|
|
* @type Number
|
|
* @default 0
|
|
* @protected
|
|
**/
|
|
this._prevPosition = 0;
|
|
|
|
/**
|
|
* @property _prevPos
|
|
* @type Number
|
|
* @default -1
|
|
* @protected
|
|
**/
|
|
this._prevPos = -1;
|
|
|
|
/**
|
|
* @property _useTicks
|
|
* @type Boolean
|
|
* @default false
|
|
* @protected
|
|
**/
|
|
this._useTicks = false;
|
|
|
|
/**
|
|
* Indicates whether the timeline is currently registered with Tween.
|
|
* @property _registered
|
|
* @type {boolean}
|
|
* @default false
|
|
* @protected
|
|
*/
|
|
this._registered = false;
|
|
|
|
if (props) {
|
|
this._useTicks = props.useTicks;
|
|
this.loop = props.loop;
|
|
this.ignoreGlobalPause = props.ignoreGlobalPause;
|
|
props.onChange && this.addEventListener("change", props.onChange);
|
|
}
|
|
if (tweens) {
|
|
this.addTween.apply(this, tweens);
|
|
}
|
|
this.setLabels(labels);
|
|
if (props && props.paused) {
|
|
this._paused = true;
|
|
} else {
|
|
createjs.Tween._register(this, true);
|
|
}
|
|
if (props && props.position != null) {
|
|
this.setPosition(props.position, createjs.Tween.NONE);
|
|
}
|
|
}
|
|
|
|
var p = createjs.extend(Timeline, createjs.EventDispatcher);
|
|
|
|
// TODO: deprecated
|
|
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
|
|
|
|
// events:
|
|
/**
|
|
* Called whenever the timeline's position changes.
|
|
* @event change
|
|
* @since 0.5.0
|
|
**/
|
|
|
|
// public methods:
|
|
/**
|
|
* Adds one or more tweens (or timelines) to this timeline. The tweens will be paused (to remove them from the
|
|
* normal ticking system) and managed by this timeline. Adding a tween to multiple timelines will result in
|
|
* unexpected behaviour.
|
|
* @method addTween
|
|
* @param {Tween} ...tween The tween(s) to add. Accepts multiple arguments.
|
|
* @return {Tween} The first tween that was passed in.
|
|
**/
|
|
p.addTween = function (tween) {
|
|
var l = arguments.length;
|
|
if (l > 1) {
|
|
for (var i = 0; i < l; i++) {
|
|
this.addTween(arguments[i]);
|
|
}
|
|
return arguments[0];
|
|
} else if (l == 0) {
|
|
return null;
|
|
}
|
|
this.removeTween(tween);
|
|
this._tweens.push(tween);
|
|
tween.setPaused(true);
|
|
tween._paused = false;
|
|
tween._useTicks = this._useTicks;
|
|
if (tween.duration > this.duration) {
|
|
this.duration = tween.duration;
|
|
}
|
|
if (this._prevPos >= 0) {
|
|
tween.setPosition(this._prevPos, createjs.Tween.NONE);
|
|
}
|
|
return tween;
|
|
};
|
|
|
|
/**
|
|
* Removes one or more tweens from this timeline.
|
|
* @method removeTween
|
|
* @param {Tween} ...tween The tween(s) to remove. Accepts multiple arguments.
|
|
* @return Boolean Returns `true` if all of the tweens were successfully removed.
|
|
**/
|
|
p.removeTween = function (tween) {
|
|
var l = arguments.length;
|
|
if (l > 1) {
|
|
var good = true;
|
|
for (var i = 0; i < l; i++) {
|
|
good = good && this.removeTween(arguments[i]);
|
|
}
|
|
return good;
|
|
} else if (l == 0) {
|
|
return false;
|
|
}
|
|
|
|
var tweens = this._tweens;
|
|
var i = tweens.length;
|
|
while (i--) {
|
|
if (tweens[i] == tween) {
|
|
tweens.splice(i, 1);
|
|
if (tween.duration >= this.duration) {
|
|
this.updateDuration();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Adds a label that can be used with {{#crossLink "Timeline/gotoAndPlay"}}{{/crossLink}}/{{#crossLink "Timeline/gotoAndStop"}}{{/crossLink}}.
|
|
* @method addLabel
|
|
* @param {String} label The label name.
|
|
* @param {Number} position The position this label represents.
|
|
**/
|
|
p.addLabel = function (label, position) {
|
|
this._labels[label] = position;
|
|
var list = this._labelList;
|
|
if (list) {
|
|
for (var i = 0, l = list.length; i < l; i++) {
|
|
if (position < list[i].position) {
|
|
break;
|
|
}
|
|
}
|
|
list.splice(i, 0, { label: label, position: position });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Defines labels for use with gotoAndPlay/Stop. Overwrites any previously set labels.
|
|
* @method setLabels
|
|
* @param {Object} o An object defining labels for using {{#crossLink "Timeline/gotoAndPlay"}}{{/crossLink}}/{{#crossLink "Timeline/gotoAndStop"}}{{/crossLink}}
|
|
* in the form `{labelName:time}` where time is in milliseconds (or ticks if `useTicks` is `true`).
|
|
**/
|
|
p.setLabels = function (o) {
|
|
this._labels = o ? o : {};
|
|
};
|
|
|
|
/**
|
|
* Returns a sorted list of the labels defined on this timeline.
|
|
* @method getLabels
|
|
* @return {Array[Object]} A sorted array of objects with label and position properties.
|
|
**/
|
|
p.getLabels = function () {
|
|
var list = this._labelList;
|
|
if (!list) {
|
|
list = this._labelList = [];
|
|
var labels = this._labels;
|
|
for (var n in labels) {
|
|
list.push({ label: n, position: labels[n] });
|
|
}
|
|
list.sort(function (a, b) {
|
|
return a.position - b.position;
|
|
});
|
|
}
|
|
return list;
|
|
};
|
|
|
|
/**
|
|
* Returns the name of the label on or immediately before the current position. For example, given a timeline with
|
|
* two labels, "first" on frame index 4, and "second" on frame 8, getCurrentLabel would return:
|
|
* <UL>
|
|
* <LI>null if the current position is 2.</LI>
|
|
* <LI>"first" if the current position is 4.</LI>
|
|
* <LI>"first" if the current position is 7.</LI>
|
|
* <LI>"second" if the current position is 15.</LI>
|
|
* </UL>
|
|
* @method getCurrentLabel
|
|
* @return {String} The name of the current label or null if there is no label
|
|
**/
|
|
p.getCurrentLabel = function () {
|
|
var labels = this.getLabels();
|
|
var pos = this.position;
|
|
var l = labels.length;
|
|
if (l) {
|
|
for (var i = 0; i < l; i++) {
|
|
if (pos < labels[i].position) {
|
|
break;
|
|
}
|
|
}
|
|
return i == 0 ? null : labels[i - 1].label;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Unpauses this timeline and jumps to the specified position or label.
|
|
* @method gotoAndPlay
|
|
* @param {String|Number} positionOrLabel The position in milliseconds (or ticks if `useTicks` is `true`)
|
|
* or label to jump to.
|
|
**/
|
|
p.gotoAndPlay = function (positionOrLabel) {
|
|
this.setPaused(false);
|
|
this._goto(positionOrLabel);
|
|
};
|
|
|
|
/**
|
|
* Pauses this timeline and jumps to the specified position or label.
|
|
* @method gotoAndStop
|
|
* @param {String|Number} positionOrLabel The position in milliseconds (or ticks if `useTicks` is `true`) or label
|
|
* to jump to.
|
|
**/
|
|
p.gotoAndStop = function (positionOrLabel) {
|
|
this.setPaused(true);
|
|
this._goto(positionOrLabel);
|
|
};
|
|
|
|
/**
|
|
* Advances the timeline to the specified position.
|
|
* @method setPosition
|
|
* @param {Number} value The position to seek to in milliseconds (or ticks if `useTicks` is `true`).
|
|
* @param {Number} [actionsMode] parameter specifying how actions are handled. See the Tween {{#crossLink "Tween/setPosition"}}{{/crossLink}}
|
|
* method for more details.
|
|
* @return {Boolean} Returns `true` if the timeline is complete (ie. the full timeline has run & {{#crossLink "Timeline/loop:property"}}{{/crossLink}}
|
|
* is `false`).
|
|
**/
|
|
p.setPosition = function (value, actionsMode) {
|
|
var t = this._calcPosition(value);
|
|
var end = !this.loop && value >= this.duration;
|
|
if (t == this._prevPos) {
|
|
return end;
|
|
}
|
|
this._prevPosition = value;
|
|
this.position = this._prevPos = t; // in case an action changes the current frame.
|
|
for (var i = 0, l = this._tweens.length; i < l; i++) {
|
|
this._tweens[i].setPosition(t, actionsMode);
|
|
if (t != this._prevPos) {
|
|
return false;
|
|
} // an action changed this timeline's position.
|
|
}
|
|
if (end) {
|
|
this.setPaused(true);
|
|
}
|
|
this.dispatchEvent("change");
|
|
return end;
|
|
};
|
|
|
|
/**
|
|
* Pauses or plays this timeline.
|
|
* @method setPaused
|
|
* @param {Boolean} value Indicates whether the tween should be paused (`true`) or played (`false`).
|
|
**/
|
|
p.setPaused = function (value) {
|
|
this._paused = !!value;
|
|
createjs.Tween._register(this, !value);
|
|
};
|
|
|
|
/**
|
|
* Recalculates the duration of the timeline. The duration is automatically updated when tweens are added or removed,
|
|
* but this method is useful if you modify a tween after it was added to the timeline.
|
|
* @method updateDuration
|
|
**/
|
|
p.updateDuration = function () {
|
|
this.duration = 0;
|
|
for (var i = 0, l = this._tweens.length; i < l; i++) {
|
|
var tween = this._tweens[i];
|
|
if (tween.duration > this.duration) {
|
|
this.duration = tween.duration;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Advances this timeline by the specified amount of time in milliseconds (or ticks if `useTicks` is `true`).
|
|
* This is normally called automatically by the Tween engine (via the {{#crossLink "Tween/tick:event"}}{{/crossLink}}
|
|
* event), but is exposed for advanced uses.
|
|
* @method tick
|
|
* @param {Number} delta The time to advance in milliseconds (or ticks if useTicks is true).
|
|
**/
|
|
p.tick = function (delta) {
|
|
this.setPosition(this._prevPosition + delta);
|
|
};
|
|
|
|
/**
|
|
* If a numeric position is passed, it is returned unchanged. If a string is passed, the position of the
|
|
* corresponding frame label will be returned, or `null` if a matching label is not defined.
|
|
* @method resolve
|
|
* @param {String|Number} positionOrLabel A numeric position value or label string.
|
|
**/
|
|
p.resolve = function (positionOrLabel) {
|
|
var pos = Number(positionOrLabel);
|
|
if (isNaN(pos)) {
|
|
pos = this._labels[positionOrLabel];
|
|
}
|
|
return pos;
|
|
};
|
|
|
|
/**
|
|
* Returns a string representation of this object.
|
|
* @method toString
|
|
* @return {String} a string representation of the instance.
|
|
**/
|
|
p.toString = function () {
|
|
return "[Timeline]";
|
|
};
|
|
|
|
/**
|
|
* @method clone
|
|
* @protected
|
|
**/
|
|
p.clone = function () {
|
|
throw "Timeline can not be cloned.";
|
|
};
|
|
|
|
// private methods:
|
|
/**
|
|
* @method _goto
|
|
* @param {String | Number} positionOrLabel
|
|
* @protected
|
|
**/
|
|
p._goto = function (positionOrLabel) {
|
|
var pos = this.resolve(positionOrLabel);
|
|
if (pos != null) {
|
|
this.setPosition(pos);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method _calcPosition
|
|
* @param {Number} value
|
|
* @return {Number}
|
|
* @protected
|
|
**/
|
|
p._calcPosition = function (value) {
|
|
if (value < 0) {
|
|
return 0;
|
|
}
|
|
if (value < this.duration) {
|
|
return value;
|
|
}
|
|
return this.loop ? value % this.duration : this.duration;
|
|
};
|
|
|
|
createjs.Timeline = createjs.promote(Timeline, "EventDispatcher");
|
|
})();
|
|
|
|
//##############################################################################
|
|
// Ease.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* The Ease class provides a collection of easing functions for use with TweenJS. It does not use the standard 4 param
|
|
* easing signature. Instead it uses a single param which indicates the current linear ratio (0 to 1) of the tween.
|
|
*
|
|
* Most methods on Ease can be passed directly as easing functions:
|
|
*
|
|
* Tween.get(target).to({x:100}, 500, Ease.linear);
|
|
*
|
|
* However, methods beginning with "get" will return an easing function based on parameter values:
|
|
*
|
|
* Tween.get(target).to({y:200}, 500, Ease.getPowIn(2.2));
|
|
*
|
|
* Please see the <a href="http://www.createjs.com/Demos/TweenJS/Tween_SparkTable">spark table demo</a> for an
|
|
* overview of the different ease types on <a href="http://tweenjs.com">TweenJS.com</a>.
|
|
*
|
|
* <em>Equations derived from work by Robert Penner.</em>
|
|
* @class Ease
|
|
* @static
|
|
**/
|
|
function Ease() {
|
|
throw "Ease cannot be instantiated.";
|
|
}
|
|
|
|
// static methods and properties
|
|
/**
|
|
* @method linear
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.linear = function (t) {
|
|
return t;
|
|
};
|
|
|
|
/**
|
|
* Identical to linear.
|
|
* @method none
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.none = Ease.linear;
|
|
|
|
/**
|
|
* Mimics the simple -100 to 100 easing in Flash Pro.
|
|
* @method get
|
|
* @param {Number} amount A value from -1 (ease in) to 1 (ease out) indicating the strength and direction of the ease.
|
|
* @static
|
|
* @return {Function}
|
|
**/
|
|
Ease.get = function (amount) {
|
|
if (amount < -1) {
|
|
amount = -1;
|
|
}
|
|
if (amount > 1) {
|
|
amount = 1;
|
|
}
|
|
return function (t) {
|
|
if (amount == 0) {
|
|
return t;
|
|
}
|
|
if (amount < 0) {
|
|
return t * (t * -amount + 1 + amount);
|
|
}
|
|
return t * ((2 - t) * amount + (1 - amount));
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Configurable exponential ease.
|
|
* @method getPowIn
|
|
* @param {Number} pow The exponent to use (ex. 3 would return a cubic ease).
|
|
* @static
|
|
* @return {Function}
|
|
**/
|
|
Ease.getPowIn = function (pow) {
|
|
return function (t) {
|
|
return Math.pow(t, pow);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Configurable exponential ease.
|
|
* @method getPowOut
|
|
* @param {Number} pow The exponent to use (ex. 3 would return a cubic ease).
|
|
* @static
|
|
* @return {Function}
|
|
**/
|
|
Ease.getPowOut = function (pow) {
|
|
return function (t) {
|
|
return 1 - Math.pow(1 - t, pow);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Configurable exponential ease.
|
|
* @method getPowInOut
|
|
* @param {Number} pow The exponent to use (ex. 3 would return a cubic ease).
|
|
* @static
|
|
* @return {Function}
|
|
**/
|
|
Ease.getPowInOut = function (pow) {
|
|
return function (t) {
|
|
if ((t *= 2) < 1) return 0.5 * Math.pow(t, pow);
|
|
return 1 - 0.5 * Math.abs(Math.pow(2 - t, pow));
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @method quadIn
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.quadIn = Ease.getPowIn(2);
|
|
/**
|
|
* @method quadOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.quadOut = Ease.getPowOut(2);
|
|
/**
|
|
* @method quadInOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.quadInOut = Ease.getPowInOut(2);
|
|
|
|
/**
|
|
* @method cubicIn
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.cubicIn = Ease.getPowIn(3);
|
|
/**
|
|
* @method cubicOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.cubicOut = Ease.getPowOut(3);
|
|
/**
|
|
* @method cubicInOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.cubicInOut = Ease.getPowInOut(3);
|
|
|
|
/**
|
|
* @method quartIn
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.quartIn = Ease.getPowIn(4);
|
|
/**
|
|
* @method quartOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.quartOut = Ease.getPowOut(4);
|
|
/**
|
|
* @method quartInOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.quartInOut = Ease.getPowInOut(4);
|
|
|
|
/**
|
|
* @method quintIn
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.quintIn = Ease.getPowIn(5);
|
|
/**
|
|
* @method quintOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.quintOut = Ease.getPowOut(5);
|
|
/**
|
|
* @method quintInOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.quintInOut = Ease.getPowInOut(5);
|
|
|
|
/**
|
|
* @method sineIn
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.sineIn = function (t) {
|
|
return 1 - Math.cos((t * Math.PI) / 2);
|
|
};
|
|
|
|
/**
|
|
* @method sineOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.sineOut = function (t) {
|
|
return Math.sin((t * Math.PI) / 2);
|
|
};
|
|
|
|
/**
|
|
* @method sineInOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.sineInOut = function (t) {
|
|
return -0.5 * (Math.cos(Math.PI * t) - 1);
|
|
};
|
|
|
|
/**
|
|
* Configurable "back in" ease.
|
|
* @method getBackIn
|
|
* @param {Number} amount The strength of the ease.
|
|
* @static
|
|
* @return {Function}
|
|
**/
|
|
Ease.getBackIn = function (amount) {
|
|
return function (t) {
|
|
return t * t * ((amount + 1) * t - amount);
|
|
};
|
|
};
|
|
/**
|
|
* @method backIn
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.backIn = Ease.getBackIn(1.7);
|
|
|
|
/**
|
|
* Configurable "back out" ease.
|
|
* @method getBackOut
|
|
* @param {Number} amount The strength of the ease.
|
|
* @static
|
|
* @return {Function}
|
|
**/
|
|
Ease.getBackOut = function (amount) {
|
|
return function (t) {
|
|
return --t * t * ((amount + 1) * t + amount) + 1;
|
|
};
|
|
};
|
|
/**
|
|
* @method backOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.backOut = Ease.getBackOut(1.7);
|
|
|
|
/**
|
|
* Configurable "back in out" ease.
|
|
* @method getBackInOut
|
|
* @param {Number} amount The strength of the ease.
|
|
* @static
|
|
* @return {Function}
|
|
**/
|
|
Ease.getBackInOut = function (amount) {
|
|
amount *= 1.525;
|
|
return function (t) {
|
|
if ((t *= 2) < 1) return 0.5 * (t * t * ((amount + 1) * t - amount));
|
|
return 0.5 * ((t -= 2) * t * ((amount + 1) * t + amount) + 2);
|
|
};
|
|
};
|
|
/**
|
|
* @method backInOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.backInOut = Ease.getBackInOut(1.7);
|
|
|
|
/**
|
|
* @method circIn
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.circIn = function (t) {
|
|
return -(Math.sqrt(1 - t * t) - 1);
|
|
};
|
|
|
|
/**
|
|
* @method circOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.circOut = function (t) {
|
|
return Math.sqrt(1 - --t * t);
|
|
};
|
|
|
|
/**
|
|
* @method circInOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.circInOut = function (t) {
|
|
if ((t *= 2) < 1) return -0.5 * (Math.sqrt(1 - t * t) - 1);
|
|
return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
|
|
};
|
|
|
|
/**
|
|
* @method bounceIn
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.bounceIn = function (t) {
|
|
return 1 - Ease.bounceOut(1 - t);
|
|
};
|
|
|
|
/**
|
|
* @method bounceOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.bounceOut = function (t) {
|
|
if (t < 1 / 2.75) {
|
|
return 7.5625 * t * t;
|
|
} else if (t < 2 / 2.75) {
|
|
return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75;
|
|
} else if (t < 2.5 / 2.75) {
|
|
return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375;
|
|
} else {
|
|
return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method bounceInOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.bounceInOut = function (t) {
|
|
if (t < 0.5) return Ease.bounceIn(t * 2) * 0.5;
|
|
return Ease.bounceOut(t * 2 - 1) * 0.5 + 0.5;
|
|
};
|
|
|
|
/**
|
|
* Configurable elastic ease.
|
|
* @method getElasticIn
|
|
* @param {Number} amplitude
|
|
* @param {Number} period
|
|
* @static
|
|
* @return {Function}
|
|
**/
|
|
Ease.getElasticIn = function (amplitude, period) {
|
|
var pi2 = Math.PI * 2;
|
|
return function (t) {
|
|
if (t == 0 || t == 1) return t;
|
|
var s = (period / pi2) * Math.asin(1 / amplitude);
|
|
return -(
|
|
amplitude *
|
|
Math.pow(2, 10 * (t -= 1)) *
|
|
Math.sin(((t - s) * pi2) / period)
|
|
);
|
|
};
|
|
};
|
|
/**
|
|
* @method elasticIn
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.elasticIn = Ease.getElasticIn(1, 0.3);
|
|
|
|
/**
|
|
* Configurable elastic ease.
|
|
* @method getElasticOut
|
|
* @param {Number} amplitude
|
|
* @param {Number} period
|
|
* @static
|
|
* @return {Function}
|
|
**/
|
|
Ease.getElasticOut = function (amplitude, period) {
|
|
var pi2 = Math.PI * 2;
|
|
return function (t) {
|
|
if (t == 0 || t == 1) return t;
|
|
var s = (period / pi2) * Math.asin(1 / amplitude);
|
|
return (
|
|
amplitude * Math.pow(2, -10 * t) * Math.sin(((t - s) * pi2) / period) +
|
|
1
|
|
);
|
|
};
|
|
};
|
|
/**
|
|
* @method elasticOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.elasticOut = Ease.getElasticOut(1, 0.3);
|
|
|
|
/**
|
|
* Configurable elastic ease.
|
|
* @method getElasticInOut
|
|
* @param {Number} amplitude
|
|
* @param {Number} period
|
|
* @static
|
|
* @return {Function}
|
|
**/
|
|
Ease.getElasticInOut = function (amplitude, period) {
|
|
var pi2 = Math.PI * 2;
|
|
return function (t) {
|
|
var s = (period / pi2) * Math.asin(1 / amplitude);
|
|
if ((t *= 2) < 1)
|
|
return (
|
|
-0.5 *
|
|
(amplitude *
|
|
Math.pow(2, 10 * (t -= 1)) *
|
|
Math.sin(((t - s) * pi2) / period))
|
|
);
|
|
return (
|
|
amplitude *
|
|
Math.pow(2, -10 * (t -= 1)) *
|
|
Math.sin(((t - s) * pi2) / period) *
|
|
0.5 +
|
|
1
|
|
);
|
|
};
|
|
};
|
|
/**
|
|
* @method elasticInOut
|
|
* @param {Number} t
|
|
* @static
|
|
* @return {Number}
|
|
**/
|
|
Ease.elasticInOut = Ease.getElasticInOut(1, 0.3 * 1.5);
|
|
|
|
createjs.Ease = Ease;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// MotionGuidePlugin.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* A TweenJS plugin for working with motion guides.
|
|
*
|
|
* To use, install the plugin after TweenJS has loaded. Next tween the 'guide' property with an object as detailed below.
|
|
*
|
|
* createjs.MotionGuidePlugin.install();
|
|
*
|
|
* <h4>Example</h4>
|
|
*
|
|
* // Using a Motion Guide
|
|
* createjs.Tween.get(target).to({guide:{ path:[0,0, 0,200,200,200, 200,0,0,0] }},7000);
|
|
* // Visualizing the line
|
|
* graphics.moveTo(0,0).curveTo(0,200,200,200).curveTo(200,0,0,0);
|
|
*
|
|
* Each path needs pre-computation to ensure there's fast performance. Because of the pre-computation there's no
|
|
* built in support for path changes mid tween. These are the Guide Object's properties:<UL>
|
|
* <LI> path: Required, Array : The x/y points used to draw the path with a moveTo and 1 to n curveTo calls.</LI>
|
|
* <LI> start: Optional, 0-1 : Initial position, default 0 except for when continuing along the same path.</LI>
|
|
* <LI> end: Optional, 0-1 : Final position, default 1 if not specified.</LI>
|
|
* <LI> orient: Optional, string : "fixed"/"auto"/"cw"/"ccw"<UL>
|
|
* <LI>"fixed" forces the object to face down the path all movement (relative to start rotation),</LI>
|
|
* <LI>"auto" rotates the object along the path relative to the line.</LI>
|
|
* <LI>"cw"/"ccw" force clockwise or counter clockwise rotations including flash like behaviour</LI>
|
|
* </UL></LI>
|
|
* </UL>
|
|
* Guide objects should not be shared between tweens even if all properties are identical, the library stores
|
|
* information on these objects in the background and sharing them can cause unexpected behaviour. Values
|
|
* outside 0-1 range of tweens will be a "best guess" from the appropriate part of the defined curve.
|
|
*
|
|
* @class MotionGuidePlugin
|
|
* @constructor
|
|
**/
|
|
function MotionGuidePlugin() {
|
|
throw "MotionGuidePlugin cannot be instantiated.";
|
|
}
|
|
|
|
// static properties:
|
|
/**
|
|
* @property priority
|
|
* @protected
|
|
* @static
|
|
**/
|
|
MotionGuidePlugin.priority = 0; // high priority, should run sooner
|
|
|
|
/**
|
|
* @property temporary variable storage
|
|
* @private
|
|
* @static
|
|
*/
|
|
MotionGuidePlugin._rotOffS;
|
|
/**
|
|
* @property temporary variable storage
|
|
* @private
|
|
* @static
|
|
*/
|
|
MotionGuidePlugin._rotOffE;
|
|
/**
|
|
* @property temporary variable storage
|
|
* @private
|
|
* @static
|
|
*/
|
|
MotionGuidePlugin._rotNormS;
|
|
/**
|
|
* @property temporary variable storage
|
|
* @private
|
|
* @static
|
|
*/
|
|
MotionGuidePlugin._rotNormE;
|
|
|
|
// static methods
|
|
/**
|
|
* Installs this plugin for use with TweenJS. Call this once after TweenJS is loaded to enable this plugin.
|
|
* @method install
|
|
* @static
|
|
**/
|
|
MotionGuidePlugin.install = function () {
|
|
createjs.Tween.installPlugin(MotionGuidePlugin, [
|
|
"guide",
|
|
"x",
|
|
"y",
|
|
"rotation",
|
|
]);
|
|
return createjs.Tween.IGNORE;
|
|
};
|
|
|
|
/**
|
|
* @method init
|
|
* @protected
|
|
* @static
|
|
**/
|
|
MotionGuidePlugin.init = function (tween, prop, value) {
|
|
var target = tween.target;
|
|
if (!target.hasOwnProperty("x")) {
|
|
target.x = 0;
|
|
}
|
|
if (!target.hasOwnProperty("y")) {
|
|
target.y = 0;
|
|
}
|
|
if (!target.hasOwnProperty("rotation")) {
|
|
target.rotation = 0;
|
|
}
|
|
|
|
if (prop == "rotation") {
|
|
tween.__needsRot = true;
|
|
}
|
|
return prop == "guide" ? null : value;
|
|
};
|
|
|
|
/**
|
|
* @method step
|
|
* @protected
|
|
* @static
|
|
**/
|
|
MotionGuidePlugin.step = function (
|
|
tween,
|
|
prop,
|
|
startValue,
|
|
endValue,
|
|
injectProps
|
|
) {
|
|
// other props
|
|
if (prop == "rotation") {
|
|
tween.__rotGlobalS = startValue;
|
|
tween.__rotGlobalE = endValue;
|
|
MotionGuidePlugin.testRotData(tween, injectProps);
|
|
}
|
|
if (prop != "guide") {
|
|
return endValue;
|
|
}
|
|
|
|
// guide only information - Start -
|
|
var temp,
|
|
data = endValue;
|
|
if (!data.hasOwnProperty("path")) {
|
|
data.path = [];
|
|
}
|
|
var path = data.path;
|
|
if (!data.hasOwnProperty("end")) {
|
|
data.end = 1;
|
|
}
|
|
if (!data.hasOwnProperty("start")) {
|
|
data.start =
|
|
startValue &&
|
|
startValue.hasOwnProperty("end") &&
|
|
startValue.path === path
|
|
? startValue.end
|
|
: 0;
|
|
}
|
|
|
|
// Figure out subline information
|
|
if (data.hasOwnProperty("_segments") && data._length) {
|
|
return endValue;
|
|
}
|
|
var l = path.length;
|
|
var accuracy = 10; // Adjust to improve line following precision but sacrifice performance (# of seg)
|
|
if (l >= 6 && (l - 2) % 4 == 0) {
|
|
// Enough points && contains correct number per entry ignoring start
|
|
data._segments = [];
|
|
data._length = 0;
|
|
for (var i = 2; i < l; i += 4) {
|
|
var sx = path[i - 2],
|
|
sy = path[i - 1];
|
|
var cx = path[i + 0],
|
|
cy = path[i + 1];
|
|
var ex = path[i + 2],
|
|
ey = path[i + 3];
|
|
var oldX = sx,
|
|
oldY = sy;
|
|
var tempX,
|
|
tempY,
|
|
total = 0;
|
|
var sublines = [];
|
|
for (var j = 1; j <= accuracy; j++) {
|
|
var t = j / accuracy;
|
|
var inv = 1 - t;
|
|
tempX = inv * inv * sx + 2 * inv * t * cx + t * t * ex;
|
|
tempY = inv * inv * sy + 2 * inv * t * cy + t * t * ey;
|
|
total +=
|
|
sublines[
|
|
sublines.push(
|
|
Math.sqrt(
|
|
(temp = tempX - oldX) * temp + (temp = tempY - oldY) * temp
|
|
)
|
|
) - 1
|
|
];
|
|
oldX = tempX;
|
|
oldY = tempY;
|
|
}
|
|
data._segments.push(total);
|
|
data._segments.push(sublines);
|
|
data._length += total;
|
|
}
|
|
} else {
|
|
throw "invalid 'path' data, please see documentation for valid paths";
|
|
}
|
|
|
|
// Setup x/y tweens
|
|
temp = data.orient;
|
|
data.orient = true;
|
|
var o = {};
|
|
MotionGuidePlugin.calc(data, data.start, o);
|
|
tween.__rotPathS = Number(o.rotation.toFixed(5));
|
|
MotionGuidePlugin.calc(data, data.end, o);
|
|
tween.__rotPathE = Number(o.rotation.toFixed(5));
|
|
data.orient = false; //here and now we don't know if we need to
|
|
MotionGuidePlugin.calc(data, data.end, injectProps);
|
|
data.orient = temp;
|
|
|
|
// Setup rotation properties
|
|
if (!data.orient) {
|
|
return endValue;
|
|
}
|
|
tween.__guideData = data;
|
|
MotionGuidePlugin.testRotData(tween, injectProps);
|
|
return endValue;
|
|
};
|
|
|
|
/**
|
|
* @method testRotData
|
|
* @protected
|
|
* @static
|
|
**/
|
|
MotionGuidePlugin.testRotData = function (tween, injectProps) {
|
|
// no rotation informat? if we need it come back, if we don't use 0 & ensure we have guide data
|
|
if (tween.__rotGlobalS === undefined || tween.__rotGlobalE === undefined) {
|
|
if (tween.__needsRot) {
|
|
return;
|
|
}
|
|
if (tween._curQueueProps.rotation !== undefined) {
|
|
tween.__rotGlobalS = tween.__rotGlobalE = tween._curQueueProps.rotation;
|
|
} else {
|
|
tween.__rotGlobalS = tween.__rotGlobalE = injectProps.rotation =
|
|
tween.target.rotation || 0;
|
|
}
|
|
}
|
|
if (tween.__guideData === undefined) {
|
|
return;
|
|
}
|
|
|
|
// Process rotation properties
|
|
var data = tween.__guideData;
|
|
var rotGlobalD = tween.__rotGlobalE - tween.__rotGlobalS;
|
|
var rotPathD = tween.__rotPathE - tween.__rotPathS;
|
|
var rot = rotGlobalD - rotPathD;
|
|
|
|
if (data.orient == "auto") {
|
|
if (rot > 180) {
|
|
rot -= 360;
|
|
} else if (rot < -180) {
|
|
rot += 360;
|
|
}
|
|
} else if (data.orient == "cw") {
|
|
while (rot < 0) {
|
|
rot += 360;
|
|
}
|
|
if (rot == 0 && rotGlobalD > 0 && rotGlobalD != 180) {
|
|
rot += 360;
|
|
}
|
|
} else if (data.orient == "ccw") {
|
|
rot = rotGlobalD - (rotPathD > 180 ? 360 - rotPathD : rotPathD); // sign flipping on path
|
|
while (rot > 0) {
|
|
rot -= 360;
|
|
}
|
|
if (rot == 0 && rotGlobalD < 0 && rotGlobalD != -180) {
|
|
rot -= 360;
|
|
}
|
|
}
|
|
|
|
data.rotDelta = rot;
|
|
data.rotOffS = tween.__rotGlobalS - tween.__rotPathS;
|
|
|
|
// reset
|
|
tween.__rotGlobalS = tween.__rotGlobalE = tween.__guideData = tween.__needsRot = undefined;
|
|
};
|
|
|
|
/**
|
|
* @method tween
|
|
* @protected
|
|
* @static
|
|
**/
|
|
MotionGuidePlugin.tween = function (
|
|
tween,
|
|
prop,
|
|
value,
|
|
startValues,
|
|
endValues,
|
|
ratio,
|
|
wait,
|
|
end
|
|
) {
|
|
var data = endValues.guide;
|
|
if (data == undefined || data === startValues.guide) {
|
|
return value;
|
|
}
|
|
if (data.lastRatio != ratio) {
|
|
// first time through so calculate what I need to
|
|
var t = (data.end - data.start) * (wait ? data.end : ratio) + data.start;
|
|
MotionGuidePlugin.calc(data, t, tween.target);
|
|
switch (data.orient) {
|
|
case "cw": // mix in the original rotation
|
|
case "ccw":
|
|
case "auto":
|
|
tween.target.rotation += data.rotOffS + data.rotDelta * ratio;
|
|
break;
|
|
case "fixed": // follow fixed behaviour to solve potential issues
|
|
default:
|
|
tween.target.rotation += data.rotOffS;
|
|
break;
|
|
}
|
|
data.lastRatio = ratio;
|
|
}
|
|
if (prop == "rotation" && (!data.orient || data.orient == "false")) {
|
|
return value;
|
|
}
|
|
return tween.target[prop];
|
|
};
|
|
|
|
/**
|
|
* Determine the appropriate x/y/rotation information about a path for a given ratio along the path.
|
|
* Assumes a path object with all optional parameters specified.
|
|
* @param data Data object you would pass to the "guide:" property in a Tween
|
|
* @param ratio 0-1 Distance along path, values outside 0-1 are "best guess"
|
|
* @param target Object to copy the results onto, will use a new object if not supplied.
|
|
* @return {Object} The target object or a new object w/ the tweened properties
|
|
* @static
|
|
*/
|
|
MotionGuidePlugin.calc = function (data, ratio, target) {
|
|
if (data._segments == undefined) {
|
|
throw "Missing critical pre-calculated information, please file a bug";
|
|
}
|
|
if (target == undefined) {
|
|
target = { x: 0, y: 0, rotation: 0 };
|
|
}
|
|
var seg = data._segments;
|
|
var path = data.path;
|
|
|
|
// find segment
|
|
var pos = data._length * ratio;
|
|
var cap = seg.length - 2;
|
|
var n = 0;
|
|
while (pos > seg[n] && n < cap) {
|
|
pos -= seg[n];
|
|
n += 2;
|
|
}
|
|
|
|
// find subline
|
|
var sublines = seg[n + 1];
|
|
var i = 0;
|
|
cap = sublines.length - 1;
|
|
while (pos > sublines[i] && i < cap) {
|
|
pos -= sublines[i];
|
|
i++;
|
|
}
|
|
var t = i / ++cap + pos / (cap * sublines[i]);
|
|
|
|
// find x/y
|
|
n = n * 2 + 2;
|
|
var inv = 1 - t;
|
|
target.x =
|
|
inv * inv * path[n - 2] + 2 * inv * t * path[n + 0] + t * t * path[n + 2];
|
|
target.y =
|
|
inv * inv * path[n - 1] + 2 * inv * t * path[n + 1] + t * t * path[n + 3];
|
|
|
|
// orientation
|
|
if (data.orient) {
|
|
target.rotation =
|
|
57.2957795 *
|
|
Math.atan2(
|
|
(path[n + 1] - path[n - 1]) * inv + (path[n + 3] - path[n + 1]) * t,
|
|
(path[n + 0] - path[n - 2]) * inv + (path[n + 2] - path[n + 0]) * t
|
|
);
|
|
}
|
|
|
|
return target;
|
|
};
|
|
|
|
createjs.MotionGuidePlugin = MotionGuidePlugin;
|
|
})();
|
|
|
|
//##############################################################################
|
|
// version.js
|
|
//##############################################################################
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
/**
|
|
* Static class holding library specific information such as the version and buildDate of
|
|
* the library.
|
|
* @class TweenJS
|
|
**/
|
|
var s = (createjs.TweenJS = createjs.TweenJS || {});
|
|
|
|
/**
|
|
* The version string for this release.
|
|
* @property version
|
|
* @type String
|
|
* @static
|
|
**/
|
|
s.version = /*=version*/ "0.6.2"; // injected by build process
|
|
|
|
/**
|
|
* The build date for this release in UTC format.
|
|
* @property buildDate
|
|
* @type String
|
|
* @static
|
|
**/
|
|
s.buildDate = /*=date*/ "Thu, 26 Nov 2015 20:44:31 GMT"; // injected by build process
|
|
})();
|
|
|