@createjs/easeljs
Version:
The Easel JavaScript library provides a full, hierarchical display list, a core interaction model, and helper classes to make working with the HTML5 Canvas element much easier. Part of the CreateJS suite of libraries.
780 lines (701 loc) • 28 kB
JavaScript
/**
* @license Stage
* Visit http://createjs.com/ for documentation, updates and examples.
*
* Copyright (c) 2017 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.
*/
import Container from "./Container";
import DisplayObject from "./DisplayObject";
import { Event } from "@createjs/core";
import MouseEvent from "../events/MouseEvent";
/**
* A stage is the root level {@link easeljs.Container} for a display list. Each time its {@link easeljs.Stage#tick}
* method is called, it will render its display list to its target canvas.
*
* @memberof easeljs
* @extends easeljs.Container
* @example
* let stage = new Stage("canvasElementId");
* let image = new Bitmap("imagePath.png");
* stage.addChild(image);
* Ticker.addEventListener("tick", event => {
* image.x += 10;
* stage.update();
* });
*
* @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.
*/
export default class Stage extends Container {
constructor (canvas) {
super();
/**
* Indicates whether the stage should automatically clear the canvas before each render. You can set this to `false`
* to manually control clearing (for generative art, or when pointing multiple stages at the same canvas for
* example).
*
* @example
* let stage = new Stage("canvasId");
* stage.autoClear = false;
*
* @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.
*
* @example
* stage.enableDOMEvents(false);
* stage.canvas = anotherCanvas;
* stage.enableDOMEvents(true);
*
* @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.
* @type {Number}
* @default 0
* @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.
* @type {Number}
* @default 0
* @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.
* @type {easeljs.Rectangle}
*/
this.drawRect = null;
/**
* Indicates whether display objects should be rendered on whole pixels. You can set the {@link easeljs.DisplayObject.snapToPixelEnabled}
* property of display objects to false to enable/disable this behaviour on a per instance basis.
* @type {Boolean}
* @default false
*/
this.snapToPixelEnabled = false;
/**
* Indicates whether the mouse is currently within the bounds of the canvas.
* @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.
* @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 {@link easeljs.Stage#mouseInBounds}, and {@link easeljs.MouseEvent} x/y/rawX/rawY.
* @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.
* @type {Boolean}
* @default true
*/
this.preventSelection = true;
/**
* The hitArea property is not supported for Stage.
* @property hitArea
* @override
* @default null
* @private
*/
/**
* 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)
* @type {Object}
* @private
*/
this._pointerData = {};
/**
* Number of active pointers.
* @type {Number}
* @private
*/
this._pointerCount = 0;
/**
* The ID of the primary pointer.
* @type {String}
* @private
*/
this._primaryPointerID = null;
/**
* @protected
* @type {Number}
*/
this._mouseOverIntervalID = null;
/**
* @protected
* @type {easeljs.Stage}
*/
this._nextStage = null;
/**
* @protected
* @type {easeljs.Stage}
*/
this._prevStage = null;
this.enableDOMEvents(true);
}
/**
* 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.
*
* 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>
*
* @example <caption>Relay mouse events from topStage to bottomStage</caption>
* topStage.nextStage = bottomStage;
*
* @example <caption>Disable DOM events</caption>
* stage.enableDOMEvents(false);
*
* @type {easeljs.Stage}
*/
get nextStage () { return this._nextStage; }
set nextStage (stage) {
if (this._nextStage) { this._nextStage._prevStage = null; }
if (stage) { stage._prevStage = this; }
this._nextStage = stage;
}
// public methods:
/**
* Each time the update method is called, the stage will call {@link easeljs.Stage#tick}
* unless {@link easeljs.Stage#tickOnupdate} is set to false,
* and then render the display list to the canvas.
*
* @param {Object} [props] Props object to pass to `tick()`. Should usually be a {@link core.Ticker} event object, or similar object with a delta property.
*/
update (props) {
if (!this.canvas) { return; }
if (this.tickOnUpdate) { this.tick(props); }
if (this.dispatchEvent("drawstart", false, true) === false) { return; }
DisplayObject._snapToPixelEnabled = this.snapToPixelEnabled;
let 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 {@link easeljs.Stage#update}
* unless {@link easeljs.Stage#tickOnUpdate} 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 {@link easeljs.Sprite#framerate} require that
* a {@link core.Ticker#event:tick} event object (or equivalent object with a delta property) be
* passed as the `props` parameter to `tick()`.
*
* @example
* Ticker.on("tick", (evt) => {
* // clone the event object from Ticker, and add some custom data to it:
* let data = evt.clone().set({ greeting: "hello", name: "world" });
* // pass it to stage.update():
* stage.update(data); // subsequently calls tick() with the same param
* });
*
* shape.on("tick", (evt) => {
* console.log(evt.delta); // the delta property from the Ticker tick event object
* console.log(evt.greeting, evt.name); // custom data: "hello world"
* });
*
* @emits easeljs.Stage#event:tickstart
* @emits easeljs.Stage#event:tickend
* @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.
*/
tick (props) {
if (!this.tickEnabled || this.dispatchEvent("tickstart", false, true) === false) { return; }
let evtObj = new Event("tick");
if (props) {
for (let n in props) {
if (props.hasOwnProperty(n)) { evtObj[n] = props[n]; }
}
}
this._tick(evtObj);
this.dispatchEvent("tickend");
}
/**
* Default event handler that calls the Stage {@link easeljs.Stage#update} method when a {@link easeljs.DisplayObject#event:tick}
* event is received. This allows you to register a Stage instance as a event listener on {@link core.Ticker} directly.
* 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 `delta` and `paused` parameters.
*/
handleEvent (evt) {
if (evt.type === "tick") { this.update(evt); }
}
/**
* Clears the target canvas. Useful if {@link easeljs.State#autoClear} is set to `false`.
*/
clear () {
if (!this.canvas) { return; }
let 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.
*
* @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. 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.
*/
toDataURL (backgroundColor, mimeType = "image/png") {
let 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);
}
let dataURL = this.canvas.toDataURL(mimeType);
if (backgroundColor) {
ctx.putImageData(data, 0, 0);
ctx.globalCompositeOperation = compositeOperation;
}
return dataURL;
}
/**
* Enables or disables (by passing a frequency of 0) mouse over {@link easeljs.DisplayObject#event:mouseover}
* and {@link easeljs.DisplayObject#event:mouseout} and roll over events {@link easeljs.DisplayObject#event:rollover}
* and {@link easeljs.DisplayObject#event:rollout} 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.
*
* @example
* const stage = new Stage("canvasId");
* stage.enableMouseOver(10); // 10 updates per second
*
* @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.
*/
enableMouseOver (frequency = 20) {
if (this._mouseOverIntervalID) {
clearInterval(this._mouseOverIntervalID);
this._mouseOverIntervalID = null;
if (frequency === 0) {
this._testMouseOver(true);
}
}
if (frequency <= 0) { return; }
this._mouseOverIntervalID = setInterval(() => this._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.
*
* @example
* stage.enableDOMEvents(false);
* stage.canvas = anotherCanvas;
* stage.enableDOMEvents(true);
*
* @param {Boolean} [enable=true] Indicates whether to enable or disable the events.
*/
enableDOMEvents (enable = true) {
let ls = this._eventListeners;
if (!enable && ls) {
for (let n in ls) {
let o = ls[n];
o.t.removeEventListener(n, o.f, false);
}
this._eventListeners = null;
} else if (enable && !ls && this.canvas) {
let t = window.addEventListener ? window : document;
ls = this._eventListeners = {
mouseup: {t, f:e => this._handleMouseUp(e) },
mousemove: {t, f:e => this._handleMouseMove(e) },
dblclick: {t:this.canvas, f:e => this._handleDoubleClick(e) },
mousedown: {t:this.canvas, f:e => this._handleMouseDown(e) }
};
for (let n in ls) {
let o = ls[n];
o.t.addEventListener && o.t.addEventListener(n, o.f, false);
}
}
}
/**
* Stage instances cannot be cloned.
* @throws Stage cannot be cloned
* @override
*/
clone () {
throw "Stage cannot be cloned.";
}
/**
* @protected
* @param {HTMLElement} e
* @returns {Object}
*/
_getElementRect (e) {
let 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}; }
let offX = (window.pageXOffset || document.scrollLeft || 0) - (document.clientLeft || document.body.clientLeft || 0);
let offY = (window.pageYOffset || document.scrollTop || 0) - (document.clientTop || document.body.clientTop || 0);
let styles = window.getComputedStyle ? getComputedStyle(e, null) : e.currentStyle; // IE <9 compatibility.
let padL = parseInt(styles.paddingLeft)+parseInt(styles.borderLeftWidth);
let padT = parseInt(styles.paddingTop)+parseInt(styles.borderTopWidth);
let padR = parseInt(styles.paddingRight)+parseInt(styles.borderRightWidth);
let 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
};
}
/**
* @protected
* @param {Number} id
* @returns {Object}
*/
_getPointerData (id) {
let data = this._pointerData[id];
if (!data) { data = this._pointerData[id] = {x:0, y:0}; }
return data;
}
/**
* @protected
* @param {easeljs.MouseEvent} [e=window.event]
*/
_handleMouseMove (e = window.event) {
this._handlePointerMove(-1, e, e.pageX, e.pageY);
}
/**
* @emits {@link easeljs.DisplayObject#event:mouseleave}
* @emits {@link easeljs.DisplayObject#event:mouseenter}
* @emits {@link easeljs.DisplayObject#event:pressmove}
* @emits {@link easeljs.Stage#event:stagemousemove}
* @protected
* @param {Number} id
* @param {easeljs.MouseEvent | Event} e
* @param {Number} pageX
* @param {Number} pageY
* @param {easeljs.Stage} owner Indicates that the event has already been captured & handled by the indicated stage.
*/
_handlePointerMove (id, e, pageX, pageY, owner) {
if (this._prevStage && owner === undefined) { return; } // redundant listener.
if (!this.canvas) { return; }
let nextStage=this._nextStage, o=this._getPointerData(id);
let 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);
}
/**
* @protected
* @param {Number} id
* @param {easeljs.MouseEvent | Event} e
* @param {Number} pageX
* @param {Number} pageY
*/
_updatePointerPosition (id, e, pageX, pageY) {
let rect = this._getElementRect(this.canvas);
pageX -= rect.left;
pageY -= rect.top;
let w = this.canvas.width;
let h = this.canvas.height;
pageX /= (rect.right-rect.left)/w;
pageY /= (rect.bottom-rect.top)/h;
let 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;
}
}
/**
* @protected
* @param {easeljs.MouseEvent} e
*/
_handleMouseUp (e) {
this._handlePointerUp(-1, e, false);
}
/**
* @emits {@link easeljs.Stage#event:stagemouseup}
* @emits {@link easeljs.DisplayObject#event:click}
* @emits {@link easeljs.DisplayObject#event:pressup}
* @protected
* @param {Number} id
* @param {easeljs.MouseEvent | Event} e
* @param {Boolean} clear
* @param {easeljs.Stage} owner Indicates that the event has already been captured & handled by the indicated stage.
*/
_handlePointerUp (id, e, clear, owner) {
let nextStage = this._nextStage, o = this._getPointerData(id);
if (this._prevStage && owner === undefined) { return; } // redundant listener.
let 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);
}
/**
* @protected
* @param {easeljs.MouseEvent} e
*/
_handleMouseDown (e) {
this._handlePointerDown(-1, e, e.pageX, e.pageY);
}
/**
* @emits {@link easeljs.Stage#event:stagemousedown}
* @emits {@link easeljs.DisplayObject#event:mousedown}
* @protected
* @param {Number} id
* @param {easeljs.MouseEvent | Event} e
* @param {Number} pageX
* @param {Number} pageY
* @param {easeljs.Stage} owner Indicates that the event has already been captured & handled by the indicated stage.
*/
_handlePointerDown (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); }
let 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);
}
/**
* @emits {@link easeljs.DisplayObject#event:mouseout}
* @emits {@link easeljs.DisplayObject#event:rollout}
* @emits {@link easeljs.DisplayObject#event:rollover}
* @emits {@link easeljs.DisplayObject#event:mouseover}
* @param {Boolean} clear If true, clears the mouseover / rollover (ie. no target)
* @param {easeljs.Stage} owner Indicates that the event has already been captured & handled by the indicated stage.
* @param {easeljs.Stage} eventTarget The stage that the cursor is actively over.
* @protected
*/
_testMouseOver (clear, owner, eventTarget) {
if (this._prevStage && owner === undefined) { return; } // redundant listener.
let nextStage = this._nextStage;
if (!this._mouseOverIntervalID) {
// not enabled for mouseover, but should still relay the event.
nextStage&&nextStage._testMouseOver(clear, owner, eventTarget);
return;
}
let 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; }
let e = o.posEvtObj;
let isEventTarget = eventTarget || e&&(e.target === this.canvas);
let target=null, common = -1, cursor="";
if (!owner && (clear || this.mouseInBounds && isEventTarget)) {
target = this._getObjectsUnderPoint(this.mouseX, this.mouseY, null, true);
this._mouseOverX = this.mouseX;
this._mouseOverY = this.mouseY;
}
let oldList = this._mouseOverTarget||[];
let oldTarget = oldList[oldList.length-1];
let list = this._mouseOverTarget = [];
// generate ancestor list and check for cursor:
let 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 (let 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 (let i=oldList.length-1; i>common; i--) {
this._dispatchMouseEvent(oldList[i], "rollout", false, -1, o, e, target);
}
for (let 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);
}
/**
* @emits {@link easeljs.DisplayObject#event:dblclick}
* @protected
* @param {easeljs.MouseEvent} e
* @param {easeljs.Stage} owner Indicates that the event has already been captured & handled by the indicated stage.
*/
_handleDoubleClick (e, owner) {
let 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);
}
/**
* @protected
* @param {easeljs.DisplayObject} target
* @param {String} type
* @param {Boolean} bubbles
* @param {Number} pointerId
* @param {Object} o
* @param {easeljs.MouseEvent} [nativeEvent]
* @param {easeljs.DisplayObject} [relatedTarget]
*/
_dispatchMouseEvent (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();
let pt = this._mtx.transformPoint(o.x, o.y);
let evt = new MouseEvent(type, bubbles, false, pt.x, pt.y, nativeEvent, pointerId, pointerId==this._primaryPointerID || pointerId==-1, o.rawX, o.rawY);
*/
let evt = new MouseEvent(type, bubbles, false, o.x, o.y, nativeEvent, pointerId, pointerId === this._primaryPointerID || pointerId === -1, o.rawX, o.rawY, relatedTarget);
target.dispatchEvent(evt);
}
}
/**
* Dispatched when the user moves the mouse over the canvas.
* @see {@link easeljs.MouseEvent}
* @event easeljs.Stage#stagemousemove
* @since 0.6.0
*/
/**
* Dispatched when the user presses their left mouse button on the canvas.
* You can use {@link easeljs.Stage#mouseInBounds} to check whether the mouse is currently within the stage bounds.
* @see {@link easeljs.MouseEvent}
* @event easeljs.Stage#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 {@link easeljs.Stage#mouseInBounds} to check whether the mouse is currently within the stage bounds.
* @see {@link easeljs.MouseEvent}
* @event easeljs.Stage#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 {@link easeljs.MouseEvent}
* @event easeljs.Stage#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 {@link easeljs.MouseEvent}
* @event easeljs.Stage#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 easeljs.Stage#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 easeljs.Stage#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 easeljs.Stage#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 easeljs.Stage#drawend
* @since 0.7.0
*/