add brush preview

This commit is contained in:
liquidex 2024-09-08 12:09:14 +02:00
parent 4430d6d125
commit 37520f34f7
7 changed files with 167 additions and 22 deletions

View file

@ -26,6 +26,10 @@ body {
line-height: var(--line-height); line-height: var(--line-height);
} }
* {
box-sizing: border-box;
}
/* Fonts */ /* Fonts */
:root { :root {

View file

@ -1,4 +1,5 @@
import { CodeEditor, getLineStart } from "rkgk/code-editor.js"; import { CodeEditor, getLineStart } from "rkgk/code-editor.js";
import { BrushPreview } from "rkgk/brush-preview.js";
const defaultBrush = ` const defaultBrush = `
-- This is your brush. -- This is your brush.

57
static/brush-preview.js Normal file
View file

@ -0,0 +1,57 @@
import { Pixmap } from "rkgk/haku.js";
export class BrushPreview extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.canvas = this.appendChild(document.createElement("canvas"));
this.ctx = this.canvas.getContext("2d");
this.#resizeCanvas();
}
#resizeCanvas() {
this.canvas.width = this.clientWidth;
this.canvas.height = this.clientHeight;
if (this.pixmap != null) {
this.pixmap.destroy();
}
this.pixmap = new Pixmap(this.canvas.width, this.canvas.height);
}
#renderBrushInner(haku) {
haku.resetVm();
let evalResult = haku.evalBrush();
if (evalResult.status != "ok") {
return { status: "error", phase: "eval", result: evalResult };
}
this.pixmap.clear();
let renderResult = haku.renderValue(
this.pixmap,
this.canvas.width / 2,
this.canvas.height / 2,
);
if (renderResult.status != "ok") {
return { status: "error", phase: "render", result: renderResult };
}
this.ctx.putImageData(this.pixmap.getImageData(), 0, 0);
return { status: "ok" };
}
renderBrush(haku) {
this.classList.remove("error");
let result = this.#renderBrushInner(haku);
if (result.status == "error") {
this.classList.add("error");
}
return result;
}
}
customElements.define("rkgk-brush-preview", BrushPreview);

View file

@ -32,7 +32,7 @@ main {
padding: 16px; padding: 16px;
display: grid; display: grid;
grid-template-columns: [left] 1fr [right-resize] auto [right] var(--right-width); grid-template-columns: [left] 1fr [right-resize] auto [right] minmax(0, var(--right-width));
/* Pass all events through. Children may receive events as normal. */ /* Pass all events through. Children may receive events as normal. */
pointer-events: none; pointer-events: none;
@ -43,23 +43,34 @@ main {
&>.right { &>.right {
grid-column: right / right; grid-column: right / right;
height: fit-content;
min-height: 0; min-height: 0;
max-height: 100%;
display: flex; display: grid;
flex-direction: row; grid-template-rows: minmax(0, min-content);
justify-content: stretch; grid-template-columns: [floating] max-content [resize] min-content [docked] auto;
&>rkgk-resize-handle { padding-left: 16px;
flex-shrink: 0;
height: auto; pointer-events: none;
&>* {
min-width: 0;
min-height: 0;
} }
&>rkgk-brush-editor { &>rkgk-resize-handle {
height: auto; pointer-events: auto;
overflow: auto; }
flex-grow: 1;
&>.docked>rkgk-brush-editor {
max-height: 100%;
pointer-events: auto;
}
&>.floating>rkgk-brush-preview {
width: 128px;
height: 128px;
pointer-events: auto;
} }
} }
} }
@ -175,6 +186,8 @@ rkgk-code-editor {
position: relative; position: relative;
width: 100%; width: 100%;
overflow: auto;
&>.layer { &>.layer {
position: absolute; position: absolute;
left: 0; left: 0;
@ -259,14 +272,29 @@ rkgk-code-editor {
resize: none; resize: none;
white-space: pre-wrap; white-space: pre-wrap;
border: none; border: none;
&:focus {
outline: none;
}
}
&:has(textarea:focus) {
outline: 1px solid var(--color-brand-blue);
outline-offset: 4px;
} }
} }
/* Brush editor */ /* Brush editor */
rkgk-brush-editor { rkgk-brush-editor.rkgk-panel {
padding: 12px; padding: 12px;
display: flex;
flex-direction: column;
gap: 4px;
position: relative;
&>.text-area { &>.text-area {
display: block; display: block;
width: 100%; width: 100%;
@ -283,8 +311,8 @@ rkgk-brush-editor {
} }
&>.error-header { &>.error-header {
margin-top: 1em; margin: 0;
margin-bottom: 0; margin-top: 0.5em;
font-size: 1rem; font-size: 1rem;
color: var(--color-error); color: var(--color-error);
} }
@ -296,6 +324,36 @@ rkgk-brush-editor {
} }
} }
/* Brush preview */
rkgk-brush-preview {
--checkerboard-light: #f2f2f2;
--checkerboard-dark: #e1e1e1;
--checkerboard-size: 64px;
display: block;
position: relative;
background:
repeating-conic-gradient(var(--checkerboard-light) 0% 25%, var(--checkerboard-dark) 0% 50%)
50% 50% / var(--checkerboard-size) var(--checkerboard-size);
border-radius: 4px;
&.error {
&>canvas {
display: none;
}
&::before {
content: "(error)";
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
}
}
/* Welcome screen */ /* Welcome screen */
rkgk-welcome { rkgk-welcome {
@ -359,7 +417,8 @@ rkgk-connection-status {
} }
&.icon { &.icon {
padding: 4px 4px; width: 24px;
height: 24px;
} }
&:first-child { &:first-child {

View file

@ -16,6 +16,7 @@ let main = document.querySelector("main");
let canvasRenderer = main.querySelector("rkgk-canvas-renderer"); let canvasRenderer = main.querySelector("rkgk-canvas-renderer");
let reticleRenderer = main.querySelector("rkgk-reticle-renderer"); let reticleRenderer = main.querySelector("rkgk-reticle-renderer");
let brushEditor = main.querySelector("rkgk-brush-editor"); let brushEditor = main.querySelector("rkgk-brush-editor");
let brushPreview = main.querySelector("rkgk-brush-preview");
let welcome = main.querySelector("rkgk-welcome"); let welcome = main.querySelector("rkgk-welcome");
let connectionStatus = main.querySelector("rkgk-connection-status"); let connectionStatus = main.querySelector("rkgk-connection-status");
@ -260,10 +261,25 @@ function readUrl(urlString) {
updateUrl(session, canvasRenderer.viewport), updateUrl(session, canvasRenderer.viewport),
); );
brushEditor.renderHakuResult("Compilation", currentUser.setBrush(brushEditor.code)); function compileBrush() {
let compileResult = currentUser.setBrush(brushEditor.code);
brushEditor.renderHakuResult("Compilation", compileResult);
if (compileResult.status != "ok") return;
let previewResult = brushPreview.renderBrush(currentUser.haku);
if (previewResult.status == "error") {
brushEditor.renderHakuResult(
previewResult.phase == "eval" ? "Evaluation" : "Rendering",
previewResult.result,
);
}
}
compileBrush();
brushEditor.addEventListener(".codeChanged", async () => { brushEditor.addEventListener(".codeChanged", async () => {
flushPlotQueue(); flushPlotQueue();
brushEditor.renderHakuResult("Compilation", currentUser.setBrush(brushEditor.code)); compileBrush();
session.sendSetBrush(brushEditor.code); session.sendSetBrush(brushEditor.code);
}); });

View file

@ -47,6 +47,7 @@ export class ResizeHandle extends HTMLElement {
let mouseDown = await listen([this, "mousedown"]); let mouseDown = await listen([this, "mousedown"]);
let startingSize = this.size; let startingSize = this.size;
if (mouseDown.button == 0) { if (mouseDown.button == 0) {
mouseDown.preventDefault();
this.classList.add("dragging"); this.classList.add("dragging");
while (true) { while (true) {

View file

@ -22,6 +22,7 @@
import "rkgk/live-reload.js"; import "rkgk/live-reload.js";
import "rkgk/brush-editor.js"; import "rkgk/brush-editor.js";
import "rkgk/brush-preview.js";
import "rkgk/canvas-renderer.js"; import "rkgk/canvas-renderer.js";
import "rkgk/connection-status.js"; import "rkgk/connection-status.js";
import "rkgk/framework.js"; import "rkgk/framework.js";
@ -59,16 +60,22 @@
<hr> <hr>
<a href="/docs/rkgk.html">Manual</a> <a href="/docs/rkgk.html">Manual</a>
</div> </div>
<div class="right"> <div class="right">
<div class="floating">
<rkgk-brush-preview></rkgk-brush-preview>
</div>
<rkgk-resize-handle <rkgk-resize-handle
data-direction="vertical" data-direction="vertical"
data-target="panels-overlay" data-target="panels-overlay"
data-target-property="--right-width" data-target-property="--right-width"
data-init-size="384" data-init-size="512"
data-min-size="128"></rkgk-resize-handle> data-min-size="384"></rkgk-resize-handle>
<div class="docked">
<rkgk-brush-editor></rkgk-brush-editor> <rkgk-brush-editor></rkgk-brush-editor>
</div> </div>
</div> </div>
</div>
<rkgk-welcome> <rkgk-welcome>
<dialog name="welcome-dialog" class="rkgk-panel"> <dialog name="welcome-dialog" class="rkgk-panel">