Split editor across multiple files, begin inspector pane

main
Shad Amethyst 2 years ago
parent c01ae784a8
commit bf483159bf
Signed by: amethyst
GPG Key ID: D970C8DD1D6DEE36

@ -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];
}

@ -17,8 +17,20 @@
Your browser needs to support canvases, sowwy :(
</canvas>
</div>
<div id="right-pane">
<div id="right-pane" class="">
<h2>Coordinates:</h2>
<div id="coordinates"></div>
<h2>State:</h2>
<div id="state"></div>
<h2>Signal:</h2>
<div id="signal" class="indent">
<h3>Stack:</h3>
<ol id="stack"></ol>
</div>
<h2>Tile:</h2>
<div id="tile" class="indent">
<span>Type:</span> <span id="tile-name"></span>
</div>
</div>
</body>
</html>

@ -6,12 +6,12 @@ import init, {
available_tiles,
} from "/stackline-wasm/pkg/stackline_wasm.js";
import * as canvas from "./canvas.js";
import * as inspect from "./inspect.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);
@ -19,268 +19,48 @@ await Promise.all(promises);
font = await font.loaded;
document.fonts.add(font);
let world = World.deserialize(await (await fetch("/stackline/tests/other/prime.json")).json());
export let world = World.deserialize(await (await fetch("/stackline/tests/other/prime.json")).json());
world.init();
// 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 selected = null;
let running = null;
let tile = world.get(4, 0);
tile.signal = new Signal();
tile.state = "Active";
world.set(4, 0, tile);
// 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());
console.log(world.serialize());
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);
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);
}
export let selected = null;
export function select(x, y) {
selected = [x, y];
inspect.update_selected(x, y);
}
function resize() {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
draw();
}
function loop() {
draw();
window.requestAnimationFrame(loop);
}
canvas.addEventListener("mousedown", (evt) => {
click_x = mouse_x = evt.clientX;
click_y = mouse_y = evt.clientY;
mouse_down = true;
});
canvas.addEventListener("mousemove", (evt) => {
hovered = true;
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;
});
export let running = null;
canvas.addEventListener("mouseup", (evt) => {
mouse_down = false;
export function step() {
world.init();
world.step();
let dist = Math.sqrt((mouse_x - click_x) ** 2 + (mouse_y - click_y) ** 2);
if (dist < 10) {
selected = get_hovered();
if (selected) {
inspect.update_selected(selected[0], selected[1]);
}
});
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 !== null) {
clearInterval(running);
running = null;
} else if (event.shiftKey) {
world.init();
running = setInterval(() => {
world.step();
}, 100);
} else {
world.init();
world.step();
}
} else if (event.code === "KeyG") {
grid = !grid;
export function play() {
world.init();
running = setInterval(() => {
world.step();
if (selected) {
inspect.update_selected(selected[0], selected[1]);
}
}
});
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];
}, 100);
}
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];
export function pause() {
clearInterval(running);
running = null;
}
canvas.init();
inspect.init();

@ -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);
// }
}

@ -1,3 +1,8 @@
@font-face {
font-family: "Stackline Classic";
src: url("/font/StacklineClassic-Medium.otf");
}
body {
margin: 0;
height: 100vh;
@ -8,7 +13,13 @@ body {
}
#left-pane, #right-pane {
min-width: 20em;
max-width: 25vw;
background: #18181b;
color: #f0f0f0;
padding: 1.5em 1em;
font-family: monospace;
font-size: 15px;
}
#middle-pane {
@ -19,3 +30,79 @@ body {
width: 100%;
height: 100vh;
}
h2 {
font-size: inherit;
margin: 0;
color: white;
/* font-weight: normal; */
/* text-decoration: underline; */
}
h3 {
font-size: inherit;
margin: 0;
color: white;
font-weight: normal;
}
#right-pane:not(.selected)::before {
content: "Nothing selected";
font-style: italic;
color: #808080;
}
#right-pane:not(.selected) > * {
display: none;
}
#signal, #tile {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
#signal:not(.has-signal), #tile:not(.has-tile) {
padding-left: 0;
border-left: none;
margin-left: 0;
}
#signal:not(.has-signal) > *, #tile:not(.has-tile) > * {
display: none;
}
#signal:not(.has-signal)::before {
content: "No signal";
}
#tile:not(.has-tile)::before {
content: "No tile";
}
#signal:not(.has-signal)::before, #tile:not(.has-signal)::before {
font-style: italic;
color: #808080;
}
#stack {
list-style: none;
padding-left: 0em;
margin: 0 0;
}
#stack > li {
margin-top: 2px;
margin-bottom: 2px;
}
#stack > li::before {
content: "\25b6";
margin-right: 0.5em;
color: #808080;
}
.indent {
margin-left: 0.5em;
padding-left: calc(0.5em - 1px);
border-left: 1px solid #808080;
}

Binary file not shown.

@ -1,7 +1,7 @@
Stackline Classic
Shad Amethyst
Medium
11:11:10:9:-1:0:10
11:11:10:9:-1:0:10:0
65:AAPAzBhGCMEf4wRgnjgAAA==
77:ABwZxj3G6MkYIwRgnjgAAA==
66:AB/hgjBGCP4YIwRgn+AAAA==
@ -30,7 +30,7 @@ Medium
90:AB/yDAMAwDAMAwBgn/AAAA==
112:AAAAAD8DEGIMQfAwBgHgAA==
62:AAAAYAYAYAcBgGAYAAAAAA==
60:AAAAGAYBgOAGAGAGAAAAAA==
60:AAAAMAwDAcAMAMAMAAAAAA==
43:AAEAIAQAgf8CAEAIAQAAAA==
9532:AACAEAIAQP+BACAEAIAAAA==
9474:AACAEAIAQAgBACAEAIAAAA==
@ -105,4 +105,18 @@ Medium
10689:AA/jBkhIiQkiJITBj+AAAA==
10688:AA/jBkJIiSEiJCTBj+AAAA==
8805:AA/jBkhIiQkiJOTBj+AAAA==
8804:AA/jBkJIiSEiJOTBj+AAAA==
8804:AA/jBkJIiSEiJOTBj+AAAA==
49:AACAMA4CwBgDAGAMA8AAAA==
48:AAPAxBiDEGIMQYgxA8AAAA==
50:AAPAhACAMAwDAMAxB+AAAA==
51:AAPAjAGAMBwAwBgjA8AAAA==
52:AABAGAcBoGQPwBACAOAAAA==
53:AAfgxBgDAHwAwBgjA8AAAA==
54:AAPAxBgDAHwMQYgxA8AAAA==
55:AAfgjAGAYAwDAGAMAYAAAA==
56:AAPAxBiDEDwMQYgxA8AAAA==
57:AAPAxBiDED4AQQgxA8AAAA==
46:AAAAAAAAAAAAAAAYAwAAAA==
44:AAAAAAAAAAAAAAAYAwDAAA==
9500:CAEAIAQAgB+CAEAIAQAgAA==
9492:CAEAIAQAgB+AAAAAAAAAAA==
Loading…
Cancel
Save