-
-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathindex.html
605 lines (573 loc) · 21.7 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
<!DOCTYPE html>
<html lang="en">
<head>
<meta content="text/html" http-equiv="content-type">
<meta charset="utf-8">
<title>Mini Padder - Game Controller Input Overlay</title>
<link rel="icon" type="image/x-icon" href="./favicon.ico">
<meta name="theme-color" content="#65849f">
<meta property="og:title" content="Mini Padder - Game Controller Input Overlay">
<meta name="author" content="Dinir Nertan">
<meta name="description" content="A simple and clear input overlay for your gamepads and joysticks. Show your moves in your stream and videos.">
<meta property="og:description" content="A simple and clear input overlay for your gamepads and joysticks. Show your moves in your stream and videos.">
<link rel="canonical" href="https://dinir.github.io/mini-padder/">
<meta property="og:url" content="https://dinir.github.io/mini-padder/">
<meta property="og:image" content="https://dinir.github.io/mini-padder/image/open-graph-image.png">
<meta property="og:image:type" content="image/png">
<meta property="og:image:width" content="1280">
<meta property="og:image:height" content="640">
<meta property="og:image:alt" content="An overlay of four gamepads. They resemble XInput, 8-button Joystick, DInput, and 6-button gamepad. Some buttons in all gamepads are being pressed. For XInput they are left stick at upright, right stick at three-quarter down, Y, LB, and RT at two-third. For Joystick they are D-pad at upright, first and last face buttons. For DInput they are D-pad at upright, X, and R1. For 6-button gamepad they're D-pad at upright, B, and C.">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">
<link rel="alternate" type="application/json+oembed" href="https://dinir.github.io/mini-padder/oembed.json">
<meta name="viewport" content="width=1048">
<meta name="google-site-verification" content="Giwp1vVm5mdTEbNFZEphTPtCn2VKV85F2LIDoSrCsCk">
<link rel="stylesheet" href="./css/main.css">
<link rel="stylesheet" href="./css/canvasContainer.css">
<link rel="stylesheet" href="./css/controlPanel.css">
<link rel="stylesheet" href="./css/OnBrowserTextEditor.css">
<script src="./js/module/MPCommon.js"></script>
<script src="./js/module/ErrorLogCollector.js"></script>
<script src="./js/module/GamepadWatcher.js"></script>
<script src="./js/module/MappingManager.js"></script>
<script src="./js/module/GamepadRenderer.js"></script>
<script src="./js/module/OnBrowserTextEditor.js"></script>
<script src="./js/module/Updater.js"></script>
<script src="./js/interface/Mapper.js"></script>
<script src="./js/interface/controlPanel.js"></script>
<script src="./js/interface/MiniPadderUpdater.js"></script>
</head>
<body>
<div id="canvas-container">
<div>
</div>
<div>
</div>
<div>
</div>
<div>
</div>
</div>
<div id="control-panel" class="control-panel" data-nosnippet>
<div class="option" data-name="layout">
<b>Gamepad Skin</b>
<label class="full-width inactive">
<span>Gamepad Name</span> is
<select></select><br>
</label>
<label class="full-width inactive">
<span>Gamepad Name</span> is
<select></select><br>
</label>
<label class="full-width inactive">
<span>Gamepad Name</span> is
<select></select><br>
</label>
<label class="full-width inactive">
<span>Gamepad Name</span> is
<select></select>
</label>
<span class="description">
Change skin for each gamepad kinds. Use Enter and Arrow keys if buggy.
</span>
</div>
<div class="option" data-name="custom-skin">
<label id="customSkinLoadArea" class="full-width">
<b>Import Custom Skin</b>
<span class="after-margin file-input-container inactive">
<input class="visually-hidden"
type="file" accept="text/plain,application/json,image/*" multiple>
<button data-name="fileSelect">Choose Files</button>
<span id="currentlyLoadedCustomSkin">Nothing</span>
<button data-name="remove" class="align-right">X</button>
</span>
<span class="description">
Upload a json and up to 4 images for the custom skin.
New files replace old ones.<br>
Click anywhere in this area to bring the file select screen.
</span>
</label>
</div>
<div class="option half-width" data-name="displayWidth">
<label for="display-width-input"><b>Display Width</b></label>
<div style="width: 10em;">
<datalist class="hashmark" id="display-width-options">
<option value="1" label="1"></option>
<option value="2" label="2"></option>
<option value="3" label="4"></option>
</datalist>
<input class="allGamepad with-hashmark" id="display-width-input" type="range" list="display-width-options" min="1" max="3" step="1">
</div>
<span class="description">
<b>Crop Values</b><br>
<span class="monospace">Right 792, Bottom 456</span> (one pad)<br>
<span class="monospace" id="displaySizeDescriptor"></span> (all pads)
</span>
</div>
<div class="option half-width" data-name="fade">
<b>Fade-out</b>
<label>
<span>Time: </span>
<input type="text" placeholder="e.g. 0,8,12">
</label>
<span class="description">seconds for each fade-out level</span><br>
<label>
<span>Opacity: </span>
<input type="text" placeholder="e.g. 0.5,0.1,0">
</label>
<span class="description">transparency values for each level</span><br>
<label>
<span>Duration: </span>
<input type="text" placeholder="e.g. 1">
</label>
<span class="description">transition time of fade-out effect</span><br>
<span class="description">If you want to disable the effect, empty the Opacity input.</span>
</div>
<div></div>
<div class="option half-width" data-name="assignment">
<b>Input Assignment</b>
<div id="inputAssignment" class="after-margin button-container one-button-each-line">
<button class="inactive">Gamepad Name</button>
<button class="inactive">Gamepad Name</button>
<button class="inactive">Gamepad Name</button>
<button class="inactive">Gamepad Name</button>
</div>
<span class="description">
Make an input after pressing the button to start assigning.
Click the button again anytime to abort assigning.<br>
Assignments are saved per gamepad kind
and shared with others of the same kind.
</span>
</div>
<div class="option half-width" data-name="deadzone">
<b>Update Deadzone</b>
<div id="deadzoneUpdate" class="after-margin button-container two-buttons-each-line">
<div class="inactive">
<button data-position="left">0.000</button>
<button data-position="right">0.000</button>
</div>
<div class="inactive">
<button data-position="left">0.000</button>
<button data-position="right">0.000</button>
</div>
<div class="inactive">
<button data-position="left">0.000</button>
<button data-position="right">0.000</button>
</div>
<div class="inactive">
<button data-position="left">0.000</button>
<button data-position="right">0.000</button>
</div>
</div>
<span class="description">
New deadzone value will be applied at the moment of click.
Click again until the sticks are not anymore appearing by their own.<br>
If you don't use the fade-out, you might not need this option.
</span>
</div>
<div class="option top-divider" data-name="management">
<b>Export & Import</b>
<div class="after-margin button-container">
<button data-name="skinList">Skin List</button>
<button data-name="customSkin">Custom Skin</button>
<button data-name="mappings">Gamepad Mappings</button>
<button data-name="controlPanel">Control Panel Settings</button>
<button data-name="errorLog">Error Log</button>
</div>
<span class="description">
You can copy JSON of the data to save in a text file,
or paste from such text file and
store them in the local storage of this web page.
</span>
</div>
<div>
<div>
<b class="inline">Mini Padder</b><sub> <span class="version"></span></sub>
© 2022 Dinir Nertan
</div>
<address>
<span class="link-list">
<a class="head"
href="https://github.com/Dinir/mini-padder" target="_blank"
>Repository</a>
<a href="https://github.com/Dinir/mini-padder/wiki/How-to-Use" target="_blank"
>How to Use</a>
<a href="https://github.com/Dinir/mini-padder/wiki" target="_blank"
>Wiki</a>
<a href="https://github.com/Dinir/mini-padder/issues" target="_blank"
>Support</a>
</span>
<span class="align-right">
<a href="https://ko-fi.com/dinir" target="_blank">Donate</a>
</span>
</address>
</div>
</div>
<!-- dom references -->
<script>
const canvasContainer = document.getElementById('canvas-container')
const canvas = Array.from(canvasContainer.querySelectorAll('div'))
const cpDom = document.getElementById('control-panel')
const cpDomSizeDescriptor = document.getElementById('displaySizeDescriptor')
const cpDomSkinUploader = {
dropArea: cpDom.querySelector('#customSkinLoadArea'),
input: cpDom.querySelector('#customSkinLoadArea input'),
button: cpDom.querySelector('#customSkinLoadArea button[data-name="fileSelect"]'),
indicator: document.getElementById('currentlyLoadedCustomSkin'),
removeButton: cpDom.querySelector('#customSkinLoadArea button[data-name="remove"]')
}
</script>
<!-- store error logs -->
<script>
const Logger = new ErrorLogCollector()
// get gamepad viewer messages on console
window.addEventListener('MPMessage', e => {
const consoleString = e.detail.from + ': ' + e.detail.message
switch (e.detail.type) {
case 'error':
console.error(consoleString)
break
case 'warning':
case 'warn':
console.warn(consoleString)
break
default:
console.log(consoleString)
break
}
if (e.detail.type !== 'log') {
window.dispatchEvent(new CustomEvent('customErrorMessage', {
detail: e.detail.message instanceof Error ?
e.detail.message : consoleString
}))
}
})
</script>
<!-- default skin list -->
<script>
const defaultSkins = new Map([
['gamepad-xinput', 'XInput'],
['gamepad-xinput_red', 'XInput (Red)'],
['gamepad-xinput_orange', 'XInput (Orange)'],
['gamepad-xinput_yellow', 'XInput (Yellow)'],
['gamepad-xinput_lime', 'XInput (Lime)'],
['gamepad-xinput_green', 'XInput (Green)'],
['gamepad-xinput_mint', 'XInput (Mint)'],
['gamepad-xinput_cyan', 'XInput (Cyan)'],
['gamepad-xinput_azure', 'XInput (Azure)'],
['gamepad-xinput_blue', 'XInput (Blue)'],
['gamepad-xinput_violet', 'XInput (Violet)'],
['gamepad-xinput_magenta', 'XInput (Magenta)'],
['gamepad-xinput_rose', 'XInput (Rose)'],
['gamepad-dinput', 'DInput'],
['gamepad-dinput_red', 'DInput (Red)'],
['gamepad-dinput_orange', 'DInput (Orange)'],
['gamepad-dinput_yellow', 'DInput (Yellow)'],
['gamepad-dinput_lime', 'DInput (Lime)'],
['gamepad-dinput_green', 'DInput (Green)'],
['gamepad-dinput_mint', 'DInput (Mint)'],
['gamepad-dinput_cyan', 'DInput (Cyan)'],
['gamepad-dinput_azure', 'DInput (Azure)'],
['gamepad-dinput_blue', 'DInput (Blue)'],
['gamepad-dinput_violet', 'DInput (Violet)'],
['gamepad-dinput_magenta', 'DInput (Magenta)'],
['gamepad-dinput_rose', 'DInput (Rose)'],
['gamepad-disc', 'Disc D-pad'],
['gamepad-disc_xinput', 'Disc D-pad (X Button)'],
['gamepad-disc_dinput', 'Disc D-pad (D Button)'],
['joystick-v', 'Joystick (V Layout)'],
['joystick-v_x', 'Joystick (V Layout) (X Button)'],
['joystick-v_d', 'Joystick (V Layout) (D Button)'],
['joystick-a', 'Joystick (A Layout)'],
['joystick-a_x', 'Joystick (A Layout) (X Button)'],
['joystick-a_d', 'Joystick (A Layout) (D Button)'],
['joystick-n', 'Joystick (N Layout)'],
['joystick-n_x', 'Joystick (N Layout) (X Button)'],
['joystick-n_d', 'Joystick (N Layout) (D Button)'],
['joystick-m', 'Joystick (M Layout)'],
['joystick-m_coloured', 'Joystick (M Layout) (Coloured)'],
['megapad-rb_m', 'Mega Pad (RB M Layout)'],
['megapad-rb_s', 'Mega Pad (RB S Layout)'],
['megapad-8bd', 'Mega Pad (8BD Layout)'],
['gamepad-super', 'Super Pad'],
['gamepad-super_ls', 'Super Pad (LS)'],
['gamepad-fcommander', 'F Commander'],
['gamepad-fcommander_ls', 'F Commander (LS)'],
['gamepad-fcommander_d', 'F Commander (D Button)'],
['gamepad-fcommander_d_ls', 'F Commander (D Button) (LS)'],
['gamepad-fcommander_d_t', 'F Commander (D Toggled)'],
['gamepad-fcommander_d_t_ls', 'F Commander (D Toggled) (LS)'],
['gamepad-fcommander_s', 'F Commander (S Button)'],
['gamepad-fcommander_s_ls', 'F Commander (S Button) (LS)'],
['gamepad-fcommander_s_t', 'F Commander (S Toggled)'],
['gamepad-fcommander_s_t_ls', 'F Commander (S Toggled) (LS)'],
['gamepad-fcommander_o', 'F Commander Octa'],
['hbox', 'HBox'],
['biker', 'Biker']
])
</script>
<!-- class instances -->
<script>
const Watcher = new GamepadWatcher()
const Mapper = new MappingInterface()
const Renderer = new GamepadRenderer(canvas, defaultSkins)
const Obte = new OnBrowserTextEditor()
Obte.dom.wrapper.classList.add('control-panel', 'inactive')
Obte.appendToParent(document.body, cpDom)
</script>
<!-- control panel -->
<script>
const Cp = new ControlPanel({
layout: 'selectFromMap',
customSkin: 'uploader',
displayWidth: 'slider',
fadeout: 'textArray',
assignment: 'dynamicButtons',
deadzone: 'dynamicButtons',
management: 'buttons',
}, [
'gamepadconnected', 'gamepaddisconnected'
], cpDom)
const cpPanel = Cp.panel
cpPanel.layout.assign(
cpDom.querySelector('div[data-name="layout"]'),
cpDom.querySelectorAll('div[data-name="layout"] label span'),
Renderer.skinList,
Renderer.changeSkinOfSlot,
Renderer.skinMapping
)
const customSkinLocalStorageKey = 'customskin'
cpPanel.customSkin.assign(
customSkinLocalStorageKey, {
input: cpDomSkinUploader.input,
dropArea: cpDomSkinUploader.dropArea,
visibleButton: cpDomSkinUploader.button,
removeButton: cpDomSkinUploader.removeButton
}, {
typeCheckFunction: typeString =>
typeString === 'text/plain' ? 'text' :
typeString === 'application/json' ? 'json' :
typeString.startsWith('image/') ? 'image' :
null
, indicatorUpdateCallback: text => {
cpDomSkinUploader.indicator.innerHTML = text
if (text && text.length) {
cpDomSkinUploader.indicator.parentElement.classList.remove('inactive')
} else {
cpDomSkinUploader.indicator.parentElement.classList.add('inactive')
}
}, customCallback: customSkinConfig => {
if (!customSkinConfig) {
Renderer.unloadSkin(customSkinLocalStorageKey)
} else {
Renderer.reloadSkin(customSkinLocalStorageKey)
// refresh skin slots using the custom skin
for (let i = 0; i < Renderer.skinSlot.length; i++) {
const skinSlot = Renderer.skinSlot[i]
if (
!skinSlot ||
!skinSlot.hasOwnProperty('internalName') ||
skinSlot.internalName !== customSkinLocalStorageKey
) { continue }
Renderer.changeSkinOfSlot(i, skinSlot.gamepadId)
}
}
// update the layout select list
cpPanel.layout.updateItems(cpPanel.layout.list)
}
}
)
cpPanel.displayWidth.assign(
cpDom.querySelector('div[data-name="displayWidth"] input'), e => {
canvasContainer.dataset.width = e.target.value
const cropValue = [
document.body.offsetWidth - canvasContainer.offsetWidth,
document.body.offsetHeight - canvasContainer.offsetHeight
]
const cropValueString =
cropValue.map(v => String(v).padStart(3, '\xa0'))
cpDomSizeDescriptor.innerHTML =
`Right ${cropValueString[0]}, Bottom ${cropValueString[1]}`
}
)
cpPanel.fadeout.assign(
cpDom.querySelector('div[data-name="fade"]'),
Renderer.setFadeoutOptionFromTextArray
)
if (!cpPanel.fadeout.panelValue) {
cpPanel.fadeout.receivePanelValue(
Renderer.getFadeoutOptionAsTextArray()
)
cpPanel.fadeout.updatePanelValue(
cpPanel.fadeout.panelValue
)
}
cpPanel.assignment.assign(
document.getElementById('inputAssignment'),
Mapper.startAssignment
)
cpPanel.deadzone.assign(
document.getElementById('deadzoneUpdate'),
function (gamepadIndex, name, gamepadId, side) {
if (!Watcher.gamepads[gamepadIndex]) { return false }
const rawAxesData = Watcher.gamepads[gamepadIndex].axes
const mappedGamepadId = Mapper.getMappedGamepadId(gamepadId)
const stickMappings = Mapper.mappings[mappedGamepadId].sticks
const changeAxes = []
if (stickMappings[side]) {
changeAxes[stickMappings[side].x] =
{ value: rawAxesData[stickMappings[side].x] }
changeAxes[stickMappings[side].y] =
{ value: rawAxesData[stickMappings[side].y] }
}
const updateDone =
MappingManager.setDeadzone(stickMappings[side], changeAxes)
if (updateDone) { Mapper.store() }
}, {
customButtons: ControlPanel.getIndexedElements(
document.getElementById('deadzoneUpdate'), 'div'
),
customEventCallback: function (e) {
if (e.target.tagName !== 'BUTTON') return
const indexedContainer = e.target.parentElement
this.callback(
indexedContainer.dataset.index,
indexedContainer.dataset.name,
indexedContainer.dataset.gamepadId,
e.target.dataset.position
)
if (!this.updateLabel) return
this.buttons.forEach(v => {
if (v.classList.contains('inactive')) return
const id = {
name: v.dataset.name,
gamepadId: v.dataset.gamepadId
}
const labelPair = this.makeLabel(id)
this.changeLabel(
v.dataset.index, id, labelPair
)
})
},
makeLabel: function (idObj) {
const mappedGamepadId = Mapper.getMappedGamepadId(idObj.gamepadId)
const stickMappings = Mapper.mappings[mappedGamepadId].sticks
const getDeadzoneValueString = value => {
switch (value) {
case 0:
case 1:
return value + '.000'
case null:
return false
default:
if (isNaN(value)) {
return false
}
return String(value).padEnd(5, '0')
}
}
const labelPair = Array(2).fill('')
if (stickMappings) {
const deadzoneStringLeft = getDeadzoneValueString(
stickMappings.left && stickMappings.left.deadzone
)
const deadzoneStringRight = getDeadzoneValueString(
stickMappings.right && stickMappings.right.deadzone
)
labelPair[0] +=
typeof deadzoneStringLeft === 'string' ?
deadzoneStringLeft : 'N/A'
labelPair[1] +=
typeof deadzoneStringRight === 'string' ?
deadzoneStringRight : 'N/A'
} else {
labelPair.fill('N/A')
}
return labelPair
},
changeLabel: function (index, id, newTextPair) {
if (
newTextPair.constructor !== Array ||
!newTextPair.length
) { return }
this.buttons[index].dataset.name = id ? id.name : ''
this.buttons[index].dataset.gamepadId = id ? id.gamepadId : ''
const buttonPair = Array.from(this.buttons[index].children)
buttonPair.forEach((b, i) => {
b.innerHTML = newTextPair[i]
})
},
updateLabel: true
}
)
cpPanel.management.assign(
cpDom.querySelector('div[data-name="management"]'), e => {
switch (e.target.dataset.name) {
case 'mappings':
Obte.changeFocus(
'Gamepad Mappings',
Mapper.mappings,
Mapper.import
)
break
case 'skinList':
Obte.changeFocus(
'Skin List',
Renderer.skinList,
Renderer.reloadSkins,
{ type: 'map' }
)
break
case 'customSkin':
Obte.changeFocus(
'Custom Skin',
cpPanel.customSkin.loadedData,
cpPanel.customSkin.loadDataObj,
{ nospace: true }
)
break
case 'controlPanel':
Obte.changeFocus(
'Control Panel Configuration',
Cp.panelValues,
Cp.setPanelValuesInBulk
)
break
case 'errorLog':
Obte.changeFocus(
'Error Log',
Logger.errorLog,
null,
{ raw: true }
)
break
}
}
)
</script>
<!-- onload alerts -->
<script>
if (Cp.browser !== 'Chromium') {
Cp.insertAlert(
'The page is running on a browser that isn\'t Chromium-based. ' +
'Even if features work, the overlay may suffer a low framerate.',
'caution'
)
}
const inputAwaitAlertDom = Cp.insertAlert(
'Make an input from any connected gamepads!'
)
window.addEventListener('gamepadconnected', () => {
Cp.removeAlert(inputAwaitAlertDom)
}, {once: true})
</script>
<!-- updater -->
<script>
const version = '5.4.3'
const MPUpdater = new MiniPadderUpdater(version, Cp.insertAlert.bind(Cp))
const versionDoms = document.getElementsByClassName('version')
const lastFoundVersion = Updater.getVersionString(MPUpdater.lastFoundVersion)
Array.from(versionDoms).forEach(v => v.innerHTML = lastFoundVersion)
</script>
</body>
</html>