Skip to content

Added gyro aiming support (gamepad & mouse)#1173

Draft
unbelievableflavour wants to merge 1 commit intoutkarshdalal:masterfrom
unbelievableflavour:gyro-aiming-v2
Draft

Added gyro aiming support (gamepad & mouse)#1173
unbelievableflavour wants to merge 1 commit intoutkarshdalal:masterfrom
unbelievableflavour:gyro-aiming-v2

Conversation

@unbelievableflavour
Copy link
Copy Markdown
Contributor

@unbelievableflavour unbelievableflavour commented Apr 10, 2026

Description

A better gyro implementation not relying on code from original winlator implementations. This better fits in gamenative as it works for both gamepad joysticks & mouse.

Recording

image

Another vids can be found in discord (they were too big for the github upload):
https://discord.com/channels/1378308569287622737/1452868088277241979

Checklist

  • If I have access to #code-changes, I have discussed this change there and it has been green-lighted. If I do not have access, I have still provided clear context in this PR. If I skip both, I accept that this change may face delays in review, may not be reviewed at all, or may be closed.
  • I have attached a recording of the change.
  • I have read and agree to the contribution guidelines in CONTRIBUTING.md.

Summary by CodeRabbit

  • New Features

    • Added gyro controller support: enable/disable, mapping targets (1–3 / left/right/mouse), sensitivity slider, X/Y invert toggles, persisted settings, and a Gyro tab in the Quick Menu.
  • Bug Fixes

    • Improved gamepad motion handling and input-control lifecycle to preserve profiles when hiding controls.
  • Tests

    • Added unit tests for gyro mapping, rotation/inversion, sensitivity, clamping, deadzone, and lifecycle.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 60f8279a-e67d-4adf-a26e-20d0ed23e573

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds end-to-end gyro controller support: persisted gyro preferences, a SensorEventListener-based GyroController, UI controls in QuickMenu/XServerScreen, gyro mixing in InputControlsView, string resources, and unit tests.

Changes

Cohort / File(s) Summary
Preferences
app/src/main/java/app/gamenative/PrefManager.kt
Added persisted gyro prefs and accessors: controls_gyro_mode, controls_gyro_last_target (clamped 1–3), controls_gyro_invert_x, controls_gyro_invert_y, plus setGyroMode.
Quick Menu UI
app/src/main/java/app/gamenative/ui/component/QuickMenu.kt
Extended QuickMenu composable with gyro params/callbacks and added ControllerGyroSection (enable toggle, mapping chips 1–3, sensitivity row with stepped clamping, invert toggles).
Screen wiring
app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt
New gyro preference constants/parsers; Compose state for gyro settings initialized from PrefManager; persists changes and forwards to InputControlsView; motion-event handling tweak; hideInputControls() no longer clears profile.
Gyro sensor mapping
app/src/main/java/com/winlator/widget/GyroController.java
New package-private GyroController implementing SensorEventListener: registration lifecycle, rotation-aware axis remapping, invert/sensitivity/deadzone handling, emits normalized outputs via Listener.
InputControls integration
app/src/main/java/com/winlator/widget/InputControlsView.java
Added GYRO_MODE constants, GyroController field, gyro config APIs, lifecycle forwarding, separate baseThumb/gyroThumb contributions, mixing logic, gyro→mouse/stick update helpers, reset on profile clear.
Resources
app/src/main/res/values/strings.xml
Added strings for gyro quick-menu tab, subtitle, mapping label, sensitivity label/value format, and invert X/Y labels.
Tests
app/src/test/java/com/winlator/widget/GyroControllerTest.kt
New unit tests covering rotation remapping, inversion, sensitivity/clamping/deadzone, mode/edit/profile transitions, emitted callbacks, and sensor registration lifecycle.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as QuickMenu / XServerScreen
    participant Prefs as PrefManager
    participant ICView as InputControlsView
    participant Gyro as GyroController
    participant Sensor as SensorManager
    participant Handler as WinHandler

    User->>UI: change gyro settings (mode/sensitivity/invert)
    UI->>Prefs: persist settings
    UI->>ICView: setGyroMode / setGyroSensitivity / setGyroInvert*
    ICView->>Gyro: apply configuration (mode,sens,invert,hasProfile)
    Gyro->>Sensor: register/unregister listener (attach / mode/profile/edit changes)
    Sensor->>Gyro: onSensorChanged(gyroData)
    Gyro->>Gyro: remap by rotation, apply invert/sensitivity/deadzone
    Gyro->>ICView: onGyroOutput(x,y,rightStick,isMouse)
    ICView->>ICView: mix gyroThumb + baseThumb → GamepadState
    ICView->>Handler: sendGamepadState / sendVirtualGamepadState
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I twitched my whiskers, tilted my head,

Sensors humming where soft paws tread,
Sensitivity set, inversions aligned,
Sticks and mouse dance, all neatly combined,
Hop, wobble, play — a rabbit's joy spread.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Added gyro aiming support (gamepad & mouse)' directly and clearly summarizes the main change across all modified files.
Description check ✅ Passed The PR description follows the template structure with completed sections, includes a recording screenshot, and all checklist items are marked complete.

✏️ 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)
app/src/main/java/com/winlator/widget/InputControlsView.java (1)

283-293: ⚠️ Potential issue | 🟠 Major

Reset thumb state on any profile swap.

baseThumb* and gyroThumb* live on the view, not on ControlsProfile. Replacing one non-null profile with another keeps the previous profile’s stick contribution alive, so the first gyro/controller update on the new profile can inherit a stale axis value and latch movement/aim.

Proposed fix
 public synchronized void setProfile(ControlsProfile profile) {
+        if (this.profile != profile) {
+            resetThumbContributions();
+        }
         if (profile != null) {
             this.profile = profile;
             deselectAllElements();
         }
-        else {
+        else {
             this.profile = null;
-            resetThumbContributions();
         }
         gyroController.setHasProfile(this.profile != null);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/winlator/widget/InputControlsView.java` around lines
283 - 293, When swapping profiles in setProfile(ControlsProfile profile) ensure
you reset the view-local thumb state so previous profile stick contributions
can't carry over: always call resetThumbContributions() when the active profile
reference changes (both when replacing a non-null profile with another and when
clearing it). Update setProfile to compare/replace the profile and invoke
resetThumbContributions() before/after assigning this.profile (and still call
deselectAllElements() for the non-null branch), then keep the existing
gyroController.setHasProfile(this.profile != null) call.
🧹 Nitpick comments (2)
app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt (2)

2083-2087: Clamp sensitivity at the callback boundary too.

The UI currently sends clamped values, but this callback persists and applies whatever it receives. Reapplying the same 0.1f..2.0f guard here keeps future callers from pushing invalid values into prefs or InputControlsView.

Suggested change
             gyroSensitivity = controlsGyroSensitivity,
             onGyroSensitivityChanged = { sensitivity ->
-                controlsGyroSensitivity = sensitivity
-                PrefManager.setFloat(PREF_CONTROLS_GYRO_SENSITIVITY, sensitivity)
-                PluviaApp.inputControlsView?.setGyroSensitivity(sensitivity)
+                val clamped = sensitivity.coerceIn(0.1f, 2.0f)
+                controlsGyroSensitivity = clamped
+                PrefManager.setFloat(PREF_CONTROLS_GYRO_SENSITIVITY, clamped)
+                PluviaApp.inputControlsView?.setGyroSensitivity(clamped)
             },
🤖 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/screen/xserver/XServerScreen.kt` around
lines 2083 - 2087, The onGyroSensitivityChanged callback currently writes and
forwards whatever value it receives; enforce the same 0.1f..2.0f clamp at the
callback boundary by clamping the incoming sensitivity before assigning to
controlsGyroSensitivity, before calling
PrefManager.setFloat(PREF_CONTROLS_GYRO_SENSITIVITY, ...), and before calling
PluviaApp.inputControlsView?.setGyroSensitivity(...); update the callback in
XServerScreen (the onGyroSensitivityChanged lambda) to compute a local clamped
value and use that everywhere so invalid values never reach prefs or
InputControlsView.

197-217: Share the gyro mode representation instead of duplicating raw ints.

These helpers are the source of truth for gyro mode, but QuickMenu.kt now has to hardcode 0/1/2 to match them. That coupling is easy to drift and would silently remap both saved prefs and the runtime mode sent to InputControlsView. Please move this into a shared enum/constants type and reuse it from both files.

🤖 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/screen/xserver/XServerScreen.kt` around
lines 197 - 217, Replace the duplicated raw-int gyro representation with a
shared enum: create an enum GyroMode { DISABLED, LEFT_STICK, RIGHT_STICK }
(include a companion fromPref(value: String?): GyroMode and a toPrefValue():
String and an explicit int property if numeric values are required), then remove
or refactor the existing constants
GYRO_MODE_DISABLED/GYRO_MODE_LEFT_STICK/GYRO_MODE_RIGHT_STICK and the helpers
parseGyroMode and gyroModeToPrefValue to use this enum (or delegate to the enum
methods); finally update QuickMenu.kt (and any other callers) to use GyroMode.*
instead of hardcoded 0/1/2 so all pref parsing, saving and runtime mode passing
(e.g., into InputControlsView) uses the shared GyroMode type.
🤖 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/com/winlator/widget/GyroController.java`:
- Around line 56-59: When entering edit mode in setEditMode(boolean editMode),
clear the active stick before stopping sensors so the UI doesn't retain the last
gyro vector; call whatever routine the view uses to accept stick/input updates
(e.g. invoke InputControlsView's method that applies a stick vector or the
existing gyro input handler) to emit a zero-value vector, then proceed to
updateRegistration(); modify setEditMode to, when editMode is true, send a zero
stick/update to InputControlsView (clearActiveStick or equivalent) before
calling updateRegistration().

---

Outside diff comments:
In `@app/src/main/java/com/winlator/widget/InputControlsView.java`:
- Around line 283-293: When swapping profiles in setProfile(ControlsProfile
profile) ensure you reset the view-local thumb state so previous profile stick
contributions can't carry over: always call resetThumbContributions() when the
active profile reference changes (both when replacing a non-null profile with
another and when clearing it). Update setProfile to compare/replace the profile
and invoke resetThumbContributions() before/after assigning this.profile (and
still call deselectAllElements() for the non-null branch), then keep the
existing gyroController.setHasProfile(this.profile != null) call.

---

Nitpick comments:
In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt`:
- Around line 2083-2087: The onGyroSensitivityChanged callback currently writes
and forwards whatever value it receives; enforce the same 0.1f..2.0f clamp at
the callback boundary by clamping the incoming sensitivity before assigning to
controlsGyroSensitivity, before calling
PrefManager.setFloat(PREF_CONTROLS_GYRO_SENSITIVITY, ...), and before calling
PluviaApp.inputControlsView?.setGyroSensitivity(...); update the callback in
XServerScreen (the onGyroSensitivityChanged lambda) to compute a local clamped
value and use that everywhere so invalid values never reach prefs or
InputControlsView.
- Around line 197-217: Replace the duplicated raw-int gyro representation with a
shared enum: create an enum GyroMode { DISABLED, LEFT_STICK, RIGHT_STICK }
(include a companion fromPref(value: String?): GyroMode and a toPrefValue():
String and an explicit int property if numeric values are required), then remove
or refactor the existing constants
GYRO_MODE_DISABLED/GYRO_MODE_LEFT_STICK/GYRO_MODE_RIGHT_STICK and the helpers
parseGyroMode and gyroModeToPrefValue to use this enum (or delegate to the enum
methods); finally update QuickMenu.kt (and any other callers) to use GyroMode.*
instead of hardcoded 0/1/2 so all pref parsing, saving and runtime mode passing
(e.g., into InputControlsView) uses the shared GyroMode type.
🪄 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: 72a80493-dd69-41fe-91a2-3f380c9ca073

📥 Commits

Reviewing files that changed from the base of the PR and between 55c0796 and 1b49efc.

📒 Files selected for processing (7)
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/main/java/app/gamenative/ui/component/QuickMenu.kt
  • app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt
  • app/src/main/java/com/winlator/widget/GyroController.java
  • app/src/main/java/com/winlator/widget/InputControlsView.java
  • app/src/main/res/values/strings.xml
  • app/src/test/java/com/winlator/widget/GyroControllerTest.kt

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.

2 issues found across 7 files

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/com/winlator/widget/GyroController.java">

<violation number="1" location="app/src/main/java/com/winlator/widget/GyroController.java:152">
P2: Disabling gyro via edit mode or missing profile only unregisters the sensor without clearing the active stick value, so the last non-zero gyro input can remain latched until another subsystem overwrites it.</violation>
</file>

<file name="app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt">

<violation number="1" location="app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt:1156">
P2: Gamepad motion events are double-dispatched to physical and overlay handlers, allowing duplicate axis/gyro processing.</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.

Caution

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

⚠️ Outside diff range comments (1)
app/src/main/java/com/winlator/widget/InputControlsView.java (1)

283-293: ⚠️ Potential issue | 🟠 Major

Reset thumb contributions on every profile swap, not only when profile is null.

At Line 284-Line 287, switching to another non-null ControlsProfile keeps previous baseThumb*/gyroThumb* values alive, so the new profile can inherit stale stick state until the next input update.

Proposed fix
 public synchronized void setProfile(ControlsProfile profile) {
-    if (profile != null) {
-        this.profile = profile;
-        deselectAllElements();
-    }
-    else {
-        this.profile = null;
-        resetThumbContributions();
-    }
+    // Always clear mixed stick contributions when profile changes.
+    resetThumbContributions();
+
+    if (profile != null) {
+        this.profile = profile;
+        deselectAllElements();
+    } else {
+        this.profile = null;
+    }
     gyroController.setHasProfile(this.profile != null);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/winlator/widget/InputControlsView.java` around lines
283 - 293, The current setProfile(ControlsProfile) only calls
resetThumbContributions() when profile is null, leaving previous
baseThumb*/gyroThumb* state when swapping between non-null profiles; modify
setProfile so thumb contributions are reset on every profile change — call
resetThumbContributions() whenever the incoming profile differs from the current
(or unconditionally on any setProfile call before assigning the new profile),
then assign this.profile, call deselectAllElements() for non-null profiles, and
still call gyroController.setHasProfile(this.profile != null) so stale stick
state cannot be carried into the new ControlsProfile.
🧹 Nitpick comments (1)
app/src/main/java/com/winlator/widget/InputControlsView.java (1)

1026-1031: Extract duplicated gamepad-dispatch block into one helper.

Both blocks send identical updates to WinHandler. Centralizing this avoids drift between touch and gyro paths.

Refactor sketch
+    private void dispatchGamepadState(GamepadState state) {
+        WinHandler winHandler = xServer != null ? xServer.getWinHandler() : null;
+        if (winHandler != null) {
+            ExternalController controller = winHandler.getCurrentController();
+            if (controller != null) controller.state.copy(state);
+            winHandler.sendGamepadState();
+            winHandler.sendVirtualGamepadState(state);
+        }
+    }

Then replace both call sites with dispatchGamepadState(state);.

Also applies to: 1075-1081

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

In `@app/src/main/java/com/winlator/widget/InputControlsView.java` around lines
1026 - 1031, Extract the duplicated WinHandler dispatch logic into a single
helper method on InputControlsView (e.g., dispatchGamepadState(State state)):
inside it, null-check winHandler, get ExternalController via
winHandler.getCurrentController(), copy the state into controller.state if
controller != null, then call winHandler.sendGamepadState() and
winHandler.sendVirtualGamepadState(state). Replace both duplicated blocks that
currently reference winHandler, ExternalController, sendGamepadState and
sendVirtualGamepadState with calls to dispatchGamepadState(state).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@app/src/main/java/com/winlator/widget/InputControlsView.java`:
- Around line 283-293: The current setProfile(ControlsProfile) only calls
resetThumbContributions() when profile is null, leaving previous
baseThumb*/gyroThumb* state when swapping between non-null profiles; modify
setProfile so thumb contributions are reset on every profile change — call
resetThumbContributions() whenever the incoming profile differs from the current
(or unconditionally on any setProfile call before assigning the new profile),
then assign this.profile, call deselectAllElements() for non-null profiles, and
still call gyroController.setHasProfile(this.profile != null) so stale stick
state cannot be carried into the new ControlsProfile.

---

Nitpick comments:
In `@app/src/main/java/com/winlator/widget/InputControlsView.java`:
- Around line 1026-1031: Extract the duplicated WinHandler dispatch logic into a
single helper method on InputControlsView (e.g., dispatchGamepadState(State
state)): inside it, null-check winHandler, get ExternalController via
winHandler.getCurrentController(), copy the state into controller.state if
controller != null, then call winHandler.sendGamepadState() and
winHandler.sendVirtualGamepadState(state). Replace both duplicated blocks that
currently reference winHandler, ExternalController, sendGamepadState and
sendVirtualGamepadState with calls to dispatchGamepadState(state).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e42c0667-4684-41e3-87a9-e0fa19f3a6fc

📥 Commits

Reviewing files that changed from the base of the PR and between 1b49efc and 3e73314.

📒 Files selected for processing (7)
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/main/java/app/gamenative/ui/component/QuickMenu.kt
  • app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt
  • app/src/main/java/com/winlator/widget/GyroController.java
  • app/src/main/java/com/winlator/widget/InputControlsView.java
  • app/src/main/res/values/strings.xml
  • app/src/test/java/com/winlator/widget/GyroControllerTest.kt
✅ Files skipped from review due to trivial changes (2)
  • app/src/main/res/values/strings.xml
  • app/src/main/java/com/winlator/widget/GyroController.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/src/test/java/com/winlator/widget/GyroControllerTest.kt
  • app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt
  • app/src/main/java/app/gamenative/ui/component/QuickMenu.kt

@unbelievableflavour unbelievableflavour changed the title Added gamepad gyro aiming support Added gyro aiming support (gamepad & mouse) Apr 12, 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.

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
app/src/main/java/com/winlator/widget/InputControlsView.java (1)

290-300: ⚠️ Potential issue | 🟠 Major

Clear gyro output before dropping the active profile.

Line 296 sets this.profile to null before gyroController.setHasProfile(false) runs. In left/right-stick modes that makes GyroController.clearCurrentStick() ineffective, because updateGyroStick() immediately returns once profile == null, so the last gyro deflection can stay latched.

🛠️ Suggested fix
     public synchronized void setProfile(ControlsProfile profile) {
-        if (profile != null) {
+        if (profile == null && this.profile != null) {
+            gyroController.setHasProfile(false);
+            resetThumbContributions();
+            this.profile = null;
+            return;
+        }
+        if (profile != null) {
             this.profile = profile;
             deselectAllElements();
-        }
-        else {
+        } else {
             this.profile = null;
             resetThumbContributions();
         }
         gyroController.setHasProfile(this.profile != null);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/winlator/widget/InputControlsView.java` around lines
290 - 300, The bug: when clearing the active ControlsProfile in setProfile the
code sets this.profile = null before informing GyroController, which lets
updateGyroStick() short-circuit and leaves the last gyro deflection latched; to
fix, ensure gyro output is cleared before dropping the profile by calling
GyroController.clearCurrentStick() (or at minimum calling
gyroController.setHasProfile(false)) while this.profile is still non-null —
adjust setProfile so you either call gyroController.clearCurrentStick() then
reset this.profile to null and resetThumbContributions(), or call
gyroController.setHasProfile(false) before setting this.profile = null; update
the method references setProfile, gyroController.setHasProfile,
GyroController.clearCurrentStick, resetThumbContributions, deselectAllElements
accordingly.
🧹 Nitpick comments (1)
app/src/main/java/com/winlator/widget/GyroController.java (1)

114-145: Add coverage for the remaining rotation branches.

The provided tests only exercise Surface.ROTATION_90 and inversion at Surface.ROTATION_0 (app/src/test/java/com/winlator/widget/GyroControllerTest.kt:25-50). ROTATION_180, ROTATION_270, and invert+rotation combinations are still unverified here.

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

In `@app/src/main/java/com/winlator/widget/GyroController.java` around lines 114 -
145, Add unit tests covering Surface.ROTATION_180 and Surface.ROTATION_270
branches and combinations with invertX/invertY for the GyroController.mapToStick
behavior; locate mapToStick in GyroController and extend GyroControllerTest
(app/src/test/java/com/winlator/widget/GyroControllerTest.kt) to assert expected
mapped outputs for raw inputs under ROTATION_180 and ROTATION_270 and repeat
assertions with invertX/invertY toggled, also include small-deadzone behavior
(values within 0.03 clamp to 0) and clamping to [-1,1] so each
rotation/inversion path is exercised.
🤖 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/screen/xserver/XServerScreen.kt`:
- Around line 2090-2101: When disabling gyro in the onGyroEnabledChanged lambda,
persist the current in-memory target before overwriting the preference with
"disabled": ensure you read the active target (controlsGyroLastTarget /
controlsGyroMode), write it to PrefManager.controlsGyroLastTarget and call
PrefManager.setGyroMode(gyroModeToPrefValue(target)) (or equivalent) so the last
target is saved, then set controlsGyroMode = GYRO_MODE_DISABLED and finally call
PrefManager.setGyroMode("disabled") and
PluviaApp.inputControlsView?.setGyroMode(GYRO_MODE_DISABLED); update the order
in onGyroEnabledChanged to save the target first to avoid losing the mapping.

In `@app/src/main/java/com/winlator/widget/GyroController.java`:
- Line 31: The code currently calls updateRegistration() from setMode(),
setEditMode(), and setHasProfile() without checking whether the view is actually
attached, allowing the gyroscope to be registered while off-screen; add an
attached-state flag updated in onAttachedToWindow() and onDetachedFromWindow()
(or use isAttachedToWindow()) and modify updateRegistration() to require that
attached==true before registering the sensor, and always unregister in
onDetachedFromWindow(); update references to isRegistered, updateRegistration(),
setMode(), setEditMode(), and setHasProfile() accordingly so registration only
happens when the view is attached and is unregistered on detach.

---

Outside diff comments:
In `@app/src/main/java/com/winlator/widget/InputControlsView.java`:
- Around line 290-300: The bug: when clearing the active ControlsProfile in
setProfile the code sets this.profile = null before informing GyroController,
which lets updateGyroStick() short-circuit and leaves the last gyro deflection
latched; to fix, ensure gyro output is cleared before dropping the profile by
calling GyroController.clearCurrentStick() (or at minimum calling
gyroController.setHasProfile(false)) while this.profile is still non-null —
adjust setProfile so you either call gyroController.clearCurrentStick() then
reset this.profile to null and resetThumbContributions(), or call
gyroController.setHasProfile(false) before setting this.profile = null; update
the method references setProfile, gyroController.setHasProfile,
GyroController.clearCurrentStick, resetThumbContributions, deselectAllElements
accordingly.

---

Nitpick comments:
In `@app/src/main/java/com/winlator/widget/GyroController.java`:
- Around line 114-145: Add unit tests covering Surface.ROTATION_180 and
Surface.ROTATION_270 branches and combinations with invertX/invertY for the
GyroController.mapToStick behavior; locate mapToStick in GyroController and
extend GyroControllerTest
(app/src/test/java/com/winlator/widget/GyroControllerTest.kt) to assert expected
mapped outputs for raw inputs under ROTATION_180 and ROTATION_270 and repeat
assertions with invertX/invertY toggled, also include small-deadzone behavior
(values within 0.03 clamp to 0) and clamping to [-1,1] so each
rotation/inversion path is exercised.
🪄 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: e55f8b64-7b82-46ae-9c4e-715b66e65298

📥 Commits

Reviewing files that changed from the base of the PR and between 3e73314 and 25dc489.

📒 Files selected for processing (7)
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/main/java/app/gamenative/ui/component/QuickMenu.kt
  • app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt
  • app/src/main/java/com/winlator/widget/GyroController.java
  • app/src/main/java/com/winlator/widget/InputControlsView.java
  • app/src/main/res/values/strings.xml
  • app/src/test/java/com/winlator/widget/GyroControllerTest.kt
✅ Files skipped from review due to trivial changes (2)
  • app/src/main/res/values/strings.xml
  • app/src/test/java/com/winlator/widget/GyroControllerTest.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/app/gamenative/PrefManager.kt

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)
app/src/main/java/com/winlator/widget/InputControlsView.java (1)

290-300: ⚠️ Potential issue | 🟡 Minor

Reset thumb contributions on every profile swap.

baseThumb*/gyroThumb* are view-scoped, but they are only cleared when profile == null. XServerScreen.kt switches this view between non-null profiles during in-game profile creation/save flows, so a held stick or gyro vector can bleed into the newly assigned profile until another input event overwrites it.

Proposed fix
 public synchronized void setProfile(ControlsProfile profile) {
+        if (this.profile != profile) {
+            resetThumbContributions();
+        }
         if (profile != null) {
             this.profile = profile;
             deselectAllElements();
         }
         else {
             this.profile = null;
-            resetThumbContributions();
         }
         gyroController.setHasProfile(this.profile != null);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/winlator/widget/InputControlsView.java` around lines
290 - 300, The profile swap currently only clears view-scoped thumb state when
profile == null; modify setProfile(ControlsProfile) to reset the thumb
contribution fields on every profile change (not just when setting null) by
invoking resetThumbContributions() whenever this.profile is replaced (e.g., call
resetThumbContributions() before assigning a new profile or whenever profile !=
incoming profile), while preserving deselectAllElements() for non-null
assignments and keeping gyroController.setHasProfile(this.profile != null).
🤖 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/screen/xserver/XServerScreen.kt`:
- Around line 1163-1169: The short-circuit in XServerScreen (the handled =
physicalControllerHandler?.onGenericMotionEvent(...) then if (!handled) call to
PluviaApp.inputControlsView?.onGenericMotionEvent(...)) drops gyro mixing
because PhysicalControllerHandler.handleInputEvent(...) assigns raw stick values
directly; change the routing so both handlers can process motion events: remove
the early-exit logic and always call
PluviaApp.inputControlsView.onGenericMotionEvent(...) after
physicalControllerHandler.onGenericMotionEvent(...), or alternatively update
PhysicalControllerHandler.handleInputEvent(...) to perform the same additive
gyro blend (use baseThumb* + gyroThumb* like InputControlsView.handleInputEvent
does) for stick axes; also ensure both handlers write to the same shared gamepad
state object (the same state instance) and avoid double-applying axes/triggers
by making PhysicalControllerHandler only consume events that must stop
propagation (e.g., button/axis releases) and returning true only in those cases.

---

Outside diff comments:
In `@app/src/main/java/com/winlator/widget/InputControlsView.java`:
- Around line 290-300: The profile swap currently only clears view-scoped thumb
state when profile == null; modify setProfile(ControlsProfile) to reset the
thumb contribution fields on every profile change (not just when setting null)
by invoking resetThumbContributions() whenever this.profile is replaced (e.g.,
call resetThumbContributions() before assigning a new profile or whenever
profile != incoming profile), while preserving deselectAllElements() for
non-null assignments and keeping gyroController.setHasProfile(this.profile !=
null).
🪄 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: 4f30ad62-081a-4bf2-8817-e70471971cee

📥 Commits

Reviewing files that changed from the base of the PR and between 25dc489 and ecf1d18.

📒 Files selected for processing (7)
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/main/java/app/gamenative/ui/component/QuickMenu.kt
  • app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt
  • app/src/main/java/com/winlator/widget/GyroController.java
  • app/src/main/java/com/winlator/widget/InputControlsView.java
  • app/src/main/res/values/strings.xml
  • app/src/test/java/com/winlator/widget/GyroControllerTest.kt
✅ Files skipped from review due to trivial changes (3)
  • app/src/main/res/values/strings.xml
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/test/java/com/winlator/widget/GyroControllerTest.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/app/gamenative/ui/component/QuickMenu.kt

@unbelievableflavour unbelievableflavour marked this pull request as draft April 12, 2026 12:27
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.

1 participant