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