added comments to Gamepad class

This commit is contained in:
e7d 2017-05-14 11:50:49 +02:00
parent 0170909afd
commit 23b474582a

View File

@ -1,13 +1,15 @@
class Gamepad { class Gamepad {
constructor() { constructor() {
this.haveEvents = 'ongamepadconnected' in window; this.haveEvents = 'ongamepadconnected' in window;
this.debug = false;
this.scanGamepadsDelay = 1000; // cached DOM references
this.gamepads = {};
this.$gamepad = $('.gamepad'); this.$gamepad = $('.gamepad');
this.$nogamepad = $('.no-gamepad'); this.$nogamepad = $('.no-gamepad');
this.$debug = $('.debug'); this.$debug = $('.debug');
this.$help = $('.help'); this.$help = $('.help');
// gamepad collection default values
this.gamepads = {};
this.gamepadIdentifiers = { this.gamepadIdentifiers = {
'debug': { 'debug': {
'id': /debug/, 'id': /debug/,
@ -22,8 +24,14 @@ class Gamepad {
'colors': ['black', 'white'] 'colors': ['black', 'white']
} }
}; };
// gamepad help default values
this.gamepadHelpTimeout = null; this.gamepadHelpTimeout = null;
this.gamepadHelpDelay = 5000; this.gamepadHelpDelay = 5000;
// active gamepad default values
this.scanGamepadsDelay = 500;
this.debug = false;
this.activeGamepad = null; this.activeGamepad = null;
this.activeGamepadIndex = null; this.activeGamepadIndex = null;
this.activeGamepadType = null; this.activeGamepadType = null;
@ -36,35 +44,59 @@ class Gamepad {
axes: [] axes: []
}; };
// listen for gamepad related events
window.addEventListener("gamepadconnected", this.onGamepadConnect.bind(this)); window.addEventListener("gamepadconnected", this.onGamepadConnect.bind(this));
window.addEventListener("gamepaddisconnected", this.onGamepadDisconnect.bind(this)); window.addEventListener("gamepaddisconnected", this.onGamepadDisconnect.bind(this));
// listen for keyboard events
window.addEventListener("keydown", this.onKeyDown.bind(this)); window.addEventListener("keydown", this.onKeyDown.bind(this));
// bind a gamepads scan
window.setInterval(this.scanGamepads.bind(this), this.scanGamepadsDelay); window.setInterval(this.scanGamepads.bind(this), this.scanGamepadsDelay);
// read URI for display parameters to initalize
this.params = { this.params = {
gamepadIndex: $.urlParam('index') || $.urlParam('i') || null,
gamepadColor: $.urlParam('color') || $.urlParam('c') || null, gamepadColor: $.urlParam('color') || $.urlParam('c') || null,
zoom: $.urlParam('zoom') || $.urlParam('z') || null zoom: $.urlParam('zoom') || $.urlParam('z') || null
}; };
// if a gamepad index is specified, try to map the corresponding gamepad
if (this.params.gamepadIndex) {
this.refreshGamepads();
this.mapGamepad(+this.params.gamepadIndex);
return;
}
// by default, enqueue a delayed display of the help tooltip
this.displayGamepadHelp(); this.displayGamepadHelp();
} }
displayGamepadHelp() { displayGamepadHelp(displayNow = false) {
this.gamepadHelpTimeout = window.setTimeout(() => { // display help tooltip if no gamepad is active after X ms
this.gamepadHelpTimeout = window.setTimeout(
() => {
this.$nogamepad.fadeIn(); this.$nogamepad.fadeIn();
}, this.gamepadHelpDelay); },
displayNow ? 0 : this.gamepadHelpDelay
);
} }
hideGamepadHelp() { hideGamepadHelp() {
// cancel the queued display of the help tooltip, if any
window.clearTimeout(this.gamepadHelpTimeout); window.clearTimeout(this.gamepadHelpTimeout);
// hide the help tooltip
this.$nogamepad.hide(); this.$nogamepad.hide();
} }
onGamepadConnect(e) { onGamepadConnect(e) {
// on gamepad connection, add it to the list
this.addGamepad(e.gamepad); this.addGamepad(e.gamepad);
} }
onGamepadDisconnect(e) { onGamepadDisconnect(e) {
// on gamepad disconnection, remove it from the list
this.removeGamepad(e.gamepad.index); this.removeGamepad(e.gamepad.index);
} }
@ -99,6 +131,7 @@ class Gamepad {
} }
refreshGamepads() { refreshGamepads() {
// get fresh information from DOM about gamepads
this.gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads() : []); this.gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads() : []);
} }
@ -111,35 +144,48 @@ class Gamepad {
} }
removeGamepad(gamepadIndex) { removeGamepad(gamepadIndex) {
// ensure we have an index to remove
if ('undefined' === typeof gamepadIndex) { if ('undefined' === typeof gamepadIndex) {
return; return;
} }
// if this is the active gamepad
if (gamepadIndex === this.activeGamepadIndex) { if (gamepadIndex === this.activeGamepadIndex) {
// clear associated date
this.activeGamepadIndex = null; this.activeGamepadIndex = null;
this.$gamepad.empty(); this.$gamepad.empty();
} }
delete this.gamepads[gamepadIndex]; delete this.gamepads[gamepadIndex];
// enqueue a display of the help tooltip
this.displayGamepadHelp(); this.displayGamepadHelp();
this.debug = false; this.debug = false;
} }
scanGamepads() { scanGamepads() {
// don't scan if we have an active gamepad
if (null !== this.activeGamepadIndex) { if (null !== this.activeGamepadIndex) {
return; return;
} }
// refresh gamepad information
this.refreshGamepads(); this.refreshGamepads();
// read information for each gamepad
for (let gamepadIndex = 0; gamepadIndex < this.gamepads.length; gamepadIndex++) { for (let gamepadIndex = 0; gamepadIndex < this.gamepads.length; gamepadIndex++) {
const gamepad = this.gamepads[gamepadIndex]; const gamepad = this.gamepads[gamepadIndex];
if (gamepad) { if (gamepad) {
// store the current gamepad give its index
if (gamepad.index in this.gamepads) { if (gamepad.index in this.gamepads) {
this.gamepads[gamepad.index] = gamepad; this.gamepads[gamepad.index] = gamepad;
} }
// read the gamepad buttons
let button; let button;
for (let buttonIndex = 0; buttonIndex < gamepad.buttons.length; buttonIndex++) { for (let buttonIndex = 0; buttonIndex < gamepad.buttons.length; buttonIndex++) {
button = gamepad.buttons[buttonIndex]; button = gamepad.buttons[buttonIndex];
// if one of its button is pressed, activate this gamepad
if (button.pressed) { if (button.pressed) {
this.mapGamepad(gamepad.index); this.mapGamepad(gamepad.index);
} }
@ -149,27 +195,35 @@ class Gamepad {
} }
mapGamepad(gamepadIndex) { mapGamepad(gamepadIndex) {
// ensure a gamepad need to be mapped
if ('undefined' === typeof gamepadIndex) { if ('undefined' === typeof gamepadIndex) {
return; return;
} }
// update local references
this.activeGamepadIndex = gamepadIndex; this.activeGamepadIndex = gamepadIndex;
this.activeGamepad = this.gamepads[this.activeGamepadIndex]; this.activeGamepad = this.gamepads[this.activeGamepadIndex];
// ensure that a gamepad is currently active // ensure that a gamepad was actually found for this index
if (!this.activeGamepad) { if (!this.activeGamepad) {
// this mapping request was probably a mistake :
// - remove the active gamepad index and reference
this.activeGamepadIndex = null; this.activeGamepadIndex = null;
this.activeGamepad = null; this.activeGamepad = null;
// - enqueue a display of the help tooltip right away
this.displayGamepadHelp(true); this.displayGamepadHelp(true);
return; return;
} }
this.activeGamepadType = null;
if (this.debug) { if (this.debug) {
// if the debug option is active, use the associated template
this.activeGamepadType = 'debug'; this.activeGamepadType = 'debug';
this.activeGamepadIdentifier = this.gamepadIdentifiers[this.activeGamepadType]; this.activeGamepadIdentifier = this.gamepadIdentifiers[this.activeGamepadType];
this.activeGamepadColorIndex = 0; this.activeGamepadColorIndex = 0;
} else { } else {
// else, determine the template to use from the gamepad identifier
for (let gamepadType in this.gamepadIdentifiers) { for (let gamepadType in this.gamepadIdentifiers) {
if (this.gamepadIdentifiers[gamepadType].id.test(this.activeGamepad.id)) { if (this.gamepadIdentifiers[gamepadType].id.test(this.activeGamepad.id)) {
this.activeGamepadType = gamepadType; this.activeGamepadType = gamepadType;
@ -179,37 +233,50 @@ class Gamepad {
} }
} }
// ensure a valid gamepad type was discovered
if (!this.activeGamepadType) { if (!this.activeGamepadType) {
return; return;
} }
// hoist some template related variables
let button; let button;
let axis; let axis;
// hide the help before displaying the template
this.hideGamepadHelp(); this.hideGamepadHelp();
// load the template HTML file
$.ajax( $.ajax(
'templates/' + this.activeGamepadType + '/template.html' 'templates/' + this.activeGamepadType + '/template.html'
).done((template) => { ).done((template) => {
// inject the template HTML
this.$gamepad.html(template); this.$gamepad.html(template);
// read for parameters to apply:
// - color
if (this.params.gamepadColor) { if (this.params.gamepadColor) {
this.changeGamepadColor(this.params.gamepadColor); this.changeGamepadColor(this.params.gamepadColor);
} }
// - zoom
if (this.params.zoom) { if (this.params.zoom) {
this.changeZoom(this.params.zoom); this.changeZoom(this.params.zoom);
} }
// save the buttons mapping of this template
this.mapping.buttons = []; this.mapping.buttons = [];
for (let buttonIndex = 0; buttonIndex < this.activeGamepad.buttons.length; buttonIndex++) { for (let buttonIndex = 0; buttonIndex < this.activeGamepad.buttons.length; buttonIndex++) {
button = this.activeGamepad.buttons[buttonIndex]; button = this.activeGamepad.buttons[buttonIndex];
this.mapping.buttons[buttonIndex] = $('[data-button=' + buttonIndex + ']'); this.mapping.buttons[buttonIndex] = $('[data-button=' + buttonIndex + ']');
} }
// save the axes mapping of this template
this.mapping.axes = []; this.mapping.axes = [];
for (let axisIndex = 0; axisIndex < this.activeGamepad.axes.length; axisIndex++) { for (let axisIndex = 0; axisIndex < this.activeGamepad.axes.length; axisIndex++) {
axis = this.activeGamepad.axes[axisIndex]; axis = this.activeGamepad.axes[axisIndex];
this.mapping.axes[axisIndex] = $('[data-axis=' + axisIndex + '], [data-axis-x=' + axisIndex + '], [data-axis-y=' + axisIndex + '], [data-axis-z=' + axisIndex + ']'); this.mapping.axes[axisIndex] = $('[data-axis=' + axisIndex + '], [data-axis-x=' + axisIndex + '], [data-axis-y=' + axisIndex + '], [data-axis-z=' + axisIndex + ']');
} }
// enqueue the initial display refresh
this.updateVisualStatus(); this.updateVisualStatus();
}); });
} }
@ -220,38 +287,53 @@ class Gamepad {
return; return;
} }
this.refreshGamepads(); // enqueue the next refresh right away
requestAnimationFrame(this.updateVisualStatus.bind(this)); requestAnimationFrame(this.updateVisualStatus.bind(this));
// load latest gamepad data
this.refreshGamepads();
// hoist some variables
let button; let button;
let $button; let $button;
let axis;
let $axis;
// update the buttons
for (let buttonIndex = 0; buttonIndex < this.activeGamepad.buttons.length; buttonIndex++) { for (let buttonIndex = 0; buttonIndex < this.activeGamepad.buttons.length; buttonIndex++) {
// find the DOM element
$button = this.mapping.buttons[buttonIndex]; $button = this.mapping.buttons[buttonIndex];
if (!$button) { if (!$button) {
// nothing to do for this button if no DOM element exists
break; break;
} }
// read the button data
button = this.activeGamepad.buttons[buttonIndex]; button = this.activeGamepad.buttons[buttonIndex];
// update the display values
$button.attr('data-pressed', button.pressed); $button.attr('data-pressed', button.pressed);
$button.attr('data-value', button.value); $button.attr('data-value', button.value);
// hook the template defined button update method
if ("function" === typeof this.updateButton) { if ("function" === typeof this.updateButton) {
this.updateButton($button); this.updateButton($button);
} }
} }
let axis; // update the axes
let $axis;
for (let axisIndex = 0; axisIndex < this.activeGamepad.axes.length; axisIndex++) { for (let axisIndex = 0; axisIndex < this.activeGamepad.axes.length; axisIndex++) {
// find the DOM element
$axis = this.mapping.axes[axisIndex]; $axis = this.mapping.axes[axisIndex];
if (!$axis) { if (!$axis) {
// nothing to do for this button if no DOM element exists
break; break;
} }
// read the axis data
axis = this.activeGamepad.axes[axisIndex]; axis = this.activeGamepad.axes[axisIndex];
// update the display values
if ($axis.is('[data-axis=' + axisIndex + ']')) { if ($axis.is('[data-axis=' + axisIndex + ']')) {
$axis.attr('data-value', axis); $axis.attr('data-value', axis);
} }
@ -265,6 +347,7 @@ class Gamepad {
$axis.attr('data-value-z', axis); $axis.attr('data-value-z', axis);
} }
// hook the template defined axis update method
if ("function" === typeof this.updateAxis) { if ("function" === typeof this.updateAxis) {
this.updateAxis($axis); this.updateAxis($axis);
} }
@ -277,7 +360,8 @@ class Gamepad {
return; return;
} }
if (!gamepadColor) { if ('undefined' === typeof zoomLevel) {
// no color was specified, load the next one in list
this.activeGamepadColorIndex++; this.activeGamepadColorIndex++;
if (this.activeGamepadColorIndex > this.activeGamepadIdentifier.colors.length - 1) { if (this.activeGamepadColorIndex > this.activeGamepadIdentifier.colors.length - 1) {
this.activeGamepadColorIndex = 0; this.activeGamepadColorIndex = 0;
@ -285,10 +369,12 @@ class Gamepad {
this.activeGamepadColorName = this.activeGamepadIdentifier.colors[this.activeGamepadColorIndex]; this.activeGamepadColorName = this.activeGamepadIdentifier.colors[this.activeGamepadColorIndex];
} else { } else {
if (! isNaN(parseInt(gamepadColor))) { if (!isNaN(parseInt(gamepadColor))) {
// the color is a number, load it by its index
this.activeGamepadColorIndex = gamepadColor; this.activeGamepadColorIndex = gamepadColor;
this.activeGamepadColorName = this.activeGamepadIdentifier.colors[this.activeGamepadColorIndex]; this.activeGamepadColorName = this.activeGamepadIdentifier.colors[this.activeGamepadColorIndex];
} else { } else {
// the color is a string, load it by its name
this.activeGamepadColorName = gamepadColor; this.activeGamepadColorName = gamepadColor;
this.activeGamepadColorIndex = 0; this.activeGamepadColorIndex = 0;
for (let gamepadColorName in this.activeGamepadIdentifier.colors) { for (let gamepadColorName in this.activeGamepadIdentifier.colors) {
@ -300,6 +386,7 @@ class Gamepad {
} }
} }
// update the DOM with the color value
this.$gamepad.attr('data-color', this.activeGamepadColorName); this.$gamepad.attr('data-color', this.activeGamepadColorName);
} }
@ -309,26 +396,32 @@ class Gamepad {
return; return;
} }
if (!zoomLevel) { // ensure we have some data to process
if ('undefined' === typeof zoomLevel) {
return; return;
} }
if ('0' === zoomLevel) { if ('0' === zoomLevel) {
// "0" means a zoom reset
this.activeGamepadZoomLevel = 1; this.activeGamepadZoomLevel = 1;
} }
else if ('+' === zoomLevel && this.activeGamepadZoomLevel < 2) { else if ('+' === zoomLevel && this.activeGamepadZoomLevel < 2) {
// "+" means a zoom in if we still can
this.activeGamepadZoomLevel += 0.1; this.activeGamepadZoomLevel += 0.1;
} }
else if ('-' === zoomLevel && this.activeGamepadZoomLevel > 0.2) { else if ('-' === zoomLevel && this.activeGamepadZoomLevel > 0.2) {
// "-" means a zoom out if we still can
this.activeGamepadZoomLevel -= 0.1; this.activeGamepadZoomLevel -= 0.1;
} }
else if (! isNaN(zoomLevel = parseFloat(zoomLevel))) { else if (!isNaN(zoomLevel = parseFloat(zoomLevel))) {
// an integer value means a value-based zoom
this.activeGamepadZoomLevel = zoomLevel; this.activeGamepadZoomLevel = zoomLevel;
} }
// hack: fix js float issues // hack: fix js float issues
this.activeGamepadZoomLevel = +this.activeGamepadZoomLevel.toFixed(1); this.activeGamepadZoomLevel = +this.activeGamepadZoomLevel.toFixed(1);
// update the DOM with the zoom value
this.$gamepad.css( this.$gamepad.css(
'transform', 'transform',
'translate(-50%, -50%) scale(' + this.activeGamepadZoomLevel + ', ' + this.activeGamepadZoomLevel + ')' 'translate(-50%, -50%) scale(' + this.activeGamepadZoomLevel + ', ' + this.activeGamepadZoomLevel + ')'