wm 1.1.1, fixes and improventments

This commit is contained in:
Annie 2025-03-09 15:18:05 +03:00
parent fda61ff4eb
commit 65e9329a56
15 changed files with 179 additions and 51 deletions

1
.gitignore vendored
View file

@ -92,6 +92,7 @@ out
# Nuxt.js build / generate output
.nuxt
dist
nfdist
# Gatsby files
.cache/

View file

@ -2,12 +2,12 @@
"name": "neofunkin-1",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"main": "nfdist/index.js",
"build": {
"appId": "asper.games.neofunkin",
"productName": "NeoFunkin [EARLY ALPHA]",
"productName": "NeoFunkin",
"files": [
"dist/**/*"
"nfdist/**/*"
],
"win": {
"target": "portable"
@ -18,7 +18,7 @@
},
"scripts": {
"build": "tsc && node scripts/finalizeBuild.js",
"electron": "electron dist",
"electron": "electron nfdist",
"bundle": "electron-builder"
},
"keywords": [],

View file

@ -2,7 +2,7 @@ const fs = require('fs-extra');
const path = require('path');
const srcDir = path.join(process.cwd(), 'src');
const destDir = path.join(process.cwd(), 'dist');
const destDir = path.join(process.cwd(), 'nfdist');
const copyAssets = async () => {
try {

View file

@ -5,7 +5,14 @@ import path from 'path';
import { createServer } from 'http';
import WebSocket from 'ws';
import { displayMainMenu } from './screens/mainmenu';
import { start } from 'repl';
import { displayOptions } from './screens/options';
import { displayPlayMenu } from './screens/play';
import EventDispatcher from './lib/nfevents';
import { wdata } from './lib/windowManager';
import { screenSize, wait } from './lib/utils';
const eventDispatcher = new EventDispatcher();
declare global {
namespace NodeJS {
interface Process {
@ -30,16 +37,21 @@ async function startWebRoot() {
app.use(express.static(path.join(__dirname, 'webroot')));
app.get('/eval', (req: express.Request, res: express.Response): any => {
app.get('/debug', (req: express.Request, res: express.Response): any => {
console.log(screenSize());
res.status(200).send('OK');
});
app.get('/eval', async (req: express.Request, res: express.Response): Promise<any> => {
const code = req.headers['x-code'] as string;
if (!code) return res.status(400).send('No code provided');
try {
const result = (function(require) {
return eval(code);
const result = (async function(require) {
return await eval(decodeURIComponent(code));
})(require);
res.status(200).send(result as string);
res.status(200).send(await result as string);
} catch (error: any) {
res.status(500).send(error.message as string);
}
@ -47,16 +59,19 @@ async function startWebRoot() {
wss.on('connection', (ws: WebSocket) => {
console.log('[ws] client++');
eventDispatcher.dispatch('nf_ws_connection', 'open');
ws.on('message', (message) => {
const msg = JSON.parse(message.toString());
if (!msg.ch) msg.ch = 'public'
console.log('[ws]', msg.ch, '-', msg.m);
ws.send(JSON.stringify(msg));
console.log('[ws]', msg.ch, '-', msg.m);
eventDispatcher.dispatch('nf_ws_message', msg);
});
ws.on('close', () => {
console.log('[ws] client--');
eventDispatcher.dispatch('nf_ws_connection', 'close');
});
});
@ -67,6 +82,36 @@ app.on('ready', async () => {
try {
startWebRoot();
displayMainMenu();
eventDispatcher.on('nf_ws_message', async (message) => {
// screenManager
if (message.m.startsWith('OPENSCREEN')) {
const screen = message.m.split(':')[1];
console.log('[screenManager] Trying to dispatch screen', screen)
const bwdata = { ...wdata };
switch (screen) {
case 'Options':
displayOptions();
break;
case 'Play':
displayPlayMenu();
break;
case 'MainMenu':
displayMainMenu();
break;
default:
console.error('[screenManager] Unknown screen:', screen);
displayCrashScreen();
};
await wait(200);
Object.keys(bwdata).forEach(async (id) => {
bwdata[id].win.destroy();
delete bwdata[id]; // Cleanup
});
console.log('[screenManager] Cleaned old windows');
}
});
} catch (e: any) {
console.error(e);
process.nfenv.crash(e.message);

View file

@ -15,9 +15,8 @@ export default async function displayCrashScreen() {
customProps: { maximizable: false }
});
const button = wm.create({
w: 10,
h: 5,
whp: true,
w: Math.round(toP(sw, 10)),
h: Math.round(toP(sh, 5)),
noBorder: true,
noBackground: true,
onCreate: (win: WindowData) => { win.win.loadURL(`http://localhost:${process.nfenv.serverPort}/buttons/exit.html?code=1`) },

26
src/lib/nfevents.ts Normal file
View file

@ -0,0 +1,26 @@
type Listener<T> = (data: T) => void;
export default class EventDispatcher<T = any> {
private listeners: { [event: string]: Listener<T>[] } = {};
on(event: string, listener: Listener<T>): void {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(listener);
}
off(event: string, listener: Listener<T>): void {
if (!this.listeners[event]) return;
this.listeners[event] = this.listeners[event].filter(l => l !== listener);
}
dispatch(event: string, data: T): void {
if (!this.listeners[event]) return;
this.listeners[event].forEach(listener => {
listener(data);
});
}
}

View file

@ -16,16 +16,17 @@ const wm = {
create: function (c: wm_create_conf) {
let wid = Array.from({ length: 32 }, () => '0123456789abcdef'[Math.floor(Math.random() * 16)]).join('');
while (wdata[wid]) {
console.warn('[wm] create: got a wid duplicate');
wid = Array.from({ length: 32 }, () => '0123456789abcdef'[Math.floor(Math.random() * 16)]).join('');
}
const { width: sw, height: sh } = screenSize();
console.log('[wm] create', wid);
const { width: sw, height: sh } = screenSize();
const win = new BrowserWindow({
...c.customProps,
width: c.whp ? toP(sw, c.w!) : c.w,
height: c.whp ? toP(sh, c.h!) : c.h,
x: c.xyp ? toP(sw, c.x!) : c.x,
y: c.xyp ? toP(sh, c.y!) : c.y,
width: Math.round(c.whp ? toP(sw, c.w) : c.w),
height: Math.round(c.whp ? toP(sh, c.h) : c.h),
x: c.x ? Math.round(c.xyp ? toP(sw, c.x) : c.x) : undefined,
y: c.y ? Math.round(c.xyp ? toP(sh, c.y) : c.y) : undefined,
frame: !c.noBorder,
transparent: c.noBackground,
webPreferences: {
@ -56,7 +57,7 @@ const wm = {
if (!wdata[id]) return
console.log('[wm] destroy', id);
const win = wdata[id];
if (typeof win.conf.onDestroy === 'function') await win.conf.onDestroy!(win.win);
if (typeof win.conf.onDestroy === 'function') await win.conf.onDestroy!(win);
win.win.destroy();
delete wdata[id];
},
@ -70,19 +71,19 @@ const wm = {
const sX = wb.x;
const sY = wb.y;
c.x = c.x === undefined ? sX : c.x;
c.y = c.y === undefined ? sY : c.y;
c.x = c.x === undefined ? sX : Math.round(c.x);
c.y = c.y === undefined ? sY : Math.round(c.y);
const duration = c.duration || 500;
const tick = c.tick || 1000 / 30;
const totalSteps = Math.floor(duration / tick);
// is relative? add current window's x, y
// is relative? add current window's x/y
// | relative | | absolute
// | | is %? | | | is %?
// | | | % | px | | | % px
let targetX = c.r ? c.p ? toP(sw, c.x) + cwx : c.x + cwx : c.p ? toP(sw, c.x) : c.x;
let targetY = c.r ? c.p ? toP(sh, c.y) + cwy : c.y + cwy : c.p ? toP(sh, c.y) : c.y;
let targetX = Math.round(c.r ? c.p ? toP(sw, c.x) + cwx : c.x + cwx : c.p ? toP(sw, c.x) : c.x);
let targetY = Math.round(c.r ? c.p ? toP(sh, c.y) + cwy : c.y + cwy : c.p ? toP(sh, c.y) : c.y);
if (c.fromCenter) {
wb = win.win.getBounds();
@ -131,17 +132,17 @@ const wm = {
}
const wb = win.win.getBounds();
const sW = wb.width, sH = wb.height;
c.w = c.w === undefined ? sW : c.w;
c.h = c.h === undefined ? sH : c.h;
c.w = c.w === undefined ? sW : Math.round(c.w);
c.h = c.h === undefined ? sH : Math.round(c.h);
const duration = c.duration || 500;
const tick = c.tick || 1000 / 30;
const totalSteps = Math.floor(duration / tick);
const { width: sw, height: sh } = screenSize();
const targetW = toP(c.p ? sw : 1, c.w);
const targetH = toP(c.p ? sh : 1, c.h);
const targetW = Math.round(toP(c.p ? sw : 1, c.w));
const targetH = Math.round(toP(c.p ? sh : 1, c.h));
const centerX = wb.x + wb.width / 2;
const centerY = wb.y + wb.height / 2;
const centerX = Math.round(wb.x + wb.width / 2);
const centerY = Math.round(wb.y + wb.height / 2);
const getAnchoredPosition = (curWidth: number, curHeight: number) => {
let newX = wb.x;

View file

@ -1,5 +1,5 @@
import { app } from "electron";
import { easeOutCirc, easeOutQuart, easeOutQuint } from "../lib/animations";
import { easeOutQuart } from "../lib/animations";
import { screenSize, toP, wait } from "../lib/utils";
import wm, {WindowData, wdata} from "../lib/windowManager";
@ -20,9 +20,8 @@ export async function displayMainMenu() {
const { height: sh, width: sw } = screenSize();
const pb = wm.create({
w: 10,
h: 5,
whp: true,
w: Math.round(toP(sw, 10)),
h: Math.round(toP(sh, 5)),
noBorder: true,
noBackground: true,
onCreate: (win: WindowData) => { win.win.loadURL(`http://localhost:${process.nfenv.serverPort}/buttons/openscreen.html?screen=Play`) },
@ -34,9 +33,8 @@ export async function displayMainMenu() {
wm.follow.start(pb, mw);
const eb = wm.create({
w: 10,
h: 5,
whp: true,
w: Math.round(toP(sw, 10)),
h: Math.round(toP(sh, 5)),
noBorder: true,
noBackground: true,
onCreate: (win: WindowData) => { win.win.loadURL(`http://localhost:${process.nfenv.serverPort}/buttons/exit.html`) },
@ -48,9 +46,8 @@ export async function displayMainMenu() {
wm.follow.start(eb, mw);
const ob = wm.create({
w: 10,
h: 5,
whp: true,
w: Math.round(toP(sw, 10)),
h: Math.round(toP(sh, 5)),
noBorder: true,
noBackground: true,
onCreate: (win: WindowData) => { win.win.loadURL(`http://localhost:${process.nfenv.serverPort}/buttons/openscreen.html?screen=Options`) },

19
src/screens/options.ts Normal file
View file

@ -0,0 +1,19 @@
import { app } from "electron";
import { easeOutQuart } from "../lib/animations";
import { wait } from "../lib/utils";
import wm, {WindowData, wdata} from "../lib/windowManager";
export async function displayOptions() {
await app.whenReady();
const mw = wm.create({
w: 20,
h: 20,
whp: true,
onCreate: (win: WindowData) => { win.win.loadURL(`http://localhost:${process.nfenv.serverPort}/screens/options/`) },
onClose: () => { process.exit(0) }
});
wm.move({ id: mw, x: 50, y: 50, p: true, fromCenter: true });
await wait(500);
wm.resize({ id: mw, w: 40, h: 40, smooth: true, p: true, ease: easeOutQuart });
await wait(500);
};

19
src/screens/play.ts Normal file
View file

@ -0,0 +1,19 @@
import { app } from "electron";
import { easeOutQuart } from "../lib/animations";
import { wait } from "../lib/utils";
import wm, {WindowData, wdata} from "../lib/windowManager";
export async function displayPlayMenu() {
await app.whenReady();
const mw = wm.create({
w: 20,
h: 20,
whp: true,
onCreate: (win: WindowData) => { win.win.loadURL(`http://localhost:${process.nfenv.serverPort}/screens/play/`) },
onClose: () => { process.exit(0) }
});
wm.move({ id: mw, x: 50, y: 50, p: true, fromCenter: true });
await wait(500);
wm.resize({ id: mw, w: 40, h: 40, smooth: true, p: true, ease: easeOutQuart });
await wait(500);
};

View file

@ -15,7 +15,7 @@ export type wm_create_conf = {
onUnfocus?: Function; // USER unfocused on the window
onFocus?: Function; // USER focused on the window
onClose?: Function; // USER pressed 'x'
onDestroy?: Function; // USER pressed 'x' OR wm.destroy(...)
onDestroy?: Function; // wm.destroy(...)
onMove?: Function; // Window moves
onResize?: Function; // Window resizes
customProps?: object; // for example for setting maximizable to false

View file

@ -15,14 +15,15 @@
<body>
<button style="width: 100vw; height: 100vh; margin: 0;">Loading</button>
<script>
const screen = new URL(window.location.href).searchParams.get('screen');
document.querySelector('button').textContent = screen || 'USE ?screen=...';
const url = new URL(window.location.href);
const screen = url.searchParams.get('screen');
const display = url.searchParams.get('display') ?? screen;
document.querySelector('button').textContent = display || 'USE ?screen=...&display=...';
document.querySelector('button').addEventListener('click', () => {
const socket = new WebSocket(`ws://${window.location.host}/`);
socket.onopen = function() {
socket.send(JSON.stringify({ m: `OPENSCREEN_${screen || 'UNDEFINED'}` }))
socket.send(JSON.stringify({ m: `OPENSCREEN:${screen || 'UNDEFINED'}` }))
};
socket.onmessage = function(event) { window.close() };
});
</script>
</body>

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>NeoFunkin</title>
</head>
<body>
OPTIONS
<iframe src="/buttons/openscreen.html?screen=MainMenu&display=Back"></iframe>
</body>
</html>

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>NeoFunkin</title>
</head>
<body>
PLAY
<iframe src="/buttons/openscreen.html?screen=MainMenu&display=Back"></iframe>
</body>
</html>

View file

@ -6,7 +6,7 @@
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"outDir": "./nfdist",
"rootDir": "./src"
},
"include": ["src/**/*"],