diff --git a/src/GameManager.js b/src/GameManager.js index 08e0b7b..5ec2caf 100644 --- a/src/GameManager.js +++ b/src/GameManager.js @@ -7,7 +7,9 @@ class EJS_GameManager { getStateInfo: this.Module.cwrap('get_state_info', 'string', []), //these names are dumb saveStateInfo: this.Module.cwrap('save_state_info', 'null', []), loadState: this.Module.cwrap('load_state', 'number', ['string', 'number']), - screenshot: this.Module.cwrap('cmd_take_screenshot', '', []) + screenshot: this.Module.cwrap('cmd_take_screenshot', '', []), + simulateInput: this.Module.cwrap('simulate_input', 'null', ['number', 'number', 'number']), + toggleMainLoop: this.Module.cwrap('toggleMainLoop', 'null', ['number']) } } restart() { @@ -71,6 +73,12 @@ class EJS_GameManager { this.functions.loadState(name, 0); })(); } + simulateInput(player, index, value) { + this.functions.simulateInput(player, index, value); + } + toggleMainLoop(playing) { + this.functions.toggleMainLoop(playing); + } } diff --git a/src/css/main.css b/src/css/main.css index e87e0a6..fd05747 100644 --- a/src/css/main.css +++ b/src/css/main.css @@ -275,7 +275,6 @@ } .ejs_control_body input[type='text'] { - overflow: auto; background-color: #fff; border: 1px solid #000; font-size: 12px; @@ -307,3 +306,18 @@ .ejs_control_selected a { color: #000 !important; } + +.ejs_control_bar:hover { + background-color: #2d2d2d; +} + +.ejs_popup_box { + position: absolute; + width: 300px; + top: 50%; + margin-left: -150px; + margin-top: -50px; + left: 50%; + background: rgba(0,0,0,0.8) !important; + padding: 15px 0; +} diff --git a/src/emulator.js b/src/emulator.js index a246e50..6ebd9dd 100644 --- a/src/emulator.js +++ b/src/emulator.js @@ -68,6 +68,7 @@ class EmulatorJS { } } constructor(element, config) { + window.EJS_TESTING = this; this.debug = (window.EJS_DEBUG_XX === true); this.setElements(element); this.started = false; @@ -77,6 +78,7 @@ class EmulatorJS { this.canvas = this.createElement('canvas'); this.canvas.classList.add('ejs_canvas'); this.bindListeners(); + this.fullscreen = false; this.game.classList.add("ejs_game"); @@ -222,10 +224,21 @@ class EmulatorJS { } getCore() { const core = this.config.system; - //switch case or an object holding this data - if (core === 'nes') { - return 'fceumm'; + const options = { + 'nes': 'fceumm', + 'snes': 'snes9x', + 'atari5200': 'a5200', + 'gb': 'gambatte', + 'gba': 'mgba', + 'vb': 'beetle_vb', + 'n64': 'mupen64plus_next', + 'nds': 'desmume2015', + 'mame2003': 'mame2003', + 'arcade': 'fbalpha2012_cps1', // I need to find a more compatible arcade core + 'psx': 'mednafen_psx_hw', + '3do': 'opera' } + return options[core] || core; } downloadRom() { this.gameManager = new window.EJS_GameManager(this.Module); @@ -286,23 +299,25 @@ class EmulatorJS { const args = []; if (this.debug) args.push('-v'); args.push('/game'); - Module.callMain(args); - Module.resumeMainLoop(); + this.Module.callMain(args); + this.Module.resumeMainLoop(); this.started = true; this.paused = false; - Module.setCanvasSize(800, 600); - let i=0; - // this needs to be fixed. Ugh. - let j = setInterval(function() { // some cores have a messed up screen size on load (for example - gba) - if (i>20) clearInterval(j); - i++; - Module.setCanvasSize(800, 600); + + //this needs to be fixed... + setInterval(() => { + if (document.fullscreenElement !== null) { + this.Module.setCanvasSize(this.canvas.getBoundingClientRect().width-10, this.canvas.getBoundingClientRect().height-10); + } else { + this.Module.setCanvasSize(800, 600); + } }, 100) } bindListeners() { this.createContextMenu(); this.createBottomMenuBar(); this.createControlSettingMenu(); + this.addEventListener(document, "keydown keyup", this.keyChange.bind(this)); //keyboard, etc... } createContextMenu() { @@ -411,8 +426,9 @@ class EmulatorJS { popup.appendChild(button); } if (!hidden) { - popup.style.display = "none"; this.currentPopup = popup; + } else { + popup.style.display = "none"; } return main; @@ -478,6 +494,7 @@ class EmulatorJS { if (callback instanceof Function) { this.addEventListener(button, 'click', callback); } + return button; } //todo. Center text on not restart button @@ -485,6 +502,26 @@ class EmulatorJS { addButton("Restart", '', () => { this.gameManager.restart(); }); + const pauseButton = addButton("Pause", '', () => { + this.togglePlaying(); + }); + const playButton = addButton("Play", '', () => { + this.togglePlaying(); + }); + playButton.style.display = "none"; + this.togglePlaying = () => { + this.paused = !this.paused; + if (this.paused) { + pauseButton.style.display = "none"; + playButton.style.display = ""; + } else { + pauseButton.style.display = ""; + playButton.style.display = "none"; + } + this.gameManager.toggleMainLoop(this.paused ? 0 : 1); + } + + let stateUrl; addButton("Save State", '', async () => { if (stateUrl) URL.revokeObjectURL(stateUrl); @@ -505,13 +542,47 @@ class EmulatorJS { this.controlMenu.style.display = ""; }); + const spacer = this.createElement("span"); + spacer.style = "flex:1;"; + this.elements.menu.appendChild(spacer); + + + const enter = addButton("Enter Fullscreen", '', () => { + if (this.elements.parent.requestFullscreen) { + this.elements.parent.requestFullscreen(); + } else if (this.elements.parent.mozRequestFullScreen) { + this.elements.parent.mozRequestFullScreen(); + } else if (this.elements.parent.webkitRequestFullscreen) { + this.elements.parent.webkitRequestFullscreen(); + } else if (this.elements.parent.msRequestFullscreen) { + this.elements.parent.msRequestFullscreen(); + } + exit.style.display = ""; + enter.style.display = "none"; + }); + const exit = addButton("Exit Fullscreen", '', () => { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } + exit.style.display = "none"; + enter.style.display = ""; + }); + exit.style.display = "none"; + } createControlSettingMenu() { + this.controls = this.defaultControllers; const body = this.createPopup("Control Settings", { "Close": () => { this.controlMenu.style.display = "none"; } - }); + }, true); this.controlMenu = body.parentElement; body.classList.add("ejs_control_body"); @@ -546,6 +617,7 @@ class EmulatorJS { } let selectedPlayer; let players = []; + let playerDivs = []; const playerSelect = this.createElement("ul"); playerSelect.classList.add("ejs_control_player_bar"); @@ -559,10 +631,13 @@ class EmulatorJS { player.setAttribute("aria-controls", "controls-"+(i-1)); player.setAttribute("aria-selected", "false"); player.id = "controls-"+(i-1)+"-label"; - this.addEventListener(player, "click", () => { + this.addEventListener(player, "click", (e) => { + e.preventDefault(); players[selectedPlayer].classList.remove("ejs_control_selected"); + playerDivs[selectedPlayer].setAttribute("hidden", ""); selectedPlayer = i-1; players[i-1].classList.add("ejs_control_selected"); + playerDivs[i-1].removeAttribute("hidden"); }) playerContainer.appendChild(player); playerSelect.appendChild(playerContainer); @@ -570,10 +645,244 @@ class EmulatorJS { } body.appendChild(playerSelect); + + const controls = this.createElement("div"); + for (let i=0; i<4; i++) { + const player = this.createElement("div"); + const playerTitle = this.createElement("div"); + + const gamepadTitle = this.createElement("div"); + gamepadTitle.style = "font-size:12px;"; + gamepadTitle.innerText = "Connected Gamepad: "; + + const gamepadName = this.createElement("span"); + gamepadName.innerText = "n/a"; + gamepadTitle.appendChild(gamepadName); + + const leftPadding = this.createElement("div"); + leftPadding.style = "width:25%;float:left;"; + leftPadding.innerHTML = " "; + + const aboutParent = this.createElement("div"); + aboutParent.style = "font-size:12px;width:50%;float:left;"; + const gamepad = this.createElement("div"); + gamepad.style = "text-align:center;width:50%;float:left;"; + gamepad.innerText = "Gamepad"; + aboutParent.appendChild(gamepad); + const keyboard = this.createElement("div"); + keyboard.style = "text-align:center;width:50%;float:left;"; + keyboard.innerText = "Keyboard"; + aboutParent.appendChild(keyboard); + + const headingPadding = this.createElement("div"); + headingPadding.style = "clear:both;"; + + playerTitle.appendChild(gamepadTitle); + playerTitle.appendChild(leftPadding); + playerTitle.appendChild(aboutParent); + playerTitle.appendChild(headingPadding); + + + player.appendChild(playerTitle); + + for (const k in buttons) { + const buttonText = this.createElement("div"); + buttonText.setAttribute("data-id", k); + buttonText.setAttribute("data-index", i); + buttonText.setAttribute("data-label", buttons[k]); + buttonText.style = "margin-bottom:10px;"; + buttonText.classList.add("ejs_control_bar"); + + + const title = this.createElement("div"); + title.style = "width:25%;float:left;font-size:12px;"; + const label = this.createElement("label"); + label.innerText = buttons[k]+":"; + title.appendChild(label); + + const textBoxes = this.createElement("div"); + textBoxes.style = "width:50%;float:left;"; + + const textBox1Parent = this.createElement("div"); + textBox1Parent.style = "width:50%;float:left;padding: 0 5px;"; + const textBox1 = this.createElement("input"); + textBox1.style = "text-align:center;height:25px;width: 100%;"; + textBox1.type = "text"; + textBox1.setAttribute("readonly", ""); + textBox1.setAttribute("placeholder", ""); + textBox1Parent.appendChild(textBox1); + + const textBox2Parent = this.createElement("div"); + textBox2Parent.style = "width:50%;float:left;padding: 0 5px;"; + const textBox2 = this.createElement("input"); + textBox2.style = "text-align:center;height:25px;width: 100%;"; + textBox2.type = "text"; + textBox2.setAttribute("readonly", ""); + textBox2.setAttribute("placeholder", ""); + textBox2Parent.appendChild(textBox2); + + if (this.controls[i][k] && this.controls[i][k].value) { + textBox2.value = this.controls[i][k].value; + } + if (this.controls[i][k] && this.controls[i][k].value2) { + textBox1.value = this.controls[i][k].value2; + } + + textBoxes.appendChild(textBox1Parent); + textBoxes.appendChild(textBox2Parent); + + const padding = this.createElement("div"); + padding.style = "clear:both;"; + textBoxes.appendChild(padding); + + const setButton = this.createElement("div"); + setButton.style = "width:25%;float:left;"; + const button = this.createElement("button"); + button.innerText = "Set"; + setButton.appendChild(button); + + const padding2 = this.createElement("div"); + padding2.style = "clear:both;"; + + buttonText.appendChild(title); + buttonText.appendChild(textBoxes); + buttonText.appendChild(setButton); + buttonText.appendChild(padding2); + + player.appendChild(buttonText); + + this.addEventListener(buttonText, "mousedown", (e) => { + e.preventDefault(); + this.controlPopup.parentElement.removeAttribute("hidden"); + this.controlPopup.innerText = "[ " + buttons[k] + " ]\ntest"; + this.controlPopup.setAttribute("button-num", k); + this.controlPopup.setAttribute("player-num", i); + this.updateTextBoxes = () => { + if (this.controls[i][k] && this.controls[i][k].value) { + textBox2.value = this.controls[i][k].value; + } + if (this.controls[i][k] && this.controls[i][k].value2) { + textBox1.value = this.controls[i][k].value2; + } + delete this.updateTextBoxes; + } + }) + } + controls.appendChild(player); + player.setAttribute("hidden", ""); + playerDivs.push(player); + } + body.appendChild(controls); + + selectedPlayer = 0; players[0].classList.add("ejs_control_selected"); + playerDivs[0].removeAttribute("hidden"); + const popup = this.createElement('div'); + popup.classList.add("ejs_popup_container"); + const popupMsg = this.createElement("div"); + popupMsg.classList.add("ejs_popup_box"); + popupMsg.innerText = "yes"; + popup.setAttribute("hidden", ""); + this.controlPopup = popupMsg; + popup.appendChild(popupMsg); + this.controlMenu.appendChild(popup); + } + defaultControllers = { + 0: { + 0: { + 'value': 'x' + }, + 1: { + 'value': 's' + }, + 2: { + 'value': 'v' + }, + 3: { + 'value': 'enter' + }, + 4: { + 'value': 'arrowup' + }, + 5: { + 'value': 'arrowdown' + }, + 6: { + 'value': 'arrowleft' + }, + 7: { + 'value': 'arrowright' + }, + 8: { + 'value': 'z' + }, + 9: { + 'value': 'a' + }, + 10: { + 'value': 'q' + }, + 11: { + 'value': 'e' + }, + 12: { + 'value': 'e' + }, + 13: { + 'value': 'w' + }, + 14: {}, + 15: {}, + 16: { + 'value': 'h' + }, + 17: { + 'value': 'f' + }, + 18: { + 'value': 'g' + }, + 19: { + 'value': 't' + }, + 20: {'value': 'l'}, + 21: {'value': 'j'}, + 22: {'value': 'k'}, + 23: {'value': 'i'}, + 24: {}, + 25: {}, + 26: {} + }, + 1: {}, + 2: {}, + 3: {} + } + controls; + keyChange(e) { + if (!this.started) return; + e.preventDefault(); + if (this.controlPopup.parentElement.getAttribute("hidden") === null) { + const num = this.controlPopup.getAttribute("button-num"); + const player = this.controlPopup.getAttribute("player-num"); + if (!this.controls[player][num]) { + this.controls[player][num] = {}; + } + this.controls[player][num].value = e.key.toLowerCase(); + this.controlPopup.parentElement.setAttribute("hidden", ""); + this.updateTextBoxes(); + return; + } + const special = [16, 17, 18, 19, 20, 21, 22, 23]; + for (let i=0; i<4; i++) { + for (let j=0; j<26; j++) { + if (this.controls[i][j] && this.controls[i][j].value === e.key.toLowerCase()) { + this.gameManager.simulateInput(i, j, (e.type === 'keyup' ? 0 : (special.includes(j) ? 0x7fff : 1))); + } + } + } + } - }