diff --git a/package.json b/package.json
index e19caa9..24b6b23 100644
--- a/package.json
+++ b/package.json
@@ -2,17 +2,30 @@
"name": "neofunkin-1",
"version": "1.0.0",
"description": "",
- "main": "index.js",
+ "main": "dist/index.js",
+ "build": {
+ "appId": "asper.games.neofunkin",
+ "productName": "NeoFunkin [EARLY ALPHA]",
+ "files": [
+ "dist/**/*"
+ ],
+ "win": {
+ "target": "portable"
+ },
+ "linux": {
+ "target": "AppImage"
+ }
+ },
"scripts": {
"build": "tsc && node scripts/finalizeBuild.js",
- "electron": "electron dist"
+ "electron": "electron dist",
+ "bundle": "electron-builder"
},
"keywords": [],
"author": "",
"license": "AGPL-3.0-only",
"packageManager": "pnpm@10.4.1",
"dependencies": {
- "electron": "^35.0.0",
"express": "^4.21.2"
},
"pnpm": {
@@ -21,8 +34,10 @@
]
},
"devDependencies": {
- "@types/electron": "^1.6.12",
+ "electron": "^35.0.0",
+ "@types/express": "^5.0.0",
"@types/node": "^22.13.9",
+ "electron-builder": "^25.1.8",
"fs-extra": "^11.3.0",
"typescript": "^5.8.2"
}
diff --git a/src/index.ts b/src/index.ts
index 05522af..7a08f04 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,60 +1,72 @@
import wm from './lib/windowManager';
-import { easeInOutQuad } from './lib/animations';
+import { wait } from './lib/utils';
import { app } from 'electron';
+import displayCrashScreen from './lib/crash';
+import express from 'express';
+import path from 'path';
+import { createServer } from 'http';
-app.on('ready', () => {
- try {
- const mw = wm.create({
- w: 10,
- h: 10,
- whp: true,
- onCreate: (win: any) => {
- win.loadURL('https://example.com');
- },
- });
-
- const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
-
- (async () => {
- await wait(200);
- wm.resize({ id: mw, w: 25, h: 25, smooth: true, p: true, fromCenter: true, ease: easeInOutQuad});
- await wait(1000);
- wm.resize({ id: mw, w: 50, h: 50, smooth: true, p: true, fromCenter: true, ease: easeInOutQuad});
- await wait(650);
- wm.move({ id: mw, x: 0, y: 50, p: true, fromCenter: true});
- wm.resize({ id: mw, w: 33, h: 100, p: true });
- wm.resize({ id: mw, w: 33, h: 10, smooth: true, p: true, ease: easeInOutQuad, anchor: 'bottom'});
- await wait(500);
- wm.move({ id: mw, x: 50, y: 50, p: true, fromCenter: true});
- wm.resize({ id: mw, w: 33, h: 100, p: true });
- wm.resize({ id: mw, w: 33, h: 10, smooth: true, p: true, ease: easeInOutQuad, anchor: 'bottom'});
- await wait(500);
- wm.move({ id: mw, x: 100, y: 50, p: true, fromCenter: true});
- wm.resize({ id: mw, w: 33, h: 100, p: true });
- wm.resize({ id: mw, w: 33, h: 10, smooth: true, p: true, ease: easeInOutQuad, anchor: 'bottom'});
- await wait(500);
- wm.resize({ id: mw, w: 100, h: 100, smooth: true, p: true, fromCenter: true, ease: easeInOutQuad});
- await wait(550);
- wm.resize({ id: mw, w: 100, h: 10, smooth: true, p: true, ease: easeInOutQuad, anchor: 'bottom'});
- await wait(550);
- wm.resize({ id: mw, w: 33, h: 10, smooth: true, p: true, fromCenter: true, ease: easeInOutQuad, anchor: 'bottom'});
- await wait(500);
- wm.move({ id: mw, x: 50, y: 50, p: true, fromCenter: true, ease: easeInOutQuad, smooth: true});
- await wait(500);
- wm.resize({ id: mw, w: 50, h: 50, smooth: true, p: true, fromCenter: true, ease: easeInOutQuad});
- await wait(510);
- wm.eval(mw, `document.querySelector('body').innerHTML = '
NeoFunkin
'`);
- await wait(500);
- wm.eval(mw, `document.querySelector('body').innerHTML = 'MrpGimlom
'`);
- await wait(100);
- wm.eval(mw, `document.querySelector('body').innerHTML = 'NeoFunkin: Window manager
'`);
- await wait(500);
- wm.eval(mw, `document.querySelector('body').innerHTML = 'NeoFunkin: Window manager
DEMO BY @TRUE1ANN, NEOFUNKIN IS PROPERTY OF ASPER'`);
- await wait(1500);
- wm.eval(mw, `document.querySelector('body').innerHTML = 'NeoFunkin: Window manager
DEMO BY @TRUE1ANN, NEOFUNKIN IS PROPERTY OF ASPER
this took so much to do omfg'`);
- })();
-
- } catch (e) {
- console.error(e);
+declare global {
+ namespace NodeJS {
+ interface Process {
+ nfenv?: any;
+ }
}
-});
\ No newline at end of file
+}
+
+process.nfenv = {
+ serverPort: 65034 as number, // todo: pick a random one instead, not for now bcuz easier to debug and use /eval endpoint
+ crash: function (reason?: string) {
+ console.error('[nfcrash] Simulating a crash with reason:', reason || '*no reason*');
+ displayCrashScreen();
+ }
+}
+
+async function startWebRoot() {
+ const app = express();
+ const server = createServer(app);
+
+ app.use(express.static(path.join(__dirname, 'webroot')));
+
+ app.get('/eval', (req: express.Request, res: express.Response): 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);
+ })(require);
+ res.status(200).send(result as string);
+ } catch (error: any) {
+ res.status(500).send(error.message as string);
+ }
+ });
+
+ // to-do: add echo webhook endpoint and endpoints to create more webhook endpoints (and also delete those)
+
+ server.listen(process.nfenv.serverPort, 'localhost');
+};
+
+startWebRoot().then(() => {
+ app.on('ready', async () => {
+ try {
+ const mw = wm.create({
+ w: 20,
+ h: 20,
+ whp: true,
+ onCreate: (win: any) => {
+ win.loadURL(`http://localhost:${process.nfenv.serverPort}/screens/mainmenu/test.html`);
+ },
+ });
+
+ 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});
+
+ } catch (e) {
+ console.error(e);
+ process.nfenv.crash();
+ }
+ });
+})
\ No newline at end of file
diff --git a/src/lib/crash.ts b/src/lib/crash.ts
new file mode 100644
index 0000000..67272f2
--- /dev/null
+++ b/src/lib/crash.ts
@@ -0,0 +1,34 @@
+import { wm, wdata } from './windowManager';
+import { easeInOutQuad } from './animations';
+import { wait, toP } from './utils'
+import { app, screen } from 'electron';
+
+export default async function displayCrashScreen() {
+ const bwdata = { ...wdata };
+ const win = wm.create({
+ w: 40,
+ h: 40,
+ whp: true,
+ onCreate: (win: any) => {
+ win.loadURL(`http://localhost:${process.nfenv.serverPort}/screens/crash/index.html`);
+ },
+ });
+ const button = wm.create({
+ w: 10,
+ h: 5,
+ whp: true,
+ noBorder: true,
+ noBackground: true,
+ onCreate: (win: any) => {
+ win.loadURL(`http://localhost:${process.nfenv.serverPort}/screens/crash/exit.html`);
+ },
+ });
+ await wait(300);
+ console.log('[nfcrash] Deleted all windows');
+ Object.keys(bwdata).forEach(async (id) => {
+ bwdata[id].win.destroy(); // Note: We dont use wm.destroy(...) so ...onDestroy wont get called and cause trouble
+ });
+ const { width: sw, height: sh } = screen.getPrimaryDisplay().bounds;
+ wm.move({ id: button, y: 75, x: 50, p: true, fromCenter: true });
+ wm.follow.start(button, win);
+};
\ No newline at end of file
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
new file mode 100644
index 0000000..e962f8b
--- /dev/null
+++ b/src/lib/utils.ts
@@ -0,0 +1,4 @@
+export const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
+export function toP(value: number, percentage: number) {
+ return value * (percentage / 100) as number;
+}
\ No newline at end of file
diff --git a/src/lib/windowManager.ts b/src/lib/windowManager.ts
index 3b3fdae..450dfbc 100644
--- a/src/lib/windowManager.ts
+++ b/src/lib/windowManager.ts
@@ -1,17 +1,16 @@
-import { BrowserWindow, screen } from 'electron';
+import { BrowserWindow, Rectangle, screen } from 'electron';
import { wm_create_conf, wm_move_conf, wm_resize_conf } from './../types/wm';
+import { toP } from './utils';
interface WindowData {
win: BrowserWindow;
conf: wm_create_conf;
+ follows?: string;
+ followStuff?: any;
}
let wdata: { [key: string]: WindowData } = {};
-function toP(value: number, percentage: number) {
- return value * (percentage / 100) as number;
-}
-
const wm = {
create: function (c: wm_create_conf) {
const wid = Array.from({ length: 8 }, () => '0123456789abcdef'[Math.floor(Math.random() * 16)]).join('');
@@ -21,7 +20,7 @@ const wm = {
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.x,
+ y: c.xyp ? toP(sh, c.y!) : c.y,
frame: !c.noBorder,
transparent: c.noBackground,
webPreferences: {
@@ -30,7 +29,7 @@ const wm = {
},
});
- wdata[wid] = { win: win, conf: c }; // for further pullin
+ wdata[wid] = { win: win, conf: c };
win.setMenuBarVisibility(false); // tell me WHY should i NOT make this the default
if (typeof c.onMinimize === 'function') win.on('minimize', () => c.onMinimize!(win));
@@ -43,7 +42,7 @@ const wm = {
if (c.onCreate) c.onCreate(win);
return wid // just so you can reference it later on
},
- destroy: async function (id: number) {
+ destroy: async function (id: string) {
console.log('[wm] destroy', id);
const win = wdata[id];
if (typeof win.conf.onDestroy === 'function') await win.conf.onDestroy!(win.win);
@@ -108,86 +107,85 @@ const wm = {
resize: function (c: wm_resize_conf) {
console.log('[wm] resize', c.id);
const win = wdata[c.id];
- let wb = win.win.getBounds();
+ if (!win || !win.win) {
+ console.error('Window data not found for id:', c.id);
+ return;
+ }
+ const wb = win.win.getBounds();
const sW = wb.width, sH = wb.height;
+ // Use the given dimensions if provided; otherwise fall back to the current window size
c.w = c.w === undefined ? sW : c.w;
c.h = c.h === undefined ? sH : c.h;
const duration = c.duration || 500;
const tick = c.tick || 16;
const totalSteps = Math.floor(duration / tick);
+ // Get primary display bounds – make sure that screen is imported from Electron
const { width: sw, height: sh } = screen.getPrimaryDisplay().bounds;
- let targetW = toP(c.p ? sw : 1, c.w);
- let targetH = toP(c.p ? sh : 1, c.h);
+ // Calculate target width and height using percentage conversion if needed.
+ // Assuming toP converts a percentage value to pixels
+ const targetW = toP(c.p ? sw : 1, c.w);
+ const targetH = toP(c.p ? sh : 1, c.h);
+
+ // Compute window center for centering if needed
const centerX = wb.x + wb.width / 2;
const centerY = wb.y + wb.height / 2;
+
+ // Adjust position based on the chosen anchor.
const getAnchoredPosition = (curWidth: number, curHeight: number) => {
let newX = wb.x;
let newY = wb.y;
- if (!c.fromCenter && c.anchor) {
- switch (c.anchor) {
- case 'top':
- newX = wb.x;
- newY = wb.y;
- break;
- case 'bottom':
- newX = wb.x;
- newY = wb.y + wb.height - curHeight;
- break;
- case 'left':
- newX = wb.x;
- newY = wb.y;
- break;
- case 'right':
- newX = wb.x + wb.width - curWidth;
- newY = wb.y;
- break;
- default:
- newX = wb.x;
- newY = wb.y;
- }
+ switch (c.anchor) {
+ case 'top':
+ // Align to top – x remains same
+ newY = wb.y;
+ break;
+ case 'bottom':
+ // Align to bottom
+ newY = wb.y + wb.height - curHeight;
+ break;
+ case 'left':
+ // Align to left – y remains same
+ newX = wb.x;
+ break;
+ case 'right':
+ // Align to right
+ newX = wb.x + wb.width - curWidth;
+ break;
+ default:
+ // Default: center the resized window relative to the original bounds
+ newX = centerX - curWidth / 2;
+ newY = centerY - curHeight / 2;
}
return { newX, newY };
};
+
+ // Animation function to smooth the resize
const animate = () => {
let currentStep = 0;
const startW = wb.width;
const startH = wb.height;
const step = () => {
currentStep++;
- const t = currentStep / totalSteps;
+ const t = Math.min(currentStep / totalSteps, 1);
const eT = c.ease ? c.ease(t) : t;
const newW = startW + (targetW - startW) * eT;
const newH = startH + (targetH - startH) * eT;
- let newX: number, newY: number;
- if (c.fromCenter) {
- newX = Math.round(centerX - newW / 2);
- newY = Math.round(centerY - newH / 2);
- } else {
- const pos = getAnchoredPosition(Math.round(newW), Math.round(newH));
- newX = pos.newX;
- newY = pos.newY;
- }
+ // Get new positions based on the current width and height
+ const pos = getAnchoredPosition(Math.round(newW), Math.round(newH));
win.win.setBounds({
- x: newX,
- y: newY,
+ x: pos.newX,
+ y: pos.newY,
width: Math.round(newW),
height: Math.round(newH)
});
if (currentStep < totalSteps) {
setTimeout(step, tick);
} else {
- let finalX: number, finalY: number;
- if (c.fromCenter) {
- finalX = Math.round(centerX - targetW / 2);
- finalY = Math.round(centerY - targetH / 2);
- } else {
- const pos = getAnchoredPosition(Math.round(targetW), Math.round(targetH));
- finalX = pos.newX;
- finalY = pos.newY;
- }
+ // Make sure to set final bounds exactly
+ const finalPos = getAnchoredPosition(Math.round(targetW), Math.round(targetH));
win.win.setBounds({
- x: finalX,
- y: finalY,
+ x: finalPos.newX,
+ y: finalPos.newY,
width: Math.round(targetW),
height: Math.round(targetH)
});
@@ -195,21 +193,15 @@ const wm = {
};
step();
};
+
+ // Use animate if smooth animation is requested, otherwise set bounds immediately.
if (c.smooth) {
animate();
} else {
- let finalX: number, finalY: number;
- if (c.fromCenter) {
- finalX = Math.round(centerX - targetW / 2);
- finalY = Math.round(centerY - targetH / 2);
- } else {
- const pos = getAnchoredPosition(Math.round(targetW), Math.round(targetH));
- finalX = pos.newX;
- finalY = pos.newY;
- }
+ const finalPos = getAnchoredPosition(Math.round(targetW), Math.round(targetH));
win.win.setBounds({
- x: finalX,
- y: finalY,
+ x: finalPos.newX,
+ y: finalPos.newY,
width: Math.round(targetW),
height: Math.round(targetH)
});
@@ -222,6 +214,42 @@ const wm = {
} catch (error) {
console.error('Error executing JavaScript:', error);
}
+ },
+ follow: {
+ INTERNAL_follow: function (id: string) {
+ const leadWin = wdata[wdata[id].follows!].win;
+ const followWin = wdata[id].win;
+ const leadBounds = leadWin.getBounds();
+ const followBounds = followWin.getBounds();
+
+ if (wdata[id].followStuff === undefined) {
+ wdata[id].followStuff = {};
+ }
+
+ if (wdata[id].followStuff.offsetX === undefined || wdata[id].followStuff.offsetY === undefined) {
+ wdata[id].followStuff.offsetX = followBounds.x - leadBounds.x;
+ wdata[id].followStuff.offsetY = followBounds.y - leadBounds.y;
+ }
+
+ const newX = leadBounds.x + wdata[id].followStuff.offsetX;
+ const newY = leadBounds.y + wdata[id].followStuff.offsetY;
+
+ followWin.setPosition(newX, newY);
+ followWin.show();
+ },
+ INTERNAL_followfocus: function (id: string) {
+ wdata[id].win.show();
+ },
+ start: function (id: string, follows: string) {
+ console.log('[wm]', id, 'follows', follows);
+ wdata[id].follows = follows;
+ wdata[follows].win.on('move', this.INTERNAL_follow.bind(this, id));
+ wdata[follows].win.on('focus', this.INTERNAL_followfocus.bind(this, id));
+ },
+ stop: function (id: string) {
+ wdata[wdata[id].follows!].win.removeListener('move', this.INTERNAL_follow.bind(this, id));
+ wdata[wdata[id].follows!].win.removeListener('focus', this.INTERNAL_followfocus.bind(this, id));
+ }
}
};
diff --git a/src/types/wm.d.ts b/src/types/wm.d.ts
deleted file mode 100644
index e3e2a55..0000000
--- a/src/types/wm.d.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { BrowserWindow } from 'electron';
-
-export type wm_create_conf = {
- w?: number; // Width
- h?: number; // Height
- x?: number; // X start pos
- y?: number; // Y start pos
- whp?: boolean; // is w and h in percentages
- xyp?: boolean; // is x and y in percentages
- noBorder?: boolean; // Hide OS window decorations
- noBackground?: boolean; // Hide window background
- onCreate?: Function; // wm.create(...)
- onMinimize?: Function; // USER pressed '_'
- onMaximize?: Function; // USER pressed '[]'
- onRestore?: Function; // USER pressed '[]]'
- 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(...)
-};
-
-export type wm_move_conf = {
- id: string; // ID of the window to perform everythin on
- x?: number; // New X
- y?: number; // New Y
- p?: boolean; // Use percentages
- fromCenter?: boolean; // Should be performed from the center of the window?
- smooth?: boolean; // Should it be smooth?
- ease?: Function; // Calculates the easing (lib/animations.ts)
- duration?: number; // (ms) The total duration of the animation
- tick?: number; // (ms) Delay between window movenments
-}
-
-export type wm_resize_conf = {
- id: string; // ID of the window to perform everythin on
- w?: number; // New width
- h?: number; // New height
- p?: boolean; // Use percentages
- fromCenter?: boolean; // Should be performed from the center of the window?
- smooth?: boolean; // Should it be smooth?
- ease?: Function; // Calculates the easing (lib/animations.ts)
- duration?: number; // (ms) The total duration of the animation
- tick?: number; // (ms) Delay between window resizings
-}
-
-interface WindowData {
- win: BrowserWindow;
- conf: wm_create_conf;
-}
-
-declare const wm: {
- create: (c: wm_create_conf) => string;
- destroy: (id: string) => Promise;
- move: (c: wm_move_conf) => void;
- resize: (c: wm_resize_conf) => void;
- eval: (id: string, code: Function) => Promise;
-};
-
-export default wm;
-export { WindowData, wm };
\ No newline at end of file
diff --git a/src/types/wm.ts b/src/types/wm.ts
index 05ac8d2..a2868c1 100644
--- a/src/types/wm.ts
+++ b/src/types/wm.ts
@@ -1,6 +1,6 @@
export type wm_create_conf = {
- w?: number; // Width
- h?: number; // Height
+ w: number; // Width
+ h: number; // Height
x?: number; // X start pos
y?: number; // Y start pos
whp?: boolean; // is w and h in percentages
@@ -34,10 +34,9 @@ export type wm_resize_conf = {
w?: number; // New width
h?: number; // New height
p?: boolean; // Use percentages
- fromCenter?: boolean; // Should be performed from the center of the window?
smooth?: boolean; // Should it be smooth?
ease?: Function; // Calculates the easing (lib/animations.ts)
duration?: number; // (ms) The total duration of the animation
tick?: number; // (ms) Delay between window resizings
- anchor?: string; // bottom, top, left, right
+ anchor?: string; // bottom, top, left, right, center
}
\ No newline at end of file
diff --git a/src/webroot/screens/crash/exit.html b/src/webroot/screens/crash/exit.html
new file mode 100644
index 0000000..5e664ad
--- /dev/null
+++ b/src/webroot/screens/crash/exit.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+ NeoFunkin
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/screens/crash/index.html b/src/webroot/screens/crash/index.html
similarity index 67%
rename from src/screens/crash/index.html
rename to src/webroot/screens/crash/index.html
index 885f66a..00e76ae 100644
--- a/src/screens/crash/index.html
+++ b/src/webroot/screens/crash/index.html
@@ -5,5 +5,6 @@
Uh oh.
+ NeoFunkin crashed. Check logs for more details.