parent
c01ae784a8
commit
bf483159bf
@ -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];
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
import {
|
||||
world,
|
||||
} from "./index.js";
|
||||
|
||||
import {
|
||||
available_tiles,
|
||||
} from "/stackline-wasm/pkg/stackline_wasm.js";
|
||||
|
||||
const right_pane = document.getElementById("right-pane");
|
||||
const coordinates_elem = document.getElementById("coordinates");
|
||||
const stack_elem = document.getElementById("stack");
|
||||
const signal_elem = document.getElementById("signal");
|
||||
const state_elem = document.getElementById("state");
|
||||
const tile_elem = document.getElementById("tile");
|
||||
const tile_name_elem = document.getElementById("tile-name");
|
||||
|
||||
export function update_selected(x, y) {
|
||||
coordinates.innerText = `(${x}, ${y})`;
|
||||
|
||||
let full_tile = world.get(x, y);
|
||||
|
||||
if (!full_tile) {
|
||||
right_pane.classList.remove("selected");
|
||||
return;
|
||||
}
|
||||
|
||||
let signal = full_tile.signal;
|
||||
while (stack_elem.children.length > 0) {
|
||||
stack_elem.removeChild(stack_elem.firstChild);
|
||||
}
|
||||
|
||||
if (signal) {
|
||||
signal_elem.classList.add("has-signal");
|
||||
for (let element of signal.stack) {
|
||||
let li = document.createElement("li");
|
||||
if (typeof element === "string") {
|
||||
li.innerText = `"${element}"`;
|
||||
} else if (typeof element === "number") {
|
||||
li.innerText = element.toString();
|
||||
} else {
|
||||
throw new Error("Unexpected element type: " + typeof element);
|
||||
}
|
||||
stack_elem.appendChild(li);
|
||||
}
|
||||
|
||||
signal.free();
|
||||
} else {
|
||||
signal_elem.classList.remove("has-signal");
|
||||
}
|
||||
|
||||
let tile = full_tile.tile;
|
||||
if (tile) {
|
||||
tile_elem.classList.add("has-tile");
|
||||
|
||||
let name = Object.keys(tile)[0];
|
||||
tile_name_elem.innerText = name;
|
||||
} else {
|
||||
tile_elem.classList.remove("has-tile");
|
||||
}
|
||||
|
||||
state_elem.innerText = full_tile.state;
|
||||
|
||||
full_tile.free();
|
||||
|
||||
right_pane.classList.add("selected");
|
||||
}
|
||||
|
||||
export function init() {
|
||||
// for (let name of available_tiles()) {
|
||||
// let option = document.createElement("option");
|
||||
// option.innerText = name;
|
||||
// option.value = name;
|
||||
// tile_name_elem.appendChild(option);
|
||||
// }
|
||||
}
|
Binary file not shown.
Loading…
Reference in new issue