diff --git a/static/base.css b/static/base.css index 5330e5b..e830dd7 100644 --- a/static/base.css +++ b/static/base.css @@ -11,6 +11,9 @@ --color-shaded-background: rgba(0, 0, 0, 5%); --dialog-backdrop: rgba(255, 255, 255, 0.5); + + --font-body: "Atkinson Hyperlegible", sans-serif; + --font-monospace: "Iosevka Hyperlegible", monospace; } /* Reset */ @@ -24,7 +27,7 @@ body { /* Fonts */ :root { - font-family: "Atkinson Hyperlegible", sans-serif; + font-family: var(--font-body); } button, textarea, input { @@ -33,7 +36,7 @@ button, textarea, input { } pre, code, textarea { - font-family: "Iosevka Hyperlegible", monospace; + font-family: var(--font-monospace); line-height: 1.5; } diff --git a/static/code-editor.js b/static/code-editor.js index a132600..f22a197 100644 --- a/static/code-editor.js +++ b/static/code-editor.js @@ -6,6 +6,9 @@ export class CodeEditor extends HTMLElement { connectedCallback() { this.indentWidth = 2; + this.layerGutter = this.appendChild(document.createElement("pre")); + this.layerGutter.classList.add("layer", "layer-gutter"); + this.textArea = this.appendChild(document.createElement("textarea")); this.textArea.spellcheck = false; this.textArea.rows = 1; @@ -29,13 +32,17 @@ export class CodeEditor extends HTMLElement { this.#textAreaAutoSizingBehaviour(); this.#keyShortcutBehaviours(); + + this.#renderLayers(); } get code() { return this.textArea.value; } - #sendCodeChanged() { + #codeChanged() { + this.#resizeTextArea(); + this.#renderLayers(); this.dispatchEvent( Object.assign(new Event(".codeChanged"), { newCode: this.code, @@ -45,14 +52,14 @@ export class CodeEditor extends HTMLElement { setCode(value) { this.textArea.value = value; - this.#resizeTextArea(); - this.#sendCodeChanged(); + this.#codeChanged(); } + // Resizing the text area + #textAreaAutoSizingBehaviour() { this.textArea.addEventListener("input", () => { - this.#resizeTextArea(); - this.#sendCodeChanged(); + this.#codeChanged(); }); this.#resizeTextArea(); document.fonts.addEventListener("loadingdone", () => this.#resizeTextArea()); @@ -84,6 +91,24 @@ export class CodeEditor extends HTMLElement { this.textArea.style.height = `${this.textArea.scrollHeight}px`; } + // Layers + + #renderLayers() { + this.#renderGutter(); + } + + #renderGutter() { + this.layerGutter.replaceChildren(); + + for (let line of this.code.split("\n")) { + let lineElement = this.layerGutter.appendChild(document.createElement("span")); + lineElement.classList.add("line"); + lineElement.textContent = line; + } + } + + // Text editing + #keyShortcutBehaviours() { this.textArea.addEventListener("keydown", (event) => { let keyComponents = []; diff --git a/static/index.css b/static/index.css index c83f76f..bc1e442 100644 --- a/static/index.css +++ b/static/index.css @@ -169,15 +169,72 @@ rkgk-reticle-cursor { /* Code editor */ rkgk-code-editor { + --gutter-width: 2.5em; + + display: block; + position: relative; + width: 100%; + + &>.layer { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + box-sizing: border-box; + + margin: 0; + + pointer-events: none; + + display: flex; + flex-direction: column; + + &>.line { + white-space: pre-wrap; + } + } + + &>.layer-gutter { + user-select: none; + + counter-reset: line; + + color: transparent; + + &>.line { + display: flex; + flex-direction: row; + + counter-increment: line; + + &::before { + display: block; + width: var(--gutter-width); + flex-shrink: 0; + padding-right: 0.5em; + box-sizing: border-box; + + content: counter(line); + text-align: right; + + color: var(--color-text); + opacity: 40%; + } + } + } + &>textarea { display: block; - width: 100%; + width: calc(100% - var(--gutter-width)); margin: 0; + margin-left: var(--gutter-width); + padding: 0; + box-sizing: border-box; + overflow: hidden; resize: none; white-space: pre-wrap; border: none; - overflow: hidden; - box-sizing: border-box; } }