diff --git a/editor/canvas.js b/editor/canvas.js new file mode 100644 index 0000000..c089fec --- /dev/null +++ b/editor/canvas.js @@ -0,0 +1,255 @@ +import { + world, + select, + running, + play, + step, + pause, + selected, +} from "./index.js"; + +export const canvas = document.getElementById("main-canvas"); +export const ctx = canvas.getContext("2d"); + +// let world = World.new(); +let cx = 0; +let cy = 0; +let zoom = 1; +let grid = true; + +let click_x = 0, click_y = 0; +let mouse_x = 0, mouse_y = 0; +let mouse_down = false; +let hovered = false; + +export 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); + + function stroke_rect(x, y, width, height) { + ctx.lineWidth = zoom_factor; + ctx.strokeRect( + Math.round(x * tile_size + cx + canvas.width / 2) - zoom_factor * 1.5, + Math.round(y * tile_size + cy + canvas.height / 2) - zoom_factor * 1.5, + Math.round(width * tile_size) + zoom_factor * 2, + Math.round(height * tile_size) + zoom_factor * 2, + ); + } + + function fill_rect(x, y, width, height) { + ctx.fillRect( + Math.round(x * tile_size + cx + canvas.width / 2) - zoom_factor * 1, + Math.round(y * tile_size + cy + canvas.height / 2) - zoom_factor * 1, + Math.round(width * tile_size) + zoom_factor * 1, + Math.round(height * tile_size) + zoom_factor * 1, + ); + } + + let panes = []; + + for (let name of world.panes()) { + let pane = world.get_pane(name); + + panes.push({ + x: pane.x, + y: pane.y, + width: pane.width, + height: pane.height + }); + + ctx.strokeStyle = "rgba(128, 128, 128, 0.5)"; + stroke_rect( + pane.x, + pane.y, + pane.width, + pane.height, + ); + + 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]); + if (ch !== " ") { + let fg = to_color(chars[index + 1]); + let bg = chars[index + 2] === 0 ? null : to_color(chars[index + 2]); + + 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 if (grid) { + // 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; + } + } + + if (inside || width < 100 && height < 100) { + ctx.fillStyle = inside ? "#404040" : "#303030"; + + ctx.fillRect( + Math.round(x2 + tile_size / 2) - zoom_factor, + Math.round(y2 + tile_size / 2) - zoom_factor, + zoom_factor, + zoom_factor + ); + } + } + } + } + + if (hovered) { + let [x, y] = get_hovered(); + + ctx.strokeStyle = "rgba(230, 255, 230, 0.2)"; + ctx.fillStyle = "rgba(230, 255, 230, 0.07)"; + stroke_rect(x, y, 1, 1); + fill_rect(x, y, 1, 1); + } + + if (selected) { + let [x, y] = selected; + ctx.strokeStyle = "rgba(230, 230, 255, 0.1)"; + stroke_rect(x, y, 1, 1); + } +} + +export function resize() { + canvas.width = canvas.clientWidth; + canvas.height = canvas.clientHeight; + + draw(); +} + +export function loop() { + draw(); + window.requestAnimationFrame(loop); +} + +export function init() { + canvas.addEventListener("mousedown", (evt) => { + click_x = mouse_x = evt.clientX - canvas.offsetLeft; + click_y = mouse_y = evt.clientY - canvas.offsetTop; + mouse_down = true; + }); + + canvas.addEventListener("mousemove", (evt) => { + hovered = true; + + if (mouse_down) { + // TODO: numerically stable solution + cx += (evt.clientX - canvas.offsetLeft) - mouse_x; + cy += (evt.clientY - canvas.offsetTop) - mouse_y; + } + + mouse_x = evt.clientX - canvas.offsetLeft; + mouse_y = evt.clientY - canvas.offsetTop; + }); + + canvas.addEventListener("mouseup", (evt) => { + mouse_down = false; + + let dist = Math.sqrt((mouse_x - click_x) ** 2 + (mouse_y - click_y) ** 2); + if (dist < 10) { + select(...get_hovered()); + } + }); + + canvas.addEventListener("mouseenter", (evt) => { + hovered = true; + }); + + canvas.addEventListener("mouseleave", (evt) => { + mouse_down = false; + hovered = false; + }); + + canvas.addEventListener("wheel", (event) => { + const ZOOM_STRENGTH = -0.005; + let old_zoom = zoom; + zoom += event.deltaY * ZOOM_STRENGTH; + zoom = Math.min(Math.max(zoom, 0.0), 4.0); + + let delta = Math.ceil(Math.pow(2, zoom)) / Math.ceil(Math.pow(2, old_zoom)); + cx *= delta; + cy *= delta; + }); + + window.addEventListener("keydown", (event) => { + if (hovered) { + if (event.code === "Space") { + if (running) { + pause(); + } else if (event.shiftKey) { + play(); + } else { + step(); + } + } else if (event.code === "KeyG") { + grid = !grid; + } + } + }); + + + resize(); + window.addEventListener("resize", resize); + window.requestAnimationFrame(loop); +} + +export function get_hovered() { + let zoom_factor = Math.ceil(Math.pow(2, zoom)); + let tile_size = 10 * zoom_factor; + + let x = Math.floor((mouse_x - canvas.width / 2 - cx) / tile_size); + let y = Math.floor((mouse_y - canvas.height / 2 - cy) / tile_size); + + return [x, y]; +} + +export 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/index.html b/editor/index.html index 3347583..26d8824 100644 --- a/editor/index.html +++ b/editor/index.html @@ -17,8 +17,20 @@ Your browser needs to support canvases, sowwy :( -