diff --git a/res/keymap.png b/res/keymap.png
new file mode 100644
index 000000000..0c94d2c8d
Binary files /dev/null and b/res/keymap.png differ
diff --git a/res/keymap.svg b/res/keymap.svg
new file mode 100644
index 000000000..5ec9d5099
--- /dev/null
+++ b/res/keymap.svg
@@ -0,0 +1,136 @@
+
+
+
+
diff --git a/src/gba/gba-input.c b/src/gba/gba-input.c
index 0a41bd9ad..ad70d6168 100644
--- a/src/gba/gba-input.c
+++ b/src/gba/gba-input.c
@@ -109,6 +109,27 @@ void GBAInputBindKey(struct GBAInputMap* map, uint32_t type, int key, enum GBAKe
impl->map[input] = key;
}
+int GBAInputQueryBinding(const struct GBAInputMap* map, uint32_t type, enum GBAKey input) {
+ if (input >= GBA_KEY_MAX) {
+ return 0;
+ }
+
+ size_t m;
+ const struct GBAInputMapImpl* impl = 0;
+ for (m = 0; m < map->numMaps; ++m) {
+ if (map->maps[m].type == type) {
+ impl = &map->maps[m];
+ break;
+ }
+ }
+ if (!impl || !impl->map) {
+ return 0;
+ }
+
+ return impl->map[input];
+}
+
+
void GBAInputMapLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config) {
_loadKey(map, type, config, GBA_KEY_A, "A");
_loadKey(map, type, config, GBA_KEY_B, "B");
diff --git a/src/gba/gba-input.h b/src/gba/gba-input.h
index 2f785540b..f7e7cc51a 100644
--- a/src/gba/gba-input.h
+++ b/src/gba/gba-input.h
@@ -15,6 +15,7 @@ void GBAInputMapDeinit(struct GBAInputMap*);
enum GBAKey GBAInputMapKey(const struct GBAInputMap*, uint32_t type, int key);
void GBAInputBindKey(struct GBAInputMap*, uint32_t type, int key, enum GBAKey input);
+int GBAInputQueryBinding(const struct GBAInputMap*, uint32_t type, enum GBAKey input);
void GBAInputMapLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*);
diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt
index c044c4591..0a0456ffc 100644
--- a/src/platform/qt/CMakeLists.txt
+++ b/src/platform/qt/CMakeLists.txt
@@ -34,8 +34,10 @@ set(SOURCE_FILES
ConfigController.cpp
Display.cpp
GBAApp.cpp
+ GBAKeyEditor.cpp
GameController.cpp
InputController.cpp
+ KeyEditor.cpp
LoadSaveState.cpp
LogView.cpp
SavestateButton.cpp
diff --git a/src/platform/qt/GBAKeyEditor.cpp b/src/platform/qt/GBAKeyEditor.cpp
new file mode 100644
index 000000000..b69f5c8fd
--- /dev/null
+++ b/src/platform/qt/GBAKeyEditor.cpp
@@ -0,0 +1,171 @@
+#include "GBAKeyEditor.h"
+
+#include
+#include
+#include
+#include
+
+#include "InputController.h"
+#include "KeyEditor.h"
+
+extern "C" {
+#include "gba-input.h"
+}
+
+using namespace QGBA;
+
+const qreal GBAKeyEditor::DPAD_CENTER_X = 0.247;
+const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.431;
+const qreal GBAKeyEditor::DPAD_WIDTH = 0.1;
+const qreal GBAKeyEditor::DPAD_HEIGHT = 0.1;
+
+GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, QWidget* parent)
+ : QWidget(parent)
+ , m_background(QString(":/res/keymap.png"))
+{
+ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint);
+ setMinimumSize(300, 300);
+
+ const GBAInputMap* map = controller->map();
+
+ m_keyDU = new KeyEditor(this);
+ m_keyDD = new KeyEditor(this);
+ m_keyDL = new KeyEditor(this);
+ m_keyDR = new KeyEditor(this);
+ m_keySelect = new KeyEditor(this);
+ m_keyStart = new KeyEditor(this);
+ m_keyA = new KeyEditor(this);
+ m_keyB = new KeyEditor(this);
+ m_keyL = new KeyEditor(this);
+ m_keyR = new KeyEditor(this);
+ m_keyDU->setValue(GBAInputQueryBinding(map, type, GBA_KEY_UP));
+ m_keyDD->setValue(GBAInputQueryBinding(map, type, GBA_KEY_DOWN));
+ m_keyDL->setValue(GBAInputQueryBinding(map, type, GBA_KEY_LEFT));
+ m_keyDR->setValue(GBAInputQueryBinding(map, type, GBA_KEY_RIGHT));
+ m_keySelect->setValue(GBAInputQueryBinding(map, type, GBA_KEY_SELECT));
+ m_keyStart->setValue(GBAInputQueryBinding(map, type, GBA_KEY_START));
+ m_keyA->setValue(GBAInputQueryBinding(map, type, GBA_KEY_A));
+ m_keyB->setValue(GBAInputQueryBinding(map, type, GBA_KEY_B));
+ m_keyL->setValue(GBAInputQueryBinding(map, type, GBA_KEY_L));
+ m_keyR->setValue(GBAInputQueryBinding(map, type, GBA_KEY_R));
+
+ connect(m_keyDU, &KeyEditor::valueChanged, [this, type, controller](int key) {
+ controller->bindKey(type, key, GBA_KEY_UP);
+ setNext();
+ });
+
+ connect(m_keyDD, &KeyEditor::valueChanged, [this, type, controller](int key) {
+ controller->bindKey(type, key, GBA_KEY_DOWN);
+ setNext();
+ });
+
+ connect(m_keyDL, &KeyEditor::valueChanged, [this, type, controller](int key) {
+ controller->bindKey(type, key, GBA_KEY_LEFT);
+ setNext();
+ });
+
+ connect(m_keyDR, &KeyEditor::valueChanged, [this, type, controller](int key) {
+ controller->bindKey(type, key, GBA_KEY_RIGHT);
+ setNext();
+ });
+
+ connect(m_keySelect, &KeyEditor::valueChanged, [this, type, controller](int key) {
+ controller->bindKey(type, key, GBA_KEY_SELECT);
+ setNext();
+ });
+
+ connect(m_keyStart, &KeyEditor::valueChanged, [this, type, controller](int key) {
+ controller->bindKey(type, key, GBA_KEY_START);
+ setNext();
+ });
+
+ connect(m_keyA, &KeyEditor::valueChanged, [this, type, controller](int key) {
+ controller->bindKey(type, key, GBA_KEY_A);
+ setNext();
+ });
+
+ connect(m_keyB, &KeyEditor::valueChanged, [this, type, controller](int key) {
+ controller->bindKey(type, key, GBA_KEY_B);
+ setNext();
+ });
+
+ connect(m_keyL, &KeyEditor::valueChanged, [this, type, controller](int key) {
+ controller->bindKey(type, key, GBA_KEY_L);
+ setNext();
+ });
+
+ connect(m_keyR, &KeyEditor::valueChanged, [this, type, controller](int key) {
+ controller->bindKey(type, key, GBA_KEY_R);
+ setNext();
+ });
+
+ m_setAll = new QPushButton(tr("Set all"), this);
+ connect(m_setAll, SIGNAL(pressed()), this, SLOT(setAll()));
+
+ m_keyOrder = QList{
+ m_keyDU,
+ m_keyDR,
+ m_keyDD,
+ m_keyDL,
+ m_keyA,
+ m_keyB,
+ m_keySelect,
+ m_keyStart,
+ m_keyL,
+ m_keyR
+ };
+
+ m_currentKey = m_keyOrder.end();
+
+ QPixmap background(":/res/keymap.png");
+ m_background = background.scaled(QSize(300, 300) * devicePixelRatio(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ m_background.setDevicePixelRatio(devicePixelRatio());
+}
+
+void GBAKeyEditor::setAll() {
+ m_currentKey = m_keyOrder.begin();
+ (*m_currentKey)->setFocus();
+}
+
+void GBAKeyEditor::resizeEvent(QResizeEvent* event) {
+ setLocation(m_setAll, 0.5, 0.2);
+ setLocation(m_keyDU, DPAD_CENTER_X, DPAD_CENTER_Y - DPAD_HEIGHT);
+ setLocation(m_keyDD, DPAD_CENTER_X, DPAD_CENTER_Y + DPAD_HEIGHT);
+ setLocation(m_keyDL, DPAD_CENTER_X - DPAD_WIDTH, DPAD_CENTER_Y);
+ setLocation(m_keyDR, DPAD_CENTER_X + DPAD_WIDTH, DPAD_CENTER_Y);
+ setLocation(m_keySelect, 0.415, 0.93);
+ setLocation(m_keyStart, 0.585, 0.93);
+ setLocation(m_keyA, 0.826, 0.451);
+ setLocation(m_keyB, 0.667, 0.490);
+ setLocation(m_keyL, 0.1, 0.1);
+ setLocation(m_keyR, 0.9, 0.1);
+}
+
+void GBAKeyEditor::paintEvent(QPaintEvent* event) {
+ QPainter painter(this);
+ painter.drawPixmap(0, 0, m_background);
+}
+
+void GBAKeyEditor::setNext() {
+ if (m_currentKey == m_keyOrder.end()) {
+ return;
+ }
+
+ if (!(*m_currentKey)->hasFocus()) {
+ m_currentKey = m_keyOrder.end();
+ }
+
+ ++m_currentKey;
+ if (m_currentKey != m_keyOrder.end()) {
+ (*m_currentKey)->setFocus();
+ } else {
+ (*(m_currentKey - 1))->clearFocus();
+ }
+}
+
+void GBAKeyEditor::setLocation(QWidget* widget, qreal x, qreal y) {
+ QSize s = size();
+ QSize hint = widget->sizeHint();
+ widget->setGeometry(s.width() * x - hint.width() / 2.0, s.height() * y - hint.height() / 2.0, hint.width(), hint.height());
+}
diff --git a/src/platform/qt/GBAKeyEditor.h b/src/platform/qt/GBAKeyEditor.h
new file mode 100644
index 000000000..1f03a9674
--- /dev/null
+++ b/src/platform/qt/GBAKeyEditor.h
@@ -0,0 +1,59 @@
+#ifndef QGBA_GBA_KEY_EDITOR
+#define QGBA_GBA_KEY_EDITOR
+
+#include
+#include
+#include
+
+class QPushButton;
+
+namespace QGBA {
+
+class InputController;
+class KeyEditor;
+
+class GBAKeyEditor : public QWidget {
+Q_OBJECT
+
+public:
+ GBAKeyEditor(InputController* controller, int type, QWidget* parent = nullptr);
+
+public slots:
+ void setAll();
+
+protected:
+ virtual void resizeEvent(QResizeEvent*) override;
+ virtual void paintEvent(QPaintEvent*) override;
+
+private:
+ static const qreal DPAD_CENTER_X;
+ static const qreal DPAD_CENTER_Y;
+ static const qreal DPAD_WIDTH;
+ static const qreal DPAD_HEIGHT;
+
+ void setNext();
+
+ void setLocation(QWidget* widget, qreal x, qreal y);
+
+ QPushButton* m_setAll;
+ KeyEditor* m_keyDU;
+ KeyEditor* m_keyDD;
+ KeyEditor* m_keyDL;
+ KeyEditor* m_keyDR;
+ KeyEditor* m_keySelect;
+ KeyEditor* m_keyStart;
+ KeyEditor* m_keyA;
+ KeyEditor* m_keyB;
+ KeyEditor* m_keyL;
+ KeyEditor* m_keyR;
+ QList m_keyOrder;
+ QList::iterator m_currentKey;
+
+ InputController* m_controller;
+
+ QPixmap m_background;
+};
+
+}
+
+#endif
diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp
index 4470e16e9..da5a1b696 100644
--- a/src/platform/qt/InputController.cpp
+++ b/src/platform/qt/InputController.cpp
@@ -52,6 +52,10 @@ GBAKey InputController::mapKeyboard(int key) const {
return GBAInputMapKey(&m_inputMap, KEYBOARD, key);
}
+void InputController::bindKey(uint32_t type, int key, GBAKey gbaKey) {
+ return GBAInputBindKey(&m_inputMap, type, key, gbaKey);
+}
+
#ifdef BUILD_SDL
int InputController::testSDLEvents() {
SDL_Joystick* joystick = m_sdlEvents.joystick;
diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h
index fb4593341..ea54b88cc 100644
--- a/src/platform/qt/InputController.h
+++ b/src/platform/qt/InputController.h
@@ -25,6 +25,10 @@ public:
GBAKey mapKeyboard(int key) const;
+ void bindKey(uint32_t type, int key, GBAKey);
+
+ const GBAInputMap* map() const { return &m_inputMap; }
+
#ifdef BUILD_SDL
int testSDLEvents();
#endif
diff --git a/src/platform/qt/KeyEditor.cpp b/src/platform/qt/KeyEditor.cpp
new file mode 100644
index 000000000..958ce69e1
--- /dev/null
+++ b/src/platform/qt/KeyEditor.cpp
@@ -0,0 +1,28 @@
+#include "KeyEditor.h"
+
+#include
+
+using namespace QGBA;
+
+KeyEditor::KeyEditor(QWidget* parent)
+ : QLineEdit(parent)
+{
+ setAlignment(Qt::AlignCenter);
+}
+
+void KeyEditor::setValue(int key) {
+ setText(QKeySequence(key).toString(QKeySequence::NativeText));
+ m_key = key;
+ emit valueChanged(key);
+}
+
+QSize KeyEditor::sizeHint() const {
+ QSize hint = QLineEdit::sizeHint();
+ hint.setWidth(40);
+ return hint;
+}
+
+void KeyEditor::keyPressEvent(QKeyEvent* event) {
+ setValue(event->key());
+ event->accept();
+}
diff --git a/src/platform/qt/KeyEditor.h b/src/platform/qt/KeyEditor.h
new file mode 100644
index 000000000..362bcd6ab
--- /dev/null
+++ b/src/platform/qt/KeyEditor.h
@@ -0,0 +1,31 @@
+#ifndef QGBA_KEY_EDITOR
+#define QGBA_KEY_EDITOR
+
+#include
+
+namespace QGBA {
+
+class KeyEditor : public QLineEdit {
+Q_OBJECT
+
+public:
+ KeyEditor(QWidget* parent = nullptr);
+
+ void setValue(int key);
+ int value() const { return m_key; }
+
+ virtual QSize sizeHint() const override;
+
+signals:
+ void valueChanged(int key);
+
+protected:
+ virtual void keyPressEvent(QKeyEvent* event) override;
+
+private:
+ int m_key;
+};
+
+}
+
+#endif
diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp
index e59624d90..e64fb7ec0 100644
--- a/src/platform/qt/Window.cpp
+++ b/src/platform/qt/Window.cpp
@@ -8,6 +8,7 @@
#include "ConfigController.h"
#include "GameController.h"
+#include "GBAKeyEditor.h"
#include "GDBController.h"
#include "GDBWindow.h"
#include "LoadSaveState.h"
@@ -136,6 +137,13 @@ void Window::selectPatch() {
}
}
+void Window::openKeymapWindow() {
+ GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, InputController::KEYBOARD);
+ connect(this, SIGNAL(shutdown()), keyEditor, SLOT(close()));
+ keyEditor->setAttribute(Qt::WA_DeleteOnClose);
+ keyEditor->show();
+}
+
#ifdef USE_FFMPEG
void Window::openVideoWindow() {
if (!m_videoView) {
@@ -395,6 +403,11 @@ void Window::setupMenu(QMenuBar* menubar) {
audioSync->connect([this](const QVariant& value) { m_controller->setAudioSync(value.toBool()); });
m_config->updateOption("audioSync");
+ emulationMenu->addSeparator();
+ QAction* keymap = new QAction(tr("Remap keyboard..."), emulationMenu);
+ connect(keymap, SIGNAL(triggered()), this, SLOT(openKeymapWindow()));
+ emulationMenu->addAction(keymap);
+
QMenu* videoMenu = menubar->addMenu(tr("&Video"));
QMenu* frameMenu = videoMenu->addMenu(tr("Frame size"));
QAction* setSize = new QAction(tr("1x"), videoMenu);
diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h
index a6e7fb350..312ee6f23 100644
--- a/src/platform/qt/Window.h
+++ b/src/platform/qt/Window.h
@@ -50,6 +50,8 @@ public slots:
void loadConfig();
void saveConfig();
+ void openKeymapWindow();
+
#ifdef USE_FFMPEG
void openVideoWindow();
#endif
diff --git a/src/platform/qt/resources.qrc b/src/platform/qt/resources.qrc
index d90ba8844..492193011 100644
--- a/src/platform/qt/resources.qrc
+++ b/src/platform/qt/resources.qrc
@@ -1,5 +1,6 @@
../../../res/mgba-1024.png
+ ../../../res/keymap.png