From 5e3b54e98e018ab5c08732f20b467bf5a1e9d78e Mon Sep 17 00:00:00 2001 From: obligaron Date: Sun, 7 Nov 2021 23:09:14 +0100 Subject: [PATCH] Introduce OptionEntry/OptionCategory --- Source/options.cpp | 126 ++++++++++++++++++++++++++++++++++++ Source/options.h | 157 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 283 insertions(+) diff --git a/Source/options.cpp b/Source/options.cpp index 856eeaa62..8b6fa03e9 100644 --- a/Source/options.cpp +++ b/Source/options.cpp @@ -215,6 +215,12 @@ bool sbWasOptionsLoaded = false; void LoadOptions() { + for (OptionCategoryBase *pCategory : sgOptions.GetCategories()) { + for (OptionEntryBase *pEntry : pCategory->GetEntries()) { + pEntry->LoadFromIni(pCategory->GetKey()); + } + } + sgOptions.Diablo.bIntro = GetIniBool("Diablo", "Intro", true); sgOptions.Diablo.lastSinglePlayerHero = GetIniInt("Diablo", "LastSinglePlayerHero", 0); sgOptions.Diablo.lastMultiplayerHero = GetIniInt("Diablo", "LastMultiplayerHero", 0); @@ -373,6 +379,12 @@ void LoadOptions() void SaveOptions() { + for (OptionCategoryBase *pCategory : sgOptions.GetCategories()) { + for (OptionEntryBase *pEntry : pCategory->GetEntries()) { + pEntry->SaveToIni(pCategory->GetKey()); + } + } + SetIniValue("Diablo", "Intro", sgOptions.Diablo.bIntro); SetIniValue("Diablo", "LastSinglePlayerHero", sgOptions.Diablo.lastSinglePlayerHero); SetIniValue("Diablo", "LastMultiplayerHero", sgOptions.Diablo.lastMultiplayerHero); @@ -459,4 +471,118 @@ void SaveOptions() SaveIni(); } +string_view OptionEntryBase::GetName() const +{ + return _(name.data()); +} +string_view OptionEntryBase::GetDescription() const +{ + return _(description.data()); +} +OptionEntryFlags OptionEntryBase::GetFlags() const +{ + return flags; +} +void OptionEntryBase::SetValueChangedCallback(std::function callback) +{ + this->callback = callback; +} +void OptionEntryBase::NotifyValueChanged() +{ + if (callback) + callback(); +} + +void OptionEntryBoolean::LoadFromIni(string_view category) +{ + value = GetIniBool(category.data(), key.data(), defaultValue); +} +void OptionEntryBoolean::SaveToIni(string_view category) const +{ + SetIniValue(category.data(), key.data(), value); +} +bool OptionEntryBoolean::operator*() const +{ + return value; +} +void OptionEntryBoolean::SetValue(bool value) +{ + this->value = value; + this->NotifyValueChanged(); +} +OptionEntryType OptionEntryBoolean::GetType() const +{ + return OptionEntryType::Boolean; +} +string_view OptionEntryBoolean::GetValueDescription() const +{ + return value ? _("ON") : _("OFF"); +} + +OptionEntryType OptionEntryListBase::GetType() const +{ + return OptionEntryType::List; +} +string_view OptionEntryListBase::GetValueDescription() const +{ + return GetListDescription(GetActiveListIndex()); +} + +void OptionEntryEnumBase::LoadFromIni(string_view category) +{ + value = GetIniInt(category.data(), key.data(), defaultValue); +} +void OptionEntryEnumBase::SaveToIni(string_view category) const +{ + SetIniValue(category.data(), key.data(), value); +} +int OptionEntryEnumBase::GetValueInternal() +{ + return value; +} +void OptionEntryEnumBase::AddEntry(int value, string_view name) +{ + entryValues.push_back(value); + entryNames.push_back(name); +} +size_t OptionEntryEnumBase::GetListSize() const +{ + return entryValues.size(); +} +string_view OptionEntryEnumBase::GetListDescription(size_t index) const +{ + return _(entryNames[index].data()); +} +size_t OptionEntryEnumBase::GetActiveListIndex() const +{ + auto iterator = std::find(entryValues.begin(), entryValues.end(), value); + if (iterator == entryValues.end()) + return 0; + return std::distance(entryValues.begin(), iterator); +} +void OptionEntryEnumBase::SetActiveListIndex(size_t index) +{ + this->value = entryValues[index]; + this->NotifyValueChanged(); +} + +OptionCategoryBase::OptionCategoryBase(string_view key, string_view name, string_view description) + : key(key) + , name(name) + , description(description) +{ +} +string_view OptionCategoryBase::GetKey() const +{ + return key; +} +string_view OptionCategoryBase::GetName() const +{ + return _(name.data()); +} +string_view OptionCategoryBase::GetDescription() const +{ + return _(description.data()); +} + } // namespace devilution diff --git a/Source/options.h b/Source/options.h index 522f82fbe..7b7bb61f5 100644 --- a/Source/options.h +++ b/Source/options.h @@ -5,6 +5,8 @@ #include #include "pack.h" +#include "utils/enum_traits.h" +#include "utils/stdcompat/string_view.hpp" namespace devilution { @@ -14,6 +16,156 @@ enum class StartUpGameOption { Diablo, }; +enum class OptionEntryType { + Boolean, + List, +}; + +enum class OptionEntryFlags { + /** @brief No special logic. */ + None = 0, + /** @brief Shouldn't be shown in settings dialog. */ + Invisible = 1 << 0, + /** @brief Need to restart the current running game (single- or multiplayer) to take effect. */ + CantChangeInGame = 1 << 1, + /** @brief Need to restart the current running multiplayer game to take effect. */ + CantChangeInMultiPlayer = 1 << 2, + /** @brief Option is only relevant for Hellfire. */ + OnlyHellfire = 1 << 3, + /** @brief Option is only relevant for Diablo. */ + OnlyDiablo = 1 << 4, +}; +use_enum_as_flags(OptionEntryFlags); + +class OptionEntryBase { +public: + OptionEntryBase(string_view key, OptionEntryFlags flags, string_view name, string_view description) + : key(key) + , flags(flags) + , name(name) + , description(description) + { + } + [[nodiscard]] string_view GetName() const; + [[nodiscard]] string_view GetDescription() const; + [[nodiscard]] virtual OptionEntryType GetType() const = 0; + [[nodiscard]] OptionEntryFlags GetFlags() const; + + void SetValueChangedCallback(std::function callback); + + [[nodiscard]] virtual string_view GetValueDescription() const = 0; + virtual void LoadFromIni(string_view category) = 0; + virtual void SaveToIni(string_view category) const = 0; + +protected: + string_view key; + string_view name; + string_view description; + OptionEntryFlags flags; + void NotifyValueChanged(); + +private: + std::function callback; +}; + +class OptionEntryBoolean : public OptionEntryBase { +public: + OptionEntryBoolean(string_view key, OptionEntryFlags flags, string_view name, string_view description, bool defaultValue) + : OptionEntryBase(key, flags, name, description) + , defaultValue(defaultValue) + , value(defaultValue) + { + } + [[nodiscard]] bool operator*() const; + void SetValue(bool value); + + [[nodiscard]] OptionEntryType GetType() const override; + [[nodiscard]] string_view GetValueDescription() const override; + void LoadFromIni(string_view category) override; + void SaveToIni(string_view category) const override; + +private: + bool value; + bool defaultValue; +}; + +class OptionEntryListBase : public OptionEntryBase { +public: + [[nodiscard]] virtual size_t GetListSize() const = 0; + [[nodiscard]] virtual string_view GetListDescription(size_t index) const = 0; + [[nodiscard]] virtual size_t GetActiveListIndex() const = 0; + virtual void SetActiveListIndex(size_t index) = 0; + + [[nodiscard]] OptionEntryType GetType() const override; + [[nodiscard]] string_view GetValueDescription() const override; + +protected: + OptionEntryListBase(string_view key, OptionEntryFlags flags, string_view name, string_view description) + : OptionEntryBase(key, flags, name, description) + { + } +}; + +class OptionEntryEnumBase : public OptionEntryListBase { +public: + void LoadFromIni(string_view category) override; + void SaveToIni(string_view category) const override; + + [[nodiscard]] virtual size_t GetListSize() const override; + [[nodiscard]] virtual string_view GetListDescription(size_t index) const override; + [[nodiscard]] size_t GetActiveListIndex() const override; + void SetActiveListIndex(size_t index) override; + +protected: + OptionEntryEnumBase(string_view key, OptionEntryFlags flags, string_view name, string_view description, int defaultValue) + : OptionEntryListBase(key, flags, name, description) + , defaultValue(defaultValue) + , value(defaultValue) + { + } + + int GetValueInternal(); + + void AddEntry(int value, string_view name); + +private: + int value; + int defaultValue; + std::vector entryNames; + std::vector entryValues; +}; + +template +class OptionEntryEnum : public OptionEntryEnumBase { +public: + OptionEntryEnum(string_view key, OptionEntryFlags flags, string_view name, string_view description, T defaultValue, std::initializer_list> entries) + : OptionEntryEnumBase(key, flags, name, description, static_cast(defaultValue)) + { + for (auto entry : entries) { + AddEntry(static_cast(entry.first), entry.second); + } + } + [[nodiscard]] T operator*() const + { + return static_cast(GetValueInternal()); + } +}; + +struct OptionCategoryBase { + OptionCategoryBase(string_view key, string_view name, string_view description); + + [[nodiscard]] string_view GetKey() const; + [[nodiscard]] string_view GetName() const; + [[nodiscard]] string_view GetDescription() const; + + virtual std::vector GetEntries() = 0; + +protected: + string_view key; + string_view name; + string_view description; +}; + struct DiabloOptions { /** @brief Play game intro video on startup. */ bool bIntro; @@ -184,6 +336,11 @@ struct Options { NetworkOptions Network; ChatOptions Chat; LanguageOptions Language; + + [[nodiscard]] std::vector GetCategories() + { + return {}; + } }; bool GetIniValue(const char *sectionName, const char *keyName, char *string, int stringSize, const char *defaultString = "");