Skip to content

Movable in-game UI Panes#3487

Open
ryanbarlow97 wants to merge 3 commits intomainfrom
movablepanes
Open

Movable in-game UI Panes#3487
ryanbarlow97 wants to merge 3 commits intomainfrom
movablepanes

Conversation

@ryanbarlow97
Copy link
Contributor

If this PR fixes an issue, link it below. If not, delete these two lines.
Resolves #(issue number)

Description:

Describe the PR.

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

Please put your Discord username so you can be contacted if a bug or regression is found:

DISCORD_USERNAME

@ryanbarlow97 ryanbarlow97 marked this pull request as ready for review March 21, 2026 23:14
@ryanbarlow97 ryanbarlow97 requested a review from a team as a code owner March 21, 2026 23:14
Copilot AI review requested due to automatic review settings March 21, 2026 23:14
@ryanbarlow97 ryanbarlow97 changed the title first commit Movable in-game UI Panes Mar 21, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 21, 2026

Walkthrough

Adds a draggable panel system: new DraggableController and DraggableManager handle pointer-driven dragging, viewport clamping, overlap avoidance, and localStorage persistence. Multiple UI components were updated to expose data-draggable anchors and render <draggable-panel> wrappers for lock/reset controls and responsive fixed layouts.

Changes

Cohort / File(s) Summary
Core Draggable System
src/client/graphics/DraggableController.ts, src/client/graphics/DraggableManager.ts, src/client/graphics/layers/DraggablePanel.ts
New classes and custom element implementing pointer-based dragging, commit threshold, viewport clamping, obstacle snapshotting, ResizeObserver coordination, and localStorage persistence with lock/reset UI.
Layout & HTML
index.html, src/client/graphics/layers/ControlPanel.ts, src/client/graphics/layers/EventsDisplay.ts, src/client/graphics/layers/GameLeftSidebar.ts
Switched several containers to sm:contents mobile-first layout, added data-draggable anchors, and inserted <draggable-panel> placeholders.
Additional UI Integrations
src/client/graphics/layers/GameRightSidebar.ts, src/client/graphics/layers/PlayerInfoOverlay.ts, src/client/graphics/layers/InGamePromo.ts, src/client/graphics/layers/UnitDisplay.ts
Right sidebar, player info, and ad promo now include draggable wrappers; right sidebar visibility simplified; unit items mark data-no-drag.
Renderer Hook
src/client/graphics/GameRenderer.ts
Window resize handler now calls DraggableManager.instance.reclampAll() after canvas resize.
Localization
resources/lang/en.json
Adds draggable_panel.unlock_to_move, draggable_panel.lock_position, and draggable_panel.reset_position keys.

Sequence Diagram

sequenceDiagram
    participant User as User / Pointer
    participant Controller as DraggableController
    participant Manager as DraggableManager
    participant DOM as DOM & Viewport
    participant Storage as localStorage

    User->>Controller: pointerdown on element
    Controller->>Controller: check locked, record pointer & start coords
    Controller->>DOM: setPointerCapture()

    User->>Controller: pointermove
    Controller->>Controller: check commit threshold
    alt committed
        Controller->>Manager: snapshotObstacles(exclude)
        Manager-->>Controller: obstacle rects
        Controller->>Controller: compute candidate offsets
        Controller->>Controller: clamp to viewport & resolve overlaps
        Controller->>DOM: apply CSS transform (translate)
        Controller->>Storage: persist x,y,locked
    end

    User->>Controller: pointerup / lostpointercapture
    Controller->>Controller: finalize move, restore z-index
    Controller->>DOM: release pointer capture
    Controller->>Manager: notify resize / reclamp if needed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

Drag a panel, find its place,
Move with pointer, keep the pace.
Lock it tight or set it free,
Tiny toolbar: reset, lock, key.
UI floats where you decide,
Pixels settle, side by side.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description contains only placeholder boilerplate text ('Describe the PR.') and a checklist template with no actual implementation details, rationale, or description of the changes made. Replace the placeholder text with a concrete description of the draggable UI panes feature, explain the implementation approach, and provide rationale for the design decisions made.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Movable in-game UI Panes' clearly and concisely describes the main feature being added—draggable UI panels in-game. It accurately reflects the changeset's primary objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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


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.

This comment was marked as resolved.

Copy link
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

🧹 Nitpick comments (2)
src/client/graphics/layers/PlayerInfoOverlay.ts (1)

522-522: Hardcoded offset couples positioning to panel width.

The calc(50%-250px) offset assumes a 500px panel width (from line 534). If the panel width changes, this value must be updated manually.

Consider using CSS custom properties or deriving the offset from the actual width for easier maintenance:

/* Example: Use transform for true centering without hardcoded values */
sm:left-1/2 sm:-translate-x-1/2

However, if transform conflicts with the draggable system's translate() usage, the current approach is acceptable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/PlayerInfoOverlay.ts` at line 522, The hardcoded
centering in PlayerInfoOverlay (class attribute containing
"sm:left-[calc(50%-250px)]") ties the offset to a fixed 500px panel width;
replace this with a maintenance-friendly approach by using true centering (e.g.,
set the small-screen left to 50% and apply a negative 50% translate) or use a
CSS custom property for the panel width and compute the calc from that property
so the position updates automatically if the panel size changes; if the
component's draggable system relies on transform-based translations, prefer the
CSS custom property approach to avoid conflicts with the draggable translate()
behavior and update the class on the element referenced in PlayerInfoOverlay
accordingly.
src/client/graphics/DraggableController.ts (1)

286-293: Consider a brief comment for the silent catch.

The empty catch block is acceptable here since releasePointerCapture can throw if already released. A short inline comment would clarify intent for future readers.

📝 Suggested clarification
       try {
         this.el.releasePointerCapture(this._pointerId);
-      } catch {
-        // already released
+      } catch {
+        // Pointer capture may already be released; safe to ignore
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/DraggableController.ts` around lines 286 - 293, Add a
brief inline comment inside the empty catch in DraggableController where
releasePointerCapture is called (the block that checks this._pointerId and calls
this.el.releasePointerCapture(this._pointerId)) explaining that the catch is
intentionally silent because releasePointerCapture can throw when the pointer is
already released; keep the catch empty otherwise so behavior stays the same and
reference this._pointerId and el.releasePointerCapture in the comment for
clarity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/graphics/DraggableController.ts`:
- Around line 333-345: The load() method currently assigns data.x/data.y and
data.locked directly from parsed localStorage which can be non-number or
non-boolean; update load() to defensively validate and coerce these fields
before assigning to this._offsetX, this._offsetY and this._locked: parse numeric
values with Number(...) or parseFloat and check Number.isFinite(...) (falling
back to 0 on invalid input), and ensure locked is a boolean (use === true or
Boolean(...) with a safe default); keep the existing try/catch and
localStorage.removeItem(this.storageKey) behavior but replace direct assignments
with validated/coerced values to prevent NaN or string values from propagating
into transforms.

---

Nitpick comments:
In `@src/client/graphics/DraggableController.ts`:
- Around line 286-293: Add a brief inline comment inside the empty catch in
DraggableController where releasePointerCapture is called (the block that checks
this._pointerId and calls this.el.releasePointerCapture(this._pointerId))
explaining that the catch is intentionally silent because releasePointerCapture
can throw when the pointer is already released; keep the catch empty otherwise
so behavior stays the same and reference this._pointerId and
el.releasePointerCapture in the comment for clarity.

In `@src/client/graphics/layers/PlayerInfoOverlay.ts`:
- Line 522: The hardcoded centering in PlayerInfoOverlay (class attribute
containing "sm:left-[calc(50%-250px)]") ties the offset to a fixed 500px panel
width; replace this with a maintenance-friendly approach by using true centering
(e.g., set the small-screen left to 50% and apply a negative 50% translate) or
use a CSS custom property for the panel width and compute the calc from that
property so the position updates automatically if the panel size changes; if the
component's draggable system relies on transform-based translations, prefer the
CSS custom property approach to avoid conflicts with the draggable translate()
behavior and update the class on the element referenced in PlayerInfoOverlay
accordingly.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 78625b5a-57c2-42a9-a96d-2a4b1b996a99

📥 Commits

Reviewing files that changed from the base of the PR and between 95cdd3b and e1a4c36.

📒 Files selected for processing (13)
  • index.html
  • resources/lang/en.json
  • src/client/graphics/DraggableController.ts
  • src/client/graphics/DraggableManager.ts
  • src/client/graphics/GameRenderer.ts
  • src/client/graphics/layers/ControlPanel.ts
  • src/client/graphics/layers/DraggablePanel.ts
  • src/client/graphics/layers/EventsDisplay.ts
  • src/client/graphics/layers/GameLeftSidebar.ts
  • src/client/graphics/layers/GameRightSidebar.ts
  • src/client/graphics/layers/InGamePromo.ts
  • src/client/graphics/layers/PlayerInfoOverlay.ts
  • src/client/graphics/layers/UnitDisplay.ts

@github-project-automation github-project-automation bot moved this from Triage to Development in OpenFront Release Management Mar 21, 2026
Copy link
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

🧹 Nitpick comments (1)
src/client/graphics/DraggableController.ts (1)

231-263: Collision resolution can fail to converge with clustered obstacles.

When multiple obstacles overlap the candidate rect, resolving one collision can push the element into another. The single-pass approach may leave residual overlaps. For the current use case with few panels, this is tolerable.

Consider adding a bounded iteration loop (e.g., max 3 passes) if future layouts add more draggable panels.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/DraggableController.ts` around lines 231 - 263, The
collision resolution in resolveCollisions(nr: DOMRect) does a single-pass over
this._obstacleRects and can leave residual overlaps; modify resolveCollisions to
loop up to a small bounded number of passes (e.g., max 3) re-checking all
obstacles each pass and breaking early if this._offsetX and this._offsetY do not
change between passes, so each iteration applies the same overlap logic but will
converge against clustered obstacles.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/graphics/layers/DraggablePanel.ts`:
- Around line 97-124: The two toolbar <button> elements in DraggablePanel's
template (the reset button that calls resetPosition() and uses
DraggablePanel.resetSvg, and the lock toggle button that calls toggleLock() and
switches between DraggablePanel.lockSvg / DraggablePanel.unlockSvg based on
this._locked) lack ARIA labels; add aria-label attributes to both buttons using
the same translated strings as their title props (e.g.
aria-label=${translateText("draggable_panel.reset_position")} for the reset
button and aria-label=${this._locked ?
translateText("draggable_panel.unlock_to_move") :
translateText("draggable_panel.lock_position")} for the lock button) so screen
readers will announce them.

---

Nitpick comments:
In `@src/client/graphics/DraggableController.ts`:
- Around line 231-263: The collision resolution in resolveCollisions(nr:
DOMRect) does a single-pass over this._obstacleRects and can leave residual
overlaps; modify resolveCollisions to loop up to a small bounded number of
passes (e.g., max 3) re-checking all obstacles each pass and breaking early if
this._offsetX and this._offsetY do not change between passes, so each iteration
applies the same overlap logic but will converge against clustered obstacles.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6036c1ba-43c1-4a41-8c37-1457d0acab2e

📥 Commits

Reviewing files that changed from the base of the PR and between e1a4c36 and 9da1e76.

📒 Files selected for processing (4)
  • index.html
  • src/client/graphics/DraggableController.ts
  • src/client/graphics/layers/DraggablePanel.ts
  • src/client/graphics/layers/PlayerInfoOverlay.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Development

Development

Successfully merging this pull request may close these issues.

2 participants