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 0000000..ab86693 Binary files /dev/null and b/font/StacklineClassic-Medium.otf differ 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