From 3a410e2a53f3b24be93ec1421506f340b22ac1ae Mon Sep 17 00:00:00 2001 From: e7d Date: Tue, 26 Jul 2022 17:55:19 +0200 Subject: [PATCH] various update: - settings overlay - better url params handling - debug toggle fixes --- css/main.css | 86 +++++++++++++++++---- index.html | 48 ++++++++++-- js/gamepad.js | 204 +++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 291 insertions(+), 47 deletions(-) diff --git a/css/main.css b/css/main.css index f12d357..6e1a238 100644 --- a/css/main.css +++ b/css/main.css @@ -94,6 +94,34 @@ body.unsupported #gamepad { transform: translate(-50%, -50%); } +#overlay { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + height: auto; + padding: 8px 0; + display: flex; + justify-content: center; + align-self: center; +} + +#overlay > span { + padding: 0 8px; +} + +#overlay #color, +#overlay #triggers { + display: none; +} + +#overlay select { + padding: 4px 0; + border-radius: 4px; + background: whitesmoke; + border: none; +} + #help-popout { background: whitesmoke; border: 1px solid rgba(0, 0, 0, 0.2); @@ -149,26 +177,26 @@ body.unsupported #gamepad { vertical-align: top; } -#help-popout table>thead>tr>th { +#help-popout table > thead > tr > th { border-bottom: 2px solid #ddd; border-top: 0; vertical-align: bottom; } kbd { - -moz-border-radius: .3em; + -moz-border-radius: 0.3em; -moz-user-select: none; - -webkit-border-radius: .3em; + -webkit-border-radius: 0.3em; -webkit-user-select: none; - border-radius: .3em; + border-radius: 0.3em; border: none; cursor: default; display: inline-block; display: inline; font: normal 1em monospace; - margin: 0 .2em; + margin: 0 0.2em; min-width: 1em; - padding: .2em .6em; + padding: 0.2em 0.6em; text-align: center; text-decoration: none; font-weight: bold; @@ -183,23 +211,51 @@ kbd[title], kbd, kbd.dark { - -moz-box-shadow: inset 0 0 1px rgb(150, 150, 150), inset 0 -.05em .4em rgb(80, 80, 80), 0 .1em 0 rgb(30, 30, 30), 0 .1em .1em rgba(0, 0, 0, .3); - -webkit-box-shadow: inset 0 0 1px rgb(150, 150, 150), inset 0 -.05em .4em rgb(80, 80, 80), 0 .1em 0 rgb(30, 30, 30), 0 .1em .1em rgba(0, 0, 0, .3); + -moz-box-shadow: inset 0 0 1px rgb(150, 150, 150), + inset 0 -0.05em 0.4em rgb(80, 80, 80), 0 0.1em 0 rgb(30, 30, 30), + 0 0.1em 0.1em rgba(0, 0, 0, 0.3); + -webkit-box-shadow: inset 0 0 1px rgb(150, 150, 150), + inset 0 -0.05em 0.4em rgb(80, 80, 80), 0 0.1em 0 rgb(30, 30, 30), + 0 0.1em 0.1em rgba(0, 0, 0, 0.3); background: -moz-linear-gradient(top, rgb(60, 60, 60), rgb(80, 80, 80)); - background: -webkit-gradient(linear, left top, left bottom, from(rgb(60, 60, 60)), to(rgb(80, 80, 80))); + background: -webkit-gradient( + linear, + left top, + left bottom, + from(rgb(60, 60, 60)), + to(rgb(80, 80, 80)) + ); background: rgb(80, 80, 80); - box-shadow: inset 0 0 1px rgb(150, 150, 150), inset 0 -.05em .4em rgb(80, 80, 80), 0 .1em 0 rgb(30, 30, 30), 0 .1em .1em rgba(0, 0, 0, .3); + box-shadow: inset 0 0 1px rgb(150, 150, 150), + inset 0 -0.05em 0.4em rgb(80, 80, 80), 0 0.1em 0 rgb(30, 30, 30), + 0 0.1em 0.1em rgba(0, 0, 0, 0.3); color: rgb(250, 250, 250); text-shadow: -1px -1px 0 rgb(70, 70, 70); } kbd.light { - -moz-box-shadow: inset 0 0 1px rgb(255, 255, 255), inset 0 0 .4em rgb(200, 200, 200), 0 .1em 0 rgb(130, 130, 130), 0 .11em 0 rgba(0, 0, 0, .4), 0 .1em .11em rgba(0, 0, 0, .9); - -webkit-box-shadow: inset 0 0 1px rgb(255, 255, 255), inset 0 0 .4em rgb(200, 200, 200), 0 .1em 0 rgb(130, 130, 130), 0 .11em 0 rgba(0, 0, 0, .4), 0 .1em .11em rgba(0, 0, 0, .9); - background: -moz-linear-gradient(top, rgb(210, 210, 210), rgb(255, 255, 255)); - background: -webkit-gradient(linear, left top, left bottom, from(rgb(210, 210, 210)), to(rgb(255, 255, 255))); + -moz-box-shadow: inset 0 0 1px rgb(255, 255, 255), + inset 0 0 0.4em rgb(200, 200, 200), 0 0.1em 0 rgb(130, 130, 130), + 0 0.11em 0 rgba(0, 0, 0, 0.4), 0 0.1em 0.11em rgba(0, 0, 0, 0.9); + -webkit-box-shadow: inset 0 0 1px rgb(255, 255, 255), + inset 0 0 0.4em rgb(200, 200, 200), 0 0.1em 0 rgb(130, 130, 130), + 0 0.11em 0 rgba(0, 0, 0, 0.4), 0 0.1em 0.11em rgba(0, 0, 0, 0.9); + background: -moz-linear-gradient( + top, + rgb(210, 210, 210), + rgb(255, 255, 255) + ); + background: -webkit-gradient( + linear, + left top, + left bottom, + from(rgb(210, 210, 210)), + to(rgb(255, 255, 255)) + ); background: rgb(250, 250, 250); - box-shadow: inset 0 0 1px rgb(255, 255, 255), inset 0 0 .4em rgb(200, 200, 200), 0 .1em 0 rgb(130, 130, 130), 0 .11em 0 rgba(0, 0, 0, .4), 0 .1em .11em rgba(0, 0, 0, .9); + box-shadow: inset 0 0 1px rgb(255, 255, 255), + inset 0 0 0.4em rgb(200, 200, 200), 0 0.1em 0 rgb(130, 130, 130), + 0 0.11em 0 rgba(0, 0, 0, 0.4), 0 0.1em 0.11em rgba(0, 0, 0, 0.9); color: rgb(50, 50, 50); text-shadow: 0 0 2px rgb(255, 255, 255); } diff --git a/index.html b/index.html index 4e6ce9d..27eb3ef 100644 --- a/index.html +++ b/index.html @@ -14,11 +14,11 @@ -
Sorry, but your browser does not support the Gamepad API.
+
Sorry, but your browser does not support the Gamepad API.

Press H to read instructions.

- + @@ -81,11 +81,48 @@
+
+ + + + + + + + + + + + + + + + +

Help

Instructions

-

Press and hold any of your gamepad buttons for at least 1 second. If your gamepad is supported, it shows up.

+

Press and hold any of your gamepad buttons for at least 1 second. If your gamepad is supported, it shows up. +

Detected Controllers

@@ -155,7 +192,8 @@

Credits

-

All information and source code can be found on GitHub at e7d/gamepad-viewer.

+

All information and source code can be found on GitHub at e7d/gamepad-viewer.

diff --git a/js/gamepad.js b/js/gamepad.js index 3ccfff3..0f54d5c 100644 --- a/js/gamepad.js +++ b/js/gamepad.js @@ -10,8 +10,17 @@ class Gamepad { constructor() { // cached DOM references this.$body = $("body"); - this.$gamepad = $("#gamepad"); this.$instructions = $("#instructions"); + this.$gamepad = $("#gamepad"); + this.$overlay = $("#overlay"); + this.$skinSelect = $("select[name=skin]"); + this.$backgroundSelect = $("select[name=background]"); + this.$colorOverlay = this.$overlay.find("#color"); + this.$colorSelect = this.$colorOverlay.find("select[name=color]"); + this.$triggersOverlay = this.$overlay.find("#triggers"); + this.$triggersSelect = this.$triggersOverlay.find( + "select[name=triggers]" + ); this.$helpPopout = $("#help-popout"); this.$gamepadList = $("#gamepad-list"); @@ -20,6 +29,7 @@ class Gamepad { "checkered", "dimgrey", "black", + "white", "lime", "magenta", ]; @@ -30,11 +40,14 @@ class Gamepad { "white", "black", "black", + "black", ]; // ensure the GamePad API is available on this browser this.assertGamepadAPI(); + this.initOverlaySelectors(); + // gamepad collection default values this.gamepads = {}; this.identifiers = { @@ -42,22 +55,22 @@ class Gamepad { debug: { id: /debug/, name: "Debug", - colors: [], }, ds4: { id: /054c|54c|09cc|046d|0810|2563/, // 054c = Sony vendor code, 046d,0810,2563 = PS-like controllers vendor codes name: "DualShock 4", colors: ["black", "white", "red", "blue"], + triggers: true, }, // gamecube: { // id: /0079/, // 0079 = Nintendo GameCube vendor code // name: "GameCube Controller", - // colors: ["black"], + // colors: ["black", "purple"], // }, // "joy-con": { // id: /200e/, // 0079 = Joy-Con specific product code // name: "Joy-Con (L+R) Controllers", - // colors: ["blue-red"], + // colors: ["blue-red", "grey-grey"], // }, // stadia: { // id: /18d1/, // 18d1 = Google vendor code @@ -73,12 +86,15 @@ class Gamepad { id: /045e|xinput|XInput/, // 045e = Microsoft vendor code, xinput = standard Windows controller name: "Xbox One", colors: ["black", "white"], + triggers: true, }, }; // gamepad help default values this.instructionsTimeout = null; - this.instructionsDelay = 12000; + this.instructionsDelay = 5000; + this.overlayTimeout = null; + this.overlayDelay = 5000; // active gamepad default values this.scanDelay = 200; @@ -125,7 +141,13 @@ class Gamepad { // bind a gamepads scan window.setInterval(this.scan.bind(this), this.scanDelay); - // change the background is specified + // change the type if specified + const skin = this.getUrlParam("type"); + if (skin) { + this.changeSkin(skin); + } + + // change the background if specified const background = this.getUrlParam("background"); if (background) { let backgroundStyleIndex; @@ -144,6 +166,9 @@ class Gamepad { this.displayInstructions(); } + /** + * Ensures the availability of the Gamepad API in the current navigator + */ assertGamepadAPI() { const getGamepadsFn = navigator.getGamepads ? () => navigator.getGamepads() @@ -157,6 +182,24 @@ class Gamepad { this.getNavigatorGamepads = getGamepadsFn; } + /** + * Initialises the overlay selectors + */ + initOverlaySelectors() { + this.$skinSelect.on("change", () => + this.changeSkin(this.$skinSelect.val()) + ); + this.$backgroundSelect.on("change", () => + this.changeBackgroundStyle(this.$backgroundSelect.val()) + ); + this.$colorSelect.on("change", () => + this.changeGamepadColor(this.$colorSelect.val()) + ); + this.$triggersSelect.on("change", () => + this.toggleTriggersMeter(this.$triggersSelect.val() === "meter") + ); + } + /** * Displays the instructions animation on screen */ @@ -190,6 +233,76 @@ class Gamepad { }, this.instructionsDelay); } + /** + * Displays the overlay animation on screen + */ + displayOverlay() { + // cancel the queued display of the overlay animation, if any + window.clearTimeout(this.overlayTimeout); + // show the overlay + this.$overlay.show(); + + // enqueue a delayed display of the overlay animation + this.hideOverlay(); + } + + /** + * Hides the overlay animation + * + * @param {boolean} [hideNow=false] + */ + hideOverlay(hideNow = false) { + // hide the message right away if needed + if (hideNow) { + this.$overlay.hide(); + } + + // hide overlay animation if no gamepad is active after X ms + this.overlayTimeout = window.setTimeout(() => { + this.$overlay.fadeOut(); + }, this.overlayDelay); + } + + /** + * Update colors following the active/inactive gamepad + */ + updateColors() { + if (!this.type) { + this.$colorOverlay.hide(); + return; + } + + const colors = this.identifiers[this.type].colors; + if (!colors) { + this.$colorOverlay.hide(); + return; + } + + const colorOptions = colors.map( + (color) => `` + ); + this.$colorSelect.html(colorOptions); + this.$colorOverlay.fadeIn(); + } + + /** + * Update triggers following the active/inactive gamepad + */ + updateTriggers() { + if (!this.type) { + this.$triggersOverlay.hide(); + return; + } + + const triggers = this.identifiers[this.type].triggers; + if (!triggers) { + this.$triggersOverlay.hide(); + return; + } + + this.$triggersOverlay.fadeIn(); + } + /** * Handles the gamepad connection event * @@ -223,6 +336,7 @@ class Gamepad { */ onMouseMove() { this.displayInstructions(); + this.displayOverlay(); } /** @@ -438,11 +552,14 @@ class Gamepad { // ensure a valid gamepad type is used this.type = this.getType(gamepad); if (!this.type) return; - this.updateUrlParams({ type: this.type }); // initial setup of the gamepad this.identifier = this.identifiers[this.type]; + // update gamepad color and triggers selectors on overlay + this.updateColors(); + this.updateTriggers(); + // load the HTML template file this.loadTemplate(gamepad); @@ -481,6 +598,8 @@ class Gamepad { this.colorName = null; this.zoomLevel = 1; this.$gamepad.empty(); + this.updateColors(); + this.updateTriggers(); this.clearUrlParams(); // save statistics @@ -500,6 +619,9 @@ class Gamepad { * @param {*} gamepad */ loadTemplate(gamepad) { + // hide the gamepad while we prepare it + this.$gamepad.hide(); + $.ajax(`templates/${this.type}/template.html`).done((template) => { // inject the template HTML this.$gamepad.html(template); @@ -513,7 +635,7 @@ class Gamepad { window.setTimeout(() => this.changeZoom( this.type === "debug" - ? 0 + ? "auto" : this.getUrlParam("zoom") || "auto" ) ); @@ -534,6 +656,9 @@ class Gamepad { // enqueue the initial display refresh this.pollStatus(true); + + // once fully loaded, display the gamepad + this.$gamepad.fadeIn(); }); } @@ -632,6 +757,21 @@ class Gamepad { } } + /** + * Changes the skin + * + * @param {any} skin + */ + changeSkin(skin) { + // update the visual skin selector + this.$skinSelect.val(skin); + + // set the selected skin + this.debug = skin === 'debug'; + this.updateUrlParams({ type: skin !== "auto" ? skin : undefined }); + this.map(this.index); + } + /** * Changes the background style * @@ -643,13 +783,15 @@ class Gamepad { if (this.backgroundStyleIndex > this.backgroundStyle.length - 1) { this.backgroundStyleIndex = 0; } + } else if ("string" === typeof style) { + this.backgroundStyleIndex = this.backgroundStyle.findIndex( + (s) => s === style + ); } else { this.backgroundStyleIndex = style; - this.backgroundStyleName; } - this.backgroundStyleName = this.backgroundStyle[ - this.backgroundStyleIndex - ]; + this.backgroundStyleName = + this.backgroundStyle[this.backgroundStyleIndex]; this.$body.css({ background: @@ -661,6 +803,7 @@ class Gamepad { // update current settings this.updateUrlParams({ background: this.backgroundStyleName }); + this.$backgroundSelect.val(this.backgroundStyleName); // save statistics if (!!window.ga) { @@ -688,6 +831,10 @@ class Gamepad { if (this.colorIndex > this.identifier.colors.length - 1) { this.colorIndex = 0; } + } else if ("string" === typeof style) { + this.colorIndex = this.identifier.colors.findIndex( + (c) => c === color + ); } else { if (!isNaN(parseInt(color))) { // the color is a number, load it by its index @@ -703,13 +850,16 @@ class Gamepad { } } } - this.colorName = this.identifier.colors[this.colorIndex]; + this.colorName = this.identifier.colors + ? this.identifier.colors[this.colorIndex] + : null; // update the DOM with the color value this.$gamepad.attr("data-color", this.colorName); // update current settings this.updateUrlParams({ color: this.colorName }); + this.$colorSelect.val(this.colorName); // save statistics if (!!window.ga) { @@ -736,7 +886,7 @@ class Gamepad { this.zoomMode = level === "auto" ? "auto" : "manual"; - if (level === "auto") { + if (this.zoomMode === "auto") { // "auto" means a "contained in window" zoom, with a max zoom of 1 this.zoomLevel = Math.min( window.innerWidth / this.$gamepad.width(), @@ -748,10 +898,10 @@ class Gamepad { this.zoomLevel = 1; } else if (level === "+" && this.zoomLevel < 2) { // "+" means a zoom in if we still can - this.zoomLevel *= 1.1; - } else if (level === "-" && this.zoomLevel > 0.2) { + this.zoomLevel += 0.1; + } else if (level === "-" && this.zoomLevel > 0.1) { // "-" means a zoom out if we still can - this.zoomLevel /= 1.1; + this.zoomLevel -= 0.1; } else if (!isNaN((level = parseFloat(level)))) { // an integer value means a value-based zoom this.zoomLevel = level; @@ -821,12 +971,12 @@ class Gamepad { /** * Toggles the debug template for the active gamepad, if any */ - toggleDebug() { + toggleDebug(debug = null) { // ensure that a gamepad is currently active if (this.index === null) return; // update debug value - this.debug = !this.debug; + this.debug = debug !== null ? debug : !this.debug; // save statistics if (!!window.ga) { @@ -839,10 +989,7 @@ class Gamepad { } // update current settings - this.updateUrlParams({ type: this.debug ? "debug" : undefined }); - - // remap current gamepad - this.map(this.index); + this.changeSkin(this.debug ? 'debug' : 'auto') } /** @@ -871,6 +1018,9 @@ class Gamepad { * Toggles the triggers meter display */ toggleTriggersMeter(useMeter) { + // ensure that a gamepad is currently active + if (this.index === null) return; + this.triggersMeter = useMeter !== undefined ? useMeter : !this.triggersMeter; this.$gamepad[this.triggersMeter ? "addClass" : "removeClass"]( @@ -878,9 +1028,9 @@ class Gamepad { ); // update current settings - this.updateUrlParams({ - triggers: this.triggersMeter ? "meter" : undefined, - }); + const triggers = this.triggersMeter ? "meter" : "opacity"; + this.updateUrlParams({ triggers }); + this.$triggersSelect.val(triggers); } /** @@ -932,7 +1082,7 @@ class Gamepad { updateUrlParams(newParams) { const params = Object.assign(this.getUrlParams(), newParams); const query = Object.entries(params) - .filter(([, value]) => value !== undefined) + .filter(([, value]) => value !== undefined && value !== null) .map(([key, value]) => `${key}=${value}`) .join("&"); window.history.replaceState({}, document.title, `/?${query}`);