From de75a3f0e47e020672a203c7b6b3fd1dd4dc61e7 Mon Sep 17 00:00:00 2001 From: w1n5t0n Date: Sun, 22 Mar 2026 22:40:15 +0200 Subject: [PATCH] feat(playground): expand gamepad support with face buttons and Steam Deck fixes - Map A=Train, B=Clear Examples, X=Randomize, LB=Thumbs Down, RB=Thumbs Up - Add periodic gamepad polling fallback for environments where gamepadconnected event doesn't fire reliably (Steam Deck, some Linux browsers) - Show "Press any gamepad button to connect" hint on page load --- playground/js/a-app.js | 13 +++++++++++-- playground/js/b-app.js | 12 ++++++++++-- playground/js/c-app.js | 9 +++++++-- playground/js/ui/gamepad.js | 33 ++++++++++++++++++++++----------- 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/playground/js/a-app.js b/playground/js/a-app.js index 92ac2d4..940be01 100644 --- a/playground/js/a-app.js +++ b/playground/js/a-app.js @@ -1365,14 +1365,23 @@ function wireGamepad() { onJoystickMove(); }, onButton: (btn) => { - if (btn === 'rb') onThumbsUp(); if (btn === 'lb') onThumbsDown(); + if (btn === 'rb') onThumbsUp(); + if (btn === 'a') onTrain(); + if (btn === 'x') onRandomize(); + if (btn === 'b') onClearExamples(); }, onConnectionChange: (connected) => { const el = document.getElementById('gamepad-status'); - if (el) el.textContent = connected ? 'Gamepad connected' : ''; + if (el) el.textContent = connected ? 'Gamepad connected' : 'Press any button to activate gamepad'; }, }); + + // Show hint immediately if no gamepad detected yet + if (!gamepad.connected) { + const el = document.getElementById('gamepad-status'); + if (el) el.textContent = 'Press any gamepad button to connect'; + } } // ---- Keyboard ---- diff --git a/playground/js/b-app.js b/playground/js/b-app.js index bc5988b..553cd26 100644 --- a/playground/js/b-app.js +++ b/playground/js/b-app.js @@ -1241,15 +1241,23 @@ function init() { mappingView.drawOverlay(); }, onButton: (btn) => { - if (btn === 'rb') onThumbsUp(); if (btn === 'lb') onThumbsDown(); + if (btn === 'rb') onThumbsUp(); + if (btn === 'a') onTrain(); + if (btn === 'x') onRandomize(); + if (btn === 'b') onClearExamples(); }, onConnectionChange: (connected) => { const el = document.getElementById('gamepad-status'); - if (el) el.textContent = connected ? 'Gamepad connected' : ''; + if (el) el.textContent = connected ? 'Gamepad connected' : 'Press any button to activate gamepad'; }, }); + if (!gamepad.connected) { + const el = document.getElementById('gamepad-status'); + if (el) el.textContent = 'Press any gamepad button to connect'; + } + // Resize window.addEventListener('resize', () => { visualizer.resize(); diff --git a/playground/js/c-app.js b/playground/js/c-app.js index 7a3bf01..8c42b52 100644 --- a/playground/js/c-app.js +++ b/playground/js/c-app.js @@ -287,15 +287,20 @@ function init() { drawJoystick(); }, onButton: (btn) => { - if (btn === 'rb') onThumbsUp(); if (btn === 'lb') onThumbsDown(); + if (btn === 'rb') onThumbsUp(); }, onConnectionChange: (connected) => { const el = document.getElementById('gamepad-status'); - if (el) el.textContent = connected ? 'Gamepad connected' : ''; + if (el) el.textContent = connected ? 'Gamepad connected' : 'Press any button to activate gamepad'; }, }); + if (!gamepad.connected) { + const el = document.getElementById('gamepad-status'); + if (el) el.textContent = 'Press any gamepad button to connect'; + } + // Follow mode wiring wireFollowMode(); diff --git a/playground/js/ui/gamepad.js b/playground/js/ui/gamepad.js index 82f96ff..9856d12 100644 --- a/playground/js/ui/gamepad.js +++ b/playground/js/ui/gamepad.js @@ -37,6 +37,17 @@ export class GamepadInput { // Check if a gamepad is already connected this._findGamepad(); + + // Periodic fallback: some environments (Steam Deck, some Linux browsers) + // don't fire gamepadconnected reliably. Poll every 500ms until found. + this._probeInterval = setInterval(() => { + if (!this.connected) { + this._findGamepad(); + } else { + clearInterval(this._probeInterval); + this._probeInterval = null; + } + }, 500); } _onConnected(e) { @@ -126,18 +137,17 @@ export class GamepadInput { this.onMove(mappedX, mappedY); } - // Buttons: LB=4, RB=5 + // Buttons: A=0, B=1, X=2, Y=3, LB=4, RB=5 if (this.onButton) { - const lbPressed = !!gp.buttons[4]?.pressed; - const rbPressed = !!gp.buttons[5]?.pressed; - const lbPrev = !!this._buttonsPrev[4]; - const rbPrev = !!this._buttonsPrev[5]; - - if (lbPressed && !lbPrev) this.onButton('lb'); - if (rbPressed && !rbPrev) this.onButton('rb'); - - this._buttonsPrev[4] = lbPressed; - this._buttonsPrev[5] = rbPressed; + const watched = [0, 1, 2, 3, 4, 5]; + const names = ['a', 'b', 'x', 'y', 'lb', 'rb']; + for (let i = 0; i < watched.length; i++) { + const idx = watched[i]; + const pressed = !!gp.buttons[idx]?.pressed; + const prev = !!this._buttonsPrev[idx]; + if (pressed && !prev) this.onButton(names[i]); + this._buttonsPrev[idx] = pressed; + } } return moved; @@ -153,5 +163,6 @@ export class GamepadInput { destroy() { window.removeEventListener('gamepadconnected', this._onConnected); window.removeEventListener('gamepaddisconnected', this._onDisconnected); + if (this._probeInterval) clearInterval(this._probeInterval); } }