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 @@