Qt: Support binding multiple buttons/axis to controllers

This commit is contained in:
Connor McLaughlin 2020-07-23 02:35:15 +10:00
parent 08a8434140
commit 6c162eb3c5
9 changed files with 595 additions and 15 deletions

View file

@ -33,6 +33,9 @@ add_executable(duckstation-qt
gpusettingswidget.ui gpusettingswidget.ui
hotkeysettingswidget.cpp hotkeysettingswidget.cpp
hotkeysettingswidget.h hotkeysettingswidget.h
inputbindingdialog.cpp
inputbindingdialog.h
inputbindingdialog.ui
inputbindingwidgets.cpp inputbindingwidgets.cpp
inputbindingwidgets.h inputbindingwidgets.h
main.cpp main.cpp

View file

@ -42,6 +42,7 @@
<ClCompile Include="generalsettingswidget.cpp" /> <ClCompile Include="generalsettingswidget.cpp" />
<ClCompile Include="gpusettingswidget.cpp" /> <ClCompile Include="gpusettingswidget.cpp" />
<ClCompile Include="hotkeysettingswidget.cpp" /> <ClCompile Include="hotkeysettingswidget.cpp" />
<ClCompile Include="inputbindingdialog.cpp" />
<ClCompile Include="inputbindingwidgets.cpp" /> <ClCompile Include="inputbindingwidgets.cpp" />
<ClCompile Include="qtdisplaywidget.cpp" /> <ClCompile Include="qtdisplaywidget.cpp" />
<ClCompile Include="gamelistsettingswidget.cpp" /> <ClCompile Include="gamelistsettingswidget.cpp" />
@ -68,6 +69,7 @@
<QtMoc Include="inputbindingwidgets.h" /> <QtMoc Include="inputbindingwidgets.h" />
<QtMoc Include="advancedsettingswidget.h" /> <QtMoc Include="advancedsettingswidget.h" />
<QtMoc Include="qtprogresscallback.h" /> <QtMoc Include="qtprogresscallback.h" />
<QtMoc Include="inputbindingdialog.h" />
<ClInclude Include="resource.h" /> <ClInclude Include="resource.h" />
<ClInclude Include="settingwidgetbinder.h" /> <ClInclude Include="settingwidgetbinder.h" />
<QtMoc Include="consolesettingswidget.h" /> <QtMoc Include="consolesettingswidget.h" />
@ -148,6 +150,7 @@
<ClCompile Include="$(IntDir)moc_generalsettingswidget.cpp" /> <ClCompile Include="$(IntDir)moc_generalsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_gpusettingswidget.cpp" /> <ClCompile Include="$(IntDir)moc_gpusettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_hotkeysettingswidget.cpp" /> <ClCompile Include="$(IntDir)moc_hotkeysettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_inputbindingdialog.cpp" />
<ClCompile Include="$(IntDir)moc_inputbindingwidgets.cpp" /> <ClCompile Include="$(IntDir)moc_inputbindingwidgets.cpp" />
<ClCompile Include="$(IntDir)moc_mainwindow.cpp" /> <ClCompile Include="$(IntDir)moc_mainwindow.cpp" />
<ClCompile Include="$(IntDir)moc_memorycardsettingswidget.cpp" /> <ClCompile Include="$(IntDir)moc_memorycardsettingswidget.cpp" />
@ -171,6 +174,11 @@
<ItemGroup> <ItemGroup>
<Image Include="duckstation-qt.ico" /> <Image Include="duckstation-qt.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<QtUi Include="inputbindingdialog.ui">
<FileType>Document</FileType>
</QtUi>
</ItemGroup>
<Target Name="CopyCommonDataFiles" AfterTargets="Build" Inputs="@(CommonDataFiles)" Outputs="@(CommonDataFiles -> '$(BinaryOutputDir)%(RecursiveDir)%(Filename)%(Extension)')"> <Target Name="CopyCommonDataFiles" AfterTargets="Build" Inputs="@(CommonDataFiles)" Outputs="@(CommonDataFiles -> '$(BinaryOutputDir)%(RecursiveDir)%(Filename)%(Extension)')">
<Message Text="Copying common data files" Importance="High" /> <Message Text="Copying common data files" Importance="High" />
<Copy SourceFiles="@(CommonDataFiles)" DestinationFolder="$(BinaryOutputDir)\%(RecursiveDir)" SkipUnchangedFiles="true" /> <Copy SourceFiles="@(CommonDataFiles)" DestinationFolder="$(BinaryOutputDir)\%(RecursiveDir)" SkipUnchangedFiles="true" />

View file

@ -40,11 +40,13 @@
<ClCompile Include="$(IntDir)moc_controllersettingswidget.cpp" /> <ClCompile Include="$(IntDir)moc_controllersettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_memorycardsettingswidget.cpp" /> <ClCompile Include="$(IntDir)moc_memorycardsettingswidget.cpp" />
<ClCompile Include="$(IntDir)qrc_resources.cpp" /> <ClCompile Include="$(IntDir)qrc_resources.cpp" />
<ClCompile Include="inputbindingdialog.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="qtutils.h" /> <ClInclude Include="qtutils.h" />
<ClInclude Include="settingwidgetbinder.h" /> <ClInclude Include="settingwidgetbinder.h" />
<ClInclude Include="resource.h" /> <ClInclude Include="resource.h" />
<ClInclude Include="inputbindingdialog.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Filter Include="resources"> <Filter Include="resources">
@ -95,4 +97,7 @@
<ItemGroup> <ItemGroup>
<Image Include="duckstation-qt.ico" /> <Image Include="duckstation-qt.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="inputbindingdialog.ui" />
</ItemGroup>
</Project> </Project>

View file

@ -0,0 +1,319 @@
#include "inputbindingdialog.h"
#include "common/bitutils.h"
#include "common/string_util.h"
#include "core/settings.h"
#include "frontend-common/controller_interface.h"
#include "qthostinterface.h"
#include "qtutils.h"
#include <QtCore/QTimer>
#include <QtGui/QKeyEvent>
#include <QtGui/QMouseEvent>
#include <algorithm>
#include <cmath>
InputBindingDialog::InputBindingDialog(QtHostInterface* host_interface, std::string section_name, std::string key_name,
std::vector<std::string> bindings, QWidget* parent)
: QDialog(parent), m_host_interface(host_interface), m_section_name(std::move(section_name)),
m_key_name(std::move(key_name)), m_bindings(std::move(bindings))
{
m_ui.setupUi(this);
m_ui.title->setText(
tr("Bindings for %1 %2").arg(QString::fromStdString(m_section_name)).arg(QString::fromStdString(m_key_name)));
connect(m_ui.addBinding, &QPushButton::clicked, this, &InputBindingDialog::onAddBindingButtonClicked);
connect(m_ui.removeBinding, &QPushButton::clicked, this, &InputBindingDialog::onRemoveBindingButtonClicked);
connect(m_ui.clearBindings, &QPushButton::clicked, this, &InputBindingDialog::onClearBindingsButtonClicked);
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, [this]() { done(0); });
updateList();
}
InputBindingDialog::~InputBindingDialog()
{
Q_ASSERT(!isListeningForInput());
}
bool InputBindingDialog::eventFilter(QObject* watched, QEvent* event)
{
const QEvent::Type event_type = event->type();
if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonRelease ||
event_type == QEvent::MouseButtonDblClick)
{
return true;
}
return false;
}
void InputBindingDialog::onInputListenTimerTimeout()
{
m_input_listen_remaining_seconds--;
if (m_input_listen_remaining_seconds == 0)
{
stopListeningForInput();
return;
}
m_ui.status->setText(tr("Push Button/Axis... [%1]").arg(m_input_listen_remaining_seconds));
}
void InputBindingDialog::startListeningForInput(u32 timeout_in_seconds)
{
m_input_listen_timer = new QTimer(this);
m_input_listen_timer->setSingleShot(false);
m_input_listen_timer->start(1000);
m_input_listen_timer->connect(m_input_listen_timer, &QTimer::timeout, this,
&InputBindingDialog::onInputListenTimerTimeout);
m_input_listen_remaining_seconds = timeout_in_seconds;
m_ui.status->setText(tr("Push Button/Axis... [%1]").arg(m_input_listen_remaining_seconds));
m_ui.addBinding->setEnabled(false);
m_ui.removeBinding->setEnabled(false);
m_ui.clearBindings->setEnabled(false);
m_ui.buttonBox->setEnabled(false);
installEventFilter(this);
grabKeyboard();
grabMouse();
}
void InputBindingDialog::stopListeningForInput()
{
m_ui.status->clear();
m_ui.addBinding->setEnabled(true);
m_ui.removeBinding->setEnabled(true);
m_ui.clearBindings->setEnabled(true);
m_ui.buttonBox->setEnabled(true);
delete m_input_listen_timer;
m_input_listen_timer = nullptr;
releaseMouse();
releaseKeyboard();
removeEventFilter(this);
}
void InputBindingDialog::addNewBinding(std::string new_binding)
{
if (std::find(m_bindings.begin(), m_bindings.end(), new_binding) != m_bindings.end())
return;
m_ui.bindingList->addItem(QString::fromStdString(new_binding));
m_bindings.push_back(std::move(new_binding));
saveListToSettings();
}
void InputBindingDialog::onAddBindingButtonClicked()
{
if (isListeningForInput())
stopListeningForInput();
startListeningForInput(TIMEOUT_FOR_BINDING);
}
void InputBindingDialog::onRemoveBindingButtonClicked()
{
const int row = m_ui.bindingList->currentRow();
if (row < 0 || static_cast<size_t>(row) >= m_bindings.size())
return;
m_bindings.erase(m_bindings.begin() + row);
delete m_ui.bindingList->takeItem(row);
saveListToSettings();
}
void InputBindingDialog::onClearBindingsButtonClicked()
{
m_bindings.clear();
m_ui.bindingList->clear();
saveListToSettings();
}
void InputBindingDialog::updateList()
{
m_ui.bindingList->clear();
for (const std::string& binding : m_bindings)
m_ui.bindingList->addItem(QString::fromStdString(binding));
}
void InputBindingDialog::saveListToSettings()
{
if (!m_bindings.empty())
m_host_interface->SetStringListSettingValue(m_section_name.c_str(), m_key_name.c_str(), m_bindings);
else
m_host_interface->RemoveSettingValue(m_section_name.c_str(), m_key_name.c_str());
m_host_interface->updateInputMap();
}
InputButtonBindingDialog::InputButtonBindingDialog(QtHostInterface* host_interface, std::string section_name,
std::string key_name, std::vector<std::string> bindings,
QWidget* parent)
: InputBindingDialog(host_interface, std::move(section_name), std::move(key_name), std::move(bindings), parent)
{
}
InputButtonBindingDialog::~InputButtonBindingDialog()
{
if (isListeningForInput())
InputButtonBindingDialog::stopListeningForInput();
}
bool InputButtonBindingDialog::eventFilter(QObject* watched, QEvent* event)
{
const QEvent::Type event_type = event->type();
// if the key is being released, set the input
if (event_type == QEvent::KeyRelease)
{
addNewBinding(std::move(m_new_binding_value));
stopListeningForInput();
return true;
}
else if (event_type == QEvent::KeyPress)
{
QString binding = QtUtils::KeyEventToString(static_cast<QKeyEvent*>(event));
if (!binding.isEmpty())
m_new_binding_value = QStringLiteral("Keyboard/%1").arg(binding).toStdString();
return true;
}
else if (event_type == QEvent::MouseButtonRelease)
{
const u32 button_mask = static_cast<u32>(static_cast<const QMouseEvent*>(event)->button());
const u32 button_index = (button_mask == 0u) ? 0 : CountTrailingZeros(button_mask);
m_new_binding_value = StringUtil::StdStringFromFormat("Mouse/Button%d", button_index + 1);
addNewBinding(std::move(m_new_binding_value));
stopListeningForInput();
return true;
}
return InputBindingDialog::eventFilter(watched, event);
}
void InputButtonBindingDialog::hookControllerInput()
{
ControllerInterface* controller_interface = m_host_interface->getControllerInterface();
if (!controller_interface)
return;
controller_interface->SetHook([this](const ControllerInterface::Hook& ei) {
if (ei.type == ControllerInterface::Hook::Type::Axis)
{
// wait until it's at least half pushed so we don't get confused between axises with small movement
if (std::abs(ei.value) < 0.5f)
return ControllerInterface::Hook::CallbackResult::ContinueMonitoring;
// TODO: this probably should consider the "last value"
QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index),
Q_ARG(int, ei.button_or_axis_number), Q_ARG(bool, ei.value > 0));
return ControllerInterface::Hook::CallbackResult::StopMonitoring;
}
else if (ei.type == ControllerInterface::Hook::Type::Button && ei.value > 0.0f)
{
QMetaObject::invokeMethod(this, "bindToControllerButton", Q_ARG(int, ei.controller_index),
Q_ARG(int, ei.button_or_axis_number));
return ControllerInterface::Hook::CallbackResult::StopMonitoring;
}
return ControllerInterface::Hook::CallbackResult::ContinueMonitoring;
});
}
void InputButtonBindingDialog::unhookControllerInput()
{
ControllerInterface* controller_interface = m_host_interface->getControllerInterface();
if (!controller_interface)
return;
controller_interface->ClearHook();
}
void InputButtonBindingDialog::bindToControllerAxis(int controller_index, int axis_index, bool positive)
{
std::string binding =
StringUtil::StdStringFromFormat("Controller%d/%cAxis%d", controller_index, positive ? '+' : '-', axis_index);
addNewBinding(std::move(binding));
stopListeningForInput();
}
void InputButtonBindingDialog::bindToControllerButton(int controller_index, int button_index)
{
std::string binding = StringUtil::StdStringFromFormat("Controller%d/Button%d", controller_index, button_index);
addNewBinding(std::move(binding));
stopListeningForInput();
}
void InputButtonBindingDialog::startListeningForInput(u32 timeout_in_seconds)
{
InputBindingDialog::startListeningForInput(timeout_in_seconds);
hookControllerInput();
}
void InputButtonBindingDialog::stopListeningForInput()
{
unhookControllerInput();
InputBindingDialog::stopListeningForInput();
}
InputAxisBindingDialog::InputAxisBindingDialog(QtHostInterface* host_interface, std::string section_name,
std::string key_name, std::vector<std::string> bindings, QWidget* parent)
: InputBindingDialog(host_interface, std::move(section_name), std::move(key_name), std::move(bindings), parent)
{
}
InputAxisBindingDialog::~InputAxisBindingDialog()
{
if (isListeningForInput())
InputAxisBindingDialog::stopListeningForInput();
}
void InputAxisBindingDialog::hookControllerInput()
{
ControllerInterface* controller_interface = m_host_interface->getControllerInterface();
if (!controller_interface)
return;
controller_interface->SetHook([this](const ControllerInterface::Hook& ei) {
if (ei.type == ControllerInterface::Hook::Type::Axis)
{
// wait until it's at least half pushed so we don't get confused between axises with small movement
if (std::abs(ei.value) < 0.5f)
return ControllerInterface::Hook::CallbackResult::ContinueMonitoring;
QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index),
Q_ARG(int, ei.button_or_axis_number));
return ControllerInterface::Hook::CallbackResult::StopMonitoring;
}
return ControllerInterface::Hook::CallbackResult::ContinueMonitoring;
});
}
void InputAxisBindingDialog::unhookControllerInput()
{
ControllerInterface* controller_interface = m_host_interface->getControllerInterface();
if (!controller_interface)
return;
controller_interface->ClearHook();
}
void InputAxisBindingDialog::bindToControllerAxis(int controller_index, int axis_index)
{
std::string binding = StringUtil::StdStringFromFormat("Controller%d/Axis%d", controller_index, axis_index);
addNewBinding(std::move(binding));
stopListeningForInput();
}
void InputAxisBindingDialog::startListeningForInput(u32 timeout_in_seconds)
{
InputBindingDialog::startListeningForInput(timeout_in_seconds);
hookControllerInput();
}
void InputAxisBindingDialog::stopListeningForInput()
{
unhookControllerInput();
InputBindingDialog::stopListeningForInput();
}

View file

@ -0,0 +1,95 @@
#pragma once
#include "common/types.h"
#include "ui_inputbindingdialog.h"
#include <QtWidgets/QDialog>
#include <string>
#include <vector>
class QtHostInterface;
class InputBindingDialog : public QDialog
{
Q_OBJECT
public:
InputBindingDialog(QtHostInterface* host_interface, std::string section_name, std::string key_name,
std::vector<std::string> bindings, QWidget* parent);
~InputBindingDialog();
protected Q_SLOTS:
void onAddBindingButtonClicked();
void onRemoveBindingButtonClicked();
void onClearBindingsButtonClicked();
void onInputListenTimerTimeout();
protected:
enum : u32
{
TIMEOUT_FOR_BINDING = 5
};
virtual bool eventFilter(QObject* watched, QEvent* event) override;
virtual void startListeningForInput(u32 timeout_in_seconds);
virtual void stopListeningForInput();
bool isListeningForInput() const { return m_input_listen_timer != nullptr; }
void addNewBinding(std::string new_binding);
void updateList();
void saveListToSettings();
Ui::InputBindingDialog m_ui;
QtHostInterface* m_host_interface;
std::string m_section_name;
std::string m_key_name;
std::vector<std::string> m_bindings;
std::string m_new_binding_value;
QTimer* m_input_listen_timer = nullptr;
u32 m_input_listen_remaining_seconds = 0;
};
class InputButtonBindingDialog : public InputBindingDialog
{
Q_OBJECT
public:
InputButtonBindingDialog(QtHostInterface* host_interface, std::string section_name, std::string key_name,
std::vector<std::string> bindings, QWidget* parent);
~InputButtonBindingDialog();
protected:
bool eventFilter(QObject* watched, QEvent* event) override;
private Q_SLOTS:
void bindToControllerAxis(int controller_index, int axis_index, bool positive);
void bindToControllerButton(int controller_index, int button_index);
protected:
void startListeningForInput(u32 timeout_in_seconds) override;
void stopListeningForInput() override;
void hookControllerInput();
void unhookControllerInput();
};
class InputAxisBindingDialog : public InputBindingDialog
{
Q_OBJECT
public:
InputAxisBindingDialog(QtHostInterface* host_interface, std::string section_name, std::string key_name,
std::vector<std::string> bindings, QWidget* parent);
~InputAxisBindingDialog();
private Q_SLOTS:
void bindToControllerAxis(int controller_index, int axis_index);
protected:
void startListeningForInput(u32 timeout_in_seconds) override;
void stopListeningForInput() override;
void hookControllerInput();
void unhookControllerInput();
};

View file

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>InputBindingDialog</class>
<widget class="QDialog" name="InputBindingDialog">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>533</width>
<height>283</height>
</rect>
</property>
<property name="windowTitle">
<string>Edit Bindings</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="title">
<property name="text">
<string>Bindings for Controller0/ButtonCircle</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="bindingList"/>
</item>
<item>
<widget class="QLabel" name="status">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="addBinding">
<property name="text">
<string>Add Binding</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeBinding">
<property name="text">
<string>Remove Binding</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clearBindings">
<property name="text">
<string>Clear Bindings</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -3,6 +3,7 @@
#include "common/string_util.h" #include "common/string_util.h"
#include "core/settings.h" #include "core/settings.h"
#include "frontend-common/controller_interface.h" #include "frontend-common/controller_interface.h"
#include "inputbindingdialog.h"
#include "qthostinterface.h" #include "qthostinterface.h"
#include "qtutils.h" #include "qtutils.h"
#include <QtCore/QTimer> #include <QtCore/QTimer>
@ -15,8 +16,9 @@ InputBindingWidget::InputBindingWidget(QtHostInterface* host_interface, std::str
: QPushButton(parent), m_host_interface(host_interface), m_section_name(std::move(section_name)), : QPushButton(parent), m_host_interface(host_interface), m_section_name(std::move(section_name)),
m_key_name(std::move(key_name)) m_key_name(std::move(key_name))
{ {
m_current_binding_value = m_host_interface->GetStringSettingValue(m_section_name.c_str(), m_key_name.c_str()); m_bindings = m_host_interface->GetSettingStringList(m_section_name.c_str(), m_key_name.c_str());
setText(QString::fromStdString(m_current_binding_value)); updateText();
setMinimumWidth(150); setMinimumWidth(150);
setMaximumWidth(150); setMaximumWidth(150);
@ -28,6 +30,16 @@ InputBindingWidget::~InputBindingWidget()
Q_ASSERT(!isListeningForInput()); Q_ASSERT(!isListeningForInput());
} }
void InputBindingWidget::updateText()
{
if (m_bindings.empty())
setText(QString());
else if (m_bindings.size() > 1)
setText(tr("%1 bindings").arg(m_bindings.size()));
else
setText(QString::fromStdString(m_bindings[0]));
}
void InputBindingWidget::beginRebindAll() void InputBindingWidget::beginRebindAll()
{ {
m_is_binding_all = true; m_is_binding_all = true;
@ -50,6 +62,21 @@ bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event)
return false; return false;
} }
bool InputBindingWidget::event(QEvent* event)
{
if (event->type() == QEvent::MouseButtonRelease)
{
QMouseEvent* mev = static_cast<QMouseEvent*>(event);
if (mev->button() == Qt::LeftButton && mev->modifiers() & Qt::ShiftModifier)
{
openDialog();
return false;
}
}
return QPushButton::event(event);
}
void InputBindingWidget::mouseReleaseEvent(QMouseEvent* e) void InputBindingWidget::mouseReleaseEvent(QMouseEvent* e)
{ {
if (e->button() == Qt::RightButton) if (e->button() == Qt::RightButton)
@ -69,26 +96,32 @@ void InputBindingWidget::setNewBinding()
m_host_interface->SetStringSettingValue(m_section_name.c_str(), m_key_name.c_str(), m_new_binding_value.c_str()); m_host_interface->SetStringSettingValue(m_section_name.c_str(), m_key_name.c_str(), m_new_binding_value.c_str());
m_host_interface->updateInputMap(); m_host_interface->updateInputMap();
m_current_binding_value = std::move(m_new_binding_value); m_bindings.clear();
m_new_binding_value.clear(); m_bindings.push_back(std::move(m_new_binding_value));
} }
void InputBindingWidget::clearBinding() void InputBindingWidget::clearBinding()
{ {
m_current_binding_value.clear(); m_bindings.clear();
m_host_interface->RemoveSettingValue(m_section_name.c_str(), m_key_name.c_str()); m_host_interface->RemoveSettingValue(m_section_name.c_str(), m_key_name.c_str());
m_host_interface->updateInputMap(); m_host_interface->updateInputMap();
setText(QString::fromStdString(m_current_binding_value)); updateText();
} }
void InputBindingWidget::reloadBinding() void InputBindingWidget::reloadBinding()
{ {
m_current_binding_value = m_host_interface->GetStringSettingValue(m_section_name.c_str(), m_key_name.c_str()); m_bindings = m_host_interface->GetSettingStringList(m_section_name.c_str(), m_key_name.c_str());
setText(QString::fromStdString(m_current_binding_value)); updateText();
} }
void InputBindingWidget::onClicked() void InputBindingWidget::onClicked()
{ {
if (m_bindings.size() > 1)
{
openDialog();
return;
}
if (isListeningForInput()) if (isListeningForInput())
stopListeningForInput(); stopListeningForInput();
@ -125,7 +158,7 @@ void InputBindingWidget::startListeningForInput(u32 timeout_in_seconds)
void InputBindingWidget::stopListeningForInput() void InputBindingWidget::stopListeningForInput()
{ {
setText(QString::fromStdString(m_current_binding_value)); updateText();
delete m_input_listen_timer; delete m_input_listen_timer;
m_input_listen_timer = nullptr; m_input_listen_timer = nullptr;
@ -138,6 +171,8 @@ void InputBindingWidget::stopListeningForInput()
m_is_binding_all = false; m_is_binding_all = false;
} }
void InputBindingWidget::openDialog() {}
InputButtonBindingWidget::InputButtonBindingWidget(QtHostInterface* host_interface, std::string section_name, InputButtonBindingWidget::InputButtonBindingWidget(QtHostInterface* host_interface, std::string section_name,
std::string key_name, QWidget* parent) std::string key_name, QWidget* parent)
: InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), parent) : InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), parent)
@ -247,6 +282,14 @@ void InputButtonBindingWidget::stopListeningForInput()
InputBindingWidget::stopListeningForInput(); InputBindingWidget::stopListeningForInput();
} }
void InputButtonBindingWidget::openDialog()
{
InputButtonBindingDialog binding_dialog(m_host_interface, m_section_name, m_key_name, m_bindings,
QtUtils::GetRootWidget(this));
binding_dialog.exec();
reloadBinding();
}
InputAxisBindingWidget::InputAxisBindingWidget(QtHostInterface* host_interface, std::string section_name, InputAxisBindingWidget::InputAxisBindingWidget(QtHostInterface* host_interface, std::string section_name,
std::string key_name, QWidget* parent) std::string key_name, QWidget* parent)
: InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), parent) : InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), parent)
@ -309,6 +352,14 @@ void InputAxisBindingWidget::stopListeningForInput()
InputBindingWidget::stopListeningForInput(); InputBindingWidget::stopListeningForInput();
} }
void InputAxisBindingWidget::openDialog()
{
InputAxisBindingDialog binding_dialog(m_host_interface, m_section_name, m_key_name, m_bindings,
QtUtils::GetRootWidget(this));
binding_dialog.exec();
reloadBinding();
}
InputRumbleBindingWidget::InputRumbleBindingWidget(QtHostInterface* host_interface, std::string section_name, InputRumbleBindingWidget::InputRumbleBindingWidget(QtHostInterface* host_interface, std::string section_name,
std::string key_name, QWidget* parent) std::string key_name, QWidget* parent)
: InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), parent) : InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), parent)

View file

@ -34,18 +34,21 @@ protected:
}; };
virtual bool eventFilter(QObject* watched, QEvent* event) override; virtual bool eventFilter(QObject* watched, QEvent* event) override;
virtual bool event(QEvent* event) override;
virtual void mouseReleaseEvent(QMouseEvent* e) override; virtual void mouseReleaseEvent(QMouseEvent* e) override;
virtual void startListeningForInput(u32 timeout_in_seconds); virtual void startListeningForInput(u32 timeout_in_seconds);
virtual void stopListeningForInput(); virtual void stopListeningForInput();
virtual void openDialog();
bool isListeningForInput() const { return m_input_listen_timer != nullptr; } bool isListeningForInput() const { return m_input_listen_timer != nullptr; }
void setNewBinding(); void setNewBinding();
void updateText();
QtHostInterface* m_host_interface; QtHostInterface* m_host_interface;
std::string m_section_name; std::string m_section_name;
std::string m_key_name; std::string m_key_name;
std::string m_current_binding_value; std::vector<std::string> m_bindings;
std::string m_new_binding_value; std::string m_new_binding_value;
QTimer* m_input_listen_timer = nullptr; QTimer* m_input_listen_timer = nullptr;
u32 m_input_listen_remaining_seconds = 0; u32 m_input_listen_remaining_seconds = 0;
@ -59,7 +62,8 @@ class InputButtonBindingWidget : public InputBindingWidget
Q_OBJECT Q_OBJECT
public: public:
InputButtonBindingWidget(QtHostInterface* host_interface, std::string section_name, std::string key_name, QWidget* parent); InputButtonBindingWidget(QtHostInterface* host_interface, std::string section_name, std::string key_name,
QWidget* parent);
~InputButtonBindingWidget(); ~InputButtonBindingWidget();
protected: protected:
@ -72,6 +76,7 @@ private Q_SLOTS:
protected: protected:
void startListeningForInput(u32 timeout_in_seconds) override; void startListeningForInput(u32 timeout_in_seconds) override;
void stopListeningForInput() override; void stopListeningForInput() override;
void openDialog() override;
void hookControllerInput(); void hookControllerInput();
void unhookControllerInput(); void unhookControllerInput();
}; };
@ -81,7 +86,8 @@ class InputAxisBindingWidget : public InputBindingWidget
Q_OBJECT Q_OBJECT
public: public:
InputAxisBindingWidget(QtHostInterface* host_interface, std::string section_name, std::string key_name, QWidget* parent); InputAxisBindingWidget(QtHostInterface* host_interface, std::string section_name, std::string key_name,
QWidget* parent);
~InputAxisBindingWidget(); ~InputAxisBindingWidget();
private Q_SLOTS: private Q_SLOTS:
@ -90,6 +96,7 @@ private Q_SLOTS:
protected: protected:
void startListeningForInput(u32 timeout_in_seconds) override; void startListeningForInput(u32 timeout_in_seconds) override;
void stopListeningForInput() override; void stopListeningForInput() override;
void openDialog() override;
void hookControllerInput(); void hookControllerInput();
void unhookControllerInput(); void unhookControllerInput();
}; };
@ -99,7 +106,8 @@ class InputRumbleBindingWidget : public InputBindingWidget
Q_OBJECT Q_OBJECT
public: public:
InputRumbleBindingWidget(QtHostInterface* host_interface, std::string section_name, std::string key_name, QWidget* parent); InputRumbleBindingWidget(QtHostInterface* host_interface, std::string section_name, std::string key_name,
QWidget* parent);
~InputRumbleBindingWidget(); ~InputRumbleBindingWidget();
private Q_SLOTS: private Q_SLOTS:

View file

@ -25,12 +25,14 @@ static constexpr std::array<const char*, static_cast<int>(SettingsDialog::Catego
"<strong>Hotkey Settings</strong><hr>Binding a hotkey allows you to trigger events such as a resetting or taking " "<strong>Hotkey Settings</strong><hr>Binding a hotkey allows you to trigger events such as a resetting or taking "
"screenshots at the press of a key/controller button. Hotkey titles are self-explanatory. Clicking a binding will " "screenshots at the press of a key/controller button. Hotkey titles are self-explanatory. Clicking a binding will "
"start a countdown, in which case you should press the key or controller button/axis you wish to bind. If no button " "start a countdown, in which case you should press the key or controller button/axis you wish to bind. If no button "
"is pressed and the timer lapses, the binding will be unchanged. To clear a binding, right-click the button.", "is pressed and the timer lapses, the binding will be unchanged. To clear a binding, right-click the button. To "
"bind multiple buttons, hold Shift and click the button.",
"<strong>Controller Settings</strong><hr>This page lets you choose the type of controller you wish to simulate for " "<strong>Controller Settings</strong><hr>This page lets you choose the type of controller you wish to simulate for "
"the console, and rebind the keys or host game controller buttons to your choosing. Clicking a binding will start a " "the console, and rebind the keys or host game controller buttons to your choosing. Clicking a binding will start a "
"countdown, in which case you should press the key or controller button/axis you wish to bind. (For rumble, press " "countdown, in which case you should press the key or controller button/axis you wish to bind. (For rumble, press "
"any button/axis on the controller you wish to send rumble to.) If no button is pressed and the timer lapses, " "any button/axis on the controller you wish to send rumble to.) If no button is pressed and the timer lapses, "
"the binding will be unchanged. To clear a binding, right-click the button.", "the binding will be unchanged. To clear a binding, right-click the button. To bind multiple buttons, hold Shift "
"and click the button.",
"<strong>Memory Card Settings</strong><hr>This page lets you control what mode the memory card emulation will " "<strong>Memory Card Settings</strong><hr>This page lets you control what mode the memory card emulation will "
"function in, and where the images for these cards will be stored on disk.", "function in, and where the images for these cards will be stored on disk.",
"<strong>GPU Settings</strong><hr>These options control the simulation of the GPU in the console. Various " "<strong>GPU Settings</strong><hr>These options control the simulation of the GPU in the console. Various "