diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt
index 628b89bcf..eaaa8e068 100644
--- a/src/duckstation-qt/CMakeLists.txt
+++ b/src/duckstation-qt/CMakeLists.txt
@@ -33,6 +33,9 @@ add_executable(duckstation-qt
gpusettingswidget.ui
hotkeysettingswidget.cpp
hotkeysettingswidget.h
+ inputbindingdialog.cpp
+ inputbindingdialog.h
+ inputbindingdialog.ui
inputbindingwidgets.cpp
inputbindingwidgets.h
main.cpp
diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj
index 40e647562..662700f55 100644
--- a/src/duckstation-qt/duckstation-qt.vcxproj
+++ b/src/duckstation-qt/duckstation-qt.vcxproj
@@ -42,6 +42,7 @@
+
@@ -68,6 +69,7 @@
+
@@ -148,6 +150,7 @@
+
@@ -171,6 +174,11 @@
+
+
+ Document
+
+
diff --git a/src/duckstation-qt/duckstation-qt.vcxproj.filters b/src/duckstation-qt/duckstation-qt.vcxproj.filters
index 6ae320217..b96180b5e 100644
--- a/src/duckstation-qt/duckstation-qt.vcxproj.filters
+++ b/src/duckstation-qt/duckstation-qt.vcxproj.filters
@@ -40,11 +40,13 @@
+
+
@@ -95,4 +97,7 @@
+
+
+
\ No newline at end of file
diff --git a/src/duckstation-qt/inputbindingdialog.cpp b/src/duckstation-qt/inputbindingdialog.cpp
new file mode 100644
index 000000000..56b261152
--- /dev/null
+++ b/src/duckstation-qt/inputbindingdialog.cpp
@@ -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
+#include
+#include
+#include
+#include
+
+InputBindingDialog::InputBindingDialog(QtHostInterface* host_interface, std::string section_name, std::string key_name,
+ std::vector 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(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 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(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(static_cast(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 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();
+}
diff --git a/src/duckstation-qt/inputbindingdialog.h b/src/duckstation-qt/inputbindingdialog.h
new file mode 100644
index 000000000..84ee7efe0
--- /dev/null
+++ b/src/duckstation-qt/inputbindingdialog.h
@@ -0,0 +1,95 @@
+#pragma once
+#include "common/types.h"
+#include "ui_inputbindingdialog.h"
+#include
+#include
+#include
+
+class QtHostInterface;
+
+class InputBindingDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ InputBindingDialog(QtHostInterface* host_interface, std::string section_name, std::string key_name,
+ std::vector 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 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 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 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();
+};
diff --git a/src/duckstation-qt/inputbindingdialog.ui b/src/duckstation-qt/inputbindingdialog.ui
new file mode 100644
index 000000000..e9d720d6e
--- /dev/null
+++ b/src/duckstation-qt/inputbindingdialog.ui
@@ -0,0 +1,89 @@
+
+
+ InputBindingDialog
+
+
+ Qt::WindowModal
+
+
+
+ 0
+ 0
+ 533
+ 283
+
+
+
+ Edit Bindings
+
+
+ true
+
+
+ -
+
+
+ Bindings for Controller0/ButtonCircle
+
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Add Binding
+
+
+
+ -
+
+
+ Remove Binding
+
+
+
+ -
+
+
+ Clear Bindings
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ QDialogButtonBox::Close
+
+
+
+
+
+
+
+
+
+
diff --git a/src/duckstation-qt/inputbindingwidgets.cpp b/src/duckstation-qt/inputbindingwidgets.cpp
index a6853c9e3..ec97936dc 100644
--- a/src/duckstation-qt/inputbindingwidgets.cpp
+++ b/src/duckstation-qt/inputbindingwidgets.cpp
@@ -3,6 +3,7 @@
#include "common/string_util.h"
#include "core/settings.h"
#include "frontend-common/controller_interface.h"
+#include "inputbindingdialog.h"
#include "qthostinterface.h"
#include "qtutils.h"
#include
@@ -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)),
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());
- setText(QString::fromStdString(m_current_binding_value));
+ m_bindings = m_host_interface->GetSettingStringList(m_section_name.c_str(), m_key_name.c_str());
+ updateText();
+
setMinimumWidth(150);
setMaximumWidth(150);
@@ -28,6 +30,16 @@ InputBindingWidget::~InputBindingWidget()
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()
{
m_is_binding_all = true;
@@ -50,6 +62,21 @@ bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event)
return false;
}
+bool InputBindingWidget::event(QEvent* event)
+{
+ if (event->type() == QEvent::MouseButtonRelease)
+ {
+ QMouseEvent* mev = static_cast(event);
+ if (mev->button() == Qt::LeftButton && mev->modifiers() & Qt::ShiftModifier)
+ {
+ openDialog();
+ return false;
+ }
+ }
+
+ return QPushButton::event(event);
+}
+
void InputBindingWidget::mouseReleaseEvent(QMouseEvent* e)
{
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->updateInputMap();
- m_current_binding_value = std::move(m_new_binding_value);
- m_new_binding_value.clear();
+ m_bindings.clear();
+ m_bindings.push_back(std::move(m_new_binding_value));
}
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->updateInputMap();
- setText(QString::fromStdString(m_current_binding_value));
+ updateText();
}
void InputBindingWidget::reloadBinding()
{
- m_current_binding_value = m_host_interface->GetStringSettingValue(m_section_name.c_str(), m_key_name.c_str());
- setText(QString::fromStdString(m_current_binding_value));
+ m_bindings = m_host_interface->GetSettingStringList(m_section_name.c_str(), m_key_name.c_str());
+ updateText();
}
void InputBindingWidget::onClicked()
{
+ if (m_bindings.size() > 1)
+ {
+ openDialog();
+ return;
+ }
+
if (isListeningForInput())
stopListeningForInput();
@@ -125,7 +158,7 @@ void InputBindingWidget::startListeningForInput(u32 timeout_in_seconds)
void InputBindingWidget::stopListeningForInput()
{
- setText(QString::fromStdString(m_current_binding_value));
+ updateText();
delete m_input_listen_timer;
m_input_listen_timer = nullptr;
@@ -138,6 +171,8 @@ void InputBindingWidget::stopListeningForInput()
m_is_binding_all = false;
}
+void InputBindingWidget::openDialog() {}
+
InputButtonBindingWidget::InputButtonBindingWidget(QtHostInterface* host_interface, std::string section_name,
std::string key_name, QWidget* parent)
: InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), parent)
@@ -247,6 +282,14 @@ void InputButtonBindingWidget::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,
std::string key_name, QWidget* parent)
: InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), parent)
@@ -309,6 +352,14 @@ void InputAxisBindingWidget::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,
std::string key_name, QWidget* parent)
: InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), parent)
diff --git a/src/duckstation-qt/inputbindingwidgets.h b/src/duckstation-qt/inputbindingwidgets.h
index 2e826b2e2..9c3bc338d 100644
--- a/src/duckstation-qt/inputbindingwidgets.h
+++ b/src/duckstation-qt/inputbindingwidgets.h
@@ -34,18 +34,21 @@ protected:
};
virtual bool eventFilter(QObject* watched, QEvent* event) override;
+ virtual bool event(QEvent* event) override;
virtual void mouseReleaseEvent(QMouseEvent* e) override;
virtual void startListeningForInput(u32 timeout_in_seconds);
virtual void stopListeningForInput();
+ virtual void openDialog();
bool isListeningForInput() const { return m_input_listen_timer != nullptr; }
void setNewBinding();
+ void updateText();
QtHostInterface* m_host_interface;
std::string m_section_name;
std::string m_key_name;
- std::string m_current_binding_value;
+ std::vector m_bindings;
std::string m_new_binding_value;
QTimer* m_input_listen_timer = nullptr;
u32 m_input_listen_remaining_seconds = 0;
@@ -59,7 +62,8 @@ class InputButtonBindingWidget : public InputBindingWidget
Q_OBJECT
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();
protected:
@@ -72,6 +76,7 @@ private Q_SLOTS:
protected:
void startListeningForInput(u32 timeout_in_seconds) override;
void stopListeningForInput() override;
+ void openDialog() override;
void hookControllerInput();
void unhookControllerInput();
};
@@ -81,7 +86,8 @@ class InputAxisBindingWidget : public InputBindingWidget
Q_OBJECT
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();
private Q_SLOTS:
@@ -90,6 +96,7 @@ private Q_SLOTS:
protected:
void startListeningForInput(u32 timeout_in_seconds) override;
void stopListeningForInput() override;
+ void openDialog() override;
void hookControllerInput();
void unhookControllerInput();
};
@@ -99,7 +106,8 @@ class InputRumbleBindingWidget : public InputBindingWidget
Q_OBJECT
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();
private Q_SLOTS:
diff --git a/src/duckstation-qt/settingsdialog.cpp b/src/duckstation-qt/settingsdialog.cpp
index d1ab694bb..fa749b867 100644
--- a/src/duckstation-qt/settingsdialog.cpp
+++ b/src/duckstation-qt/settingsdialog.cpp
@@ -25,12 +25,14 @@ static constexpr std::array(SettingsDialog::Catego
"Hotkey Settings
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 "
"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.",
"Controller Settings
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 "
"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, "
- "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.",
"Memory Card Settings
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.",
"GPU Settings
These options control the simulation of the GPU in the console. Various "