From 89d1a5140f5bb15048bb50e00801c33fed42c263 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Tue, 13 Aug 2024 13:34:33 +0300 Subject: [PATCH 01/54] new settings stuff that i need to commit because main broke --- loader/include/Geode/loader/ModMetadata.hpp | 9 +- loader/include/Geode/loader/SettingV3.hpp | 325 ++++++++ loader/include/Geode/utils/JsonValidation.hpp | 191 ++++- loader/src/loader/ModImpl.cpp | 5 +- loader/src/loader/ModImpl.hpp | 5 +- loader/src/loader/ModMetadataImpl.cpp | 126 ++- loader/src/loader/ModMetadataImpl.hpp | 3 +- loader/src/loader/ModSettingsManager.cpp | 11 + loader/src/loader/ModSettingsManager.hpp | 25 + loader/src/loader/SettingV3.cpp | 746 ++++++++++++++++++ loader/src/utils/JsonValidation.cpp | 424 ++++++---- 11 files changed, 1623 insertions(+), 247 deletions(-) create mode 100644 loader/include/Geode/loader/SettingV3.hpp create mode 100644 loader/src/loader/ModSettingsManager.cpp create mode 100644 loader/src/loader/ModSettingsManager.hpp create mode 100644 loader/src/loader/SettingV3.cpp diff --git a/loader/include/Geode/loader/ModMetadata.hpp b/loader/include/Geode/loader/ModMetadata.hpp index d0e3c793..9628d5ee 100644 --- a/loader/include/Geode/loader/ModMetadata.hpp +++ b/loader/include/Geode/loader/ModMetadata.hpp @@ -184,7 +184,12 @@ namespace geode { * Mod settings * @note Not a map because insertion order must be preserved */ - [[nodiscard]] std::vector> getSettings() const; + [[nodiscard, deprecated("Use getSettingsV3")]] std::vector> getSettings() const; + /** + * Mod settings + * @note Not a map because insertion order must be preserved + */ + [[nodiscard]] std::vector> getSettingsV3() const; /** * Get the tags for this mod */ @@ -232,7 +237,9 @@ namespace geode { void setDependencies(std::vector const& value); void setIncompatibilities(std::vector const& value); void setSpritesheets(std::vector const& value); + [[deprecated("This function does NOTHING")]] void setSettings(std::vector> const& value); + void setSettings(std::vector> const& value); void setTags(std::unordered_set const& value); void setNeedsEarlyLoad(bool const& value); void setIsAPI(bool const& value); diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp new file mode 100644 index 00000000..c25afc0d --- /dev/null +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -0,0 +1,325 @@ +#pragma once + +#include "../DefaultInclude.hpp" +#include +#include +// todo: remove this header in 4.0.0 +#include "Setting.hpp" + +namespace geode { + class SettingNodeV3; + class JsonExpectedValue; + + class GEODE_DLL SettingV3 { + private: + class GeodeImpl; + std::shared_ptr m_impl; + + public: + SettingV3(std::string const& key, std::string const& modID); + virtual ~SettingV3(); + + /** + * Get the key of this setting + */ + std::string getKey() const; + /** + * Get the mod ID this setting is for + */ + std::string getModID() const; + /** + * Get the mod this setting is for. Note that this may return null + * while the mod is still being initialized + */ + Mod* getMod() const; + + virtual Result<> parse(std::string const& modID, matjson::Value const& json) = 0; + virtual bool load(matjson::Value const& json) = 0; + virtual bool save(matjson::Value& json) const = 0; + virtual SettingNodeV3* createNode(float width) = 0; + + virtual bool isDefaultValue() const = 0; + /** + * Reset this setting's value back to its original value + */ + virtual void reset() = 0; + + [[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; + + static Result> parseBuiltin(std::string const& modID, matjson::Value const& json); + }; + + namespace detail { + class GEODE_DLL GeodeSettingBaseV3 : public SettingV3 { + private: + class Impl; + std::shared_ptr m_impl; + + protected: + Result<> parseShared(JsonExpectedValue& json); + + public: + std::string getName() const; + std::optional getDescription() const; + std::optional getEnableIf() const; + }; + } + + class GEODE_DLL TitleSettingV3 final : public SettingV3 { + private: + class Impl; + std::shared_ptr m_impl; + + 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; + + bool isDefaultValue() const override; + void reset() override; + }; + + class GEODE_DLL UnresolvedCustomSettingV3 final : public SettingV3 { + private: + class Impl; + std::shared_ptr m_impl; + + 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; + + bool isDefaultValue() const override; + void reset() override; + + std::optional convertToLegacy() const override; + std::optional> convertToLegacyValue() const override; + }; + + class GEODE_DLL BoolSettingV3 final : public detail::GeodeSettingBaseV3 { + private: + class Impl; + std::shared_ptr m_impl; + + public: + bool getValue() const; + void setValue(bool value); + Result<> isValid(bool value) const; + + bool 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; + }; + + class GEODE_DLL IntSettingV3 final : public detail::GeodeSettingBaseV3 { + private: + class Impl; + std::shared_ptr m_impl; + + public: + int64_t getValue() const; + void setValue(int64_t value); + Result<> isValid(int64_t value) const; + + int64_t getDefaultValue() const; + std::optional getMinValue() const; + std::optional getMaxValue() const; + + bool isArrowsEnabled() const; + bool isBigArrowsEnabled() const; + size_t getArrowStepSize() const; + size_t getBigArrowStepSize() const; + bool isSliderEnabled() const; + 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; + }; + + class GEODE_DLL FloatSettingV3 final : public detail::GeodeSettingBaseV3 { + private: + class Impl; + std::shared_ptr m_impl; + + public: + double getValue() const; + void setValue(double value); + Result<> isValid(double value) const; + + double getDefaultValue() const; + std::optional getMinValue() const; + std::optional getMaxValue() const; + + bool isArrowsEnabled() const; + bool isBigArrowsEnabled() const; + size_t getArrowStepSize() const; + size_t getBigArrowStepSize() const; + bool isSliderEnabled() const; + 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; + }; + + class GEODE_DLL StringSettingV3 final : public detail::GeodeSettingBaseV3 { + 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; + + std::string getDefaultValue() const; + + 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; + }; + + class GEODE_DLL FileSettingV3 final : public detail::GeodeSettingBaseV3 { + 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; + + 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; + }; + + class GEODE_DLL Color3BSettingV3 final : public detail::GeodeSettingBaseV3 { + private: + class Impl; + std::shared_ptr m_impl; + + public: + cocos2d::ccColor3B getValue() const; + void setValue(cocos2d::ccColor3B value); + Result<> isValid(cocos2d::ccColor3B value) const; + + 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; + }; + + class GEODE_DLL Color4BSettingV3 final : public detail::GeodeSettingBaseV3 { + private: + class Impl; + std::shared_ptr m_impl; + + public: + cocos2d::ccColor4B getValue() const; + void setValue(cocos2d::ccColor4B value); + Result<> isValid(cocos2d::ccColor4B value) const; + + 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; + }; + + class GEODE_DLL SettingNodeV3 : public cocos2d::CCNode { + protected: + bool init(); + + /** + * Mark this setting as changed. This updates the UI for committing + * the value + */ + void markChanged(); + + /** + * When the setting value is committed (aka can't be undone), this + * function will be called. This should take care of actually saving + * the value in some sort of global manager + */ + virtual void onCommit() = 0; + + void dispatchChanged(); + void dispatchCommitted(); + + public: + virtual void commit() = 0; + virtual bool hasUncommittedChanges() = 0; + virtual bool hasNonDefaultValue() = 0; + virtual void resetToDefault() = 0; + }; +} diff --git a/loader/include/Geode/utils/JsonValidation.hpp b/loader/include/Geode/utils/JsonValidation.hpp index 12190338..6ac23168 100644 --- a/loader/include/Geode/utils/JsonValidation.hpp +++ b/loader/include/Geode/utils/JsonValidation.hpp @@ -77,7 +77,9 @@ namespace geode { struct JsonMaybeObject; struct JsonMaybeValue; - struct GEODE_DLL JsonMaybeSomething { + struct GEODE_DLL + [[deprecated("Use JsonExpectedValue via the checkJson function instead")]] + JsonMaybeSomething { protected: JsonChecker& m_checker; matjson::Value& m_json; @@ -102,7 +104,9 @@ namespace geode { operator bool() const; }; - struct GEODE_DLL JsonMaybeValue : public JsonMaybeSomething { + struct GEODE_DLL + [[deprecated("Use JsonExpectedValue via the checkJson function instead")]] + JsonMaybeValue : public JsonMaybeSomething { bool m_inferType = true; JsonMaybeValue( @@ -254,7 +258,9 @@ namespace geode { Iterator> items(); }; - struct GEODE_DLL JsonMaybeObject : JsonMaybeSomething { + struct + [[deprecated("Use JsonExpectedValue via the checkJson function instead")]] + GEODE_DLL JsonMaybeObject : JsonMaybeSomething { std::set m_knownKeys; JsonMaybeObject( @@ -276,7 +282,9 @@ namespace geode { void checkUnknownKeys(); }; - struct GEODE_DLL JsonChecker { + struct + [[deprecated("Use JsonExpectedValue via the checkJson function instead")]] + GEODE_DLL JsonChecker { std::variant m_result; matjson::Value& m_json; @@ -289,4 +297,179 @@ namespace geode { JsonMaybeValue root(std::string const& hierarchy); }; + class GEODE_DLL JsonExpectedValue final { + protected: + class Impl; + std::unique_ptr m_impl; + + JsonExpectedValue(); + JsonExpectedValue(Impl* from, matjson::Value& scope, std::string_view key); + + bool hasError() const; + void setError(std::string_view error); + + matjson::Value const& getJSONRef() const; + + template + void setError(fmt::format_string error, Args&&... args) { + this->setError(fmt::format(error, std::forward(args)...)); + } + + template + std::optional tryGet() { + if (this->hasError()) return std::nullopt; + try { + return this->getJSONRef().template as(); + } + catch(matjson::JsonException const& e) { + this->setError("invalid json type: {}", e); + } + return std::nullopt; + } + + public: + JsonExpectedValue(matjson::Value const& value, std::string_view rootScopeName); + ~JsonExpectedValue(); + + JsonExpectedValue(JsonExpectedValue&&); + JsonExpectedValue& operator=(JsonExpectedValue&&); + JsonExpectedValue(JsonExpectedValue const&) = delete; + JsonExpectedValue& operator=(JsonExpectedValue const&) = delete; + + /** + * Get a copy of the underlying raw JSON value + */ + matjson::Value json() const; + /** + * Get the key name of this JSON value. If this is an array index, + * returns the index as a string. If this is the root object, + * returns the root scope name. + */ + std::string key() const; + + /** + * Check the type of this JSON value. Does not set an error. If an + * error is already set, always returns false + */ + bool is(matjson::Type type) const; + bool isNull() const; + bool isBool() const; + bool isNumber() const; + bool isString() const; + bool isArray() const; + bool isObject() const; + /** + * Asserts that this JSON value is of the specified type. If it is + * not, an error is set and all subsequent operations are no-ops + * @returns Itself + */ + JsonExpectedValue& assertIs(matjson::Type type); + JsonExpectedValue& assertIsNull(); + JsonExpectedValue& assertIsBool(); + JsonExpectedValue& assertIsNumber(); + JsonExpectedValue& assertIsString(); + JsonExpectedValue& assertIsArray(); + JsonExpectedValue& assertIsObject(); + /** + * Asserts that this JSON value is one of a list of specified types + * @returns Itself + */ + JsonExpectedValue& assertIs(std::initializer_list type); + + // -- Dealing with values -- + + template + T get() { + if (auto v = this->template tryGet()) { + return *std::move(v); + } + return T(); + } + template + JsonExpectedValue& into(T& value) { + if (auto v = this->template tryGet()) { + value = *std::move(v); + } + return *this; + } + template + JsonExpectedValue& into(std::optional& value) { + if (auto v = this->template tryGet()) { + value.emplace(*std::move(v)); + } + return *this; + } + template + JsonExpectedValue& mustBe(std::string_view name, auto predicate) requires requires { + { predicate(std::declval()) } -> std::convertible_to; + } { + if (this->hasError()) return *this; + if (auto v = this->template tryGet()) { + if (!predicate(v)) { + this->setError("json value is not {}", name); + } + } + return *this; + } + + // -- Dealing with objects -- + + /** + * Check if this object has an optional key. Asserts that this JSON + * value is an object. If the key doesn't exist, returns a + * `JsonExpectValue` that does nothing + * @returns The key, which is a no-op value if it didn't exist + */ + JsonExpectedValue has(std::string_view key); + /** + * Check if this object has an optional key. Asserts that this JSON + * value is an object. If the key doesn't exist, sets an error and + * returns a `JsonExpectValue` that does nothing + * @returns The key, which is a no-op value if it didn't exist + */ + JsonExpectedValue needs(std::string_view key); + /** + * Asserts that this JSON value is an object. Get all object + * properties + */ + std::vector> properties(); + /** + * Asserts that this JSON value is an object. Logs unknown keys to + * the console as warnings + */ + void checkUnknownKeys(); + + // -- Dealing with arrays -- + + /** + * Asserts that this JSON value is an array. Returns the length of + * the array, or 0 on error + */ + size_t length(); + /** + * Asserts that this JSON value is an array. Returns the value at + * the specified index. If there is no value at that index, sets an + * error + */ + JsonExpectedValue at(size_t index); + /** + * Asserts that this JSON value is an array. Returns the array items + * @warning The old JsonChecker used `items` for iterating object + * properties - on this new API that function is called `properties`! + */ + std::vector items(); + + operator bool() const; + + Result<> ok(); + template + Result ok(T&& value) { + auto ok = this->ok(); + if (!ok) { + return Err(ok.unwrapErr()); + } + return Ok(std::move(value)); + } + }; + GEODE_DLL JsonExpectedValue checkJson(matjson::Value const& json, std::string_view rootScopeName); } diff --git a/loader/src/loader/ModImpl.cpp b/loader/src/loader/ModImpl.cpp index 43b3271d..be2c24a4 100644 --- a/loader/src/loader/ModImpl.cpp +++ b/loader/src/loader/ModImpl.cpp @@ -341,9 +341,10 @@ std::optional Mod::Impl::getSettingDefinition(std::string_view const ke SettingValue* Mod::Impl::getSetting(std::string_view const key) const { auto keystr = std::string(key); - if (m_settings.count(keystr)) { - return m_settings.at(keystr).get(); + if (auto value = m_settings.at(keystr)->convertToLegacyValue()) { + return value->get(); + } } return nullptr; } diff --git a/loader/src/loader/ModImpl.hpp b/loader/src/loader/ModImpl.hpp index 2346fd10..9dad1934 100644 --- a/loader/src/loader/ModImpl.hpp +++ b/loader/src/loader/ModImpl.hpp @@ -4,6 +4,7 @@ #include "ModPatch.hpp" #include #include +#include "ModSettingsManager.hpp" namespace geode { class Mod::Impl { @@ -48,9 +49,9 @@ namespace geode { */ matjson::Value m_saved = matjson::Object(); /** - * Setting values + * Setting values. This is behind unique_ptr for interior mutability */ - std::unordered_map> m_settings; + std::unique_ptr m_settings = std::make_unique(); /** * Settings save data. Stored for efficient loading of custom settings */ diff --git a/loader/src/loader/ModMetadataImpl.cpp b/loader/src/loader/ModMetadataImpl.cpp index cacc4aa6..9b601b1f 100644 --- a/loader/src/loader/ModMetadataImpl.cpp +++ b/loader/src/loader/ModMetadataImpl.cpp @@ -125,45 +125,28 @@ Result ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs } catch (...) { } - JsonChecker checker(impl->m_rawJSON); - auto root = checker.root(checkerRoot).obj(); - + auto root = checkJson(impl->m_rawJSON, checkerRoot); root.needs("geode").into(impl->m_geodeVersion); - root.addKnownKey("gd"); - - // Check GD version - // (use rawJson because i dont like JsonMaybeValue) - if (rawJson.contains("gd")) { - std::string ver; - if (rawJson["gd"].is_object()) { - auto key = PlatformID::toShortString(GEODE_PLATFORM_TARGET, true); - if (rawJson["gd"].contains(key) && rawJson["gd"][key].is_string()) - ver = rawJson["gd"][key].as_string(); - } else if (rawJson["gd"].is_string()) { + + if (auto gd = root.needs("gd")) { + // In the future when we get rid of support for string format just + // change all of this to the gd.needs(...) stuff + gd.assertIs({ matjson::Type::Object, matjson::Type::String }); + if (gd.isObject()) { + gd.needs(GEODE_PLATFORM_SHORT_IDENTIFIER) + .mustBe("a valid gd version", [](auto const& str) { + return str == "*" || numFromString(str).isOk(); + }) + .into(impl->m_gdVersion); + } + else if (gd.isString()) { impl->m_softInvalidReason = "mod.json uses old syntax"; - goto dontCheckVersion; - } else { - return Err("[mod.json] has invalid target GD version"); } - if (ver.empty()) { - // this will show an error later on, but will at least load the rest of the metadata - ver = "0.000"; - } - if (ver != "*") { - auto res = numFromString(ver); - if (res.isErr()) { - return Err("[mod.json] has invalid target GD version"); - } - impl->m_gdVersion = ver; - } - } else { - return Err("[mod.json] is missing target GD version"); } - dontCheckVersion: + constexpr auto ID_REGEX = "[a-z0-9\\-_]+\\.[a-z0-9\\-_]+"; root.needs("id") - // todo: make this use validateID in full 2.0.0 release - .validate(MiniFunction(&ModMetadata::Impl::validateOldID)) + .mustBe(ID_REGEX, &ModMetadata::Impl::validateOldID) .into(impl->m_id); // if (!isDeprecatedIDForm(impl->m_id)) { @@ -180,7 +163,7 @@ Result ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs if (root.has("developer")) { return Err("[mod.json] can not have both \"developer\" and \"developers\" specified"); } - for (auto& dev : root.needs("developers").iterate()) { + for (auto& dev : root.needs("developers").items()) { impl->m_developers.push_back(dev.template get()); } } @@ -205,11 +188,9 @@ Result ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs }); } - for (auto& dep : root.has("dependencies").iterate()) { - auto obj = dep.obj(); - - bool onThisPlatform = !obj.has("platforms"); - for (auto& plat : obj.has("platforms").iterate()) { + for (auto& dep : root.has("dependencies").items()) { + bool onThisPlatform = !dep.has("platforms"); + for (auto& plat : dep.has("platforms").items()) { if (PlatformID::coveredBy(plat.get(), GEODE_PLATFORM_TARGET)) { onThisPlatform = true; } @@ -219,11 +200,10 @@ Result ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs } Dependency dependency; - // todo: make this use validateID in full 2.0.0 release - obj.needs("id").validate(MiniFunction(&ModMetadata::Impl::validateOldID)).into(dependency.id); - obj.needs("version").into(dependency.version); - obj.has("importance").into(dependency.importance); - obj.checkUnknownKeys(); + dep.needs("id").mustBe(ID_REGEX, &ModMetadata::Impl::validateOldID).into(dependency.id); + dep.needs("version").into(dependency.version); + dep.has("importance").into(dependency.importance); + dep.checkUnknownKeys(); if ( dependency.version.getComparison() != VersionCompare::MoreEq && @@ -247,24 +227,20 @@ Result ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs impl->m_dependencies.push_back(dependency); } - for (auto& incompat : root.has("incompatibilities").iterate()) { - auto obj = incompat.obj(); - + for (auto& incompat : root.has("incompatibilities").items()) { Incompatibility incompatibility; - obj.needs("id").validate(MiniFunction(&ModMetadata::Impl::validateOldID)).into(incompatibility.id); - obj.needs("version").into(incompatibility.version); - obj.has("importance").into(incompatibility.importance); - obj.checkUnknownKeys(); - + incompat.needs("id").mustBe(ID_REGEX, &ModMetadata::Impl::validateOldID).into(incompatibility.id); + incompat.needs("version").into(incompatibility.version); + incompat.has("importance").into(incompatibility.importance); + incompat.checkUnknownKeys(); impl->m_incompatibilities.push_back(incompatibility); } - for (auto& [key, value] : root.has("settings").items()) { + for (auto& [key, value] : root.has("settings").properties()) { // Skip settings not on this platform - if (value.template is()) { - auto obj = value.obj(); - bool onThisPlatform = !obj.has("platforms"); - for (auto& plat : obj.has("platforms").iterate()) { + if (value.is(matjson::Type::Object)) { + bool onThisPlatform = !value.has("platforms"); + for (auto& plat : value.has("platforms").items()) { if (PlatformID::coveredBy(plat.get(), GEODE_PLATFORM_TARGET)) { onThisPlatform = true; } @@ -274,24 +250,24 @@ Result ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs } } - GEODE_UNWRAP_INTO(auto sett, Setting::parse(key, impl->m_id, value)); + GEODE_UNWRAP_INTO(auto sett, SettingV3::parseBuiltin(impl->m_id, value.json())); impl->m_settings.emplace_back(key, sett); } - if (auto resources = root.has("resources").obj()) { - for (auto& [key, _] : resources.has("spritesheets").items()) { + if (auto resources = root.has("resources")) { + for (auto& [key, _] : resources.has("spritesheets").properties()) { impl->m_spritesheets.push_back(impl->m_id + "/" + key); } } - if (auto issues = root.has("issues").obj()) { + if (auto issues = root.has("issues")) { IssuesInfo issuesInfo; issues.needs("info").into(issuesInfo.info); - issues.has("url").intoAs(issuesInfo.url); + issues.has("url").into(issuesInfo.url); impl->m_issues = issuesInfo; } - if (auto links = root.has("links").obj()) { + if (auto links = root.has("links")) { links.has("homepage").into(info.getLinksMut().getImpl()->m_homepage); links.has("source").into(info.getLinksMut().getImpl()->m_source); links.has("community").into(info.getLinksMut().getImpl()->m_community); @@ -299,19 +275,15 @@ Result ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs } // Tags. Actual validation is done when interacting with the server in the UI - for (auto& tag : root.has("tags").iterate()) { + for (auto& tag : root.has("tags").items()) { impl->m_tags.insert(tag.template get()); } // with new cli, binary name is always mod id impl->m_binaryName = impl->m_id + GEODE_PLATFORM_EXTENSION; - if (checker.isError()) { - return Err(checker.getError()); - } root.checkUnknownKeys(); - - return Ok(info); + return root.ok(info); } Result ModMetadata::Impl::create(ModJson const& json) { @@ -542,6 +514,18 @@ std::vector ModMetadata::getSpritesheets() const { return m_impl->m_spritesheets; } std::vector> ModMetadata::getSettings() const { + std::vector> res; + for (auto [key, sett] : m_impl->m_settings) { + auto checker = JsonChecker(sett); + auto value = checker.root(""); + auto legacy = Setting::parse(key, m_impl->m_id, value); + if (!checker.isError() && legacy.isOk()) { + res.push_back(std::make_pair(key, *legacy)); + } + } + return res; +} +std::vector> ModMetadata::getSettingsV3() const { return m_impl->m_settings; } std::unordered_set ModMetadata::getTags() const { @@ -643,6 +627,10 @@ void ModMetadata::setSpritesheets(std::vector const& value) { m_impl->m_spritesheets = value; } void ModMetadata::setSettings(std::vector> const& value) { + // intentionally no-op because no one is supposed to be using this + // without subscribing to "internals are not stable" mentality +} +void ModMetadata::setSettings(std::vector> const& value) { m_impl->m_settings = value; } void ModMetadata::setTags(std::unordered_set const& value) { diff --git a/loader/src/loader/ModMetadataImpl.hpp b/loader/src/loader/ModMetadataImpl.hpp index b47d092c..fbf97448 100644 --- a/loader/src/loader/ModMetadataImpl.hpp +++ b/loader/src/loader/ModMetadataImpl.hpp @@ -4,6 +4,7 @@ #include #include #include +#include using namespace geode::prelude; @@ -36,7 +37,7 @@ namespace geode { std::vector m_dependencies; std::vector m_incompatibilities; std::vector m_spritesheets; - std::vector> m_settings; + std::vector> m_settings; std::unordered_set m_tags; bool m_needsEarlyLoad = false; bool m_isAPI = false; diff --git a/loader/src/loader/ModSettingsManager.cpp b/loader/src/loader/ModSettingsManager.cpp new file mode 100644 index 00000000..12eefff5 --- /dev/null +++ b/loader/src/loader/ModSettingsManager.cpp @@ -0,0 +1,11 @@ +#include "ModSettingsManager.hpp" + +SettingV3* ModSettingsManager::get(std::string const& id) {} + +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(); + } + if (m_v3.count(id)) {} +} diff --git a/loader/src/loader/ModSettingsManager.hpp b/loader/src/loader/ModSettingsManager.hpp new file mode 100644 index 00000000..455f220b --- /dev/null +++ b/loader/src/loader/ModSettingsManager.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +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; + +public: + SettingV3* get(std::string const& id); + SettingValue* getLegacy(std::string const& id); +}; diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp new file mode 100644 index 00000000..ab6b95fb --- /dev/null +++ b/loader/src/loader/SettingV3.cpp @@ -0,0 +1,746 @@ +#include +#include + +using namespace geode::prelude; + +class SettingV3::GeodeImpl { +public: + std::string modID; + std::string key; +}; + +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; +} + +std::string SettingV3::getKey() const { + return m_impl->key; +} +std::string SettingV3::getModID() const { + return m_impl->modID; +} +Mod* SettingV3::getMod() const { + return Loader::get()->getInstalledMod(m_impl->modID); +} + +Result> SettingV3::parseBuiltin(std::string const& modID, matjson::Value const& json) { + auto root = checkJson(json, "SettingV3"); + std::string type; + root.needs("type").into(type); + std::shared_ptr ret; + switch (hash(type)) { + case hash("bool"): ret = std::make_shared(); break; + case hash("int"): ret = std::make_shared(); break; + case hash("float"): ret = std::make_shared(); break; + case hash("string"): ret = std::make_shared(); break; + 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; + } + GEODE_UNWRAP(ret->parse(modID, json)); + return root.ok(ret); +} +std::optional SettingV3::convertToLegacy() const { + return std::nullopt; +} +std::optional> SettingV3::convertToLegacyValue() const { + return std::nullopt; +} + +class geode::detail::GeodeSettingBaseV3::Impl final { +public: + std::string name; + std::optional description; + std::optional enableIf; +}; + +std::string geode::detail::GeodeSettingBaseV3::getName() const { + return m_impl->name; +} +std::optional geode::detail::GeodeSettingBaseV3::getDescription() const { + return m_impl->description; +} +std::optional geode::detail::GeodeSettingBaseV3::getEnableIf() const { + return m_impl->enableIf; +} + +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); + return Ok(); +} + +class TitleSettingV3::Impl final { +public: + std::string title; +}; + +std::string TitleSettingV3::getTitle() const { + return m_impl->title; +} + +Result<> TitleSettingV3::parse(std::string const& modID, matjson::Value const& json) { + auto root = checkJson(json, "TitleSettingV3"); + root.needs("title").into(m_impl->title); + root.checkUnknownKeys(); + return root.ok(); +} +bool TitleSettingV3::load(matjson::Value const& json) { + return true; +} +bool TitleSettingV3::save(matjson::Value&) const { + return true; +} +SettingNodeV3* TitleSettingV3::createNode(float width) { + // todo +} +bool TitleSettingV3::isDefaultValue() const { + return true; +} +void TitleSettingV3::reset() {} + +class UnresolvedCustomSettingV3::Impl final { +public: + matjson::Value json; +}; + +Result<> UnresolvedCustomSettingV3::parse(std::string const& modID, matjson::Value const& json) { + m_impl->json = json; + return Ok(); +} +bool UnresolvedCustomSettingV3::load(matjson::Value const& json) { + return true; +} +bool UnresolvedCustomSettingV3::save(matjson::Value& json) const { + return true; +} +SettingNodeV3* UnresolvedCustomSettingV3::createNode(float width) { + // todo +} + +bool UnresolvedCustomSettingV3::isDefaultValue() const { + return true; +} +void UnresolvedCustomSettingV3::reset() {} + +std::optional UnresolvedCustomSettingV3::convertToLegacy() const { + return Setting(this->getKey(), this->getModID(), SettingKind(CustomSetting { + .json = std::make_shared(m_impl->json) + })); +} +std::optional> UnresolvedCustomSettingV3::convertToLegacyValue() const { + return std::nullopt; +} + +class BoolSettingV3::Impl final { +public: + bool value; + bool defaultValue; +}; + +bool BoolSettingV3::getValue() const { + return m_impl->value; +} +void BoolSettingV3::setValue(bool value) { + m_impl->value = value; +} +Result<> BoolSettingV3::isValid(bool value) const {} +bool BoolSettingV3::getDefaultValue() const { + return m_impl->defaultValue; +} + +Result<> BoolSettingV3::parse(std::string const& modID, matjson::Value const& json) { + auto root = checkJson(json, "BoolSettingV3"); + + GEODE_UNWRAP(this->parseShared(root)); + root.needs("default").into(m_impl->defaultValue); + m_impl->value = m_impl->defaultValue; + + root.checkUnknownKeys(); + return root.ok(); +} +bool BoolSettingV3::load(matjson::Value const& json) { + if (json.is_bool()) { + m_impl->value = json.as_bool(); + return true; + } + return false; +} +bool BoolSettingV3::save(matjson::Value& json) const { + json = m_impl->value; + 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; +} + +std::optional BoolSettingV3::convertToLegacy() const { + return Setting(this->getKey(), this->getModID(), SettingKind(BoolSetting { + .name = this->getName(), + .description = this->getDescription(), + .defaultValue = this->getDefaultValue(), + })); +} +std::optional> BoolSettingV3::convertToLegacyValue() const { + return std::make_unique(this->getKey(), this->getModID(), *this->convertToLegacy()); +} + +class IntSettingV3::Impl final { +public: + int64_t value; + int64_t defaultValue; + std::optional minValue; + std::optional maxValue; + + struct { + // 0 means not enabled + size_t arrowStepSize; + size_t bigArrowStepSize; + bool sliderEnabled; + std::optional sliderSnap; + bool textInputEnabled; + } controls; +}; + +bool IntSettingV3::isArrowsEnabled() const { + return m_impl->controls.arrowStepSize > 0; +} +bool IntSettingV3::isBigArrowsEnabled() const { + return m_impl->controls.bigArrowStepSize > 0; +} +size_t IntSettingV3::getArrowStepSize() const { + return m_impl->controls.arrowStepSize; +} +size_t IntSettingV3::getBigArrowStepSize() const { + return m_impl->controls.bigArrowStepSize; +} +bool IntSettingV3::isSliderEnabled() const { + return m_impl->controls.sliderEnabled; +} +std::optional IntSettingV3::getSliderSnap() const { + return m_impl->controls.sliderSnap; +} +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) { + auto root = checkJson(json, "IntSettingV3"); + + GEODE_UNWRAP(this->parseShared(root)); + root.needs("default").into(m_impl->defaultValue); + m_impl->value = m_impl->defaultValue; + + root.has("min").into(m_impl->minValue); + root.has("max").into(m_impl->maxValue); + if (auto controls = root.has("control")) { + controls.has("arrow-step").into(m_impl->controls.arrowStepSize); + if (!controls.has("arrows").template get()) { + m_impl->controls.arrowStepSize = 0; + } + controls.has("big-arrow-step").into(m_impl->controls.bigArrowStepSize); + if (!controls.has("big-arrows").template get()) { + m_impl->controls.bigArrowStepSize = 0; + } + controls.has("slider").into(m_impl->controls.sliderEnabled); + controls.has("slider-step").into(m_impl->controls.sliderSnap); + controls.has("input").into(m_impl->controls.textInputEnabled); + controls.checkUnknownKeys(); + } + + root.checkUnknownKeys(); + return root.ok(); +} +bool IntSettingV3::load(matjson::Value const& json) { + if (json.is_number()) { + m_impl->value = json.as_int(); + return true; + } + return false; +} +bool IntSettingV3::save(matjson::Value& json) const { + json = m_impl->value; + 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; +} + +std::optional IntSettingV3::convertToLegacy() const { + return Setting(this->getKey(), this->getModID(), SettingKind(IntSetting { + .name = this->getName(), + .description = this->getDescription(), + .defaultValue = this->getDefaultValue(), + .min = this->getMinValue(), + .max = this->getMaxValue(), + .controls = { + .arrows = this->isArrowsEnabled(), + .bigArrows = this->isBigArrowsEnabled(), + .arrowStep = this->getArrowStepSize(), + .bigArrowStep = this->getBigArrowStepSize(), + .slider = this->isSliderEnabled(), + .sliderStep = this->getSliderSnap(), + .input = this->isInputEnabled(), + }, + })); +} +std::optional> IntSettingV3::convertToLegacyValue() const { + return std::make_unique(this->getKey(), this->getModID(), *this->convertToLegacy()); +} + +class FloatSettingV3::Impl final { +public: + double value; + double defaultValue; + std::optional minValue; + std::optional maxValue; + + struct { + // 0 means not enabled + size_t arrowStepSize; + size_t bigArrowStepSize; + bool sliderEnabled; + std::optional sliderSnap; + bool textInputEnabled; + } controls; +}; + +bool FloatSettingV3::isArrowsEnabled() const { + return m_impl->controls.arrowStepSize > 0; +} +bool FloatSettingV3::isBigArrowsEnabled() const { + return m_impl->controls.bigArrowStepSize > 0; +} +size_t FloatSettingV3::getArrowStepSize() const { + return m_impl->controls.arrowStepSize; +} +size_t FloatSettingV3::getBigArrowStepSize() const { + return m_impl->controls.bigArrowStepSize; +} +bool FloatSettingV3::isSliderEnabled() const { + return m_impl->controls.sliderEnabled; +} +std::optional FloatSettingV3::getSliderSnap() const { + return m_impl->controls.sliderSnap; +} +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) { + auto root = checkJson(json, "FloatSettingV3"); + + GEODE_UNWRAP(this->parseShared(root)); + root.needs("default").into(m_impl->defaultValue); + m_impl->value = m_impl->defaultValue; + + root.has("min").into(m_impl->minValue); + root.has("max").into(m_impl->maxValue); + if (auto controls = root.has("control")) { + controls.has("arrow-step").into(m_impl->controls.arrowStepSize); + if (!controls.has("arrows").template get()) { + m_impl->controls.arrowStepSize = 0; + } + controls.has("big-arrow-step").into(m_impl->controls.bigArrowStepSize); + if (!controls.has("big-arrows").template get()) { + m_impl->controls.bigArrowStepSize = 0; + } + controls.has("slider").into(m_impl->controls.sliderEnabled); + controls.has("slider-step").into(m_impl->controls.sliderSnap); + controls.has("input").into(m_impl->controls.textInputEnabled); + controls.checkUnknownKeys(); + } + + root.checkUnknownKeys(); + return root.ok(); +} +bool FloatSettingV3::load(matjson::Value const& json) { + if (json.is_number()) { + m_impl->value = json.as_double(); + return true; + } + return false; +} +bool FloatSettingV3::save(matjson::Value& json) const { + json = m_impl->value; + 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; +} + +std::optional FloatSettingV3::convertToLegacy() const { + return Setting(this->getKey(), this->getModID(), SettingKind(FloatSetting { + .name = this->getName(), + .description = this->getDescription(), + .defaultValue = this->getDefaultValue(), + .min = this->getMinValue(), + .max = this->getMaxValue(), + .controls = { + .arrows = this->isArrowsEnabled(), + .bigArrows = this->isBigArrowsEnabled(), + .arrowStep = this->getArrowStepSize(), + .bigArrowStep = this->getBigArrowStepSize(), + .slider = this->isSliderEnabled(), + .sliderStep = this->getSliderSnap(), + .input = this->isInputEnabled(), + }, + })); +} +std::optional> FloatSettingV3::convertToLegacyValue() const { + return std::make_unique(this->getKey(), this->getModID(), *this->convertToLegacy()); +} + +class StringSettingV3::Impl final { +public: + std::string value; + std::string defaultValue; + std::optional match; + std::optional filter; + std::optional> oneOf; +}; + +std::string StringSettingV3::getValue() 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; +} + +std::optional StringSettingV3::getRegexValidator() const { + return m_impl->match; +} +std::optional StringSettingV3::getAllowedCharacters() const { + return m_impl->filter; +} +std::optional> StringSettingV3::getEnumOptions() const { + return m_impl->oneOf; +} + +Result<> StringSettingV3::parse(std::string const& modID, matjson::Value const& json) { + auto root = checkJson(json, "StringSettingV3"); + + GEODE_UNWRAP(this->parseShared(root)); + root.needs("default").into(m_impl->defaultValue); + m_impl->value = m_impl->defaultValue; + + root.has("match").into(m_impl->match); + root.has("filter").into(m_impl->filter); + root.has("one-of").into(m_impl->oneOf); + + root.checkUnknownKeys(); + return root.ok(); +} +bool StringSettingV3::load(matjson::Value const& json) { + if (json.is_string()) { + m_impl->value = json.as_string(); + return true; + } + return false; +} +bool StringSettingV3::save(matjson::Value& json) const { + json = m_impl->value; + 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; +} + +std::optional StringSettingV3::convertToLegacy() const { + auto setting = StringSetting(); + setting.name = this->getName(); + setting.description = this->getDescription(); + setting.defaultValue = this->getDefaultValue(); + setting.controls->filter = this->getAllowedCharacters(); + setting.controls->match = this->getRegexValidator(); + 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()); +} + +class FileSettingV3::Impl final { +public: + std::filesystem::path value; + std::filesystem::path defaultValue; + std::optional> filters; +}; + +std::filesystem::path FileSettingV3::getDefaultValue() const { + return m_impl->defaultValue; +} +std::filesystem::path FileSettingV3::getValue() const { + return m_impl->value; +} +std::optional> FileSettingV3::getFilters() const { + return m_impl->filters; +} + +Result<> FileSettingV3::parse(std::string const& modID, matjson::Value const& json) { + auto root = checkJson(json, "FileSettingV3"); + + GEODE_UNWRAP(this->parseShared(root)); + + root.needs("default").into(m_impl->defaultValue); + + // Replace known paths like `{gd-save-dir}/` + try { + m_impl->defaultValue = fmt::format( + fmt::runtime(m_impl->defaultValue.string()), + fmt::arg("gd-save-dir", dirs::getSaveDir()), + fmt::arg("gd-game-dir", dirs::getGameDir()), + fmt::arg("mod-config-dir", dirs::getModConfigDir() / modID), + fmt::arg("mod-save-dir", dirs::getModsSaveDir() / modID), + fmt::arg("temp-dir", dirs::getTempDir()) + ); + } + catch(fmt::format_error const&) { + return Err("Invalid format string for file setting path"); + } + m_impl->value = m_impl->defaultValue; + + if (auto controls = root.has("control")) { + auto filters = std::vector(); + for (auto& item : controls.has("filters").items()) { + utils::file::FilePickOptions::Filter filter; + item.has("description").into(filter.description); + item.has("files").into(filter.files); + filters.push_back(filter); + } + if (!filters.empty()) { + m_impl->filters.emplace(filters); + } + } + + root.checkUnknownKeys(); + return root.ok(); +} +bool FileSettingV3::load(matjson::Value const& json) { + if (json.is_string()) { + m_impl->value = json.as_string(); + return true; + } + return false; +} +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; +} + +std::optional FileSettingV3::convertToLegacy() const { + auto setting = FileSetting(); + setting.name = this->getName(); + setting.description = this->getDescription(); + setting.defaultValue = this->getDefaultValue(); + 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()); +} + +class Color3BSettingV3::Impl final { +public: + ccColor3B value; + ccColor3B defaultValue; +}; + +ccColor3B Color3BSettingV3::getDefaultValue() const { + return m_impl->defaultValue; +} +ccColor3B Color3BSettingV3::getValue() const { + return m_impl->value; +} + +Result<> Color3BSettingV3::parse(std::string const& modID, matjson::Value const& json) { + auto root = checkJson(json, "Color3BSettingV3"); + + GEODE_UNWRAP(this->parseShared(root)); + root.needs("default").into(m_impl->defaultValue); + m_impl->value = m_impl->defaultValue; + + root.checkUnknownKeys(); + return root.ok(); +} +bool Color3BSettingV3::load(matjson::Value const& json) { + if (json.template is()) { + m_impl->value = json.template as(); + return true; + } + return false; +} +bool Color3BSettingV3::save(matjson::Value& json) const { + json = m_impl->value; + 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; +} + +std::optional Color3BSettingV3::convertToLegacy() const { + auto setting = ColorSetting(); + setting.name = this->getName(); + setting.description = this->getDescription(); + 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()); +} + +class Color4BSettingV3::Impl final { +public: + ccColor4B value; + ccColor4B defaultValue; +}; + +ccColor4B Color4BSettingV3::getDefaultValue() const { + return m_impl->defaultValue; +} +ccColor4B Color4BSettingV3::getValue() const { + return m_impl->value; +} + +Result<> Color4BSettingV3::parse(std::string const& modID, matjson::Value const& json) { + auto root = checkJson(json, "Color4BSettingV3"); + + GEODE_UNWRAP(this->parseShared(root)); + root.needs("default").into(m_impl->defaultValue); + m_impl->value = m_impl->defaultValue; + + root.checkUnknownKeys(); + return root.ok(); +} +bool Color4BSettingV3::load(matjson::Value const& json) { + if (json.template is()) { + m_impl->value = json.template as(); + return true; + } + return false; +} +bool Color4BSettingV3::save(matjson::Value& json) const { + json = m_impl->value; + 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; +} + +std::optional Color4BSettingV3::convertToLegacy() const { + auto setting = ColorAlphaSetting(); + setting.name = this->getName(); + setting.description = this->getDescription(); + 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()); +} diff --git a/loader/src/utils/JsonValidation.cpp b/loader/src/utils/JsonValidation.cpp index 4e654041..de775c91 100644 --- a/loader/src/utils/JsonValidation.cpp +++ b/loader/src/utils/JsonValidation.cpp @@ -2,201 +2,50 @@ using namespace geode::prelude; - matjson::Value& JsonMaybeSomething::json() { return m_json; } - JsonMaybeSomething::JsonMaybeSomething( JsonChecker& checker, matjson::Value& json, std::string const& hierarchy, bool hasValue ) : m_checker(checker), m_json(json), m_hierarchy(hierarchy), m_hasValue(hasValue) {} - bool JsonMaybeSomething::isError() const { return m_checker.isError() || !m_hasValue; } - std::string JsonMaybeSomething::getError() const { return m_checker.getError(); } - JsonMaybeSomething::operator bool() const { return !isError(); } - void JsonMaybeSomething::setError(std::string const& error) { m_checker.m_result = error; } - JsonMaybeValue::JsonMaybeValue( JsonChecker& checker, matjson::Value& json, std::string const& hierarchy, bool hasValue -) : - JsonMaybeSomething(checker, json, hierarchy, hasValue) {} - +) : JsonMaybeSomething(checker, json, hierarchy, hasValue) {} JsonMaybeSomething& JsonMaybeValue::self() { return *static_cast(this); } -// template -// template -// JsonMaybeValue& JsonMaybeValue::as() { -// if (this->isError()) return *this; -// if (!jsonConvertibleTo(self().m_json.type(), T)) { -// this->setError( -// self().m_hierarchy + ": Invalid type \"" + -// self().m_json.type_name() + "\", expected \"" + -// jsonValueTypeToString(T) + "\"" -// ); -// } -// m_inferType = false; -// return *this; -// } - - JsonMaybeValue& JsonMaybeValue::array() { this->as(); return *this; } -// template -// template -// JsonMaybeValue JsonMaybeValue::asOneOf() { -// if (this->isError()) return *this; -// bool isOneOf = (... || jsonConvertibleTo(self().m_json.type(), T)); -// if (!isOneOf) { -// this->setError( -// self().m_hierarchy + ": Invalid type \"" + -// self().m_json.type_name() + "\", expected one of \"" + -// (jsonValueTypeToString(T), ...) + "\"" -// ); -// } -// m_inferType = false; -// return *this; -// } - -// template -// template -// JsonMaybeValue JsonMaybeValue::is() { -// if (this->isError()) return *this; -// self().m_hasValue = jsonConvertibleTo(self().m_json.type(), T); -// m_inferType = false; -// return *this; -// } - -// template -// template -// JsonMaybeValue JsonMaybeValue::validate(JsonValueValidator validator) { -// if (this->isError()) return *this; -// try { -// if (!validator(self().m_json.template get())) { -// this->setError(self().m_hierarchy + ": Invalid value format"); -// } -// } catch(...) { -// this->setError( -// self().m_hierarchy + ": Invalid type \"" + -// std::string(self().m_json.type_name()) + "\"" -// ); -// } -// return *this; -// } - -// template -// template -// JsonMaybeValue JsonMaybeValue::inferType() { -// if (this->isError() || !m_inferType) return *this; -// return this->as()>(); -// } - -// template -// template -// JsonMaybeValue JsonMaybeValue::intoRaw(T& target) { -// if (this->isError()) return *this; -// target = self().m_json; -// return *this; -// } - -// template -// template -// JsonMaybeValue JsonMaybeValue::into(T& target) { -// return this->intoAs(target); -// } - -// template -// template -// JsonMaybeValue JsonMaybeValue::into(std::optional& target) { -// return this->intoAs>(target); -// } - -// template -// template -// JsonMaybeValue JsonMaybeValue::intoAs(T& target) { -// this->inferType(); -// if (this->isError()) return *this; -// try { -// target = self().m_json.template get(); -// } catch(...) { -// this->setError( -// self().m_hierarchy + ": Invalid type \"" + -// std::string(self().m_json.type_name()) + "\"" -// ); -// } -// return *this; -// } - -// template -// template -// T JsonMaybeValue::get() { -// this->inferType(); -// if (this->isError()) return T(); -// try { -// return self().m_json.template get(); -// } catch(...) { -// this->setError( -// self().m_hierarchy + ": Invalid type to get \"" + -// std::string(self().m_json.type_name()) + "\"" -// ); -// } -// return T(); -// } - - JsonMaybeObject JsonMaybeValue::obj() { this->as(); return JsonMaybeObject(self().m_checker, self().m_json, self().m_hierarchy, self().m_hasValue); } -// template -// template -// struct JsonMaybeValue::Iterator { -// std::vector m_values; - -// using iterator = typename std::vector::iterator; -// using const_iterator = typename std::vector::const_iterator; - -// iterator begin() { -// return m_values.begin(); -// } -// iterator end() { -// return m_values.end(); -// } - -// const_iterator begin() const { -// return m_values.begin(); -// } -// const_iterator end() const { -// return m_values.end(); -// } -// }; - - JsonMaybeValue JsonMaybeValue::at(size_t i) { this->as(); if (this->isError()) return *this; @@ -216,7 +65,6 @@ JsonMaybeValue JsonMaybeValue::at(size_t i) { ); } - typename JsonMaybeValue::template Iterator JsonMaybeValue::iterate() { this->as(); Iterator iter; @@ -232,7 +80,6 @@ typename JsonMaybeValue::template Iterator JsonMaybeValue::itera return iter; } - typename JsonMaybeValue::template Iterator> JsonMaybeValue::items() { this->as(); Iterator> iter; @@ -248,33 +95,26 @@ typename JsonMaybeValue::template Iterator(this); } - void JsonMaybeObject::addKnownKey(std::string const& key) { m_knownKeys.insert(key); } - matjson::Value& JsonMaybeObject::json() { return self().m_json; } - JsonMaybeValue JsonMaybeObject::emptyValue() { return JsonMaybeValue(self().m_checker, self().m_json, "", false); } - JsonMaybeValue JsonMaybeObject::has(std::string const& key) { this->addKnownKey(key); if (this->isError()) return emptyValue(); @@ -284,7 +124,6 @@ JsonMaybeValue JsonMaybeObject::has(std::string const& key) { return JsonMaybeValue(self().m_checker, self().m_json[key], key, true); } - JsonMaybeValue JsonMaybeObject::needs(std::string const& key) { this->addKnownKey(key); if (this->isError()) return emptyValue(); @@ -295,7 +134,6 @@ JsonMaybeValue JsonMaybeObject::needs(std::string const& key) { return JsonMaybeValue(self().m_checker, self().m_json[key], key, true); } - // TODO: gross hack :3 (ctrl+f this comment to find the other part) extern bool s_jsonCheckerShouldCheckUnknownKeys; bool s_jsonCheckerShouldCheckUnknownKeys = true; @@ -309,20 +147,270 @@ void JsonMaybeObject::checkUnknownKeys() { } } - JsonChecker::JsonChecker(matjson::Value& json) : m_json(json), m_result(std::monostate()) {} - bool JsonChecker::isError() const { return std::holds_alternative(m_result); } - std::string JsonChecker::getError() const { return std::get(m_result); } - JsonMaybeValue JsonChecker::root(std::string const& hierarchy) { return JsonMaybeValue(*this, m_json, hierarchy, true); } + +static const char* matJsonTypeToString(matjson::Type ty) { + switch (ty) { + case matjson::Type::Null: return "null"; + case matjson::Type::Bool: return "bool"; + case matjson::Type::Number: return "number"; + case matjson::Type::String: return "string"; + case matjson::Type::Array: return "array"; + case matjson::Type::Object: return "object"; + default: return "unknown"; + } +} + +// This is used for null JsonExpectedValues (for example when doing +// `json.has("key")` where "key" doesn't exist) +static matjson::Value NULL_SCOPED_VALUE = nullptr; + +class JsonExpectedValue::Impl final { +public: + // Values shared between JsonExpectedValues related to the same JSON + struct Shared final { + matjson::Value originalJson; + std::optional error; + std::string rootScopeName; + + Shared(matjson::Value const& json, std::string_view rootScopeName) + : originalJson(json), rootScopeName(rootScopeName) {} + }; + + // this may be null if the JsonExpectedValue is a "null" value + std::shared_ptr shared; + matjson::Value& scope; + std::string scopeName; + std::string key; + std::unordered_set knownKeys; + + Impl() + : shared(nullptr), + scope(NULL_SCOPED_VALUE) + {} + + // Create a root Impl + Impl(std::shared_ptr shared) + : shared(shared), + scope(shared->originalJson), + scopeName(shared->rootScopeName) + {} + + // Create a derived Impl + Impl(Impl* from, matjson::Value& scope, std::string_view key) + : shared(from->shared), + scope(scope), + scopeName(fmt::format("{}.{}", from->scopeName, key)), + key(key) + {} +}; + +JsonExpectedValue::JsonExpectedValue() + : m_impl(std::make_unique()) +{} +JsonExpectedValue::JsonExpectedValue(Impl* from, matjson::Value& scope, std::string_view key) + : 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))) +{} +JsonExpectedValue::~JsonExpectedValue() {} + +JsonExpectedValue::JsonExpectedValue(JsonExpectedValue&&) = default; +JsonExpectedValue& JsonExpectedValue::operator=(JsonExpectedValue&&) = default; + +matjson::Value const& JsonExpectedValue::getJSONRef() const { + return m_impl->scope; +} +matjson::Value JsonExpectedValue::json() const { + return m_impl->scope; +} +std::string JsonExpectedValue::key() const { + return m_impl->key; +} + +bool JsonExpectedValue::hasError() const { + return !m_impl->shared || m_impl->shared->error.has_value(); +} +void JsonExpectedValue::setError(std::string_view error) { + m_impl->shared->error.emplace(fmt::format("[{}]: {}", m_impl->scopeName, error)); +} + +bool JsonExpectedValue::is(matjson::Type type) const { + if (this->hasError()) return false; + return m_impl->scope.type() == type; +} +bool JsonExpectedValue::isNull() const { + return this->is(matjson::Type::Null); +} +bool JsonExpectedValue::isBool() const { + return this->is(matjson::Type::Bool); +} +bool JsonExpectedValue::isNumber() const { + return this->is(matjson::Type::Number); +} +bool JsonExpectedValue::isString() const { + return this->is(matjson::Type::String); +} +bool JsonExpectedValue::isArray() const { + return this->is(matjson::Type::Array); +} +bool JsonExpectedValue::isObject() const { + return this->is(matjson::Type::Object); +} +JsonExpectedValue& JsonExpectedValue::assertIs(matjson::Type type) { + if (this->hasError()) return *this; + if (m_impl->scope.type() != type) { + this->setError( + "invalid type {}, expected {}", + matJsonTypeToString(m_impl->scope.type()), + matJsonTypeToString(type) + ); + } + return *this; +} +JsonExpectedValue& JsonExpectedValue::assertIsNull() { + return this->assertIs(matjson::Type::Null); +} +JsonExpectedValue& JsonExpectedValue::assertIsBool() { + return this->assertIs(matjson::Type::Bool); +} +JsonExpectedValue& JsonExpectedValue::assertIsNumber() { + return this->assertIs(matjson::Type::Number); +} +JsonExpectedValue& JsonExpectedValue::assertIsString() { + return this->assertIs(matjson::Type::String); +} +JsonExpectedValue& JsonExpectedValue::assertIsArray() { + return this->assertIs(matjson::Type::Array); +} +JsonExpectedValue& JsonExpectedValue::assertIsObject() { + return this->assertIs(matjson::Type::Object); +} +JsonExpectedValue& JsonExpectedValue::assertIs(std::initializer_list types) { + if (this->hasError()) return *this; + if (!std::any_of(types.begin(), types.end(), [this](matjson::Type t) { return t == m_impl->scope.type(); })) { + this->setError( + "invalid type {}, expected either {}", + matJsonTypeToString(m_impl->scope.type()), + ranges::join(ranges::map>(types, [](matjson::Type t) { + return matJsonTypeToString(t); + }), " or ") + ); + } + return *this; +} + +JsonExpectedValue JsonExpectedValue::has(std::string_view key) { + if (this->hasError()) { + return JsonExpectedValue(); + } + if (!this->assertIs(matjson::Type::Object)) { + return JsonExpectedValue(); + } + if (!m_impl->scope.contains(key)) { + return JsonExpectedValue(); + } + return JsonExpectedValue(m_impl.get(), m_impl->scope[key], key); +} +JsonExpectedValue JsonExpectedValue::needs(std::string_view key) { + if (this->hasError()) { + return JsonExpectedValue(); + } + if (!this->assertIs(matjson::Type::Object)) { + return JsonExpectedValue(); + } + if (!m_impl->scope.contains(key)) { + this->setError("missing required key {}", key); + return JsonExpectedValue(); + } + return JsonExpectedValue(m_impl.get(), m_impl->scope[key], key); +} +std::vector> JsonExpectedValue::properties() { + if (this->hasError()) { + return std::vector>(); + } + if (!this->assertIs(matjson::Type::Object)) { + return std::vector>(); + } + std::vector> res; + for (auto& [k, v] : m_impl->scope.as_object()) { + res.push_back(std::make_pair(k, JsonExpectedValue(m_impl.get(), v, k))); + } + return res; +} +void JsonExpectedValue::checkUnknownKeys() { + for (auto& [key, _] : this->properties()) { + if (!m_impl->knownKeys.count(key)) { + log::warn("{} contains unknown key \"{}\"", m_impl->scopeName, key); + } + } +} + +size_t JsonExpectedValue::length() { + if (this->hasError()) { + return 0; + } + if (!this->assertIs(matjson::Type::Array)) { + return 0; + } + return m_impl->scope.as_array().size(); +} +JsonExpectedValue JsonExpectedValue::at(size_t index) { + if (this->hasError()) { + return JsonExpectedValue(); + } + if (!this->assertIs(matjson::Type::Array)) { + return JsonExpectedValue(); + } + auto& arr = m_impl->scope.as_array(); + if (arr.size() <= index) { + this->setError( + "array expected to have at least size {}, but its size was only {}", + index + 1, arr.size() + ); + return JsonExpectedValue(); + } + return JsonExpectedValue(m_impl.get(), arr.at(index), std::to_string(index)); +} +std::vector JsonExpectedValue::items() { + if (this->hasError()) { + return std::vector(); + } + if (!this->assertIs(matjson::Type::Object)) { + return std::vector(); + } + std::vector res; + size_t i = 0; + for (auto& v : m_impl->scope.as_array()) { + res.push_back(JsonExpectedValue(m_impl.get(), v, std::to_string(i++))); + } + return res; +} + +JsonExpectedValue::operator bool() const { + return !this->hasError(); +} + +Result<> JsonExpectedValue::ok() { + if (m_impl->shared && m_impl->shared->error) { + return Err(*m_impl->shared->error); + } + return Ok(); +} + +JsonExpectedValue geode::checkJson(matjson::Value const& json, std::string_view rootScopeName) { + return JsonExpectedValue(json, rootScopeName); +} From c6bbf4b3a16f7f52ae915863dd2f745c9f32abfb Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Tue, 13 Aug 2024 13:41:49 +0300 Subject: [PATCH 02/54] once again main is broken --- loader/include/Geode/loader/SettingV3.hpp | 1 + loader/src/loader/SettingV3.cpp | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index c25afc0d..f2ef2c55 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -60,6 +60,7 @@ namespace geode { protected: Result<> parseShared(JsonExpectedValue& json); + Result<> isValidShared() const; public: std::string getName() const; diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index ab6b95fb..13071058 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -47,6 +47,7 @@ Result> SettingV3::parseBuiltin(std::string const& mo GEODE_UNWRAP(ret->parse(modID, json)); return root.ok(ret); } + std::optional SettingV3::convertToLegacy() const { return std::nullopt; } @@ -77,6 +78,11 @@ Result<> geode::detail::GeodeSettingBaseV3::parseShared(JsonExpectedValue& json) json.needs("enable-if").into(m_impl->enableIf); return Ok(); } +Result<> geode::detail::GeodeSettingBaseV3::isValidShared() const { + // In the future if something like `enable-if` preventing + // programmatic modification of settings it should be added here + return Ok(); +} class TitleSettingV3::Impl final { public: @@ -152,7 +158,11 @@ bool BoolSettingV3::getValue() const { void BoolSettingV3::setValue(bool value) { m_impl->value = value; } -Result<> BoolSettingV3::isValid(bool value) const {} +Result<> BoolSettingV3::isValid(bool value) const { + GEODE_UNWRAP(this->isValidShared()); + return Ok(); +} + bool BoolSettingV3::getDefaultValue() const { return m_impl->defaultValue; } From f37c903160e808f71fc90c9945a43450ab9ca331 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Mon, 19 Aug 2024 19:19:29 +0300 Subject: [PATCH 03/54] new settings should work now --- loader/include/Geode/loader/Mod.hpp | 27 +- loader/include/Geode/loader/Setting.hpp | 8 +- loader/include/Geode/loader/SettingV3.hpp | 280 +++++++---- loader/include/Geode/platform/windows.hpp | 8 + loader/include/Geode/utils/JsonValidation.hpp | 6 +- loader/src/load.cpp | 1 + loader/src/loader/Loader.cpp | 2 +- loader/src/loader/Mod.cpp | 18 +- loader/src/loader/ModImpl.cpp | 129 +---- loader/src/loader/ModImpl.hpp | 9 +- loader/src/loader/ModMetadataImpl.cpp | 6 +- loader/src/loader/ModSettingsManager.cpp | 127 ++++- loader/src/loader/ModSettingsManager.hpp | 27 +- loader/src/loader/Setting.cpp | 50 +- loader/src/loader/SettingNodeV3.cpp | 445 ++++++++++++++++++ loader/src/loader/SettingNodeV3.hpp | 174 +++++++ loader/src/loader/SettingV3.cpp | 334 ++++++------- loader/src/loader/SettingV3Impl.hpp | 18 + loader/src/ui/mods/GeodeStyle.hpp | 4 +- .../src/ui/mods/settings/ModSettingsPopup.cpp | 88 ++-- .../src/ui/mods/settings/ModSettingsPopup.hpp | 12 +- loader/src/utils/JsonValidation.cpp | 4 +- 22 files changed, 1282 insertions(+), 495 deletions(-) create mode 100644 loader/src/loader/SettingNodeV3.cpp create mode 100644 loader/src/loader/SettingNodeV3.hpp create mode 100644 loader/src/loader/SettingV3Impl.hpp 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; From 1032d9afa8ccbc45880941ac4a4615aee55f5765 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Tue, 20 Aug 2024 00:31:41 +0300 Subject: [PATCH 04/54] no longer crashes on startup :3 --- loader/include/Geode/loader/SettingV3.hpp | 40 +++++++++++++++- loader/include/Geode/platform/cplatform.h | 4 ++ loader/include/Geode/utils/JsonValidation.hpp | 3 +- loader/src/loader/Mod.cpp | 4 +- loader/src/loader/ModMetadataImpl.cpp | 3 +- loader/src/loader/SettingV3.cpp | 48 ++++++++++++------- loader/src/utils/JsonValidation.cpp | 10 ++-- 7 files changed, 85 insertions(+), 27 deletions(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index 794bea31..91af75ad 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -6,6 +6,8 @@ // todo: remove this header in 4.0.0 #include "Setting.hpp" #include "../utils/cocos.hpp" +// this unfortunately has to be included because of C++ templates +#include "../utils/JsonValidation.hpp" // todo in v4: these can be removed as well as the friend decl in UnresolvedCustomSettingV3 class ModSettingsManager; @@ -13,7 +15,6 @@ class LegacyCustomSettingToV3Node; namespace geode { class SettingNodeV3; - class JsonExpectedValue; class GEODE_DLL SettingV3 : public std::enable_shared_from_this { private: @@ -70,11 +71,28 @@ namespace geode { class Impl; std::shared_ptr m_impl; + Result<> parseSharedBase(JsonExpectedValue& json); + protected: - Result<> parseShared(JsonExpectedValue& json); Result<> isValidShared() const; + template + Result<> parseShared(JsonExpectedValue& json, T& defaultValue) { + GEODE_UNWRAP(this->parseSharedBase(json)); + auto value = json.needs("default"); + // Check if this is a platform-specific default value + if (value.isObject() && value.has(GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH)) { + value.needs(GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH).into(defaultValue); + } + else { + value.into(defaultValue); + } + return Ok(); + } + public: + GeodeSettingBaseV3(); + std::string getName() const; std::optional getDescription() const; std::optional getEnableIf() const; @@ -116,6 +134,8 @@ namespace geode { Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; public: + TitleSettingV3(); + std::string getTitle() const; bool load(matjson::Value const& json) override; @@ -138,6 +158,8 @@ namespace geode { Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; public: + UnresolvedCustomSettingV3(); + bool load(matjson::Value const& json) override; bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; @@ -159,6 +181,8 @@ namespace geode { Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; public: + BoolSettingV3(); + bool getDefaultValue() const override; Result<> isValid(bool value) const override; @@ -180,6 +204,8 @@ namespace geode { Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; public: + IntSettingV3(); + int64_t getDefaultValue() const override; Result<> isValid(int64_t value) const override; @@ -212,6 +238,8 @@ namespace geode { Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; public: + FloatSettingV3(); + double getDefaultValue() const override; Result<> isValid(double value) const override; @@ -244,6 +272,8 @@ namespace geode { Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; public: + StringSettingV3(); + std::string getDefaultValue() const override; Result<> isValid(std::string_view value) const override; @@ -269,6 +299,8 @@ namespace geode { Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; public: + FileSettingV3(); + std::filesystem::path getDefaultValue() const override; Result<> isValid(std::filesystem::path const& value) const override; @@ -292,6 +324,8 @@ namespace geode { Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; public: + Color3BSettingV3(); + cocos2d::ccColor3B getDefaultValue() const override; Result<> isValid(cocos2d::ccColor3B value) const override; @@ -313,6 +347,8 @@ namespace geode { Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; public: + Color4BSettingV3(); + cocos2d::ccColor4B getDefaultValue() const override; Result<> isValid(cocos2d::ccColor4B value) const override; diff --git a/loader/include/Geode/platform/cplatform.h b/loader/include/Geode/platform/cplatform.h index c930d4c2..54676313 100644 --- a/loader/include/Geode/platform/cplatform.h +++ b/loader/include/Geode/platform/cplatform.h @@ -16,6 +16,7 @@ #define GEODE_PLATFORM_NAME "Windows" #define GEODE_PLATFORM_EXTENSION ".dll" #define GEODE_PLATFORM_SHORT_IDENTIFIER "win" + #define GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH "win" #define CC_TARGET_OS_WIN32 #if defined(WIN64) || defined(_WIN64) || defined(__WIN64) && !defined(__CYGWIN__) @@ -47,6 +48,7 @@ #define GEODE_PLATFORM_NAME "iOS" #define GEODE_PLATFORM_EXTENSION ".ios.dylib" #define GEODE_PLATFORM_SHORT_IDENTIFIER "ios" + #define GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH "ios" #define CC_TARGET_OS_IPHONE #else #define GEODE_IOS(...) @@ -54,6 +56,7 @@ #define GEODE_IS_MACOS #define GEODE_IS_DESKTOP #define GEODE_PLATFORM_EXTENSION ".dylib" + #define GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH "mac" #define CC_TARGET_OS_MAC #if TARGET_CPU_ARM64 @@ -85,6 +88,7 @@ #define GEODE_IS_MOBILE #define GEODE_CALL #define CC_TARGET_OS_ANDROID + #define GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH "android" #if defined(__arm__) #define GEODE_ANDROID32(...) __VA_ARGS__ diff --git a/loader/include/Geode/utils/JsonValidation.hpp b/loader/include/Geode/utils/JsonValidation.hpp index 6b0cf6d1..3f0d6ef8 100644 --- a/loader/include/Geode/utils/JsonValidation.hpp +++ b/loader/include/Geode/utils/JsonValidation.hpp @@ -321,7 +321,8 @@ namespace geode { try { return this->getJSONRef().template as(); } - catch(matjson::JsonException const& e) { + // matjson can throw variant exceptions too so you need to do this + catch(std::exception const& e) { this->setError("invalid json type: {}", e); } return std::nullopt; diff --git a/loader/src/loader/Mod.cpp b/loader/src/loader/Mod.cpp index bb2d5c46..fa54fb97 100644 --- a/loader/src/loader/Mod.cpp +++ b/loader/src/loader/Mod.cpp @@ -164,9 +164,7 @@ SettingValue* Mod::getSetting(std::string_view const key) const { } 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; + return m_impl->m_settings->get(std::string(key)); } void Mod::registerCustomSetting(std::string_view const key, std::unique_ptr value) { diff --git a/loader/src/loader/ModMetadataImpl.cpp b/loader/src/loader/ModMetadataImpl.cpp index d7e397c2..e4029d37 100644 --- a/loader/src/loader/ModMetadataImpl.cpp +++ b/loader/src/loader/ModMetadataImpl.cpp @@ -125,8 +125,6 @@ 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); @@ -283,6 +281,7 @@ Result ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs impl->m_binaryName = impl->m_id + GEODE_PLATFORM_EXTENSION; root.checkUnknownKeys(); + return root.ok(info); } diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index f1cec18d..e919dd9e 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -67,6 +67,8 @@ public: std::optional enableIf; }; +geode::detail::GeodeSettingBaseV3::GeodeSettingBaseV3() : m_impl(std::make_shared()) {} + std::string geode::detail::GeodeSettingBaseV3::getName() const { return m_impl->name.value_or(this->getKey()); } @@ -77,7 +79,11 @@ std::optional geode::detail::GeodeSettingBaseV3::getEnableIf() cons return m_impl->enableIf; } -Result<> geode::detail::GeodeSettingBaseV3::parseShared(JsonExpectedValue& json) { +Result<> geode::detail::GeodeSettingBaseV3::parseSharedBase(JsonExpectedValue& json) { + // Mark keys that have been checked before-hand + json.needs("type"); + json.has("platforms"); + json.has("name").into(m_impl->name); json.has("description").into(m_impl->description); json.has("enable-if").into(m_impl->enableIf); @@ -94,6 +100,8 @@ public: std::string title; }; +TitleSettingV3::TitleSettingV3() : m_impl(std::make_shared()) {} + std::string TitleSettingV3::getTitle() const { return m_impl->title; } @@ -126,6 +134,8 @@ void TitleSettingV3::reset() {} // replace this comment with it // put it riiiiiight here +UnresolvedCustomSettingV3::UnresolvedCustomSettingV3() : m_impl(std::make_shared()) {} + Result<> UnresolvedCustomSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) { m_impl->json = json; return Ok(); @@ -167,6 +177,8 @@ public: bool defaultValue; }; +BoolSettingV3::BoolSettingV3() : m_impl(std::make_shared()) {} + bool& BoolSettingV3::getValueMut() const { return m_impl->value; } @@ -181,8 +193,7 @@ Result<> BoolSettingV3::isValid(bool value) const { 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)); - root.needs("default").into(m_impl->defaultValue); + GEODE_UNWRAP(this->parseShared(root, m_impl->defaultValue)); m_impl->value = m_impl->defaultValue; root.checkUnknownKeys(); @@ -233,6 +244,8 @@ public: } controls; }; +IntSettingV3::IntSettingV3() : m_impl(std::make_shared()) {} + int64_t& IntSettingV3::getValueMut() const { return m_impl->value; } @@ -282,8 +295,7 @@ bool IntSettingV3::isInputEnabled() const { 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)); - root.needs("default").into(m_impl->defaultValue); + GEODE_UNWRAP(this->parseShared(root, m_impl->defaultValue)); m_impl->value = m_impl->defaultValue; root.has("min").into(m_impl->minValue); @@ -362,6 +374,8 @@ public: } controls; }; +FloatSettingV3::FloatSettingV3() : m_impl(std::make_shared()) {} + double& FloatSettingV3::getValueMut() const { return m_impl->value; } @@ -411,8 +425,7 @@ bool FloatSettingV3::isInputEnabled() const { 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)); - root.needs("default").into(m_impl->defaultValue); + GEODE_UNWRAP(this->parseShared(root, m_impl->defaultValue)); m_impl->value = m_impl->defaultValue; root.has("min").into(m_impl->minValue); @@ -483,6 +496,8 @@ public: std::optional> oneOf; }; +StringSettingV3::StringSettingV3() : m_impl(std::make_shared()) {} + std::string& StringSettingV3::getValueMut() const { return m_impl->value; } @@ -517,8 +532,7 @@ std::optional> StringSettingV3::getEnumOptions() const 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)); - root.needs("default").into(m_impl->defaultValue); + GEODE_UNWRAP(this->parseShared(root, m_impl->defaultValue)); m_impl->value = m_impl->defaultValue; root.has("match").into(m_impl->match); @@ -566,6 +580,8 @@ public: std::optional> filters; }; +FileSettingV3::FileSettingV3() : m_impl(std::make_shared()) {} + std::filesystem::path& FileSettingV3::getValueMut() const { return m_impl->value; } @@ -584,9 +600,7 @@ std::optional> FileSettingV3:: 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)); - - root.needs("default").into(m_impl->defaultValue); + GEODE_UNWRAP(this->parseShared(root, m_impl->defaultValue)); // Replace known paths like `{gd-save-dir}/` try { @@ -655,6 +669,8 @@ public: ccColor3B defaultValue; }; +Color3BSettingV3::Color3BSettingV3() : m_impl(std::make_shared()) {} + ccColor3B& Color3BSettingV3::getValueMut() const { return m_impl->value; } @@ -669,8 +685,7 @@ Result<> Color3BSettingV3::isValid(ccColor3B value) const { 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)); - root.needs("default").into(m_impl->defaultValue); + GEODE_UNWRAP(this->parseShared(root, m_impl->defaultValue)); m_impl->value = m_impl->defaultValue; root.checkUnknownKeys(); @@ -710,6 +725,8 @@ public: ccColor4B defaultValue; }; +Color4BSettingV3::Color4BSettingV3() : m_impl(std::make_shared()) {} + ccColor4B& Color4BSettingV3::getValueMut() const { return m_impl->value; } @@ -724,8 +741,7 @@ Result<> Color4BSettingV3::isValid(ccColor4B value) const { 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)); - root.needs("default").into(m_impl->defaultValue); + GEODE_UNWRAP(this->parseShared(root, m_impl->defaultValue)); m_impl->value = m_impl->defaultValue; root.checkUnknownKeys(); diff --git a/loader/src/utils/JsonValidation.cpp b/loader/src/utils/JsonValidation.cpp index 7af8adf9..3cd4489b 100644 --- a/loader/src/utils/JsonValidation.cpp +++ b/loader/src/utils/JsonValidation.cpp @@ -320,6 +320,7 @@ JsonExpectedValue JsonExpectedValue::has(std::string_view key) { if (!this->assertIs(matjson::Type::Object)) { return JsonExpectedValue(); } + m_impl->knownKeys.insert(std::string(key)); if (!m_impl->scope.contains(key)) { return JsonExpectedValue(); } @@ -332,6 +333,7 @@ JsonExpectedValue JsonExpectedValue::needs(std::string_view key) { if (!this->assertIs(matjson::Type::Object)) { return JsonExpectedValue(); } + m_impl->knownKeys.insert(std::string(key)); if (!m_impl->scope.contains(key)) { this->setError("missing required key {}", key); return JsonExpectedValue(); @@ -352,8 +354,9 @@ std::vector> JsonExpectedValue::proper return res; } void JsonExpectedValue::checkUnknownKeys() { - for (auto& [key, _] : this->properties()) { - if (!m_impl->knownKeys.count(key)) { + if (this->hasError()) return; + for (auto&& [key, _] : this->properties()) { + if (!m_impl->knownKeys.contains(key)) { log::warn("{} contains unknown key \"{}\"", m_impl->scopeName, key); } } @@ -401,7 +404,8 @@ std::vector JsonExpectedValue::items() { } JsonExpectedValue::operator bool() const { - return !this->hasError(); + // The shared check is because null values should evaluate to false so `obj.has("key")` evaluates to false + return m_impl->shared && !this->hasError(); } Result<> JsonExpectedValue::ok() { From f061dd4bf2d1a180cb94647e845c53385a6a44a4 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Tue, 20 Aug 2024 12:50:04 +0300 Subject: [PATCH 05/54] make setting constructors private --- loader/include/Geode/loader/SettingV3.hpp | 54 +++++++++++++++++++---- loader/src/loader/SettingV3.cpp | 36 +++++++-------- 2 files changed, 63 insertions(+), 27 deletions(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index 91af75ad..c03f0c81 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -133,8 +133,12 @@ namespace geode { protected: Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; + private: + class PrivateMarker {}; + friend class SettingV3; + public: - TitleSettingV3(); + TitleSettingV3(PrivateMarker); std::string getTitle() const; @@ -157,8 +161,12 @@ namespace geode { protected: Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; + private: + class PrivateMarker {}; + friend class SettingV3; + public: - UnresolvedCustomSettingV3(); + UnresolvedCustomSettingV3(PrivateMarker); bool load(matjson::Value const& json) override; bool save(matjson::Value& json) const override; @@ -180,8 +188,12 @@ namespace geode { bool& getValueMut() const override; Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; + private: + class PrivateMarker {}; + friend class SettingV3; + public: - BoolSettingV3(); + BoolSettingV3(PrivateMarker); bool getDefaultValue() const override; Result<> isValid(bool value) const override; @@ -203,8 +215,12 @@ namespace geode { int64_t& getValueMut() const override; Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; + private: + class PrivateMarker {}; + friend class SettingV3; + public: - IntSettingV3(); + IntSettingV3(PrivateMarker); int64_t getDefaultValue() const override; Result<> isValid(int64_t value) const override; @@ -237,8 +253,12 @@ namespace geode { double& getValueMut() const override; Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; + private: + class PrivateMarker {}; + friend class SettingV3; + public: - FloatSettingV3(); + FloatSettingV3(PrivateMarker); double getDefaultValue() const override; Result<> isValid(double value) const override; @@ -271,8 +291,12 @@ namespace geode { std::string& getValueMut() const override; Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; + private: + class PrivateMarker {}; + friend class SettingV3; + public: - StringSettingV3(); + StringSettingV3(PrivateMarker); std::string getDefaultValue() const override; Result<> isValid(std::string_view value) const override; @@ -298,8 +322,12 @@ namespace geode { std::filesystem::path& getValueMut() const override; Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; + private: + class PrivateMarker {}; + friend class SettingV3; + public: - FileSettingV3(); + FileSettingV3(PrivateMarker); std::filesystem::path getDefaultValue() const override; Result<> isValid(std::filesystem::path const& value) const override; @@ -323,8 +351,12 @@ namespace geode { cocos2d::ccColor3B& getValueMut() const override; Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; + private: + class PrivateMarker {}; + friend class SettingV3; + public: - Color3BSettingV3(); + Color3BSettingV3(PrivateMarker); cocos2d::ccColor3B getDefaultValue() const override; Result<> isValid(cocos2d::ccColor3B value) const override; @@ -346,8 +378,12 @@ namespace geode { cocos2d::ccColor4B& getValueMut() const override; Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; + private: + class PrivateMarker {}; + friend class SettingV3; + public: - Color4BSettingV3(); + Color4BSettingV3(PrivateMarker); cocos2d::ccColor4B getDefaultValue() const override; Result<> isValid(cocos2d::ccColor4B value) const override; diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index e919dd9e..2e80637f 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -38,16 +38,16 @@ Result> SettingV3::parseBuiltin(std::string const& ke root.needs("type").into(type); std::shared_ptr ret; switch (hash(type)) { - case hash("bool"): ret = std::make_shared(); break; - case hash("int"): ret = std::make_shared(); break; - case hash("float"): ret = std::make_shared(); break; - case hash("string"): ret = std::make_shared(); break; - 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("title"): ret = std::make_shared(); break; + case hash("bool"): ret = std::make_shared(BoolSettingV3::PrivateMarker()); break; + case hash("int"): ret = std::make_shared(IntSettingV3::PrivateMarker()); break; + case hash("float"): ret = std::make_shared(FloatSettingV3::PrivateMarker()); break; + case hash("string"): ret = std::make_shared(StringSettingV3::PrivateMarker()); break; + case hash("rgb"): case hash("color"): ret = std::make_shared(Color3BSettingV3::PrivateMarker()); break; + case hash("rgba"): ret = std::make_shared(Color4BSettingV3::PrivateMarker()); break; + case hash("path"): case hash("file"): ret = std::make_shared(FileSettingV3::PrivateMarker()); break; + case hash("title"): ret = std::make_shared(TitleSettingV3::PrivateMarker()); break; default: - case hash("custom"): ret = std::make_shared(); break; + case hash("custom"): ret = std::make_shared(UnresolvedCustomSettingV3::PrivateMarker()); break; } GEODE_UNWRAP(ret->parse(key, modID, json)); return root.ok(std::move(ret)); @@ -100,7 +100,7 @@ public: std::string title; }; -TitleSettingV3::TitleSettingV3() : m_impl(std::make_shared()) {} +TitleSettingV3::TitleSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} std::string TitleSettingV3::getTitle() const { return m_impl->title; @@ -134,7 +134,7 @@ void TitleSettingV3::reset() {} // replace this comment with it // put it riiiiiight here -UnresolvedCustomSettingV3::UnresolvedCustomSettingV3() : m_impl(std::make_shared()) {} +UnresolvedCustomSettingV3::UnresolvedCustomSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} Result<> UnresolvedCustomSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) { m_impl->json = json; @@ -177,7 +177,7 @@ public: bool defaultValue; }; -BoolSettingV3::BoolSettingV3() : m_impl(std::make_shared()) {} +BoolSettingV3::BoolSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} bool& BoolSettingV3::getValueMut() const { return m_impl->value; @@ -244,7 +244,7 @@ public: } controls; }; -IntSettingV3::IntSettingV3() : m_impl(std::make_shared()) {} +IntSettingV3::IntSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} int64_t& IntSettingV3::getValueMut() const { return m_impl->value; @@ -374,7 +374,7 @@ public: } controls; }; -FloatSettingV3::FloatSettingV3() : m_impl(std::make_shared()) {} +FloatSettingV3::FloatSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} double& FloatSettingV3::getValueMut() const { return m_impl->value; @@ -496,7 +496,7 @@ public: std::optional> oneOf; }; -StringSettingV3::StringSettingV3() : m_impl(std::make_shared()) {} +StringSettingV3::StringSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} std::string& StringSettingV3::getValueMut() const { return m_impl->value; @@ -580,7 +580,7 @@ public: std::optional> filters; }; -FileSettingV3::FileSettingV3() : m_impl(std::make_shared()) {} +FileSettingV3::FileSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} std::filesystem::path& FileSettingV3::getValueMut() const { return m_impl->value; @@ -669,7 +669,7 @@ public: ccColor3B defaultValue; }; -Color3BSettingV3::Color3BSettingV3() : m_impl(std::make_shared()) {} +Color3BSettingV3::Color3BSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} ccColor3B& Color3BSettingV3::getValueMut() const { return m_impl->value; @@ -725,7 +725,7 @@ public: ccColor4B defaultValue; }; -Color4BSettingV3::Color4BSettingV3() : m_impl(std::make_shared()) {} +Color4BSettingV3::Color4BSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} ccColor4B& Color4BSettingV3::getValueMut() const { return m_impl->value; From 28cc6fdbd307a64787aa13e5dae3e9c69a9286f8 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Tue, 20 Aug 2024 12:50:20 +0300 Subject: [PATCH 06/54] move typeinfo_pointer_cast to a cross-platform header --- loader/include/Geode/platform/platform.hpp | 10 ++++++++++ loader/include/Geode/platform/windows.hpp | 7 ------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/loader/include/Geode/platform/platform.hpp b/loader/include/Geode/platform/platform.hpp index afca28da..b4c5bf71 100644 --- a/loader/include/Geode/platform/platform.hpp +++ b/loader/include/Geode/platform/platform.hpp @@ -242,3 +242,13 @@ namespace std { #elif defined(GEODE_IS_ANDROID64) #define GEODE_PLATFORM_TARGET PlatformID::Android64 #endif + +// this is cross-platform so not duplicating it across the typeinfo_cast definitions +namespace geode::cast { + 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/platform/windows.hpp b/loader/include/Geode/platform/windows.hpp index 374e0e1e..ee22e0d3 100644 --- a/loader/include/Geode/platform/windows.hpp +++ b/loader/include/Geode/platform/windows.hpp @@ -130,11 +130,4 @@ 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); - } } From 0d318ceda6352b3cc7a2e4bb5124f78a269ecc19 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Tue, 20 Aug 2024 12:56:32 +0300 Subject: [PATCH 07/54] fix typeinfo_pointer_cast usage --- loader/include/Geode/loader/Mod.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loader/include/Geode/loader/Mod.hpp b/loader/include/Geode/loader/Mod.hpp index af89e6e4..6e28a13c 100644 --- a/loader/include/Geode/loader/Mod.hpp +++ b/loader/include/Geode/loader/Mod.hpp @@ -264,7 +264,7 @@ namespace geode { template T getSettingValue(std::string_view const key) const { using S = typename SettingTypeForValueType::SettingType; - if (auto sett = typeinfo_pointer_cast(this->getSettingV3(key))) { + if (auto sett = cast::typeinfo_pointer_cast(this->getSettingV3(key))) { return sett->getValue(); } return T(); @@ -273,7 +273,7 @@ namespace geode { template T setSettingValue(std::string_view const key, T const& value) { using S = typename SettingTypeForValueType::SettingType; - if (auto sett = typeinfo_pointer_cast(this->getSettingV3(key))) { + if (auto sett = cast::typeinfo_pointer_cast(this->getSettingV3(key))) { auto old = sett->getValue(); sett->setValue(value); return old; From 07001bf36f9cd73fbbabd4f8e4fe39bf7a70063f Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Tue, 20 Aug 2024 12:56:46 +0300 Subject: [PATCH 08/54] fix warnings --- loader/include/Geode/loader/SettingV3.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index c03f0c81..42594762 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -119,7 +119,7 @@ namespace geode { bool isDefaultValue() const override { return this->getValue() == this->getDefaultValue(); } - void reset() { + void reset() override { this->setValue(this->getDefaultValue()); } }; @@ -155,8 +155,8 @@ namespace geode { class Impl; std::shared_ptr m_impl; - friend class ModSettingsManager; - friend class LegacyCustomSettingToV3Node; + friend class ::ModSettingsManager; + friend class ::LegacyCustomSettingToV3Node; protected: Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; From 71eb4a2041ef566013b3525930535ddc3198f861 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Tue, 20 Aug 2024 12:57:04 +0300 Subject: [PATCH 09/54] fix target gd version not working if it's set to * --- loader/src/loader/ModMetadataImpl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loader/src/loader/ModMetadataImpl.cpp b/loader/src/loader/ModMetadataImpl.cpp index e4029d37..c377f6b3 100644 --- a/loader/src/loader/ModMetadataImpl.cpp +++ b/loader/src/loader/ModMetadataImpl.cpp @@ -133,7 +133,7 @@ Result ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs // change all of this to the gd.needs(...) stuff gd.assertIs({ matjson::Type::Object, matjson::Type::String }); if (gd.isObject()) { - gd.needs(GEODE_PLATFORM_SHORT_IDENTIFIER) + gd.needs(GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH) .mustBe("a valid gd version", [](auto const& str) { return str == "*" || numFromString(str).isOk(); }) @@ -544,7 +544,7 @@ VersionInfo ModMetadata::getGeodeVersion() const { return m_impl->m_geodeVersion; } Result<> ModMetadata::checkGameVersion() const { - if (!m_impl->m_gdVersion.empty()) { + if (!m_impl->m_gdVersion.empty() && m_impl->m_gdVersion != "*") { auto const ver = m_impl->m_gdVersion; auto res = numFromString(ver); From a5f56cb7cb36a0657f9906b2a0a01261d5132eb1 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Tue, 20 Aug 2024 13:18:59 +0300 Subject: [PATCH 10/54] a bit of docs on modsettingsmanager --- loader/src/loader/ModSettingsManager.cpp | 2 ++ loader/src/loader/ModSettingsManager.hpp | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/loader/src/loader/ModSettingsManager.cpp b/loader/src/loader/ModSettingsManager.cpp index 6c68b12a..6d43ca58 100644 --- a/loader/src/loader/ModSettingsManager.cpp +++ b/loader/src/loader/ModSettingsManager.cpp @@ -30,6 +30,8 @@ ModSettingsManager::ModSettingsManager(ModMetadata const& metadata) } ModSettingsManager::~ModSettingsManager() {} +ModSettingsManager::ModSettingsManager(ModSettingsManager&&) = default; + Result<> ModSettingsManager::registerCustomSetting(std::string_view key, std::shared_ptr ptr) { if (!ptr) { return Err("Custom settings must not be null!"); diff --git a/loader/src/loader/ModSettingsManager.hpp b/loader/src/loader/ModSettingsManager.hpp index 5f78c340..a1593f91 100644 --- a/loader/src/loader/ModSettingsManager.hpp +++ b/loader/src/loader/ModSettingsManager.hpp @@ -14,10 +14,28 @@ public: ModSettingsManager(ModMetadata const& metadata); ~ModSettingsManager(); + ModSettingsManager(ModSettingsManager&&); + ModSettingsManager(ModSettingsManager const&) = delete; + + /** + * Load setting values from savedata. + * The format of the savedata should be an object with the keys being + * setting IDs and then the values the values of the saved settings + * @returns Ok if no horrible errors happened. Note that a setting value + * missing is not considered a horrible error, but will instead just log a + * warning into the console! + */ Result<> load(matjson::Value const& json); + /** + * Save setting values to savedata. + * The format of the savedata will be an object with the keys being + * setting IDs and then the values the values of the saved settings + * @note If saving a setting fails, it will log a warning to the console + */ void save(matjson::Value& json); Result<> registerCustomSetting(std::string_view key, std::shared_ptr ptr); + // remove in v4 Result<> registerLegacyCustomSetting(std::string_view key, std::unique_ptr&& ptr); std::shared_ptr get(std::string_view key); From 40a28eec7ba9d427a7ca896a3906462c0d688822 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Wed, 21 Aug 2024 21:41:44 +0300 Subject: [PATCH 11/54] make custom settings be based on custom setting types --- loader/include/Geode/loader/Mod.hpp | 10 +- .../Geode/loader/ModSettingsManager.hpp | 46 ++ loader/include/Geode/loader/SettingV3.hpp | 99 ++--- loader/include/Geode/utils/JsonValidation.hpp | 15 +- loader/src/loader/Mod.cpp | 4 +- loader/src/loader/ModImpl.hpp | 2 +- loader/src/loader/ModSettingsManager.cpp | 164 +++++-- loader/src/loader/ModSettingsManager.hpp | 44 -- loader/src/loader/SettingNodeV3.cpp | 15 +- loader/src/loader/SettingNodeV3.hpp | 10 +- loader/src/loader/SettingV3.cpp | 414 +++++++++--------- loader/src/loader/SettingV3Impl.hpp | 18 - 12 files changed, 462 insertions(+), 379 deletions(-) create mode 100644 loader/include/Geode/loader/ModSettingsManager.hpp delete mode 100644 loader/src/loader/ModSettingsManager.hpp delete mode 100644 loader/src/loader/SettingV3Impl.hpp diff --git a/loader/include/Geode/loader/Mod.hpp b/loader/include/Geode/loader/Mod.hpp index 6e28a13c..a5f19187 100644 --- a/loader/include/Geode/loader/Mod.hpp +++ b/loader/include/Geode/loader/Mod.hpp @@ -191,11 +191,6 @@ 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 @@ -213,6 +208,11 @@ namespace geode { this->registerCustomSetting(key, std::make_unique(std::string(key), this->getID(), value)); } + /** + * Register a custom setting type + */ + Result<> registerCustomSettingType(std::string_view type, SettingGenerator generator); + /** * Returns a prefixed launch argument name. See `Mod::getLaunchArgument` * for details about mod-specific launch arguments. diff --git a/loader/include/Geode/loader/ModSettingsManager.hpp b/loader/include/Geode/loader/ModSettingsManager.hpp new file mode 100644 index 00000000..36b8ac34 --- /dev/null +++ b/loader/include/Geode/loader/ModSettingsManager.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include "SettingV3.hpp" + +namespace geode { + class GEODE_DLL ModSettingsManager final { + private: + class Impl; + std::unique_ptr m_impl; + + public: + static ModSettingsManager* from(Mod* mod); + + ModSettingsManager(ModMetadata const& metadata); + ~ModSettingsManager(); + + ModSettingsManager(ModSettingsManager&&); + ModSettingsManager(ModSettingsManager const&) = delete; + + /** + * Load setting values from savedata. + * The format of the savedata should be an object with the keys being + * setting IDs and then the values the values of the saved settings + * @returns Ok if no horrible errors happened. Note that a setting value + * missing is not considered a horrible error, but will instead just log a + * warning into the console! + */ + Result<> load(matjson::Value const& json); + /** + * Save setting values to savedata. + * The format of the savedata will be an object with the keys being + * setting IDs and then the values the values of the saved settings + * @note If saving a setting fails, it will log a warning to the console + */ + void save(matjson::Value& json); + + Result<> registerCustomSettingType(std::string_view type, SettingGenerator generator); + // todo in v4: remove this + 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/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index 42594762..83ff851f 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -9,22 +9,22 @@ // this unfortunately has to be included because of C++ templates #include "../utils/JsonValidation.hpp" -// todo in v4: these can be removed as well as the friend decl in UnresolvedCustomSettingV3 -class ModSettingsManager; +// todo in v4: these can be removed as well as the friend decl in LegacyCustomSettingV3 class LegacyCustomSettingToV3Node; namespace geode { + class ModSettingsManager; class SettingNodeV3; 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; + void init(std::string const& key, std::string const& modID); + Result<> parseSharedProperties(std::string const& key, std::string const& modID, matjson::Value const& value); + void parseSharedProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value); public: SettingV3(); @@ -43,8 +43,23 @@ namespace geode { * while the mod is still being initialized */ Mod* getMod() const; + /** + * Get the name of this setting + */ + std::string getName() const; + /** + * Get the description of this setting + */ + std::optional getDescription() const; + /** + * Get the "enable-if" scheme for this setting + */ + std::optional getEnableIf() const; + /** + * Whether this setting requires a restart on change + */ + bool requiresRestart() const; - 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; @@ -59,26 +74,22 @@ namespace geode { virtual std::optional convertToLegacy() const; [[deprecated("This function will be removed alongside legacy settings in 4.0.0!")]] virtual std::optional> convertToLegacyValue() const; - - static Result> parseBuiltin( - std::string const& key, std::string const& modID, matjson::Value const& json - ); }; + + using SettingGenerator = std::function>( + std::string const& key, + std::string const& modID, + matjson::Value const& json + )>; namespace detail { - class GEODE_DLL GeodeSettingBaseV3 : public SettingV3 { - private: - class Impl; - std::shared_ptr m_impl; - - Result<> parseSharedBase(JsonExpectedValue& json); - + template + class GeodeSettingBaseValueV3 : public SettingV3 { protected: - Result<> isValidShared() const; + virtual T& getValueMut() const = 0; template - Result<> parseShared(JsonExpectedValue& json, T& defaultValue) { - GEODE_UNWRAP(this->parseSharedBase(json)); + void parseDefaultValue(JsonExpectedValue& json, T& defaultValue) { auto value = json.needs("default"); // Check if this is a platform-specific default value if (value.isObject() && value.has(GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH)) { @@ -87,22 +98,8 @@ namespace geode { else { value.into(defaultValue); } - return Ok(); } - public: - GeodeSettingBaseV3(); - - std::string getName() const; - std::optional getDescription() const; - std::optional getEnableIf() const; - }; - - template - class GeodeSettingBaseValueV3 : public GeodeSettingBaseV3 { - protected: - virtual T& getValueMut() const = 0; - public: using ValueType = T; @@ -130,15 +127,13 @@ 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; - private: class PrivateMarker {}; friend class SettingV3; public: TitleSettingV3(PrivateMarker); + static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); std::string getTitle() const; @@ -150,23 +145,25 @@ namespace geode { void reset() override; }; - class GEODE_DLL UnresolvedCustomSettingV3 final : public SettingV3 { + // todo in v4: remove this class completely + class GEODE_DLL LegacyCustomSettingV3 final : public SettingV3 { private: class Impl; std::shared_ptr m_impl; - friend class ::ModSettingsManager; + friend class ::geode::ModSettingsManager; friend class ::LegacyCustomSettingToV3Node; - protected: - Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; - private: class PrivateMarker {}; friend class SettingV3; public: - UnresolvedCustomSettingV3(PrivateMarker); + LegacyCustomSettingV3(PrivateMarker); + static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); + + std::shared_ptr getValue() const; + void setValue(std::shared_ptr value); bool load(matjson::Value const& json) override; bool save(matjson::Value& json) const override; @@ -186,7 +183,6 @@ namespace geode { protected: bool& getValueMut() const override; - Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; private: class PrivateMarker {}; @@ -194,6 +190,7 @@ namespace geode { public: BoolSettingV3(PrivateMarker); + static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); bool getDefaultValue() const override; Result<> isValid(bool value) const override; @@ -213,7 +210,6 @@ namespace geode { protected: int64_t& getValueMut() const override; - Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; private: class PrivateMarker {}; @@ -221,6 +217,7 @@ namespace geode { public: IntSettingV3(PrivateMarker); + static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); int64_t getDefaultValue() const override; Result<> isValid(int64_t value) const override; @@ -251,7 +248,6 @@ namespace geode { protected: double& getValueMut() const override; - Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; private: class PrivateMarker {}; @@ -259,6 +255,7 @@ namespace geode { public: FloatSettingV3(PrivateMarker); + static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); double getDefaultValue() const override; Result<> isValid(double value) const override; @@ -289,7 +286,6 @@ namespace geode { protected: std::string& getValueMut() const override; - Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; private: class PrivateMarker {}; @@ -297,6 +293,7 @@ namespace geode { public: StringSettingV3(PrivateMarker); + static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); std::string getDefaultValue() const override; Result<> isValid(std::string_view value) const override; @@ -320,7 +317,6 @@ namespace geode { protected: std::filesystem::path& getValueMut() const override; - Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; private: class PrivateMarker {}; @@ -328,6 +324,7 @@ namespace geode { public: FileSettingV3(PrivateMarker); + static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); std::filesystem::path getDefaultValue() const override; Result<> isValid(std::filesystem::path const& value) const override; @@ -349,7 +346,6 @@ namespace geode { protected: cocos2d::ccColor3B& getValueMut() const override; - Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; private: class PrivateMarker {}; @@ -357,6 +353,7 @@ namespace geode { public: Color3BSettingV3(PrivateMarker); + static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); cocos2d::ccColor3B getDefaultValue() const override; Result<> isValid(cocos2d::ccColor3B value) const override; @@ -376,7 +373,6 @@ namespace geode { protected: cocos2d::ccColor4B& getValueMut() const override; - Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override; private: class PrivateMarker {}; @@ -384,6 +380,7 @@ namespace geode { public: Color4BSettingV3(PrivateMarker); + static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); cocos2d::ccColor4B getDefaultValue() const override; Result<> isValid(cocos2d::ccColor4B value) const override; diff --git a/loader/include/Geode/utils/JsonValidation.hpp b/loader/include/Geode/utils/JsonValidation.hpp index 3f0d6ef8..b17594a0 100644 --- a/loader/include/Geode/utils/JsonValidation.hpp +++ b/loader/include/Geode/utils/JsonValidation.hpp @@ -318,12 +318,17 @@ namespace geode { template std::optional tryGet() { if (this->hasError()) return std::nullopt; - try { - return this->getJSONRef().template as(); + if constexpr (std::is_same_v) { + return this->getJSONRef(); } - // matjson can throw variant exceptions too so you need to do this - catch(std::exception const& e) { - this->setError("invalid json type: {}", e); + else { + try { + return this->getJSONRef().template as(); + } + // matjson can throw variant exceptions too so you need to do this + catch(std::exception const& e) { + this->setError("invalid json type: {}", e); + } } return std::nullopt; } diff --git a/loader/src/loader/Mod.cpp b/loader/src/loader/Mod.cpp index fa54fb97..a4b71515 100644 --- a/loader/src/loader/Mod.cpp +++ b/loader/src/loader/Mod.cpp @@ -173,8 +173,8 @@ void Mod::registerCustomSetting(std::string_view const key, std::unique_ptr Mod::registerCustomSettingV3(std::string_view const key, std::shared_ptr value) { - return m_impl->m_settings->registerCustomSetting(key, value); +Result<> Mod::registerCustomSettingType(std::string_view type, SettingGenerator generator) { + return m_impl->m_settings->registerCustomSettingType(type, generator); } std::vector Mod::getLaunchArgumentNames() const { diff --git a/loader/src/loader/ModImpl.hpp b/loader/src/loader/ModImpl.hpp index 832060be..f33bc0ad 100644 --- a/loader/src/loader/ModImpl.hpp +++ b/loader/src/loader/ModImpl.hpp @@ -4,7 +4,7 @@ #include "ModPatch.hpp" #include #include -#include "ModSettingsManager.hpp" +#include namespace geode { class Mod::Impl { diff --git a/loader/src/loader/ModSettingsManager.cpp b/loader/src/loader/ModSettingsManager.cpp index 6d43ca58..b64fc3a6 100644 --- a/loader/src/loader/ModSettingsManager.cpp +++ b/loader/src/loader/ModSettingsManager.cpp @@ -1,65 +1,161 @@ -#include "ModSettingsManager.hpp" -#include "SettingV3Impl.hpp" +#include #include +#include "ModImpl.hpp" + +using namespace geode::prelude; + +// All setting type generators are put in a shared pool for two reasons: +// #1 no need to duplicate the built-in settings between all mods +// #2 easier lookup of custom settings if a mod uses another mod's custom setting type + +class SharedSettingTypesPool final { +private: + std::unordered_map m_types; + + SharedSettingTypesPool() : m_types({ + // todo in v4: remove this + { "custom", &LegacyCustomSettingV3::parse }, + { "title", &TitleSettingV3::parse }, + { "bool", &BoolSettingV3::parse }, + { "int", &IntSettingV3::parse }, + { "float", &FloatSettingV3::parse }, + { "string", &StringSettingV3::parse }, + { "file", &FileSettingV3::parse }, + { "path", &FileSettingV3::parse }, + { "rgb", &Color3BSettingV3::parse }, + { "color", &Color3BSettingV3::parse }, + { "rgba", &Color4BSettingV3::parse }, + }) {} + +public: + static SharedSettingTypesPool& get() { + static auto inst = SharedSettingTypesPool(); + return inst; + } + + Result<> add(std::string_view modID, std::string_view type, SettingGenerator generator) { + // Limit type to just [a-z0-9\-]+ + if (type.empty() || !std::all_of(type.begin(), type.end(), +[](char c) { + return + ('a' <= c && c <= 'z') || + ('0' <= c && c <= '9') || + (c == '-'); + })) { + return Err("Custom setting types must match the regex [a-z0-9\\-]+"); + } + auto full = fmt::format("{}/{}", modID, type); + if (m_types.contains(full)) { + return Err("Type \"{}\" has already been registered for mod {}", type, modID); + } + m_types.emplace(full, generator); + return Ok(); + } + std::optional find(std::string_view modID, std::string_view fullType) { + auto full = std::string( + fullType.starts_with("custom:") ? + fullType.substr(fullType.find(':') + 1) : + fullType + ); + if (!full.find('/')) { + full = fmt::format("{}/{}", modID, full); + } + if (m_types.contains(full)) { + return m_types.at(full); + } + return std::nullopt; + } +}; class ModSettingsManager::Impl final { public: struct SettingInfo final { - std::shared_ptr v3; + std::string type; + matjson::Value json; + std::shared_ptr v3 = nullptr; // todo: remove in v4 std::shared_ptr legacy = nullptr; }; std::string modID; - std::unordered_map list; + std::unordered_map settings; + + void createSettings() { + for (auto& [key, setting] : settings) { + if (setting.v3) { + continue; + } + auto gen = SharedSettingTypesPool::get().find(modID, setting.type); + // The type was not found, meaning it probably hasn't been registered yet + if (!gen) { + continue; + } + if (auto v3 = (*gen)(key, modID, setting.json)) { + setting.v3 = *v3; + } + else { + log::error( + "Unable to parse setting '{}' for mod {}: {}", + key, modID, v3.unwrapErr() + ); + } + } + } }; +ModSettingsManager* ModSettingsManager::from(Mod* mod) { + return ModImpl::getImpl(mod)->m_settings.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); + auto setting = Impl::SettingInfo(); + setting.json = json; + auto root = checkJson(json, "setting"); + root.needs("type").into(setting.type); + if (root) { + if (setting.type == "custom") { + log::warn( + "Setting \"{}\" in mod {} has the old \"custom\" type - " + "this type has been deprecated and will be removed in Geode v4.0.0. " + "Use the new \"custom:type-name-here\" syntax for defining custom " + "setting types - see more in INSERT TUTORIAL HERE", + key, m_impl->modID + ); + } + m_impl->settings.emplace(key, setting); } else { - log::error("Unable to parse setting '{}' for mod {}: {}", key, m_impl->modID, v3.unwrapErr()); + log::error("Setting '{}' in mod {} is missing type", key, m_impl->modID); } } + m_impl->createSettings(); } ModSettingsManager::~ModSettingsManager() {} - ModSettingsManager::ModSettingsManager(ModSettingsManager&&) = default; -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); +Result<> ModSettingsManager::registerCustomSettingType(std::string_view type, SettingGenerator generator) { + GEODE_UNWRAP(SharedSettingTypesPool::get().add(m_impl->modID, type, generator)); + m_impl->createSettings(); return Ok(); } Result<> ModSettingsManager::registerLegacyCustomSetting(std::string_view key, std::unique_ptr&& ptr) { auto id = std::string(key); - if (!m_impl->list.count(id)) { + if (!m_impl->settings.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); + auto& sett = m_impl->settings.at(id); + if (auto custom = typeinfo_pointer_cast(sett.v3)) { + if (!custom->getValue()) { + custom->setValue(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 Err("Setting '{}' in mod {} is not a legacy custom setting", id, m_impl->modID); } return Ok(); } @@ -67,9 +163,9 @@ Result<> ModSettingsManager::registerLegacyCustomSetting(std::string_view key, s 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)) { + if (m_impl->settings.contains(key)) { try { - if (!m_impl->list.at(key).v3->load(value.json())) { + if (!m_impl->settings.at(key).v3->load(value.json())) { log::error("Unable to load setting '{}' for mod {}", key, m_impl->modID); } } @@ -81,7 +177,7 @@ Result<> ModSettingsManager::load(matjson::Value const& json) { return Ok(); } void ModSettingsManager::save(matjson::Value& json) { - for (auto& [key, sett] : m_impl->list) { + for (auto& [key, sett] : m_impl->settings) { // Store the value in an intermediary so if `save` fails the existing // value loaded from disk isn't overwritten matjson::Value value; @@ -101,18 +197,22 @@ void ModSettingsManager::save(matjson::Value& json) { 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; + return m_impl->settings.count(id) ? m_impl->settings.at(id).v3 : nullptr; } std::shared_ptr ModSettingsManager::getLegacy(std::string_view key) { auto id = std::string(key); - if (!m_impl->list.count(id)) { + if (!m_impl->settings.count(id)) { return nullptr; } - auto& info = m_impl->list.at(id); + auto& info = m_impl->settings.at(id); // If this setting has alreay been given a legacy interface, give that if (info.legacy) { return info.legacy; } + // Uninitialized settings are null + if (!info.v3) { + return nullptr; + } // Generate new legacy interface if (auto legacy = info.v3->convertToLegacyValue()) { info.legacy.swap(*legacy); diff --git a/loader/src/loader/ModSettingsManager.hpp b/loader/src/loader/ModSettingsManager.hpp deleted file mode 100644 index a1593f91..00000000 --- a/loader/src/loader/ModSettingsManager.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include -#include - -using namespace geode::prelude; - -class ModSettingsManager final { -private: - class Impl; - std::unique_ptr m_impl; - -public: - ModSettingsManager(ModMetadata const& metadata); - ~ModSettingsManager(); - - ModSettingsManager(ModSettingsManager&&); - ModSettingsManager(ModSettingsManager const&) = delete; - - /** - * Load setting values from savedata. - * The format of the savedata should be an object with the keys being - * setting IDs and then the values the values of the saved settings - * @returns Ok if no horrible errors happened. Note that a setting value - * missing is not considered a horrible error, but will instead just log a - * warning into the console! - */ - Result<> load(matjson::Value const& json); - /** - * Save setting values to savedata. - * The format of the savedata will be an object with the keys being - * setting IDs and then the values the values of the saved settings - * @note If saving a setting fails, it will log a warning to the console - */ - void save(matjson::Value& json); - - Result<> registerCustomSetting(std::string_view key, std::shared_ptr ptr); - // remove in v4 - 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/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index 3bdd4d8d..339eb74d 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -1,5 +1,4 @@ #include "SettingNodeV3.hpp" -#include "SettingV3Impl.hpp" #include class SettingNodeSizeChangeEventV3::Impl final { @@ -367,7 +366,7 @@ std::shared_ptr Color4BSettingNodeV3::getSetting() const { // UnresolvedCustomSettingNodeV3 -bool UnresolvedCustomSettingNodeV3::init(std::shared_ptr setting, float width) { +bool UnresolvedCustomSettingNodeV3::init(std::shared_ptr setting, float width) { if (!SettingNodeV3::init(setting, width)) return false; @@ -385,7 +384,7 @@ bool UnresolvedCustomSettingNodeV3::init(std::shared_ptr setting, float width) { +UnresolvedCustomSettingNodeV3* UnresolvedCustomSettingNodeV3::create(std::shared_ptr setting, float width) { auto ret = new UnresolvedCustomSettingNodeV3(); if (ret && ret->init(setting, width)) { ret->autorelease(); @@ -403,17 +402,17 @@ bool UnresolvedCustomSettingNodeV3::hasNonDefaultValue() const { } void UnresolvedCustomSettingNodeV3::resetToDefault() {} -std::shared_ptr UnresolvedCustomSettingNodeV3::getSetting() const { - return std::static_pointer_cast(SettingNodeV3::getSetting()); +std::shared_ptr UnresolvedCustomSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); } // LegacyCustomSettingToV3Node -bool LegacyCustomSettingToV3Node::init(std::shared_ptr original, float width) { +bool LegacyCustomSettingToV3Node::init(std::shared_ptr original, float width) { if (!SettingNodeV3::init(original, width)) return false; - m_original = original->m_impl->legacyValue->createNode(width); + m_original = original->getValue()->createNode(width); this->setContentSize({ width, m_original->getContentHeight() }); this->addChildAtPosition(m_original, Anchor::Center); @@ -424,7 +423,7 @@ void LegacyCustomSettingToV3Node::onCommit() { m_original->commit(); } -LegacyCustomSettingToV3Node* LegacyCustomSettingToV3Node::create(std::shared_ptr original, float width) { +LegacyCustomSettingToV3Node* LegacyCustomSettingToV3Node::create(std::shared_ptr original, float width) { auto ret = new LegacyCustomSettingToV3Node(); if (ret && ret->init(original, width)) { ret->autorelease(); diff --git a/loader/src/loader/SettingNodeV3.hpp b/loader/src/loader/SettingNodeV3.hpp index 0c2feefe..f1faa862 100644 --- a/loader/src/loader/SettingNodeV3.hpp +++ b/loader/src/loader/SettingNodeV3.hpp @@ -141,18 +141,18 @@ public: class UnresolvedCustomSettingNodeV3 : public SettingNodeV3 { protected: - bool init(std::shared_ptr setting, float width); + bool init(std::shared_ptr setting, float width); void onCommit() override; public: - static UnresolvedCustomSettingNodeV3* create(std::shared_ptr setting, float width); + 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; + std::shared_ptr getSetting() const; }; // If these classes do get exposed in headers, this SHOULD NOT BE EXPOSED!!!!!! DO NOT DO THAT!!!! @@ -161,12 +161,12 @@ class LegacyCustomSettingToV3Node : public SettingNodeV3 { protected: SettingNode* m_original; - bool init(std::shared_ptr original, float width); + bool init(std::shared_ptr original, float width); void onCommit() override; public: - static LegacyCustomSettingToV3Node* create(std::shared_ptr original, float width); + static LegacyCustomSettingToV3Node* create(std::shared_ptr original, float width); bool hasUncommittedChanges() const override; bool hasNonDefaultValue() const override; diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index 2e80637f..e7e8039a 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -1,7 +1,6 @@ #include #include #include -#include "SettingV3Impl.hpp" #include "SettingNodeV3.hpp" using namespace geode::prelude; @@ -10,11 +9,32 @@ class SettingV3::GeodeImpl { public: std::string modID; std::string key; + std::optional name; + std::optional description; + std::optional enableIf; + bool requiresRestart = false; }; +SettingV3::SettingV3() : m_impl(std::make_shared()) {} SettingV3::~SettingV3() = default; -SettingV3::SettingV3() : m_impl(std::make_shared()) {} +Result<> SettingV3::parseSharedProperties(std::string const& key, std::string const& modID, matjson::Value const& value) { + auto json = checkJson(value, "SettingV3"); + this->parseSharedProperties(key, modID, json); + return json.ok(); +} +void SettingV3::parseSharedProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value) { + this->init(key, modID); + value.needs("type"); + value.has("name").into(m_impl->name); + value.has("description").into(m_impl->description); + value.has("enable-if").into(m_impl->enableIf); + value.has("requires-restart").into(m_impl->requiresRestart); +} +void SettingV3::init(std::string const& key, std::string const& modID) { + m_impl->key = key; + m_impl->modID = modID; +} std::string SettingV3::getKey() const { return m_impl->key; @@ -22,37 +42,22 @@ std::string SettingV3::getKey() const { std::string SettingV3::getModID() const { return m_impl->modID; } +std::string SettingV3::getName() const { + return m_impl->name.value_or(m_impl->key); +} +std::optional SettingV3::getDescription() const { + return m_impl->description; +} +std::optional SettingV3::getEnableIf() const { + return m_impl->enableIf; +} +bool SettingV3::requiresRestart() const { + return m_impl->requiresRestart; +} Mod* SettingV3::getMod() const { return Loader::get()->getInstalledMod(m_impl->modID); } -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); - std::shared_ptr ret; - switch (hash(type)) { - case hash("bool"): ret = std::make_shared(BoolSettingV3::PrivateMarker()); break; - case hash("int"): ret = std::make_shared(IntSettingV3::PrivateMarker()); break; - case hash("float"): ret = std::make_shared(FloatSettingV3::PrivateMarker()); break; - case hash("string"): ret = std::make_shared(StringSettingV3::PrivateMarker()); break; - case hash("rgb"): case hash("color"): ret = std::make_shared(Color3BSettingV3::PrivateMarker()); break; - case hash("rgba"): ret = std::make_shared(Color4BSettingV3::PrivateMarker()); break; - case hash("path"): case hash("file"): ret = std::make_shared(FileSettingV3::PrivateMarker()); break; - case hash("title"): ret = std::make_shared(TitleSettingV3::PrivateMarker()); break; - default: - case hash("custom"): ret = std::make_shared(UnresolvedCustomSettingV3::PrivateMarker()); break; - } - GEODE_UNWRAP(ret->parse(key, modID, json)); - return root.ok(std::move(ret)); -} - std::optional SettingV3::convertToLegacy() const { return std::nullopt; } @@ -60,41 +65,6 @@ std::optional> SettingV3::convertToLegacyValue() c return std::nullopt; } -class geode::detail::GeodeSettingBaseV3::Impl final { -public: - std::optional name; - std::optional description; - std::optional enableIf; -}; - -geode::detail::GeodeSettingBaseV3::GeodeSettingBaseV3() : m_impl(std::make_shared()) {} - -std::string geode::detail::GeodeSettingBaseV3::getName() const { - return m_impl->name.value_or(this->getKey()); -} -std::optional geode::detail::GeodeSettingBaseV3::getDescription() const { - return m_impl->description; -} -std::optional geode::detail::GeodeSettingBaseV3::getEnableIf() const { - return m_impl->enableIf; -} - -Result<> geode::detail::GeodeSettingBaseV3::parseSharedBase(JsonExpectedValue& json) { - // Mark keys that have been checked before-hand - json.needs("type"); - json.has("platforms"); - - 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 { - // In the future if something like `enable-if` preventing - // programmatic modification of settings it should be added here - return Ok(); -} - class TitleSettingV3::Impl final { public: std::string title; @@ -102,16 +72,19 @@ public: TitleSettingV3::TitleSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} +Result> TitleSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) { + auto ret = std::make_shared(PrivateMarker()); + auto root = checkJson(json, "TitleSettingV3"); + ret->init(key, modID); + root.needs("title").into(ret->m_impl->title); + root.checkUnknownKeys(); + return root.ok(ret); +} + std::string TitleSettingV3::getTitle() const { return m_impl->title; } -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(); - return root.ok(); -} bool TitleSettingV3::load(matjson::Value const& json) { return true; } @@ -128,46 +101,56 @@ bool TitleSettingV3::isDefaultValue() const { } void TitleSettingV3::reset() {} -// 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 +class LegacyCustomSettingV3::Impl final { +public: + matjson::Value json; + std::shared_ptr legacyValue = nullptr; +}; -UnresolvedCustomSettingV3::UnresolvedCustomSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} +LegacyCustomSettingV3::LegacyCustomSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} -Result<> UnresolvedCustomSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) { - m_impl->json = json; - return Ok(); +Result> LegacyCustomSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) { + auto ret = std::make_shared(PrivateMarker()); + ret->init(key, modID); + ret->m_impl->json = json; + return Ok(ret); } -bool UnresolvedCustomSettingV3::load(matjson::Value const& json) { + +std::shared_ptr LegacyCustomSettingV3::getValue() const { + return m_impl->legacyValue; +} +void LegacyCustomSettingV3::setValue(std::shared_ptr value) { + m_impl->legacyValue = value; +} + +bool LegacyCustomSettingV3::load(matjson::Value const& json) { return true; } -bool UnresolvedCustomSettingV3::save(matjson::Value& json) const { +bool LegacyCustomSettingV3::save(matjson::Value& json) const { return true; } -SettingNodeV3* UnresolvedCustomSettingV3::createNode(float width) { +SettingNodeV3* LegacyCustomSettingV3::createNode(float width) { if (m_impl->legacyValue) { return LegacyCustomSettingToV3Node::create( - std::static_pointer_cast(shared_from_this()), width + std::static_pointer_cast(shared_from_this()), width ); } return UnresolvedCustomSettingNodeV3::create( - std::static_pointer_cast(shared_from_this()), width + std::static_pointer_cast(shared_from_this()), width ); } -bool UnresolvedCustomSettingV3::isDefaultValue() const { +bool LegacyCustomSettingV3::isDefaultValue() const { return true; } -void UnresolvedCustomSettingV3::reset() {} +void LegacyCustomSettingV3::reset() {} -std::optional UnresolvedCustomSettingV3::convertToLegacy() const { +std::optional LegacyCustomSettingV3::convertToLegacy() const { return Setting(this->getKey(), this->getModID(), SettingKind(CustomSetting { .json = std::make_shared(m_impl->json) })); } -std::optional> UnresolvedCustomSettingV3::convertToLegacyValue() const { +std::optional> LegacyCustomSettingV3::convertToLegacyValue() const { return m_impl->legacyValue ? std::optional(m_impl->legacyValue) : std::nullopt; } @@ -179,6 +162,18 @@ public: BoolSettingV3::BoolSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} +Result> BoolSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) { + auto ret = std::make_shared(PrivateMarker()); + + auto root = checkJson(json, "BoolSettingV3"); + ret->parseSharedProperties(key, modID, root); + ret->parseDefaultValue(root, ret->m_impl->defaultValue); + ret->m_impl->value = ret->m_impl->defaultValue; + + root.checkUnknownKeys(); + return root.ok(ret); +} + bool& BoolSettingV3::getValueMut() const { return m_impl->value; } @@ -186,19 +181,9 @@ bool BoolSettingV3::getDefaultValue() const { return m_impl->defaultValue; } Result<> BoolSettingV3::isValid(bool value) const { - GEODE_UNWRAP(this->isValidShared()); return Ok(); } -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, m_impl->defaultValue)); - m_impl->value = m_impl->defaultValue; - - root.checkUnknownKeys(); - return root.ok(); -} bool BoolSettingV3::load(matjson::Value const& json) { if (json.is_bool()) { m_impl->value = json.as_bool(); @@ -244,6 +229,35 @@ public: } controls; }; +Result> IntSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) { + auto ret = std::make_shared(PrivateMarker()); + + auto root = checkJson(json, "IntSettingV3"); + ret->parseSharedProperties(key, modID, root); + ret->parseDefaultValue(root, ret->m_impl->defaultValue); + ret->m_impl->value = ret->m_impl->defaultValue; + + root.has("min").into(ret->m_impl->minValue); + root.has("max").into(ret->m_impl->maxValue); + if (auto controls = root.has("control")) { + controls.has("arrow-step").into(ret->m_impl->controls.arrowStepSize); + if (!controls.has("arrows").template get()) { + ret->m_impl->controls.arrowStepSize = 0; + } + controls.has("big-arrow-step").into(ret->m_impl->controls.bigArrowStepSize); + if (!controls.has("big-arrows").template get()) { + ret->m_impl->controls.bigArrowStepSize = 0; + } + controls.has("slider").into(ret->m_impl->controls.sliderEnabled); + controls.has("slider-step").into(ret->m_impl->controls.sliderSnap); + controls.has("input").into(ret->m_impl->controls.textInputEnabled); + controls.checkUnknownKeys(); + } + + root.checkUnknownKeys(); + return root.ok(ret); +} + IntSettingV3::IntSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} int64_t& IntSettingV3::getValueMut() const { @@ -253,7 +267,6 @@ 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); } @@ -292,32 +305,6 @@ bool IntSettingV3::isInputEnabled() const { return m_impl->controls.textInputEnabled; } -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, m_impl->defaultValue)); - m_impl->value = m_impl->defaultValue; - - root.has("min").into(m_impl->minValue); - root.has("max").into(m_impl->maxValue); - if (auto controls = root.has("control")) { - controls.has("arrow-step").into(m_impl->controls.arrowStepSize); - if (!controls.has("arrows").template get()) { - m_impl->controls.arrowStepSize = 0; - } - controls.has("big-arrow-step").into(m_impl->controls.bigArrowStepSize); - if (!controls.has("big-arrows").template get()) { - m_impl->controls.bigArrowStepSize = 0; - } - controls.has("slider").into(m_impl->controls.sliderEnabled); - controls.has("slider-step").into(m_impl->controls.sliderSnap); - controls.has("input").into(m_impl->controls.textInputEnabled); - controls.checkUnknownKeys(); - } - - root.checkUnknownKeys(); - return root.ok(); -} bool IntSettingV3::load(matjson::Value const& json) { if (json.is_number()) { m_impl->value = json.as_int(); @@ -376,6 +363,35 @@ public: FloatSettingV3::FloatSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} +Result> FloatSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) { + auto ret = std::make_shared(PrivateMarker()); + + auto root = checkJson(json, "FloatSettingV3"); + ret->parseSharedProperties(key, modID, root); + ret->parseDefaultValue(root, ret->m_impl->defaultValue); + ret->m_impl->value = ret->m_impl->defaultValue; + + root.has("min").into(ret->m_impl->minValue); + root.has("max").into(ret->m_impl->maxValue); + if (auto controls = root.has("control")) { + controls.has("arrow-step").into(ret->m_impl->controls.arrowStepSize); + if (!controls.has("arrows").template get()) { + ret->m_impl->controls.arrowStepSize = 0; + } + controls.has("big-arrow-step").into(ret->m_impl->controls.bigArrowStepSize); + if (!controls.has("big-arrows").template get()) { + ret->m_impl->controls.bigArrowStepSize = 0; + } + controls.has("slider").into(ret->m_impl->controls.sliderEnabled); + controls.has("slider-step").into(ret->m_impl->controls.sliderSnap); + controls.has("input").into(ret->m_impl->controls.textInputEnabled); + controls.checkUnknownKeys(); + } + + root.checkUnknownKeys(); + return root.ok(ret); +} + double& FloatSettingV3::getValueMut() const { return m_impl->value; } @@ -383,7 +399,6 @@ 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); } @@ -422,32 +437,6 @@ bool FloatSettingV3::isInputEnabled() const { return m_impl->controls.textInputEnabled; } -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, m_impl->defaultValue)); - m_impl->value = m_impl->defaultValue; - - root.has("min").into(m_impl->minValue); - root.has("max").into(m_impl->maxValue); - if (auto controls = root.has("control")) { - controls.has("arrow-step").into(m_impl->controls.arrowStepSize); - if (!controls.has("arrows").template get()) { - m_impl->controls.arrowStepSize = 0; - } - controls.has("big-arrow-step").into(m_impl->controls.bigArrowStepSize); - if (!controls.has("big-arrows").template get()) { - m_impl->controls.bigArrowStepSize = 0; - } - controls.has("slider").into(m_impl->controls.sliderEnabled); - controls.has("slider-step").into(m_impl->controls.sliderSnap); - controls.has("input").into(m_impl->controls.textInputEnabled); - controls.checkUnknownKeys(); - } - - root.checkUnknownKeys(); - return root.ok(); -} bool FloatSettingV3::load(matjson::Value const& json) { if (json.is_number()) { m_impl->value = json.as_double(); @@ -498,6 +487,22 @@ public: StringSettingV3::StringSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} +Result> StringSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) { + auto ret = std::make_shared(PrivateMarker()); + + auto root = checkJson(json, "StringSettingV3"); + ret->parseSharedProperties(key, modID, root); + ret->parseDefaultValue(root, ret->m_impl->defaultValue); + ret->m_impl->value = ret->m_impl->defaultValue; + + root.has("match").into(ret->m_impl->match); + root.has("filter").into(ret->m_impl->filter); + root.has("one-of").into(ret->m_impl->oneOf); + + root.checkUnknownKeys(); + return root.ok(ret); +} + std::string& StringSettingV3::getValueMut() const { return m_impl->value; } @@ -505,7 +510,6 @@ 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); @@ -529,19 +533,6 @@ std::optional> StringSettingV3::getEnumOptions() const return m_impl->oneOf; } -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, m_impl->defaultValue)); - m_impl->value = m_impl->defaultValue; - - root.has("match").into(m_impl->match); - root.has("filter").into(m_impl->filter); - root.has("one-of").into(m_impl->oneOf); - - root.checkUnknownKeys(); - return root.ok(); -} bool StringSettingV3::load(matjson::Value const& json) { if (json.is_string()) { m_impl->value = json.as_string(); @@ -582,30 +573,18 @@ public: FileSettingV3::FileSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} -std::filesystem::path& FileSettingV3::getValueMut() const { - return m_impl->value; -} -std::filesystem::path FileSettingV3::getDefaultValue() const { - return m_impl->defaultValue; -} -Result<> FileSettingV3::isValid(std::filesystem::path const& value) const { - GEODE_UNWRAP(this->isValidShared()); - return Ok(); -} +Result> FileSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) { + auto ret = std::make_shared(PrivateMarker()); -std::optional> FileSettingV3::getFilters() const { - return m_impl->filters; -} - -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, m_impl->defaultValue)); + ret->parseSharedProperties(key, modID, root); + ret->parseDefaultValue(root, ret->m_impl->defaultValue); + ret->m_impl->value = ret->m_impl->defaultValue; // Replace known paths like `{gd-save-dir}/` try { - m_impl->defaultValue = fmt::format( - fmt::runtime(m_impl->defaultValue.string()), + ret->m_impl->defaultValue = fmt::format( + fmt::runtime(ret->m_impl->defaultValue.string()), fmt::arg("gd-save-dir", dirs::getSaveDir()), fmt::arg("gd-game-dir", dirs::getGameDir()), fmt::arg("mod-config-dir", dirs::getModConfigDir() / modID), @@ -616,7 +595,7 @@ Result<> FileSettingV3::onParse(std::string const& key, std::string const& modID catch(fmt::format_error const&) { return Err("Invalid format string for file setting path"); } - m_impl->value = m_impl->defaultValue; + ret->m_impl->value = ret->m_impl->defaultValue; if (auto controls = root.has("control")) { auto filters = std::vector(); @@ -627,13 +606,28 @@ Result<> FileSettingV3::onParse(std::string const& key, std::string const& modID filters.push_back(filter); } if (!filters.empty()) { - m_impl->filters.emplace(filters); + ret->m_impl->filters.emplace(filters); } } root.checkUnknownKeys(); - return root.ok(); + return root.ok(ret); } + +std::filesystem::path& FileSettingV3::getValueMut() const { + return m_impl->value; +} +std::filesystem::path FileSettingV3::getDefaultValue() const { + return m_impl->defaultValue; +} +Result<> FileSettingV3::isValid(std::filesystem::path const& value) const { + return Ok(); +} + +std::optional> FileSettingV3::getFilters() const { + return m_impl->filters; +} + bool FileSettingV3::load(matjson::Value const& json) { if (json.is_string()) { m_impl->value = json.as_string(); @@ -671,6 +665,18 @@ public: Color3BSettingV3::Color3BSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} +Result> Color3BSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) { + auto ret = std::make_shared(PrivateMarker()); + + auto root = checkJson(json, "Color3BSettingV3"); + ret->parseSharedProperties(key, modID, root); + ret->parseDefaultValue(root, ret->m_impl->defaultValue); + ret->m_impl->value = ret->m_impl->defaultValue; + + root.checkUnknownKeys(); + return root.ok(ret); +} + ccColor3B& Color3BSettingV3::getValueMut() const { return m_impl->value; } @@ -678,19 +684,9 @@ ccColor3B Color3BSettingV3::getDefaultValue() const { return m_impl->defaultValue; } Result<> Color3BSettingV3::isValid(ccColor3B value) const { - GEODE_UNWRAP(this->isValidShared()); return Ok(); } -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, m_impl->defaultValue)); - m_impl->value = m_impl->defaultValue; - - root.checkUnknownKeys(); - return root.ok(); -} bool Color3BSettingV3::load(matjson::Value const& json) { if (json.template is()) { m_impl->value = json.template as(); @@ -727,6 +723,18 @@ public: Color4BSettingV3::Color4BSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} +Result> Color4BSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) { + auto ret = std::make_shared(PrivateMarker()); + + auto root = checkJson(json, "Color4BSettingV3"); + ret->parseSharedProperties(key, modID, root); + ret->parseDefaultValue(root, ret->m_impl->defaultValue); + ret->m_impl->value = ret->m_impl->defaultValue; + + root.checkUnknownKeys(); + return root.ok(ret); +} + ccColor4B& Color4BSettingV3::getValueMut() const { return m_impl->value; } @@ -734,19 +742,9 @@ ccColor4B Color4BSettingV3::getDefaultValue() const { return m_impl->defaultValue; } Result<> Color4BSettingV3::isValid(ccColor4B value) const { - GEODE_UNWRAP(this->isValidShared()); return Ok(); } -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, m_impl->defaultValue)); - m_impl->value = m_impl->defaultValue; - - root.checkUnknownKeys(); - return root.ok(); -} bool Color4BSettingV3::load(matjson::Value const& json) { if (json.template is()) { m_impl->value = json.template as(); diff --git a/loader/src/loader/SettingV3Impl.hpp b/loader/src/loader/SettingV3Impl.hpp deleted file mode 100644 index 227c1615..00000000 --- a/loader/src/loader/SettingV3Impl.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#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; -}; - From 5263483b0a04f13dafb4401d02b606cc12df3fd9 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Wed, 21 Aug 2024 23:35:31 +0300 Subject: [PATCH 12/54] bool setting nodes are so back now --- loader/include/Geode/loader/SettingV3.hpp | 17 ++- loader/resources/mod.json.in | 31 ++--- loader/src/loader/SettingNodeV3.cpp | 108 +++++++++++++++--- loader/src/loader/SettingNodeV3.hpp | 2 + loader/src/loader/SettingV3.cpp | 24 ++-- .../src/ui/mods/settings/ModSettingsPopup.cpp | 19 +-- 6 files changed, 147 insertions(+), 54 deletions(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index 83ff851f..dd784837 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -23,8 +23,8 @@ namespace geode { protected: void init(std::string const& key, std::string const& modID); - Result<> parseSharedProperties(std::string const& key, std::string const& modID, matjson::Value const& value); - void parseSharedProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value); + Result<> parseSharedProperties(std::string const& key, std::string const& modID, matjson::Value const& value, bool onlyNameAndDesc = false); + void parseSharedProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value, bool onlyNameAndDesc = false); public: SettingV3(); @@ -46,7 +46,7 @@ namespace geode { /** * Get the name of this setting */ - std::string getName() const; + std::optional getName() const; /** * Get the description of this setting */ @@ -135,8 +135,6 @@ namespace geode { TitleSettingV3(PrivateMarker); static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); - std::string getTitle() const; - bool load(matjson::Value const& json) override; bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; @@ -401,6 +399,8 @@ namespace geode { protected: bool init(std::shared_ptr setting, float width); + virtual void updateState(); + /** * Mark this setting as changed. This updates the UI for committing * the value @@ -414,12 +414,19 @@ namespace geode { */ virtual void onCommit() = 0; + void onDescription(CCObject*); + void onReset(CCObject*); + public: void commit(); virtual bool hasUncommittedChanges() const = 0; virtual bool hasNonDefaultValue() const = 0; virtual void resetToDefault() = 0; + cocos2d::CCLabelBMFont* getNameLabel() const; + cocos2d::CCMenu* getNameMenu() const; + cocos2d::CCMenu* getButtonMenu() const; + void setContentSize(cocos2d::CCSize const& size) override; std::shared_ptr getSetting() const; diff --git a/loader/resources/mod.json.in b/loader/resources/mod.json.in index dcbe69de..89e17202 100644 --- a/loader/resources/mod.json.in +++ b/loader/resources/mod.json.in @@ -64,13 +64,6 @@ } }, "settings": { - "show-platform-console": { - "type": "bool", - "default": false, - "name": "Show Platform Console", - "description": "Show the native console (if one exists). This setting is meant for developers", - "platforms": ["win", "mac"] - }, "auto-check-updates": { "type": "bool", "default": true, @@ -83,6 +76,24 @@ "name": "Disable Crash Popup", "description": "Disables the popup at startup asking if you'd like to send a bug report; intended for developers" }, + "enable-geode-theme": { + "type": "bool", + "default": true, + "name": "Enable Geode-Themed Colors", + "description": "When enabled, the Geode menu has a Geode-themed color scheme. This does not affect any other menus!" + }, + "developer-title": { + "type": "title", + "name": "Developer Settings" + }, + "show-platform-console": { + "type": "bool", + "default": false, + "name": "Show Platform Console", + "description": "Show the native console (if one exists). This setting is meant for developers", + "platforms": ["win", "mac"], + "restart-required": true + }, "server-cache-size-limit": { "type": "int", "default": 20, @@ -90,12 +101,6 @@ "max": 100, "name": "Server Cache Size Limit", "description": "Limits the size of the cache used for loading mods. Higher values result in higher memory usage." - }, - "enable-geode-theme": { - "type": "bool", - "default": true, - "name": "Enable Geode-Themed Colors", - "description": "When enabled, the Geode menu has a Geode-themed color scheme. This does not affect any other menus!" } }, "issues": { diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index 339eb74d..21260852 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -36,6 +36,10 @@ bool SettingNodeValueChangeEventV3::isCommit() const { class SettingNodeV3::Impl final { public: std::shared_ptr setting; + CCLabelBMFont* nameLabel; + CCMenu* nameMenu; + CCMenu* buttonMenu; + CCMenuItemSpriteExtra* resetButton; }; bool SettingNodeV3::init(std::shared_ptr setting, float width) { @@ -45,23 +49,93 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { m_impl = std::make_shared(); m_impl->setting = setting; + m_impl->nameMenu = CCMenu::create(); + m_impl->nameMenu->setContentWidth(width / 2 - 20); + + m_impl->nameLabel = CCLabelBMFont::create( + setting->getName().value_or(setting->getKey()).c_str(), + "bigFont.fnt" + ); + m_impl->nameLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .4f)->setScalePriority(1)); + m_impl->nameMenu->addChild(m_impl->nameLabel); + + if (setting->getDescription()) { + auto descSpr = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png"); + descSpr->setScale(.5f); + auto descBtn = CCMenuItemSpriteExtra::create( + descSpr, this, menu_selector(SettingNodeV3::onDescription) + ); + m_impl->nameMenu->addChild(descBtn); + } + + auto resetSpr = CCSprite::createWithSpriteFrameName("reset-gold.png"_spr); + resetSpr->setScale(.5f); + m_impl->resetButton = CCMenuItemSpriteExtra::create( + resetSpr, this, menu_selector(SettingNodeV3::onReset) + ); + m_impl->nameMenu->addChild(m_impl->resetButton); + + m_impl->nameMenu->setLayout(RowLayout::create()->setAxisAlignment(AxisAlignment::Start)); + this->addChildAtPosition(m_impl->nameMenu, Anchor::Left, ccp(10, 0), ccp(0, .5f)); + + m_impl->buttonMenu = CCMenu::create(); + m_impl->buttonMenu->setContentWidth(width / 2 - 20); + m_impl->buttonMenu->setLayout(RowLayout::create()->setAxisAlignment(AxisAlignment::End)); + this->addChildAtPosition(m_impl->buttonMenu, Anchor::Right, ccp(-10, 0), ccp(1, .5f)); + + this->setAnchorPoint({ .5f, .5f }); + this->setContentSize({ width, 30 }); + return true; } -void SettingNodeV3::markChanged() { - SettingNodeValueChangeEventV3(false).post(); +void SettingNodeV3::updateState() { + this->getNameLabel()->setColor(this->hasUncommittedChanges() ? ccc3(17, 221, 0) : ccWHITE); + m_impl->resetButton->setVisible(this->hasNonDefaultValue()); + m_impl->nameMenu->updateLayout(); } +void SettingNodeV3::onDescription(CCObject*) { + auto title = m_impl->setting->getName().value_or(m_impl->setting->getKey()); + FLAlertLayer::create( + nullptr, + title.c_str(), + m_impl->setting->getDescription().value_or("No description provided"), + "OK", nullptr, + clamp(title.size() * 16, 240, 400) + )->show(); +} +void SettingNodeV3::onReset(CCObject*) { + this->resetToDefault(); + this->updateState(); +} + +void SettingNodeV3::markChanged() { + this->updateState(); + SettingNodeValueChangeEventV3(false).post(); +} void SettingNodeV3::commit() { this->onCommit(); + this->updateState(); SettingNodeValueChangeEventV3(true).post(); } void SettingNodeV3::setContentSize(CCSize const& size) { CCNode::setContentSize(size); + this->updateLayout(); SettingNodeSizeChangeEventV3(this).post(); } +CCLabelBMFont* SettingNodeV3::getNameLabel() const { + return m_impl->nameLabel; +} +CCMenu* SettingNodeV3::getNameMenu() const { + return m_impl->nameMenu; +} +CCMenu* SettingNodeV3::getButtonMenu() const { + return m_impl->buttonMenu; +} + std::shared_ptr SettingNodeV3::getSetting() const { return m_impl->setting; } @@ -72,11 +146,10 @@ bool TitleSettingNodeV3::init(std::shared_ptr setting, float wid if (!SettingNodeV3::init(setting, width)) return false; + this->getNameLabel()->setFntFile("goldFont.fnt"); + this->getNameMenu()->updateLayout(); 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)); + this->updateState(); return true; } @@ -111,16 +184,19 @@ 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, menu_selector(BoolSettingNodeV3::onToggle), .55f ); - this->addChildAtPosition(m_toggle, Anchor::Right, ccp(-m_obContentSize.height / 2, 0)); + m_toggle->m_onButton->setContentSize({ 25, 25 }); + m_toggle->m_onButton->getNormalImage()->setPosition(ccp(25, 25) / 2); + m_toggle->m_offButton->setContentSize({ 25, 25 }); + m_toggle->m_offButton->getNormalImage()->setPosition(ccp(25, 25) / 2); + m_toggle->m_notClickable = true; + m_toggle->toggle(setting->getValue()); + this->getButtonMenu()->addChild(m_toggle); + this->getButtonMenu()->updateLayout(); + + this->updateState(); return true; } @@ -128,6 +204,10 @@ bool BoolSettingNodeV3::init(std::shared_ptr setting, float width void BoolSettingNodeV3::onCommit() { this->getSetting()->setValue(m_toggle->isToggled()); } +void BoolSettingNodeV3::onToggle(CCObject*) { + m_toggle->toggle(!m_toggle->isToggled()); + this->markChanged(); +} BoolSettingNodeV3* BoolSettingNodeV3::create(std::shared_ptr setting, float width) { auto ret = new BoolSettingNodeV3(); diff --git a/loader/src/loader/SettingNodeV3.hpp b/loader/src/loader/SettingNodeV3.hpp index f1faa862..ca1753e1 100644 --- a/loader/src/loader/SettingNodeV3.hpp +++ b/loader/src/loader/SettingNodeV3.hpp @@ -33,6 +33,8 @@ protected: void onCommit() override; + void onToggle(CCObject*); + public: static BoolSettingNodeV3* create(std::shared_ptr setting, float width); diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index e7e8039a..119821d8 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -18,18 +18,20 @@ public: SettingV3::SettingV3() : m_impl(std::make_shared()) {} SettingV3::~SettingV3() = default; -Result<> SettingV3::parseSharedProperties(std::string const& key, std::string const& modID, matjson::Value const& value) { +Result<> SettingV3::parseSharedProperties(std::string const& key, std::string const& modID, matjson::Value const& value, bool onlyNameAndDesc) { auto json = checkJson(value, "SettingV3"); - this->parseSharedProperties(key, modID, json); + this->parseSharedProperties(key, modID, json, onlyNameAndDesc); return json.ok(); } -void SettingV3::parseSharedProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value) { +void SettingV3::parseSharedProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value, bool onlyNameAndDesc) { this->init(key, modID); value.needs("type"); value.has("name").into(m_impl->name); value.has("description").into(m_impl->description); - value.has("enable-if").into(m_impl->enableIf); - value.has("requires-restart").into(m_impl->requiresRestart); + if (!onlyNameAndDesc) { + value.has("enable-if").into(m_impl->enableIf); + value.has("requires-restart").into(m_impl->requiresRestart); + } } void SettingV3::init(std::string const& key, std::string const& modID) { m_impl->key = key; @@ -42,8 +44,8 @@ std::string SettingV3::getKey() const { std::string SettingV3::getModID() const { return m_impl->modID; } -std::string SettingV3::getName() const { - return m_impl->name.value_or(m_impl->key); +std::optional SettingV3::getName() const { + return m_impl->name; } std::optional SettingV3::getDescription() const { return m_impl->description; @@ -67,7 +69,6 @@ std::optional> SettingV3::convertToLegacyValue() c class TitleSettingV3::Impl final { public: - std::string title; }; TitleSettingV3::TitleSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} @@ -75,16 +76,11 @@ TitleSettingV3::TitleSettingV3(PrivateMarker) : m_impl(std::make_shared()) Result> TitleSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) { auto ret = std::make_shared(PrivateMarker()); auto root = checkJson(json, "TitleSettingV3"); - ret->init(key, modID); - root.needs("title").into(ret->m_impl->title); + ret->parseSharedProperties(key, modID, root, true); root.checkUnknownKeys(); return root.ok(ret); } -std::string TitleSettingV3::getTitle() const { - return m_impl->title; -} - bool TitleSettingV3::load(matjson::Value const& json) { return true; } diff --git a/loader/src/ui/mods/settings/ModSettingsPopup.cpp b/loader/src/ui/mods/settings/ModSettingsPopup.cpp index e80f72b2..020ca261 100644 --- a/loader/src/ui/mods/settings/ModSettingsPopup.cpp +++ b/loader/src/ui/mods/settings/ModSettingsPopup.cpp @@ -28,7 +28,7 @@ bool ModSettingsPopup::setup(Mod* mod) { hasBG = !hasBG; auto bg = CCLayerColor::create({ 0, 0, 0, 50 }); - bg->setOpacity(hasBG ? 50 : 0); + bg->setOpacity(hasBG ? 60 : 20); SettingNodeV3* node; if (auto sett = mod->getSettingV3(key)) { @@ -40,11 +40,12 @@ bool ModSettingsPopup::setup(Mod* mod) { } bg->setContentSize(node->getScaledContentSize()); - bg->addChildAtPosition(node, Anchor::Center); + bg->addChildAtPosition(node, Anchor::Center, ccp(0, 0), ccp(.5f, .5f)); - auto separator = CCLayerColor::create({ 0, 0, 0, 50 }, layerSize.width, 1.f); - separator->setOpacity(hasBG ? 100 : 50); - bg->addChildAtPosition(separator, Anchor::Bottom); + // auto separator = CCLayerColor::create({ 0, 0, 0, 50 }, layerSize.width, 1.f); + // separator->setOpacity(hasBG ? 100 : 50); + // separator->ignoreAnchorPointForPosition(false); + // bg->addChildAtPosition(separator, Anchor::Bottom, ccp(0, 0), ccp(.5f, .5f)); m_settings.push_back(node); @@ -132,11 +133,13 @@ void ModSettingsPopup::onResetAll(CCObject*) { void ModSettingsPopup::updateState() { if (this->hasUncommitted()) { - m_applyBtnSpr->setColor({0xff, 0xff, 0xff}); + m_applyBtnSpr->setColor(ccWHITE); + m_applyBtnSpr->setOpacity(255); m_applyBtn->setEnabled(true); } else { - m_applyBtnSpr->setColor({0x44, 0x44, 0x44}); + m_applyBtnSpr->setColor(ccGRAY); + m_applyBtnSpr->setOpacity(155); m_applyBtn->setEnabled(false); } } @@ -172,7 +175,7 @@ void ModSettingsPopup::onOpenSaveDirectory(CCObject*) { ModSettingsPopup* ModSettingsPopup::create(Mod* mod) { auto ret = new ModSettingsPopup(); - if (ret->init(440.f, 280.f, mod)) { + if (ret->init(440, 280, mod)) { ret->autorelease(); return ret; } From 6ed6b326697cc47447b12cecf2901269f8f35635 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Thu, 22 Aug 2024 14:22:57 +0300 Subject: [PATCH 13/54] int and float setting nodes --- loader/include/Geode/loader/SettingV3.hpp | 7 +- loader/src/loader/SettingNodeV3.cpp | 505 +++++++++++++----- loader/src/loader/SettingNodeV3.hpp | 41 +- loader/src/loader/SettingV3.cpp | 35 +- .../src/ui/mods/settings/ModSettingsPopup.cpp | 2 + 5 files changed, 442 insertions(+), 148 deletions(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index dd784837..03506ca5 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -47,6 +47,10 @@ namespace geode { * Get the name of this setting */ std::optional getName() const; + /** + * Get the name of this setting, or its key if it has no name + */ + std::string getDisplayName() const; /** * Get the description of this setting */ @@ -413,15 +417,16 @@ namespace geode { * the value in some sort of global manager */ virtual void onCommit() = 0; + virtual void onResetToDefault() = 0; void onDescription(CCObject*); void onReset(CCObject*); public: void commit(); + void resetToDefault(); virtual bool hasUncommittedChanges() const = 0; virtual bool hasNonDefaultValue() const = 0; - virtual void resetToDefault() = 0; cocos2d::CCLabelBMFont* getNameLabel() const; cocos2d::CCMenu* getNameMenu() const; diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index 21260852..a8a25cb8 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -1,6 +1,27 @@ #include "SettingNodeV3.hpp" #include +template +static float valueToSlider(std::shared_ptr setting, typename T::ValueType value) { + auto min = setting->getMinValue().value_or(-100); + auto max = setting->getMaxValue().value_or(+100); + auto range = max - min; + return static_cast(clamp(static_cast(value - min) / range, 0.0, 1.0)); +} +template +static typename T::ValueType valueFromSlider(std::shared_ptr setting, float num) { + auto min = setting->getMinValue().value_or(-100); + auto max = setting->getMaxValue().value_or(+100); + auto range = max - min; + auto value = static_cast(num * range + min); + if (auto step = setting->getSliderSnap()) { + value = static_cast( + round(value / *step) * (*step) + ); + } + return value; +} + class SettingNodeSizeChangeEventV3::Impl final { public: SettingNodeV3* node; @@ -52,10 +73,7 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { m_impl->nameMenu = CCMenu::create(); m_impl->nameMenu->setContentWidth(width / 2 - 20); - m_impl->nameLabel = CCLabelBMFont::create( - setting->getName().value_or(setting->getKey()).c_str(), - "bigFont.fnt" - ); + m_impl->nameLabel = CCLabelBMFont::create(setting->getDisplayName().c_str(), "bigFont.fnt"); m_impl->nameLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .4f)->setScalePriority(1)); m_impl->nameMenu->addChild(m_impl->nameLabel); @@ -96,7 +114,7 @@ void SettingNodeV3::updateState() { } void SettingNodeV3::onDescription(CCObject*) { - auto title = m_impl->setting->getName().value_or(m_impl->setting->getKey()); + auto title = m_impl->setting->getDisplayName(); FLAlertLayer::create( nullptr, title.c_str(), @@ -106,8 +124,19 @@ void SettingNodeV3::onDescription(CCObject*) { )->show(); } void SettingNodeV3::onReset(CCObject*) { - this->resetToDefault(); - this->updateState(); + createQuickPopup( + "Reset", + fmt::format( + "Are you sure you want to reset {} to default?", + this->getSetting()->getDisplayName() + ), + "Cancel", "Reset", + [this](auto, bool btn2) { + if (btn2) { + this->resetToDefault(); + } + } + ); } void SettingNodeV3::markChanged() { @@ -119,6 +148,12 @@ void SettingNodeV3::commit() { this->updateState(); SettingNodeValueChangeEventV3(true).post(); } +void SettingNodeV3::resetToDefault() { + m_impl->setting->reset(); + this->onResetToDefault(); + this->updateState(); + SettingNodeValueChangeEventV3(false).post(); +} void SettingNodeV3::setContentSize(CCSize const& size) { CCNode::setContentSize(size); @@ -156,6 +191,18 @@ bool TitleSettingNodeV3::init(std::shared_ptr setting, float wid void TitleSettingNodeV3::onCommit() {} +bool TitleSettingNodeV3::hasUncommittedChanges() const { + return false; +} +bool TitleSettingNodeV3::hasNonDefaultValue() const { + return false; +} +void TitleSettingNodeV3::onResetToDefault() {} + +std::shared_ptr TitleSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} + TitleSettingNodeV3* TitleSettingNodeV3::create(std::shared_ptr setting, float width) { auto ret = new TitleSettingNodeV3(); if (ret && ret->init(setting, width)) { @@ -166,18 +213,6 @@ TitleSettingNodeV3* TitleSettingNodeV3::create(std::shared_ptr s 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) { @@ -209,6 +244,20 @@ void BoolSettingNodeV3::onToggle(CCObject*) { this->markChanged(); } +bool BoolSettingNodeV3::hasUncommittedChanges() const { + return m_toggle->isToggled() != this->getSetting()->getValue(); +} +bool BoolSettingNodeV3::hasNonDefaultValue() const { + return m_toggle->isToggled() != this->getSetting()->getDefaultValue(); +} +void BoolSettingNodeV3::onResetToDefault() { + m_toggle->toggle(this->getSetting()->getDefaultValue()); +} + +std::shared_ptr BoolSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} + BoolSettingNodeV3* BoolSettingNodeV3::create(std::shared_ptr setting, float width) { auto ret = new BoolSettingNodeV3(); if (ret && ret->init(setting, width)) { @@ -219,33 +268,134 @@ BoolSettingNodeV3* BoolSettingNodeV3::create(std::shared_ptr sett 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; + + auto bigArrowLeftSpr = CCSprite::create(); + auto bigArrowLeftSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); + auto bigArrowLeftSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); - // todo + bigArrowLeftSpr->setContentSize(bigArrowLeftSpr1->getContentSize() + ccp(20, 0)); + bigArrowLeftSpr->addChildAtPosition(bigArrowLeftSpr2, Anchor::Center, ccp(10, 0)); + bigArrowLeftSpr->addChildAtPosition(bigArrowLeftSpr1, Anchor::Center, ccp(-10, 0)); + bigArrowLeftSpr->setScale(.45f); + + auto bigArrowLeftBtn = CCMenuItemSpriteExtra::create( + bigArrowLeftSpr, this, menu_selector(IntSettingNodeV3::onArrow) + ); + bigArrowLeftBtn->setTag(-setting->getBigArrowStepSize()); + bigArrowLeftBtn->setVisible(setting->isBigArrowsEnabled()); + this->getButtonMenu()->addChild(bigArrowLeftBtn); + + auto arrowLeftSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); + arrowLeftSpr->setScale(.65f); + auto arrowLeftBtn = CCMenuItemSpriteExtra::create( + arrowLeftSpr, this, menu_selector(IntSettingNodeV3::onArrow) + ); + arrowLeftBtn->setTag(-setting->getArrowStepSize()); + arrowLeftBtn->setVisible(setting->isArrowsEnabled()); + this->getButtonMenu()->addChild(arrowLeftBtn); + + m_input = TextInput::create(width / 2 - 70, "Num"); + m_input->setCallback([this](auto const&) { + this->markChanged(); + }); + if (!setting->isInputEnabled()) { + m_input->getBGSprite()->setVisible(false); + m_input->setEnabled(false); + } + this->getButtonMenu()->addChild(m_input); + + auto arrowRightSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); + arrowRightSpr->setFlipX(true); + arrowRightSpr->setScale(.65f); + auto arrowRightBtn = CCMenuItemSpriteExtra::create( + arrowRightSpr, this, menu_selector(IntSettingNodeV3::onArrow) + ); + arrowRightBtn->setTag(setting->getArrowStepSize()); + arrowRightBtn->setVisible(setting->isArrowsEnabled()); + this->getButtonMenu()->addChild(arrowRightBtn); + + auto bigArrowRightSpr = CCSprite::create(); + auto bigArrowRightSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); + bigArrowRightSpr1->setFlipX(true); + auto bigArrowRightSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); + bigArrowRightSpr2->setFlipX(true); + + bigArrowRightSpr->setContentSize(bigArrowRightSpr1->getContentSize() + ccp(20, 0)); + bigArrowRightSpr->addChildAtPosition(bigArrowRightSpr1, Anchor::Center, ccp(-10, 0)); + bigArrowRightSpr->addChildAtPosition(bigArrowRightSpr2, Anchor::Center, ccp(10, 0)); + bigArrowRightSpr->setScale(.45f); + + auto bigArrowRightBtn = CCMenuItemSpriteExtra::create( + bigArrowRightSpr, this, menu_selector(IntSettingNodeV3::onArrow) + ); + bigArrowRightBtn->setTag(setting->getBigArrowStepSize()); + bigArrowRightBtn->setVisible(setting->isBigArrowsEnabled()); + this->getButtonMenu()->addChild(bigArrowRightBtn); + + if (setting->isSliderEnabled()) { + this->setContentHeight(45); + this->getButtonMenu()->updateAnchoredPosition(Anchor::Right, ccp(0, 7)); + + m_slider = Slider::create(this, menu_selector(IntSettingNodeV3::onSlider)); + m_slider->setScale(.5f); + this->addChildAtPosition(m_slider, Anchor::Right, ccp(-75, -12), ccp(0, 0)); + } + + this->setCurrentValue(setting->getValue()); + this->getButtonMenu()->updateLayout(); + this->updateState(); return true; } -void IntSettingNodeV3::onCommit() {} +void IntSettingNodeV3::updateState() { + SettingNodeV3::updateState(); + m_slider->m_touchLogic->m_thumb->setValue(valueToSlider( + this->getSetting(), this->getCurrentValue() + )); + m_slider->updateBar(); +} + +int64_t IntSettingNodeV3::getCurrentValue() const { + return numFromString(m_input->getString()).value_or(this->getSetting()->getDefaultValue()); +} +void IntSettingNodeV3::setCurrentValue(int64_t value) { + m_input->setString(std::to_string(value)); + this->markChanged(); +} + +void IntSettingNodeV3::onCommit() { + this->getSetting()->setValue(this->getCurrentValue()); +} +void IntSettingNodeV3::onArrow(CCObject* sender) { + this->setCurrentValue(this->getCurrentValue() + sender->getTag()); + this->updateState(); +} +void IntSettingNodeV3::onSlider(CCObject* sender) { + this->setCurrentValue(valueFromSlider( + this->getSetting(), + m_slider->m_touchLogic->m_thumb->getValue() + )); +} + +bool IntSettingNodeV3::hasUncommittedChanges() const { + return this->getSetting()->getValue() != this->getCurrentValue(); +} +bool IntSettingNodeV3::hasNonDefaultValue() const { + return this->getSetting()->getDefaultValue() != this->getCurrentValue(); +} +void IntSettingNodeV3::onResetToDefault() { + this->setCurrentValue(this->getSetting()->getDefaultValue()); +} + +std::shared_ptr IntSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} IntSettingNodeV3* IntSettingNodeV3::create(std::shared_ptr setting, float width) { auto ret = new IntSettingNodeV3(); @@ -257,30 +407,133 @@ IntSettingNodeV3* IntSettingNodeV3::create(std::shared_ptr setting 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 + auto bigArrowLeftSpr = CCSprite::create(); + auto bigArrowLeftSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); + auto bigArrowLeftSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); + + bigArrowLeftSpr->setContentSize(bigArrowLeftSpr1->getContentSize() + ccp(20, 0)); + bigArrowLeftSpr->addChildAtPosition(bigArrowLeftSpr2, Anchor::Center, ccp(10, 0)); + bigArrowLeftSpr->addChildAtPosition(bigArrowLeftSpr1, Anchor::Center, ccp(-10, 0)); + bigArrowLeftSpr->setScale(.45f); + + auto bigArrowLeftBtn = CCMenuItemSpriteExtra::create( + bigArrowLeftSpr, this, menu_selector(FloatSettingNodeV3::onArrow) + ); + bigArrowLeftBtn->setTag(-setting->getBigArrowStepSize()); + bigArrowLeftBtn->setVisible(setting->isBigArrowsEnabled()); + this->getButtonMenu()->addChild(bigArrowLeftBtn); + + auto arrowLeftSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); + arrowLeftSpr->setScale(.65f); + auto arrowLeftBtn = CCMenuItemSpriteExtra::create( + arrowLeftSpr, this, menu_selector(FloatSettingNodeV3::onArrow) + ); + arrowLeftBtn->setTag(-setting->getArrowStepSize()); + arrowLeftBtn->setVisible(setting->isArrowsEnabled()); + this->getButtonMenu()->addChild(arrowLeftBtn); + + m_input = TextInput::create(width / 2 - 70, "Num"); + m_input->setCallback([this](auto const&) { + this->markChanged(); + }); + if (!setting->isInputEnabled()) { + m_input->getBGSprite()->setVisible(false); + m_input->setEnabled(false); + } + this->getButtonMenu()->addChild(m_input); + + auto arrowRightSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); + arrowRightSpr->setFlipX(true); + arrowRightSpr->setScale(.65f); + auto arrowRightBtn = CCMenuItemSpriteExtra::create( + arrowRightSpr, this, menu_selector(FloatSettingNodeV3::onArrow) + ); + arrowRightBtn->setTag(setting->getArrowStepSize()); + arrowRightBtn->setVisible(setting->isArrowsEnabled()); + this->getButtonMenu()->addChild(arrowRightBtn); + + auto bigArrowRightSpr = CCSprite::create(); + auto bigArrowRightSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); + bigArrowRightSpr1->setFlipX(true); + auto bigArrowRightSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); + bigArrowRightSpr2->setFlipX(true); + + bigArrowRightSpr->setContentSize(bigArrowRightSpr1->getContentSize() + ccp(20, 0)); + bigArrowRightSpr->addChildAtPosition(bigArrowRightSpr1, Anchor::Center, ccp(-10, 0)); + bigArrowRightSpr->addChildAtPosition(bigArrowRightSpr2, Anchor::Center, ccp(10, 0)); + bigArrowRightSpr->setScale(.45f); + + auto bigArrowRightBtn = CCMenuItemSpriteExtra::create( + bigArrowRightSpr, this, menu_selector(FloatSettingNodeV3::onArrow) + ); + bigArrowRightBtn->setTag(setting->getBigArrowStepSize()); + bigArrowRightBtn->setVisible(setting->isBigArrowsEnabled()); + this->getButtonMenu()->addChild(bigArrowRightBtn); + + if (setting->isSliderEnabled()) { + this->setContentHeight(45); + this->getButtonMenu()->updateAnchoredPosition(Anchor::Right, ccp(0, 7)); + + m_slider = Slider::create(this, menu_selector(FloatSettingNodeV3::onSlider)); + m_slider->setScale(.5f); + this->addChildAtPosition(m_slider, Anchor::Right, ccp(-75, -12), ccp(0, 0)); + } + + this->setCurrentValue(setting->getValue()); + this->getButtonMenu()->updateLayout(); + this->updateState(); return true; } -void FloatSettingNodeV3::onCommit() {} +void FloatSettingNodeV3::updateState() { + SettingNodeV3::updateState(); + m_slider->m_touchLogic->m_thumb->setValue(valueToSlider( + this->getSetting(), this->getCurrentValue() + )); + m_slider->updateBar(); +} + +double FloatSettingNodeV3::getCurrentValue() const { + return numFromString(m_input->getString()).value_or(this->getSetting()->getDefaultValue()); +} +void FloatSettingNodeV3::setCurrentValue(double value) { + m_input->setString(numToString(value)); +} + +void FloatSettingNodeV3::onCommit() { + this->getSetting()->setValue(this->getCurrentValue()); +} +void FloatSettingNodeV3::onArrow(CCObject* sender) { + this->setCurrentValue(this->getCurrentValue() + sender->getTag()); + this->updateState(); +} +void FloatSettingNodeV3::onSlider(CCObject* sender) { + this->setCurrentValue(valueFromSlider( + this->getSetting(), + m_slider->m_touchLogic->m_thumb->getValue() + )); +} + +bool FloatSettingNodeV3::hasUncommittedChanges() const { + return this->getSetting()->getValue() != this->getCurrentValue(); +} +bool FloatSettingNodeV3::hasNonDefaultValue() const { + return this->getSetting()->getDefaultValue() != this->getCurrentValue(); +} +void FloatSettingNodeV3::onResetToDefault() { + this->setCurrentValue(this->getSetting()->getDefaultValue()); +} + +std::shared_ptr FloatSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} FloatSettingNodeV3* FloatSettingNodeV3::create(std::shared_ptr setting, float width) { auto ret = new FloatSettingNodeV3(); @@ -292,18 +545,6 @@ FloatSettingNodeV3* FloatSettingNodeV3::create(std::shared_ptr s 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) { @@ -317,6 +558,18 @@ bool StringSettingNodeV3::init(std::shared_ptr setting, float w void StringSettingNodeV3::onCommit() {} +bool StringSettingNodeV3::hasUncommittedChanges() const { + return false; +} +bool StringSettingNodeV3::hasNonDefaultValue() const { + return false; +} +void StringSettingNodeV3::onResetToDefault() {} + +std::shared_ptr StringSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} + StringSettingNodeV3* StringSettingNodeV3::create(std::shared_ptr setting, float width) { auto ret = new StringSettingNodeV3(); if (ret && ret->init(setting, width)) { @@ -327,18 +580,6 @@ StringSettingNodeV3* StringSettingNodeV3::create(std::shared_ptr StringSettingNodeV3::getSetting() const { - return std::static_pointer_cast(SettingNodeV3::getSetting()); -} - // FileSettingNodeV3 bool FileSettingNodeV3::init(std::shared_ptr setting, float width) { @@ -352,6 +593,18 @@ bool FileSettingNodeV3::init(std::shared_ptr setting, float width void FileSettingNodeV3::onCommit() {} +bool FileSettingNodeV3::hasUncommittedChanges() const { + return false; +} +bool FileSettingNodeV3::hasNonDefaultValue() const { + return false; +} +void FileSettingNodeV3::onResetToDefault() {} + +std::shared_ptr FileSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} + FileSettingNodeV3* FileSettingNodeV3::create(std::shared_ptr setting, float width) { auto ret = new FileSettingNodeV3(); if (ret && ret->init(setting, width)) { @@ -362,18 +615,6 @@ FileSettingNodeV3* FileSettingNodeV3::create(std::shared_ptr sett 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) { @@ -387,6 +628,18 @@ bool Color3BSettingNodeV3::init(std::shared_ptr setting, float void Color3BSettingNodeV3::onCommit() {} +bool Color3BSettingNodeV3::hasUncommittedChanges() const { + return false; +} +bool Color3BSettingNodeV3::hasNonDefaultValue() const { + return false; +} +void Color3BSettingNodeV3::onResetToDefault() {} + +std::shared_ptr Color3BSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} + Color3BSettingNodeV3* Color3BSettingNodeV3::create(std::shared_ptr setting, float width) { auto ret = new Color3BSettingNodeV3(); if (ret && ret->init(setting, width)) { @@ -397,18 +650,6 @@ Color3BSettingNodeV3* Color3BSettingNodeV3::create(std::shared_ptr Color3BSettingNodeV3::getSetting() const { - return std::static_pointer_cast(SettingNodeV3::getSetting()); -} - // Color4BSettingNodeV3 bool Color4BSettingNodeV3::init(std::shared_ptr setting, float width) { @@ -422,6 +663,18 @@ bool Color4BSettingNodeV3::init(std::shared_ptr setting, float void Color4BSettingNodeV3::onCommit() {} +bool Color4BSettingNodeV3::hasUncommittedChanges() const { + return false; +} +bool Color4BSettingNodeV3::hasNonDefaultValue() const { + return false; +} +void Color4BSettingNodeV3::onResetToDefault() {} + +std::shared_ptr Color4BSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} + Color4BSettingNodeV3* Color4BSettingNodeV3::create(std::shared_ptr setting, float width) { auto ret = new Color4BSettingNodeV3(); if (ret && ret->init(setting, width)) { @@ -432,18 +685,6 @@ Color4BSettingNodeV3* Color4BSettingNodeV3::create(std::shared_ptr Color4BSettingNodeV3::getSetting() const { - return std::static_pointer_cast(SettingNodeV3::getSetting()); -} - // UnresolvedCustomSettingNodeV3 bool UnresolvedCustomSettingNodeV3::init(std::shared_ptr setting, float width) { @@ -464,6 +705,18 @@ bool UnresolvedCustomSettingNodeV3::init(std::shared_ptr void UnresolvedCustomSettingNodeV3::onCommit() {} +bool UnresolvedCustomSettingNodeV3::hasUncommittedChanges() const { + return false; +} +bool UnresolvedCustomSettingNodeV3::hasNonDefaultValue() const { + return false; +} +void UnresolvedCustomSettingNodeV3::onResetToDefault() {} + +std::shared_ptr UnresolvedCustomSettingNodeV3::getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); +} + UnresolvedCustomSettingNodeV3* UnresolvedCustomSettingNodeV3::create(std::shared_ptr setting, float width) { auto ret = new UnresolvedCustomSettingNodeV3(); if (ret && ret->init(setting, width)) { @@ -474,18 +727,6 @@ UnresolvedCustomSettingNodeV3* UnresolvedCustomSettingNodeV3::create(std::shared 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) { @@ -503,6 +744,16 @@ void LegacyCustomSettingToV3Node::onCommit() { m_original->commit(); } +bool LegacyCustomSettingToV3Node::hasUncommittedChanges() const { + return m_original->hasUncommittedChanges(); +} +bool LegacyCustomSettingToV3Node::hasNonDefaultValue() const { + return m_original->hasNonDefaultValue(); +} +void LegacyCustomSettingToV3Node::onResetToDefault() { + m_original->resetToDefault(); +} + LegacyCustomSettingToV3Node* LegacyCustomSettingToV3Node::create(std::shared_ptr original, float width) { auto ret = new LegacyCustomSettingToV3Node(); if (ret && ret->init(original, width)) { @@ -512,13 +763,3 @@ LegacyCustomSettingToV3Node* LegacyCustomSettingToV3Node::create(std::shared_ptr 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 index ca1753e1..419522ba 100644 --- a/loader/src/loader/SettingNodeV3.hpp +++ b/loader/src/loader/SettingNodeV3.hpp @@ -20,7 +20,7 @@ public: bool hasUncommittedChanges() const override; bool hasNonDefaultValue() const override; - void resetToDefault() override; + void onResetToDefault() override; std::shared_ptr getSetting() const; }; @@ -32,7 +32,6 @@ protected: bool init(std::shared_ptr setting, float width); void onCommit() override; - void onToggle(CCObject*); public: @@ -40,39 +39,59 @@ public: bool hasUncommittedChanges() const override; bool hasNonDefaultValue() const override; - void resetToDefault() override; + void onResetToDefault() override; std::shared_ptr getSetting() const; }; class IntSettingNodeV3 : public SettingNodeV3 { protected: + TextInput* m_input; + Slider* m_slider; + bool init(std::shared_ptr setting, float width); + void updateState() override; + void onCommit() override; + void onArrow(CCObject* sender); + void onSlider(CCObject*); + + int64_t getCurrentValue() const; + void setCurrentValue(int64_t value); public: static IntSettingNodeV3* create(std::shared_ptr setting, float width); bool hasUncommittedChanges() const override; bool hasNonDefaultValue() const override; - void resetToDefault() override; + void onResetToDefault() override; std::shared_ptr getSetting() const; }; class FloatSettingNodeV3 : public SettingNodeV3 { protected: + TextInput* m_input; + Slider* m_slider; + bool init(std::shared_ptr setting, float width); + void updateState() override; + void onCommit() override; + void onArrow(CCObject* sender); + void onSlider(CCObject*); + + double getCurrentValue() const; + void setCurrentValue(double value); public: static FloatSettingNodeV3* create(std::shared_ptr setting, float width); bool hasUncommittedChanges() const override; bool hasNonDefaultValue() const override; - void resetToDefault() override; + void onResetToDefault() override; std::shared_ptr getSetting() const; }; @@ -88,7 +107,7 @@ public: bool hasUncommittedChanges() const override; bool hasNonDefaultValue() const override; - void resetToDefault() override; + void onResetToDefault() override; std::shared_ptr getSetting() const; }; @@ -104,7 +123,7 @@ public: bool hasUncommittedChanges() const override; bool hasNonDefaultValue() const override; - void resetToDefault() override; + void onResetToDefault() override; std::shared_ptr getSetting() const; }; @@ -120,7 +139,7 @@ public: bool hasUncommittedChanges() const override; bool hasNonDefaultValue() const override; - void resetToDefault() override; + void onResetToDefault() override; std::shared_ptr getSetting() const; }; @@ -136,7 +155,7 @@ public: bool hasUncommittedChanges() const override; bool hasNonDefaultValue() const override; - void resetToDefault() override; + void onResetToDefault() override; std::shared_ptr getSetting() const; }; @@ -152,7 +171,7 @@ public: bool hasUncommittedChanges() const override; bool hasNonDefaultValue() const override; - void resetToDefault() override; + void onResetToDefault() override; std::shared_ptr getSetting() const; }; @@ -172,5 +191,5 @@ public: bool hasUncommittedChanges() const override; bool hasNonDefaultValue() const override; - void resetToDefault() override; + void onResetToDefault() override; }; diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index 119821d8..d44574a7 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -47,6 +47,9 @@ std::string SettingV3::getModID() const { std::optional SettingV3::getName() const { return m_impl->name; } +std::string SettingV3::getDisplayName() const { + return m_impl->name.value_or(m_impl->key); +} std::optional SettingV3::getDescription() const { return m_impl->description; } @@ -217,11 +220,11 @@ public: struct { // 0 means not enabled - size_t arrowStepSize; - size_t bigArrowStepSize; - bool sliderEnabled; + size_t arrowStepSize = 1; + size_t bigArrowStepSize = 5; + bool sliderEnabled = true; std::optional sliderSnap; - bool textInputEnabled; + bool textInputEnabled = true; } controls; }; @@ -247,6 +250,18 @@ Result> IntSettingV3::parse(std::string const& key controls.has("slider").into(ret->m_impl->controls.sliderEnabled); controls.has("slider-step").into(ret->m_impl->controls.sliderSnap); controls.has("input").into(ret->m_impl->controls.textInputEnabled); + // Without "min" or "max" slider makes no sense + if (!ret->m_impl->minValue || !ret->m_impl->maxValue) { + if (ret->m_impl->controls.sliderEnabled) { + log::warn( + "Setting '{}' has \"controls.slider\" enabled but doesn't " + "have both \"min\" and \"max\" defined - the slider has " + "been force-disabled!", + key + ); + } + ret->m_impl->controls.sliderEnabled = false; + } controls.checkUnknownKeys(); } @@ -381,6 +396,18 @@ Result> FloatSettingV3::parse(std::string const& controls.has("slider").into(ret->m_impl->controls.sliderEnabled); controls.has("slider-step").into(ret->m_impl->controls.sliderSnap); controls.has("input").into(ret->m_impl->controls.textInputEnabled); + // Without "min" or "max" slider makes no sense + if (!ret->m_impl->minValue || !ret->m_impl->maxValue) { + if (ret->m_impl->controls.sliderEnabled) { + log::warn( + "Setting '{}' has \"controls.slider\" enabled but doesn't " + "have both \"min\" and \"max\" defined - the slider has " + "been force-disabled!", + key + ); + } + ret->m_impl->controls.sliderEnabled = false; + } controls.checkUnknownKeys(); } diff --git a/loader/src/ui/mods/settings/ModSettingsPopup.cpp b/loader/src/ui/mods/settings/ModSettingsPopup.cpp index 020ca261..5c5fdf72 100644 --- a/loader/src/ui/mods/settings/ModSettingsPopup.cpp +++ b/loader/src/ui/mods/settings/ModSettingsPopup.cpp @@ -132,6 +132,8 @@ void ModSettingsPopup::onResetAll(CCObject*) { } void ModSettingsPopup::updateState() { + m_applyBtnSpr->setCascadeColorEnabled(true); + m_applyBtnSpr->setCascadeOpacityEnabled(true); if (this->hasUncommitted()) { m_applyBtnSpr->setColor(ccWHITE); m_applyBtnSpr->setOpacity(255); From 85e7b5e3ab0f29d8ed03884f0d550a44009fc8f0 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Fri, 23 Aug 2024 11:32:26 +0300 Subject: [PATCH 14/54] make AxisLayout size hint more reasonable --- loader/src/cocos2d-ext/AxisLayout.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/loader/src/cocos2d-ext/AxisLayout.cpp b/loader/src/cocos2d-ext/AxisLayout.cpp index 02d8b285..cedded25 100644 --- a/loader/src/cocos2d-ext/AxisLayout.cpp +++ b/loader/src/cocos2d-ext/AxisLayout.cpp @@ -823,6 +823,13 @@ CCSize AxisLayout::getSizeHint(CCNode* on) const { axis.crossLength = cross; } } + if (auto l = m_impl->m_autoGrowAxisMinLength) { + length = std::max(length, *l); + } + // No overflow + else { + length = std::min(length, nodeAxis(on, m_impl->m_axis, 1.f).axisLength); + } if (!m_impl->m_allowCrossAxisOverflow) { cross = nodeAxis(on, m_impl->m_axis, 1.f).crossLength; } From dc6637e1306fc9ed024c194078fc75940ace1cad Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Fri, 23 Aug 2024 12:36:49 +0300 Subject: [PATCH 15/54] string setting nodes + move number setting nodes to a shared class + legacy custom setting nodes proven to work --- loader/src/loader/SettingNodeV3.cpp | 384 ++++++---------------------- loader/src/loader/SettingNodeV3.hpp | 203 ++++++++++++--- loader/src/loader/SettingV3.cpp | 11 +- 3 files changed, 244 insertions(+), 354 deletions(-) diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index a8a25cb8..58973ef9 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -1,27 +1,6 @@ #include "SettingNodeV3.hpp" #include -template -static float valueToSlider(std::shared_ptr setting, typename T::ValueType value) { - auto min = setting->getMinValue().value_or(-100); - auto max = setting->getMaxValue().value_or(+100); - auto range = max - min; - return static_cast(clamp(static_cast(value - min) / range, 0.0, 1.0)); -} -template -static typename T::ValueType valueFromSlider(std::shared_ptr setting, float num) { - auto min = setting->getMinValue().value_or(-100); - auto max = setting->getMaxValue().value_or(+100); - auto range = max - min; - auto value = static_cast(num * range + min); - if (auto step = setting->getSliderSnap()) { - value = static_cast( - round(value / *step) * (*step) - ); - } - return value; -} - class SettingNodeSizeChangeEventV3::Impl final { public: SettingNodeV3* node; @@ -71,7 +50,7 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { m_impl->setting = setting; m_impl->nameMenu = CCMenu::create(); - m_impl->nameMenu->setContentWidth(width / 2 - 20); + m_impl->nameMenu->setContentWidth(width / 2 + 25); m_impl->nameLabel = CCLabelBMFont::create(setting->getDisplayName().c_str(), "bigFont.fnt"); m_impl->nameLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .4f)->setScalePriority(1)); @@ -97,8 +76,8 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { this->addChildAtPosition(m_impl->nameMenu, Anchor::Left, ccp(10, 0), ccp(0, .5f)); m_impl->buttonMenu = CCMenu::create(); - m_impl->buttonMenu->setContentWidth(width / 2 - 20); - m_impl->buttonMenu->setLayout(RowLayout::create()->setAxisAlignment(AxisAlignment::End)); + m_impl->buttonMenu->setContentSize({ width / 2 - 55, 30 }); + m_impl->buttonMenu->setLayout(AnchorLayout::create()); this->addChildAtPosition(m_impl->buttonMenu, Anchor::Right, ccp(-10, 0), ccp(1, .5f)); this->setAnchorPoint({ .5f, .5f }); @@ -120,7 +99,7 @@ void SettingNodeV3::onDescription(CCObject*) { title.c_str(), m_impl->setting->getDescription().value_or("No description provided"), "OK", nullptr, - clamp(title.size() * 16, 240, 400) + clamp(title.size() * 16, 300, 400) )->show(); } void SettingNodeV3::onReset(CCObject*) { @@ -228,8 +207,10 @@ bool BoolSettingNodeV3::init(std::shared_ptr setting, float width m_toggle->m_offButton->getNormalImage()->setPosition(ccp(25, 25) / 2); m_toggle->m_notClickable = true; m_toggle->toggle(setting->getValue()); - this->getButtonMenu()->addChild(m_toggle); - this->getButtonMenu()->updateLayout(); + this->getButtonMenu()->addChildAtPosition(m_toggle, Anchor::Right, ccp(-10, 0)); + + this->getNameMenu()->setContentWidth(width - 50); + this->getNameMenu()->updateLayout(); this->updateState(); @@ -268,303 +249,75 @@ BoolSettingNodeV3* BoolSettingNodeV3::create(std::shared_ptr sett return nullptr; } -// IntSettingNodeV3 - -bool IntSettingNodeV3::init(std::shared_ptr setting, float width) { - if (!SettingNodeV3::init(setting, width)) - return false; - - auto bigArrowLeftSpr = CCSprite::create(); - auto bigArrowLeftSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); - auto bigArrowLeftSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); - - bigArrowLeftSpr->setContentSize(bigArrowLeftSpr1->getContentSize() + ccp(20, 0)); - bigArrowLeftSpr->addChildAtPosition(bigArrowLeftSpr2, Anchor::Center, ccp(10, 0)); - bigArrowLeftSpr->addChildAtPosition(bigArrowLeftSpr1, Anchor::Center, ccp(-10, 0)); - bigArrowLeftSpr->setScale(.45f); - - auto bigArrowLeftBtn = CCMenuItemSpriteExtra::create( - bigArrowLeftSpr, this, menu_selector(IntSettingNodeV3::onArrow) - ); - bigArrowLeftBtn->setTag(-setting->getBigArrowStepSize()); - bigArrowLeftBtn->setVisible(setting->isBigArrowsEnabled()); - this->getButtonMenu()->addChild(bigArrowLeftBtn); - - auto arrowLeftSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); - arrowLeftSpr->setScale(.65f); - auto arrowLeftBtn = CCMenuItemSpriteExtra::create( - arrowLeftSpr, this, menu_selector(IntSettingNodeV3::onArrow) - ); - arrowLeftBtn->setTag(-setting->getArrowStepSize()); - arrowLeftBtn->setVisible(setting->isArrowsEnabled()); - this->getButtonMenu()->addChild(arrowLeftBtn); - - m_input = TextInput::create(width / 2 - 70, "Num"); - m_input->setCallback([this](auto const&) { - this->markChanged(); - }); - if (!setting->isInputEnabled()) { - m_input->getBGSprite()->setVisible(false); - m_input->setEnabled(false); - } - this->getButtonMenu()->addChild(m_input); - - auto arrowRightSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); - arrowRightSpr->setFlipX(true); - arrowRightSpr->setScale(.65f); - auto arrowRightBtn = CCMenuItemSpriteExtra::create( - arrowRightSpr, this, menu_selector(IntSettingNodeV3::onArrow) - ); - arrowRightBtn->setTag(setting->getArrowStepSize()); - arrowRightBtn->setVisible(setting->isArrowsEnabled()); - this->getButtonMenu()->addChild(arrowRightBtn); - - auto bigArrowRightSpr = CCSprite::create(); - auto bigArrowRightSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); - bigArrowRightSpr1->setFlipX(true); - auto bigArrowRightSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); - bigArrowRightSpr2->setFlipX(true); - - bigArrowRightSpr->setContentSize(bigArrowRightSpr1->getContentSize() + ccp(20, 0)); - bigArrowRightSpr->addChildAtPosition(bigArrowRightSpr1, Anchor::Center, ccp(-10, 0)); - bigArrowRightSpr->addChildAtPosition(bigArrowRightSpr2, Anchor::Center, ccp(10, 0)); - bigArrowRightSpr->setScale(.45f); - - auto bigArrowRightBtn = CCMenuItemSpriteExtra::create( - bigArrowRightSpr, this, menu_selector(IntSettingNodeV3::onArrow) - ); - bigArrowRightBtn->setTag(setting->getBigArrowStepSize()); - bigArrowRightBtn->setVisible(setting->isBigArrowsEnabled()); - this->getButtonMenu()->addChild(bigArrowRightBtn); - - if (setting->isSliderEnabled()) { - this->setContentHeight(45); - this->getButtonMenu()->updateAnchoredPosition(Anchor::Right, ccp(0, 7)); - - m_slider = Slider::create(this, menu_selector(IntSettingNodeV3::onSlider)); - m_slider->setScale(.5f); - this->addChildAtPosition(m_slider, Anchor::Right, ccp(-75, -12), ccp(0, 0)); - } - - this->setCurrentValue(setting->getValue()); - this->getButtonMenu()->updateLayout(); - this->updateState(); - - return true; -} - -void IntSettingNodeV3::updateState() { - SettingNodeV3::updateState(); - m_slider->m_touchLogic->m_thumb->setValue(valueToSlider( - this->getSetting(), this->getCurrentValue() - )); - m_slider->updateBar(); -} - -int64_t IntSettingNodeV3::getCurrentValue() const { - return numFromString(m_input->getString()).value_or(this->getSetting()->getDefaultValue()); -} -void IntSettingNodeV3::setCurrentValue(int64_t value) { - m_input->setString(std::to_string(value)); - this->markChanged(); -} - -void IntSettingNodeV3::onCommit() { - this->getSetting()->setValue(this->getCurrentValue()); -} -void IntSettingNodeV3::onArrow(CCObject* sender) { - this->setCurrentValue(this->getCurrentValue() + sender->getTag()); - this->updateState(); -} -void IntSettingNodeV3::onSlider(CCObject* sender) { - this->setCurrentValue(valueFromSlider( - this->getSetting(), - m_slider->m_touchLogic->m_thumb->getValue() - )); -} - -bool IntSettingNodeV3::hasUncommittedChanges() const { - return this->getSetting()->getValue() != this->getCurrentValue(); -} -bool IntSettingNodeV3::hasNonDefaultValue() const { - return this->getSetting()->getDefaultValue() != this->getCurrentValue(); -} -void IntSettingNodeV3::onResetToDefault() { - this->setCurrentValue(this->getSetting()->getDefaultValue()); -} - -std::shared_ptr IntSettingNodeV3::getSetting() const { - return std::static_pointer_cast(SettingNodeV3::getSetting()); -} - -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; -} - -// FloatSettingNodeV3 - -bool FloatSettingNodeV3::init(std::shared_ptr setting, float width) { - if (!SettingNodeV3::init(setting, width)) - return false; - - auto bigArrowLeftSpr = CCSprite::create(); - auto bigArrowLeftSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); - auto bigArrowLeftSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); - - bigArrowLeftSpr->setContentSize(bigArrowLeftSpr1->getContentSize() + ccp(20, 0)); - bigArrowLeftSpr->addChildAtPosition(bigArrowLeftSpr2, Anchor::Center, ccp(10, 0)); - bigArrowLeftSpr->addChildAtPosition(bigArrowLeftSpr1, Anchor::Center, ccp(-10, 0)); - bigArrowLeftSpr->setScale(.45f); - - auto bigArrowLeftBtn = CCMenuItemSpriteExtra::create( - bigArrowLeftSpr, this, menu_selector(FloatSettingNodeV3::onArrow) - ); - bigArrowLeftBtn->setTag(-setting->getBigArrowStepSize()); - bigArrowLeftBtn->setVisible(setting->isBigArrowsEnabled()); - this->getButtonMenu()->addChild(bigArrowLeftBtn); - - auto arrowLeftSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); - arrowLeftSpr->setScale(.65f); - auto arrowLeftBtn = CCMenuItemSpriteExtra::create( - arrowLeftSpr, this, menu_selector(FloatSettingNodeV3::onArrow) - ); - arrowLeftBtn->setTag(-setting->getArrowStepSize()); - arrowLeftBtn->setVisible(setting->isArrowsEnabled()); - this->getButtonMenu()->addChild(arrowLeftBtn); - - m_input = TextInput::create(width / 2 - 70, "Num"); - m_input->setCallback([this](auto const&) { - this->markChanged(); - }); - if (!setting->isInputEnabled()) { - m_input->getBGSprite()->setVisible(false); - m_input->setEnabled(false); - } - this->getButtonMenu()->addChild(m_input); - - auto arrowRightSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); - arrowRightSpr->setFlipX(true); - arrowRightSpr->setScale(.65f); - auto arrowRightBtn = CCMenuItemSpriteExtra::create( - arrowRightSpr, this, menu_selector(FloatSettingNodeV3::onArrow) - ); - arrowRightBtn->setTag(setting->getArrowStepSize()); - arrowRightBtn->setVisible(setting->isArrowsEnabled()); - this->getButtonMenu()->addChild(arrowRightBtn); - - auto bigArrowRightSpr = CCSprite::create(); - auto bigArrowRightSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); - bigArrowRightSpr1->setFlipX(true); - auto bigArrowRightSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); - bigArrowRightSpr2->setFlipX(true); - - bigArrowRightSpr->setContentSize(bigArrowRightSpr1->getContentSize() + ccp(20, 0)); - bigArrowRightSpr->addChildAtPosition(bigArrowRightSpr1, Anchor::Center, ccp(-10, 0)); - bigArrowRightSpr->addChildAtPosition(bigArrowRightSpr2, Anchor::Center, ccp(10, 0)); - bigArrowRightSpr->setScale(.45f); - - auto bigArrowRightBtn = CCMenuItemSpriteExtra::create( - bigArrowRightSpr, this, menu_selector(FloatSettingNodeV3::onArrow) - ); - bigArrowRightBtn->setTag(setting->getBigArrowStepSize()); - bigArrowRightBtn->setVisible(setting->isBigArrowsEnabled()); - this->getButtonMenu()->addChild(bigArrowRightBtn); - - if (setting->isSliderEnabled()) { - this->setContentHeight(45); - this->getButtonMenu()->updateAnchoredPosition(Anchor::Right, ccp(0, 7)); - - m_slider = Slider::create(this, menu_selector(FloatSettingNodeV3::onSlider)); - m_slider->setScale(.5f); - this->addChildAtPosition(m_slider, Anchor::Right, ccp(-75, -12), ccp(0, 0)); - } - - this->setCurrentValue(setting->getValue()); - this->getButtonMenu()->updateLayout(); - this->updateState(); - - return true; -} - -void FloatSettingNodeV3::updateState() { - SettingNodeV3::updateState(); - m_slider->m_touchLogic->m_thumb->setValue(valueToSlider( - this->getSetting(), this->getCurrentValue() - )); - m_slider->updateBar(); -} - -double FloatSettingNodeV3::getCurrentValue() const { - return numFromString(m_input->getString()).value_or(this->getSetting()->getDefaultValue()); -} -void FloatSettingNodeV3::setCurrentValue(double value) { - m_input->setString(numToString(value)); -} - -void FloatSettingNodeV3::onCommit() { - this->getSetting()->setValue(this->getCurrentValue()); -} -void FloatSettingNodeV3::onArrow(CCObject* sender) { - this->setCurrentValue(this->getCurrentValue() + sender->getTag()); - this->updateState(); -} -void FloatSettingNodeV3::onSlider(CCObject* sender) { - this->setCurrentValue(valueFromSlider( - this->getSetting(), - m_slider->m_touchLogic->m_thumb->getValue() - )); -} - -bool FloatSettingNodeV3::hasUncommittedChanges() const { - return this->getSetting()->getValue() != this->getCurrentValue(); -} -bool FloatSettingNodeV3::hasNonDefaultValue() const { - return this->getSetting()->getDefaultValue() != this->getCurrentValue(); -} -void FloatSettingNodeV3::onResetToDefault() { - this->setCurrentValue(this->getSetting()->getDefaultValue()); -} - -std::shared_ptr FloatSettingNodeV3::getSetting() const { - return std::static_pointer_cast(SettingNodeV3::getSetting()); -} - -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; -} - // StringSettingNodeV3 bool StringSettingNodeV3::init(std::shared_ptr setting, float width) { if (!SettingNodeV3::init(setting, width)) return false; + + m_input = TextInput::create(width / 2 - 50, "Num"); + m_input->setCallback([this](auto const&) { + this->markChanged(); + }); + m_input->setScale(.7f); + m_input->setString(this->getSetting()->getValue()); + this->getButtonMenu()->addChildAtPosition(m_input, Anchor::Center); - // todo + if (setting->getEnumOptions()) { + m_input->getBGSprite()->setVisible(false); + m_input->setEnabled(false); + m_input->getInputNode()->m_placeholderLabel->setOpacity(255); + m_input->getInputNode()->m_placeholderLabel->setColor(ccWHITE); + + auto arrowLeftSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png"); + arrowLeftSpr->setFlipX(true); + arrowLeftSpr->setScale(.4f); + auto arrowLeftBtn = CCMenuItemSpriteExtra::create( + arrowLeftSpr, this, menu_selector(StringSettingNodeV3::onArrow) + ); + arrowLeftBtn->setTag(-1); + this->getButtonMenu()->addChildAtPosition(arrowLeftBtn, Anchor::Left, ccp(5, 0)); + + auto arrowRightSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png"); + arrowRightSpr->setScale(.4f); + auto arrowRightBtn = CCMenuItemSpriteExtra::create( + arrowRightSpr, this, menu_selector(StringSettingNodeV3::onArrow) + ); + arrowRightBtn->setTag(1); + this->getButtonMenu()->addChildAtPosition(arrowRightBtn, Anchor::Right, ccp(-5, 0)); + } + + this->updateState(); return true; } -void StringSettingNodeV3::onCommit() {} +void StringSettingNodeV3::onArrow(CCObject* sender) { + auto options = *this->getSetting()->getEnumOptions(); + auto index = ranges::indexOf(options, m_input->getString()).value_or(0); + if (sender->getTag() > 0) { + index = index < options.size() - 1 ? index + 1 : 0; + } + else { + index = index > 0 ? index - 1 : options.size() - 1; + } + m_input->setString(options.at(index)); + this->updateState(); +} + +void StringSettingNodeV3::onCommit() { + this->getSetting()->setValue(m_input->getString()); +} bool StringSettingNodeV3::hasUncommittedChanges() const { - return false; + return m_input->getString() != this->getSetting()->getValue(); } bool StringSettingNodeV3::hasNonDefaultValue() const { - return false; + return m_input->getString() != this->getSetting()->getDefaultValue(); +} +void StringSettingNodeV3::onResetToDefault() { + m_input->setString(this->getSetting()->getDefaultValue()); } -void StringSettingNodeV3::onResetToDefault() {} std::shared_ptr StringSettingNodeV3::getSetting() const { return std::static_pointer_cast(SettingNodeV3::getSetting()); @@ -732,14 +485,25 @@ UnresolvedCustomSettingNodeV3* UnresolvedCustomSettingNodeV3::create(std::shared bool LegacyCustomSettingToV3Node::init(std::shared_ptr original, float width) { if (!SettingNodeV3::init(original, width)) return false; + + this->getNameMenu()->setVisible(false); + this->getButtonMenu()->setVisible(false); m_original = original->getValue()->createNode(width); + m_original->setDelegate(this); this->setContentSize({ width, m_original->getContentHeight() }); - this->addChildAtPosition(m_original, Anchor::Center); + this->addChildAtPosition(m_original, Anchor::BottomLeft, ccp(0, 0), ccp(0, 0)); return true; } +void LegacyCustomSettingToV3Node::settingValueChanged(SettingNode*) { + SettingNodeValueChangeEventV3(false).post(); +} +void LegacyCustomSettingToV3Node::settingValueCommitted(SettingNode*) { + SettingNodeValueChangeEventV3(true).post(); +} + void LegacyCustomSettingToV3Node::onCommit() { m_original->commit(); } diff --git a/loader/src/loader/SettingNodeV3.hpp b/loader/src/loader/SettingNodeV3.hpp index 419522ba..09458b9a 100644 --- a/loader/src/loader/SettingNodeV3.hpp +++ b/loader/src/loader/SettingNodeV3.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include using namespace geode::prelude; @@ -44,64 +45,182 @@ public: std::shared_ptr getSetting() const; }; -class IntSettingNodeV3 : public SettingNodeV3 { +template +class NumberSettingNodeV3 : public SettingNodeV3 { protected: + using ValueType = typename S::ValueType; + TextInput* m_input; Slider* m_slider; - bool init(std::shared_ptr setting, float width); + float valueToSlider(ValueType value) { + auto min = this->getSetting()->getMinValue().value_or(-100); + auto max = this->getSetting()->getMaxValue().value_or(+100); + auto range = max - min; + return static_cast(clamp(static_cast(value - min) / range, 0.0, 1.0)); + } + ValueType valueFromSlider(float num) { + auto min = this->getSetting()->getMinValue().value_or(-100); + auto max = this->getSetting()->getMaxValue().value_or(+100); + auto range = max - min; + auto value = static_cast(num * range + min); + if (auto step = this->getSetting()->getSliderSnap()) { + value = static_cast(round(value / *step) * (*step)); + } + return value; + } - void updateState() override; + bool init(std::shared_ptr setting, float width) { + if (!SettingNodeV3::init(setting, width)) + return false; - void onCommit() override; - void onArrow(CCObject* sender); - void onSlider(CCObject*); + auto bigArrowLeftSpr = CCSprite::create(); + auto bigArrowLeftSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); + auto bigArrowLeftSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); + + bigArrowLeftSpr->setContentSize(bigArrowLeftSpr1->getContentSize() + ccp(20, 0)); + bigArrowLeftSpr->addChildAtPosition(bigArrowLeftSpr2, Anchor::Center, ccp(10, 0)); + bigArrowLeftSpr->addChildAtPosition(bigArrowLeftSpr1, Anchor::Center, ccp(-10, 0)); + bigArrowLeftSpr->setScale(.3f); - int64_t getCurrentValue() const; - void setCurrentValue(int64_t value); + auto bigArrowLeftBtn = CCMenuItemSpriteExtra::create( + bigArrowLeftSpr, this, menu_selector(NumberSettingNodeV3::onArrow) + ); + bigArrowLeftBtn->setTag(-setting->getBigArrowStepSize()); + bigArrowLeftBtn->setVisible(setting->isBigArrowsEnabled()); + this->getButtonMenu()->addChildAtPosition(bigArrowLeftBtn, Anchor::Left, ccp(5, 0)); + + auto arrowLeftSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); + arrowLeftSpr->setScale(.5f); + auto arrowLeftBtn = CCMenuItemSpriteExtra::create( + arrowLeftSpr, this, menu_selector(NumberSettingNodeV3::onArrow) + ); + arrowLeftBtn->setTag(-setting->getArrowStepSize()); + arrowLeftBtn->setVisible(setting->isArrowsEnabled()); + this->getButtonMenu()->addChildAtPosition(arrowLeftBtn, Anchor::Left, ccp(22, 0)); + + m_input = TextInput::create(this->getButtonMenu()->getContentWidth() - 40, "Num"); + m_input->setScale(.7f); + m_input->setCallback([this](auto const&) { + this->markChanged(); + }); + if (!setting->isInputEnabled()) { + m_input->getBGSprite()->setVisible(false); + m_input->setEnabled(false); + m_input->getInputNode()->m_placeholderLabel->setOpacity(255); + m_input->getInputNode()->m_placeholderLabel->setColor(ccWHITE); + } + this->getButtonMenu()->addChildAtPosition(m_input, Anchor::Center); + + auto arrowRightSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); + arrowRightSpr->setFlipX(true); + arrowRightSpr->setScale(.5f); + auto arrowRightBtn = CCMenuItemSpriteExtra::create( + arrowRightSpr, this, menu_selector(NumberSettingNodeV3::onArrow) + ); + arrowRightBtn->setTag(setting->getArrowStepSize()); + arrowRightBtn->setVisible(setting->isArrowsEnabled()); + this->getButtonMenu()->addChildAtPosition(arrowRightBtn, Anchor::Right, ccp(-22, 0)); + + auto bigArrowRightSpr = CCSprite::create(); + auto bigArrowRightSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); + bigArrowRightSpr1->setFlipX(true); + auto bigArrowRightSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); + bigArrowRightSpr2->setFlipX(true); + + bigArrowRightSpr->setContentSize(bigArrowRightSpr1->getContentSize() + ccp(20, 0)); + bigArrowRightSpr->addChildAtPosition(bigArrowRightSpr1, Anchor::Center, ccp(-10, 0)); + bigArrowRightSpr->addChildAtPosition(bigArrowRightSpr2, Anchor::Center, ccp(10, 0)); + bigArrowRightSpr->setScale(.3f); + + auto bigArrowRightBtn = CCMenuItemSpriteExtra::create( + bigArrowRightSpr, this, menu_selector(NumberSettingNodeV3::onArrow) + ); + bigArrowRightBtn->setTag(setting->getBigArrowStepSize()); + bigArrowRightBtn->setVisible(setting->isBigArrowsEnabled()); + this->getButtonMenu()->addChildAtPosition(bigArrowRightBtn, Anchor::Right, ccp(-5, 0)); + + if (setting->isSliderEnabled()) { + this->setContentHeight(45); + this->getButtonMenu()->updateAnchoredPosition(Anchor::Right, ccp(-10, 7)); + + m_slider = Slider::create(this, menu_selector(NumberSettingNodeV3::onSlider)); + m_slider->setScale(.5f); + this->getButtonMenu()->addChildAtPosition(m_slider, Anchor::Center, ccp(0, -20), ccp(0, 0)); + } + + this->setCurrentValue(setting->getValue()); + this->updateState(); + + return true; + } + + void updateState() override { + SettingNodeV3::updateState(); + if (m_slider) { + m_slider->m_touchLogic->m_thumb->setValue(this->valueToSlider(this->getCurrentValue())); + m_slider->updateBar(); + } + } + + void onCommit() override { + this->getSetting()->setValue(this->getCurrentValue()); + } + void onArrow(CCObject* sender) { + this->setCurrentValue(this->getCurrentValue() + sender->getTag()); + } + void onSlider(CCObject*) { + this->setCurrentValue(this->valueFromSlider(m_slider->m_touchLogic->m_thumb->getValue())); + } + + ValueType getCurrentValue() const { + return numFromString(m_input->getString()) + .value_or(this->getSetting()->getDefaultValue()); + } + void setCurrentValue(ValueType value) { + m_input->setString(numToString(value)); + this->markChanged(); + } public: - static IntSettingNodeV3* create(std::shared_ptr setting, float width); + static NumberSettingNodeV3* create(std::shared_ptr setting, float width) { + auto ret = new NumberSettingNodeV3(); + if (ret && ret->init(setting, width)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; + } - bool hasUncommittedChanges() const override; - bool hasNonDefaultValue() const override; - void onResetToDefault() override; + bool hasUncommittedChanges() const override { + return this->getSetting()->getValue() != this->getCurrentValue(); + } + bool hasNonDefaultValue() const override { + return this->getSetting()->getDefaultValue() != this->getCurrentValue(); + } + void onResetToDefault() override { + this->setCurrentValue(this->getSetting()->getDefaultValue()); + } - std::shared_ptr getSetting() const; + std::shared_ptr getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); + } }; -class FloatSettingNodeV3 : public SettingNodeV3 { -protected: - TextInput* m_input; - Slider* m_slider; - - bool init(std::shared_ptr setting, float width); - - void updateState() override; - - void onCommit() override; - void onArrow(CCObject* sender); - void onSlider(CCObject*); - - double getCurrentValue() const; - void setCurrentValue(double value); - -public: - static FloatSettingNodeV3* create(std::shared_ptr setting, float width); - - bool hasUncommittedChanges() const override; - bool hasNonDefaultValue() const override; - void onResetToDefault() override; - - std::shared_ptr getSetting() const; -}; +using IntSettingNodeV3 = NumberSettingNodeV3; +using FloatSettingNodeV3 = NumberSettingNodeV3; class StringSettingNodeV3 : public SettingNodeV3 { protected: + TextInput* m_input; + bool init(std::shared_ptr setting, float width); void onCommit() override; + void onArrow(CCObject* sender); + public: static StringSettingNodeV3* create(std::shared_ptr setting, float width); @@ -176,9 +295,10 @@ public: std::shared_ptr getSetting() const; }; -// If these classes do get exposed in headers, this SHOULD NOT BE EXPOSED!!!!!! DO NOT DO THAT!!!! +// If these classes do get exposed in headers, +// LegacyCustomSettingToV3Node SHOULD NOT BE EXPOSED!!!!!! DO NOT DO THAT!!!! -class LegacyCustomSettingToV3Node : public SettingNodeV3 { +class LegacyCustomSettingToV3Node : public SettingNodeV3, public SettingNodeDelegate { protected: SettingNode* m_original; @@ -186,6 +306,9 @@ protected: void onCommit() override; + void settingValueChanged(SettingNode*) override; + void settingValueCommitted(SettingNode*) override; + public: static LegacyCustomSettingToV3Node* create(std::shared_ptr original, float width); diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index d44574a7..6c7390f1 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -364,11 +364,11 @@ public: struct { // 0 means not enabled - size_t arrowStepSize; - size_t bigArrowStepSize; - bool sliderEnabled; + size_t arrowStepSize = 1; + size_t bigArrowStepSize = 5; + bool sliderEnabled = true; std::optional sliderSnap; - bool textInputEnabled; + bool textInputEnabled = true; } controls; }; @@ -521,6 +521,9 @@ Result> StringSettingV3::parse(std::string cons root.has("match").into(ret->m_impl->match); root.has("filter").into(ret->m_impl->filter); root.has("one-of").into(ret->m_impl->oneOf); + if (ret->m_impl->oneOf && ret->m_impl->oneOf->empty()) { + return Err("Setting '{}' in mod {} - \"one-of\" may not be empty!", key, modID); + } root.checkUnknownKeys(); return root.ok(ret); From 8ce5700aad41e5133cb7353a97ba66c319a4f91b Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Fri, 23 Aug 2024 12:38:15 +0300 Subject: [PATCH 16/54] fix android & mac builds --- loader/include/Geode/loader/SettingV3.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index 03506ca5..d75665b2 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -92,8 +92,8 @@ namespace geode { protected: virtual T& getValueMut() const = 0; - template - void parseDefaultValue(JsonExpectedValue& json, T& defaultValue) { + template + void parseDefaultValue(JsonExpectedValue& json, D& defaultValue) { auto value = json.needs("default"); // Check if this is a platform-specific default value if (value.isObject() && value.has(GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH)) { From 36853b785c05e7d1356d784e759cdbcc104d70e4 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Fri, 23 Aug 2024 22:55:43 +0300 Subject: [PATCH 17/54] fix crash due to not checking if setting is loaded --- loader/src/loader/ModSettingsManager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/loader/src/loader/ModSettingsManager.cpp b/loader/src/loader/ModSettingsManager.cpp index b64fc3a6..96412fec 100644 --- a/loader/src/loader/ModSettingsManager.cpp +++ b/loader/src/loader/ModSettingsManager.cpp @@ -178,6 +178,9 @@ Result<> ModSettingsManager::load(matjson::Value const& json) { } void ModSettingsManager::save(matjson::Value& json) { for (auto& [key, sett] : m_impl->settings) { + if (!sett.v3) { + continue; + } // Store the value in an intermediary so if `save` fails the existing // value loaded from disk isn't overwritten matjson::Value value; From 4ec449ed4162e88fb5b3231793fc055912ee3c56 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Fri, 23 Aug 2024 22:56:00 +0300 Subject: [PATCH 18/54] deprecate old settings functions --- loader/include/Geode/loader/Mod.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/loader/include/Geode/loader/Mod.hpp b/loader/include/Geode/loader/Mod.hpp index a5f19187..0cd436e7 100644 --- a/loader/include/Geode/loader/Mod.hpp +++ b/loader/include/Geode/loader/Mod.hpp @@ -176,7 +176,9 @@ namespace geode { bool hasSettings() const; std::vector getSettingKeys() const; bool hasSetting(std::string_view const key) const; + [[deprecated("Use Mod::getSettingV3")]] std::optional getSettingDefinition(std::string_view const key) const; + [[deprecated("Use Mod::getSettingV3")]] SettingValue* getSetting(std::string_view const key) const; std::shared_ptr getSettingV3(std::string_view const key) const; @@ -190,6 +192,7 @@ namespace geode { * @param value The SettingValue class that shall handle this setting * @see addCustomSetting */ + [[deprecated("Use Mod::registerCustomSettingType")]] void registerCustomSetting(std::string_view const key, std::unique_ptr value); /** * Register a custom setting's value class. The new SettingValue class @@ -204,6 +207,7 @@ namespace geode { * } */ template + [[deprecated("Use Mod::registerCustomSettingType")]] void addCustomSetting(std::string_view const key, V const& value) { this->registerCustomSetting(key, std::make_unique(std::string(key), this->getID(), value)); } From 908ac44a0baaf5b4dafeebba2741b0b63c8cf5c4 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Fri, 23 Aug 2024 22:56:17 +0300 Subject: [PATCH 19/54] add default value option to JsonExpectedValue::get --- loader/include/Geode/utils/JsonValidation.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loader/include/Geode/utils/JsonValidation.hpp b/loader/include/Geode/utils/JsonValidation.hpp index b17594a0..b9ce98c6 100644 --- a/loader/include/Geode/utils/JsonValidation.hpp +++ b/loader/include/Geode/utils/JsonValidation.hpp @@ -385,11 +385,11 @@ namespace geode { // -- Dealing with values -- template - T get() { + T get(T const& defaultValue = T()) { if (auto v = this->template tryGet()) { return *std::move(v); } - return T(); + return defaultValue; } template JsonExpectedValue& into(T& value) { From 35d16eb44eb81771d91498cc86a482064a42e19d Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Fri, 23 Aug 2024 23:35:29 +0300 Subject: [PATCH 20/54] improve jsonvalidation type errors --- loader/include/Geode/utils/JsonValidation.hpp | 14 +++++++++-- loader/src/utils/JsonValidation.cpp | 24 +++++++++---------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/loader/include/Geode/utils/JsonValidation.hpp b/loader/include/Geode/utils/JsonValidation.hpp index b9ce98c6..0f6ddea5 100644 --- a/loader/include/Geode/utils/JsonValidation.hpp +++ b/loader/include/Geode/utils/JsonValidation.hpp @@ -305,6 +305,8 @@ namespace geode { JsonExpectedValue(); JsonExpectedValue(Impl* from, matjson::Value& scope, std::string_view key); + static const char* matJsonTypeToString(matjson::Type ty); + bool hasError() const; void setError(std::string_view error); @@ -323,11 +325,19 @@ namespace geode { } else { try { - return this->getJSONRef().template as(); + if (this->getJSONRef().template is()) { + return this->getJSONRef().template as(); + } + else { + this->setError( + "unexpected type {}", + this->matJsonTypeToString(this->getJSONRef().type()) + ); + } } // matjson can throw variant exceptions too so you need to do this catch(std::exception const& e) { - this->setError("invalid json type: {}", e); + this->setError("unable to parse json: {}", e); } } return std::nullopt; diff --git a/loader/src/utils/JsonValidation.cpp b/loader/src/utils/JsonValidation.cpp index 3cd4489b..b211ae89 100644 --- a/loader/src/utils/JsonValidation.cpp +++ b/loader/src/utils/JsonValidation.cpp @@ -161,18 +161,6 @@ JsonMaybeValue JsonChecker::root(std::string const& hierarchy) { return JsonMaybeValue(*this, m_json, hierarchy, true); } -static const char* matJsonTypeToString(matjson::Type ty) { - switch (ty) { - case matjson::Type::Null: return "null"; - case matjson::Type::Bool: return "bool"; - case matjson::Type::Number: return "number"; - case matjson::Type::String: return "string"; - case matjson::Type::Array: return "array"; - case matjson::Type::Object: return "object"; - default: return "unknown"; - } -} - // This is used for null JsonExpectedValues (for example when doing // `json.has("key")` where "key" doesn't exist) static matjson::Value NULL_SCOPED_VALUE = nullptr; @@ -231,6 +219,18 @@ JsonExpectedValue::~JsonExpectedValue() {} JsonExpectedValue::JsonExpectedValue(JsonExpectedValue&&) = default; JsonExpectedValue& JsonExpectedValue::operator=(JsonExpectedValue&&) = default; +const char* JsonExpectedValue::matJsonTypeToString(matjson::Type ty) { + switch (ty) { + case matjson::Type::Null: return "null"; + case matjson::Type::Bool: return "bool"; + case matjson::Type::Number: return "number"; + case matjson::Type::String: return "string"; + case matjson::Type::Array: return "array"; + case matjson::Type::Object: return "object"; + default: return "unknown"; + } +} + matjson::Value const& JsonExpectedValue::getJSONRef() const { return m_impl->scope; } From 3f3b52104f2bd53d1030e6c8a73286f0e6ff5651 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Fri, 23 Aug 2024 23:35:33 +0300 Subject: [PATCH 21/54] add file sprite --- loader/resources/file.png | Bin 0 -> 764 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 loader/resources/file.png diff --git a/loader/resources/file.png b/loader/resources/file.png new file mode 100644 index 0000000000000000000000000000000000000000..a1fc8d29adcd72d1ca1826e0fc7c621ed1230268 GIT binary patch literal 764 zcmeAS@N?(olHy`uVBq!ia0vp^aX?(g!3HE9byZA(6k~CayA#8@b22Z19F}xPUq=Rp zjs4tz5?O(K&H|6fVg?4jgCNYfV`BDO1_q{yo-U3d6>)Fx8s;4`kZ60jgq@L7?})0v z_2lOga~+kWla?pXnlE)lK<2>4&li8ys?M@^W|!OPw~9ye^)9_U?bAiKWzy`^BTaK{ zw9M7t9+q97+LQNo_Ivxn;!9BU$y$lkMxQQ@283E&#_zZefpE8Z0&p9 zcC*iN?SB1}Uq1f#-+R9ICaw8%F7~Xd^ZmTH{SWKSXZoo8*mm_!EMM*FC;Hb8>|A;G zX?0j=sL%4tpYH^3$vwXNQE~R0sr-M|A3MVSZ`sBVjr%W4{5VkU)yrSwulcdDK0vGH z!F&x?yAS?i((*sVyE^&*bRRv$Znprairq{^{s$xPLH4?Yg+C6sYp}@w@Yvfm-}X<9 zUHRn?aR<-;yEtF;_s>tp{eqh#KeoNj-hR7x-zOk*+C!;N)>E#^<=^(ae@cGy8@+Y! zo8~`_J!}0Sz1n)^vGZd0f*z%phA%$x{lD`y?KsH~iyFjP_h>wDWz^@Y2t3fu6elv6 zg!KQsEGZIh-F7qQ&Xu#=`TJSwD*FFaZI}9Rxa!#1-*PLyR=rh|x|FoBAlO=O-J(4o zjz7NWD{?DqYf-Sd{-2_qEcFg;brv#rBDddL%l2o3+(Pr(TSu?Ph5!F>^_9rl*x2iz z`=7aAo%3aX(A=a_x9t!2Ty3!T3NLxR>v;40En>T_?%L5be+l2Nt2 Date: Sat, 24 Aug 2024 00:33:38 +0300 Subject: [PATCH 22/54] path settings done --- loader/include/Geode/loader/SettingV3.hpp | 12 +- loader/src/loader/SettingNodeV3.cpp | 91 +++++++++++-- loader/src/loader/SettingNodeV3.hpp | 77 ++++++++--- loader/src/loader/SettingV3.cpp | 156 ++++++++++++++-------- 4 files changed, 252 insertions(+), 84 deletions(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index d75665b2..8f17c193 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -267,8 +267,8 @@ namespace geode { bool isArrowsEnabled() const; bool isBigArrowsEnabled() const; - size_t getArrowStepSize() const; - size_t getBigArrowStepSize() const; + double getArrowStepSize() const; + double getBigArrowStepSize() const; bool isSliderEnabled() const; std::optional getSliderSnap() const; bool isInputEnabled() const; @@ -328,9 +328,17 @@ namespace geode { FileSettingV3(PrivateMarker); static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); + enum class FileType { + Any = 0, + File = 1, + Folder = 2, + }; + std::filesystem::path getDefaultValue() const override; Result<> isValid(std::filesystem::path const& value) const override; + FileType getFileType() const; + std::optional> getFilters() const; bool load(matjson::Value const& json) override; diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index 58973ef9..c9b80726 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -73,6 +73,7 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { m_impl->nameMenu->addChild(m_impl->resetButton); m_impl->nameMenu->setLayout(RowLayout::create()->setAxisAlignment(AxisAlignment::Start)); + m_impl->nameMenu->getLayout()->ignoreInvisibleChildren(true); this->addChildAtPosition(m_impl->nameMenu, Anchor::Left, ccp(10, 0), ccp(0, .5f)); m_impl->buttonMenu = CCMenu::create(); @@ -197,6 +198,10 @@ TitleSettingNodeV3* TitleSettingNodeV3::create(std::shared_ptr s bool BoolSettingNodeV3::init(std::shared_ptr setting, float width) { if (!SettingNodeV3::init(setting, width)) return false; + + this->getButtonMenu()->setContentWidth(20); + this->getNameMenu()->setContentWidth(width - 50); + this->getNameMenu()->updateLayout(); m_toggle = CCMenuItemToggler::createWithStandardSprites( this, menu_selector(BoolSettingNodeV3::onToggle), .55f @@ -209,9 +214,6 @@ bool BoolSettingNodeV3::init(std::shared_ptr setting, float width m_toggle->toggle(setting->getValue()); this->getButtonMenu()->addChildAtPosition(m_toggle, Anchor::Right, ccp(-10, 0)); - this->getNameMenu()->setContentWidth(width - 50); - this->getNameMenu()->updateLayout(); - this->updateState(); return true; @@ -255,7 +257,7 @@ bool StringSettingNodeV3::init(std::shared_ptr setting, float w if (!SettingNodeV3::init(setting, width)) return false; - m_input = TextInput::create(width / 2 - 50, "Num"); + m_input = TextInput::create(setting->getEnumOptions() ? width / 2 - 50 : width / 2, "Text"); m_input->setCallback([this](auto const&) { this->markChanged(); }); @@ -339,20 +341,91 @@ bool FileSettingNodeV3::init(std::shared_ptr setting, float width if (!SettingNodeV3::init(setting, width)) return false; - // todo + m_path = setting->getValue(); + + auto labelBG = extension::CCScale9Sprite::create("square02b_001.png", { 0, 0, 80, 80 }); + labelBG->setScale(.25f); + labelBG->setColor({ 0, 0, 0 }); + labelBG->setOpacity(90); + labelBG->setContentSize({ 400, 80 }); + this->getButtonMenu()->addChildAtPosition(labelBG, Anchor::Center, ccp(-15, 0)); + + m_fileIcon = CCSprite::create(); + this->getButtonMenu()->addChildAtPosition(m_fileIcon, Anchor::Left, ccp(3, 0)); + + m_nameLabel = CCLabelBMFont::create("", "bigFont.fnt"); + this->getButtonMenu()->addChildAtPosition(m_nameLabel, Anchor::Left, ccp(11, 0), ccp(0, .5f)); + + auto selectSpr = CCSprite::createWithSpriteFrameName("GJ_plus2Btn_001.png"); + selectSpr->setScale(.75f); + auto selectBtn = CCMenuItemSpriteExtra::create( + selectSpr, this, menu_selector(FileSettingNodeV3::onPickFile) + ); + this->getButtonMenu()->addChildAtPosition(selectBtn, Anchor::Right, ccp(-5, 0)); + + this->updateState(); return true; } -void FileSettingNodeV3::onCommit() {} +void FileSettingNodeV3::updateState() { + SettingNodeV3::updateState(); + auto ty = this->getSetting()->getFileType(); + if (ty == FileSettingV3::FileType::Any) { + ty = std::filesystem::is_directory(m_path) ? + FileSettingV3::FileType::Folder : + FileSettingV3::FileType::File; + } + m_fileIcon->setDisplayFrame(CCSpriteFrameCache::get()->spriteFrameByName( + ty == FileSettingV3::FileType::File ? "file.png"_spr : "folderIcon_001.png" + )); + limitNodeSize(m_fileIcon, ccp(10, 10), 1.f, .1f); + m_nameLabel->setString(m_path.filename().string().c_str()); + m_nameLabel->limitLabelWidth(75, .4f, .1f); +} + +void FileSettingNodeV3::onCommit() { + this->getSetting()->setValue(m_path); +} + +void FileSettingNodeV3::onPickFile(CCObject*) { + m_pickListener.bind([this](auto* event) { + auto value = event->getValue(); + if (!value) { + return; + } + if (value->isOk()) { + m_path = value->unwrap().string(); + this->markChanged(); + } + else { + FLAlertLayer::create( + "Failed", + fmt::format("Failed to pick file: {}", value->unwrapErr()), + "Ok" + )->show(); + } + }); + m_pickListener.setFilter(file::pick( + this->getSetting()->getFileType() == FileSettingV3::FileType::Folder ? + file::PickMode::OpenFolder : + file::PickMode::OpenFile, + { + dirs::getGameDir(), + this->getSetting()->getFilters().value_or(std::vector()) + } + )); +} bool FileSettingNodeV3::hasUncommittedChanges() const { - return false; + return m_path != this->getSetting()->getValue(); } bool FileSettingNodeV3::hasNonDefaultValue() const { - return false; + return m_path != this->getSetting()->getDefaultValue(); +} +void FileSettingNodeV3::onResetToDefault() { + m_path = this->getSetting()->getDefaultValue(); } -void FileSettingNodeV3::onResetToDefault() {} std::shared_ptr FileSettingNodeV3::getSetting() const { return std::static_pointer_cast(SettingNodeV3::getSetting()); diff --git a/loader/src/loader/SettingNodeV3.hpp b/loader/src/loader/SettingNodeV3.hpp index 09458b9a..34e6e32e 100644 --- a/loader/src/loader/SettingNodeV3.hpp +++ b/loader/src/loader/SettingNodeV3.hpp @@ -52,6 +52,10 @@ protected: TextInput* m_input; Slider* m_slider; + CCMenuItemSpriteExtra* m_arrowLeftBtn; + CCMenuItemSpriteExtra* m_bigArrowLeftBtn; + CCMenuItemSpriteExtra* m_arrowRightBtn; + CCMenuItemSpriteExtra* m_bigArrowRightBtn; float valueToSlider(ValueType value) { auto min = this->getSetting()->getMinValue().value_or(-100); @@ -75,6 +79,8 @@ protected: return false; auto bigArrowLeftSpr = CCSprite::create(); + bigArrowLeftSpr->setCascadeColorEnabled(true); + bigArrowLeftSpr->setCascadeOpacityEnabled(true); auto bigArrowLeftSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); auto bigArrowLeftSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); @@ -83,21 +89,21 @@ protected: bigArrowLeftSpr->addChildAtPosition(bigArrowLeftSpr1, Anchor::Center, ccp(-10, 0)); bigArrowLeftSpr->setScale(.3f); - auto bigArrowLeftBtn = CCMenuItemSpriteExtra::create( + m_bigArrowLeftBtn = CCMenuItemSpriteExtra::create( bigArrowLeftSpr, this, menu_selector(NumberSettingNodeV3::onArrow) ); - bigArrowLeftBtn->setTag(-setting->getBigArrowStepSize()); - bigArrowLeftBtn->setVisible(setting->isBigArrowsEnabled()); - this->getButtonMenu()->addChildAtPosition(bigArrowLeftBtn, Anchor::Left, ccp(5, 0)); + m_bigArrowLeftBtn->setUserObject(ObjWrapper::create(-setting->getBigArrowStepSize())); + m_bigArrowLeftBtn->setVisible(setting->isBigArrowsEnabled()); + this->getButtonMenu()->addChildAtPosition(m_bigArrowLeftBtn, Anchor::Left, ccp(5, 0)); auto arrowLeftSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); arrowLeftSpr->setScale(.5f); - auto arrowLeftBtn = CCMenuItemSpriteExtra::create( + m_arrowLeftBtn = CCMenuItemSpriteExtra::create( arrowLeftSpr, this, menu_selector(NumberSettingNodeV3::onArrow) ); - arrowLeftBtn->setTag(-setting->getArrowStepSize()); - arrowLeftBtn->setVisible(setting->isArrowsEnabled()); - this->getButtonMenu()->addChildAtPosition(arrowLeftBtn, Anchor::Left, ccp(22, 0)); + m_arrowLeftBtn->setUserObject(ObjWrapper::create(-setting->getArrowStepSize())); + m_arrowLeftBtn->setVisible(setting->isArrowsEnabled()); + this->getButtonMenu()->addChildAtPosition(m_arrowLeftBtn, Anchor::Left, ccp(22, 0)); m_input = TextInput::create(this->getButtonMenu()->getContentWidth() - 40, "Num"); m_input->setScale(.7f); @@ -115,14 +121,16 @@ protected: auto arrowRightSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); arrowRightSpr->setFlipX(true); arrowRightSpr->setScale(.5f); - auto arrowRightBtn = CCMenuItemSpriteExtra::create( + m_arrowRightBtn = CCMenuItemSpriteExtra::create( arrowRightSpr, this, menu_selector(NumberSettingNodeV3::onArrow) ); - arrowRightBtn->setTag(setting->getArrowStepSize()); - arrowRightBtn->setVisible(setting->isArrowsEnabled()); - this->getButtonMenu()->addChildAtPosition(arrowRightBtn, Anchor::Right, ccp(-22, 0)); + m_arrowRightBtn->setUserObject(ObjWrapper::create(setting->getArrowStepSize())); + m_arrowRightBtn->setVisible(setting->isArrowsEnabled()); + this->getButtonMenu()->addChildAtPosition(m_arrowRightBtn, Anchor::Right, ccp(-22, 0)); auto bigArrowRightSpr = CCSprite::create(); + bigArrowRightSpr->setCascadeColorEnabled(true); + bigArrowRightSpr->setCascadeOpacityEnabled(true); auto bigArrowRightSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); bigArrowRightSpr1->setFlipX(true); auto bigArrowRightSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); @@ -133,12 +141,12 @@ protected: bigArrowRightSpr->addChildAtPosition(bigArrowRightSpr2, Anchor::Center, ccp(10, 0)); bigArrowRightSpr->setScale(.3f); - auto bigArrowRightBtn = CCMenuItemSpriteExtra::create( + m_bigArrowRightBtn = CCMenuItemSpriteExtra::create( bigArrowRightSpr, this, menu_selector(NumberSettingNodeV3::onArrow) ); - bigArrowRightBtn->setTag(setting->getBigArrowStepSize()); - bigArrowRightBtn->setVisible(setting->isBigArrowsEnabled()); - this->getButtonMenu()->addChildAtPosition(bigArrowRightBtn, Anchor::Right, ccp(-5, 0)); + m_bigArrowRightBtn->setUserObject(ObjWrapper::create(setting->getBigArrowStepSize())); + m_bigArrowRightBtn->setVisible(setting->isBigArrowsEnabled()); + this->getButtonMenu()->addChildAtPosition(m_bigArrowRightBtn, Anchor::Right, ccp(-5, 0)); if (setting->isSliderEnabled()) { this->setContentHeight(45); @@ -161,13 +169,40 @@ protected: m_slider->m_touchLogic->m_thumb->setValue(this->valueToSlider(this->getCurrentValue())); m_slider->updateBar(); } + if (auto min = this->getSetting()->getMinValue()) { + auto enable = this->getCurrentValue() > *min; + m_arrowLeftBtn->setEnabled(enable); + m_bigArrowLeftBtn->setEnabled(enable); + static_cast(m_arrowLeftBtn->getNormalImage())->setOpacity(enable ? 255 : 155); + static_cast(m_arrowLeftBtn->getNormalImage())->setColor(enable ? ccWHITE : ccGRAY); + static_cast(m_bigArrowLeftBtn->getNormalImage())->setOpacity(enable ? 255 : 155); + static_cast(m_bigArrowLeftBtn->getNormalImage())->setColor(enable ? ccWHITE : ccGRAY); + } + if (auto max = this->getSetting()->getMaxValue()) { + auto enable = this->getCurrentValue() < *max; + m_arrowRightBtn->setEnabled(enable); + m_bigArrowRightBtn->setEnabled(enable); + static_cast(m_arrowRightBtn->getNormalImage())->setOpacity(enable ? 255 : 155); + static_cast(m_arrowRightBtn->getNormalImage())->setColor(enable ? ccWHITE : ccGRAY); + static_cast(m_bigArrowRightBtn->getNormalImage())->setOpacity(enable ? 255 : 155); + static_cast(m_bigArrowRightBtn->getNormalImage())->setColor(enable ? ccWHITE : ccGRAY); + } } void onCommit() override { this->getSetting()->setValue(this->getCurrentValue()); } void onArrow(CCObject* sender) { - this->setCurrentValue(this->getCurrentValue() + sender->getTag()); + auto value = this->getCurrentValue() + static_cast*>( + static_cast(sender)->getUserObject() + )->getValue(); + if (auto min = this->getSetting()->getMinValue()) { + value = std::max(*min, value); + } + if (auto max = this->getSetting()->getMaxValue()) { + value = std::min(*max, value); + } + this->setCurrentValue(value); } void onSlider(CCObject*) { this->setCurrentValue(this->valueFromSlider(m_slider->m_touchLogic->m_thumb->getValue())); @@ -233,9 +268,17 @@ public: class FileSettingNodeV3 : public SettingNodeV3 { protected: + CCSprite* m_fileIcon; + std::filesystem::path m_path; + CCLabelBMFont* m_nameLabel; + EventListener>> m_pickListener; + bool init(std::shared_ptr setting, float width); + void updateState() override; + void onCommit() override; + void onPickFile(CCObject*); public: static FileSettingNodeV3* create(std::shared_ptr setting, float width); diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index 6c7390f1..b3b096c5 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -26,6 +26,7 @@ Result<> SettingV3::parseSharedProperties(std::string const& key, std::string co void SettingV3::parseSharedProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value, bool onlyNameAndDesc) { this->init(key, modID); value.needs("type"); + value.has("platforms"); value.has("name").into(m_impl->name); value.has("description").into(m_impl->description); if (!onlyNameAndDesc) { @@ -239,31 +240,39 @@ Result> IntSettingV3::parse(std::string const& key root.has("min").into(ret->m_impl->minValue); root.has("max").into(ret->m_impl->maxValue); if (auto controls = root.has("control")) { + controls.has("arrows"); + controls.has("big-arrows"); controls.has("arrow-step").into(ret->m_impl->controls.arrowStepSize); - if (!controls.has("arrows").template get()) { - ret->m_impl->controls.arrowStepSize = 0; - } controls.has("big-arrow-step").into(ret->m_impl->controls.bigArrowStepSize); - if (!controls.has("big-arrows").template get()) { - ret->m_impl->controls.bigArrowStepSize = 0; - } controls.has("slider").into(ret->m_impl->controls.sliderEnabled); controls.has("slider-step").into(ret->m_impl->controls.sliderSnap); controls.has("input").into(ret->m_impl->controls.textInputEnabled); - // Without "min" or "max" slider makes no sense - if (!ret->m_impl->minValue || !ret->m_impl->maxValue) { - if (ret->m_impl->controls.sliderEnabled) { - log::warn( - "Setting '{}' has \"controls.slider\" enabled but doesn't " - "have both \"min\" and \"max\" defined - the slider has " - "been force-disabled!", - key - ); - } - ret->m_impl->controls.sliderEnabled = false; - } controls.checkUnknownKeys(); } + + // Disable arrows if they aren't enabled + // This silly code is because step size being 0 is what defines if they are enabled + + // Small arrows are enabled by default + if (!root.has("control").has("arrows").template get(true)) { + ret->m_impl->controls.arrowStepSize = 0; + } + if (!root.has("control").has("big-arrows").template get()) { + ret->m_impl->controls.bigArrowStepSize = 0; + } + + // Without "min" or "max" slider makes no sense + if (!ret->m_impl->minValue || !ret->m_impl->maxValue) { + if (ret->m_impl->controls.sliderEnabled && root.has("control").has("slider")) { + log::warn( + "Setting '{}' has \"controls.slider\" enabled but doesn't " + "have both \"min\" and \"max\" defined - the slider has " + "been force-disabled!", + key + ); + } + ret->m_impl->controls.sliderEnabled = false; + } root.checkUnknownKeys(); return root.ok(ret); @@ -279,10 +288,10 @@ int64_t IntSettingV3::getDefaultValue() const { } Result<> IntSettingV3::isValid(int64_t value) const { if (m_impl->minValue && value < *m_impl->minValue) { - return Err("value must be at least {}", *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 Err("Value must be at most {}", *m_impl->maxValue); } return Ok(); } @@ -364,8 +373,8 @@ public: struct { // 0 means not enabled - size_t arrowStepSize = 1; - size_t bigArrowStepSize = 5; + double arrowStepSize = 1; + double bigArrowStepSize = 5; bool sliderEnabled = true; std::optional sliderSnap; bool textInputEnabled = true; @@ -385,32 +394,38 @@ Result> FloatSettingV3::parse(std::string const& root.has("min").into(ret->m_impl->minValue); root.has("max").into(ret->m_impl->maxValue); if (auto controls = root.has("control")) { + controls.has("arrows"); + controls.has("big-arrows"); controls.has("arrow-step").into(ret->m_impl->controls.arrowStepSize); - if (!controls.has("arrows").template get()) { - ret->m_impl->controls.arrowStepSize = 0; - } controls.has("big-arrow-step").into(ret->m_impl->controls.bigArrowStepSize); - if (!controls.has("big-arrows").template get()) { - ret->m_impl->controls.bigArrowStepSize = 0; - } controls.has("slider").into(ret->m_impl->controls.sliderEnabled); controls.has("slider-step").into(ret->m_impl->controls.sliderSnap); controls.has("input").into(ret->m_impl->controls.textInputEnabled); - // Without "min" or "max" slider makes no sense - if (!ret->m_impl->minValue || !ret->m_impl->maxValue) { - if (ret->m_impl->controls.sliderEnabled) { - log::warn( - "Setting '{}' has \"controls.slider\" enabled but doesn't " - "have both \"min\" and \"max\" defined - the slider has " - "been force-disabled!", - key - ); - } - ret->m_impl->controls.sliderEnabled = false; - } controls.checkUnknownKeys(); } + // Disable arrows if they aren't enabled + // Small arrows are enabled by default + if (!root.has("control").has("arrows").template get(true)) { + ret->m_impl->controls.arrowStepSize = 0; + } + if (!root.has("control").has("big-arrows").template get()) { + ret->m_impl->controls.bigArrowStepSize = 0; + } + + // Without "min" or "max" slider makes no sense + if (!ret->m_impl->minValue || !ret->m_impl->maxValue) { + if (ret->m_impl->controls.sliderEnabled && root.has("control").has("slider")) { + log::warn( + "Setting '{}' has \"controls.slider\" enabled but doesn't " + "have both \"min\" and \"max\" defined - the slider has " + "been force-disabled!", + key + ); + } + ret->m_impl->controls.sliderEnabled = false; + } + root.checkUnknownKeys(); return root.ok(ret); } @@ -423,10 +438,10 @@ double FloatSettingV3::getDefaultValue() const { } Result<> FloatSettingV3::isValid(double value) const { if (m_impl->minValue && value < *m_impl->minValue) { - return Err("value must be at least {}", *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 Err("Value must be at most {}", *m_impl->maxValue); } return Ok(); } @@ -444,10 +459,10 @@ bool FloatSettingV3::isArrowsEnabled() const { bool FloatSettingV3::isBigArrowsEnabled() const { return m_impl->controls.bigArrowStepSize > 0; } -size_t FloatSettingV3::getArrowStepSize() const { +double FloatSettingV3::getArrowStepSize() const { return m_impl->controls.arrowStepSize; } -size_t FloatSettingV3::getBigArrowStepSize() const { +double FloatSettingV3::getBigArrowStepSize() const { return m_impl->controls.bigArrowStepSize; } bool FloatSettingV3::isSliderEnabled() const { @@ -487,8 +502,8 @@ std::optional FloatSettingV3::convertToLegacy() const { .controls = { .arrows = this->isArrowsEnabled(), .bigArrows = this->isBigArrowsEnabled(), - .arrowStep = this->getArrowStepSize(), - .bigArrowStep = this->getBigArrowStepSize(), + .arrowStep = static_cast(this->getArrowStepSize()), + .bigArrowStep = static_cast(this->getBigArrowStepSize()), .slider = this->isSliderEnabled(), .sliderStep = this->getSliderSnap(), .input = this->isInputEnabled(), @@ -538,12 +553,12 @@ std::string StringSettingV3::getDefaultValue() const { Result<> StringSettingV3::isValid(std::string_view value) const { 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); + 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 Err("Value must be one of {}", fmt::join(*m_impl->oneOf, ", ")); } } return Ok(); @@ -594,6 +609,7 @@ class FileSettingV3::Impl final { public: std::filesystem::path value; std::filesystem::path defaultValue; + FileType fileType; std::optional> filters; }; @@ -610,19 +626,32 @@ Result> FileSettingV3::parse(std::string const& k // Replace known paths like `{gd-save-dir}/` try { ret->m_impl->defaultValue = fmt::format( - fmt::runtime(ret->m_impl->defaultValue.string()), - fmt::arg("gd-save-dir", dirs::getSaveDir()), - fmt::arg("gd-game-dir", dirs::getGameDir()), - fmt::arg("mod-config-dir", dirs::getModConfigDir() / modID), - fmt::arg("mod-save-dir", dirs::getModsSaveDir() / modID), - fmt::arg("temp-dir", dirs::getTempDir()) + fmt::runtime(ret->m_impl->defaultValue.string()), + fmt::arg("gd_dir", dirs::getGameDir()), + fmt::arg("gd_save_dir", dirs::getSaveDir()), + fmt::arg("mod_config_dir", dirs::getModConfigDir() / modID), + fmt::arg("mod_save_dir", dirs::getModsSaveDir() / modID), + fmt::arg("temp_dir", dirs::getTempDir()) ); } - catch(fmt::format_error const&) { - return Err("Invalid format string for file setting path"); + catch(fmt::format_error const& e) { + return Err("Invalid format string for file setting path: {}", e.what()); } ret->m_impl->value = ret->m_impl->defaultValue; + if (auto ty = root.has("filetype")) { + ty.assertIsString(); + switch (hash(ty.template get())) { + case hash("any"): ret->m_impl->fileType = FileType::Any; break; + case hash("file"): ret->m_impl->fileType = FileType::File; break; + case hash("folder"): ret->m_impl->fileType = FileType::Folder; break; + default: return Err( + "Setting '{}' in mod {}: Invalid filetype \"{}\"", + key, modID, ty.template get() + ); + } + } + if (auto controls = root.has("control")) { auto filters = std::vector(); for (auto& item : controls.has("filters").items()) { @@ -647,9 +676,24 @@ std::filesystem::path FileSettingV3::getDefaultValue() const { return m_impl->defaultValue; } Result<> FileSettingV3::isValid(std::filesystem::path const& value) const { + if (m_impl->fileType != FileType::Any) { + if (!std::filesystem::exists(value)) { + return Err("{} must exist", m_impl->fileType == FileType::File ? "File" : "Folder"); + } + if (m_impl->fileType == FileType::File && !std::filesystem::is_regular_file(value)) { + return Err("Value must be a file"); + } + if (m_impl->fileType == FileType::Folder && !std::filesystem::is_directory(value)) { + return Err("Value must be a folder"); + } + } return Ok(); } +FileSettingV3::FileType FileSettingV3::getFileType() const { + return m_impl->fileType; +} + std::optional> FileSettingV3::getFilters() const { return m_impl->filters; } From 971e3fb2546df13cdc6d73c3cacd2f868789bc5e Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sat, 24 Aug 2024 11:26:28 +0300 Subject: [PATCH 23/54] make file picker dialog cancellation not return an error but just cancel the task --- loader/src/platform/windows/util.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/loader/src/platform/windows/util.cpp b/loader/src/platform/windows/util.cpp index 984c4baf..b2a6e8b7 100644 --- a/loader/src/platform/windows/util.cpp +++ b/loader/src/platform/windows/util.cpp @@ -123,11 +123,13 @@ Task> file::pick(PickMode mode, FilePickOptions co Result result; auto pickresult = nfdPick(nfdMode, options, &path); if (pickresult.isErr()) { - result = Err(pickresult.err().value()); + if (pickresult.unwrapErr() == "Dialog cancelled") { + return RetTask::cancelled(); + } + return RetTask::immediate(Err(pickresult.unwrapErr())); } else { - result = Ok(path); + return RetTask::immediate(Ok(path)); } - return RetTask::immediate(std::move(result)); } Task>> file::pickMany(FilePickOptions const& options) { From 1a82d12b7b7654d8ec3a0e23ba96f8541695f180 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sat, 24 Aug 2024 11:31:53 +0300 Subject: [PATCH 24/54] add Task::cancelled --- loader/include/Geode/utils/Task.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/loader/include/Geode/utils/Task.hpp b/loader/include/Geode/utils/Task.hpp index 38868bf1..d6691257 100644 --- a/loader/include/Geode/utils/Task.hpp +++ b/loader/include/Geode/utils/Task.hpp @@ -390,6 +390,15 @@ namespace geode { return m_handle == nullptr; } + /** + * Create a new Task that is immediately cancelled + * @param name The name of the Task; used for debugging + */ + static Task cancelled(std::string_view const name = "") { + auto task = Task(Handle::create(name)); + Task::cancel(task.m_handle); + return task; + } /** * Create a new Task that immediately finishes with the given * value From 56f878ccb3104791676c628f0a77c16914660c16 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sat, 24 Aug 2024 12:13:58 +0300 Subject: [PATCH 25/54] folder settings & allow picking which dialog to use --- loader/include/Geode/loader/SettingV3.hpp | 9 +-- loader/src/loader/ModSettingsManager.cpp | 1 + loader/src/loader/SettingNodeV3.cpp | 37 ++++++----- loader/src/loader/SettingV3.cpp | 80 ++++++++++++++--------- 4 files changed, 71 insertions(+), 56 deletions(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index 8f17c193..a5a09128 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -328,16 +328,11 @@ namespace geode { FileSettingV3(PrivateMarker); static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); - enum class FileType { - Any = 0, - File = 1, - Folder = 2, - }; - std::filesystem::path getDefaultValue() const override; Result<> isValid(std::filesystem::path const& value) const override; - FileType getFileType() const; + bool isFolder() const; + bool useSaveDialog() const; std::optional> getFilters() const; diff --git a/loader/src/loader/ModSettingsManager.cpp b/loader/src/loader/ModSettingsManager.cpp index 96412fec..dc625ac7 100644 --- a/loader/src/loader/ModSettingsManager.cpp +++ b/loader/src/loader/ModSettingsManager.cpp @@ -21,6 +21,7 @@ private: { "float", &FloatSettingV3::parse }, { "string", &StringSettingV3::parse }, { "file", &FileSettingV3::parse }, + { "folder", &FileSettingV3::parse }, { "path", &FileSettingV3::parse }, { "rgb", &Color3BSettingV3::parse }, { "color", &Color3BSettingV3::parse }, diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index c9b80726..93e2ad94 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -347,17 +347,17 @@ bool FileSettingNodeV3::init(std::shared_ptr setting, float width labelBG->setScale(.25f); labelBG->setColor({ 0, 0, 0 }); labelBG->setOpacity(90); - labelBG->setContentSize({ 400, 80 }); - this->getButtonMenu()->addChildAtPosition(labelBG, Anchor::Center, ccp(-15, 0)); + labelBG->setContentSize({ 420, 80 }); + this->getButtonMenu()->addChildAtPosition(labelBG, Anchor::Center, ccp(-10, 0)); m_fileIcon = CCSprite::create(); - this->getButtonMenu()->addChildAtPosition(m_fileIcon, Anchor::Left, ccp(3, 0)); + this->getButtonMenu()->addChildAtPosition(m_fileIcon, Anchor::Left, ccp(5, 0)); m_nameLabel = CCLabelBMFont::create("", "bigFont.fnt"); - this->getButtonMenu()->addChildAtPosition(m_nameLabel, Anchor::Left, ccp(11, 0), ccp(0, .5f)); + this->getButtonMenu()->addChildAtPosition(m_nameLabel, Anchor::Left, ccp(13, 0), ccp(0, .5f)); auto selectSpr = CCSprite::createWithSpriteFrameName("GJ_plus2Btn_001.png"); - selectSpr->setScale(.75f); + selectSpr->setScale(.7f); auto selectBtn = CCMenuItemSpriteExtra::create( selectSpr, this, menu_selector(FileSettingNodeV3::onPickFile) ); @@ -370,18 +370,21 @@ bool FileSettingNodeV3::init(std::shared_ptr setting, float width void FileSettingNodeV3::updateState() { SettingNodeV3::updateState(); - auto ty = this->getSetting()->getFileType(); - if (ty == FileSettingV3::FileType::Any) { - ty = std::filesystem::is_directory(m_path) ? - FileSettingV3::FileType::Folder : - FileSettingV3::FileType::File; - } m_fileIcon->setDisplayFrame(CCSpriteFrameCache::get()->spriteFrameByName( - ty == FileSettingV3::FileType::File ? "file.png"_spr : "folderIcon_001.png" + this->getSetting()->isFolder() ? "folderIcon_001.png" : "file.png"_spr )); limitNodeSize(m_fileIcon, ccp(10, 10), 1.f, .1f); - m_nameLabel->setString(m_path.filename().string().c_str()); - m_nameLabel->limitLabelWidth(75, .4f, .1f); + if (m_path.empty()) { + m_nameLabel->setString(this->getSetting()->isFolder() ? "No Folder Selected" : "No File Selected"); + m_nameLabel->setColor(ccGRAY); + m_nameLabel->setOpacity(155); + } + else { + m_nameLabel->setString(m_path.filename().string().c_str()); + m_nameLabel->setColor(ccWHITE); + m_nameLabel->setOpacity(255); + } + m_nameLabel->limitLabelWidth(75, .35f, .1f); } void FileSettingNodeV3::onCommit() { @@ -407,9 +410,9 @@ void FileSettingNodeV3::onPickFile(CCObject*) { } }); m_pickListener.setFilter(file::pick( - this->getSetting()->getFileType() == FileSettingV3::FileType::Folder ? - file::PickMode::OpenFolder : - file::PickMode::OpenFile, + this->getSetting()->isFolder() ? + file::PickMode::OpenFolder : + (this->getSetting()->useSaveDialog() ? file::PickMode::SaveFile : file::PickMode::OpenFile), { dirs::getGameDir(), this->getSetting()->getFilters().value_or(std::vector()) diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index b3b096c5..8f74da61 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -609,7 +609,8 @@ class FileSettingV3::Impl final { public: std::filesystem::path value; std::filesystem::path defaultValue; - FileType fileType; + bool folder = false; + bool useSaveDialog = false; // this option makes no sense if folder = true std::optional> filters; }; @@ -623,7 +624,7 @@ Result> FileSettingV3::parse(std::string const& k ret->parseDefaultValue(root, ret->m_impl->defaultValue); ret->m_impl->value = ret->m_impl->defaultValue; - // Replace known paths like `{gd-save-dir}/` + // Replace known paths like `{gd-save-dir}/` try { ret->m_impl->defaultValue = fmt::format( fmt::runtime(ret->m_impl->defaultValue.string()), @@ -639,29 +640,41 @@ Result> FileSettingV3::parse(std::string const& k } ret->m_impl->value = ret->m_impl->defaultValue; - if (auto ty = root.has("filetype")) { - ty.assertIsString(); - switch (hash(ty.template get())) { - case hash("any"): ret->m_impl->fileType = FileType::Any; break; - case hash("file"): ret->m_impl->fileType = FileType::File; break; - case hash("folder"): ret->m_impl->fileType = FileType::Folder; break; - default: return Err( - "Setting '{}' in mod {}: Invalid filetype \"{}\"", - key, modID, ty.template get() + std::string type; + root.needs("type").into(type); + if (type == "folder") { + ret->m_impl->folder = true; + // folder-specific stuff if they ever exist + } + else if (type == "file" || type == "path") { + if (type == "path") { + log::warn( + "Setting '{}' in mod {}: the \"path\" type has been " + "deprecated, use \"type\": \"file\" or \"type\": \"folder\" instead", + key, modID ); } - } - - if (auto controls = root.has("control")) { - auto filters = std::vector(); - for (auto& item : controls.has("filters").items()) { - utils::file::FilePickOptions::Filter filter; - item.has("description").into(filter.description); - item.has("files").into(filter.files); - filters.push_back(filter); + std::string dialogType; + root.has("dialog").into(dialogType); + switch (hash(dialogType)) { + case hash("save"): ret->m_impl->useSaveDialog = true; break; + case hash("open"): ret->m_impl->useSaveDialog = false; break; + case hash(""): break; + default: return Err("Setting '{}' in mod {}: unknown \"dialog\" type \"{}\"", key, modID, dialogType); } - if (!filters.empty()) { - ret->m_impl->filters.emplace(filters); + + // Filter controls only make sense for files but not for folders + if (auto controls = root.has("control")) { + auto filters = std::vector(); + for (auto& item : controls.has("filters").items()) { + utils::file::FilePickOptions::Filter filter; + item.has("description").into(filter.description); + item.has("files").into(filter.files); + filters.push_back(filter); + } + if (!filters.empty()) { + ret->m_impl->filters.emplace(filters); + } } } @@ -676,22 +689,25 @@ std::filesystem::path FileSettingV3::getDefaultValue() const { return m_impl->defaultValue; } Result<> FileSettingV3::isValid(std::filesystem::path const& value) const { - if (m_impl->fileType != FileType::Any) { - if (!std::filesystem::exists(value)) { - return Err("{} must exist", m_impl->fileType == FileType::File ? "File" : "Folder"); - } - if (m_impl->fileType == FileType::File && !std::filesystem::is_regular_file(value)) { - return Err("Value must be a file"); - } - if (m_impl->fileType == FileType::Folder && !std::filesystem::is_directory(value)) { + std::error_code ec; + if (m_impl->folder) { + if (!std::filesystem::is_directory(value, ec)) { return Err("Value must be a folder"); } } + else { + if (!std::filesystem::is_regular_file(value, ec)) { + return Err("Value must be a file"); + } + } return Ok(); } -FileSettingV3::FileType FileSettingV3::getFileType() const { - return m_impl->fileType; +bool FileSettingV3::isFolder() const { + return m_impl->folder; +} +bool FileSettingV3::useSaveDialog() const { + return m_impl->useSaveDialog; } std::optional> FileSettingV3::getFilters() const { From 62b6241e37ff12341cb9fb5db8c206f90d2b55af Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sat, 24 Aug 2024 12:40:32 +0300 Subject: [PATCH 26/54] fix dialog opening stuff --- loader/src/loader/SettingNodeV3.cpp | 4 +++- loader/src/platform/windows/nfdwin.cpp | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index 93e2ad94..d74bf383 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -409,12 +409,14 @@ void FileSettingNodeV3::onPickFile(CCObject*) { )->show(); } }); + std::error_code ec; m_pickListener.setFilter(file::pick( this->getSetting()->isFolder() ? file::PickMode::OpenFolder : (this->getSetting()->useSaveDialog() ? file::PickMode::SaveFile : file::PickMode::OpenFile), { - dirs::getGameDir(), + // Prefer opening the current path directly if possible + m_path.empty() || !std::filesystem::exists(m_path.parent_path(), ec) ? dirs::getGameDir() : m_path, this->getSetting()->getFilters().value_or(std::vector()) } )); diff --git a/loader/src/platform/windows/nfdwin.cpp b/loader/src/platform/windows/nfdwin.cpp index 872963e0..6842135d 100644 --- a/loader/src/platform/windows/nfdwin.cpp +++ b/loader/src/platform/windows/nfdwin.cpp @@ -182,6 +182,7 @@ Result<> nfdPick( } if (options.defaultPath && options.defaultPath.value().wstring().size()) { std::filesystem::path path = options.defaultPath.value(); + path.make_preferred(); if (mode == NFDMode::OpenFile || mode == NFDMode::SaveFile) { if (!std::filesystem::exists(path) || !std::filesystem::is_directory(path)) { if (path.has_filename()) { From a2f74431c1f310d581c1b1fe587e501dbe2df61d Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sat, 24 Aug 2024 20:05:28 +0300 Subject: [PATCH 27/54] no real changes because they didn't work --- loader/include/Geode/ui/ColorPickPopup.hpp | 11 ++++- loader/src/ui/nodes/ColorPickPopup.cpp | 53 +++++++++++++++++++++- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/loader/include/Geode/ui/ColorPickPopup.hpp b/loader/include/Geode/ui/ColorPickPopup.hpp index 4c0b5d4b..8a0dc68d 100644 --- a/loader/include/Geode/ui/ColorPickPopup.hpp +++ b/loader/include/Geode/ui/ColorPickPopup.hpp @@ -2,16 +2,23 @@ #include "Popup.hpp" #include "TextInput.hpp" -#include "Popup.hpp" - +#include "../loader/Event.hpp" #include namespace geode { + class ColorPickPopup; + class GEODE_DLL ColorPickPopupDelegate { public: virtual void updateColor(cocos2d::ccColor4B const& color) {} }; + // todo in v4: make this pimpl and maybe use events over the delegate? + // thing with events is that if you just filter via ColorPickPopup* it + // won't work unless you automatically detach the filter when closing the + // popup (otherwise opening another popup really quickly will just be + // allocated into the same memory and now the old filter is catching the + // new popup too) class GEODE_DLL ColorPickPopup : public Popup, public cocos2d::extension::ColorPickerDelegate, diff --git a/loader/src/ui/nodes/ColorPickPopup.cpp b/loader/src/ui/nodes/ColorPickPopup.cpp index 01083a6f..7a6b46c3 100644 --- a/loader/src/ui/nodes/ColorPickPopup.cpp +++ b/loader/src/ui/nodes/ColorPickPopup.cpp @@ -10,6 +10,55 @@ using namespace geode::prelude; +// class ColorPickPopupEvent::Impl final { +// public: +// ColorPickPopup* popup; +// ccColor4B color; +// bool closed = false; +// }; + +// ColorPickPopupEvent::ColorPickPopupEvent(ColorPickPopup* popup, ccColor4B const& color) +// : m_impl(std::make_shared()) +// { +// m_impl->popup = popup; +// m_impl->color = color; +// } +// ColorPickPopupEvent::~ColorPickPopupEvent() = default; + +// ColorPickPopup* ColorPickPopupEvent::getPopup() const { +// return m_impl->popup; +// } +// ccColor4B ColorPickPopupEvent::getColor() const { +// return m_impl->color; +// } +// bool ColorPickPopupEvent::isPopupClosed() const { +// return m_impl->closed; +// } + +// class ColorPickPopupEventFilter::Impl final { +// public: +// ColorPickPopup* popup; +// }; + +// ListenerResult ColorPickPopupEventFilter::handle(utils::MiniFunction fn, ColorPickPopupEvent* event) { +// if (event->getPopup() == m_impl->popup) { +// if (event->isPopupClosed()) { +// m_impl->popup = nullptr; +// } +// else { +// fn(event); +// } +// } +// return ListenerResult::Propagate; +// } +// ColorPickPopupEventFilter::ColorPickPopupEventFilter() : ColorPickPopupEventFilter(nullptr) {} +// ColorPickPopupEventFilter::ColorPickPopupEventFilter(ColorPickPopup* popup) +// : m_impl(std::make_shared()) +// { +// m_impl->popup = popup; +// } +// ColorPickPopupEventFilter::~ColorPickPopupEventFilter() = default; + bool ColorPickPopup::setup(ccColor4B const& color, bool isRGBA) { m_noElasticity = true; m_color = color; @@ -334,7 +383,9 @@ void ColorPickPopup::updateState(CCNode* except) { } m_resetBtn->setVisible(m_originalColor != m_color); m_newColorSpr->setColor(to3B(m_color)); - if (m_delegate) m_delegate->updateColor(m_color); + if (m_delegate) { + m_delegate->updateColor(m_color); + } } void ColorPickPopup::onOpacitySlider(CCObject* sender) { From dd173017613a513070471499d8bd1f00e87debc8 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sat, 24 Aug 2024 20:05:34 +0300 Subject: [PATCH 28/54] color settings --- loader/src/loader/SettingNodeV3.cpp | 84 ++++++++++++++++++++++++----- loader/src/loader/SettingNodeV3.hpp | 19 ++++++- 2 files changed, 87 insertions(+), 16 deletions(-) diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index d74bf383..b2703d93 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -387,10 +387,6 @@ void FileSettingNodeV3::updateState() { m_nameLabel->limitLabelWidth(75, .35f, .1f); } -void FileSettingNodeV3::onCommit() { - this->getSetting()->setValue(m_path); -} - void FileSettingNodeV3::onPickFile(CCObject*) { m_pickListener.bind([this](auto* event) { auto value = event->getValue(); @@ -422,6 +418,9 @@ void FileSettingNodeV3::onPickFile(CCObject*) { )); } +void FileSettingNodeV3::onCommit() { + this->getSetting()->setValue(m_path); +} bool FileSettingNodeV3::hasUncommittedChanges() const { return m_path != this->getSetting()->getValue(); } @@ -452,20 +451,48 @@ bool Color3BSettingNodeV3::init(std::shared_ptr setting, float if (!SettingNodeV3::init(setting, width)) return false; - // todo + m_value = setting->getValue(); + + m_colorSprite = ColorChannelSprite::create(); + m_colorSprite->setScale(.65f); + + auto button = CCMenuItemSpriteExtra::create( + m_colorSprite, this, menu_selector(Color3BSettingNodeV3::onSelectColor) + ); + this->getButtonMenu()->addChildAtPosition(button, Anchor::Right, ccp(-10, 0)); + + this->updateState(); return true; } -void Color3BSettingNodeV3::onCommit() {} +void Color3BSettingNodeV3::updateState() { + SettingNodeV3::updateState(); + m_colorSprite->setColor(m_value); +} +void Color3BSettingNodeV3::onSelectColor(CCObject*) { + auto popup = ColorPickPopup::create(m_value); + popup->setDelegate(this); + popup->show(); +} +void Color3BSettingNodeV3::updateColor(ccColor4B const& color) { + m_value = to3B(color); + this->markChanged(); +} + +void Color3BSettingNodeV3::onCommit() { + this->getSetting()->setValue(m_value); +} bool Color3BSettingNodeV3::hasUncommittedChanges() const { - return false; + return m_value != this->getSetting()->getValue(); } bool Color3BSettingNodeV3::hasNonDefaultValue() const { - return false; + return m_value != this->getSetting()->getDefaultValue(); +} +void Color3BSettingNodeV3::onResetToDefault() { + m_value = this->getSetting()->getDefaultValue(); } -void Color3BSettingNodeV3::onResetToDefault() {} std::shared_ptr Color3BSettingNodeV3::getSetting() const { return std::static_pointer_cast(SettingNodeV3::getSetting()); @@ -487,20 +514,49 @@ bool Color4BSettingNodeV3::init(std::shared_ptr setting, float if (!SettingNodeV3::init(setting, width)) return false; - // todo + m_value = setting->getValue(); + + m_colorSprite = ColorChannelSprite::create(); + m_colorSprite->setScale(.65f); + + auto button = CCMenuItemSpriteExtra::create( + m_colorSprite, this, menu_selector(Color4BSettingNodeV3::onSelectColor) + ); + this->getButtonMenu()->addChildAtPosition(button, Anchor::Right, ccp(-10, 0)); + + this->updateState(); return true; } -void Color4BSettingNodeV3::onCommit() {} +void Color4BSettingNodeV3::updateState() { + SettingNodeV3::updateState(); + m_colorSprite->setColor(to3B(m_value)); + m_colorSprite->updateOpacity(m_value.a / 255.f); +} +void Color4BSettingNodeV3::onSelectColor(CCObject*) { + auto popup = ColorPickPopup::create(m_value); + popup->setDelegate(this); + popup->show(); +} +void Color4BSettingNodeV3::updateColor(ccColor4B const& color) { + m_value = color; + this->markChanged(); +} + +void Color4BSettingNodeV3::onCommit() { + this->getSetting()->setValue(m_value); +} bool Color4BSettingNodeV3::hasUncommittedChanges() const { - return false; + return m_value != this->getSetting()->getValue(); } bool Color4BSettingNodeV3::hasNonDefaultValue() const { - return false; + return m_value != this->getSetting()->getDefaultValue(); +} +void Color4BSettingNodeV3::onResetToDefault() { + m_value = this->getSetting()->getDefaultValue(); } -void Color4BSettingNodeV3::onResetToDefault() {} std::shared_ptr Color4BSettingNodeV3::getSetting() const { return std::static_pointer_cast(SettingNodeV3::getSetting()); diff --git a/loader/src/loader/SettingNodeV3.hpp b/loader/src/loader/SettingNodeV3.hpp index 34e6e32e..816acfae 100644 --- a/loader/src/loader/SettingNodeV3.hpp +++ b/loader/src/loader/SettingNodeV3.hpp @@ -3,6 +3,7 @@ #include #include #include +#include using namespace geode::prelude; @@ -290,11 +291,18 @@ public: std::shared_ptr getSetting() const; }; -class Color3BSettingNodeV3 : public SettingNodeV3 { +class Color3BSettingNodeV3 : public SettingNodeV3, public ColorPickPopupDelegate { protected: + ccColor3B m_value; + ColorChannelSprite* m_colorSprite; + bool init(std::shared_ptr setting, float width); + void updateState() override; + void onCommit() override; + void onSelectColor(CCObject*); + void updateColor(ccColor4B const& color) override; public: static Color3BSettingNodeV3* create(std::shared_ptr setting, float width); @@ -306,11 +314,18 @@ public: std::shared_ptr getSetting() const; }; -class Color4BSettingNodeV3 : public SettingNodeV3 { +class Color4BSettingNodeV3 : public SettingNodeV3, public ColorPickPopupDelegate { protected: + ccColor4B m_value; + ColorChannelSprite* m_colorSprite; + bool init(std::shared_ptr setting, float width); + void updateState() override; + void onCommit() override; + void onSelectColor(CCObject*); + void updateColor(ccColor4B const& color) override; public: static Color4BSettingNodeV3* create(std::shared_ptr setting, float width); From 4fb42754cb94be2654653a9783986e67c85f0f8d Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sat, 24 Aug 2024 23:39:10 +0300 Subject: [PATCH 29/54] add restart required label --- loader/include/Geode/loader/SettingV3.hpp | 4 ++ loader/src/loader/SettingNodeV3.cpp | 42 ++++++++++++++++++- .../src/ui/mods/settings/ModSettingsPopup.cpp | 16 +++---- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index a5a09128..6021450f 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -431,6 +431,10 @@ namespace geode { virtual bool hasUncommittedChanges() const = 0; virtual bool hasNonDefaultValue() const = 0; + // Can be overridden by the setting itself + // Can / should be used to do alternating BG + void setBGColor(cocos2d::ccColor4B const& color); + cocos2d::CCLabelBMFont* getNameLabel() const; cocos2d::CCMenu* getNameMenu() const; cocos2d::CCMenu* getButtonMenu() const; diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index b2703d93..927ec27a 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -1,5 +1,7 @@ #include "SettingNodeV3.hpp" #include +#include +#include class SettingNodeSizeChangeEventV3::Impl final { public: @@ -36,10 +38,14 @@ bool SettingNodeValueChangeEventV3::isCommit() const { class SettingNodeV3::Impl final { public: std::shared_ptr setting; + CCLayerColor* bg; CCLabelBMFont* nameLabel; CCMenu* nameMenu; CCMenu* buttonMenu; CCMenuItemSpriteExtra* resetButton; + ButtonSprite* restartRequiredLabel; + ccColor4B bgColor = ccc4(0, 0, 0, 0); + bool committed = false; }; bool SettingNodeV3::init(std::shared_ptr setting, float width) { @@ -49,6 +55,12 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { m_impl = std::make_shared(); m_impl->setting = setting; + m_impl->bg = CCLayerColor::create({ 0, 0, 0, 0 }); + m_impl->bg->setContentSize({ width, 0 }); + m_impl->bg->ignoreAnchorPointForPosition(false); + m_impl->bg->setAnchorPoint(ccp(.5f, .5f)); + this->addChildAtPosition(m_impl->bg, Anchor::Center); + m_impl->nameMenu = CCMenu::create(); m_impl->nameMenu->setContentWidth(width / 2 + 25); @@ -56,6 +68,16 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { m_impl->nameLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .4f)->setScalePriority(1)); m_impl->nameMenu->addChild(m_impl->nameLabel); + m_impl->restartRequiredLabel = createGeodeTagLabel( + "Restart Required", + {{ + to3B(ColorProvider::get()->color("mod-list-restart-required-label"_spr)), + to3B(ColorProvider::get()->color("mod-list-restart-required-label-bg"_spr)) + }} + ); + m_impl->restartRequiredLabel->setScale(.25f); + this->addChildAtPosition(m_impl->restartRequiredLabel, Anchor::Left, ccp(10, -10), ccp(0, .5f)); + if (setting->getDescription()) { auto descSpr = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png"); descSpr->setScale(.5f); @@ -88,8 +110,19 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { } void SettingNodeV3::updateState() { - this->getNameLabel()->setColor(this->hasUncommittedChanges() ? ccc3(17, 221, 0) : ccWHITE); + m_impl->nameLabel->setColor(this->hasUncommittedChanges() ? ccc3(17, 221, 0) : ccWHITE); m_impl->resetButton->setVisible(this->hasNonDefaultValue()); + + m_impl->bg->setColor(to3B(m_impl->bgColor)); + m_impl->bg->setOpacity(m_impl->bgColor.a); + + m_impl->restartRequiredLabel->setVisible(false); + if (m_impl->setting->requiresRestart() && (this->hasUncommittedChanges() || m_impl->committed)) { + m_impl->restartRequiredLabel->setVisible(true); + m_impl->bg->setColor("mod-list-restart-required-label-bg"_cc3b); + m_impl->bg->setOpacity(75); + } + m_impl->nameMenu->updateLayout(); } @@ -119,12 +152,18 @@ void SettingNodeV3::onReset(CCObject*) { ); } +void SettingNodeV3::setBGColor(ccColor4B const& color) { + m_impl->bgColor = color; + this->updateState(); +} + void SettingNodeV3::markChanged() { this->updateState(); SettingNodeValueChangeEventV3(false).post(); } void SettingNodeV3::commit() { this->onCommit(); + m_impl->committed = true; this->updateState(); SettingNodeValueChangeEventV3(true).post(); } @@ -137,6 +176,7 @@ void SettingNodeV3::resetToDefault() { void SettingNodeV3::setContentSize(CCSize const& size) { CCNode::setContentSize(size); + m_impl->bg->setContentSize(size); this->updateLayout(); SettingNodeSizeChangeEventV3(this).post(); } diff --git a/loader/src/ui/mods/settings/ModSettingsPopup.cpp b/loader/src/ui/mods/settings/ModSettingsPopup.cpp index 5c5fdf72..4b618b57 100644 --- a/loader/src/ui/mods/settings/ModSettingsPopup.cpp +++ b/loader/src/ui/mods/settings/ModSettingsPopup.cpp @@ -23,12 +23,9 @@ bool ModSettingsPopup::setup(Mod* mod) { auto layer = ScrollLayer::create(layerSize); layer->setTouchEnabled(true); - bool hasBG = false; + bool bg = false; for (auto& key : mod->getSettingKeys()) { - hasBG = !hasBG; - - auto bg = CCLayerColor::create({ 0, 0, 0, 50 }); - bg->setOpacity(hasBG ? 60 : 20); + bg = !bg; SettingNodeV3* node; if (auto sett = mod->getSettingV3(key)) { @@ -38,18 +35,15 @@ bool ModSettingsPopup::setup(Mod* mod) { // todo: placeholder node continue; } + node->setBGColor(ccc4(0, 0, 0, bg ? 60 : 20)); - bg->setContentSize(node->getScaledContentSize()); - bg->addChildAtPosition(node, Anchor::Center, ccp(0, 0), ccp(.5f, .5f)); - // auto separator = CCLayerColor::create({ 0, 0, 0, 50 }, layerSize.width, 1.f); - // separator->setOpacity(hasBG ? 100 : 50); + // separator->setOpacity(bg ? 100 : 50); // separator->ignoreAnchorPointForPosition(false); // bg->addChildAtPosition(separator, Anchor::Bottom, ccp(0, 0), ccp(.5f, .5f)); m_settings.push_back(node); - - layer->m_contentLayer->addChild(bg); + layer->m_contentLayer->addChild(node); } layer->m_contentLayer->setLayout( ColumnLayout::create() From db9e2ccb48c3f40a1a473fe556bdc15453528ba5 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Wed, 28 Aug 2024 22:38:07 +0300 Subject: [PATCH 30/54] fully implement enable-if!!!! --- loader/include/Geode/loader/SettingV3.hpp | 15 +- loader/include/Geode/utils/JsonValidation.hpp | 13 + loader/src/loader/SettingNodeV3.cpp | 118 +++-- loader/src/loader/SettingNodeV3.hpp | 108 +++-- loader/src/loader/SettingV3.cpp | 423 +++++++++++++++++- .../src/ui/mods/settings/ModSettingsPopup.cpp | 16 +- .../src/ui/mods/settings/ModSettingsPopup.hpp | 3 +- 7 files changed, 608 insertions(+), 88 deletions(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index 6021450f..fa9e1c7a 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -9,8 +9,9 @@ // this unfortunately has to be included because of C++ templates #include "../utils/JsonValidation.hpp" -// todo in v4: these can be removed as well as the friend decl in LegacyCustomSettingV3 +// todo in v4: this can be removed as well as the friend decl in LegacyCustomSettingV3 class LegacyCustomSettingToV3Node; +class ModSettingsPopup; namespace geode { class ModSettingsManager; @@ -59,6 +60,11 @@ namespace geode { * Get the "enable-if" scheme for this setting */ std::optional getEnableIf() const; + /** + * Check if this setting should be enabled based on the "enable-if" scheme + */ + bool shouldEnable() const; + std::optional getEnableIfDescription() const; /** * Whether this setting requires a restart on change */ @@ -402,6 +408,8 @@ namespace geode { private: class Impl; std::shared_ptr m_impl; + + friend class ::ModSettingsPopup; protected: bool init(std::shared_ptr setting, float width); @@ -453,7 +461,7 @@ namespace geode { SettingNodeSizeChangeEventV3(SettingNodeV3* node); virtual ~SettingNodeSizeChangeEventV3(); - SettingNodeV3* getTargetNode() const; + SettingNodeV3* getNode() const; }; class GEODE_DLL SettingNodeValueChangeEventV3 : public Event { private: @@ -461,9 +469,10 @@ namespace geode { std::shared_ptr m_impl; public: - SettingNodeValueChangeEventV3(bool commit); + SettingNodeValueChangeEventV3(SettingNodeV3* node, bool commit); virtual ~SettingNodeValueChangeEventV3(); + SettingNodeV3* getNode() const; bool isCommit() const; }; diff --git a/loader/include/Geode/utils/JsonValidation.hpp b/loader/include/Geode/utils/JsonValidation.hpp index 0f6ddea5..53357a6f 100644 --- a/loader/include/Geode/utils/JsonValidation.hpp +++ b/loader/include/Geode/utils/JsonValidation.hpp @@ -427,6 +427,19 @@ namespace geode { } return *this; } + template + JsonExpectedValue& mustBe(std::string_view name, auto predicate) requires requires { + { predicate(std::declval()) } -> std::convertible_to>; + } { + if (this->hasError()) return *this; + if (auto v = this->template tryGet()) { + auto p = predicate(*v); + if (!p) { + this->setError("json value is not {}: {}", name, p.unwrapErr()); + } + } + return *this; + } // -- Dealing with objects -- diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index 927ec27a..5f8add60 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -15,22 +15,27 @@ SettingNodeSizeChangeEventV3::SettingNodeSizeChangeEventV3(SettingNodeV3* node) } SettingNodeSizeChangeEventV3::~SettingNodeSizeChangeEventV3() = default; -SettingNodeV3* SettingNodeSizeChangeEventV3::getTargetNode() const { +SettingNodeV3* SettingNodeSizeChangeEventV3::getNode() const { return m_impl->node; } class SettingNodeValueChangeEventV3::Impl final { public: + SettingNodeV3* node; bool commit = false; }; -SettingNodeValueChangeEventV3::SettingNodeValueChangeEventV3(bool commit) +SettingNodeValueChangeEventV3::SettingNodeValueChangeEventV3(SettingNodeV3* node, bool commit) : m_impl(std::make_shared()) { + m_impl->node = node; m_impl->commit = commit; } SettingNodeValueChangeEventV3::~SettingNodeValueChangeEventV3() = default; +SettingNodeV3* SettingNodeValueChangeEventV3::getNode() const { + return m_impl->node; +} bool SettingNodeValueChangeEventV3::isCommit() const { return m_impl->commit; } @@ -43,7 +48,7 @@ public: CCMenu* nameMenu; CCMenu* buttonMenu; CCMenuItemSpriteExtra* resetButton; - ButtonSprite* restartRequiredLabel; + CCLabelBMFont* errorLabel; ccColor4B bgColor = ccc4(0, 0, 0, 0); bool committed = false; }; @@ -68,15 +73,9 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { m_impl->nameLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .4f)->setScalePriority(1)); m_impl->nameMenu->addChild(m_impl->nameLabel); - m_impl->restartRequiredLabel = createGeodeTagLabel( - "Restart Required", - {{ - to3B(ColorProvider::get()->color("mod-list-restart-required-label"_spr)), - to3B(ColorProvider::get()->color("mod-list-restart-required-label-bg"_spr)) - }} - ); - m_impl->restartRequiredLabel->setScale(.25f); - this->addChildAtPosition(m_impl->restartRequiredLabel, Anchor::Left, ccp(10, -10), ccp(0, .5f)); + m_impl->errorLabel = CCLabelBMFont::create("", "bigFont.fnt"); + m_impl->errorLabel->setScale(.25f); + this->addChildAtPosition(m_impl->errorLabel, Anchor::Left, ccp(10, -10), ccp(0, .5f)); if (setting->getDescription()) { auto descSpr = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png"); @@ -110,15 +109,26 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { } void SettingNodeV3::updateState() { + m_impl->errorLabel->setVisible(false); + m_impl->nameLabel->setColor(this->hasUncommittedChanges() ? ccc3(17, 221, 0) : ccWHITE); m_impl->resetButton->setVisible(this->hasNonDefaultValue()); m_impl->bg->setColor(to3B(m_impl->bgColor)); m_impl->bg->setOpacity(m_impl->bgColor.a); - m_impl->restartRequiredLabel->setVisible(false); + if (!m_impl->setting->shouldEnable()) { + if (auto desc = m_impl->setting->getEnableIfDescription()) { + m_impl->nameLabel->setColor(ccGRAY); + m_impl->errorLabel->setVisible(true); + m_impl->errorLabel->setColor("mod-list-errors-found"_cc3b); + m_impl->errorLabel->setString(desc->c_str()); + } + } if (m_impl->setting->requiresRestart() && (this->hasUncommittedChanges() || m_impl->committed)) { - m_impl->restartRequiredLabel->setVisible(true); + m_impl->errorLabel->setVisible(true); + m_impl->errorLabel->setColor("mod-list-restart-required-label"_cc3b); + m_impl->errorLabel->setString("Restart Required"); m_impl->bg->setColor("mod-list-restart-required-label-bg"_cc3b); m_impl->bg->setOpacity(75); } @@ -159,19 +169,19 @@ void SettingNodeV3::setBGColor(ccColor4B const& color) { void SettingNodeV3::markChanged() { this->updateState(); - SettingNodeValueChangeEventV3(false).post(); + SettingNodeValueChangeEventV3(this, false).post(); } void SettingNodeV3::commit() { this->onCommit(); m_impl->committed = true; this->updateState(); - SettingNodeValueChangeEventV3(true).post(); + SettingNodeValueChangeEventV3(this, true).post(); } void SettingNodeV3::resetToDefault() { m_impl->setting->reset(); this->onResetToDefault(); this->updateState(); - SettingNodeValueChangeEventV3(false).post(); + SettingNodeValueChangeEventV3(this, false).post(); } void SettingNodeV3::setContentSize(CCSize const& size) { @@ -259,6 +269,16 @@ bool BoolSettingNodeV3::init(std::shared_ptr setting, float width return true; } +void BoolSettingNodeV3::updateState() { + SettingNodeV3::updateState(); + auto enable = this->getSetting()->shouldEnable(); + m_toggle->setCascadeColorEnabled(true); + m_toggle->setCascadeOpacityEnabled(true); + m_toggle->setEnabled(enable); + m_toggle->setColor(enable ? ccWHITE : ccGRAY); + m_toggle->setOpacity(enable ? 255 : 155); +} + void BoolSettingNodeV3::onCommit() { this->getSetting()->setValue(m_toggle->isToggled()); } @@ -311,19 +331,19 @@ bool StringSettingNodeV3::init(std::shared_ptr setting, float w m_input->getInputNode()->m_placeholderLabel->setOpacity(255); m_input->getInputNode()->m_placeholderLabel->setColor(ccWHITE); - auto arrowLeftSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png"); - arrowLeftSpr->setFlipX(true); - arrowLeftSpr->setScale(.4f); + m_arrowLeftSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png"); + m_arrowLeftSpr->setFlipX(true); + m_arrowLeftSpr->setScale(.4f); auto arrowLeftBtn = CCMenuItemSpriteExtra::create( - arrowLeftSpr, this, menu_selector(StringSettingNodeV3::onArrow) + m_arrowLeftSpr, this, menu_selector(StringSettingNodeV3::onArrow) ); arrowLeftBtn->setTag(-1); this->getButtonMenu()->addChildAtPosition(arrowLeftBtn, Anchor::Left, ccp(5, 0)); - auto arrowRightSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png"); - arrowRightSpr->setScale(.4f); + m_arrowRightSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png"); + m_arrowRightSpr->setScale(.4f); auto arrowRightBtn = CCMenuItemSpriteExtra::create( - arrowRightSpr, this, menu_selector(StringSettingNodeV3::onArrow) + m_arrowRightSpr, this, menu_selector(StringSettingNodeV3::onArrow) ); arrowRightBtn->setTag(1); this->getButtonMenu()->addChildAtPosition(arrowRightBtn, Anchor::Right, ccp(-5, 0)); @@ -334,6 +354,20 @@ bool StringSettingNodeV3::init(std::shared_ptr setting, float w return true; } +void StringSettingNodeV3::updateState() { + SettingNodeV3::updateState(); + auto enable = this->getSetting()->shouldEnable(); + if (!this->getSetting()->getEnumOptions()) { + m_input->setEnabled(enable); + } + else { + m_arrowRightSpr->setOpacity(enable ? 255 : 155); + m_arrowRightSpr->setColor(enable ? ccWHITE : ccGRAY); + m_arrowLeftSpr->setOpacity(enable ? 255 : 155); + m_arrowLeftSpr->setColor(enable ? ccWHITE : ccGRAY); + } +} + void StringSettingNodeV3::onArrow(CCObject* sender) { auto options = *this->getSetting()->getEnumOptions(); auto index = ranges::indexOf(options, m_input->getString()).value_or(0); @@ -350,7 +384,6 @@ void StringSettingNodeV3::onArrow(CCObject* sender) { void StringSettingNodeV3::onCommit() { this->getSetting()->setValue(m_input->getString()); } - bool StringSettingNodeV3::hasUncommittedChanges() const { return m_input->getString() != this->getSetting()->getValue(); } @@ -396,12 +429,12 @@ bool FileSettingNodeV3::init(std::shared_ptr setting, float width m_nameLabel = CCLabelBMFont::create("", "bigFont.fnt"); this->getButtonMenu()->addChildAtPosition(m_nameLabel, Anchor::Left, ccp(13, 0), ccp(0, .5f)); - auto selectSpr = CCSprite::createWithSpriteFrameName("GJ_plus2Btn_001.png"); - selectSpr->setScale(.7f); - auto selectBtn = CCMenuItemSpriteExtra::create( - selectSpr, this, menu_selector(FileSettingNodeV3::onPickFile) + m_selectBtnSpr = CCSprite::createWithSpriteFrameName("GJ_plus2Btn_001.png"); + m_selectBtnSpr->setScale(.7f); + m_selectBtn = CCMenuItemSpriteExtra::create( + m_selectBtnSpr, this, menu_selector(FileSettingNodeV3::onPickFile) ); - this->getButtonMenu()->addChildAtPosition(selectBtn, Anchor::Right, ccp(-5, 0)); + this->getButtonMenu()->addChildAtPosition(m_selectBtn, Anchor::Right, ccp(-5, 0)); this->updateState(); @@ -425,6 +458,11 @@ void FileSettingNodeV3::updateState() { m_nameLabel->setOpacity(255); } m_nameLabel->limitLabelWidth(75, .35f, .1f); + + auto enable = this->getSetting()->shouldEnable(); + m_selectBtnSpr->setOpacity(enable ? 255 : 155); + m_selectBtnSpr->setColor(enable ? ccWHITE : ccGRAY); + m_selectBtn->setEnabled(enable); } void FileSettingNodeV3::onPickFile(CCObject*) { @@ -496,10 +534,10 @@ bool Color3BSettingNodeV3::init(std::shared_ptr setting, float m_colorSprite = ColorChannelSprite::create(); m_colorSprite->setScale(.65f); - auto button = CCMenuItemSpriteExtra::create( + m_colorBtn = CCMenuItemSpriteExtra::create( m_colorSprite, this, menu_selector(Color3BSettingNodeV3::onSelectColor) ); - this->getButtonMenu()->addChildAtPosition(button, Anchor::Right, ccp(-10, 0)); + this->getButtonMenu()->addChildAtPosition(m_colorBtn, Anchor::Right, ccp(-10, 0)); this->updateState(); @@ -509,6 +547,10 @@ bool Color3BSettingNodeV3::init(std::shared_ptr setting, float void Color3BSettingNodeV3::updateState() { SettingNodeV3::updateState(); m_colorSprite->setColor(m_value); + + auto enable = this->getSetting()->shouldEnable(); + m_colorSprite->setOpacity(enable ? 255 : 155); + m_colorBtn->setEnabled(enable); } void Color3BSettingNodeV3::onSelectColor(CCObject*) { @@ -559,10 +601,10 @@ bool Color4BSettingNodeV3::init(std::shared_ptr setting, float m_colorSprite = ColorChannelSprite::create(); m_colorSprite->setScale(.65f); - auto button = CCMenuItemSpriteExtra::create( + m_colorBtn = CCMenuItemSpriteExtra::create( m_colorSprite, this, menu_selector(Color4BSettingNodeV3::onSelectColor) ); - this->getButtonMenu()->addChildAtPosition(button, Anchor::Right, ccp(-10, 0)); + this->getButtonMenu()->addChildAtPosition(m_colorBtn, Anchor::Right, ccp(-10, 0)); this->updateState(); @@ -573,6 +615,10 @@ void Color4BSettingNodeV3::updateState() { SettingNodeV3::updateState(); m_colorSprite->setColor(to3B(m_value)); m_colorSprite->updateOpacity(m_value.a / 255.f); + + auto enable = this->getSetting()->shouldEnable(); + m_colorSprite->setOpacity(enable ? 255 : 155); + m_colorBtn->setEnabled(enable); } void Color4BSettingNodeV3::onSelectColor(CCObject*) { @@ -672,10 +718,10 @@ bool LegacyCustomSettingToV3Node::init(std::shared_ptr or } void LegacyCustomSettingToV3Node::settingValueChanged(SettingNode*) { - SettingNodeValueChangeEventV3(false).post(); + SettingNodeValueChangeEventV3(this, false).post(); } void LegacyCustomSettingToV3Node::settingValueCommitted(SettingNode*) { - SettingNodeValueChangeEventV3(true).post(); + SettingNodeValueChangeEventV3(this, true).post(); } void LegacyCustomSettingToV3Node::onCommit() { diff --git a/loader/src/loader/SettingNodeV3.hpp b/loader/src/loader/SettingNodeV3.hpp index 816acfae..8d942902 100644 --- a/loader/src/loader/SettingNodeV3.hpp +++ b/loader/src/loader/SettingNodeV3.hpp @@ -32,6 +32,8 @@ protected: CCMenuItemToggler* m_toggle; bool init(std::shared_ptr setting, float width); + + void updateState() override; void onCommit() override; void onToggle(CCObject*); @@ -57,6 +59,10 @@ protected: CCMenuItemSpriteExtra* m_bigArrowLeftBtn; CCMenuItemSpriteExtra* m_arrowRightBtn; CCMenuItemSpriteExtra* m_bigArrowRightBtn; + CCSprite* m_arrowLeftBtnSpr; + CCSprite* m_bigArrowLeftBtnSpr; + CCSprite* m_arrowRightBtnSpr; + CCSprite* m_bigArrowRightBtnSpr; float valueToSlider(ValueType value) { auto min = this->getSetting()->getMinValue().value_or(-100); @@ -79,28 +85,28 @@ protected: if (!SettingNodeV3::init(setting, width)) return false; - auto bigArrowLeftSpr = CCSprite::create(); - bigArrowLeftSpr->setCascadeColorEnabled(true); - bigArrowLeftSpr->setCascadeOpacityEnabled(true); + m_bigArrowLeftBtnSpr = CCSprite::create(); + m_bigArrowLeftBtnSpr->setCascadeColorEnabled(true); + m_bigArrowLeftBtnSpr->setCascadeOpacityEnabled(true); + auto bigArrowLeftSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); auto bigArrowLeftSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); - - bigArrowLeftSpr->setContentSize(bigArrowLeftSpr1->getContentSize() + ccp(20, 0)); - bigArrowLeftSpr->addChildAtPosition(bigArrowLeftSpr2, Anchor::Center, ccp(10, 0)); - bigArrowLeftSpr->addChildAtPosition(bigArrowLeftSpr1, Anchor::Center, ccp(-10, 0)); - bigArrowLeftSpr->setScale(.3f); + m_bigArrowLeftBtnSpr->setContentSize(bigArrowLeftSpr1->getContentSize() + ccp(20, 0)); + m_bigArrowLeftBtnSpr->addChildAtPosition(bigArrowLeftSpr2, Anchor::Center, ccp(10, 0)); + m_bigArrowLeftBtnSpr->addChildAtPosition(bigArrowLeftSpr1, Anchor::Center, ccp(-10, 0)); + m_bigArrowLeftBtnSpr->setScale(.3f); m_bigArrowLeftBtn = CCMenuItemSpriteExtra::create( - bigArrowLeftSpr, this, menu_selector(NumberSettingNodeV3::onArrow) + m_bigArrowLeftBtnSpr, this, menu_selector(NumberSettingNodeV3::onArrow) ); m_bigArrowLeftBtn->setUserObject(ObjWrapper::create(-setting->getBigArrowStepSize())); m_bigArrowLeftBtn->setVisible(setting->isBigArrowsEnabled()); this->getButtonMenu()->addChildAtPosition(m_bigArrowLeftBtn, Anchor::Left, ccp(5, 0)); - auto arrowLeftSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); - arrowLeftSpr->setScale(.5f); + m_arrowLeftBtnSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); + m_arrowLeftBtnSpr->setScale(.5f); m_arrowLeftBtn = CCMenuItemSpriteExtra::create( - arrowLeftSpr, this, menu_selector(NumberSettingNodeV3::onArrow) + m_arrowLeftBtnSpr, this, menu_selector(NumberSettingNodeV3::onArrow) ); m_arrowLeftBtn->setUserObject(ObjWrapper::create(-setting->getArrowStepSize())); m_arrowLeftBtn->setVisible(setting->isArrowsEnabled()); @@ -119,31 +125,31 @@ protected: } this->getButtonMenu()->addChildAtPosition(m_input, Anchor::Center); - auto arrowRightSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); - arrowRightSpr->setFlipX(true); - arrowRightSpr->setScale(.5f); + m_arrowRightBtnSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); + m_arrowRightBtnSpr->setFlipX(true); + m_arrowRightBtnSpr->setScale(.5f); m_arrowRightBtn = CCMenuItemSpriteExtra::create( - arrowRightSpr, this, menu_selector(NumberSettingNodeV3::onArrow) + m_arrowRightBtnSpr, this, menu_selector(NumberSettingNodeV3::onArrow) ); m_arrowRightBtn->setUserObject(ObjWrapper::create(setting->getArrowStepSize())); m_arrowRightBtn->setVisible(setting->isArrowsEnabled()); this->getButtonMenu()->addChildAtPosition(m_arrowRightBtn, Anchor::Right, ccp(-22, 0)); - auto bigArrowRightSpr = CCSprite::create(); - bigArrowRightSpr->setCascadeColorEnabled(true); - bigArrowRightSpr->setCascadeOpacityEnabled(true); + m_bigArrowRightBtnSpr = CCSprite::create(); + m_bigArrowRightBtnSpr->setCascadeColorEnabled(true); + m_bigArrowRightBtnSpr->setCascadeOpacityEnabled(true); auto bigArrowRightSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); bigArrowRightSpr1->setFlipX(true); auto bigArrowRightSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); bigArrowRightSpr2->setFlipX(true); - bigArrowRightSpr->setContentSize(bigArrowRightSpr1->getContentSize() + ccp(20, 0)); - bigArrowRightSpr->addChildAtPosition(bigArrowRightSpr1, Anchor::Center, ccp(-10, 0)); - bigArrowRightSpr->addChildAtPosition(bigArrowRightSpr2, Anchor::Center, ccp(10, 0)); - bigArrowRightSpr->setScale(.3f); + m_bigArrowRightBtnSpr->setContentSize(bigArrowRightSpr1->getContentSize() + ccp(20, 0)); + m_bigArrowRightBtnSpr->addChildAtPosition(bigArrowRightSpr1, Anchor::Center, ccp(-10, 0)); + m_bigArrowRightBtnSpr->addChildAtPosition(bigArrowRightSpr2, Anchor::Center, ccp(10, 0)); + m_bigArrowRightBtnSpr->setScale(.3f); m_bigArrowRightBtn = CCMenuItemSpriteExtra::create( - bigArrowRightSpr, this, menu_selector(NumberSettingNodeV3::onArrow) + m_bigArrowRightBtnSpr, this, menu_selector(NumberSettingNodeV3::onArrow) ); m_bigArrowRightBtn->setUserObject(ObjWrapper::create(setting->getBigArrowStepSize())); m_bigArrowRightBtn->setVisible(setting->isBigArrowsEnabled()); @@ -166,27 +172,35 @@ protected: void updateState() override { SettingNodeV3::updateState(); + auto enable = this->getSetting()->shouldEnable(); + if (this->getSetting()->isInputEnabled()) { + m_input->setEnabled(enable); + } + + auto min = this->getSetting()->getMinValue(); + auto enableLeft = enable && (!min || this->getCurrentValue() > *min); + m_arrowLeftBtn->setEnabled(enableLeft); + m_bigArrowLeftBtn->setEnabled(enableLeft); + m_arrowLeftBtnSpr->setOpacity(enableLeft ? 255 : 155); + m_arrowLeftBtnSpr->setColor(enableLeft ? ccWHITE : ccGRAY); + m_bigArrowLeftBtnSpr->setOpacity(enableLeft ? 255 : 155); + m_bigArrowLeftBtnSpr->setColor(enableLeft ? ccWHITE : ccGRAY); + + auto max = this->getSetting()->getMaxValue(); + auto enableRight = enable && (!max || this->getCurrentValue() < *max); + m_arrowRightBtn->setEnabled(enableRight); + m_bigArrowRightBtn->setEnabled(enableRight); + m_arrowRightBtnSpr->setOpacity(enableRight ? 255 : 155); + m_arrowRightBtnSpr->setColor(enableRight ? ccWHITE : ccGRAY); + m_bigArrowRightBtnSpr->setOpacity(enableRight ? 255 : 155); + m_bigArrowRightBtnSpr->setColor(enableRight ? ccWHITE : ccGRAY); + if (m_slider) { m_slider->m_touchLogic->m_thumb->setValue(this->valueToSlider(this->getCurrentValue())); m_slider->updateBar(); - } - if (auto min = this->getSetting()->getMinValue()) { - auto enable = this->getCurrentValue() > *min; - m_arrowLeftBtn->setEnabled(enable); - m_bigArrowLeftBtn->setEnabled(enable); - static_cast(m_arrowLeftBtn->getNormalImage())->setOpacity(enable ? 255 : 155); - static_cast(m_arrowLeftBtn->getNormalImage())->setColor(enable ? ccWHITE : ccGRAY); - static_cast(m_bigArrowLeftBtn->getNormalImage())->setOpacity(enable ? 255 : 155); - static_cast(m_bigArrowLeftBtn->getNormalImage())->setColor(enable ? ccWHITE : ccGRAY); - } - if (auto max = this->getSetting()->getMaxValue()) { - auto enable = this->getCurrentValue() < *max; - m_arrowRightBtn->setEnabled(enable); - m_bigArrowRightBtn->setEnabled(enable); - static_cast(m_arrowRightBtn->getNormalImage())->setOpacity(enable ? 255 : 155); - static_cast(m_arrowRightBtn->getNormalImage())->setColor(enable ? ccWHITE : ccGRAY); - static_cast(m_bigArrowRightBtn->getNormalImage())->setOpacity(enable ? 255 : 155); - static_cast(m_bigArrowRightBtn->getNormalImage())->setColor(enable ? ccWHITE : ccGRAY); + m_slider->m_sliderBar->setColor(enable ? ccWHITE : ccGRAY); + m_slider->m_touchLogic->m_thumb->setColor(enable ? ccWHITE : ccGRAY); + m_slider->m_touchLogic->m_thumb->setEnabled(enable); } } @@ -250,13 +264,17 @@ using FloatSettingNodeV3 = NumberSettingNodeV3; class StringSettingNodeV3 : public SettingNodeV3 { protected: TextInput* m_input; + CCSprite* m_arrowLeftSpr = nullptr; + CCSprite* m_arrowRightSpr = nullptr; bool init(std::shared_ptr setting, float width); - void onCommit() override; + void updateState() override; void onArrow(CCObject* sender); + void onCommit() override; + public: static StringSettingNodeV3* create(std::shared_ptr setting, float width); @@ -273,6 +291,8 @@ protected: std::filesystem::path m_path; CCLabelBMFont* m_nameLabel; EventListener>> m_pickListener; + CCMenuItemSpriteExtra* m_selectBtn; + CCSprite* m_selectBtnSpr; bool init(std::shared_ptr setting, float width); @@ -294,6 +314,7 @@ public: class Color3BSettingNodeV3 : public SettingNodeV3, public ColorPickPopupDelegate { protected: ccColor3B m_value; + CCMenuItemSpriteExtra* m_colorBtn; ColorChannelSprite* m_colorSprite; bool init(std::shared_ptr setting, float width); @@ -317,6 +338,7 @@ public: class Color4BSettingNodeV3 : public SettingNodeV3, public ColorPickPopupDelegate { protected: ccColor4B m_value; + CCMenuItemSpriteExtra* m_colorBtn; ColorChannelSprite* m_colorSprite; bool init(std::shared_ptr setting, float width); diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index 8f74da61..8f63dc97 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -5,6 +5,398 @@ using namespace geode::prelude; +namespace enable_if_parsing { + struct Component { + virtual ~Component() = default; + virtual Result<> check() const = 0; + virtual Result<> eval(std::string const& defaultModID) const = 0; + }; + struct RequireModLoaded final : public Component { + std::string modID; + RequireModLoaded(std::string const& modID) + : modID(modID) {} + + Result<> check() const override { + return Ok(); + } + Result<> eval(std::string const& defaultModID) const override { + if (Loader::get()->getLoadedMod(modID)) { + return Ok(); + } + auto modName = modID; + if (auto mod = Loader::get()->getInstalledMod(modID)) { + modName = mod->getName(); + } + return Err("Enable the mod {}", modName); + } + }; + struct RequireSettingEnabled final : public Component { + std::string modID; + std::string settingID; + RequireSettingEnabled(std::string const& modID, std::string const& settingID) + : modID(modID), settingID(settingID) {} + + Result<> check() const override { + if (auto mod = Loader::get()->getInstalledMod(modID)) { + if (!mod->hasSetting(settingID)) { + return Err("Mod '{}' does not have setting '{}'", mod->getName(), settingID); + } + if (!typeinfo_pointer_cast(mod->getSettingV3(settingID))) { + return Err("Setting '{}' in mod '{}' is not a boolean setting", settingID, mod->getName()); + } + } + return Ok(); + } + Result<> eval(std::string const& defaultModID) const override { + if (auto mod = Loader::get()->getLoadedMod(modID)) { + if (mod->template getSettingValue(settingID)) { + return Ok(); + } + // This is an if-check just in case, even though check() should already + // make sure that getSettingV3 is guaranteed to return true + auto name = settingID; + if (auto sett = mod->getSettingV3(settingID)) { + name = sett->getDisplayName(); + } + if (modID == defaultModID) { + return Err("Enable the setting '{}'", name); + } + return Err("Enable the setting '{}' from the mod {}", name, mod->getName()); + } + auto modName = modID; + if (auto mod = Loader::get()->getInstalledMod(modID)) { + modName = mod->getName(); + } + return Err("Enable the mod {}", modName); + } + }; + struct RequireSavedValueEnabled final : public Component { + std::string modID; + std::string savedValue; + RequireSavedValueEnabled(std::string const& modID, std::string const& savedValue) + : modID(modID), savedValue(savedValue) {} + + Result<> check() const override { + return Ok(); + } + Result<> eval(std::string const& defaultModID) const override { + if (auto mod = Loader::get()->getLoadedMod(modID)) { + if (mod->template getSavedValue(savedValue)) { + return Ok(); + } + if (modID == defaultModID) { + return Err("Enable the value '{}'", savedValue); + } + return Err("Enable the value '{}' from the mod {}", savedValue, mod->getName()); + } + auto modName = modID; + if (auto mod = Loader::get()->getInstalledMod(modID)) { + modName = mod->getName(); + } + return Err("Enable the mod {}", modName); + } + }; + struct RequireNot final : public Component { + std::unique_ptr component; + RequireNot(std::unique_ptr&& component) + : component(std::move(component)) {} + + Result<> check() const override { + return component->check(); + } + Result<> eval(std::string const& defaultModID) const override { + if (auto res = component->eval(defaultModID)) { + // Surely this will never break! + auto str = res.unwrapErr(); + string::replaceIP(str, "Enable", "___TEMP"); + string::replaceIP(str, "Disable", "Enable"); + string::replaceIP(str, "___TEMP", "Disable"); + return Err(str); + } + return Ok(); + } + }; + struct RequireAll final : public Component { + std::vector> components; + RequireAll(std::vector>&& components) + : components(std::move(components)) {} + + Result<> check() const override { + for (auto& comp : components) { + GEODE_UNWRAP(comp->check()); + } + return Ok(); + } + Result<> eval(std::string const& defaultModID) const override { + // Only print out whatever the first erroring condition is to not shit out + // "Please enable X and Y and Z and Ö and Å and" + for (auto& comp : components) { + GEODE_UNWRAP(comp->eval(defaultModID)); + } + return Ok(); + } + }; + struct RequireSome final : public Component { + std::vector> components; + RequireSome(std::vector>&& components) + : components(std::move(components)) {} + + Result<> check() const override { + for (auto& comp : components) { + GEODE_UNWRAP(comp->check()); + } + return Ok(); + } + Result<> eval(std::string const& defaultModID) const override { + Result<> err = Ok(); + for (auto& comp : components) { + auto res = comp->eval(defaultModID); + if (res) { + return Ok(); + } + // Only show first condition that isn't met + if (err.isOk()) { + err = Err(res.unwrapErr()); + } + } + return err; + } + }; + + static bool isComponentStartChar(char c) { + return + ('a' <= c && c <= 'z') || + ('A' <= c && c <= 'Z') || + c == '_'; + } + static bool isComponentContinueChar(char c) { + return + ('a' <= c && c <= 'z') || + ('A' <= c && c <= 'Z') || + ('0' <= c && c <= '9') || + c == '_' || c == '-' || c == '/' || + c == '.' || c == ':'; + } + + class Parser final { + private: + std::string_view m_src; + size_t m_index = 0; + std::string m_defaultModID; + + static bool isUnOpWord(std::string_view op) { + return op == "!"; + } + static bool isBiOpWord(std::string_view op) { + return op == "&&" || op == "||"; + } + + Result> nextWord() { + // Skip whitespace + while (m_index < m_src.size() && std::isspace(m_src[m_index])) { + m_index += 1; + } + if (m_index == m_src.size()) { + return Ok(std::nullopt); + } + // Parentheses & single operators + if (m_src[m_index] == '(' || m_src[m_index] == ')' || m_src[m_index] == '!') { + m_index += 1; + return Ok(m_src.substr(m_index - 1, 1)); + } + // Double-character operators + if (m_src[m_index] == '&' || m_src[m_index] == '|') { + // Consume first character + m_index += 1; + // Next character must be the same + if (m_index == m_src.size() || m_src[m_index - 1] != m_src[m_index]) { + return Err("Expected '{}' at index {}", m_src[m_index - 1], m_index - 1); + } + // Consume second character + m_index += 1; + return Ok(m_src.substr(m_index - 2, 2)); + } + // Components + if (isComponentStartChar(m_src[m_index])) { + auto start = m_index; + m_index += 1; + while (m_index < m_src.size() && isComponentContinueChar(m_src[m_index])) { + m_index += 1; + } + return Ok(m_src.substr(start, m_index - start)); + } + return Err("Unexpected character '{}' at index {}", m_src[m_index], m_index); + } + std::optional peekWord() { + auto original = m_index; + auto ret = this->nextWord(); + m_index = original; + return ret ? *ret : std::nullopt; + } + Result> nextComponent() { + GEODE_UNWRAP_INTO(auto maybeWord, this->nextWord()); + if (!maybeWord) { + return Err("Expected component, got end-of-enable-if-string"); + } + const auto word = *maybeWord; + if (isUnOpWord(word) || isBiOpWord(word)) { + return Err("Expected component, got operator \"{}\" at index {}", word, m_index - word.size()); + } + if (word == ")") { + return Err("Unexpected closing parenthesis at index {}", m_index - 1); + } + if (word == "(") { + GEODE_UNWRAP_INTO(auto op, this->next()); + GEODE_UNWRAP_INTO(auto maybeClosing, this->nextWord()); + if (!maybeClosing) { + return Err("Expected closing parenthesis, got end-of-enable-if-string"); + } + if (maybeClosing != ")") { + return Err( + "Expected closing parenthesis, got \"{}\" at index {}", + *maybeClosing, m_index - maybeClosing->size() + ); + } + return Ok(std::move(op)); + } + std::string_view ty = "setting"; + std::string_view value = word; + if (word.find(':') != std::string::npos) { + ty = word.substr(0, word.find(':')); + value = word.substr(word.find(':') + 1); + } + switch (hash(ty)) { + case hash("setting"): { + std::string modID = m_defaultModID; + std::string settingID = std::string(value); + // mod.id/setting-id + if (value.find('/') != std::string::npos) { + modID = value.substr(0, value.find('/')); + settingID = value.substr(value.find('/') + 1); + } + if (!ModMetadata::validateID(std::string(modID))) { + return Err("Invalid mod ID '{}'", modID); + } + return Ok(std::make_unique(modID, settingID)); + } break; + + case hash("saved"): { + std::string modID = m_defaultModID; + std::string savedValue = std::string(value); + // mod.id/setting-id + if (value.find('/') != std::string::npos) { + modID = value.substr(0, value.find('/')); + savedValue = value.substr(value.find('/') + 1); + } + if (!ModMetadata::validateID(std::string(modID))) { + return Err("Invalid mod ID '{}'", modID); + } + return Ok(std::make_unique(modID, savedValue)); + } break; + + case hash("loaded"): { + if (!ModMetadata::validateID(std::string(value))) { + return Err("Invalid mod ID '{}'", value); + } + return Ok(std::make_unique(std::string(value))); + } break; + + default: { + return Err("Invalid designator '{}' at index {}", ty, m_index - word.size()); + } break; + } + } + Result> nextUnOp() { + std::string op; + if (auto peek = this->peekWord()) { + if (isUnOpWord(*peek)) { + op = *peek; + } + } + GEODE_UNWRAP_INTO(auto comp, this->nextComponent()); + if (op.empty()) { + return Ok(std::move(comp)); + } + switch (hash(op)) { + case hash("!"): { + return Ok(std::make_unique(std::move(comp))); + } break; + default: { + return Err( + "THIS SHOULD BE UNREACHABLE!! \"{}\" was an unhandled " + "unary operator despite isUnOpWord claiming it's valid! " + "REPORT THIS BUG TO GEODE DEVELOPERS", + op + ); + } break; + } + } + Result> nextBiOp() { + GEODE_UNWRAP_INTO(auto first, this->nextUnOp()); + std::string firstOp; + std::vector> components; + while (auto peek = this->peekWord()) { + if (!isBiOpWord(*peek)) { + break; + } + GEODE_UNWRAP_INTO(auto word, this->nextWord()); + auto op = *word; + if (firstOp.empty()) { + firstOp = op; + } + if (op != firstOp) { + return Err( + "Expected operator \"{}\", got operator \"{}\" - " + "parentheses are required to disambiguate operator chains", + firstOp, op + ); + } + GEODE_UNWRAP_INTO(auto comp, this->nextUnOp()); + components.emplace_back(std::move(comp)); + } + if (components.size()) { + components.emplace(components.begin(), std::move(first)); + switch (hash(firstOp)) { + case hash("&&"): { + return Ok(std::make_unique(std::move(components))); + } break; + case hash("||"): { + return Ok(std::make_unique(std::move(components))); + } break; + default: { + return Err( + "THIS SHOULD BE UNREACHABLE!! \"{}\" was an unhandled " + "binary operator despite isBiOpWord claiming it's valid! " + "REPORT THIS BUG TO GEODE DEVELOPERS", + firstOp + ); + } break; + } + } + return Ok(std::move(first)); + } + Result> next() { + return this->nextBiOp(); + } + + public: + static Result> parse(std::string_view str, std::string const& defaultModID) { + auto ret = Parser(); + ret.m_src = str; + ret.m_defaultModID = defaultModID; + GEODE_UNWRAP_INTO(auto comp, ret.next()); + GEODE_UNWRAP_INTO(auto shouldBeEOF, ret.nextWord()); + if (shouldBeEOF) { + return Err( + "Expected end-of-enable-if-string, got \"{}\" at index {}", + *shouldBeEOF, ret.m_index - shouldBeEOF->size() + ); + } + return Ok(std::move(comp)); + } + }; +} + class SettingV3::GeodeImpl { public: std::string modID; @@ -12,6 +404,8 @@ public: std::optional name; std::optional description; std::optional enableIf; + std::unique_ptr enableIfTree; + std::optional enableIfDescription; bool requiresRestart = false; }; @@ -30,8 +424,16 @@ void SettingV3::parseSharedProperties(std::string const& key, std::string const& value.has("name").into(m_impl->name); value.has("description").into(m_impl->description); if (!onlyNameAndDesc) { - value.has("enable-if").into(m_impl->enableIf); value.has("requires-restart").into(m_impl->requiresRestart); + value.has("enable-if") + .template mustBe("a valid \"enable-if\" scheme", [this](std::string const& str) -> Result<> { + GEODE_UNWRAP_INTO(auto tree, enable_if_parsing::Parser::parse(str, m_impl->modID)); + GEODE_UNWRAP(tree->check()); + m_impl->enableIfTree = std::move(tree); + return Ok(); + }) + .into(m_impl->enableIf); + value.has("enable-if-description").into(m_impl->enableIfDescription); } } void SettingV3::init(std::string const& key, std::string const& modID) { @@ -57,6 +459,25 @@ std::optional SettingV3::getDescription() const { std::optional SettingV3::getEnableIf() const { return m_impl->enableIf; } +bool SettingV3::shouldEnable() const { + if (m_impl->enableIfTree) { + return m_impl->enableIfTree->eval(m_impl->modID).isOk(); + } + return true; +} +std::optional SettingV3::getEnableIfDescription() const { + if (m_impl->enableIfDescription) { + return *m_impl->enableIfDescription; + } + if (!m_impl->enableIfTree) { + return std::nullopt; + } + auto res = m_impl->enableIfTree->eval(m_impl->modID); + if (res) { + return std::nullopt; + } + return res.unwrapErr(); +} bool SettingV3::requiresRestart() const { return m_impl->requiresRestart; } diff --git a/loader/src/ui/mods/settings/ModSettingsPopup.cpp b/loader/src/ui/mods/settings/ModSettingsPopup.cpp index 4b618b57..9c8a1546 100644 --- a/loader/src/ui/mods/settings/ModSettingsPopup.cpp +++ b/loader/src/ui/mods/settings/ModSettingsPopup.cpp @@ -87,8 +87,8 @@ bool ModSettingsPopup::setup(Mod* mod) { ); m_buttonMenu->addChildAtPosition(openDirBtn, Anchor::BottomRight, ccp(-53, 20)); - m_changeListener.bind([this](auto) { - this->updateState(); + m_changeListener.bind([this](auto* ev) { + this->updateState(ev->getNode()); return ListenerResult::Propagate; }); this->updateState(); @@ -125,7 +125,17 @@ void ModSettingsPopup::onResetAll(CCObject*) { ); } -void ModSettingsPopup::updateState() { +void ModSettingsPopup::updateState(SettingNodeV3* invoker) { + // Update all settings with "enable-if" schemes + for (auto& sett : m_settings) { + // Avoid infinite loops + if (sett == invoker) { + continue; + } + if (sett->getSetting()->getEnableIf()) { + sett->updateState(); + } + } m_applyBtnSpr->setCascadeColorEnabled(true); m_applyBtnSpr->setCascadeOpacityEnabled(true); if (this->hasUncommitted()) { diff --git a/loader/src/ui/mods/settings/ModSettingsPopup.hpp b/loader/src/ui/mods/settings/ModSettingsPopup.hpp index 51e8a9c2..bc349293 100644 --- a/loader/src/ui/mods/settings/ModSettingsPopup.hpp +++ b/loader/src/ui/mods/settings/ModSettingsPopup.hpp @@ -16,8 +16,7 @@ protected: EventListener> m_changeListener; bool setup(Mod* mod) override; - void updateState(); - void onChangeEvent(SettingNodeValueChangeEventV3* event); + void updateState(SettingNodeV3* invoker = nullptr); bool hasUncommitted() const; void onClose(CCObject*) override; void onApply(CCObject*); From 659c168a14347aa8c718dfd472008effed4c90b5 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sat, 31 Aug 2024 21:35:41 +0300 Subject: [PATCH 31/54] add generic function helpers --- loader/include/Geode/utils/function.hpp | 59 +++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 loader/include/Geode/utils/function.hpp diff --git a/loader/include/Geode/utils/function.hpp b/loader/include/Geode/utils/function.hpp new file mode 100644 index 00000000..855f1951 --- /dev/null +++ b/loader/include/Geode/utils/function.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include + +namespace geode::utils::function { + namespace detail { + template + struct ImplExtract; + + template + struct ImplExtract { + using Type = R(A...); + using Return = R; + using Args = std::tuple; + static constexpr std::size_t ARG_COUNT = std::tuple_size_v; + }; + template + struct ImplExtract { + using Type = R(A...); + using Return = R; + using Args = std::tuple; + static constexpr std::size_t ARG_COUNT = std::tuple_size_v; + }; + template + struct ImplExtract { + using Type = R(A...); + using Class = C; + using Return = R; + using Args = std::tuple; + static constexpr std::size_t ARG_COUNT = std::tuple_size_v; + }; + template + struct ImplExtract { + using Type = R(A...); + using Class = C; + using Return = R; + using Args = std::tuple; + static constexpr std::size_t ARG_COUNT = std::tuple_size_v; + }; + template + requires requires { &F::operator(); } + struct ImplExtract : public ImplExtract {}; + + template + using Extract = ImplExtract>; + } + + template + using FunctionInfo = detail::Extract; + + template + using Return = typename detail::Extract::Return; + + template + using Args = typename detail::Extract::Args; + + template + using Arg = std::tuple_element_t::Args>; +} From 6eb079735fe71ee5b352843a9c21311509cc7f84 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sat, 31 Aug 2024 21:36:53 +0300 Subject: [PATCH 32/54] setting events & restart stuff --- .../Geode/loader/ModSettingsManager.hpp | 10 +++ loader/include/Geode/loader/SettingEvent.hpp | 25 ++----- loader/include/Geode/loader/SettingV3.hpp | 65 +++++++++++++++++ loader/include/Geode/utils/general.hpp | 9 +++ loader/src/loader/ModSettingsManager.cpp | 10 +++ loader/src/loader/Setting.cpp | 6 +- loader/src/loader/SettingNodeV3.cpp | 2 +- loader/src/loader/SettingV3.cpp | 72 +++++++++++++++++++ loader/src/ui/mods/ModsLayer.cpp | 5 ++ loader/src/ui/mods/ModsLayer.hpp | 2 + loader/src/ui/mods/sources/ModListSource.cpp | 4 ++ 11 files changed, 185 insertions(+), 25 deletions(-) diff --git a/loader/include/Geode/loader/ModSettingsManager.hpp b/loader/include/Geode/loader/ModSettingsManager.hpp index 36b8ac34..6281bf7b 100644 --- a/loader/include/Geode/loader/ModSettingsManager.hpp +++ b/loader/include/Geode/loader/ModSettingsManager.hpp @@ -9,6 +9,10 @@ namespace geode { class Impl; std::unique_ptr m_impl; + friend class ::geode::SettingV3; + + void markRestartRequired(); + public: static ModSettingsManager* from(Mod* mod); @@ -42,5 +46,11 @@ namespace geode { std::shared_ptr get(std::string_view key); std::shared_ptr getLegacy(std::string_view key); std::optional getLegacyDefinition(std::string_view key); + + /** + * Returns true if any setting with the `"restart-required"` attribute + * has been altered + */ + bool restartRequired() const; }; } diff --git a/loader/include/Geode/loader/SettingEvent.hpp b/loader/include/Geode/loader/SettingEvent.hpp index 7ad33fe1..b84b7884 100644 --- a/loader/include/Geode/loader/SettingEvent.hpp +++ b/loader/include/Geode/loader/SettingEvent.hpp @@ -4,18 +4,18 @@ #include "Loader.hpp" #include "Setting.hpp" #include "Mod.hpp" - +#include "SettingV3.hpp" #include namespace geode { - struct GEODE_DLL SettingChangedEvent : public Event { + struct GEODE_DLL [[deprecated("Use SettingChangedEventV3 from SettingV3.hpp instead")]] SettingChangedEvent : public Event { Mod* mod; SettingValue* value; SettingChangedEvent(Mod* mod, SettingValue* value); }; - class GEODE_DLL SettingChangedFilter : public EventFilter { + class GEODE_DLL [[deprecated("Use SettingChangedEventV3 from SettingV3.hpp instead")]] SettingChangedFilter : public EventFilter { protected: std::string m_modID; std::optional m_targetKey; @@ -40,7 +40,7 @@ namespace geode { * Listen for built-in setting changes */ template - class GeodeSettingChangedFilter : public SettingChangedFilter { + class [[deprecated("Use SettingChangedEventV3 from SettingV3.hpp instead")]] GeodeSettingChangedFilter : public SettingChangedFilter { public: using Callback = void(T); @@ -60,21 +60,4 @@ namespace geode { ) : SettingChangedFilter(modID, settingID) {} GeodeSettingChangedFilter(GeodeSettingChangedFilter const&) = default; }; - - template - std::monostate listenForSettingChanges( - std::string const& settingKey, void (*callback)(T) - ) { - (void)new EventListener( - callback, GeodeSettingChangedFilter(getMod()->getID(), settingKey) - ); - return std::monostate(); - } - - static std::monostate listenForAllSettingChanges(void (*callback)(SettingValue*)) { - (void)new EventListener( - callback, SettingChangedFilter(getMod()->getID(), std::nullopt) - ); - return std::monostate(); - } } diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index fa9e1c7a..9d51732d 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -8,6 +8,7 @@ #include "../utils/cocos.hpp" // this unfortunately has to be included because of C++ templates #include "../utils/JsonValidation.hpp" +#include "../utils/function.hpp" // todo in v4: this can be removed as well as the friend decl in LegacyCustomSettingV3 class LegacyCustomSettingToV3Node; @@ -16,6 +17,8 @@ class ModSettingsPopup; namespace geode { class ModSettingsManager; class SettingNodeV3; + // todo in v4: remove this + class SettingValue; class GEODE_DLL SettingV3 : public std::enable_shared_from_this { private: @@ -27,6 +30,14 @@ namespace geode { Result<> parseSharedProperties(std::string const& key, std::string const& modID, matjson::Value const& value, bool onlyNameAndDesc = false); void parseSharedProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value, bool onlyNameAndDesc = false); + /** + * Mark that the value of this setting has changed. This should be + * ALWAYS called on every setter that can modify the setting's state! + */ + void markChanged(); + + friend class ::geode::SettingValue; + public: SettingV3(); virtual ~SettingV3(); @@ -120,6 +131,7 @@ namespace geode { } void setValue(V value) { this->getValueMut() = this->isValid(value) ? value : this->getDefaultValue(); + this->markChanged(); } virtual Result<> isValid(V value) const = 0; @@ -452,6 +464,38 @@ namespace geode { std::shared_ptr getSetting() const; }; + class GEODE_DLL SettingChangedEventV3 final : public Event { + private: + class Impl; + std::shared_ptr m_impl; + + public: + SettingChangedEventV3(std::shared_ptr setting); + + std::shared_ptr getSetting() const; + }; + class GEODE_DLL SettingChangedFilterV3 final : public EventFilter { + private: + class Impl; + std::shared_ptr m_impl; + + public: + using Callback = void(std::shared_ptr); + + ListenerResult handle(utils::MiniFunction fn, SettingChangedEventV3* event); + /** + * Listen to changes on a setting, or all settings + * @param modID Mod whose settings to listen to + * @param settingKey Setting to listen to, or all settings if nullopt + */ + SettingChangedFilterV3( + std::string const& modID, + std::optional const& settingKey + ); + SettingChangedFilterV3(Mod* mod, std::optional const& settingKey); + SettingChangedFilterV3(SettingChangedFilterV3 const&); + }; + class GEODE_DLL SettingNodeSizeChangeEventV3 : public Event { private: class Impl; @@ -512,4 +556,25 @@ namespace geode { struct SettingTypeForValueType { using SettingType = Color4BSettingV3; }; + + template + EventListener* listenForSettingChanges(std::string_view settingKey, auto&& callback, Mod* mod = getMod()) { + using Ty = typename SettingTypeForValueType::SettingType; + return new EventListener( + [callback = std::move(callback)](std::shared_ptr setting) { + if (auto ty = typeinfo_pointer_cast(setting)) { + callback(ty->getValue()); + } + }, + SettingChangedFilterV3(mod, std::string(settingKey)) + ); + } + EventListener* listenForSettingChanges(std::string_view settingKey, auto&& callback, Mod* mod = getMod()) { + using T = std::remove_cvref_t>; + return listenForSettingChanges(settingKey, std::move(callback), mod); + } + GEODE_DLL EventListener* listenForAllSettingChanges( + std::function)> const& callback, + Mod* mod = getMod() + ); } diff --git a/loader/include/Geode/utils/general.hpp b/loader/include/Geode/utils/general.hpp index d23b3c63..c174658c 100644 --- a/loader/include/Geode/utils/general.hpp +++ b/loader/include/Geode/utils/general.hpp @@ -17,13 +17,22 @@ namespace geode { using ByteVector = std::vector; + // todo in v4: remove this template + [[deprecated("Use geode::toBytes instead")]] ByteVector toByteArray(T const& a) { ByteVector out; out.resize(sizeof(T)); std::memcpy(out.data(), &a, sizeof(T)); return out; } + template + ByteVector toBytes(T const& a) { + ByteVector out; + out.resize(sizeof(T)); + std::memcpy(out.data(), &a, sizeof(T)); + return out; + } namespace utils { // helper for std::visit diff --git a/loader/src/loader/ModSettingsManager.cpp b/loader/src/loader/ModSettingsManager.cpp index dc625ac7..e01d1887 100644 --- a/loader/src/loader/ModSettingsManager.cpp +++ b/loader/src/loader/ModSettingsManager.cpp @@ -78,6 +78,7 @@ public: }; std::string modID; std::unordered_map settings; + bool restartRequired = false; void createSettings() { for (auto& [key, setting] : settings) { @@ -103,6 +104,7 @@ public: }; ModSettingsManager* ModSettingsManager::from(Mod* mod) { + if (!mod) return nullptr; return ModImpl::getImpl(mod)->m_settings.get(); } @@ -136,6 +138,10 @@ ModSettingsManager::ModSettingsManager(ModMetadata const& metadata) ModSettingsManager::~ModSettingsManager() {} ModSettingsManager::ModSettingsManager(ModSettingsManager&&) = default; +void ModSettingsManager::markRestartRequired() { + m_impl->restartRequired = true; +} + Result<> ModSettingsManager::registerCustomSettingType(std::string_view type, SettingGenerator generator) { GEODE_UNWRAP(SharedSettingTypesPool::get().add(m_impl->modID, type, generator)); m_impl->createSettings(); @@ -230,3 +236,7 @@ std::optional ModSettingsManager::getLegacyDefinition(std::string_view } return std::nullopt; } + +bool ModSettingsManager::restartRequired() const { + return m_impl->restartRequired; +} diff --git a/loader/src/loader/Setting.cpp b/loader/src/loader/Setting.cpp index c7892cd3..f9562eb2 100644 --- a/loader/src/loader/Setting.cpp +++ b/loader/src/loader/Setting.cpp @@ -280,10 +280,10 @@ std::string SettingValue::getModID() const { } void SettingValue::valueChanged() { - // this is actually p neat because now if the mod gets disabled this wont - // post the event so that side-effect is automatically handled :3 if (auto mod = Loader::get()->getLoadedMod(m_modID)) { - SettingChangedEvent(mod, this).post(); + if (auto sett = mod->getSettingV3(m_key)) { + sett->markChanged(); + } } } diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index 5f8add60..0caee58d 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -125,7 +125,7 @@ void SettingNodeV3::updateState() { m_impl->errorLabel->setString(desc->c_str()); } } - if (m_impl->setting->requiresRestart() && (this->hasUncommittedChanges() || m_impl->committed)) { + if (m_impl->setting->requiresRestart() && m_impl->committed) { m_impl->errorLabel->setVisible(true); m_impl->errorLabel->setColor("mod-list-restart-required-label"_cc3b); m_impl->errorLabel->setString("Restart Required"); diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index 8f63dc97..af2d5a43 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include "SettingNodeV3.hpp" @@ -397,6 +399,63 @@ namespace enable_if_parsing { }; } +class SettingChangedEventV3::Impl final { +public: + std::shared_ptr setting; +}; + +SettingChangedEventV3::SettingChangedEventV3(std::shared_ptr setting) + : m_impl(std::make_shared()) +{ + m_impl->setting = setting; +} + +std::shared_ptr SettingChangedEventV3::getSetting() const { + return m_impl->setting; +} + +class SettingChangedFilterV3::Impl final { +public: + std::string modID; + std::optional settingKey; +}; + +ListenerResult SettingChangedFilterV3::handle(utils::MiniFunction fn, SettingChangedEventV3* event) { + if ( + event->getSetting()->getModID() == m_impl->modID && + !m_impl->settingKey || event->getSetting()->getKey() == m_impl->settingKey + ) { + fn(event->getSetting()); + } + return ListenerResult::Propagate; +} + +SettingChangedFilterV3::SettingChangedFilterV3( + std::string const& modID, + std::optional const& settingKey +) : m_impl(std::make_shared()) +{ + m_impl->modID = modID; + m_impl->settingKey = settingKey; +} + +SettingChangedFilterV3::SettingChangedFilterV3(Mod* mod, std::optional const& settingKey) + : SettingChangedFilterV3(mod->getID(), settingKey) {} + +SettingChangedFilterV3::SettingChangedFilterV3(SettingChangedFilterV3 const&) = default; + +EventListener* geode::listenForAllSettingChanges( + std::function)> const& callback, + Mod* mod +) { + return new EventListener( + [callback](std::shared_ptr setting) { + callback(setting); + }, + SettingChangedFilterV3(mod->getID(), std::nullopt) + ); +} + class SettingV3::GeodeImpl { public: std::string modID; @@ -485,6 +544,19 @@ Mod* SettingV3::getMod() const { return Loader::get()->getInstalledMod(m_impl->modID); } +void SettingV3::markChanged() { + auto manager = ModSettingsManager::from(this->getMod()); + if (m_impl->requiresRestart) { + manager->markRestartRequired(); + } + SettingChangedEventV3(shared_from_this()).post(); + if (manager) { + // Use ModSettingsManager rather than convertToLegacyValue since it + // caches the result and we want to have that for performance + SettingChangedEvent(this->getMod(), manager->getLegacy(this->getKey()).get()).post(); + } +} + std::optional SettingV3::convertToLegacy() const { return std::nullopt; } diff --git a/loader/src/ui/mods/ModsLayer.cpp b/loader/src/ui/mods/ModsLayer.cpp index 1b83dbcd..ce7562f8 100644 --- a/loader/src/ui/mods/ModsLayer.cpp +++ b/loader/src/ui/mods/ModsLayer.cpp @@ -90,6 +90,11 @@ bool ModsStatusNode::init() { m_downloadListener.bind([this](auto) { this->updateState(); }); + m_settingNodeListener.bind([this](SettingNodeValueChangeEventV3* ev) { + this->updateState(); + return ListenerResult::Propagate; + }); + this->updateState(); return true; diff --git a/loader/src/ui/mods/ModsLayer.hpp b/loader/src/ui/mods/ModsLayer.hpp index 7e7de630..35e36b97 100644 --- a/loader/src/ui/mods/ModsLayer.hpp +++ b/loader/src/ui/mods/ModsLayer.hpp @@ -12,6 +12,7 @@ #include "sources/ModListSource.hpp" #include "UpdateModListState.hpp" #include +#include using namespace geode::prelude; @@ -39,6 +40,7 @@ protected: EventListener m_updateStateListener; EventListener m_downloadListener; DownloadState m_lastState = DownloadState::None; + EventListener> m_settingNodeListener; bool init(); void updateState(); diff --git a/loader/src/ui/mods/sources/ModListSource.cpp b/loader/src/ui/mods/sources/ModListSource.cpp index 19ced217..e19e1c43 100644 --- a/loader/src/ui/mods/sources/ModListSource.cpp +++ b/loader/src/ui/mods/sources/ModListSource.cpp @@ -1,5 +1,6 @@ #include "ModListSource.hpp" #include +#include #define FTS_FUZZY_MATCH_IMPLEMENTATION #include @@ -90,6 +91,9 @@ bool ModListSource::isRestartRequired() { if (mod->getRequestedAction() != ModRequestedAction::None) { return true; } + if (ModSettingsManager::from(mod)->restartRequired()) { + return true; + } } if (server::ModDownloadManager::get()->wantsRestart()) { return true; From 8a826065a1c1d4f64e9b00ebf37e40564a957180 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sat, 31 Aug 2024 22:00:01 +0300 Subject: [PATCH 33/54] add restart required because of settings to moditem and modpopup --- loader/src/ui/mods/list/ModItem.cpp | 5 +++++ loader/src/ui/mods/list/ModItem.hpp | 1 + loader/src/ui/mods/popups/ModPopup.cpp | 27 ++++++++++++++++++++++-- loader/src/ui/mods/popups/ModPopup.hpp | 2 ++ loader/src/ui/mods/sources/ModSource.cpp | 4 +++- 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/loader/src/ui/mods/list/ModItem.cpp b/loader/src/ui/mods/list/ModItem.cpp index ee85ab85..91e769ae 100644 --- a/loader/src/ui/mods/list/ModItem.cpp +++ b/loader/src/ui/mods/list/ModItem.cpp @@ -283,6 +283,11 @@ bool ModItem::init(ModSource&& source) { m_downloadListener.bind([this](auto) { this->updateState(); }); m_downloadListener.setFilter(server::ModDownloadFilter(m_source.getID())); + m_settingNodeListener.bind([this](SettingNodeValueChangeEventV3*) { + this->updateState(); + return ListenerResult::Propagate; + }); + return true; } diff --git a/loader/src/ui/mods/list/ModItem.hpp b/loader/src/ui/mods/list/ModItem.hpp index 717c030c..f5f9fc90 100644 --- a/loader/src/ui/mods/list/ModItem.hpp +++ b/loader/src/ui/mods/list/ModItem.hpp @@ -35,6 +35,7 @@ protected: EventListener>> m_checkUpdateListener; EventListener m_downloadListener; std::optional m_availableUpdate; + EventListener> m_settingNodeListener; /** * @warning Make sure `getMetadata` and `createModLogo` are callable diff --git a/loader/src/ui/mods/popups/ModPopup.cpp b/loader/src/ui/mods/popups/ModPopup.cpp index 82006b0b..03427bf3 100644 --- a/loader/src/ui/mods/popups/ModPopup.cpp +++ b/loader/src/ui/mods/popups/ModPopup.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include "ConfirmUninstallPopup.hpp" @@ -557,6 +558,13 @@ bool ModPopup::setup(ModSource&& src) { mainContainer->updateLayout(); m_mainLayer->addChildAtPosition(mainContainer, Anchor::Center); + m_settingsBG = CCScale9Sprite::create("square02b_001.png"); + m_settingsBG->setColor({ 0, 0, 0 }); + m_settingsBG->setOpacity(75); + m_settingsBG->setScale(.3f); + m_settingsBG->setContentSize(ccp(35, 30) / linksBG->getScale()); + m_buttonMenu->addChildAtPosition(m_settingsBG, Anchor::BottomLeft, ccp(28, 25)); + auto settingsSpr = createGeodeCircleButton(CCSprite::createWithSpriteFrameName("settings.png"_spr)); settingsSpr->setScale(.6f); auto settingsBtn = CCMenuItemSpriteExtra::create( @@ -601,15 +609,30 @@ bool ModPopup::setup(ModSource&& src) { m_downloadListener.bind([this](auto) { this->updateState(); }); m_downloadListener.setFilter(m_source.getID()); + m_settingNodeListener.bind([this](SettingNodeValueChangeEventV3*) { + this->updateState(); + return ListenerResult::Propagate; + }); + return true; } void ModPopup::updateState() { auto asMod = m_source.asMod(); auto wantsRestart = m_source.wantsRestart(); + auto wantsRestartBecauseOfSettings = asMod && ModSettingsManager::from(asMod)->restartRequired(); - m_installBG->setColor(wantsRestart ? to3B(ColorProvider::get()->color("mod-list-restart-required-label"_spr)) : ccc3(0, 0, 0)); - m_installBG->setOpacity(wantsRestart ? 40 : 75); + m_installBG->setColor((wantsRestart && !wantsRestartBecauseOfSettings) ? + to3B(ColorProvider::get()->color("mod-list-restart-required-label"_spr)) : + ccBLACK + ); + m_installBG->setOpacity((wantsRestart && !wantsRestartBecauseOfSettings) ? 40 : 75); + m_settingsBG->setColor(wantsRestartBecauseOfSettings ? + to3B(ColorProvider::get()->color("mod-list-restart-required-label"_spr)) : + ccBLACK + ); + m_settingsBG->setOpacity(wantsRestartBecauseOfSettings ? 40 : 75); + m_settingsBG->setVisible(wantsRestartBecauseOfSettings); m_restartRequiredLabel->setVisible(wantsRestart); if (!wantsRestart && asMod) { diff --git a/loader/src/ui/mods/popups/ModPopup.hpp b/loader/src/ui/mods/popups/ModPopup.hpp index b91c5092..a6bbf789 100644 --- a/loader/src/ui/mods/popups/ModPopup.hpp +++ b/loader/src/ui/mods/popups/ModPopup.hpp @@ -30,6 +30,7 @@ protected: CCMenuItemSpriteExtra* m_cancelBtn; CCLabelBMFont* m_installStatusLabel; CCScale9Sprite* m_installBG; + CCScale9Sprite* m_settingsBG; CCLabelBMFont* m_enabledStatusLabel; ButtonSprite* m_restartRequiredLabel; CCNode* m_rightColumn; @@ -40,6 +41,7 @@ protected: EventListener>> m_checkUpdateListener; EventListener m_updateStateListener; EventListener m_downloadListener; + EventListener> m_settingNodeListener; bool setup(ModSource&& src) override; void updateState(); diff --git a/loader/src/ui/mods/sources/ModSource.cpp b/loader/src/ui/mods/sources/ModSource.cpp index 0c5ef5d3..fe7bb2e0 100644 --- a/loader/src/ui/mods/sources/ModSource.cpp +++ b/loader/src/ui/mods/sources/ModSource.cpp @@ -1,6 +1,7 @@ #include "ModSource.hpp" #include +#include #include #include #include @@ -106,7 +107,8 @@ bool ModSource::wantsRestart() const { } return std::visit(makeVisitor { [](Mod* mod) { - return mod->getRequestedAction() != ModRequestedAction::None; + return mod->getRequestedAction() != ModRequestedAction::None || + ModSettingsManager::from(mod)->restartRequired(); }, [](server::ServerModMetadata const& metdata) { return false; From 6b8dd4a1ae8d24f6c05f79c1ce150c871393466e Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sat, 31 Aug 2024 23:07:29 +0300 Subject: [PATCH 34/54] add restart and config dir buttons to settings popup --- .../src/ui/mods/settings/ModSettingsPopup.cpp | 74 ++++++++++++++++--- .../src/ui/mods/settings/ModSettingsPopup.hpp | 5 ++ 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/loader/src/ui/mods/settings/ModSettingsPopup.cpp b/loader/src/ui/mods/settings/ModSettingsPopup.cpp index 9c8a1546..f3c65103 100644 --- a/loader/src/ui/mods/settings/ModSettingsPopup.cpp +++ b/loader/src/ui/mods/settings/ModSettingsPopup.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -63,13 +64,26 @@ bool ModSettingsPopup::setup(Mod* mod) { // buttons + m_applyMenu = CCMenu::create(); + m_applyMenu->setContentWidth(150); + m_applyMenu->setLayout(RowLayout::create()); + m_applyMenu->getLayout()->ignoreInvisibleChildren(true); + + auto restartBtnSpr = createGeodeButton("Restart Now", true); + restartBtnSpr->setScale(.6f); + m_restartBtn = CCMenuItemSpriteExtra::create( + restartBtnSpr, this, menu_selector(ModSettingsPopup::onRestart) + ); + m_applyMenu->addChildAtPosition(m_restartBtn, Anchor::Bottom, ccp(0, 20)); + m_applyBtnSpr = createGeodeButton("Apply", true); m_applyBtnSpr->setScale(.6f); - m_applyBtn = CCMenuItemSpriteExtra::create( m_applyBtnSpr, this, menu_selector(ModSettingsPopup::onApply) ); - m_buttonMenu->addChildAtPosition(m_applyBtn, Anchor::Bottom, ccp(0, 20)); + m_applyMenu->addChildAtPosition(m_applyBtn, Anchor::Bottom, ccp(0, 20)); + + m_mainLayer->addChildAtPosition(m_applyMenu, Anchor::Bottom, ccp(0, 20)); auto resetBtnSpr = createGeodeButton("Reset All", true); resetBtnSpr->setScale(.6f); @@ -79,13 +93,28 @@ bool ModSettingsPopup::setup(Mod* mod) { ); m_buttonMenu->addChildAtPosition(resetBtn, Anchor::BottomLeft, ccp(45, 20)); - auto openDirBtnSpr = createGeodeButton("Open Folder", true); - openDirBtnSpr->setScale(.6f); + auto configFolderSpr = CCSprite::createWithSpriteFrameName("folderIcon_001.png"); + m_openConfigDirBtnSpr = createGeodeButton(configFolderSpr, ""); + m_openConfigDirBtnSpr->setScale(.6f); + m_openConfigDirBtnSpr->getIcon()->setScale(m_openConfigDirBtnSpr->getIcon()->getScale() * 1.4f); + auto openConfigDirBtn = CCMenuItemSpriteExtra::create( + m_openConfigDirBtnSpr, this, menu_selector(ModSettingsPopup::onOpenConfigDirectory) + ); + m_buttonMenu->addChildAtPosition(openConfigDirBtn, Anchor::BottomRight, ccp(-50, 20)); + auto settingFolderSpr = CCSprite::createWithSpriteFrameName("folderIcon_001.png"); + auto settingFolderSprSub = CCSprite::createWithSpriteFrameName("settings.png"_spr); + settingFolderSprSub->setColor(ccBLACK); + settingFolderSprSub->setOpacity(155); + settingFolderSprSub->setScale(.55f); + settingFolderSpr->addChildAtPosition(settingFolderSprSub, Anchor::Center, ccp(0, -3)); + auto openDirBtnSpr = createGeodeButton(settingFolderSpr, ""); + openDirBtnSpr->setScale(.6f); + openDirBtnSpr->getIcon()->setScale(openDirBtnSpr->getIcon()->getScale() * 1.4f); auto openDirBtn = CCMenuItemSpriteExtra::create( openDirBtnSpr, this, menu_selector(ModSettingsPopup::onOpenSaveDirectory) ); - m_buttonMenu->addChildAtPosition(openDirBtn, Anchor::BottomRight, ccp(-53, 20)); + m_buttonMenu->addChildAtPosition(openDirBtn, Anchor::BottomRight, ccp(-20, 20)); m_changeListener.bind([this](auto* ev) { this->updateState(ev->getNode()); @@ -108,7 +137,21 @@ void ModSettingsPopup::onApply(CCObject*) { FLAlertLayer::create("Info", "No changes have been made.", "OK")->show(); } } +void ModSettingsPopup::onRestart(CCObject*) { + // Update button state to let user know it's restarting but it might take a bit + m_restartBtn->setEnabled(false); + static_cast(m_restartBtn->getNormalImage())->setString("Restarting..."); + m_restartBtn->updateSprite(); + // Actually restart + Loader::get()->queueInMainThread([] { + // Delayed by 2 frames - one is needed to render the "Restarting text" + Loader::get()->queueInMainThread([] { + // the other never finishes rendering because the game actually restarts at this point + game::restart(); + }); + }); +} void ModSettingsPopup::onResetAll(CCObject*) { createQuickPopup( "Reset All", @@ -124,8 +167,24 @@ void ModSettingsPopup::onResetAll(CCObject*) { } ); } +void ModSettingsPopup::onOpenSaveDirectory(CCObject*) { + file::openFolder(m_mod->getSaveDir()); +} +void ModSettingsPopup::onOpenConfigDirectory(CCObject*) { + file::openFolder(m_mod->getConfigDir()); + this->updateState(); +} void ModSettingsPopup::updateState(SettingNodeV3* invoker) { + m_restartBtn->setVisible(ModSettingsManager::from(m_mod)->restartRequired()); + m_applyMenu->updateLayout(); + + auto configDirExists = std::filesystem::exists(m_mod->getConfigDir(false)); + m_openConfigDirBtnSpr->setCascadeColorEnabled(true); + m_openConfigDirBtnSpr->setCascadeOpacityEnabled(true); + m_openConfigDirBtnSpr->setColor(configDirExists ? ccWHITE : ccGRAY); + m_openConfigDirBtnSpr->setOpacity(configDirExists ? 255 : 155); + // Update all settings with "enable-if" schemes for (auto& sett : m_settings) { // Avoid infinite loops @@ -136,6 +195,7 @@ void ModSettingsPopup::updateState(SettingNodeV3* invoker) { sett->updateState(); } } + m_applyBtnSpr->setCascadeColorEnabled(true); m_applyBtnSpr->setCascadeOpacityEnabled(true); if (this->hasUncommitted()) { @@ -175,10 +235,6 @@ void ModSettingsPopup::onClose(CCObject* sender) { Popup::onClose(sender); } -void ModSettingsPopup::onOpenSaveDirectory(CCObject*) { - file::openFolder(m_mod->getSaveDir()); -} - ModSettingsPopup* ModSettingsPopup::create(Mod* mod) { auto ret = new ModSettingsPopup(); if (ret->init(440, 280, mod)) { diff --git a/loader/src/ui/mods/settings/ModSettingsPopup.hpp b/loader/src/ui/mods/settings/ModSettingsPopup.hpp index bc349293..f2df3db8 100644 --- a/loader/src/ui/mods/settings/ModSettingsPopup.hpp +++ b/loader/src/ui/mods/settings/ModSettingsPopup.hpp @@ -11,8 +11,11 @@ class ModSettingsPopup : public GeodePopup { protected: Mod* m_mod; std::vector m_settings; + CCMenu* m_applyMenu; CCMenuItemSpriteExtra* m_applyBtn; + CCMenuItemSpriteExtra* m_restartBtn; ButtonSprite* m_applyBtnSpr; + IconButtonSprite* m_openConfigDirBtnSpr; EventListener> m_changeListener; bool setup(Mod* mod) override; @@ -20,8 +23,10 @@ protected: bool hasUncommitted() const; void onClose(CCObject*) override; void onApply(CCObject*); + void onRestart(CCObject*); void onResetAll(CCObject*); void onOpenSaveDirectory(CCObject*); + void onOpenConfigDirectory(CCObject*); public: static ModSettingsPopup* create(Mod* mod); From 741bc93a3bc149ed240d0d6ba6e369df529a6d14 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Wed, 4 Sep 2024 22:18:23 +0300 Subject: [PATCH 35/54] make sure resetting also marks settings as committed --- loader/src/loader/SettingNodeV3.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index 0caee58d..b331b654 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -179,6 +179,7 @@ void SettingNodeV3::commit() { } void SettingNodeV3::resetToDefault() { m_impl->setting->reset(); + m_impl->committed = true; this->onResetToDefault(); this->updateState(); SettingNodeValueChangeEventV3(this, false).post(); From 63fa43738670d8aa6abc4138176fb2f34a4185ea Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:08:09 +0300 Subject: [PATCH 36/54] fix android build --- loader/include/Geode/loader/SettingV3.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index 9d51732d..01b07833 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -562,7 +562,7 @@ namespace geode { using Ty = typename SettingTypeForValueType::SettingType; return new EventListener( [callback = std::move(callback)](std::shared_ptr setting) { - if (auto ty = typeinfo_pointer_cast(setting)) { + if (auto ty = geode::cast::typeinfo_pointer_cast(setting)) { callback(ty->getValue()); } }, From fdcdfbd30c05139ab4ce5bbfcbf288faebcf27d7 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:35:12 +0300 Subject: [PATCH 37/54] bump version & update changelog --- CHANGELOG.md | 11 +++++++++++ VERSION | 2 +- loader/include/Geode/platform/platform.hpp | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5118ed95..4febf3d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Geode Changelog +## v3.5.0 + * Major rework of the entire settings system with lots of new features; see the [docs page](todo) for more + * Rework JSON validation; now uses the `JsonExpectedValue` class with the `checkJson` helper (89d1a51) + * Add `Task::cancelled` for creating immediately cancelled Tasks (1a82d12) + * Add function type utilities in `utils/function.hpp` (659c168) + * Add `typeinfo_pointer_cast` for casting `std::shared_ptr`s (28cc6fd) + * Add `GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH` (1032d9a) + * Rename `toByteArray` to `toBytes` (6eb0797) + * Improve `AxisLayout::getSizeHint` (85e7b5e) + * Fix issues with file dialogs on Windows (62b6241, 971e3fb) + ## v3.4.0 * Add an API for modifying the Geode UI via events; see [the corresponding docs page](https://docs.geode-sdk.org/tutorials/modify-geode) (2a3c35f) * Add `openInfoPopup` overload that accepts a mod ID and can open both an installed mod page or a server page (028bbf9) diff --git a/VERSION b/VERSION index fbcbf738..e5b82034 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.4.0 \ No newline at end of file +3.5.0 \ No newline at end of file diff --git a/loader/include/Geode/platform/platform.hpp b/loader/include/Geode/platform/platform.hpp index b4c5bf71..2b5df536 100644 --- a/loader/include/Geode/platform/platform.hpp +++ b/loader/include/Geode/platform/platform.hpp @@ -3,6 +3,7 @@ #include "cplatform.h" #include #include +#include #if !defined(__PRETTY_FUNCTION__) && !defined(__GNUC__) #define GEODE_PRETTY_FUNCTION std::string(__FUNCSIG__) From 81d5dc0caea14d3de6f255118601c25a665ae1ba Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sat, 7 Sep 2024 11:36:07 +0300 Subject: [PATCH 38/54] reworks on setting nodes; Custom setting types are now tested to work! --- loader/include/Geode/loader/SettingV3.hpp | 290 ++++++++++++------ loader/resources/mod.json.in | 2 +- loader/src/loader/ModSettingsManager.cpp | 33 +- loader/src/loader/SettingNodeV3.cpp | 202 +++++------- loader/src/loader/SettingNodeV3.hpp | 130 +++----- loader/src/loader/SettingV3.cpp | 187 +---------- .../src/ui/mods/settings/ModSettingsPopup.cpp | 10 +- 7 files changed, 361 insertions(+), 493 deletions(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index 01b07833..19bd374d 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -27,8 +27,8 @@ namespace geode { protected: void init(std::string const& key, std::string const& modID); - Result<> parseSharedProperties(std::string const& key, std::string const& modID, matjson::Value const& value, bool onlyNameAndDesc = false); - void parseSharedProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value, bool onlyNameAndDesc = false); + Result<> parseBaseProperties(std::string const& key, std::string const& modID, matjson::Value const& value, bool onlyNameAndDesc = false); + void parseBaseProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value, bool onlyNameAndDesc = false); /** * Mark that the value of this setting has changed. This should be @@ -103,46 +103,124 @@ namespace geode { matjson::Value const& json )>; - namespace detail { - template - class GeodeSettingBaseValueV3 : public SettingV3 { - protected: - virtual T& getValueMut() const = 0; - - template - void parseDefaultValue(JsonExpectedValue& json, D& defaultValue) { - auto value = json.needs("default"); - // Check if this is a platform-specific default value - if (value.isObject() && value.has(GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH)) { - value.needs(GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH).into(defaultValue); - } - else { - value.into(defaultValue); - } - } - - 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(); - this->markChanged(); - } - virtual Result<> isValid(V value) const = 0; - - bool isDefaultValue() const override { - return this->getValue() == this->getDefaultValue(); - } - void reset() override { - this->setValue(this->getDefaultValue()); - } + /** + * A helper class for creating a basic setting with a simple value. + * Override the virtual function `isValid` to + * @tparam T The type of the setting's value. This type must be JSON- + * serializable and deserializable! + * @tparam V The type used for the `setValue` function, if it differs from T + */ + template + class SettingBaseValueV3 : public SettingV3 { + private: + class Impl final { + private: + T defaultValue; + T value; + friend class SettingBaseValueV3; }; - } + std::shared_ptr m_impl; + + protected: + /** + * Parse shared value, including the default value for this setting + * @param key The key of the setting + * @param modID The ID of the mod this setting is being parsed for + * @param json The current JSON checking instance being used. If you + * aren't using Geode's JSON checking utilities, use the other overload + * of this function + */ + void parseBaseProperties(std::string const& key, std::string const& modID, JsonExpectedValue& json) { + SettingV3::parseBaseProperties(key, modID, json, false); + auto root = json.needs("default"); + // Check if this is a platform-specific default value + if (root.isObject() && root.has(GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH)) { + root.needs(GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH).into(m_impl->defaultValue); + } + else { + root.into(m_impl->defaultValue); + } + m_impl->value = m_impl->defaultValue; + } + /** + * Parse shared value, including the default value for this setting + * @param key The key of the setting + * @param modID The ID of the mod this setting is being parsed for + * @param json The JSON value. If you are using Geode's JSON checking + * utilities (`checkJson` / `JsonExpectedValue`), you should use the + * other overload directly! + */ + Result<> parseBaseProperties(std::string const& key, std::string const& modID, matjson::Value const& json) { + auto root = checkJson(json, "SettingBaseValueV3"); + this->parseBaseProperties(key, modID, root); + return root.ok(); + } + + /** + * Set the default value. This does not check that the value is + * actually valid! + */ + void setDefaultValue(V value) { + m_impl->defaultValue = value; + } + + public: + SettingBaseValueV3() : m_impl(std::make_shared()) {} + + using ValueType = T; + using ValueAssignType = V; + + /** + * Get the default value for this setting + */ + T getDefaultValue() const { + return m_impl->defaultValue; + } + + /** + * Get the current value of this setting + */ + T getValue() const { + return m_impl->value; + } + /** + * Set the value of this setting. This will broadcast a new + * SettingChangedEventV3, letting any listeners now the value has changed + * @param value The new value for the setting. If the value is not a + * valid value for this setting (as determined by `isValue`), then the + * setting's value is reset to the default value + */ + void setValue(V value) { + m_impl->value = this->isValid(value) ? value : m_impl->defaultValue; + this->markChanged(); + } + /** + * Check if a given value is valid for this setting. If not, an error + * describing why the value isn't valid is returned + */ + virtual Result<> isValid(V value) const { + return Ok(); + } + + bool isDefaultValue() const override { + return m_impl->value == m_impl->defaultValue; + } + void reset() override { + this->setValue(m_impl->defaultValue); + } + + bool load(matjson::Value const& json) override { + if (json.template is()) { + m_impl->value = json.template as(); + return true; + } + return false; + } + bool save(matjson::Value& json) const override { + json = m_impl->value; + return true; + } + }; class GEODE_DLL TitleSettingV3 final : public SettingV3 { private: @@ -196,14 +274,11 @@ namespace geode { std::optional> convertToLegacyValue() const override; }; - class GEODE_DLL BoolSettingV3 final : public detail::GeodeSettingBaseValueV3 { + class GEODE_DLL BoolSettingV3 final : public SettingBaseValueV3 { private: class Impl; std::shared_ptr m_impl; - protected: - bool& getValueMut() const override; - private: class PrivateMarker {}; friend class SettingV3; @@ -212,25 +287,19 @@ namespace geode { BoolSettingV3(PrivateMarker); static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); - bool getDefaultValue() const override; Result<> isValid(bool value) const override; - bool load(matjson::Value const& json) override; - bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; std::optional convertToLegacy() const override; std::optional> convertToLegacyValue() const override; }; - class GEODE_DLL IntSettingV3 final : public detail::GeodeSettingBaseValueV3 { + class GEODE_DLL IntSettingV3 final : public SettingBaseValueV3 { private: class Impl; std::shared_ptr m_impl; - protected: - int64_t& getValueMut() const override; - private: class PrivateMarker {}; friend class SettingV3; @@ -239,7 +308,6 @@ namespace geode { IntSettingV3(PrivateMarker); static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); - int64_t getDefaultValue() const override; Result<> isValid(int64_t value) const override; std::optional getMinValue() const; @@ -253,22 +321,17 @@ namespace geode { std::optional getSliderSnap() const; bool isInputEnabled() const; - bool load(matjson::Value const& json) override; - bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; std::optional convertToLegacy() const override; std::optional> convertToLegacyValue() const override; }; - class GEODE_DLL FloatSettingV3 final : public detail::GeodeSettingBaseValueV3 { + class GEODE_DLL FloatSettingV3 final : public SettingBaseValueV3 { private: class Impl; std::shared_ptr m_impl; - protected: - double& getValueMut() const override; - private: class PrivateMarker {}; friend class SettingV3; @@ -277,7 +340,6 @@ namespace geode { FloatSettingV3(PrivateMarker); static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); - double getDefaultValue() const override; Result<> isValid(double value) const override; std::optional getMinValue() const; @@ -291,22 +353,17 @@ namespace geode { std::optional getSliderSnap() const; bool isInputEnabled() const; - bool load(matjson::Value const& json) override; - bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; std::optional convertToLegacy() const override; std::optional> convertToLegacyValue() const override; }; - class GEODE_DLL StringSettingV3 final : public detail::GeodeSettingBaseValueV3 { + class GEODE_DLL StringSettingV3 final : public SettingBaseValueV3 { private: class Impl; std::shared_ptr m_impl; - protected: - std::string& getValueMut() const override; - private: class PrivateMarker {}; friend class SettingV3; @@ -315,29 +372,23 @@ namespace geode { StringSettingV3(PrivateMarker); static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); - 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; - bool load(matjson::Value const& json) override; - bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; std::optional convertToLegacy() const override; std::optional> convertToLegacyValue() const override; }; - class GEODE_DLL FileSettingV3 final : public detail::GeodeSettingBaseValueV3 { + class GEODE_DLL FileSettingV3 final : public SettingBaseValueV3 { private: class Impl; std::shared_ptr m_impl; - protected: - std::filesystem::path& getValueMut() const override; - private: class PrivateMarker {}; friend class SettingV3; @@ -346,7 +397,6 @@ namespace geode { FileSettingV3(PrivateMarker); static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); - std::filesystem::path getDefaultValue() const override; Result<> isValid(std::filesystem::path const& value) const override; bool isFolder() const; @@ -354,22 +404,17 @@ namespace geode { std::optional> getFilters() const; - bool load(matjson::Value const& json) override; - bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; std::optional convertToLegacy() const override; std::optional> convertToLegacyValue() const override; }; - class GEODE_DLL Color3BSettingV3 final : public detail::GeodeSettingBaseValueV3 { + class GEODE_DLL Color3BSettingV3 final : public SettingBaseValueV3 { private: class Impl; std::shared_ptr m_impl; - protected: - cocos2d::ccColor3B& getValueMut() const override; - private: class PrivateMarker {}; friend class SettingV3; @@ -378,25 +423,19 @@ namespace geode { Color3BSettingV3(PrivateMarker); static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); - cocos2d::ccColor3B getDefaultValue() const override; Result<> isValid(cocos2d::ccColor3B value) const override; - bool load(matjson::Value const& json) override; - bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; std::optional convertToLegacy() const override; std::optional> convertToLegacyValue() const override; }; - class GEODE_DLL Color4BSettingV3 final : public detail::GeodeSettingBaseValueV3 { + class GEODE_DLL Color4BSettingV3 final : public SettingBaseValueV3 { private: class Impl; std::shared_ptr m_impl; - protected: - cocos2d::ccColor4B& getValueMut() const override; - private: class PrivateMarker {}; friend class SettingV3; @@ -405,11 +444,8 @@ namespace geode { Color4BSettingV3(PrivateMarker); static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); - cocos2d::ccColor4B getDefaultValue() const override; Result<> isValid(cocos2d::ccColor4B value) const override; - bool load(matjson::Value const& json) override; - bool save(matjson::Value& json) const override; SettingNodeV3* createNode(float width) override; std::optional convertToLegacy() const override; @@ -426,13 +462,28 @@ namespace geode { protected: bool init(std::shared_ptr setting, float width); - virtual void updateState(); + /** + * Update the state of this setting node, bringing all inputs + * up-to-date with the current value. Derivatives of `SettingNodeV3` + * should set update the state (such as visibility, value, etc.) of all + * its controls, except for the one that's passed as the `invoker` + * argument. Derivatives should remember to **always call the base + * class's `updateState` function**, as it updates the built-in title + * label as well as the description and reset buttons! + * @param invoker The button or other interactive element that caused + * this state update. If that element is for example a text input, it + * may wish to ignore the state update, as it itself is the source of + * truth for the node's value at that moment. May be nullptr to mark + * that no specific node requested this state update + */ + virtual void updateState(cocos2d::CCNode* invoker); /** * Mark this setting as changed. This updates the UI for committing - * the value + * the value, as well as posts a `SettingNodeValueChangeEventV3` + * @param invoker The node to be passed onto `updateState` */ - void markChanged(); + void markChanged(cocos2d::CCNode* invoker); /** * When the setting value is committed (aka can't be undone), this @@ -453,17 +504,68 @@ namespace geode { // Can be overridden by the setting itself // Can / should be used to do alternating BG - void setBGColor(cocos2d::ccColor4B const& color); + void setDefaultBGColor(cocos2d::ccColor4B color); cocos2d::CCLabelBMFont* getNameLabel() const; cocos2d::CCMenu* getNameMenu() const; cocos2d::CCMenu* getButtonMenu() const; + cocos2d::CCLayerColor* getBG() const; void setContentSize(cocos2d::CCSize const& size) override; std::shared_ptr getSetting() const; }; + /** + * Helper class for creating `SettingNode`s for simple settings that + * implement `SettingBaseValueV3` + */ + template + class SettingValueNodeV3 : public SettingNodeV3 { + protected: + bool init(std::shared_ptr setting, float width) { + if (!SettingNodeV3::init(setting, width)) + return false; + + return true; + } + + void onCommit() override { + this->getSetting()->setValue(this->getValue()); + } + bool hasUncommittedChanges() const override { + return this->getValue() != this->getSetting()->getValue(); + } + bool hasNonDefaultValue() const override { + return this->getValue() != this->getSetting()->getDefaultValue(); + } + void onResetToDefault() override { + this->setValue(this->getSetting()->getDefaultValue(), nullptr); + } + + virtual void onSetValue(typename S::ValueAssignType value) = 0; + + public: + /** + * Get the **uncommitted** value for this node + */ + virtual typename S::ValueType getValue() const = 0; + /** + * Set the **uncommitted** value for this node + * @param value The value to set + * @param invoker The node that invoked this value change; see the docs + * for `SettingNodeV3::updateState` to know more + */ + void setValue(typename S::ValueAssignType value, cocos2d::CCNode* invoker) { + this->onSetValue(value); + this->markChanged(invoker); + } + + std::shared_ptr getSetting() const { + return std::static_pointer_cast(SettingNodeV3::getSetting()); + } + }; + class GEODE_DLL SettingChangedEventV3 final : public Event { private: class Impl; diff --git a/loader/resources/mod.json.in b/loader/resources/mod.json.in index 89e17202..c4915538 100644 --- a/loader/resources/mod.json.in +++ b/loader/resources/mod.json.in @@ -92,7 +92,7 @@ "name": "Show Platform Console", "description": "Show the native console (if one exists). This setting is meant for developers", "platforms": ["win", "mac"], - "restart-required": true + "requires-restart": true }, "server-cache-size-limit": { "type": "int", diff --git a/loader/src/loader/ModSettingsManager.cpp b/loader/src/loader/ModSettingsManager.cpp index e01d1887..83cd04d3 100644 --- a/loader/src/loader/ModSettingsManager.cpp +++ b/loader/src/loader/ModSettingsManager.cpp @@ -52,17 +52,25 @@ public: return Ok(); } std::optional find(std::string_view modID, std::string_view fullType) { - auto full = std::string( - fullType.starts_with("custom:") ? - fullType.substr(fullType.find(':') + 1) : - fullType - ); - if (!full.find('/')) { - full = fmt::format("{}/{}", modID, full); + // Find custom settings via namespaced lookup + if (fullType.starts_with("custom:")) { + auto full = std::string(fullType.substr(fullType.find(':') + 1)); + // If there's no mod ID in the type name, use the current mod's ID + if (full.find('/') == std::string_view::npos) { + full = fmt::format("{}/{}", modID, full); + } + if (m_types.contains(full)) { + return m_types.at(full); + } } - if (m_types.contains(full)) { - return m_types.at(full); + // Otherwise find a built-in setting + else { + auto full = std::string(fullType); + if (m_types.contains(full)) { + return m_types.at(full); + } } + // Return null if nothing was found return std::nullopt; } }; @@ -171,12 +179,15 @@ Result<> ModSettingsManager::load(matjson::Value const& json) { auto root = checkJson(json, "Settings"); for (auto const& [key, value] : root.properties()) { if (m_impl->settings.contains(key)) { + auto& sett = m_impl->settings.at(key); + if (!sett.v3) continue; try { - if (!m_impl->settings.at(key).v3->load(value.json())) { + if (!sett.v3->load(value.json())) { log::error("Unable to load setting '{}' for mod {}", key, m_impl->modID); } } - catch(matjson::JsonException const& e) { + // matjson::JsonException doesn't catch all possible json errors + catch(std::exception const& e) { log::error("Unable to load setting '{}' for mod {} (JSON exception): {}", key, m_impl->modID, e.what()); } } diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index b331b654..273caeca 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -56,6 +56,8 @@ public: bool SettingNodeV3::init(std::shared_ptr setting, float width) { if (!CCNode::init()) return false; + + // note: setting may be null due to UnresolvedCustomSettingNodeV3 m_impl = std::make_shared(); m_impl->setting = setting; @@ -69,7 +71,7 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { m_impl->nameMenu = CCMenu::create(); m_impl->nameMenu->setContentWidth(width / 2 + 25); - m_impl->nameLabel = CCLabelBMFont::create(setting->getDisplayName().c_str(), "bigFont.fnt"); + m_impl->nameLabel = CCLabelBMFont::create(setting ? setting->getDisplayName().c_str() : "", "bigFont.fnt"); m_impl->nameLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .4f)->setScalePriority(1)); m_impl->nameMenu->addChild(m_impl->nameLabel); @@ -77,7 +79,7 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { m_impl->errorLabel->setScale(.25f); this->addChildAtPosition(m_impl->errorLabel, Anchor::Left, ccp(10, -10), ccp(0, .5f)); - if (setting->getDescription()) { + if (setting && setting->getDescription()) { auto descSpr = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png"); descSpr->setScale(.5f); auto descBtn = CCMenuItemSpriteExtra::create( @@ -108,7 +110,7 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { return true; } -void SettingNodeV3::updateState() { +void SettingNodeV3::updateState(CCNode* invoker) { m_impl->errorLabel->setVisible(false); m_impl->nameLabel->setColor(this->hasUncommittedChanges() ? ccc3(17, 221, 0) : ccWHITE); @@ -117,7 +119,7 @@ void SettingNodeV3::updateState() { m_impl->bg->setColor(to3B(m_impl->bgColor)); m_impl->bg->setOpacity(m_impl->bgColor.a); - if (!m_impl->setting->shouldEnable()) { + if (m_impl->setting && !m_impl->setting->shouldEnable()) { if (auto desc = m_impl->setting->getEnableIfDescription()) { m_impl->nameLabel->setColor(ccGRAY); m_impl->errorLabel->setVisible(true); @@ -125,7 +127,7 @@ void SettingNodeV3::updateState() { m_impl->errorLabel->setString(desc->c_str()); } } - if (m_impl->setting->requiresRestart() && m_impl->committed) { + if (m_impl->setting && m_impl->setting->requiresRestart() && m_impl->committed) { m_impl->errorLabel->setVisible(true); m_impl->errorLabel->setColor("mod-list-restart-required-label"_cc3b); m_impl->errorLabel->setString("Restart Required"); @@ -133,6 +135,7 @@ void SettingNodeV3::updateState() { m_impl->bg->setOpacity(75); } + m_impl->nameMenu->setContentWidth(this->getContentWidth() - m_impl->buttonMenu->getContentWidth() - 20); m_impl->nameMenu->updateLayout(); } @@ -162,26 +165,27 @@ void SettingNodeV3::onReset(CCObject*) { ); } -void SettingNodeV3::setBGColor(ccColor4B const& color) { +void SettingNodeV3::setDefaultBGColor(ccColor4B color) { m_impl->bgColor = color; - this->updateState(); + this->updateState(nullptr); } -void SettingNodeV3::markChanged() { - this->updateState(); +void SettingNodeV3::markChanged(CCNode* invoker) { + this->updateState(invoker); SettingNodeValueChangeEventV3(this, false).post(); } void SettingNodeV3::commit() { this->onCommit(); m_impl->committed = true; - this->updateState(); + this->updateState(nullptr); SettingNodeValueChangeEventV3(this, true).post(); } void SettingNodeV3::resetToDefault() { + if (!m_impl->setting) return; m_impl->setting->reset(); m_impl->committed = true; this->onResetToDefault(); - this->updateState(); + this->updateState(nullptr); SettingNodeValueChangeEventV3(this, false).post(); } @@ -201,6 +205,9 @@ CCMenu* SettingNodeV3::getNameMenu() const { CCMenu* SettingNodeV3::getButtonMenu() const { return m_impl->buttonMenu; } +CCLayerColor* SettingNodeV3::getBG() const { + return m_impl->bg; +} std::shared_ptr SettingNodeV3::getSetting() const { return m_impl->setting; @@ -215,7 +222,7 @@ bool TitleSettingNodeV3::init(std::shared_ptr setting, float wid this->getNameLabel()->setFntFile("goldFont.fnt"); this->getNameMenu()->updateLayout(); this->setContentHeight(20); - this->updateState(); + this->updateState(nullptr); return true; } @@ -247,12 +254,10 @@ TitleSettingNodeV3* TitleSettingNodeV3::create(std::shared_ptr s // BoolSettingNodeV3 bool BoolSettingNodeV3::init(std::shared_ptr setting, float width) { - if (!SettingNodeV3::init(setting, width)) + if (!SettingValueNodeV3::init(setting, width)) return false; this->getButtonMenu()->setContentWidth(20); - this->getNameMenu()->setContentWidth(width - 50); - this->getNameMenu()->updateLayout(); m_toggle = CCMenuItemToggler::createWithStandardSprites( this, menu_selector(BoolSettingNodeV3::onToggle), .55f @@ -265,13 +270,13 @@ bool BoolSettingNodeV3::init(std::shared_ptr setting, float width m_toggle->toggle(setting->getValue()); this->getButtonMenu()->addChildAtPosition(m_toggle, Anchor::Right, ccp(-10, 0)); - this->updateState(); + this->updateState(nullptr); return true; } -void BoolSettingNodeV3::updateState() { - SettingNodeV3::updateState(); +void BoolSettingNodeV3::updateState(CCNode* invoker) { + SettingNodeV3::updateState(invoker); auto enable = this->getSetting()->shouldEnable(); m_toggle->setCascadeColorEnabled(true); m_toggle->setCascadeOpacityEnabled(true); @@ -280,26 +285,16 @@ void BoolSettingNodeV3::updateState() { m_toggle->setOpacity(enable ? 255 : 155); } -void BoolSettingNodeV3::onCommit() { - this->getSetting()->setValue(m_toggle->isToggled()); -} void BoolSettingNodeV3::onToggle(CCObject*) { m_toggle->toggle(!m_toggle->isToggled()); - this->markChanged(); + this->markChanged(m_toggle); } -bool BoolSettingNodeV3::hasUncommittedChanges() const { - return m_toggle->isToggled() != this->getSetting()->getValue(); +bool BoolSettingNodeV3::getValue() const { + return m_toggle->isToggled(); } -bool BoolSettingNodeV3::hasNonDefaultValue() const { - return m_toggle->isToggled() != this->getSetting()->getDefaultValue(); -} -void BoolSettingNodeV3::onResetToDefault() { - m_toggle->toggle(this->getSetting()->getDefaultValue()); -} - -std::shared_ptr BoolSettingNodeV3::getSetting() const { - return std::static_pointer_cast(SettingNodeV3::getSetting()); +void BoolSettingNodeV3::onSetValue(bool value) { + m_toggle->toggle(value); } BoolSettingNodeV3* BoolSettingNodeV3::create(std::shared_ptr setting, float width) { @@ -315,12 +310,12 @@ BoolSettingNodeV3* BoolSettingNodeV3::create(std::shared_ptr sett // StringSettingNodeV3 bool StringSettingNodeV3::init(std::shared_ptr setting, float width) { - if (!SettingNodeV3::init(setting, width)) + if (!SettingValueNodeV3::init(setting, width)) return false; m_input = TextInput::create(setting->getEnumOptions() ? width / 2 - 50 : width / 2, "Text"); m_input->setCallback([this](auto const&) { - this->markChanged(); + this->markChanged(m_input); }); m_input->setScale(.7f); m_input->setString(this->getSetting()->getValue()); @@ -350,13 +345,13 @@ bool StringSettingNodeV3::init(std::shared_ptr setting, float w this->getButtonMenu()->addChildAtPosition(arrowRightBtn, Anchor::Right, ccp(-5, 0)); } - this->updateState(); + this->updateState(nullptr); return true; } -void StringSettingNodeV3::updateState() { - SettingNodeV3::updateState(); +void StringSettingNodeV3::updateState(CCNode* invoker) { + SettingNodeV3::updateState(invoker); auto enable = this->getSetting()->shouldEnable(); if (!this->getSetting()->getEnumOptions()) { m_input->setEnabled(enable); @@ -379,24 +374,14 @@ void StringSettingNodeV3::onArrow(CCObject* sender) { index = index > 0 ? index - 1 : options.size() - 1; } m_input->setString(options.at(index)); - this->updateState(); + this->updateState(static_cast(sender)); } -void StringSettingNodeV3::onCommit() { - this->getSetting()->setValue(m_input->getString()); +std::string StringSettingNodeV3::getValue() const { + return m_input->getString(); } -bool StringSettingNodeV3::hasUncommittedChanges() const { - return m_input->getString() != this->getSetting()->getValue(); -} -bool StringSettingNodeV3::hasNonDefaultValue() const { - return m_input->getString() != this->getSetting()->getDefaultValue(); -} -void StringSettingNodeV3::onResetToDefault() { - m_input->setString(this->getSetting()->getDefaultValue()); -} - -std::shared_ptr StringSettingNodeV3::getSetting() const { - return std::static_pointer_cast(SettingNodeV3::getSetting()); +void StringSettingNodeV3::onSetValue(std::string_view value) { + m_input->setString(std::string(value)); } StringSettingNodeV3* StringSettingNodeV3::create(std::shared_ptr setting, float width) { @@ -412,7 +397,7 @@ StringSettingNodeV3* StringSettingNodeV3::create(std::shared_ptr setting, float width) { - if (!SettingNodeV3::init(setting, width)) + if (!SettingValueNodeV3::init(setting, width)) return false; m_path = setting->getValue(); @@ -437,13 +422,13 @@ bool FileSettingNodeV3::init(std::shared_ptr setting, float width ); this->getButtonMenu()->addChildAtPosition(m_selectBtn, Anchor::Right, ccp(-5, 0)); - this->updateState(); + this->updateState(nullptr); return true; } -void FileSettingNodeV3::updateState() { - SettingNodeV3::updateState(); +void FileSettingNodeV3::updateState(CCNode* invoker) { + SettingNodeV3::updateState(invoker); m_fileIcon->setDisplayFrame(CCSpriteFrameCache::get()->spriteFrameByName( this->getSetting()->isFolder() ? "folderIcon_001.png" : "file.png"_spr )); @@ -474,7 +459,7 @@ void FileSettingNodeV3::onPickFile(CCObject*) { } if (value->isOk()) { m_path = value->unwrap().string(); - this->markChanged(); + this->markChanged(nullptr); } else { FLAlertLayer::create( @@ -497,21 +482,11 @@ void FileSettingNodeV3::onPickFile(CCObject*) { )); } -void FileSettingNodeV3::onCommit() { - this->getSetting()->setValue(m_path); +std::filesystem::path FileSettingNodeV3::getValue() const { + return m_path; } -bool FileSettingNodeV3::hasUncommittedChanges() const { - return m_path != this->getSetting()->getValue(); -} -bool FileSettingNodeV3::hasNonDefaultValue() const { - return m_path != this->getSetting()->getDefaultValue(); -} -void FileSettingNodeV3::onResetToDefault() { - m_path = this->getSetting()->getDefaultValue(); -} - -std::shared_ptr FileSettingNodeV3::getSetting() const { - return std::static_pointer_cast(SettingNodeV3::getSetting()); +void FileSettingNodeV3::onSetValue(std::filesystem::path const& value) { + m_path = value; } FileSettingNodeV3* FileSettingNodeV3::create(std::shared_ptr setting, float width) { @@ -527,7 +502,7 @@ FileSettingNodeV3* FileSettingNodeV3::create(std::shared_ptr sett // Color3BSettingNodeV3 bool Color3BSettingNodeV3::init(std::shared_ptr setting, float width) { - if (!SettingNodeV3::init(setting, width)) + if (!SettingValueNodeV3::init(setting, width)) return false; m_value = setting->getValue(); @@ -540,13 +515,13 @@ bool Color3BSettingNodeV3::init(std::shared_ptr setting, float ); this->getButtonMenu()->addChildAtPosition(m_colorBtn, Anchor::Right, ccp(-10, 0)); - this->updateState(); + this->updateState(nullptr); return true; } -void Color3BSettingNodeV3::updateState() { - SettingNodeV3::updateState(); +void Color3BSettingNodeV3::updateState(CCNode* invoker) { + SettingNodeV3::updateState(invoker); m_colorSprite->setColor(m_value); auto enable = this->getSetting()->shouldEnable(); @@ -561,24 +536,14 @@ void Color3BSettingNodeV3::onSelectColor(CCObject*) { } void Color3BSettingNodeV3::updateColor(ccColor4B const& color) { m_value = to3B(color); - this->markChanged(); + this->markChanged(nullptr); } -void Color3BSettingNodeV3::onCommit() { - this->getSetting()->setValue(m_value); +ccColor3B Color3BSettingNodeV3::getValue() const { + return m_value; } -bool Color3BSettingNodeV3::hasUncommittedChanges() const { - return m_value != this->getSetting()->getValue(); -} -bool Color3BSettingNodeV3::hasNonDefaultValue() const { - return m_value != this->getSetting()->getDefaultValue(); -} -void Color3BSettingNodeV3::onResetToDefault() { - m_value = this->getSetting()->getDefaultValue(); -} - -std::shared_ptr Color3BSettingNodeV3::getSetting() const { - return std::static_pointer_cast(SettingNodeV3::getSetting()); +void Color3BSettingNodeV3::onSetValue(ccColor3B value) { + m_value = value; } Color3BSettingNodeV3* Color3BSettingNodeV3::create(std::shared_ptr setting, float width) { @@ -594,7 +559,7 @@ Color3BSettingNodeV3* Color3BSettingNodeV3::create(std::shared_ptr setting, float width) { - if (!SettingNodeV3::init(setting, width)) + if (!SettingValueNodeV3::init(setting, width)) return false; m_value = setting->getValue(); @@ -607,13 +572,13 @@ bool Color4BSettingNodeV3::init(std::shared_ptr setting, float ); this->getButtonMenu()->addChildAtPosition(m_colorBtn, Anchor::Right, ccp(-10, 0)); - this->updateState(); + this->updateState(nullptr); return true; } -void Color4BSettingNodeV3::updateState() { - SettingNodeV3::updateState(); +void Color4BSettingNodeV3::updateState(CCNode* invoker) { + SettingNodeV3::updateState(invoker); m_colorSprite->setColor(to3B(m_value)); m_colorSprite->updateOpacity(m_value.a / 255.f); @@ -629,24 +594,14 @@ void Color4BSettingNodeV3::onSelectColor(CCObject*) { } void Color4BSettingNodeV3::updateColor(ccColor4B const& color) { m_value = color; - this->markChanged(); + this->markChanged(nullptr); } -void Color4BSettingNodeV3::onCommit() { - this->getSetting()->setValue(m_value); +ccColor4B Color4BSettingNodeV3::getValue() const { + return m_value; } -bool Color4BSettingNodeV3::hasUncommittedChanges() const { - return m_value != this->getSetting()->getValue(); -} -bool Color4BSettingNodeV3::hasNonDefaultValue() const { - return m_value != this->getSetting()->getDefaultValue(); -} -void Color4BSettingNodeV3::onResetToDefault() { - m_value = this->getSetting()->getDefaultValue(); -} - -std::shared_ptr Color4BSettingNodeV3::getSetting() const { - return std::static_pointer_cast(SettingNodeV3::getSetting()); +void Color4BSettingNodeV3::onSetValue(ccColor4B value) { + m_value = value; } Color4BSettingNodeV3* Color4BSettingNodeV3::create(std::shared_ptr setting, float width) { @@ -661,22 +616,29 @@ Color4BSettingNodeV3* Color4BSettingNodeV3::create(std::shared_ptr setting, float width) { - if (!SettingNodeV3::init(setting, width)) +bool UnresolvedCustomSettingNodeV3::init(std::string_view key, float width) { + if (!SettingNodeV3::init(nullptr, width)) return false; this->setContentHeight(30); auto label = CCLabelBMFont::create( - fmt::format("Missing setting '{}'", setting->getKey()).c_str(), + fmt::format("Missing setting '{}'", key).c_str(), "bigFont.fnt" ); - label->limitLabelWidth(width - m_obContentSize.height, .5f, .1f); - this->addChildAtPosition(label, Anchor::Left, ccp(m_obContentSize.height / 2, 0)); - + label->setColor("mod-list-errors-found-2"_cc3b); + label->limitLabelWidth(width - m_obContentSize.height, .3f, .1f); + this->addChildAtPosition(label, Anchor::Left, ccp(m_obContentSize.height / 2, 0), ccp(0, .5f)); + return true; } +void UnresolvedCustomSettingNodeV3::updateState(CCNode* invoker) { + SettingNodeV3::updateState(invoker); + this->getBG()->setColor("mod-list-errors-found"_cc3b); + this->getBG()->setOpacity(75); +} + void UnresolvedCustomSettingNodeV3::onCommit() {} bool UnresolvedCustomSettingNodeV3::hasUncommittedChanges() const { @@ -687,13 +649,9 @@ bool UnresolvedCustomSettingNodeV3::hasNonDefaultValue() const { } void UnresolvedCustomSettingNodeV3::onResetToDefault() {} -std::shared_ptr UnresolvedCustomSettingNodeV3::getSetting() const { - return std::static_pointer_cast(SettingNodeV3::getSetting()); -} - -UnresolvedCustomSettingNodeV3* UnresolvedCustomSettingNodeV3::create(std::shared_ptr setting, float width) { +UnresolvedCustomSettingNodeV3* UnresolvedCustomSettingNodeV3::create(std::string_view key, float width) { auto ret = new UnresolvedCustomSettingNodeV3(); - if (ret && ret->init(setting, width)) { + if (ret && ret->init(key, width)) { ret->autorelease(); return ret; } diff --git a/loader/src/loader/SettingNodeV3.hpp b/loader/src/loader/SettingNodeV3.hpp index 8d942902..b0e9724d 100644 --- a/loader/src/loader/SettingNodeV3.hpp +++ b/loader/src/loader/SettingNodeV3.hpp @@ -27,31 +27,26 @@ public: std::shared_ptr getSetting() const; }; -class BoolSettingNodeV3 : public SettingNodeV3 { +class BoolSettingNodeV3 : public SettingValueNodeV3 { protected: CCMenuItemToggler* m_toggle; bool init(std::shared_ptr setting, float width); - - void updateState() override; - - void onCommit() override; + void updateState(CCNode* invoker) override; void onToggle(CCObject*); public: - static BoolSettingNodeV3* create(std::shared_ptr setting, float width); - - bool hasUncommittedChanges() const override; - bool hasNonDefaultValue() const override; - void onResetToDefault() override; + bool getValue() const override; + void onSetValue(bool value) override; - std::shared_ptr getSetting() const; + static BoolSettingNodeV3* create(std::shared_ptr setting, float width); }; template -class NumberSettingNodeV3 : public SettingNodeV3 { +class NumberSettingNodeV3 : public SettingValueNodeV3 { protected: using ValueType = typename S::ValueType; + using ValueAssignType = typename S::ValueAssignType; TextInput* m_input; Slider* m_slider; @@ -82,7 +77,7 @@ protected: } bool init(std::shared_ptr setting, float width) { - if (!SettingNodeV3::init(setting, width)) + if (!SettingValueNodeV3::init(setting, width)) return false; m_bigArrowLeftBtnSpr = CCSprite::create(); @@ -115,7 +110,7 @@ protected: m_input = TextInput::create(this->getButtonMenu()->getContentWidth() - 40, "Num"); m_input->setScale(.7f); m_input->setCallback([this](auto const&) { - this->markChanged(); + this->markChanged(m_input); }); if (!setting->isInputEnabled()) { m_input->getBGSprite()->setVisible(false); @@ -164,21 +159,21 @@ protected: this->getButtonMenu()->addChildAtPosition(m_slider, Anchor::Center, ccp(0, -20), ccp(0, 0)); } - this->setCurrentValue(setting->getValue()); - this->updateState(); + this->setValue(setting->getValue(), nullptr); + this->updateState(nullptr); return true; } - void updateState() override { - SettingNodeV3::updateState(); + void updateState(CCNode* invoker) override { + SettingNodeV3::updateState(invoker); auto enable = this->getSetting()->shouldEnable(); if (this->getSetting()->isInputEnabled()) { m_input->setEnabled(enable); } auto min = this->getSetting()->getMinValue(); - auto enableLeft = enable && (!min || this->getCurrentValue() > *min); + auto enableLeft = enable && (!min || this->getValue() > *min); m_arrowLeftBtn->setEnabled(enableLeft); m_bigArrowLeftBtn->setEnabled(enableLeft); m_arrowLeftBtnSpr->setOpacity(enableLeft ? 255 : 155); @@ -187,7 +182,7 @@ protected: m_bigArrowLeftBtnSpr->setColor(enableLeft ? ccWHITE : ccGRAY); auto max = this->getSetting()->getMaxValue(); - auto enableRight = enable && (!max || this->getCurrentValue() < *max); + auto enableRight = enable && (!max || this->getValue() < *max); m_arrowRightBtn->setEnabled(enableRight); m_bigArrowRightBtn->setEnabled(enableRight); m_arrowRightBtnSpr->setOpacity(enableRight ? 255 : 155); @@ -196,7 +191,7 @@ protected: m_bigArrowRightBtnSpr->setColor(enableRight ? ccWHITE : ccGRAY); if (m_slider) { - m_slider->m_touchLogic->m_thumb->setValue(this->valueToSlider(this->getCurrentValue())); + m_slider->m_touchLogic->m_thumb->setValue(this->valueToSlider(this->getValue())); m_slider->updateBar(); m_slider->m_sliderBar->setColor(enable ? ccWHITE : ccGRAY); m_slider->m_touchLogic->m_thumb->setColor(enable ? ccWHITE : ccGRAY); @@ -204,11 +199,8 @@ protected: } } - void onCommit() override { - this->getSetting()->setValue(this->getCurrentValue()); - } void onArrow(CCObject* sender) { - auto value = this->getCurrentValue() + static_cast*>( + auto value = this->getValue() + static_cast*>( static_cast(sender)->getUserObject() )->getValue(); if (auto min = this->getSetting()->getMinValue()) { @@ -217,19 +209,18 @@ protected: if (auto max = this->getSetting()->getMaxValue()) { value = std::min(*max, value); } - this->setCurrentValue(value); + this->setValue(value, static_cast(sender)); } void onSlider(CCObject*) { - this->setCurrentValue(this->valueFromSlider(m_slider->m_touchLogic->m_thumb->getValue())); + this->setValue(this->valueFromSlider(m_slider->m_touchLogic->m_thumb->getValue()), m_slider); } - ValueType getCurrentValue() const { + ValueType getValue() const override { return numFromString(m_input->getString()) .value_or(this->getSetting()->getDefaultValue()); } - void setCurrentValue(ValueType value) { + void onSetValue(ValueAssignType value) override { m_input->setString(numToString(value)); - this->markChanged(); } public: @@ -242,50 +233,29 @@ public: CC_SAFE_DELETE(ret); return nullptr; } - - bool hasUncommittedChanges() const override { - return this->getSetting()->getValue() != this->getCurrentValue(); - } - bool hasNonDefaultValue() const override { - return this->getSetting()->getDefaultValue() != this->getCurrentValue(); - } - void onResetToDefault() override { - this->setCurrentValue(this->getSetting()->getDefaultValue()); - } - - std::shared_ptr getSetting() const { - return std::static_pointer_cast(SettingNodeV3::getSetting()); - } }; using IntSettingNodeV3 = NumberSettingNodeV3; using FloatSettingNodeV3 = NumberSettingNodeV3; -class StringSettingNodeV3 : public SettingNodeV3 { +class StringSettingNodeV3 : public SettingValueNodeV3 { protected: TextInput* m_input; CCSprite* m_arrowLeftSpr = nullptr; CCSprite* m_arrowRightSpr = nullptr; bool init(std::shared_ptr setting, float width); - - void updateState() override; - + void updateState(CCNode* invoker) override; void onArrow(CCObject* sender); - void onCommit() override; - public: static StringSettingNodeV3* create(std::shared_ptr setting, float width); - - bool hasUncommittedChanges() const override; - bool hasNonDefaultValue() const override; - void onResetToDefault() override; - std::shared_ptr getSetting() const; + std::string getValue() const override; + void onSetValue(std::string_view value) override; }; -class FileSettingNodeV3 : public SettingNodeV3 { +class FileSettingNodeV3 : public SettingValueNodeV3 { protected: CCSprite* m_fileIcon; std::filesystem::path m_path; @@ -295,84 +265,66 @@ protected: CCSprite* m_selectBtnSpr; bool init(std::shared_ptr setting, float width); - - void updateState() override; - - void onCommit() override; + void updateState(CCNode* invoker) override; void onPickFile(CCObject*); public: static FileSettingNodeV3* create(std::shared_ptr setting, float width); - - bool hasUncommittedChanges() const override; - bool hasNonDefaultValue() const override; - void onResetToDefault() override; - std::shared_ptr getSetting() const; + std::filesystem::path getValue() const override; + void onSetValue(std::filesystem::path const& value) override; }; -class Color3BSettingNodeV3 : public SettingNodeV3, public ColorPickPopupDelegate { +class Color3BSettingNodeV3 : public SettingValueNodeV3, public ColorPickPopupDelegate { protected: ccColor3B m_value; CCMenuItemSpriteExtra* m_colorBtn; ColorChannelSprite* m_colorSprite; bool init(std::shared_ptr setting, float width); - - void updateState() override; - - void onCommit() override; + void updateState(CCNode* invoker) override; void onSelectColor(CCObject*); void updateColor(ccColor4B const& color) override; public: static Color3BSettingNodeV3* create(std::shared_ptr setting, float width); - - bool hasUncommittedChanges() const override; - bool hasNonDefaultValue() const override; - void onResetToDefault() override; - std::shared_ptr getSetting() const; + ccColor3B getValue() const override; + void onSetValue(ccColor3B value) override; }; -class Color4BSettingNodeV3 : public SettingNodeV3, public ColorPickPopupDelegate { +class Color4BSettingNodeV3 : public SettingValueNodeV3, public ColorPickPopupDelegate { protected: ccColor4B m_value; CCMenuItemSpriteExtra* m_colorBtn; ColorChannelSprite* m_colorSprite; bool init(std::shared_ptr setting, float width); - - void updateState() override; - - void onCommit() override; + void updateState(CCNode* invoker) override; void onSelectColor(CCObject*); void updateColor(ccColor4B const& color) override; public: static Color4BSettingNodeV3* create(std::shared_ptr setting, float width); - bool hasUncommittedChanges() const override; - bool hasNonDefaultValue() const override; - void onResetToDefault() override; - - std::shared_ptr getSetting() const; + ccColor4B getValue() const override; + void onSetValue(ccColor4B value) override; }; class UnresolvedCustomSettingNodeV3 : public SettingNodeV3 { protected: - bool init(std::shared_ptr setting, float width); + bool init(std::string_view key, float width); + + void updateState(CCNode* invoker) override; void onCommit() override; public: - static UnresolvedCustomSettingNodeV3* create(std::shared_ptr setting, float width); + static UnresolvedCustomSettingNodeV3* create(std::string_view key, float width); bool hasUncommittedChanges() const override; bool hasNonDefaultValue() const override; void onResetToDefault() override; - - std::shared_ptr getSetting() const; }; // If these classes do get exposed in headers, diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index af2d5a43..abce0468 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -471,12 +471,12 @@ public: SettingV3::SettingV3() : m_impl(std::make_shared()) {} SettingV3::~SettingV3() = default; -Result<> SettingV3::parseSharedProperties(std::string const& key, std::string const& modID, matjson::Value const& value, bool onlyNameAndDesc) { +Result<> SettingV3::parseBaseProperties(std::string const& key, std::string const& modID, matjson::Value const& value, bool onlyNameAndDesc) { auto json = checkJson(value, "SettingV3"); - this->parseSharedProperties(key, modID, json, onlyNameAndDesc); + this->parseBaseProperties(key, modID, json, onlyNameAndDesc); return json.ok(); } -void SettingV3::parseSharedProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value, bool onlyNameAndDesc) { +void SettingV3::parseBaseProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value, bool onlyNameAndDesc) { this->init(key, modID); value.needs("type"); value.has("platforms"); @@ -573,7 +573,7 @@ TitleSettingV3::TitleSettingV3(PrivateMarker) : m_impl(std::make_shared()) Result> TitleSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) { auto ret = std::make_shared(PrivateMarker()); auto root = checkJson(json, "TitleSettingV3"); - ret->parseSharedProperties(key, modID, root, true); + ret->parseBaseProperties(key, modID, root, true); root.checkUnknownKeys(); return root.ok(ret); } @@ -628,9 +628,7 @@ SettingNodeV3* LegacyCustomSettingV3::createNode(float width) { std::static_pointer_cast(shared_from_this()), width ); } - return UnresolvedCustomSettingNodeV3::create( - std::static_pointer_cast(shared_from_this()), width - ); + return UnresolvedCustomSettingNodeV3::create(this->getKey(), width); } bool LegacyCustomSettingV3::isDefaultValue() const { @@ -649,45 +647,22 @@ std::optional> LegacyCustomSettingV3::convertToLeg class BoolSettingV3::Impl final { public: - bool value; - bool defaultValue; }; BoolSettingV3::BoolSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} Result> BoolSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) { auto ret = std::make_shared(PrivateMarker()); - auto root = checkJson(json, "BoolSettingV3"); - ret->parseSharedProperties(key, modID, root); - ret->parseDefaultValue(root, ret->m_impl->defaultValue); - ret->m_impl->value = ret->m_impl->defaultValue; - + ret->parseBaseProperties(key, modID, root); root.checkUnknownKeys(); return root.ok(ret); } -bool& BoolSettingV3::getValueMut() const { - return m_impl->value; -} -bool BoolSettingV3::getDefaultValue() const { - return m_impl->defaultValue; -} Result<> BoolSettingV3::isValid(bool value) const { return Ok(); } -bool BoolSettingV3::load(matjson::Value const& json) { - if (json.is_bool()) { - m_impl->value = json.as_bool(); - return true; - } - return false; -} -bool BoolSettingV3::save(matjson::Value& json) const { - json = m_impl->value; - return true; -} SettingNodeV3* BoolSettingV3::createNode(float width) { return BoolSettingNodeV3::create( std::static_pointer_cast(shared_from_this()), width @@ -707,8 +682,6 @@ std::optional> BoolSettingV3::convertToLegacyValue class IntSettingV3::Impl final { public: - int64_t value; - int64_t defaultValue; std::optional minValue; std::optional maxValue; @@ -726,9 +699,7 @@ Result> IntSettingV3::parse(std::string const& key auto ret = std::make_shared(PrivateMarker()); auto root = checkJson(json, "IntSettingV3"); - ret->parseSharedProperties(key, modID, root); - ret->parseDefaultValue(root, ret->m_impl->defaultValue); - ret->m_impl->value = ret->m_impl->defaultValue; + ret->parseBaseProperties(key, modID, root); root.has("min").into(ret->m_impl->minValue); root.has("max").into(ret->m_impl->maxValue); @@ -773,12 +744,6 @@ Result> IntSettingV3::parse(std::string const& key IntSettingV3::IntSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} -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 { if (m_impl->minValue && value < *m_impl->minValue) { return Err("Value must be at least {}", *m_impl->minValue); @@ -818,17 +783,6 @@ bool IntSettingV3::isInputEnabled() const { return m_impl->controls.textInputEnabled; } -bool IntSettingV3::load(matjson::Value const& json) { - if (json.is_number()) { - m_impl->value = json.as_int(); - return true; - } - return false; -} -bool IntSettingV3::save(matjson::Value& json) const { - json = m_impl->value; - return true; -} SettingNodeV3* IntSettingV3::createNode(float width) { return IntSettingNodeV3::create( std::static_pointer_cast(shared_from_this()), width @@ -859,8 +813,6 @@ std::optional> IntSettingV3::convertToLegacyValue( class FloatSettingV3::Impl final { public: - double value; - double defaultValue; std::optional minValue; std::optional maxValue; @@ -880,9 +832,7 @@ Result> FloatSettingV3::parse(std::string const& auto ret = std::make_shared(PrivateMarker()); auto root = checkJson(json, "FloatSettingV3"); - ret->parseSharedProperties(key, modID, root); - ret->parseDefaultValue(root, ret->m_impl->defaultValue); - ret->m_impl->value = ret->m_impl->defaultValue; + ret->parseBaseProperties(key, modID, root); root.has("min").into(ret->m_impl->minValue); root.has("max").into(ret->m_impl->maxValue); @@ -923,12 +873,6 @@ Result> FloatSettingV3::parse(std::string const& return root.ok(ret); } -double& FloatSettingV3::getValueMut() const { - return m_impl->value; -} -double FloatSettingV3::getDefaultValue() const { - return m_impl->defaultValue; -} Result<> FloatSettingV3::isValid(double value) const { if (m_impl->minValue && value < *m_impl->minValue) { return Err("Value must be at least {}", *m_impl->minValue); @@ -968,17 +912,6 @@ bool FloatSettingV3::isInputEnabled() const { return m_impl->controls.textInputEnabled; } -bool FloatSettingV3::load(matjson::Value const& json) { - if (json.is_number()) { - m_impl->value = json.as_double(); - return true; - } - return false; -} -bool FloatSettingV3::save(matjson::Value& json) const { - json = m_impl->value; - return true; -} SettingNodeV3* FloatSettingV3::createNode(float width) { return FloatSettingNodeV3::create( std::static_pointer_cast(shared_from_this()), width @@ -1009,8 +942,6 @@ std::optional> FloatSettingV3::convertToLegacyValu class StringSettingV3::Impl final { public: - std::string value; - std::string defaultValue; std::optional match; std::optional filter; std::optional> oneOf; @@ -1022,9 +953,7 @@ Result> StringSettingV3::parse(std::string cons auto ret = std::make_shared(PrivateMarker()); auto root = checkJson(json, "StringSettingV3"); - ret->parseSharedProperties(key, modID, root); - ret->parseDefaultValue(root, ret->m_impl->defaultValue); - ret->m_impl->value = ret->m_impl->defaultValue; + ret->parseBaseProperties(key, modID, root); root.has("match").into(ret->m_impl->match); root.has("filter").into(ret->m_impl->filter); @@ -1037,12 +966,6 @@ Result> StringSettingV3::parse(std::string cons return root.ok(ret); } -std::string& StringSettingV3::getValueMut() const { - return m_impl->value; -} -std::string StringSettingV3::getDefaultValue() const { - return m_impl->defaultValue; -} Result<> StringSettingV3::isValid(std::string_view value) const { if (m_impl->match) { if (!std::regex_match(std::string(value), std::regex(*m_impl->match))) { @@ -1067,17 +990,6 @@ std::optional> StringSettingV3::getEnumOptions() const return m_impl->oneOf; } -bool StringSettingV3::load(matjson::Value const& json) { - if (json.is_string()) { - m_impl->value = json.as_string(); - return true; - } - return false; -} -bool StringSettingV3::save(matjson::Value& json) const { - json = m_impl->value; - return true; -} SettingNodeV3* StringSettingV3::createNode(float width) { return StringSettingNodeV3::create( std::static_pointer_cast(shared_from_this()), width @@ -1100,8 +1012,6 @@ std::optional> StringSettingV3::convertToLegacyVal class FileSettingV3::Impl final { public: - std::filesystem::path value; - std::filesystem::path defaultValue; bool folder = false; bool useSaveDialog = false; // this option makes no sense if folder = true std::optional> filters; @@ -1113,25 +1023,23 @@ Result> FileSettingV3::parse(std::string const& k auto ret = std::make_shared(PrivateMarker()); auto root = checkJson(json, "FileSettingV3"); - ret->parseSharedProperties(key, modID, root); - ret->parseDefaultValue(root, ret->m_impl->defaultValue); - ret->m_impl->value = ret->m_impl->defaultValue; + ret->parseBaseProperties(key, modID, root); // Replace known paths like `{gd-save-dir}/` try { - ret->m_impl->defaultValue = fmt::format( - fmt::runtime(ret->m_impl->defaultValue.string()), + ret->setDefaultValue(fmt::format( + fmt::runtime(ret->getDefaultValue().string()), fmt::arg("gd_dir", dirs::getGameDir()), fmt::arg("gd_save_dir", dirs::getSaveDir()), fmt::arg("mod_config_dir", dirs::getModConfigDir() / modID), fmt::arg("mod_save_dir", dirs::getModsSaveDir() / modID), fmt::arg("temp_dir", dirs::getTempDir()) - ); + )); } catch(fmt::format_error const& e) { return Err("Invalid format string for file setting path: {}", e.what()); } - ret->m_impl->value = ret->m_impl->defaultValue; + ret->setValue(ret->getDefaultValue()); std::string type; root.needs("type").into(type); @@ -1175,12 +1083,6 @@ Result> FileSettingV3::parse(std::string const& k return root.ok(ret); } -std::filesystem::path& FileSettingV3::getValueMut() const { - return m_impl->value; -} -std::filesystem::path FileSettingV3::getDefaultValue() const { - return m_impl->defaultValue; -} Result<> FileSettingV3::isValid(std::filesystem::path const& value) const { std::error_code ec; if (m_impl->folder) { @@ -1207,17 +1109,6 @@ std::optional> FileSettingV3:: return m_impl->filters; } -bool FileSettingV3::load(matjson::Value const& json) { - if (json.is_string()) { - m_impl->value = json.as_string(); - return true; - } - return false; -} -bool FileSettingV3::save(matjson::Value& json) const { - json = m_impl->value; - return true; -} SettingNodeV3* FileSettingV3::createNode(float width) { return FileSettingNodeV3::create( std::static_pointer_cast(shared_from_this()), width @@ -1238,45 +1129,22 @@ std::optional> FileSettingV3::convertToLegacyValue class Color3BSettingV3::Impl final { public: - ccColor3B value; - ccColor3B defaultValue; }; Color3BSettingV3::Color3BSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} Result> Color3BSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) { auto ret = std::make_shared(PrivateMarker()); - auto root = checkJson(json, "Color3BSettingV3"); - ret->parseSharedProperties(key, modID, root); - ret->parseDefaultValue(root, ret->m_impl->defaultValue); - ret->m_impl->value = ret->m_impl->defaultValue; - + ret->parseBaseProperties(key, modID, root); root.checkUnknownKeys(); return root.ok(ret); } -ccColor3B& Color3BSettingV3::getValueMut() const { - return m_impl->value; -} -ccColor3B Color3BSettingV3::getDefaultValue() const { - return m_impl->defaultValue; -} Result<> Color3BSettingV3::isValid(ccColor3B value) const { return Ok(); } -bool Color3BSettingV3::load(matjson::Value const& json) { - if (json.template is()) { - m_impl->value = json.template as(); - return true; - } - return false; -} -bool Color3BSettingV3::save(matjson::Value& json) const { - json = m_impl->value; - return true; -} SettingNodeV3* Color3BSettingV3::createNode(float width) { return Color3BSettingNodeV3::create( std::static_pointer_cast(shared_from_this()), width @@ -1296,45 +1164,22 @@ std::optional> Color3BSettingV3::convertToLegacyVa class Color4BSettingV3::Impl final { public: - ccColor4B value; - ccColor4B defaultValue; }; Color4BSettingV3::Color4BSettingV3(PrivateMarker) : m_impl(std::make_shared()) {} Result> Color4BSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) { auto ret = std::make_shared(PrivateMarker()); - auto root = checkJson(json, "Color4BSettingV3"); - ret->parseSharedProperties(key, modID, root); - ret->parseDefaultValue(root, ret->m_impl->defaultValue); - ret->m_impl->value = ret->m_impl->defaultValue; - + ret->parseBaseProperties(key, modID, root); root.checkUnknownKeys(); return root.ok(ret); } -ccColor4B& Color4BSettingV3::getValueMut() const { - return m_impl->value; -} -ccColor4B Color4BSettingV3::getDefaultValue() const { - return m_impl->defaultValue; -} Result<> Color4BSettingV3::isValid(ccColor4B value) const { return Ok(); } -bool Color4BSettingV3::load(matjson::Value const& json) { - if (json.template is()) { - m_impl->value = json.template as(); - return true; - } - return false; -} -bool Color4BSettingV3::save(matjson::Value& json) const { - json = m_impl->value; - return true; -} SettingNodeV3* Color4BSettingV3::createNode(float width) { return Color4BSettingNodeV3::create( std::static_pointer_cast(shared_from_this()), width diff --git a/loader/src/ui/mods/settings/ModSettingsPopup.cpp b/loader/src/ui/mods/settings/ModSettingsPopup.cpp index f3c65103..06ea2485 100644 --- a/loader/src/ui/mods/settings/ModSettingsPopup.cpp +++ b/loader/src/ui/mods/settings/ModSettingsPopup.cpp @@ -6,6 +6,7 @@ #include #include #include +#include bool ModSettingsPopup::setup(Mod* mod) { m_noElasticity = true; @@ -33,10 +34,9 @@ bool ModSettingsPopup::setup(Mod* mod) { node = sett->createNode(layerSize.width); } else { - // todo: placeholder node - continue; + node = UnresolvedCustomSettingNodeV3::create(key, layerSize.width); } - node->setBGColor(ccc4(0, 0, 0, bg ? 60 : 20)); + node->setDefaultBGColor(ccc4(0, 0, 0, bg ? 60 : 20)); // auto separator = CCLayerColor::create({ 0, 0, 0, 50 }, layerSize.width, 1.f); // separator->setOpacity(bg ? 100 : 50); @@ -191,8 +191,8 @@ void ModSettingsPopup::updateState(SettingNodeV3* invoker) { if (sett == invoker) { continue; } - if (sett->getSetting()->getEnableIf()) { - sett->updateState(); + if (sett->getSetting() && sett->getSetting()->getEnableIf()) { + sett->updateState(nullptr); } } From ab9654a596b820ea8ed0a674d47a0a15f6952968 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sat, 7 Sep 2024 23:49:53 +0300 Subject: [PATCH 39/54] disable the search clear button if there is no search --- loader/src/ui/mods/list/ModList.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/loader/src/ui/mods/list/ModList.cpp b/loader/src/ui/mods/list/ModList.cpp index 8ec95174..3335f7d7 100644 --- a/loader/src/ui/mods/list/ModList.cpp +++ b/loader/src/ui/mods/list/ModList.cpp @@ -577,6 +577,7 @@ void ModList::updateState() { filterSpr->setState(!isDefaultQuery); auto clearSpr = static_cast(m_clearFiltersBtn->getNormalImage()); + m_clearFiltersBtn->setEnabled(!isDefaultQuery); clearSpr->setColor(isDefaultQuery ? ccGRAY : ccWHITE); clearSpr->setOpacity(isDefaultQuery ? 90 : 255); clearSpr->getTopSprite()->setColor(isDefaultQuery ? ccGRAY : ccWHITE); From 8e38551ea479af94e3dc84136adddc0694fbfc44 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sat, 7 Sep 2024 23:50:01 +0300 Subject: [PATCH 40/54] add settings searching :3 --- .../src/ui/mods/settings/ModSettingsPopup.cpp | 88 +++++++++++++++++-- .../src/ui/mods/settings/ModSettingsPopup.hpp | 6 +- 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/loader/src/ui/mods/settings/ModSettingsPopup.cpp b/loader/src/ui/mods/settings/ModSettingsPopup.cpp index 06ea2485..5c831fad 100644 --- a/loader/src/ui/mods/settings/ModSettingsPopup.cpp +++ b/loader/src/ui/mods/settings/ModSettingsPopup.cpp @@ -7,6 +7,32 @@ #include #include #include +// needed for weightedFuzzyMatch +#include + +static bool matchSearch(SettingNodeV3* node, std::string const& query) { + if (typeinfo_cast(node)) { + return true; + } + bool addToList = false; + auto setting = node->getSetting(); + double weighted = 0; + if (auto name = setting->getName()) { + addToList |= weightedFuzzyMatch(setting->getKey(), query, 0.5, weighted); + addToList |= weightedFuzzyMatch(*name, query, 1, weighted); + } + // If there's no name, give full weight to key + else { + addToList |= weightedFuzzyMatch(setting->getKey(), query, 1, weighted); + } + if (auto desc = setting->getDescription()) { + addToList |= weightedFuzzyMatch(*desc, query, 0.02, weighted); + } + if (weighted < 2) { + addToList = false; + } + return addToList; +} bool ModSettingsPopup::setup(Mod* mod) { m_noElasticity = true; @@ -20,10 +46,34 @@ bool ModSettingsPopup::setup(Mod* mod) { auto layerBG = CCLayerColor::create({ 0, 0, 0, 75 }); layerBG->setContentSize(layerSize); - m_mainLayer->addChildAtPosition(layerBG, Anchor::Center, -layerSize / 2); + layerBG->ignoreAnchorPointForPosition(false); + m_mainLayer->addChildAtPosition(layerBG, Anchor::Center); - auto layer = ScrollLayer::create(layerSize); - layer->setTouchEnabled(true); + auto searchContainer = CCMenu::create(); + searchContainer->setContentSize({ layerSize.width, 30 }); + + m_searchInput = TextInput::create((layerSize.width - 15) / .7f - 40, "Search Settings..."); + m_searchInput->setTextAlign(TextInputAlign::Left); + m_searchInput->setScale(.7f); + m_searchInput->setCallback([this](auto const&) { + this->updateState(); + m_list->moveToTop(); + }); + m_searchInput->setID("search-input"); + searchContainer->addChildAtPosition(m_searchInput, Anchor::Left, ccp(7.5f, 0), ccp(0, .5f)); + + auto searchClearSpr = GeodeSquareSprite::createWithSpriteFrameName("GJ_deleteIcon_001.png"); + searchClearSpr->setScale(.45f); + m_searchClearBtn = CCMenuItemSpriteExtra::create( + searchClearSpr, this, menu_selector(ModSettingsPopup::onClearSearch) + ); + m_searchClearBtn->setID("clear-search-button"); + searchContainer->addChildAtPosition(m_searchClearBtn, Anchor::Right, ccp(-20, 0)); + + layerBG->addChildAtPosition(searchContainer, Anchor::Top, ccp(0, 0), ccp(.5f, 1)); + + m_list = ScrollLayer::create(layerSize - ccp(0, searchContainer->getContentHeight())); + m_list->setTouchEnabled(true); bool bg = false; for (auto& key : mod->getSettingKeys()) { @@ -44,9 +94,9 @@ bool ModSettingsPopup::setup(Mod* mod) { // bg->addChildAtPosition(separator, Anchor::Bottom, ccp(0, 0), ccp(.5f, .5f)); m_settings.push_back(node); - layer->m_contentLayer->addChild(node); + m_list->m_contentLayer->addChild(node); } - layer->m_contentLayer->setLayout( + m_list->m_contentLayer->setLayout( ColumnLayout::create() ->setAxisReverse(true) ->setAutoGrowAxis(layerSize.height) @@ -54,13 +104,13 @@ bool ModSettingsPopup::setup(Mod* mod) { ->setAxisAlignment(AxisAlignment::End) ->setGap(0) ); - layer->moveToTop(); + m_list->moveToTop(); - layerBG->addChild(layer); + layerBG->addChildAtPosition(m_list, Anchor::BottomLeft); // layer borders - m_mainLayer->addChildAtPosition(createGeodeListBorders({layerSize.width, layerSize.height - 2}), Anchor::Center); + m_mainLayer->addChildAtPosition(createGeodeListBorders(layerSize), Anchor::Center); // buttons @@ -174,8 +224,16 @@ void ModSettingsPopup::onOpenConfigDirectory(CCObject*) { file::openFolder(m_mod->getConfigDir()); this->updateState(); } +void ModSettingsPopup::onClearSearch(CCObject*) { + m_searchInput->setString(""); + this->updateState(); + m_list->moveToTop(); +} void ModSettingsPopup::updateState(SettingNodeV3* invoker) { + auto search = m_searchInput->getString(); + auto hasSearch = !search.empty(); + m_restartBtn->setVisible(ModSettingsManager::from(m_mod)->restartRequired()); m_applyMenu->updateLayout(); @@ -185,8 +243,12 @@ void ModSettingsPopup::updateState(SettingNodeV3* invoker) { m_openConfigDirBtnSpr->setColor(configDirExists ? ccWHITE : ccGRAY); m_openConfigDirBtnSpr->setOpacity(configDirExists ? 255 : 155); - // Update all settings with "enable-if" schemes + // Update search visibility + all settings with "enable-if" schemes for (auto& sett : m_settings) { + sett->removeFromParent(); + if (!hasSearch || matchSearch(sett, search)) { + m_list->m_contentLayer->addChild(sett); + } // Avoid infinite loops if (sett == invoker) { continue; @@ -195,6 +257,7 @@ void ModSettingsPopup::updateState(SettingNodeV3* invoker) { sett->updateState(nullptr); } } + m_list->m_contentLayer->updateLayout(); m_applyBtnSpr->setCascadeColorEnabled(true); m_applyBtnSpr->setCascadeOpacityEnabled(true); @@ -208,6 +271,13 @@ void ModSettingsPopup::updateState(SettingNodeV3* invoker) { m_applyBtnSpr->setOpacity(155); m_applyBtn->setEnabled(false); } + + auto clearSpr = static_cast(m_searchClearBtn->getNormalImage()); + m_searchClearBtn->setEnabled(hasSearch); + clearSpr->setColor(hasSearch ? ccWHITE : ccGRAY); + clearSpr->setOpacity(hasSearch ? 255 : 90); + clearSpr->getTopSprite()->setColor(hasSearch ? ccWHITE : ccGRAY); + clearSpr->getTopSprite()->setOpacity(hasSearch ? 255 : 90); } bool ModSettingsPopup::hasUncommitted() const { diff --git a/loader/src/ui/mods/settings/ModSettingsPopup.hpp b/loader/src/ui/mods/settings/ModSettingsPopup.hpp index f2df3db8..147149a2 100644 --- a/loader/src/ui/mods/settings/ModSettingsPopup.hpp +++ b/loader/src/ui/mods/settings/ModSettingsPopup.hpp @@ -10,12 +10,15 @@ using namespace geode::prelude; class ModSettingsPopup : public GeodePopup { protected: Mod* m_mod; - std::vector m_settings; + ScrollLayer* m_list; + std::vector> m_settings; CCMenu* m_applyMenu; CCMenuItemSpriteExtra* m_applyBtn; CCMenuItemSpriteExtra* m_restartBtn; ButtonSprite* m_applyBtnSpr; IconButtonSprite* m_openConfigDirBtnSpr; + TextInput* m_searchInput; + CCMenuItemSpriteExtra* m_searchClearBtn; EventListener> m_changeListener; bool setup(Mod* mod) override; @@ -27,6 +30,7 @@ protected: void onResetAll(CCObject*); void onOpenSaveDirectory(CCObject*); void onOpenConfigDirectory(CCObject*); + void onClearSearch(CCObject*); public: static ModSettingsPopup* create(Mod* mod); From f1715cda2868b34e7196a2e40603ca1e48dc4307 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sun, 8 Sep 2024 12:07:10 +0300 Subject: [PATCH 41/54] make SettingValueNodeV3 keep track of the current value + some docs --- loader/include/Geode/loader/Mod.hpp | 24 ++++++- loader/include/Geode/loader/SettingV3.hpp | 26 ++++++-- loader/src/loader/SettingNodeV3.cpp | 81 ++++++----------------- loader/src/loader/SettingNodeV3.hpp | 30 ++------- loader/src/loader/SettingV3.cpp | 19 +++--- 5 files changed, 78 insertions(+), 102 deletions(-) diff --git a/loader/include/Geode/loader/Mod.hpp b/loader/include/Geode/loader/Mod.hpp index 0cd436e7..52a87771 100644 --- a/loader/include/Geode/loader/Mod.hpp +++ b/loader/include/Geode/loader/Mod.hpp @@ -173,13 +173,30 @@ namespace geode { */ std::filesystem::path getConfigDir(bool create = true) const; + /** + * Returns true if this mod has any settings + */ bool hasSettings() const; + /** + * Get a list of all this mod's setting keys (in the order they were + * declared in `mod.json`) + */ std::vector getSettingKeys() const; bool hasSetting(std::string_view const key) const; + + // todo in v4: remove these [[deprecated("Use Mod::getSettingV3")]] std::optional getSettingDefinition(std::string_view const key) const; [[deprecated("Use Mod::getSettingV3")]] SettingValue* getSetting(std::string_view const key) const; + + // todo in v4: possibly rename this to getSetting? + /** + * Get the definition of a setting, or null if the setting was not found, + * or if it's a custom setting that has not yet been registered using + * `Mod::registerCustomSettingType` + * @param key The key of the setting as defined in `mod.json` + */ std::shared_ptr getSettingV3(std::string_view const key) const; /** @@ -213,7 +230,12 @@ namespace geode { } /** - * Register a custom setting type + * Register a custom setting type. See + * [the setting docs](https://docs.geode-sdk.org/mods/settings) for more + * @param type The type of the setting. This should **not** include the + * `custom:` prefix! + * @param generator A pointer to a function that, when called, returns a + * newly-created instance of the setting type */ Result<> registerCustomSettingType(std::string_view type, SettingGenerator generator); diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index 19bd374d..1a78ffe5 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -522,34 +522,46 @@ namespace geode { */ template class SettingValueNodeV3 : public SettingNodeV3 { + protected: + private: + class Impl final { + private: + typename S::ValueType currentValue; + friend class SettingValueNodeV3; + }; + std::shared_ptr m_impl; + protected: bool init(std::shared_ptr setting, float width) { if (!SettingNodeV3::init(setting, width)) return false; + m_impl = std::make_shared(); + m_impl->currentValue = setting->getValue(); + return true; } void onCommit() override { - this->getSetting()->setValue(this->getValue()); + this->getSetting()->setValue(m_impl->currentValue); } bool hasUncommittedChanges() const override { - return this->getValue() != this->getSetting()->getValue(); + return m_impl->currentValue != this->getSetting()->getValue(); } bool hasNonDefaultValue() const override { - return this->getValue() != this->getSetting()->getDefaultValue(); + return m_impl->currentValue != this->getSetting()->getDefaultValue(); } void onResetToDefault() override { this->setValue(this->getSetting()->getDefaultValue(), nullptr); } - virtual void onSetValue(typename S::ValueAssignType value) = 0; - public: /** * Get the **uncommitted** value for this node */ - virtual typename S::ValueType getValue() const = 0; + typename S::ValueType getValue() const { + return m_impl->currentValue; + } /** * Set the **uncommitted** value for this node * @param value The value to set @@ -557,7 +569,7 @@ namespace geode { * for `SettingNodeV3::updateState` to know more */ void setValue(typename S::ValueAssignType value, cocos2d::CCNode* invoker) { - this->onSetValue(value); + m_impl->currentValue = value; this->markChanged(invoker); } diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index 273caeca..4acba028 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -278,6 +278,7 @@ bool BoolSettingNodeV3::init(std::shared_ptr setting, float width void BoolSettingNodeV3::updateState(CCNode* invoker) { SettingNodeV3::updateState(invoker); auto enable = this->getSetting()->shouldEnable(); + m_toggle->toggle(this->getValue()); m_toggle->setCascadeColorEnabled(true); m_toggle->setCascadeOpacityEnabled(true); m_toggle->setEnabled(enable); @@ -286,17 +287,10 @@ void BoolSettingNodeV3::updateState(CCNode* invoker) { } void BoolSettingNodeV3::onToggle(CCObject*) { - m_toggle->toggle(!m_toggle->isToggled()); + this->setValue(!m_toggle->isToggled(), m_toggle); this->markChanged(m_toggle); } -bool BoolSettingNodeV3::getValue() const { - return m_toggle->isToggled(); -} -void BoolSettingNodeV3::onSetValue(bool value) { - m_toggle->toggle(value); -} - BoolSettingNodeV3* BoolSettingNodeV3::create(std::shared_ptr setting, float width) { auto ret = new BoolSettingNodeV3(); if (ret && ret->init(setting, width)) { @@ -352,6 +346,11 @@ bool StringSettingNodeV3::init(std::shared_ptr setting, float w void StringSettingNodeV3::updateState(CCNode* invoker) { SettingNodeV3::updateState(invoker); + + if (invoker != m_input) { + m_input->setString(this->getValue()); + } + auto enable = this->getSetting()->shouldEnable(); if (!this->getSetting()->getEnumOptions()) { m_input->setEnabled(enable); @@ -366,22 +365,14 @@ void StringSettingNodeV3::updateState(CCNode* invoker) { void StringSettingNodeV3::onArrow(CCObject* sender) { auto options = *this->getSetting()->getEnumOptions(); - auto index = ranges::indexOf(options, m_input->getString()).value_or(0); + auto index = ranges::indexOf(options, this->getValue()).value_or(0); if (sender->getTag() > 0) { index = index < options.size() - 1 ? index + 1 : 0; } else { index = index > 0 ? index - 1 : options.size() - 1; } - m_input->setString(options.at(index)); - this->updateState(static_cast(sender)); -} - -std::string StringSettingNodeV3::getValue() const { - return m_input->getString(); -} -void StringSettingNodeV3::onSetValue(std::string_view value) { - m_input->setString(std::string(value)); + this->setValue(options.at(index), static_cast(sender)); } StringSettingNodeV3* StringSettingNodeV3::create(std::shared_ptr setting, float width) { @@ -400,8 +391,6 @@ bool FileSettingNodeV3::init(std::shared_ptr setting, float width if (!SettingValueNodeV3::init(setting, width)) return false; - m_path = setting->getValue(); - auto labelBG = extension::CCScale9Sprite::create("square02b_001.png", { 0, 0, 80, 80 }); labelBG->setScale(.25f); labelBG->setColor({ 0, 0, 0 }); @@ -433,13 +422,13 @@ void FileSettingNodeV3::updateState(CCNode* invoker) { this->getSetting()->isFolder() ? "folderIcon_001.png" : "file.png"_spr )); limitNodeSize(m_fileIcon, ccp(10, 10), 1.f, .1f); - if (m_path.empty()) { + if (this->getValue().empty()) { m_nameLabel->setString(this->getSetting()->isFolder() ? "No Folder Selected" : "No File Selected"); m_nameLabel->setColor(ccGRAY); m_nameLabel->setOpacity(155); } else { - m_nameLabel->setString(m_path.filename().string().c_str()); + m_nameLabel->setString(this->getValue().filename().string().c_str()); m_nameLabel->setColor(ccWHITE); m_nameLabel->setOpacity(255); } @@ -458,8 +447,7 @@ void FileSettingNodeV3::onPickFile(CCObject*) { return; } if (value->isOk()) { - m_path = value->unwrap().string(); - this->markChanged(nullptr); + this->setValue(value->unwrap(), nullptr); } else { FLAlertLayer::create( @@ -476,19 +464,14 @@ void FileSettingNodeV3::onPickFile(CCObject*) { (this->getSetting()->useSaveDialog() ? file::PickMode::SaveFile : file::PickMode::OpenFile), { // Prefer opening the current path directly if possible - m_path.empty() || !std::filesystem::exists(m_path.parent_path(), ec) ? dirs::getGameDir() : m_path, + this->getValue().empty() || !std::filesystem::exists(this->getValue().parent_path(), ec) ? + dirs::getGameDir() : + this->getValue(), this->getSetting()->getFilters().value_or(std::vector()) } )); } -std::filesystem::path FileSettingNodeV3::getValue() const { - return m_path; -} -void FileSettingNodeV3::onSetValue(std::filesystem::path const& value) { - m_path = value; -} - FileSettingNodeV3* FileSettingNodeV3::create(std::shared_ptr setting, float width) { auto ret = new FileSettingNodeV3(); if (ret && ret->init(setting, width)) { @@ -505,8 +488,6 @@ bool Color3BSettingNodeV3::init(std::shared_ptr setting, float if (!SettingValueNodeV3::init(setting, width)) return false; - m_value = setting->getValue(); - m_colorSprite = ColorChannelSprite::create(); m_colorSprite->setScale(.65f); @@ -522,7 +503,7 @@ bool Color3BSettingNodeV3::init(std::shared_ptr setting, float void Color3BSettingNodeV3::updateState(CCNode* invoker) { SettingNodeV3::updateState(invoker); - m_colorSprite->setColor(m_value); + m_colorSprite->setColor(this->getValue()); auto enable = this->getSetting()->shouldEnable(); m_colorSprite->setOpacity(enable ? 255 : 155); @@ -530,20 +511,12 @@ void Color3BSettingNodeV3::updateState(CCNode* invoker) { } void Color3BSettingNodeV3::onSelectColor(CCObject*) { - auto popup = ColorPickPopup::create(m_value); + auto popup = ColorPickPopup::create(this->getValue()); popup->setDelegate(this); popup->show(); } void Color3BSettingNodeV3::updateColor(ccColor4B const& color) { - m_value = to3B(color); - this->markChanged(nullptr); -} - -ccColor3B Color3BSettingNodeV3::getValue() const { - return m_value; -} -void Color3BSettingNodeV3::onSetValue(ccColor3B value) { - m_value = value; + this->setValue(to3B(color), nullptr); } Color3BSettingNodeV3* Color3BSettingNodeV3::create(std::shared_ptr setting, float width) { @@ -562,8 +535,6 @@ bool Color4BSettingNodeV3::init(std::shared_ptr setting, float if (!SettingValueNodeV3::init(setting, width)) return false; - m_value = setting->getValue(); - m_colorSprite = ColorChannelSprite::create(); m_colorSprite->setScale(.65f); @@ -579,8 +550,8 @@ bool Color4BSettingNodeV3::init(std::shared_ptr setting, float void Color4BSettingNodeV3::updateState(CCNode* invoker) { SettingNodeV3::updateState(invoker); - m_colorSprite->setColor(to3B(m_value)); - m_colorSprite->updateOpacity(m_value.a / 255.f); + m_colorSprite->setColor(to3B(this->getValue())); + m_colorSprite->updateOpacity(this->getValue().a / 255.f); auto enable = this->getSetting()->shouldEnable(); m_colorSprite->setOpacity(enable ? 255 : 155); @@ -588,20 +559,12 @@ void Color4BSettingNodeV3::updateState(CCNode* invoker) { } void Color4BSettingNodeV3::onSelectColor(CCObject*) { - auto popup = ColorPickPopup::create(m_value); + auto popup = ColorPickPopup::create(this->getValue()); popup->setDelegate(this); popup->show(); } void Color4BSettingNodeV3::updateColor(ccColor4B const& color) { - m_value = color; - this->markChanged(nullptr); -} - -ccColor4B Color4BSettingNodeV3::getValue() const { - return m_value; -} -void Color4BSettingNodeV3::onSetValue(ccColor4B value) { - m_value = value; + this->setValue(color, nullptr); } Color4BSettingNodeV3* Color4BSettingNodeV3::create(std::shared_ptr setting, float width) { diff --git a/loader/src/loader/SettingNodeV3.hpp b/loader/src/loader/SettingNodeV3.hpp index b0e9724d..1d4efdf0 100644 --- a/loader/src/loader/SettingNodeV3.hpp +++ b/loader/src/loader/SettingNodeV3.hpp @@ -36,9 +36,6 @@ protected: void onToggle(CCObject*); public: - bool getValue() const override; - void onSetValue(bool value) override; - static BoolSettingNodeV3* create(std::shared_ptr setting, float width); }; @@ -172,6 +169,10 @@ protected: m_input->setEnabled(enable); } + if (invoker != m_input) { + m_input->setString(numToString(this->getValue())); + } + auto min = this->getSetting()->getMinValue(); auto enableLeft = enable && (!min || this->getValue() > *min); m_arrowLeftBtn->setEnabled(enableLeft); @@ -215,14 +216,6 @@ protected: this->setValue(this->valueFromSlider(m_slider->m_touchLogic->m_thumb->getValue()), m_slider); } - ValueType getValue() const override { - return numFromString(m_input->getString()) - .value_or(this->getSetting()->getDefaultValue()); - } - void onSetValue(ValueAssignType value) override { - m_input->setString(numToString(value)); - } - public: static NumberSettingNodeV3* create(std::shared_ptr setting, float width) { auto ret = new NumberSettingNodeV3(); @@ -250,15 +243,11 @@ protected: public: static StringSettingNodeV3* create(std::shared_ptr setting, float width); - - std::string getValue() const override; - void onSetValue(std::string_view value) override; }; class FileSettingNodeV3 : public SettingValueNodeV3 { protected: CCSprite* m_fileIcon; - std::filesystem::path m_path; CCLabelBMFont* m_nameLabel; EventListener>> m_pickListener; CCMenuItemSpriteExtra* m_selectBtn; @@ -270,14 +259,10 @@ protected: public: static FileSettingNodeV3* create(std::shared_ptr setting, float width); - - std::filesystem::path getValue() const override; - void onSetValue(std::filesystem::path const& value) override; }; class Color3BSettingNodeV3 : public SettingValueNodeV3, public ColorPickPopupDelegate { protected: - ccColor3B m_value; CCMenuItemSpriteExtra* m_colorBtn; ColorChannelSprite* m_colorSprite; @@ -288,14 +273,10 @@ protected: public: static Color3BSettingNodeV3* create(std::shared_ptr setting, float width); - - ccColor3B getValue() const override; - void onSetValue(ccColor3B value) override; }; class Color4BSettingNodeV3 : public SettingValueNodeV3, public ColorPickPopupDelegate { protected: - ccColor4B m_value; CCMenuItemSpriteExtra* m_colorBtn; ColorChannelSprite* m_colorSprite; @@ -306,9 +287,6 @@ protected: public: static Color4BSettingNodeV3* create(std::shared_ptr setting, float width); - - ccColor4B getValue() const override; - void onSetValue(ccColor4B value) override; }; class UnresolvedCustomSettingNodeV3 : public SettingNodeV3 { diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index abce0468..43e8e937 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -1055,17 +1055,18 @@ Result> FileSettingV3::parse(std::string const& k key, modID ); } - std::string dialogType; - root.has("dialog").into(dialogType); - switch (hash(dialogType)) { - case hash("save"): ret->m_impl->useSaveDialog = true; break; - case hash("open"): ret->m_impl->useSaveDialog = false; break; - case hash(""): break; - default: return Err("Setting '{}' in mod {}: unknown \"dialog\" type \"{}\"", key, modID, dialogType); - } - // Filter controls only make sense for files but not for folders + // Controls only make sense for files but not for folders if (auto controls = root.has("control")) { + std::string dialogType; + controls.has("dialog").into(dialogType); + switch (hash(dialogType)) { + case hash("save"): ret->m_impl->useSaveDialog = true; break; + case hash("open"): ret->m_impl->useSaveDialog = false; break; + case hash(""): break; + default: return Err("Setting '{}' in mod {}: unknown \"dialog\" type \"{}\"", key, modID, dialogType); + } + auto filters = std::vector(); for (auto& item : controls.has("filters").items()) { utils::file::FilePickOptions::Filter filter; From 0efde83fd4bbf4ef0fbac857e7f514eb4b00a2a3 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Mon, 9 Sep 2024 00:50:49 +0300 Subject: [PATCH 42/54] allow fine-grained parsing for settings --- loader/include/Geode/loader/SettingV3.hpp | 138 ++++++++++++++++++++-- loader/src/loader/SettingV3.cpp | 71 +++++++---- 2 files changed, 172 insertions(+), 37 deletions(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index 1a78ffe5..a46ce508 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -26,9 +26,98 @@ namespace geode { std::shared_ptr m_impl; protected: + /** + * Only call this function if you aren't going to call + * `parseBaseProperties`, which will call it for you! + * If you don't want to call `parseBaseProperties`, at the very least + * you **must** call this! + * Select which properties you want to parse using the `parseX` + * functions + * @param key The setting's key as defined in `mod.json` + * @param modID The ID of the mod this settings is being parsed for + * @param json The current JSON checking instance being used. This + * should be the JSON object that defines the setting. If you aren't + * using Geode's JSON checking utilities, you can use the other + * overload of `init` + */ + void init(std::string const& key, std::string const& modID, JsonExpectedValue& json); + /** + * Only call this function if you aren't going to call + * `parseBaseProperties`, which will call it for you! + * If you don't want to call `parseBaseProperties`, at the very least + * you **must** call this! + * Select which properties you want to parse using the `parseX` + * functions + * @param key The setting's key as defined in `mod.json` + * @param modID The ID of the mod this settings is being parsed for + * @note If you are using Geode's JSON checking utilities + * (`checkJson` / `JsonExpectedValue`), you should be using the other + * overload that takes a `JsonExpectedValue&`! + */ void init(std::string const& key, std::string const& modID); - Result<> parseBaseProperties(std::string const& key, std::string const& modID, matjson::Value const& value, bool onlyNameAndDesc = false); - void parseBaseProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value, bool onlyNameAndDesc = false); + + /** + * Parses the `"name"` and `"description"` keys from the setting's + * definition in `mod.json` (if they exist), so their values can be + * accessed via `getName` and `getDescription`. + * @param json The current JSON checking instance being used. This + * should be the JSON object that defines the setting + * @warning In most cases, you should be using `parseBaseProperties` + * instead to do all of this in one go! + * If you do need the fine-grained control however, make sure to call + * `init` before calling these parsing functions! + */ + void parseNameAndDescription(JsonExpectedValue& json); + /** + * Parses the `"enable-if"` and `"enable-if-description"` keys from + * the setting's definition in `mod.json` (if they exist), so + * `shouldEnable` and `getEnableIfDescription` work. + * @param json The current JSON checking instance being used. This + * should be the JSON object that defines the setting + * @warning In most cases, you should be using `parseBaseProperties` + * instead to do all of this in one go! + * If you do need the fine-grained control however, make sure to call + * `init` before calling these parsing functions! + */ + void parseEnableIf(JsonExpectedValue& json); + /** + * Parses the `"requires-restart"` key from the setting's definition in + * `mod.json` (if they exist), so `requiresRestart` works. + * @param json The current JSON checking instance being used. This + * should be the JSON object that defines the setting + * @warning In most cases, you should be using `parseBaseProperties` + * instead to do all of this in one go! + * If you do need the fine-grained control however, make sure to call + * `init` before calling these parsing functions! + */ + void parseValueProperties(JsonExpectedValue& json); + + /** + * Parse all of the base properties such as `"name"` and `"description"` + * for this setting + * @param key The setting's key as defined in `mod.json` + * @param modID The ID of the mod this settings is being parsed for + * @param json The current JSON checking instance being used. If you + * aren't using Geode's JSON checking utilities, use the other overload + * of this function + * @note If you don't want to parse some of the base properties, such as + * `"requires-restart"` (because you're doing a cosmetic setting), then + * you can call `init` instead and then the specific `parseX` functions + */ + void parseBaseProperties(std::string const& key, std::string const& modID, JsonExpectedValue& json); + /** + * Parse all of the base properties such as `"name"` and `"description"` + * for this setting + * @param key The setting's key as defined in `mod.json` + * @param modID The ID of the mod this settings is being parsed for + * @param json The JSON value. If you are using Geode's JSON checking + * utilities (`checkJson` / `JsonExpectedValue`), you should use the + * other overload directly! + * @note If you don't want to parse some of the base properties, such as + * `"requires-restart"` (because you're doing a cosmetic setting), then + * you can call `init` instead and then the specific `parseX` functions + */ + Result<> parseBaseProperties(std::string const& key, std::string const& modID, matjson::Value const& json); /** * Mark that the value of this setting has changed. This should be @@ -80,6 +169,10 @@ namespace geode { * Whether this setting requires a restart on change */ bool requiresRestart() const; + /** + * Get the platforms this setting is available on + */ + std::vector getPlatforms() const; virtual bool load(matjson::Value const& json) = 0; virtual bool save(matjson::Value& json) const = 0; @@ -91,9 +184,15 @@ namespace geode { */ virtual void reset() = 0; - [[deprecated("This function will be removed alongside legacy settings in 4.0.0!")]] + [[deprecated( + "This function will be removed alongside legacy settings in 4.0.0! " + "You should NOT be implementing it for your own custom setting classes" + )]] virtual std::optional convertToLegacy() const; - [[deprecated("This function will be removed alongside legacy settings in 4.0.0!")]] + [[deprecated( + "This function will be removed alongside legacy settings in 4.0.0! " + "You should NOT be implementing it for your own custom setting classes" + )]] virtual std::optional> convertToLegacyValue() const; }; @@ -123,15 +222,17 @@ namespace geode { protected: /** - * Parse shared value, including the default value for this setting - * @param key The key of the setting - * @param modID The ID of the mod this setting is being parsed for - * @param json The current JSON checking instance being used. If you - * aren't using Geode's JSON checking utilities, use the other overload - * of this function + * Parses the `"default"` key from the setting's definition in + * `mod.json`. The key may also be defined per-platform, i.e. + * `"default": { "win": ..., "android": ... }` + * @param json The current JSON checking instance being used. This + * should be the JSON object that defines the setting + * @warning In most cases, you should be using `parseBaseProperties` + * instead to do all of this in one go! + * If you do need the fine-grained control however, make sure to call + * `init` before calling these parsing functions! */ - void parseBaseProperties(std::string const& key, std::string const& modID, JsonExpectedValue& json) { - SettingV3::parseBaseProperties(key, modID, json, false); + void parseDefaultValue(JsonExpectedValue& json) { auto root = json.needs("default"); // Check if this is a platform-specific default value if (root.isObject() && root.has(GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH)) { @@ -142,6 +243,19 @@ namespace geode { } m_impl->value = m_impl->defaultValue; } + + /** + * Parse shared value, including the default value for this setting + * @param key The key of the setting + * @param modID The ID of the mod this setting is being parsed for + * @param json The current JSON checking instance being used. If you + * aren't using Geode's JSON checking utilities, use the other overload + * of this function + */ + void parseBaseProperties(std::string const& key, std::string const& modID, JsonExpectedValue& json) { + SettingV3::parseBaseProperties(key, modID, json); + this->parseDefaultValue(json); + } /** * Parse shared value, including the default value for this setting * @param key The key of the setting diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index 43e8e937..1c7b8c2e 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -460,6 +460,7 @@ class SettingV3::GeodeImpl { public: std::string modID; std::string key; + std::vector platforms; std::optional name; std::optional description; std::optional enableIf; @@ -471,34 +472,50 @@ public: SettingV3::SettingV3() : m_impl(std::make_shared()) {} SettingV3::~SettingV3() = default; -Result<> SettingV3::parseBaseProperties(std::string const& key, std::string const& modID, matjson::Value const& value, bool onlyNameAndDesc) { - auto json = checkJson(value, "SettingV3"); - this->parseBaseProperties(key, modID, json, onlyNameAndDesc); - return json.ok(); -} -void SettingV3::parseBaseProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value, bool onlyNameAndDesc) { - this->init(key, modID); - value.needs("type"); - value.has("platforms"); - value.has("name").into(m_impl->name); - value.has("description").into(m_impl->description); - if (!onlyNameAndDesc) { - value.has("requires-restart").into(m_impl->requiresRestart); - value.has("enable-if") - .template mustBe("a valid \"enable-if\" scheme", [this](std::string const& str) -> Result<> { - GEODE_UNWRAP_INTO(auto tree, enable_if_parsing::Parser::parse(str, m_impl->modID)); - GEODE_UNWRAP(tree->check()); - m_impl->enableIfTree = std::move(tree); - return Ok(); - }) - .into(m_impl->enableIf); - value.has("enable-if-description").into(m_impl->enableIfDescription); - } -} void SettingV3::init(std::string const& key, std::string const& modID) { m_impl->key = key; m_impl->modID = modID; } +void SettingV3::init(std::string const& key, std::string const& modID, JsonExpectedValue& json) { + this->init(key, modID); + + // Keys every setting must have + json.needs("type"); + for (auto& plat : json.has("platforms").items()) { + m_impl->platforms.push_back(PlatformID::from(plat.template get())); + } +} + +void SettingV3::parseNameAndDescription(JsonExpectedValue& json) { + json.needs("name").into(m_impl->name); + json.has("description").into(m_impl->description); +} +void SettingV3::parseEnableIf(JsonExpectedValue& json) { + json.has("enable-if") + .template mustBe("a valid \"enable-if\" scheme", [this](std::string const& str) -> Result<> { + GEODE_UNWRAP_INTO(auto tree, enable_if_parsing::Parser::parse(str, m_impl->modID)); + GEODE_UNWRAP(tree->check()); + m_impl->enableIfTree = std::move(tree); + return Ok(); + }) + .into(m_impl->enableIf); + json.has("enable-if-description").into(m_impl->enableIfDescription); +} +void SettingV3::parseValueProperties(JsonExpectedValue& json) { + json.has("requires-restart").into(m_impl->requiresRestart); +} + +Result<> SettingV3::parseBaseProperties(std::string const& key, std::string const& modID, matjson::Value const& value) { + auto json = checkJson(value, "SettingV3"); + this->parseBaseProperties(key, modID, json); + return json.ok(); +} +void SettingV3::parseBaseProperties(std::string const& key, std::string const& modID, JsonExpectedValue& json) { + this->init(key, modID, json); + this->parseNameAndDescription(json); + this->parseValueProperties(json); + this->parseEnableIf(json); +} std::string SettingV3::getKey() const { return m_impl->key; @@ -540,6 +557,9 @@ std::optional SettingV3::getEnableIfDescription() const { bool SettingV3::requiresRestart() const { return m_impl->requiresRestart; } +std::vector SettingV3::getPlatforms() const { + return m_impl->platforms; +} Mod* SettingV3::getMod() const { return Loader::get()->getInstalledMod(m_impl->modID); } @@ -573,7 +593,8 @@ TitleSettingV3::TitleSettingV3(PrivateMarker) : m_impl(std::make_shared()) Result> TitleSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) { auto ret = std::make_shared(PrivateMarker()); auto root = checkJson(json, "TitleSettingV3"); - ret->parseBaseProperties(key, modID, root, true); + ret->init(key, modID, root); + ret->parseNameAndDescription(root); root.checkUnknownKeys(); return root.ok(ret); } From c28f312d750498a3b67e6620c76bf46e805a0a94 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Mon, 9 Sep 2024 01:00:34 +0300 Subject: [PATCH 43/54] update flash.toml --- flash.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/flash.toml b/flash.toml index 84c671b0..f574da47 100644 --- a/flash.toml +++ b/flash.toml @@ -13,6 +13,7 @@ icon = "loader/resources/logos/geode-circle.png" dir = "docs" assets = [ "docs/assets/*.png", + "docs/assets/settings/*.png", "docs/assets/handbook/vol1/*.png", "docs/assets/handbook/vol2/*.png", ] From 9f1c70ad09c9b2caaf8250995c2f16aea0bebfa4 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Mon, 9 Sep 2024 22:29:34 +0300 Subject: [PATCH 44/54] allow making incompatibilities platform-specific --- loader/src/loader/ModMetadataImpl.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/loader/src/loader/ModMetadataImpl.cpp b/loader/src/loader/ModMetadataImpl.cpp index c377f6b3..51181915 100644 --- a/loader/src/loader/ModMetadataImpl.cpp +++ b/loader/src/loader/ModMetadataImpl.cpp @@ -228,6 +228,16 @@ Result ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs } for (auto& incompat : root.has("incompatibilities").items()) { + bool onThisPlatform = !incompat.has("platforms"); + for (auto& plat : incompat.has("platforms").items()) { + if (PlatformID::coveredBy(plat.get(), GEODE_PLATFORM_TARGET)) { + onThisPlatform = true; + } + } + if (!onThisPlatform) { + continue; + } + Incompatibility incompatibility; incompat.needs("id").mustBe(ID_REGEX, &ModMetadata::Impl::validateOldID).into(incompatibility.id); incompat.needs("version").into(incompatibility.version); From d5718be4224e23ddf97703eda1996782ed779a35 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Mon, 9 Sep 2024 22:29:47 +0300 Subject: [PATCH 45/54] add PlatformID::getCovered --- loader/include/Geode/platform/platform.hpp | 25 ++++++++---- loader/src/utils/PlatformID.cpp | 46 ++++++++++++++-------- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/loader/include/Geode/platform/platform.hpp b/loader/include/Geode/platform/platform.hpp index 2b5df536..c8d52484 100644 --- a/loader/include/Geode/platform/platform.hpp +++ b/loader/include/Geode/platform/platform.hpp @@ -114,15 +114,16 @@ namespace geode { class PlatformID { public: + // todo in v4: make these flags and add archless Mac and Android as well as Desktop and Mobile and remove Linux enum { - Unknown = -1, - Windows, - MacIntel, - MacArm, - iOS, - Android32, - Android64, - Linux, + Unknown = -1, + Windows = 0, + MacIntel = 1, + MacArm = 2, + iOS = 3, + Android32 = 4, + Android64 = 5, + Linux = 6, }; using Type = decltype(Unknown); @@ -172,7 +173,14 @@ namespace geode { */ static GEODE_DLL bool coveredBy(const char* str, PlatformID t); static GEODE_DLL bool coveredBy(std::string const& str, PlatformID t); + /** + * Returns the list of platforms covered by this string name. For + * example, "android" would return both Android32 and Android64 + * todo in v4: deprecate this as the flagged version deals with this + */ + static GEODE_DLL std::vector getCovered(std::string_view str); + // todo in v4: this does not need to be constexpr in the header. dllexport it static constexpr char const* toString(Type lp) { switch (lp) { case Unknown: return "Unknown"; @@ -188,6 +196,7 @@ namespace geode { return "Undefined"; } + // todo in v4: this does not need to be constexpr in the header. dllexport it static constexpr char const* toShortString(Type lp, bool ignoreArch = false) { switch (lp) { case Unknown: return "unknown"; diff --git a/loader/src/utils/PlatformID.cpp b/loader/src/utils/PlatformID.cpp index 02ec045c..49a746c5 100644 --- a/loader/src/utils/PlatformID.cpp +++ b/loader/src/utils/PlatformID.cpp @@ -5,6 +5,12 @@ using namespace geode::prelude; PlatformID PlatformID::from(const char* str) { + // todo in v4: this should just be + // "win" -> Windows + // "mac", "mac-intel", "mac-arm" -> Mac + // "ios" -> iOS + // "android", "android32", "android64" -> Android + // no linux switch (hash(str)) { case hash("win"): case hash("Windows"): @@ -33,29 +39,35 @@ PlatformID PlatformID::from(const char* str) { } bool PlatformID::coveredBy(const char* str, PlatformID t) { - switch (hash(str)) { - case hash("win"): return t == PlatformID::Windows; - - case hash("mac"): return t == PlatformID::MacIntel || t == PlatformID::MacArm; - case hash("mac-intel"): return t == PlatformID::MacIntel; - case hash("mac-arm"): return t == PlatformID::MacArm; - - case hash("ios"): return t == PlatformID::iOS; - - case hash("android"): return t == PlatformID::Android32 || t == PlatformID::Android64; - case hash("android32"): return t == PlatformID::Android32; - case hash("android64"): return t == PlatformID::Android64; - - case hash("linux"): return t == PlatformID::Linux; - - default: return false; - } + // todo in v4: this is ridiculously inefficient currently - in v4 just use a flag check! + return ranges::contains(getCovered(str), t); } bool PlatformID::coveredBy(std::string const& str, PlatformID t) { return PlatformID::coveredBy(str.c_str(), t); } +std::vector PlatformID::getCovered(std::string_view str) { + switch (hash(str)) { + case hash("win"): return { PlatformID::Windows }; + + case hash("mac"): return { PlatformID::MacIntel, PlatformID::MacArm }; + case hash("mac-intel"): return { PlatformID::MacIntel }; + case hash("mac-arm"): return { PlatformID::MacArm }; + + case hash("ios"): return { PlatformID::iOS }; + + case hash("android"): return { PlatformID::Android32, PlatformID::Android64 }; + case hash("android32"): return { PlatformID::Android32 }; + case hash("android64"): return { PlatformID::Android64 }; + + // todo in v4: no linux + case hash("linux"): return { PlatformID::Linux }; + + default: return {}; + } +} + PlatformID PlatformID::from(std::string const& str) { return PlatformID::from(str.c_str()); } From b66bcca2a6d11f0af011dea788098fa416af7420 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Mon, 9 Sep 2024 22:30:04 +0300 Subject: [PATCH 46/54] show invalid values in the UI --- loader/include/Geode/loader/SettingV3.hpp | 11 +++++++ loader/src/loader/SettingNodeV3.cpp | 35 ++++++++++++----------- loader/src/loader/SettingNodeV3.hpp | 2 +- loader/src/loader/SettingV3.cpp | 2 +- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index a46ce508..d4968644 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -621,6 +621,7 @@ namespace geode { void setDefaultBGColor(cocos2d::ccColor4B color); cocos2d::CCLabelBMFont* getNameLabel() const; + cocos2d::CCLabelBMFont* getStatusLabel() const; cocos2d::CCMenu* getNameMenu() const; cocos2d::CCMenu* getButtonMenu() const; cocos2d::CCLayerColor* getBG() const; @@ -656,6 +657,16 @@ namespace geode { return true; } + void updateState(cocos2d::CCNode* invoker) { + SettingNodeV3::updateState(invoker); + auto validate = this->getSetting()->isValid(m_impl->currentValue); + if (!validate) { + this->getStatusLabel()->setVisible(true); + this->getStatusLabel()->setString(validate.unwrapErr().c_str()); + this->getStatusLabel()->setColor(cocos2d::ccc3(235, 35, 52)); + } + } + void onCommit() override { this->getSetting()->setValue(m_impl->currentValue); } diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index 4acba028..06d495ed 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -48,7 +48,7 @@ public: CCMenu* nameMenu; CCMenu* buttonMenu; CCMenuItemSpriteExtra* resetButton; - CCLabelBMFont* errorLabel; + CCLabelBMFont* statusLabel; ccColor4B bgColor = ccc4(0, 0, 0, 0); bool committed = false; }; @@ -75,9 +75,9 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { m_impl->nameLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .4f)->setScalePriority(1)); m_impl->nameMenu->addChild(m_impl->nameLabel); - m_impl->errorLabel = CCLabelBMFont::create("", "bigFont.fnt"); - m_impl->errorLabel->setScale(.25f); - this->addChildAtPosition(m_impl->errorLabel, Anchor::Left, ccp(10, -10), ccp(0, .5f)); + m_impl->statusLabel = CCLabelBMFont::create("", "bigFont.fnt"); + m_impl->statusLabel->setScale(.25f); + this->addChildAtPosition(m_impl->statusLabel, Anchor::Left, ccp(10, -10), ccp(0, .5f)); if (setting && setting->getDescription()) { auto descSpr = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png"); @@ -111,7 +111,7 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { } void SettingNodeV3::updateState(CCNode* invoker) { - m_impl->errorLabel->setVisible(false); + m_impl->statusLabel->setVisible(false); m_impl->nameLabel->setColor(this->hasUncommittedChanges() ? ccc3(17, 221, 0) : ccWHITE); m_impl->resetButton->setVisible(this->hasNonDefaultValue()); @@ -122,15 +122,15 @@ void SettingNodeV3::updateState(CCNode* invoker) { if (m_impl->setting && !m_impl->setting->shouldEnable()) { if (auto desc = m_impl->setting->getEnableIfDescription()) { m_impl->nameLabel->setColor(ccGRAY); - m_impl->errorLabel->setVisible(true); - m_impl->errorLabel->setColor("mod-list-errors-found"_cc3b); - m_impl->errorLabel->setString(desc->c_str()); + m_impl->statusLabel->setVisible(true); + m_impl->statusLabel->setColor("mod-list-errors-found"_cc3b); + m_impl->statusLabel->setString(desc->c_str()); } } if (m_impl->setting && m_impl->setting->requiresRestart() && m_impl->committed) { - m_impl->errorLabel->setVisible(true); - m_impl->errorLabel->setColor("mod-list-restart-required-label"_cc3b); - m_impl->errorLabel->setString("Restart Required"); + m_impl->statusLabel->setVisible(true); + m_impl->statusLabel->setColor("mod-list-restart-required-label"_cc3b); + m_impl->statusLabel->setString("Restart Required"); m_impl->bg->setColor("mod-list-restart-required-label-bg"_cc3b); m_impl->bg->setOpacity(75); } @@ -199,6 +199,9 @@ void SettingNodeV3::setContentSize(CCSize const& size) { CCLabelBMFont* SettingNodeV3::getNameLabel() const { return m_impl->nameLabel; } +CCLabelBMFont* SettingNodeV3::getStatusLabel() const { + return m_impl->statusLabel; +} CCMenu* SettingNodeV3::getNameMenu() const { return m_impl->nameMenu; } @@ -276,7 +279,7 @@ bool BoolSettingNodeV3::init(std::shared_ptr setting, float width } void BoolSettingNodeV3::updateState(CCNode* invoker) { - SettingNodeV3::updateState(invoker); + SettingValueNodeV3::updateState(invoker); auto enable = this->getSetting()->shouldEnable(); m_toggle->toggle(this->getValue()); m_toggle->setCascadeColorEnabled(true); @@ -345,7 +348,7 @@ bool StringSettingNodeV3::init(std::shared_ptr setting, float w } void StringSettingNodeV3::updateState(CCNode* invoker) { - SettingNodeV3::updateState(invoker); + SettingValueNodeV3::updateState(invoker); if (invoker != m_input) { m_input->setString(this->getValue()); @@ -417,7 +420,7 @@ bool FileSettingNodeV3::init(std::shared_ptr setting, float width } void FileSettingNodeV3::updateState(CCNode* invoker) { - SettingNodeV3::updateState(invoker); + SettingValueNodeV3::updateState(invoker); m_fileIcon->setDisplayFrame(CCSpriteFrameCache::get()->spriteFrameByName( this->getSetting()->isFolder() ? "folderIcon_001.png" : "file.png"_spr )); @@ -502,7 +505,7 @@ bool Color3BSettingNodeV3::init(std::shared_ptr setting, float } void Color3BSettingNodeV3::updateState(CCNode* invoker) { - SettingNodeV3::updateState(invoker); + SettingValueNodeV3::updateState(invoker); m_colorSprite->setColor(this->getValue()); auto enable = this->getSetting()->shouldEnable(); @@ -549,7 +552,7 @@ bool Color4BSettingNodeV3::init(std::shared_ptr setting, float } void Color4BSettingNodeV3::updateState(CCNode* invoker) { - SettingNodeV3::updateState(invoker); + SettingValueNodeV3::updateState(invoker); m_colorSprite->setColor(to3B(this->getValue())); m_colorSprite->updateOpacity(this->getValue().a / 255.f); diff --git a/loader/src/loader/SettingNodeV3.hpp b/loader/src/loader/SettingNodeV3.hpp index 1d4efdf0..945ef24d 100644 --- a/loader/src/loader/SettingNodeV3.hpp +++ b/loader/src/loader/SettingNodeV3.hpp @@ -163,7 +163,7 @@ protected: } void updateState(CCNode* invoker) override { - SettingNodeV3::updateState(invoker); + SettingValueNodeV3::updateState(invoker); auto enable = this->getSetting()->shouldEnable(); if (this->getSetting()->isInputEnabled()) { m_input->setEnabled(enable); diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index 1c7b8c2e..54c7052d 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -482,7 +482,7 @@ void SettingV3::init(std::string const& key, std::string const& modID, JsonExpec // Keys every setting must have json.needs("type"); for (auto& plat : json.has("platforms").items()) { - m_impl->platforms.push_back(PlatformID::from(plat.template get())); + ranges::push(m_impl->platforms, PlatformID::getCovered(plat.template get())); } } From 72cb957afddf49c51b875003c262bf3fda8d9ccd Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Mon, 9 Sep 2024 22:52:18 +0300 Subject: [PATCH 47/54] add "desktop" and "mobile" to PlatformID::getCovered --- loader/src/utils/PlatformID.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/loader/src/utils/PlatformID.cpp b/loader/src/utils/PlatformID.cpp index 49a746c5..183791c3 100644 --- a/loader/src/utils/PlatformID.cpp +++ b/loader/src/utils/PlatformID.cpp @@ -49,6 +49,9 @@ bool PlatformID::coveredBy(std::string const& str, PlatformID t) { std::vector PlatformID::getCovered(std::string_view str) { switch (hash(str)) { + case hash("desktop"): return { PlatformID::Windows, PlatformID::MacArm, PlatformID::MacIntel }; + case hash("mobile"): return { PlatformID::iOS, PlatformID::Android32, PlatformID::Android64 }; + case hash("win"): return { PlatformID::Windows }; case hash("mac"): return { PlatformID::MacIntel, PlatformID::MacArm }; From 77268271ee97cf1094fc9d554af1c1750267d495 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Mon, 9 Sep 2024 23:53:58 +0300 Subject: [PATCH 48/54] fix esc not showing unsaved changes popup --- loader/src/ui/mods/settings/ModSettingsPopup.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/loader/src/ui/mods/settings/ModSettingsPopup.cpp b/loader/src/ui/mods/settings/ModSettingsPopup.cpp index 5c831fad..45d519a6 100644 --- a/loader/src/ui/mods/settings/ModSettingsPopup.cpp +++ b/loader/src/ui/mods/settings/ModSettingsPopup.cpp @@ -290,19 +290,21 @@ bool ModSettingsPopup::hasUncommitted() const { } void ModSettingsPopup::onClose(CCObject* sender) { - if (sender && this->hasUncommitted()) { + if (this->hasUncommitted()) { createQuickPopup( "Unsaved Changes", "You have unsaved changes! Are you sure you " "want to exit?", "Cancel", "Discard", [this](FLAlertLayer*, bool btn2) { - if (btn2) this->onClose(nullptr); + if (btn2) { + GeodePopup::onClose(nullptr); + } } ); return; } - Popup::onClose(sender); + GeodePopup::onClose(sender); } ModSettingsPopup* ModSettingsPopup::create(Mod* mod) { From 75003b9afa179425d590b65d654f58c37cec854e Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Mon, 9 Sep 2024 23:54:14 +0300 Subject: [PATCH 49/54] fix inputs not working & make slider snaps not optional --- loader/include/Geode/loader/SettingV3.hpp | 7 +++++-- loader/src/loader/SettingNodeV3.cpp | 4 ++-- loader/src/loader/SettingNodeV3.hpp | 9 +++++---- loader/src/loader/SettingV3.cpp | 10 +++++----- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index d4968644..f7796346 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -432,7 +432,7 @@ namespace geode { size_t getArrowStepSize() const; size_t getBigArrowStepSize() const; bool isSliderEnabled() const; - std::optional getSliderSnap() const; + int64_t getSliderSnap() const; bool isInputEnabled() const; SettingNodeV3* createNode(float width) override; @@ -464,7 +464,7 @@ namespace geode { double getArrowStepSize() const; double getBigArrowStepSize() const; bool isSliderEnabled() const; - std::optional getSliderSnap() const; + double getSliderSnap() const; bool isInputEnabled() const; SettingNodeV3* createNode(float width) override; @@ -669,6 +669,9 @@ namespace geode { void onCommit() override { this->getSetting()->setValue(m_impl->currentValue); + // The value may be different, if the current value was an invalid + // value for the setting + this->setValue(this->getSetting()->getValue(), nullptr); } bool hasUncommittedChanges() const override { return m_impl->currentValue != this->getSetting()->getValue(); diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index 06d495ed..2e57c85f 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -311,8 +311,8 @@ bool StringSettingNodeV3::init(std::shared_ptr setting, float w return false; m_input = TextInput::create(setting->getEnumOptions() ? width / 2 - 50 : width / 2, "Text"); - m_input->setCallback([this](auto const&) { - this->markChanged(m_input); + m_input->setCallback([this](auto const& str) { + this->setValue(str, m_input); }); m_input->setScale(.7f); m_input->setString(this->getSetting()->getValue()); diff --git a/loader/src/loader/SettingNodeV3.hpp b/loader/src/loader/SettingNodeV3.hpp index 945ef24d..89569931 100644 --- a/loader/src/loader/SettingNodeV3.hpp +++ b/loader/src/loader/SettingNodeV3.hpp @@ -67,8 +67,9 @@ protected: auto max = this->getSetting()->getMaxValue().value_or(+100); auto range = max - min; auto value = static_cast(num * range + min); - if (auto step = this->getSetting()->getSliderSnap()) { - value = static_cast(round(value / *step) * (*step)); + auto step = this->getSetting()->getSliderSnap(); + if (step > 0) { + value = static_cast(round(value / step) * step); } return value; } @@ -106,8 +107,8 @@ protected: m_input = TextInput::create(this->getButtonMenu()->getContentWidth() - 40, "Num"); m_input->setScale(.7f); - m_input->setCallback([this](auto const&) { - this->markChanged(m_input); + m_input->setCallback([this, setting](auto const& str) { + this->setValue(numFromString(str).unwrapOr(setting->getDefaultValue()), m_input); }); if (!setting->isInputEnabled()) { m_input->getBGSprite()->setVisible(false); diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index 54c7052d..5418e844 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -487,7 +487,7 @@ void SettingV3::init(std::string const& key, std::string const& modID, JsonExpec } void SettingV3::parseNameAndDescription(JsonExpectedValue& json) { - json.needs("name").into(m_impl->name); + json.has("name").into(m_impl->name); json.has("description").into(m_impl->description); } void SettingV3::parseEnableIf(JsonExpectedValue& json) { @@ -711,7 +711,7 @@ public: size_t arrowStepSize = 1; size_t bigArrowStepSize = 5; bool sliderEnabled = true; - std::optional sliderSnap; + int64_t sliderSnap = 1; bool textInputEnabled = true; } controls; }; @@ -797,7 +797,7 @@ size_t IntSettingV3::getBigArrowStepSize() const { bool IntSettingV3::isSliderEnabled() const { return m_impl->controls.sliderEnabled; } -std::optional IntSettingV3::getSliderSnap() const { +int64_t IntSettingV3::getSliderSnap() const { return m_impl->controls.sliderSnap; } bool IntSettingV3::isInputEnabled() const { @@ -842,7 +842,7 @@ public: double arrowStepSize = 1; double bigArrowStepSize = 5; bool sliderEnabled = true; - std::optional sliderSnap; + double sliderSnap = 0.1; bool textInputEnabled = true; } controls; }; @@ -926,7 +926,7 @@ double FloatSettingV3::getBigArrowStepSize() const { bool FloatSettingV3::isSliderEnabled() const { return m_impl->controls.sliderEnabled; } -std::optional FloatSettingV3::getSliderSnap() const { +double FloatSettingV3::getSliderSnap() const { return m_impl->controls.sliderSnap; } bool FloatSettingV3::isInputEnabled() const { From 7592af4a142598bc80a14186535fe9a1a899b5e7 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Mon, 9 Sep 2024 23:57:32 +0300 Subject: [PATCH 50/54] undef commenttype --- loader/src/platform/mac/LoaderImpl.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/loader/src/platform/mac/LoaderImpl.mm b/loader/src/platform/mac/LoaderImpl.mm index 5ef6269a..7f09ca48 100644 --- a/loader/src/platform/mac/LoaderImpl.mm +++ b/loader/src/platform/mac/LoaderImpl.mm @@ -5,6 +5,7 @@ #include #include #include +#undef CommentType #import #include #include From 9bd1da3215d87013a0864c5d6f99b29d143c50e6 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Mon, 9 Sep 2024 23:58:00 +0300 Subject: [PATCH 51/54] wait no include foundation first instead --- loader/src/platform/mac/LoaderImpl.mm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/loader/src/platform/mac/LoaderImpl.mm b/loader/src/platform/mac/LoaderImpl.mm index 7f09ca48..11956e1c 100644 --- a/loader/src/platform/mac/LoaderImpl.mm +++ b/loader/src/platform/mac/LoaderImpl.mm @@ -1,3 +1,4 @@ +#import #include #include #include @@ -5,8 +6,6 @@ #include #include #include -#undef CommentType -#import #include #include From 324cc768fe69fafeb342a2fa9626522a4def9b7b Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Tue, 10 Sep 2024 00:00:55 +0300 Subject: [PATCH 52/54] include apple stuff first --- loader/src/load.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader/src/load.mm b/loader/src/load.mm index 55756495..5a9fd07d 100644 --- a/loader/src/load.mm +++ b/loader/src/load.mm @@ -3,10 +3,10 @@ #ifdef GEODE_IS_MACOS +#include #include #include #include -#include #include bool safeModeCheck() { From de9acbc8eb7eb351895d4f25c482b2409dd3e72a Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Tue, 10 Sep 2024 17:14:37 +0300 Subject: [PATCH 53/54] add scrollbar + different errors for missing settings --- loader/src/loader/SettingNodeV3.cpp | 19 ++++++++++++------- loader/src/loader/SettingNodeV3.hpp | 6 ++++-- loader/src/loader/SettingV3.cpp | 2 +- loader/src/ui/mods/GeodeStyle.cpp | 1 + .../src/ui/mods/settings/ModSettingsPopup.cpp | 7 ++++++- 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index 2e57c85f..36e4fbee 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -582,17 +582,22 @@ Color4BSettingNodeV3* Color4BSettingNodeV3::create(std::shared_ptrsetContentHeight(30); + m_mod = mod; + this->setContentHeight(30); + auto label = CCLabelBMFont::create( - fmt::format("Missing setting '{}'", key).c_str(), + (mod && mod->isEnabled() ? + fmt::format("Missing setting '{}'", key) : + fmt::format("Enable the Mod to Edit '{}'", key) + ).c_str(), "bigFont.fnt" ); - label->setColor("mod-list-errors-found-2"_cc3b); + label->setColor(mod && mod->isEnabled() ? "mod-list-errors-found-2"_cc3b : "mod-list-gray"_cc3b); label->limitLabelWidth(width - m_obContentSize.height, .3f, .1f); this->addChildAtPosition(label, Anchor::Left, ccp(m_obContentSize.height / 2, 0), ccp(0, .5f)); @@ -601,7 +606,7 @@ bool UnresolvedCustomSettingNodeV3::init(std::string_view key, float width) { void UnresolvedCustomSettingNodeV3::updateState(CCNode* invoker) { SettingNodeV3::updateState(invoker); - this->getBG()->setColor("mod-list-errors-found"_cc3b); + this->getBG()->setColor(m_mod && m_mod->isEnabled() ? "mod-list-errors-found-2"_cc3b : "mod-list-gray"_cc3b); this->getBG()->setOpacity(75); } @@ -615,9 +620,9 @@ bool UnresolvedCustomSettingNodeV3::hasNonDefaultValue() const { } void UnresolvedCustomSettingNodeV3::onResetToDefault() {} -UnresolvedCustomSettingNodeV3* UnresolvedCustomSettingNodeV3::create(std::string_view key, float width) { +UnresolvedCustomSettingNodeV3* UnresolvedCustomSettingNodeV3::create(std::string_view key, Mod* mod, float width) { auto ret = new UnresolvedCustomSettingNodeV3(); - if (ret && ret->init(key, width)) { + if (ret && ret->init(key, mod, width)) { ret->autorelease(); return ret; } diff --git a/loader/src/loader/SettingNodeV3.hpp b/loader/src/loader/SettingNodeV3.hpp index 89569931..cde5874f 100644 --- a/loader/src/loader/SettingNodeV3.hpp +++ b/loader/src/loader/SettingNodeV3.hpp @@ -292,14 +292,16 @@ public: class UnresolvedCustomSettingNodeV3 : public SettingNodeV3 { protected: - bool init(std::string_view key, float width); + Mod* m_mod; + + bool init(std::string_view key, Mod* mod, float width); void updateState(CCNode* invoker) override; void onCommit() override; public: - static UnresolvedCustomSettingNodeV3* create(std::string_view key, float width); + static UnresolvedCustomSettingNodeV3* create(std::string_view key, Mod* mod, float width); bool hasUncommittedChanges() const override; bool hasNonDefaultValue() const override; diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index 5418e844..4c8398f1 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -649,7 +649,7 @@ SettingNodeV3* LegacyCustomSettingV3::createNode(float width) { std::static_pointer_cast(shared_from_this()), width ); } - return UnresolvedCustomSettingNodeV3::create(this->getKey(), width); + return UnresolvedCustomSettingNodeV3::create(this->getKey(), this->getMod(), width); } bool LegacyCustomSettingV3::isDefaultValue() const { diff --git a/loader/src/ui/mods/GeodeStyle.cpp b/loader/src/ui/mods/GeodeStyle.cpp index 920244c9..3fcd4f96 100644 --- a/loader/src/ui/mods/GeodeStyle.cpp +++ b/loader/src/ui/mods/GeodeStyle.cpp @@ -18,6 +18,7 @@ $on_mod(Loaded) { ColorProvider::get()->define("mod-list-updates-available-bg-2"_spr, { 45, 110, 222, 255 }); ColorProvider::get()->define("mod-list-errors-found"_spr, { 235, 35, 112, 255 }); ColorProvider::get()->define("mod-list-errors-found-2"_spr, { 245, 27, 27, 255 }); + ColorProvider::get()->define("mod-list-gray"_spr, { 205, 205, 205, 255 }); ColorProvider::get()->define("mod-list-tab-deselected-bg"_spr, { 26, 24, 29, 255 }); ColorProvider::get()->define("mod-list-tab-selected-bg"_spr, { 168, 147, 185, 255 }); ColorProvider::get()->define("mod-list-tab-selected-bg-alt"_spr, { 147, 163, 185, 255 }); diff --git a/loader/src/ui/mods/settings/ModSettingsPopup.cpp b/loader/src/ui/mods/settings/ModSettingsPopup.cpp index 45d519a6..39664b2b 100644 --- a/loader/src/ui/mods/settings/ModSettingsPopup.cpp +++ b/loader/src/ui/mods/settings/ModSettingsPopup.cpp @@ -84,7 +84,7 @@ bool ModSettingsPopup::setup(Mod* mod) { node = sett->createNode(layerSize.width); } else { - node = UnresolvedCustomSettingNodeV3::create(key, layerSize.width); + node = UnresolvedCustomSettingNodeV3::create(key, mod, layerSize.width); } node->setDefaultBGColor(ccc4(0, 0, 0, bg ? 60 : 20)); @@ -112,6 +112,11 @@ bool ModSettingsPopup::setup(Mod* mod) { m_mainLayer->addChildAtPosition(createGeodeListBorders(layerSize), Anchor::Center); + auto scrollBar = Scrollbar::create(m_list); + m_mainLayer->addChildAtPosition( + scrollBar, Anchor::Center, ccp(layerBG->getContentWidth() / 2 + 10, 0) + ); + // buttons m_applyMenu = CCMenu::create(); From 361102eb8f783243ef187bd56b3adb1b9f99159a Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Tue, 10 Sep 2024 18:08:07 +0300 Subject: [PATCH 54/54] allow collapsing titles --- loader/include/Geode/loader/SettingV3.hpp | 2 +- loader/src/loader/SettingNodeV3.cpp | 33 +++++++++++++++++++ loader/src/loader/SettingNodeV3.hpp | 5 +++ .../src/ui/mods/settings/ModSettingsPopup.cpp | 30 +++++++++++++---- 4 files changed, 63 insertions(+), 7 deletions(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index f7796346..3552f5a7 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -653,7 +653,7 @@ namespace geode { m_impl = std::make_shared(); m_impl->currentValue = setting->getValue(); - + return true; } diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index 36e4fbee..10ecfc15 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -222,6 +222,30 @@ bool TitleSettingNodeV3::init(std::shared_ptr setting, float wid if (!SettingNodeV3::init(setting, width)) return false; + auto collapseSprBG = CCSprite::create("square02c_001.png"); + collapseSprBG->setColor(ccc3(25, 25, 25)); + collapseSprBG->setOpacity(105); + auto collapseSpr = CCSprite::createWithSpriteFrameName("edit_downBtn_001.png"); + collapseSpr->setScale(1.9f); + collapseSprBG->addChildAtPosition(collapseSpr, Anchor::Center); + collapseSprBG->setScale(.2f); + + auto uncollapseSprBG = CCSprite::create("square02c_001.png"); + uncollapseSprBG->setColor(ccc3(25, 25, 25)); + uncollapseSprBG->setOpacity(105); + auto uncollapseSpr = CCSprite::createWithSpriteFrameName("edit_delCBtn_001.png"); + uncollapseSpr->setScale(1.5f); + uncollapseSprBG->addChildAtPosition(uncollapseSpr, Anchor::Center); + uncollapseSprBG->setScale(.2f); + + m_collapseToggle = CCMenuItemToggler::create( + collapseSprBG, uncollapseSprBG, + this, menu_selector(TitleSettingNodeV3::onCollapse) + ); + m_collapseToggle->m_notClickable = true; + this->getButtonMenu()->setContentWidth(20); + this->getButtonMenu()->addChildAtPosition(m_collapseToggle, Anchor::Center); + this->getNameLabel()->setFntFile("goldFont.fnt"); this->getNameMenu()->updateLayout(); this->setContentHeight(20); @@ -230,8 +254,17 @@ bool TitleSettingNodeV3::init(std::shared_ptr setting, float wid return true; } +void TitleSettingNodeV3::onCollapse(CCObject* sender) { + m_collapseToggle->toggle(!m_collapseToggle->isToggled()); + // This triggers popup state to update due to SettingNodeValueChangeEventV3 being posted + this->markChanged(static_cast(sender)); +} void TitleSettingNodeV3::onCommit() {} +bool TitleSettingNodeV3::isCollapsed() const { + return m_collapseToggle->isToggled(); +} + bool TitleSettingNodeV3::hasUncommittedChanges() const { return false; } diff --git a/loader/src/loader/SettingNodeV3.hpp b/loader/src/loader/SettingNodeV3.hpp index cde5874f..781db5f2 100644 --- a/loader/src/loader/SettingNodeV3.hpp +++ b/loader/src/loader/SettingNodeV3.hpp @@ -13,12 +13,17 @@ using namespace geode::prelude; class TitleSettingNodeV3 : public SettingNodeV3 { protected: + CCMenuItemToggler* m_collapseToggle; + bool init(std::shared_ptr setting, float width); void onCommit() override; + void onCollapse(CCObject*); public: static TitleSettingNodeV3* create(std::shared_ptr setting, float width); + + bool isCollapsed() const; bool hasUncommittedChanges() const override; bool hasNonDefaultValue() const override; diff --git a/loader/src/ui/mods/settings/ModSettingsPopup.cpp b/loader/src/ui/mods/settings/ModSettingsPopup.cpp index 39664b2b..ff2d9bda 100644 --- a/loader/src/ui/mods/settings/ModSettingsPopup.cpp +++ b/loader/src/ui/mods/settings/ModSettingsPopup.cpp @@ -75,10 +75,7 @@ bool ModSettingsPopup::setup(Mod* mod) { m_list = ScrollLayer::create(layerSize - ccp(0, searchContainer->getContentHeight())); m_list->setTouchEnabled(true); - bool bg = false; for (auto& key : mod->getSettingKeys()) { - bg = !bg; - SettingNodeV3* node; if (auto sett = mod->getSettingV3(key)) { node = sett->createNode(layerSize.width); @@ -86,7 +83,6 @@ bool ModSettingsPopup::setup(Mod* mod) { else { node = UnresolvedCustomSettingNodeV3::create(key, mod, layerSize.width); } - node->setDefaultBGColor(ccc4(0, 0, 0, bg ? 60 : 20)); // auto separator = CCLayerColor::create({ 0, 0, 0, 50 }, layerSize.width, 1.f); // separator->setOpacity(bg ? 100 : 50); @@ -248,11 +244,27 @@ void ModSettingsPopup::updateState(SettingNodeV3* invoker) { m_openConfigDirBtnSpr->setColor(configDirExists ? ccWHITE : ccGRAY); m_openConfigDirBtnSpr->setOpacity(configDirExists ? 255 : 155); - // Update search visibility + all settings with "enable-if" schemes + auto listPosBefore = m_list->m_contentLayer->getPositionY(); + auto listHeightBefore = m_list->m_contentLayer->getContentHeight(); + + // Update search visibility + all settings with "enable-if" schemes + + // checkerboard BG + TitleSettingNodeV3* lastTitle = nullptr; + bool bg = false; for (auto& sett : m_settings) { + if (auto asTitle = typeinfo_cast(sett.data())) { + lastTitle = asTitle; + } sett->removeFromParent(); - if (!hasSearch || matchSearch(sett, search)) { + if ( + // Show if the setting is not a title and is not subject to a collapsed title + !(lastTitle && lastTitle != sett && lastTitle->isCollapsed()) && + // Show if there's no search query or if the setting matches it + (!hasSearch || matchSearch(sett, search)) + ) { m_list->m_contentLayer->addChild(sett); + sett->setDefaultBGColor(ccc4(0, 0, 0, bg ? 60 : 20)); + bg = !bg; } // Avoid infinite loops if (sett == invoker) { @@ -264,6 +276,12 @@ void ModSettingsPopup::updateState(SettingNodeV3* invoker) { } m_list->m_contentLayer->updateLayout(); + // Preserve relative list position if something has been collapsed + m_list->m_contentLayer->setPositionY( + listPosBefore + + (listHeightBefore - m_list->m_contentLayer->getContentHeight()) + ); + m_applyBtnSpr->setCascadeColorEnabled(true); m_applyBtnSpr->setCascadeOpacityEnabled(true); if (this->hasUncommitted()) {