[HID/UI] Ensure guide button closes dialogs / button presses consumed

Should prevent button close actions to carry into the game when dialogs
are closed with controller inputs
This commit is contained in:
Herman S.
2025-12-30 12:34:52 +09:00
parent e837c36fd3
commit 9da9a735fc
5 changed files with 49 additions and 4 deletions
+27 -2
View File
@@ -127,7 +127,20 @@ X_RESULT InputSystem::GetState(uint32_t user_index, uint32_t flags,
return X_ERROR_SUCCESS;
}
return GetStateForUI(user_index, flags, out_state);
X_RESULT result = GetStateForUI(user_index, flags, out_state);
// Handle consumed buttons - these are buttons that were held when a UI dialog
// closed. We mask them from the game until they are released.
if (result == X_ERROR_SUCCESS && user_index < XUserMaxUserCount &&
consumed_buttons_[user_index] != 0) {
uint16_t buttons = out_state->gamepad.buttons;
// Update consumed_buttons_ to only include buttons still being held
consumed_buttons_[user_index] &= buttons;
// Mask consumed buttons from the output
out_state->gamepad.buttons = buttons & ~consumed_buttons_[user_index];
}
return result;
}
X_RESULT InputSystem::GetStateForUI(uint32_t user_index, uint32_t flags,
@@ -157,7 +170,19 @@ X_RESULT InputSystem::GetStateForUI(uint32_t user_index, uint32_t flags,
void InputSystem::AddUIInputBlocker() { ui_input_blockers_.fetch_add(1); }
void InputSystem::RemoveUIInputBlocker() { ui_input_blockers_.fetch_sub(1); }
void InputSystem::RemoveUIInputBlocker() {
// Before removing the blocker, capture any currently pressed buttons.
// These will be masked from game input until they are released, preventing
// the button press that closed the UI from carrying over into the game.
X_INPUT_STATE state;
for (uint32_t user_index = 0; user_index < XUserMaxUserCount; user_index++) {
if (GetStateForUI(user_index, 1, &state) == X_ERROR_SUCCESS) {
consumed_buttons_[user_index] |= state.gamepad.buttons;
}
}
ui_input_blockers_.fetch_sub(1);
}
X_RESULT InputSystem::SetState(uint32_t user_index,
X_INPUT_VIBRATION* vibration) {
+6
View File
@@ -10,6 +10,7 @@
#ifndef XENIA_HID_INPUT_SYSTEM_H_
#define XENIA_HID_INPUT_SYSTEM_H_
#include <array>
#include <atomic>
#include <bitset>
#include <memory>
@@ -96,6 +97,11 @@ class InputSystem {
// Reference count for UI elements blocking game input
std::atomic<int> ui_input_blockers_{0};
// Buttons that should be masked from game input until released (per slot).
// This prevents button presses used to close UI dialogs from being
// seen by the game immediately after the dialog closes.
std::array<uint16_t, XUserMaxUserCount> consumed_buttons_{};
};
} // namespace hid
+12 -2
View File
@@ -46,6 +46,16 @@ ContextMenuWidgetQt::ContextMenuWidgetQt(QWidget* parent,
// Block input to the game while this menu is open
input_system_->AddUIInputBlocker();
// Initialize prev_buttons_ to current state so held buttons aren't
// detected as "just pressed" when the menu opens
hid::X_INPUT_STATE state;
for (uint32_t user_index = 0; user_index < 4; user_index++) {
if (input_system_->GetStateForUI(user_index, 1, &state) == 0) {
prev_buttons_ = state.gamepad.buttons;
break;
}
}
poll_timer_ = new QTimer(this);
connect(poll_timer_, &QTimer::timeout, this,
&ContextMenuWidgetQt::PollGamepad);
@@ -257,8 +267,8 @@ void ContextMenuWidgetQt::PollGamepad() {
ActivateFocusedItem();
}
// B button to close
if (pressed & 0x2000) {
// B button, Back button, or Guide button to close
if (pressed & (0x2000 | 0x0020 | 0x0400)) {
close();
}
+3
View File
@@ -115,6 +115,9 @@ void GamepadDialog::PollGamepad() {
if (pressed & 0x0020) { // Back button
OnGamepadBack();
}
if (pressed & 0x0400) { // Guide button
OnGamepadGuide();
}
// D-pad navigation with repeat
// First press (edge detection)
+1
View File
@@ -44,6 +44,7 @@ class GamepadDialog : public QDialog {
virtual void OnGamepadButtonY() {}
virtual void OnGamepadStart() { AcceptFocusedButton(); }
virtual void OnGamepadBack() { reject(); }
virtual void OnGamepadGuide() { reject(); }
// Override to customize which widgets are focusable
virtual bool IsWidgetGamepadFocusable(QWidget* widget) const;