telemetry wizard

This commit is contained in:
e7d 2023-06-07 00:22:44 +02:00
parent 9f765b671f
commit 0fcb7c1afc
No known key found for this signature in database
GPG Key ID: F320BE007C0B8881
16 changed files with 790 additions and 352 deletions

View File

@ -99,6 +99,11 @@ body.unsupported #gamepad {
} }
#gamepad { #gamepad {
width: 100vw;
height: 100vh;
}
#gamepad .controller {
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: contain; background-size: contain;

View File

@ -759,7 +759,7 @@ class Gamepad {
script.src = `templates/${this.type}/template.js`; script.src = `templates/${this.type}/template.js`;
script.onload = () => { script.onload = () => {
// initialize the template // initialize the template
new this.template(); this.template = new this.templateClass();
// enqueue the initial display refresh // enqueue the initial display refresh
this.startTemplate(); this.startTemplate();
@ -1087,7 +1087,7 @@ class Gamepad {
// update the DOM with the zoom value // update the DOM with the zoom value
this.$gamepad.style.setProperty( this.$gamepad.style.setProperty(
'transform', 'transform',
`translate(-50%, -50%) scale(${this.zoomLevel}, ${this.zoomLevel})` `scale(${this.zoomLevel}, ${this.zoomLevel})`
); );
// update current settings // update current settings
@ -1127,7 +1127,7 @@ class Gamepad {
* *
* @param {boolean|undefined} debug * @param {boolean|undefined} debug
*/ */
toggleDebug(debug = null) { toggleDebug(debug) {
// ensure that a gamepad is currently active // ensure that a gamepad is currently active
if (this.index === null) return; if (this.index === null) return;

View File

@ -1,62 +1,64 @@
<div class="info"> <div class="controller">
<div class="container"> <div class="info">
<div id="info-name" class="box extra-large"> <div class="container">
<div class="content"> <div id="info-name" class="box extra-large">
<div class="label">Name</div> <div class="content">
<div class="value"></div> <div class="label">Name</div>
<div class="value"></div>
</div>
</div> </div>
</div> <div id="info-vendor" class="box small">
<div id="info-vendor" class="box small"> <div class="content">
<div class="content"> <div class="label">Vendor</div>
<div class="label">Vendor</div> <div class="value"></div>
<div class="value"></div> </div>
</div> </div>
</div> <div id="info-product" class="box small">
<div id="info-product" class="box small"> <div class="content">
<div class="content"> <div class="label">Product</div>
<div class="label">Product</div> <div class="value"></div>
<div class="value"></div> </div>
</div> </div>
</div> <div id="info-id" class="box medium">
<div id="info-id" class="box medium"> <div class="content">
<div class="content"> <div class="label">ID</div>
<div class="label">ID</div> <div class="value"></div>
<div class="value"></div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="info">
<div class="info"> <div class="container">
<div class="container"> <div id="info-timestamp" class="box medium">
<div id="info-timestamp" class="box medium"> <div class="content">
<div class="content"> <div class="label">Timestamp</div>
<div class="label">Timestamp</div> <div class="value"></div>
<div class="value"></div> </div>
</div> </div>
</div> <div id="info-index" class="box medium">
<div id="info-index" class="box medium"> <div class="content">
<div class="content"> <div class="label">Index</div>
<div class="label">Index</div> <div class="value"></div>
<div class="value"></div> </div>
</div> </div>
</div> <div id="info-mapping" class="box medium">
<div id="info-mapping" class="box medium"> <div class="content">
<div class="content"> <div class="label">Mapping</div>
<div class="label">Mapping</div> <div class="value"></div>
<div class="value"></div> </div>
</div> </div>
</div> <div id="info-rumble" class="box medium">
<div id="info-rumble" class="box medium"> <div class="content">
<div class="content"> <div class="label">Vibration</div>
<div class="label">Vibration</div> <div class="value"></div>
<div class="value"></div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="axes">
<div class="axes"> <div class="container"></div>
<div class="container"></div> </div>
</div> <div class="buttons">
<div class="buttons"> <div class="container"></div>
<div class="container"></div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
window.gamepad.template = class DebugTemplate { window.gamepad.templateClass = class DebugTemplate {
/** /**
* Instanciates a new debug template * Instanciates a new debug template
*/ */
@ -98,7 +98,7 @@ window.gamepad.template = class DebugTemplate {
* Updates the value of an element * Updates the value of an element
* *
* @param {Element} $elem * @param {Element} $elem
* @param {Number} precision * @param {number} precision
*/ */
updateElem($elem, value, precision = 2) { updateElem($elem, value, precision = 2) {
this.updateTimestamp(); this.updateTimestamp();

View File

@ -1,67 +1,67 @@
#gamepad { .controller {
width: 806px; width: 806px;
height: 598px; height: 598px;
} }
#gamepad[data-color="black"] { #gamepad[data-color="black"] .controller {
background-image: url(base-black.svg); background-image: url(base-black.svg);
} }
#gamepad[data-color="white"] { #gamepad[data-color="white"] .controller {
background-image: url(base-white.svg); background-image: url(base-white.svg);
} }
#gamepad[data-color="red"] { #gamepad[data-color="red"] .controller {
background-image: url(base-red.svg); background-image: url(base-red.svg);
} }
#gamepad[data-color="blue"] { #gamepad[data-color="blue"] .controller {
background-image: url(base-blue.svg); background-image: url(base-blue.svg);
} }
#gamepad.disconnected { #gamepad.disconnected .controller {
background-image: url(disconnected.svg); background-image: url(disconnected.svg);
} }
#gamepad.disconnected div { #gamepad.disconnected .controller div {
display: none; display: none;
} }
#gamepad .triggers { .controller .triggers {
width: 588px; width: 588px;
height: 90px; height: 90px;
position: absolute; position: absolute;
left: 109px; left: 109px;
} }
#gamepad .trigger { .controller .trigger {
width: 99px; width: 99px;
height: 100%; height: 100%;
background: url(triggers.svg); background: url(triggers.svg);
clip-path: inset(100% 0px 0px 0pc); clip-path: inset(100% 0px 0px 0pc);
} }
#gamepad .trigger[data-value="0"] { .controller .trigger[data-value="0"] {
opacity: 0; opacity: 0;
} }
#gamepad .trigger.left { .controller .trigger.left {
float: left; float: left;
} }
#gamepad .trigger.right { .controller .trigger.right {
float: right; float: right;
background-position-x: 99px; background-position-x: 99px;
} }
#gamepad .bumper { .controller .bumper {
width: 99px; width: 99px;
height: 23px; height: 23px;
background: url(bumper.svg) no-repeat; background: url(bumper.svg) no-repeat;
opacity: 0; opacity: 0;
} }
#gamepad .bumpers { .controller .bumpers {
position: absolute; position: absolute;
width: 588px; width: 588px;
height: 23px; height: 23px;
@ -69,22 +69,22 @@
top: 94px; top: 94px;
} }
#gamepad .bumper[data-pressed="true"] { .controller .bumper[data-pressed="true"] {
opacity: 1; opacity: 1;
} }
#gamepad .bumper.left { .controller .bumper.left {
/* -webkit-transform: rotateY(180deg); */ /* -webkit-transform: rotateY(180deg); */
/* transform: rotateY(180deg); */ /* transform: rotateY(180deg); */
float: left; float: left;
} }
#gamepad .bumper.right { .controller .bumper.right {
float: right; float: right;
transform: rotateY(180deg); transform: rotateY(180deg);
} }
#gamepad .touchpad { .controller .touchpad {
width: 262px; width: 262px;
height: 151px; height: 151px;
position: absolute; position: absolute;
@ -92,11 +92,11 @@
top: 122px; top: 122px;
} }
#gamepad .touchpad[data-pressed="true"] { .controller .touchpad[data-pressed="true"] {
background: url(touchpad.svg) no-repeat center; background: url(touchpad.svg) no-repeat center;
} }
#gamepad .meta { .controller .meta {
width: 42px; width: 42px;
height: 42px; height: 42px;
position: absolute; position: absolute;
@ -104,11 +104,11 @@
bottom: 216px; bottom: 216px;
} }
#gamepad .meta[data-pressed="true"] { .controller .meta[data-pressed="true"] {
background: url(meta.svg) no-repeat center; background: url(meta.svg) no-repeat center;
} }
#gamepad .arrows { .controller .arrows {
position: absolute; position: absolute;
width: 352px; width: 352px;
height: 46px; height: 46px;
@ -116,29 +116,29 @@
left: 227px; left: 227px;
} }
#gamepad .select, .controller .select,
#gamepad .start { .controller .start {
background: url(start.svg); background: url(start.svg);
width: 28px; width: 28px;
height: 46px; height: 46px;
opacity: 0; opacity: 0;
} }
#gamepad .select[data-pressed="true"], .controller .select[data-pressed="true"],
#gamepad .start[data-pressed="true"] { .controller .start[data-pressed="true"] {
opacity: 1; opacity: 1;
} }
#gamepad .select { .controller .select {
float: left; float: left;
} }
#gamepad .start { .controller .start {
float: right; float: right;
background-position: 28px 0; background-position: 28px 0;
} }
#gamepad .buttons { .controller .buttons {
position: absolute; position: absolute;
width: 170px; width: 170px;
height: 170px; height: 170px;
@ -146,40 +146,40 @@
left: 567px; left: 567px;
} }
#gamepad .button { .controller .button {
position: absolute; position: absolute;
width: 56px; width: 56px;
height: 56px; height: 56px;
background: url(buttons.svg); background: url(buttons.svg);
} }
#gamepad .button[data-pressed="true"] { .controller .button[data-pressed="true"] {
background-position-y: 56px; background-position-y: 56px;
} }
#gamepad .a { .controller .a {
background-position: 0 0; background-position: 0 0;
bottom: 0px; bottom: 0px;
left: 56px; left: 56px;
} }
#gamepad .b { .controller .b {
background-position: -56px 0; background-position: -56px 0;
top: 56px; top: 56px;
right: 0px; right: 0px;
} }
#gamepad .x { .controller .x {
background-position: 112px 0; background-position: 112px 0;
top: 56px; top: 56px;
} }
#gamepad .y { .controller .y {
background-position: 56px 0; background-position: 56px 0;
left: 56px; left: 56px;
} }
#gamepad .sticks { .controller .sticks {
position: absolute; position: absolute;
width: 361px; width: 361px;
height: 105px; height: 105px;
@ -187,32 +187,32 @@
left: 228px; left: 228px;
} }
#gamepad .stick { .controller .stick {
position: absolute; position: absolute;
background: url(sticks.svg); background: url(sticks.svg);
height: 94px; height: 94px;
width: 94px; width: 94px;
} }
#gamepad .stick[data-pressed="true"].left { .controller .stick[data-pressed="true"].left {
background-position-x: -96px; background-position-x: -96px;
} }
#gamepad .stick[data-pressed="true"].right { .controller .stick[data-pressed="true"].right {
background-position-x: -192px; background-position-x: -192px;
} }
#gamepad .stick.left { .controller .stick.left {
top: 0; top: 0;
left: 0; left: 0;
} }
#gamepad .stick.right { .controller .stick.right {
top: calc(100% - 105px); top: calc(100% - 105px);
left: calc(100% - 105px); left: calc(100% - 105px);
} }
#gamepad .dpad { .controller .dpad {
position: absolute; position: absolute;
width: 125px; width: 125px;
height: 126px; height: 126px;
@ -220,52 +220,52 @@
left: 92px; left: 92px;
} }
#gamepad .face { .controller .face {
background: url(dpad.svg); background: url(dpad.svg);
position: absolute; position: absolute;
} }
#gamepad .face.up, .controller .face.up,
#gamepad .face.down { .controller .face.down {
width: 36px; width: 36px;
height: 52px; height: 52px;
} }
#gamepad .face.left, .controller .face.left,
#gamepad .face.right { .controller .face.right {
width: 52px; width: 52px;
height: 36px; height: 36px;
} }
#gamepad .face.up { .controller .face.up {
left: 44px; left: 44px;
top: 0; top: 0;
background-position: -37px 0px; background-position: -37px 0px;
} }
#gamepad .face.down { .controller .face.down {
left: 44px; left: 44px;
bottom: 0; bottom: 0;
background-position: 0px 0; background-position: 0px 0;
} }
#gamepad .face.left { .controller .face.left {
top: 44px; top: 44px;
left: 0; left: 0;
background-position: 104px 0; background-position: 104px 0;
} }
#gamepad .face.right { .controller .face.right {
top: 44px; top: 44px;
right: 0px; right: 0px;
background-position: 52px 0; background-position: 52px 0;
} }
#gamepad .face[data-pressed="true"] { .controller .face[data-pressed="true"] {
/* margin-top: 5px; */ /* margin-top: 5px; */
background-position-y: 52px; background-position-y: 52px;
} }
#gamepad.half { .controller.half {
margin-top: -300px; margin-top: -300px;
} }

View File

@ -1,33 +1,35 @@
<div class="triggers"> <div class="controller">
<span class="trigger left" data-button="6"></span> <div class="triggers">
<span class="trigger right" data-button="7"></span> <span class="trigger left" data-button="6"></span>
<span class="clear"></span> <span class="trigger right" data-button="7"></span>
</div> <span class="clear"></span>
<div class="bumpers"> </div>
<span class="bumper left" data-button="4"></span> <div class="bumpers">
<span class="bumper right" data-button="5"></span> <span class="bumper left" data-button="4"></span>
<span class="clear"></span> <span class="bumper right" data-button="5"></span>
</div> <span class="clear"></span>
<div class="touchpad" data-button="17"></div> </div>
<div class="meta" data-button="16"></div> <div class="touchpad" data-button="17"></div>
<div class="arrows"> <div class="meta" data-button="16"></div>
<span class="select" data-button="8"></span> <div class="arrows">
<span class="start" data-button="9"></span> <span class="select" data-button="8"></span>
<span class="clear"></span> <span class="start" data-button="9"></span>
</div> <span class="clear"></span>
<div class="buttons"> </div>
<span class="button a" data-button="0"></span> <div class="buttons">
<span class="button b" data-button="1"></span> <span class="button a" data-button="0"></span>
<span class="button x" data-button="2"></span> <span class="button b" data-button="1"></span>
<span class="button y" data-button="3"></span> <span class="button x" data-button="2"></span>
</div> <span class="button y" data-button="3"></span>
<div class="sticks"> </div>
<span class="stick left" data-button="10" data-axis-x="0" data-axis-y="1"></span> <div class="sticks">
<span class="stick right" data-button="11" data-axis-x="2" data-axis-y="3"></span> <span class="stick left" data-button="10" data-axis-x="0" data-axis-y="1"></span>
</div> <span class="stick right" data-button="11" data-axis-x="2" data-axis-y="3"></span>
<div class="dpad"> </div>
<span class="face up" data-button="12"></span> <div class="dpad">
<span class="face down" data-button="13"></span> <span class="face up" data-button="12"></span>
<span class="face left" data-button="14"></span> <span class="face down" data-button="13"></span>
<span class="face right" data-button="15"></span> <span class="face left" data-button="14"></span>
<span class="face right" data-button="15"></span>
</div>
</div> </div>

View File

@ -1,4 +1,4 @@
window.gamepad.template = class DualShock4Template { window.gamepad.templateClass = class DualShock4Template {
/** /**
* Instanciates a new DualShock 4 controller template * Instanciates a new DualShock 4 controller template
*/ */

View File

@ -1,17 +1,17 @@
#gamepad { .controller {
height: 700px; height: 700px;
width: 1200px; width: 1200px;
} }
#gamepad[data-color="black"] { #gamepad[data-color="black"] .controller {
background-image: url(base-black.png); background-image: url(base-black.png);
} }
#gamepad[data-color="white"] { #gamepad[data-color="white"] .controller {
background-image: url(base-white.png); background-image: url(base-white.png);
} }
#gamepad.disconnected div { #gamepad.disconnected .controller {
display: none; display: none;
} }

View File

@ -1,33 +1,35 @@
<div class="triggers"> <div class="controller">
<span class="trigger left" data-button="6"></span> <div class="triggers">
<span class="trigger right" data-button="7"></span> <span class="trigger left" data-button="6"></span>
<span class="clear"></span> <span class="trigger right" data-button="7"></span>
</div> <span class="clear"></span>
<div class="bumpers"> </div>
<span class="bumper left" data-button="4"></span> <div class="bumpers">
<span class="bumper right" data-button="5"></span> <span class="bumper left" data-button="4"></span>
<span class="clear"></span> <span class="bumper right" data-button="5"></span>
</div> <span class="clear"></span>
<div class="touchpad" data-button="17"></div> </div>
<div class="meta" data-button="16"></div> <div class="touchpad" data-button="17"></div>
<div class="arrows"> <div class="meta" data-button="16"></div>
<span class="select" data-button="8"></span> <div class="arrows">
<span class="start" data-button="9"></span> <span class="select" data-button="8"></span>
<span class="clear"></span> <span class="start" data-button="9"></span>
</div> <span class="clear"></span>
<div class="buttons"> </div>
<span class="button a" data-button="0"></span> <div class="buttons">
<span class="button b" data-button="1"></span> <span class="button a" data-button="0"></span>
<span class="button x" data-button="2"></span> <span class="button b" data-button="1"></span>
<span class="button y" data-button="3"></span> <span class="button x" data-button="2"></span>
</div> <span class="button y" data-button="3"></span>
<div class="sticks"> </div>
<span class="stick left" data-button="10" data-axis-x="0" data-axis-y="1"></span> <div class="sticks">
<span class="stick right" data-button="11" data-axis-x="2" data-axis-y="3"></span> <span class="stick left" data-button="10" data-axis-x="0" data-axis-y="1"></span>
</div> <span class="stick right" data-button="11" data-axis-x="2" data-axis-y="3"></span>
<div class="dpad"> </div>
<span class="face up" data-button="12"></span> <div class="dpad">
<span class="face down" data-button="13"></span> <span class="face up" data-button="12"></span>
<span class="face left" data-button="14"></span> <span class="face down" data-button="13"></span>
<span class="face right" data-button="15"></span> <span class="face left" data-button="14"></span>
<span class="face right" data-button="15"></span>
</div>
</div> </div>

View File

@ -1,4 +1,4 @@
window.gamepad.template = class DualSenseTemplate { window.gamepad.templateClass = class DualSenseTemplate {
/** /**
* Instanciates a new DualSense controller template * Instanciates a new DualSense controller template
*/ */

View File

@ -1,12 +1,9 @@
#gamepad #telemetry { #gamepad #telemetry {
background: var(--main-bg-color); background: var(--main-bg-color);
display: flex; display: flex;
width: 650px;
height: 120px; height: 120px;
border-top-left-radius: 6px; padding: 4px;
border-bottom-left-radius: 6px; border-radius: 6px;
border-top-right-radius: 60px;
border-bottom-right-radius: 60px;
--black-color: black; --black-color: black;
--white-color: white; --white-color: white;
@ -17,13 +14,23 @@
--throttle-color: #0CA818; --throttle-color: #0CA818;
} }
#gamepad #telemetry.with-steering {
border-top-right-radius: 60px;
border-bottom-right-radius: 60px;
}
#gamepad #telemetry,
#gamepad #telemetry * { #gamepad #telemetry * {
box-sizing: border-box; box-sizing: border-box;
} }
#gamepad #telemetry>*+* {
margin-left: 2px;
}
#gamepad #telemetry #chart { #gamepad #telemetry #chart {
flex: 1; flex: 1;
margin: 4px; width: 400px;
background-color: var(--main-component-color); background-color: var(--main-component-color);
border: 1px solid var(--black-color); border: 1px solid var(--black-color);
border-radius: 4px; border-radius: 4px;
@ -32,17 +39,20 @@
#gamepad #telemetry #meters { #gamepad #telemetry #meters {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
width: 90px;
} }
#gamepad #telemetry #meters .meter { #gamepad #telemetry #meters .meter {
position: relative; position: relative;
flex: 1; flex: 1;
margin: 4px 2px;
background-color: var(--main-component-color); background-color: var(--main-component-color);
border: 1px solid var(--black-color); border: 1px solid var(--black-color);
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
width: 30px;
}
#gamepad #telemetry #meters .meter+.meter {
margin-left: 2px;
} }
#gamepad #telemetry #meters .meter .value { #gamepad #telemetry #meters .meter .value {
@ -62,28 +72,31 @@
height: 0%; height: 0%;
transition: height 100ms; transition: height 100ms;
} }
#gamepad #telemetry #meters #clutch.meter .bar { #gamepad #telemetry #meters #clutch.meter .bar {
background-color: var(--clutch-color); background-color: var(--clutch-color);
} }
#gamepad #telemetry #meters #brake.meter .bar { #gamepad #telemetry #meters #brake.meter .bar {
background-color: var(--brake-color); background-color: var(--brake-color);
} }
#gamepad #telemetry #meters #throttle.meter .bar { #gamepad #telemetry #meters #throttle.meter .bar {
background-color: var(--throttle-color); background-color: var(--throttle-color);
} }
#gamepad #telemetry #direction { #gamepad #telemetry #steering {
position: relative; position: relative;
display: flex; display: flex;
justify-content: center; justify-content: center;
width: 100px; width: 100px;
height: 100px; height: 100px;
margin: 10px; margin: 6px;
border-radius: 50px; border-radius: 50px;
background-color: var(--main-component-color); background-color: var(--main-component-color);
} }
#gamepad #telemetry #direction .center { #gamepad #telemetry #steering .center {
position: absolute; position: absolute;
top: 20%; top: 20%;
left: 20%; left: 20%;
@ -93,7 +106,7 @@
background-color: var(--main-bg-color); background-color: var(--main-bg-color);
} }
#gamepad #telemetry #direction .indicator { #gamepad #telemetry #steering .indicator {
display: block; display: block;
width: 5%; width: 5%;
height: 50%; height: 50%;
@ -101,3 +114,36 @@
transform-origin: bottom; transform-origin: bottom;
transition: transform 100ms; transition: transform 100ms;
} }
#wizard #wizard-instructions h4,
#wizard #wizard-instructions p {
text-align: center;
}
#wizard #wizard-instructions h4 {
margin-bottom: 0.5em;
}
#wizard .wizard-options {
display: flex;
margin: auto;
justify-content: center;
}
#wizard .wizard-options div {
margin: 0 8px;
line-height: 1.6em;
}
#wizard .wizard-options div label,
#wizard .wizard-options div input {
cursor: pointer;
}
#wizard .wizard-options input[name="history-length-option"] {
width: 35px;
}
#wizard .wizard-options input[name="steering-angle-option"] {
width: 50px;
}

View File

@ -1,5 +1,4 @@
<div id="fps"></div> <div id="telemetry" class="controller">
<div id="telemetry">
<div id="chart"></div> <div id="chart"></div>
<div id="meters"> <div id="meters">
<div id="clutch" class="meter"> <div id="clutch" class="meter">
@ -15,8 +14,14 @@
<div class="value">0</div> <div class="value">0</div>
</div> </div>
</div> </div>
<div id="direction"> <div id="steering">
<div class="indicator"></div> <div class="indicator"></div>
<div class="center"></div> <div class="center"></div>
</div> </div>
</div> </div>
<div id="wizard" class="popout">
<div class="content">
<h2>Telemetry Wizard</h2>
<div id="wizard-instructions"></div>
</div>
</div>

View File

@ -1,9 +1,19 @@
window.gamepad.template = class TelemetryTemplate { gamepad.templateClass = class TelemetryTemplate {
/** /**
* Instanciates a new telemetry template * Instanciates a new telemetry template
*/ */
constructor() { constructor() {
this.AXES = ['clutch', 'brake', 'throttle', 'steering'];
this.gamepad = window.gamepad; this.gamepad = window.gamepad;
this.loadSelectors();
this.loadUrlParams();
if (!this.AXES.some((axis) => this[axis].index)) {
this.wizard();
return;
}
this.init(); this.init();
} }
@ -17,28 +27,28 @@ window.gamepad.template = class TelemetryTemplate {
/** /**
* Converts a value to a percentage * Converts a value to a percentage
* *
* @param {Number} value * @param {number} value
* @param {Number} min * @param {number} min
* @param {Number} max * @param {number} max
* @returns {Number} * @returns {number}
*/ */
toPercentage(value, min, max) { toPercentage(value, min, max) {
return value !== undefined return value !== undefined
? Math.round((value - min) * (100 / (max - min))) ? Math.max(0, Math.min(100, Math.round((value - min) * (100 / (max - min)))))
: 0; : 0;
} }
/** /**
* Converts a value to degrees * Converts a value to degrees
* *
* @param {Number} value * @param {number} value
* @param {Number} min * @param {number} min
* @param {Number} max * @param {number} max
* @returns {Number} * @returns {number}
*/ */
toDegrees(value, min, max) { toDegrees(value, min, max) {
const percentage = this.toPercentage(value, min, max); const percentage = this.toPercentage(value, min, max);
return (this.directionDegrees) * (percentage - 50) / 100; return (this.angle) * (percentage - 50) / 100;
} }
/** /**
@ -46,85 +56,128 @@ window.gamepad.template = class TelemetryTemplate {
* *
* @param {object} gamepad * @param {object} gamepad
* @param {string} axis * @param {string} axis
* @returns {Number} * @returns {number}
*/ */
toAxisValue(gamepad, axis) { toAxisValue(gamepad, axis) {
const { [`${axis}Type`]: type, [`${axis}Index`]: index, [`${axis}Min`]: min, [`${axis}Max`]: max } = this[axis]; const { type, index, min, max } = this[axis];
if (!type || !index) return null;
const value = type === 'button' ? gamepad.buttons[index].value : gamepad.axes[index]; const value = type === 'button' ? gamepad.buttons[index].value : gamepad.axes[index];
return axis === 'direction' ? this.toDegrees(value, min, max) : this.toPercentage(value, min, max); return axis === 'steering' ? this.toDegrees(value, min, max) : this.toPercentage(value, min, max);
} }
/** /**
* Loads the axes * Loads the DOM selectors
*/ */
loadAxes() { loadSelectors() {
this.$telemetry = document.querySelector('#telemetry');
this.$chart = this.$telemetry.querySelector('#chart');
this.$meters = this.$telemetry.querySelector('#meters');
this.$clutch = this.$telemetry.querySelector('#clutch');
this.$clutchBar = this.$clutch.querySelector('.bar');
this.$clutchValue = this.$clutch.querySelector('.value');
this.$brake = this.$telemetry.querySelector('#brake');
this.$brakeBar = this.$brake.querySelector('.bar');
this.$brakeValue = this.$brake.querySelector('.value');
this.$throttle = this.$telemetry.querySelector('#throttle');
this.$throttleBar = this.$throttle.querySelector('.bar');
this.$throttleValue = this.$throttle.querySelector('.value');
this.$steering = this.$telemetry.querySelector('#steering');
this.$steeringIndicator = this.$steering.querySelector('.indicator');
this.$wizard = document.querySelector('#wizard');
this.$wizardInstructions = this.$wizard.querySelector('#wizard-instructions');
}
/**
* Loads the params from the URL
*/
loadUrlParams() {
this.withChart = gamepad.getUrlParam('chart') !== 'false';
this.historyLength = gamepad.getUrlParam('history') || 5000;
this.withMeters = gamepad.getUrlParam('meters') !== 'false';
this.withSteering = gamepad.getUrlParam('steeringIndex') !== null;
this.angle = gamepad.getUrlParam('angle') || 360;
this.frequency = gamepad.getUrlParam('fps') || 60;
this.AXES.forEach((axis) => { this.AXES.forEach((axis) => {
this[axis] = { this[axis] = {
[`${axis}Type`]: (gamepad.getUrlParam(`${axis}Type`) || 'axis').replace('axis', 'axe'), type: (gamepad.getUrlParam(`${axis}Type`) || 'axis').replace('axis', 'axe'),
[`${axis}Index`]: gamepad.getUrlParam(`${axis}Index`), index: gamepad.getUrlParam(`${axis}Index`),
[`${axis}Min`]: gamepad.getUrlParam(`${axis}Min`) || -1, min: gamepad.getUrlParam(`${axis}Min`) || -1,
[`${axis}Max`]: gamepad.getUrlParam(`${axis}Max`) || 1, max: gamepad.getUrlParam(`${axis}Max`) || 1,
} }
}); });
this.directionDegrees = gamepad.getUrlParam('directionDegrees') || 360;
} }
/** /**
* Initializes the template * Sets up the template
*/ */
init() { setupTemplate() {
this.$fps = document.querySelector('#fps'); if (!this.withChart) this.$chart.remove();
this.$clutchBar = document.querySelector('#clutch .bar');
this.$clutchValue = document.querySelector('#clutch .value'); if (!this.clutch.index) this.$clutch.remove();
this.$brakeBar = document.querySelector('#brake .bar'); if (!this.brake.index) this.$brake.remove();
this.$brakeValue = document.querySelector('#brake .value'); if (!this.throttle.index) this.$throttle.remove();
this.$throttleBar = document.querySelector('#throttle .bar'); if (!this.withMeters) this.$meters.remove();
this.$throttleValue = document.querySelector('#throttle .value');
this.$directionIndicator = document.querySelector('#direction .indicator'); if (!this.steering.index) {
this.AXES = ['clutch', 'brake', 'throttle', 'direction']; this.$steering.remove();
this.frequency = gamepad.getUrlParam('fps') || 60; } else {
this.historyLength = gamepad.getUrlParam('history') || 5000; this.$telemetry.classList.add('with-steering');
this.index = 0; }
this.interval = 1000 / this.frequency; }
this.length = this.historyLength / this.interval;
this.loadAxes(); async setupChart() {
this.initChart(); if (!this.withChart) return;
return new Promise((resolve) => {
const script = document.createElement('script');
script.async = true;
script.src = `https://www.gstatic.com/charts/loader.js`;
script.onload = () => {
if (!google || !google.visualization) {
this.loadGoogleCharts(resolve);
return;
}
this.drawChart(resolve);
};
this.gamepad.$gamepad.appendChild(script);
});
} }
/** /**
* Initializes the live chart * Initializes the live chart
*/ */
initChart() { async init() {
const script = document.createElement('script'); this.interval = 1000 / this.frequency;
script.async = true; this.length = this.historyLength / this.interval;
script.src = `https://www.gstatic.com/charts/loader.js`;
script.onload = () => { this.setupTemplate();
if (!google || !google.visualization) { await this.setupChart();
this.loadGoogleCharts();
return; this.running = true;
} this.update();
this.drawChart();
};
this.gamepad.$gamepad.appendChild(script);
} }
/** /**
* Loads the Google Charts library * Loads the Google Charts library
*/ */
loadGoogleCharts() { loadGoogleCharts(resolve) {
google.charts.load('current', { packages: ['corechart', 'line'], }); google.charts.load('current', { packages: ['corechart', 'line'], });
google.charts.setOnLoadCallback(this.drawChart.bind(this)); google.charts.setOnLoadCallback(this.drawChart.bind(this, resolve));
} }
/** /**
* Draws the live chart with the initial data * Draws the live chart with the initial data and starts the draw update loop
*/ */
drawChart() { drawChart(resolve) {
this.initialData = [['time', 'clutch', 'brake', 'throttle']]; const now = Date.now();
for (this.index = 0; this.index < this.length; this.index++) { const initialData = [['time', 'clutch', 'brake', 'throttle']];
this.initialData.push([this.index, 0, 0, 0]); for (let index = now - this.historyLength; index < now; index += this.interval) {
initialData.push([index, 0, 0, 0]);
} }
this.data = google.visualization.arrayToDataTable(this.initialData); this.data = google.visualization.arrayToDataTable(initialData);
this.options = { this.options = {
backgroundColor: 'transparent', backgroundColor: 'transparent',
chartArea: { chartArea: {
@ -138,6 +191,10 @@ window.gamepad.template = class TelemetryTemplate {
textPosition: 'none', textPosition: 'none',
gridlines: { gridlines: {
color: 'transparent', color: 'transparent',
},
viewWindow: {
min: now - this.historyLength,
max: now
} }
}, },
vAxis: { vAxis: {
@ -153,13 +210,14 @@ window.gamepad.template = class TelemetryTemplate {
} }
}, },
colors: ['#2D64B9', '#A52725', '#0CA818'], colors: ['#2D64B9', '#A52725', '#0CA818'],
legend: 'none' legend: 'none',
tooltip: {
trigger: 'none'
}
}; };
this.chart = new google.visualization.LineChart(document.querySelector('#chart')); this.chart = new google.visualization.LineChart(document.querySelector('#chart'));
this.chart.draw(this.data, this.options); this.chart.draw(this.data, this.options);
resolve();
this.running = true;
this.update();
} }
/** /**
@ -168,11 +226,11 @@ window.gamepad.template = class TelemetryTemplate {
update() { update() {
if (!this.running) return; if (!this.running) return;
const activeGamepad = this.gamepad.getActive(); const gamepad = this.gamepad.getActive();
if (!activeGamepad) return; const [clutch, brake, throttle, steering] = this.AXES.map((axis) => this.toAxisValue(gamepad, axis));
const [clutch, brake, throttle, direction] = this.AXES.map((axis) => this.toAxisValue(activeGamepad, axis)); if (this.withChart) this.updateChart(clutch, brake, throttle);
this.updateChart(clutch, brake, throttle); if (this.withMeters) this.updateMeters(clutch, brake, throttle);
this.updateMeters(clutch, brake, throttle, direction); if (this.withSteering) this.updateSteering(steering);
window.setTimeout(this.update.bind(this), this.interval); window.setTimeout(this.update.bind(this), this.interval);
} }
@ -180,34 +238,350 @@ window.gamepad.template = class TelemetryTemplate {
/** /**
* Updates the live chart with the latest data * Updates the live chart with the latest data
* *
* @param {Number} clutch * @param {number} clutch
* @param {Number} brake * @param {number} brake
* @param {Number} throttle * @param {number} throttle
*/ */
updateChart(clutch, brake, throttle) { updateChart(clutch, brake, throttle) {
this.data.removeRows(0, 1); const now = Date.now();
this.data.addRow([this.index, clutch, brake, throttle]); this.data.removeRows(0, this.data.getFilteredRows([{ column: 0, maxValue: now - this.historyLength }]).length);
this.data.addRow([now, clutch, brake, throttle]);
this.options.hAxis.viewWindow = {
min: now - this.historyLength,
max: now
};
this.chart.draw(this.data, this.options); this.chart.draw(this.data, this.options);
this.index++;
} }
/** /**
* Updates the meters with the latest data * Updates the meters with the latest data
* *
* @param {Number} clutch * @param {number} clutch
* @param {Number} brake * @param {number} brake
* @param {Number} throttle * @param {number} throttle
* @param {Number} direction * @param {number} steering
*/ */
updateMeters(clutch, brake, throttle, direction) { updateMeters(clutch, brake, throttle) {
Object.entries({ clutch, brake, throttle, direction }).forEach(([axis, value]) => { Object.entries({ clutch, brake, throttle }).forEach(([axis, value]) => {
if (axis === 'direction') { if (value === null) return;
this.$directionIndicator.style.transform = `rotate(${value}deg)`;
return;
}
this[`$${axis}Value`].innerHTML = value; this[`$${axis}Value`].innerHTML = value;
this[`$${axis}Value`].style.opacity = `${Math.round(33 + (value / 1.5))}%`; this[`$${axis}Value`].style.opacity = `${Math.round(33 + (value / 1.5))}%`;
this[`$${axis}Bar`].style.height = `${value}%`; this[`$${axis}Bar`].style.height = `${value}%`;
}); });
} }
/**
* Updates the steering indicator with the latest data
*
* @param {number} steering
*/
updateSteering(steering) {
this.$steeringIndicator.style.transform = `rotate(${steering}deg)`;
}
/**
* Waits for one or all buttons of a gamepad to be released
*
* @param {number} [index]
* @returns {Promise}
*/
async waitButtonRelease(index = undefined) {
return new Promise((resolve) => {
const interval = window.setInterval(() => {
const gamepad = this.gamepad.getActive();
const pressedButton = index !== undefined
? gamepad.buttons[index].pressed
: gamepad.buttons.some((button) => button.pressed);
if (pressedButton) return;
window.clearInterval(interval);
resolve();
}, 100);
});
}
/**
* Waits for a button to be pressed, then released
*
* @returns {Promise}
*/
async waitButtonClick() {
return new Promise((resolve) => {
const pressInterval = window.setInterval(() => {
const gamepad = this.gamepad.getActive();
const index = gamepad.buttons.findIndex((button) => button.pressed);
if (index === -1) return;
window.clearInterval(pressInterval);
const releaseInterval = window.setInterval(() => {
const gamepad = this.gamepad.getActive();
if (gamepad.buttons[index].pressed) return;
window.clearInterval(releaseInterval);
resolve(index);
}, 100);
}, 100);
});
}
/**
* Waits for an axis to be pushed out, and of the reference value, if any
*
* @param {number} index
* @param {number} [referenceValue]
* @param {number} [duration]
* @returns {Promise}
*/
async getAxisPush(index, referenceValue = undefined, duration = 1000) {
let start;
let value;
return new Promise((resolve) => {
const interval = window.setInterval(() => {
const gamepad = this.gamepad.getActive();
if (start === undefined) {
start = Date.now();
value = gamepad.axes[index];
return;
}
if (referenceValue !== undefined && Math.abs(gamepad.axes[index] - referenceValue) < 0.5) {
}
if (Math.abs(gamepad.axes[index] - value) > 0.05) {
start = undefined;
value = undefined;
return;
}
if (Date.now() - start < duration) {
value = gamepad.axes[index];
return;
}
window.clearInterval(interval);
resolve(value);
}, 100);
});
}
/**
* Capitalizes a word
*
* @param {string} word
* @returns {string}
*/
capitalize(word) {
return `${word.charAt(0).toUpperCase()}${word.slice(1)}`;
}
/**
* Asks the user for the telemetry options
*
* @returns {Promise}
*/
async askForOptions() {
this.$wizardInstructions.innerHTML = `
<h4>Inputs</h4>
<div class="wizard-options">
<div>
<input id="clutch-option" name="clutch-option" type="checkbox" checked>
<label for="clutch-option">Clutch</label>
</div>
<div>
<input id="brake-option" name="brake-option" type="checkbox" readonly checked>
<label for="brake-option">Brake</label>
</div>
<div>
<input id="throttle-option" name="throttle-option" type="checkbox" checked>
<label for="throttle-option">Throttle</label>
</div>
<div>
<input id="steering-option" name="steering-option" type="checkbox" checked>
<label for="steering-option">Steering</label>
</div>
</div>
<h4>Chart widget</h4>
<div class="wizard-options">
<div>
<input id="chart-option" name="chart-option" type="checkbox" checked>
<label for="chart-option">Enable</label>
</div>
<div>
<label for="history-option">History</label>
<input id="history-option" name="history-option" type="number" min="1" max="30" step="1" value="5">s
</div>
</div>
<h4>Meters widget</h4>
<div class="wizard-options">
<div>
<input id="meters-option" name="meters-option" type="checkbox" checked>
<label for="meters-option">Enable</label>
</div>
</div>
<h4>Steering widget</h4>
<div class="wizard-options">
<div>
<label for="steering-angle-option">Angle</label>
<input id="steering-angle-option" name="steering-angle-option" type="number" min="180" max="1080" step="10" value="360">°
</div>
</div>
<h4>Display mode</h4><div class="wizard-options">
<div>
<label for="fps-option">Mode</label>
<select id="fps-option" name="fps-option">
<option value="30">30 FPS (performance)</option>
<option value="60" selected>60 FPS (quality)</option>
</select>
</div>
</div>
<p>Then, press any button to continue.</p>
`;
await this.waitButtonRelease();
await this.waitButtonClick();
return {
...this.AXES.reduce((options, axis) => {
options[`with${this.capitalize(axis)}`] = document.querySelector(`[name=${axis}-option]`).checked;
return options;
}, {}),
chart: document.querySelector('[name=chart-option]').checked,
history: parseInt(document.querySelector('[name=history-option]').value) * 1000,
meters: document.querySelector('[name=meters-option]').checked,
angle: this.$wizardInstructions.querySelector('input[name="steering-angle-option"]').value,
fps: parseInt(document.querySelector('[name=fps-option]').value)
};
}
/**
* Detects activity on any button or axis of the gamepad
*
* @param {string} [type='button'|'axis'|undefined]
* @param {number} [distance=0.5]
* @returns {Promise}
*/
async detectActivity(type = undefined, distance = 0.5) {
const before = this.gamepad.getActive();
return new Promise((resolve) => {
const interval = window.setInterval(async () => {
const gamepad = this.gamepad.getActive();
const buttonIndex = ['button', undefined].includes(type)
? gamepad.buttons.findIndex((button, index) => button.pressed && Math.abs(button.value - before.buttons[index].value) > distance)
: -1;
const axisIndex = ['axis', undefined].includes(type)
? gamepad.axes.findIndex((axis, index) => Math.abs(axis - before.axes[index]) > distance)
: -1;
if (buttonIndex === -1 && axisIndex === -1) return;
window.clearInterval(interval);
resolve({
type: buttonIndex === -1 ? 'axis' : 'button',
index: buttonIndex === -1 ? axisIndex : buttonIndex,
});
}, 100);
});
}
/**
* Calibrates a pedal
*
* @param {string} name
* @returns {Promise}
*/
async calibratePedal(name) {
this.$wizardInstructions.innerHTML = `
<p>Waiting for <strong>${name}</strong> activity.</p>
`;
const { type, index } = await this.detectActivity();
if (type === 'button') {
this.$wizardInstructions.innerHTML = `
<p>Release the <strong>${name}</strong> button.</p>
`;
await this.waitButtonRelease(index);
return { type, index, releasedValue: 0, pressedValue: 1 };
}
this.$wizardInstructions.innerHTML = `
<p>Press and hold the <strong>${name}</strong> axis.</p>
`;
const pressedValue = await this.getAxisPush(index);
this.$wizardInstructions.innerHTML = `
<p>Release the <strong>${name}</strong> axis.</p>
`;
const releasedValue = await this.getAxisPush(index, pressedValue);
return { type, index, releasedValue, pressedValue };
}
/**
* Calibrates the steering axis
*
* @returns {Promise}
*/
async calibrateSteering() {
this.$wizardInstructions.innerHTML = `
<p>Turn the <strong>steering</strong> axis all the way to the <strong>left</strong>.</p>
`;
const { index } = await this.detectActivity('axis', 0.2);
const leftValue = await this.getAxisPush(index, 0);
this.$wizardInstructions.innerHTML = `
<p>Turn the <strong>steering</strong> axis all the way to the <strong>right</strong>.</p>
`;
const rightValue = await this.getAxisPush(index, leftValue);
return { index, leftValue, rightValue };
}
/**
* Converts an object to a query string, ignoring empty values
*
* @param {Object} options
* @returns {string}
*/
toOptionsParams(options) {
return Object.entries(options)
.filter(([, value]) => value !== undefined && value !== null)
.map(([key, value]) => `${key}=${value}`)
.join('&');
}
/**
* Generates the query string for a pedal
*
* @param {string} name
* @param {Object} data
* @returns {string}
*/
toPedalParams(name, { type, index, releasedValue, pressedValue }) {
return `${name}Type=${type}&${name}Index=${index}&${name}Min=${releasedValue}&${name}Max=${pressedValue}`;
}
/**
* Generates the query string for the steering
*
* @param {Object} data
* @returns {string}
*/
toSteeringParams({ index, leftValue, rightValue }) {
return `steeringIndex=${index}&steeringMin=${leftValue}&steeringMax=${rightValue}`;
}
/**
* Starts the wizard
*
* @returns {Promise}
*/
async wizard() {
this.$wizard.classList.add('active');
const gamepad = this.gamepad.getActive();
const { withClutch, withBrake, withThrottle, withSteering, chart, history, meters, angle, fps } = await this.askForOptions();
const clutch = withClutch && await this.calibratePedal('clutch');
const brake = withBrake && await this.calibratePedal('brake');
const throttle = withThrottle && await this.calibratePedal('throttle');
const steering = withSteering && await this.calibrateSteering();
window.location.href = [
`?gamepad=${gamepad.id}&type=telemetry`,
this.toOptionsParams({ chart, history, meters, angle, fps }),
withClutch ? this.toPedalParams('clutch', clutch) : null,
withBrake ? this.toPedalParams('brake', brake) : null,
withThrottle ? this.toPedalParams('throttle', throttle) : null,
withSteering ? this.toSteeringParams(steering) : null
].filter(e => e !== null).join('&');
}
}; };

View File

@ -1,17 +1,17 @@
#gamepad { .controller {
height: 630px; height: 630px;
width: 750px; width: 750px;
} }
#gamepad[data-color="black"] { #gamepad[data-color="black"] .controller {
background-image: url(base-black.svg); background-image: url(base-black.svg);
} }
#gamepad[data-color="white"] { #gamepad[data-color="white"] .controller {
background-image: url(base-white.svg); background-image: url(base-white.svg);
} }
#gamepad.disconnected { #gamepad.disconnected .controller {
background-image: url(disconnected.svg); background-image: url(disconnected.svg);
} }
@ -19,42 +19,42 @@
display: none; display: none;
} }
#gamepad .triggers { .controller .triggers {
width: 448px; width: 448px;
height: 122px; height: 122px;
position: absolute; position: absolute;
left: 151px; left: 151px;
} }
#gamepad .trigger { .controller .trigger {
width: 89px; width: 89px;
height: 122px; height: 122px;
background: url(trigger.svg); background: url(trigger.svg);
clip-path: inset(100% 0px 0px 0pc); clip-path: inset(100% 0px 0px 0pc);
} }
#gamepad .trigger[data-value="0"] { .controller .trigger[data-value="0"] {
opacity: 0; opacity: 0;
} }
#gamepad .trigger.left { .controller .trigger.left {
float: left; float: left;
background-position: 0 0; background-position: 0 0;
} }
#gamepad .trigger.right { .controller .trigger.right {
float: right; float: right;
transform: rotateY(180deg); transform: rotateY(180deg);
} }
#gamepad .bumper { .controller .bumper {
width: 170px; width: 170px;
height: 61px; height: 61px;
background: url(bumper.svg); background: url(bumper.svg);
opacity: 0; opacity: 0;
} }
#gamepad .bumpers { .controller .bumpers {
position: absolute; position: absolute;
width: 536px; width: 536px;
height: 61px; height: 61px;
@ -62,41 +62,41 @@
top: 129px; top: 129px;
} }
#gamepad .bumper[data-pressed="true"] { .controller .bumper[data-pressed="true"] {
opacity: 1; opacity: 1;
} }
#gamepad .bumper.left { .controller .bumper.left {
float: left; float: left;
} }
#gamepad .bumper.right { .controller .bumper.right {
float: right; float: right;
-webkit-transform: rotateY(180deg); -webkit-transform: rotateY(180deg);
transform: rotateY(180deg); transform: rotateY(180deg);
} }
#gamepad .p0 { .controller .p0 {
-webkit-transform: rotate(0deg); -webkit-transform: rotate(0deg);
transform: rotate(0deg); transform: rotate(0deg);
} }
#gamepad .p1 { .controller .p1 {
-webkit-transform: rotate(90deg); -webkit-transform: rotate(90deg);
transform: rotate(90deg); transform: rotate(90deg);
} }
#gamepad .p2 { .controller .p2 {
-webkit-transform: rotate(270deg); -webkit-transform: rotate(270deg);
transform: rotate(270deg); transform: rotate(270deg);
} }
#gamepad .p3 { .controller .p3 {
-webkit-transform: rotate(180deg); -webkit-transform: rotate(180deg);
transform: rotate(180deg); transform: rotate(180deg);
} }
#gamepad .arrows { .controller .arrows {
position: absolute; position: absolute;
width: 141px; width: 141px;
height: 33px; height: 33px;
@ -104,29 +104,29 @@
left: 306px; left: 306px;
} }
#gamepad .select, .controller .select,
#gamepad .start { .controller .start {
background: url(start-select.svg); background: url(start-select.svg);
width: 33px; width: 33px;
height: 33px; height: 33px;
opacity: 0; opacity: 0;
} }
#gamepad .select[data-pressed="true"], .controller .select[data-pressed="true"],
#gamepad .start[data-pressed="true"] { .controller .start[data-pressed="true"] {
opacity: 1; opacity: 1;
} }
#gamepad .select { .controller .select {
float: left; float: left;
} }
#gamepad .start { .controller .start {
background-position: 33px 0px; background-position: 33px 0px;
float: right; float: right;
} }
#gamepad .buttons { .controller .buttons {
position: absolute; position: absolute;
width: 155px; width: 155px;
height: 156px; height: 156px;
@ -134,43 +134,43 @@
left: 489px; left: 489px;
} }
#gamepad .button { .controller .button {
position: absolute; position: absolute;
background: url(buttons.svg); background: url(buttons.svg);
width: 53px; width: 53px;
height: 53px; height: 53px;
} }
#gamepad .button[data-pressed="true"] { .controller .button[data-pressed="true"] {
background-position-y: -53px; background-position-y: -53px;
opacity: 1; opacity: 1;
} }
#gamepad .a { .controller .a {
background-position: 0 0; background-position: 0 0;
top: 102px; top: 102px;
left: 51px; left: 51px;
} }
#gamepad .b { .controller .b {
background-position: -53px 0; background-position: -53px 0;
top: 52px; top: 52px;
right: 1px; right: 1px;
} }
#gamepad .x { .controller .x {
background-position: -106px 0; background-position: -106px 0;
top: 52px; top: 52px;
left: 1px; left: 1px;
} }
#gamepad .y { .controller .y {
background-position: -159px 0; background-position: -159px 0;
top: 1px; top: 1px;
left: 51px; left: 51px;
} }
#gamepad .sticks { .controller .sticks {
position: absolute; position: absolute;
width: 371px; width: 371px;
height: 196px; height: 196px;
@ -178,7 +178,7 @@
left: 144px; left: 144px;
} }
#gamepad .stick { .controller .stick {
position: absolute; position: absolute;
background: url(stick.svg); background: url(stick.svg);
background-position: -85px 0; background-position: -85px 0;
@ -186,21 +186,21 @@
width: 83px; width: 83px;
} }
#gamepad .stick[data-pressed="true"] { .controller .stick[data-pressed="true"] {
background-position: 0 0; background-position: 0 0;
} }
#gamepad .stick.left { .controller .stick.left {
top: 0; top: 0;
left: 0; left: 0;
} }
#gamepad .stick.right { .controller .stick.right {
top: 113px; top: 113px;
left: 288px; left: 288px;
} }
#gamepad .dpad { .controller .dpad {
position: absolute; position: absolute;
width: 110px; width: 110px;
height: 111px; height: 111px;
@ -208,17 +208,17 @@
left: 223px; left: 223px;
} }
#gamepad .face { .controller .face {
background: url(dpad.svg); background: url(dpad.svg);
position: absolute; position: absolute;
opacity: 0; opacity: 0;
} }
#gamepad .face[data-pressed="true"] { .controller .face[data-pressed="true"] {
opacity: 1; opacity: 1;
} }
#gamepad .face.up { .controller .face.up {
background-position: 35px 0; background-position: 35px 0;
left: 38px; left: 38px;
top: 1px; top: 1px;
@ -226,14 +226,14 @@
height: 56px; height: 56px;
} }
#gamepad .face.down { .controller .face.down {
left: 38px; left: 38px;
bottom: 0; bottom: 0;
width: 34px; width: 34px;
height: 56px; height: 56px;
} }
#gamepad .face.left { .controller .face.left {
background-position: 0 -93px; background-position: 0 -93px;
width: 56px; width: 56px;
height: 34px; height: 34px;
@ -241,7 +241,7 @@
left: 0; left: 0;
} }
#gamepad .face.right { .controller .face.right {
background-position: 0 -57px; background-position: 0 -57px;
width: 56px; width: 56px;
height: 34px; height: 34px;

View File

@ -1,32 +1,34 @@
<div class="triggers"> <div class="controller">
<span class="trigger left" data-button="6"></span> <div class="triggers">
<span class="trigger right" data-button="7"></span> <span class="trigger left" data-button="6"></span>
<span class="clear"></span> <span class="trigger right" data-button="7"></span>
</div> <span class="clear"></span>
<div class="bumpers"> </div>
<span class="bumper left" data-button="4"></span> <div class="bumpers">
<span class="bumper right" data-button="5"></span> <span class="bumper left" data-button="4"></span>
<span class="clear"></span> <span class="bumper right" data-button="5"></span>
</div> <span class="clear"></span>
<div class="meta" data-button="16"></div> </div>
<div class="arrows"> <div class="meta" data-button="16"></div>
<span class="select" data-button="8"></span> <div class="arrows">
<span class="start" data-button="9"></span> <span class="select" data-button="8"></span>
<span class="clear"></span> <span class="start" data-button="9"></span>
</div> <span class="clear"></span>
<div class="buttons"> </div>
<span class="button a" data-button="0"></span> <div class="buttons">
<span class="button b" data-button="1"></span> <span class="button a" data-button="0"></span>
<span class="button x" data-button="2"></span> <span class="button b" data-button="1"></span>
<span class="button y" data-button="3"></span> <span class="button x" data-button="2"></span>
</div> <span class="button y" data-button="3"></span>
<div class="sticks"> </div>
<span class="stick left" data-button="10" data-axis-x="0" data-axis-y="1"></span> <div class="sticks">
<span class="stick right" data-button="11" data-axis-x="2" data-axis-y="3"></span> <span class="stick left" data-button="10" data-axis-x="0" data-axis-y="1"></span>
</div> <span class="stick right" data-button="11" data-axis-x="2" data-axis-y="3"></span>
<div class="dpad"> </div>
<span class="face up" data-button="12"></span> <div class="dpad">
<span class="face down" data-button="13"></span> <span class="face up" data-button="12"></span>
<span class="face left" data-button="14"></span> <span class="face down" data-button="13"></span>
<span class="face right" data-button="15"></span> <span class="face left" data-button="14"></span>
<span class="face right" data-button="15"></span>
</div>
</div> </div>

View File

@ -1,4 +1,4 @@
window.gamepad.template = class XboxOneTemplate { window.gamepad.templateClass = class XboxOneTemplate {
/** /**
* Instanciates a new Xbox One controller template * Instanciates a new Xbox One controller template
*/ */