mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-12 17:19:28 +01:00
Config: Describe the settings for the UI, and find them by pointer
ConfigSetting gets a ConfigSettingInfo, set with constexpr chained setters: a label, choices, a range with step and units, labels for 0 and negative values (like Unlimited and Disabled), and a callback for what has to happen when the UI changes the value. Using a setter on the wrong type of setting is a compile error. The settings in the config tables now have these, so the settings screen can take them from there instead of repeating them, including what changing them needs (resize and GPU config messages, fullscreen, audio and camera notifications and so on). The settings screen starts using them in a later commit. Config::FindSetting finds a setting from a pointer to its value. Defaults can now also be looked up for blocks that reset as a whole (display layouts, touch and gesture controls), by reading them from a freshly reset copy of the block. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
09b899845a
commit
3bb4664fd6
6 files changed
+707
-202
No files matched your search
+552
-197
File diff suppressed because it is too large.
Load diff
+9
-2
@@ -65,6 +65,7 @@ private:
|
||||
};
|
||||
|
||||
struct ConfigSetting;
|
||||
struct ConfigSettingRef;
|
||||
|
||||
struct ConfigSectionMeta {
|
||||
ConfigBlock *configBlock;
|
||||
@@ -97,6 +98,7 @@ struct DisplayLayoutConfig : public ConfigBlock {
|
||||
bool InternalRotationIsPortrait() const;
|
||||
bool CanResetToDefault() const override { return true; }
|
||||
bool ResetToDefault(std::string_view blockName) override;
|
||||
std::unique_ptr<ConfigBlock> NewDefault(std::string_view blockName) const override;
|
||||
size_t Size() const override { return sizeof(DisplayLayoutConfig); } // For sanity checks
|
||||
};
|
||||
|
||||
@@ -146,6 +148,7 @@ struct TouchControlConfig : public ConfigBlock {
|
||||
|
||||
bool CanResetToDefault() const override { return true; }
|
||||
bool ResetToDefault(std::string_view blockName) override;
|
||||
std::unique_ptr<ConfigBlock> NewDefault(std::string_view blockName) const override;
|
||||
size_t Size() const override { return sizeof(TouchControlConfig); } // For sanity checks
|
||||
};
|
||||
|
||||
@@ -164,6 +167,7 @@ struct GestureControlConfig : public ConfigBlock {
|
||||
|
||||
bool CanResetToDefault() const override { return true; }
|
||||
bool ResetToDefault(std::string_view blockName) override;
|
||||
std::unique_ptr<ConfigBlock> NewDefault(std::string_view blockName) const override;
|
||||
size_t Size() const override { return sizeof(GestureControlConfig); } // For sanity checks
|
||||
};
|
||||
|
||||
@@ -813,7 +817,10 @@ public:
|
||||
return orientation == DeviceOrientation::Portrait ? touchControlsPortrait : touchControlsLandscape;
|
||||
}
|
||||
|
||||
static int GetDefaultValueInt(int *configSetting);
|
||||
// Finds the setting a pointer into g_Config (or one of its blocks) points to. Null if it isn't one.
|
||||
static const ConfigSettingRef *FindSetting(const void *ptr);
|
||||
static int GetDefaultValueInt(const int *configSetting);
|
||||
static float GetDefaultValueFloat(const float *configSetting);
|
||||
|
||||
void DoNotSaveSetting(void *configSetting) {
|
||||
settingsNotToSave_.push_back(configSetting);
|
||||
@@ -828,7 +835,7 @@ private:
|
||||
|
||||
friend struct ConfigSetting;
|
||||
|
||||
static std::map<const void *, std::pair<const ConfigBlock *, const ConfigSetting *>> &getPtrLUT();
|
||||
static std::map<const void *, ConfigSettingRef> &getPtrLUT();
|
||||
|
||||
// Applies defaults for missing settings.
|
||||
void ReadAllSettings(const IniFile &iniFile);
|
||||
|
||||
@@ -8,8 +8,16 @@
|
||||
#include "Core/ConfigValues.h"
|
||||
#include "Core/Config.h"
|
||||
|
||||
void ConfigSettingTableError() {
|
||||
_dbg_assert_msg_(false, "ConfigSetting info setter used on the wrong kind of setting");
|
||||
}
|
||||
|
||||
bool ConfigSetting::PerGame(void *ptr) {
|
||||
return g_Config.IsGameSpecific() && g_Config.getPtrLUT().count(ptr) > 0 && g_Config.getPtrLUT()[ptr].second->PerGame();
|
||||
if (!g_Config.IsGameSpecific()) {
|
||||
return false;
|
||||
}
|
||||
const ConfigSettingRef *ref = Config::FindSetting(ptr);
|
||||
return ref && ref->setting->PerGame();
|
||||
}
|
||||
|
||||
bool ConfigSetting::ReadFromIniSection(ConfigBlock *configBlock, const Section *section, bool applyDefaultIfMissing) const {
|
||||
|
||||
+108
-2
@@ -6,6 +6,7 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/Data/Text/I18n.h"
|
||||
#include "Core/ConfigValues.h"
|
||||
|
||||
class Path;
|
||||
@@ -28,6 +29,38 @@ struct ConfigSettingOffset {
|
||||
uint32_t offset;
|
||||
};
|
||||
|
||||
// What a setting means, for the UI: its name, and which values make sense. Set with the chained
|
||||
// setters on ConfigSetting. All texts are translation keys in the label's category.
|
||||
struct ConfigSettingInfo {
|
||||
I18NCat cat = I18NCat::NONE;
|
||||
const char *label = nullptr;
|
||||
const char *labelDefault = nullptr; // English text, if the key isn't it.
|
||||
|
||||
// Int settings, with the values choiceMin and up.
|
||||
const char *const *choices = nullptr;
|
||||
int choiceCount = 0;
|
||||
int choiceMin = 0;
|
||||
|
||||
// Int and float settings.
|
||||
float minValue = 0.0f;
|
||||
float maxValue = 0.0f;
|
||||
float step = 0.0f;
|
||||
const char *units = nullptr;
|
||||
|
||||
// Int settings: texts shown instead of 0 (like "Unlimited") and negative values (like "Disabled").
|
||||
const char *zeroLabel = nullptr;
|
||||
const char *negativeLabel = nullptr;
|
||||
|
||||
// Called when the UI changes the value, for what has to happen because of the new value.
|
||||
void (*onChanged)() = nullptr;
|
||||
|
||||
constexpr bool HasRange() const { return maxValue > minValue; }
|
||||
constexpr bool HasChoices() const { return choiceCount > 0; }
|
||||
};
|
||||
|
||||
// Called when a setter is used wrongly. It isn't constexpr, so in a constexpr table that's a compile error.
|
||||
void ConfigSettingTableError();
|
||||
|
||||
struct ConfigSetting {
|
||||
enum class Type {
|
||||
TYPE_TERMINATOR,
|
||||
@@ -143,6 +176,56 @@ struct ConfigSetting {
|
||||
constexpr ConfigSetting(std::string_view iniX, const char *iniY, const char *iniScale, const char *iniShow, ConfigSettingOffset<ConfigTouchPos> v, TouchPosDefaultCallback def, CfgFlag flags) noexcept
|
||||
: ini2_(iniY), ini3_(iniScale), ini4_(iniShow), type_(Type::TYPE_TOUCH_POS), iniKey_(iniX), flags_(flags), defaultCallback_(def), offset_(v.offset) {}
|
||||
|
||||
// Chained setters for the info. Each returns a modified copy, so they can be used in constexpr tables.
|
||||
constexpr ConfigSetting Label(I18NCat cat, const char *label, const char *labelDefault = nullptr) const {
|
||||
ConfigSetting s = *this;
|
||||
s.info_.cat = cat;
|
||||
s.info_.label = label;
|
||||
s.info_.labelDefault = labelDefault;
|
||||
return s;
|
||||
}
|
||||
// Int values from minValue and up.
|
||||
template <size_t N>
|
||||
constexpr ConfigSetting Choices(const char *const (&names)[N], int minValue = 0) const {
|
||||
Check(type_ == Type::TYPE_INT && !info_.HasChoices() && !info_.HasRange());
|
||||
ConfigSetting s = *this;
|
||||
s.info_.choices = names;
|
||||
s.info_.choiceCount = (int)N;
|
||||
s.info_.choiceMin = minValue;
|
||||
return s;
|
||||
}
|
||||
constexpr ConfigSetting Range(float minValue, float maxValue, float step = 0.0f) const {
|
||||
Check((type_ == Type::TYPE_INT || type_ == Type::TYPE_FLOAT) && !info_.HasChoices() && maxValue > minValue);
|
||||
ConfigSetting s = *this;
|
||||
s.info_.minValue = minValue;
|
||||
s.info_.maxValue = maxValue;
|
||||
s.info_.step = step;
|
||||
return s;
|
||||
}
|
||||
constexpr ConfigSetting Units(const char *units) const {
|
||||
Check(type_ == Type::TYPE_INT || type_ == Type::TYPE_FLOAT);
|
||||
ConfigSetting s = *this;
|
||||
s.info_.units = units;
|
||||
return s;
|
||||
}
|
||||
constexpr ConfigSetting OnChanged(void (*func)()) const {
|
||||
ConfigSetting s = *this;
|
||||
s.info_.onChanged = func;
|
||||
return s;
|
||||
}
|
||||
constexpr ConfigSetting ZeroLabel(const char *label) const {
|
||||
Check(type_ == Type::TYPE_INT);
|
||||
ConfigSetting s = *this;
|
||||
s.info_.zeroLabel = label;
|
||||
return s;
|
||||
}
|
||||
constexpr ConfigSetting NegativeLabel(const char *label) const {
|
||||
Check(type_ == Type::TYPE_INT);
|
||||
ConfigSetting s = *this;
|
||||
s.info_.negativeLabel = label;
|
||||
return s;
|
||||
}
|
||||
|
||||
bool ReadFromIniSection(ConfigBlock *configBlock, const Section *section, bool applyDefaultIfMissing) const;
|
||||
|
||||
// Yes, this can be const because what's modified is not the ConfigSetting struct, but the value which is stored elsewhere.
|
||||
@@ -164,6 +247,14 @@ struct ConfigSetting {
|
||||
_dbg_assert_(type_ == Type::TYPE_INT);
|
||||
return defaultCallback_.i ? defaultCallback_.i() : default_.i;
|
||||
}
|
||||
float GetDefaultFloat() const {
|
||||
_dbg_assert_(type_ == Type::TYPE_FLOAT);
|
||||
return defaultCallback_.f ? defaultCallback_.f() : default_.f;
|
||||
}
|
||||
|
||||
constexpr const ConfigSettingInfo &Info() const {
|
||||
return info_;
|
||||
}
|
||||
|
||||
std::string_view IniKey() const {
|
||||
return iniKey_;
|
||||
@@ -179,12 +270,18 @@ struct ConfigSetting {
|
||||
// Returns false if per-game settings are not currently used
|
||||
static bool PerGame(void *ptr);
|
||||
|
||||
const void *GetVoidPtr(ConfigBlock *configBlock) const {
|
||||
char *configBlockBase = (char *)configBlock;
|
||||
const void *GetVoidPtr(const ConfigBlock *configBlock) const {
|
||||
const char *configBlockBase = (const char *)configBlock;
|
||||
return (const void *)(configBlockBase + offset_);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr void Check(bool ok) {
|
||||
if (!ok) {
|
||||
ConfigSettingTableError();
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view iniKey_;
|
||||
CfgFlag flags_;
|
||||
DefaultValue default_{};
|
||||
@@ -194,4 +291,13 @@ private:
|
||||
// We only support transform for ints.
|
||||
std::string (*translateTo_)(int) = nullptr;
|
||||
int (*translateFrom_)(const std::string &) = nullptr;
|
||||
|
||||
ConfigSettingInfo info_{};
|
||||
};
|
||||
|
||||
// Where a setting lives: the block instance it's in, and the name of that block's ini section.
|
||||
struct ConfigSettingRef {
|
||||
ConfigBlock *block;
|
||||
const ConfigSetting *setting;
|
||||
std::string_view section;
|
||||
};
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#ifndef _MSC_VER
|
||||
@@ -32,6 +33,8 @@ struct ConfigBlock {
|
||||
virtual bool CanResetToDefault() const { return false; }
|
||||
// If a block returns false here (like Config itself does), resetting to default will happen by the old per-setting mechanism.
|
||||
virtual bool ResetToDefault(std::string_view blockName) { return false; }
|
||||
// For blocks that can reset themselves: a new block with the defaults of the named one, to read defaults from.
|
||||
virtual std::unique_ptr<ConfigBlock> NewDefault(std::string_view blockName) const { return nullptr; }
|
||||
virtual size_t Size() const { return sizeof(ConfigBlock); } // For sanity checks
|
||||
};
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
#include "Common/Log.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Core/Config.h"
|
||||
#include "Core/ConfigSettings.h"
|
||||
#include "Common/Data/Convert/ColorConv.h"
|
||||
#include "Common/File/VFS/VFS.h"
|
||||
#include "Common/File/VFS/DirectoryReader.h"
|
||||
@@ -3217,6 +3218,30 @@ bool TestVolumeFunc() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The info setters have to work in constant expressions, since the config tables are constexpr.
|
||||
static constexpr const char *testChoices[] = { "Off", "On", "Auto" };
|
||||
static constexpr ConfigSetting testSliderSetting = ConfigSetting("TestSlider", ConfigSettingOffset<int>{ 0 }, 50, CfgFlag::DEFAULT)
|
||||
.Label(I18NCat::AUDIO, "Test slider").Range(0, 100, 5).Units("%").ZeroLabel("Off").NegativeLabel("Disabled");
|
||||
static_assert(testSliderSetting.Info().HasRange() && !testSliderSetting.Info().HasChoices());
|
||||
static_assert(testSliderSetting.Info().zeroLabel && testSliderSetting.Info().negativeLabel);
|
||||
static constexpr ConfigSetting testChoiceSetting = ConfigSetting("TestChoice", ConfigSettingOffset<int>{ 0 }, 1, CfgFlag::DEFAULT)
|
||||
.Label(I18NCat::AUDIO, "Test choice").Choices(testChoices, 1);
|
||||
static_assert(testChoiceSetting.Info().choiceCount == 3 && testChoiceSetting.Info().choiceMin == 1 && !testChoiceSetting.Info().HasRange());
|
||||
|
||||
bool TestConfigSettings() {
|
||||
g_Config.Init();
|
||||
|
||||
const ConfigSettingRef *ref = Config::FindSetting(&g_Config.iUIVolume);
|
||||
EXPECT_TRUE(ref && ref->setting->IniKey() == "UIVolume");
|
||||
EXPECT_EQ_INT(Config::GetDefaultValueInt(&g_Config.iUIVolume), 75);
|
||||
|
||||
// Blocks that reset as a whole, where the default can depend on which block it is.
|
||||
EXPECT_EQ_FLOAT(Config::GetDefaultValueFloat(&g_Config.displayLayoutLandscape.fDisplayOffsetY), 0.5f);
|
||||
EXPECT_EQ_FLOAT(Config::GetDefaultValueFloat(&g_Config.displayLayoutPortrait.fDisplayOffsetY), 0.25f);
|
||||
EXPECT_EQ_INT(Config::GetDefaultValueInt(&g_Config.gestureControls[1].iSwipeUp), 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TestLinAlg() {
|
||||
static const float m1[16] = {
|
||||
1, 2, 3, 4,
|
||||
@@ -4080,6 +4105,7 @@ TestItem availableTests[] = {
|
||||
TEST_ITEM(SIMD),
|
||||
TEST_ITEM(CrossSIMD),
|
||||
TEST_ITEM(VolumeFunc),
|
||||
TEST_ITEM(ConfigSettings),
|
||||
TEST_ITEM(SplitSearch),
|
||||
TEST_ITEM(FriendlyPath),
|
||||
TEST_ITEM(LinAlg),
|
||||
|
||||
Reference in new issue
Block a user