Qt: Revamp all input to go through InputController

This commit is contained in:
Vicki Pfau 2017-03-26 23:32:28 -07:00
parent 5fef1b72e4
commit 34dc2ae6f5
16 changed files with 513 additions and 825 deletions

View File

@ -75,7 +75,6 @@ set(SOURCE_FILES
DisplayGL.cpp
DisplayQt.cpp
GBAApp.cpp
GBAKeyEditor.cpp
GIFView.cpp
GameController.cpp
GamepadAxisEvent.cpp

View File

@ -1,409 +0,0 @@
/* Copyright (c) 2013-2014 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "GBAKeyEditor.h"
#include <QComboBox>
#include <QHBoxLayout>
#include <QPaintEvent>
#include <QPainter>
#include <QPushButton>
#include <QVBoxLayout>
#include "InputController.h"
#include "KeyEditor.h"
#ifdef BUILD_SDL
#include "platform/sdl/sdl-events.h"
#endif
using namespace QGBA;
const qreal GBAKeyEditor::DPAD_CENTER_X = 0.247;
const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.432;
const qreal GBAKeyEditor::DPAD_WIDTH = 0.12;
const qreal GBAKeyEditor::DPAD_HEIGHT = 0.12;
GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& profile, QWidget* parent)
: QWidget(parent)
, m_profileSelect(nullptr)
, m_clear(nullptr)
, m_type(type)
, m_profile(profile)
, m_controller(controller)
{
setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint);
setMinimumSize(300, 300);
const mInputMap* map = controller->map();
controller->stealFocus(this);
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);
refresh();
#ifdef BUILD_SDL
if (type == SDL_BINDING_BUTTON) {
m_profileSelect = new QComboBox(this);
connect(m_profileSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(selectGamepad(int)));
updateJoysticks();
m_clear = new QWidget(this);
QHBoxLayout* layout = new QHBoxLayout;
m_clear->setLayout(layout);
layout->setSpacing(6);
QPushButton* clearButton = new QPushButton(tr("Clear Button"));
layout->addWidget(clearButton);
connect(clearButton, &QAbstractButton::pressed, [this]() {
if (!findFocus()) {
return;
}
bool signalsBlocked = (*m_currentKey)->blockSignals(true);
(*m_currentKey)->clearButton();
(*m_currentKey)->clearHat();
(*m_currentKey)->blockSignals(signalsBlocked);
});
QPushButton* clearAxis = new QPushButton(tr("Clear Analog"));
layout->addWidget(clearAxis);
connect(clearAxis, &QAbstractButton::pressed, [this]() {
if (!findFocus()) {
return;
}
bool signalsBlocked = (*m_currentKey)->blockSignals(true);
(*m_currentKey)->clearAxis();
(*m_currentKey)->blockSignals(signalsBlocked);
});
QPushButton* updateJoysticksButton = new QPushButton(tr("Refresh"));
layout->addWidget(updateJoysticksButton);
connect(updateJoysticksButton, SIGNAL(pressed()), this, SLOT(updateJoysticks()));
}
#endif
m_buttons = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout;
m_buttons->setLayout(layout);
QPushButton* setAll = new QPushButton(tr("Set all"));
connect(setAll, SIGNAL(pressed()), this, SLOT(setAll()));
layout->addWidget(setAll);
layout->setSpacing(6);
m_keyOrder = QList<KeyEditor*>{
m_keyDU,
m_keyDR,
m_keyDD,
m_keyDL,
m_keyA,
m_keyB,
m_keySelect,
m_keyStart,
m_keyL,
m_keyR
};
for (auto& key : m_keyOrder) {
connect(key, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
connect(key, SIGNAL(axisChanged(int, int)), this, SLOT(setNext()));
connect(key, SIGNAL(hatChanged(int, int)), this, SLOT(setNext()));
key->installEventFilter(this);
}
m_currentKey = m_keyOrder.end();
m_background.load(":/res/keymap.qpic");
setAll->setFocus();
}
GBAKeyEditor::~GBAKeyEditor() {
m_controller->releaseFocus(this);
}
void GBAKeyEditor::setAll() {
m_currentKey = m_keyOrder.begin();
(*m_currentKey)->setFocus();
}
void GBAKeyEditor::resizeEvent(QResizeEvent* event) {
setLocation(m_buttons, 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.475);
setLocation(m_keyB, 0.667, 0.514);
setLocation(m_keyL, 0.1, 0.1);
setLocation(m_keyR, 0.9, 0.1);
if (m_profileSelect) {
setLocation(m_profileSelect, 0.5, 0.67);
}
if (m_clear) {
setLocation(m_clear, 0.5, 0.77);
}
}
void GBAKeyEditor::paintEvent(QPaintEvent* event) {
QPainter painter(this);
painter.scale(width() / 480.0, height() / 480.0);
painter.drawPicture(0, 0, m_background);
}
void GBAKeyEditor::closeEvent(QCloseEvent*) {
m_controller->releaseFocus(this);
}
bool GBAKeyEditor::event(QEvent* event) {
QEvent::Type type = event->type();
if (type == QEvent::WindowActivate || type == QEvent::Show) {
m_controller->stealFocus(this);
} else if (type == QEvent::WindowDeactivate || type == QEvent::Hide) {
m_controller->releaseFocus(this);
}
return QWidget::event(event);
}
bool GBAKeyEditor::eventFilter(QObject* obj, QEvent* event) {
if (event->type() != QEvent::FocusIn) {
return false;
}
findFocus(static_cast<KeyEditor*>(obj));
return true;
}
void GBAKeyEditor::setNext() {
if (m_currentKey == m_keyOrder.end()) {
return;
}
++m_currentKey;
if (m_currentKey != m_keyOrder.end()) {
(*m_currentKey)->setFocus();
} else {
(*(m_currentKey - 1))->clearFocus();
}
}
void GBAKeyEditor::save() {
#ifdef BUILD_SDL
m_controller->unbindAllAxes(m_type);
#endif
bindKey(m_keyDU, GBA_KEY_UP);
bindKey(m_keyDD, GBA_KEY_DOWN);
bindKey(m_keyDL, GBA_KEY_LEFT);
bindKey(m_keyDR, GBA_KEY_RIGHT);
bindKey(m_keySelect, GBA_KEY_SELECT);
bindKey(m_keyStart, GBA_KEY_START);
bindKey(m_keyA, GBA_KEY_A);
bindKey(m_keyB, GBA_KEY_B);
bindKey(m_keyL, GBA_KEY_L);
bindKey(m_keyR, GBA_KEY_R);
m_controller->saveConfiguration(m_type);
#ifdef BUILD_SDL
if (m_profileSelect) {
m_controller->setPreferredGamepad(m_type, m_profileSelect->currentText());
}
#endif
if (!m_profile.isNull()) {
m_controller->saveProfile(m_type, m_profile);
}
}
void GBAKeyEditor::refresh() {
const mInputMap* map = m_controller->map();
lookupBinding(map, m_keyDU, GBA_KEY_UP);
lookupBinding(map, m_keyDD, GBA_KEY_DOWN);
lookupBinding(map, m_keyDL, GBA_KEY_LEFT);
lookupBinding(map, m_keyDR, GBA_KEY_RIGHT);
lookupBinding(map, m_keySelect, GBA_KEY_SELECT);
lookupBinding(map, m_keyStart, GBA_KEY_START);
lookupBinding(map, m_keyA, GBA_KEY_A);
lookupBinding(map, m_keyB, GBA_KEY_B);
lookupBinding(map, m_keyL, GBA_KEY_L);
lookupBinding(map, m_keyR, GBA_KEY_R);
#ifdef BUILD_SDL
lookupAxes(map);
lookupHats(map);
#endif
}
void GBAKeyEditor::lookupBinding(const mInputMap* map, KeyEditor* keyEditor, GBAKey key) {
#ifdef BUILD_SDL
if (m_type == SDL_BINDING_BUTTON) {
int value = mInputQueryBinding(map, m_type, key);
keyEditor->setValueButton(value);
return;
}
#endif
keyEditor->setValueKey(mInputQueryBinding(map, m_type, key));
}
#ifdef BUILD_SDL
void GBAKeyEditor::lookupAxes(const mInputMap* map) {
mInputEnumerateAxes(map, m_type, [](int axis, const mInputAxis* description, void* user) {
GBAKeyEditor* self = static_cast<GBAKeyEditor*>(user);
if (description->highDirection != GBA_KEY_NONE) {
KeyEditor* key = self->keyById(static_cast<enum GBAKey>(description->highDirection));
if (key) {
key->setValueAxis(axis, description->deadHigh);
}
}
if (description->lowDirection != GBA_KEY_NONE) {
KeyEditor* key = self->keyById(static_cast<enum GBAKey>(description->lowDirection));
if (key) {
key->setValueAxis(axis, description->deadLow);
}
}
}, this);
}
void GBAKeyEditor::lookupHats(const mInputMap* map) {
struct mInputHatBindings bindings;
int i = 0;
while (mInputQueryHat(map, m_type, i, &bindings)) {
if (bindings.up >= 0) {
KeyEditor* key = keyById(static_cast<enum GBAKey>(bindings.up));
if (key) {
key->setValueHat(i, GamepadHatEvent::UP);
}
}
if (bindings.right >= 0) {
KeyEditor* key = keyById(static_cast<enum GBAKey>(bindings.right));
if (key) {
key->setValueHat(i, GamepadHatEvent::RIGHT);
}
}
if (bindings.down >= 0) {
KeyEditor* key = keyById(static_cast<enum GBAKey>(bindings.down));
if (key) {
key->setValueHat(i, GamepadHatEvent::DOWN);
}
}
if (bindings.left >= 0) {
KeyEditor* key = keyById(static_cast<enum GBAKey>(bindings.left));
if (key) {
key->setValueHat(i, GamepadHatEvent::LEFT);
}
}
++i;
}
}
#endif
void GBAKeyEditor::bindKey(const KeyEditor* keyEditor, GBAKey key) {
#ifdef BUILD_SDL
if (m_type == SDL_BINDING_BUTTON && keyEditor->axis() >= 0) {
m_controller->bindAxis(m_type, keyEditor->axis(), keyEditor->direction(), key);
}
if (m_type == SDL_BINDING_BUTTON && keyEditor->hat() >= 0) {
m_controller->bindHat(m_type, keyEditor->hat(), keyEditor->hatDirection(), key);
}
#endif
m_controller->bindKey(m_type, keyEditor->value(), key);
}
bool GBAKeyEditor::findFocus(KeyEditor* needle) {
if (m_currentKey != m_keyOrder.end() && (*m_currentKey)->hasFocus()) {
return true;
}
for (auto key = m_keyOrder.begin(); key != m_keyOrder.end(); ++key) {
if ((*key)->hasFocus() || needle == *key) {
m_currentKey = key;
return true;
}
}
return m_currentKey != m_keyOrder.end();
}
#ifdef BUILD_SDL
void GBAKeyEditor::setAxisValue(int axis, int32_t value) {
if (!findFocus()) {
return;
}
KeyEditor* focused = *m_currentKey;
focused->setValueAxis(axis, value);
}
void GBAKeyEditor::selectGamepad(int index) {
m_controller->setGamepad(m_type, index);
m_profile = m_profileSelect->currentText();
m_controller->loadProfile(m_type, m_profile);
refresh();
}
#endif
KeyEditor* GBAKeyEditor::keyById(GBAKey key) {
switch (key) {
case GBA_KEY_UP:
return m_keyDU;
case GBA_KEY_DOWN:
return m_keyDD;
case GBA_KEY_LEFT:
return m_keyDL;
case GBA_KEY_RIGHT:
return m_keyDR;
case GBA_KEY_A:
return m_keyA;
case GBA_KEY_B:
return m_keyB;
case GBA_KEY_L:
return m_keyL;
case GBA_KEY_R:
return m_keyR;
case GBA_KEY_SELECT:
return m_keySelect;
case GBA_KEY_START:
return m_keyStart;
default:
break;
}
return nullptr;
}
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());
}
#ifdef BUILD_SDL
void GBAKeyEditor::updateJoysticks() {
m_controller->updateJoysticks();
m_controller->recalibrateAxes();
m_profileSelect->clear();
m_profileSelect->addItems(m_controller->connectedGamepads(m_type));
int activeGamepad = m_controller->gamepad(m_type);
selectGamepad(activeGamepad);
if (activeGamepad > 0) {
m_profileSelect->setCurrentIndex(activeGamepad);
}
lookupAxes(m_controller->map());
lookupHats(m_controller->map());
}
#endif

View File

@ -1,96 +0,0 @@
/* Copyright (c) 2013-2015 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_GBA_KEY_EDITOR
#define QGBA_GBA_KEY_EDITOR
#include <QList>
#include <QPicture>
#include <QSet>
#include <QWidget>
#include <mgba/internal/gba/input.h>
class QComboBox;
class QTimer;
namespace QGBA {
class InputController;
class KeyEditor;
class GBAKeyEditor : public QWidget {
Q_OBJECT
public:
GBAKeyEditor(InputController* controller, int type, const QString& profile = QString(), QWidget* parent = nullptr);
virtual ~GBAKeyEditor();
public slots:
void setAll();
void save();
protected:
virtual void resizeEvent(QResizeEvent*) override;
virtual void paintEvent(QPaintEvent*) override;
virtual bool event(QEvent*) override;
virtual void closeEvent(QCloseEvent*) override;
virtual bool eventFilter(QObject* obj, QEvent* event) override;
private slots:
void setNext();
void refresh();
#ifdef BUILD_SDL
void setAxisValue(int axis, int32_t value);
void selectGamepad(int index);
void updateJoysticks();
#endif
private:
static const qreal DPAD_CENTER_X;
static const qreal DPAD_CENTER_Y;
static const qreal DPAD_WIDTH;
static const qreal DPAD_HEIGHT;
void setLocation(QWidget* widget, qreal x, qreal y);
void lookupBinding(const mInputMap*, KeyEditor*, GBAKey);
void bindKey(const KeyEditor*, GBAKey);
bool findFocus(KeyEditor* needle = nullptr);
#ifdef BUILD_SDL
void lookupAxes(const mInputMap*);
void lookupHats(const mInputMap*);
#endif
KeyEditor* keyById(GBAKey);
QComboBox* m_profileSelect;
QWidget* m_clear;
QWidget* m_buttons;
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<KeyEditor*> m_keyOrder;
QList<KeyEditor*>::iterator m_currentKey;
uint32_t m_type;
QString m_profile;
InputController* m_controller;
QPicture m_background;
};
}
#endif

View File

@ -27,6 +27,7 @@ GamepadAxisEvent::GamepadAxisEvent(int axis, Direction direction, bool isNew, in
QEvent::Type GamepadAxisEvent::Type() {
if (s_type == None) {
qRegisterMetaType<Direction>("GamepadAxisEvent::Direction");
s_type = static_cast<enum Type>(registerEventType());
}
return s_type;

View File

@ -27,6 +27,7 @@ GamepadHatEvent::GamepadHatEvent(QEvent::Type pressType, int hatId, Direction di
QEvent::Type GamepadHatEvent::Down() {
if (s_downType == None) {
qRegisterMetaType<Direction>("GamepadHatEvent::Direction");
s_downType = static_cast<Type>(registerEventType());
}
return s_downType;

View File

@ -6,11 +6,15 @@
#include "InputController.h"
#include "ConfigController.h"
#include "GameController.h"
#include "GamepadAxisEvent.h"
#include "GamepadButtonEvent.h"
#include "InputModel.h"
#include "InputProfile.h"
#include <QApplication>
#include <QKeyEvent>
#include <QMenu>
#include <QTimer>
#include <QWidget>
@ -24,8 +28,10 @@ int InputController::s_sdlInited = 0;
mSDLEvents InputController::s_sdlEvents;
#endif
InputController::InputController(int playerId, QWidget* topLevel, QObject* parent)
InputController::InputController(InputModel* model, int playerId, QWidget* topLevel, QObject* parent)
: QObject(parent)
, m_inputModel(model)
, m_platform(PLATFORM_NONE)
, m_playerId(playerId)
, m_config(nullptr)
, m_gamepadTimer(nullptr)
@ -37,15 +43,11 @@ InputController::InputController(int playerId, QWidget* topLevel, QObject* paren
, m_topLevel(topLevel)
, m_focusParent(topLevel)
{
mInputMapInit(&m_inputMap, &GBAInputInfo);
#ifdef BUILD_SDL
if (s_sdlInited == 0) {
mSDLInitEvents(&s_sdlEvents);
}
++s_sdlInited;
m_sdlPlayer.bindings = &m_inputMap;
mSDLInitBindingsGBA(&m_inputMap);
updateJoysticks();
#endif
@ -60,20 +62,21 @@ InputController::InputController(int playerId, QWidget* topLevel, QObject* paren
m_gamepadTimer.setInterval(50);
m_gamepadTimer.start();
mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_X, GBA_KEY_A);
mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Z, GBA_KEY_B);
mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_A, GBA_KEY_L);
mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_S, GBA_KEY_R);
mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Return, GBA_KEY_START);
mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Backspace, GBA_KEY_SELECT);
mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Up, GBA_KEY_UP);
mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Down, GBA_KEY_DOWN);
mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Left, GBA_KEY_LEFT);
mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Right, GBA_KEY_RIGHT);
m_autofireMenu = std::unique_ptr<QMenu>(new QMenu(tr("Autofire")));
m_inputModel->addMenu(m_autofireMenu.get());
m_inputMenu = std::unique_ptr<QMenu>(new QMenu(tr("Bindings")));
m_inputModel->addMenu(m_inputMenu.get());
connect(m_inputModel, SIGNAL(keyRebound(const QModelIndex&, int)), this, SLOT(bindKey(const QModelIndex&, int)));
connect(m_inputModel, SIGNAL(buttonRebound(const QModelIndex&, int)), this, SLOT(bindButton(const QModelIndex&, int)));
connect(m_inputModel, SIGNAL(axisRebound(const QModelIndex&, int, GamepadAxisEvent::Direction)), this, SLOT(bindAxis(const QModelIndex&, int, GamepadAxisEvent::Direction)));
}
InputController::~InputController() {
mInputMapDeinit(&m_inputMap);
for (auto& inputMap : m_inputMaps) {
mInputMapDeinit(&inputMap);
}
#ifdef BUILD_SDL
if (m_playerAttached) {
@ -87,6 +90,42 @@ InputController::~InputController() {
#endif
}
void InputController::addPlatform(mPlatform platform, const QString& visibleName, const mInputPlatformInfo* info) {
mInputMap* inputMap = &m_inputMaps[platform];
mInputMapInit(inputMap, info);
QMenu* input = m_inputMenu->addMenu(visibleName);
QMenu* autofire = m_autofireMenu->addMenu(visibleName);
m_inputMenuIndices[platform] = m_inputModel->addMenu(input, m_inputMenu.get());
m_inputModel->addMenu(autofire, m_autofireMenu.get());
for (size_t i = 0; i < info->nKeys; ++i) {
m_inputModel->addKey(input, platform, i, 0, info->keyId[i], info->keyId[i]);
m_inputModel->addKey(autofire, platform, i, 0, info->keyId[i], info->keyId[i]);
}
#ifdef BUILD_SDL
mSDLInitBindingsGBA(inputMap);
#endif
mInputBindKey(inputMap, KEYBOARD, Qt::Key_X, GBA_KEY_A);
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Z, GBA_KEY_B);
mInputBindKey(inputMap, KEYBOARD, Qt::Key_A, GBA_KEY_L);
mInputBindKey(inputMap, KEYBOARD, Qt::Key_S, GBA_KEY_R);
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Return, GBA_KEY_START);
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Backspace, GBA_KEY_SELECT);
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Up, GBA_KEY_UP);
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Down, GBA_KEY_DOWN);
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Left, GBA_KEY_LEFT);
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Right, GBA_KEY_RIGHT);
}
void InputController::setPlatform(mPlatform platform) {
#ifdef BUILD_SDL
m_sdlPlayer.bindings = &m_inputMaps[platform];
#endif
m_platform = platform;
}
void InputController::setConfiguration(ConfigController* config) {
m_config = config;
setAllowOpposing(config->getOption("allowOpposingDirections").toInt());
@ -99,26 +138,35 @@ void InputController::setConfiguration(ConfigController* config) {
loadConfiguration(SDL_BINDING_BUTTON);
loadProfile(SDL_BINDING_BUTTON, profileForType(SDL_BINDING_BUTTON));
#endif
restoreModel();
}
void InputController::loadConfiguration(uint32_t type) {
mInputMapLoad(&m_inputMap, type, m_config->input());
for (auto& inputMap : m_inputMaps) {
mInputMapLoad(&inputMap, type, m_config->input());
#ifdef BUILD_SDL
if (m_playerAttached) {
mSDLPlayerLoadConfig(&m_sdlPlayer, m_config->input());
}
if (m_playerAttached) {
mInputMap* bindings = m_sdlPlayer.bindings;
m_sdlPlayer.bindings = &inputMap;
mSDLPlayerLoadConfig(&m_sdlPlayer, m_config->input());
m_sdlPlayer.bindings = bindings;
}
#endif
}
}
void InputController::loadProfile(uint32_t type, const QString& profile) {
bool loaded = mInputProfileLoad(&m_inputMap, type, m_config->input(), profile.toUtf8().constData());
recalibrateAxes();
if (!loaded) {
const InputProfile* ip = InputProfile::findProfile(profile);
if (ip) {
ip->apply(this);
for (auto iter = m_inputMaps.begin(); iter != m_inputMaps.end(); ++iter) {
bool loaded = mInputProfileLoad(&iter.value(), type, m_config->input(), profile.toUtf8().constData());
if (!loaded) {
const InputProfile* ip = InputProfile::findProfile(iter.key(), profile);
if (ip) {
ip->apply(iter.key(), this);
}
}
}
recalibrateAxes();
m_inputModel->loadProfile(PLATFORM_NONE, profile); // TODO
emit profileLoaded(profile);
}
@ -135,12 +183,16 @@ void InputController::saveConfiguration() {
}
void InputController::saveConfiguration(uint32_t type) {
mInputMapSave(&m_inputMap, type, m_config->input());
for (auto& inputMap : m_inputMaps) {
mInputMapSave(&inputMap, type, m_config->input());
}
m_config->write();
}
void InputController::saveProfile(uint32_t type, const QString& profile) {
mInputProfileSave(&m_inputMap, type, m_config->input(), profile.toUtf8().constData());
for (auto& inputMap : m_inputMaps) {
mInputProfileSave(&inputMap, type, m_config->input(), profile.toUtf8().constData());
}
m_config->write();
}
@ -277,14 +329,6 @@ void InputController::setGyroSensitivity(float sensitivity) {
#endif
}
GBAKey InputController::mapKeyboard(int key) const {
return static_cast<GBAKey>(mInputMapKey(&m_inputMap, KEYBOARD, key));
}
void InputController::bindKey(uint32_t type, int key, GBAKey gbaKey) {
return mInputBindKey(&m_inputMap, type, key, gbaKey);
}
void InputController::updateJoysticks() {
#ifdef BUILD_SDL
QString profile = profileForType(SDL_BINDING_BUTTON);
@ -296,6 +340,10 @@ void InputController::updateJoysticks() {
#endif
}
const mInputMap* InputController::map() {
return &m_inputMaps[m_platform];
}
int InputController::pollEvents() {
int activeButtons = 0;
#ifdef BUILD_SDL
@ -305,7 +353,7 @@ int InputController::pollEvents() {
int numButtons = SDL_JoystickNumButtons(joystick);
int i;
for (i = 0; i < numButtons; ++i) {
GBAKey key = static_cast<GBAKey>(mInputMapKey(&m_inputMap, SDL_BINDING_BUTTON, i));
GBAKey key = static_cast<GBAKey>(mInputMapKey(&m_inputMaps[m_platform], SDL_BINDING_BUTTON, i));
if (key == GBA_KEY_NONE) {
continue;
}
@ -319,14 +367,14 @@ int InputController::pollEvents() {
int numHats = SDL_JoystickNumHats(joystick);
for (i = 0; i < numHats; ++i) {
int hat = SDL_JoystickGetHat(joystick, i);
activeButtons |= mInputMapHat(&m_inputMap, SDL_BINDING_BUTTON, i, hat);
activeButtons |= mInputMapHat(&m_inputMaps[m_platform], SDL_BINDING_BUTTON, i, hat);
}
int numAxes = SDL_JoystickNumAxes(joystick);
for (i = 0; i < numAxes; ++i) {
int value = SDL_JoystickGetAxis(joystick, i);
enum GBAKey key = static_cast<GBAKey>(mInputMapAxis(&m_inputMap, SDL_BINDING_BUTTON, i, value));
enum GBAKey key = static_cast<GBAKey>(mInputMapAxis(&m_inputMaps[m_platform], SDL_BINDING_BUTTON, i, value));
if (key != GBA_KEY_NONE) {
activeButtons |= 1 << key;
}
@ -396,8 +444,25 @@ QSet<QPair<int, GamepadAxisEvent::Direction>> InputController::activeGamepadAxes
return activeAxes;
}
void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction direction, GBAKey key) {
const mInputAxis* old = mInputQueryAxis(&m_inputMap, type, axis);
void InputController::bindKey(mPlatform platform, uint32_t type, int key, int coreKey) {
QModelIndex index = m_inputModel->index(coreKey, 0, m_inputMenuIndices[platform]);
bool signalsBlocked = m_inputModel->blockSignals(true);
if (type != KEYBOARD) {
m_inputModel->updateButton(index, key);
} else {
m_inputModel->updateKey(index, key);
}
m_inputModel->blockSignals(signalsBlocked);
mInputBindKey(&m_inputMaps[platform], type, key, coreKey);
}
void InputController::bindAxis(mPlatform platform, uint32_t type, int axis, GamepadAxisEvent::Direction direction, int key) {
QModelIndex index = m_inputModel->index(key, 0, m_inputMenuIndices[platform]);
bool signalsBlocked = m_inputModel->blockSignals(true);
m_inputModel->updateAxis(index, axis, direction);
m_inputModel->blockSignals(signalsBlocked);
const mInputAxis* old = mInputQueryAxis(&m_inputMaps[platform], type, axis);
mInputAxis description = { GBA_KEY_NONE, GBA_KEY_NONE, -AXIS_THRESHOLD, AXIS_THRESHOLD };
if (old) {
description = *old;
@ -419,11 +484,7 @@ void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direct
default:
return;
}
mInputBindAxis(&m_inputMap, type, axis, &description);
}
void InputController::unbindAllAxes(uint32_t type) {
mInputUnbindAllAxes(&m_inputMap, type);
mInputBindAxis(&m_inputMaps[platform], type, axis, &description);
}
QSet<QPair<int, GamepadHatEvent::Direction>> InputController::activeGamepadHats(int type) {
@ -458,26 +519,29 @@ QSet<QPair<int, GamepadHatEvent::Direction>> InputController::activeGamepadHats(
return activeHats;
}
void InputController::bindHat(uint32_t type, int hat, GamepadHatEvent::Direction direction, GBAKey gbaKey) {
void InputController::bindHat(mPlatform platform, uint32_t type, int hat, GamepadHatEvent::Direction direction, int coreKey) {
QModelIndex index = m_inputModel->index(coreKey, 0, m_inputMenuIndices[platform]);
//m_inputModel->updateHat(index, hat, direction);
mInputHatBindings bindings{ -1, -1, -1, -1 };
mInputQueryHat(&m_inputMap, type, hat, &bindings);
mInputQueryHat(&m_inputMaps[platform], type, hat, &bindings);
switch (direction) {
case GamepadHatEvent::UP:
bindings.up = gbaKey;
bindings.up = coreKey;
break;
case GamepadHatEvent::RIGHT:
bindings.right = gbaKey;
bindings.right = coreKey;
break;
case GamepadHatEvent::DOWN:
bindings.down = gbaKey;
bindings.down = coreKey;
break;
case GamepadHatEvent::LEFT:
bindings.left = gbaKey;
bindings.left = coreKey;
break;
default:
return;
}
mInputBindHat(&m_inputMap, type, hat, &bindings);
mInputBindHat(&m_inputMaps[platform], type, hat, &bindings);
}
void InputController::testGamepad(int type) {
@ -569,15 +633,15 @@ void InputController::sendGamepadEvent(QEvent* event) {
QApplication::sendEvent(focusWidget, event);
}
void InputController::postPendingEvent(GBAKey key) {
void InputController::postPendingEvent(int key) {
m_pendingEvents.insert(key);
}
void InputController::clearPendingEvent(GBAKey key) {
void InputController::clearPendingEvent(int key) {
m_pendingEvents.remove(key);
}
bool InputController::hasPendingEvent(GBAKey key) const {
bool InputController::hasPendingEvent(int key) const {
return m_pendingEvents.contains(key);
}
@ -614,3 +678,139 @@ void InputController::releaseFocus(QWidget* focus) {
m_focusParent = m_topLevel;
}
}
void InputController::setupCallback(GameController* controller) {
m_inputModel->setKeyCallback([this, controller](QMenu* menu, int key, bool down) {
if (menu == m_autofireMenu.get()) {
controller->setAutofire(key, down);
} else {
if (down) {
controller->keyPressed(key);
} else {
controller->keyReleased(key);
}
}
});
}
void InputController::bindKey(const QModelIndex& index, int key) {
int coreKey = m_inputModel->keyAt(index);
if (coreKey < 0) {
return;
}
mPlatform platform = m_inputMenuIndices.key(index.parent(), PLATFORM_NONE);
bindKey(platform, KEYBOARD, key, coreKey);
}
#ifdef BUILD_SDL
void InputController::bindButton(const QModelIndex& index, int key) {
int coreKey = m_inputModel->keyAt(index);
if (coreKey < 0) {
return;
}
mPlatform platform = m_inputMenuIndices.key(index.parent(), PLATFORM_NONE);
bindKey(platform, SDL_BINDING_BUTTON, key, coreKey);
}
void InputController::bindAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) {
int coreKey = m_inputModel->keyAt(index);
if (coreKey < 0) {
return;
}
mPlatform platform = m_inputMenuIndices.key(index.parent(), PLATFORM_NONE);
bindAxis(platform, SDL_BINDING_BUTTON, axis, direction, coreKey);
}
void InputController::bindHat(const QModelIndex& index, int hat, GamepadHatEvent::Direction direction) {
int coreKey = m_inputModel->keyAt(index);
if (coreKey < 0) {
return;
}
mPlatform platform = m_inputMenuIndices.key(index.parent(), PLATFORM_NONE);
bindHat(platform, SDL_BINDING_BUTTON, hat, direction, coreKey);
}
#else
void InputController::bindButton(const QModelIndex& index, int key, int) {}
void InputController::bindAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction, int) {}
void InputController::bindHat(const QModelIndex& index, int hat, GamepadHatEvent::Direction, int) {}
#endif
bool InputController::eventFilter(QObject*, QEvent* event) {
if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
int key = keyEvent->key();
if (!InputModel::isModifierKey(key)) {
key |= (keyEvent->modifiers() & ~Qt::KeypadModifier);
} else {
key = InputModel::toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier));
}
if (keyEvent->isAutoRepeat()) {
event->accept();
return true;
}
if (m_inputModel->triggerKey(key, event->type() == QEvent::KeyPress, m_platform)) {
event->accept();
return true;
}
}
if (event->type() == GamepadButtonEvent::Down() || event->type() == GamepadButtonEvent::Up()) {
GamepadButtonEvent* gbe = static_cast<GamepadButtonEvent*>(event);
if (m_inputModel->triggerButton(gbe->value(), event->type() == GamepadButtonEvent::Down())) {
event->accept();
return true;
}
}
if (event->type() == GamepadAxisEvent::Type()) {
GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event);
if (m_inputModel->triggerAxis(gae->axis(), gae->direction(), gae->isNew())) {
event->accept();
return true;
}
}
return false;
}
void InputController::restoreModel() {
bool signalsBlocked = m_inputModel->blockSignals(true);
for (auto iter = m_inputMaps.begin(); iter != m_inputMaps.end(); ++iter) {
mPlatform platform = iter.key();
QModelIndex parent = m_inputMenuIndices[platform];
int nKeys = iter->info->nKeys;
for (int i = 0; i < nKeys; ++i) {
int key = mInputQueryBinding(&iter.value(), KEYBOARD, i);
if (key >= 0) {
m_inputModel->updateKey(m_inputModel->index(i, 0, parent), key);
} else {
m_inputModel->clearKey(m_inputModel->index(i, 0, parent));
}
#ifdef BUILD_SDL
key = mInputQueryBinding(&iter.value(), SDL_BINDING_BUTTON, i);
if (key >= 0) {
m_inputModel->updateButton(m_inputModel->index(i, 0, parent), key);
} else {
m_inputModel->clearButton(m_inputModel->index(i, 0, parent));
}
#endif
}
#ifdef BUILD_SDL
struct Context {
InputModel* model;
QModelIndex parent;
} context{ m_inputModel, parent };
mInputEnumerateAxes(&iter.value(), SDL_BINDING_BUTTON, [](int axis, const struct mInputAxis* description, void* user) {
Context* context = static_cast<Context*>(user);
if (description->highDirection >= 0) {
context->model->updateAxis(context->model->index(description->highDirection, 0, context->parent), axis, GamepadAxisEvent::POSITIVE);
}
if (description->lowDirection >= 0) {
context->model->updateAxis(context->model->index(description->lowDirection, 0, context->parent), axis, GamepadAxisEvent::NEGATIVE);
}
}, &context);
#endif
}
m_inputModel->blockSignals(signalsBlocked);
}

View File

@ -9,23 +9,29 @@
#include "GamepadAxisEvent.h"
#include "GamepadHatEvent.h"
#include <QMap>
#include <QObject>
#include <QSet>
#include <QTimer>
#include <QVector>
#include <mgba/internal/gba/input.h>
#include <mgba/core/core.h>
#include <mgba/core/input.h>
#ifdef BUILD_SDL
#include "platform/sdl/sdl-events.h"
#endif
class QMenu;
struct mRotationSource;
struct mRumble;
namespace QGBA {
class ConfigController;
class GameController;
class InputModel;
class InputController : public QObject {
Q_OBJECT
@ -33,9 +39,12 @@ Q_OBJECT
public:
static const uint32_t KEYBOARD = 0x51545F4B;
InputController(int playerId = 0, QWidget* topLevel = nullptr, QObject* parent = nullptr);
InputController(InputModel* model, int playerId = 0, QWidget* topLevel = nullptr, QObject* parent = nullptr);
~InputController();
void addPlatform(mPlatform, const QString& visibleName, const mInputPlatformInfo*);
void setPlatform(mPlatform);
void setConfiguration(ConfigController* config);
void saveConfiguration();
void loadConfiguration(uint32_t type);
@ -47,11 +56,7 @@ public:
bool allowOpposing() const { return m_allowOpposing; }
void setAllowOpposing(bool allowOpposing) { m_allowOpposing = allowOpposing; }
GBAKey mapKeyboard(int key) const;
void bindKey(uint32_t type, int key, GBAKey);
const mInputMap* map() const { return &m_inputMap; }
const mInputMap* map();
int pollEvents();
@ -61,10 +66,9 @@ public:
QSet<QPair<int, GamepadHatEvent::Direction>> activeGamepadHats(int type);
void recalibrateAxes();
void bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction, GBAKey);
void unbindAllAxes(uint32_t type);
void bindHat(uint32_t type, int hat, GamepadHatEvent::Direction, GBAKey);
void bindKey(mPlatform platform, uint32_t type, int key, int);
void bindAxis(mPlatform platform, uint32_t type, int axis, GamepadAxisEvent::Direction, int);
void bindHat(mPlatform platform, uint32_t type, int hat, GamepadHatEvent::Direction, int);
QStringList connectedGamepads(uint32_t type) const;
int gamepad(uint32_t type) const;
@ -85,6 +89,8 @@ public:
mRumble* rumble();
mRotationSource* rotationSource();
void setupCallback(GameController* controller);
signals:
void profileLoaded(const QString& profile);
@ -97,13 +103,25 @@ public slots:
void resumeScreensaver();
void setScreensaverSuspendable(bool);
private:
void postPendingEvent(GBAKey);
void clearPendingEvent(GBAKey);
bool hasPendingEvent(GBAKey) const;
void sendGamepadEvent(QEvent*);
private slots:
void bindKey(const QModelIndex&, int key);
void bindButton(const QModelIndex&, int key);
void bindAxis(const QModelIndex&, int axis, GamepadAxisEvent::Direction);
void bindHat(const QModelIndex&, int hat, GamepadHatEvent::Direction);
mInputMap m_inputMap;
protected:
bool eventFilter(QObject*, QEvent*) override;
private:
void postPendingEvent(int key);
void clearPendingEvent(int key);
bool hasPendingEvent(int key) const;
void sendGamepadEvent(QEvent*);
void restoreModel();
InputModel* m_inputModel;
mPlatform m_platform;
QMap<mPlatform, mInputMap> m_inputMaps;
ConfigController* m_config;
int m_playerId;
bool m_allowOpposing;
@ -119,12 +137,16 @@ private:
QVector<int> m_deadzones;
std::unique_ptr<QMenu> m_inputMenu;
std::unique_ptr<QMenu> m_autofireMenu;
QMap<mPlatform, QModelIndex> m_inputMenuIndices;
QSet<int> m_activeButtons;
QSet<QPair<int, GamepadAxisEvent::Direction>> m_activeAxes;
QSet<QPair<int, GamepadHatEvent::Direction>> m_activeHats;
QTimer m_gamepadTimer;
QSet<GBAKey> m_pendingEvents;
QSet<int> m_pendingEvents;
};
}

View File

@ -12,11 +12,13 @@ using namespace QGBA;
InputItem::InputItem(QAction* action, const QString& name, InputItem* parent)
: m_action(action)
, m_shortcut(action->shortcut().isEmpty() ? 0 : action->shortcut()[0])
, m_keys(-1)
, m_menu(nullptr)
, m_name(name)
, m_button(-1)
, m_axis(-1)
, m_direction(GamepadAxisEvent::NEUTRAL)
, m_platform(PLATFORM_NONE)
, m_parent(parent)
{
m_visibleName = action->text()
@ -28,12 +30,29 @@ InputItem::InputItem(InputItem::Functions functions, int shortcut, const QString
: m_action(nullptr)
, m_shortcut(shortcut)
, m_functions(functions)
, m_keys(-1)
, m_menu(nullptr)
, m_name(name)
, m_visibleName(visibleName)
, m_button(-1)
, m_axis(-1)
, m_direction(GamepadAxisEvent::NEUTRAL)
, m_platform(PLATFORM_NONE)
, m_parent(parent)
{
}
InputItem::InputItem(mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name, InputItem* parent)
: m_action(nullptr)
, m_shortcut(shortcut)
, m_keys(key)
, m_menu(nullptr)
, m_name(name)
, m_visibleName(visibleName)
, m_button(-1)
, m_axis(-1)
, m_direction(GamepadAxisEvent::NEUTRAL)
, m_platform(platform)
, m_parent(parent)
{
}
@ -64,6 +83,10 @@ void InputItem::addFunctions(InputItem::Functions functions,
m_items.append(InputItem(functions, shortcut, visibleName, name, this));
}
void InputItem::addKey(mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name) {
m_items.append(InputItem(platform, key, shortcut, visibleName, name, this));
}
void InputItem::addSubmenu(QMenu* menu) {
m_items.append(InputItem(menu, this));
}

View File

@ -7,9 +7,12 @@
#define QGBA_INPUT_ITEM
#include "GamepadAxisEvent.h"
#include "GamepadHatEvent.h"
#include <QAction>
#include <mgba/core/core.h>
namespace QGBA {
class InputItem {
@ -19,12 +22,15 @@ public:
InputItem(QAction* action, const QString& name, InputItem* parent = nullptr);
InputItem(Functions functions, int shortcut, const QString& visibleName,
const QString& name, InputItem* parent = nullptr);
InputItem(mPlatform platform, int key, int shortcut, const QString& name, const QString& visibleName, InputItem* parent = nullptr);
InputItem(QMenu* action, InputItem* parent = nullptr);
QAction* action() { return m_action; }
const QAction* action() const { return m_action; }
const int shortcut() const { return m_shortcut; }
int shortcut() const { return m_shortcut; }
mPlatform platform() const { return m_platform; }
Functions functions() const { return m_functions; }
int key() const { return m_keys; }
QMenu* menu() { return m_menu; }
const QMenu* menu() const { return m_menu; }
const QString& visibleName() const { return m_visibleName; }
@ -36,6 +42,7 @@ public:
void addAction(QAction* action, const QString& name);
void addFunctions(Functions functions, int shortcut, const QString& visibleName,
const QString& name);
void addKey(mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name);
void addSubmenu(QMenu* menu);
int button() const { return m_button; }
void setShortcut(int sequence);
@ -57,7 +64,9 @@ private:
QString m_visibleName;
int m_button;
int m_axis;
int m_keys;
GamepadAxisEvent::Direction m_direction;
mPlatform m_platform;
QList<InputItem> m_items;
InputItem* m_parent;
};

View File

@ -87,10 +87,14 @@ QModelIndex InputModel::parent(const QModelIndex& index) const {
return QModelIndex();
}
InputItem* item = static_cast<InputItem*>(index.internalPointer());
if (!item->parent() || !item->parent()->parent()) {
return this->index(item->parent());
}
QModelIndex InputModel::index(InputItem* item) const {
if (!item || !item->parent()) {
return QModelIndex();
}
return createIndex(item->parent()->parent()->items().indexOf(*item->parent()), 0, item->parent());
return createIndex(item->parent()->items().indexOf(*item), 0, item);
}
int InputModel::columnCount(const QModelIndex& index) const {
@ -105,38 +109,42 @@ int InputModel::rowCount(const QModelIndex& index) const {
return item->items().count();
}
void InputModel::addAction(QMenu* menu, QAction* action, const QString& name) {
InputItem* InputModel::add(QMenu* menu, std::function<void (InputItem*)> callback) {
InputItem* smenu = m_menuMap[menu];
if (!smenu) {
return;
return nullptr;
}
InputItem* pmenu = smenu->parent();
int row = pmenu->items().indexOf(*smenu);
QModelIndex parent = createIndex(row, 0, smenu);
QModelIndex parent = index(smenu);
beginInsertRows(parent, smenu->items().count(), smenu->items().count());
smenu->addAction(action, name);
callback(smenu);
endInsertRows();
InputItem* item = &smenu->items().last();
emit dataChanged(createIndex(smenu->items().count() - 1, 0, item),
createIndex(smenu->items().count() - 1, 2, item));
return item;
}
void InputModel::addAction(QMenu* menu, QAction* action, const QString& name) {
InputItem* item = add(menu, [&](InputItem* smenu) {
smenu->addAction(action, name);
});
if (!item) {
return;
}
if (m_config) {
loadShortcuts(item);
}
emit dataChanged(createIndex(smenu->items().count() - 1, 0, item),
createIndex(smenu->items().count() - 1, 2, item));
}
void InputModel::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release,
int shortcut, const QString& visibleName, const QString& name) {
InputItem* smenu = m_menuMap[menu];
if (!smenu) {
InputItem* item = add(menu, [&](InputItem* smenu) {
smenu->addFunctions(qMakePair(press, release), shortcut, visibleName, name);
});
if (!item) {
return;
}
InputItem* pmenu = smenu->parent();
int row = pmenu->items().indexOf(*smenu);
QModelIndex parent = createIndex(row, 0, smenu);
beginInsertRows(parent, smenu->items().count(), smenu->items().count());
smenu->addFunctions(qMakePair(press, release), shortcut, visibleName, name);
endInsertRows();
InputItem* item = &smenu->items().last();
bool loadedShortcut = false;
if (m_config) {
loadedShortcut = loadShortcuts(item);
@ -144,8 +152,6 @@ void InputModel::addFunctions(QMenu* menu, std::function<void()> press, std::fun
if (!loadedShortcut && !m_heldKeys.contains(shortcut)) {
m_heldKeys[shortcut] = item;
}
emit dataChanged(createIndex(smenu->items().count() - 1, 0, item),
createIndex(smenu->items().count() - 1, 2, item));
}
void InputModel::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release,
@ -153,17 +159,22 @@ void InputModel::addFunctions(QMenu* menu, std::function<void()> press, std::fun
addFunctions(menu, press, release, shortcut[0], visibleName, name);
}
void InputModel::addMenu(QMenu* menu, QMenu* parentMenu) {
void InputModel::addKey(QMenu* menu, mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name) {
InputItem* item = add(menu, [&](InputItem* smenu) {
smenu->addKey(platform, key, shortcut, visibleName, name);
});
if (!item) {
return;
}
m_keys[qMakePair(platform, key)] = item;
}
QModelIndex InputModel::addMenu(QMenu* menu, QMenu* parentMenu) {
InputItem* smenu = m_menuMap[parentMenu];
if (!smenu) {
smenu = &m_rootMenu;
}
QModelIndex parent;
InputItem* pmenu = smenu->parent();
if (pmenu) {
int row = pmenu->items().indexOf(*smenu);
parent = createIndex(row, 0, smenu);
}
QModelIndex parent = index(smenu);
beginInsertRows(parent, smenu->items().count(), smenu->items().count());
smenu->addSubmenu(menu);
endInsertRows();
@ -171,20 +182,35 @@ void InputModel::addMenu(QMenu* menu, QMenu* parentMenu) {
emit dataChanged(createIndex(smenu->items().count() - 1, 0, item),
createIndex(smenu->items().count() - 1, 2, item));
m_menuMap[menu] = item;
return index(item);
}
InputItem* InputModel::itemAt(const QModelIndex& index) {
if (!index.isValid()) {
return nullptr;
}
return static_cast<InputItem*>(index.internalPointer());
if (index.internalPointer()) {
return static_cast<InputItem*>(index.internalPointer());
}
if (!index.parent().isValid()) {
return nullptr;
}
InputItem* pmenu = static_cast<InputItem*>(index.parent().internalPointer());
return &pmenu->items()[index.row()];
}
const InputItem* InputModel::itemAt(const QModelIndex& index) const {
if (!index.isValid()) {
return nullptr;
}
return static_cast<const InputItem*>(index.internalPointer());
if (index.internalPointer()) {
return static_cast<InputItem*>(index.internalPointer());
}
if (!index.parent().isValid()) {
return nullptr;
}
InputItem* pmenu = static_cast<InputItem*>(index.parent().internalPointer());
return &pmenu->items()[index.row()];
}
int InputModel::shortcutAt(const QModelIndex& index) const {
@ -195,6 +221,14 @@ int InputModel::shortcutAt(const QModelIndex& index) const {
return item->shortcut();
}
int InputModel::keyAt(const QModelIndex& index) const {
const InputItem* item = itemAt(index);
if (!item) {
return -1;
}
return item->key();
}
bool InputModel::isMenuAt(const QModelIndex& index) const {
const InputItem* item = itemAt(index);
if (!item) {
@ -216,22 +250,25 @@ void InputModel::updateKey(const QModelIndex& index, int keySequence) {
if (m_config) {
m_config->setQtOption(item->name(), QKeySequence(keySequence).toString(), KEY_SECTION);
}
emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
createIndex(index.row(), 2, index.internalPointer()));
}
void InputModel::updateKey(InputItem* item, int keySequence) {
int oldShortcut = item->shortcut();
if (item->functions().first) {
if (item->functions().first || item->key() >= 0) {
if (oldShortcut > 0) {
m_heldKeys.take(oldShortcut);
}
if (keySequence > 0) {
m_heldKeys[keySequence] = item;
if (keySequence >= 0) {
m_keys[qMakePair(item->platform(), keySequence)] = item;
}
}
item->setShortcut(keySequence);
emit dataChanged(createIndex(index(item).row(), 0, item),
createIndex(index(item).row(), 2, item));
emit keyRebound(index(item), keySequence);
}
void InputModel::updateButton(const QModelIndex& index, int button) {
@ -260,6 +297,8 @@ void InputModel::updateButton(const QModelIndex& index, int button) {
}
emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
createIndex(index.row(), 2, index.internalPointer()));
emit buttonRebound(index, button);
}
void InputModel::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) {
@ -296,6 +335,8 @@ void InputModel::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent
}
emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
createIndex(index.row(), 2, index.internalPointer()));
emit axisRebound(index, axis, direction);
}
void InputModel::clearKey(const QModelIndex& index) {
@ -306,76 +347,16 @@ void InputModel::clearButton(const QModelIndex& index) {
updateButton(index, -1);
}
bool InputModel::eventFilter(QObject*, QEvent* event) {
if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->isAutoRepeat()) {
return false;
}
int key = keyEvent->key();
if (!isModifierKey(key)) {
key |= (keyEvent->modifiers() & ~Qt::KeypadModifier);
} else {
key = toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier));
}
auto item = m_heldKeys.find(key);
if (item != m_heldKeys.end()) {
auto pair = item.value()->functions();
if (event->type() == QEvent::KeyPress) {
if (pair.first) {
pair.first();
}
} else {
if (pair.second) {
pair.second();
}
}
event->accept();
return true;
}
}
if (event->type() == GamepadButtonEvent::Down()) {
auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());
if (item == m_buttons.end()) {
return false;
}
QAction* action = item.value()->action();
if (action && action->isEnabled()) {
action->trigger();
}
auto pair = item.value()->functions();
if (pair.first) {
pair.first();
}
event->accept();
bool InputModel::triggerKey(int keySequence, bool down, mPlatform platform) {
auto key = m_keys.find(qMakePair(platform, keySequence));
if (key != m_keys.end()) {
m_keyCallback(key.value()->parent()->menu(), key.value()->key(), down);
return true;
}
if (event->type() == GamepadButtonEvent::Up()) {
auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());
if (item == m_buttons.end()) {
return false;
}
auto pair = item.value()->functions();
if (pair.second) {
pair.second();
}
event->accept();
return true;
}
if (event->type() == GamepadAxisEvent::Type()) {
GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event);
auto item = m_axes.find(qMakePair(gae->axis(), gae->direction()));
if (item == m_axes.end()) {
return false;
}
if (gae->isNew()) {
QAction* action = item.value()->action();
if (action && action->isEnabled()) {
action->trigger();
}
}
auto pair = item.value()->functions();
if (gae->isNew()) {
auto heldKey = m_heldKeys.find(keySequence);
if (heldKey != m_heldKeys.end()) {
auto pair = heldKey.value()->functions();
if (down) {
if (pair.first) {
pair.first();
}
@ -384,12 +365,58 @@ bool InputModel::eventFilter(QObject*, QEvent* event) {
pair.second();
}
}
event->accept();
return true;
}
return false;
}
bool InputModel::triggerButton(int button, bool down) {
auto item = m_buttons.find(button);
if (item == m_buttons.end()) {
return false;
}
if (down) {
QAction* action = item.value()->action();
if (action && action->isEnabled()) {
action->trigger();
}
auto pair = item.value()->functions();
if (pair.first) {
pair.first();
}
} else {
auto pair = item.value()->functions();
if (pair.second) {
pair.second();
}
}
return true;
}
bool InputModel::triggerAxis(int axis, GamepadAxisEvent::Direction direction, bool isNew) {
auto item = m_axes.find(qMakePair(axis, direction));
if (item == m_axes.end()) {
return false;
}
if (isNew) {
QAction* action = item.value()->action();
if (action && action->isEnabled()) {
action->trigger();
}
}
auto pair = item.value()->functions();
if (isNew) {
if (pair.first) {
pair.first();
}
} else {
if (pair.second) {
pair.second();
}
}
return true;
}
bool InputModel::loadShortcuts(InputItem* item) {
if (item->name().isNull()) {
return false;
@ -462,9 +489,9 @@ void InputModel::loadGamepadShortcuts(InputItem* item) {
}
}
void InputModel::loadProfile(const QString& profile) {
void InputModel::loadProfile(mPlatform platform, const QString& profile) {
m_profileName = profile;
m_profile = InputProfile::findProfile(profile);
m_profile = InputProfile::findProfile(platform, profile);
onSubitems(&m_rootMenu, [this](InputItem* item) {
loadGamepadShortcuts(item);
});
@ -534,5 +561,4 @@ int InputModel::toModifierKey(int key) {
break;
}
return modifiers;
}

View File

@ -6,6 +6,8 @@
#ifndef QGBA_INPUT_MODEL
#define QGBA_INPUT_MODEL
#include <mgba/core/core.h>
#include "GamepadAxisEvent.h"
#include "InputItem.h"
@ -38,6 +40,7 @@ public:
void setConfigController(ConfigController* controller);
void setProfile(const QString& profile);
void setKeyCallback(std::function<void (QMenu*, int, bool)> callback) { m_keyCallback = callback; }
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
@ -50,18 +53,21 @@ public:
void addAction(QMenu* menu, QAction* action, const QString& name);
void addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release,
int shortcut, const QString& visibleName, const QString& name);
int shortcut, const QString& visibleName, const QString& name);
void addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release,
const QKeySequence& shortcut, const QString& visibleName, const QString& name);
void addMenu(QMenu* menu, QMenu* parent = nullptr);
const QKeySequence& shortcut, const QString& visibleName, const QString& name);
void addKey(QMenu* menu, mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name);
QModelIndex addMenu(QMenu* menu, QMenu* parent = nullptr);
QAction* getAction(const QString& name);
int shortcutAt(const QModelIndex& index) const;
int keyAt(const QModelIndex& index) const;
bool isMenuAt(const QModelIndex& index) const;
void updateKey(const QModelIndex& index, int keySequence);
void updateButton(const QModelIndex& index, int button);
void updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction);
void updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction);
void updateHat(const QModelIndex& index, int hat, GamepadHatEvent::Direction);
void clearKey(const QModelIndex& index);
void clearButton(const QModelIndex& index);
@ -70,13 +76,21 @@ public:
static bool isModifierKey(int key);
static int toModifierKey(int key);
public slots:
void loadProfile(const QString& profile);
void loadProfile(mPlatform platform, const QString& profile);
protected:
bool eventFilter(QObject*, QEvent*) override;
bool triggerKey(int keySequence, bool down, mPlatform platform = PLATFORM_NONE);
bool triggerButton(int button, bool down);
bool triggerAxis(int axis, GamepadAxisEvent::Direction, bool isNew);
bool triggerHat(int hat, GamepadHatEvent::Direction);
signals:
void keyRebound(const QModelIndex&, int keySequence);
void buttonRebound(const QModelIndex&, int button);
void axisRebound(const QModelIndex& index, int axis, GamepadAxisEvent::Direction);
void hatRebound(const QModelIndex& index, int hat, GamepadHatEvent::Direction);
private:
InputItem* add(QMenu* menu, std::function<void (InputItem*)>);
InputItem* itemAt(const QModelIndex& index);
const InputItem* itemAt(const QModelIndex& index) const;
bool loadShortcuts(InputItem*);
@ -84,12 +98,16 @@ private:
void onSubitems(InputItem*, std::function<void(InputItem*)> func);
void updateKey(InputItem* item, int keySequence);
QModelIndex index(InputItem* item) const;
InputItem m_rootMenu;
QMap<QMenu*, InputItem*> m_menuMap;
QMap<int, InputItem*> m_buttons;
QMap<QPair<int, GamepadAxisEvent::Direction>, InputItem*> m_axes;
QMap<int, InputItem*> m_heldKeys;
QMap<QPair<mPlatform, int>, InputItem*> m_keys;
ConfigController* m_config;
std::function<void (QMenu*, int key, bool down)> m_keyCallback;
QString m_profileName;
const InputProfile* m_profile;
};

View File

@ -199,7 +199,8 @@ constexpr InputProfile::InputProfile(const char* name,
{
}
const InputProfile* InputProfile::findProfile(const QString& name) {
const InputProfile* InputProfile::findProfile(mPlatform platform, const QString& name) {
// TODO: Use platform
for (size_t i = 0; i < sizeof(s_defaultMaps) / sizeof(*s_defaultMaps); ++i) {
QRegExp re(s_defaultMaps[i].m_profileName);
if (re.exactMatch(name)) {
@ -209,11 +210,11 @@ const InputProfile* InputProfile::findProfile(const QString& name) {
return nullptr;
}
void InputProfile::apply(InputController* controller) const {
void InputProfile::apply(mPlatform platform, InputController* controller) const {
for (size_t i = 0; i < GBA_KEY_MAX; ++i) {
#ifdef BUILD_SDL
controller->bindKey(SDL_BINDING_BUTTON, m_keys[i], static_cast<GBAKey>(i));
controller->bindAxis(SDL_BINDING_BUTTON, m_axes[i].axis, m_axes[i].direction, static_cast<GBAKey>(i));
controller->bindKey(platform, SDL_BINDING_BUTTON, m_keys[i], static_cast<GBAKey>(i));
controller->bindAxis(platform, SDL_BINDING_BUTTON, m_axes[i].axis, m_axes[i].direction, static_cast<GBAKey>(i));
#endif
}
controller->registerTiltAxisX(m_tiltAxis.x);

View File

@ -8,6 +8,7 @@
#include "GamepadAxisEvent.h"
#include <mgba/core/core.h>
#include <mgba/gba/interface.h>
namespace QGBA {
@ -16,9 +17,9 @@ class InputController;
class InputProfile {
public:
static const InputProfile* findProfile(const QString& name);
static const InputProfile* findProfile(mPlatform platform, const QString& name);
void apply(InputController*) const;
void apply(mPlatform platform, InputController*) const;
bool lookupShortcutButton(const QString& shortcut, int* button) const;
bool lookupShortcutAxis(const QString& shortcut, int* axis, GamepadAxisEvent::Direction* direction) const;

View File

@ -9,7 +9,6 @@
#include "ConfigController.h"
#include "Display.h"
#include "GBAApp.h"
#include "GBAKeyEditor.h"
#include "InputController.h"
#include "ShortcutView.h"
@ -137,31 +136,7 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
selectBios(m_ui.gbcBios);
});
GBAKeyEditor* editor = new GBAKeyEditor(inputController, InputController::KEYBOARD, QString(), this);
m_ui.stackedWidget->addWidget(editor);
m_ui.tabs->addItem(tr("Keyboard"));
connect(m_ui.buttonBox, SIGNAL(accepted()), editor, SLOT(save()));
GBAKeyEditor* buttonEditor = nullptr;
#ifdef BUILD_SDL
inputController->recalibrateAxes();
const char* profile = inputController->profileForType(SDL_BINDING_BUTTON);
buttonEditor = new GBAKeyEditor(inputController, SDL_BINDING_BUTTON, profile);
m_ui.stackedWidget->addWidget(buttonEditor);
m_ui.tabs->addItem(tr("Controllers"));
connect(m_ui.buttonBox, SIGNAL(accepted()), buttonEditor, SLOT(save()));
#endif
connect(m_ui.buttonBox, SIGNAL(accepted()), this, SLOT(updateConfig()));
connect(m_ui.buttonBox, &QDialogButtonBox::clicked, [this, editor, buttonEditor](QAbstractButton* button) {
if (m_ui.buttonBox->buttonRole(button) == QDialogButtonBox::ApplyRole) {
updateConfig();
editor->save();
if (buttonEditor) {
buttonEditor->save();
}
}
});
ShortcutView* shortcutView = new ShortcutView();
shortcutView->setModel(inputModel);

View File

@ -51,6 +51,7 @@
#endif
#ifdef M_CORE_GBA
#include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/input.h>
#include <mgba/internal/gba/video.h>
#endif
#include "feature/commandline.h"
@ -67,7 +68,8 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
, m_screenWidget(new WindowBackground())
, m_logo(":/res/mgba-1024.png")
, m_config(config)
, m_inputController(playerId, this)
, m_inputModel(new InputModel(this))
, m_inputController(m_inputModel, playerId, this)
#ifdef USE_FFMPEG
, m_videoView(nullptr)
#endif
@ -81,7 +83,6 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
, m_console(nullptr)
#endif
, m_mruMenu(nullptr)
, m_inputModel(new InputModel(this))
, m_fullscreenOnStart(false)
, m_autoresume(false)
, m_wasOpened(false)
@ -194,7 +195,6 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
connect(m_display, &Display::showCursor, [this]() {
m_screenWidget->unsetCursor();
});
connect(&m_inputController, SIGNAL(profileLoaded(const QString&)), m_inputModel, SLOT(loadProfile(const QString&)));
m_log.setLevels(mLOG_WARN | mLOG_ERROR | mLOG_FATAL);
m_fpsTimer.setInterval(FPS_TIMER_INTERVAL);
@ -202,6 +202,14 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
m_inputModel->setConfigController(m_config);
setupMenu(menuBar());
#ifdef M_CORE_GBA
m_inputController.addPlatform(PLATFORM_GBA, tr("Game Boy Advance"), &GBAInputInfo);
#endif
#ifdef M_CORE_GB
m_inputController.addPlatform(PLATFORM_GB, tr("Game Boy"), &GBAInputInfo);
#endif
m_inputController.setupCallback(m_controller);
}
Window::~Window() {
@ -559,34 +567,6 @@ void Window::consoleOpen() {
}
#endif
void Window::keyPressEvent(QKeyEvent* event) {
if (event->isAutoRepeat()) {
QWidget::keyPressEvent(event);
return;
}
GBAKey key = m_inputController.mapKeyboard(event->key());
if (key == GBA_KEY_NONE) {
QWidget::keyPressEvent(event);
return;
}
m_controller->keyPressed(key);
event->accept();
}
void Window::keyReleaseEvent(QKeyEvent* event) {
if (event->isAutoRepeat()) {
QWidget::keyReleaseEvent(event);
return;
}
GBAKey key = m_inputController.mapKeyboard(event->key());
if (key == GBA_KEY_NONE) {
QWidget::keyPressEvent(event);
return;
}
m_controller->keyReleased(key);
event->accept();
}
void Window::resizeEvent(QResizeEvent* event) {
if (!isFullScreen()) {
m_config->setOption("height", m_screenWidget->height());
@ -755,6 +735,8 @@ void Window::gameStarted(mCoreThread* context, const QString& fname) {
}
#endif
m_inputController.setPlatform(m_controller->platform());
m_hitUnimplementedBiosCall = false;
m_fpsTimer.start();
m_focusCheck.start();
@ -923,7 +905,7 @@ void Window::setupMenu(QMenuBar* menubar) {
menubar->clear();
QMenu* fileMenu = menubar->addMenu(tr("&File"));
m_inputModel->addMenu(fileMenu);
installEventFilter(m_inputModel);
installEventFilter(&m_inputController);
addControlledAction(fileMenu, fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open),
"loadROM");
#ifdef USE_SQLITE3
@ -1480,69 +1462,6 @@ void Window::setupMenu(QMenuBar* menubar) {
exitFullScreen->setShortcut(QKeySequence("Esc"));
addHiddenAction(frameMenu, exitFullScreen, "exitFullScreen");
QMenu* autofireMenu = new QMenu(tr("Autofire"), this);
m_inputModel->addMenu(autofireMenu);
m_inputModel->addFunctions(autofireMenu, [this]() {
m_controller->setAutofire(GBA_KEY_A, true);
}, [this]() {
m_controller->setAutofire(GBA_KEY_A, false);
}, QKeySequence(), tr("Autofire A"), "autofireA");
m_inputModel->addFunctions(autofireMenu, [this]() {
m_controller->setAutofire(GBA_KEY_B, true);
}, [this]() {
m_controller->setAutofire(GBA_KEY_B, false);
}, QKeySequence(), tr("Autofire B"), "autofireB");
m_inputModel->addFunctions(autofireMenu, [this]() {
m_controller->setAutofire(GBA_KEY_L, true);
}, [this]() {
m_controller->setAutofire(GBA_KEY_L, false);
}, QKeySequence(), tr("Autofire L"), "autofireL");
m_inputModel->addFunctions(autofireMenu, [this]() {
m_controller->setAutofire(GBA_KEY_R, true);
}, [this]() {
m_controller->setAutofire(GBA_KEY_R, false);
}, QKeySequence(), tr("Autofire R"), "autofireR");
m_inputModel->addFunctions(autofireMenu, [this]() {
m_controller->setAutofire(GBA_KEY_START, true);
}, [this]() {
m_controller->setAutofire(GBA_KEY_START, false);
}, QKeySequence(), tr("Autofire Start"), "autofireStart");
m_inputModel->addFunctions(autofireMenu, [this]() {
m_controller->setAutofire(GBA_KEY_SELECT, true);
}, [this]() {
m_controller->setAutofire(GBA_KEY_SELECT, false);
}, QKeySequence(), tr("Autofire Select"), "autofireSelect");
m_inputModel->addFunctions(autofireMenu, [this]() {
m_controller->setAutofire(GBA_KEY_UP, true);
}, [this]() {
m_controller->setAutofire(GBA_KEY_UP, false);
}, QKeySequence(), tr("Autofire Up"), "autofireUp");
m_inputModel->addFunctions(autofireMenu, [this]() {
m_controller->setAutofire(GBA_KEY_RIGHT, true);
}, [this]() {
m_controller->setAutofire(GBA_KEY_RIGHT, false);
}, QKeySequence(), tr("Autofire Right"), "autofireRight");
m_inputModel->addFunctions(autofireMenu, [this]() {
m_controller->setAutofire(GBA_KEY_DOWN, true);
}, [this]() {
m_controller->setAutofire(GBA_KEY_DOWN, false);
}, QKeySequence(), tr("Autofire Down"), "autofireDown");
m_inputModel->addFunctions(autofireMenu, [this]() {
m_controller->setAutofire(GBA_KEY_LEFT, true);
}, [this]() {
m_controller->setAutofire(GBA_KEY_LEFT, false);
}, QKeySequence(), tr("Autofire Left"), "autofireLeft");
foreach (QAction* action, m_gameActions) {
action->setDisabled(true);
}

View File

@ -98,8 +98,6 @@ public slots:
#endif
protected:
virtual void keyPressEvent(QKeyEvent* event) override;
virtual void keyReleaseEvent(QKeyEvent* event) override;
virtual void resizeEvent(QResizeEvent*) override;
virtual void showEvent(QShowEvent*) override;
virtual void closeEvent(QCloseEvent*) override;
@ -168,12 +166,12 @@ private:
WindowBackground* m_screenWidget;
QPixmap m_logo;
ConfigController* m_config;
InputModel* m_inputModel;
InputController m_inputController;
QList<QDateTime> m_frameList;
QTimer m_fpsTimer;
QList<QString> m_mruFiles;
QMenu* m_mruMenu;
InputModel* m_inputModel;
ShaderSelector* m_shaderView;
bool m_fullscreenOnStart;
QTimer m_focusCheck;