From bd2566f9d6aaaa8798ee7151dbe5727f719eba3b Mon Sep 17 00:00:00 2001 From: lawsie <5183697+lawsie@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:13:56 +0100 Subject: [PATCH 1/5] Add overlay menu --- main/accessibility.js | 112 ++++++++++++++++++++++++++++++++++++++++-- style.css | 31 +++++++++++- 2 files changed, 137 insertions(+), 6 deletions(-) diff --git a/main/accessibility.js b/main/accessibility.js index 225f07e7..acee3a75 100644 --- a/main/accessibility.js +++ b/main/accessibility.js @@ -1,7 +1,7 @@ // Area menu accessed with Ctrl + B to quickly skip to // different areas on the interface -const AccessibilityManager = { +const AreaManager = { overlay: null, areas: [ { selector: "#menuleft", label: "1" }, // Top left menu (line 148 input.js - demo menu is excluded?) @@ -24,9 +24,7 @@ const AccessibilityManager = { div.id = "area-menu-overlay"; div.className = "hidden"; div.classList.add("hidden"); - div.innerHTML = ` -
- `; + div.innerHTML = ``; document.body.appendChild(div); this.overlay = div; }, @@ -140,5 +138,109 @@ const AccessibilityManager = { }, }; +/* Overlay for gizmo buttons */ +const GizmoMenuManager = { + overlay: null, + buttons: [ + { id: "showShapesButton", label: "1" }, + { id: "colorPickerButton", label: "2" }, + { id: "positionButton", label: "3" }, + { id: "rotationButton", label: "4" }, + { id: "scaleButton", label: "5" }, + { id: "selectButton", label: "6" }, + { id: "duplicateButton", label: "7" }, + { id: "deleteButton", label: "8" }, + { id: "cameraButton", label: "9" }, + ], + + init() { + this.createOverlay(); + this.setupListeners(); + }, + + createOverlay() { + const div = document.createElement("div"); + div.id = "gizmo-menu-overlay"; + div.className = "hidden"; + div.innerHTML = ``; + document.body.appendChild(div); + this.overlay = div; + }, + + isOpen() { + return !this.overlay.classList.contains("hidden"); + }, + + toggle(show) { + if (!this.overlay) return; + if (show) { + this.renderBadges(); + // Focus 1st button if nothing in gizmos is already focused, + // but if another gizmo is active, leave focus there + const alreadyFocused = document.activeElement?.closest("#gizmoButtons"); + if (!alreadyFocused) { + const btn = document.getElementById("showShapesButton"); + if (btn && !btn.disabled && btn.offsetParent !== null) btn.focus(); + } + } + this.overlay.classList.toggle("hidden", !show); + }, + + setupListeners() { + window.addEventListener( + "keydown", + (e) => { + // Show the overlay on Ctrl+G + if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "g") { + e.preventDefault(); + e.stopPropagation(); // prevent main.js from also handling this + this.toggle(!this.isOpen()); + return; + } + + // Do nothing if the overlay isn't open + if (!this.isOpen()) return; + + // If the overlay is open and a number key is pressed, + // activate the gizmo + if (e.key >= "1" && e.key <= "9") { + const entry = this.buttons.find((b) => b.label === e.key); + if (entry) this.activateButton(entry); + } + + if (e.key === "Tab") { + console.log("current focus", document.activeElement); + } + }, + true, + ); + }, + + activateButton(entry) { + this.toggle(false); + const el = document.getElementById(entry.id); + if (!el) return; + el.focus(); + if (!el.disabled) el.click(); + }, + + renderBadges() { + const container = document.getElementById("gizmo-menu-content"); + container.innerHTML = ""; + this.buttons.forEach((entry) => { + const el = document.getElementById(entry.id); + if (!el || el.offsetParent === null) return; + const rect = el.getBoundingClientRect(); + const badge = document.createElement("div"); + badge.className = "gizmo-key-badge"; + badge.innerText = entry.label; + badge.style.top = `${rect.top + rect.width + 10}px`; + badge.style.left = `${rect.left + rect.width / 2}px`; + container.appendChild(badge); + }); + }, +}; + // Start it up -AccessibilityManager.init(); +AreaManager.init(); +GizmoMenuManager.init(); diff --git a/style.css b/style.css index 02e36bfb..51748f42 100644 --- a/style.css +++ b/style.css @@ -1493,7 +1493,6 @@ body.color-picker-open #renderCanvas { } /* Area Menu Styles */ - #area-menu-overlay { position: fixed; top: 0; @@ -1539,3 +1538,33 @@ body.color-picker-open #renderCanvas { pointer-events: none; /* Let clicks pass through */ z-index: 10000; } + +/* Gizmo menu overlay */ +#gizmo-menu-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 10000; + pointer-events: none; +} + +.gizmo-key-badge { + position: absolute; + transform: translate(-50%, -50%); + background: #fff; + color: #000; + border: 1px solid #aaa; + border-radius: 4px; + box-shadow: 0 2px 0 #888; + padding: 2px 6px; + font-size: 13px; + font-weight: bold; + font-family: monospace; + pointer-events: none; + z-index: 10001; + min-width: 10px; + text-align: center; + line-height: 1.4; +} From 1c6a32c82aef7bd93a630ca5e9d5ea0fc6b40b68 Mon Sep 17 00:00:00 2001 From: lawsie <5183697+lawsie@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:15:41 +0100 Subject: [PATCH 2/5] Don't overlap the keyboard control box --- main/accessibility.js | 2 +- style.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/main/accessibility.js b/main/accessibility.js index acee3a75..49e9947e 100644 --- a/main/accessibility.js +++ b/main/accessibility.js @@ -234,7 +234,7 @@ const GizmoMenuManager = { const badge = document.createElement("div"); badge.className = "gizmo-key-badge"; badge.innerText = entry.label; - badge.style.top = `${rect.top + rect.width + 10}px`; + badge.style.top = `${rect.top + rect.height + 8}px`; badge.style.left = `${rect.left + rect.width / 2}px`; container.appendChild(badge); }); diff --git a/style.css b/style.css index 51748f42..2b530602 100644 --- a/style.css +++ b/style.css @@ -259,7 +259,7 @@ } #info-panel { - margin-top: 15px; + margin-top: 25px; margin-left: 10px; margin-right: 10px; } From 11b3715e8a285660ff11be02eb8fd04678b195a8 Mon Sep 17 00:00:00 2001 From: lawsie <5183697+lawsie@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:19:59 +0100 Subject: [PATCH 3/5] Input box guard --- main/accessibility.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/main/accessibility.js b/main/accessibility.js index 49e9947e..a54234ea 100644 --- a/main/accessibility.js +++ b/main/accessibility.js @@ -201,6 +201,17 @@ const GizmoMenuManager = { // Do nothing if the overlay isn't open if (!this.isOpen()) return; + // Guard against typing in inputs triggering gizmo shortcuts + const t = e.target; + const tag = (t?.tagName || "").toLowerCase(); + if ( + t?.isContentEditable || + tag === "input" || + tag === "textarea" || + tag === "select" + ) + return; + // If the overlay is open and a number key is pressed, // activate the gizmo if (e.key >= "1" && e.key <= "9") { From 24408b4d5dc8482d143924afcd3f48aed678a13b Mon Sep 17 00:00:00 2001 From: lawsie <5183697+lawsie@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:23:57 +0100 Subject: [PATCH 4/5] Remove debug --- main/accessibility.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/main/accessibility.js b/main/accessibility.js index a54234ea..f7dc11ad 100644 --- a/main/accessibility.js +++ b/main/accessibility.js @@ -218,10 +218,6 @@ const GizmoMenuManager = { const entry = this.buttons.find((b) => b.label === e.key); if (entry) this.activateButton(entry); } - - if (e.key === "Tab") { - console.log("current focus", document.activeElement); - } }, true, ); From 5bb4d58267bf39812b2e2055bff6a9fcc9244d31 Mon Sep 17 00:00:00 2001 From: lawsie <5183697+lawsie@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:33:39 +0100 Subject: [PATCH 5/5] Focus properly if focus on canvas --- main/accessibility.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/main/accessibility.js b/main/accessibility.js index f7dc11ad..a733b007 100644 --- a/main/accessibility.js +++ b/main/accessibility.js @@ -178,8 +178,11 @@ const GizmoMenuManager = { // Focus 1st button if nothing in gizmos is already focused, // but if another gizmo is active, leave focus there const alreadyFocused = document.activeElement?.closest("#gizmoButtons"); + if (!alreadyFocused) { - const btn = document.getElementById("showShapesButton"); + const btn = + document.querySelector(".gizmo-button.active") || + document.getElementById("showShapesButton"); if (btn && !btn.disabled && btn.offsetParent !== null) btn.focus(); } }