Skip to content

Instantly share code, notes, and snippets.

@Dinir
Last active November 25, 2018 21:21
Show Gist options
  • Save Dinir/a13650869b2d8f697399ddc872c35d10 to your computer and use it in GitHub Desktop.
Save Dinir/a13650869b2d8f697399ddc872c35d10 to your computer and use it in GitHub Desktop.
Simple 4-button Gamepad Input Display, made as a makeshift tool, so maybe only works with the first pad connected. 😟
/* eslint-disable no-undef */
window.onload = function () {
const axisDom = document.getElementsByClassName('analogLeft')[0]
const dpadDoms = document.getElementsByClassName('dpad')[0]
const buttonDoms = document.getElementsByClassName('buttons')[0]
const cos45 = Math.cos(1 / 4 * Math.PI)
let mapping = {
analogLeft: [0, 1], // hori, vert
dpad: [12, 13, 14, 15], // up, down, left, right
buttons: [2, 0, 3, 1, 9] // four buttons and start
}
let useAnalog = false
let mappingNow = false
const findPressedInput = (() => {
let previouslyPressed = []
const isAlreadyPressed = newPress => {
return previouslyPressed[0] === newPress[0] &&
previouslyPressed[1] === newPress[1]
}
return (gamepad) => {
/*
in linux some shoulder triggers stay at -1.0 when not touched,
and it triggers the logic as being pressed.
Since this input display is meant to be used as a joystick-like
layout, and joysticks and joypads I used often has left analog stick
connected to first two axes, I decided to use only the axes.
*/
const axes = gamepad.axes.slice(0, 2)
const buttons = gamepad.buttons.map(v => v.value)
let pressed = []
axes.some((v, i) => {
if (Math.abs(v) > 0.5) {
pressed = pressed.concat(['a', i])
return true
}
})
if (!pressed.length) {
buttons.some((v, i) => {
if (Math.abs(v) === 1) {
pressed = pressed.concat(['b', i])
return true
}
})
}
if (pressed.length) {
if (isAlreadyPressed(pressed)) {
return false
} else {
previouslyPressed = pressed
console.log(pressed)
return pressed
}
} else {
if (previouslyPressed.length) {
previouslyPressed = []
}
return false
}
}
})()
const isRightClick = e => {
e = e || window.event
if ('which' in e) {
return e.which === 3
} else if ('button' in e) {
return e.button === 2
}
}
const countNulls = array => {
let count = 0
for (let i = 0; i < array.length; i++) {
count += array[i] === null
}
return count
}
const addClass = (dom, className) => {
const oldClassName = dom.getAttribute('class') || ''
if (oldClassName.indexOf(className) === -1) {
dom.setAttribute(
'class',
`${oldClassName} ${className}`
)
}
}
const removeClass = (dom, className) => {
dom.setAttribute(
'class',
dom.getAttribute('class').replace(` ${className}`, '')
)
}
rAF(() => handler.refresh(gamepads))
const gpDomSet = (() => {
const analogLeftJog = axisDom.children[0]
const buttons = buttonDoms.children
const setters = {
analogLeft: (...values) => {
analogLeftJog.style.left = (0.75 + (0.25 * values[0])) + 'em'
analogLeftJog.style.top = (0.75 + (0.25 * values[1])) + 'em'
return setters
},
dpad: (...values) => {
const valuesNumber = values.map(v => v.value)
const vert = (-1 * valuesNumber[0] + valuesNumber[1]) *
Math.min(1, (valuesNumber[2] + valuesNumber[3] === 0) + cos45)
const hori = (-1 * valuesNumber[2] + valuesNumber[3]) *
Math.min(1, (valuesNumber[0] + valuesNumber[1] === 0) + cos45)
analogLeftJog.style.top = (0.75 + (0.25 * vert)) + 'em'
analogLeftJog.style.left = (0.75 + (0.25 * hori)) + 'em'
return setters
},
buttons: (...values) => {
values.forEach((v, i) => {
if (buttons[i]) {
buttons[i].className = values[i].pressed ? 'pressed' : ''
}
})
return setters
}
}
return setters
})()
handler.update = () => {
if (mappingNow) {
let pressedInput = findPressedInput(gamepads[0])
if (mappingNow === 'axis') {
const analogLeftJog = axisDom.children[0]
const buttonAmount = mapping.analogLeft.length
const buttonToMap = buttonAmount - countNulls(mapping.analogLeft)
switch (buttonToMap) {
case 0:
addClass(analogLeftJog, 'mapping')
analogLeftJog.innerText = '↔'
break
case 1:
analogLeftJog.innerText = '↕'
break
default:
removeClass(analogLeftJog, 'mapping')
analogLeftJog.innerText = ''
mappingNow = false
}
if (mappingNow && pressedInput) {
mapping.analogLeft[buttonToMap] = pressedInput[1]
}
}
if (mappingNow === 'dpad') {
const buttonAmount = mapping.dpad.length
const buttonToMap = buttonAmount - countNulls(mapping.dpad)
if (buttonToMap !== buttonAmount) {
const currentButtonDom = dpadDoms.children[buttonToMap]
if (buttonToMap !== 0) {
removeClass(currentButtonDom.previousElementSibling, 'mapping')
}
addClass(currentButtonDom, 'mapping')
} else {
removeClass(dpadDoms.children[buttonToMap - 1], 'mapping')
mappingNow = false
}
if (mappingNow && pressedInput) {
mapping.dpad[buttonToMap] = pressedInput[1]
}
}
if (mappingNow === 'button') {
const buttonAmount = mapping.buttons.length
const buttonToMap = buttonAmount - countNulls(mapping.buttons)
if (buttonToMap !== buttonAmount) {
const currentButtonDom = buttonDoms.children[buttonToMap]
if (buttonToMap !== 0) {
removeClass(currentButtonDom.previousElementSibling, 'mapping')
}
addClass(currentButtonDom, 'mapping')
} else {
removeClass(buttonDoms.children[buttonToMap - 1], 'mapping')
mappingNow = false
}
if (mappingNow && pressedInput) {
mapping.buttons[buttonToMap] = pressedInput[1]
}
}
if (!mappingNow) {
const domBeingMapped = document.getElementsByClassName('mapping')
for (let i = 0; i < domBeingMapped.length; i++) {
removeClass(domBeingMapped[i], 'mapping')
}
}
} else {
if (useAnalog) {
gpDomSet.analogLeft(
gamepads[0].axes[mapping.analogLeft[0]],
gamepads[0].axes[mapping.analogLeft[1]]
)
} else {
gpDomSet.dpad(
gamepads[0].buttons[mapping.dpad[0]],
gamepads[0].buttons[mapping.dpad[1]],
gamepads[0].buttons[mapping.dpad[2]],
gamepads[0].buttons[mapping.dpad[3]]
)
}
gpDomSet.buttons(
gamepads[0].buttons[mapping.buttons[0]],
gamepads[0].buttons[mapping.buttons[1]],
gamepads[0].buttons[mapping.buttons[2]],
gamepads[0].buttons[mapping.buttons[3]],
gamepads[0].buttons[mapping.buttons[4]]
)
}
}
axisDom.addEventListener('mousedown', function (e) {
if (isRightClick(e)) {
useAnalog = !useAnalog
} else if (!mappingNow) {
/*
axis dom lies after dpad doms,
and I want the user to be able to remap either analog or dpad
by clicking representing area.
if you click the arrows the className will be 'analogLeft',
if you click the circle it will be empty.
*/
if (e.target.className === 'analogLeft') {
mappingNow = 'dpad'
mapping.dpad = [null, null, null, null]
} else {
mappingNow = 'axis'
mapping.analogLeft = [null, null]
}
}
})
buttonDoms.addEventListener('click', function (e) {
if (!mappingNow) {
mappingNow = 'button'
mapping.buttons = [null, null, null, null, null]
}
})
const popupButton = document.getElementById('openPopup')
if (window.name === 'popup') {
popupButton.style.display = 'none'
}
popupButton.addEventListener('click', function () {
window.open(
window.document.URL, 'popup',
`width=${16 * 7}px,height=${16 * 3}px,` +
`top=${(screen.availHeight - 39) / 2},` +
`left=${(screen.availWidth - 91) / 2}`
)
})
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Simple 4-Button Gamepad Input Display</title>
<!-- grabGamepadInfo.js: https://gist.github.com/Dinir/75bf0d028cf30f27e131364dc4905cd4 -->
<script src="grabGamepadInfo.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="gamepad">
<div class="dpad">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
<div class="analogLeft">
<span></span>
</div>
<div class="buttons">
<span>γ…‡</span>
<span>γ…‡</span>
<span>γ…‡</span>
<span>γ…‡</span>
<span>γ…‡</span>
</div>
</div>
<div id="openPopup">
<span>NEW WINDOW</span>
</div>
<script src="logic.js"></script>
</body>
</html>
body {
margin: 0;
padding: 0;
background-color: transparent;
font-family: "Fira Code Retina", monospace;
font-size: 16px; }
span {
color: white;
display: block;
position: absolute;
width: 1em;
height: 1em;
text-align: center; }
div#gamepad {
width: 7em;
height: 3em; }
div.analogLeft,
div.dpad,
div.buttons {
height: 3em;
position: absolute; }
div.analogLeft,
div.dpad {
width: 3em;
top: 0;
left: 0; }
div.buttons {
width: 4em; }
div.analogLeft span {
background-color: white;
border-radius: 100%;
top: .75em;
left: .75em;
width: 1.5em;
height: 1.5em; }
div.analogLeft.pressed span {
background-color: red; }
div.dpad span:nth-child(1) {
top: -.25em;
left: 1em; }
div.dpad span:nth-child(2) {
top: 2.25em;
left: 1em; }
div.dpad span:nth-child(3) {
top: 1em;
left: -.25em; }
div.dpad span:nth-child(4) {
top: 1em;
left: 2.25em; }
div.buttons {
top: 0;
left: 3em; }
div.buttons span:nth-child(1),
div.buttons span:nth-child(2) {
bottom: .5em; }
div.buttons span:nth-child(3),
div.buttons span:nth-child(4) {
top: .5em; }
div.buttons span:nth-child(1) {
left: 0.25em; }
div.buttons span:nth-child(2) {
left: 1.25em; }
div.buttons span:nth-child(3) {
left: 0.75em; }
div.buttons span:nth-child(4) {
left: 1.75em; }
div.buttons span:nth-child(5) {
top: 1em;
left: 3em; }
div.dpad span {
color: grey;
border: 0.5em solid transparent;
width: 0;
height: 0; }
div.dpad span:nth-child(1) {
border-bottom: 0.5em solid grey; }
div.dpad span:nth-child(2) {
border-top: 0.5em solid grey; }
div.dpad span:nth-child(3) {
border-right: 0.5em solid grey; }
div.dpad span:nth-child(4) {
border-left: 0.5em solid grey; }
div.buttons span {
border-radius: 100%; }
div.buttons span.pressed {
color: orange;
background-color: red; }
div.dpad span.mapping:nth-child(1) {
border-bottom-color: yellow; }
div.dpad span.mapping:nth-child(2) {
border-top-color: yellow; }
div.dpad span.mapping:nth-child(3) {
border-right-color: yellow; }
div.dpad span.mapping:nth-child(4) {
border-left-color: yellow; }
div.analogLeft span.mapping,
div.buttons span.mapping {
background-color: yellow;
color: grey; }
div#openPopup {
cursor: pointer;
position: absolute;
top: 1em;
left: 8em;
width: 6em; }
div#openPopup span {
width: initial;
height: initial;
background-color: darkgrey;
color: initial; }
body {
margin: 0;
padding: 0;
background-color: transparent;
font-family: "Fira Code Retina", monospace;
font-size: 16px;
}
span {
color: white;
display: block;
position: absolute;
width: 1em;
height: 1em;
text-align: center;
}
// position
div#gamepad {
width: 7em;
height: 3em;
}
div.analogLeft,
div.dpad,
div.buttons
{
height: 3em;
position: absolute;
}
div.analogLeft,
div.dpad {
width: 3em;
top: 0;
left: 0;
}
div.buttons {
width: 4em;
}
div.analogLeft {
span {
background-color: white;
border-radius: 100%;
top: .75em;
left: .75em;
width: 1.5em;
height: 1.5em;
}
&.pressed span {
background-color: red;
}
}
div.dpad span {
&:nth-child(1) {
top: -.25em;
left: 1em;
}
&:nth-child(2) {
top: 2.25em;
left: 1em;
}
&:nth-child(3) {
top: 1em;
left: -.25em;
}
&:nth-child(4) {
top: 1em;
left: 2.25em;
}
}
div.buttons {
top: 0;
left: 3em;
span:nth-child(1),
span:nth-child(2) {
bottom: .5em;
}
span:nth-child(3),
span:nth-child(4) {
top: .5em;
}
span:nth-child(1) { left: 0.25em; }
span:nth-child(2) { left: 1.25em; }
span:nth-child(3) { left: 0.75em; }
span:nth-child(4) { left: 1.75em; }
span:nth-child(5) { top: 1em; left: 3em; }
}
// decoration
div.dpad span {
$dpad-border-area: .5em solid;
color: grey;
border: $dpad-border-area transparent;
width: 0;
height: 0;
&:nth-child(1) {
border-bottom: $dpad-border-area grey;
}
&:nth-child(2) {
border-top: $dpad-border-area grey;
}
&:nth-child(3) {
border-right: $dpad-border-area grey;
}
&:nth-child(4) {
border-left: $dpad-border-area grey;
}
}
div.buttons span {
border-radius: 100%;
&.pressed {
color: orange;
background-color: red;
}
}
div.dpad span.mapping {
&:nth-child(1) { border-bottom-color: yellow; }
&:nth-child(2) { border-top-color: yellow; }
&:nth-child(3) { border-right-color: yellow; }
&:nth-child(4) { border-left-color: yellow; }
}
div.analogLeft span.mapping,
div.buttons span.mapping {
background-color: yellow;
color: grey;
}
// popup button style
div#openPopup {
cursor: pointer;
position: absolute;
top: 1em;
left: 8em;
width: 6em;
span {
width: initial;
height: initial;
background-color: darkgrey;
color: initial;
}
}
@Dinir
Copy link
Author

Dinir commented Aug 7, 2017

peek 2017-08-07 21-41

@Dinir
Copy link
Author

Dinir commented Jul 9, 2018

Now it supports custom mapping.
Click either dpad arrows, analog stick, buttons to set new buttons for them to react for.
Right click the dpad/analog stick area to change which one to react.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment