/* * Stage * 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. */ // namespace: this.createjs = this.createjs||{}; (function() { /** * 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. * *
autoClear
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, this.canvas.height);
}
/**
* 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. The value can be any value HTML color
* value, including HEX colors, rgb and rgba. The default value is a transparent background.
* @param {String} mimeType 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) {
if(!mimeType) {
mimeType = "image/png";
}
var ctx = this.canvas.getContext('2d');
var w = this.canvas.width;
var h = this.canvas.height;
var data;
if(backgroundColor) {
//get the current ImageData for the canvas.
data = ctx.getImageData(0, 0, w, h);
//store the current globalCompositeOperation
var compositeOperation = ctx.globalCompositeOperation;
//set to draw behind current content
ctx.globalCompositeOperation = "destination-over";
//set background color
ctx.fillStyle = backgroundColor;
//draw background on entire canvas
ctx.fillRect(0, 0, w, h);
}
//get the image data from the canvas
var dataURL = this.canvas.toDataURL(mimeType);
if(backgroundColor) {
//clear the canvas
ctx.clearRect (0, 0, w, h);
//restore it with original settings
ctx.putImageData(data, 0, 0);
//reset the globalCompositeOperation to what it was
ctx.globalCompositeOperation = compositeOperation;
}
return dataURL;
}
/**
* Enables or disables (by passing a frequency of 0) mouse over events (mouseover and mouseout) for this stage's display
* list. These events can be expensive to generate, so they are disabled by default, and the frequency of the events
* can be controlled independently of mouse move events via the optional frequency
parameter.
* @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 == 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);
}
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:t, 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);
}
}
}
/**
* Returns a clone of this Stage.
* @return {Stage} A clone of the current Container instance.
**/
p.clone = function() {
var o = new Stage(null);
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 "[Stage (name="+ this.name +")]";
}
// private methods:
/**
* @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};
// if it's the mouse (id == NaN) or the first new touch, then make it the primary pointer id:
if (this._primaryPointerID == null) { this._primaryPointerID = id; }
}
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
**/
p._handlePointerMove = function(id, e, pageX, pageY) {
if (!this.canvas) { return; } // this.mouseX = this.mouseY = null;
var evt;
var o = this._getPointerData(id);
var inBounds = o.inBounds;
this._updatePointerPosition(id, pageX, pageY);
if (!inBounds && !o.inBounds && !this.mouseMoveOutside) { return; }
if (this.onMouseMove || this.hasEventListener("stagemousemove")) {
evt = new createjs.MouseEvent("stagemousemove", o.x, o.y, this, e, id, id == this._primaryPointerID, o.rawX, o.rawY);
this.onMouseMove&&this.onMouseMove(evt);
this.dispatchEvent(evt);
}
var oEvt = o.event;
if (oEvt && (oEvt.onMouseMove || oEvt.hasEventListener("mousemove"))) {
evt = new createjs.MouseEvent("mousemove", o.x, o.y, oEvt.target, e, id, id == this._primaryPointerID, o.rawX, o.rawY);
oEvt.onMouseMove&&oEvt.onMouseMove(evt);
oEvt.dispatchEvent(evt, oEvt.target);
}
}
/**
* @method _updatePointerPosition
* @protected
* @param {Number} id
* @param {Number} pageX
* @param {Number} pageY
**/
p._updatePointerPosition = function(id, 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.rawX = pageX;
o.rawY = pageY;
if (id == this._primaryPointerID) {
this.mouseX = o.x;
this.mouseY = o.y;
this.mouseInBounds = o.inBounds;
}
}
/**
* @method _getElementRect
* @protected
* @param {HTMLElement} e
**/
p._getElementRect = function(e) {
var bounds;
try { bounds = e.getBoundingClientRect(); } // this can fail on disconnected DOM elements in IE9
catch (err) { 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) : 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 _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
**/
p._handlePointerUp = function(id, e, clear) {
var o = this._getPointerData(id);
var evt;
if (this.onMouseMove || this.hasEventListener("stagemouseup")) {
evt = new createjs.MouseEvent("stagemouseup", o.x, o.y, this, e, id, id==this._primaryPointerID, o.rawX, o.rawY);
this.onMouseUp&&this.onMouseUp(evt);
this.dispatchEvent(evt);
}
var oEvt = o.event;
if (oEvt && (oEvt.onMouseUp || oEvt.hasEventListener("mouseup"))) {
evt = new createjs.MouseEvent("mouseup", o.x, o.y, oEvt.target, e, id, id==this._primaryPointerID, o.rawX, o.rawY);
oEvt.onMouseUp&&oEvt.onMouseUp(evt);
oEvt.dispatchEvent(evt, oEvt.target);
}
var oTarget = o.target;
if (oTarget && (oTarget.onClick || oTarget.hasEventListener("click")) && this._getObjectsUnderPoint(o.x, o.y, null, true, (this._mouseOverIntervalID ? 3 : 1)) == oTarget) {
evt = new createjs.MouseEvent("click", o.x, o.y, oTarget, e, id, id==this._primaryPointerID, o.rawX, o.rawY);
oTarget.onClick&&oTarget.onClick(evt);
oTarget.dispatchEvent(evt);
}
if (clear) {
if (id == this._primaryPointerID) { this._primaryPointerID = null; }
delete(this._pointerData[id]);
} else { o.event = o.target = null; }
}
/**
* @method _handleMouseDown
* @protected
* @param {MouseEvent} e
**/
p._handleMouseDown = function(e) {
this._handlePointerDown(-1, e, false);
}
/**
* @method _handlePointerDown
* @protected
* @param {Number} id
* @param {Event} e
* @param {Number} x
* @param {Number} y
**/
p._handlePointerDown = function(id, e, x, y) {
var o = this._getPointerData(id);
if (y != null) { this._updatePointerPosition(id, x, y); }
if (this.onMouseDown || this.hasEventListener("stagemousedown")) {
var evt = new createjs.MouseEvent("stagemousedown", o.x, o.y, this, e, id, id==this._primaryPointerID, o.rawX, o.rawY);
this.onMouseDown&&this.onMouseDown(evt);
this.dispatchEvent(evt);
}
var target = this._getObjectsUnderPoint(o.x, o.y, null, (this._mouseOverIntervalID ? 3 : 1));
if (target) {
o.target = target;
if (target.onPress || target.hasEventListener("mousedown")) {
evt = new createjs.MouseEvent("mousedown", o.x, o.y, target, e, id, id==this._primaryPointerID, o.rawX, o.rawY);
target.onPress&&target.onPress(evt);
target.dispatchEvent(evt);
if (evt.onMouseMove || evt.onMouseUp || evt.hasEventListener("mousemove") || evt.hasEventListener("mouseup")) { o.event = evt; }
}
}
}
/**
* @method _testMouseOver
* @protected
**/
p._testMouseOver = function() {
// for now, this only tests the mouse.
if (this._primaryPointerID != -1) { return; }
// only update if the mouse position has changed. This provides a lot of optimization, but has some trade-offs.
if (this.mouseX == this._mouseOverX && this.mouseY == this._mouseOverY && this.mouseInBounds) { return; }
var target = null;
if (this.mouseInBounds) {
target = this._getObjectsUnderPoint(this.mouseX, this.mouseY, null, 3);
this._mouseOverX = this.mouseX;
this._mouseOverY = this.mouseY;
}
var mouseOverTarget = this._mouseOverTarget;
if (mouseOverTarget != target) {
var o = this._getPointerData(-1);
if (mouseOverTarget && (mouseOverTarget.onMouseOut || mouseOverTarget.hasEventListener("mouseout"))) {
var evt = new createjs.MouseEvent("mouseout", o.x, o.y, mouseOverTarget, null, -1, o.rawX, o.rawY);
mouseOverTarget.onMouseOut&&mouseOverTarget.onMouseOut(evt);
mouseOverTarget.dispatchEvent(evt);
}
if (mouseOverTarget) { this.canvas.style.cursor = ""; }
if (target && (target.onMouseOver || target.hasEventListener("mouseover"))) {
evt = new createjs.MouseEvent("mouseover", o.x, o.y, target, null, -1, o.rawX, o.rawY);
target.onMouseOver&&target.onMouseOver(evt);
target.dispatchEvent(evt);
}
if (target) { this.canvas.style.cursor = target.cursor||""; }
this._mouseOverTarget = target;
}
}
/**
* @method _handleDoubleClick
* @protected
* @param {MouseEvent} e
**/
p._handleDoubleClick = function(e) {
var o = this._getPointerData(-1);
var target = this._getObjectsUnderPoint(o.x, o.y, null, (this._mouseOverIntervalID ? 3 : 1));
if (target && (target.onDoubleClick || target.hasEventListener("dblclick"))) {
evt = new createjs.MouseEvent("dblclick", o.x, o.y, target, e, -1, true, o.rawX, o.rawY);
target.onDoubleClick&&target.onDoubleClick(evt);
target.dispatchEvent(evt);
}
}
createjs.Stage = Stage;
}());