Skip to content

Migration to InputManager#615

Merged
tracygardner merged 8 commits into
flipcomputing:mainfrom
lawsie:gizmo-overlay-fixes
May 7, 2026
Merged

Migration to InputManager#615
tracygardner merged 8 commits into
flipcomputing:mainfrom
lawsie:gizmo-overlay-fixes

Conversation

@lawsie
Copy link
Copy Markdown
Contributor

@lawsie lawsie commented May 7, 2026

Summary

  • Migrate various keyboard input listeners to be handled by the InputManager to make things clearer.
  • Gizmo button overlay now appears on Ctrl + G (in any context except overlay or typing) and persists until the context is switched away from gizmo/navigation - i.e. primarily if you do something in the editor.
  • Gizmo button overlay hides if color picker is opened

Some keyboard input listeners have NOT been migrated on purpose, because they are limited to a specific context (e.g. when the keyboard shortcuts overlay specifically is active) or because they are extremely complicated!

AI usage

Claude Sonnet 4.6 used throughout as a co-author, carefully checked by a human.

Questions

  • Currently, the numbers overlay comes up if you mouse click on a gizmo too. I think it is fairly straightforward to turn it off, but what is the desired behaviour here @tracygardner ? Currently the overlay is visible while any gizmo is active or focused.

Summary by CodeRabbit

  • Bug Fixes
    • Keyboard shortcuts now respect application context—disabled while typing or in overlays to prevent unintended activation.
    • Gizmo and area menus automatically close when focus or pointer interaction leaves the appropriate context.
    • Color picker keyboard shortcut behavior refined for improved usability.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR systematically migrates keyboard event handling across the application from direct window and document listeners to a centralized InputManager shortcut system, refines context detection around Blockly activity, and updates gizmo menu state transitions to integrate with the new architecture.

Changes

InputManager keyboard shortcut migration

Layer / File(s) Summary
InputManager shortcut infrastructure
main/inputmanager.js
Key combo resolution computes a single combo string, unifies modifiers to "Mod", and resolves handlers via multiple registry lookups (context+combo, wildcard+combo, context+code, wildcard+code). Debug output now displays resolved registry key.
GIZMO context detection
main/context.js
getCurrentContext() now checks whether Blockly is actively being used (via gesture drag or active element in SVG/toolbox) before returning "GIZMO" context, preventing premature classification.
Main application shortcuts
main/main.js
Global shortcuts for open, export, focus, and panel toggle (Mod+O, Mod+S, Mod+P, Mod+Slash, Mod+M, Mod+E) are wired via InputManager.on(...) instead of document keydown capture listener.
Area and Gizmo menu shortcuts
main/accessibility.js
Area menu toggle (Mod+B), close (Escape), and digit activation implemented via InputManager. Gizmo menu toggle (Mod+G) with context guards, digit shortcuts when menu open, and auto-closing via focusin/pointerdown listeners when focus leaves GIZMO/NAVIGATION contexts. activateArea now opens gizmo menu when target is #gizmoButtons.
Color picker shortcut
ui/colourpicker.js
Color picker "use" shortcut migrated from document keydown to InputManager.on("*", "KeyP") with context-aware skipping when in TYPING mode. Cleanup via InputManager.off() during close.

Gizmo state transition and menu integration

Layer / File(s) Summary
Gizmo state transitions
ui/gizmos.js
Color picker close re-activates picker button before returning to mesh-paint. Button clicks toggle GizmoMenuManager off. Canvas picking registers onExit cleanup to restore cursor and stop keyboard mode. toggleGizmo() calls exitGizmoState() first, then enables menu and gizmo.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • flipcomputing/flock#609: Modifies InputManager and ContextManager input/context subsystems and keyboard handling in accessibility.js with GizmoMenuManager, directly related to shortcut system refactoring.
  • flipcomputing/flock#561: Modifies ui/gizmos.js gizmo keyboard/cleanup behavior (exitGizmoState, toggleGizmo, GizmoMenuManager interactions) similarly.
  • flipcomputing/flock#591: Modifies main/accessibility.js for Area overlay and introduces GizmoMenuManager with numeric (1–9) and Mod+G keyboard handling.

Suggested labels

codex

Poem

🐰 Hop and tap, the keys now dance,
Through InputManager's guiding prance,
Contexts shift, and gizmos play,
State transitions light the way,
Blockly, menus, pickers too,
All the shortcuts shiny new!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Migration to InputManager' accurately summarizes the main change across the pull request—a comprehensive refactor migrating keyboard input listeners from global handlers to the centralized InputManager system.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
main/accessibility.js (1)

175-201: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

_watcher event listeners are never removed — accumulates leaked listeners on every toggle(true) call.

Each call to toggle(true) overwrites this._watcher with a new function and calls document.addEventListener twice (for focusin and pointerdown capture), but the previous function reference is discarded without removeEventListener. Since toggle(false) does no cleanup either, the old listeners remain attached permanently.

This is called from at least three sites: Ctrl+G (line 211), toggleGizmo() in gizmos.js, and activateArea() (line 104). Every gizmo button click adds two more permanent document-level listeners that fire on every subsequent user interaction.

🔒 Proposed fix — clean up stale watcher on both `true` and `false` paths
  toggle(show) {
    if (!this.overlay) return;
    if (show) {
      this.renderBadges();
+     if (this._watcher) {
+       document.removeEventListener("focusin", this._watcher);
+       document.removeEventListener("pointerdown", this._watcher, { capture: true });
+     }
      this._watcher = () => {
        const ctx = ContextManager.getCurrentContext();
        if (ctx !== "GIZMO" && ctx !== "NAVIGATION") this.toggle(false);
      };
      document.addEventListener("focusin", this._watcher);
      document.addEventListener("pointerdown", this._watcher, {
        capture: true,
      });

      // 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.querySelector(".gizmo-button.active") ||
          document.getElementById("showShapesButton");
        if (btn && !btn.disabled && btn.offsetParent !== null) btn.focus();
      }
+   } else {
+     if (this._watcher) {
+       document.removeEventListener("focusin", this._watcher);
+       document.removeEventListener("pointerdown", this._watcher, { capture: true });
+       this._watcher = null;
+     }
    }
    this.overlay.classList.toggle("hidden", !show);
  },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main/accessibility.js` around lines 175 - 201, The toggle(show) method is
adding document-level listeners via this._watcher every time show is true but
never removes them, leaking handlers; update toggle and related cleanup so any
existing watcher is removed before adding a new one and also removed when
hiding: check for this._watcher and call document.removeEventListener("focusin",
this._watcher) and document.removeEventListener("pointerdown", this._watcher,
{capture:true}) before assigning a new watcher, and ensure when toggling false
you remove the watchers and clear this._watcher; keep the existing behavior
around renderBadges(), focus logic and overlay.classList.toggle but add this
watcher removal logic to prevent accumulation.
♻️ Duplicate comments (1)
ui/gizmos.js (1)

1299-1303: ⚠️ Potential issue | 🟠 Major

GizmoMenuManager.toggle(true) here compounds the listener leak in accessibility.js.

Every gizmo button click invokes toggleGizmoGizmoMenuManager.toggle(true), which adds a new focusin/pointerdown _watcher pair on document without removing the previous one (root cause is in GizmoMenuManager.toggle). The fix below (in accessibility.js) eliminates the leak for this call site too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/gizmos.js` around lines 1299 - 1303, GizmoMenuManager.toggle(true) here
keeps adding duplicate document listeners causing a leak; make toggle idempotent
by modifying GizmoMenuManager.toggle so it checks for existing watcher state
before adding listeners (e.g., if enabling and a watcher already exists, do
nothing; if disabling remove the watcher and clear its reference). Update the
toggle logic used by exitGizmoState/resetAttachedMeshIfMeshAttached callers so
calling GizmoMenuManager.toggle(true) won’t register additional
focusin/pointerdown watchers when one is already active.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@main/main.js`:
- Around line 607-619: The exportCode call-site arguments are
inconsistent—InputManager.on("*", "Mod+KeyS", (e) => exportCode(workspace))
passes workspace but exportCodeButton.addEventListener("click", exportCode) and
other calls use no args; standardize by always passing the current workspace:
update exportCodeButton.addEventListener("click", ...) and any no-arg
exportCode() invocations to call exportCode(workspace) so all callers
consistently supply the workspace (refer to exportCode,
exportCodeButton.addEventListener, and the InputManager.on handler).

---

Outside diff comments:
In `@main/accessibility.js`:
- Around line 175-201: The toggle(show) method is adding document-level
listeners via this._watcher every time show is true but never removes them,
leaking handlers; update toggle and related cleanup so any existing watcher is
removed before adding a new one and also removed when hiding: check for
this._watcher and call document.removeEventListener("focusin", this._watcher)
and document.removeEventListener("pointerdown", this._watcher, {capture:true})
before assigning a new watcher, and ensure when toggling false you remove the
watchers and clear this._watcher; keep the existing behavior around
renderBadges(), focus logic and overlay.classList.toggle but add this watcher
removal logic to prevent accumulation.

---

Duplicate comments:
In `@ui/gizmos.js`:
- Around line 1299-1303: GizmoMenuManager.toggle(true) here keeps adding
duplicate document listeners causing a leak; make toggle idempotent by modifying
GizmoMenuManager.toggle so it checks for existing watcher state before adding
listeners (e.g., if enabling and a watcher already exists, do nothing; if
disabling remove the watcher and clear its reference). Update the toggle logic
used by exitGizmoState/resetAttachedMeshIfMeshAttached callers so calling
GizmoMenuManager.toggle(true) won’t register additional focusin/pointerdown
watchers when one is already active.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c6a27dc4-83e6-4194-84bc-301ac692f5f5

📥 Commits

Reviewing files that changed from the base of the PR and between 299e1ae and 70fc026.

📒 Files selected for processing (6)
  • main/accessibility.js
  • main/context.js
  • main/inputmanager.js
  • main/main.js
  • ui/colourpicker.js
  • ui/gizmos.js

Comment thread main/main.js
@tracygardner tracygardner merged commit 1659dce into flipcomputing:main May 7, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants