sidebar layout
switch the app from floating panels to a static sidebar on the right with resizable tools expect more layout bugs from now on
This commit is contained in:
parent
f78f3136d9
commit
0ddc42c00f
10 changed files with 272 additions and 178 deletions
|
@ -18,6 +18,9 @@
|
||||||
--font-monospace: "Iosevka Hyperlegible", monospace;
|
--font-monospace: "Iosevka Hyperlegible", monospace;
|
||||||
--line-height: 1.5;
|
--line-height: 1.5;
|
||||||
--line-height-em: 1.5em;
|
--line-height-em: 1.5em;
|
||||||
|
|
||||||
|
--z-resize-handle: 50;
|
||||||
|
--z-modal: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reset */
|
/* Reset */
|
||||||
|
@ -92,6 +95,10 @@ button.icon {
|
||||||
input {
|
input {
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: 1px solid var(--color-panel-border);
|
border-bottom: 1px solid var(--color-panel-border);
|
||||||
|
|
||||||
|
&:hover:not(:focus) {
|
||||||
|
background-color: var(--color-shaded-background);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*:focus-visible {
|
*:focus-visible {
|
||||||
|
@ -101,15 +108,24 @@ input {
|
||||||
|
|
||||||
/* Modal dialogs */
|
/* Modal dialogs */
|
||||||
|
|
||||||
dialog:not([open]) {
|
dialog {
|
||||||
/* Weird this doesn't seem to work by default. */
|
&:not([open]) {
|
||||||
|
/* Default to dialogs being invisible.
|
||||||
|
Otherwise dialogs placed on the page via HTML display on top of everything. */
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog::backdrop {
|
&::backdrop {
|
||||||
background-color: var(--dialog-backdrop);
|
background-color: var(--dialog-backdrop);
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog:not([open]) {
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog::backdrop {
|
||||||
|
}
|
||||||
|
|
||||||
/* Details */
|
/* Details */
|
||||||
|
|
||||||
|
@ -217,8 +233,6 @@ abbr {
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
|
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: 50% 50%;
|
background-position: 50% 50%;
|
||||||
|
|
|
@ -132,8 +132,6 @@ export class BrushBox extends HTMLElement {
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.saveData.attachToElement(this);
|
this.saveData.attachToElement(this);
|
||||||
|
|
||||||
this.classList.add("rkgk-panel");
|
|
||||||
|
|
||||||
this.brushes = [];
|
this.brushes = [];
|
||||||
|
|
||||||
this.brushesContainer = this.appendChild(document.createElement("div"));
|
this.brushesContainer = this.appendChild(document.createElement("div"));
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import { CodeEditor, Selection } from "rkgk/code-editor.js";
|
import { CodeEditor, Selection } from "rkgk/code-editor.js";
|
||||||
import { SaveData } from "rkgk/framework.js";
|
import { SaveData } from "rkgk/framework.js";
|
||||||
import { builtInPresets } from "rkgk/brush-box.js";
|
import { builtInPresets } from "rkgk/brush-box.js";
|
||||||
|
import { ResizeHandle } from "rkgk/resize-handle.js";
|
||||||
|
import { BrushPreview } from "rkgk/brush-preview.js";
|
||||||
|
import { BrushCostGauges } from "rkgk/brush-cost.js";
|
||||||
|
|
||||||
export class BrushEditor extends HTMLElement {
|
export class BrushEditor extends HTMLElement {
|
||||||
saveData = new SaveData("brushEditor");
|
saveData = new SaveData("brushEditor");
|
||||||
|
@ -10,13 +13,14 @@ export class BrushEditor extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.classList.add("rkgk-panel");
|
|
||||||
|
|
||||||
this.saveData.attachToElement(this);
|
this.saveData.attachToElement(this);
|
||||||
|
|
||||||
const defaultBrush = builtInPresets[0];
|
const defaultBrush = builtInPresets[0];
|
||||||
|
|
||||||
this.nameEditor = this.appendChild(document.createElement("input"));
|
this.editorContainer = this.appendChild(document.createElement("div"));
|
||||||
|
this.editorContainer.classList.add("editor");
|
||||||
|
|
||||||
|
this.nameEditor = this.editorContainer.appendChild(document.createElement("input"));
|
||||||
this.nameEditor.value = this.saveData.get("name", defaultBrush.name);
|
this.nameEditor.value = this.saveData.get("name", defaultBrush.name);
|
||||||
this.nameEditor.classList.add("name");
|
this.nameEditor.classList.add("name");
|
||||||
this.nameEditor.addEventListener("input", () => {
|
this.nameEditor.addEventListener("input", () => {
|
||||||
|
@ -31,7 +35,7 @@ export class BrushEditor extends HTMLElement {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.codeEditor = this.appendChild(
|
this.codeEditor = this.editorContainer.appendChild(
|
||||||
new CodeEditor([
|
new CodeEditor([
|
||||||
{
|
{
|
||||||
className: "layer-syntax",
|
className: "layer-syntax",
|
||||||
|
@ -59,11 +63,28 @@ export class BrushEditor extends HTMLElement {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.errorHeader = this.appendChild(document.createElement("h1"));
|
this.output = document.createElement("output");
|
||||||
this.errorHeader.classList.add("error-header");
|
|
||||||
|
|
||||||
this.errorArea = this.appendChild(document.createElement("pre"));
|
this.appendChild(
|
||||||
this.errorArea.classList.add("errors");
|
new ResizeHandle({
|
||||||
|
direction: "horizontal",
|
||||||
|
inverse: true,
|
||||||
|
targetElement: this,
|
||||||
|
targetProperty: "--brush-preview-height",
|
||||||
|
initSize: 192,
|
||||||
|
minSize: 64,
|
||||||
|
}),
|
||||||
|
).classList.add("always-visible");
|
||||||
|
|
||||||
|
this.appendChild(this.output);
|
||||||
|
|
||||||
|
this.ok = this.output.appendChild(document.createElement("div"));
|
||||||
|
this.ok.classList.add("ok");
|
||||||
|
this.brushPreview = this.ok.appendChild(new BrushPreview());
|
||||||
|
this.brushCostGauges = this.ok.appendChild(new BrushCostGauges());
|
||||||
|
|
||||||
|
this.errors = this.output.appendChild(document.createElement("pre"));
|
||||||
|
this.errors.classList.add("errors");
|
||||||
|
|
||||||
// NOTE(localStorage): Migration from old storage key.
|
// NOTE(localStorage): Migration from old storage key.
|
||||||
localStorage.removeItem("rkgk.brushEditor.code");
|
localStorage.removeItem("rkgk.brushEditor.code");
|
||||||
|
@ -87,11 +108,19 @@ export class BrushEditor extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
resetErrors() {
|
resetErrors() {
|
||||||
this.errorHeader.textContent = "";
|
this.output.dataset.state = "ok";
|
||||||
this.errorArea.textContent = "";
|
this.errors.textContent = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHakuResult(phase, result) {
|
async updatePreview(haku, { getStats }) {
|
||||||
|
let previewResult = await this.brushPreview.renderBrush(haku);
|
||||||
|
this.brushCostGauges.update(getStats());
|
||||||
|
if (previewResult.status == "error") {
|
||||||
|
this.renderHakuResult(previewResult.result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderHakuResult(result) {
|
||||||
this.resetErrors();
|
this.resetErrors();
|
||||||
this.errorSquiggles = null;
|
this.errorSquiggles = null;
|
||||||
|
|
||||||
|
@ -102,7 +131,7 @@ export class BrushEditor extends HTMLElement {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.errorHeader.textContent = `${phase} failed`;
|
this.output.dataset.state = "error";
|
||||||
|
|
||||||
if (result.errorKind == "diagnostics") {
|
if (result.errorKind == "diagnostics") {
|
||||||
this.codeEditor.rebuildLineMap();
|
this.codeEditor.rebuildLineMap();
|
||||||
|
@ -112,13 +141,13 @@ export class BrushEditor extends HTMLElement {
|
||||||
);
|
);
|
||||||
this.codeEditor.renderLayer("layer-error-squiggles");
|
this.codeEditor.renderLayer("layer-error-squiggles");
|
||||||
|
|
||||||
this.errorArea.textContent = result.diagnostics
|
this.errors.textContent = result.diagnostics
|
||||||
.map(
|
.map(
|
||||||
(diagnostic) => `${diagnostic.start}..${diagnostic.end}: ${diagnostic.message}`,
|
(diagnostic) => `${diagnostic.start}..${diagnostic.end}: ${diagnostic.message}`,
|
||||||
)
|
)
|
||||||
.join("\n");
|
.join("\n");
|
||||||
} else if (result.errorKind == "plain") {
|
} else if (result.errorKind == "plain") {
|
||||||
this.errorHeader.textContent = result.message;
|
this.errors.textContent = result.message;
|
||||||
} else if (result.errorKind == "exception") {
|
} else if (result.errorKind == "exception") {
|
||||||
let renderer = new ErrorException(result);
|
let renderer = new ErrorException(result);
|
||||||
let squiggles = renderer.prepareSquiggles();
|
let squiggles = renderer.prepareSquiggles();
|
||||||
|
@ -144,11 +173,11 @@ export class BrushEditor extends HTMLElement {
|
||||||
this.codeEditor.setSelection(new Selection(span.start, span.end));
|
this.codeEditor.setSelection(new Selection(span.start, span.end));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.errorArea.replaceChildren();
|
this.errors.replaceChildren();
|
||||||
this.errorArea.appendChild(renderer);
|
this.errors.appendChild(renderer);
|
||||||
} else {
|
} else {
|
||||||
console.warn(`unknown error kind: ${result.errorKind}`);
|
console.warn(`unknown error kind: ${result.errorKind}`);
|
||||||
this.errorHeader.textContent = "(unknown error kind)";
|
this.errors.textContent = "(unknown error kind)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,16 +13,24 @@ export class BrushPreview extends HTMLElement {
|
||||||
this.ctx = this.canvas.getContext("2d");
|
this.ctx = this.canvas.getContext("2d");
|
||||||
|
|
||||||
this.#resizeCanvas();
|
this.#resizeCanvas();
|
||||||
|
if (this.width == null || this.height == null) {
|
||||||
|
new ResizeObserver(() => this.#resizeCanvas()).observe(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#resizeCanvas() {
|
#resizeCanvas() {
|
||||||
this.canvas.width = this.width ?? this.clientWidth;
|
this.canvas.width = this.width ?? this.clientWidth;
|
||||||
this.canvas.height = this.height ?? this.clientHeight;
|
this.canvas.height = this.height ?? this.clientHeight;
|
||||||
|
|
||||||
|
// This can happen if the element's `display: none`.
|
||||||
|
if (this.canvas.width == 0 || this.canvas.height == 0) return;
|
||||||
|
|
||||||
if (this.pixmap != null) {
|
if (this.pixmap != null) {
|
||||||
this.pixmap.destroy();
|
this.pixmap.destroy();
|
||||||
}
|
}
|
||||||
this.pixmap = new Pixmap(this.canvas.width, this.canvas.height);
|
this.pixmap = new Pixmap(this.canvas.width, this.canvas.height);
|
||||||
|
|
||||||
|
this.dispatchEvent(new Event(".pixmapLost"));
|
||||||
}
|
}
|
||||||
|
|
||||||
async #renderBrushInner(haku) {
|
async #renderBrushInner(haku) {
|
||||||
|
@ -31,9 +39,9 @@ export class BrushPreview extends HTMLElement {
|
||||||
runDotter: async () => {
|
runDotter: async () => {
|
||||||
return {
|
return {
|
||||||
fromX: this.canvas.width / 2,
|
fromX: this.canvas.width / 2,
|
||||||
fromY: this.canvas.width / 2,
|
fromY: this.canvas.height / 2,
|
||||||
toX: this.canvas.width / 2,
|
toX: this.canvas.width / 2,
|
||||||
toY: this.canvas.width / 2,
|
toY: this.canvas.height / 2,
|
||||||
num: 0,
|
num: 0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -44,6 +44,9 @@ export class CodeEditor extends HTMLElement {
|
||||||
this.undoHistory = [];
|
this.undoHistory = [];
|
||||||
this.undoHistoryTop = 0;
|
this.undoHistoryTop = 0;
|
||||||
|
|
||||||
|
this.textArea.addEventListener("input", () => {
|
||||||
|
this.#codeChanged();
|
||||||
|
});
|
||||||
this.#textAreaAutoSizingBehaviour();
|
this.#textAreaAutoSizingBehaviour();
|
||||||
this.#keyShortcutBehaviours();
|
this.#keyShortcutBehaviours();
|
||||||
|
|
||||||
|
@ -76,9 +79,6 @@ export class CodeEditor extends HTMLElement {
|
||||||
// Resizing the text area
|
// Resizing the text area
|
||||||
|
|
||||||
#textAreaAutoSizingBehaviour() {
|
#textAreaAutoSizingBehaviour() {
|
||||||
this.textArea.addEventListener("input", () => {
|
|
||||||
this.#codeChanged();
|
|
||||||
});
|
|
||||||
this.#resizeTextArea();
|
this.#resizeTextArea();
|
||||||
document.fonts.addEventListener("loadingdone", () => this.#resizeTextArea());
|
document.fonts.addEventListener("loadingdone", () => this.#resizeTextArea());
|
||||||
new ResizeObserver(() => this.#resizeTextArea()).observe(this.textArea);
|
new ResizeObserver(() => this.#resizeTextArea()).observe(this.textArea);
|
||||||
|
|
|
@ -65,7 +65,7 @@ export class SaveData {
|
||||||
}
|
}
|
||||||
|
|
||||||
attachToElement(element) {
|
attachToElement(element) {
|
||||||
this.elementId = element.dataset.storageId;
|
this.elementId = element.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
getRaw(key) {
|
getRaw(key) {
|
||||||
|
|
220
static/index.css
220
static/index.css
|
@ -44,9 +44,6 @@ main {
|
||||||
& > .panels {
|
& > .panels {
|
||||||
--right-width: 384px; /* Overridden by JavaScript */
|
--right-width: 384px; /* Overridden by JavaScript */
|
||||||
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: [left] 1fr [right-resize] auto [right] minmax(
|
grid-template-columns: [left] 1fr [right-resize] auto [right] minmax(
|
||||||
0,
|
0,
|
||||||
|
@ -63,9 +60,9 @@ main {
|
||||||
& > .left {
|
& > .left {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
@ -80,8 +77,8 @@ main {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: minmax(0, min-content);
|
grid-template-rows: 100%;
|
||||||
grid-template-columns: [floating] max-content [resize] min-content [docked] auto;
|
grid-template-columns: [resize] min-content [docked] auto;
|
||||||
|
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
|
|
||||||
|
@ -92,29 +89,14 @@ main {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > rkgk-resize-handle {
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .docked {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .docked > rkgk-brush-editor {
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .floating {
|
& > .floating {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
& > rkgk-brush-preview {
|
& > rkgk-brush-preview {
|
||||||
width: 128px;
|
width: 128px;
|
||||||
height: 128px;
|
height: 128px;
|
||||||
|
@ -126,6 +108,35 @@ main {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& > rkgk-resize-handle {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .docked {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
|
||||||
|
background-color: var(--color-panel-background);
|
||||||
|
box-shadow: 0 0 0 1px var(--color-panel-border);
|
||||||
|
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > rkgk-brush-editor {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .menu-bar {
|
||||||
|
border-bottom: 1px solid var(--color-panel-border);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,6 +159,7 @@ main {
|
||||||
|
|
||||||
& > #js-loading {
|
& > #js-loading {
|
||||||
background-color: var(--color-panel-background);
|
background-color: var(--color-panel-background);
|
||||||
|
z-index: var(--z-modal);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -158,47 +170,54 @@ main {
|
||||||
/* Resize handle */
|
/* Resize handle */
|
||||||
|
|
||||||
rkgk-resize-handle {
|
rkgk-resize-handle {
|
||||||
--width: 16px;
|
--width: 8px;
|
||||||
--line-width: 2px;
|
--line: none;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
z-index: var(--z-resize-handle);
|
||||||
|
|
||||||
|
&.always-visible {
|
||||||
|
--line: 1px solid var(--color-panel-border);
|
||||||
|
}
|
||||||
|
|
||||||
&[data-direction="horizontal"] {
|
&[data-direction="horizontal"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: var(--width);
|
height: var(--width);
|
||||||
|
margin: calc(var(--width) / -2) 0;
|
||||||
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
cursor: row-resize;
|
cursor: row-resize;
|
||||||
|
|
||||||
& > .visual {
|
& > .visual {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: var(--line-width);
|
height: 0;
|
||||||
|
border-bottom: var(--line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-direction="vertical"] {
|
&[data-direction="vertical"] {
|
||||||
width: var(--width);
|
width: var(--width);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
margin: 0 calc(var(--width) / -2);
|
||||||
|
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
|
|
||||||
& > .visual {
|
& > .visual {
|
||||||
width: var(--line-width);
|
width: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
border-left: var(--line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .visual {
|
|
||||||
background-color: var(--color-brand-blue);
|
|
||||||
opacity: 0%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover > .visual,
|
&:hover > .visual,
|
||||||
&.dragging > .visual {
|
&.dragging > .visual {
|
||||||
opacity: 100%;
|
--line: 2px solid var(--color-brand-blue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,12 +280,15 @@ rkgk-reticle-cursor {
|
||||||
|
|
||||||
/* Brush box */
|
/* Brush box */
|
||||||
|
|
||||||
rkgk-brush-box.rkgk-panel {
|
rkgk-brush-box {
|
||||||
--button-size: 56px;
|
--button-size: 56px;
|
||||||
|
|
||||||
height: var(--height);
|
height: var(--height);
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
& > .brushes {
|
& > .brushes {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(
|
grid-template-columns: repeat(
|
||||||
|
@ -275,8 +297,6 @@ rkgk-brush-box.rkgk-panel {
|
||||||
);
|
);
|
||||||
|
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
& > button {
|
& > button {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -337,20 +357,20 @@ rkgk-brush-box.rkgk-panel {
|
||||||
/* Code editor */
|
/* Code editor */
|
||||||
|
|
||||||
rkgk-code-editor {
|
rkgk-code-editor {
|
||||||
--gutter-width: 2.75em;
|
--gutter-width: 3.5em;
|
||||||
|
--vertical-padding: 12px;
|
||||||
|
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
|
padding: var(--vertical-padding) 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
& > .layer {
|
& > .layer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: var(--vertical-padding);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -468,47 +488,60 @@ rkgk-code-editor {
|
||||||
|
|
||||||
&:has(textarea:focus) {
|
&:has(textarea:focus) {
|
||||||
outline: 1px solid var(--color-brand-blue);
|
outline: 1px solid var(--color-brand-blue);
|
||||||
outline-offset: 4px;
|
outline-offset: -1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Brush editor */
|
/* Brush editor */
|
||||||
|
|
||||||
rkgk-brush-editor.rkgk-panel {
|
rkgk-brush-editor {
|
||||||
padding: 12px;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
& > .editor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
height: calc(100% - var(--brush-preview-height));
|
||||||
|
|
||||||
& > .name {
|
& > .name {
|
||||||
margin-bottom: 4px;
|
margin: 12px;
|
||||||
|
margin-bottom: 0;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .text-area {
|
& > rkgk-code-editor {
|
||||||
display: block;
|
height: 100%;
|
||||||
width: 100%;
|
flex-shrink: 1;
|
||||||
margin: 0;
|
}
|
||||||
resize: none;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
border: none;
|
|
||||||
overflow: hidden;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .errors:empty,
|
& > output {
|
||||||
& > .error-header:empty {
|
height: 64px;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
user-select: text;
|
||||||
|
|
||||||
|
&[data-state="ok"] > .errors {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&[data-state="error"] > .ok {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .error-header {
|
& > .ok {
|
||||||
margin: 0;
|
display: flex;
|
||||||
margin-top: 0.5em;
|
flex-direction: row;
|
||||||
font-size: 1rem;
|
|
||||||
color: var(--color-error);
|
height: 100%;
|
||||||
|
|
||||||
|
& > rkgk-brush-preview {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .errors {
|
& > .errors {
|
||||||
|
@ -516,12 +549,11 @@ rkgk-brush-editor.rkgk-panel {
|
||||||
color: var(--color-error);
|
color: var(--color-error);
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
|
||||||
user-select: text;
|
|
||||||
|
|
||||||
max-height: 20em;
|
max-height: 20em;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rkgk-brush-editor-error-exception {
|
rkgk-brush-editor-error-exception {
|
||||||
& > .message {
|
& > .message {
|
||||||
|
@ -594,7 +626,6 @@ rkgk-brush-preview {
|
||||||
var(--checkerboard-dark) 0% 50%
|
var(--checkerboard-dark) 0% 50%
|
||||||
)
|
)
|
||||||
50% 50% / var(--checkerboard-size) var(--checkerboard-size);
|
50% 50% / var(--checkerboard-size) var(--checkerboard-size);
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
& > canvas {
|
& > canvas {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -625,26 +656,20 @@ rkgk-brush-preview {
|
||||||
|
|
||||||
/* Brush cost gauges */
|
/* Brush cost gauges */
|
||||||
|
|
||||||
rkgk-brush-cost-gauges,
|
rkgk-brush-cost-gauges {
|
||||||
rkgk-brush-cost-gauges.rkgk-panel {
|
|
||||||
--gauge-size: 20px;
|
--gauge-size: 20px;
|
||||||
|
|
||||||
height: var(--gauge-size);
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
overflow: clip; /* clip corners */
|
|
||||||
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > rkgk-brush-cost-gauge {
|
& > rkgk-brush-cost-gauge {
|
||||||
display: block;
|
display: block;
|
||||||
height: var(--gauge-size);
|
width: var(--gauge-size);
|
||||||
flex-grow: 1;
|
height: 100%;
|
||||||
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -654,7 +679,7 @@ rkgk-brush-cost-gauges.rkgk-panel {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
clip-path: xywh(0 0 var(--progress) 100%);
|
clip-path: inset(calc(100% - var(--progress)) 0 0 0);
|
||||||
|
|
||||||
background-color: var(--gauge-color);
|
background-color: var(--gauge-color);
|
||||||
}
|
}
|
||||||
|
@ -672,6 +697,10 @@ rkgk-brush-cost-gauges.rkgk-panel {
|
||||||
--gauge-color: #5aca40;
|
--gauge-color: #5aca40;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& .icon {
|
||||||
|
background-position: 50% calc(100% - 4px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Zoom indicator */
|
/* Zoom indicator */
|
||||||
|
@ -789,21 +818,38 @@ rkgk-context-menu.rkgk-panel {
|
||||||
/* Menu bar */
|
/* Menu bar */
|
||||||
|
|
||||||
.menu-bar {
|
.menu-bar {
|
||||||
--border-radius: 4px;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
width: fit-content;
|
width: 100%;
|
||||||
height: 24px;
|
height: 28px;
|
||||||
border-radius: var(--border-radius);
|
|
||||||
|
|
||||||
& > a {
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
& > li {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > li.icon {
|
||||||
display: block;
|
display: block;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > li > a {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
padding: 4px 8px;
|
height: 100%;
|
||||||
|
padding: 0 8px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
@ -816,16 +862,6 @@ rkgk-context-menu.rkgk-panel {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
border-top-left-radius: var(--border-radius);
|
|
||||||
border-bottom-left-radius: var(--border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-top-right-radius: var(--border-radius);
|
|
||||||
border-bottom-right-radius: var(--border-radius);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
& > hr {
|
& > hr {
|
||||||
|
|
|
@ -18,8 +18,6 @@ let canvasRenderer = main.querySelector("rkgk-canvas-renderer");
|
||||||
let reticleRenderer = main.querySelector("rkgk-reticle-renderer");
|
let reticleRenderer = main.querySelector("rkgk-reticle-renderer");
|
||||||
let brushBox = main.querySelector("rkgk-brush-box");
|
let brushBox = main.querySelector("rkgk-brush-box");
|
||||||
let brushEditor = main.querySelector("rkgk-brush-editor");
|
let brushEditor = main.querySelector("rkgk-brush-editor");
|
||||||
let brushPreview = main.querySelector("rkgk-brush-preview");
|
|
||||||
let brushCostGauges = main.querySelector("rkgk-brush-cost-gauges");
|
|
||||||
let zoomIndicator = main.querySelector("rkgk-zoom-indicator");
|
let zoomIndicator = main.querySelector("rkgk-zoom-indicator");
|
||||||
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");
|
||||||
|
@ -253,7 +251,7 @@ function readUrl(urlString) {
|
||||||
let result = await currentUser.haku.evalBrush(
|
let result = await currentUser.haku.evalBrush(
|
||||||
selfController(interactionQueue, wall, event),
|
selfController(interactionQueue, wall, event),
|
||||||
);
|
);
|
||||||
brushEditor.renderHakuResult(result.phase == "eval" ? "Evaluation" : "Rendering", result);
|
brushEditor.renderHakuResult(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
canvasRenderer.addEventListener(".viewportUpdate", () => reticleRenderer.render());
|
canvasRenderer.addEventListener(".viewportUpdate", () => reticleRenderer.render());
|
||||||
|
@ -263,26 +261,19 @@ function readUrl(urlString) {
|
||||||
|
|
||||||
// Brush editor
|
// Brush editor
|
||||||
|
|
||||||
|
function updateBrushPreview() {
|
||||||
|
brushEditor.updatePreview(currentUser.haku, {
|
||||||
|
getStats: () => currentUser.getStats(session.wallInfo),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function compileBrush() {
|
function compileBrush() {
|
||||||
let compileResult = currentUser.setBrush(brushEditor.code);
|
let compileResult = currentUser.setBrush(brushEditor.code);
|
||||||
brushEditor.renderHakuResult("Compilation", compileResult);
|
brushEditor.renderHakuResult(compileResult);
|
||||||
|
|
||||||
brushCostGauges.update(currentUser.getStats(session.wallInfo));
|
if (compileResult.status == "ok") {
|
||||||
|
updateBrushPreview();
|
||||||
if (compileResult.status != "ok") {
|
|
||||||
brushPreview.setErrorFlag();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
brushPreview.renderBrush(currentUser.haku).then((previewResult) => {
|
|
||||||
brushCostGauges.update(currentUser.getStats(session.wallInfo));
|
|
||||||
if (previewResult.status == "error") {
|
|
||||||
brushEditor.renderHakuResult(
|
|
||||||
previewResult.phase == "eval" ? "Evaluation" : "Rendering",
|
|
||||||
previewResult.result,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compileBrush();
|
compileBrush();
|
||||||
|
@ -294,6 +285,8 @@ function readUrl(urlString) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
brushEditor.brushPreview.addEventListener(".pixmapLost", updateBrushPreview);
|
||||||
|
|
||||||
// Brush box
|
// Brush box
|
||||||
|
|
||||||
function updateBrushBoxDirtyState() {
|
function updateBrushBoxDirtyState() {
|
||||||
|
|
|
@ -3,13 +3,31 @@ import { listen, SaveData } from "rkgk/framework.js";
|
||||||
export class ResizeHandle extends HTMLElement {
|
export class ResizeHandle extends HTMLElement {
|
||||||
saveData = new SaveData("resizeHandle");
|
saveData = new SaveData("resizeHandle");
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super();
|
||||||
|
this.props = props;
|
||||||
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.direction = this.getAttribute("data-direction");
|
let props = this.props ?? this.dataset;
|
||||||
this.targetId = this.getAttribute("data-target");
|
|
||||||
|
this.direction = this.dataset.direction = props.direction;
|
||||||
|
this.targetProperty = props.targetProperty;
|
||||||
|
this.initSize = parseInt(props.initSize);
|
||||||
|
this.minSize = parseInt(props.minSize);
|
||||||
|
this.inverse = props.inverse != null;
|
||||||
|
|
||||||
|
if (props.targetElement != null) {
|
||||||
|
// In case you want to construct the resize handle programatically:
|
||||||
|
// pass in the target element via targetElement.
|
||||||
|
// Don't forget to set its id.
|
||||||
|
this.target = props.targetElement;
|
||||||
|
this.targetId = this.target.id;
|
||||||
|
} else {
|
||||||
|
// Else use data-target.
|
||||||
|
this.targetId = props.target;
|
||||||
this.target = document.getElementById(this.targetId);
|
this.target = document.getElementById(this.targetId);
|
||||||
this.targetProperty = this.getAttribute("data-target-property");
|
}
|
||||||
this.initSize = parseInt(this.getAttribute("data-init-size"));
|
|
||||||
this.minSize = parseInt(this.getAttribute("data-min-size"));
|
|
||||||
|
|
||||||
this.saveData.elementId = this.targetId;
|
this.saveData.elementId = this.targetId;
|
||||||
|
|
||||||
|
@ -58,8 +76,10 @@ export class ResizeHandle extends HTMLElement {
|
||||||
if (event.type == "mousemove") {
|
if (event.type == "mousemove") {
|
||||||
let delta =
|
let delta =
|
||||||
this.direction == "vertical"
|
this.direction == "vertical"
|
||||||
? mouseDown.clientX - event.clientX
|
? event.clientX - mouseDown.clientX
|
||||||
: event.clientY - mouseDown.clientY;
|
: event.clientY - mouseDown.clientY;
|
||||||
|
if (this.inverse) delta = -delta;
|
||||||
|
|
||||||
this.#setSize(startingSize + delta);
|
this.#setSize(startingSize + delta);
|
||||||
this.#updateTargetProperty();
|
this.#updateTargetProperty();
|
||||||
} else if (event.type == "mouseup") {
|
} else if (event.type == "mouseup") {
|
||||||
|
|
|
@ -70,39 +70,35 @@
|
||||||
<rkgk-reticle-renderer class="fullscreen"></rkgk-reticle-renderer>
|
<rkgk-reticle-renderer class="fullscreen"></rkgk-reticle-renderer>
|
||||||
<div class="panels fullscreen" id="panels-overlay">
|
<div class="panels fullscreen" id="panels-overlay">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="rkgk-panel menu-bar">
|
|
||||||
<a class="icon icon-rkgk-grayscale" title="I know this menu bar is really ugly. Sorry about that."></a>
|
|
||||||
<hr>
|
|
||||||
<a href="/docs/rkgk.html">Manual</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<rkgk-zoom-indicator class="rkgk-panel"></rkgk-zoom-indicator>
|
<rkgk-zoom-indicator class="rkgk-panel"></rkgk-zoom-indicator>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div class="floating">
|
|
||||||
<rkgk-brush-preview></rkgk-brush-preview>
|
|
||||||
<rkgk-brush-cost-gauges class="rkgk-panel"></rkgk-brush-cost-gauges>
|
|
||||||
</div>
|
|
||||||
<rkgk-resize-handle
|
<rkgk-resize-handle
|
||||||
data-direction="vertical"
|
data-direction="vertical"
|
||||||
|
data-inverse
|
||||||
data-target="panels-overlay"
|
data-target="panels-overlay"
|
||||||
data-target-property="--right-width"
|
data-target-property="--right-width"
|
||||||
data-init-size="528"
|
data-init-size="528"
|
||||||
data-min-size="384"></rkgk-resize-handle>
|
data-min-size="256"></rkgk-resize-handle>
|
||||||
<div class="docked">
|
<div class="docked">
|
||||||
<rkgk-brush-box
|
<menu class="menu-bar">
|
||||||
id="brush-box"
|
<li class="icon icon-rkgk-grayscale"></li>
|
||||||
data-storage-id="brush-box"></rkgk-brush-box>
|
<hr>
|
||||||
|
<li><a href="/docs/rkgk.html">Manual</a></li>
|
||||||
|
</menu>
|
||||||
|
|
||||||
|
<rkgk-brush-box id="brush-box"></rkgk-brush-box>
|
||||||
|
|
||||||
<rkgk-resize-handle
|
<rkgk-resize-handle
|
||||||
|
class="always-visible"
|
||||||
data-direction="horizontal"
|
data-direction="horizontal"
|
||||||
data-target="brush-box"
|
data-target="brush-box"
|
||||||
data-target-property="--height"
|
data-target-property="--height"
|
||||||
data-init-size="168"
|
data-init-size="168"
|
||||||
data-min-size="96"></rkgk-resize-handle>
|
data-min-size="96"></rkgk-resize-handle>
|
||||||
|
|
||||||
<rkgk-brush-editor
|
<rkgk-brush-editor id="brush-editor"></rkgk-brush-editor>
|
||||||
data-storage-id="brush-editor"></rkgk-brush-editor>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue