From f917cf6ad5320f7fa3c4445ca51c51c3c8113695 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Sat, 21 Mar 2026 18:38:43 +1100 Subject: [PATCH] Add initial Pico-8 cartridges for testing - Created test_cart.p8 with basic drawing functionality and a print statement. - Created test_unix.p8 with a simple initialization function. --- electron-main.js | 166 +++- index.html | 120 ++- preload.js | 116 +++ public/pico8-temp-player.html | 2 +- scripts/core/LuaGenerator.js | 213 ++--- scripts/core/NodeGraph.js | 7 +- scripts/core/SpriteSheet.js | 268 ++++++ scripts/core/TileSet.js | 167 ++++ scripts/core/lua/LuaLiteralBoolean.js | 41 + scripts/core/lua/LuaLiteralColor.js | 39 + scripts/core/lua/LuaLiteralNumber.js | 39 + scripts/core/lua/LuaLiteralString.js | 38 + scripts/core/lua/LuaLiteralTable.js | 27 + scripts/core/lua/LuaLiteralTypes.js | 56 ++ scripts/main.js | 263 ++++-- scripts/nodes/library/index.js | 17 +- .../nodes/library/input/controllerButtons.js | 4 + scripts/nodes/library/localVariables.js | 58 +- scripts/nodes/library/math/add.js | 41 + scripts/nodes/library/math/divide.js | 41 + scripts/nodes/library/math/index.js | 12 + scripts/nodes/library/math/multiply.js | 41 + scripts/nodes/library/math/subtract.js | 41 + scripts/nodes/library/reroute.js | 53 ++ scripts/nodes/library/subtractNumber.js | 41 + scripts/nodes/library/system/extcmd.js | 2 +- scripts/nodes/nodeTypes.js | 2 + scripts/types/AnyType.js | 13 + scripts/types/BooleanType.js | 19 + scripts/types/ColorType.js | 18 + scripts/types/ExecType.js | 13 + scripts/types/NumberType.js | 17 + scripts/types/StringType.js | 14 + scripts/types/TableType.js | 15 + scripts/types/TypeRegistry.js | 165 ++++ scripts/ui/BlueprintWorkspace.js | 366 ++++++-- scripts/ui/ColorSwatchPicker.js | 184 ++++ scripts/ui/EmbeddedPreview.js | 4 +- scripts/ui/SpriteEditor.js | 622 ++++++++++++++ scripts/ui/SpriteEditorManager.js | 205 +++++ scripts/ui/TileManager.js | 114 +++ scripts/ui/TileSetBrowser.js | 511 ++++++++++++ scripts/ui/workspace/WorkspaceDragManager.js | 28 +- scripts/ui/workspace/WorkspaceDropManager.js | 22 +- scripts/ui/workspace/WorkspaceGeometry.js | 18 +- .../workspace/WorkspaceInlineEditorManager.js | 243 +++--- .../workspace/WorkspaceNavigationManager.js | 46 +- scripts/ui/workspace/WorkspaceNodeRenderer.js | 2 + .../workspace/WorkspacePersistenceManager.js | 26 +- .../ui/workspace/WorkspaceWireCutManager.js | 6 +- scripts/ui/workspace/renderVariableList.js | 3 + sprite-editor.html | 176 ++++ styles/main.css | 789 +++++++++++++----- test2.p8 | 302 +++++++ test_cart.p8 | 24 + test_unix.p8 | 8 + 56 files changed, 5200 insertions(+), 688 deletions(-) create mode 100644 scripts/core/SpriteSheet.js create mode 100644 scripts/core/TileSet.js create mode 100644 scripts/core/lua/LuaLiteralBoolean.js create mode 100644 scripts/core/lua/LuaLiteralColor.js create mode 100644 scripts/core/lua/LuaLiteralNumber.js create mode 100644 scripts/core/lua/LuaLiteralString.js create mode 100644 scripts/core/lua/LuaLiteralTable.js create mode 100644 scripts/core/lua/LuaLiteralTypes.js create mode 100644 scripts/nodes/library/math/add.js create mode 100644 scripts/nodes/library/math/divide.js create mode 100644 scripts/nodes/library/math/multiply.js create mode 100644 scripts/nodes/library/math/subtract.js create mode 100644 scripts/nodes/library/reroute.js create mode 100644 scripts/nodes/library/subtractNumber.js create mode 100644 scripts/types/AnyType.js create mode 100644 scripts/types/BooleanType.js create mode 100644 scripts/types/ColorType.js create mode 100644 scripts/types/ExecType.js create mode 100644 scripts/types/NumberType.js create mode 100644 scripts/types/StringType.js create mode 100644 scripts/types/TableType.js create mode 100644 scripts/types/TypeRegistry.js create mode 100644 scripts/ui/ColorSwatchPicker.js create mode 100644 scripts/ui/SpriteEditor.js create mode 100644 scripts/ui/SpriteEditorManager.js create mode 100644 scripts/ui/TileManager.js create mode 100644 scripts/ui/TileSetBrowser.js create mode 100644 sprite-editor.html create mode 100644 test2.p8 create mode 100644 test_cart.p8 create mode 100644 test_unix.p8 diff --git a/electron-main.js b/electron-main.js index fff6849..e6cee67 100644 --- a/electron-main.js +++ b/electron-main.js @@ -15,9 +15,10 @@ try { // Ignore errors in production (when electron-reloader isn't installed) } -const { app, BrowserWindow, ipcMain, Menu } = require("electron"); +const { app, BrowserWindow, ipcMain, Menu, shell, dialog } = require("electron"); const fs = require("node:fs/promises"); const path = require("node:path"); +const { spawn } = require("node:child_process"); const OUTPUT_FILENAME = "compiled.lua"; @@ -40,6 +41,16 @@ function getOutputPath() { return path.join(getOutputDirectory(), OUTPUT_FILENAME); } +/** + * Resolves the absolute directory where tileset images are stored. + * + * @returns {string} + */ +function getTilesetsDirectory() { + const applicationData = app.getPath("appData"); + return path.join(applicationData, "pico-8", "carts", "picograph", "tilesets"); +} + /** @type {import("electron").BrowserWindow | null} */ let mainWindow = null; @@ -54,6 +65,7 @@ function createMainWindow() { height: 900, minWidth: 960, minHeight: 640, + frame: false, webPreferences: { contextIsolation: true, nodeIntegration: false, @@ -78,6 +90,13 @@ function createMainWindow() { } }); + const emitMaximizedState = () => { + window.webContents.send("window:maximized-changed", window.isMaximized()); + }; + + window.on("maximize", emitMaximizedState); + window.on("unmaximize", emitMaximizedState); + window.on("closed", () => { mainWindow = null; }); @@ -135,6 +154,151 @@ function registerIpcHandlers() { return { filePath: cartPath }; }); + + ipcMain.handle("window:minimize", () => { + mainWindow?.minimize(); + }); + + ipcMain.handle("window:toggle-maximize", () => { + if (!mainWindow) { + return false; + } + + if (mainWindow.isMaximized()) { + mainWindow.unmaximize(); + } else { + mainWindow.maximize(); + } + + return mainWindow.isMaximized(); + }); + + ipcMain.handle("window:close", () => { + mainWindow?.close(); + }); + + ipcMain.handle("window:is-maximized", () => { + return mainWindow?.isMaximized() ?? false; + }); + + ipcMain.handle("tileset:save", async (_event, fileBuffer, suggestedName) => { + if (!(fileBuffer instanceof Uint8Array)) { + throw new TypeError("File buffer must be a Uint8Array"); + } + if (typeof suggestedName !== "string") { + throw new TypeError("Suggested name must be a string"); + } + + const tilesetsDir = getTilesetsDirectory(); + await fs.mkdir(tilesetsDir, { recursive: true }); + + // Ensure unique filename + let fileName = suggestedName.endsWith(".png") ? suggestedName : `${suggestedName}.png`; + let filePath = path.join(tilesetsDir, fileName); + let counter = 1; + + while (await fs.access(filePath).then(() => true).catch(() => false)) { + const nameWithoutExt = suggestedName.replace(/\.png$/i, ""); + fileName = `${nameWithoutExt}_${counter}.png`; + filePath = path.join(tilesetsDir, fileName); + counter++; + } + + await fs.writeFile(filePath, Buffer.from(fileBuffer)); + + return { filePath, fileName }; + }); + + ipcMain.handle("tileset:show-in-folder", async (_event, filePath) => { + if (typeof filePath !== "string") { + throw new TypeError("File path must be a string"); + } + + shell.showItemInFolder(filePath); + }); + + ipcMain.handle("tileset:reload", async (_event, filePath) => { + if (typeof filePath !== "string") { + throw new TypeError("File path must be a string"); + } + + const buffer = await fs.readFile(filePath); + return { buffer: Array.from(buffer) }; + }); + + ipcMain.handle("cart:get-stats", async (_event, cartContent) => { + if (typeof cartContent !== "string") { + throw new TypeError("Cart content must be a string"); + } + + // Write cart to a temporary file + const tempDir = app.getPath("temp"); + const tempCartPath = path.join(tempDir, `stats_${Date.now()}.p8`); + + try { + await fs.writeFile(tempCartPath, cartContent, "utf8"); + + // Run p8tool stats command + const picotoolPath = path.join(__dirname, "picotool"); + + return new Promise((resolve, reject) => { + const p8tool = spawn("p8tool", ["stats", tempCartPath], { + cwd: __dirname, + shell: true + }); + + let stdout = ""; + let stderr = ""; + + p8tool.stdout.on("data", (data) => { + stdout += data.toString(); + }); + + p8tool.stderr.on("data", (data) => { + stderr += data.toString(); + }); + + p8tool.on("close", async (code) => { + // Clean up temp file + try { + await fs.unlink(tempCartPath); + } catch (cleanupError) { + console.warn("Failed to delete temp cart:", cleanupError); + } + + if (code !== 0) { + reject(new Error(`p8tool stats exited with code ${code}: ${stderr}`)); + return; + } + + // Parse the output to extract token count + // Expected format: "version: 0 lines: 48 chars: 419 tokens: 134" + const tokenMatch = stdout.match(/tokens:\s*(\d+)/); + const linesMatch = stdout.match(/lines:\s*(\d+)/); + const charsMatch = stdout.match(/chars:\s*(\d+)/); + + resolve({ + tokens: tokenMatch ? parseInt(tokenMatch[1], 10) : 0, + lines: linesMatch ? parseInt(linesMatch[1], 10) : 0, + chars: charsMatch ? parseInt(charsMatch[1], 10) : 0, + rawOutput: stdout + }); + }); + + p8tool.on("error", (error) => { + reject(new Error(`Failed to run p8tool: ${error.message}`)); + }); + }); + } catch (error) { + // Clean up temp file if it exists + try { + await fs.unlink(tempCartPath); + } catch (_) { + // Ignore cleanup errors + } + throw error; + } + }); } /** diff --git a/index.html b/index.html index d72eb37..be86b9b 100644 --- a/index.html +++ b/index.html @@ -3,18 +3,23 @@ - + picoGraph
-
- - -
-
picoGraph
-
+
+ + + + + +
+
+
picoGraph
+
+
+
+ + + +
@@ -129,6 +187,36 @@ +