Qt: Allow rebinding keyboard controls (#779)

* Initial input UI draft

Co-Authored-By: Paris Oplopoios <parisoplop@gmail.com>

* More keybinding work

Co-Authored-By: Paris Oplopoios <parisoplop@gmail.com>

* Nit

Co-Authored-By: Paris Oplopoios <parisoplop@gmail.com>

* More nits

Co-Authored-By: Paris Oplopoios <parisoplop@gmail.com>

---------

Co-authored-by: Paris Oplopoios <parisoplop@gmail.com>
This commit is contained in:
wheremyfoodat
2025-07-18 04:08:08 +03:00
committed by GitHub
parent 3cae1bd256
commit 81f37e1699
14 changed files with 385 additions and 14 deletions

View File

@@ -0,0 +1,127 @@
#include "panda_qt/input_window.hpp"
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include "input_mappings.hpp"
#include "services/hid.hpp"
InputWindow::InputWindow(QWidget* parent) : QDialog(parent) {
auto mainLayout = new QVBoxLayout(this);
QStringList actions = {
"A",
"B",
"X",
"Y",
"L",
"R",
"ZL",
"ZR",
"Start",
"Select",
"D-Pad Up",
"D-Pad Down",
"D-Pad Left",
"D-Pad Right",
"CirclePad Up",
"CirclePad Down",
"CirclePad Left",
"CirclePad Right",
};
for (const QString& action : actions) {
auto row = new QHBoxLayout();
row->addWidget(new QLabel(action));
auto button = new QPushButton(tr("Not set"));
buttonMap[action] = button;
keyMappings[action] = QKeySequence();
connect(button, &QPushButton::clicked, this, [=, this]() { startKeyCapture(action); });
row->addWidget(button);
mainLayout->addLayout(row);
}
auto resetButton = new QPushButton(tr("Reset Defaults"));
connect(resetButton, &QPushButton::pressed, this, [&]() {
// Restore the keymappings to the default ones for Qt
auto defaultMappings = InputMappings::defaultKeyboardMappings();
loadFromMappings(defaultMappings);
emit mappingsChanged();
});
mainLayout->addWidget(resetButton);
installEventFilter(this);
}
void InputWindow::startKeyCapture(const QString& action) {
waitingForAction = action;
grabKeyboard();
}
bool InputWindow::eventFilter(QObject* obj, QEvent* event) {
// If we're waiting for a button to be inputted, handle the keypress
if (!waitingForAction.isEmpty() && event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
QKeySequence key(keyEvent->key());
// If this key is already bound to something else, unbind it
for (auto it = keyMappings.begin(); it != keyMappings.end(); ++it) {
if (it.key() != waitingForAction && it.value() == key) {
it.value() = QKeySequence();
buttonMap[it.key()]->setText(tr("Not set"));
break;
}
}
keyMappings[waitingForAction] = key;
buttonMap[waitingForAction]->setText(key.toString());
releaseKeyboard();
waitingForAction.clear();
emit mappingsChanged();
return true;
}
return false;
}
void InputWindow::loadFromMappings(const InputMappings& mappings) {
for (const auto& action : buttonMap.keys()) {
u32 key = HID::Keys::nameToKey(action.toStdString());
for (const auto& [scancode, mappedKey] : mappings) {
if (mappedKey == key) {
QKeySequence qkey(scancode);
keyMappings[action] = qkey;
buttonMap[action]->setText(qkey.toString());
break;
}
}
}
}
void InputWindow::applyToMappings(InputMappings& mappings) const {
// Clear existing keyboard mappings before mapping the buttons
mappings = InputMappings();
for (const auto& action : keyMappings.keys()) {
const QKeySequence& qkey = keyMappings[action];
if (!qkey.isEmpty()) {
InputMappings::Scancode scancode = qkey[0].key();
u32 key = HID::Keys::nameToKey(action.toStdString());
if (key != HID::Keys::Null) {
mappings.setMapping(scancode, key);
}
}
}
}