/** * Manager handling the keyboard and mouse/touch events. * * @author Alain Pitiot * @version 3.0.0b11 * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com}) * @license Distributed under the terms of the MIT License */ import { MonotonicClock, Clock } from '../util/Clock'; import { PsychoJS } from './PsychoJS'; import * as util from '../util/Util'; /** * @class *
This manager handles all participant interactions with the experiment, i.e. keyboard, mouse and touch events.
* * @name module:core.EventManager * @class * @param {Object} options * @param {PsychoJS} options.psychoJS - the PsychoJS instance */ export class EventManager { constructor(psychoJS) { this._psychoJS = psychoJS; // populate the reverse pyglet map: for (var keyName in EventManager._pygletMap) EventManager._reversePygletMap[EventManager._pygletMap[keyName]] = keyName; // add key listeners: this._keyBuffer = []; this._addKeyListeners(); // mouse info: // note: (a) clocks are reset on mouse button presses // (b) the mouse listeners are added to the PIXI renderer, upon the latter's creation (see Window.js) this._mouseInfo = { pos: [0, 0], wheelRel: [0.0, 0.0], buttons: { pressed: [0, 0, 0], clocks: [new Clock(), new Clock(), new Clock()], // time elapsed from last reset of the button.Clocks: times: [0.0, 0.0, 0.0] }, // clock reset when mouse is moved: moveClock: new Clock() }; } /** * Get the list of keys pressed by the participant. * *Note: The w3c [key-event viewer]{@link https://w3c.github.io/uievents/tools/key-event-viewer.html} can be used to see possible values for the items in the keyList given the user's keyboard and chosen layout. The "key" and "code" columns in the UI Events fields are the relevant values for the keyList argument.
* * @name module:core.EventManager#getKeys * @function * @public * @param {Object} options * @param {Array.string} [options.keyList= null] - keyList allows the user to specify a set of keys to check for. Only keypresses from this set of keys will be removed from the keyboard buffer. If no keyList is given, all keys will be checked and the key buffer will be cleared completely. * @param {boolean} [options.timeStamped= false] - If true will return a list of tuples instead of a list of keynames. Each tuple has (keyname, time). * @return {Array.string} the list of keys that were pressed. */ getKeys({ keyList = null, timeStamped = false } = {}) { if (keyList != null) keyList = this._pyglet2w3c(keyList); let newBuffer = []; let keys = []; for (let i = 0; i < this._keyBuffer.length; ++i) { const key = this._keyBuffer[i]; let keyId = null; if (keyList != null) { let index = keyList.indexOf(key.code); if (index < 0) index = keyList.indexOf(EventManager._keycodeMap[key.keyCode]); if (index >= 0) keyId = EventManager._reversePygletMap[keyList[index]]; } else keyId = EventManager._reversePygletMap[key.code]; if (keyId != null) { if (timeStamped) keys.push([keyId, key.timestamp]); else keys.push(keyId); } else newBuffer.push(key); // keep key press in buffer } this._keyBuffer = newBuffer; return keys; } /** * @typedef EventManager.ButtonInfo * @property {Array.number} pressed - the status of each mouse button [left, center, right]: 1 for pressed, 0 for released * @property {Array.Clock} clocks - the clocks associated to the mouse buttons, reset whenever the button is pressed * @property {Array.number} times - the time elapsed since the last rest of the associated clock */ /** * @typedef EventManager.MouseInfo * @property {Array.number} pos - the position of the mouse [x, y] * @property {Array.number} wheelRel - the relative position of the wheel [x, y] * @property {EventManager.ButtonInfo} buttons - the mouse button info * @property {Clock} moveClock - the clock that is reset whenever the mouse moves */ /** * Get the mouse info. * * @name module:core.EventManager#getMouseInfo * @function * @public * @return {EventManager.MouseInfo} the mouse info. */ getMouseInfo() { return this._mouseInfo; } /** * Clear all events from the event buffer. * * @name module:core.EventManager#clearEvents * @function * @public * * @todo handle the attribs argument */ clearEvents(attribs) { this.clearKeys(); } /** * Clear all keys from the key buffer. * * @name module:core.EventManager#clearKeys * @function * @public */ clearKeys() { this._keyBuffer = []; } /** * Start the move clock. * * @name module:core.EventManager#startMoveClock * @function * @public * * @todo not implemented */ startMoveClock() { } /** * Stop the move clock. * * @name module:core.EventManager#stopMoveClock * @function * @public * * @todo not implemented */ stopMoveClock() { } /** * Reset the move clock. * * @name module:core.EventManager#resetMoveClock * @function * @public * * @todo not implemented */ resetMoveClock() { } /** * Add various mouse listeners to the Pixi renderer of the {@link Window}. * * @name module:core.EventManager#addMouseListeners * @function * @public * @param {PIXI.Renderer} renderer - The Pixi renderer */ addMouseListeners(renderer) { let self = this; const view = renderer.view; /* // TEMPORARY DEBUG FOR IPAD/IPHONE: for (let eventName of ['click', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'mouseupoutside', 'pointercancel', 'pointerdown', 'pointermove', 'pointerout', 'pointerover', 'pointertap', 'pointerup', 'pointerupoutside', 'rightclick', 'rightdown', 'rightup', ' rightupoutside', 'tap', 'touchcancel', 'touchend', 'touchendoutside', 'touchmove', 'touchstart']) view.addEventListener(eventName, event => { console.log('event: ' + eventName + ' -> ', event); }); */ view.addEventListener("pointerdown", event => { self._mouseInfo.buttons.pressed[event.button] = 1; self._mouseInfo.buttons.times[event.button] = self._psychoJS._monotonicClock.getTime() - self._mouseInfo.buttons.clocks[event.button].getLastResetTime(); self._mouseInfo.pos = [event.offsetX, event.offsetY]; //psychoJS.logging.data("Mouse: " + label + " button down, pos=(" + x + "," + y + ")"); }, false); view.addEventListener("pointerup", event => { self._mouseInfo.buttons.pressed[event.button] = 0; self._mouseInfo.buttons.times[event.button] = self._psychoJS._monotonicClock.getTime() - self._mouseInfo.buttons.clocks[event.button].getLastResetTime(); self._mouseInfo.pos = [event.offsetX, event.offsetY]; //psychoJS.logging.data("Mouse: " + label + " button down, pos=(" + x + "," + y + ")"); }, false); view.addEventListener("pointermove", event => { self._mouseInfo.pos = [event.offsetX, event.offsetY]; self._mouseInfo.moveClock.reset(); }, false); view.addEventListener("wheel", event => { self._mouseInfo.wheelRel[0] += event.deltaX; self._mouseInfo.wheelRel[1] += event.deltaY; //var x = ev.offsetX; //var y = ev.offsetY; //var msg = "Mouse: wheel shift=(" + ev.deltaX + "," + ev.deltaY + "), pos=(" + x + "," + y + ")"; //psychoJS.logging.data(msg); }, false); } /** * Add key listeners to the document. * * @name module:core.EventManager#_addKeyListeners * @function * @private */ _addKeyListeners() { let self = this; // add a keydown listener: document.addEventListener("keydown", (e) => { self._keyBuffer.push({ code: e.code, key: e.key, keyCode: e.keyCode, timestamp: MonotonicClock.getReferenceTime() / 1000 }); self._psychoJS.logger.trace('keys pressed : ', util.toString(self._keyBuffer)); }); } /** * Convert a keylist that uses pyglet key names to one that uses W3C KeyboardEvent.code values. *This allows key lists that work in the builder environment to work in psychoJS web experiments.
* * @name module:core.EventManager#_pyglet2w3c * @function * @private * @param {Array.string} keyList - the array of pyglet key names * @return {Array.string} the w3c keyList */ _pyglet2w3c(pygletKeyList) { let w3cKeyList = []; for (let i = 0; i < pygletKeyList.length; i++) { if (typeof EventManager._pygletMap[pygletKeyList[i]] === 'undefined') w3cKeyList.push(pygletKeyList[i]); else w3cKeyList.push(EventManager._pygletMap[pygletKeyList[i]]); } return w3cKeyList; } } /** *This map provides support for browsers that have not yet * adopted the W3C KeyboardEvent.code standard for detecting key presses. * It maps the deprecated KeyboardEvent.keycode values to the W3C UI event codes.
* * @name module:core.EventManager#_keycodeMap * @readonly * @private * @type {Object.This map associates pyglet key names to the corresponding W3C KeyboardEvent.codes.
*
* @name module:core.EventManager#_pygletMap
* @readonly
* @private
* @type {Object. This map associates W3C KeyboardEvent.codes to the corresponding pyglet key names.
*
* @name module:core.EventManager#_reversePygletMap
* @readonly
* @private
* @type {Object.