diff --git a/loader/include/Geode/loader/Mod.hpp b/loader/include/Geode/loader/Mod.hpp index 33b43175..af89e6e4 100644 --- a/loader/include/Geode/loader/Mod.hpp +++ b/loader/include/Geode/loader/Mod.hpp @@ -10,6 +10,7 @@ #include "Hook.hpp" #include "ModMetadata.hpp" #include "Setting.hpp" +#include "SettingV3.hpp" #include "Types.hpp" #include "Loader.hpp" @@ -23,6 +24,8 @@ #include namespace geode { + class SettingV3; + template struct HandleToSaved : public T { Mod* m_mod; @@ -175,6 +178,7 @@ namespace geode { bool hasSetting(std::string_view const key) const; std::optional getSettingDefinition(std::string_view const key) const; SettingValue* getSetting(std::string_view const key) const; + std::shared_ptr getSettingV3(std::string_view const key) const; /** * Register a custom setting's value class. See Mod::addCustomSetting @@ -187,6 +191,11 @@ namespace geode { * @see addCustomSetting */ void registerCustomSetting(std::string_view const key, std::unique_ptr value); + + /** + * Register a custom setting + */ + Result<> registerCustomSettingV3(std::string_view const key, std::shared_ptr value); /** * Register a custom setting's value class. The new SettingValue class * will be created in-place using `std::make_unique`. See @@ -246,19 +255,27 @@ namespace geode { matjson::Value& getSaveContainer(); matjson::Value& getSavedSettingsData(); + /** + * Get the value of a [setting](https://docs.geode-sdk.org/mods/settings). + * To use this for custom settings, first specialize the + * `SettingTypeForValueType` class, and then make sure your custom + * setting type has a `getValue` function which returns the value + */ template T getSettingValue(std::string_view const key) const { - if (auto sett = this->getSetting(key)) { - return SettingValueSetter::get(sett); + using S = typename SettingTypeForValueType::SettingType; + if (auto sett = typeinfo_pointer_cast(this->getSettingV3(key))) { + return sett->getValue(); } return T(); } template T setSettingValue(std::string_view const key, T const& value) { - if (auto sett = this->getSetting(key)) { - auto old = this->getSettingValue(key); - SettingValueSetter::set(sett, value); + using S = typename SettingTypeForValueType::SettingType; + if (auto sett = typeinfo_pointer_cast(this->getSettingV3(key))) { + auto old = sett->getValue(); + sett->setValue(value); return old; } return T(); diff --git a/loader/include/Geode/loader/Setting.hpp b/loader/include/Geode/loader/Setting.hpp index 50317b70..45cc6491 100644 --- a/loader/include/Geode/loader/Setting.hpp +++ b/loader/include/Geode/loader/Setting.hpp @@ -286,9 +286,7 @@ namespace geode { return Setting(m_key, m_modID, m_definition); } - ValueType getValue() const { - return m_value; - } + GEODE_DLL ValueType getValue() const; GEODE_DLL void setValue(ValueType const& value); GEODE_DLL Result<> validate(ValueType const& value) const; }; @@ -301,8 +299,10 @@ namespace geode { using ColorSettingValue = GeodeSettingValue; using ColorAlphaSettingValue = GeodeSettingValue; + // todo: remove in v3 + template - struct GEODE_DLL SettingValueSetter { + struct [[deprecated("Use SettingTypeForValueType from SettingV3 instead")]] GEODE_DLL SettingValueSetter { static T get(SettingValue* setting); static void set(SettingValue* setting, T const& value); }; diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index f2ef2c55..794bea31 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -5,18 +5,28 @@ #include // todo: remove this header in 4.0.0 #include "Setting.hpp" +#include "../utils/cocos.hpp" + +// todo in v4: these can be removed as well as the friend decl in UnresolvedCustomSettingV3 +class ModSettingsManager; +class LegacyCustomSettingToV3Node; namespace geode { class SettingNodeV3; class JsonExpectedValue; - class GEODE_DLL SettingV3 { + class GEODE_DLL SettingV3 : public std::enable_shared_from_this { private: class GeodeImpl; std::shared_ptr m_impl; + protected: + virtual Result<> onParse( + std::string const& key, std::string const& modID, matjson::Value const& json + ) = 0; + public: - SettingV3(std::string const& key, std::string const& modID); + SettingV3(); virtual ~SettingV3(); /** @@ -33,7 +43,7 @@ namespace geode { */ Mod* getMod() const; - virtual Result<> parse(std::string const& modID, matjson::Value const& json) = 0; + Result<> parse(std::string const& key, std::string const& modID, matjson::Value const& json); virtual bool load(matjson::Value const& json) = 0; virtual bool save(matjson::Value& json) const = 0; virtual SettingNodeV3* createNode(float width) = 0; @@ -47,9 +57,11 @@ namespace geode { [[deprecated("This function will be removed alongside legacy settings in 4.0.0!")]] virtual std::optional convertToLegacy() const; [[deprecated("This function will be removed alongside legacy settings in 4.0.0!")]] - virtual std::optional> convertToLegacyValue() const; + virtual std::optional> convertToLegacyValue() const; - static Result> parseBuiltin(std::string const& modID, matjson::Value const& json); + static Result> parseBuiltin( + std::string const& key, std::string const& modID, matjson::Value const& json + ); }; namespace detail { @@ -67,6 +79,32 @@ namespace geode { std::optional getDescription() const; std::optional getEnableIf() const; }; + + template + class GeodeSettingBaseValueV3 : public GeodeSettingBaseV3 { + protected: + virtual T& getValueMut() const = 0; + + public: + using ValueType = T; + + virtual T getDefaultValue() const = 0; + + T getValue() const { + return this->getValueMut(); + } + void setValue(V value) { + this->getValueMut() = this->isValid(value) ? value : this->getDefaultValue(); + } + virtual Result<> isValid(V value) const = 0; + + bool isDefaultValue() const override { + return this->getValue() == this->getDefaultValue(); + } + void reset() { + this->setValue(this->getDefaultValue()); + } + }; } class GEODE_DLL TitleSettingV3 final : public SettingV3 { @@ -74,10 +112,12 @@ namespace geode { class Impl; std::shared_ptr m_impl; + protected: + Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; + public: std::string getTitle() const; - Result<> parse(std::string const& modID, matjson::Value const& json) override; bool load(matjson::Value const& json) override; bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; @@ -90,9 +130,14 @@ namespace geode { private: class Impl; std::shared_ptr m_impl; + + friend class ModSettingsManager; + friend class LegacyCustomSettingToV3Node; + protected: + Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; + public: - Result<> parse(std::string const& modID, matjson::Value const& json) override; bool load(matjson::Value const& json) override; bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; @@ -101,44 +146,43 @@ namespace geode { void reset() override; std::optional convertToLegacy() const override; - std::optional> convertToLegacyValue() const override; + std::optional> convertToLegacyValue() const override; }; - class GEODE_DLL BoolSettingV3 final : public detail::GeodeSettingBaseV3 { + class GEODE_DLL BoolSettingV3 final : public detail::GeodeSettingBaseValueV3 { private: class Impl; std::shared_ptr m_impl; - public: - bool getValue() const; - void setValue(bool value); - Result<> isValid(bool value) const; + protected: + bool& getValueMut() const override; + Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; - bool getDefaultValue() const; + public: + bool getDefaultValue() const override; + Result<> isValid(bool value) const override; - Result<> parse(std::string const& modID, matjson::Value const& json) override; bool load(matjson::Value const& json) override; bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; - bool isDefaultValue() const override; - void reset() override; - std::optional convertToLegacy() const override; - std::optional> convertToLegacyValue() const override; + std::optional> convertToLegacyValue() const override; }; - class GEODE_DLL IntSettingV3 final : public detail::GeodeSettingBaseV3 { + class GEODE_DLL IntSettingV3 final : public detail::GeodeSettingBaseValueV3 { private: class Impl; std::shared_ptr m_impl; - public: - int64_t getValue() const; - void setValue(int64_t value); - Result<> isValid(int64_t value) const; + protected: + int64_t& getValueMut() const override; + Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; + + public: + int64_t getDefaultValue() const override; + Result<> isValid(int64_t value) const override; - int64_t getDefaultValue() const; std::optional getMinValue() const; std::optional getMaxValue() const; @@ -150,29 +194,27 @@ namespace geode { std::optional getSliderSnap() const; bool isInputEnabled() const; - Result<> parse(std::string const& modID, matjson::Value const& json) override; bool load(matjson::Value const& json) override; bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; - bool isDefaultValue() const override; - void reset() override; - std::optional convertToLegacy() const override; - std::optional> convertToLegacyValue() const override; + std::optional> convertToLegacyValue() const override; }; - class GEODE_DLL FloatSettingV3 final : public detail::GeodeSettingBaseV3 { + class GEODE_DLL FloatSettingV3 final : public detail::GeodeSettingBaseValueV3 { private: class Impl; std::shared_ptr m_impl; - public: - double getValue() const; - void setValue(double value); - Result<> isValid(double value) const; + protected: + double& getValueMut() const override; + Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; + + public: + double getDefaultValue() const override; + Result<> isValid(double value) const override; - double getDefaultValue() const; std::optional getMinValue() const; std::optional getMaxValue() const; @@ -184,122 +226,111 @@ namespace geode { std::optional getSliderSnap() const; bool isInputEnabled() const; - Result<> parse(std::string const& modID, matjson::Value const& json) override; bool load(matjson::Value const& json) override; bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; - bool isDefaultValue() const override; - void reset() override; - std::optional convertToLegacy() const override; - std::optional> convertToLegacyValue() const override; + std::optional> convertToLegacyValue() const override; }; - class GEODE_DLL StringSettingV3 final : public detail::GeodeSettingBaseV3 { + class GEODE_DLL StringSettingV3 final : public detail::GeodeSettingBaseValueV3 { private: class Impl; std::shared_ptr m_impl; - public: - std::string getValue() const; - void setValue(std::string_view value); - Result<> isValid(std::string_view value) const; + protected: + std::string& getValueMut() const override; + Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; - std::string getDefaultValue() const; + public: + std::string getDefaultValue() const override; + Result<> isValid(std::string_view value) const override; std::optional getRegexValidator() const; std::optional getAllowedCharacters() const; std::optional> getEnumOptions() const; - Result<> parse(std::string const& modID, matjson::Value const& json) override; bool load(matjson::Value const& json) override; bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; - bool isDefaultValue() const override; - void reset() override; - std::optional convertToLegacy() const override; - std::optional> convertToLegacyValue() const override; + std::optional> convertToLegacyValue() const override; }; - class GEODE_DLL FileSettingV3 final : public detail::GeodeSettingBaseV3 { + class GEODE_DLL FileSettingV3 final : public detail::GeodeSettingBaseValueV3 { private: class Impl; std::shared_ptr m_impl; - public: - std::filesystem::path getValue() const; - void setValue(std::filesystem::path const& value); - Result<> isValid(std::filesystem::path value) const; + protected: + std::filesystem::path& getValueMut() const override; + Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; + + public: + std::filesystem::path getDefaultValue() const override; + Result<> isValid(std::filesystem::path const& value) const override; - std::filesystem::path getDefaultValue() const; std::optional> getFilters() const; - Result<> parse(std::string const& modID, matjson::Value const& json) override; bool load(matjson::Value const& json) override; bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; - bool isDefaultValue() const override; - void reset() override; - std::optional convertToLegacy() const override; - std::optional> convertToLegacyValue() const override; + std::optional> convertToLegacyValue() const override; }; - class GEODE_DLL Color3BSettingV3 final : public detail::GeodeSettingBaseV3 { + class GEODE_DLL Color3BSettingV3 final : public detail::GeodeSettingBaseValueV3 { private: class Impl; std::shared_ptr m_impl; - public: - cocos2d::ccColor3B getValue() const; - void setValue(cocos2d::ccColor3B value); - Result<> isValid(cocos2d::ccColor3B value) const; + protected: + cocos2d::ccColor3B& getValueMut() const override; + Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; + + public: + cocos2d::ccColor3B getDefaultValue() const override; + Result<> isValid(cocos2d::ccColor3B value) const override; - cocos2d::ccColor3B getDefaultValue() const; - - Result<> parse(std::string const& modID, matjson::Value const& json) override; bool load(matjson::Value const& json) override; bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; - bool isDefaultValue() const override; - void reset() override; - std::optional convertToLegacy() const override; - std::optional> convertToLegacyValue() const override; + std::optional> convertToLegacyValue() const override; }; - class GEODE_DLL Color4BSettingV3 final : public detail::GeodeSettingBaseV3 { + class GEODE_DLL Color4BSettingV3 final : public detail::GeodeSettingBaseValueV3 { private: class Impl; std::shared_ptr m_impl; - public: - cocos2d::ccColor4B getValue() const; - void setValue(cocos2d::ccColor4B value); - Result<> isValid(cocos2d::ccColor4B value) const; + protected: + cocos2d::ccColor4B& getValueMut() const override; + Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; + + public: + cocos2d::ccColor4B getDefaultValue() const override; + Result<> isValid(cocos2d::ccColor4B value) const override; - cocos2d::ccColor4B getDefaultValue() const; - - Result<> parse(std::string const& modID, matjson::Value const& json) override; bool load(matjson::Value const& json) override; bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; - bool isDefaultValue() const override; - void reset() override; - std::optional convertToLegacy() const override; - std::optional> convertToLegacyValue() const override; + std::optional> convertToLegacyValue() const override; }; class GEODE_DLL SettingNodeV3 : public cocos2d::CCNode { + private: + class Impl; + std::shared_ptr m_impl; + protected: - bool init(); + bool init(std::shared_ptr setting, float width); /** * Mark this setting as changed. This updates the UI for committing @@ -314,13 +345,74 @@ namespace geode { */ virtual void onCommit() = 0; - void dispatchChanged(); - void dispatchCommitted(); - public: - virtual void commit() = 0; - virtual bool hasUncommittedChanges() = 0; - virtual bool hasNonDefaultValue() = 0; + void commit(); + virtual bool hasUncommittedChanges() const = 0; + virtual bool hasNonDefaultValue() const = 0; virtual void resetToDefault() = 0; + + void setContentSize(cocos2d::CCSize const& size) override; + + std::shared_ptr getSetting() const; + }; + + class GEODE_DLL SettingNodeSizeChangeEventV3 : public Event { + private: + class Impl; + std::shared_ptr m_impl; + + public: + SettingNodeSizeChangeEventV3(SettingNodeV3* node); + virtual ~SettingNodeSizeChangeEventV3(); + + SettingNodeV3* getTargetNode() const; + }; + class GEODE_DLL SettingNodeValueChangeEventV3 : public Event { + private: + class Impl; + std::shared_ptr m_impl; + + public: + SettingNodeValueChangeEventV3(bool commit); + virtual ~SettingNodeValueChangeEventV3(); + + bool isCommit() const; + }; + + template + struct SettingTypeForValueType { + static_assert( + !std::is_same_v, + "specialize the SettingTypeForValueType class to use Mod::getSettingValue for custom settings" + ); + }; + + template <> + struct SettingTypeForValueType { + using SettingType = BoolSettingV3; + }; + template <> + struct SettingTypeForValueType { + using SettingType = IntSettingV3; + }; + template <> + struct SettingTypeForValueType { + using SettingType = FloatSettingV3; + }; + template <> + struct SettingTypeForValueType { + using SettingType = StringSettingV3; + }; + template <> + struct SettingTypeForValueType { + using SettingType = FileSettingV3; + }; + template <> + struct SettingTypeForValueType { + using SettingType = Color3BSettingV3; + }; + template <> + struct SettingTypeForValueType { + using SettingType = Color4BSettingV3; }; } diff --git a/loader/include/Geode/platform/windows.hpp b/loader/include/Geode/platform/windows.hpp index 84c34891..374e0e1e 100644 --- a/loader/include/Geode/platform/windows.hpp +++ b/loader/include/Geode/platform/windows.hpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace geode { struct PlatformInfo { @@ -129,4 +130,11 @@ namespace geode::cast { return nullptr; } + + template + std::shared_ptr typeinfo_pointer_cast(std::shared_ptr const& r) noexcept { + // https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast + auto p = typeinfo_cast::element_type*>(r.get()); + return std::shared_ptr(r, p); + } } diff --git a/loader/include/Geode/utils/JsonValidation.hpp b/loader/include/Geode/utils/JsonValidation.hpp index 6ac23168..6b0cf6d1 100644 --- a/loader/include/Geode/utils/JsonValidation.hpp +++ b/loader/include/Geode/utils/JsonValidation.hpp @@ -405,7 +405,7 @@ namespace geode { } { if (this->hasError()) return *this; if (auto v = this->template tryGet()) { - if (!predicate(v)) { + if (!predicate(*v)) { this->setError("json value is not {}", name); } } @@ -463,12 +463,12 @@ namespace geode { Result<> ok(); template - Result ok(T&& value) { + Result ok(T value) { auto ok = this->ok(); if (!ok) { return Err(ok.unwrapErr()); } - return Ok(std::move(value)); + return Ok(std::forward(value)); } }; GEODE_DLL JsonExpectedValue checkJson(matjson::Value const& json, std::string_view rootScopeName); diff --git a/loader/src/load.cpp b/loader/src/load.cpp index cfa6e0e4..5c109365 100644 --- a/loader/src/load.cpp +++ b/loader/src/load.cpp @@ -185,6 +185,7 @@ int geodeEntry(void* platformData) { log::popNest(); // download and install new loader update in the background + if (Mod::get()->getSettingValue("auto-check-updates")) { log::info("Starting loader update check"); updater::checkForLoaderUpdates(); diff --git a/loader/src/loader/Loader.cpp b/loader/src/loader/Loader.cpp index 55298464..e5223914 100644 --- a/loader/src/loader/Loader.cpp +++ b/loader/src/loader/Loader.cpp @@ -9,7 +9,7 @@ Loader::Loader() : m_impl(new Impl) {} Loader::~Loader() {} Loader* Loader::get() { - static auto g_geode = new Loader; + static auto g_geode = new Loader(); return g_geode; } diff --git a/loader/src/loader/Mod.cpp b/loader/src/loader/Mod.cpp index 66170f69..bb2d5c46 100644 --- a/loader/src/loader/Mod.cpp +++ b/loader/src/loader/Mod.cpp @@ -156,15 +156,27 @@ bool Mod::hasSetting(std::string_view const key) const { } std::optional Mod::getSettingDefinition(std::string_view const key) const { - return m_impl->getSettingDefinition(key); + return m_impl->m_settings->getLegacyDefinition(std::string(key)); } SettingValue* Mod::getSetting(std::string_view const key) const { - return m_impl->getSetting(key); + return m_impl->m_settings->getLegacy(std::string(key)).get(); +} + +std::shared_ptr Mod::getSettingV3(std::string_view const key) const { + auto sett = m_impl->m_settings->get(std::string(key)); + (void)file::writeString(".AAAAAk2", fmt::format("got it: {}, {}", key, fmt::ptr(sett))); + return sett; } void Mod::registerCustomSetting(std::string_view const key, std::unique_ptr value) { - return m_impl->registerCustomSetting(key, std::move(value)); + auto reg = m_impl->m_settings->registerLegacyCustomSetting(key, std::move(value)); + if (!reg) { + log::error("Unable to register custom setting: {}", reg.unwrapErr()); + } +} +Result<> Mod::registerCustomSettingV3(std::string_view const key, std::shared_ptr value) { + return m_impl->m_settings->registerCustomSetting(key, value); } std::vector Mod::getLaunchArgumentNames() const { diff --git a/loader/src/loader/ModImpl.cpp b/loader/src/loader/ModImpl.cpp index be2c24a4..12e7755c 100644 --- a/loader/src/loader/ModImpl.cpp +++ b/loader/src/loader/ModImpl.cpp @@ -53,7 +53,7 @@ Result<> Mod::Impl::setup() { // always create temp dir for all mods, even if disabled, so resources can be loaded GEODE_UNWRAP(this->createTempDir().expect("Unable to create temp dir: {error}")); - this->setupSettings(); + m_settings = std::make_unique(m_metadata); auto loadRes = this->loadData(); if (!loadRes) { log::warn("Unable to load data for \"{}\": {}", m_metadata.getID(), loadRes.unwrapErr()); @@ -182,49 +182,11 @@ Result<> Mod::Impl::loadData() { // Check if settings exist auto settingPath = m_saveDirPath / "settings.json"; if (std::filesystem::exists(settingPath)) { - GEODE_UNWRAP_INTO(auto settingData, utils::file::readString(settingPath)); - // parse settings.json - std::string error; - auto res = matjson::parse(settingData, error); - if (error.size() > 0) { - return Err("Unable to parse settings.json: " + error); - } - auto json = res.value(); - - JsonChecker checker(json); - auto root = checker.root(fmt::format("[{}/settings.json]", this->getID())); - + GEODE_UNWRAP_INTO(auto json, utils::file::readJson(settingPath)); m_savedSettingsData = json; - - for (auto& [key, value] : root.items()) { - // check if this is a known setting - if (auto setting = this->getSetting(key)) { - // load its value - if (!setting->load(value.json())) { - log::logImpl( - Severity::Error, - m_self, - "{}: Unable to load value for setting \"{}\"", - m_metadata.getID(), - key - ); - } - } - else { - if (auto definition = this->getSettingDefinition(key)) { - // Found a definition for this setting, it's most likely a custom setting - // Don't warn it, as it's expected to be loaded by the mod - } - else { - log::logImpl( - Severity::Warning, - m_self, - "Encountered unknown setting \"{}\" while loading " - "settings", - key - ); - } - } + auto load = m_settings->load(json); + if (!load) { + log::warn("Unable to load settings: {}", load.unwrapErr()); } } @@ -253,104 +215,45 @@ Result<> Mod::Impl::saveData() { return Ok(); } - // saveData is expected to be synchronous, and always called from GD thread - ModStateEvent(m_self, ModEventType::DataSaved).post(); - // Data saving should be fully fail-safe - - std::unordered_set coveredSettings; - - // Settings - matjson::Value json = matjson::Object(); - for (auto& [key, value] : m_settings) { - coveredSettings.insert(key); - if (!value->save(json[key])) { - log::error("Unable to save setting \"{}\"", key); - } - } - - // if some settings weren't provided a custom settings handler (for example, + // If some settings weren't provided a custom settings handler (for example, // the mod was not loaded) then make sure to save their previous state in // order to not lose data - log::debug("Check covered"); if (!m_savedSettingsData.is_object()) { m_savedSettingsData = matjson::Object(); } - for (auto& [key, value] : m_savedSettingsData.as_object()) { - log::debug("Check if {} is saved", key); - if (!coveredSettings.contains(key)) { - json[key] = value; - } - } + matjson::Value json = m_savedSettingsData; + m_settings->save(json); - std::string settingsStr = json.dump(); - std::string savedStr = m_saved.dump(); - - auto res = utils::file::writeString(m_saveDirPath / "settings.json", settingsStr); + auto res = utils::file::writeString(m_saveDirPath / "settings.json", json.dump()); if (!res) { log::error("Unable to save settings: {}", res.unwrapErr()); } - - auto res2 = utils::file::writeString(m_saveDirPath / "saved.json", savedStr); + auto res2 = utils::file::writeString(m_saveDirPath / "saved.json", m_saved.dump()); if (!res2) { log::error("Unable to save values: {}", res2.unwrapErr()); } + // saveData is expected to be synchronous, and always called from GD thread + ModStateEvent(m_self, ModEventType::DataSaved).post(); + return Ok(); } -void Mod::Impl::setupSettings() { - for (auto& [key, sett] : m_metadata.getSettings()) { - if (auto value = sett.createDefaultValue()) { - m_settings.emplace(key, std::move(value)); - } - } -} - -void Mod::Impl::registerCustomSetting(std::string_view const key, std::unique_ptr value) { - auto keystr = std::string(key); - if (!m_settings.count(keystr)) { - // load data - if (m_savedSettingsData.contains(key)) { - value->load(m_savedSettingsData[key]); - } - m_settings.emplace(keystr, std::move(value)); - } -} - bool Mod::Impl::hasSettings() const { - return m_metadata.getSettings().size(); + return m_metadata.getSettingsV3().size(); } std::vector Mod::Impl::getSettingKeys() const { std::vector keys; - for (auto& [key, _] : m_metadata.getSettings()) { + for (auto& [key, _] : m_metadata.getSettingsV3()) { keys.push_back(key); } return keys; } -std::optional Mod::Impl::getSettingDefinition(std::string_view const key) const { - for (auto& setting : m_metadata.getSettings()) { - if (setting.first == key) { - return setting.second; - } - } - return std::nullopt; -} - -SettingValue* Mod::Impl::getSetting(std::string_view const key) const { - auto keystr = std::string(key); - if (m_settings.count(keystr)) { - if (auto value = m_settings.at(keystr)->convertToLegacyValue()) { - return value->get(); - } - } - return nullptr; -} - bool Mod::Impl::hasSetting(std::string_view const key) const { - for (auto& setting : m_metadata.getSettings()) { + for (auto& setting : m_metadata.getSettingsV3()) { if (setting.first == key) { return true; } diff --git a/loader/src/loader/ModImpl.hpp b/loader/src/loader/ModImpl.hpp index 9dad1934..832060be 100644 --- a/loader/src/loader/ModImpl.hpp +++ b/loader/src/loader/ModImpl.hpp @@ -51,7 +51,7 @@ namespace geode { /** * Setting values. This is behind unique_ptr for interior mutability */ - std::unique_ptr m_settings = std::make_unique(); + std::unique_ptr m_settings = nullptr; /** * Settings save data. Stored for efficient loading of custom settings */ @@ -75,6 +75,8 @@ namespace geode { Impl(Mod* self, ModMetadata const& metadata); ~Impl(); + Impl(Impl const&) = delete; + Impl(Impl&&) = delete; Result<> setup(); @@ -84,8 +86,6 @@ namespace geode { // called on a separate thread Result<> unzipGeodeFile(ModMetadata metadata); - void setupSettings(); - std::string getID() const; std::string getName() const; std::vector getDevelopers() const; @@ -117,9 +117,6 @@ namespace geode { bool hasSettings() const; std::vector getSettingKeys() const; bool hasSetting(std::string_view const key) const; - std::optional getSettingDefinition(std::string_view const key) const; - SettingValue* getSetting(std::string_view const key) const; - void registerCustomSetting(std::string_view const key, std::unique_ptr value); std::string getLaunchArgumentName(std::string_view const name) const; std::vector getLaunchArgumentNames() const; diff --git a/loader/src/loader/ModMetadataImpl.cpp b/loader/src/loader/ModMetadataImpl.cpp index 9b601b1f..d7e397c2 100644 --- a/loader/src/loader/ModMetadataImpl.cpp +++ b/loader/src/loader/ModMetadataImpl.cpp @@ -125,6 +125,8 @@ Result ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs } catch (...) { } + return Ok(info); + auto root = checkJson(impl->m_rawJSON, checkerRoot); root.needs("geode").into(impl->m_geodeVersion); @@ -249,9 +251,7 @@ Result ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs continue; } } - - GEODE_UNWRAP_INTO(auto sett, SettingV3::parseBuiltin(impl->m_id, value.json())); - impl->m_settings.emplace_back(key, sett); + impl->m_settings.emplace_back(key, value.json()); } if (auto resources = root.has("resources")) { diff --git a/loader/src/loader/ModSettingsManager.cpp b/loader/src/loader/ModSettingsManager.cpp index 12eefff5..6c68b12a 100644 --- a/loader/src/loader/ModSettingsManager.cpp +++ b/loader/src/loader/ModSettingsManager.cpp @@ -1,11 +1,126 @@ #include "ModSettingsManager.hpp" +#include "SettingV3Impl.hpp" +#include -SettingV3* ModSettingsManager::get(std::string const& id) {} +class ModSettingsManager::Impl final { +public: + struct SettingInfo final { + std::shared_ptr v3; + // todo: remove in v4 + std::shared_ptr legacy = nullptr; + }; + std::string modID; + std::unordered_map list; +}; -SettingValue* ModSettingsManager::getLegacy(std::string const& id) { - // If this setting has alreay been given a legacy interface, give that - if (m_legacy.count(id)) { - return m_legacy.at(id).get(); +ModSettingsManager::ModSettingsManager(ModMetadata const& metadata) + : m_impl(std::make_unique()) +{ + m_impl->modID = metadata.getID(); + for (auto const& [key, json] : metadata.getSettingsV3()) { + if (auto v3 = SettingV3::parseBuiltin(key, m_impl->modID, json)) { + auto setting = Impl::SettingInfo(); + setting.v3.swap(*v3); + m_impl->list.emplace(key, setting); + } + else { + log::error("Unable to parse setting '{}' for mod {}: {}", key, m_impl->modID, v3.unwrapErr()); + } } - if (m_v3.count(id)) {} +} +ModSettingsManager::~ModSettingsManager() {} + +Result<> ModSettingsManager::registerCustomSetting(std::string_view key, std::shared_ptr ptr) { + if (!ptr) { + return Err("Custom settings must not be null!"); + } + auto id = std::string(key); + if (!m_impl->list.count(id)) { + return Err("No such setting '{}' in mod {}", id, m_impl->modID); + } + auto& sett = m_impl->list.at(id); + sett.v3.swap(ptr); + return Ok(); +} +Result<> ModSettingsManager::registerLegacyCustomSetting(std::string_view key, std::unique_ptr&& ptr) { + auto id = std::string(key); + if (!m_impl->list.count(id)) { + return Err("No such setting '{}' in mod {}", id, m_impl->modID); + } + auto& sett = m_impl->list.at(id); + if (auto custom = typeinfo_pointer_cast(sett.v3)) { + if (!custom->m_impl->legacyValue) { + custom->m_impl->legacyValue = std::move(ptr); + } + else { + return Err("Setting '{}' in mod {} has already been registed", id, m_impl->modID); + } + } + else { + return Err("Setting '{}' in mod {} is not a custom setting", id, m_impl->modID); + } + return Ok(); +} + +Result<> ModSettingsManager::load(matjson::Value const& json) { + auto root = checkJson(json, "Settings"); + for (auto const& [key, value] : root.properties()) { + if (m_impl->list.contains(key)) { + try { + if (!m_impl->list.at(key).v3->load(value.json())) { + log::error("Unable to load setting '{}' for mod {}", key, m_impl->modID); + } + } + catch(matjson::JsonException const& e) { + log::error("Unable to load setting '{}' for mod {} (JSON exception): {}", key, m_impl->modID, e.what()); + } + } + } + return Ok(); +} +void ModSettingsManager::save(matjson::Value& json) { + for (auto& [key, sett] : m_impl->list) { + // Store the value in an intermediary so if `save` fails the existing + // value loaded from disk isn't overwritten + matjson::Value value; + try { + if (sett.v3->save(value)) { + json[key] = value; + } + else { + log::error("Unable to save setting '{}' for mod {}", key, m_impl->modID); + } + } + catch(matjson::JsonException const& e) { + log::error("Unable to save setting '{}' for mod {} (JSON exception): {}", key, m_impl->modID, e.what()); + } + } +} + +std::shared_ptr ModSettingsManager::get(std::string_view key) { + auto id = std::string(key); + return m_impl->list.count(id) ? m_impl->list.at(id).v3 : nullptr; +} +std::shared_ptr ModSettingsManager::getLegacy(std::string_view key) { + auto id = std::string(key); + if (!m_impl->list.count(id)) { + return nullptr; + } + auto& info = m_impl->list.at(id); + // If this setting has alreay been given a legacy interface, give that + if (info.legacy) { + return info.legacy; + } + // Generate new legacy interface + if (auto legacy = info.v3->convertToLegacyValue()) { + info.legacy.swap(*legacy); + return info.legacy; + } + return nullptr; +} +std::optional ModSettingsManager::getLegacyDefinition(std::string_view key) { + if (auto s = this->get(key)) { + return s->convertToLegacy(); + } + return std::nullopt; } diff --git a/loader/src/loader/ModSettingsManager.hpp b/loader/src/loader/ModSettingsManager.hpp index 455f220b..5f78c340 100644 --- a/loader/src/loader/ModSettingsManager.hpp +++ b/loader/src/loader/ModSettingsManager.hpp @@ -5,21 +5,22 @@ using namespace geode::prelude; -// This class should NEVER be exposed in a header!!! -// It is an implementation detail!!! - class ModSettingsManager final { private: - struct SettingInfo final { - std::unique_ptr v3; - std::unique_ptr legacy; - }; - - std::unordered_map> m_v3; - // todo: remove in v4 - std::unordered_map> m_legacy; + class Impl; + std::unique_ptr m_impl; public: - SettingV3* get(std::string const& id); - SettingValue* getLegacy(std::string const& id); + ModSettingsManager(ModMetadata const& metadata); + ~ModSettingsManager(); + + Result<> load(matjson::Value const& json); + void save(matjson::Value& json); + + Result<> registerCustomSetting(std::string_view key, std::shared_ptr ptr); + Result<> registerLegacyCustomSetting(std::string_view key, std::unique_ptr&& ptr); + + std::shared_ptr get(std::string_view key); + std::shared_ptr getLegacy(std::string_view key); + std::optional getLegacyDefinition(std::string_view key); }; diff --git a/loader/src/loader/Setting.cpp b/loader/src/loader/Setting.cpp index 35434110..c7892cd3 100644 --- a/loader/src/loader/Setting.cpp +++ b/loader/src/loader/Setting.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -296,29 +297,49 @@ void SettingValue::valueChanged() { return type_##SettingNode::create(this, width); \ } \ template<> \ + typename GeodeSettingValue::ValueType \ + GeodeSettingValue::getValue() const { \ + using S = typename SettingTypeForValueType::SettingType; \ + if (auto mod = Loader::get()->getInstalledMod(m_modID)) { \ + if (auto setting = typeinfo_pointer_cast(mod->getSettingV3(m_key))) {\ + return setting->getValue(); \ + } \ + } \ + return m_value; \ + } \ + template<> \ void GeodeSettingValue< \ type_##Setting \ >::setValue(ValueType const& value) { \ - m_value = this->toValid(value).first; \ - this->valueChanged(); \ + using S = typename SettingTypeForValueType::SettingType; \ + if (auto mod = Loader::get()->getInstalledMod(m_modID)) { \ + if (auto setting = typeinfo_pointer_cast(mod->getSettingV3(m_key))) {\ + return setting->setValue(value); \ + } \ + } \ } \ template<> \ Result<> GeodeSettingValue< \ type_##Setting \ >::validate(ValueType const& value) const { \ - auto reason = this->toValid(value).second; \ - if (reason.has_value()) { \ - return Err(static_cast(reason.value())); \ - } \ - return Ok(); \ + using S = typename SettingTypeForValueType::SettingType; \ + if (auto mod = Loader::get()->getInstalledMod(m_modID)) { \ + if (auto setting = typeinfo_pointer_cast(mod->getSettingV3(m_key))) {\ + return setting->isValid(value); \ + } \ + } \ + return Ok(); \ } \ template<> \ typename type_##Setting::ValueType SettingValueSetter< \ typename type_##Setting::ValueType \ >::get(SettingValue* setting) { \ - if (auto b = typeinfo_cast(setting)) { \ - return b->getValue(); \ - } \ + using S = typename SettingTypeForValueType::SettingType; \ + if (auto mod = Loader::get()->getInstalledMod(setting->getModID())) { \ + if (auto sett = typeinfo_pointer_cast(mod->getSettingV3(setting->getKey()))) { \ + return sett->getValue(); \ + } \ + } \ return typename type_##Setting::ValueType(); \ } \ template<> \ @@ -328,9 +349,12 @@ void SettingValue::valueChanged() { SettingValue* setting, \ typename type_##Setting::ValueType const& value \ ) { \ - if (auto b = typeinfo_cast(setting)) { \ - b->setValue(value); \ - } \ + using S = typename SettingTypeForValueType::SettingType; \ + if (auto mod = Loader::get()->getInstalledMod(setting->getModID())) { \ + if (auto sett = typeinfo_pointer_cast(mod->getSettingV3(setting->getKey()))) { \ + return sett->setValue(value); \ + } \ + } \ } #define IMPL_TO_VALID(type_) \ diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp new file mode 100644 index 00000000..3bdd4d8d --- /dev/null +++ b/loader/src/loader/SettingNodeV3.cpp @@ -0,0 +1,445 @@ +#include "SettingNodeV3.hpp" +#include "SettingV3Impl.hpp" +#include + +class SettingNodeSizeChangeEventV3::Impl final { +public: + SettingNodeV3* node; +}; + +SettingNodeSizeChangeEventV3::SettingNodeSizeChangeEventV3(SettingNodeV3* node) + : m_impl(std::make_shared()) +{ + m_impl->node = node; +} +SettingNodeSizeChangeEventV3::~SettingNodeSizeChangeEventV3() = default; + +SettingNodeV3* SettingNodeSizeChangeEventV3::getTargetNode() const { + return m_impl->node; +} + +class SettingNodeValueChangeEventV3::Impl final { +public: + bool commit = false; +}; + +SettingNodeValueChangeEventV3::SettingNodeValueChangeEventV3(bool commit) + : m_impl(std::make_shared()) +{ + m_impl->commit = commit; +} +SettingNodeValueChangeEventV3::~SettingNodeValueChangeEventV3() = default; + +bool SettingNodeValueChangeEventV3::isCommit() const { + return m_impl->commit; +} + +class SettingNodeV3::Impl final { +public: + std::shared_ptr setting; +}; + +bool SettingNodeV3::init(std::shared_ptr setting, float width) { + if (!CCNode::init()) + return false; + + m_impl = std::make_shared(); + m_impl->setting = setting; + + return true; +} + +void SettingNodeV3::markChanged() { + SettingNodeValueChangeEventV3(false).post(); +} + +void SettingNodeV3::commit() { + this->onCommit(); + SettingNodeValueChangeEventV3(true).post(); +} + +void SettingNodeV3::setContentSize(CCSize const& size) { + CCNode::setContentSize(size); + SettingNodeSizeChangeEventV3(this).post(); +} + +std::shared_ptr SettingNodeV3::getSetting() const { + return m_impl->setting; +} + +// TitleSettingNodeV3 + +bool TitleSettingNodeV3::init(std::shared_ptr setting, float width) { + if (!SettingNodeV3::init(setting, width)) + return false; + + this->setContentHeight(20); + + auto label = CCLabelBMFont::create(setting->getTitle().c_str(), "goldFont.fnt"); + label->limitLabelWidth(width - m_obContentSize.height, .7f, .1f); + this->addChildAtPosition(label, Anchor::Left, ccp(m_obContentSize.height / 2, 0)); + + return true; +} + +void TitleSettingNodeV3::onCommit() {} + +TitleSettingNodeV3* TitleSettingNodeV3::create(std::shared_ptr setting, float width) { + auto ret = new TitleSettingNodeV3(); + if (ret && ret->init(setting, width)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +bool TitleSettingNodeV3::hasUncommittedChanges() const { + return false; +} +bool TitleSettingNodeV3::hasNonDefaultValue() const { + return false; +} +void TitleSettingNodeV3::resetToDefault() {} + +std::shared_ptr TitleSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} + +// BoolSettingNodeV3 + +bool BoolSettingNodeV3::init(std::shared_ptr setting, float width) { + if (!SettingNodeV3::init(setting, width)) + return false; + + this->setContentHeight(30); + + auto label = CCLabelBMFont::create(setting->getName().c_str(), "bigFont.fnt"); + label->limitLabelWidth(width - m_obContentSize.height * 1.5f, .5f, .1f); + this->addChildAtPosition(label, Anchor::Left, ccp(m_obContentSize.height / 2, 0)); + + m_toggle = CCMenuItemToggler::createWithStandardSprites( + this, nullptr, .8f + ); + this->addChildAtPosition(m_toggle, Anchor::Right, ccp(-m_obContentSize.height / 2, 0)); + + return true; +} + +void BoolSettingNodeV3::onCommit() { + this->getSetting()->setValue(m_toggle->isToggled()); +} + +BoolSettingNodeV3* BoolSettingNodeV3::create(std::shared_ptr setting, float width) { + auto ret = new BoolSettingNodeV3(); + if (ret && ret->init(setting, width)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +bool BoolSettingNodeV3::hasUncommittedChanges() const { + return m_toggle->isToggled() != this->getSetting()->getValue(); +} +bool BoolSettingNodeV3::hasNonDefaultValue() const { + return m_toggle->isToggled() != this->getSetting()->getDefaultValue(); +} +void BoolSettingNodeV3::resetToDefault() { + this->getSetting()->reset(); + m_toggle->toggle(this->getSetting()->getDefaultValue()); +} + +std::shared_ptr BoolSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} + +// IntSettingNodeV3 + +bool IntSettingNodeV3::init(std::shared_ptr setting, float width) { + if (!SettingNodeV3::init(setting, width)) + return false; + + // todo + + return true; +} + +void IntSettingNodeV3::onCommit() {} + +IntSettingNodeV3* IntSettingNodeV3::create(std::shared_ptr setting, float width) { + auto ret = new IntSettingNodeV3(); + if (ret && ret->init(setting, width)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +bool IntSettingNodeV3::hasUncommittedChanges() const { + return false; +} +bool IntSettingNodeV3::hasNonDefaultValue() const { + return false; +} +void IntSettingNodeV3::resetToDefault() {} + +std::shared_ptr IntSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} + +// FloatSettingNodeV3 + +bool FloatSettingNodeV3::init(std::shared_ptr setting, float width) { + if (!SettingNodeV3::init(setting, width)) + return false; + + // todo + + return true; +} + +void FloatSettingNodeV3::onCommit() {} + +FloatSettingNodeV3* FloatSettingNodeV3::create(std::shared_ptr setting, float width) { + auto ret = new FloatSettingNodeV3(); + if (ret && ret->init(setting, width)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +bool FloatSettingNodeV3::hasUncommittedChanges() const { + return false; +} +bool FloatSettingNodeV3::hasNonDefaultValue() const { + return false; +} +void FloatSettingNodeV3::resetToDefault() {} + +std::shared_ptr FloatSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} + +// StringSettingNodeV3 + +bool StringSettingNodeV3::init(std::shared_ptr setting, float width) { + if (!SettingNodeV3::init(setting, width)) + return false; + + // todo + + return true; +} + +void StringSettingNodeV3::onCommit() {} + +StringSettingNodeV3* StringSettingNodeV3::create(std::shared_ptr setting, float width) { + auto ret = new StringSettingNodeV3(); + if (ret && ret->init(setting, width)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +bool StringSettingNodeV3::hasUncommittedChanges() const { + return false; +} +bool StringSettingNodeV3::hasNonDefaultValue() const { + return false; +} +void StringSettingNodeV3::resetToDefault() {} + +std::shared_ptr StringSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} + +// FileSettingNodeV3 + +bool FileSettingNodeV3::init(std::shared_ptr setting, float width) { + if (!SettingNodeV3::init(setting, width)) + return false; + + // todo + + return true; +} + +void FileSettingNodeV3::onCommit() {} + +FileSettingNodeV3* FileSettingNodeV3::create(std::shared_ptr setting, float width) { + auto ret = new FileSettingNodeV3(); + if (ret && ret->init(setting, width)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +bool FileSettingNodeV3::hasUncommittedChanges() const { + return false; +} +bool FileSettingNodeV3::hasNonDefaultValue() const { + return false; +} +void FileSettingNodeV3::resetToDefault() {} + +std::shared_ptr FileSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} + +// Color3BSettingNodeV3 + +bool Color3BSettingNodeV3::init(std::shared_ptr setting, float width) { + if (!SettingNodeV3::init(setting, width)) + return false; + + // todo + + return true; +} + +void Color3BSettingNodeV3::onCommit() {} + +Color3BSettingNodeV3* Color3BSettingNodeV3::create(std::shared_ptr setting, float width) { + auto ret = new Color3BSettingNodeV3(); + if (ret && ret->init(setting, width)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +bool Color3BSettingNodeV3::hasUncommittedChanges() const { + return false; +} +bool Color3BSettingNodeV3::hasNonDefaultValue() const { + return false; +} +void Color3BSettingNodeV3::resetToDefault() {} + +std::shared_ptr Color3BSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} + +// Color4BSettingNodeV3 + +bool Color4BSettingNodeV3::init(std::shared_ptr setting, float width) { + if (!SettingNodeV3::init(setting, width)) + return false; + + // todo + + return true; +} + +void Color4BSettingNodeV3::onCommit() {} + +Color4BSettingNodeV3* Color4BSettingNodeV3::create(std::shared_ptr setting, float width) { + auto ret = new Color4BSettingNodeV3(); + if (ret && ret->init(setting, width)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +bool Color4BSettingNodeV3::hasUncommittedChanges() const { + return false; +} +bool Color4BSettingNodeV3::hasNonDefaultValue() const { + return false; +} +void Color4BSettingNodeV3::resetToDefault() {} + +std::shared_ptr Color4BSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} + +// UnresolvedCustomSettingNodeV3 + +bool UnresolvedCustomSettingNodeV3::init(std::shared_ptr setting, float width) { + if (!SettingNodeV3::init(setting, width)) + return false; + + this->setContentHeight(30); + + auto label = CCLabelBMFont::create( + fmt::format("Missing setting '{}'", setting->getKey()).c_str(), + "bigFont.fnt" + ); + label->limitLabelWidth(width - m_obContentSize.height, .5f, .1f); + this->addChildAtPosition(label, Anchor::Left, ccp(m_obContentSize.height / 2, 0)); + + return true; +} + +void UnresolvedCustomSettingNodeV3::onCommit() {} + +UnresolvedCustomSettingNodeV3* UnresolvedCustomSettingNodeV3::create(std::shared_ptr setting, float width) { + auto ret = new UnresolvedCustomSettingNodeV3(); + if (ret && ret->init(setting, width)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +bool UnresolvedCustomSettingNodeV3::hasUncommittedChanges() const { + return false; +} +bool UnresolvedCustomSettingNodeV3::hasNonDefaultValue() const { + return false; +} +void UnresolvedCustomSettingNodeV3::resetToDefault() {} + +std::shared_ptr UnresolvedCustomSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} + +// LegacyCustomSettingToV3Node + +bool LegacyCustomSettingToV3Node::init(std::shared_ptr original, float width) { + if (!SettingNodeV3::init(original, width)) + return false; + + m_original = original->m_impl->legacyValue->createNode(width); + this->setContentSize({ width, m_original->getContentHeight() }); + this->addChildAtPosition(m_original, Anchor::Center); + + return true; +} + +void LegacyCustomSettingToV3Node::onCommit() { + m_original->commit(); +} + +LegacyCustomSettingToV3Node* LegacyCustomSettingToV3Node::create(std::shared_ptr original, float width) { + auto ret = new LegacyCustomSettingToV3Node(); + if (ret && ret->init(original, width)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +bool LegacyCustomSettingToV3Node::hasUncommittedChanges() const { + return m_original->hasUncommittedChanges(); +} +bool LegacyCustomSettingToV3Node::hasNonDefaultValue() const { + return m_original->hasNonDefaultValue(); +} +void LegacyCustomSettingToV3Node::resetToDefault() { + m_original->resetToDefault(); +} diff --git a/loader/src/loader/SettingNodeV3.hpp b/loader/src/loader/SettingNodeV3.hpp new file mode 100644 index 00000000..0c2feefe --- /dev/null +++ b/loader/src/loader/SettingNodeV3.hpp @@ -0,0 +1,174 @@ +#pragma once + +#include +#include + +using namespace geode::prelude; + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// !! If these classes are ever exposed in a public header, make sure to pimpl EVERYTHING! !! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +class TitleSettingNodeV3 : public SettingNodeV3 { +protected: + bool init(std::shared_ptr setting, float width); + + void onCommit() override; + +public: + static TitleSettingNodeV3* create(std::shared_ptr setting, float width); + + bool hasUncommittedChanges() const override; + bool hasNonDefaultValue() const override; + void resetToDefault() override; + + std::shared_ptr getSetting() const; +}; + +class BoolSettingNodeV3 : public SettingNodeV3 { +protected: + CCMenuItemToggler* m_toggle; + + bool init(std::shared_ptr setting, float width); + + void onCommit() override; + +public: + static BoolSettingNodeV3* create(std::shared_ptr setting, float width); + + bool hasUncommittedChanges() const override; + bool hasNonDefaultValue() const override; + void resetToDefault() override; + + std::shared_ptr getSetting() const; +}; + +class IntSettingNodeV3 : public SettingNodeV3 { +protected: + bool init(std::shared_ptr setting, float width); + + void onCommit() override; + +public: + static IntSettingNodeV3* create(std::shared_ptr setting, float width); + + bool hasUncommittedChanges() const override; + bool hasNonDefaultValue() const override; + void resetToDefault() override; + + std::shared_ptr getSetting() const; +}; + +class FloatSettingNodeV3 : public SettingNodeV3 { +protected: + bool init(std::shared_ptr setting, float width); + + void onCommit() override; + +public: + static FloatSettingNodeV3* create(std::shared_ptr setting, float width); + + bool hasUncommittedChanges() const override; + bool hasNonDefaultValue() const override; + void resetToDefault() override; + + std::shared_ptr getSetting() const; +}; + +class StringSettingNodeV3 : public SettingNodeV3 { +protected: + bool init(std::shared_ptr setting, float width); + + void onCommit() override; + +public: + static StringSettingNodeV3* create(std::shared_ptr setting, float width); + + bool hasUncommittedChanges() const override; + bool hasNonDefaultValue() const override; + void resetToDefault() override; + + std::shared_ptr getSetting() const; +}; + +class FileSettingNodeV3 : public SettingNodeV3 { +protected: + bool init(std::shared_ptr setting, float width); + + void onCommit() override; + +public: + static FileSettingNodeV3* create(std::shared_ptr setting, float width); + + bool hasUncommittedChanges() const override; + bool hasNonDefaultValue() const override; + void resetToDefault() override; + + std::shared_ptr getSetting() const; +}; + +class Color3BSettingNodeV3 : public SettingNodeV3 { +protected: + bool init(std::shared_ptr setting, float width); + + void onCommit() override; + +public: + static Color3BSettingNodeV3* create(std::shared_ptr setting, float width); + + bool hasUncommittedChanges() const override; + bool hasNonDefaultValue() const override; + void resetToDefault() override; + + std::shared_ptr getSetting() const; +}; + +class Color4BSettingNodeV3 : public SettingNodeV3 { +protected: + bool init(std::shared_ptr setting, float width); + + void onCommit() override; + +public: + static Color4BSettingNodeV3* create(std::shared_ptr setting, float width); + + bool hasUncommittedChanges() const override; + bool hasNonDefaultValue() const override; + void resetToDefault() override; + + std::shared_ptr getSetting() const; +}; + +class UnresolvedCustomSettingNodeV3 : public SettingNodeV3 { +protected: + bool init(std::shared_ptr setting, float width); + + void onCommit() override; + +public: + static UnresolvedCustomSettingNodeV3* create(std::shared_ptr setting, float width); + + bool hasUncommittedChanges() const override; + bool hasNonDefaultValue() const override; + void resetToDefault() override; + + std::shared_ptr getSetting() const; +}; + +// If these classes do get exposed in headers, this SHOULD NOT BE EXPOSED!!!!!! DO NOT DO THAT!!!! + +class LegacyCustomSettingToV3Node : public SettingNodeV3 { +protected: + SettingNode* m_original; + + bool init(std::shared_ptr original, float width); + + void onCommit() override; + +public: + static LegacyCustomSettingToV3Node* create(std::shared_ptr original, float width); + + bool hasUncommittedChanges() const override; + bool hasNonDefaultValue() const override; + void resetToDefault() override; +}; diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index 13071058..f1cec18d 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -1,5 +1,8 @@ #include #include +#include +#include "SettingV3Impl.hpp" +#include "SettingNodeV3.hpp" using namespace geode::prelude; @@ -11,12 +14,7 @@ public: SettingV3::~SettingV3() = default; -SettingV3::SettingV3(std::string const& key, std::string const& modID) - : m_impl(std::make_shared()) -{ - m_impl->key = key; - m_impl->modID = modID; -} +SettingV3::SettingV3() : m_impl(std::make_shared()) {} std::string SettingV3::getKey() const { return m_impl->key; @@ -28,7 +26,13 @@ Mod* SettingV3::getMod() const { return Loader::get()->getInstalledMod(m_impl->modID); } -Result> SettingV3::parseBuiltin(std::string const& modID, matjson::Value const& json) { +Result<> SettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) { + m_impl->key = key; + m_impl->modID = modID; + return this->onParse(key, modID, json); +} + +Result> SettingV3::parseBuiltin(std::string const& key, std::string const& modID, matjson::Value const& json) { auto root = checkJson(json, "SettingV3"); std::string type; root.needs("type").into(type); @@ -41,29 +45,30 @@ Result> SettingV3::parseBuiltin(std::string const& mo case hash("rgb"): case hash("color"): ret = std::make_shared(); break; case hash("rgba"): ret = std::make_shared(); break; case hash("path"): case hash("file"): ret = std::make_shared(); break; - case hash("custom"): ret = std::make_shared(); break; case hash("title"): ret = std::make_shared(); break; + default: + case hash("custom"): ret = std::make_shared(); break; } - GEODE_UNWRAP(ret->parse(modID, json)); - return root.ok(ret); + GEODE_UNWRAP(ret->parse(key, modID, json)); + return root.ok(std::move(ret)); } std::optional SettingV3::convertToLegacy() const { return std::nullopt; } -std::optional> SettingV3::convertToLegacyValue() const { +std::optional> SettingV3::convertToLegacyValue() const { return std::nullopt; } class geode::detail::GeodeSettingBaseV3::Impl final { public: - std::string name; + std::optional name; std::optional description; std::optional enableIf; }; std::string geode::detail::GeodeSettingBaseV3::getName() const { - return m_impl->name; + return m_impl->name.value_or(this->getKey()); } std::optional geode::detail::GeodeSettingBaseV3::getDescription() const { return m_impl->description; @@ -73,9 +78,9 @@ std::optional geode::detail::GeodeSettingBaseV3::getEnableIf() cons } Result<> geode::detail::GeodeSettingBaseV3::parseShared(JsonExpectedValue& json) { - json.needs("name").into(m_impl->name); - json.needs("description").into(m_impl->description); - json.needs("enable-if").into(m_impl->enableIf); + json.has("name").into(m_impl->name); + json.has("description").into(m_impl->description); + json.has("enable-if").into(m_impl->enableIf); return Ok(); } Result<> geode::detail::GeodeSettingBaseV3::isValidShared() const { @@ -93,7 +98,7 @@ std::string TitleSettingV3::getTitle() const { return m_impl->title; } -Result<> TitleSettingV3::parse(std::string const& modID, matjson::Value const& json) { +Result<> TitleSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) { auto root = checkJson(json, "TitleSettingV3"); root.needs("title").into(m_impl->title); root.checkUnknownKeys(); @@ -106,19 +111,22 @@ bool TitleSettingV3::save(matjson::Value&) const { return true; } SettingNodeV3* TitleSettingV3::createNode(float width) { - // todo + return TitleSettingNodeV3::create( + std::static_pointer_cast(shared_from_this()), width + ); } bool TitleSettingV3::isDefaultValue() const { return true; } void TitleSettingV3::reset() {} -class UnresolvedCustomSettingV3::Impl final { -public: - matjson::Value json; -}; +// todo in v4: move the UnresolvedCustomSettingV3::Impl definition from SettingV3Impl.hpp to here +// on this line in particular +// right here +// replace this comment with it +// put it riiiiiight here -Result<> UnresolvedCustomSettingV3::parse(std::string const& modID, matjson::Value const& json) { +Result<> UnresolvedCustomSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) { m_impl->json = json; return Ok(); } @@ -129,7 +137,14 @@ bool UnresolvedCustomSettingV3::save(matjson::Value& json) const { return true; } SettingNodeV3* UnresolvedCustomSettingV3::createNode(float width) { - // todo + if (m_impl->legacyValue) { + return LegacyCustomSettingToV3Node::create( + std::static_pointer_cast(shared_from_this()), width + ); + } + return UnresolvedCustomSettingNodeV3::create( + std::static_pointer_cast(shared_from_this()), width + ); } bool UnresolvedCustomSettingV3::isDefaultValue() const { @@ -142,8 +157,8 @@ std::optional UnresolvedCustomSettingV3::convertToLegacy() const { .json = std::make_shared(m_impl->json) })); } -std::optional> UnresolvedCustomSettingV3::convertToLegacyValue() const { - return std::nullopt; +std::optional> UnresolvedCustomSettingV3::convertToLegacyValue() const { + return m_impl->legacyValue ? std::optional(m_impl->legacyValue) : std::nullopt; } class BoolSettingV3::Impl final { @@ -152,22 +167,18 @@ public: bool defaultValue; }; -bool BoolSettingV3::getValue() const { +bool& BoolSettingV3::getValueMut() const { return m_impl->value; } -void BoolSettingV3::setValue(bool value) { - m_impl->value = value; +bool BoolSettingV3::getDefaultValue() const { + return m_impl->defaultValue; } Result<> BoolSettingV3::isValid(bool value) const { GEODE_UNWRAP(this->isValidShared()); return Ok(); } -bool BoolSettingV3::getDefaultValue() const { - return m_impl->defaultValue; -} - -Result<> BoolSettingV3::parse(std::string const& modID, matjson::Value const& json) { +Result<> BoolSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) { auto root = checkJson(json, "BoolSettingV3"); GEODE_UNWRAP(this->parseShared(root)); @@ -189,13 +200,9 @@ bool BoolSettingV3::save(matjson::Value& json) const { return true; } SettingNodeV3* BoolSettingV3::createNode(float width) { - // todo -} -bool BoolSettingV3::isDefaultValue() const { - return m_impl->value == m_impl->defaultValue; -} -void BoolSettingV3::reset() { - m_impl->value = m_impl->defaultValue; + return BoolSettingNodeV3::create( + std::static_pointer_cast(shared_from_this()), width + ); } std::optional BoolSettingV3::convertToLegacy() const { @@ -205,8 +212,8 @@ std::optional BoolSettingV3::convertToLegacy() const { .defaultValue = this->getDefaultValue(), })); } -std::optional> BoolSettingV3::convertToLegacyValue() const { - return std::make_unique(this->getKey(), this->getModID(), *this->convertToLegacy()); +std::optional> BoolSettingV3::convertToLegacyValue() const { + return this->convertToLegacy()->createDefaultValue(); } class IntSettingV3::Impl final { @@ -226,6 +233,30 @@ public: } controls; }; +int64_t& IntSettingV3::getValueMut() const { + return m_impl->value; +} +int64_t IntSettingV3::getDefaultValue() const { + return m_impl->defaultValue; +} +Result<> IntSettingV3::isValid(int64_t value) const { + GEODE_UNWRAP(this->isValidShared()); + if (m_impl->minValue && value < *m_impl->minValue) { + return Err("value must be at least {}", *m_impl->minValue); + } + if (m_impl->maxValue && value > *m_impl->maxValue) { + return Err("value must be at most {}", *m_impl->maxValue); + } + return Ok(); +} + +std::optional IntSettingV3::getMinValue() const { + return m_impl->minValue; +} +std::optional IntSettingV3::getMaxValue() const { + return m_impl->maxValue; +} + bool IntSettingV3::isArrowsEnabled() const { return m_impl->controls.arrowStepSize > 0; } @@ -248,27 +279,7 @@ bool IntSettingV3::isInputEnabled() const { return m_impl->controls.textInputEnabled; } -int64_t IntSettingV3::getValue() const { - return m_impl->value; -} -void IntSettingV3::setValue(int64_t value) { - m_impl->value = clamp( - value, - m_impl->minValue.value_or(std::numeric_limits::min()), - m_impl->maxValue.value_or(std::numeric_limits::max()) - ); -} -int64_t IntSettingV3::getDefaultValue() const { - return m_impl->defaultValue; -} -std::optional IntSettingV3::getMinValue() const { - return m_impl->minValue; -} -std::optional IntSettingV3::getMaxValue() const { - return m_impl->maxValue; -} - -Result<> IntSettingV3::parse(std::string const& modID, matjson::Value const& json) { +Result<> IntSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) { auto root = checkJson(json, "IntSettingV3"); GEODE_UNWRAP(this->parseShared(root)); @@ -307,14 +318,9 @@ bool IntSettingV3::save(matjson::Value& json) const { return true; } SettingNodeV3* IntSettingV3::createNode(float width) { - // todo -} - -bool IntSettingV3::isDefaultValue() const { - return m_impl->value == m_impl->defaultValue; -} -void IntSettingV3::reset() { - m_impl->value = m_impl->defaultValue; + return IntSettingNodeV3::create( + std::static_pointer_cast(shared_from_this()), width + ); } std::optional IntSettingV3::convertToLegacy() const { @@ -335,8 +341,8 @@ std::optional IntSettingV3::convertToLegacy() const { }, })); } -std::optional> IntSettingV3::convertToLegacyValue() const { - return std::make_unique(this->getKey(), this->getModID(), *this->convertToLegacy()); +std::optional> IntSettingV3::convertToLegacyValue() const { + return this->convertToLegacy()->createDefaultValue(); } class FloatSettingV3::Impl final { @@ -356,6 +362,30 @@ public: } controls; }; +double& FloatSettingV3::getValueMut() const { + return m_impl->value; +} +double FloatSettingV3::getDefaultValue() const { + return m_impl->defaultValue; +} +Result<> FloatSettingV3::isValid(double value) const { + GEODE_UNWRAP(this->isValidShared()); + if (m_impl->minValue && value < *m_impl->minValue) { + return Err("value must be at least {}", *m_impl->minValue); + } + if (m_impl->maxValue && value > *m_impl->maxValue) { + return Err("value must be at most {}", *m_impl->maxValue); + } + return Ok(); +} + +std::optional FloatSettingV3::getMinValue() const { + return m_impl->minValue; +} +std::optional FloatSettingV3::getMaxValue() const { + return m_impl->maxValue; +} + bool FloatSettingV3::isArrowsEnabled() const { return m_impl->controls.arrowStepSize > 0; } @@ -378,30 +408,7 @@ bool FloatSettingV3::isInputEnabled() const { return m_impl->controls.textInputEnabled; } -double FloatSettingV3::getValue() const { - return m_impl->value; -} -Result<> FloatSettingV3::setValue(double value) { - if (m_impl->minValue && value < *m_impl->minValue) { - return Err("Value must be under "); - } - m_impl->value = clamp( - value, - m_impl->minValue.value_or(std::numeric_limits::min()), - m_impl->maxValue.value_or(std::numeric_limits::max()) - ); -} -double FloatSettingV3::getDefaultValue() const { - return m_impl->defaultValue; -} -std::optional FloatSettingV3::getMinValue() const { - return m_impl->minValue; -} -std::optional FloatSettingV3::getMaxValue() const { - return m_impl->maxValue; -} - -Result<> FloatSettingV3::parse(std::string const& modID, matjson::Value const& json) { +Result<> FloatSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) { auto root = checkJson(json, "FloatSettingV3"); GEODE_UNWRAP(this->parseShared(root)); @@ -440,14 +447,9 @@ bool FloatSettingV3::save(matjson::Value& json) const { return true; } SettingNodeV3* FloatSettingV3::createNode(float width) { - // todo -} - -bool FloatSettingV3::isDefaultValue() const { - return m_impl->value == m_impl->defaultValue; -} -void FloatSettingV3::reset() { - m_impl->value = m_impl->defaultValue; + return FloatSettingNodeV3::create( + std::static_pointer_cast(shared_from_this()), width + ); } std::optional FloatSettingV3::convertToLegacy() const { @@ -468,8 +470,8 @@ std::optional FloatSettingV3::convertToLegacy() const { }, })); } -std::optional> FloatSettingV3::convertToLegacyValue() const { - return std::make_unique(this->getKey(), this->getModID(), *this->convertToLegacy()); +std::optional> FloatSettingV3::convertToLegacyValue() const { + return this->convertToLegacy()->createDefaultValue(); } class StringSettingV3::Impl final { @@ -481,15 +483,26 @@ public: std::optional> oneOf; }; -std::string StringSettingV3::getValue() const { +std::string& StringSettingV3::getValueMut() const { return m_impl->value; } -Result<> StringSettingV3::setValue(std::string_view value) { - m_impl->value = value; -} std::string StringSettingV3::getDefaultValue() const { return m_impl->defaultValue; } +Result<> StringSettingV3::isValid(std::string_view value) const { + GEODE_UNWRAP(this->isValidShared()); + if (m_impl->match) { + if (!std::regex_match(std::string(value), std::regex(*m_impl->match))) { + return Err("value must match regex {}", *m_impl->match); + } + } + else if (m_impl->oneOf) { + if (!ranges::contains(*m_impl->oneOf, std::string(value))) { + return Err("value must be one of {}", fmt::join(*m_impl->oneOf, ", ")); + } + } + return Ok(); +} std::optional StringSettingV3::getRegexValidator() const { return m_impl->match; @@ -501,7 +514,7 @@ std::optional> StringSettingV3::getEnumOptions() const return m_impl->oneOf; } -Result<> StringSettingV3::parse(std::string const& modID, matjson::Value const& json) { +Result<> StringSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) { auto root = checkJson(json, "StringSettingV3"); GEODE_UNWRAP(this->parseShared(root)); @@ -527,14 +540,9 @@ bool StringSettingV3::save(matjson::Value& json) const { return true; } SettingNodeV3* StringSettingV3::createNode(float width) { - // todo -} - -bool StringSettingV3::isDefaultValue() const { - return m_impl->value == m_impl->defaultValue; -} -void StringSettingV3::reset() { - m_impl->value = m_impl->defaultValue; + return StringSettingNodeV3::create( + std::static_pointer_cast(shared_from_this()), width + ); } std::optional StringSettingV3::convertToLegacy() const { @@ -547,8 +555,8 @@ std::optional StringSettingV3::convertToLegacy() const { setting.controls->options = this->getEnumOptions(); return Setting(this->getKey(), this->getModID(), SettingKind(setting)); } -std::optional> StringSettingV3::convertToLegacyValue() const { - return std::make_unique(this->getKey(), this->getModID(), *this->convertToLegacy()); +std::optional> StringSettingV3::convertToLegacyValue() const { + return this->convertToLegacy()->createDefaultValue(); } class FileSettingV3::Impl final { @@ -558,17 +566,22 @@ public: std::optional> filters; }; +std::filesystem::path& FileSettingV3::getValueMut() const { + return m_impl->value; +} std::filesystem::path FileSettingV3::getDefaultValue() const { return m_impl->defaultValue; } -std::filesystem::path FileSettingV3::getValue() const { - return m_impl->value; +Result<> FileSettingV3::isValid(std::filesystem::path const& value) const { + GEODE_UNWRAP(this->isValidShared()); + return Ok(); } + std::optional> FileSettingV3::getFilters() const { return m_impl->filters; } -Result<> FileSettingV3::parse(std::string const& modID, matjson::Value const& json) { +Result<> FileSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) { auto root = checkJson(json, "FileSettingV3"); GEODE_UNWRAP(this->parseShared(root)); @@ -618,15 +631,10 @@ bool FileSettingV3::save(matjson::Value& json) const { json = m_impl->value; return true; } -SettingNodeV3* createNode(float width) { - // todo -} - -bool FileSettingV3::isDefaultValue() const { - return m_impl->value == m_impl->defaultValue; -} -void FileSettingV3::reset() { - m_impl->value = m_impl->defaultValue; +SettingNodeV3* FileSettingV3::createNode(float width) { + return FileSettingNodeV3::create( + std::static_pointer_cast(shared_from_this()), width + ); } std::optional FileSettingV3::convertToLegacy() const { @@ -637,8 +645,8 @@ std::optional FileSettingV3::convertToLegacy() const { setting.controls.filters = this->getFilters().value_or(std::vector()); return Setting(this->getKey(), this->getModID(), SettingKind(setting)); } -std::optional> FileSettingV3::convertToLegacyValue() const { - return std::make_unique(this->getKey(), this->getModID(), *this->convertToLegacy()); +std::optional> FileSettingV3::convertToLegacyValue() const { + return this->convertToLegacy()->createDefaultValue(); } class Color3BSettingV3::Impl final { @@ -647,14 +655,18 @@ public: ccColor3B defaultValue; }; +ccColor3B& Color3BSettingV3::getValueMut() const { + return m_impl->value; +} ccColor3B Color3BSettingV3::getDefaultValue() const { return m_impl->defaultValue; } -ccColor3B Color3BSettingV3::getValue() const { - return m_impl->value; +Result<> Color3BSettingV3::isValid(ccColor3B value) const { + GEODE_UNWRAP(this->isValidShared()); + return Ok(); } -Result<> Color3BSettingV3::parse(std::string const& modID, matjson::Value const& json) { +Result<> Color3BSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) { auto root = checkJson(json, "Color3BSettingV3"); GEODE_UNWRAP(this->parseShared(root)); @@ -676,15 +688,9 @@ bool Color3BSettingV3::save(matjson::Value& json) const { return true; } SettingNodeV3* Color3BSettingV3::createNode(float width) { - // todo -} - -bool Color3BSettingV3::isDefaultValue() const { - return m_impl->value == m_impl->defaultValue; - -} -void Color3BSettingV3::reset() { - m_impl->value = m_impl->defaultValue; + return Color3BSettingNodeV3::create( + std::static_pointer_cast(shared_from_this()), width + ); } std::optional Color3BSettingV3::convertToLegacy() const { @@ -694,8 +700,8 @@ std::optional Color3BSettingV3::convertToLegacy() const { setting.defaultValue = this->getDefaultValue(); return Setting(this->getKey(), this->getModID(), SettingKind(setting)); } -std::optional> Color3BSettingV3::convertToLegacyValue() const { - return std::make_unique(this->getKey(), this->getModID(), *this->convertToLegacy()); +std::optional> Color3BSettingV3::convertToLegacyValue() const { + return this->convertToLegacy()->createDefaultValue(); } class Color4BSettingV3::Impl final { @@ -704,14 +710,18 @@ public: ccColor4B defaultValue; }; +ccColor4B& Color4BSettingV3::getValueMut() const { + return m_impl->value; +} ccColor4B Color4BSettingV3::getDefaultValue() const { return m_impl->defaultValue; } -ccColor4B Color4BSettingV3::getValue() const { - return m_impl->value; +Result<> Color4BSettingV3::isValid(ccColor4B value) const { + GEODE_UNWRAP(this->isValidShared()); + return Ok(); } -Result<> Color4BSettingV3::parse(std::string const& modID, matjson::Value const& json) { +Result<> Color4BSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) { auto root = checkJson(json, "Color4BSettingV3"); GEODE_UNWRAP(this->parseShared(root)); @@ -733,15 +743,9 @@ bool Color4BSettingV3::save(matjson::Value& json) const { return true; } SettingNodeV3* Color4BSettingV3::createNode(float width) { - // todo -} - -bool Color4BSettingV3::isDefaultValue() const { - return m_impl->value == m_impl->defaultValue; - -} -void Color4BSettingV3::reset() { - m_impl->value = m_impl->defaultValue; + return Color4BSettingNodeV3::create( + std::static_pointer_cast(shared_from_this()), width + ); } std::optional Color4BSettingV3::convertToLegacy() const { @@ -751,6 +755,6 @@ std::optional Color4BSettingV3::convertToLegacy() const { setting.defaultValue = this->getDefaultValue(); return Setting(this->getKey(), this->getModID(), SettingKind(setting)); } -std::optional> Color4BSettingV3::convertToLegacyValue() const { - return std::make_unique(this->getKey(), this->getModID(), *this->convertToLegacy()); +std::optional> Color4BSettingV3::convertToLegacyValue() const { + return this->convertToLegacy()->createDefaultValue(); } diff --git a/loader/src/loader/SettingV3Impl.hpp b/loader/src/loader/SettingV3Impl.hpp new file mode 100644 index 00000000..227c1615 --- /dev/null +++ b/loader/src/loader/SettingV3Impl.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +using namespace geode::prelude; + +// todo in v4: this header can be fully removed and the impl moved back into SettingV3.cpp +// for now it has to be exposed for ModSettingsManager legacy compatibility + +class UnresolvedCustomSettingV3::Impl final { +public: + matjson::Value json; + // todo: remove in v4 + // this is for compatability with legacy custom settings + // in v3 settings custom settings just replace the definition fully like a normal person + std::shared_ptr legacyValue = nullptr; +}; + diff --git a/loader/src/ui/mods/GeodeStyle.hpp b/loader/src/ui/mods/GeodeStyle.hpp index 21429d72..5a9375b2 100644 --- a/loader/src/ui/mods/GeodeStyle.hpp +++ b/loader/src/ui/mods/GeodeStyle.hpp @@ -19,8 +19,8 @@ enum class GeodePopupStyle { template class GeodePopup : public Popup { protected: - bool init(float width, float height, Args... args, GeodePopupStyle style = GeodePopupStyle::Default) { - const bool geodeTheme = Mod::get()->template getSettingValue("enable-geode-theme"); + bool init(float width, float height, Args... args, GeodePopupStyle style = GeodePopupStyle::Default, bool forceDisableTheme = false) { + const bool geodeTheme = !forceDisableTheme && Mod::get()->template getSettingValue("enable-geode-theme"); const char* bg; switch (style) { default: diff --git a/loader/src/ui/mods/settings/ModSettingsPopup.cpp b/loader/src/ui/mods/settings/ModSettingsPopup.cpp index 573218dc..e80f72b2 100644 --- a/loader/src/ui/mods/settings/ModSettingsPopup.cpp +++ b/loader/src/ui/mods/settings/ModSettingsPopup.cpp @@ -1,12 +1,10 @@ #include "ModSettingsPopup.hpp" - #include #include #include #include #include #include -#include "GeodeSettingNode.hpp" bool ModSettingsPopup::setup(Mod* mod) { m_noElasticity = true; @@ -25,56 +23,41 @@ bool ModSettingsPopup::setup(Mod* mod) { auto layer = ScrollLayer::create(layerSize); layer->setTouchEnabled(true); - float totalHeight = .0f; - std::vector rendered; - bool hasBG = true; + bool hasBG = false; for (auto& key : mod->getSettingKeys()) { - SettingNode* node; - if (auto sett = mod->getSetting(key)) { + hasBG = !hasBG; + + auto bg = CCLayerColor::create({ 0, 0, 0, 50 }); + bg->setOpacity(hasBG ? 50 : 0); + + SettingNodeV3* node; + if (auto sett = mod->getSettingV3(key)) { node = sett->createNode(layerSize.width); } else { - node = CustomSettingPlaceholderNode::create(key, layerSize.width); + // todo: placeholder node + continue; } - node->setDelegate(this); + + bg->setContentSize(node->getScaledContentSize()); + bg->addChildAtPosition(node, Anchor::Center); - totalHeight += node->getScaledContentSize().height; + auto separator = CCLayerColor::create({ 0, 0, 0, 50 }, layerSize.width, 1.f); + separator->setOpacity(hasBG ? 100 : 50); + bg->addChildAtPosition(separator, Anchor::Bottom); - if (hasBG) { - auto bg = CCLayerColor::create({ 0, 0, 0, 50 }); - bg->setContentSize(node->getScaledContentSize()); - bg->setPosition(0.f, -totalHeight); - bg->setZOrder(-10); - layer->m_contentLayer->addChild(bg); - - rendered.push_back(bg); - - hasBG = false; - } - else { - hasBG = true; - } - - node->setPosition(0.f, -totalHeight); - layer->m_contentLayer->addChild(node); - - auto separator = CCLayerColor::create( - { 0, 0, 0, static_cast(hasBG ? 100 : 50) }, layerSize.width, 1.f - ); - separator->setPosition(0.f, -totalHeight); - layer->m_contentLayer->addChild(separator); - rendered.push_back(separator); - - rendered.push_back(node); m_settings.push_back(node); + + layer->m_contentLayer->addChild(bg); } - if (totalHeight < layerSize.height) { - totalHeight = layerSize.height; - } - for (auto& node : rendered) { - node->setPositionY(node->getPositionY() + totalHeight); - } - layer->m_contentLayer->setContentSize({ layerSize.width, totalHeight }); + layer->m_contentLayer->setLayout( + ColumnLayout::create() + ->setAxisReverse(true) + ->setAutoGrowAxis(layerSize.height) + ->setCrossAxisOverflow(false) + ->setAxisAlignment(AxisAlignment::End) + ->setGap(0) + ); layer->moveToTop(); layerBG->addChild(layer); @@ -109,7 +92,11 @@ bool ModSettingsPopup::setup(Mod* mod) { ); m_buttonMenu->addChildAtPosition(openDirBtn, Anchor::BottomRight, ccp(-53, 20)); - this->settingValueChanged(nullptr); + m_changeListener.bind([this](auto) { + this->updateState(); + return ListenerResult::Propagate; + }); + this->updateState(); return true; } @@ -143,18 +130,7 @@ void ModSettingsPopup::onResetAll(CCObject*) { ); } -void ModSettingsPopup::settingValueCommitted(SettingNode*) { - if (this->hasUncommitted()) { - m_applyBtnSpr->setColor({0xff, 0xff, 0xff}); - m_applyBtn->setEnabled(true); - } - else { - m_applyBtnSpr->setColor({0x44, 0x44, 0x44}); - m_applyBtn->setEnabled(false); - } -} - -void ModSettingsPopup::settingValueChanged(SettingNode*) { +void ModSettingsPopup::updateState() { if (this->hasUncommitted()) { m_applyBtnSpr->setColor({0xff, 0xff, 0xff}); m_applyBtn->setEnabled(true); diff --git a/loader/src/ui/mods/settings/ModSettingsPopup.hpp b/loader/src/ui/mods/settings/ModSettingsPopup.hpp index f61cb3ce..51e8a9c2 100644 --- a/loader/src/ui/mods/settings/ModSettingsPopup.hpp +++ b/loader/src/ui/mods/settings/ModSettingsPopup.hpp @@ -1,23 +1,23 @@ #pragma once -#include +#include #include #include #include "../GeodeStyle.hpp" using namespace geode::prelude; -class ModSettingsPopup : public GeodePopup, public SettingNodeDelegate { +class ModSettingsPopup : public GeodePopup { protected: Mod* m_mod; - std::vector m_settings; + std::vector m_settings; CCMenuItemSpriteExtra* m_applyBtn; ButtonSprite* m_applyBtnSpr; - - void settingValueChanged(SettingNode*) override; - void settingValueCommitted(SettingNode*) override; + EventListener> m_changeListener; bool setup(Mod* mod) override; + void updateState(); + void onChangeEvent(SettingNodeValueChangeEventV3* event); bool hasUncommitted() const; void onClose(CCObject*) override; void onApply(CCObject*); diff --git a/loader/src/utils/JsonValidation.cpp b/loader/src/utils/JsonValidation.cpp index de775c91..7af8adf9 100644 --- a/loader/src/utils/JsonValidation.cpp +++ b/loader/src/utils/JsonValidation.cpp @@ -224,7 +224,7 @@ JsonExpectedValue::JsonExpectedValue(Impl* from, matjson::Value& scope, std::str : m_impl(std::make_unique(from, scope, key)) {} JsonExpectedValue::JsonExpectedValue(matjson::Value const& json, std::string_view rootScopeName) - : m_impl(std::make_unique(std::make_shared(json, rootScopeName))) + : m_impl(std::make_unique(std::make_shared(json, rootScopeName))) {} JsonExpectedValue::~JsonExpectedValue() {} @@ -389,7 +389,7 @@ std::vector JsonExpectedValue::items() { if (this->hasError()) { return std::vector(); } - if (!this->assertIs(matjson::Type::Object)) { + if (!this->assertIs(matjson::Type::Array)) { return std::vector(); } std::vector res;