/*
* 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.
*/
/**
* @module EaselJS
*/
// namespace:
this.createjs = this.createjs||{};
(function() {
"use strict";
/**
* 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.
**/
var Stage = function(canvas) {
this.initialize(canvas);
};
var p = Stage.prototype = new createjs.Container();
// static properties:
/**
* @property _snapToPixelEnabled
* @protected
* @static
* @type {Boolean}
* @default false
* @deprecated Hardware acceleration in modern browsers makes this unnecessary.
**/
Stage._snapToPixelEnabled = false; // snapToPixelEnabled is temporarily copied here during a draw to provide global access.
// 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 releases the mouse button anywhere that the page can detect it (this varies slightly between browsers).
* 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. Does not fire if
* tickOnUpdate is false.
* @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.
* @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
*/
// 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
**/
p.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
**/
p.canvas = null;
/**
* 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
**/
p.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
**/
p.mouseY = 0;
// TODO: deprecated.
/**
* REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the "{{#crossLink "Stage/stagemousemove:event"}}{{/crossLink}}
* event.
* @property onMouseMove
* @type Function
* @deprecated Use addEventListener and the "stagemousemove" event.
*/
/**
* REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "Stage/stagemouseup:event"}}{{/crossLink}}
* event.
* @property onMouseUp
* @type Function
* @deprecated Use addEventListener and the "stagemouseup" event.
*/
/**
* REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "Stage/stagemousedown:event"}}{{/crossLink}}
* event.
* @property onMouseDown
* @type Function
* @deprecated Use addEventListener and the "stagemousedown" event.
*/
// TODO: deprecated.
/**
* Indicates whether this stage should use the {{#crossLink "DisplayObject/snapToPixel"}}{{/crossLink}} property of
* display objects when rendering them.
* @property snapToPixelEnabled
* @type Boolean
* @default false
* @deprecated Hardware acceleration makes this not beneficial
**/
p.snapToPixelEnabled = false;
/**
* Indicates whether the mouse is currently within the bounds of the canvas.
* @property mouseInBounds
* @type Boolean
* @default false
**/
p.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
**/
p.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
**/
p.mouseMoveOutside = false;
// TODO: confirm naming and inclusion.
/**
* NOTE: this name is not final. Feedback is appreciated.
*
* The stage assigned to this property will have mouse interactions relayed to it after this stage handles them.
* This can be useful in cases where you have multiple canvases layered on top of one another and want your mouse
* events to pass through. For example, this would relay mouse events from topStage to bottomStage:
*
* topStage.nextStage = bottomStage;
*
* Note that each stage handles the interactions independently. As such, you could have a click register on an
* object in the top stage, and another click register in the bottom stage. Consider using a single canvas with
* cached {{#crossLink "Container"}}{{/crossLink}} instances instead of multiple canvases.
*
* MouseOver, MouseOut, RollOver, and RollOut interactions will not be passed through. They must be enabled using
* {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}} for each stage individually.
*
* In most instances, you will also want to disable DOM events for the next stage to avoid duplicate interactions.
* myNextStage.enableDOMEvents(false);
*
* @property nextStage
* @type Stage
**/
p.nextStage = null;
/**
* 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
*/
p._pointerData = null;
/**
* Number of active pointers.
* @property _pointerCount
* @type {Object}
* @private
*/
p._pointerCount = 0;
/**
* The ID of the primary pointer.
* @property _primaryPointerID
* @type {Object}
* @private
*/
p._primaryPointerID = null;
/**
* @property _mouseOverIntervalID
* @protected
* @type Number
**/
p._mouseOverIntervalID = null;
// constructor:
/**
* @property DisplayObject_initialize
* @type Function
* @private
**/
p.Container_initialize = p.initialize;
/**
* Initialization method.
* @method initialize
* @param {HTMLCanvasElement | String | Object} canvas A canvas object, or the string id of a canvas object in the current document.
* @protected
**/
p.initialize = function(canvas) {
this.Container_initialize();
this.canvas = (typeof canvas == "string") ? document.getElementById(canvas) : canvas;
this._pointerData = {};
this.enableDOMEvents(true);
};
// public methods:
/**
* Each time the update method is called, the stage will tick all descendants (see: {{#crossLink "DisplayObject/tick"}}{{/crossLink}})
* and then render the display list to the canvas. Any parameters passed to `update()` will be passed on to any
* {{#crossLink "DisplayObject/tick:event"}}{{/crossLink}} event handlers.
*
* Some time-based features in EaselJS (for example {{#crossLink "Sprite/framerate"}}{{/crossLink}} require that
* a tick event object (or equivalent) be passed as the first parameter to update(). For example:
*
* Ticker.addEventListener("tick", handleTick);
* function handleTick(evtObj) {
* // do some work here, then update the stage, passing through the event object:
* myStage.update(evtObj);
* }
*
* @method update
* @param {*} [params]* Params to include when ticking descendants. The first param should usually be a tick event.
**/
p.update = function(params) {
if (!this.canvas) { return; }
if (this.tickOnUpdate) {
this.dispatchEvent("tickstart"); // TODO: make cancellable?
this.tickEnabled&&this._tick((arguments.length ? arguments : null));
this.dispatchEvent("tickend");
}
this.dispatchEvent("drawstart"); // TODO: make cancellable?
Stage._snapToPixelEnabled = this.snapToPixelEnabled;
if (this.autoClear) { this.clear(); }
var ctx = this.canvas.getContext("2d");
ctx.save();
this.updateContext(ctx);
this.draw(ctx, false);
ctx.restore();
this.dispatchEvent("drawend");
};
/**
* 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. 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+1, h+1);
//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 ({{#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);
}
}
};
/**
* Returns a clone of this Stage.
* @method clone
* @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 _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 _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 first new touch, then make it the primary pointer id:
if (this._primaryPointerID == null) { this._primaryPointerID = id; }
// if it's the mouse (id == -1) or the first new touch, then make it the primary pointer id:
if (this._primaryPointerID == null || this._primaryPointerID == -1) { 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; }
var o = this._getPointerData(id);
var inBounds = o.inBounds;
this._updatePointerPosition(id, e, pageX, pageY);
if (!inBounds && !o.inBounds && !this.mouseMoveOutside) { return; }
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);
// TODO: deprecated:
var oEvent = o.event;
if (oEvent && oEvent.hasEventListener("mousemove")) {
// this doesn't use _dispatchMouseEvent because it requires re-targeting.
oEvent.dispatchEvent(new createjs.MouseEvent("mousemove", false, false, o.x, o.y, e, id, (id == this._primaryPointerID), o.rawX, o.rawY), o.target);
}
this.nextStage&&this.nextStage._handlePointerMove(id, e, pageX, pageY);
};
/**
* @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) {
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
**/
p._handlePointerUp = function(id, e, clear) {
var o = this._getPointerData(id);
this._dispatchMouseEvent(this, "stagemouseup", false, id, o, e);
var oTarget = o.target;
if (oTarget) {
if (this._getObjectsUnderPoint(o.x, o.y, null, true) == oTarget) {
this._dispatchMouseEvent(oTarget, "click", true, id, o, e);
}
this._dispatchMouseEvent(oTarget, "pressup", true, id, o, e);
}
// TODO: deprecated:
var oEvent = o.event;
if (oEvent && oEvent.hasEventListener("mouseup")) {
// this doesn't use _dispatchMouseEvent because it requires re-targeting.
oEvent.dispatchEvent(new createjs.MouseEvent("mouseup", false, false, o.x, o.y, e, id, (id==this._primaryPointerID), o.rawX, o.rawY), oTarget);
}
if (clear) {
if (id==this._primaryPointerID) { this._primaryPointerID = null; }
delete(this._pointerData[id]);
} else { o.event = o.target = null; }
this.nextStage&&this.nextStage._handlePointerUp(id, e, clear);
};
/**
* @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
**/
p._handlePointerDown = function(id, e, pageX, pageY) {
if (pageY != null) { this._updatePointerPosition(id, e, pageX, pageY); }
var o = this._getPointerData(id);
this._dispatchMouseEvent(this, "stagemousedown", false, id, o, e);
o.target = this._getObjectsUnderPoint(o.x, o.y, null, true);
// TODO: holding onto the event is deprecated:
o.event = this._dispatchMouseEvent(o.target, "mousedown", true, id, o, e);
this.nextStage&&this.nextStage._handlePointerDown(id, e, pageX, pageY);
};
/**
* @method _testMouseOver
* @param {Boolean} clear If true, clears the mouseover / rollover (ie. no target)
* @protected
**/
p._testMouseOver = function(clear) {
// only update if the mouse position has changed. This provides a lot of optimization, but has some trade-offs.
if (this._primaryPointerID != -1 || (!clear && this.mouseX == this._mouseOverX && this.mouseY == this._mouseOverY && this.mouseInBounds)) { return; }
var o = this._getPointerData(-1);
var e = o.posEvtObj;
var target, common = -1, cursor="", t, i, l;
if (clear || this.mouseInBounds && e && e.target == this.canvas) {
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 (t.cursor != null) { cursor = t.cursor; }
t = t.parent;
}
this.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);
}
for (i=oldList.length-1; i>common; i--) {
this._dispatchMouseEvent(oldList[i], "rollout", false, -1, o, e);
}
for (i=list.length-1; i>common; i--) {
this._dispatchMouseEvent(list[i], "rollover", false, -1, o, e);
}
if (oldTarget != target) {
this._dispatchMouseEvent(target, "mouseover", true, -1, o, e);
}
};
/**
* @method _handleDoubleClick
* @protected
* @param {MouseEvent} e
**/
p._handleDoubleClick = function(e) {
var o = this._getPointerData(-1);
var target = this._getObjectsUnderPoint(o.x, o.y, null, true);
this._dispatchMouseEvent(target, "dblclick", true, -1, o, e);
this.nextStage&&this.nextStage._handleDoubleClick(e);
};
/**
* @method _dispatchMouseEvent
* @protected
* @param {DisplayObject} target
* @param {String} type
* @param {Boolean} bubbles
* @param {Number} pointerId
* @param {Object} o
* @param {MouseEvent} [nativeEvent]
**/
p._dispatchMouseEvent = function(target, type, bubbles, pointerId, o, nativeEvent) {
// 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, o.rawX, o.rawY);
*/
var evt = new createjs.MouseEvent(type, bubbles, false, o.x, o.y, nativeEvent, pointerId, pointerId==this._primaryPointerID, o.rawX, o.rawY);
target.dispatchEvent(evt);
// TODO: returning evt is deprecated:
return evt;
};
createjs.Stage = Stage;
}());