From c560655f1feda234eb76cf6fa293d17cd958cf7b Mon Sep 17 00:00:00 2001 From: Adrien Burgun Date: Tue, 16 Aug 2022 14:48:42 +0200 Subject: [PATCH] :sparkles: WIP editor --- editor/index.html | 24 ++++ editor/index.js | 194 +++++++++++++++++++++++++++++++ editor/style.css | 21 ++++ font/StacklineClassic-Medium.otf | Bin 0 -> 18000 bytes font/StacklineClassic-Medium.pfs | 108 +++++++++++++++++ 5 files changed, 347 insertions(+) create mode 100644 editor/index.html create mode 100644 editor/index.js create mode 100644 editor/style.css create mode 100644 font/StacklineClassic-Medium.otf create mode 100644 font/StacklineClassic-Medium.pfs diff --git a/editor/index.html b/editor/index.html new file mode 100644 index 0000000..3347583 --- /dev/null +++ b/editor/index.html @@ -0,0 +1,24 @@ + + + + + + + Stackline web editor + + + + +
+ +
+
+ + Your browser needs to support canvases, sowwy :( + +
+
+ +
+ + diff --git a/editor/index.js b/editor/index.js new file mode 100644 index 0000000..1054a5f --- /dev/null +++ b/editor/index.js @@ -0,0 +1,194 @@ +import init, { + World, + Pane, + FullTile, + Signal, + available_tiles, +} from "/stackline-wasm/pkg/stackline_wasm.js"; + +let promises = []; +promises.push(init()); + +const canvas = document.getElementById("main-canvas"); +const ctx = canvas.getContext("2d"); + +let font = new FontFace("Stackline Classic", "url(\"/font/StacklineClassic-Medium.otf\")"); +promises.push(font.load()); +await Promise.all(promises); + +font = await font.loaded; +document.fonts.add(font); + +let world = World.new(); +let cx = 0; +let cy = 0; +let zoom = 1; + +let pane = Pane.empty(5, 5); +world.set_pane("main", pane); + +let available = available_tiles(); +console.log(available); +for (let n = 0; n < available.length; n++) { + world.set( + n % 5, + ~~(n / 5), + new FullTile(available[n]) + ); +} + +console.log(world.toString()); + +resize(); +window.addEventListener("resize", resize); +window.requestAnimationFrame(loop); + +function draw() { + let zoom_factor = Math.ceil(Math.pow(2, zoom)); + let tile_size = 10 * zoom_factor; + ctx.fillStyle = "#202027"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + let panes = []; + + for (let name of world.panes()) { + let pane = world.get_pane(name); + + ctx.strokeStyle = "rgba(128, 128, 128, 0.5)"; + ctx.lineWidth = zoom_factor; + + panes.push({ + x: pane.x, + y: pane.y, + width: pane.width, + height: pane.height + }); + + ctx.strokeRect( + Math.round(pane.x * tile_size + cx + canvas.width / 2) - zoom_factor * 1.5, + Math.round(pane.y * tile_size + cy + canvas.height / 2) - zoom_factor * 1.5, + Math.round(pane.width * tile_size) + zoom_factor * 3, + Math.round(pane.height * tile_size) + zoom_factor * 3, + ); + + pane.free(); + } + + let from_y = Math.floor((-cy - canvas.height / 2) / tile_size); + let to_y = Math.ceil((-cy + canvas.height / 2) / tile_size); + let from_x = Math.floor((-cx - canvas.width / 2) / tile_size); + let to_x = Math.ceil((-cx + canvas.width / 2) / tile_size); + + let width = to_x - from_x + 1; + let height = to_y - from_y + 1; + + // TODO: memoize + let chars = world.draw(-from_x, -from_y, width, height); + ctx.textAlign = "left"; + ctx.textBaseline = "top"; + ctx.font = `${tile_size}px Stackline Classic`; + + for (let y = from_y; y <= to_y; y++) { + for (let x = from_x; x <= to_x; x++) { + let x2 = x * tile_size + cx + canvas.width / 2; + let y2 = y * tile_size + cy + canvas.height / 2; + + let index = 3 * (width * (y - from_y) + x - from_x); + + let ch = String.fromCharCode(chars[index]); + let fg = to_color(chars[index + 1]); + let bg = chars[index + 2] === 0 ? null : to_color(chars[index + 2]); + + if (ch !== " ") { + if (bg) { + ctx.fillStyle = `rgba(${bg.join(",")})`; + ctx.fillRect( + Math.round(x2), + Math.round(y2), + Math.round(x2 + tile_size) - Math.round(x2), + Math.round(y2 + tile_size) - Math.round(y2), + ); + } + ctx.fillStyle = `rgba(${fg.join(",")})`; + ctx.fillText( + ch, + Math.round(x2), + Math.round(y2), + ); + } else { + // Draw grid + let inside = false; + + for (let pane of panes) { + if (x >= pane.x && x < pane.x + pane.width && y >= pane.y && y < pane.y + pane.height) { + inside = true; + break; + } + } + + ctx.fillStyle = inside ? "#606060" : "#404040"; + ctx.fillRect( + Math.round(x2 + tile_size / 2) - zoom_factor, + Math.round(y2 + tile_size / 2) - zoom_factor, + Math.max(zoom_factor, 2.0), + Math.max(zoom_factor, 2.0) + ); + } + } + } +} + +function resize() { + canvas.width = canvas.clientWidth; + canvas.height = canvas.clientHeight; + + draw(); +} + +function loop() { + draw(); + window.requestAnimationFrame(loop); +} + +let mouse_x = 0, mouse_y = 0; +let mouse_down = false; + +canvas.addEventListener("mousedown", (evt) => { + mouse_x = evt.clientX; + mouse_y = evt.clientY; + mouse_down = true; +}); + +canvas.addEventListener("mousemove", (evt) => { + if (mouse_down) { + // TODO: numerically stable solution + cx += evt.clientX - mouse_x; + cy += evt.clientY - mouse_y; + + mouse_x = evt.clientX; + mouse_y = evt.clientY; + } +}); + +canvas.addEventListener("mouseup", (evt) => { + mouse_down = false; +}); + +canvas.addEventListener("mouseleave", (evt) => { + mouse_down = false; +}); + +canvas.addEventListener("wheel", (event) => { + const ZOOM_STRENGTH = -0.005; + zoom += event.deltaY * ZOOM_STRENGTH; + zoom = Math.min(Math.max(zoom, 0.0), 4.0); +}); + +function to_color(num) { + let alpha = (num >> 24) & 0xff; + let red = (num >> 16) & 0xff; + let green = (num >> 8) & 0xff; + let blue = num & 0xff; + + return [red, green, blue, alpha]; +} diff --git a/editor/style.css b/editor/style.css new file mode 100644 index 0000000..7d70bb1 --- /dev/null +++ b/editor/style.css @@ -0,0 +1,21 @@ +body { + margin: 0; + height: 100vh; + display: flex; + flex-direction: row; + align-items: stretch; + overflow-y: hidden; +} + +#left-pane, #right-pane { + max-width: 25vw; +} + +#middle-pane { + flex-grow: 1; +} + +#main-canvas { + width: 100%; + height: 100vh; +} diff --git a/font/StacklineClassic-Medium.otf b/font/StacklineClassic-Medium.otf new file mode 100644 index 0000000000000000000000000000000000000000..ab866933bf55320f416980832dd19a0e4560ef80 GIT binary patch literal 18000 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 Ylj_hfBTUD22>(|~wC}e+zwR9TAI_-=o&W#< literal 0 HcmV?d00001 diff --git a/font/StacklineClassic-Medium.pfs b/font/StacklineClassic-Medium.pfs new file mode 100644 index 0000000..dfaa353 --- /dev/null +++ b/font/StacklineClassic-Medium.pfs @@ -0,0 +1,108 @@ +Stackline Classic +Shad Amethyst +Medium +11:11:10:9:-1:0:10 +65:AAPAzBhGCMEf4wRgnjgAAA== +77:ABwZxj3G6MkYIwRgnjgAAA== +66:AB/hgjBGCP4YIwRgn+AAAA== +67:AAfhwjAGAMAYAwBwh+AAAA== +68:AB/hgjBGCMEYIwRgn+AAAA== +69:AB/xgjAGAPgYAwBgn/AAAA== +70:AB/xgjAGAPgYAwBgHgAAAA== +71:AAfhwjAGAMAY4wRwh+AAAA== +72:AB45gjBGCP8YIwRgnjgAAA== +73:AAPAMAYAwBgDAGAMA8AAAA== +74:AAHgGAMAYAwBgjBGB4AAAA== +75:AB5xhDCGIPgYgwhhHnAAAA== +76:AB4BgDAGAMAYAwBgn/AAAA== +78:AB5xhDiHkNoZwxhhHnAAAA== +79:AAfhxjBGCMEYIwRxh+AAAA== +80:AB/BhDCGEPwYAwBgHgAAAA== +81:AAfhxjBGCMEZIzRzh/ADAA== +82:AB/hgjBGCP4ZAxBhHjAAAA== +83:AAfhwjAGAH4AYAxDh+AAAA== +84:AA/xMgYAwBgDAGAMA8AAAA== +85:AB45gjBGCMEYIwRgh+AAAA== +86:AB45gjBGCMEYIYgaAYAAAA== +87:AB45gjBGCMkZIyQ1A0AAAA== +88:ABxxhBkBwBAHATBDHHAAAA== +89:AB45gjBHEHQHAGAMA8AAAA== +90:AB/yDAMAwDAMAwBgn/AAAA== +112:AAAAAD8DEGIMQfAwBgHgAA== +62:AAAAYAYAYAcBgGAYAAAAAA== +60:AAAAGAYBgOAGAGAGAAAAAA== +43:AAEAIAQAgf8CAEAIAQAAAA== +9532:AACAEAIAQP+BACAEAIAAAA== +9474:AACAEAIAQAgBACAEAIAAAA== +9472:AAAAAAAAAP+AAAAAAAAAAA== +118:AAAAAD3DEGIMQcgeAYAAAA== +115:AAAAAA4DIGAHADAmA4AAAA== +8853:AA/jBkRIiX0iJETBj+AAAA== +8854:AA/jBkBICX0gJATBj+AAAA== +8855:AA/jBlFJSRElJRTBj+AAAA== +8856:AA/jBkFISREkJQTBj+AAAA== +8857:AA/jBllKSREkpTTBj+AAAA== +45:AAAAAAAAAf8AAAAAAAAAAA== +124:AAEAIAQAgBACAEAIAQAAAA== +109:AAAAAH2GSMkZIyRknrgAAA== +110:AAAAAD8DEGIMQYgxD3AAAA== +111:AAAAAA8DEGIMQYgxA8AAAA== +113:AAAAAA+DIGQMgPACAEAcAA== +114:AAAAAB0B0DAGAMAYB4AAAA== +11363:AR/BlDSHEPwYBwBgHgAAAA== +9013:AAAAAAAEEMYNgOAIAQAAAA== +116:AAAAYAwD4DAGAMAaAYAAAA== +117:AAAAADiDEGIMQYgxA8AAAA== +94:AAEAIA4DYMYQQAAAAAAAAA== +97:AAAAAA8CEAIHwcgxA9AAAA== +98:AAABwBgDAHwMQYgxD8AAAA== +99:AAAAAA8DEGAMAYAxA8AAAA== +100:AAAADgCAED4MQYgxA/AAAA== +101:AAAAAA8DEGIPwYAxA8AAAA== +102:AAAAOAyBgDAPAMAYB4AAAA== +103:AAAAAA/DEGIMQPgBBCB4AA== +104:AAABwBgDAHwMQYgxDnAAAA== +108:AAAA4AwBgDAGAMAYAYAAAA== +107:AAABwBnDEHQNAZAxDnAAAA== +105:AAAAMAYAABgDAGAMAcAAAA== +106:AAAAGAMAAAwBgDAGBMBwAA== +119:AAAAAHjmCMEZIyQ1A8AAAA== +120:AAAAADmDIDgCAOAmDOAAAA== +121:AAAAAD3DEGIGgGAEAcAAAA== +122:AAAAAB/CMAwDAMAYh/AAAA== +9673:AAAA+CCF0LoXQgg+AAAAAA== +9675:AAAA+CCEEIIQQgg+AAAAAA== +9677:AAAA+CCFUKoVQgg+AAAAAA== +9678:AAAA+CCF0KoXQgg+AAAAAA== +9676:AAAAqCCAAIIAAggqAAAAAA== +9679:AAAA+D+H8P4fw/g+AAAAAA== +9680:AAAA+DyHkPIeQ8g+AAAAAA== +9681:AAAA+CeE8J4Twng+AAAAAA== +9682:AAAA+CCEEP4fw/g+AAAAAA== +9683:AAAA+D+H8P4QQgg+AAAAAA== +9684:AAAA+CeE8J4QQgg+AAAAAA== +9685:AAAA+CeE8P4fw/g+AAAAAA== +9686:AAAA4DwHgPAeA8A4AAAAAA== +9687:AAAAOAeA8B4DwHgOAAAAAA== +9716:AAAA+CSEkPIQQgg+AAAAAA== +9717:AAAA+CCEEPISQkg+AAAAAA== +9718:AAAA+CCEEJ4SQkg+AAAAAA== +9719:AAAA+CSEkJ4QQgg+AAAAAA== +9655:AAABgCwEYIMRgsBgAAAAAA== +9661:AAAB/CCCIEQFAKAIAQAAAA== +9665:AAAABgNBiMEGIDQBgAAAAA== +9651:AAAAIAQBQCgIgRBBD+AAAA== +8593:AAEAcB8GsJICAEAIAQAAAA== +8592:AAAAYBgGAf8YAYAYAAAAAA== +8594:AAAAMAMAMf8AwDAMAAAAAA== +8595:AAEAIAQAgJIawfAcAQAAAA== +8280:AAOAIAQICccgIEAIA4AAAA== +8860:AA/jBkBL6QEvpATBj+AAAA== +63:AAPAjAGAMAwDAAAMAYAAAA== +191:AAGAMAAAwDAMAYAxA8AAAA== +934:AAEAIA4CoFQKgOAIAQAAAA== +42826:AAAAAA4CIf8IgOAAAAAAAA== +10689:AA/jBkhIiQkiJITBj+AAAA== +10688:AA/jBkJIiSEiJCTBj+AAAA== +8805:AA/jBkhIiQkiJOTBj+AAAA== +8804:AA/jBkJIiSEiJOTBj+AAAA== \ No newline at end of file