glass-gamepad (b3dGamepad)
A split on-screen ("glass") gamepad for touch contexts, as a Component (so
it's both the overlay element and a [[virtual-gamepad]] GamepadSource, like
gameController/keyboardGamepad). Instead of one controller body, the controls
are grouped into independently-anchored clusters, each a small SVG pinned to a
corner of the view:
| Cluster | Default anchor | Controls |
|---|---|---|
left |
bottom-left | left stick, d-pad, left bumper/trigger |
right |
bottom-right | A/B/X/Y, right stick, right bumper/trigger |
top |
top-center | view, menu |
Each cluster is loaded from a self-contained SVG (default /gamepad-left.svg,
/gamepad-right.svg, /gamepad-top.svg) whose paths are labelled by id
(copied to data-part on load). There's no outer shell — just the clusters.
Usually you don't place this yourself: set the gamepad attribute on
[[tosi-b3d]] and it mounts one and wires it into the active input system. Placed
directly, it's a GamepadSource whose poll() merges all clusters.
import { b3dGamepad } from 'tosijs-3d'
import { elements } from 'tosijs'
const { div, pre } = elements
const pad = b3dGamepad()
const readout = pre({ class: 'readout' })
function update() {
const s = pad.poll()
const lines = []
if (s.leftStickX || s.leftStickY)
lines.push(`L: ${s.leftStickX.toFixed(2)}, ${s.leftStickY.toFixed(2)}`)
if (s.rightStickX || s.rightStickY)
lines.push(`R: ${s.rightStickX.toFixed(2)}, ${s.rightStickY.toFixed(2)}`)
const btns = ['buttonA','buttonB','buttonX','buttonY','leftBumper','rightBumper',
'leftTrigger','rightTrigger','dpadUp','dpadDown','dpadLeft','dpadRight']
.filter((k) => s[k] > 0)
if (btns.length) lines.push(btns.join(', '))
readout.textContent = lines.join('\n') || 'Touch or drag the controls'
requestAnimationFrame(update)
}
update()
// height:100% so the vmin-scaled clusters are exercised at the card's real size.
preview.append(div({ class: 'glass-stage' }, pad, readout))
.glass-stage {
position: relative;
height: 100%;
min-height: 240px;
background: radial-gradient(circle at 50% 30%, #20303a, #0b0f14);
border-radius: 8px;
overflow: hidden;
}
.glass-stage .readout {
position: absolute;
top: 12px;
left: 12px;
margin: 0;
font-family: ui-monospace, monospace;
font-size: 13px;
color: #cfe;
white-space: pre;
pointer-events: none;
}