mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-09-11 20:15:42 +00:00
More keybinding work
Co-Authored-By: Paris Oplopoios <parisoplop@gmail.com>
This commit is contained in:
parent
b18937f985
commit
2d0b22761e
7 changed files with 97 additions and 36 deletions
|
@ -21,7 +21,8 @@ struct InputMappings {
|
||||||
|
|
||||||
void setMapping(Scancode scancode, u32 key) { container[scancode] = key; }
|
void setMapping(Scancode scancode, u32 key) { container[scancode] = key; }
|
||||||
|
|
||||||
void serialize(const std::filesystem::path& path, const std::string& frontend) const {
|
template <typename ScancodeToString>
|
||||||
|
void serialize(const std::filesystem::path& path, const std::string& frontend, ScancodeToString scancodeToString) const {
|
||||||
toml::basic_value<toml::preserve_comments, std::map> data;
|
toml::basic_value<toml::preserve_comments, std::map> data;
|
||||||
|
|
||||||
std::error_code error;
|
std::error_code error;
|
||||||
|
@ -46,18 +47,21 @@ struct InputMappings {
|
||||||
data["Mappings"] = toml::table{};
|
data["Mappings"] = toml::table{};
|
||||||
|
|
||||||
for (const auto& [scancode, key] : container) {
|
for (const auto& [scancode, key] : container) {
|
||||||
if (!data["Mappings"].contains(HID::Keys::keyToName(key))) {
|
const std::string& keyName = HID::Keys::keyToName(key);
|
||||||
data["Mappings"][HID::Keys::keyToName(key)] = toml::array{};
|
if (!data["Mappings"].contains(keyName)) {
|
||||||
|
data["Mappings"][keyName] = toml::array{};
|
||||||
}
|
}
|
||||||
|
data["Mappings"][keyName].push_back(scancodeToString(scancode));
|
||||||
data["Mappings"][HID::Keys::keyToName(key)].push_back(scancodeToName(scancode));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ofstream file(path, std::ios::out);
|
std::ofstream file(path, std::ios::out);
|
||||||
file << data;
|
file << data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<InputMappings> deserialize(const std::filesystem::path& path, const std::string& wantFrontend) {
|
template <typename ScancodeFromString>
|
||||||
|
static std::optional<InputMappings> deserialize(
|
||||||
|
const std::filesystem::path& path, const std::string& wantFrontend, ScancodeFromString stringToScancode
|
||||||
|
) {
|
||||||
toml::basic_value<toml::preserve_comments, std::map> data;
|
toml::basic_value<toml::preserve_comments, std::map> data;
|
||||||
std::error_code error;
|
std::error_code error;
|
||||||
|
|
||||||
|
@ -84,23 +88,24 @@ struct InputMappings {
|
||||||
return std::tolower(a) == std::tolower(b);
|
return std::tolower(a) == std::tolower(b);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (equal) {
|
if (!equal) {
|
||||||
Helpers::warn(
|
Helpers::warn(
|
||||||
"Mappings file %s was created for frontend %s, but we are using frontend %s\n", path.string().c_str(), haveFrontend.c_str(),
|
"Mappings file %s was created for frontend %s, but we are using frontend %s\n", path.string().c_str(), haveFrontend.c_str(),
|
||||||
wantFrontend.c_str()
|
wantFrontend.c_str()
|
||||||
);
|
);
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
} catch (const std::exception& ex) {
|
} catch (const std::exception& ex) {
|
||||||
Helpers::warn("Exception trying to parse config file. Exception: %s\n", ex.what());
|
Helpers::warn("Exception trying to parse config file. Exception: %s\n", ex.what());
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& mappingsTable = toml::find_or<toml::table>(data, "Mappings", toml::table{});
|
const auto& mappingsTable = toml::find_or<toml::table>(data, "Mappings", toml::table{});
|
||||||
for (const auto& [key, scancodes] : mappingsTable) {
|
for (const auto& [keyName, scancodes] : mappingsTable) {
|
||||||
for (const auto& scancode : scancodes.as_array()) {
|
for (const auto& scancodeVal : scancodes.as_array()) {
|
||||||
mappings.setMapping(nameToScancode(scancode.as_string()), HID::Keys::nameToKey(key));
|
std::string scancodeStr = scancodeVal.as_string();
|
||||||
|
mappings.setMapping(stringToScancode(scancodeStr), HID::Keys::nameToKey(keyName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,8 +113,6 @@ struct InputMappings {
|
||||||
}
|
}
|
||||||
|
|
||||||
static InputMappings defaultKeyboardMappings();
|
static InputMappings defaultKeyboardMappings();
|
||||||
static std::string scancodeToName(Scancode scancode);
|
|
||||||
static Scancode nameToScancode(const std::string& name);
|
|
||||||
|
|
||||||
auto begin() { return container.begin(); }
|
auto begin() { return container.begin(); }
|
||||||
auto end() { return container.end(); }
|
auto end() { return container.end(); }
|
||||||
|
@ -121,4 +124,4 @@ struct InputMappings {
|
||||||
Container container;
|
Container container;
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string device;
|
std::string device;
|
||||||
};
|
};
|
||||||
|
|
|
@ -43,7 +43,6 @@ class MainWindow : public QMainWindow {
|
||||||
Pause,
|
Pause,
|
||||||
Resume,
|
Resume,
|
||||||
TogglePause,
|
TogglePause,
|
||||||
DumpRomFS,
|
|
||||||
PressKey,
|
PressKey,
|
||||||
ReleaseKey,
|
ReleaseKey,
|
||||||
SetCirclePadX,
|
SetCirclePadX,
|
||||||
|
@ -134,6 +133,9 @@ class MainWindow : public QMainWindow {
|
||||||
void dispatchMessage(const EmulatorMessage& message);
|
void dispatchMessage(const EmulatorMessage& message);
|
||||||
void loadTranslation();
|
void loadTranslation();
|
||||||
|
|
||||||
|
void loadKeybindings();
|
||||||
|
void saveKeybindings();
|
||||||
|
|
||||||
// Tracks whether we are using an OpenGL-backed renderer or a Vulkan-backed renderer
|
// Tracks whether we are using an OpenGL-backed renderer or a Vulkan-backed renderer
|
||||||
bool usingGL = false;
|
bool usingGL = false;
|
||||||
bool usingVk = false;
|
bool usingVk = false;
|
||||||
|
@ -145,6 +147,9 @@ class MainWindow : public QMainWindow {
|
||||||
bool keyboardAnalogX = false;
|
bool keyboardAnalogX = false;
|
||||||
bool keyboardAnalogY = false;
|
bool keyboardAnalogY = false;
|
||||||
|
|
||||||
|
// Tracks if keybindings changed, in which case we should update the keybindings file when closing the emulator
|
||||||
|
bool keybindingsChanged = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MainWindow(QApplication* app, QWidget* parent = nullptr);
|
MainWindow(QApplication* app, QWidget* parent = nullptr);
|
||||||
~MainWindow();
|
~MainWindow();
|
||||||
|
|
|
@ -3,31 +3,60 @@
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
#include <QPushButton>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include "input_mappings.hpp"
|
||||||
#include "services/hid.hpp"
|
#include "services/hid.hpp"
|
||||||
|
|
||||||
InputWindow::InputWindow(QWidget* parent) : QDialog(parent) {
|
InputWindow::InputWindow(QWidget* parent) : QDialog(parent) {
|
||||||
auto mainLayout = new QVBoxLayout(this);
|
auto mainLayout = new QVBoxLayout(this);
|
||||||
|
|
||||||
QStringList actions = {
|
QStringList actions = {
|
||||||
"A", "B", "X", "Y", "L", "R", "ZL", "ZR", "Start", "Select", "D-Pad Up", "D-Pad Down", "D-Pad Left", "D-Pad Right",
|
"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) {
|
for (const QString& action : actions) {
|
||||||
auto row = new QHBoxLayout;
|
auto row = new QHBoxLayout();
|
||||||
row->addWidget(new QLabel(action));
|
row->addWidget(new QLabel(action));
|
||||||
|
|
||||||
auto button = new QPushButton("Not set");
|
auto button = new QPushButton(tr("Not set"));
|
||||||
buttonMap[action] = button;
|
buttonMap[action] = button;
|
||||||
keyMappings[action] = QKeySequence();
|
keyMappings[action] = QKeySequence();
|
||||||
|
|
||||||
connect(button, &QPushButton::clicked, this, [=]() { startKeyCapture(action); });
|
connect(button, &QPushButton::clicked, this, [=, this]() { startKeyCapture(action); });
|
||||||
|
|
||||||
row->addWidget(button);
|
row->addWidget(button);
|
||||||
mainLayout->addLayout(row);
|
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);
|
installEventFilter(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,10 +66,20 @@ void InputWindow::startKeyCapture(const QString& action) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InputWindow::eventFilter(QObject* obj, QEvent* event) {
|
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) {
|
if (!waitingForAction.isEmpty() && event->type() == QEvent::KeyPress) {
|
||||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||||
QKeySequence key(keyEvent->key());
|
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;
|
keyMappings[waitingForAction] = key;
|
||||||
buttonMap[waitingForAction]->setText(key.toString());
|
buttonMap[waitingForAction]->setText(key.toString());
|
||||||
|
|
||||||
|
@ -60,7 +99,7 @@ void InputWindow::loadFromMappings(const InputMappings& mappings) {
|
||||||
|
|
||||||
for (const auto& [scancode, mappedKey] : mappings) {
|
for (const auto& [scancode, mappedKey] : mappings) {
|
||||||
if (mappedKey == key) {
|
if (mappedKey == key) {
|
||||||
QKeySequence qkey(QString::fromStdString(InputMappings::scancodeToName(scancode)));
|
QKeySequence qkey(scancode);
|
||||||
keyMappings[action] = qkey;
|
keyMappings[action] = qkey;
|
||||||
buttonMap[action]->setText(qkey.toString());
|
buttonMap[action]->setText(qkey.toString());
|
||||||
break;
|
break;
|
||||||
|
@ -77,7 +116,7 @@ void InputWindow::applyToMappings(InputMappings& mappings) const {
|
||||||
const QKeySequence& qkey = keyMappings[action];
|
const QKeySequence& qkey = keyMappings[action];
|
||||||
|
|
||||||
if (!qkey.isEmpty()) {
|
if (!qkey.isEmpty()) {
|
||||||
InputMappings::Scancode scancode = InputMappings::nameToScancode(qkey.toString().toStdString());
|
InputMappings::Scancode scancode = qkey[0].key();
|
||||||
u32 key = HID::Keys::nameToKey(action.toStdString());
|
u32 key = HID::Keys::nameToKey(action.toStdString());
|
||||||
|
|
||||||
if (key != HID::Keys::Null) {
|
if (key != HID::Keys::Null) {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
|
||||||
#include "panda_qt/main_window.hpp"
|
#include "panda_qt/main_window.hpp"
|
||||||
#include "panda_qt/screen.hpp"
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
#include "services/dsp.hpp"
|
#include "services/dsp.hpp"
|
||||||
#include "version.hpp"
|
#include "version.hpp"
|
||||||
|
|
||||||
MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent), keyboardMappings(InputMappings::defaultKeyboardMappings()) {
|
MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent), keyboardMappings(InputMappings()) {
|
||||||
emu = new Emulator();
|
emu = new Emulator();
|
||||||
|
|
||||||
loadTranslation();
|
loadTranslation();
|
||||||
|
@ -115,9 +115,10 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
||||||
[&]() { return this; }, emu->getConfig(), this
|
[&]() { return this; }, emu->getConfig(), this
|
||||||
);
|
);
|
||||||
|
|
||||||
configWindow->getInputWindow()->loadFromMappings(keyboardMappings);
|
loadKeybindings();
|
||||||
|
|
||||||
connect(configWindow->getInputWindow(), &InputWindow::mappingsChanged, this, [&]() {
|
connect(configWindow->getInputWindow(), &InputWindow::mappingsChanged, this, [&]() {
|
||||||
|
keybindingsChanged = true;
|
||||||
configWindow->getInputWindow()->applyToMappings(keyboardMappings);
|
configWindow->getInputWindow()->applyToMappings(keyboardMappings);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -280,6 +281,10 @@ void MainWindow::closeEvent(QCloseEvent* event) {
|
||||||
|
|
||||||
// Cleanup when the main window closes
|
// Cleanup when the main window closes
|
||||||
MainWindow::~MainWindow() {
|
MainWindow::~MainWindow() {
|
||||||
|
if (keybindingsChanged) {
|
||||||
|
saveKeybindings();
|
||||||
|
}
|
||||||
|
|
||||||
delete emu;
|
delete emu;
|
||||||
delete menuBar;
|
delete menuBar;
|
||||||
delete aboutWindow;
|
delete aboutWindow;
|
||||||
|
@ -772,3 +777,23 @@ void MainWindow::setupControllerSensors(SDL_GameController* controller) {
|
||||||
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
|
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::loadKeybindings() {
|
||||||
|
auto keyMappings = InputMappings::deserialize(emu->getAppDataRoot() / "controls_qt.toml", "Qt", [](const std::string& name) {
|
||||||
|
return InputMappings::Scancode(QKeySequence(QString::fromStdString(name))[0].key());
|
||||||
|
});
|
||||||
|
|
||||||
|
if (keyMappings) {
|
||||||
|
keyboardMappings = *keyMappings;
|
||||||
|
} else {
|
||||||
|
keyboardMappings = InputMappings::defaultKeyboardMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
configWindow->getInputWindow()->loadFromMappings(keyboardMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::saveKeybindings() {
|
||||||
|
keyboardMappings.serialize(emu->getAppDataRoot() / "controls_qt.toml", "Qt", [](InputMappings::Scancode scancode) {
|
||||||
|
return QKeySequence(scancode).toString().toStdString();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include <QKeySequence>
|
#include <QKeySequence>
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "input_mappings.hpp"
|
#include "input_mappings.hpp"
|
||||||
|
|
||||||
|
@ -26,8 +25,4 @@ InputMappings InputMappings::defaultKeyboardMappings() {
|
||||||
mappings.setMapping(Qt::Key_A, HID::Keys::CirclePadLeft);
|
mappings.setMapping(Qt::Key_A, HID::Keys::CirclePadLeft);
|
||||||
|
|
||||||
return mappings;
|
return mappings;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string InputMappings::scancodeToName(Scancode scancode) { return QKeySequence(scancode).toString().toStdString(); }
|
|
||||||
|
|
||||||
InputMappings::Scancode InputMappings::nameToScancode(const std::string& name) { return QKeySequence(QString::fromStdString(name))[0].key(); }
|
|
|
@ -1,7 +1,5 @@
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "input_mappings.hpp"
|
#include "input_mappings.hpp"
|
||||||
|
|
||||||
InputMappings InputMappings::defaultKeyboardMappings() {
|
InputMappings InputMappings::defaultKeyboardMappings() {
|
||||||
|
@ -26,7 +24,4 @@ InputMappings InputMappings::defaultKeyboardMappings() {
|
||||||
mappings.setMapping(SDLK_a, HID::Keys::CirclePadLeft);
|
mappings.setMapping(SDLK_a, HID::Keys::CirclePadLeft);
|
||||||
|
|
||||||
return mappings;
|
return mappings;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string InputMappings::scancodeToName(Scancode scancode) { return SDL_GetKeyName(scancode); }
|
|
||||||
InputMappings::Scancode InputMappings::nameToScancode(const std::string& name) { return SDL_GetKeyFromName(name.c_str()); }
|
|
Loading…
Add table
Add a link
Reference in a new issue