js/Mobilizing/input/Pointer.js
import Component from '../core/Component';
import Mouse from './Mouse';
import Touch from './Touch';
const TOUCH_TYPE = "touch";
const MOUSE_TYPE = "mouse";
/**
PointerObject is a thin layer that unify mouse and touch inputs to simplify UI interactions.
@private
*/
class PointerObject {
/**
* Creates a new PointerObject.
*
* @param {Object} params Parameters object, given by the constructor.
* @param {Number} params.x
* @param {Number} params.y
* @param {Number} params.state
* @param {Number} params.type
*/
constructor({
x = undefined,
y = undefined,
state = undefined,
type = undefined
} = {}) {
this.x = x;
this.y = y;
this.state = state;
this.type = type;
this.available = undefined;
/**
x delta coordinate, the difference between the previous and the current frame
@type {Number}
*/
this.deltaX = 0;
/**
x delta coordinate, the difference between the previous and the current frame
@type {Number}
*/
this.deltaY = 0;
/**
the component this pointer is attached to
@type {Component}
*/
this.component = null;
}
}
/**
* Fired when a pointer is on (pressed or down)
* @event on
*/
const EVT_ON = 'on';
/**
* Fired when a pointer is off (released or erased)
* @event off
*/
const EVT_OFF = 'off';
/**
* Fired when a pointer moved
* @event moved
*/
const EVT_MOVED = 'moved';
/**
* Pointer is an abstraction that enables various input devices to send the same kind of events. It is designed to accumulate various type of inputs in a Map of PointerOject. Each input device is converted in a PointerOject in order to unify its interface. Usefully mainly internally for interactive UI objects like buttons.
*
* @example
* //TODO
*/
export default class Pointer extends Component {
/**
* @param {Object} params Parameters object, given by the constructor.
* @param {[Component]} params.components an array of input components to add to this pointer (i.e. Mouse)
*/
constructor({
components = undefined
} = {}) {
super(...arguments);
this.components = components;
this.pointers = new Map();
// special pointer to get the last active pointer
this.lastActivePointer = new PointerObject();
//this.lastActivePointer.type = "lastActive";
if (Array.isArray(this.components)) {
this.components.forEach((component) => {
this.add(component);
});
}
}
/**
* Remove unused Pointers
*/
postUpdate() {
for (const pointerKey of this.pointers.keys()) {
const pointer = this.pointers.get(pointerKey);
//why did I ever did this??
/* if (pointer.state === false && pointer.type === TOUCH_TYPE) {
console.log("erase",pointer);
this.pointers.delete(pointerKey);
} */
//update ce qui doit l'être mais qui ne peut l'être ailleurs
if (pointer.type === "touch") {
pointer.deltaX = pointer.component.getDeltaX();
pointer.deltaY = pointer.component.getDeltaY();
}
else if (pointer.type === "mouse") {
pointer.deltaX = pointer.component.getDeltaX();
pointer.deltaY = pointer.component.getDeltaY();
}
}
}
/**
* Adds the specified input component as a PointerObject to the pointers list.
* @param {Component} component
*/
add(component) {
if (component instanceof Touch) {
this.addTouch(component);
}
else if (component instanceof Mouse) {
this.addMouse(component);
}
}
addTouch(component) {
const pointer = new PointerObject();
pointer.component = component;
pointer.type = TOUCH_TYPE;
pointer.available = true;
if (!("ontouchstart" in window)) {
pointer.available = false;
}
this.pointers.set(component.id, pointer);
//register a new Pointer at touch creation
component.events.on("touchstart", (touch) => {
//const pointer = this.pointers.get(touch.id);
pointer.state = true;
pointer.x = touch.x;
pointer.y = touch.y;
this.events.trigger(EVT_ON, {
pointer,
"x": pointer.x,
"y": pointer.y
});
this.lastActivePointer.state = true;
this.lastActivePointer.x = touch.x;
this.lastActivePointer.y = touch.y;
this.lastActivePointer.type = pointer.type;
});
//updates a Pointer at touch moved
component.events.on("touchmoved", (touch) => {
pointer.state = true;
pointer.x = touch.x;
pointer.y = touch.y;
pointer.deltaX = touch.xDelta;
pointer.deltaY = touch.yDelta;
this.events.trigger(EVT_MOVED, {
pointer,
"x": pointer.x,
"y": pointer.y
});
this.lastActivePointer.state = true;
this.lastActivePointer.x = touch.x;
this.lastActivePointer.y = touch.y;
this.lastActivePointer.deltaX = touch.xDelta;
this.lastActivePointer.deltaY = touch.yDelta;
});
//updates a Pointer at touch end
component.events.on("touchend", (touch) => {
pointer.state = false;
pointer.x = touch.x;
pointer.y = touch.y;
this.events.trigger(EVT_OFF, { pointer });
this.lastActivePointer.state = false;
this.lastActivePointer.x = touch.x;
this.lastActivePointer.y = touch.y;
});
}
addMouse(component) {
const mouseComponent = component;//just to understand better
const pointer = new PointerObject();
pointer.component = component;
pointer.type = MOUSE_TYPE;
pointer.available = true;
if (!("onmousedown" in window)) {
pointer.available = false;
}
this.pointers.set(component.id, pointer);
mouseComponent.events.on("mousepress", () => {
//const pointer = this.pointers.get(component.id);
pointer.state = true;
this.events.trigger(EVT_ON, {
pointer,
"x": pointer.x,
"y": pointer.y
});
this.lastActivePointer.state = true;
//this is needed to keep the current pointer type in lastActivePointer.
//used in Button, through Clickable, to have specific behaviour depending on input type.
this.lastActivePointer.type = pointer.type;
});
mouseComponent.events.on("mouserelease", () => {
//const pointer = this.pointers.get(component.id);
pointer.state = false;
this.events.trigger(EVT_OFF, { pointer });
this.lastActivePointer.state = false;
});
mouseComponent.events.on("mousemove", (coords) => {
//const pointer = this.pointers.get(component.id);
pointer.deltaX = mouseComponent.getDeltaX();
pointer.deltaY = mouseComponent.getDeltaY();
pointer.x = coords.x;
pointer.y = coords.y;
this.events.trigger(EVT_MOVED, {
pointer,
"x": pointer.x,
"y": pointer.y
});
this.lastActivePointer.state = true;
this.lastActivePointer.x = pointer.x;
this.lastActivePointer.y = pointer.y;
this.lastActivePointer.deltaX = pointer.deltaX;
this.lastActivePointer.deltaY = pointer.deltaY;
});
mouseComponent.events.on("mouseupdate", () => {
//const pointer = this.pointers.get(component.id);
pointer.deltaX = mouseComponent.getDeltaX();
pointer.deltaY = mouseComponent.getDeltaY();
/*this.events.trigger(EVT_MOVED, {pointer:pointer, x:pointer.getX(), y:pointer.getY()});*/
this.lastActivePointer.state = true;
this.lastActivePointer.deltaX = pointer.deltaX;
this.lastActivePointer.deltaY = pointer.deltaY;
});
}
/**
* Returns the specified PointerObject to work with its state
* @param {Component} component
* @return {Component} the corresponding input Component
*/
get(component) {
return this.pointers.get(component.id);
}
/**
* Returns an Array from the pointers Map object. For debug purpose.
* @return {Array} Pointers array
*/
getPointers() {
return Array.from(this.pointers.values());
}
/**
* Returns true if ANY of the PointerObjects (input Component) state is true, false otherwise. i.e. if you add a mouse and a touch component to the pointer, this will return true if any of these 2 has it's state to true.
* @return {Array} pointers array
*/
getState() {
for (const pointer of this.pointers.values()) {
//the virtual pointer is on true
if (pointer.state) {
return true;
}
}
this.events.trigger("off");
return false;
}
/**
* Returns x coordinates of the PointerObject (input Component) of the given index. If no index is given, the last active pointer will return.
* @param {Number} [index] the pointer object we want to get
* @return {Number} x coordinate
*/
getX(index) {
const values = [];
for (const pointer of this.pointers.values()) {
values.push(pointer.x);
}
if (index) { // we want a specific pointer in the list
return index === undefined ? values : values[index];
}
// we want a value
return this.lastActivePointer.x;
}
/**
* Returns y coordinates of the PointerObject (input Component) of the given index. If no index is given, the last active pointer will return.
* @param {Number} [index] the pointer object we want to get
* @return {Number} y coordinate
*/
getY(index) {
const values = [];
for (const pointer of this.pointers.values()) {
values.push(pointer.y);
}
if (index) { // we want a specific pointer in the list
return index === undefined ? values : values[index];
}
// we want a value
return this.lastActivePointer.y;
}
/**
* Returns x delta coordinates of the PointerObject (input Component) of the given index. If no index is given, the last active pointer will return.
* @param {Number} [index] the pointer object we want to get
* @return {Number} coordinate
*/
getDeltaX(index) {
const values = [];
for (const pointer of this.pointers.values()) {
values.push(pointer.deltaX);
}
if (index) {// we want a specific pointer in the list
return index === undefined ? values : values[index];
}
// we want a value
return this.lastActivePointer.deltaX;
}
/**
* Returns y delta coordinates of the PointerObject (input Component) of the given index. If no index is given, the last active pointer will return.
* @param {Number} [index] the pointer object we want to get
* @return {Number} coordinate
*/
getDeltaY(index) {
const values = [];
for (const pointer of this.pointers.values()) {
values.push(pointer.deltaY);
}
if (index) {// we want a specific pointer in the list
return index === undefined ? values : values[index];
}
// we want a value
return this.lastActivePointer.deltaY;
}
}