From b4acab2c9c95ff2774b4a8df17eec321f5a5f8ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=AA=E3=82=AD=E8=90=8C?= Date: Thu, 26 Jun 2025 18:53:17 +0200 Subject: [PATCH 1/3] fix jank when moving the mouse cursor outside the canvas mouse events are now consistently sourced from the window, so if you try to draw and move your mouse over the panel, it won't glitch out --- static/canvas-renderer.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/static/canvas-renderer.js b/static/canvas-renderer.js index 95d07e7..6b5821b 100644 --- a/static/canvas-renderer.js +++ b/static/canvas-renderer.js @@ -451,10 +451,10 @@ class CanvasRenderer extends HTMLElement { async #cursorReportingBehaviour() { while (true) { - let event = await listen([this, "mousemove"]); + let event = await listen([window, "mousemove"]); let [x, y] = this.viewport.toViewportSpace( event.clientX - this.clientLeft, - event.offsetY - this.clientTop, + event.clientY - this.clientTop, this.getWindowSize(), ); this.dispatchEvent(Object.assign(new Event(".cursor"), { x, y })); @@ -493,10 +493,7 @@ class InteractEvent extends Event { continueAsDotter() { (async () => { - let event = await listen( - [this.canvasRenderer, "mousemove"], - [this.canvasRenderer, "mouseup"], - ); + let event = await listen([window, "mousemove"], [window, "mouseup"]); if (event.type == "mousemove") { let [mouseX, mouseY] = this.canvasRenderer.viewport.toViewportSpace( From f78f3136d96f803a601e7a8be945fbf382d524b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=AA=E3=82=AD=E8=90=8C?= Date: Thu, 26 Jun 2025 19:17:06 +0200 Subject: [PATCH 2/3] optimizing loading times - reducing the waterfall for JavaScript - making the throbber appear properly if not all CSS is loaded; fonts.css and index.css are made non render-blocking --- template/index.hbs.html | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/template/index.hbs.html b/template/index.hbs.html index fa55672..75d88a5 100644 --- a/template/index.hbs.html +++ b/template/index.hbs.html @@ -7,8 +7,6 @@ rakugaki - - @@ -27,13 +25,20 @@ import "rkgk/brush-editor.js"; import "rkgk/brush-preview.js"; import "rkgk/canvas-renderer.js"; + import "rkgk/code-editor.js"; import "rkgk/connection-status.js"; + import "rkgk/context-menu.js"; import "rkgk/framework.js"; + import "rkgk/haku.js"; + import "rkgk/online-users.js"; + import "rkgk/painter.js"; + import "rkgk/random.js"; import "rkgk/resize-handle.js"; import "rkgk/reticle-renderer.js"; import "rkgk/session.js"; import "rkgk/throbber.js"; import "rkgk/viewport.js"; + import "rkgk/wall.js"; import "rkgk/welcome.js"; import "rkgk/zoom-indicator.js"; @@ -55,6 +60,11 @@ + + + +
From 0ddc42c00f35253c5a7aa8c878cf0602d1b5c521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=AA=E3=82=AD=E8=90=8C?= Date: Fri, 27 Jun 2025 23:24:09 +0200 Subject: [PATCH 3/3] 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 --- static/base.css | 26 ++++- static/brush-box.js | 2 - static/brush-editor.js | 63 +++++++--- static/brush-preview.js | 12 +- static/code-editor.js | 6 +- static/framework.js | 2 +- static/index.css | 246 +++++++++++++++++++++++----------------- static/index.js | 31 ++--- static/resize-handle.js | 34 ++++-- template/index.hbs.html | 28 ++--- 10 files changed, 272 insertions(+), 178 deletions(-) diff --git a/static/base.css b/static/base.css index 6a9ede5..6fc9acd 100644 --- a/static/base.css +++ b/static/base.css @@ -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%; diff --git a/static/brush-box.js b/static/brush-box.js index 1c2bc92..3470c94 100644 --- a/static/brush-box.js +++ b/static/brush-box.js @@ -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")); diff --git a/static/brush-editor.js b/static/brush-editor.js index 8e4f611..ad97df1 100644 --- a/static/brush-editor.js +++ b/static/brush-editor.js @@ -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)"; } } diff --git a/static/brush-preview.js b/static/brush-preview.js index 1697415..97a2015 100644 --- a/static/brush-preview.js +++ b/static/brush-preview.js @@ -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, }; }, diff --git a/static/code-editor.js b/static/code-editor.js index b0197f1..e71d614 100644 --- a/static/code-editor.js +++ b/static/code-editor.js @@ -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); diff --git a/static/framework.js b/static/framework.js index cf18f65..ed382e1 100644 --- a/static/framework.js +++ b/static/framework.js @@ -65,7 +65,7 @@ export class SaveData { } attachToElement(element) { - this.elementId = element.dataset.storageId; + this.elementId = element.id; } getRaw(key) { diff --git a/static/index.css b/static/index.css index 4ff0072..40fc17d 100644 --- a/static/index.css +++ b/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 { diff --git a/static/index.js b/static/index.js index 4fb9f78..3bcb378 100644 --- a/static/index.js +++ b/static/index.js @@ -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() { diff --git a/static/resize-handle.js b/static/resize-handle.js index b441be5..9a15fda 100644 --- a/static/resize-handle.js +++ b/static/resize-handle.js @@ -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") { diff --git a/template/index.hbs.html b/template/index.hbs.html index 75d88a5..9ff2603 100644 --- a/template/index.hbs.html +++ b/template/index.hbs.html @@ -70,39 +70,35 @@
- -
-
- - -
+ data-min-size="256">
- + +
  • +
    +
  • Manual
  • +
    + + + - +