Qt: Fix per-game settings with sliders

This commit is contained in:
Stenzek 2023-01-01 22:46:07 +10:00 committed by refractionpcsx2
parent 336ef58f61
commit b3bf3e46be
3 changed files with 177 additions and 57 deletions

View File

@ -20,11 +20,13 @@
#include <QtCore/QtCore>
#include <QtGui/QAction>
#include <QtGui/QFont>
#include <QtWidgets/QAbstractButton>
#include <QtWidgets/QCheckBox>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDoubleSpinBox>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QMenu>
#include <QtWidgets/QSlider>
@ -101,7 +103,10 @@ namespace SettingWidgetBinder
static void setStringValue(QLineEdit* widget, const QString& value) { widget->setText(value); }
static void makeNullableString(QLineEdit* widget, const QString& globalValue) { widget->setEnabled(false); }
static std::optional<QString> getNullableStringValue(const QLineEdit* widget) { return getStringValue(widget); }
static void setNullableStringValue(QLineEdit* widget, std::optional<QString> value) { setStringValue(widget, value.value_or(QString())); }
static void setNullableStringValue(QLineEdit* widget, std::optional<QString> value)
{
setStringValue(widget, value.value_or(QString()));
}
template <typename F>
static void connectValueChanged(QLineEdit* widget, F func)
@ -120,15 +125,16 @@ namespace SettingWidgetBinder
static void makeNullableBool(QComboBox* widget, bool globalValue)
{
widget->insertItem(0, globalValue ? qApp->translate("SettingsDialog", "Use Global Setting [Enabled]") :
qApp->translate("SettingsDialog", "Use Global Setting [Disabled]"));
qApp->translate("SettingsDialog", "Use Global Setting [Disabled]"));
}
static int getIntValue(const QComboBox* widget) { return widget->currentIndex(); }
static void setIntValue(QComboBox* widget, int value) { widget->setCurrentIndex(value); }
static void makeNullableInt(QComboBox* widget, int globalValue)
{
widget->insertItem(0, qApp->translate("SettingsDialog", "Use Global Setting [%1]")
.arg((globalValue >= 0 && globalValue < widget->count()) ? widget->itemText(globalValue) : QString()));
widget->insertItem(
0, qApp->translate("SettingsDialog", "Use Global Setting [%1]")
.arg((globalValue >= 0 && globalValue < widget->count()) ? widget->itemText(globalValue) : QString()));
}
static std::optional<int> getNullableIntValue(const QComboBox* widget)
{
@ -143,10 +149,10 @@ namespace SettingWidgetBinder
static void setFloatValue(QComboBox* widget, float value) { widget->setCurrentIndex(static_cast<int>(value)); }
static void makeNullableFloat(QComboBox* widget, float globalValue)
{
widget->insertItem(0,
qApp->translate("SettingsDialog", "Use Global Setting [%1]")
.arg((globalValue >= 0.0f && static_cast<int>(globalValue) < widget->count()) ? widget->itemText(static_cast<int>(globalValue)) :
QString()));
widget->insertItem(0, qApp->translate("SettingsDialog", "Use Global Setting [%1]")
.arg((globalValue >= 0.0f && static_cast<int>(globalValue) < widget->count()) ?
widget->itemText(static_cast<int>(globalValue)) :
QString()));
}
static std::optional<float> getNullableFloatValue(const QComboBox* widget)
{
@ -176,7 +182,10 @@ namespace SettingWidgetBinder
widget->setCurrentText(value);
}
static void makeNullableString(QComboBox* widget, const QString& globalValue) { makeNullableInt(widget, widget->findData(globalValue)); }
static void makeNullableString(QComboBox* widget, const QString& globalValue)
{
makeNullableInt(widget, widget->findData(globalValue));
}
static std::optional<QString> getNullableStringValue(const QComboBox* widget)
{
return isNullValue(widget) ? std::nullopt : std::optional<QString>(getStringValue(widget));
@ -239,7 +248,7 @@ namespace SettingWidgetBinder
{
return (widget->checkState() == Qt::PartiallyChecked) ?
std::nullopt :
std::optional<QString>(widget->isChecked() ? QStringLiteral("1") : QStringLiteral("0"));
std::optional<QString>(widget->isChecked() ? QStringLiteral("1") : QStringLiteral("0"));
}
static void setNullableStringValue(QCheckBox* widget, std::optional<QString> value)
{
@ -278,7 +287,10 @@ namespace SettingWidgetBinder
static void setStringValue(QSlider* widget, const QString& value) { widget->setValue(value.toInt()); }
static void makeNullableString(QSlider* widget, const QString& globalValue) { widget->setEnabled(false); }
static std::optional<QString> getNullableStringValue(const QSlider* widget) { return getStringValue(widget); }
static void setNullableStringValue(QSlider* widget, std::optional<QString> value) { setStringValue(widget, value.value_or(QString())); }
static void setNullableStringValue(QSlider* widget, std::optional<QString> value)
{
setStringValue(widget, value.value_or(QString()));
}
template <typename F>
static void connectValueChanged(QSlider* widget, F func)
@ -568,7 +580,10 @@ namespace SettingWidgetBinder
static void setStringValue(QAction* widget, const QString& value) { widget->setChecked(value.toInt() != 0); }
static void makeNullableString(QAction* widget, const QString& globalValue) { widget->setEnabled(false); }
static std::optional<QString> getNullableStringValue(const QAction* widget) { return getStringValue(widget); }
static void setNullableStringValue(QAction* widget, std::optional<QString> value) { setStringValue(widget, value.value_or(QString())); }
static void setNullableStringValue(QAction* widget, std::optional<QString> value)
{
setStringValue(widget, value.value_or(QString()));
}
template <typename F>
static void connectValueChanged(QAction* widget, F func)
@ -580,7 +595,8 @@ namespace SettingWidgetBinder
/// Binds a widget's value to a setting, updating it when the value changes.
template <typename WidgetType>
static void BindWidgetToBoolSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, bool default_value)
static void BindWidgetToBoolSetting(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, bool default_value)
{
using Accessor = SettingAccessor<WidgetType>;
@ -661,7 +677,8 @@ namespace SettingWidgetBinder
}
template <typename WidgetType>
static void BindWidgetToFloatSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, float default_value)
static void BindWidgetToFloatSetting(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, float default_value)
{
using Accessor = SettingAccessor<WidgetType>;
@ -788,7 +805,8 @@ namespace SettingWidgetBinder
template <typename WidgetType, typename DataType>
static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key,
std::optional<DataType> (*from_string_function)(const char* str), const char* (*to_string_function)(DataType value), DataType default_value)
std::optional<DataType> (*from_string_function)(const char* str), const char* (*to_string_function)(DataType value),
DataType default_value)
{
using Accessor = SettingAccessor<WidgetType>;
using UnderlyingType = std::underlying_type_t<DataType>;
@ -798,7 +816,8 @@ namespace SettingWidgetBinder
if (sif)
{
Accessor::makeNullableInt(widget, typed_value.has_value() ? static_cast<int>(static_cast<UnderlyingType>(typed_value.value())) : 0);
Accessor::makeNullableInt(
widget, typed_value.has_value() ? static_cast<int>(static_cast<UnderlyingType>(typed_value.value())) : 0);
std::string sif_value;
if (sif->GetStringValue(section.c_str(), key.c_str(), &sif_value))
@ -909,8 +928,8 @@ namespace SettingWidgetBinder
}
template <typename WidgetType>
static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, const char** enum_names,
const char** enum_values, const char* default_value)
static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key,
const char** enum_names, const char** enum_values, const char* default_value)
{
using Accessor = SettingAccessor<WidgetType>;
@ -973,8 +992,9 @@ namespace SettingWidgetBinder
}
template <typename WidgetType>
static void BindWidgetToFolderSetting(SettingsInterface* sif, WidgetType* widget, QAbstractButton* browse_button, QAbstractButton* open_button,
QAbstractButton* reset_button, std::string section, std::string key, std::string default_value, bool use_relative = true)
static void BindWidgetToFolderSetting(SettingsInterface* sif, WidgetType* widget, QAbstractButton* browse_button,
QAbstractButton* open_button, QAbstractButton* reset_button, std::string section, std::string key, std::string default_value,
bool use_relative = true)
{
using Accessor = SettingAccessor<WidgetType>;
@ -1042,8 +1062,76 @@ namespace SettingWidgetBinder
}
if (reset_button)
{
QObject::connect(reset_button, &QAbstractButton::clicked, reset_button,
[widget, default_value = std::move(default_value)]() { Accessor::setStringValue(widget, QString::fromStdString(default_value)); });
QObject::connect(reset_button, &QAbstractButton::clicked, reset_button, [widget, default_value = std::move(default_value)]() {
Accessor::setStringValue(widget, QString::fromStdString(default_value));
});
}
}
static void BindSliderToIntSetting(SettingsInterface* sif, QSlider* slider, QLabel* label, const QString& label_suffix,
std::string section, std::string key, s32 default_value)
{
const s32 global_value = Host::GetBaseIntSettingValue(section.c_str(), key.c_str(), default_value);
if (sif)
{
const QFont orig_font(label->font());
QFont bold_font(orig_font);
bold_font.setBold(true);
const s32 current_value = sif->GetOptionalIntValue(section.c_str(), key.c_str()).value_or(global_value);
slider->setValue(current_value);
label->setText(QStringLiteral("%1%2").arg(current_value).arg(label_suffix));
if (current_value == global_value)
label->setFont(orig_font);
else
label->setFont(bold_font);
slider->setContextMenuPolicy(Qt::CustomContextMenu);
slider->connect(slider, &QSpinBox::customContextMenuRequested, slider,
[sif, slider, label, label_suffix, orig_font = std::move(orig_font), section, key, default_value](const QPoint& pt) {
QMenu menu(slider);
slider->connect(menu.addAction(qApp->translate("SettingWidgetBinder", "Reset")), &QAction::triggered, slider,
[sif, slider, label, label_suffix, orig_font, section, key, default_value]() {
const s32 global_value = Host::GetBaseIntSettingValue(section.c_str(), key.c_str(), default_value);
label->setText(QStringLiteral("%1%2").arg(global_value).arg(label_suffix));
label->setFont(orig_font);
if (sif->ContainsValue(section.c_str(), key.c_str()))
{
sif->DeleteValue(section.c_str(), key.c_str());
sif->Save();
g_emu_thread->reloadGameSettings();
}
});
menu.exec(slider->mapToGlobal(pt));
});
slider->connect(slider, &QSlider::valueChanged, slider,
[sif, label, label_suffix, section = std::move(section), key = std::move(key), default_value,
orig_font = std::move(orig_font), bold_font = std::move(bold_font)](int value) {
label->setText(QStringLiteral("%1%2").arg(value).arg(label_suffix));
if (label->font() != bold_font)
label->setFont(bold_font);
sif->SetIntValue(section.c_str(), key.c_str(), value);
sif->Save();
g_emu_thread->reloadGameSettings();
});
}
else
{
slider->setValue(global_value);
label->setText(QStringLiteral("%1%2").arg(global_value).arg(label_suffix));
slider->connect(slider, &QSlider::valueChanged, slider,
[sif, label, label_suffix, section = std::move(section), key = std::move(key), default_value](int value) {
label->setText(QStringLiteral("%1%2").arg(value).arg(label_suffix));
Host::SetBaseIntSettingValue(section.c_str(), key.c_str(), value);
Host::CommitBaseSettingChanges();
g_emu_thread->applySettings();
});
}
}
} // namespace SettingWidgetBinder

View File

@ -71,8 +71,10 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsDialog* dialog, QWidget* parent
SettingWidgetBinder::BindWidgetToEnumSetting(
sif, m_ui.outputModule, "SPU2/Output", "OutputModule", s_output_module_entries, s_output_module_values, DEFAULT_OUTPUT_MODULE);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.targetLatency, "SPU2/Output", "Latency", DEFAULT_TARGET_LATENCY);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.outputLatency, "SPU2/Output", "OutputLatency", DEFAULT_OUTPUT_LATENCY);
SettingWidgetBinder::BindSliderToIntSetting(
sif, m_ui.targetLatency, m_ui.targetLatencyLabel, tr(" ms"), "SPU2/Output", "Latency", DEFAULT_TARGET_LATENCY);
SettingWidgetBinder::BindSliderToIntSetting(
sif, m_ui.outputLatency, m_ui.outputLatencyLabel, tr(" ms"), "SPU2/Output", "OutputLatency", DEFAULT_OUTPUT_LATENCY);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.outputLatencyMinimal, "SPU2/Output", "OutputLatencyMinimal", false);
connect(m_ui.outputModule, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::outputModuleChanged);
connect(m_ui.backend, &QComboBox::currentIndexChanged, this, &AudioSettingsWidget::outputBackendChanged);
@ -83,26 +85,30 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsDialog* dialog, QWidget* parent
outputModuleChanged();
m_ui.volume->setValue(m_dialog->getEffectiveIntValue("SPU2/Mixing", "FinalVolume", DEFAULT_VOLUME));
m_ui.volume->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui.volume, &QSlider::valueChanged, this, &AudioSettingsWidget::volumeChanged);
connect(m_ui.volume, &QSlider::customContextMenuRequested, this, &AudioSettingsWidget::volumeContextMenuRequested);
updateVolumeLabel();
if (sif && sif->ContainsValue("SPU2/Mixing", "FinalVolume"))
{
QFont bold_font(m_ui.volume->font());
bold_font.setBold(true);
m_ui.volumeLabel->setFont(bold_font);
}
SettingWidgetBinder::BindWidgetToIntSetting(
sif, m_ui.sequenceLength, "Soundtouch", "SequenceLengthMS", DEFAULT_SOUNDTOUCH_SEQUENCE_LENGTH);
connect(m_ui.sequenceLength, &QSlider::valueChanged, this, &AudioSettingsWidget::updateTimestretchSequenceLengthLabel);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.seekWindowSize, "Soundtouch", "SeekWindowMS", DEFAULT_SOUNDTOUCH_SEEK_WINDOW);
connect(m_ui.seekWindowSize, &QSlider::valueChanged, this, &AudioSettingsWidget::updateTimestretchSeekwindowLengthLabel);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.overlap, "Soundtouch", "OverlapMS", DEFAULT_SOUNDTOUCH_OVERLAP);
connect(m_ui.overlap, &QSlider::valueChanged, this, &AudioSettingsWidget::updateTimestretchOverlapLabel);
SettingWidgetBinder::BindSliderToIntSetting(sif, m_ui.sequenceLength, m_ui.sequenceLengthLabel, tr(" ms"), "Soundtouch",
"SequenceLengthMS", DEFAULT_SOUNDTOUCH_SEQUENCE_LENGTH);
SettingWidgetBinder::BindSliderToIntSetting(
sif, m_ui.seekWindowSize, m_ui.seekWindowSizeLabel, tr(" ms"), "Soundtouch", "SeekWindowMS", DEFAULT_SOUNDTOUCH_SEEK_WINDOW);
SettingWidgetBinder::BindSliderToIntSetting(
sif, m_ui.overlap, m_ui.overlapLabel, tr(" ms"), "Soundtouch", "OverlapMS", DEFAULT_SOUNDTOUCH_OVERLAP);
connect(m_ui.resetTimestretchDefaults, &QPushButton::clicked, this, &AudioSettingsWidget::resetTimestretchDefaults);
m_ui.label_3b->setVisible(false);
m_ui.dplLevel->setVisible(false);
volumeChanged(m_ui.volume->value());
onMinimalOutputLatencyStateChanged();
updateLatencyLabels();
updateTimestretchSequenceLengthLabel();
updateTimestretchSeekwindowLengthLabel();
updateTimestretchOverlapLabel();
dialog->registerWidgetHelp(m_ui.interpolation, tr("Interpolation"), tr("Gaussian (PS2-like / great sound)"), tr(""));
@ -127,7 +133,8 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsDialog* dialog, QWidget* parent
dialog->registerWidgetHelp(m_ui.overlap, tr("Overlap"), tr("10 ms"), tr(""));
dialog->registerWidgetHelp(m_ui.volume, tr("Volume"), tr("100%"), tr(""));
dialog->registerWidgetHelp(m_ui.volume, tr("Volume"), tr("100%"),
tr("Pre-applies a volume modifier to the game's audio output before forwarding it to your computer."));
}
AudioSettingsWidget::~AudioSettingsWidget() = default;
@ -229,11 +236,16 @@ void AudioSettingsWidget::updateDevices()
void AudioSettingsWidget::volumeChanged(int value)
{
m_ui.volumeLabel->setText(tr("%1%").arg(value, 3));
// Nasty, but needed so we don't do a full settings apply and lag while dragging.
if (SettingsInterface* sif = m_dialog->getSettingsInterface())
{
if (!m_ui.volumeLabel->font().bold())
{
QFont bold_font(m_ui.volumeLabel->font());
bold_font.setBold(true);
m_ui.volumeLabel->setFont(bold_font);
}
sif->SetIntValue("SPU2/Mixing", "FinalVolume", value);
sif->Save();
}
@ -254,6 +266,42 @@ void AudioSettingsWidget::volumeChanged(int value)
SPU2::SetOutputVolume(value);
});
}
updateVolumeLabel();
}
void AudioSettingsWidget::volumeContextMenuRequested(const QPoint& pt)
{
QMenu menu(m_ui.volume);
m_ui.volume->connect(menu.addAction(qApp->translate("SettingWidgetBinder", "Reset")), &QAction::triggered, this, [this]() {
const s32 global_value = Host::GetBaseIntSettingValue("SPU2/Mixing", "FinalVolume", DEFAULT_VOLUME);
{
QSignalBlocker sb(m_ui.volumeLabel);
m_ui.volume->setValue(global_value);
updateVolumeLabel();
}
if (m_ui.volumeLabel->font().bold())
{
QFont orig_font(m_ui.volumeLabel->font());
orig_font.setBold(false);
m_ui.volumeLabel->setFont(orig_font);
}
SettingsInterface* sif = m_dialog->getSettingsInterface();
if (sif->ContainsValue("SPU2/Mixing", "FinalVolume"))
{
sif->DeleteValue("SPU2/Mixing", "FinalVolume");
sif->Save();
g_emu_thread->reloadGameSettings();
}
});
menu.exec(m_ui.volume->mapToGlobal(pt));
}
void AudioSettingsWidget::updateVolumeLabel()
{
m_ui.volumeLabel->setText(tr("%1%").arg(m_ui.volume->value()));
}
void AudioSettingsWidget::updateTargetLatencyRange()
@ -271,7 +319,6 @@ void AudioSettingsWidget::updateLatencyLabels()
{
const bool minimal_output = m_dialog->getEffectiveBoolValue("SPU2/Output", "OutputLatencyMinimal", false);
m_ui.targetLatencyLabel->setText(tr("%1 ms").arg(m_ui.targetLatency->value()));
m_ui.outputLatencyLabel->setText(minimal_output ? tr("N/A") : tr("%1 ms").arg(m_ui.outputLatency->value()));
const u32 output_latency_ms =
@ -295,21 +342,6 @@ void AudioSettingsWidget::onMinimalOutputLatencyStateChanged()
m_ui.outputLatency->setEnabled(!m_dialog->getEffectiveBoolValue("SPU2/Output", "OutputLatencyMinimal", false));
}
void AudioSettingsWidget::updateTimestretchSequenceLengthLabel()
{
m_ui.sequenceLengthLabel->setText(tr("%1 ms").arg(m_ui.sequenceLength->value()));
}
void AudioSettingsWidget::updateTimestretchSeekwindowLengthLabel()
{
m_ui.seekWindowSizeLabel->setText(tr("%1 ms").arg(m_ui.seekWindowSize->value()));
}
void AudioSettingsWidget::updateTimestretchOverlapLabel()
{
m_ui.overlapLabel->setText(tr("%1 ms").arg(m_ui.overlap->value()));
}
void AudioSettingsWidget::resetTimestretchDefaults()
{
m_ui.sequenceLength->setValue(DEFAULT_SOUNDTOUCH_SEQUENCE_LENGTH);

View File

@ -35,15 +35,15 @@ private Q_SLOTS:
void outputBackendChanged();
void updateDevices();
void volumeChanged(int value);
void volumeContextMenuRequested(const QPoint& pt);
void updateTargetLatencyRange();
void updateLatencyLabels();
void onMinimalOutputLatencyStateChanged();
void updateTimestretchSequenceLengthLabel();
void updateTimestretchSeekwindowLengthLabel();
void updateTimestretchOverlapLabel();
void resetTimestretchDefaults();
private:
void updateVolumeLabel();
SettingsDialog* m_dialog;
Ui::AudioSettingsWidget m_ui;
u32 m_output_device_latency = 0;