new settings should work now

This commit is contained in:
HJfod 2024-08-19 19:19:29 +03:00
parent 05a4daf794
commit f37c903160
22 changed files with 1282 additions and 495 deletions

View file

@ -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 <vector>
namespace geode {
class SettingV3;
template <class T>
struct HandleToSaved : public T {
Mod* m_mod;
@ -175,6 +178,7 @@ namespace geode {
bool hasSetting(std::string_view const key) const;
std::optional<Setting> getSettingDefinition(std::string_view const key) const;
SettingValue* getSetting(std::string_view const key) const;
std::shared_ptr<SettingV3> 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<SettingValue> value);
/**
* Register a custom setting
*/
Result<> registerCustomSettingV3(std::string_view const key, std::shared_ptr<SettingV3> 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 <class T>
T getSettingValue(std::string_view const key) const {
if (auto sett = this->getSetting(key)) {
return SettingValueSetter<T>::get(sett);
using S = typename SettingTypeForValueType<T>::SettingType;
if (auto sett = typeinfo_pointer_cast<S>(this->getSettingV3(key))) {
return sett->getValue();
}
return T();
}
template <class T>
T setSettingValue(std::string_view const key, T const& value) {
if (auto sett = this->getSetting(key)) {
auto old = this->getSettingValue<T>(key);
SettingValueSetter<T>::set(sett, value);
using S = typename SettingTypeForValueType<T>::SettingType;
if (auto sett = typeinfo_pointer_cast<S>(this->getSettingV3(key))) {
auto old = sett->getValue();
sett->setValue(value);
return old;
}
return T();

View file

@ -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<ColorSetting>;
using ColorAlphaSettingValue = GeodeSettingValue<ColorAlphaSetting>;
// todo: remove in v3
template<class T>
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);
};

View file

@ -5,18 +5,28 @@
#include <cocos2d.h>
// 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<SettingV3> {
private:
class GeodeImpl;
std::shared_ptr<GeodeImpl> 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<Setting> convertToLegacy() const;
[[deprecated("This function will be removed alongside legacy settings in 4.0.0!")]]
virtual std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const;
virtual std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const;
static Result<std::shared_ptr<SettingV3>> parseBuiltin(std::string const& modID, matjson::Value const& json);
static Result<std::shared_ptr<SettingV3>> parseBuiltin(
std::string const& key, std::string const& modID, matjson::Value const& json
);
};
namespace detail {
@ -67,6 +79,32 @@ namespace geode {
std::optional<std::string> getDescription() const;
std::optional<std::string> getEnableIf() const;
};
template <class T, class V = T>
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<Impl> 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<Impl> 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<Setting> convertToLegacy() const override;
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL BoolSettingV3 final : public detail::GeodeSettingBaseV3 {
class GEODE_DLL BoolSettingV3 final : public detail::GeodeSettingBaseValueV3<bool> {
private:
class Impl;
std::shared_ptr<Impl> 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<Setting> convertToLegacy() const override;
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL IntSettingV3 final : public detail::GeodeSettingBaseV3 {
class GEODE_DLL IntSettingV3 final : public detail::GeodeSettingBaseValueV3<int64_t> {
private:
class Impl;
std::shared_ptr<Impl> 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<int64_t> getMinValue() const;
std::optional<int64_t> getMaxValue() const;
@ -150,29 +194,27 @@ namespace geode {
std::optional<int64_t> 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<Setting> convertToLegacy() const override;
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL FloatSettingV3 final : public detail::GeodeSettingBaseV3 {
class GEODE_DLL FloatSettingV3 final : public detail::GeodeSettingBaseValueV3<double> {
private:
class Impl;
std::shared_ptr<Impl> 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<double> getMinValue() const;
std::optional<double> getMaxValue() const;
@ -184,122 +226,111 @@ namespace geode {
std::optional<double> 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<Setting> convertToLegacy() const override;
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL StringSettingV3 final : public detail::GeodeSettingBaseV3 {
class GEODE_DLL StringSettingV3 final : public detail::GeodeSettingBaseValueV3<std::string, std::string_view> {
private:
class Impl;
std::shared_ptr<Impl> 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<std::string> getRegexValidator() const;
std::optional<std::string> getAllowedCharacters() const;
std::optional<std::vector<std::string>> 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<Setting> convertToLegacy() const override;
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL FileSettingV3 final : public detail::GeodeSettingBaseV3 {
class GEODE_DLL FileSettingV3 final : public detail::GeodeSettingBaseValueV3<std::filesystem::path, std::filesystem::path const&> {
private:
class Impl;
std::shared_ptr<Impl> 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<std::vector<utils::file::FilePickOptions::Filter>> 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<Setting> convertToLegacy() const override;
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL Color3BSettingV3 final : public detail::GeodeSettingBaseV3 {
class GEODE_DLL Color3BSettingV3 final : public detail::GeodeSettingBaseValueV3<cocos2d::ccColor3B> {
private:
class Impl;
std::shared_ptr<Impl> 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<Setting> convertToLegacy() const override;
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL Color4BSettingV3 final : public detail::GeodeSettingBaseV3 {
class GEODE_DLL Color4BSettingV3 final : public detail::GeodeSettingBaseValueV3<cocos2d::ccColor4B> {
private:
class Impl;
std::shared_ptr<Impl> 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<Setting> convertToLegacy() const override;
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL SettingNodeV3 : public cocos2d::CCNode {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
protected:
bool init();
bool init(std::shared_ptr<SettingV3> 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<SettingV3> getSetting() const;
};
class GEODE_DLL SettingNodeSizeChangeEventV3 : public Event {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
public:
SettingNodeSizeChangeEventV3(SettingNodeV3* node);
virtual ~SettingNodeSizeChangeEventV3();
SettingNodeV3* getTargetNode() const;
};
class GEODE_DLL SettingNodeValueChangeEventV3 : public Event {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
public:
SettingNodeValueChangeEventV3(bool commit);
virtual ~SettingNodeValueChangeEventV3();
bool isCommit() const;
};
template <class T>
struct SettingTypeForValueType {
static_assert(
!std::is_same_v<T, T>,
"specialize the SettingTypeForValueType class to use Mod::getSettingValue for custom settings"
);
};
template <>
struct SettingTypeForValueType<bool> {
using SettingType = BoolSettingV3;
};
template <>
struct SettingTypeForValueType<int64_t> {
using SettingType = IntSettingV3;
};
template <>
struct SettingTypeForValueType<double> {
using SettingType = FloatSettingV3;
};
template <>
struct SettingTypeForValueType<std::string> {
using SettingType = StringSettingV3;
};
template <>
struct SettingTypeForValueType<std::filesystem::path> {
using SettingType = FileSettingV3;
};
template <>
struct SettingTypeForValueType<cocos2d::ccColor3B> {
using SettingType = Color3BSettingV3;
};
template <>
struct SettingTypeForValueType<cocos2d::ccColor4B> {
using SettingType = Color4BSettingV3;
};
}

View file

@ -8,6 +8,7 @@
#include <cstring>
#include <type_traits>
#include <typeinfo>
#include <memory>
namespace geode {
struct PlatformInfo {
@ -129,4 +130,11 @@ namespace geode::cast {
return nullptr;
}
template<class T, class U>
std::shared_ptr<T> typeinfo_pointer_cast(std::shared_ptr<U> const& r) noexcept {
// https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast
auto p = typeinfo_cast<typename std::shared_ptr<T>::element_type*>(r.get());
return std::shared_ptr<T>(r, p);
}
}

View file

@ -405,7 +405,7 @@ namespace geode {
} {
if (this->hasError()) return *this;
if (auto v = this->template tryGet<T>()) {
if (!predicate(v)) {
if (!predicate(*v)) {
this->setError("json value is not {}", name);
}
}
@ -463,12 +463,12 @@ namespace geode {
Result<> ok();
template <class T>
Result<T> ok(T&& value) {
Result<T> ok(T value) {
auto ok = this->ok();
if (!ok) {
return Err(ok.unwrapErr());
}
return Ok(std::move(value));
return Ok(std::forward<T>(value));
}
};
GEODE_DLL JsonExpectedValue checkJson(matjson::Value const& json, std::string_view rootScopeName);

View file

@ -185,6 +185,7 @@ int geodeEntry(void* platformData) {
log::popNest();
// download and install new loader update in the background
if (Mod::get()->getSettingValue<bool>("auto-check-updates")) {
log::info("Starting loader update check");
updater::checkForLoaderUpdates();

View file

@ -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;
}

View file

@ -156,15 +156,27 @@ bool Mod::hasSetting(std::string_view const key) const {
}
std::optional<Setting> 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<SettingV3> 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<SettingValue> 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<SettingV3> value) {
return m_impl->m_settings->registerCustomSetting(key, value);
}
std::vector<std::string> Mod::getLaunchArgumentNames() const {

View file

@ -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<ModSettingsManager>(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<std::string> 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<SettingValue> 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<std::string> Mod::Impl::getSettingKeys() const {
std::vector<std::string> keys;
for (auto& [key, _] : m_metadata.getSettings()) {
for (auto& [key, _] : m_metadata.getSettingsV3()) {
keys.push_back(key);
}
return keys;
}
std::optional<Setting> 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;
}

View file

@ -51,7 +51,7 @@ namespace geode {
/**
* Setting values. This is behind unique_ptr for interior mutability
*/
std::unique_ptr<ModSettingsManager> m_settings = std::make_unique<ModSettingsManager>();
std::unique_ptr<ModSettingsManager> 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<std::string> getDevelopers() const;
@ -117,9 +117,6 @@ namespace geode {
bool hasSettings() const;
std::vector<std::string> getSettingKeys() const;
bool hasSetting(std::string_view const key) const;
std::optional<Setting> 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<SettingValue> value);
std::string getLaunchArgumentName(std::string_view const name) const;
std::vector<std::string> getLaunchArgumentNames() const;

View file

@ -125,6 +125,8 @@ Result<ModMetadata> 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> 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")) {

View file

@ -1,11 +1,126 @@
#include "ModSettingsManager.hpp"
#include "SettingV3Impl.hpp"
#include <Geode/utils/JsonValidation.hpp>
SettingV3* ModSettingsManager::get(std::string const& id) {}
class ModSettingsManager::Impl final {
public:
struct SettingInfo final {
std::shared_ptr<SettingV3> v3;
// todo: remove in v4
std::shared_ptr<SettingValue> legacy = nullptr;
};
std::string modID;
std::unordered_map<std::string, SettingInfo> 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<Impl>())
{
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<SettingV3> 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<SettingValue>&& 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<UnresolvedCustomSettingV3>(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<SettingV3> 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<SettingValue> 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<Setting> ModSettingsManager::getLegacyDefinition(std::string_view key) {
if (auto s = this->get(key)) {
return s->convertToLegacy();
}
return std::nullopt;
}

View file

@ -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<SettingV3> v3;
std::unique_ptr<SettingValue> legacy;
};
std::unordered_map<std::string, std::unique_ptr<SettingV3>> m_v3;
// todo: remove in v4
std::unordered_map<std::string, std::unique_ptr<SettingValue>> m_legacy;
class Impl;
std::unique_ptr<Impl> 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<SettingV3> ptr);
Result<> registerLegacyCustomSetting(std::string_view key, std::unique_ptr<SettingValue>&& ptr);
std::shared_ptr<SettingV3> get(std::string_view key);
std::shared_ptr<SettingValue> getLegacy(std::string_view key);
std::optional<Setting> getLegacyDefinition(std::string_view key);
};

View file

@ -1,6 +1,7 @@
#include <ui/mods/settings/GeodeSettingNode.hpp>
#include <Geode/loader/Mod.hpp>
#include <Geode/loader/Setting.hpp>
#include <Geode/loader/SettingV3.hpp>
#include <Geode/loader/SettingEvent.hpp>
#include <Geode/loader/SettingNode.hpp>
#include <Geode/utils/general.hpp>
@ -296,29 +297,49 @@ void SettingValue::valueChanged() {
return type_##SettingNode::create(this, width); \
} \
template<> \
typename GeodeSettingValue<type_##Setting>::ValueType \
GeodeSettingValue<type_##Setting>::getValue() const { \
using S = typename SettingTypeForValueType<ValueType>::SettingType; \
if (auto mod = Loader::get()->getInstalledMod(m_modID)) { \
if (auto setting = typeinfo_pointer_cast<S>(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<ValueType>::SettingType; \
if (auto mod = Loader::get()->getInstalledMod(m_modID)) { \
if (auto setting = typeinfo_pointer_cast<S>(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<std::string>(reason.value())); \
} \
return Ok(); \
using S = typename SettingTypeForValueType<ValueType>::SettingType; \
if (auto mod = Loader::get()->getInstalledMod(m_modID)) { \
if (auto setting = typeinfo_pointer_cast<S>(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<type_##SettingValue*>(setting)) { \
return b->getValue(); \
} \
using S = typename SettingTypeForValueType<typename type_##Setting::ValueType>::SettingType; \
if (auto mod = Loader::get()->getInstalledMod(setting->getModID())) { \
if (auto sett = typeinfo_pointer_cast<S>(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<type_##SettingValue*>(setting)) { \
b->setValue(value); \
} \
using S = typename SettingTypeForValueType<typename type_##Setting::ValueType>::SettingType; \
if (auto mod = Loader::get()->getInstalledMod(setting->getModID())) { \
if (auto sett = typeinfo_pointer_cast<S>(mod->getSettingV3(setting->getKey()))) { \
return sett->setValue(value); \
} \
} \
}
#define IMPL_TO_VALID(type_) \

View file

@ -0,0 +1,445 @@
#include "SettingNodeV3.hpp"
#include "SettingV3Impl.hpp"
#include <Geode/loader/SettingNode.hpp>
class SettingNodeSizeChangeEventV3::Impl final {
public:
SettingNodeV3* node;
};
SettingNodeSizeChangeEventV3::SettingNodeSizeChangeEventV3(SettingNodeV3* node)
: m_impl(std::make_shared<Impl>())
{
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<Impl>())
{
m_impl->commit = commit;
}
SettingNodeValueChangeEventV3::~SettingNodeValueChangeEventV3() = default;
bool SettingNodeValueChangeEventV3::isCommit() const {
return m_impl->commit;
}
class SettingNodeV3::Impl final {
public:
std::shared_ptr<SettingV3> setting;
};
bool SettingNodeV3::init(std::shared_ptr<SettingV3> setting, float width) {
if (!CCNode::init())
return false;
m_impl = std::make_shared<Impl>();
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<SettingV3> SettingNodeV3::getSetting() const {
return m_impl->setting;
}
// TitleSettingNodeV3
bool TitleSettingNodeV3::init(std::shared_ptr<TitleSettingV3> 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<TitleSettingV3> 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<TitleSettingV3> TitleSettingNodeV3::getSetting() const {
return std::static_pointer_cast<TitleSettingV3>(SettingNodeV3::getSetting());
}
// BoolSettingNodeV3
bool BoolSettingNodeV3::init(std::shared_ptr<BoolSettingV3> 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<BoolSettingV3> 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<BoolSettingV3> BoolSettingNodeV3::getSetting() const {
return std::static_pointer_cast<BoolSettingV3>(SettingNodeV3::getSetting());
}
// IntSettingNodeV3
bool IntSettingNodeV3::init(std::shared_ptr<IntSettingV3> setting, float width) {
if (!SettingNodeV3::init(setting, width))
return false;
// todo
return true;
}
void IntSettingNodeV3::onCommit() {}
IntSettingNodeV3* IntSettingNodeV3::create(std::shared_ptr<IntSettingV3> 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<IntSettingV3> IntSettingNodeV3::getSetting() const {
return std::static_pointer_cast<IntSettingV3>(SettingNodeV3::getSetting());
}
// FloatSettingNodeV3
bool FloatSettingNodeV3::init(std::shared_ptr<FloatSettingV3> setting, float width) {
if (!SettingNodeV3::init(setting, width))
return false;
// todo
return true;
}
void FloatSettingNodeV3::onCommit() {}
FloatSettingNodeV3* FloatSettingNodeV3::create(std::shared_ptr<FloatSettingV3> 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<FloatSettingV3> FloatSettingNodeV3::getSetting() const {
return std::static_pointer_cast<FloatSettingV3>(SettingNodeV3::getSetting());
}
// StringSettingNodeV3
bool StringSettingNodeV3::init(std::shared_ptr<StringSettingV3> setting, float width) {
if (!SettingNodeV3::init(setting, width))
return false;
// todo
return true;
}
void StringSettingNodeV3::onCommit() {}
StringSettingNodeV3* StringSettingNodeV3::create(std::shared_ptr<StringSettingV3> 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<StringSettingV3> StringSettingNodeV3::getSetting() const {
return std::static_pointer_cast<StringSettingV3>(SettingNodeV3::getSetting());
}
// FileSettingNodeV3
bool FileSettingNodeV3::init(std::shared_ptr<FileSettingV3> setting, float width) {
if (!SettingNodeV3::init(setting, width))
return false;
// todo
return true;
}
void FileSettingNodeV3::onCommit() {}
FileSettingNodeV3* FileSettingNodeV3::create(std::shared_ptr<FileSettingV3> 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<FileSettingV3> FileSettingNodeV3::getSetting() const {
return std::static_pointer_cast<FileSettingV3>(SettingNodeV3::getSetting());
}
// Color3BSettingNodeV3
bool Color3BSettingNodeV3::init(std::shared_ptr<Color3BSettingV3> setting, float width) {
if (!SettingNodeV3::init(setting, width))
return false;
// todo
return true;
}
void Color3BSettingNodeV3::onCommit() {}
Color3BSettingNodeV3* Color3BSettingNodeV3::create(std::shared_ptr<Color3BSettingV3> 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<Color3BSettingV3> Color3BSettingNodeV3::getSetting() const {
return std::static_pointer_cast<Color3BSettingV3>(SettingNodeV3::getSetting());
}
// Color4BSettingNodeV3
bool Color4BSettingNodeV3::init(std::shared_ptr<Color4BSettingV3> setting, float width) {
if (!SettingNodeV3::init(setting, width))
return false;
// todo
return true;
}
void Color4BSettingNodeV3::onCommit() {}
Color4BSettingNodeV3* Color4BSettingNodeV3::create(std::shared_ptr<Color4BSettingV3> 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<Color4BSettingV3> Color4BSettingNodeV3::getSetting() const {
return std::static_pointer_cast<Color4BSettingV3>(SettingNodeV3::getSetting());
}
// UnresolvedCustomSettingNodeV3
bool UnresolvedCustomSettingNodeV3::init(std::shared_ptr<UnresolvedCustomSettingV3> 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<UnresolvedCustomSettingV3> 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<UnresolvedCustomSettingV3> UnresolvedCustomSettingNodeV3::getSetting() const {
return std::static_pointer_cast<UnresolvedCustomSettingV3>(SettingNodeV3::getSetting());
}
// LegacyCustomSettingToV3Node
bool LegacyCustomSettingToV3Node::init(std::shared_ptr<UnresolvedCustomSettingV3> 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<UnresolvedCustomSettingV3> 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();
}

View file

@ -0,0 +1,174 @@
#pragma once
#include <Geode/loader/SettingV3.hpp>
#include <Geode/binding/CCMenuItemToggler.hpp>
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<TitleSettingV3> setting, float width);
void onCommit() override;
public:
static TitleSettingNodeV3* create(std::shared_ptr<TitleSettingV3> setting, float width);
bool hasUncommittedChanges() const override;
bool hasNonDefaultValue() const override;
void resetToDefault() override;
std::shared_ptr<TitleSettingV3> getSetting() const;
};
class BoolSettingNodeV3 : public SettingNodeV3 {
protected:
CCMenuItemToggler* m_toggle;
bool init(std::shared_ptr<BoolSettingV3> setting, float width);
void onCommit() override;
public:
static BoolSettingNodeV3* create(std::shared_ptr<BoolSettingV3> setting, float width);
bool hasUncommittedChanges() const override;
bool hasNonDefaultValue() const override;
void resetToDefault() override;
std::shared_ptr<BoolSettingV3> getSetting() const;
};
class IntSettingNodeV3 : public SettingNodeV3 {
protected:
bool init(std::shared_ptr<IntSettingV3> setting, float width);
void onCommit() override;
public:
static IntSettingNodeV3* create(std::shared_ptr<IntSettingV3> setting, float width);
bool hasUncommittedChanges() const override;
bool hasNonDefaultValue() const override;
void resetToDefault() override;
std::shared_ptr<IntSettingV3> getSetting() const;
};
class FloatSettingNodeV3 : public SettingNodeV3 {
protected:
bool init(std::shared_ptr<FloatSettingV3> setting, float width);
void onCommit() override;
public:
static FloatSettingNodeV3* create(std::shared_ptr<FloatSettingV3> setting, float width);
bool hasUncommittedChanges() const override;
bool hasNonDefaultValue() const override;
void resetToDefault() override;
std::shared_ptr<FloatSettingV3> getSetting() const;
};
class StringSettingNodeV3 : public SettingNodeV3 {
protected:
bool init(std::shared_ptr<StringSettingV3> setting, float width);
void onCommit() override;
public:
static StringSettingNodeV3* create(std::shared_ptr<StringSettingV3> setting, float width);
bool hasUncommittedChanges() const override;
bool hasNonDefaultValue() const override;
void resetToDefault() override;
std::shared_ptr<StringSettingV3> getSetting() const;
};
class FileSettingNodeV3 : public SettingNodeV3 {
protected:
bool init(std::shared_ptr<FileSettingV3> setting, float width);
void onCommit() override;
public:
static FileSettingNodeV3* create(std::shared_ptr<FileSettingV3> setting, float width);
bool hasUncommittedChanges() const override;
bool hasNonDefaultValue() const override;
void resetToDefault() override;
std::shared_ptr<FileSettingV3> getSetting() const;
};
class Color3BSettingNodeV3 : public SettingNodeV3 {
protected:
bool init(std::shared_ptr<Color3BSettingV3> setting, float width);
void onCommit() override;
public:
static Color3BSettingNodeV3* create(std::shared_ptr<Color3BSettingV3> setting, float width);
bool hasUncommittedChanges() const override;
bool hasNonDefaultValue() const override;
void resetToDefault() override;
std::shared_ptr<Color3BSettingV3> getSetting() const;
};
class Color4BSettingNodeV3 : public SettingNodeV3 {
protected:
bool init(std::shared_ptr<Color4BSettingV3> setting, float width);
void onCommit() override;
public:
static Color4BSettingNodeV3* create(std::shared_ptr<Color4BSettingV3> setting, float width);
bool hasUncommittedChanges() const override;
bool hasNonDefaultValue() const override;
void resetToDefault() override;
std::shared_ptr<Color4BSettingV3> getSetting() const;
};
class UnresolvedCustomSettingNodeV3 : public SettingNodeV3 {
protected:
bool init(std::shared_ptr<UnresolvedCustomSettingV3> setting, float width);
void onCommit() override;
public:
static UnresolvedCustomSettingNodeV3* create(std::shared_ptr<UnresolvedCustomSettingV3> setting, float width);
bool hasUncommittedChanges() const override;
bool hasNonDefaultValue() const override;
void resetToDefault() override;
std::shared_ptr<UnresolvedCustomSettingV3> 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<UnresolvedCustomSettingV3> original, float width);
void onCommit() override;
public:
static LegacyCustomSettingToV3Node* create(std::shared_ptr<UnresolvedCustomSettingV3> original, float width);
bool hasUncommittedChanges() const override;
bool hasNonDefaultValue() const override;
void resetToDefault() override;
};

View file

@ -1,5 +1,8 @@
#include <Geode/loader/SettingV3.hpp>
#include <Geode/utils/JsonValidation.hpp>
#include <regex>
#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<GeodeImpl>())
{
m_impl->key = key;
m_impl->modID = modID;
}
SettingV3::SettingV3() : m_impl(std::make_shared<GeodeImpl>()) {}
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<std::shared_ptr<SettingV3>> 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<std::shared_ptr<SettingV3>> 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<std::shared_ptr<SettingV3>> SettingV3::parseBuiltin(std::string const& mo
case hash("rgb"): case hash("color"): ret = std::make_shared<Color3BSettingV3>(); break;
case hash("rgba"): ret = std::make_shared<Color4BSettingV3>(); break;
case hash("path"): case hash("file"): ret = std::make_shared<FileSettingV3>(); break;
case hash("custom"): ret = std::make_shared<UnresolvedCustomSettingV3>(); break;
case hash("title"): ret = std::make_shared<TitleSettingV3>(); break;
default:
case hash("custom"): ret = std::make_shared<UnresolvedCustomSettingV3>(); 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<Setting> SettingV3::convertToLegacy() const {
return std::nullopt;
}
std::optional<std::unique_ptr<SettingValue>> SettingV3::convertToLegacyValue() const {
std::optional<std::shared_ptr<SettingValue>> SettingV3::convertToLegacyValue() const {
return std::nullopt;
}
class geode::detail::GeodeSettingBaseV3::Impl final {
public:
std::string name;
std::optional<std::string> name;
std::optional<std::string> description;
std::optional<std::string> enableIf;
};
std::string geode::detail::GeodeSettingBaseV3::getName() const {
return m_impl->name;
return m_impl->name.value_or(this->getKey());
}
std::optional<std::string> geode::detail::GeodeSettingBaseV3::getDescription() const {
return m_impl->description;
@ -73,9 +78,9 @@ std::optional<std::string> 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<TitleSettingV3>(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<UnresolvedCustomSettingV3>(shared_from_this()), width
);
}
return UnresolvedCustomSettingNodeV3::create(
std::static_pointer_cast<UnresolvedCustomSettingV3>(shared_from_this()), width
);
}
bool UnresolvedCustomSettingV3::isDefaultValue() const {
@ -142,8 +157,8 @@ std::optional<Setting> UnresolvedCustomSettingV3::convertToLegacy() const {
.json = std::make_shared<ModJson>(m_impl->json)
}));
}
std::optional<std::unique_ptr<SettingValue>> UnresolvedCustomSettingV3::convertToLegacyValue() const {
return std::nullopt;
std::optional<std::shared_ptr<SettingValue>> 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<BoolSettingV3>(shared_from_this()), width
);
}
std::optional<Setting> BoolSettingV3::convertToLegacy() const {
@ -205,8 +212,8 @@ std::optional<Setting> BoolSettingV3::convertToLegacy() const {
.defaultValue = this->getDefaultValue(),
}));
}
std::optional<std::unique_ptr<SettingValue>> BoolSettingV3::convertToLegacyValue() const {
return std::make_unique<BoolSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
std::optional<std::shared_ptr<SettingValue>> 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<int64_t> IntSettingV3::getMinValue() const {
return m_impl->minValue;
}
std::optional<int64_t> 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<int64_t>::min()),
m_impl->maxValue.value_or(std::numeric_limits<int64_t>::max())
);
}
int64_t IntSettingV3::getDefaultValue() const {
return m_impl->defaultValue;
}
std::optional<int64_t> IntSettingV3::getMinValue() const {
return m_impl->minValue;
}
std::optional<int64_t> 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<IntSettingV3>(shared_from_this()), width
);
}
std::optional<Setting> IntSettingV3::convertToLegacy() const {
@ -335,8 +341,8 @@ std::optional<Setting> IntSettingV3::convertToLegacy() const {
},
}));
}
std::optional<std::unique_ptr<SettingValue>> IntSettingV3::convertToLegacyValue() const {
return std::make_unique<IntSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
std::optional<std::shared_ptr<SettingValue>> 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<double> FloatSettingV3::getMinValue() const {
return m_impl->minValue;
}
std::optional<double> 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<double>::min()),
m_impl->maxValue.value_or(std::numeric_limits<double>::max())
);
}
double FloatSettingV3::getDefaultValue() const {
return m_impl->defaultValue;
}
std::optional<double> FloatSettingV3::getMinValue() const {
return m_impl->minValue;
}
std::optional<double> 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<FloatSettingV3>(shared_from_this()), width
);
}
std::optional<Setting> FloatSettingV3::convertToLegacy() const {
@ -468,8 +470,8 @@ std::optional<Setting> FloatSettingV3::convertToLegacy() const {
},
}));
}
std::optional<std::unique_ptr<SettingValue>> FloatSettingV3::convertToLegacyValue() const {
return std::make_unique<FloatSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
std::optional<std::shared_ptr<SettingValue>> FloatSettingV3::convertToLegacyValue() const {
return this->convertToLegacy()->createDefaultValue();
}
class StringSettingV3::Impl final {
@ -481,15 +483,26 @@ public:
std::optional<std::vector<std::string>> 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<std::string> StringSettingV3::getRegexValidator() const {
return m_impl->match;
@ -501,7 +514,7 @@ std::optional<std::vector<std::string>> 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<StringSettingV3>(shared_from_this()), width
);
}
std::optional<Setting> StringSettingV3::convertToLegacy() const {
@ -547,8 +555,8 @@ std::optional<Setting> StringSettingV3::convertToLegacy() const {
setting.controls->options = this->getEnumOptions();
return Setting(this->getKey(), this->getModID(), SettingKind(setting));
}
std::optional<std::unique_ptr<SettingValue>> StringSettingV3::convertToLegacyValue() const {
return std::make_unique<StringSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
std::optional<std::shared_ptr<SettingValue>> StringSettingV3::convertToLegacyValue() const {
return this->convertToLegacy()->createDefaultValue();
}
class FileSettingV3::Impl final {
@ -558,17 +566,22 @@ public:
std::optional<std::vector<utils::file::FilePickOptions::Filter>> 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<std::vector<utils::file::FilePickOptions::Filter>> 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<FileSettingV3>(shared_from_this()), width
);
}
std::optional<Setting> FileSettingV3::convertToLegacy() const {
@ -637,8 +645,8 @@ std::optional<Setting> FileSettingV3::convertToLegacy() const {
setting.controls.filters = this->getFilters().value_or(std::vector<utils::file::FilePickOptions::Filter>());
return Setting(this->getKey(), this->getModID(), SettingKind(setting));
}
std::optional<std::unique_ptr<SettingValue>> FileSettingV3::convertToLegacyValue() const {
return std::make_unique<FileSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
std::optional<std::shared_ptr<SettingValue>> 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<Color3BSettingV3>(shared_from_this()), width
);
}
std::optional<Setting> Color3BSettingV3::convertToLegacy() const {
@ -694,8 +700,8 @@ std::optional<Setting> Color3BSettingV3::convertToLegacy() const {
setting.defaultValue = this->getDefaultValue();
return Setting(this->getKey(), this->getModID(), SettingKind(setting));
}
std::optional<std::unique_ptr<SettingValue>> Color3BSettingV3::convertToLegacyValue() const {
return std::make_unique<ColorSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
std::optional<std::shared_ptr<SettingValue>> 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<Color4BSettingV3>(shared_from_this()), width
);
}
std::optional<Setting> Color4BSettingV3::convertToLegacy() const {
@ -751,6 +755,6 @@ std::optional<Setting> Color4BSettingV3::convertToLegacy() const {
setting.defaultValue = this->getDefaultValue();
return Setting(this->getKey(), this->getModID(), SettingKind(setting));
}
std::optional<std::unique_ptr<SettingValue>> Color4BSettingV3::convertToLegacyValue() const {
return std::make_unique<ColorAlphaSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
std::optional<std::shared_ptr<SettingValue>> Color4BSettingV3::convertToLegacyValue() const {
return this->convertToLegacy()->createDefaultValue();
}

View file

@ -0,0 +1,18 @@
#pragma once
#include <Geode/loader/SettingV3.hpp>
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<SettingValue> legacyValue = nullptr;
};

View file

@ -19,8 +19,8 @@ enum class GeodePopupStyle {
template <class... Args>
class GeodePopup : public Popup<Args...> {
protected:
bool init(float width, float height, Args... args, GeodePopupStyle style = GeodePopupStyle::Default) {
const bool geodeTheme = Mod::get()->template getSettingValue<bool>("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<bool>("enable-geode-theme");
const char* bg;
switch (style) {
default:

View file

@ -1,12 +1,10 @@
#include "ModSettingsPopup.hpp"
#include <Geode/binding/ButtonSprite.hpp>
#include <Geode/loader/Mod.hpp>
#include <Geode/loader/Setting.hpp>
#include <Geode/ui/ScrollLayer.hpp>
#include <Geode/utils/cocos.hpp>
#include <Geode/ui/General.hpp>
#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<CCNode*> 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<GLubyte>(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);

View file

@ -1,23 +1,23 @@
#pragma once
#include <Geode/loader/SettingNode.hpp>
#include <Geode/loader/SettingV3.hpp>
#include <Geode/ui/Popup.hpp>
#include <Geode/utils/cocos.hpp>
#include "../GeodeStyle.hpp"
using namespace geode::prelude;
class ModSettingsPopup : public GeodePopup<Mod*>, public SettingNodeDelegate {
class ModSettingsPopup : public GeodePopup<Mod*> {
protected:
Mod* m_mod;
std::vector<SettingNode*> m_settings;
std::vector<SettingNodeV3*> m_settings;
CCMenuItemSpriteExtra* m_applyBtn;
ButtonSprite* m_applyBtnSpr;
void settingValueChanged(SettingNode*) override;
void settingValueCommitted(SettingNode*) override;
EventListener<EventFilter<SettingNodeValueChangeEventV3>> m_changeListener;
bool setup(Mod* mod) override;
void updateState();
void onChangeEvent(SettingNodeValueChangeEventV3* event);
bool hasUncommitted() const;
void onClose(CCObject*) override;
void onApply(CCObject*);

View file

@ -224,7 +224,7 @@ JsonExpectedValue::JsonExpectedValue(Impl* from, matjson::Value& scope, std::str
: m_impl(std::make_unique<Impl>(from, scope, key))
{}
JsonExpectedValue::JsonExpectedValue(matjson::Value const& json, std::string_view rootScopeName)
: m_impl(std::make_unique<Impl>(std::make_shared<matjson::Value>(json, rootScopeName)))
: m_impl(std::make_unique<Impl>(std::make_shared<Impl::Shared>(json, rootScopeName)))
{}
JsonExpectedValue::~JsonExpectedValue() {}
@ -389,7 +389,7 @@ std::vector<JsonExpectedValue> JsonExpectedValue::items() {
if (this->hasError()) {
return std::vector<JsonExpectedValue>();
}
if (!this->assertIs(matjson::Type::Object)) {
if (!this->assertIs(matjson::Type::Array)) {
return std::vector<JsonExpectedValue>();
}
std::vector<JsonExpectedValue> res;