From 4e12899d1d946e3044f5b63733463b65d3637c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=ADo=20Villanueva?= Date: Thu, 23 May 2024 20:06:00 +0100 Subject: [PATCH 1/3] feat: add Triangular grid game of life module (#882) * add triangles module * hue settings * fix: add missing features, field reset and a few nice-to-haves --------- Co-authored-by: Sam Wray --- src/application/sample-modules/Triangles.js | 541 ++++++++++++++++++++ 1 file changed, 541 insertions(+) create mode 100644 src/application/sample-modules/Triangles.js diff --git a/src/application/sample-modules/Triangles.js b/src/application/sample-modules/Triangles.js new file mode 100644 index 00000000..8ab19a24 --- /dev/null +++ b/src/application/sample-modules/Triangles.js @@ -0,0 +1,541 @@ +export default { + meta: { + name: "TriangleLife", + type: "2d", + version: "0.0.0", + author: "radiodario" + }, + + props: { + N: { + label: "Grid Size", + type: "int", + min: 2, + max: 512, + default: 128 + }, + stroke: { + label: "Draw Stroke", + type: "bool", + default: false + }, + fill: { + label: "Draw Fill", + type: "bool", + default: true + }, + strokeWeight: { + type: "int", + label: "Stroke Weight", + min: 1, + max: 30, + default: 1, + abs: true + }, + strokeJoin: { + type: "enum", + label: "Stroke Join", + default: "round", + enum: [ + { label: "Round", value: "round" }, + { label: "Bevel", value: "bevel" }, + { label: "Miter", value: "miter" } + ] + }, + speed: { + label: "Colour Speed", + type: "int", + min: 1, + max: 1000, + default: 500 + }, + fg_alpha: { + label: "FG Alpha", + type: "float", + min: 0, + max: 1, + default: 1 + }, + seeds: { + label: "Seeds", + type: "int", + min: 1, + max: 25000, + default: 350 + }, + updateInterval: { + label: "Simulation Steps per Sec", + type: "int", + min: 1, + max: 120, // lol + default: 60 + }, + drawInterval: { + label: "Draw steps per Sec", + type: "int", + min: 1, + max: 120, // lol + default: 60 + }, + waveform: { + type: "enum", + label: "Colour Waveform", + default: "sin", + enum: [ + { label: "Sine", value: "sin" }, + { label: "Cosine", value: "cos" }, + { label: "Triangle", value: "triangle" }, + { label: "Saw", value: "saw" } + ] + }, + monochrome: { + label: "Monochrome", + type: "bool", + default: true + }, + monochromeHue: { + label: "Monochrome Hue", + type: "int", + default: 180, + min: 0, + max: 360 + }, + cycleHue: { + label: "Cycle Hue", + type: "bool", + default: false + }, + rule: { + type: "enum", + label: "Life Rule", + enum: [ + "2/1", + "2/2", + "0,1/3,3", + "1,2/4,6", + "*2/3", + "*2,3/3,3", + "*2,3/4,5", + "*2,3/4,6", + "*3,4/4,5", + "*3,4/4,6", + "**4,5/4,6", + "*4,6/4,4" + ].map(r => ({ label: r, value: r })), + default: "2/1", + set(args) { + return this.reset(args); + } + }, + resetField: { + type: "event", + label: "Reset Field", + set(args) { + if (args.props.resetField) { + return this.reset(args); + } + } + } + }, + + data: { + edge: 0, + field: [], + colorCounter: 0, + counterAngle: 0, + counterIncrease: 0, + lastUpdate: 0, + lastDraw: 0, + shouldDraw: true + }, + + init(args) { + return this.reset(args); + }, + + reset({ data, props }) { + data.rule = this.parseRule(props.rule); + data.field = this.initializeField(props); + const N = props.N; + for (var i = 0; i < props.seeds; i++) { + var idx = Math.floor(Math.random() * N * N); + data.field[idx] = 1; + } + + return data; + }, + + initializeField(props) { + var field = []; + var allCells = props.N * props.N; + for (var i = 0; i < allCells; i++) { + field[i] = 0; + } + return field; + }, + + resize({ canvas, data, props }) { + const width = canvas.width + 20; + const height = canvas.height; + + // triangle height and width + var kH = (2 * height) / (props.N * Math.sqrt(2)); + var kW = (2 * width) / props.N; + + data.edge = Math.max(kH, kW); + + return data; + }, + + draw({ + data, + props, + context, + context: { + canvas: { width, height } + } + }) { + if (data.shouldDraw) { + var N = props.N; + var x, + y, + i, + l, + baseY, + lEdge = data.edge * Math.cos(Math.PI / 6); + + var translate = { + x: (width - (N * data.edge) / 2) / 2, + y: (height - (N * data.edge * Math.SQRT2) / 2) / 2 + }; + + if (props.cycleHue) { + props.monochromeHue = + (props.monochromeHue + 360 / (1001 - props.speed)) % 360; + } + + for (i = 0, l = data.field.length; i < l; i++) { + if (data.field[i] <= 0) { + continue; + } + + x = i % N; + y = (i / N) | 0; + + baseY = y * lEdge; + this.drawTriangle(context, x, y, lEdge, baseY, translate, data); + this.setColors(context, x, y, props, data); + } + } + }, + + drawTriangle(context, x, y, lEdge, baseY, translate, { edge }) { + context.beginPath(); + + var type = (y % 2 << 1) + (x % 2); + + switch (type) { + case 0: + context.moveTo(((x + 0) * edge) / 2 + translate.x, baseY + translate.y); + context.lineTo(((x + 2) * edge) / 2 + translate.x, baseY + translate.y); + context.lineTo( + ((x + 1) * edge) / 2 + translate.x, + baseY + lEdge + translate.y + ); + break; + case 1: + context.moveTo(((x + 1) * edge) / 2 + translate.x, baseY + translate.y); + context.lineTo( + ((x + 2) * edge) / 2 + translate.x, + baseY + lEdge + translate.y + ); + context.lineTo( + ((x + 0) * edge) / 2 + translate.x, + baseY + lEdge + translate.y + ); + break; + case 2: + context.moveTo(((x + 1) * edge) / 2 + translate.x, baseY + translate.y); + context.lineTo( + ((x + 2) * edge) / 2 + translate.x, + baseY + translate.y + lEdge + ); + context.lineTo( + ((x + 0) * edge) / 2 + translate.x, + baseY + translate.y + lEdge + ); + break; + case 3: + context.moveTo(((x + 0) * edge) / 2 + translate.x, baseY + translate.y); + context.lineTo(((x + 2) * edge) / 2 + translate.x, baseY + translate.y); + context.lineTo( + ((x + 1) * edge) / 2 + translate.x, + baseY + translate.y + lEdge + ); + break; + } + + context.closePath(); + }, + + setColors(context, x, y, props, data) { + // var val = Field[x + (N * y)]; + const speed = 1001 - props.speed; + let color; + if (props.monochrome) { + color = hslToRgb(props.monochromeHue / 360, 1, 0.5, props.fg_alpha * 100); + } else { + data.counterIncrease = Math.PI / speed; + data.counterAngle += data.counterIncrease; + switch (props.waveform) { + case "sin": + data.colorCounter = 1 + Math.sin(data.counterAngle) / 2; + break; + case "cos": + data.colorCounter = 1 + Math.cos(data.counterAngle) / 2; + break; + case "triangle": + data.colorCounter = Math.abs((data.counterAngle % 2) - 1); + break; + case "saw": + data.colorCounter = Math.abs(data.counterAngle % 1); + break; + } + + color = hslToRgb(data.colorCounter, 1, 0.5, props.fg_alpha * 100); + } + + if (props.fill) { + context.fillStyle = color; + context.fill(); + } + if (props.stroke) { + context.strokeStyle = color; + context.lineWidth = props.strokeWeight; + context.lineJoin = props.strokeJoin; + context.stroke(); + } + }, + + update({ data, props }) { + const timestamp = performance.now(); + + if (timestamp > data.lastDraw + 1000 / props.drawInterval) { + data.shouldDraw = true; + data.lastDraw = timestamp; + } else { + data.shouldDraw = false; + } + + if (timestamp > data.lastUpdate + 1000 / props.updateInterval) { + let ln = 0; // live neighbor count + let i, l, val; + const nextField = this.initializeField(props); + + for (i = 0, l = data.field.length; i < l; i++) { + ln = this.computeLiveNeighbours(i, props, data); + val = data.field[i]; + nextField[i] = this.computeNextStateOfCell(val, ln, data.rule); + } + + data.field = nextField; + data.lastUpdate = timestamp; + } + + return data; + }, + + neighbours: { + O: [ + [-1, -2], + [-1, -1], + [-1, 0], + [-1, 1], + [-1, 2], + [0, -2], + [0, -1], + [0, 1], + [0, 2], + [1, -1], + [1, 0], + [1, 1] + ], + E: [ + [-1, -1], + [-1, 0], + [-1, 1], + [0, -2], + [0, -1], + [0, 1], + [0, 2], + [1, -2], + [1, -1], + [1, 0], + [1, 1], + [1, 2] + ] + }, + + computeLiveNeighbours(idx, props, data) { + const N = props.N; + const x = idx % N; + const y = (idx / N) | 0; + let LN = 0; + + /* + Each cell has 12 touching neighbors. There are two + types of cells, E and O cells. + */ + const type = (x + y) % 2; + let i, l, nList; + + // Even Cell + if (type === 1) { + nList = this.neighbours.E; + } + // Odd Cell + else { + nList = this.neighbours.O; + } + for (i = 0, l = nList.length; i < l; i++) { + var nb = this.neighbourAt(x, y, nList[i], props, data); + LN += nb; + } + + return LN; + }, + + neighbourAt(x, y, neighbour, props, data) { + const N = props.N; + let dx = x + neighbour[1]; + let dy = y + neighbour[0]; + // wrap around + if (dx >= N) { + dx = dx % N; + } + if (dx < 0) { + dx = N + dx; + } + if (dy >= N) { + dy = dy % N; + } + if (dy < 0) { + dy = N + dy; + } + + return data.field[dx + dy * N] || 0; + }, + + computeNextStateOfCell(val, LN, rule) { + if (val > 0) { + if (LN >= rule.env.l && LN <= rule.env.h) { + return 1; + } else { + return 0; + } + } else { + if (LN >= rule.fer.l && LN <= rule.fer.h) { + return 1; + } else { + return 0; + } + } + }, + + parseRule(ruleString) { + const ruleExp = /(\d+|\d+,\d+)\/(\d+|\d+,\d+)$/i; + + const results = ruleExp.exec(ruleString); + const rule = { + env: { + l: 0, + h: 0 + }, + fer: { + l: 0, + h: 0 + } + }; + + // environment + var env = results[1]; + if (env.length >= 3) { + var envp = env.split(","); + rule.env.l = +envp[0]; + rule.env.h = +envp[1]; + } else { + rule.env.l = +env; + rule.env.h = +env; + } + + // fertility + var fer = results[2]; + if (fer.length >= 3) { + var ferp = fer.split(","); + rule.fer.l = +ferp[0]; + rule.fer.h = +ferp[1]; + } else { + rule.fer.l = +fer; + rule.fer.h = +fer; + } + return rule; + } +}; + +function hslToRgb(h, s, l, a) { + var r, g, b; + + if (s == 0) { + r = g = b = l; // achromatic + } else { + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + if (arguments.length == 3) { + return ( + "rgb(" + + Math.floor(r * 255) + + "," + + Math.floor(g * 255) + + "," + + Math.floor(b * 255) + + ")" + ); + } + if (arguments.length == 4) { + return ( + "rgba(" + + Math.floor(r * 255) + + "," + + Math.floor(g * 255) + + "," + + Math.floor(b * 255) + + ", " + + a + + ")" + ); + } +} + +function hue2rgb(p, q, t) { + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1 / 6) { + return p + (q - p) * 6 * t; + } + if (t < 1 / 2) { + return q; + } + if (t < 2 / 3) { + return p + (q - p) * (2 / 3 - t) * 6; + } + return p; +} From 3a861aacd5fb14503754768c357ec5220ce98b08 Mon Sep 17 00:00:00 2001 From: Sam Wray Date: Fri, 2 Aug 2024 12:05:15 +0100 Subject: [PATCH 2/3] fix: remove menubar on output windows (#915) updates electron and replaces deprecated new-window event handler with setWindowOpenHandler --- package.json | 2 +- src/background/window-prefs.js | 48 ++++++++++++++++++++++------------ yarn.lock | 8 +++--- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index ce9a87e1..2a318986 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "babel-eslint": "^10.0.3", "babel-loader": "^9.1.2", "core-js": "^3.19.1", - "electron": "29.1.5", + "electron": "31.3.1", "electron-builder": "^22.9.1", "electron-notarize": "^1.2.2", "electron-playwright-helpers": "^1.5.3", diff --git a/src/background/window-prefs.js b/src/background/window-prefs.js index e63a545f..022d8309 100644 --- a/src/background/window-prefs.js +++ b/src/background/window-prefs.js @@ -77,6 +77,10 @@ const windowPrefs = { }; }, + /** + * + * @param {BrowserWindow} window + */ async create(window) { require("@electron/remote/main").enable(window.webContents); @@ -87,23 +91,35 @@ const windowPrefs = { window.setTitle("Untitled"); // Configure child windows to open without a menubar (windows/linux) - window.webContents.on( - "new-window", - (event, url, frameName, disposition, options) => { - if (frameName === "modal") { - event.preventDefault(); - event.newGuest = new BrowserWindow({ - ...options, - autoHideMenuBar: true, - closable: false, - enableLargerThanScreen: true, - title: "" - }); - - event.newGuest.removeMenu(); - } + window.webContents.setWindowOpenHandler(({ frameName }) => { + if (frameName === "modal") { + return { + action: "allow", + createWindow: options => { + const window = new BrowserWindow({ + ...options, + autoHideMenuBar: true, + closable: false, + enableLargerThanScreen: true, + title: "" + }); + + window.webContents.on("dom-ready", () => { + // Ugly hack + setTimeout(() => { + window.removeMenu(); + }, 1000); + }); + + return window.webContents; + } + }; } - ); + + return { + action: "deny" + }; + }); const mm = getMediaManager(); diff --git a/yarn.lock b/yarn.lock index 15ba10ef..f74b3c3e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5143,10 +5143,10 @@ electron-updater@^4.3.1: lodash.isequal "^4.5.0" semver "^7.3.5" -electron@29.1.5: - version "29.1.5" - resolved "https://registry.yarnpkg.com/electron/-/electron-29.1.5.tgz#b745b4d201c1ac9f84d6aa034126288dde34d5a1" - integrity sha512-1uWGRw/ffA62lcrklxGUgVxVtOHojsg/nwsYr+/F9cVjipZJn8iPv/ABGIIexhmUqWcho8BqfTJ4osCBa29gBg== +electron@31.3.1: + version "31.3.1" + resolved "https://registry.yarnpkg.com/electron/-/electron-31.3.1.tgz#de5f21f10db1ba0568e0cdd7ae76ec40a4b800c3" + integrity sha512-9fiuWlRhBfygtcT+auRd/WdBK/f8LZZcrpx0RjpXhH2DPTP/PfnkC4JB1PW55qCbGbh4wAgkYbf4ExIag8oGCA== dependencies: "@electron/get" "^2.0.0" "@types/node" "^20.9.0" From b09d4b72669f075483fb135b2af535d46976b297 Mon Sep 17 00:00:00 2001 From: Sam Wray Date: Fri, 2 Aug 2024 13:02:13 +0100 Subject: [PATCH 3/3] fix: resolve dependancy for node 20 support (#917) --- package.json | 5 +++-- yarn.lock | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 2a318986..d8c711fc 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ ] }, "resolutions": { - "electron-builder": "23.0.2" + "electron-builder": "23.0.2", + "@achrinza/node-ipc": "9.2.7" } -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f74b3c3e..6f7b2717 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.1.1.tgz#9274ec7460652f9c632c59addf24efb1684ef876" integrity sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ== -"@achrinza/node-ipc@^9.2.5": - version "9.2.6" - resolved "https://registry.yarnpkg.com/@achrinza/node-ipc/-/node-ipc-9.2.6.tgz#75c89e95fe38ff9e3dd33a0d2681dc28f1c31f33" - integrity sha512-ULSIYPy4ZPM301dfCxRz0l2GJjOwIo/PqmWonIu1bLml7UmnVQmH+juJcoyXp6E8gIRRNAjGYftJnNQlfy4vPg== +"@achrinza/node-ipc@9.2.7", "@achrinza/node-ipc@^9.2.5": + version "9.2.7" + resolved "https://registry.yarnpkg.com/@achrinza/node-ipc/-/node-ipc-9.2.7.tgz#cc418f9218d24d9b87f32207e5d6e71c64e241f8" + integrity sha512-/EvNkqB4HNxPWCZASmgrjqG8gIdPOolD67LGASvGMp/FY5ne0rbvpYg5o9x8RmgjAl8KdmNQ4YlV1et9DYiW8g== dependencies: "@node-ipc/js-queue" "2.0.3" event-pubsub "4.3.0"