diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt index 7b1c1c6b56..50b6d5a5a1 100644 --- a/pcsx2-qt/CMakeLists.txt +++ b/pcsx2-qt/CMakeLists.txt @@ -56,6 +56,8 @@ target_sources(pcsx2-qt PRIVATE Settings/ControllerGlobalSettingsWidget.cpp Settings/ControllerGlobalSettingsWidget.h Settings/ControllerGlobalSettingsWidget.ui + Settings/ControllerMacroEditWidget.ui + Settings/ControllerMacroWidget.ui Settings/ControllerSettingsDialog.cpp Settings/ControllerSettingsDialog.h Settings/ControllerSettingsDialog.ui diff --git a/pcsx2-qt/Settings/ControllerBindingWidget.ui b/pcsx2-qt/Settings/ControllerBindingWidget.ui index 327d289d3c..60f76d6f7d 100644 --- a/pcsx2-qt/Settings/ControllerBindingWidget.ui +++ b/pcsx2-qt/Settings/ControllerBindingWidget.ui @@ -7,13 +7,13 @@ 0 0 833 - 560 + 617 Form - + 0 @@ -31,9 +31,73 @@ Controller Type - + - + + + + + + + + Bindings + + + true + + + + .. + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + Settings + + + + .. + + + true + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + Macros + + + + .. + + + true + + + Qt::ToolButtonTextBesideIcon + + + true + + + + @@ -51,25 +115,31 @@ - + - Automatic binding + Automatic Mapping .. + + Qt::ToolButtonTextBesideIcon + - + - Clear Bindings + Clear Mapping .. + + Qt::ToolButtonTextBesideIcon + @@ -77,6 +147,9 @@ + + + diff --git a/pcsx2-qt/Settings/ControllerBindingWidget_DualShock2.ui b/pcsx2-qt/Settings/ControllerBindingWidget_DualShock2.ui index 030e17d480..e748245d28 100644 --- a/pcsx2-qt/Settings/ControllerBindingWidget_DualShock2.ui +++ b/pcsx2-qt/Settings/ControllerBindingWidget_DualShock2.ui @@ -415,25 +415,6 @@ - - - - - 80 - 0 - - - - x - - - 3.000000000000000 - - - 0.100000000000000 - - - @@ -488,74 +469,6 @@ - - - - Select - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - - 100 - 16777215 - - - - PushButton - - - - - - - - - - Start - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - - 100 - 16777215 - - - - PushButton - - - - - - @@ -624,62 +537,6 @@ - - - - Pressure Modifier - - - - - - - 80 - 0 - - - - x - - - 1.000000000000000 - - - 0.010000000000000 - - - 0.500000000000000 - - - - - - - - 0 - 0 - - - - - 100 - 0 - - - - - 16777215 - 16777215 - - - - PushButton - - - - - - @@ -714,6 +571,74 @@ + + + + Start + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Select + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 16777215 + + + + PushButton + + + + + + @@ -1093,19 +1018,6 @@ - - - - x - - - 3.000000000000000 - - - 0.100000000000000 - - - @@ -1181,6 +1093,19 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -1221,19 +1146,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -1274,59 +1186,63 @@ - - + + - Analog Sensitivity + Pressure Modifier - - - - - 200 + + + + + + 80 + 0 + - - Qt::Horizontal + + x + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.500000000000000 - - + + + + + 0 + 0 + + + + + 100 + 0 + + + + + 16777215 + 16777215 + + - 100% + PushButton - - - - Analog Deadzone - - - - - - 100 - - - Qt::Horizontal - - - - - - - 0% - - - - - - - + Analog @@ -1366,7 +1282,6 @@ - diff --git a/pcsx2-qt/Settings/ControllerBindingWidgets.cpp b/pcsx2-qt/Settings/ControllerBindingWidgets.cpp index 2df94d01db..7792a02477 100644 --- a/pcsx2-qt/Settings/ControllerBindingWidgets.cpp +++ b/pcsx2-qt/Settings/ControllerBindingWidgets.cpp @@ -15,6 +15,7 @@ #include "PrecompiledHeader.h" +#include #include #include #include @@ -45,15 +46,18 @@ ControllerBindingWidget::ControllerBindingWidget(QWidget* parent, ControllerSett m_ui.controllerType, m_config_section, "Type", PAD::GetDefaultPadType(port)); connect(m_ui.controllerType, QOverload::of(&QComboBox::currentIndexChanged), this, &ControllerBindingWidget::onTypeChanged); - connect(m_ui.automaticBinding, &QPushButton::clicked, this, &ControllerBindingWidget::doAutomaticBinding); - connect(m_ui.clearBindings, &QPushButton::clicked, this, &ControllerBindingWidget::doClearBindings); + connect(m_ui.bindings, &QPushButton::clicked, this, &ControllerBindingWidget::onBindingsClicked); + connect(m_ui.settings, &QPushButton::clicked, this, &ControllerBindingWidget::onSettingsClicked); + connect(m_ui.macros, &QPushButton::clicked, this, &ControllerBindingWidget::onMacrosClicked); + connect(m_ui.automaticBinding, &QPushButton::clicked, this, &ControllerBindingWidget::onAutomaticBindingClicked); + connect(m_ui.clearBindings, &QPushButton::clicked, this, &ControllerBindingWidget::onClearBindingsClicked); } ControllerBindingWidget::~ControllerBindingWidget() = default; QIcon ControllerBindingWidget::getIcon() const { - return m_current_widget->getIcon(); + return m_bindings_widget->getIcon(); } void ControllerBindingWidget::populateControllerTypes() @@ -64,36 +68,101 @@ void ControllerBindingWidget::populateControllerTypes() void ControllerBindingWidget::onTypeChanged() { - const bool is_initializing = (m_current_widget == nullptr); + const bool is_initializing = (m_ui.stackedWidget->count() == 0); m_controller_type = m_dialog->getStringValue(m_config_section.c_str(), "Type", PAD::GetDefaultPadType(m_port_number)); - if (!is_initializing) + if (m_bindings_widget) { - m_ui.verticalLayout->removeWidget(m_current_widget); - delete m_current_widget; - m_current_widget = nullptr; + m_ui.stackedWidget->removeWidget(m_bindings_widget); + delete m_bindings_widget; + m_bindings_widget = nullptr; + } + if (m_settings_widget) + { + m_ui.stackedWidget->removeWidget(m_settings_widget); + delete m_settings_widget; + m_settings_widget = nullptr; + } + if (m_macros_widget) + { + m_ui.stackedWidget->removeWidget(m_macros_widget); + delete m_macros_widget; + m_macros_widget = nullptr; } - const int index = m_ui.controllerType->findData(QString::fromStdString(m_controller_type)); - if (index >= 0 && index != m_ui.controllerType->currentIndex()) - { - QSignalBlocker sb(m_ui.controllerType); - m_ui.controllerType->setCurrentIndex(index); - } + const PAD::ControllerInfo* cinfo = PAD::GetControllerInfo(m_controller_type); + const bool has_settings = (cinfo && cinfo->num_settings > 0); + const bool has_macros = (cinfo && cinfo->num_bindings > 0); + m_ui.settings->setEnabled(has_settings); + m_ui.macros->setEnabled(has_macros); if (m_controller_type == "DualShock2") - m_current_widget = ControllerBindingWidget_DualShock2::createInstance(this); + m_bindings_widget = ControllerBindingWidget_DualShock2::createInstance(this); else - m_current_widget = new ControllerBindingWidget_Base(this); + m_bindings_widget = new ControllerBindingWidget_Base(this); - m_ui.verticalLayout->addWidget(m_current_widget, 1); + m_ui.stackedWidget->addWidget(m_bindings_widget); + m_ui.stackedWidget->setCurrentWidget(m_bindings_widget); + + if (has_settings) + { + m_settings_widget = new ControllerCustomSettingsWidget(this, m_ui.stackedWidget); + m_ui.stackedWidget->addWidget(m_settings_widget); + } + + if (has_macros) + { + m_macros_widget = new ControllerMacroWidget(this); + m_ui.stackedWidget->addWidget(m_macros_widget); + } + + updateHeaderToolButtons(); // no need to do this on first init, only changes if (!is_initializing) m_dialog->updateListDescription(m_port_number, this); } -void ControllerBindingWidget::doAutomaticBinding() +void ControllerBindingWidget::updateHeaderToolButtons() +{ + const QWidget* current_widget = m_ui.stackedWidget->currentWidget(); + const QSignalBlocker bindings_sb(m_ui.bindings); + const QSignalBlocker settings_sb(m_ui.settings); + const QSignalBlocker macros_sb(m_ui.macros); + + const bool is_bindings = (current_widget == m_bindings_widget); + m_ui.bindings->setChecked(is_bindings); + m_ui.automaticBinding->setEnabled(is_bindings); + m_ui.clearBindings->setEnabled(is_bindings); + m_ui.macros->setChecked(current_widget == m_macros_widget); + m_ui.settings->setChecked((current_widget == m_settings_widget)); +} + +void ControllerBindingWidget::onBindingsClicked() +{ + m_ui.stackedWidget->setCurrentWidget(m_bindings_widget); + updateHeaderToolButtons(); +} + +void ControllerBindingWidget::onSettingsClicked() +{ + if (!m_settings_widget) + return; + + m_ui.stackedWidget->setCurrentWidget(m_settings_widget); + updateHeaderToolButtons(); +} + +void ControllerBindingWidget::onMacrosClicked() +{ + if (!m_macros_widget) + return; + + m_ui.stackedWidget->setCurrentWidget(m_macros_widget); + updateHeaderToolButtons(); +} + +void ControllerBindingWidget::onAutomaticBindingClicked() { QMenu menu(this); bool added = false; @@ -118,7 +187,7 @@ void ControllerBindingWidget::doAutomaticBinding() menu.exec(QCursor::pos()); } -void ControllerBindingWidget::doClearBindings() +void ControllerBindingWidget::onClearBindingsClicked() { if (QMessageBox::question(QtUtils::GetRootWidget(this), tr("Clear Bindings"), tr("Are you sure you want to clear all bindings for this controller? This action cannot be undone.")) != QMessageBox::Yes) @@ -174,6 +243,388 @@ void ControllerBindingWidget::saveAndRefresh() g_emu_thread->applySettings(); } + +////////////////////////////////////////////////////////////////////////// + +ControllerMacroWidget::ControllerMacroWidget(ControllerBindingWidget* parent) + : QWidget(parent) +{ + m_ui.setupUi(this); + setWindowTitle(tr("Controller Port %1 Macros").arg(parent->getPortNumber() + 1u)); + createWidgets(parent); +} + +ControllerMacroWidget::~ControllerMacroWidget() = default; + +void ControllerMacroWidget::updateListItem(u32 index) +{ + m_ui.portList->item(static_cast(index)) + ->setText(tr("Macro %1\n%2").arg(index + 1).arg(m_macros[index]->getSummary())); +} + +void ControllerMacroWidget::createWidgets(ControllerBindingWidget* parent) +{ + for (u32 i = 0; i < NUM_MACROS; i++) + { + m_macros[i] = new ControllerMacroEditWidget(this, parent, i); + m_ui.container->addWidget(m_macros[i]); + + QListWidgetItem* item = new QListWidgetItem(); + item->setIcon(QIcon::fromTheme(QStringLiteral("flashlight-line"))); + m_ui.portList->addItem(item); + updateListItem(i); + } + + m_ui.portList->setCurrentRow(0); + m_ui.container->setCurrentIndex(0); + + connect(m_ui.portList, &QListWidget::currentRowChanged, m_ui.container, &QStackedWidget::setCurrentIndex); +} + +////////////////////////////////////////////////////////////////////////// + +ControllerMacroEditWidget::ControllerMacroEditWidget(ControllerMacroWidget* parent, ControllerBindingWidget* bwidget, + u32 index) + : QWidget(parent) + , m_parent(parent) + , m_bwidget(bwidget) + , m_index(index) +{ + m_ui.setupUi(this); + + ControllerSettingsDialog* dialog = m_bwidget->getDialog(); + const std::string& section = m_bwidget->getConfigSection(); + const PAD::ControllerInfo* cinfo = PAD::GetControllerInfo(m_bwidget->getControllerType()); + if (!cinfo) + { + // Shouldn't ever happen. + return; + } + + // load binds (single string joined by &) + const std::string binds_string( + dialog->getStringValue(section.c_str(), fmt::format("Macro{}Binds", index + 1u).c_str(), "")); + const std::vector buttons_split(StringUtil::SplitString(binds_string, '&', true)); + + for (const std::string_view& button : buttons_split) + { + for (u32 i = 0; i < cinfo->num_bindings; i++) + { + if (button == cinfo->bindings[i].name) + { + m_binds.push_back(&cinfo->bindings[i]); + break; + } + } + } + + // populate list view + for (u32 i = 0; i < cinfo->num_bindings; i++) + { + const PAD::ControllerBindingInfo& bi = cinfo->bindings[i]; + if (bi.type == PAD::ControllerBindingType::Motor) + continue; + + QListWidgetItem* item = new QListWidgetItem(); + item->setText(QString::fromUtf8(bi.display_name)); + item->setCheckState((std::find(m_binds.begin(), m_binds.end(), &bi) != m_binds.end()) ? Qt::Checked : + Qt::Unchecked); + m_ui.bindList->addItem(item); + } + + m_frequency = dialog->getIntValue(section.c_str(), fmt::format("Macro{}Frequency", index + 1u).c_str(), 0); + updateFrequencyText(); + + m_ui.trigger->initialize(dialog->getProfileSettingsInterface(), section, fmt::format("Macro{}", index + 1u)); + + connect(m_ui.increaseFrequency, &QAbstractButton::clicked, this, [this]() { modFrequency(1); }); + connect(m_ui.decreateFrequency, &QAbstractButton::clicked, this, [this]() { modFrequency(-1); }); + connect(m_ui.setFrequency, &QAbstractButton::clicked, this, &ControllerMacroEditWidget::onSetFrequencyClicked); + connect(m_ui.bindList, &QListWidget::itemChanged, this, &ControllerMacroEditWidget::updateBinds); +} + +ControllerMacroEditWidget::~ControllerMacroEditWidget() = default; + +QString ControllerMacroEditWidget::getSummary() const +{ + QString str; + for (const PAD::ControllerBindingInfo* bi : m_binds) + { + if (!str.isEmpty()) + str += static_cast('/'); + str += QString::fromUtf8(bi->name); + } + return str.isEmpty() ? tr("Not Configured") : str; +} + +void ControllerMacroEditWidget::onSetFrequencyClicked() +{ + bool okay; + int new_freq = QInputDialog::getInt(this, tr("Set Frequency"), tr("Frequency: "), static_cast(m_frequency), 0, + std::numeric_limits::max(), 1, &okay); + if (!okay) + return; + + m_frequency = static_cast(new_freq); + updateFrequency(); +} + +void ControllerMacroEditWidget::modFrequency(s32 delta) +{ + if (delta < 0 && m_frequency == 0) + return; + + m_frequency = static_cast(static_cast(m_frequency) + delta); + updateFrequency(); +} + +void ControllerMacroEditWidget::updateFrequency() +{ + m_bwidget->getDialog()->setIntValue(m_bwidget->getConfigSection().c_str(), + fmt::format("Macro{}Frequency", m_index + 1u).c_str(), + static_cast(m_frequency)); + updateFrequencyText(); +} + +void ControllerMacroEditWidget::updateFrequencyText() +{ + if (m_frequency == 0) + m_ui.frequencyText->setText(tr("Macro will not repeat.")); + else + m_ui.frequencyText->setText(tr("Macro will toggle buttons every %1 frames.").arg(m_frequency)); +} + +void ControllerMacroEditWidget::updateBinds() +{ + ControllerSettingsDialog* dialog = m_bwidget->getDialog(); + const PAD::ControllerInfo* cinfo = PAD::GetControllerInfo(m_bwidget->getControllerType()); + if (!cinfo) + return; + + std::vector new_binds; + for (u32 i = 0, bind_index = 0; i < cinfo->num_bindings; i++) + { + const PAD::ControllerBindingInfo& bi = cinfo->bindings[i]; + if (bi.type == PAD::ControllerBindingType::Motor) + continue; + + const QListWidgetItem* item = m_ui.bindList->item(static_cast(bind_index)); + bind_index++; + + if (!item) + { + // shouldn't happen + continue; + } + + if (item->checkState() == Qt::Checked) + new_binds.push_back(&bi); + } + if (m_binds == new_binds) + return; + + m_binds = std::move(new_binds); + + std::string binds_string; + for (const PAD::ControllerBindingInfo* bi : m_binds) + { + if (!binds_string.empty()) + binds_string.append(" & "); + binds_string.append(bi->name); + } + + const std::string& section = m_bwidget->getConfigSection(); + const std::string key(fmt::format("Macro{}Binds", m_index + 1u)); + if (binds_string.empty()) + dialog->clearSettingValue(section.c_str(), key.c_str()); + else + dialog->setStringValue(section.c_str(), key.c_str(), binds_string.c_str()); + + m_parent->updateListItem(m_index); +} + +////////////////////////////////////////////////////////////////////////// + +ControllerCustomSettingsWidget::ControllerCustomSettingsWidget(ControllerBindingWidget* parent, QWidget* parent_widget) + : QWidget(parent_widget) + , m_parent(parent) +{ + const PAD::ControllerInfo* cinfo = PAD::GetControllerInfo(parent->getControllerType()); + if (!cinfo || cinfo->num_settings == 0) + return; + + QGroupBox* gbox = new QGroupBox(tr("%1 Settings").arg(qApp->translate("PAD", cinfo->display_name)), this); + QGridLayout* gbox_layout = new QGridLayout(gbox); + createSettingWidgets(parent, gbox, gbox_layout, cinfo); + + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(gbox); + + QHBoxLayout* bottom_hlayout = new QHBoxLayout(); + QPushButton* restore_defaults = new QPushButton(tr("Restore Default Settings"), this); + restore_defaults->setIcon(QIcon::fromTheme(QStringLiteral("restart-line"))); + connect(restore_defaults, &QPushButton::clicked, this, &ControllerCustomSettingsWidget::restoreDefaults); + bottom_hlayout->addStretch(1); + bottom_hlayout->addWidget(restore_defaults); + layout->addLayout(bottom_hlayout); + layout->addStretch(1); +} + +ControllerCustomSettingsWidget::~ControllerCustomSettingsWidget() = default; + +void ControllerCustomSettingsWidget::createSettingWidgets(ControllerBindingWidget* parent, QWidget* widget_parent, + QGridLayout* layout, const PAD::ControllerInfo* cinfo) +{ + const std::string& section = parent->getConfigSection(); + SettingsInterface* sif = parent->getDialog()->getProfileSettingsInterface(); + int current_row = 0; + + for (u32 i = 0; i < cinfo->num_settings; i++) + { + const PAD::ControllerSettingInfo& si = cinfo->settings[i]; + std::string key_name = si.key; + + switch (si.type) + { + case PAD::ControllerSettingInfo::Type::Boolean: + { + QCheckBox* cb = new QCheckBox(qApp->translate(cinfo->name, si.visible_name), widget_parent); + cb->setObjectName(QString::fromUtf8(si.key)); + ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, cb, section, std::move(key_name), + si.BooleanDefaultValue()); + layout->addWidget(cb, current_row, 0, 1, 4); + current_row++; + } + break; + + case PAD::ControllerSettingInfo::Type::Integer: + { + QSpinBox* sb = new QSpinBox(widget_parent); + sb->setObjectName(QString::fromUtf8(si.key)); + sb->setMinimum(si.IntegerMinValue()); + sb->setMaximum(si.IntegerMaxValue()); + sb->setSingleStep(si.IntegerStepValue()); + SettingWidgetBinder::BindWidgetToIntSetting(sif, sb, section, std::move(key_name), si.IntegerDefaultValue()); + layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.visible_name), widget_parent), current_row, 0); + layout->addWidget(sb, current_row, 1, 1, 3); + current_row++; + } + break; + + case PAD::ControllerSettingInfo::Type::Float: + { + QDoubleSpinBox* sb = new QDoubleSpinBox(widget_parent); + sb->setObjectName(QString::fromUtf8(si.key)); + sb->setMinimum(si.FloatMinValue()); + sb->setMaximum(si.FloatMaxValue()); + sb->setSingleStep(si.FloatStepValue()); + SettingWidgetBinder::BindWidgetToFloatSetting(sif, sb, section, std::move(key_name), si.FloatDefaultValue()); + layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.visible_name), widget_parent), current_row, 0); + layout->addWidget(sb, current_row, 1, 1, 3); + current_row++; + } + break; + + case PAD::ControllerSettingInfo::Type::String: + { + QLineEdit* le = new QLineEdit(widget_parent); + le->setObjectName(QString::fromUtf8(si.key)); + SettingWidgetBinder::BindWidgetToStringSetting(sif, le, section, std::move(key_name), si.StringDefaultValue()); + layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.visible_name), widget_parent), current_row, 0); + layout->addWidget(le, current_row, 1, 1, 3); + current_row++; + } + break; + + case PAD::ControllerSettingInfo::Type::Path: + { + QLineEdit* le = new QLineEdit(widget_parent); + le->setObjectName(QString::fromUtf8(si.key)); + QPushButton* browse_button = new QPushButton(tr("Browse..."), widget_parent); + SettingWidgetBinder::BindWidgetToStringSetting(sif, le, section, std::move(key_name), si.StringDefaultValue()); + connect(browse_button, &QPushButton::clicked, [this, le]() { + QString path = QFileDialog::getOpenFileName(this, tr("Select File")); + if (!path.isEmpty()) + le->setText(path); + }); + + QHBoxLayout* hbox = new QHBoxLayout(); + hbox->addWidget(le, 1); + hbox->addWidget(browse_button); + + layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.visible_name), widget_parent), current_row, 0); + layout->addLayout(hbox, current_row, 1, 1, 3); + current_row++; + } + break; + } + + QLabel* label = new QLabel(si.description ? qApp->translate(cinfo->name, si.description) : QString(), widget_parent); + label->setWordWrap(true); + layout->addWidget(label, current_row++, 0, 1, 4); + + layout->addItem(new QSpacerItem(1, 10, QSizePolicy::Minimum, QSizePolicy::Fixed), current_row++, 0, 1, 4); + } +} + +void ControllerCustomSettingsWidget::restoreDefaults() +{ + const PAD::ControllerInfo* cinfo = PAD::GetControllerInfo(m_parent->getControllerType()); + if (!cinfo || cinfo->num_settings == 0) + return; + + for (u32 i = 0; i < cinfo->num_settings; i++) + { + const PAD::ControllerSettingInfo& si = cinfo->settings[i]; + const QString key(QString::fromStdString(si.key)); + + switch (si.type) + { + case PAD::ControllerSettingInfo::Type::Boolean: + { + QCheckBox* widget = findChild(QString::fromStdString(si.key)); + if (widget) + widget->setChecked(si.BooleanDefaultValue()); + } + break; + + case PAD::ControllerSettingInfo::Type::Integer: + { + QSpinBox* widget = findChild(QString::fromStdString(si.key)); + if (widget) + widget->setValue(si.IntegerDefaultValue()); + } + break; + + case PAD::ControllerSettingInfo::Type::Float: + { + QDoubleSpinBox* widget = findChild(QString::fromStdString(si.key)); + if (widget) + widget->setValue(si.FloatDefaultValue()); + } + break; + + case PAD::ControllerSettingInfo::Type::String: + { + QLineEdit* widget = findChild(QString::fromStdString(si.key)); + if (widget) + widget->setText(QString::fromUtf8(si.StringDefaultValue())); + } + break; + + case PAD::ControllerSettingInfo::Type::Path: + { + QLineEdit* widget = findChild(QString::fromStdString(si.key)); + if (widget) + widget->setText(QString::fromUtf8(si.StringDefaultValue())); + } + break; + } + } +} + + ////////////////////////////////////////////////////////////////////////// ControllerBindingWidget_Base::ControllerBindingWidget_Base(ControllerBindingWidget* parent) diff --git a/pcsx2-qt/Settings/ControllerBindingWidgets.h b/pcsx2-qt/Settings/ControllerBindingWidgets.h index c91794039a..a591c659a0 100644 --- a/pcsx2-qt/Settings/ControllerBindingWidgets.h +++ b/pcsx2-qt/Settings/ControllerBindingWidgets.h @@ -15,13 +15,20 @@ #pragma once +#include "PAD/Host/PAD.h" + #include #include "ui_ControllerBindingWidget.h" #include "ui_ControllerBindingWidget_DualShock2.h" +#include "ui_ControllerMacroWidget.h" +#include "ui_ControllerMacroEditWidget.h" class InputBindingWidget; class ControllerSettingsDialog; +class ControllerCustomSettingsWidget; +class ControllerMacroWidget; +class ControllerMacroEditWidget; class ControllerBindingWidget_Base; class ControllerBindingWidget final : public QWidget @@ -41,11 +48,15 @@ public: private Q_SLOTS: void onTypeChanged(); - void doAutomaticBinding(); - void doClearBindings(); + void onAutomaticBindingClicked(); + void onClearBindingsClicked(); + void onBindingsClicked(); + void onSettingsClicked(); + void onMacrosClicked(); private: void populateControllerTypes(); + void updateHeaderToolButtons(); void doDeviceAutomaticBinding(const QString& device); void saveAndRefresh(); @@ -57,9 +68,87 @@ private: std::string m_controller_type; u32 m_port_number; - ControllerBindingWidget_Base* m_current_widget = nullptr; + ControllerBindingWidget_Base* m_bindings_widget = nullptr; + ControllerCustomSettingsWidget* m_settings_widget = nullptr; + ControllerMacroWidget* m_macros_widget = nullptr; }; + +////////////////////////////////////////////////////////////////////////// + +class ControllerMacroWidget : public QWidget +{ + Q_OBJECT + +public: + ControllerMacroWidget(ControllerBindingWidget* parent); + ~ControllerMacroWidget(); + + void updateListItem(u32 index); + +private: + static constexpr u32 NUM_MACROS = PAD::NUM_MACRO_BUTTONS_PER_CONTROLLER; + + void createWidgets(ControllerBindingWidget* parent); + + Ui::ControllerMacroWidget m_ui; + ControllerSettingsDialog* m_dialog; + std::array m_macros; +}; + +////////////////////////////////////////////////////////////////////////// + +class ControllerMacroEditWidget : public QWidget +{ + Q_OBJECT + +public: + ControllerMacroEditWidget(ControllerMacroWidget* parent, ControllerBindingWidget* bwidget, u32 index); + ~ControllerMacroEditWidget(); + + QString getSummary() const; + +private Q_SLOTS: + void onSetFrequencyClicked(); + void updateBinds(); + +private: + void modFrequency(s32 delta); + void updateFrequency(); + void updateFrequencyText(); + + Ui::ControllerMacroEditWidget m_ui; + + ControllerMacroWidget* m_parent; + ControllerBindingWidget* m_bwidget; + u32 m_index; + + std::vector m_binds; + u32 m_frequency = 0; +}; + +////////////////////////////////////////////////////////////////////////// + +class ControllerCustomSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + ControllerCustomSettingsWidget(ControllerBindingWidget* parent, QWidget* parent_widget); + ~ControllerCustomSettingsWidget(); + + void createSettingWidgets(ControllerBindingWidget* parent, QWidget* widget_parent, QGridLayout* layout, const PAD::ControllerInfo* cinfo); + +private Q_SLOTS: + void restoreDefaults(); + +private: + ControllerBindingWidget* m_parent; +}; + +////////////////////////////////////////////////////////////////////////// + + class ControllerBindingWidget_Base : public QWidget { Q_OBJECT diff --git a/pcsx2-qt/Settings/ControllerMacroEditWidget.ui b/pcsx2-qt/Settings/ControllerMacroEditWidget.ui new file mode 100644 index 0000000000..788a59b355 --- /dev/null +++ b/pcsx2-qt/Settings/ControllerMacroEditWidget.ui @@ -0,0 +1,157 @@ + + + ControllerMacroEditWidget + + + + 0 + 0 + 595 + 473 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Binds/Buttons + + + + + + Select the buttons which you want to trigger with this macro. All buttons are activated concurrently. + + + true + + + + + + + + + + + + + Trigger + + + + + + Select the trigger to activate this macro. This can be a single button, or combination of buttons (chord). Shift-click for multiple triggers. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + PushButton + + + + + + + + + + Frequency + + + + + + + + Macro will toggle every N frames. + + + + + + + Set... + + + + + + + + 0 + 20 + + + + Qt::UpArrow + + + + + + + + 0 + 20 + + + + Qt::DownArrow + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + InputBindingWidget + QPushButton +
Settings/InputBindingWidget.h
+
+
+ + +
diff --git a/pcsx2-qt/Settings/ControllerMacroWidget.ui b/pcsx2-qt/Settings/ControllerMacroWidget.ui new file mode 100644 index 0000000000..773ffc4adc --- /dev/null +++ b/pcsx2-qt/Settings/ControllerMacroWidget.ui @@ -0,0 +1,69 @@ + + + ControllerMacroWidget + + + + 0 + 0 + 799 + 493 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + + 32 + 32 + + + + + + + + + + + + + + diff --git a/pcsx2-qt/Settings/ControllerSettingsDialog.cpp b/pcsx2-qt/Settings/ControllerSettingsDialog.cpp index 242a509d37..d2dd07f697 100644 --- a/pcsx2-qt/Settings/ControllerSettingsDialog.cpp +++ b/pcsx2-qt/Settings/ControllerSettingsDialog.cpp @@ -266,6 +266,14 @@ bool ControllerSettingsDialog::getBoolValue(const char* section, const char* key return Host::GetBaseBoolSettingValue(section, key, default_value); } +s32 ControllerSettingsDialog::getIntValue(const char* section, const char* key, s32 default_value) const +{ + if (m_profile_interface) + return m_profile_interface->GetIntValue(section, key, default_value); + else + return Host::GetBaseIntSettingValue(section, key, default_value); +} + std::string ControllerSettingsDialog::getStringValue(const char* section, const char* key, const char* default_value) const { std::string value; @@ -291,6 +299,21 @@ void ControllerSettingsDialog::setBoolValue(const char* section, const char* key } } +void ControllerSettingsDialog::setIntValue(const char* section, const char* key, s32 value) +{ + if (m_profile_interface) + { + m_profile_interface->SetIntValue(section, key, value); + m_profile_interface->Save(); + g_emu_thread->reloadGameSettings(); + } + else + { + QtHost::SetBaseIntSettingValue(section, key, value); + g_emu_thread->applySettings(); + } +} + void ControllerSettingsDialog::setStringValue(const char* section, const char* key, const char* value) { if (m_profile_interface) @@ -301,7 +324,7 @@ void ControllerSettingsDialog::setStringValue(const char* section, const char* k } else { - QtHost::SetBaseStringSettingValue(key, section, value); + QtHost::SetBaseStringSettingValue(section, key, value); g_emu_thread->applySettings(); } } diff --git a/pcsx2-qt/Settings/ControllerSettingsDialog.h b/pcsx2-qt/Settings/ControllerSettingsDialog.h index 149623cb2d..1569ebb099 100644 --- a/pcsx2-qt/Settings/ControllerSettingsDialog.h +++ b/pcsx2-qt/Settings/ControllerSettingsDialog.h @@ -64,8 +64,10 @@ public: // Helper functions for updating setting values globally or in the profile. bool getBoolValue(const char* section, const char* key, bool default_value) const; + s32 getIntValue(const char* section, const char* key, s32 default_value) const; std::string getStringValue(const char* section, const char* key, const char* default_value) const; void setBoolValue(const char* section, const char* key, bool value); + void setIntValue(const char* section, const char* key, s32 value); void setStringValue(const char* section, const char* key, const char* value); void clearSettingValue(const char* section, const char* key); diff --git a/pcsx2-qt/Settings/ControllerSettingsDialog.ui b/pcsx2-qt/Settings/ControllerSettingsDialog.ui index f1e4807824..4b4fdedede 100644 --- a/pcsx2-qt/Settings/ControllerSettingsDialog.ui +++ b/pcsx2-qt/Settings/ControllerSettingsDialog.ui @@ -9,8 +9,8 @@ 0 0 - 1276 - 672 + 1300 + 680
diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj index 8f29f2eb6a..ab6ca66a78 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj +++ b/pcsx2-qt/pcsx2-qt.vcxproj @@ -335,6 +335,12 @@ + + Document + + + Document + diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters index dcf392a431..5d3fb9c1bb 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj.filters +++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters @@ -405,6 +405,12 @@ Tools\Input Recording + + Settings + + + Settings + diff --git a/pcsx2-qt/resources/icons/black/svg/checkbox-multiple-blank-line.svg b/pcsx2-qt/resources/icons/black/svg/checkbox-multiple-blank-line.svg new file mode 100644 index 0000000000..0ca7a11c59 --- /dev/null +++ b/pcsx2-qt/resources/icons/black/svg/checkbox-multiple-blank-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pcsx2-qt/resources/icons/black/svg/flashlight-line.svg b/pcsx2-qt/resources/icons/black/svg/flashlight-line.svg new file mode 100644 index 0000000000..80452ba28c --- /dev/null +++ b/pcsx2-qt/resources/icons/black/svg/flashlight-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pcsx2-qt/resources/icons/white/svg/checkbox-multiple-blank-line.svg b/pcsx2-qt/resources/icons/white/svg/checkbox-multiple-blank-line.svg new file mode 100644 index 0000000000..672c3d0fa0 --- /dev/null +++ b/pcsx2-qt/resources/icons/white/svg/checkbox-multiple-blank-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pcsx2-qt/resources/icons/white/svg/flashlight-line.svg b/pcsx2-qt/resources/icons/white/svg/flashlight-line.svg new file mode 100644 index 0000000000..45924d102e --- /dev/null +++ b/pcsx2-qt/resources/icons/white/svg/flashlight-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pcsx2-qt/resources/resources.qrc b/pcsx2-qt/resources/resources.qrc index 9561d481ff..f2db7c78c5 100644 --- a/pcsx2-qt/resources/resources.qrc +++ b/pcsx2-qt/resources/resources.qrc @@ -7,6 +7,7 @@ icons/black/svg/artboard-2-line.svg icons/black/svg/book-open-line.svg icons/black/svg/brush-line.svg + icons/black/svg/checkbox-multiple-blank-line.svg icons/black/svg/close-line.svg icons/black/svg/dashboard-line.svg icons/black/svg/disc-line.svg @@ -21,6 +22,7 @@ icons/black/svg/file-search-line.svg icons/black/svg/file-settings-line.svg icons/black/svg/filter-line.svg + icons/black/svg/flashlight-line.svg icons/black/svg/flask-line.svg icons/black/svg/folder-add-line.svg icons/black/svg/folder-open-line.svg @@ -59,6 +61,7 @@ icons/white/svg/artboard-2-line.svg icons/white/svg/book-open-line.svg icons/white/svg/brush-line.svg + icons/white/svg/checkbox-multiple-blank-line.svg icons/white/svg/close-line.svg icons/white/svg/dashboard-line.svg icons/white/svg/disc-line.svg @@ -73,6 +76,7 @@ icons/white/svg/file-search-line.svg icons/white/svg/file-settings-line.svg icons/white/svg/filter-line.svg + icons/white/svg/flashlight-line.svg icons/white/svg/flask-line.svg icons/white/svg/folder-add-line.svg icons/white/svg/folder-open-line.svg diff --git a/pcsx2/PAD/Host/PAD.cpp b/pcsx2/PAD/Host/PAD.cpp index 19d748bc03..988721a75c 100644 --- a/pcsx2/PAD/Host/PAD.cpp +++ b/pcsx2/PAD/Host/PAD.cpp @@ -183,6 +183,62 @@ u8 PADpoll(u8 value) return pad_poll(value); } +const char* PAD::ControllerSettingInfo::StringDefaultValue() const +{ + return default_value ? default_value : ""; +} + +bool PAD::ControllerSettingInfo::BooleanDefaultValue() const +{ + return default_value ? StringUtil::FromChars(default_value).value_or(false) : false; +} + +s32 PAD::ControllerSettingInfo::IntegerDefaultValue() const +{ + return default_value ? StringUtil::FromChars(default_value).value_or(0) : 0; +} + +s32 PAD::ControllerSettingInfo::IntegerMinValue() const +{ + static constexpr s32 fallback_value = std::numeric_limits::min(); + return min_value ? StringUtil::FromChars(min_value).value_or(fallback_value) : fallback_value; +} + +s32 PAD::ControllerSettingInfo::IntegerMaxValue() const +{ + static constexpr s32 fallback_value = std::numeric_limits::max(); + return max_value ? StringUtil::FromChars(max_value).value_or(fallback_value) : fallback_value; +} + +s32 PAD::ControllerSettingInfo::IntegerStepValue() const +{ + static constexpr s32 fallback_value = 1; + return step_value ? StringUtil::FromChars(step_value).value_or(fallback_value) : fallback_value; +} + +float PAD::ControllerSettingInfo::FloatDefaultValue() const +{ + return default_value ? StringUtil::FromChars(default_value).value_or(0.0f) : 0.0f; +} + +float PAD::ControllerSettingInfo::FloatMinValue() const +{ + static constexpr float fallback_value = std::numeric_limits::min(); + return min_value ? StringUtil::FromChars(min_value).value_or(fallback_value) : fallback_value; +} + +float PAD::ControllerSettingInfo::FloatMaxValue() const +{ + static constexpr float fallback_value = std::numeric_limits::max(); + return max_value ? StringUtil::FromChars(max_value).value_or(fallback_value) : fallback_value; +} + +float PAD::ControllerSettingInfo::FloatStepValue() const +{ + static constexpr float fallback_value = 0.1f; + return step_value ? StringUtil::FromChars(step_value).value_or(fallback_value) : fallback_value; +} + std::string PAD::GetConfigSection(u32 pad_index) { return fmt::format("Pad{}", pad_index + 1); @@ -350,9 +406,31 @@ static const PAD::ControllerBindingInfo s_dualshock2_binds[] = { {"SmallMotor", "Small (High Frequency) Motor", PAD::ControllerBindingType::Motor, GenericInputBinding::SmallMotor}, }; +static const PAD::ControllerSettingInfo s_dualshock2_settings[] = { + {PAD::ControllerSettingInfo::Type::Float, "Deadzone", "Analog Deadzone", + "Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored.", + "0.00", "0.00", "1.00", "0.01"}, + {PAD::ControllerSettingInfo::Type::Float, "AxisScale", "Analog Sensitivity", + "Sets the analog stick axis scaling factor. A value between 1.30 and 1.40 is recommended when using recent " + "controllers, e.g. DualShock 4, Xbox One Controller.", + "1.33", "0.01", "2.00", "0.01"}, + {PAD::ControllerSettingInfo::Type::Float, "LargeMotorScale", "Large Motor Vibration Scale", + "Increases or decreases the intensity of low frequency vibration sent by the game.", + "1.00", "0.00", "2.00", "0.01"}, + {PAD::ControllerSettingInfo::Type::Float, "SmallMotorScale", "Small Motor Vibration Scale", + "Increases or decreases the intensity of high frequency vibration sent by the game.", + "1.00", "0.00", "2.00", "0.01"}, +}; + static const PAD::ControllerInfo s_controller_info[] = { - {"None", "Not Connected", nullptr, 0, PAD::ControllerType::NotConnected, PAD::VibrationCapabilities::NoVibration}, - {"DualShock2", "DualShock 2", s_dualshock2_binds, std::size(s_dualshock2_binds), PAD::ControllerType::DualShock2, PAD::VibrationCapabilities::LargeSmallMotors}, + {PAD::ControllerType::NotConnected, "None", "Not Connected", + nullptr, 0, + nullptr, 0, + PAD::VibrationCapabilities::NoVibration}, + {PAD::ControllerType::DualShock2, "DualShock2", "DualShock 2", + s_dualshock2_binds, std::size(s_dualshock2_binds), + s_dualshock2_settings, std::size(s_dualshock2_settings), + PAD::VibrationCapabilities::LargeSmallMotors}, }; const PAD::ControllerInfo* PAD::GetControllerInfo(ControllerType type) diff --git a/pcsx2/PAD/Host/PAD.h b/pcsx2/PAD/Host/PAD.h index 88b22e0d09..f0d6581216 100644 --- a/pcsx2/PAD/Host/PAD.h +++ b/pcsx2/PAD/Host/PAD.h @@ -71,13 +71,48 @@ namespace PAD GenericInputBinding generic_mapping; }; + struct ControllerSettingInfo + { + enum class Type + { + Boolean, + Integer, + Float, + String, + Path, + }; + + Type type; + const char* key; + const char* visible_name; + const char* description; + const char* default_value; + const char* min_value; + const char* max_value; + const char* step_value; + + const char* StringDefaultValue() const; + bool BooleanDefaultValue() const; + s32 IntegerDefaultValue() const; + s32 IntegerMinValue() const; + s32 IntegerMaxValue() const; + s32 IntegerStepValue() const; + float FloatDefaultValue() const; + float FloatMinValue() const; + float FloatMaxValue() const; + float FloatStepValue() const; + }; + + struct ControllerInfo { + ControllerType type; const char* name; const char* display_name; const ControllerBindingInfo* bindings; u32 num_bindings; - ControllerType type; + const ControllerSettingInfo* settings; + u32 num_settings; PAD::VibrationCapabilities vibration_caps; };