lettuce
Version:
Lettuce JS, Mini Mobile Framework for Romantic with DSL.
654 lines (590 loc) • 21.3 kB
JavaScript
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2014, Olivier Biot, Jason Oster
* http://www.melonjs.org/
*
*/
(function () {
/**
* The built in Event Object
* @external Event
* @see {@link https://developer.mozilla.org/en/docs/Web/API/Event|Event}
*/
/**
* Event normalized X coordinate within the game canvas itself<br>
* <img src="images/event_coord.png"/>
* @memberof! external:Event#
* @name external:Event#gameX
* @type {Number}
*/
/**
* Event normalized Y coordinate within the game canvas itself<br>
* <img src="images/event_coord.png"/>
* @memberof! external:Event#
* @name external:Event#gameY
* @type {Number}
*/
/**
* Event X coordinate relative to the viewport<br>
* @memberof! external:Event#
* @name external:Event#gameScreenX
* @type {Number}
*/
/**
* Event Y coordinate relative to the viewport<br>
* @memberof! external:Event#
* @name external:Event#gameScreenY
* @type {Number}
*/
/**
* Event X coordinate relative to the map<br>
* @memberof! external:Event#
* @name external:Event#gameWorldX
* @type {Number}
*/
/**
* Event Y coordinate relative to the map<br>
* @memberof! external:Event#
* @name external:Event#gameWorldY
* @type {Number}
*/
/**
* The unique identifier of the contact for a touch, mouse or pen <br>
* (This id is also defined on non Pointer Event Compatible platform like pure mouse or iOS-like touch event)
* @memberof! external:Event#
* @name external:Event#pointerId
* @type {Number}
* @see http://msdn.microsoft.com/en-us/library/windows/apps/hh466123.aspx
*/
/*
* PRIVATE STUFF
*/
// Reference to base class
var obj = me.input;
// list of registered Event handlers
var evtHandlers = {};
// some useful flags
var pointerInitialized = false;
// to keep track of the supported wheel event
var wheeltype = "mousewheel";
// Track last event timestamp to prevent firing events out of order
var lastTimeStamp = 0;
// "active" list of supported events
var activeEventList = null;
// list of standard pointer event type
var pointerEventList = [
"mousewheel",
"pointermove",
"pointerdown",
"pointerup",
"pointercancel",
undefined,
undefined
];
// previous MS prefixed pointer event type
var MSPointerEventList = [
"mousewheel",
"MSPointerMove",
"MSPointerDown",
"MSPointerUp",
"MSPointerCancel",
undefined,
undefined
];
// legacy mouse event type
var mouseEventList = [
"mousewheel",
"mousemove",
"mousedown",
"mouseup",
undefined,
undefined,
undefined
];
// iOS style touch event type
var touchEventList = [
undefined,
"touchmove",
"touchstart",
"touchend",
"touchcancel",
undefined,
undefined
];
// internal constants
//var MOUSE_WHEEL = 0;
var POINTER_MOVE = 1;
var POINTER_DOWN = 2;
var POINTER_UP = 3;
var POINTER_CANCEL = 4;
/**
* cache value for the offset of the canvas position within the page
* @ignore
*/
var viewportOffset = new me.Vector2d();
/**
* Array of object containing changed touch information (iOS event model)
* @ignore
*/
var changedTouches = [];
/**
* cache value for the offset of the canvas position within the page
* @ignore
*/
obj._offset = null;
/**
* addEventListerner for the specified event list and callback
* @ignore
*/
function registerEventListener(eventList, callback) {
for (var x = 2; x < eventList.length; ++x) {
if (typeof(eventList[x]) !== "undefined") {
me.video.renderer.getScreenCanvas().addEventListener(eventList[x], callback, false);
}
}
}
/**
* enable pointer event (MSPointer/Mouse/Touch)
* @ignore
*/
function enablePointerEvent() {
if (!pointerInitialized) {
// initialize mouse pos (0,0)
changedTouches.push({ x: 0, y: 0 });
obj.mouse.pos = new me.Vector2d(0, 0);
// get relative canvas position in the page
obj._offset = me.video.getPos();
// Automatically update relative canvas position on scroll
window.addEventListener("scroll", throttle(100, false,
function (e) {
obj._offset = me.video.getPos();
me.event.publish(me.event.WINDOW_ONSCROLL, [ e ]);
}
), false);
// check standard
if (navigator.pointerEnabled) {
activeEventList = pointerEventList;
}
else if (navigator.msPointerEnabled) { // check for backward compatibility with the 'MS' prefix
activeEventList = MSPointerEventList;
}
else if (me.device.touch) { // `touch****` events for iOS/Android devices
activeEventList = touchEventList;
}
else { // Regular Mouse events
activeEventList = mouseEventList;
}
registerEventListener(activeEventList, onPointerEvent);
// detect wheel event support
// Modern browsers support "wheel", Webkit and IE support at least "mousewheel
wheeltype = "onwheel" in document.createElement("div") ? "wheel" : "mousewheel";
window.addEventListener(wheeltype, onMouseWheel, false);
// set the PointerMove/touchMove/MouseMove event
if (typeof(obj.throttlingInterval) === "undefined") {
// set the default value
obj.throttlingInterval = ~~(1000 / me.sys.fps);
}
// if time interval <= 16, disable the feature
if (obj.throttlingInterval < 17) {
me.video.renderer.getScreenCanvas().addEventListener(
activeEventList[POINTER_MOVE],
onMoveEvent,
false
);
}
else {
me.video.renderer.getScreenCanvas().addEventListener(
activeEventList[POINTER_MOVE],
throttle(
obj.throttlingInterval,
false,
function (e) {
onMoveEvent(e);
}
),
false
);
}
pointerInitialized = true;
}
}
/**
* propagate events to registered objects
* @ignore
*/
function dispatchEvent(e) {
var handled = false;
var handlers = evtHandlers[e.type];
// Convert touchcancel -> touchend, and pointercancel -> pointerup
if (!handlers) {
if (activeEventList.indexOf(e.type) === POINTER_CANCEL) {
handlers = evtHandlers[activeEventList[POINTER_UP]];
} else {
handlers = evtHandlers[e.type];
}
}
if (handlers) {
// get the current screen to world offset
me.game.viewport.localToWorld(0, 0, viewportOffset);
for (var t = 0, l = changedTouches.length; t < l; t++) {
// Do not fire older events
if (typeof(e.timeStamp) !== "undefined") {
if (e.timeStamp < lastTimeStamp) {
continue;
}
lastTimeStamp = e.timeStamp;
}
// if PointerEvent is not supported
if (!me.device.pointerEnabled) {
// -> define pointerId to simulate the PointerEvent standard
e.pointerId = changedTouches[t].id;
}
/* Initialize the two coordinate space properties. */
e.gameScreenX = changedTouches[t].x;
e.gameScreenY = changedTouches[t].y;
e.gameWorldX = e.gameScreenX + viewportOffset.x;
e.gameWorldY = e.gameScreenY + viewportOffset.y;
// parse all handlers
for (var i = handlers.length, handler; i--, (handler = handlers[i]);) {
/* Set gameX and gameY depending on floating. */
if (handler.floating === true) {
e.gameX = e.gameScreenX;
e.gameY = e.gameScreenY;
}
else {
e.gameX = e.gameWorldX;
e.gameY = e.gameWorldY;
}
// call the defined handler
if (handler.rect.getBounds().containsPoint(
e.gameX,
e.gameY
)) {
// trigger the corresponding callback
if (handler.cb(e) === false) {
// stop propagating the event if return false
handled = true;
break;
}
}
}
}
}
return handled;
}
/**
* translate event coordinates
* @ignore
*/
function updateCoordFromEvent(event) {
var local;
// reset the touch array cache
changedTouches.length = 0;
// PointerEvent or standard Mouse event
if (!event.touches) {
local = obj.globalToLocal(event.clientX, event.clientY);
local.id = event.pointerId || 1;
changedTouches.push(local);
}
// iOS/Android like touch event
else {
for (var i = 0, l = event.changedTouches.length; i < l; i++) {
var t = event.changedTouches[i];
local = obj.globalToLocal(t.clientX, t.clientY);
local.id = t.identifier;
changedTouches.push(local);
}
}
// if event.isPrimary is defined and false, return
if (event.isPrimary === false) {
return;
}
// Else use the first entry to simulate mouse event
obj.mouse.pos.set(
changedTouches[0].x,
changedTouches[0].y
);
}
/**
* mouse event management (mousewheel)
* @ignore
*/
function onMouseWheel(e) {
/* jshint expr:true */
if (e.target === me.video.renderer.getScreenCanvas()) {
// create a (fake) normalized event object
var _event = {
deltaMode : 1,
type : "mousewheel",
deltaX: e.deltaX,
deltaY: e.deltaY,
deltaZ: e.deltaZ
};
if (wheeltype === "mousewheel") {
_event.deltaY = - 1 / 40 * e.wheelDelta;
// Webkit also support wheelDeltaX
e.wheelDeltaX && (_event.deltaX = - 1 / 40 * e.wheelDeltaX);
}
// dispatch mouse event to registered object
if (dispatchEvent(_event)) {
// prevent default action
return obj._preventDefault(e);
}
}
return true;
}
/**
* mouse/touch/pointer event management (move)
* @ignore
*/
function onMoveEvent(e) {
// update position
updateCoordFromEvent(e);
// dispatch mouse event to registered object
if (dispatchEvent(e)) {
// prevent default action
return obj._preventDefault(e);
}
return true;
}
/**
* mouse/touch/pointer event management (start/down, end/up)
* @ignore
*/
function onPointerEvent(e) {
// update the pointer position
updateCoordFromEvent(e);
// dispatch event to registered objects
if (dispatchEvent(e)) {
// prevent default action
return obj._preventDefault(e);
}
// in case of touch event button is undefined
var button = e.button || 0;
var keycode = obj.mouse.bind[button];
// check if mapped to a key
if (keycode) {
if (e.type === activeEventList[POINTER_DOWN]) {
return obj._keydown(e, keycode, button + 1);
}
else { // 'mouseup' or 'touchend'
return obj._keyup(e, keycode, button + 1);
}
}
return true;
}
/*
* PUBLIC STUFF
*/
/**
* Mouse information<br>
* properties : <br>
* pos (me.Vector2d) : pointer position (in screen coordinates) <br>
* LEFT : constant for left button <br>
* MIDDLE : constant for middle button <br>
* RIGHT : constant for right button <br>
* @public
* @enum {Object}
* @name mouse
* @memberOf me.input
*/
obj.mouse = {
// mouse position
pos : null,
// button constants (W3C)
LEFT: 0,
MIDDLE: 1,
RIGHT: 2,
// bind list for mouse buttons
bind: [ 0, 0, 0 ]
};
/**
* time interval for event throttling in milliseconds<br>
* default value : "1000/me.sys.fps" ms<br>
* set to 0 ms to disable the feature
* @public
* @type Number
* @name throttlingInterval
* @memberOf me.input
*/
obj.throttlingInterval = undefined;
/**
* Translate the specified x and y values from the global (absolute)
* coordinate to local (viewport) relative coordinate.
* @name globalToLocal
* @memberOf me.input
* @public
* @function
* @param {Number} x the global x coordinate to be translated.
* @param {Number} y the global y coordinate to be translated.
* @return {me.Vector2d} A vector object with the corresponding translated coordinates.
* @example
* onMouseEvent : function (e) {
* // convert the given into local (viewport) relative coordinates
* var pos = me.input.globalToLocal(e.clientX, e,clientY);
* // do something with pos !
* };
*/
obj.globalToLocal = function (x, y) {
var offset = obj._offset;
var pixelRatio = me.device.getPixelRatio();
x -= offset.left;
y -= offset.top;
var scale = me.sys.scale;
if (scale.x !== 1.0 || scale.y !== 1.0) {
x /= scale.x;
y /= scale.y;
}
return new me.Vector2d(x * pixelRatio, y * pixelRatio);
};
/**
* Associate a pointer event to a keycode<br>
* Left button – 0
* Middle button – 1
* Right button – 2
* @name bindPointer
* @memberOf me.input
* @public
* @function
* @param {Number} [button=me.input.mouse.LEFT] (accordingly to W3C values : 0,1,2 for left, middle and right buttons)
* @param {me.input#KEY} keyCode
* @example
* // enable the keyboard
* me.input.bindKey(me.input.KEY.X, "shoot");
* // map the left button click on the X key (default if the button is not specified)
* me.input.bindPointer(me.input.KEY.X);
* // map the right button click on the X key
* me.input.bindPointer(me.input.mouse.RIGHT, me.input.KEY.X);
*/
obj.bindPointer = function () {
var button = (arguments.length < 2) ? obj.mouse.LEFT : arguments[0];
var keyCode = (arguments.length < 2) ? arguments[0] : arguments[1];
// make sure the mouse is initialized
enablePointerEvent();
// throw an exception if no action is defined for the specified keycode
if (!obj._KeyBinding[keyCode]) {
throw new me.Error("no action defined for keycode " + keyCode);
}
// map the mouse button to the keycode
obj.mouse.bind[button] = keyCode;
};
/**
* unbind the defined keycode
* @name unbindPointer
* @memberOf me.input
* @public
* @function
* @param {Number} [button=me.input.mouse.LEFT] (accordingly to W3C values : 0,1,2 for left, middle and right buttons)
* @example
* me.input.unbindPointer(me.input.mouse.LEFT);
*/
obj.unbindPointer = function (button) {
// clear the event status
obj.mouse.bind[
typeof(button) === "undefined" ?
me.input.mouse.LEFT : button
] = null;
};
/**
* allows registration of event listeners on the object target. <br>
* melonJS defines the additional `gameX` and `gameY` properties when passing the Event object to the defined callback (see below)<br>
* @see external:Event
* @see {@link http://www.w3.org/TR/pointerevents/#list-of-pointer-events|W3C Pointer Event list}
* @name registerPointerEvent
* @memberOf me.input
* @public
* @function
* @param {String} eventType The event type for which the object is registering <br>
* melonJS currently support <b>['pointermove','pointerdown','pointerup','mousewheel']</b>
* @param {me.Rect} rect object target (or corresponding region defined through me.Rect)
* @param {Function} callback methods to be called when the event occurs.
* @param {Boolean} [floating] specify if the object is a floating object
* (if yes, screen coordinates are used, if not mouse/touch coordinates will
* be converted to world coordinates)
* @example
* // register on the 'pointerdown' event
* me.input.registerPointerEvent('pointerdown', this, this.pointerDown.bind(this));
*/
obj.registerPointerEvent = function (eventType, rect, callback, floating) {
// make sure the mouse/touch events are initialized
enablePointerEvent();
if (pointerEventList.indexOf(eventType) === -1) {
throw new me.Error("invalid event type : " + eventType);
}
// convert to supported event type if pointerEvent not natively supported
if (pointerEventList !== activeEventList) {
eventType = activeEventList[pointerEventList.indexOf(eventType)];
}
// register the event
if (!evtHandlers[eventType]) {
evtHandlers[eventType] = [];
}
// check if this is a floating object or not
var _float = rect.floating === true ? true : false;
// check if there is a given parameter
if (floating) {
// ovveride the previous value
_float = floating === true ? true : false;
}
// initialize the handler
evtHandlers[eventType].push({
rect : rect,
cb : callback,
floating : _float
});
return;
};
/**
* allows the removal of event listeners from the object target.
* @see {@link http://www.w3.org/TR/pointerevents/#list-of-pointer-events|W3C Pointer Event list}
* @name releasePointerEvent
* @memberOf me.input
* @public
* @function
* @param {String} eventType The event type for which the object was registered <br>
* melonJS currently support <b>['pointermove','pointerdown','pointerup','mousewheel']</b>
* @param {me.Rect} region object target (or corresponding region defined through me.Rect)
* @example
* // release the registered object/region on the 'pointerdown' event
* me.input.releasePointerEvent('pointerdown', this);
*/
obj.releasePointerEvent = function (eventType, rect) {
if (pointerEventList.indexOf(eventType) === -1) {
throw new me.Error("invalid event type : " + eventType);
}
// convert to supported event type if pointerEvent not natively supported
if (pointerEventList !== activeEventList) {
eventType = activeEventList[pointerEventList.indexOf(eventType)];
}
// unregister the event
if (!evtHandlers[eventType]) {
evtHandlers[eventType] = [];
}
var handlers = evtHandlers[eventType];
if (handlers) {
for (var i = handlers.length, handler; i--, (handler = handlers[i]);) {
if (handler.rect === rect) {
// make sure all references are null
handler.rect = handler.cb = handler.floating = null;
evtHandlers[eventType].splice(i, 1);
}
}
}
};
/**
* Will translate global (frequently used) pointer events
* which should be catched at root level, into minipubsub system events
* @name _translatePointerEvents
* @memberOf me.input
* @private
* @function
*/
obj._translatePointerEvents = function () {
// listen to mouse move (and touch move) events on the viewport
// and convert them to a system event by default
obj.registerPointerEvent("pointermove", me.game.viewport, function (e) {
me.event.publish(me.event.MOUSEMOVE, [e]);
return false;
});
};
})();