| | <html> |
| | <head> |
| | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> |
| | <title>GBA.js - Mobile Controller</title> |
| | <link rel="stylesheet" href="resources/main.css"> |
| | <style> |
| | |
| | body, html { |
| | margin: 0; |
| | padding: 0; |
| | width: 100%; |
| | height: 100%; |
| | background-color: #000; |
| | overflow: hidden; |
| | display: flex; |
| | flex-direction: column; |
| | font-family: sans-serif; |
| | } |
| | |
| | |
| | #screen-container { |
| | width: 100%; |
| | flex: 0 0 auto; |
| | display: flex; |
| | flex-direction: column; |
| | align-items: center; |
| | padding-top: 10px; |
| | } |
| | |
| | #screen { |
| | width: 100vw; |
| | height: auto; |
| | aspect-ratio: 3 / 2; |
| | image-rendering: pixelated; |
| | background: #000; |
| | } |
| | |
| | |
| | #controls { |
| | width: 100%; |
| | background: #111; |
| | padding: 10px 0; |
| | text-align: center; |
| | border-bottom: 1px solid #333; |
| | } |
| | |
| | #ingame button, #preload button { |
| | background: #333; |
| | color: #0f0; |
| | border: 1px solid #444; |
| | padding: 5px 15px; |
| | margin: 0 5px; |
| | font-family: monospace; |
| | border-radius: 4px; |
| | text-transform: uppercase; |
| | } |
| | |
| | |
| | #touchControls { |
| | flex: 1 1 auto; |
| | display: none; |
| | position: relative; |
| | width: 100%; |
| | padding: 20px; |
| | box-sizing: border-box; |
| | user-select: none; |
| | -webkit-user-select: none; |
| | } |
| | |
| | |
| | .ctrl-btn { |
| | position: absolute; |
| | background: #888; |
| | border: none; |
| | color: #eee; |
| | font-weight: bold; |
| | display: flex; |
| | align-items: center; |
| | justify-content: center; |
| | pointer-events: auto; |
| | box-shadow: 0 4px #555; |
| | } |
| | |
| | .ctrl-btn:active { |
| | background: #666; |
| | box-shadow: 0 2px #333; |
| | transform: translateY(2px); |
| | } |
| | |
| | |
| | #dpad { |
| | position: absolute; |
| | bottom: 80px; |
| | left: 20px; |
| | width: 150px; |
| | height: 150px; |
| | } |
| | |
| | #up { top: 0; left: 50px; width: 50px; height: 55px; border-radius: 5px 5px 0 0; } |
| | #down { bottom: 0; left: 50px; width: 50px; height: 55px; border-radius: 0 0 5px 5px; } |
| | #left { top: 50px; left: 0; width: 55px; height: 50px; border-radius: 5px 0 0 5px; } |
| | #right { top: 50px; right: 0; width: 55px; height: 50px; border-radius: 0 5px 5px 0; } |
| | #center-block { position: absolute; top: 50px; left: 50px; width: 50px; height: 50px; background: #888; } |
| | |
| | |
| | #action-cluster { |
| | position: absolute; |
| | bottom: 80px; |
| | right: 20px; |
| | width: 160px; |
| | height: 160px; |
| | } |
| | |
| | .round-btn { width: 70px; height: 70px; border-radius: 50%; font-size: 20px; } |
| | #a { top: 0; right: 0; } |
| | #b { bottom: 0; left: 0; } |
| | |
| | .dummy { background: #444; opacity: 0.3; } |
| | |
| | |
| | #l { top: 10px; left: 20px; width: 60px; height: 35px; border-radius: 5px; } |
| | #r { top: 10px; right: 20px; width: 60px; height: 35px; border-radius: 5px; } |
| | |
| | |
| | .pill-btn { |
| | bottom: 30px; |
| | width: 80px; |
| | height: 30px; |
| | border-radius: 15px; |
| | font-size: 12px; |
| | background: #555; |
| | } |
| | #select { left: 40px; } |
| | #start { right: 40px; } |
| | |
| | .hidden { display: none !important; } |
| | </style> |
| |
|
| | <script src="js/util.js"></script> |
| | <script src="js/core.js"></script> |
| | <script src="js/arm.js"></script> |
| | <script src="js/thumb.js"></script> |
| | <script src="js/mmu.js"></script> |
| | <script src="js/io.js"></script> |
| | <script src="js/audio.js"></script> |
| | <script src="js/video.js"></script> |
| | <script src="js/video/proxy.js"></script> |
| | <script src="js/video/software.js"></script> |
| | <script src="js/irq.js"></script> |
| | <script src="js/keypad.js"></script> |
| | <script src="js/sio.js"></script> |
| | <script src="js/savedata.js"></script> |
| | <script src="js/gpio.js"></script> |
| | <script src="js/gba.js"></script> |
| | <script src="resources/xhr.js"></script> |
| |
|
| | <script> |
| | var gba; |
| | var runCommands = []; |
| | |
| | try { |
| | gba = new GameBoyAdvance(); |
| | gba.keypad.eatInput = true; |
| | } catch (exception) { gba = null; } |
| | |
| | window.onload = function() { |
| | if (gba && FileReader) { |
| | var canvas = document.getElementById('screen'); |
| | gba.setCanvas(canvas); |
| | loadRom('resources/bios.bin', function(bios) { gba.setBios(bios); }); |
| | |
| | |
| | if ('ontouchstart' in window || navigator.maxTouchPoints > 0) { |
| | document.getElementById('touchControls').style.display = 'block'; |
| | } |
| | setupTouchControls(); |
| | } |
| | } |
| | |
| | function run(file) { |
| | gba.loadRomFromFile(file, function(result) { |
| | if (result) { |
| | document.getElementById('preload').classList.add('hidden'); |
| | document.getElementById('ingame').classList.remove('hidden'); |
| | gba.runStable(); |
| | } |
| | }); |
| | } |
| | |
| | function setupTouchControls() { |
| | function press(c) { window.dispatchEvent(new KeyboardEvent('keydown', {keyCode: c})); } |
| | function release(c) { window.dispatchEvent(new KeyboardEvent('keyup', {keyCode: c})); } |
| | |
| | const map = { |
| | 'up': 38, 'down': 40, 'left': 37, 'right': 39, |
| | 'a': 88, 'b': 90, 'l': 65, 'r': 83, |
| | 'start': 13, 'select': 16 |
| | }; |
| | |
| | Object.keys(map).forEach(id => { |
| | const el = document.getElementById(id); |
| | if(el) { |
| | el.addEventListener('touchstart', (e) => { e.preventDefault(); press(map[id]); }); |
| | el.addEventListener('touchend', (e) => { e.preventDefault(); release(map[id]); }); |
| | } |
| | }); |
| | } |
| | </script> |
| | </head> |
| | <body> |
| |
|
| | <div id="screen-container"> |
| | <div style="color:#0f0; font-family:monospace; margin-bottom:5px;">Connecting to Screen...</div> |
| | <canvas id="screen" width="480" height="320"></canvas> |
| | </div> |
| |
|
| | <section id="controls"> |
| | <div id="preload"> |
| | <button onclick="document.getElementById('loader').click()">Select ROM</button> |
| | <input id="loader" type="file" accept=".gba" onchange="run(this.files[0]);" style="display:none"> |
| | </div> |
| | <div id="ingame" class="hidden"> |
| | <button onclick="gba.pause()">Pause</button> |
| | <button onclick="gba.reset()">Reset</button> |
| | </div> |
| | </section> |
| |
|
| | <div id="touchControls"> |
| | <div id="dpad"> |
| | <button id="up" class="ctrl-btn">↑</button> |
| | <button id="left" class="ctrl-btn">←</button> |
| | <div id="center-block"></div> |
| | <button id="right" class="ctrl-btn">→</button> |
| | <button id="down" class="ctrl-btn">↓</button> |
| | </div> |
| |
|
| | <div id="action-cluster"> |
| | <button id="a" class="ctrl-btn round-btn">A</button> |
| | <button id="b" class="ctrl-btn round-btn">B</button> |
| | </div> |
| |
|
| | <button id="l" class="ctrl-btn">L</button> |
| | <button id="r" class="ctrl-btn">R</button> |
| |
|
| | <button id="select" class="ctrl-btn pill-btn">SELECT</button> |
| | <button id="start" class="ctrl-btn pill-btn">START</button> |
| | </div> |
| |
|
| | </body> |
| | </html> |
| |
|