From 6b82593414960a4748470064ae390c9963f476ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=AA=E3=82=AD=E8=90=8C?= Date: Mon, 26 May 2025 20:19:41 +0200 Subject: [PATCH] add zoom indicator in lower left corner (#39) it looks like this: - 3200% + I'm giving up on the 100% zoom button from the original idea, because rkgk's scaling curve makes it easy to go back to 100% if you need to. --- static/base.css | 18 ++++++++++++++- static/icon/minus.svg | 3 +++ static/icon/plus.svg | 4 ++++ static/index.css | 47 ++++++++++++++++++++++++++++++++++++++++ static/index.js | 21 ++++++++++++++++++ static/zoom-indicator.js | 25 +++++++++++++++++++++ template/index.hbs.html | 15 ++++++++----- 7 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 static/icon/minus.svg create mode 100644 static/icon/plus.svg create mode 100644 static/zoom-indicator.js diff --git a/static/base.css b/static/base.css index 5118f27..f2b59de 100644 --- a/static/base.css +++ b/static/base.css @@ -59,6 +59,14 @@ button { background-color: var(--color-panel-background); } +button.icon { + border: none; + border-radius: 0; + padding: 0; + width: 24px; + height: 24px; +} + /* Text areas */ input { @@ -66,7 +74,7 @@ input { border-bottom: 1px solid var(--color-panel-border); } -*:focus { +*:focus-visible { outline: 1px solid var(--color-brand-blue); outline-offset: 4px; } @@ -172,6 +180,8 @@ pre:has(code) { --icon-memory: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iLjEiPjxwYXRoIGQ9Im01LjA1IDMuMDVoNS45djkuOWgtNS45eiIvPjxwYXRoIGQ9Im0zLjA1IDQuMDVoMS45djEuOWgtMS45eiIvPjxwYXRoIGQ9Im0xMS4wNSA0LjA1aDEuOXYxLjloLTEuOXoiLz48cGF0aCBkPSJtMy4wNSA3LjA1aDEuOXYxLjloLTEuOXoiLz48cGF0aCBkPSJtMTEuMDUgNy4wNWgxLjl2MS45aC0xLjl6Ii8+PHBhdGggZD0ibTMuMDUgMTAuMDVoMS45djEuOWgtMS45eiIvPjxwYXRoIGQ9Im0xMS4wNSAxMC4wNWgxLjl2MS45aC0xLjl6Ii8+PC9nPjwvc3ZnPg=="); --icon-object: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im0xMiA2YzAgMS4xMDQ1Ny0uODk1NCAyLTIgMnYyYzIuMjA5MSAwIDQtMS43OTA4NiA0LTRzLTEuNzkwOS00LTQtNGMtMi4yMDkxNCAwLTQgMS43OTA4Ni00IDRoMmMwLTEuMTA0NTcuODk1NDMtMiAyLTIgMS4xMDQ2IDAgMiAuODk1NDMgMiAyeiIgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIi8+PHBhdGggZD0ibTMgN2g2djZoLTZ6IiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPg=="); --icon-rkgk-grayscale: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGNsaXBQYXRoIGlkPSJhIj48cGF0aCBkPSJtMCAwaDE2djE2aC0xNnoiLz48L2NsaXBQYXRoPjxnIGNsaXAtcGF0aD0idXJsKCNhKSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjIiPjxwYXRoIGQ9Im0xMiAxNy00LjU1Mjc5LTkuMTA1NTdjLS42NjQ5LTEuMzI5ODEuMzAyMDktMi44OTQ0MyAxLjc4ODg2LTIuODk0NDNoOC43NjM5MyIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxnIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0ibTUuNSAxMi0yLjUgNSIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLW9wYWNpdHk9Ii41Ii8+PHBhdGggZD0ibTMgNWgxIi8+PC9nPjwvZz48L3N2Zz4="); + --icon-plus: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIj48cGF0aCBkPSJtNyA0aDJ2OGgtMnoiLz48cGF0aCBkPSJtNCA3aDh2MmgtOHoiLz48L2c+PC9zdmc+"); + --icon-minus: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNCA3aDh2MmgtOHoiIGZpbGw9IiMwMDAiLz48L3N2Zz4="); --icon-brackets-white: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNyA0aC0zdjhoM20yLThoM3Y4aC0zIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPg=="); --icon-droplet-white: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNSAxMGMwLTEuNjM0NzIgMS4wMDIxMi00LjI3MTU2IDIuMTg3MjYtNi41NDUzNy4zNDg5My0uNjY5NDQgMS4yNzY1NS0uNjY5NDQgMS42MjU0OCAwIDEuMTg1MTQgMi4yNzM4MSAyLjE4NzI2IDQuOTEwNjUgMi4xODcyNiA2LjU0NTM3IDAgMi0xLjUgMy0zIDNzLTMtMS0zLTN6IiBmaWxsPSIjZmZmIi8+PC9zdmc+"); @@ -205,6 +215,12 @@ pre:has(code) { &.icon-rkgk-grayscale { background-image: var(--icon-rkgk-grayscale); } + &.icon-plus { + background-image: var(--icon-plus); + } + &.icon-minus { + background-image: var(--icon-minus); + } &.icon-brackets-white { background-image: var(--icon-brackets-white); diff --git a/static/icon/minus.svg b/static/icon/minus.svg new file mode 100644 index 0000000..b4d68a1 --- /dev/null +++ b/static/icon/minus.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icon/plus.svg b/static/icon/plus.svg new file mode 100644 index 0000000..225e980 --- /dev/null +++ b/static/icon/plus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/static/index.css b/static/index.css index 0c5fddb..eb3c060 100644 --- a/static/index.css +++ b/static/index.css @@ -54,6 +54,21 @@ main { pointer-events: all; } + & > .left { + display: flex; + flex-direction: column; + + pointer-events: none; + + & > * { + pointer-events: auto; + } + + & > rkgk-zoom-indicator { + margin-block-start: auto; + } + } + & > .right { grid-column: right / right; min-height: 0; @@ -439,6 +454,38 @@ rkgk-brush-cost-gauges.rkgk-panel { } } +/* Zoom indicator */ + +rkgk-zoom-indicator, +rkgk-zoom-indicator.rkgk-panel { + border-radius: 4px; + + display: flex; + flex-direction: row; + align-items: center; + + width: min-content; + + overflow: clip; /* corners */ + + & > button { + width: 24px; + height: 24px; + } + + & > p { + margin: 0; + padding: 0 4px; + line-height: 1; + width: 6ch; + + user-select: none; + + font-variant-numeric: tabular-nums; + text-align: center; + } +} + /* Welcome screen */ rkgk-welcome { diff --git a/static/index.js b/static/index.js index 58ac40e..0329cb6 100644 --- a/static/index.js +++ b/static/index.js @@ -19,6 +19,7 @@ let reticleRenderer = main.querySelector("rkgk-reticle-renderer"); 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"); @@ -62,6 +63,8 @@ function readUrl(urlString) { // In the background, connect to the server. (async () => { + // Initialization + console.info("checking for user registration status"); if (!isUserLoggedIn()) { await welcome.show({ @@ -79,6 +82,7 @@ function readUrl(urlString) { canvasRenderer.viewport.panX = urlData.viewport.x; canvasRenderer.viewport.panY = urlData.viewport.y; canvasRenderer.viewport.zoomLevel = urlData.viewport.zoom; + zoomIndicator.setZoom(canvasRenderer.viewport.zoom); let session = await newSession({ userId: getUserId(), @@ -124,6 +128,7 @@ function readUrl(urlString) { canvasRenderer.viewport.panX = newUrlData.viewport.x; canvasRenderer.viewport.panY = newUrlData.viewport.y; canvasRenderer.viewport.zoomLevel = newUrlData.viewport.zoom; + zoomIndicator.setZoom(canvasRenderer.viewport.zoom); canvasRenderer.sendViewportUpdate(); } }); @@ -140,6 +145,8 @@ function readUrl(urlString) { let currentUser = wall.onlineUsers.getUser(session.sessionId); + // Event loop + session.addEventListener("error", (event) => console.error(event)); session.addEventListener("wallEvent", (event) => { @@ -248,6 +255,8 @@ function readUrl(urlString) { updateUrl(session, canvasRenderer.viewport), ); + // Brush editor + function compileBrush() { let compileResult = currentUser.setBrush(brushEditor.code); brushEditor.renderHakuResult("Compilation", compileResult); @@ -279,5 +288,17 @@ function readUrl(urlString) { }); }); + // Zoom indicator + + canvasRenderer.addEventListener(".viewportUpdate", () => { + zoomIndicator.setZoom(canvasRenderer.viewport.zoom); + }); + zoomIndicator.addEventListener(".zoomUpdate", (event) => { + canvasRenderer.viewport.zoomLevel += event.delta; + canvasRenderer.sendViewportUpdate(); + }); + + // All done. + session.eventLoop(); })(); diff --git a/static/zoom-indicator.js b/static/zoom-indicator.js new file mode 100644 index 0000000..76df44f --- /dev/null +++ b/static/zoom-indicator.js @@ -0,0 +1,25 @@ +class ZoomIndicator extends HTMLElement { + connectedCallback() { + this.zoomOutButton = this.appendChild(document.createElement("button")); + this.zoomOutButton.classList.add("icon", "icon-minus"); + + this.zoomLevelText = this.appendChild(document.createElement("p")); + this.zoomLevelText.innerText = "100%"; + + this.zoomInButton = this.appendChild(document.createElement("button")); + this.zoomInButton.classList.add("icon", "icon-plus"); + + this.zoomInButton.addEventListener("click", () => { + this.dispatchEvent(Object.assign(new Event(".zoomUpdate"), { delta: +1 })); + }); + this.zoomOutButton.addEventListener("click", () => { + this.dispatchEvent(Object.assign(new Event(".zoomUpdate"), { delta: -1 })); + }); + } + + setZoom(value) { + this.zoomLevelText.innerText = `${Math.round(value * 100)}%`; + } +} + +customElements.define("rkgk-zoom-indicator", ZoomIndicator); diff --git a/template/index.hbs.html b/template/index.hbs.html index f9d846a..dbac156 100644 --- a/template/index.hbs.html +++ b/template/index.hbs.html @@ -18,7 +18,7 @@ const HAKU_WASM_PATH = "{{{ static 'wasm/haku.wasm' }}}"; - @@ -56,10 +57,14 @@
-