diff --git a/src/duckstation-qt/settingwidgetbinder.h b/src/duckstation-qt/settingwidgetbinder.h index 36b53bd57..b16016e45 100644 --- a/src/duckstation-qt/settingwidgetbinder.h +++ b/src/duckstation-qt/settingwidgetbinder.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #pragma once @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,7 @@ namespace SettingWidgetBinder { static constexpr const char* NULLABLE_PROPERTY = "SettingWidgetBinder_isNullable"; static constexpr const char* IS_NULL_PROPERTY = "SettingWidgetBinder_isNull"; static constexpr const char* GLOBAL_VALUE_PROPERTY = "SettingWidgetBinder_globalValue"; +static constexpr const char* IS_UPDATING_PROPERTY = "SettingWidgetBinder_isUpdating"; template struct SettingAccessor @@ -272,43 +274,121 @@ struct SettingAccessor template<> struct SettingAccessor { + static bool isNullable(const QSlider* widget) { return widget->property(NULLABLE_PROPERTY).toBool(); } + static bool getBoolValue(const QSlider* widget) { return widget->value() > 0; } static void setBoolValue(QSlider* widget, bool value) { widget->setValue(value ? 1 : 0); } - static void makeNullableBool(QSlider* widget, bool globalSetting) { widget->setEnabled(false); } - static std::optional getNullableBoolValue(const QSlider* widget) { return getBoolValue(widget); } + static void makeNullableBool(QSlider* widget, bool globalSetting) + { + widget->setProperty(NULLABLE_PROPERTY, QVariant(true)); + widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalSetting)); + } + static std::optional getNullableBoolValue(const QSlider* widget) + { + if (widget->property(IS_NULL_PROPERTY).toBool()) + return std::nullopt; + + return getBoolValue(widget); + } static void setNullableBoolValue(QSlider* widget, std::optional value) { - setBoolValue(widget, value.value_or(false)); + widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value())); + setBoolValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toBool()); } static int getIntValue(const QSlider* widget) { return widget->value(); } static void setIntValue(QSlider* widget, int value) { widget->setValue(value); } - static void makeNullableInt(QSlider* widget, int globalValue) { widget->setEnabled(false); } - static std::optional getNullableIntValue(const QSlider* widget) { return getIntValue(widget); } - static void setNullableIntValue(QSlider* widget, std::optional value) { setIntValue(widget, value.value_or(0)); } + static void makeNullableInt(QSlider* widget, int globalValue) + { + widget->setProperty(NULLABLE_PROPERTY, QVariant(true)); + widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalValue)); + } + static std::optional getNullableIntValue(const QSlider* widget) + { + if (widget->property(IS_NULL_PROPERTY).toBool()) + return std::nullopt; + + return getIntValue(widget); + } + static void setNullableIntValue(QSlider* widget, std::optional value) + { + widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value())); + setIntValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toInt()); + } static float getFloatValue(const QSlider* widget) { return static_cast(widget->value()); } static void setFloatValue(QSlider* widget, float value) { widget->setValue(static_cast(value)); } static void makeNullableFloat(QSlider* widget, float globalValue) { widget->setEnabled(false); } - static std::optional getNullableFloatValue(const QSlider* widget) { return getFloatValue(widget); } + static std::optional getNullableFloatValue(const QSlider* widget) + { + if (widget->property(IS_NULL_PROPERTY).toBool()) + return std::nullopt; + + return getFloatValue(widget); + } static void setNullableFloatValue(QSlider* widget, std::optional value) { - setFloatValue(widget, value.value_or(0.0f)); + widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value())); + setFloatValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toFloat()); } static QString getStringValue(const QSlider* widget) { return QString::number(widget->value()); } 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 getNullableStringValue(const QSlider* widget) { return getStringValue(widget); } + static std::optional getNullableStringValue(const QSlider* widget) + { + if (widget->property(IS_NULL_PROPERTY).toBool()) + return std::nullopt; + + return getStringValue(widget); + } static void setNullableStringValue(QSlider* widget, std::optional value) { - setStringValue(widget, value.value_or(QString())); + widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value())); + setStringValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toString()); } template static void connectValueChanged(QSlider* widget, F func) { - widget->connect(widget, &QSlider::valueChanged, func); + if (!isNullable(widget) || widget->contextMenuPolicy() == Qt::CustomContextMenu) + { + widget->connect(widget, &QSlider::valueChanged, func); + } + else + { + // How much do I hate this? A _lot_. We need to be able to run handlers (e.g. for labels), which get connected to + // valueChanged() above, but the user changing the control also triggers valueChanged()... so catch the recursion. + widget->setProperty(IS_UPDATING_PROPERTY, QVariant(false)); + + widget->setContextMenuPolicy(Qt::CustomContextMenu); + widget->connect(widget, &QSlider::customContextMenuRequested, widget, [widget, func](const QPoint& pt) { + QMenu menu(widget); + widget->connect(menu.addAction(qApp->translate("SettingWidgetBinder", "Reset")), &QAction::triggered, widget, + [widget, func = std::move(func)]() { + if (widget->property(IS_UPDATING_PROPERTY).toBool()) + return; + + const bool old = widget->blockSignals(true); + setNullableIntValue(widget, std::nullopt); + widget->blockSignals(old); + func(); + + widget->setProperty(IS_UPDATING_PROPERTY, QVariant(true)); + emit widget->valueChanged(widget->value()); + widget->setProperty(IS_UPDATING_PROPERTY, QVariant(false)); + }); + menu.exec(widget->mapToGlobal(pt)); + }); + widget->connect(widget, &QSlider::valueChanged, widget, [widget, func = std::move(func)]() { + if (widget->property(IS_UPDATING_PROPERTY).toBool()) + return; + + if (widget->property(IS_NULL_PROPERTY).toBool()) + widget->setProperty(IS_NULL_PROPERTY, QVariant(false)); + func(); + }); + } } }; @@ -706,6 +786,45 @@ static void BindWidgetToIntSetting(SettingsInterface* sif, WidgetType* widget, s } } +template +static inline void BindWidgetAndLabelToIntSetting(SettingsInterface* sif, T* slider, QLabel* label, + const QString& label_suffix, std::string section, std::string key, + s32 default_value) +{ + using Accessor = SettingAccessor; + + BindWidgetToIntSetting(sif, slider, std::move(section), std::move(key), default_value); + if (sif) + { + QFont orig_font(label->font()); + QFont bold_font(orig_font); + bold_font.setBold(true); + + Accessor::connectValueChanged( + slider, [slider, label, label_suffix, bold_font = std::move(bold_font), orig_font = std::move(orig_font)]() { + std::optional value = Accessor::getNullableIntValue(slider); + if (value.has_value()) + { + label->setFont(bold_font); + label->setText(QStringLiteral("%1%2").arg(value.value()).arg(label_suffix)); + } + else + { + const int global_value = Accessor::getIntValue(slider); + label->setFont(orig_font); + label->setText(QStringLiteral("%1%2").arg(global_value).arg(label_suffix)); + } + }); + } + else + { + Accessor::connectValueChanged(slider, [slider, label, label_suffix]() { + const int global_value = Accessor::getIntValue(slider); + label->setText(QStringLiteral("%1%2").arg(global_value).arg(label_suffix)); + }); + } +} + template static void BindWidgetToFloatSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, float default_value)