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