Compare commits

..

3 commits

Author SHA1 Message Date
HJfod
5263483b0a bool setting nodes are so back now 2024-08-21 23:35:31 +03:00
HJfod
40a28eec7b make custom settings be based on custom setting types 2024-08-21 21:41:44 +03:00
HJfod
a5f56cb7cb a bit of docs on modsettingsmanager 2024-08-20 13:18:59 +03:00
14 changed files with 596 additions and 400 deletions

View file

@ -191,11 +191,6 @@ 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
@ -213,6 +208,11 @@ namespace geode {
this->registerCustomSetting(key, std::make_unique<T>(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.

View file

@ -0,0 +1,46 @@
#pragma once
#include <Geode/DefaultInclude.hpp>
#include "SettingV3.hpp"
namespace geode {
class GEODE_DLL ModSettingsManager final {
private:
class Impl;
std::unique_ptr<Impl> 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<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

@ -9,11 +9,11 @@
// 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<SettingV3> {
@ -22,9 +22,9 @@ namespace geode {
std::shared_ptr<GeodeImpl> 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, bool onlyNameAndDesc = false);
void parseSharedProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value, bool onlyNameAndDesc = false);
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::optional<std::string> getName() const;
/**
* Get the description of this setting
*/
std::optional<std::string> getDescription() const;
/**
* Get the "enable-if" scheme for this setting
*/
std::optional<std::string> 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<Setting> convertToLegacy() const;
[[deprecated("This function will be removed alongside legacy settings in 4.0.0!")]]
virtual std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const;
static Result<std::shared_ptr<SettingV3>> parseBuiltin(
std::string const& key, std::string const& modID, matjson::Value const& json
);
};
using SettingGenerator = std::function<Result<std::shared_ptr<SettingV3>>(
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<Impl> m_impl;
Result<> parseSharedBase(JsonExpectedValue& json);
template <class T, class V = T>
class GeodeSettingBaseValueV3 : public SettingV3 {
protected:
Result<> isValidShared() const;
virtual T& getValueMut() const = 0;
template <class T>
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<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;
@ -130,17 +127,13 @@ 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;
private:
class PrivateMarker {};
friend class SettingV3;
public:
TitleSettingV3(PrivateMarker);
std::string getTitle() const;
static Result<std::shared_ptr<TitleSettingV3>> parse(std::string const& key, std::string const& modID, matjson::Value const& json);
bool load(matjson::Value const& json) override;
bool save(matjson::Value& json) const override;
@ -150,23 +143,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<Impl> 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<std::shared_ptr<LegacyCustomSettingV3>> parse(std::string const& key, std::string const& modID, matjson::Value const& json);
std::shared_ptr<SettingValue> getValue() const;
void setValue(std::shared_ptr<SettingValue> value);
bool load(matjson::Value const& json) override;
bool save(matjson::Value& json) const override;
@ -186,7 +181,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 +188,7 @@ namespace geode {
public:
BoolSettingV3(PrivateMarker);
static Result<std::shared_ptr<BoolSettingV3>> 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 +208,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 +215,7 @@ namespace geode {
public:
IntSettingV3(PrivateMarker);
static Result<std::shared_ptr<IntSettingV3>> 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 +246,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 +253,7 @@ namespace geode {
public:
FloatSettingV3(PrivateMarker);
static Result<std::shared_ptr<FloatSettingV3>> 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 +284,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 +291,7 @@ namespace geode {
public:
StringSettingV3(PrivateMarker);
static Result<std::shared_ptr<StringSettingV3>> 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 +315,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 +322,7 @@ namespace geode {
public:
FileSettingV3(PrivateMarker);
static Result<std::shared_ptr<FileSettingV3>> 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 +344,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 +351,7 @@ namespace geode {
public:
Color3BSettingV3(PrivateMarker);
static Result<std::shared_ptr<Color3BSettingV3>> 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 +371,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 +378,7 @@ namespace geode {
public:
Color4BSettingV3(PrivateMarker);
static Result<std::shared_ptr<Color4BSettingV3>> 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;
@ -404,6 +399,8 @@ namespace geode {
protected:
bool init(std::shared_ptr<SettingV3> setting, float width);
virtual void updateState();
/**
* Mark this setting as changed. This updates the UI for committing
* the value
@ -417,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<SettingV3> getSetting() const;

View file

@ -318,6 +318,10 @@ namespace geode {
template <class T>
std::optional<T> tryGet() {
if (this->hasError()) return std::nullopt;
if constexpr (std::is_same_v<T, matjson::Value>) {
return this->getJSONRef();
}
else {
try {
return this->getJSONRef().template as<T>();
}
@ -325,6 +329,7 @@ namespace geode {
catch(std::exception const& e) {
this->setError("invalid json type: {}", e);
}
}
return std::nullopt;
}

View file

@ -64,13 +64,6 @@
}
},
"settings": {
"show-platform-console": {
"type": "bool",
"default": false,
"name": "Show Platform Console",
"description": "Show the native console (if one exists). <cr>This setting is meant for developers</c>",
"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 <ca>Geode-themed color scheme</c>. <cy>This does not affect any other menus!</c>"
},
"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). <cr>This setting is meant for developers</c>",
"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 <ca>Geode-themed color scheme</c>. <cy>This does not affect any other menus!</c>"
}
},
"issues": {

View file

@ -173,8 +173,8 @@ void Mod::registerCustomSetting(std::string_view const key, std::unique_ptr<Sett
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);
Result<> Mod::registerCustomSettingType(std::string_view type, SettingGenerator generator) {
return m_impl->m_settings->registerCustomSettingType(type, generator);
}
std::vector<std::string> Mod::getLaunchArgumentNames() const {

View file

@ -4,7 +4,7 @@
#include "ModPatch.hpp"
#include <Geode/loader/Loader.hpp>
#include <string_view>
#include "ModSettingsManager.hpp"
#include <Geode/loader/ModSettingsManager.hpp>
namespace geode {
class Mod::Impl {

View file

@ -1,63 +1,161 @@
#include "ModSettingsManager.hpp"
#include "SettingV3Impl.hpp"
#include <Geode/loader/ModSettingsManager.hpp>
#include <Geode/utils/JsonValidation.hpp>
#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<std::string, SettingGenerator> 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<SettingGenerator> 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<SettingV3> v3;
std::string type;
matjson::Value json;
std::shared_ptr<SettingV3> v3 = nullptr;
// todo: remove in v4
std::shared_ptr<SettingValue> legacy = nullptr;
};
std::string modID;
std::unordered_map<std::string, SettingInfo> list;
std::unordered_map<std::string, SettingInfo> 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<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);
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<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);
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<SettingValue>&& 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<UnresolvedCustomSettingV3>(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<LegacyCustomSettingV3>(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();
}
@ -65,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);
}
}
@ -79,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;
@ -99,18 +197,22 @@ void ModSettingsManager::save(matjson::Value& json) {
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;
return m_impl->settings.count(id) ? m_impl->settings.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)) {
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);

View file

@ -1,26 +0,0 @@
#pragma once
#include <Geode/DefaultInclude.hpp>
#include <Geode/loader/SettingV3.hpp>
using namespace geode::prelude;
class ModSettingsManager final {
private:
class Impl;
std::unique_ptr<Impl> m_impl;
public:
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,5 +1,4 @@
#include "SettingNodeV3.hpp"
#include "SettingV3Impl.hpp"
#include <Geode/loader/SettingNode.hpp>
class SettingNodeSizeChangeEventV3::Impl final {
@ -37,6 +36,10 @@ bool SettingNodeValueChangeEventV3::isCommit() const {
class SettingNodeV3::Impl final {
public:
std::shared_ptr<SettingV3> setting;
CCLabelBMFont* nameLabel;
CCMenu* nameMenu;
CCMenu* buttonMenu;
CCMenuItemSpriteExtra* resetButton;
};
bool SettingNodeV3::init(std::shared_ptr<SettingV3> setting, float width) {
@ -46,23 +49,93 @@ bool SettingNodeV3::init(std::shared_ptr<SettingV3> setting, float width) {
m_impl = std::make_shared<Impl>();
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<SettingV3> SettingNodeV3::getSetting() const {
return m_impl->setting;
}
@ -73,11 +146,10 @@ bool TitleSettingNodeV3::init(std::shared_ptr<TitleSettingV3> 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;
}
@ -112,16 +184,19 @@ 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, 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;
}
@ -129,6 +204,10 @@ bool BoolSettingNodeV3::init(std::shared_ptr<BoolSettingV3> 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<BoolSettingV3> setting, float width) {
auto ret = new BoolSettingNodeV3();
@ -367,7 +446,7 @@ std::shared_ptr<Color4BSettingV3> Color4BSettingNodeV3::getSetting() const {
// UnresolvedCustomSettingNodeV3
bool UnresolvedCustomSettingNodeV3::init(std::shared_ptr<UnresolvedCustomSettingV3> setting, float width) {
bool UnresolvedCustomSettingNodeV3::init(std::shared_ptr<LegacyCustomSettingV3> setting, float width) {
if (!SettingNodeV3::init(setting, width))
return false;
@ -385,7 +464,7 @@ bool UnresolvedCustomSettingNodeV3::init(std::shared_ptr<UnresolvedCustomSetting
void UnresolvedCustomSettingNodeV3::onCommit() {}
UnresolvedCustomSettingNodeV3* UnresolvedCustomSettingNodeV3::create(std::shared_ptr<UnresolvedCustomSettingV3> setting, float width) {
UnresolvedCustomSettingNodeV3* UnresolvedCustomSettingNodeV3::create(std::shared_ptr<LegacyCustomSettingV3> setting, float width) {
auto ret = new UnresolvedCustomSettingNodeV3();
if (ret && ret->init(setting, width)) {
ret->autorelease();
@ -403,17 +482,17 @@ bool UnresolvedCustomSettingNodeV3::hasNonDefaultValue() const {
}
void UnresolvedCustomSettingNodeV3::resetToDefault() {}
std::shared_ptr<UnresolvedCustomSettingV3> UnresolvedCustomSettingNodeV3::getSetting() const {
return std::static_pointer_cast<UnresolvedCustomSettingV3>(SettingNodeV3::getSetting());
std::shared_ptr<LegacyCustomSettingV3> UnresolvedCustomSettingNodeV3::getSetting() const {
return std::static_pointer_cast<LegacyCustomSettingV3>(SettingNodeV3::getSetting());
}
// LegacyCustomSettingToV3Node
bool LegacyCustomSettingToV3Node::init(std::shared_ptr<UnresolvedCustomSettingV3> original, float width) {
bool LegacyCustomSettingToV3Node::init(std::shared_ptr<LegacyCustomSettingV3> 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 +503,7 @@ void LegacyCustomSettingToV3Node::onCommit() {
m_original->commit();
}
LegacyCustomSettingToV3Node* LegacyCustomSettingToV3Node::create(std::shared_ptr<UnresolvedCustomSettingV3> original, float width) {
LegacyCustomSettingToV3Node* LegacyCustomSettingToV3Node::create(std::shared_ptr<LegacyCustomSettingV3> original, float width) {
auto ret = new LegacyCustomSettingToV3Node();
if (ret && ret->init(original, width)) {
ret->autorelease();

View file

@ -33,6 +33,8 @@ protected:
void onCommit() override;
void onToggle(CCObject*);
public:
static BoolSettingNodeV3* create(std::shared_ptr<BoolSettingV3> setting, float width);
@ -141,18 +143,18 @@ public:
class UnresolvedCustomSettingNodeV3 : public SettingNodeV3 {
protected:
bool init(std::shared_ptr<UnresolvedCustomSettingV3> setting, float width);
bool init(std::shared_ptr<LegacyCustomSettingV3> setting, float width);
void onCommit() override;
public:
static UnresolvedCustomSettingNodeV3* create(std::shared_ptr<UnresolvedCustomSettingV3> setting, float width);
static UnresolvedCustomSettingNodeV3* create(std::shared_ptr<LegacyCustomSettingV3> setting, float width);
bool hasUncommittedChanges() const override;
bool hasNonDefaultValue() const override;
void resetToDefault() override;
std::shared_ptr<UnresolvedCustomSettingV3> getSetting() const;
std::shared_ptr<LegacyCustomSettingV3> getSetting() const;
};
// If these classes do get exposed in headers, this SHOULD NOT BE EXPOSED!!!!!! DO NOT DO THAT!!!!
@ -161,12 +163,12 @@ class LegacyCustomSettingToV3Node : public SettingNodeV3 {
protected:
SettingNode* m_original;
bool init(std::shared_ptr<UnresolvedCustomSettingV3> original, float width);
bool init(std::shared_ptr<LegacyCustomSettingV3> original, float width);
void onCommit() override;
public:
static LegacyCustomSettingToV3Node* create(std::shared_ptr<UnresolvedCustomSettingV3> original, float width);
static LegacyCustomSettingToV3Node* create(std::shared_ptr<LegacyCustomSettingV3> original, float width);
bool hasUncommittedChanges() const override;
bool hasNonDefaultValue() const override;

View file

@ -1,7 +1,6 @@
#include <Geode/loader/SettingV3.hpp>
#include <Geode/utils/JsonValidation.hpp>
#include <regex>
#include "SettingV3Impl.hpp"
#include "SettingNodeV3.hpp"
using namespace geode::prelude;
@ -10,11 +9,34 @@ class SettingV3::GeodeImpl {
public:
std::string modID;
std::string key;
std::optional<std::string> name;
std::optional<std::string> description;
std::optional<std::string> enableIf;
bool requiresRestart = false;
};
SettingV3::SettingV3() : m_impl(std::make_shared<GeodeImpl>()) {}
SettingV3::~SettingV3() = default;
SettingV3::SettingV3() : m_impl(std::make_shared<GeodeImpl>()) {}
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, onlyNameAndDesc);
return json.ok();
}
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);
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;
m_impl->modID = modID;
}
std::string SettingV3::getKey() const {
return m_impl->key;
@ -22,37 +44,22 @@ std::string SettingV3::getKey() const {
std::string SettingV3::getModID() const {
return m_impl->modID;
}
std::optional<std::string> SettingV3::getName() const {
return m_impl->name;
}
std::optional<std::string> SettingV3::getDescription() const {
return m_impl->description;
}
std::optional<std::string> 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<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);
std::shared_ptr<SettingV3> ret;
switch (hash(type)) {
case hash("bool"): ret = std::make_shared<BoolSettingV3>(BoolSettingV3::PrivateMarker()); break;
case hash("int"): ret = std::make_shared<IntSettingV3>(IntSettingV3::PrivateMarker()); break;
case hash("float"): ret = std::make_shared<FloatSettingV3>(FloatSettingV3::PrivateMarker()); break;
case hash("string"): ret = std::make_shared<StringSettingV3>(StringSettingV3::PrivateMarker()); break;
case hash("rgb"): case hash("color"): ret = std::make_shared<Color3BSettingV3>(Color3BSettingV3::PrivateMarker()); break;
case hash("rgba"): ret = std::make_shared<Color4BSettingV3>(Color4BSettingV3::PrivateMarker()); break;
case hash("path"): case hash("file"): ret = std::make_shared<FileSettingV3>(FileSettingV3::PrivateMarker()); break;
case hash("title"): ret = std::make_shared<TitleSettingV3>(TitleSettingV3::PrivateMarker()); break;
default:
case hash("custom"): ret = std::make_shared<UnresolvedCustomSettingV3>(UnresolvedCustomSettingV3::PrivateMarker()); break;
}
GEODE_UNWRAP(ret->parse(key, modID, json));
return root.ok(std::move(ret));
}
std::optional<Setting> SettingV3::convertToLegacy() const {
return std::nullopt;
}
@ -60,58 +67,20 @@ std::optional<std::shared_ptr<SettingValue>> SettingV3::convertToLegacyValue() c
return std::nullopt;
}
class geode::detail::GeodeSettingBaseV3::Impl final {
public:
std::optional<std::string> name;
std::optional<std::string> description;
std::optional<std::string> enableIf;
};
geode::detail::GeodeSettingBaseV3::GeodeSettingBaseV3() : m_impl(std::make_shared<Impl>()) {}
std::string geode::detail::GeodeSettingBaseV3::getName() const {
return m_impl->name.value_or(this->getKey());
}
std::optional<std::string> geode::detail::GeodeSettingBaseV3::getDescription() const {
return m_impl->description;
}
std::optional<std::string> 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;
};
TitleSettingV3::TitleSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
std::string TitleSettingV3::getTitle() const {
return m_impl->title;
Result<std::shared_ptr<TitleSettingV3>> TitleSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<TitleSettingV3>(PrivateMarker());
auto root = checkJson(json, "TitleSettingV3");
ret->parseSharedProperties(key, modID, root, true);
root.checkUnknownKeys();
return root.ok(ret);
}
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 +97,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<SettingValue> legacyValue = nullptr;
};
UnresolvedCustomSettingV3::UnresolvedCustomSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
LegacyCustomSettingV3::LegacyCustomSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
Result<> UnresolvedCustomSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) {
m_impl->json = json;
return Ok();
Result<std::shared_ptr<LegacyCustomSettingV3>> LegacyCustomSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<LegacyCustomSettingV3>(PrivateMarker());
ret->init(key, modID);
ret->m_impl->json = json;
return Ok(ret);
}
bool UnresolvedCustomSettingV3::load(matjson::Value const& json) {
std::shared_ptr<SettingValue> LegacyCustomSettingV3::getValue() const {
return m_impl->legacyValue;
}
void LegacyCustomSettingV3::setValue(std::shared_ptr<SettingValue> 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<UnresolvedCustomSettingV3>(shared_from_this()), width
std::static_pointer_cast<LegacyCustomSettingV3>(shared_from_this()), width
);
}
return UnresolvedCustomSettingNodeV3::create(
std::static_pointer_cast<UnresolvedCustomSettingV3>(shared_from_this()), width
std::static_pointer_cast<LegacyCustomSettingV3>(shared_from_this()), width
);
}
bool UnresolvedCustomSettingV3::isDefaultValue() const {
bool LegacyCustomSettingV3::isDefaultValue() const {
return true;
}
void UnresolvedCustomSettingV3::reset() {}
void LegacyCustomSettingV3::reset() {}
std::optional<Setting> UnresolvedCustomSettingV3::convertToLegacy() const {
std::optional<Setting> LegacyCustomSettingV3::convertToLegacy() const {
return Setting(this->getKey(), this->getModID(), SettingKind(CustomSetting {
.json = std::make_shared<ModJson>(m_impl->json)
}));
}
std::optional<std::shared_ptr<SettingValue>> UnresolvedCustomSettingV3::convertToLegacyValue() const {
std::optional<std::shared_ptr<SettingValue>> LegacyCustomSettingV3::convertToLegacyValue() const {
return m_impl->legacyValue ? std::optional(m_impl->legacyValue) : std::nullopt;
}
@ -179,6 +158,18 @@ public:
BoolSettingV3::BoolSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
Result<std::shared_ptr<BoolSettingV3>> BoolSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<BoolSettingV3>(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 +177,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 +225,35 @@ public:
} controls;
};
Result<std::shared_ptr<IntSettingV3>> IntSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<IntSettingV3>(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<bool>()) {
ret->m_impl->controls.arrowStepSize = 0;
}
controls.has("big-arrow-step").into(ret->m_impl->controls.bigArrowStepSize);
if (!controls.has("big-arrows").template get<bool>()) {
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<Impl>()) {}
int64_t& IntSettingV3::getValueMut() const {
@ -253,7 +263,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 +301,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<bool>()) {
m_impl->controls.arrowStepSize = 0;
}
controls.has("big-arrow-step").into(m_impl->controls.bigArrowStepSize);
if (!controls.has("big-arrows").template get<bool>()) {
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 +359,35 @@ public:
FloatSettingV3::FloatSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
Result<std::shared_ptr<FloatSettingV3>> FloatSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<FloatSettingV3>(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<bool>()) {
ret->m_impl->controls.arrowStepSize = 0;
}
controls.has("big-arrow-step").into(ret->m_impl->controls.bigArrowStepSize);
if (!controls.has("big-arrows").template get<bool>()) {
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 +395,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 +433,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<bool>()) {
m_impl->controls.arrowStepSize = 0;
}
controls.has("big-arrow-step").into(m_impl->controls.bigArrowStepSize);
if (!controls.has("big-arrows").template get<bool>()) {
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 +483,22 @@ public:
StringSettingV3::StringSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
Result<std::shared_ptr<StringSettingV3>> StringSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<StringSettingV3>(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 +506,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 +529,6 @@ std::optional<std::vector<std::string>> 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 +569,18 @@ public:
FileSettingV3::FileSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
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<std::shared_ptr<FileSettingV3>> FileSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<FileSettingV3>(PrivateMarker());
std::optional<std::vector<utils::file::FilePickOptions::Filter>> 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 +591,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<file::FilePickOptions::Filter>();
@ -627,13 +602,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<std::vector<utils::file::FilePickOptions::Filter>> 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 +661,18 @@ public:
Color3BSettingV3::Color3BSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
Result<std::shared_ptr<Color3BSettingV3>> Color3BSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<Color3BSettingV3>(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 +680,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<ccColor3B>()) {
m_impl->value = json.template as<ccColor3B>();
@ -727,6 +719,18 @@ public:
Color4BSettingV3::Color4BSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
Result<std::shared_ptr<Color4BSettingV3>> Color4BSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<Color4BSettingV3>(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 +738,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<ccColor4B>()) {
m_impl->value = json.template as<ccColor4B>();

View file

@ -1,18 +0,0 @@
#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

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