Refactor OpenXR controller input handling and update settings panel interactions

This commit is contained in:
iChris4 committed 2026-09-19 15:28:20 +02:00
1 parent 60c443654f
commit e681294709
11 files changed
+163 -95

No files matched your search

+68 -31
View File
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later
//
// The in-headset settings panel's controller handling, tested without a
// headset: the thumbstick chord that opens and closes it, the release latch that
// keeps a closing press out of the game, selection, scrolling, and where a hit
// lands on the panel's canvas.
// headset: the button that opens and closes it in each controller mode, the
// release latch that keeps a closing press out of the game, selection,
// scrolling, and where a hit lands on the panel's canvas.
#include "vr/openxr_settings_panel.h"
@@ -32,6 +32,8 @@ void CheckNear(float actual, float expected, const char* what, float tolerance =
}
constexpr float kDt = 1.0f / 90.0f;
constexpr OpenXRControllerMode kWiiRemote = OpenXRControllerMode::WiiRemote;
constexpr OpenXRControllerMode kGamepad = OpenXRControllerMode::Gamepad;
std::array<HandInputs, 2> Released() {
return {};
@@ -44,46 +46,80 @@ std::array<HandInputs, 2> Chord() {
return hands;
}
void ChordOpensAndClosesOnce() {
std::array<HandInputs, 2> LeftY() {
std::array<HandInputs, 2> hands{};
hands[0].secondary = true;
return hands;
}
void LeftYOpensAndClosesOnceAsAWiiRemote() {
Controls controls;
bool open = false;
auto right_b = Released();
right_b[1].secondary = true;
Frame frame = controls.Update(right_b, open, kDt, kWiiRemote);
Check(!open && !frame.withheld, "right B neither opens nor withholds");
frame = controls.Update(Chord(), open, kDt, kWiiRemote);
Check(!open && !frame.withheld, "as a Wii Remote the thumbstick chord does not open the panel");
controls.Update(Released(), open, kDt, kWiiRemote);
frame = controls.Update(LeftY(), open, kDt, kWiiRemote);
Check(open && frame.open && frame.withheld, "left Y opens the panel");
frame = controls.Update(LeftY(), open, kDt, kWiiRemote);
Check(open, "holding left Y does not toggle again");
frame = controls.Update(Released(), open, kDt, kWiiRemote);
Check(open && frame.withheld, "the panel stays open and keeps the controllers after left Y is released");
frame = controls.Update(LeftY(), open, kDt, kWiiRemote);
Check(!open && !frame.open, "left Y closes the panel again");
Check(frame.withheld, "the closing press is still withheld from the game");
frame = controls.Update(Released(), open, kDt, kWiiRemote);
Check(!frame.withheld, "the game gets the controllers back once everything is released");
}
void ChordOpensAndClosesOnceAsAGamepad() {
Controls controls;
bool open = false;
Frame frame = controls.Update(LeftY(), open, kDt, kGamepad);
Check(!open && !frame.withheld, "as a gamepad left Y is GameCube Y, not the panel");
auto one = Released();
one[0].thumbstick_click = true;
Frame frame = controls.Update(one, open, kDt);
frame = controls.Update(one, open, kDt, kGamepad);
Check(!open && !frame.withheld, "one thumbstick click alone neither opens nor withholds");
frame = controls.Update(Chord(), open, kDt);
frame = controls.Update(Chord(), open, kDt, kGamepad);
Check(open && frame.open && frame.withheld, "clicking both thumbsticks opens the panel");
frame = controls.Update(Chord(), open, kDt);
frame = controls.Update(Chord(), open, kDt, kGamepad);
Check(open, "holding the chord does not toggle again");
frame = controls.Update(Released(), open, kDt);
frame = controls.Update(Released(), open, kDt, kGamepad);
Check(open && frame.withheld, "the panel stays open and keeps the controllers after the chord is released");
frame = controls.Update(Chord(), open, kDt);
frame = controls.Update(Chord(), open, kDt, kGamepad);
Check(!open && !frame.open, "the chord closes the panel again");
Check(frame.withheld, "the closing chord is still withheld from the game");
frame = controls.Update(Released(), open, kDt);
frame = controls.Update(Released(), open, kDt, kGamepad);
Check(!frame.withheld, "the game gets the controllers back once everything is released");
}
void MenuClosesAndItsPressStaysOutOfTheGame() {
Controls controls;
bool open = false;
controls.Update(Chord(), open, kDt);
controls.Update(Released(), open, kDt);
controls.Update(LeftY(), open, kDt, kWiiRemote);
controls.Update(Released(), open, kDt, kWiiRemote);
auto menu = Released();
menu[0].menu = true;
Frame frame = controls.Update(menu, open, kDt);
Frame frame = controls.Update(menu, open, kDt, kWiiRemote);
Check(!open, "the left menu button closes an open panel");
frame = controls.Update(menu, open, kDt);
Check(frame.withheld, "HOME held across the close does not reach the game");
frame = controls.Update(menu, open, kDt, kWiiRemote);
Check(frame.withheld, "+ held across the close does not reach the game");
Check(!open, "a held menu button does not reopen the panel");
frame = controls.Update(Released(), open, kDt);
frame = controls.Update(Released(), open, kDt, kWiiRemote);
Check(!frame.withheld, "released, the controllers go back to the game");
frame = controls.Update(menu, open, kDt);
frame = controls.Update(menu, open, kDt, kWiiRemote);
Check(!open && !frame.withheld, "with the panel closed the menu button is the game's");
}
@@ -93,49 +129,49 @@ void SelectWaitsForAReleaseAndFollowsTheTrigger() {
// Opened from the settings bar while a trigger is held for the game.
auto trigger = Released();
trigger[1].trigger = 1.0f;
controls.Update(trigger, open, kDt);
controls.Update(trigger, open, kDt, kWiiRemote);
open = true;
Frame frame = controls.Update(trigger, open, kDt);
Frame frame = controls.Update(trigger, open, kDt, kWiiRemote);
Check(frame.open && !frame.select, "a trigger held from before the panel opened does not click");
frame = controls.Update(Released(), open, kDt);
frame = controls.Update(Released(), open, kDt, kWiiRemote);
Check(!frame.select, "nothing held, nothing selected");
frame = controls.Update(trigger, open, kDt);
frame = controls.Update(trigger, open, kDt, kWiiRemote);
Check(frame.select && frame.pointing_hand == 1, "a fresh right trigger selects and points with the right hand");
auto left = Released();
left[0].trigger = 0.9f;
controls.Update(Released(), open, kDt);
frame = controls.Update(left, open, kDt);
controls.Update(Released(), open, kDt, kWiiRemote);
frame = controls.Update(left, open, kDt, kWiiRemote);
Check(frame.select && frame.pointing_hand == 0, "pulling the left trigger hands the pointer to the left hand");
auto button = Released();
button[1].primary = true;
controls.Update(Released(), open, kDt);
frame = controls.Update(button, open, kDt);
controls.Update(Released(), open, kDt, kWiiRemote);
frame = controls.Update(button, open, kDt, kWiiRemote);
Check(frame.select && frame.pointing_hand == 0, "A selects without moving the pointer to another hand");
}
void ThumbstickScrolls() {
Controls controls;
bool open = true;
controls.Update(Released(), open, kDt);
controls.Update(Released(), open, kDt, kWiiRemote);
auto small = Released();
small[1].stick_y = 0.2f;
Check(controls.Update(small, open, kDt).wheel == 0.0f, "a resting thumbstick does not scroll");
Check(controls.Update(small, open, kDt, kWiiRemote).wheel == 0.0f, "a resting thumbstick does not scroll");
auto up = Released();
up[1].stick_y = 1.0f;
CheckNear(controls.Update(up, open, 0.5f).wheel, kScrollStepsPerSecond * 0.1f,
CheckNear(controls.Update(up, open, 0.5f, kWiiRemote).wheel, kScrollStepsPerSecond * 0.1f,
"full deflection scrolls up at the full rate, with a long frame clamped");
auto down = Released();
down[0].stick_y = -1.0f;
down[1].stick_y = 0.3f;
Check(controls.Update(down, open, kDt).wheel < 0.0f, "the more deflected stick decides the direction");
Check(controls.Update(down, open, kDt, kWiiRemote).wheel < 0.0f, "the more deflected stick decides the direction");
bool closed = false;
Controls idle;
Check(idle.Update(up, closed, kDt).wheel == 0.0f, "a closed panel does not scroll");
Check(idle.Update(up, closed, kDt, kWiiRemote).wheel == 0.0f, "a closed panel does not scroll");
}
void HitsMapOntoTheCanvas() {
@@ -175,7 +211,8 @@ void BridgeAccumulatesWheelUntilTaken() {
} // namespace
int main() {
ChordOpensAndClosesOnce();
LeftYOpensAndClosesOnceAsAWiiRemote();
ChordOpensAndClosesOnceAsAGamepad();
MenuClosesAndItsPressStaysOutOfTheGame();
SelectWaitsForAReleaseAndFollowsTheTrigger();
ThumbstickScrolls();
+17 -8
View File
@@ -247,24 +247,33 @@ void TestButtons() {
Check(RemoteButtons(left, right) == kButtonOne, "right stick up is 1");
right.stick_y = -0.9f;
Check(RemoteButtons(left, right) == kButtonTwo, "right stick down is 2");
right.stick_y = 0.3f;
Check(RemoteButtons(left, right) == 0, "a light push is no press");
right.stick_y = 0.0f;
right.stick_x = -0.9f;
Check(RemoteButtons(left, right) == kButtonMinus, "right stick left is -");
Check(RemoteButtons(left, right) == 0, "right stick left is unbound");
right.stick_x = 0.9f;
Check(RemoteButtons(left, right) == kButtonPlus, "right stick right is +");
right.stick_x = 0.3f;
Check(RemoteButtons(left, right) == 0, "a light push is no press");
Check(RemoteButtons(left, right) == 0, "right stick right is unbound");
right = {};
left.primary = true;
left.menu = true;
Check(RemoteButtons(left, right) == (kButtonMinus | kButtonPlus), "left X and menu are - and +");
left = {};
left.squeeze = 0.8f;
left.trigger = 0.7f;
Check(RemoteButtons(left, right) == (kButtonHome | kButtonC | kButtonZ), "left menu, grip, trigger are HOME, C, Z");
// Left X and Y have no Wii button in the profile.
Check(RemoteButtons(left, right) == (kButtonC | kButtonZ), "left grip and trigger are C and Z");
// Left Y is the settings panel's button, and nothing presses HOME.
left = {};
left.primary = true;
left.secondary = true;
Check(RemoteButtons(left, right) == 0, "left X/Y are unbound");
right.secondary = true;
right.thumbstick_click = true;
left.thumbstick_click = true;
Check(RemoteButtons(left, right) == 0, "left Y, right B and the stick clicks are unbound");
left = {};
right = {};
left.menu = true;
Check((RemoteButtons(left, right) & kButtonHome) == 0, "left menu is no longer HOME");
left.stick_x = 1.0f;
left.stick_y = 1.0f;