add input, pausing, and fullscreen!

This commit is contained in:
Ethan O'Brien 2023-06-23 11:33:20 -05:00
parent a6d52db1f0
commit 0dcb1df6b7
3 changed files with 349 additions and 18 deletions

View file

@ -7,7 +7,9 @@ class EJS_GameManager {
getStateInfo: this.Module.cwrap('get_state_info', 'string', []), //these names are dumb getStateInfo: this.Module.cwrap('get_state_info', 'string', []), //these names are dumb
saveStateInfo: this.Module.cwrap('save_state_info', 'null', []), saveStateInfo: this.Module.cwrap('save_state_info', 'null', []),
loadState: this.Module.cwrap('load_state', 'number', ['string', 'number']), 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() { restart() {
@ -71,6 +73,12 @@ class EJS_GameManager {
this.functions.loadState(name, 0); this.functions.loadState(name, 0);
})(); })();
} }
simulateInput(player, index, value) {
this.functions.simulateInput(player, index, value);
}
toggleMainLoop(playing) {
this.functions.toggleMainLoop(playing);
}
} }

View file

@ -275,7 +275,6 @@
} }
.ejs_control_body input[type='text'] { .ejs_control_body input[type='text'] {
overflow: auto;
background-color: #fff; background-color: #fff;
border: 1px solid #000; border: 1px solid #000;
font-size: 12px; font-size: 12px;
@ -307,3 +306,18 @@
.ejs_control_selected a { .ejs_control_selected a {
color: #000 !important; 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;
}

View file

@ -68,6 +68,7 @@ class EmulatorJS {
} }
} }
constructor(element, config) { constructor(element, config) {
window.EJS_TESTING = this;
this.debug = (window.EJS_DEBUG_XX === true); this.debug = (window.EJS_DEBUG_XX === true);
this.setElements(element); this.setElements(element);
this.started = false; this.started = false;
@ -77,6 +78,7 @@ class EmulatorJS {
this.canvas = this.createElement('canvas'); this.canvas = this.createElement('canvas');
this.canvas.classList.add('ejs_canvas'); this.canvas.classList.add('ejs_canvas');
this.bindListeners(); this.bindListeners();
this.fullscreen = false;
this.game.classList.add("ejs_game"); this.game.classList.add("ejs_game");
@ -222,10 +224,21 @@ class EmulatorJS {
} }
getCore() { getCore() {
const core = this.config.system; const core = this.config.system;
//switch case or an object holding this data const options = {
if (core === 'nes') { 'nes': 'fceumm',
return '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() { downloadRom() {
this.gameManager = new window.EJS_GameManager(this.Module); this.gameManager = new window.EJS_GameManager(this.Module);
@ -286,23 +299,25 @@ class EmulatorJS {
const args = []; const args = [];
if (this.debug) args.push('-v'); if (this.debug) args.push('-v');
args.push('/game'); args.push('/game');
Module.callMain(args); this.Module.callMain(args);
Module.resumeMainLoop(); this.Module.resumeMainLoop();
this.started = true; this.started = true;
this.paused = false; this.paused = false;
Module.setCanvasSize(800, 600);
let i=0; //this needs to be fixed...
// this needs to be fixed. Ugh. setInterval(() => {
let j = setInterval(function() { // some cores have a messed up screen size on load (for example - gba) if (document.fullscreenElement !== null) {
if (i>20) clearInterval(j); this.Module.setCanvasSize(this.canvas.getBoundingClientRect().width-10, this.canvas.getBoundingClientRect().height-10);
i++; } else {
Module.setCanvasSize(800, 600); this.Module.setCanvasSize(800, 600);
}
}, 100) }, 100)
} }
bindListeners() { bindListeners() {
this.createContextMenu(); this.createContextMenu();
this.createBottomMenuBar(); this.createBottomMenuBar();
this.createControlSettingMenu(); this.createControlSettingMenu();
this.addEventListener(document, "keydown keyup", this.keyChange.bind(this));
//keyboard, etc... //keyboard, etc...
} }
createContextMenu() { createContextMenu() {
@ -411,8 +426,9 @@ class EmulatorJS {
popup.appendChild(button); popup.appendChild(button);
} }
if (!hidden) { if (!hidden) {
popup.style.display = "none";
this.currentPopup = popup; this.currentPopup = popup;
} else {
popup.style.display = "none";
} }
return main; return main;
@ -478,6 +494,7 @@ class EmulatorJS {
if (callback instanceof Function) { if (callback instanceof Function) {
this.addEventListener(button, 'click', callback); this.addEventListener(button, 'click', callback);
} }
return button;
} }
//todo. Center text on not restart button //todo. Center text on not restart button
@ -485,6 +502,26 @@ class EmulatorJS {
addButton("Restart", '<svg viewBox="0 0 512 512"><path d="M496 48V192c0 17.69-14.31 32-32 32H320c-17.69 0-32-14.31-32-32s14.31-32 32-32h63.39c-29.97-39.7-77.25-63.78-127.6-63.78C167.7 96.22 96 167.9 96 256s71.69 159.8 159.8 159.8c34.88 0 68.03-11.03 95.88-31.94c14.22-10.53 34.22-7.75 44.81 6.375c10.59 14.16 7.75 34.22-6.375 44.81c-39.03 29.28-85.36 44.86-134.2 44.86C132.5 479.9 32 379.4 32 256s100.5-223.9 223.9-223.9c69.15 0 134 32.47 176.1 86.12V48c0-17.69 14.31-32 32-32S496 30.31 496 48z"/></svg>', () => { addButton("Restart", '<svg viewBox="0 0 512 512"><path d="M496 48V192c0 17.69-14.31 32-32 32H320c-17.69 0-32-14.31-32-32s14.31-32 32-32h63.39c-29.97-39.7-77.25-63.78-127.6-63.78C167.7 96.22 96 167.9 96 256s71.69 159.8 159.8 159.8c34.88 0 68.03-11.03 95.88-31.94c14.22-10.53 34.22-7.75 44.81 6.375c10.59 14.16 7.75 34.22-6.375 44.81c-39.03 29.28-85.36 44.86-134.2 44.86C132.5 479.9 32 379.4 32 256s100.5-223.9 223.9-223.9c69.15 0 134 32.47 176.1 86.12V48c0-17.69 14.31-32 32-32S496 30.31 496 48z"/></svg>', () => {
this.gameManager.restart(); this.gameManager.restart();
}); });
const pauseButton = addButton("Pause", '<svg viewBox="0 0 320 512"><path d="M272 63.1l-32 0c-26.51 0-48 21.49-48 47.1v288c0 26.51 21.49 48 48 48L272 448c26.51 0 48-21.49 48-48v-288C320 85.49 298.5 63.1 272 63.1zM80 63.1l-32 0c-26.51 0-48 21.49-48 48v288C0 426.5 21.49 448 48 448l32 0c26.51 0 48-21.49 48-48v-288C128 85.49 106.5 63.1 80 63.1z"/></svg>', () => {
this.togglePlaying();
});
const playButton = addButton("Play", '<svg viewBox="0 0 320 512"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z"/></svg>', () => {
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; let stateUrl;
addButton("Save State", '<svg viewBox="0 0 448 512"><path fill="currentColor" d="M433.941 129.941l-83.882-83.882A48 48 0 0 0 316.118 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V163.882a48 48 0 0 0-14.059-33.941zM224 416c-35.346 0-64-28.654-64-64 0-35.346 28.654-64 64-64s64 28.654 64 64c0 35.346-28.654 64-64 64zm96-304.52V212c0 6.627-5.373 12-12 12H76c-6.627 0-12-5.373-12-12V108c0-6.627 5.373-12 12-12h228.52c3.183 0 6.235 1.264 8.485 3.515l3.48 3.48A11.996 11.996 0 0 1 320 111.48z"/></svg>', async () => { addButton("Save State", '<svg viewBox="0 0 448 512"><path fill="currentColor" d="M433.941 129.941l-83.882-83.882A48 48 0 0 0 316.118 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V163.882a48 48 0 0 0-14.059-33.941zM224 416c-35.346 0-64-28.654-64-64 0-35.346 28.654-64 64-64s64 28.654 64 64c0 35.346-28.654 64-64 64zm96-304.52V212c0 6.627-5.373 12-12 12H76c-6.627 0-12-5.373-12-12V108c0-6.627 5.373-12 12-12h228.52c3.183 0 6.235 1.264 8.485 3.515l3.48 3.48A11.996 11.996 0 0 1 320 111.48z"/></svg>', async () => {
if (stateUrl) URL.revokeObjectURL(stateUrl); if (stateUrl) URL.revokeObjectURL(stateUrl);
@ -505,13 +542,47 @@ class EmulatorJS {
this.controlMenu.style.display = ""; this.controlMenu.style.display = "";
}); });
const spacer = this.createElement("span");
spacer.style = "flex:1;";
this.elements.menu.appendChild(spacer);
const enter = addButton("Enter Fullscreen", '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M208 281.4c-12.5-12.5-32.76-12.5-45.26-.002l-78.06 78.07l-30.06-30.06c-6.125-6.125-14.31-9.367-22.63-9.367c-4.125 0-8.279 .7891-12.25 2.43c-11.97 4.953-19.75 16.62-19.75 29.56v135.1C.0013 501.3 10.75 512 24 512h136c12.94 0 24.63-7.797 29.56-19.75c4.969-11.97 2.219-25.72-6.938-34.87l-30.06-30.06l78.06-78.07c12.5-12.49 12.5-32.75 .002-45.25L208 281.4zM487.1 0h-136c-12.94 0-24.63 7.797-29.56 19.75c-4.969 11.97-2.219 25.72 6.938 34.87l30.06 30.06l-78.06 78.07c-12.5 12.5-12.5 32.76 0 45.26l22.62 22.62c12.5 12.5 32.76 12.5 45.26 0l78.06-78.07l30.06 30.06c9.156 9.141 22.87 11.84 34.87 6.937C504.2 184.6 512 172.9 512 159.1V23.1C512 10.74 501.3 0 487.1 0z"/></svg>', () => {
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", '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M215.1 272h-136c-12.94 0-24.63 7.797-29.56 19.75C45.47 303.7 48.22 317.5 57.37 326.6l30.06 30.06l-78.06 78.07c-12.5 12.5-12.5 32.75-.0012 45.25l22.62 22.62c12.5 12.5 32.76 12.5 45.26 .0013l78.06-78.07l30.06 30.06c6.125 6.125 14.31 9.367 22.63 9.367c4.125 0 8.279-.7891 12.25-2.43c11.97-4.953 19.75-16.62 19.75-29.56V296C239.1 282.7 229.3 272 215.1 272zM296 240h136c12.94 0 24.63-7.797 29.56-19.75c4.969-11.97 2.219-25.72-6.938-34.87l-30.06-30.06l78.06-78.07c12.5-12.5 12.5-32.76 .0002-45.26l-22.62-22.62c-12.5-12.5-32.76-12.5-45.26-.0003l-78.06 78.07l-30.06-30.06c-9.156-9.141-22.87-11.84-34.87-6.937c-11.97 4.953-19.75 16.62-19.75 29.56v135.1C272 229.3 282.7 240 296 240z"/></svg>', () => {
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() { createControlSettingMenu() {
this.controls = this.defaultControllers;
const body = this.createPopup("Control Settings", { const body = this.createPopup("Control Settings", {
"Close": () => { "Close": () => {
this.controlMenu.style.display = "none"; this.controlMenu.style.display = "none";
} }
}); }, true);
this.controlMenu = body.parentElement; this.controlMenu = body.parentElement;
body.classList.add("ejs_control_body"); body.classList.add("ejs_control_body");
@ -546,6 +617,7 @@ class EmulatorJS {
} }
let selectedPlayer; let selectedPlayer;
let players = []; let players = [];
let playerDivs = [];
const playerSelect = this.createElement("ul"); const playerSelect = this.createElement("ul");
playerSelect.classList.add("ejs_control_player_bar"); playerSelect.classList.add("ejs_control_player_bar");
@ -559,10 +631,13 @@ class EmulatorJS {
player.setAttribute("aria-controls", "controls-"+(i-1)); player.setAttribute("aria-controls", "controls-"+(i-1));
player.setAttribute("aria-selected", "false"); player.setAttribute("aria-selected", "false");
player.id = "controls-"+(i-1)+"-label"; player.id = "controls-"+(i-1)+"-label";
this.addEventListener(player, "click", () => { this.addEventListener(player, "click", (e) => {
e.preventDefault();
players[selectedPlayer].classList.remove("ejs_control_selected"); players[selectedPlayer].classList.remove("ejs_control_selected");
playerDivs[selectedPlayer].setAttribute("hidden", "");
selectedPlayer = i-1; selectedPlayer = i-1;
players[i-1].classList.add("ejs_control_selected"); players[i-1].classList.add("ejs_control_selected");
playerDivs[i-1].removeAttribute("hidden");
}) })
playerContainer.appendChild(player); playerContainer.appendChild(player);
playerSelect.appendChild(playerContainer); playerSelect.appendChild(playerContainer);
@ -570,10 +645,244 @@ class EmulatorJS {
} }
body.appendChild(playerSelect); 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 = "&nbsp;";
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; selectedPlayer = 0;
players[0].classList.add("ejs_control_selected"); 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)));
}
}
}
} }
} }