diff --git a/sandbox/tests/input/pointer.html b/sandbox/tests/input/pointer.html
index 81ab98002..9383fa371 100644
--- a/sandbox/tests/input/pointer.html
+++ b/sandbox/tests/input/pointer.html
@@ -20,7 +20,7 @@
Number of Pointers
0
Pointer (Primary) Button
-
+ None
Last Pointer Page Pos (x,y)
Last Pointer Screen Pos (x,y)
diff --git a/sandbox/tests/input/pointer.ts b/sandbox/tests/input/pointer.ts
index a2b9c69d4..8c10e438f 100644
--- a/sandbox/tests/input/pointer.ts
+++ b/sandbox/tests/input/pointer.ts
@@ -8,25 +8,17 @@ var game = new ex.Engine({
});
var box = new ex.Actor(200, 200, 100, 100, ex.Color.Red);
var cursor = new ex.Actor(0, 0, 10, 10, ex.Color.Chartreuse);
-var boxPointerDown = false;
+var boxPointerDragging = false;
var uiElement = new ex.UIActor(200, 0, 200, 200);
-uiElement.color = ex.Color.Azure.clone();
+uiElement.color = ex.Color.Azure;
uiElement.on('pointerdown', (p: ex.Input.PointerEvent) => {
console.log(p);
- uiElement.color = ex.Color.Red.clone();
+ uiElement.color = ex.Color.Red;
});
-// Enable pointer input for box
-//box.enableCapturePointer = true;
-
-// Enable tracking mouse movement for box
-//box.capturePointer.captureMoveEvents = true;
-
// Change color of box when clicked
-box.on("pointerup", (pe: ex.Input.PointerEvent) => {
- boxPointerDown = false;
-
+box.on("pointerdown", (pe: ex.Input.PointerEvent) => {
if (box.color == ex.Color.Red) {
box.color = ex.Color.Blue;
} else {
@@ -34,17 +26,28 @@ box.on("pointerup", (pe: ex.Input.PointerEvent) => {
}
});
+// Set drag flag
+box.on("pointerdragstart", (pe: ex.Input.PointerEvent) => {
+ boxPointerDragging = true;
+});
+
+// Set drag flag
+box.on("pointerdragend", (pe: ex.Input.PointerEvent) => {
+ boxPointerDragging = false;
+});
+
// Drag box around
-box.on("pointermove", (pe: ex.Input.PointerEvent) => {
- if (boxPointerDown) {
- box.pos.x = pe.x;
- box.pos.y = pe.y;
+box.on("pointerdragmove", (pe: ex.Input.PointerEvent) => {
+ if (boxPointerDragging) {
+ box.pos = pe.pointer.lastWorldPos;
}
});
-// Set pointer down flag
-box.on("pointerdown", (pe: ex.Input.PointerEvent) => {
- boxPointerDown = true;
+// Drag box around
+box.on("pointerdragleave", (pe: ex.Input.PointerEvent) => {
+ if (boxPointerDragging) {
+ box.pos = pe.pointer.lastWorldPos;
+ }
});
box.on("pointerwheel", (pe: ex.Input.WheelEvent) => {
@@ -53,8 +56,7 @@ box.on("pointerwheel", (pe: ex.Input.WheelEvent) => {
// Follow cursor
game.input.pointers.primary.on("move", (pe: ex.Input.PointerEvent) => {
- cursor.pos.x = pe.x;
- cursor.pos.y = pe.y;
+ cursor.pos = pe.pointer.lastWorldPos;
});
// Button type
@@ -62,7 +64,7 @@ game.input.pointers.primary.on("down", (pe: ex.Input.PointerEvent) => {
document.getElementById("pointer-btn").innerHTML = ex.Input.PointerButton[pe.button];
});
game.input.pointers.primary.on("up", (pe: ex.Input.PointerEvent) => {
- document.getElementById("pointer-btn").innerHTML = "";
+ document.getElementById("pointer-btn").innerHTML = "None";
});
// Wheel
diff --git a/src/engine/Actor.ts b/src/engine/Actor.ts
index 3ef07e214..4095616a4 100644
--- a/src/engine/Actor.ts
+++ b/src/engine/Actor.ts
@@ -4,9 +4,10 @@ import { BoundingBox } from './Collision/BoundingBox';
import { Texture } from './Resources/Texture';
import {
InitializeEvent, KillEvent, PreUpdateEvent, PostUpdateEvent,
- PreDrawEvent, PostDrawEvent, PreDebugDrawEvent, PostDebugDrawEvent,
+ PreDrawEvent, PostDrawEvent, PreDebugDrawEvent, PostDebugDrawEvent,
GameEvent, PostCollisionEvent, PreCollisionEvent, CollisionStartEvent, CollisionEndEvent
} from './Events';
+import { PointerEvent, PointerDragEvent } from './Input/Pointer';
import { Engine } from './Engine';
import { Color } from './Drawing/Color';
import { Sprite } from './Drawing/Sprite';
@@ -382,13 +383,13 @@ export class ActorImpl extends Class implements IActionable, IEvented {
*
* The default is `null` which prevents a rectangle from being drawn.
*/
- public get color() : Color {
+ public get color(): Color {
return this._color;
}
- public set color(v : Color) {
+ public set color(v: Color) {
this._color = v.clone();
}
- private _color : Color;
+ private _color: Color;
/**
* Whether or not to enable the [[CapturePointer]] trait that propagates
@@ -400,7 +401,8 @@ export class ActorImpl extends Class implements IActionable, IEvented {
* Configuration for [[CapturePointer]] trait
*/
public capturePointer: Traits.ICapturePointerConfig = {
- captureMoveEvents: false
+ captureMoveEvents: false,
+ captureDragEvents: false
};
private _zIndex: number = 0;
@@ -435,7 +437,7 @@ export class ActorImpl extends Class implements IActionable, IEvented {
// set default opacity of an actor to the color
this.opacity = color.a;
}
-
+
// Build default pipeline
//this.traits.push(new ex.Traits.EulerMovement());
// TODO: TileMaps should be converted to a collision area
@@ -484,14 +486,34 @@ export class ActorImpl extends Class implements IActionable, IEvented {
}
}
+ private _capturePointerEvents: string[] = [
+ 'pointerup', 'pointerdown', 'pointermove', 'pointerenter', 'pointerleave',
+ 'pointerdragstart', 'pointerdragend', 'pointerdragmove', 'pointerdragenter', 'pointerdragleave'
+ ];
+
+ private _captureMoveEvents: string[] = [
+ 'pointermove', 'pointerenter', 'pointerleave',
+ 'pointerdragmove', 'pointerdragenter', 'pointerdragleave'
+ ];
+
+ private _captureDragEvents: string[] = [
+ 'pointerdragstart', 'pointerdragend', 'pointerdragmove', 'pointerdragenter', 'pointerdragleave'
+ ];
+
private _checkForPointerOptIn(eventName: string) {
if (eventName) {
const normalized = eventName.toLowerCase();
- if (normalized === 'pointerup' || normalized === 'pointerdown' || normalized === 'pointermove') {
+
+ if (this._capturePointerEvents.indexOf(normalized) !== -1) {
this.enableCapturePointer = true;
- if (normalized === 'pointermove') {
+
+ if (this._captureMoveEvents.indexOf(normalized) !== -1) {
this.capturePointer.captureMoveEvents = true;
}
+
+ if (this._captureDragEvents.indexOf(normalized) !== -1) {
+ this.capturePointer.captureDragEvents = true;
+ }
}
}
}
@@ -510,9 +532,16 @@ export class ActorImpl extends Class implements IActionable, IEvented {
public on(eventName: Events.postdebugdraw, handler: (event?: PostDebugDrawEvent) => void): void;
public on(eventName: Events.pointerup, handler: (event?: PointerEvent) => void): void;
public on(eventName: Events.pointerdown, handler: (event?: PointerEvent) => void): void;
+ public on(eventName: Events.pointerenter, handler: (event?: PointerEvent) => void): void;
+ public on(eventName: Events.pointerleave, handler: (event?: PointerEvent) => void): void;
public on(eventName: Events.pointermove, handler: (event?: PointerEvent) => void): void;
public on(eventName: Events.pointercancel, handler: (event?: PointerEvent) => void): void;
public on(eventName: Events.pointerwheel, handler: (event?: WheelEvent) => void): void;
+ public on(eventName: Events.pointerdragstart, handler: (event?: PointerDragEvent) => void): void;
+ public on(eventName: Events.pointerdragend, handler: (event?: PointerDragEvent) => void): void;
+ public on(eventName: Events.pointerdragenter, handler: (event?: PointerDragEvent) => void): void;
+ public on(eventName: Events.pointerdragleave, handler: (event?: PointerDragEvent) => void): void;
+ public on(eventName: Events.pointerdragmove, handler: (event?: PointerDragEvent) => void): void;
public on(eventName: string, handler: (event?: GameEvent) => void): void;
public on(eventName: string, handler: (event?: any) => void): void {
this._checkForPointerOptIn(eventName);
@@ -533,9 +562,16 @@ export class ActorImpl extends Class implements IActionable, IEvented {
public once(eventName: Events.postdebugdraw, handler: (event?: PostDebugDrawEvent) => void): void;
public once(eventName: Events.pointerup, handler: (event?: PointerEvent) => void): void;
public once(eventName: Events.pointerdown, handler: (event?: PointerEvent) => void): void;
+ public once(eventName: Events.pointerenter, handler: (event?: PointerEvent) => void): void;
+ public once(eventName: Events.pointerleave, handler: (event?: PointerEvent) => void): void;
public once(eventName: Events.pointermove, handler: (event?: PointerEvent) => void): void;
public once(eventName: Events.pointercancel, handler: (event?: PointerEvent) => void): void;
public once(eventName: Events.pointerwheel, handler: (event?: WheelEvent) => void): void;
+ public once(eventName: Events.pointerdragstart, handler: (event?: PointerDragEvent) => void): void;
+ public once(eventName: Events.pointerdragend, handler: (event?: PointerDragEvent) => void): void;
+ public once(eventName: Events.pointerdragenter, handler: (event?: PointerDragEvent) => void): void;
+ public once(eventName: Events.pointerdragleave, handler: (event?: PointerDragEvent) => void): void;
+ public once(eventName: Events.pointerdragmove, handler: (event?: PointerDragEvent) => void): void;
public once(eventName: string, handler: (event?: GameEvent) => void): void;
public once(eventName: string, handler: (event?: any) => void): void {
this._checkForPointerOptIn(eventName);
@@ -825,7 +861,7 @@ export class ActorImpl extends Class implements IActionable, IEvented {
/**
* Returns the actor's [[BoundingBox]] calculated for this instant in world space.
*/
- public getBounds() {
+ public getBounds(): BoundingBox {
// todo cache bounding box
var anchor = this._getCalculatedAnchor();
var pos = this.getWorldPos();
@@ -1024,7 +1060,7 @@ export class ActorImpl extends Class implements IActionable, IEvented {
}
// Update child actors
- for (var i = 0; i < this.children.length; i++) {
+ for (var i = 0; i < this.children.length; i++) {
this.children[i].update(engine, delta);
}
diff --git a/src/engine/Algebra.ts b/src/engine/Algebra.ts
index 9541b7f00..66b286e69 100644
--- a/src/engine/Algebra.ts
+++ b/src/engine/Algebra.ts
@@ -1,3 +1,6 @@
+import { Engine } from 'Engine';
+import * as Util from 'Util/Util';
+
/**
* A 2D vector on a plane.
*/
@@ -415,7 +418,7 @@ export class Line {
} else if (y !== null) {
return new Vector((y - b) / m, y);
} else {
- throw new Error('You must provide an X or a Y value');
+ throw new Error('You must provide an X or a Y value');
}
}
@@ -496,4 +499,36 @@ export class Projection {
}
return 0;
}
+}
+
+export class GlobalCoordinates {
+ public static fromPagePosition(x: number, y: number, engine: Engine): GlobalCoordinates;
+ public static fromPagePosition(pos: Vector, engine: Engine): GlobalCoordinates;
+ public static fromPagePosition(xOrPos: number | Vector, yOrEngine: number | Engine, engineOrUndefined?: Engine): GlobalCoordinates {
+ var pageX: number;
+ var pageY: number;
+ var pagePos: Vector;
+ var engine: Engine;
+
+ if (arguments.length === 3) {
+ pageX = xOrPos;
+ pageY = yOrEngine;
+ pagePos = new Vector(pageX, pageY);
+ engine = engineOrUndefined;
+ } else {
+ pagePos = xOrPos;
+ pageX = pagePos.x;
+ pageY = pagePos.y;
+ engine = yOrEngine;
+ }
+
+ var screenX: number = pageX - Util.getPosition(engine.canvas).x;
+ var screenY: number = pageY - Util.getPosition(engine.canvas).y;
+ var screenPos = new Vector(screenX, screenY);
+ var worldPos = engine.screenToWorldCoordinates(screenPos);
+
+ return new GlobalCoordinates(worldPos, pagePos, screenPos);
+ }
+
+ constructor(public worldPos: Vector, public pagePos: Vector, public screenPos: Vector) { }
}
\ No newline at end of file
diff --git a/src/engine/Events.ts b/src/engine/Events.ts
index b82e3b1ec..876c6ddca 100644
--- a/src/engine/Events.ts
+++ b/src/engine/Events.ts
@@ -56,12 +56,16 @@ export type stop = 'stop';
export type pointerup = 'pointerup';
export type pointerdown = 'pointerdown';
export type pointermove = 'pointermove';
+export type pointerenter = 'pointerenter';
+export type pointerleave = 'pointerleave';
export type pointercancel = 'pointercancel';
export type pointerwheel = 'pointerwheel';
export type up = 'up';
export type down = 'down';
export type move = 'move';
+export type enter = 'enter';
+export type leave = 'leave';
export type cancel = 'cancel';
export type wheel = 'wheel';
@@ -69,6 +73,12 @@ export type press = 'press';
export type release = 'release';
export type hold = 'hold';
+export type pointerdragstart = 'pointerdragstart';
+export type pointerdragend = 'pointerdragend';
+export type pointerdragenter = 'pointerdragenter';
+export type pointerdragleave = 'pointerdragleave';
+export type pointerdragmove = 'pointerdragmove';
+
/**
* Base event type in Excalibur that all other event types derive from. Not all event types are thrown on all Excalibur game objects,
* some events are unique to a type, others are not.
@@ -276,7 +286,7 @@ export class HiddenEvent extends GameEvent {
* Event thrown on an [[Actor|actor]] when a collision will occur this frame if it resolves
*/
export class PreCollisionEvent extends GameEvent {
-
+
/**
* @param actor The actor the event was thrown on
* @param other The actor that will collided with the current actor
@@ -389,7 +399,6 @@ export class EnterViewPortEvent extends GameEvent {
}
}
-
export class EnterTriggerEvent extends GameEvent {
constructor(public target: Trigger, public actor: Actor) {
super();
diff --git a/src/engine/Input/Pointer.ts b/src/engine/Input/Pointer.ts
index dbe7a7b4d..8daf3e61f 100644
--- a/src/engine/Input/Pointer.ts
+++ b/src/engine/Input/Pointer.ts
@@ -1,8 +1,9 @@
-import {Engine, ScrollPreventionMode} from './../Engine';
+import { Engine, ScrollPreventionMode } from './../Engine';
import { GameEvent } from '../Events';
-import { UIActor } from '../UIActor';
-import { Vector } from '../Algebra';
+import { Actor } from '../Actor';
+import { Vector, GlobalCoordinates } from '../Algebra';
import { Class } from '../Class';
+import * as Actors from '../Util/Actors';
import * as Util from '../Util/Util';
import * as Events from '../Events';
@@ -66,35 +67,86 @@ const ScrollWheelNormalizationFactor = -1 / 40;
*/
export class PointerEvent extends GameEvent {
+ /** Deprecated. Use [[PointerEvent]].worldPos.x instead. */
+ public get x(): number {
+ return this.coordinates.worldPos.x;
+ }
+
+ /** Deprecated. Use [[PointerEvent]].worldPos.y instead. */
+ public get y(): number {
+ return this.coordinates.worldPos.y;
+ }
+
+ /** Deprecated. Use [[PointerEvent]].worldPos.x instead. */
+ public get worldX(): number {
+ return this.coordinates.worldPos.x;
+ }
+
+ /** Deprecated. Use [[PointerEvent]].worldPos.y instead. */
+ public get worldY(): number {
+ return this.coordinates.worldPos.y;
+ }
+
+ /** Deprecated. Use [[PointerEvent]].pagePos.x instead. */
+ public get pageX(): number {
+ return this.coordinates.pagePos.x;
+ }
+
+ /** Deprecated. Use [[PointerEvent]].pagePos.y instead. */
+ public get pageY(): number {
+ return this.coordinates.pagePos.y;
+ }
+
+ /** Deprecated. Use [[PointerEvent]].screenPos.x instead. */
+ public get screenX(): number {
+ return this.coordinates.screenPos.x;
+ }
+
+ /** Deprecated. Use [[PointerEvent]].screenPos.y instead. */
+ public get screenY(): number {
+ return this.coordinates.screenPos.y;
+ }
+
+ /** The world coordinates of the event. */
+ public get worldPos(): Vector {
+ return this.coordinates.worldPos.clone();
+ }
+
+ /** The page coordinates of the event. */
+ public get pagePos(): Vector {
+ return this.coordinates.pagePos.clone();
+ }
+
+ /** The screen coordinates of the event. */
+ public get screenPos(): Vector {
+ return this.coordinates.screenPos.clone();
+ }
+
/**
- * @param pageX The `x` coordinate of the event (in document coordinates)
- * @param pageY The `y` coordinate of the event (in document coordinates)
- * @param screenX The `x` coordinate of the event (in screen coordinates)
- * @param screenY The `y` coordinate of the event (in screen coordinates)
- * @param index The index of the pointer (zero-based)
- * @param pointerType The type of pointer
- * @param button The button pressed (if [[PointerType.Mouse]])
- * @param ev The raw DOM event being handled
- * @param pos (Will be added to signature in 0.14.0 release) The position of the event (in world coordinates)
+ * @param coordinates The [[GlobalCoordinates]] of the event
+ * @param pointer The [[Pointer]] of the event
+ * @param index The index of the pointer (zero-based)
+ * @param pointerType The type of pointer
+ * @param button The button pressed (if [[PointerType.Mouse]])
+ * @param ev The raw DOM event being handled
+ * @param pos (Will be added to signature in 0.14.0 release) The position of the event (in world coordinates)
*/
- constructor(public x: number,
- public y: number,
- public pageX: number,
- public pageY: number,
- public screenX: number,
- public screenY: number,
- public index: number,
- public pointerType: PointerType,
- public button: PointerButton,
- public ev: any) {
+ constructor(private coordinates: GlobalCoordinates,
+ public pointer: Pointer,
+ public index: number,
+ public pointerType: PointerType,
+ public button: PointerButton,
+ public ev: any) {
super();
}
public get pos(): Vector {
- return new Vector(this.x, this.y);
+ return this.coordinates.worldPos.clone();
}
};
+export class PointerDragEvent extends PointerEvent { }
+
/**
* Wheel Events
*
@@ -118,17 +170,17 @@ export class WheelEvent extends GameEvent {
* @param ev The raw DOM event being handled
*/
constructor(public x: number,
- public y: number,
- public pageX: number,
- public pageY: number,
- public screenX: number,
- public screenY: number,
- public index: number,
- public deltaX: number,
- public deltaY: number,
- public deltaZ: number,
- public deltaMode: WheelDeltaMode,
- public ev: any) {
+ public y: number,
+ public pageX: number,
+ public pageY: number,
+ public screenX: number,
+ public screenY: number,
+ public index: number,
+ public deltaX: number,
+ public deltaY: number,
+ public deltaZ: number,
+ public deltaMode: WheelDeltaMode,
+ public ev: any) {
super();
}
};
@@ -162,6 +214,8 @@ export class Pointers extends Class {
public on(eventName: Events.up, handler: (event?: PointerEvent) => void): void;
public on(eventName: Events.down, handler: (event?: PointerEvent) => void): void;
public on(eventName: Events.move, handler: (event?: PointerEvent) => void): void;
+ public on(eventName: Events.enter, handler: (event?: PointerEvent) => void): void;
+ public on(eventName: Events.leave, handler: (event?: PointerEvent) => void): void;
public on(eventName: Events.cancel, handler: (event?: PointerEvent) => void): void;
public on(eventName: Events.wheel, handler: (event?: WheelEvent) => void): void;
public on(eventName: string, handler: (event?: GameEvent) => void): void;
@@ -194,18 +248,15 @@ export class Pointers extends Class {
target.addEventListener('pointerdown', this._handlePointerEvent('down', this._pointerDown));
target.addEventListener('pointerup', this._handlePointerEvent('up', this._pointerUp));
target.addEventListener('pointermove', this._handlePointerEvent('move', this._pointerMove));
- target.addEventListener('pointercancel', this._handlePointerEvent('cancel', this._pointerMove));
-
+ target.addEventListener('pointercancel', this._handlePointerEvent('cancel', this._pointerCancel));
} else if ((window).MSPointerEvent) {
// IE10
this._engine.canvas.style.msTouchAction = 'none';
target.addEventListener('MSPointerDown', this._handlePointerEvent('down', this._pointerDown));
target.addEventListener('MSPointerUp', this._handlePointerEvent('up', this._pointerUp));
target.addEventListener('MSPointerMove', this._handlePointerEvent('move', this._pointerMove));
- target.addEventListener('MSPointerCancel', this._handlePointerEvent('cancel', this._pointerMove));
-
+ target.addEventListener('MSPointerCancel', this._handlePointerEvent('cancel', this._pointerCancel));
} else {
-
// Mouse Events
target.addEventListener('mousedown', this._handleMouseEvent('down', this._pointerDown));
target.addEventListener('mouseup', this._handleMouseEvent('up', this._pointerUp));
@@ -231,6 +282,10 @@ export class Pointers extends Class {
this._pointerMove.length = 0;
this._pointerCancel.length = 0;
this._wheel.length = 0;
+
+ for (let i = 0; i < this._pointers.length; i++) {
+ this._pointers[i].update();
+ }
}
/**
@@ -260,34 +315,81 @@ export class Pointers extends Class {
/**
* Propogates events to actor if necessary
*/
- public propogate(actor: any) {
- var isUIActor = actor instanceof UIActor;
- var i: number = 0,
- len: number = this._pointerUp.length;
+ public propogate(actor: Actor) {
+ var isNotUIActor = !Actors.isUIActor(actor);
+ var i: number = 0;
+ var len: number = 0;
+ var pointerEvent: PointerEvent;
+ var pointer: Pointer;
+
+ i = 0;
+ len = this._pointerDown.length;
for (i; i < len; i++) {
- if (actor.contains(this._pointerUp[i].x, this._pointerUp[i].y, !isUIActor)) {
- actor.eventDispatcher.emit('pointerup', this._pointerUp[i]);
+ pointerEvent = this._pointerDown[i];
+ pointer = pointerEvent.pointer;
+
+ if (actor.contains(pointer.lastWorldPos.x, pointer.lastWorldPos.y, isNotUIActor)) {
+ actor.eventDispatcher.emit('pointerdown', pointerEvent);
+
+ if (pointer.isDragStart && actor.capturePointer.captureDragEvents) {
+ actor.eventDispatcher.emit('pointerdragstart', pointerEvent);
+ }
}
}
i = 0;
- len = this._pointerDown.length;
+ len = this._pointerUp.length;
for (i; i < len; i++) {
- if (actor.contains(this._pointerDown[i].x, this._pointerDown[i].y, !isUIActor)) {
- actor.eventDispatcher.emit('pointerdown', this._pointerDown[i]);
+ pointerEvent = this._pointerUp[i];
+ pointer = pointerEvent.pointer;
+
+ if (actor.contains(pointer.lastWorldPos.x, pointer.lastWorldPos.y, isNotUIActor)) {
+ actor.eventDispatcher.emit('pointerup', pointerEvent);
+
+ if (pointer.isDragEnd && actor.capturePointer.captureDragEvents) {
+ actor.eventDispatcher.emit('pointerdragend', pointerEvent);
+ }
}
}
if (actor.capturePointer.captureMoveEvents) {
-
i = 0;
len = this._pointerMove.length;
for (i; i < len; i++) {
- if (actor.contains(this._pointerMove[i].x, this._pointerMove[i].y, !isUIActor)) {
- actor.eventDispatcher.emit('pointermove', this._pointerMove[i]);
+ pointerEvent = this._pointerMove[i];
+ pointer = pointerEvent.pointer;
+
+ if (actor.contains(pointer.lastWorldPos.x, pointer.lastWorldPos.y, isNotUIActor)) {
+ if (!pointer.actorsUnderPointer[actor.id]) {
+ pointer.actorsUnderPointer[actor.id] = actor;
+
+ actor.eventDispatcher.emit('pointerenter', pointerEvent);
+
+ if (pointer.isDragging && actor.capturePointer.captureDragEvents) {
+ actor.eventDispatcher.emit('pointerdragenter', pointerEvent);
+ }
+ }
+
+ actor.eventDispatcher.emit('pointermove', pointerEvent);
+
+ if (pointer.isDragging && actor.capturePointer.captureDragEvents) {
+ actor.eventDispatcher.emit('pointerdragmove', pointerEvent);
+ }
+ }
+
+ if (!actor.contains(pointer.lastWorldPos.x, pointer.lastWorldPos.y, isNotUIActor)) {
+ if (pointer.actorsUnderPointer[actor.id]) {
+ delete pointer.actorsUnderPointer[actor.id];
+
+ actor.eventDispatcher.emit('pointerleave', pointerEvent);
+
+ if (pointer.isDragging && actor.capturePointer.captureDragEvents) {
+ actor.eventDispatcher.emit('pointerdragleave', pointerEvent);
+ }
+ }
}
}
}
@@ -296,15 +398,19 @@ export class Pointers extends Class {
len = this._pointerCancel.length;
for (i; i < len; i++) {
- if (actor.contains(this._pointerCancel[i].x, this._pointerCancel[i].y, !isUIActor)) {
- actor.eventDispatcher.emit('pointercancel', this._pointerCancel[i]);
+ pointerEvent = this._pointerCancel[i];
+ pointer = pointerEvent.pointer;
+
+ if (actor.contains(pointer.lastWorldPos.x, pointer.lastWorldPos.y, isNotUIActor)) {
+ actor.eventDispatcher.emit('pointercancel', pointerEvent);
}
}
i = 0;
len = this._wheel.length;
+
for (i; i < len; i++) {
- if (actor.contains(this._wheel[i].x, this._wheel[i].y, !isUIActor)) {
+ if (actor.contains(this._wheel[i].x, this._wheel[i].y, isNotUIActor)) {
actor.eventDispatcher.emit('pointerwheel', this._wheel[i]);
}
}
@@ -313,12 +419,13 @@ export class Pointers extends Class {
private _handleMouseEvent(eventName: string, eventArr: PointerEvent[]) {
return (e: MouseEvent) => {
e.preventDefault();
- var x: number = e.pageX - Util.getPosition(this._engine.canvas).x;
- var y: number = e.pageY - Util.getPosition(this._engine.canvas).y;
- var transformedPoint = this._engine.screenToWorldCoordinates(new Vector(x, y));
- var pe = new PointerEvent(transformedPoint.x, transformedPoint.y, e.pageX, e.pageY, x, y, 0, PointerType.Mouse, e.button, e);
+
+ var pointer = this.at(0);
+ var coordinates = GlobalCoordinates.fromPagePosition(e.pageX, e.pageY, this._engine);
+ var pe = new PointerEvent(coordinates, pointer, 0, PointerType.Mouse, e.button, e);
+
eventArr.push(pe);
- this.at(0).eventDispatcher.emit(eventName, pe);
+ pointer.eventDispatcher.emit(eventName, pe);
};
}
@@ -328,13 +435,14 @@ export class Pointers extends Class {
for (var i = 0, len = e.changedTouches.length; i < len; i++) {
var index = this._pointers.length > 1 ? this._getPointerIndex(e.changedTouches[i].identifier) : 0;
if (index === -1) { continue; }
- var x: number = e.changedTouches[i].pageX - Util.getPosition(this._engine.canvas).x;
- var y: number = e.changedTouches[i].pageY - Util.getPosition(this._engine.canvas).y;
- var transformedPoint = this._engine.screenToWorldCoordinates(new Vector(x, y));
- var pe = new PointerEvent(transformedPoint.x, transformedPoint.y,
- e.changedTouches[i].pageX, e.changedTouches[i].pageY, x, y, index, PointerType.Touch, PointerButton.Unknown, e);
+
+ var pointer = this.at(index);
+ var coordinates = GlobalCoordinates.fromPagePosition(
+ e.changedTouches[i].pageX, e.changedTouches[i].pageY, this._engine);
+ var pe = new PointerEvent(coordinates, pointer, index, PointerType.Touch, PointerButton.Unknown, e);
+
eventArr.push(pe);
- this.at(index).eventDispatcher.emit(eventName, pe);
+ pointer.eventDispatcher.emit(eventName, pe);
// only with multi-pointer
if (this._pointers.length > 1) {
if (eventName === 'up') {
@@ -358,13 +466,13 @@ export class Pointers extends Class {
// get the index for this pointer ID if multi-pointer is asked for
var index = this._pointers.length > 1 ? this._getPointerIndex(e.pointerId) : 0;
if (index === -1) { return; }
- var x: number = e.pageX - Util.getPosition(this._engine.canvas).x;
- var y: number = e.pageY - Util.getPosition(this._engine.canvas).y;
- var transformedPoint = this._engine.screenToWorldCoordinates(new Vector(x, y));
- var pe = new PointerEvent(transformedPoint.x, transformedPoint.y,
- e.pageX, e.pageY, x, y, index, this._stringToPointerType(e.pointerType), e.button, e);
+
+ var pointer = this.at(index);
+ var coordinates = GlobalCoordinates.fromPagePosition(e.pageX, e.pageY, this._engine);
+ var pe = new PointerEvent(coordinates, pointer, index, this._stringToPointerType(e.pointerType), e.button, e);
+
eventArr.push(pe);
- this.at(index).eventDispatcher.emit(eventName, pe);
+ pointer.eventDispatcher.emit(eventName, pe);
// only with multi-pointer
if (this._pointers.length > 1) {
@@ -385,7 +493,7 @@ export class Pointers extends Class {
return (e: MouseWheelEvent) => {
// Should we prevent page scroll because of this event
if (this._engine.pageScrollPreventionMode === ScrollPreventionMode.All ||
- (this._engine.pageScrollPreventionMode === ScrollPreventionMode.Canvas && e.target === this._engine.canvas)) {
+ (this._engine.pageScrollPreventionMode === ScrollPreventionMode.Canvas && e.target === this._engine.canvas)) {
e.preventDefault();
}
@@ -398,13 +506,13 @@ export class Pointers extends Class {
// e.detail is only used in opera
var deltaX = e.deltaX ||
- (e.wheelDeltaX * ScrollWheelNormalizationFactor) ||
- 0;
+ (e.wheelDeltaX * ScrollWheelNormalizationFactor) ||
+ 0;
var deltaY = e.deltaY ||
- (e.wheelDeltaY * ScrollWheelNormalizationFactor) ||
- (e.wheelDelta * ScrollWheelNormalizationFactor) ||
- e.detail ||
- 0;
+ (e.wheelDeltaY * ScrollWheelNormalizationFactor) ||
+ (e.wheelDelta * ScrollWheelNormalizationFactor) ||
+ e.detail ||
+ 0;
var deltaZ = e.deltaZ || 0;
var deltaMode = WheelDeltaMode.Pixel;
@@ -461,31 +569,76 @@ export class Pointers extends Class {
*/
export class Pointer extends Class {
- constructor() {
- super();
+ private _isDown: boolean = false;
+ private _wasDown: boolean = false;
- this.on('move', this._onPointerMove);
+ /**
+ * Whether the Pointer is currently dragging.
+ */
+ public get isDragging(): boolean {
+ return this._isDown;
+ }
+
+ /**
+ * Whether the Pointer just started dragging.
+ */
+ public get isDragStart(): boolean {
+ return !this._wasDown && this._isDown;
+ }
+
+
+ /**
+ * Whether the Pointer just ended dragging.
+ */
+ public get isDragEnd(): boolean {
+ return this._wasDown && !this._isDown;
}
/**
* The last position on the document this pointer was at. Can be `null` if pointer was never active.
*/
- lastPagePos: Vector = null;
+ public lastPagePos: Vector = null;
/**
* The last position on the screen this pointer was at. Can be `null` if pointer was never active.
*/
- lastScreenPos: Vector = null;
+ public lastScreenPos: Vector = null;
/**
* The last position in the game world coordinates this pointer was at. Can be `null` if pointer was never active.
*/
- lastWorldPos: Vector = null;
+ public lastWorldPos: Vector = null;
- private _onPointerMove(ev: PointerEvent) {
- this.lastWorldPos = new Vector(ev.x, ev.y);
+ public actorsUnderPointer: { [ActorId: number]: Actor; } = {};
+
+ constructor() {
+ super();
+
+ this.on('move', this._onPointerMove);
+ this.on('down', this._onPointerDown);
+ this.on('up', this._onPointerUp);
+ }
+
+ public update(): void {
+ if (this._wasDown && !this._isDown) {
+ this._wasDown = false;
+ } else if (!this._wasDown && this._isDown) {
+ this._wasDown = true;
+ }
+ }
+
+ private _onPointerMove(ev: PointerEvent): void {
this.lastPagePos = new Vector(ev.pageX, ev.pageY);
this.lastScreenPos = new Vector(ev.screenX, ev.screenY);
+ this.lastWorldPos = new Vector(ev.x, ev.y);
+ }
+
+ private _onPointerDown(): void {
+ this._isDown = true;
+ }
+
+ private _onPointerUp(): void {
+ this._isDown = false;
}
}
diff --git a/src/engine/Traits/CapturePointer.ts b/src/engine/Traits/CapturePointer.ts
index d0ee8285b..738656e49 100644
--- a/src/engine/Traits/CapturePointer.ts
+++ b/src/engine/Traits/CapturePointer.ts
@@ -3,12 +3,17 @@ import { Actor } from '../Actor';
import { Engine } from '../Engine';
export interface ICapturePointerConfig {
-
+
/**
* Capture PointerMove events (may be expensive!)
*/
captureMoveEvents: boolean;
+ /**
+ * Capture PointerDrag events (may be expensive!)
+ */
+ captureDragEvents: boolean;
+
}
/**
@@ -19,7 +24,7 @@ export class CapturePointer implements IActorTrait {
public update(actor: Actor, engine: Engine) {
if (!actor.enableCapturePointer) { return; }
if (actor.isKilled()) { return; }
-
- engine.input.pointers.propogate(actor);
+
+ engine.input.pointers.propogate(actor);
}
}
\ No newline at end of file
diff --git a/src/spec/ActorSpec.ts b/src/spec/ActorSpec.ts
index ccd244b91..a264c855c 100644
--- a/src/spec/ActorSpec.ts
+++ b/src/spec/ActorSpec.ts
@@ -2,9 +2,9 @@
///
describe('A game actor', () => {
-
+
var actor: ex.Actor;
-
+
var engine: ex.Engine;
var scene: ex.Scene;
var mock = new Mocks.Mocker();
@@ -20,7 +20,7 @@ describe('A game actor', () => {
spyOn(scene, 'debugDraw').and.callThrough();
spyOn(actor, 'draw');
- spyOn(actor, 'debugDraw');
+ spyOn(actor, 'debugDraw');
ex.Physics.useBoxPhysics();
ex.Physics.acc.setTo(0, 0);
@@ -91,7 +91,7 @@ describe('A game actor', () => {
expect(actor.pos.x).toBe(20);
expect(actor.pos.y).toBe(20);
});
-
+
it('actors should generate pair hashes in the correct order', () => {
var actor = new ex.Actor();
actor.id = 20;
@@ -234,7 +234,7 @@ describe('A game actor', () => {
actor.setHeight(100);
actor.scale.setTo(2, 2);
actor.anchor = new ex.Vector(0.5, 0.5);
-
+
expect(actor.getLeft()).toBe(-100);
expect(actor.getRight()).toBe(100);
expect(actor.getTop()).toBe(-100);
@@ -251,7 +251,7 @@ describe('A game actor', () => {
var child = new ex.Actor(0, 0, 50, 50);
actor.add(child);
-
+
expect(child.getLeft()).toBe(-50);
expect(child.getRight()).toBe(50);
expect(child.getTop()).toBe(-50);
@@ -259,7 +259,7 @@ describe('A game actor', () => {
});
it('should have the correct bounds when scaled and rotated', () => {
-
+
var actor = new ex.Actor(50, 50, 10, 10);
// actor is now 20 high
actor.scale.setTo(1, 2);
@@ -276,7 +276,7 @@ describe('A game actor', () => {
});
it('should have the correct relative bounds when scaled and rotated', () => {
-
+
var actor = new ex.Actor(50, 50, 10, 10);
// actor is now 20 high
actor.scale.setTo(1, 2);
@@ -291,7 +291,7 @@ describe('A game actor', () => {
expect(bounds.top).toBeCloseTo(-5, .001);
expect(bounds.bottom).toBeCloseTo(5, .001);
});
-
+
it('has a left, right, top, and bottom when the anchor is (0, 0)', () => {
actor.pos.x = 100;
actor.pos.y = 100;
@@ -320,7 +320,7 @@ describe('A game actor', () => {
expect(actor.contains(21, 20)).toBe(false);
expect(actor.contains(20, 21)).toBe(false);
-
+
expect(actor.contains(0, -1)).toBe(false);
expect(actor.contains(-1, 0)).toBe(false);
});
@@ -366,11 +366,11 @@ describe('A game actor', () => {
var actorCalled = 'false';
var otherCalled = 'false';
- actor.on('precollision', function() {
+ actor.on('precollision', function () {
actorCalled = 'actor';
});
- other.on('precollision', function() {
+ other.on('precollision', function () {
otherCalled = 'other';
});
@@ -381,7 +381,7 @@ describe('A game actor', () => {
expect(actorCalled).toBe('actor');
expect(otherCalled).toBe('other');
- });
+ });
it('is rotated along with its parent', () => {
var rotation = ex.Util.toRadians(90);
@@ -482,7 +482,7 @@ describe('A game actor', () => {
expect(grandchild.getWorldPos().x).toBeCloseTo(10, 0.001);
expect(grandchild.getWorldPos().y).toBeCloseTo(50, 0.001);
- });
+ });
it('can find its global coordinates if it has a parent', () => {
expect(actor.pos.x).toBe(0);
@@ -555,125 +555,125 @@ describe('A game actor', () => {
expect(actor.capturePointer.captureMoveEvents).toBeTruthy();
expect(actor.enableCapturePointer).toBeTruthy();
});
-
+
it('changes opacity on color', () => {
actor.color = ex.Color.Black.clone();
expect(actor.color.a).toBe(1);
expect(actor.color.r).toBe(0);
expect(actor.color.g).toBe(0);
expect(actor.color.b).toBe(0);
-
+
expect(actor.opacity).toBe(1.0);
actor.opacity = .5;
-
+
actor.update(engine, 100);
expect(actor.color.a).toBe(.5);
expect(actor.color.r).toBe(0);
expect(actor.color.g).toBe(0);
expect(actor.color.b).toBe(0);
});
-
+
it('can detect containment off of child actors', () => {
var parent = new ex.Actor(600, 100, 100, 100);
var child = new ex.Actor(0, 0, 100, 100);
var child2 = new ex.Actor(-600, -100, 100, 100);
-
+
parent.add(child);
child.add(child2);
-
+
// check reality
expect(parent.contains(550, 50)).toBeTruthy();
expect(parent.contains(650, 150)).toBeTruthy();
// in world coordinates this should be false
expect(child.contains(50, 50)).toBeFalsy();
-
+
// in world coordinates this should be true
expect(child.contains(550, 50)).toBeTruthy();
expect(child.contains(650, 150)).toBeTruthy();
-
+
// second order child shifted to the origin
expect(child2.contains(-50, -50)).toBeTruthy();
- expect(child2.contains(50, 50)).toBeTruthy();
+ expect(child2.contains(50, 50)).toBeTruthy();
});
-
+
it('can recursively check containment', () => {
- var parent = new ex.Actor(0, 0, 100, 100);
+ var parent = new ex.Actor(0, 0, 100, 100);
var child = new ex.Actor(100, 100, 100, 100);
var child2 = new ex.Actor(100, 100, 100, 100);
parent.add(child);
-
+
expect(parent.contains(150, 150)).toBeFalsy();
expect(child.contains(150, 150)).toBeTruthy();
expect(parent.contains(150, 150, true)).toBeTruthy();
expect(parent.contains(200, 200, true)).toBeFalsy();
-
+
child.add(child2);
- expect(parent.contains(250, 250, true)).toBeTruthy();
+ expect(parent.contains(250, 250, true)).toBeTruthy();
});
it('with an active collision type can be placed on a fixed type', () => {
ex.Physics.useBoxPhysics();
- var scene = new ex.Scene(engine);
-
+ var scene = new ex.Scene(engine);
+
var active = new ex.Actor(0, -50, 100, 100);
active.collisionType = ex.CollisionType.Active;
active.vel.y = 10;
active.acc.y = 1000;
-
+
var fixed = new ex.Actor(-100, 50, 1000, 100);
fixed.collisionType = ex.CollisionType.Fixed;
-
+
scene.add(active);
scene.add(fixed);
-
+
expect(active.pos.x).toBe(0);
expect(active.pos.y).toBe(-50);
expect(fixed.pos.x).toBe(-100);
expect(fixed.pos.y).toBe(50);
-
+
// update many times for safety
for (var i = 0; i < 40; i++) {
scene.update(engine, 100);
}
-
+
expect(active.pos.x).toBeCloseTo(0, .0001);
expect(active.pos.y).toBeCloseTo(-50, .0001);
-
+
expect(fixed.pos.x).toBe(-100);
expect(fixed.pos.y).toBe(50);
});
- it('with an active collision type can jump on a fixed type', () => {
+ it('with an active collision type can jump on a fixed type', () => {
var scene = new ex.Scene(engine);
var active = new ex.Actor(0, -50, 100, 100);
active.collisionType = ex.CollisionType.Active;
active.vel.y = -100;
ex.Physics.acc.setTo(0, 0);
-
+
var fixed = new ex.Actor(-100, 50, 1000, 100);
fixed.collisionType = ex.CollisionType.Fixed;
-
+
scene.add(active);
scene.add(fixed);
-
+
expect(active.pos.x).toBe(0);
expect(active.pos.y).toBe(-50);
expect(fixed.pos.x).toBe(-100);
expect(fixed.pos.y).toBe(50);
-
+
var iterations = 1;
// update many times for safety
for (var i = 0; i < iterations; i++) {
scene.update(engine, 1000);
}
-
+
expect(ex.Physics.acc.y).toBe(0);
expect(active.pos.x).toBeCloseTo(0, .0001);
expect(active.pos.y).toBeCloseTo(-100 * iterations + (-50) /* original y is -50 */, .0001);
-
+
expect(fixed.pos.x).toBe(-100);
expect(fixed.pos.y).toBe(50);
});
@@ -683,40 +683,40 @@ describe('A game actor', () => {
var childActor = new ex.Actor();
scene.add(parentActor);
parentActor.add(childActor);
-
+
spyOn(childActor, 'update');
-
+
scene.update(engine, 100);
-
+
expect(childActor.update).toHaveBeenCalled();
});
-
+
it('draws visible child actors', () => {
var parentActor = new ex.Actor();
var childActor = new ex.Actor();
scene.add(parentActor);
parentActor.add(childActor);
-
+
spyOn(childActor, 'draw');
-
+
childActor.visible = true;
scene.draw(engine.ctx, 100);
expect(childActor.draw).toHaveBeenCalled();
});
-
+
it('does not draw invisible child actors', () => {
var parentActor = new ex.Actor();
var childActor = new ex.Actor();
scene.add(parentActor);
parentActor.add(childActor);
-
+
spyOn(childActor, 'draw');
-
+
childActor.visible = false;
scene.draw(engine.ctx, 100);
expect(childActor.draw).not.toHaveBeenCalled();
});
-
+
it('fires a killed event when killed', () => {
var actor = new ex.Actor();
scene.add(actor);
@@ -725,10 +725,10 @@ describe('A game actor', () => {
killed = true;
});
actor.kill();
-
+
expect(killed).toBe(true);
});
-
+
it('is no longer killed when re-added to the game', () => {
var actor = new ex.Actor();
scene.add(actor);
@@ -754,11 +754,11 @@ describe('A game actor', () => {
var preupdateFired = false;
actor.on('preupdate', () => { preupdateFired = true; });
- actor.on('postupdate', () => {
+ actor.on('postupdate', () => {
expect(preupdateFired).toBe(true);
done();
});
-
+
scene.add(actor);
scene.update(engine, 100);
});
@@ -797,43 +797,434 @@ describe('A game actor', () => {
var predrawedFired = false;
actor.on('predraw', () => { predrawedFired = true; });
- actor.on('postdraw', () => {
+ actor.on('postdraw', () => {
expect(predrawedFired).toBe(true);
done();
});
-
+
scene.add(actor);
scene.update(engine, 100);
scene.draw(engine.ctx, 100);
});
- it('opt into pointer capture when pointerup', () => {
+ it('should opt into pointer capture when pointerdown', () => {
var actor = new ex.Actor(0, 0, 100, 100);
- expect(actor.enableCapturePointer).toBe(false, 'Actors start without pointer caputer enabled');
- actor.on('pointerup', () => {
- // doesn't matter;
- });
- expect(actor.enableCapturePointer).toBe(true, 'Actors should have pointer caputer enabled after pointerup');
+
+ expect(actor.enableCapturePointer).toBe(false, 'Actors start without pointer capture enabled');
+ expect(actor.capturePointer.captureMoveEvents).toBe(false, 'Actors should not start with capturing move events');
+ expect(actor.capturePointer.captureDragEvents).toBe(false, 'Actors should not start with capturing drag events');
+
+ actor.on('pointerdown', () => { /* doesn't matter; */ });
+
+ expect(actor.enableCapturePointer).toBe(true, 'Actors should have pointer capture enabled after pointerdown');
+ expect(actor.capturePointer.captureMoveEvents).toBe(false, 'Actors should not capture move events after pointerdown');
+ expect(actor.capturePointer.captureDragEvents).toBe(false, 'Actors should not capture drag events after pointerdown');
});
- it('opt into pointer capture when pointerdown', () => {
+ it('should opt into pointer capture when pointerup', () => {
var actor = new ex.Actor(0, 0, 100, 100);
- expect(actor.enableCapturePointer).toBe(false, 'Actors start without pointer caputer enabled');
- actor.on('pointerdown', () => {
- // doesn't matter;
- });
- expect(actor.enableCapturePointer).toBe(true, 'Actors should have pointer caputer enabled after pointerdown');
+
+ expect(actor.enableCapturePointer).toBe(false, 'Actors start without pointer capture enabled');
+ expect(actor.capturePointer.captureMoveEvents).toBe(false, 'Actors should not start with capturing move events');
+ expect(actor.capturePointer.captureDragEvents).toBe(false, 'Actors should not start with capturing drag events');
+
+ actor.on('pointerup', () => { /* doesn't matter; */ });
+
+ expect(actor.enableCapturePointer).toBe(true, 'Actors should have pointer capture enabled after pointerup');
+ expect(actor.capturePointer.captureMoveEvents).toBe(false, 'Actors should not capture move events after pointerdown');
+ expect(actor.capturePointer.captureDragEvents).toBe(false, 'Actors should not capture drag events after pointerdown');
});
- it('opt into pointer capture when pointermove', () => {
+ it('should opt into pointer capture when pointermove', () => {
var actor = new ex.Actor(0, 0, 100, 100);
- expect(actor.enableCapturePointer).toBe(false, 'Actors start without pointer caputer enabled');
- actor.on('pointermove', () => {
- // doesn't matter;
- });
- expect(actor.enableCapturePointer).toBe(true, 'Actors should have pointer caputer enabled after pointermove');
+
+ expect(actor.enableCapturePointer).toBe(false, 'Actors start without pointer capture enabled');
+ expect(actor.capturePointer.captureMoveEvents).toBe(false, 'Actors should not start with capturing move events');
+ expect(actor.capturePointer.captureDragEvents).toBe(false, 'Actors should not start with capturing drag events');
+
+ actor.on('pointermove', () => { /* doesn't matter; */ });
+
+ expect(actor.enableCapturePointer).toBe(true, 'Actors should have pointer capture enabled after pointermove');
expect(actor.capturePointer.captureMoveEvents).toBe(true, 'Actor should capture move events after pointermove');
+ expect(actor.capturePointer.captureDragEvents).toBe(false, 'Actors should not capture drag events after pointermove');
+ });
+
+ it('should opt into pointer capture when pointerenter', () => {
+ var actor = new ex.Actor(0, 0, 100, 100);
+
+ expect(actor.enableCapturePointer).toBe(false, 'Actors start without pointer capture enabled');
+ expect(actor.capturePointer.captureMoveEvents).toBe(false, 'Actors should not start with capturing move events');
+ expect(actor.capturePointer.captureDragEvents).toBe(false, 'Actors should not start with capturing drag events');
+
+ actor.on('pointerenter', () => { /* doesn't matter; */ });
+
+ expect(actor.enableCapturePointer).toBe(true, 'Actors should have pointer capture enabled after pointerenter');
+ expect(actor.capturePointer.captureMoveEvents).toBe(true, 'Actor should capture move events after pointerenter');
+ expect(actor.capturePointer.captureDragEvents).toBe(false, 'Actors should not capture drag events after pointerenter');
+ });
+
+ it('should opt into pointer capture when pointerleave', () => {
+ var actor = new ex.Actor(0, 0, 100, 100);
+
+ expect(actor.enableCapturePointer).toBe(false, 'Actors start without pointer capture enabled');
+ expect(actor.capturePointer.captureMoveEvents).toBe(false, 'Actors should not start with capturing move events');
+ expect(actor.capturePointer.captureDragEvents).toBe(false, 'Actors should not start with capturing drag events');
+
+ actor.on('pointerleave', () => { /* doesn't matter; */ });
+
+ expect(actor.enableCapturePointer).toBe(true, 'Actors should have pointer capture enabled after pointerleave');
+ expect(actor.capturePointer.captureMoveEvents).toBe(true, 'Actor should capture move events after pointerleave');
+ expect(actor.capturePointer.captureDragEvents).toBe(false, 'Actors should not capture drag events after pointerleave');
+ });
+ it('should opt into pointer capture when pointerdragstart', () => {
+ var actor = new ex.Actor(0, 0, 100, 100);
+
+ expect(actor.enableCapturePointer).toBe(false, 'Actors start without pointer capture enabled');
+ expect(actor.capturePointer.captureMoveEvents).toBe(false, 'Actors should not start with capturing move events');
+ expect(actor.capturePointer.captureDragEvents).toBe(false, 'Actors should not start with capturing drag events');
+
+ actor.on('pointerdragstart', () => { /* doesn't matter; */ });
+
+ expect(actor.enableCapturePointer).toBe(true, 'Actors should have pointer capture enabled after pointerdragstart');
+ expect(actor.capturePointer.captureMoveEvents).toBe(false, 'Actors should capture move events after pointerdragstart');
+ expect(actor.capturePointer.captureDragEvents).toBe(true, 'Actors should capture drag events after pointerdragstart');
+ });
+
+ it('opt into pointer capture when pointerdragend', () => {
+ var actor = new ex.Actor(0, 0, 100, 100);
+
+ expect(actor.enableCapturePointer).toBe(false, 'Actors start without pointer capture enabled');
+ expect(actor.capturePointer.captureMoveEvents).toBe(false, 'Actors should not start with capturing move events');
+ expect(actor.capturePointer.captureDragEvents).toBe(false, 'Actors should not start with capturing drag events');
+
+ actor.on('pointerdragend', () => { /* doesn't matter; */ });
+
+ expect(actor.enableCapturePointer).toBe(true, 'Actors should have pointer capture enabled after pointerdragend');
+ expect(actor.capturePointer.captureMoveEvents).toBe(false, 'Actors should capture move events after pointerdragend');
+ expect(actor.capturePointer.captureDragEvents).toBe(true, 'Actors should capture drag events after pointerdragend');
+ });
+
+ it('should opt into pointer capture when pointerdragmove', () => {
+ var actor = new ex.Actor(0, 0, 100, 100);
+
+ expect(actor.enableCapturePointer).toBe(false, 'Actors start without pointer capture enabled');
+ expect(actor.capturePointer.captureMoveEvents).toBe(false, 'Actors should not start with capturing move events');
+ expect(actor.capturePointer.captureDragEvents).toBe(false, 'Actors should not start with capturing drag events');
+
+ actor.on('pointerdragmove', () => { /* doesn't matter; */ });
+
+ expect(actor.enableCapturePointer).toBe(true, 'Actors should have pointer capture enabled after pointerdragmove');
+ expect(actor.capturePointer.captureMoveEvents).toBe(true, 'Actors should capture move events after pointerdragmove');
+ expect(actor.capturePointer.captureDragEvents).toBe(true, 'Actors should capture drag events after pointerdragmove');
+ });
+
+ it('should opt into pointer capture when pointerdragenter', () => {
+ var actor = new ex.Actor(0, 0, 100, 100);
+
+ expect(actor.enableCapturePointer).toBe(false, 'Actors start without pointer capture enabled');
+ expect(actor.capturePointer.captureMoveEvents).toBe(false, 'Actors should not start with capturing move events');
+ expect(actor.capturePointer.captureDragEvents).toBe(false, 'Actors should not start with capturing drag events');
+
+ actor.on('pointerdragenter', () => { /* doesn't matter; */ });
+
+ expect(actor.enableCapturePointer).toBe(true, 'Actors should have pointer capture enabled after pointerdragenter');
+ expect(actor.capturePointer.captureMoveEvents).toBe(true, 'Actors should capture move events after pointerdragenter');
+ expect(actor.capturePointer.captureDragEvents).toBe(true, 'Actors should capture drag events after pointerdragenter');
+ });
+
+ it('should opt into pointer capture when pointerdragleave', () => {
+ var actor = new ex.Actor(0, 0, 100, 100);
+
+ expect(actor.enableCapturePointer).toBe(false, 'Actors start without pointer capture enabled');
+ expect(actor.capturePointer.captureMoveEvents).toBe(false, 'Actors should not start with capturing move events');
+ expect(actor.capturePointer.captureDragEvents).toBe(false, 'Actors should not start with capturing drag events');
+
+ actor.on('pointerdragleave', () => { /* doesn't matter; */ });
+
+ expect(actor.enableCapturePointer).toBe(true, 'Actors should have pointer capture enabled after pointerdragleave');
+ expect(actor.capturePointer.captureMoveEvents).toBe(true, 'Actors should capture move events after pointerdragleave');
+ expect(actor.capturePointer.captureDragEvents).toBe(true, 'Actors should capture drag events after pointerdragleave');
+ });
+
+ it('should capture pointer move event', () => {
+ var actor = new ex.Actor(0, 0, 20, 20);
+ var pointerEvent: any;
+ var callables = { move: (pe: any) => pointerEvent = pe };
+ var moveSpy = spyOn(callables, 'move').and.callThrough();
+
+ actor.on('pointermove', callables.move);
+
+ scene.add(actor);
+
+ (engine.input.pointers)._pointerMove.push({
+ pointer: {
+ lastWorldPos: new ex.Vector(5, -5),
+ actorsUnderPointer: {}
+ }
+ });
+
+ scene.update(engine, 100);
+
+ expect(moveSpy).toHaveBeenCalledTimes(1);
+ expect(pointerEvent.pointer.lastWorldPos.x).toEqual(5, 'pointer event should contain correct world position x');
+ expect(pointerEvent.pointer.lastWorldPos.y).toEqual(-5, 'pointer event should contain correct world position y');
+ });
+
+ it('should capture pointer enter event', () => {
+ var actor = new ex.Actor(0, 0, 20, 20);
+ var pointerEvent: any;
+ var callables = { enter: (pe: any) => pointerEvent = pe };
+ var enterSpy = spyOn(callables, 'enter').and.callThrough();
+
+ actor.on('pointerenter', callables.enter);
+
+ scene.add(actor);
+
+ (engine.input.pointers)._pointerMove.push({
+ pointer: {
+ lastWorldPos: new ex.Vector(5, -5),
+ actorsUnderPointer: {}
+ }
+ });
+
+ scene.update(engine, 100);
+
+ (engine.input.pointers)._pointerMove.push({
+ pointer: {
+ lastWorldPos: new ex.Vector(5, -5),
+ actorsUnderPointer: (() => {
+ let obj = {};
+
+ obj[actor.id] = actor;
+
+ return obj;
+ })()
+ }
+ });
+
+ scene.update(engine, 100);
+
+ expect(enterSpy).toHaveBeenCalledTimes(1);
+ expect(pointerEvent.pointer.lastWorldPos.x).toEqual(5, 'pointer event should contain correct world position x');
+ expect(pointerEvent.pointer.lastWorldPos.y).toEqual(-5, 'pointer event should contain correct world position y');
+ });
+
+ it('should capture pointer leave event', () => {
+ var actor = new ex.Actor(0, 0, 20, 20);
+ var pointerEvent: any;
+ var callables = { leave: (pe: any) => pointerEvent = pe };
+ var leaveSpy = spyOn(callables, 'leave').and.callThrough();
+
+ actor.on('pointerleave', callables.leave);
+
+ scene.add(actor);
+
+ (engine.input.pointers)._pointerMove.push({
+ pointer: {
+ lastWorldPos: new ex.Vector(5, -5),
+ actorsUnderPointer: {}
+ }
+ });
+
+ scene.update(engine, 100);
+
+ (engine.input.pointers)._pointerMove.push({
+ pointer: {
+ lastWorldPos: new ex.Vector(15, -15),
+ actorsUnderPointer: (() => {
+ let obj = {};
+
+ obj[actor.id] = actor;
+
+ return obj;
+ })()
+ }
+ });
+
+ scene.update(engine, 100);
+ scene.update(engine, 100);
+
+ expect(leaveSpy).toHaveBeenCalledTimes(1);
+ expect(pointerEvent.pointer.lastWorldPos.x).toEqual(15, 'pointer event should contain correct world position x');
+ expect(pointerEvent.pointer.lastWorldPos.y).toEqual(-15, 'pointer event should contain correct world position y');
+ });
+
+ it('should capture pointer drag start event', () => {
+ var actor = new ex.Actor(0, 0, 20, 20);
+ var pointerEvent: any;
+ var callables = { dragStart: (pe: any) => pointerEvent = pe };
+ var dragStartSpy = spyOn(callables, 'dragStart').and.callThrough();
+
+ actor.on('pointerdragstart', callables.dragStart);
+
+ scene.add(actor);
+
+ (engine.input.pointers)._pointerDown.push({
+ pointer: {
+ isDragStart: true,
+ lastWorldPos: new ex.Vector(5, -5),
+ actorsUnderPointer: {}
+ }
+ });
+
+ scene.update(engine, 100);
+
+ expect(dragStartSpy).toHaveBeenCalledTimes(1);
+ expect(pointerEvent.pointer.lastWorldPos.x).toEqual(5, 'pointer event should contain correct world position x');
+ expect(pointerEvent.pointer.lastWorldPos.y).toEqual(-5, 'pointer event should contain correct world position y');
+ });
+
+ it('should capture pointer drag end event', () => {
+ var actor = new ex.Actor(0, 0, 20, 20);
+ var pointerEvent: any;
+ var callables = { dragEnd: (pe: any) => pointerEvent = pe };
+ var dragEndSpy = spyOn(callables, 'dragEnd').and.callThrough();
+
+ actor.on('pointerdragend', callables.dragEnd);
+
+ scene.add(actor);
+
+ (engine.input.pointers)._pointerDown.push({
+ pointer: {
+ isDragStart: true,
+ lastWorldPos: new ex.Vector(5, -5),
+ actorsUnderPointer: {}
+ }
+ });
+
+ scene.update(engine, 100);
+
+ (engine.input.pointers)._pointerUp.push({
+ pointer: {
+ isDragEnd: true,
+ lastWorldPos: new ex.Vector(5, -5),
+ actorsUnderPointer: {}
+ }
+ });
+
+ scene.update(engine, 100);
+
+ expect(dragEndSpy).toHaveBeenCalledTimes(1);
+ expect(pointerEvent.pointer.lastWorldPos.x).toEqual(5, 'pointer event should contain correct world position x');
+ expect(pointerEvent.pointer.lastWorldPos.y).toEqual(-5, 'pointer event should contain correct world position y');
+ });
+
+ it('should capture pointer drag move event', () => {
+ var actor = new ex.Actor(0, 0, 20, 20);
+ var pointerEvent: any;
+ var callables = { dragMove: (pe: any) => pointerEvent = pe };
+ var dragMoveSpy = spyOn(callables, 'dragMove').and.callThrough();
+
+ actor.on('pointerdragmove', callables.dragMove);
+
+ scene.add(actor);
+
+ (engine.input.pointers)._pointerMove.push({
+ pointer: {
+ isDragging: true,
+ lastWorldPos: new ex.Vector(5, -5),
+ actorsUnderPointer: {}
+ }
+ });
+
+ scene.update(engine, 100);
+
+ expect(dragMoveSpy).toHaveBeenCalledTimes(1);
+ expect(pointerEvent.pointer.lastWorldPos.x).toEqual(5, 'pointer event should contain correct world position x');
+ expect(pointerEvent.pointer.lastWorldPos.y).toEqual(-5, 'pointer event should contain correct world position y');
+ });
+
+ it('should capture pointer drag enter event', () => {
+ var actor = new ex.Actor(0, 0, 20, 20);
+ var pointerEvent: any;
+ var callables = { dragEnter: (pe: any) => pointerEvent = pe };
+ var dragEnterSpy = spyOn(callables, 'dragEnter').and.callThrough();
+
+ actor.on('pointerdragenter', callables.dragEnter);
+
+ scene.add(actor);
+
+ (engine.input.pointers)._pointerMove.push({
+ pointer: {
+ isDragging: true,
+ lastWorldPos: new ex.Vector(5, -5),
+ actorsUnderPointer: {}
+ }
+ });
+
+ scene.update(engine, 100);
+
+ (engine.input.pointers)._pointerMove.push({
+ pointer: {
+ isDragging: true,
+ lastWorldPos: new ex.Vector(5, -5),
+ actorsUnderPointer: (() => {
+ let obj = {};
+
+ obj[actor.id] = actor;
+
+ return obj;
+ })()
+ }
+ });
+
+ scene.update(engine, 100);
+
+ expect(dragEnterSpy).toHaveBeenCalledTimes(1);
+ expect(pointerEvent.pointer.lastWorldPos.x).toEqual(5, 'pointer event should contain correct world position x');
+ expect(pointerEvent.pointer.lastWorldPos.y).toEqual(-5, 'pointer event should contain correct world position y');
+ });
+
+ it('should capture pointer drag leave event', () => {
+ var actor = new ex.Actor(0, 0, 20, 20);
+ var pointerEvent: any;
+ var callables = { dragLeave: (pe: any) => pointerEvent = pe };
+ var dragLeaveSpy = spyOn(callables, 'dragLeave').and.callThrough();
+
+ actor.on('pointerdragleave', callables.dragLeave);
+
+ scene.add(actor);
+
+ (engine.input.pointers)._pointerMove.push({
+ pointer: {
+ isDragging: true,
+ lastWorldPos: new ex.Vector(5, -5),
+ actorsUnderPointer: {}
+ }
+ });
+
+ scene.update(engine, 100);
+
+ (engine.input.pointers)._pointerMove.push({
+ pointer: {
+ isDragging: true,
+ lastWorldPos: new ex.Vector(15, -15),
+ actorsUnderPointer: (() => {
+ let obj = {};
+
+ obj[actor.id] = actor;
+
+ return obj;
+ })()
+ }
+ });
+
+ scene.update(engine, 100);
+
+ (engine.input.pointers)._pointerMove.push({
+ pointer: {
+ isDragging: true,
+ lastWorldPos: new ex.Vector(15, -15),
+ actorsUnderPointer: {}
+ }
+ });
+
+ scene.update(engine, 100);
+
+ expect(dragLeaveSpy).toHaveBeenCalledTimes(1);
+ expect(pointerEvent.pointer.lastWorldPos.x).toEqual(15, 'pointer event should contain correct world position x');
+ expect(pointerEvent.pointer.lastWorldPos.y).toEqual(-15, 'pointer event should contain correct world position y');
});
it('only has pointer events happen once per frame', () => {
@@ -842,17 +1233,21 @@ describe('A game actor', () => {
var numPointerUps = 0;
var propSpy = spyOn(engine.input.pointers, 'propogate').and.callThrough();
- (engine.input.pointers)._pointerUp.push({x: 0, y: 0});
+ (engine.input.pointers)._pointerUp.push({
+ pointer: {
+ lastWorldPos: { x: 0, y: 0 }
+ }
+ });
actor.on('pointerup', () => {
numPointerUps++;
});
scene.add(actor);
-
+
scene.update(engine, 100);
expect(numPointerUps).toBe(1, 'Pointer up should be triggered once');
- expect(propSpy.calls.count()).toEqual(1, 'Propogate should be called once');
+ expect(propSpy).toHaveBeenCalledTimes(1);
});
});