diff --git a/index.html b/index.html
index 8fb8f05..cb1cd1c 100644
--- a/index.html
+++ b/index.html
@@ -6,7 +6,6 @@
EJS_player = '#game';
EJS_core = 'nes';
EJS_gameUrl = 'mega_mountain.nes';
- EJS_pathtodata = 'data/';
EJS_DEBUG_XX = true;
diff --git a/src/GameManager.js b/src/GameManager.js
new file mode 100644
index 0000000..08e0b7b
--- /dev/null
+++ b/src/GameManager.js
@@ -0,0 +1,77 @@
+class EJS_GameManager {
+ constructor(Module) {
+ this.Module = Module;
+ this.FS = this.Module.FS;
+ this.functions = {
+ restart: this.Module.cwrap('system_restart', '', []),
+ 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', '', [])
+ }
+ }
+ restart() {
+ this.functions.restart();
+ }
+ getState() {
+ return new Promise(async (resolve, reject) => {
+ const stateInfo = (await this.getStateInfo()).split('|')
+ let state;
+ let size = stateInfo[0] >> 0;
+ if (size > 0) {
+ state = new Uint8Array(size);
+ let start = stateInfo[1] >> 0;
+ for (let i=0; i {
+ let a;
+ let b = setInterval(() => {
+ a = this.functions.getStateInfo();
+ if (a) {
+ clearInterval(b);
+ resolve(a);
+ }
+ }, 50)
+ });
+ }
+ loadState(state) {
+ try {
+ this.FS.unlink('game.state');
+ } catch(e){}
+ this.FS.writeFile('/game.state', state);
+ this.functions.loadState("game.state", 0);
+ setTimeout(() => {
+ this.FS.unlink('game.state');
+ }, 5000)
+ }
+ screenshot() {
+ this.functions.screenshot();
+ return this.FS.readFile('screenshot.png');
+ }
+ quickSave() {
+ (async () => {
+ let slot = 0;
+ let name = slot + '-quick.state';
+ try {
+ this.FS.unlink(name);
+ } catch (e) {}
+ let data = await this.getState();
+ this.FS.writeFile('/'+name, data);
+ })();
+ }
+ quickLoad() {
+ (async () => {
+ let slot = 0;
+ let name = slot + '-quick.state';
+ this.functions.loadState(name, 0);
+ })();
+ }
+
+}
+
+window.EJS_GameManager = EJS_GameManager;
diff --git a/src/css/main.css b/src/css/main.css
index 9137a80..1c34175 100644
--- a/src/css/main.css
+++ b/src/css/main.css
@@ -3,7 +3,7 @@
overflow: hidden;
--ejs-primary-color: 26,175,255;
position: relative;
- font-family: Avenir,"Avenir Next","Helvetica Neue","Segoe UI",Helvetica,Arial,sans-serif;
+ font-family: Avenir, "Avenir Next", "Helvetica Neue", "Segoe UI", Helvetica, Arial, sans-serif;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
@@ -105,3 +105,96 @@
box-shadow: 0 0 0 5px rgba(var(--ejs-primary-color),0.5);
outline: 0;
}
+
+.ejs_menu_bar {
+ padding: 15px 10px 10px;
+ background: linear-gradient(rgba(0,0,0,0),rgba(0,0,0,0.7));
+ border-bottom-left-radius: inherit;
+ border-bottom-right-radius: inherit;
+ bottom: 0;
+ color: #fff;
+ left: 0;
+ position: absolute;
+ right: 0;
+ transition: opacity .4s ease-in-out,transform .4s ease-in-out;
+ z-index: 3;
+ align-items: center;
+ display: flex;
+ justify-content: flex-start;
+ text-align: center;
+}
+
+.ejs_menu_bar svg {
+ display: block;
+ fill: currentColor;
+ height: 18px;
+ pointer-events: none;
+ width: 18px;
+}
+
+.ejs_menu_bar_hidden {
+ opacity: 0;
+ pointer-events: none;
+ transform: translateY(100%);
+}
+
+.ejs_menu_button {
+ margin-right: 2px;
+ touch-action: manipulation;
+ background: transparent;
+ border: 0;
+ border-radius: 3px;
+ color: inherit;
+ cursor: pointer;
+ flex-shrink: 0;
+ overflow: visible;
+ padding: 7px;
+ position: relative;
+ transition: all .3s ease;
+}
+
+.ejs_menu_button:hover {
+ background: rgba(var(--ejs-primary-color),1);
+ color: #fff;
+}
+
+.ejs_menu_button:hover .ejs_menu_text {
+ transform: translate(0,0) scale(1);
+ opacity: 1;
+}
+
+.ejs_menu_text {
+ left: 0;
+ background: rgba(255,255,255,0.9);
+ border-radius: 3px;
+ bottom: 100%;
+ box-shadow: 0 1px 2px rgba(0,0,0,0.15);
+ color: #4f5b5f;
+ font-size: 14px;
+ font-weight: 500;
+ line-height: 1.3;
+ margin-bottom: 10px;
+ opacity: 0;
+ padding: 5px 7.5px;
+ pointer-events: none;
+ position: absolute;
+ transform: translate(0,10px) scale(0.8);
+ transform-origin: 0 100%;
+ transition: transform .2s .1s ease,opacity .2s .1s ease;
+ white-space: nowrap;
+ z-index: 2;
+}
+
+.ejs_menu_text::before {
+ border-left: 4px solid transparent;
+ border-right: 4px solid transparent;
+ border-top: 4px solid rgba(255,255,255,0.9);
+ bottom: -4px;
+ content: '';
+ height: 0;
+ left: 16px;
+ position: absolute;
+ transform: translateX(-50%);
+ width: 0;
+ z-index: 2;
+}
diff --git a/src/emulator.js b/src/emulator.js
index 87559e3..80806f5 100644
--- a/src/emulator.js
+++ b/src/emulator.js
@@ -2,6 +2,13 @@ class EmulatorJS {
createElement(type) {
return document.createElement(type);
}
+ addEventListener(element, listener, callback) {
+ const listeners = listener.split(" ");
+ for (let i=0; i {
if (res === -1) {
@@ -247,13 +254,13 @@ class EmulatorJS {
'preRun': [],
'postRun': [],
'canvas': this.canvas,
- 'print': function(msg) {
- if (window.EJS_DEBUG_XX === true) {
+ 'print': (msg) => {
+ if (this.debug) {
console.log(msg);
}
},
- 'printErr': function(msg) {
- if (window.EJS_DEBUG_XX === true) {
+ 'printErr': (msg) => {
+ if (this.debug) {
console.log(msg);
}
},
@@ -269,6 +276,7 @@ class EmulatorJS {
console.log(a, b, c)
}
};
+ this.Module = window.Module;
}
startGame() {
this.textElem.remove();
@@ -276,11 +284,12 @@ class EmulatorJS {
this.game.classList.remove("ejs_game");
this.game.appendChild(this.canvas);
const args = [];
- if (window.EJS_DEBUG_XX === true) args.push('-v');
+ if (this.debug) args.push('-v');
args.push('/game');
Module.callMain(args);
Module.resumeMainLoop();
this.started = true;
+ this.paused = false;
Module.setCanvasSize(800, 600);
let i=0;
// this needs to be fixed. Ugh.
@@ -292,6 +301,7 @@ class EmulatorJS {
}
bindListeners() {
this.createContextMenu();
+ this.createBottomMenuBar();
//keyboard, etc...
}
createContextMenu() {
@@ -305,41 +315,139 @@ class EmulatorJS {
}
e.preventDefault();
})
+ const hideMenu = () => {
+ this.elements.contextmenu.style.display = "none";
+ }
this.addEventListener(this.elements.contextmenu, 'contextmenu', (e) => e.preventDefault());
this.addEventListener(this.elements.parent, 'contextmenu', (e) => e.preventDefault());
- this.addEventListener(this.game, 'mousedown', (e) => {
- this.elements.contextmenu.style.display = "none";
- })
- let contextHtml = ['']
- let contextFunctions = []
+ this.addEventListener(this.game, 'mousedown', hideMenu);
+ const parent = this.createElement("ul");
const addButton = (title, hidden, functi0n) => {
+ //'+title+'
+ const li = this.createElement("li");
+ if (hidden) li.hidden = true;
+ const a = this.createElement("a");
if (functi0n instanceof Function) {
- contextFunctions.push(functi0n);
- } else {
- contextFunctions.push(() => {});
- }
- let i = contextHtml.length - 1;
- if (hidden) {
- contextHtml.splice(i, 0, ''+title+'');
- } else {
- contextHtml.splice(i, 0, ''+title+'');
+ this.addEventListener(li, 'click', functi0n);
}
+ a.href = "#";
+ a.onclick = "return false";
+ a.innerText = title;
+ li.appendChild(a);
+ parent.appendChild(li);
+ hideMenu();
}
- addButton("test 1", false, () => console.log("1"));
- addButton("test 2", false, () => console.log("2"));
- addButton("test 3", false, () => console.log("3"));
- addButton("test 4", false, () => console.log("4"));
+ let screenshotUrl;
+ addButton("Take Screenshot", false, () => {
+ if (screenshotUrl) URL.revokeObjectURL(screenshotUrl);
+ const screenshot = this.gameManager.screenshot();
+ const blob = new Blob([screenshot]);
+ screenshotUrl = URL.createObjectURL(blob);
+ const a = this.createElement("a");
+ a.href = screenshotUrl;
+ a.download = "screenshot.png";
+ a.click();
+ hideMenu();
+ });
+ addButton("Quick Save", false, () => {
+ this.gameManager.quickSave();
+ hideMenu();
+ });
+ addButton("Quick Load", false, () => {
+ this.gameManager.quickLoad();
+ hideMenu();
+ });
+ addButton("EmulatorJS", false, () => console.log("4"));
- this.elements.contextmenu.innerHTML = contextHtml.join('');
-
- let buttons = this.elements.contextmenu.getElementsByTagName('li')
- for (let i=0; i {
+ const file = this.createElement("input");
+ file.type = "file";
+ this.addEventListener(file, "change", (e) => {
+ resolve(e.target.files[0]);
+ })
+ file.click();
+ })
+ }
+ createBottomMenuBar() {
+ this.elements.menu = this.createElement("div");
+ this.elements.menu.classList.add("ejs_menu_bar");
+ this.elements.menu.classList.add("ejs_menu_bar_hidden");
-
+ let timeout = null;
+ const hide = () => {
+ if (this.paused) return;
+ this.elements.menu.classList.add("ejs_menu_bar_hidden");
+ }
+
+ this.addEventListener(this.elements.parent, 'mousemove click', (e) => {
+ if (!this.started) return;
+ if (timeout !== null) clearTimeout(timeout);
+ timeout = setTimeout(hide, 3000);
+ this.elements.menu.classList.remove("ejs_menu_bar_hidden");
+ })
+ this.menu = {
+ close: () => {
+ if (!this.started) return;
+ if (timeout !== null) clearTimeout(timeout);
+ this.elements.menu.classList.remove("ejs_menu_bar_hidden");
+ },
+ open: () => {
+ if (!this.started) return;
+ if (timeout !== null) clearTimeout(timeout);
+ timeout = setTimeout(hide, 3000);
+ this.elements.menu.classList.remove("ejs_menu_bar_hidden");
+ }
+ }
+ this.elements.parent.appendChild(this.elements.menu);
+
+ //Now add buttons
+ const addButton = (title, image, callback) => {
+ const button = this.createElement("button");
+ button.type = "button";
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ svg.setAttribute("role", "presentation");
+ svg.setAttribute("focusable", "false");
+ svg.innerHTML = image;
+ const text = this.createElement("span");
+ text.innerText = title;
+ text.classList.add("ejs_menu_text");
+
+ button.classList.add("ejs_menu_button");
+ button.appendChild(svg);
+ button.appendChild(text);
+ this.elements.menu.appendChild(button);
+ if (callback instanceof Function) {
+ this.addEventListener(button, 'click', callback);
+ }
+ }
+
+ //todo. Center text on not restart button
+
+ addButton("Restart", '', () => {
+ this.gameManager.restart();
+ });
+ let stateUrl;
+ addButton("Save State", '', async () => {
+ if (stateUrl) URL.revokeObjectURL(stateUrl);
+ const state = await this.gameManager.getState();
+ const blob = new Blob([state]);
+ stateUrl = URL.createObjectURL(blob);
+ const a = this.createElement("a");
+ a.href = stateUrl;
+ a.download = "game.state";
+ a.click();
+ });
+ addButton("Load State", '', async () => {
+ const file = await this.selectFile();
+ const state = new Uint8Array(await file.arrayBuffer());
+ this.gameManager.loadState(state);
+ });
+
+ }
}
diff --git a/src/loader.js b/src/loader.js
index aafaede..58c2703 100644
--- a/src/loader.js
+++ b/src/loader.js
@@ -35,6 +35,7 @@
if (('undefined' != typeof EJS_DEBUG_XX && true === EJS_DEBUG_XX) || true) {
await loadScript('emulator.js');
+ await loadScript('GameManager.js');
await loadStyle('css/main.css');
}
const config = {};