Skip to content

fix: prevent quick menu tab debounce when controller active#996

Merged
utkarshdalal merged 4 commits intoutkarshdalal:masterfrom
xXJSONDeruloXx:fix/quick-menu-touch-tab-double-tap
Mar 28, 2026
Merged

fix: prevent quick menu tab debounce when controller active#996
utkarshdalal merged 4 commits intoutkarshdalal:masterfrom
xXJSONDeruloXx:fix/quick-menu-touch-tab-double-tap

Conversation

@xXJSONDeruloXx
Copy link
Copy Markdown
Contributor

@xXJSONDeruloXx xXJSONDeruloXx commented Mar 23, 2026

follow up from #976

handles focus debounce in case where invoked with controller but tab touched on touch screen, rather than back with touch swipe then tab change on touch without controller active to render border focusable.

also restores selected tab and scroll position so you are not bumped to top of menu every invoke

Summary by CodeRabbit

  • Bug Fixes
    • Quick menu now preserves the previously selected tab when reopened and improves focus behavior so HUD content consistently receives focus.
  • New Features
    • Screen Effects tab now retains and accepts an external scroll state so its scroll position is preserved across view changes.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Made QuickMenu state and focus behavior conditional on visibility, removed two LaunchedEffect blocks, added a dedicated scroll state for the Screen Effects tab and passed it into ScreenEffectsTabContent, and updated the remaining focus-retry to call the HUD content’s first-item focus requester.

Changes

Cohort / File(s) Summary
QuickMenu UI & focus
app/src/main/java/app/gamenative/ui/component/QuickMenu.kt
Switched rememberSaveable to rememberSaveable(isVisible), removed LaunchedEffect blocks that forced HUD selection or gated focus by selectedTab, introduced effectsScrollState, and updated remaining LaunchedEffect to call hudItemFocusRequester.requestFocus() unconditionally.
Screen Effects scroll ownership
app/src/main/java/app/gamenative/ui/component/ScreenEffectsPanel.kt
Added scrollState: ScrollState = rememberScrollState() parameter to ScreenEffectsTabContent and used the provided scrollState for the vertical scroll modifier instead of creating an internal one.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰
A little hop, a careful peek,
The menu wakes when visibility’s unique.
No forced tab, no hurried leap,
The HUD’s first item now gets to keep
The gentle focus that the rabbit seeks. ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title claims to 'prevent quick menu tab debounce when controller active', but the actual changes focus on restoring selected tab state, managing scroll position, and adjusting focus behavior in the quick menu—not preventing debounce specifically. Update the title to accurately reflect the main changes, such as 'fix: restore quick menu tab selection and scroll state on visibility' or 'fix: improve quick menu focus handling and state restoration'.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
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

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 1 file

@xXJSONDeruloXx xXJSONDeruloXx changed the title fix: prevent quick menu tab touch double tap fix: prevent quick menu tab debounce when controller active Mar 23, 2026
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.

🧹 Nitpick comments (1)
app/src/main/java/app/gamenative/ui/component/QuickMenu.kt (1)

517-532: LGTM — consolidating focus logic with tab-aware targeting looks correct.

The change appropriately:

  1. Preserves the selected tab across menu close/reopen cycles (via rememberSaveable at line 264)
  2. Requests focus on the correct tab based on current selectedTab value
  3. Keeps selectedTab out of the effect keys intentionally, since re-focusing should only happen on visibility transitions

Optional: The catch block swallows all Exception types. Since FocusRequester.requestFocus() specifically throws IllegalStateException when the requester isn't attached, catching that explicitly would be more precise:

♻️ Optional refinement
-                } catch (_: Exception) {
+                } catch (_: IllegalStateException) {

,

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

In `@app/src/main/java/app/gamenative/ui/component/QuickMenu.kt` around lines 517
- 532, The catch currently swallows all Exception types in the
LaunchedEffect(isVisible) retry loop; change it to catch IllegalStateException
specifically so only the focus-requester-not-attached case is handled while
other exceptions bubble: update the try/catch inside the LaunchedEffect that
calls .requestFocus() for the when(selectedTab) branch (referencing
hudTabFocusRequester, effectsTabFocusRequester, controllerTabFocusRequester and
selectedTab/QuickMenuTab) to catch IllegalStateException instead of Exception
and keep the delay(80) retry behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/src/main/java/app/gamenative/ui/component/QuickMenu.kt`:
- Around line 517-532: The catch currently swallows all Exception types in the
LaunchedEffect(isVisible) retry loop; change it to catch IllegalStateException
specifically so only the focus-requester-not-attached case is handled while
other exceptions bubble: update the try/catch inside the LaunchedEffect that
calls .requestFocus() for the when(selectedTab) branch (referencing
hudTabFocusRequester, effectsTabFocusRequester, controllerTabFocusRequester and
selectedTab/QuickMenuTab) to catch IllegalStateException instead of Exception
and keep the delay(80) retry behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b41cc4b9-e41f-47ef-b3e1-569f61755d36

📥 Commits

Reviewing files that changed from the base of the PR and between 00f8b0c and 1c112e7.

📒 Files selected for processing (1)
  • app/src/main/java/app/gamenative/ui/component/QuickMenu.kt

@xXJSONDeruloXx xXJSONDeruloXx force-pushed the fix/quick-menu-touch-tab-double-tap branch from b526cf5 to 00f8b0c Compare March 23, 2026 17:56
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/src/main/java/app/gamenative/ui/component/QuickMenu.kt">

<violation number="1">
P2: Effects tab scroll position persistence was regressed by removing hoisted scroll state, causing reset when tab content is recomposed.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/app/gamenative/ui/component/QuickMenu.kt`:
- Line 264: The selectedTab reset is caused by using rememberSaveable(isVisible)
as a key and focus restoration always targets hudItemFocusRequester; change the
state to rememberSaveable() without isVisible so selectedTab persists across
visibility toggles (use var selectedTab by rememberSaveable {
mutableIntStateOf(QuickMenuTab.HUD) }) and update the LaunchedEffect that
restores focus to choose the correct FocusRequester based on the current
selectedTab (inspect QuickMenuTab values and dispatch focus to the corresponding
requester such as hudItemFocusRequester, weaponsItemFocusRequester,
inventoryItemFocusRequester, etc., instead of always calling
hudItemFocusRequester). Ensure the LaunchedEffect's key includes selectedTab (or
no key if you trigger manually) so the right element receives focus when the UI
becomes visible.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1056710f-3343-4484-bc06-84ed6ecb8961

📥 Commits

Reviewing files that changed from the base of the PR and between b526cf5 and 49d1920.

📒 Files selected for processing (2)
  • app/src/main/java/app/gamenative/ui/component/QuickMenu.kt
  • app/src/main/java/app/gamenative/ui/component/ScreenEffectsPanel.kt

}

var selectedTab by rememberSaveable { mutableIntStateOf(QuickMenuTab.HUD) }
var selectedTab by rememberSaveable(isVisible) { mutableIntStateOf(QuickMenuTab.HUD) }
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 23, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C4 '\bQuickMenu\s*\(' --type=kt

Repository: utkarshdalal/GameNative

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Find QuickMenu.kt file
fd QuickMenu.kt --type f

Repository: utkarshdalal/GameNative

Length of output: 125


🏁 Script executed:

#!/bin/bash
# Search for QuickMenu instantiations (without type restriction)
rg -n -C4 '\bQuickMenu\s*\(' --type kotlin 2>/dev/null || rg -n -C4 '\bQuickMenu\s*\('

Repository: utkarshdalal/GameNative

Length of output: 3523


🏁 Script executed:

#!/bin/bash
# Get the QuickMenu.kt file and examine key lines
# First find the file
find . -name "QuickMenu.kt" -type f | head -1 | xargs cat -n | sed -n '250,280p;510,540p'

Repository: utkarshdalal/GameNative

Length of output: 2552


🏁 Script executed:

#!/bin/bash
# Look at QuickMenu usage in XServerScreen
sed -n '1940,1965p' app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt

Repository: utkarshdalal/GameNative

Length of output: 1074


🏁 Script executed:

#!/bin/bash
# Check if there are other QuickMenu call sites besides XServerScreen
rg -l 'QuickMenu\(' --type kotlin app/src/main/java/

Repository: utkarshdalal/GameNative

Length of output: 193


🏁 Script executed:

#!/bin/bash
# Check if selectedTab is ever saved/restored elsewhere (e.g., SavedStateHandle, DataStore)
rg -n 'selectedTab' app/src/main/java/app/gamenative/ui/component/QuickMenu.kt

Repository: utkarshdalal/GameNative

Length of output: 898


🏁 Script executed:

#!/bin/bash
# Check if there's any mechanism in the broader codebase to persist QuickMenu selectedTab
rg -n 'QuickMenuTab' app/src/main/java/app/gamenative/ | grep -E '(DataStore|SavedStateHandle|remembered|saved)'

Repository: utkarshdalal/GameNative

Length of output: 49


Fix tab reset and focus targeting for selected tab restoration.

The component resets selectedTab to HUD on each visibility toggle because rememberSaveable(isVisible) uses visibility as a reset key. Additionally, the LaunchedEffect always targets hudItemFocusRequester regardless of which tab is actually selected, so even if the tab state were preserved, focus would still go to the wrong UI element. This prevents the "restore selected tab" feature from working.

Suggested fix
-    var selectedTab by rememberSaveable(isVisible) { mutableIntStateOf(QuickMenuTab.HUD) }
+    var selectedTab by rememberSaveable { mutableIntStateOf(QuickMenuTab.HUD) }

-    LaunchedEffect(isVisible) {
+    LaunchedEffect(isVisible, selectedTab, renderer) {
         if (isVisible) {
+            val targetRequester = when (selectedTab) {
+                QuickMenuTab.HUD -> hudItemFocusRequester
+                QuickMenuTab.EFFECTS -> if (renderer != null) effectsItemFocusRequester else effectsTabFocusRequester
+                QuickMenuTab.CONTROLLER -> controllerItemFocusRequester
+                else -> hudItemFocusRequester
+            }
             repeat(3) {
                 try {
-                    hudItemFocusRequester.requestFocus()
+                    targetRequester.requestFocus()
                     return@LaunchedEffect
                 } catch (_: Exception) {
                     delay(80)
                 }
             }
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var selectedTab by rememberSaveable(isVisible) { mutableIntStateOf(QuickMenuTab.HUD) }
var selectedTab by rememberSaveable { mutableIntStateOf(QuickMenuTab.HUD) }
LaunchedEffect(isVisible, selectedTab, renderer) {
if (isVisible) {
val targetRequester = when (selectedTab) {
QuickMenuTab.HUD -> hudItemFocusRequester
QuickMenuTab.EFFECTS -> if (renderer != null) effectsItemFocusRequester else effectsTabFocusRequester
QuickMenuTab.CONTROLLER -> controllerItemFocusRequester
else -> hudItemFocusRequester
}
repeat(3) {
try {
targetRequester.requestFocus()
return@LaunchedEffect
} catch (_: Exception) {
delay(80)
}
}
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/ui/component/QuickMenu.kt` at line 264, The
selectedTab reset is caused by using rememberSaveable(isVisible) as a key and
focus restoration always targets hudItemFocusRequester; change the state to
rememberSaveable() without isVisible so selectedTab persists across visibility
toggles (use var selectedTab by rememberSaveable {
mutableIntStateOf(QuickMenuTab.HUD) }) and update the LaunchedEffect that
restores focus to choose the correct FocusRequester based on the current
selectedTab (inspect QuickMenuTab values and dispatch focus to the corresponding
requester such as hudItemFocusRequester, weaponsItemFocusRequester,
inventoryItemFocusRequester, etc., instead of always calling
hudItemFocusRequester). Ensure the LaunchedEffect's key includes selectedTab (or
no key if you trigger manually) so the right element receives focus when the UI
becomes visible.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@xXJSONDeruloXx saw this one?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@phobos665
Copy link
Copy Markdown
Contributor

Just so I understand this, you're persisting the scroll-state through injection rather than of its own accord?

@xXJSONDeruloXx
Copy link
Copy Markdown
Contributor Author

@phobos665 yeah intentionally, so the state is restored if you go to a diff tab and come back, since it's conditionally composed. This way it survives changing tabs and close/open of qam

@utkarshdalal utkarshdalal merged commit 259848c into utkarshdalal:master Mar 28, 2026
3 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.

3 participants