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; let start = performance.now(); export function draw() { world.set_blink_duration((performance.now() - start) / 1000.0); 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]; }