| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"> |
| <title>GBA.js - Touch Fixed</title> |
|
|
| <link rel="stylesheet" href="resources/main.css"> |
|
|
| <style> |
| * { margin:0; padding:0; box-sizing:border-box; } |
| html, body { height:100%; overflow:hidden; background:#000; touch-action: manipulation; } |
| #screen-container { |
| position: relative; |
| width: 100%; |
| height: 100%; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| background: #111; |
| } |
| #screen { |
| width: 100%; |
| height: auto; |
| max-width: 100%; |
| max-height: 85vh; |
| image-rendering: pixelated; |
| touch-action: none; |
| } |
| #controls { |
| position: absolute; |
| top: 10px; |
| left: 10px; |
| z-index: 20; |
| color: white; |
| background: rgba(0,0,0,0.7); |
| padding: 10px; |
| border-radius: 8px; |
| font-size: 14px; |
| } |
| .bigbutton { padding: 12px 24px; font-size: 18px; margin: 6px; } |
| #touchControls { |
| position: absolute; |
| inset: 0; |
| pointer-events: none; |
| display: none; |
| z-index: 10; |
| padding: 4vmin; |
| } |
| button { |
| font-weight: bold; |
| color: white; |
| border: none; |
| opacity: 0.7; |
| touch-action: manipulation; |
| -webkit-tap-highlight-color: transparent; |
| } |
| button:active { opacity: 1; } |
| #dpad { |
| display: grid; |
| grid-template-columns: repeat(3, 22vmin); |
| grid-template-rows: repeat(3, 22vmin); |
| gap: 2vmin; |
| } |
| #faceButtons { |
| display: flex; |
| flex-direction: column; |
| align-items: flex-end; |
| gap: 4vmin; |
| } |
| .shoulder { width: 22vmin; height: 14vmin; border-radius: 14vmin; background: rgba(200,200,200,0.7); font-size: 5vmin; } |
| .startselect { width: 22vmin; height: 14vmin; border-radius: 12vmin; background: rgba(0,180,0,0.7); font-size: 4vmin; } |
| #a { background: rgba(255,80,80,0.85); width: 26vmin; height: 26vmin; font-size: 7vmin; } |
| #b { background: rgba(80,80,255,0.85); width: 26vmin; height: 26vmin; font-size: 7vmin; } |
| |
| @media (orientation: landscape) { |
| #touchControls { display: flex; flex-direction: row; justify-content: space-between; align-items: flex-end; } |
| #dpad { grid-template-columns: repeat(3, 18vmin); grid-template-rows: repeat(3, 18vmin); } |
| #faceButtons { flex-direction: row; gap: 6vmin; } |
| } |
| @media (orientation: portrait) { |
| #touchControls { display: flex; flex-direction: column; justify-content: flex-end; gap: 5vmin; } |
| #dpad { align-self: flex-start; } |
| #faceButtons { align-self: flex-end; } |
| } |
| </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 = []; |
| var debug = null; |
| |
| try { |
| gba = new GameBoyAdvance(); |
| gba.keypad.eatInput = true; |
| gba.setLogger(function(level, error) { |
| console.error(error); |
| gba.pause(); |
| |
| }); |
| } catch (e) { |
| console.error("GBA init failed:", e); |
| gba = null; |
| } |
| |
| window.onload = function() { |
| if (gba && FileReader) { |
| var canvas = document.getElementById('screen'); |
| gba.setCanvas(canvas); |
| gba.logLevel = gba.LOG_ERROR || 0; |
| |
| |
| var xhr = new XMLHttpRequest(); |
| xhr.open('GET', 'resources/bios.bin', true); |
| xhr.responseType = 'arraybuffer'; |
| xhr.onload = function() { |
| if (xhr.status === 200) gba.setBios(new Uint8Array(xhr.response)); |
| }; |
| xhr.send(); |
| |
| |
| const isTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0; |
| if (isTouch) { |
| document.getElementById('touchControls').style.display = 'flex'; |
| canvas.style.maxHeight = '75vh'; |
| } |
| |
| |
| canvas.addEventListener('touchstart', () => canvas.focus(), {passive: true}); |
| |
| setupTouchControls(); |
| } |
| }; |
| |
| |
| |
| |
| function setupTouchControls() { |
| function simulateKey(type, keyCode, keyName) { |
| const event = new KeyboardEvent(type, { |
| key: keyName, |
| code: keyName, |
| keyCode: keyCode, |
| which: keyCode, |
| bubbles: true, |
| cancelable: true |
| }); |
| console.log(`Simulating ${type} for keyCode ${keyCode} (${keyName})`); |
| window.dispatchEvent(event); |
| document.getElementById('screen').dispatchEvent(event); |
| } |
| |
| const map = { |
| up: {code: 38, name: 'ArrowUp'}, |
| down: {code: 40, name: 'ArrowDown'}, |
| left: {code: 37, name: 'ArrowLeft'}, |
| right: {code: 39, name: 'ArrowRight'}, |
| a: {code: 88, name: 'KeyX'}, |
| b: {code: 90, name: 'KeyZ'}, |
| l: {code: 65, name: 'KeyA'}, |
| r: {code: 83, name: 'KeyS'}, |
| start: {code: 13, name: 'Enter'}, |
| select:{code: 16, name: 'ShiftLeft'} |
| }; |
| |
| ['up','down','left','right','a','b','l','r','start','select'].forEach(id => { |
| const btn = document.getElementById(id); |
| if (!btn) return; |
| |
| btn.addEventListener('touchstart', e => { |
| e.preventDefault(); |
| e.stopPropagation(); |
| const k = map[id]; |
| simulateKey('keydown', k.code, k.name); |
| }); |
| |
| btn.addEventListener('touchend', e => { |
| e.preventDefault(); |
| e.stopPropagation(); |
| const k = map[id]; |
| simulateKey('keyup', k.code, k.name); |
| }); |
| |
| btn.addEventListener('touchcancel', e => { |
| e.preventDefault(); |
| const k = map[id]; |
| simulateKey('keyup', k.code, k.name); |
| }); |
| }); |
| } |
| </script> |
| </head> |
| <body> |
|
|
| <div id="screen-container"> |
| <canvas id="screen" width="480" height="320" tabindex="0"></canvas> |
| </div> |
|
|
| <section id="controls"> |
| <div id="preload"> |
| <button class="bigbutton" id="select" 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 id="pause" onclick="togglePause()">PAUSE</button> |
| |
| </div> |
| </section> |
|
|
| <div id="touchControls"> |
| <div id="dpad"> |
| <button id="up">β</button> |
| <button id="left">β</button> |
| <button id="right">β</button> |
| <button id="down">β</button> |
| </div> |
|
|
| <div id="faceButtons"> |
| <div> |
| <button id="l" class="shoulder">L</button> |
| <button id="r" class="shoulder">R</button> |
| </div> |
| <div> |
| <button id="select" class="startselect">Select</button> |
| <button id="start" class="startselect">Start</button> |
| </div> |
| <div> |
| <button id="b">B</button> |
| <button id="a">A</button> |
| </div> |
| </div> |
| </div> |
|
|
| </body> |
| </html> |