mirror of
https://github.com/geode-sdk/geode.git
synced 2025-03-22 02:45:49 -04:00
update json validation to no longer use exceptions and be enjoyable to
work with + preliminary settings & UI work + various other fixes and changes
This commit is contained in:
parent
a30f5063e7
commit
0ab32b3e25
32 changed files with 1221 additions and 2012 deletions
loader
|
@ -75,8 +75,8 @@ if (APPLE)
|
|||
)
|
||||
elseif(WIN32)
|
||||
add_custom_command(
|
||||
COMMAND ${CMAKE_COMMAND} -E rename "$<TARGET_FILE:${PROJECT_NAME}>" "${GEODE_BIN_PATH}/nightly/$<TARGET_FILE_NAME:${PROJECT_NAME}>"
|
||||
COMMAND ${CMAKE_COMMAND} -E rename "$<TARGET_LINKER_FILE:${PROJECT_NAME}>" "${GEODE_BIN_PATH}/nightly/$<TARGET_LINKER_FILE_NAME:${PROJECT_NAME}>"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:${PROJECT_NAME}>" "${GEODE_BIN_PATH}/nightly/$<TARGET_FILE_NAME:${PROJECT_NAME}>"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_LINKER_FILE:${PROJECT_NAME}>" "${GEODE_BIN_PATH}/nightly/$<TARGET_LINKER_FILE_NAME:${PROJECT_NAME}>"
|
||||
DEPENDS $<TARGET_FILE:${PROJECT_NAME}>
|
||||
VERBATIM
|
||||
TARGET ${PROJECT_NAME}
|
||||
|
@ -151,9 +151,7 @@ endif()
|
|||
|
||||
# Build test mods if needed
|
||||
if(NOT GEODE_DONT_BUILD_TEST_MODS)
|
||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
# Build index hashing algorithm test program
|
||||
|
|
|
@ -5,4 +5,5 @@
|
|||
#include "loader/Log.hpp"
|
||||
#include "loader/Mod.hpp"
|
||||
#include "loader/Loader.hpp"
|
||||
#include "loader/Interface.hpp"
|
||||
#include "loader/Interface.hpp"
|
||||
#include "loader/Setting.hpp"
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include "ui/InputNode.hpp"
|
||||
#include "ui/ListView.hpp"
|
||||
#include "ui/MDTextArea.hpp"
|
||||
#include "ui/MenuInputNode.hpp"
|
||||
#include "ui/Notification.hpp"
|
||||
#include "ui/Popup.hpp"
|
||||
#include "ui/SceneManager.hpp"
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include <type_traits>
|
||||
#include <cocos2d.h>
|
||||
|
||||
|
||||
class InternalLoader;
|
||||
class InternalMod;
|
||||
|
||||
|
@ -25,6 +24,7 @@ namespace geode {
|
|||
class Loader;
|
||||
class Log;
|
||||
class Mod;
|
||||
class Setting;
|
||||
|
||||
class Unknown;
|
||||
using unknownmemfn_t = void(Unknown::*)();
|
||||
|
@ -123,7 +123,10 @@ namespace geode {
|
|||
* Default data store values
|
||||
*/
|
||||
nlohmann::json m_defaultDataStore;
|
||||
|
||||
/**
|
||||
* Mod settings
|
||||
*/
|
||||
std::unordered_map<std::string, std::shared_ptr<Setting>> m_settings;
|
||||
/**
|
||||
* Whether the mod can be disabled or not
|
||||
*/
|
||||
|
@ -317,6 +320,9 @@ namespace geode {
|
|||
ghc::filesystem::path getTempDir() const;
|
||||
ghc::filesystem::path getBinaryPath() const;
|
||||
|
||||
bool hasSettings() const;
|
||||
decltype(ModInfo::m_settings) getSettings() const;
|
||||
|
||||
/**
|
||||
* Get the mod container stored in the Interface
|
||||
* @returns nullptr if Interface is not initialized,
|
||||
|
|
227
loader/include/Geode/loader/Setting.hpp
Normal file
227
loader/include/Geode/loader/Setting.hpp
Normal file
|
@ -0,0 +1,227 @@
|
|||
#pragma once
|
||||
|
||||
#include <Geode/DefaultInclude.hpp>
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
#include "../utils/json.hpp"
|
||||
#include "../utils/Result.hpp"
|
||||
#include "../utils/JsonValidation.hpp"
|
||||
|
||||
namespace geode {
|
||||
class SettingNode;
|
||||
|
||||
enum class SettingType {
|
||||
Bool,
|
||||
Int,
|
||||
Float,
|
||||
String,
|
||||
File,
|
||||
Color,
|
||||
ColorAlpha,
|
||||
User,
|
||||
};
|
||||
|
||||
class GEODE_DLL Setting {
|
||||
protected:
|
||||
std::string m_key;
|
||||
|
||||
static Result<std::shared_ptr<Setting>> parse(
|
||||
std::string const& type,
|
||||
std::string const& key,
|
||||
JsonMaybeObject& obj
|
||||
);
|
||||
|
||||
public:
|
||||
virtual ~Setting() = default;
|
||||
|
||||
// Load from mod.json
|
||||
static Result<std::shared_ptr<Setting>> parse(
|
||||
std::string const& key,
|
||||
nlohmann::json const& json
|
||||
);
|
||||
// Load value from saved settings
|
||||
virtual bool load(nlohmann::json const& json) = 0;
|
||||
// Save setting value
|
||||
virtual bool save(nlohmann::json& json) const = 0;
|
||||
|
||||
virtual SettingNode* createNode(float width) = 0;
|
||||
|
||||
std::string getKey() const;
|
||||
virtual SettingType getType() const = 0;
|
||||
};
|
||||
|
||||
namespace {
|
||||
#define GEODE_PARSE_SETTING_IMPL(func) \
|
||||
if constexpr (requires(JsonMaybeObject& obj) {\
|
||||
{res->func(obj)} -> std::same_as<Result<>>;\
|
||||
}) {\
|
||||
auto r = res->func(obj);\
|
||||
if (!r) return Err(r.error());\
|
||||
}
|
||||
|
||||
#define GEODE_CONSTRAIN_SETTING_IMPL(func) \
|
||||
if constexpr (requires(ValueType& value) {\
|
||||
this->func(value);\
|
||||
}) {\
|
||||
this->func(m_value);\
|
||||
}
|
||||
|
||||
template<class Class, class ValueType, SettingType Type>
|
||||
class GeodeSetting : public Setting {
|
||||
protected:
|
||||
ValueType m_value;
|
||||
std::string m_name;
|
||||
std::string m_description;
|
||||
|
||||
friend class Setting;
|
||||
|
||||
static Result<std::shared_ptr<Class>> parse(
|
||||
std::string const& key,
|
||||
JsonMaybeObject& obj
|
||||
) {
|
||||
auto res = std::make_shared<Class>();
|
||||
|
||||
res->m_key = key;
|
||||
obj.needs("default").into(res->m_value);
|
||||
obj.has("name").into(res->m_name);
|
||||
obj.has("description").into(res->m_description);
|
||||
GEODE_PARSE_SETTING_IMPL(Class::parseMinMax);
|
||||
GEODE_PARSE_SETTING_IMPL(Class::parseOneOf);
|
||||
res->setValue(res->m_value);
|
||||
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
public:
|
||||
using value_t = ValueType;
|
||||
|
||||
std::string getName() const {
|
||||
return m_name;
|
||||
}
|
||||
|
||||
std::string getDescription() const {
|
||||
return m_description;
|
||||
}
|
||||
|
||||
ValueType getValue() const {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void setValue(ValueType const& value) {
|
||||
m_value = value;
|
||||
GEODE_CONSTRAIN_SETTING_IMPL(Class::constrainMinMax);
|
||||
GEODE_CONSTRAIN_SETTING_IMPL(Class::constrainOneOf);
|
||||
}
|
||||
|
||||
bool load(nlohmann::json const& json) override {
|
||||
m_value = json["value"];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool save(nlohmann::json& json) const override {
|
||||
json["value"] = m_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
SettingType getType() const override {
|
||||
return Type;
|
||||
}
|
||||
};
|
||||
|
||||
template<class ValueType>
|
||||
class IMinMax {
|
||||
protected:
|
||||
std::optional<ValueType> m_min = std::nullopt;
|
||||
std::optional<ValueType> m_max = std::nullopt;
|
||||
|
||||
public:
|
||||
void constrainMinMax(ValueType& value) {
|
||||
if (m_min && value < m_min.value()) {
|
||||
value = m_min.value();
|
||||
}
|
||||
if (m_max && value > m_max.value()) {
|
||||
value = m_max.value();
|
||||
}
|
||||
}
|
||||
|
||||
Result<> parseMinMax(JsonMaybeObject& obj) {
|
||||
obj.has("min").intoAs<ValueType>(m_min);
|
||||
obj.has("max").intoAs<ValueType>(m_max);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
std::optional<ValueType> getMin() const {
|
||||
return m_min;
|
||||
}
|
||||
std::optional<ValueType> getMax() const {
|
||||
return m_max;
|
||||
}
|
||||
};
|
||||
|
||||
template<class ValueType>
|
||||
class IOneOf {
|
||||
protected:
|
||||
std::optional<std::unordered_set<ValueType>> m_oneOf = std::nullopt;
|
||||
|
||||
public:
|
||||
void constrainOneOf(ValueType& value) {
|
||||
if (m_oneOf && !m_oneOf.value().count(value)) {
|
||||
if (m_oneOf.value().size()) {
|
||||
value = m_oneOf.value()[0];
|
||||
} else {
|
||||
value = ValueType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result<> parseOneOf(JsonMaybeObject& obj) {
|
||||
std::unordered_set<ValueType> oneOf {};
|
||||
for (auto& item : obj.has("one-of").iterate()) {
|
||||
oneOf.insert(item.get<ValueType>());
|
||||
}
|
||||
if (oneOf.size()) {
|
||||
m_oneOf = oneOf;
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
std::optional<std::unordered_set<ValueType>> getOneOf() const {
|
||||
return m_oneOf;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class GEODE_DLL BoolSetting :
|
||||
public GeodeSetting<BoolSetting, bool, SettingType::Bool>,
|
||||
public std::enable_shared_from_this<BoolSetting>
|
||||
{
|
||||
public:
|
||||
SettingNode* createNode(float width) override;
|
||||
};
|
||||
|
||||
class GEODE_DLL IntSetting :
|
||||
public GeodeSetting<IntSetting, int64_t, SettingType::Int>,
|
||||
public IMinMax<int64_t>,
|
||||
public std::enable_shared_from_this<IntSetting>
|
||||
{
|
||||
public:
|
||||
SettingNode* createNode(float width) override;
|
||||
};
|
||||
|
||||
class GEODE_DLL FloatSetting :
|
||||
public GeodeSetting<FloatSetting, double, SettingType::Float>,
|
||||
public IMinMax<double>,
|
||||
public std::enable_shared_from_this<FloatSetting>
|
||||
{
|
||||
public:
|
||||
SettingNode* createNode(float width) override;
|
||||
};
|
||||
|
||||
class GEODE_DLL StringSetting :
|
||||
public GeodeSetting<StringSetting, std::string, SettingType::String>,
|
||||
public std::enable_shared_from_this<StringSetting>
|
||||
{
|
||||
public:
|
||||
SettingNode* createNode(float width) override;
|
||||
};
|
||||
}
|
145
loader/include/Geode/loader/SettingNode.hpp
Normal file
145
loader/include/Geode/loader/SettingNode.hpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
#pragma once
|
||||
|
||||
#include "Setting.hpp"
|
||||
#include <Geode/Bindings.hpp>
|
||||
#include <Geode/ui/InputNode.hpp>
|
||||
|
||||
namespace geode {
|
||||
class SettingNode : public cocos2d::CCNode {
|
||||
protected:
|
||||
std::shared_ptr<Setting> m_setting;
|
||||
|
||||
bool init(std::shared_ptr<Setting> setting);
|
||||
|
||||
public:
|
||||
virtual void commit() = 0;
|
||||
virtual bool hasUncommittedChanges() = 0;
|
||||
};
|
||||
|
||||
namespace {
|
||||
template<class N, class T>
|
||||
class GeodeSettingNode : public SettingNode {
|
||||
public:
|
||||
using value_t = typename T::value_t;
|
||||
|
||||
protected:
|
||||
value_t m_uncommittedValue;
|
||||
cocos2d::CCMenu* m_menu;
|
||||
cocos2d::CCLabelBMFont* m_nameLabel;
|
||||
cocos2d::CCLabelBMFont* m_editedIndicator;
|
||||
|
||||
bool init(std::shared_ptr<T> setting, float width) {
|
||||
if (!SettingNode::init(std::static_pointer_cast<Setting>(setting)))
|
||||
return false;
|
||||
|
||||
this->setContentSize({ width, 40.f });
|
||||
|
||||
m_uncommittedValue = setting->getValue();
|
||||
|
||||
m_nameLabel = cocos2d::CCLabelBMFont::create(
|
||||
(setting->getName().size() ?
|
||||
setting->getName() :
|
||||
setting->getKey()).c_str(),
|
||||
"bigFont.fnt"
|
||||
);
|
||||
m_nameLabel->setAnchorPoint({ .0f, .5f });
|
||||
m_nameLabel->limitLabelWidth(width / 2 - 50.f, .5f, .1f);
|
||||
m_nameLabel->setPosition({
|
||||
m_obContentSize.height / 2,
|
||||
m_obContentSize.height / 2
|
||||
});
|
||||
this->addChild(m_nameLabel);
|
||||
|
||||
m_editedIndicator = cocos2d::CCLabelBMFont::create("*", "bigFont.fnt");
|
||||
m_editedIndicator->setColor({ 255, 120, 80 });
|
||||
m_editedIndicator->setPosition(
|
||||
m_obContentSize.height / 2 +
|
||||
m_nameLabel->getScaledContentSize().width + 10.f,
|
||||
m_obContentSize.height / 2
|
||||
);
|
||||
m_editedIndicator->setVisible(false);
|
||||
this->addChild(m_editedIndicator);
|
||||
|
||||
m_menu = cocos2d::CCMenu::create();
|
||||
m_menu->setPosition({
|
||||
m_obContentSize.width - m_obContentSize.height / 2,
|
||||
m_obContentSize.height / 2
|
||||
});
|
||||
this->addChild(m_menu);
|
||||
|
||||
cocos2d::CCDirector::sharedDirector()->getTouchDispatcher()->incrementForcePrio(2);
|
||||
m_menu->registerWithTouchDispatcher();
|
||||
|
||||
if (!this->setup(setting, width))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool setup(std::shared_ptr<T> setting, float width) = 0;
|
||||
|
||||
void valueChanged() {
|
||||
m_editedIndicator->setVisible(
|
||||
this->hasUncommittedChanges()
|
||||
);
|
||||
}
|
||||
|
||||
public:
|
||||
static N* create(
|
||||
std::shared_ptr<T> setting,
|
||||
float width
|
||||
) {
|
||||
auto ret = new N();
|
||||
if (ret && ret->init(setting, width)) {
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
CC_SAFE_DELETE(ret);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void commit() override {
|
||||
std::static_pointer_cast<T>(m_setting)->setValue(m_uncommittedValue);
|
||||
this->valueChanged();
|
||||
}
|
||||
|
||||
bool hasUncommittedChanges() override {
|
||||
return m_uncommittedValue !=
|
||||
std::static_pointer_cast<T>(m_setting)->getValue();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class BoolSettingNode :
|
||||
public GeodeSettingNode<BoolSettingNode, BoolSetting>
|
||||
{
|
||||
protected:
|
||||
bool setup(std::shared_ptr<BoolSetting> setting, float width) override;
|
||||
};
|
||||
|
||||
class IntSettingNode :
|
||||
public GeodeSettingNode<IntSettingNode, IntSetting>
|
||||
{
|
||||
protected:
|
||||
bool setup(std::shared_ptr<IntSetting> setting, float width) override;
|
||||
};
|
||||
|
||||
class FloatSettingNode :
|
||||
public GeodeSettingNode<FloatSettingNode, FloatSetting>
|
||||
{
|
||||
protected:
|
||||
bool setup(std::shared_ptr<FloatSetting> setting, float width) override;
|
||||
};
|
||||
|
||||
class StringSettingNode :
|
||||
public GeodeSettingNode<StringSettingNode, StringSetting>
|
||||
{
|
||||
protected:
|
||||
InputNode* m_input;
|
||||
|
||||
void updateLabel();
|
||||
|
||||
bool setup(std::shared_ptr<StringSetting> setting, float width) override;
|
||||
};
|
||||
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
#include <Geode/Bindings.hpp>
|
||||
|
||||
namespace geode {
|
||||
class GEODE_DLL InputNode : public cocos2d::CCNode {
|
||||
class GEODE_DLL InputNode : public cocos2d::CCMenuItem {
|
||||
protected:
|
||||
cocos2d::extension::CCScale9Sprite* m_bgSprite;
|
||||
CCTextInputNode* m_input;
|
||||
|
@ -40,12 +40,14 @@ namespace geode {
|
|||
const char* placeholder
|
||||
);
|
||||
|
||||
CCTextInputNode* getInputNode() const;
|
||||
cocos2d::extension::CCScale9Sprite* getBGSprite() const;
|
||||
void activate() override;
|
||||
|
||||
CCTextInputNode* getInput() const;
|
||||
cocos2d::extension::CCScale9Sprite* getBG() const;
|
||||
|
||||
void setEnabled(bool);
|
||||
|
||||
void setString(const char*);
|
||||
void setString(std::string const&);
|
||||
const char* getString();
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <Geode/Bindings.hpp>
|
||||
|
||||
namespace geode {
|
||||
/**
|
||||
* Simple wrapper around CCTextInputNode that
|
||||
* turns it into a CCMenuItem that can be used
|
||||
* in a CCMenu. Can help with touch dispatcher
|
||||
* issues. Also comes with a background sprite
|
||||
*/
|
||||
class GEODE_DLL MenuInputNode : public cocos2d::CCMenuItem {
|
||||
protected:
|
||||
cocos2d::extension::CCScale9Sprite* m_bgSprite = nullptr;
|
||||
CCTextInputNode* m_input;
|
||||
|
||||
bool init(
|
||||
float width,
|
||||
float height,
|
||||
const char* placeholder,
|
||||
const char* font,
|
||||
bool bg
|
||||
);
|
||||
|
||||
public:
|
||||
static MenuInputNode* create(
|
||||
float width,
|
||||
float height,
|
||||
const char* placeholder,
|
||||
const char* font,
|
||||
bool bg = false
|
||||
);
|
||||
|
||||
void selected() override;
|
||||
|
||||
CCTextInputNode* getInput() const;
|
||||
};
|
||||
}
|
||||
|
346
loader/include/Geode/utils/JsonValidation.hpp
Normal file
346
loader/include/Geode/utils/JsonValidation.hpp
Normal file
|
@ -0,0 +1,346 @@
|
|||
#pragma once
|
||||
|
||||
#include "json.hpp"
|
||||
#include "../loader/Log.hpp"
|
||||
#include <variant>
|
||||
#include <set>
|
||||
|
||||
namespace geode {
|
||||
struct JsonChecker;
|
||||
|
||||
namespace {
|
||||
using value_t = nlohmann::detail::value_t;
|
||||
|
||||
constexpr const char* jsonValueTypeToString(value_t type) {
|
||||
switch (type) {
|
||||
default:
|
||||
case value_t::null: return "null";
|
||||
case value_t::object: return "object";
|
||||
case value_t::array: return "array";
|
||||
case value_t::string: return "string";
|
||||
case value_t::boolean: return "boolean";
|
||||
case value_t::binary: return "binary";
|
||||
case value_t::discarded: return "discarded";
|
||||
case value_t::number_integer: return "integer";
|
||||
case value_t::number_unsigned: return "integer";
|
||||
case value_t::number_float: return "number";
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
constexpr value_t getJsonType() {
|
||||
if constexpr (std::is_same_v<T, bool>) {
|
||||
return value_t::boolean;
|
||||
}
|
||||
else if constexpr (std::is_floating_point_v<T>) {
|
||||
return value_t::number_float;
|
||||
}
|
||||
else if constexpr (std::is_unsigned_v<T>) {
|
||||
return value_t::number_unsigned;
|
||||
}
|
||||
else if constexpr (std::is_integral_v<T>) {
|
||||
return value_t::number_integer;
|
||||
}
|
||||
else if constexpr (
|
||||
std::is_same_v<T, std::string> ||
|
||||
std::is_same_v<T, const char*>
|
||||
) {
|
||||
return value_t::string;
|
||||
}
|
||||
return value_t::null;
|
||||
}
|
||||
|
||||
bool jsonConvertibleTo(value_t value, value_t to) {
|
||||
if (
|
||||
value == value_t::number_float ||
|
||||
value == value_t::number_integer ||
|
||||
value == value_t::number_unsigned
|
||||
) {
|
||||
return
|
||||
to == value_t::number_float ||
|
||||
to == value_t::number_integer ||
|
||||
to == value_t::number_unsigned;
|
||||
}
|
||||
return value == to;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
using JsonValueValidator = bool(*)(T const&);
|
||||
|
||||
struct JsonMaybeObject;
|
||||
|
||||
struct GEODE_DLL JsonMaybeSomething {
|
||||
protected:
|
||||
JsonChecker& m_checker;
|
||||
nlohmann::json& m_json;
|
||||
std::string m_hierarchy;
|
||||
bool m_hasValue;
|
||||
|
||||
void setError(std::string const& error);
|
||||
|
||||
public:
|
||||
nlohmann::json& json() {
|
||||
return m_json;
|
||||
}
|
||||
|
||||
JsonMaybeSomething(
|
||||
JsonChecker& checker,
|
||||
nlohmann::json& json,
|
||||
std::string const& hierarchy,
|
||||
bool hasValue
|
||||
) : m_checker(checker),
|
||||
m_json(json),
|
||||
m_hierarchy(hierarchy),
|
||||
m_hasValue(hasValue) {}
|
||||
|
||||
bool isError() const;
|
||||
|
||||
operator bool() const {
|
||||
return !isError();
|
||||
}
|
||||
};
|
||||
|
||||
struct GEODE_DLL JsonMaybeValue : JsonMaybeSomething {
|
||||
bool m_inferType = true;
|
||||
|
||||
JsonMaybeValue(
|
||||
JsonChecker& checker,
|
||||
nlohmann::json& json,
|
||||
std::string const& hierarchy,
|
||||
bool hasValue
|
||||
) : JsonMaybeSomething(checker, json, hierarchy, hasValue) {}
|
||||
|
||||
template<nlohmann::detail::value_t T>
|
||||
JsonMaybeValue as() {
|
||||
if (this->isError()) return *this;
|
||||
if (!jsonConvertibleTo(m_json.type(), T)) {
|
||||
this->setError(
|
||||
m_hierarchy + ": Invalid type \"" +
|
||||
m_json.type_name() + "\", expected \"" +
|
||||
jsonValueTypeToString(T) + "\""
|
||||
);
|
||||
}
|
||||
m_inferType = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<nlohmann::detail::value_t... T>
|
||||
JsonMaybeValue asOneOf() {
|
||||
if (this->isError()) return *this;
|
||||
bool isOneOf = (... || jsonConvertibleTo(m_json.type(), T));
|
||||
if (!isOneOf) {
|
||||
this->setError(
|
||||
m_hierarchy + ": Invalid type \"" +
|
||||
m_json.type_name() + "\", expected one of \"" +
|
||||
(jsonValueTypeToString(T), ...) + "\""
|
||||
);
|
||||
}
|
||||
m_inferType = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<nlohmann::detail::value_t T>
|
||||
JsonMaybeValue is() {
|
||||
if (this->isError()) return *this;
|
||||
m_hasValue = jsonConvertibleTo(m_json.type(), T);
|
||||
m_inferType = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
JsonMaybeValue validate(JsonValueValidator<T> validator) {
|
||||
if (this->isError()) return *this;
|
||||
try {
|
||||
if (!validator(m_json.get<T>())) {
|
||||
this->setError(m_hierarchy + ": Invalid value format");
|
||||
}
|
||||
} catch(...) {
|
||||
this->setError(
|
||||
m_hierarchy + ": Invalid type \"" +
|
||||
std::string(m_json.type_name()) + "\""
|
||||
);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
JsonMaybeValue inferType() {
|
||||
if (this->isError() || !m_inferType) return *this;
|
||||
return this->as<getJsonType<T>()>();
|
||||
}
|
||||
|
||||
JsonMaybeValue intoRaw(nlohmann::json& target) {
|
||||
if (this->isError()) return *this;
|
||||
target = m_json;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
JsonMaybeValue into(T& target) {
|
||||
return this->intoAs<T, T>(target);
|
||||
}
|
||||
|
||||
template<class A, class T>
|
||||
JsonMaybeValue intoAs(T& target) {
|
||||
this->inferType<A>();
|
||||
if (this->isError()) return *this;
|
||||
try {
|
||||
target = m_json.get<A>();
|
||||
} catch(...) {
|
||||
this->setError(
|
||||
m_hierarchy + ": Invalid type \"" +
|
||||
std::string(m_json.type_name()) + "\""
|
||||
);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T get() {
|
||||
this->inferType<T>();
|
||||
if (this->isError()) return T();
|
||||
try {
|
||||
return m_json.get<T>();
|
||||
} catch(...) {
|
||||
this->setError(
|
||||
m_hierarchy + ": Invalid type to get \"" +
|
||||
std::string(m_json.type_name()) + "\""
|
||||
);
|
||||
}
|
||||
return T();
|
||||
}
|
||||
|
||||
JsonMaybeObject obj();
|
||||
|
||||
template<class T>
|
||||
struct Iterator {
|
||||
std::vector<T> m_values;
|
||||
|
||||
using iterator = std::vector<T>::iterator;
|
||||
using const_iterator = std::vector<T>::const_iterator;
|
||||
|
||||
iterator begin() {
|
||||
return m_values.begin();
|
||||
}
|
||||
iterator end() {
|
||||
return m_values.end();
|
||||
}
|
||||
|
||||
const_iterator begin() const {
|
||||
return m_values.begin();
|
||||
}
|
||||
const_iterator end() const {
|
||||
return m_values.end();
|
||||
}
|
||||
};
|
||||
|
||||
Iterator<JsonMaybeValue> iterate() {
|
||||
this->as<value_t::array>();
|
||||
Iterator<JsonMaybeValue> iter;
|
||||
if (this->isError()) return iter;
|
||||
size_t i = 0;
|
||||
for (auto& obj : m_json) {
|
||||
iter.m_values.emplace_back(
|
||||
m_checker, obj,
|
||||
m_hierarchy + "." + std::to_string(i++),
|
||||
m_hasValue
|
||||
);
|
||||
}
|
||||
return iter;
|
||||
}
|
||||
|
||||
Iterator<std::pair<std::string, JsonMaybeValue>> items() {
|
||||
this->as<value_t::object>();
|
||||
Iterator<std::pair<std::string, JsonMaybeValue>> iter;
|
||||
if (this->isError()) return iter;
|
||||
|
||||
for (auto& [k, v] : m_json.items()) {
|
||||
iter.m_values.emplace_back(k, JsonMaybeValue(
|
||||
m_checker, v,
|
||||
m_hierarchy + "." + k,
|
||||
m_hasValue
|
||||
));
|
||||
}
|
||||
|
||||
return iter;
|
||||
}
|
||||
};
|
||||
|
||||
struct GEODE_DLL JsonMaybeObject : JsonMaybeSomething {
|
||||
std::set<std::string> m_knownKeys;
|
||||
|
||||
JsonMaybeObject(
|
||||
JsonChecker& checker,
|
||||
nlohmann::json& json,
|
||||
std::string const& hierarchy,
|
||||
bool hasValue
|
||||
) : JsonMaybeSomething(checker, json, hierarchy, hasValue) {}
|
||||
|
||||
void addKnownKey(std::string const& key) {
|
||||
m_knownKeys.insert(key);
|
||||
}
|
||||
|
||||
nlohmann::json& json() {
|
||||
return m_json;
|
||||
}
|
||||
|
||||
JsonMaybeValue emptyValue() {
|
||||
return JsonMaybeValue(m_checker, m_json, "", false);
|
||||
}
|
||||
|
||||
JsonMaybeValue has(std::string const& key) {
|
||||
if (this->isError()) return emptyValue();
|
||||
this->addKnownKey(key);
|
||||
if (!m_json.contains(key) || m_json[key].is_null()) {
|
||||
return emptyValue();
|
||||
}
|
||||
return JsonMaybeValue(m_checker, m_json[key], key, true);
|
||||
}
|
||||
|
||||
JsonMaybeValue needs(std::string const& key) {
|
||||
if (this->isError()) return emptyValue();
|
||||
this->addKnownKey(key);
|
||||
if (!m_json.contains(key)) {
|
||||
this->setError(
|
||||
m_hierarchy + " is missing required key \"" + key + "\""
|
||||
);
|
||||
return emptyValue();
|
||||
}
|
||||
return JsonMaybeValue(m_checker, m_json[key], key, true);
|
||||
}
|
||||
|
||||
void checkUnknownKeys() {
|
||||
for (auto& [key, _] : m_json.items()) {
|
||||
if (!m_knownKeys.count(key)) {
|
||||
Log::get() << m_hierarchy + " contains unknown key \"" + key + "\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct GEODE_DLL JsonChecker {
|
||||
std::variant<std::monostate, std::string> m_result;
|
||||
nlohmann::json& m_json;
|
||||
|
||||
JsonChecker(
|
||||
nlohmann::json& json
|
||||
) : m_json(json), m_result(std::monostate()) {}
|
||||
|
||||
bool isError() const {
|
||||
return std::holds_alternative<std::string>(m_result);
|
||||
}
|
||||
|
||||
std::string getError() const {
|
||||
return std::get<std::string>(m_result);
|
||||
}
|
||||
|
||||
JsonMaybeObject root(std::string const& hierarchy) {
|
||||
if (!m_json.is_object()) {
|
||||
m_result = hierarchy + ": Root is not an object";
|
||||
return JsonMaybeObject(*this, m_json, hierarchy, false);
|
||||
}
|
||||
return JsonMaybeObject(*this, m_json, hierarchy, true);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,258 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
struct json_check_failure : public std::exception {
|
||||
std::string m_info;
|
||||
const char* what() const throw() {
|
||||
return m_info.c_str();
|
||||
}
|
||||
json_check_failure(std::string const& info) : m_info(info) {}
|
||||
};
|
||||
|
||||
struct json_check {
|
||||
nlohmann::json m_json;
|
||||
bool m_continue = true;
|
||||
bool m_branched = false;
|
||||
bool m_hasBranch= false;
|
||||
bool m_required = false;
|
||||
std::string m_key = "";
|
||||
std::string m_hierarchy = "";
|
||||
std::string m_types = "";
|
||||
std::set<std::string_view>& m_knownKeys;
|
||||
|
||||
nlohmann::json get_json() {
|
||||
return m_key.size() ? m_json[m_key] : m_json;
|
||||
}
|
||||
|
||||
json_check(
|
||||
std::set<std::string_view>& knownKeys,
|
||||
nlohmann::json const& json
|
||||
) : m_knownKeys(knownKeys), m_json(json) {}
|
||||
|
||||
json_check(
|
||||
std::set<std::string_view>& knownKeys,
|
||||
json_check const& other
|
||||
) : json_check(knownKeys, other.m_json) {}
|
||||
|
||||
json_check& has(std::string_view const& key) {
|
||||
if (!m_continue) return *this;
|
||||
m_knownKeys.insert(key);
|
||||
m_continue = m_json.contains(key);
|
||||
m_key = key;
|
||||
return *this;
|
||||
}
|
||||
json_check& needs(std::string_view const& key) {
|
||||
if (!m_continue) return *this;
|
||||
if (!m_json.contains(key))
|
||||
throw json_check_failure(
|
||||
"[mod.json]" + m_hierarchy + " is missing required key \"" + std::string(key) + "\""
|
||||
);
|
||||
m_knownKeys.insert(key);
|
||||
m_key = key;
|
||||
m_required = true;
|
||||
return *this;
|
||||
}
|
||||
template<typename T>
|
||||
json_check& as() {
|
||||
if (!m_continue) return *this;
|
||||
auto json = get_json();
|
||||
auto keyName = m_key.size() ? "[self]" : m_key;
|
||||
if constexpr (std::is_same_v<T, std::string>) {
|
||||
m_types += "string, ";
|
||||
if (!json.is_string()) {
|
||||
if (m_required) {
|
||||
throw json_check_failure("[mod.json]" + m_hierarchy + "." + keyName + " is not a string");
|
||||
} else {
|
||||
if (json.is_null()) {
|
||||
m_continue = false;
|
||||
} else {
|
||||
throw json_check_failure("[mod.json]" + m_hierarchy + "." + keyName + " is not a string nor null");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
if constexpr (std::is_same_v<T, int>) {
|
||||
m_types += "int, ";
|
||||
if (!json.is_number_integer()) {
|
||||
if (m_required) {
|
||||
throw json_check_failure("[mod.json]" + m_hierarchy + "." + keyName + " is not a int");
|
||||
} else {
|
||||
if (json.is_null()) {
|
||||
m_continue = false;
|
||||
} else {
|
||||
throw json_check_failure("[mod.json]" + m_hierarchy + "." + keyName + " is not a int nor null");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
if constexpr (std::is_same_v<T, bool>) {
|
||||
m_types += "bool, ";
|
||||
if (!json.is_boolean()) {
|
||||
if (m_required) {
|
||||
throw json_check_failure("[mod.json]" + m_hierarchy + "." + keyName + " is not a boolean");
|
||||
} else {
|
||||
if (json.is_null()) {
|
||||
m_continue = false;
|
||||
} else {
|
||||
throw json_check_failure("[mod.json]" + m_hierarchy + "." + keyName + " is not a boolean nor null");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
if constexpr (std::is_same_v<T, nlohmann::json::object_t>) {
|
||||
m_types += "object, ";
|
||||
if (!json.is_object()) {
|
||||
if (m_required) {
|
||||
throw json_check_failure("[mod.json]" + m_hierarchy + "." + keyName + " is not a object");
|
||||
} else {
|
||||
if (json.is_null()) {
|
||||
m_continue = false;
|
||||
} else {
|
||||
throw json_check_failure("[mod.json]" + m_hierarchy + "." + keyName + " is not a object nor null");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
if constexpr (std::is_same_v<T, nlohmann::json::array_t>) {
|
||||
m_types += "array, ";
|
||||
if (!json.is_array()) {
|
||||
if (m_required) {
|
||||
throw json_check_failure("[mod.json]" + m_hierarchy + "." + keyName + " is not a array");
|
||||
} else {
|
||||
if (json.is_null()) {
|
||||
m_continue = false;
|
||||
} else {
|
||||
throw json_check_failure("[mod.json]" + m_hierarchy + "." + keyName + " is not a array nor null");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
static_assert(!std::is_same_v<T, T>, "Unimplemented type for json_check");
|
||||
return *this;
|
||||
}
|
||||
template<typename T>
|
||||
json_check& is(std::function<void(nlohmann::json const&)> branch) {
|
||||
if (!m_continue) return *this;
|
||||
m_hasBranch = true;
|
||||
try { this->as<T>(); } catch(json_check_failure&) { return *this; }
|
||||
branch(get_json());
|
||||
m_continue = false;
|
||||
m_branched = true;
|
||||
return *this;
|
||||
}
|
||||
json_check& is_ok() {
|
||||
if (m_hasBranch && !m_branched) {
|
||||
throw json_check_failure("[mod.json]" + m_hierarchy + "." + m_key + " is not one of " + m_types.substr(0, m_types.size() - 2));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
json_check& validate(std::function<bool(nlohmann::json const&)> predicate) {
|
||||
if (!m_continue) return *this;
|
||||
if (!predicate(get_json())) {
|
||||
throw json_check_failure("[mod.json]" + m_hierarchy + "." + m_key + " is invalidly formatted");
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
template<typename T>
|
||||
json_check& into(T& var) {
|
||||
if (!m_continue) return *this;
|
||||
var = get_json().get<T>();
|
||||
return *this;
|
||||
}
|
||||
template<typename T>
|
||||
json_check& into_if(T& var) {
|
||||
return this->is<T>([&var](nlohmann::json const& json) -> void {
|
||||
std::set<std::string_view> knownKeys;
|
||||
json_check(knownKeys, json).as<T>().into(var);
|
||||
});
|
||||
}
|
||||
template<typename T>
|
||||
json_check& into_as(T& var) {
|
||||
return this->as<T>().into(var);
|
||||
}
|
||||
json_check& into(std::function<void(nlohmann::json const&)> var) {
|
||||
if (!m_continue) return *this;
|
||||
var(get_json());
|
||||
return *this;
|
||||
}
|
||||
json_check& each(std::function<void(std::string const&, json_check)> func) {
|
||||
if (!m_continue) return *this;
|
||||
for (auto const& [key, val] : get_json().items()) {
|
||||
std::set<std::string_view> knownKeys;
|
||||
auto c = json_check(knownKeys, *this);
|
||||
c.step();
|
||||
c.m_key = key;
|
||||
c.step();
|
||||
func(key, c);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
json_check& each(std::function<void(json_check)> func) {
|
||||
if (!m_continue) return *this;
|
||||
size_t ix = 0;
|
||||
for (auto const& val : get_json()) {
|
||||
std::set<std::string_view> knownKeys;
|
||||
auto c = json_check(knownKeys, val);
|
||||
c.m_hierarchy = m_hierarchy;
|
||||
if (m_key.size()) c.m_hierarchy += "." + m_key;
|
||||
c.m_hierarchy += "." + std::to_string(ix);
|
||||
func(c);
|
||||
ix++;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
json_check& step() {
|
||||
if (!m_continue) return *this;
|
||||
if (m_key.size()) {
|
||||
this->m_hierarchy += "." + m_key;
|
||||
this->m_json = get_json();
|
||||
m_key = "";
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T get() {
|
||||
if (!m_continue) return T();
|
||||
return this->get_json().get<T>();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct json_assign_required : json_check {
|
||||
json_assign_required(
|
||||
std::set<std::string_view>& knownKeys,
|
||||
nlohmann::json& json,
|
||||
std::string_view const& key,
|
||||
T& var
|
||||
) : json_check(knownKeys, json) {
|
||||
this->needs(key).template as<T>().into(var);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct json_assign_optional : json_check {
|
||||
json_assign_optional(
|
||||
std::set<std::string_view>& knownKeys,
|
||||
nlohmann::json& json,
|
||||
std::string_view const& key,
|
||||
T& var
|
||||
) : json_check(knownKeys, json) {
|
||||
this->has(key).template as<T>().into(var);
|
||||
}
|
||||
};
|
||||
|
||||
struct json_check_unknown {
|
||||
json_check_unknown(
|
||||
std::set<std::string_view>& knownKeys,
|
||||
nlohmann::json& json,
|
||||
std::string const& hierarchy
|
||||
) {
|
||||
for (auto& [key, _] : json.items()) {
|
||||
if (!knownKeys.count(key)) {
|
||||
throw json_check_failure(std::string("[mod.json] ") + hierarchy + " contains unknown key \"" + key + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
|
@ -20,11 +20,4 @@ namespace geode {
|
|||
|
||||
template<class T>
|
||||
using TypeIdentityType = typename TypeIdentity<T>::type;
|
||||
|
||||
#ifndef GEODE_NO_CRINGE
|
||||
|
||||
using unknown_t = uintptr_t;
|
||||
using edx_t = uintptr_t;
|
||||
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "Index.hpp"
|
||||
#include <thread>
|
||||
#include <Geode/utils/json.hpp>
|
||||
#include <Geode/utils/json_check.hpp>
|
||||
#include <Geode/utils/JsonValidation.hpp>
|
||||
#include "fetch.hpp"
|
||||
|
||||
#define GITHUB_DONT_RATE_LIMIT_ME_PLS 0
|
||||
|
@ -321,7 +321,7 @@ void Index::addIndexItemFromFolder(ghc::filesystem::path const& dir) {
|
|||
auto info = ModInfo::create(readModJson.value());
|
||||
if (!info) {
|
||||
Log::get() << Severity::Warning
|
||||
<< info.error() << ", skipping";
|
||||
<< dir << ": " << info.error() << ", skipping";
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
16
loader/src/load/JsonValidation.cpp
Normal file
16
loader/src/load/JsonValidation.cpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#include <Geode/utils/JsonValidation.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
void JsonMaybeSomething::setError(std::string const& error) {
|
||||
m_checker.m_result = error;
|
||||
}
|
||||
|
||||
bool JsonMaybeSomething::isError() const {
|
||||
return m_checker.isError() || !m_hasValue;
|
||||
}
|
||||
|
||||
JsonMaybeObject JsonMaybeValue::obj() {
|
||||
this->as<value_t::object>();
|
||||
return JsonMaybeObject(m_checker, m_json, m_hierarchy, m_hasValue);
|
||||
}
|
|
@ -4,13 +4,13 @@
|
|||
#include <Geode/loader/Log.hpp>
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
#include <Geode/loader/Interface.hpp>
|
||||
#include <Geode/loader/Setting.hpp>
|
||||
#include <Geode/utils/conststring.hpp>
|
||||
#include <Geode/utils/file.hpp>
|
||||
#include <Geode/utils/json_check.hpp>
|
||||
#include <Geode/utils/JsonValidation.hpp>
|
||||
#include <Geode/utils/map.hpp>
|
||||
#include <Geode/utils/string.hpp>
|
||||
#include <Geode/utils/vector.hpp>
|
||||
//#include <InternalLoader.hpp>
|
||||
#include <InternalMod.hpp>
|
||||
#include <ZipUtils.h>
|
||||
|
||||
|
@ -489,11 +489,19 @@ const char* Mod::expandSpriteName(const char* name) {
|
|||
return exp;
|
||||
}
|
||||
|
||||
bool Mod::hasSettings() const {
|
||||
return m_info.m_settings.size();
|
||||
}
|
||||
|
||||
decltype(ModInfo::m_settings) Mod::getSettings() const {
|
||||
return m_info.m_settings;
|
||||
}
|
||||
|
||||
std::string Mod::getLoadErrorInfo() const {
|
||||
return m_loadErrorInfo;
|
||||
}
|
||||
|
||||
std::string sanitizeDetailsData(unsigned char* start, unsigned char* end) {
|
||||
static std::string sanitizeDetailsData(unsigned char* start, unsigned char* end) {
|
||||
// delete CRLF
|
||||
return string_utils::replace(std::string(start, end), "\r", "");
|
||||
}
|
||||
|
@ -503,110 +511,98 @@ Result<ModInfo> ModInfo::createFromSchemaV010(nlohmann::json const& rawJson) {
|
|||
|
||||
auto json = rawJson;
|
||||
|
||||
try {
|
||||
#define PROPAGATE(err) \
|
||||
{ auto err__ = err; if (!err__) return Err(err__.error()); }
|
||||
|
||||
std::set<std::string_view> knownKeys;
|
||||
JsonChecker checker(json);
|
||||
auto root = checker.root("[mod.json]");
|
||||
|
||||
json_check(knownKeys, json)
|
||||
root.addKnownKey("geode");
|
||||
root.addKnownKey("binary");
|
||||
|
||||
using nlohmann::detail::value_t;
|
||||
|
||||
root
|
||||
.needs("id")
|
||||
.as<std::string>()
|
||||
.validate([](auto t) -> bool { return Mod::validateID(t.template get<std::string>()); })
|
||||
.validate(&Mod::validateID)
|
||||
.into(info.m_id);
|
||||
|
||||
json_check(knownKeys, json)
|
||||
root
|
||||
.needs("version")
|
||||
.as<std::string>()
|
||||
.validate([](auto t) -> bool { return VersionInfo::validate(t.template get<std::string>()); })
|
||||
.into([&info](auto json) -> void { info.m_version = VersionInfo(json.template get<std::string>()); });
|
||||
.validate(&VersionInfo::validate)
|
||||
.intoAs<std::string>(info.m_version);
|
||||
root.needs("name").into(info.m_name);
|
||||
root.needs("developer").into(info.m_developer);
|
||||
root.has("description").into(info.m_description);
|
||||
root.has("repository").into(info.m_repository);
|
||||
root.has("datastore").intoRaw(info.m_defaultDataStore);
|
||||
root.has("toggleable").into(info.m_supportsDisabling);
|
||||
root.has("unloadable").into(info.m_supportsUnloading);
|
||||
|
||||
json_assign_required(knownKeys, json, "name", info.m_name);
|
||||
json_assign_required(knownKeys, json, "developer", info.m_developer);
|
||||
json_assign_optional(knownKeys, json, "description", info.m_description);
|
||||
json_assign_optional(knownKeys, json, "repository", info.m_repository);
|
||||
for (auto& dep : root.has("dependencies").iterate()) {
|
||||
auto obj = dep.obj();
|
||||
|
||||
json_check(knownKeys, json)
|
||||
.has("dependencies")
|
||||
.as<nlohmann::json::array_t>()
|
||||
.each([&info](json_check dep) -> void {
|
||||
dep.as<nlohmann::json::object_t>();
|
||||
auto depobj = Dependency {};
|
||||
std::set<std::string_view> knownKeys;
|
||||
json_check(knownKeys, dep)
|
||||
.needs("id")
|
||||
.as<std::string>()
|
||||
.into(depobj.m_id);
|
||||
json_check(knownKeys, dep)
|
||||
.has("version")
|
||||
.as<std::string>()
|
||||
.validate([&](auto t) -> bool { return VersionInfo::validate(t.template get<std::string>()); })
|
||||
.into([&info](auto json) -> void { info.m_version = VersionInfo(json.template get<std::string>()); });
|
||||
json_check(knownKeys, dep).has("required").as<bool>().into(depobj.m_required);
|
||||
json_check_unknown(knownKeys, dep.m_json, dep.m_hierarchy);
|
||||
info.m_dependencies.push_back(depobj);
|
||||
});
|
||||
|
||||
json_check(knownKeys, json)
|
||||
.has("datastore")
|
||||
.as<nlohmann::json::object_t>()
|
||||
.into(info.m_defaultDataStore);
|
||||
|
||||
json_check(knownKeys, json)
|
||||
.has("resources")
|
||||
.as<nlohmann::json::object_t>()
|
||||
.step()
|
||||
.has("spritesheets")
|
||||
.as<nlohmann::json::object_t>()
|
||||
.each([&info](auto key, auto) -> void {
|
||||
info.m_spritesheets.push_back(info.m_id + "/" + key);
|
||||
});
|
||||
|
||||
json_assign_optional(knownKeys, json, "toggleable", info.m_supportsDisabling);
|
||||
json_assign_optional(knownKeys, json, "unloadable", info.m_supportsUnloading);
|
||||
auto depobj = Dependency {};
|
||||
obj
|
||||
.needs("id")
|
||||
.validate(&Mod::validateID)
|
||||
.into(depobj.m_id);
|
||||
obj
|
||||
.needs("version")
|
||||
.validate(&VersionInfo::validate)
|
||||
.intoAs<std::string>(depobj.m_version);
|
||||
obj
|
||||
.has("required")
|
||||
.into(depobj.m_required);
|
||||
obj.checkUnknownKeys();
|
||||
|
||||
knownKeys.insert("geode");
|
||||
knownKeys.insert("binary");
|
||||
knownKeys.insert("userdata");
|
||||
json_check_unknown(knownKeys, json, "");
|
||||
|
||||
} catch(std::exception& e) {
|
||||
return Err<>(e.what());
|
||||
info.m_dependencies.push_back(depobj);
|
||||
}
|
||||
|
||||
if (json.contains("binary")) {
|
||||
bool autoEnd = true;
|
||||
if (json["binary"].is_string()) {
|
||||
info.m_binaryName = json["binary"];
|
||||
} else if (json["binary"].is_object()) {
|
||||
auto bo = json["binary"];
|
||||
if (bo.contains("*") && bo["*"].is_string()) {
|
||||
info.m_binaryName = bo["*"];
|
||||
}
|
||||
if (bo.contains("auto") && bo["auto"].is_boolean()) {
|
||||
autoEnd = bo["auto"];
|
||||
}
|
||||
#if defined(GEODE_IS_WINDOWS)
|
||||
if (bo.contains("windows") && bo["windows"].is_string()) {
|
||||
info.m_binaryName = bo["windows"];
|
||||
}
|
||||
#elif defined(GEODE_IS_MACOS)
|
||||
if (bo.contains("macos") && bo["macos"].is_string()) {
|
||||
info.m_binaryName = bo["macos"];
|
||||
}
|
||||
#elif defined(GEODE_IS_ANDROID)
|
||||
if (bo.contains("android") && bo["android"].is_string()) {
|
||||
info.m_binaryName = bo["android"];
|
||||
}
|
||||
#elif defined(GEODE_IS_IOS)
|
||||
if (bo.contains("ios") && bo["ios"].is_string()) {
|
||||
info.m_binaryName = bo["ios"];
|
||||
}
|
||||
#endif
|
||||
} else goto skip_binary_check;
|
||||
if (autoEnd && !string_utils::endsWith(info.m_binaryName, GEODE_PLATFORM_EXTENSION)) {
|
||||
info.m_binaryName += GEODE_PLATFORM_EXTENSION;
|
||||
for (auto& [key, value] : root.has("settings").items()) {
|
||||
auto sett = Setting::parse(key, value.json());
|
||||
PROPAGATE(sett);
|
||||
info.m_settings.insert({ key, sett.value() });
|
||||
}
|
||||
|
||||
if (auto resources = root.has("resources").obj()) {
|
||||
for (auto& [key, _] : resources.has("spritesheets").items()) {
|
||||
info.m_spritesheets.push_back(info.m_id + "/" + key);
|
||||
}
|
||||
}
|
||||
skip_binary_check:
|
||||
|
||||
root.has("binary").asOneOf<value_t::string, value_t::object>();
|
||||
|
||||
bool autoEndBinaryName = true;
|
||||
|
||||
root.has("binary").is<value_t::string>().into(info.m_binaryName);
|
||||
|
||||
if (auto bin = root.has("binary").is<value_t::object>().obj()) {
|
||||
bin.has("*").into(info.m_binaryName);
|
||||
bin.has("auto").into(autoEndBinaryName);
|
||||
|
||||
#if defined(GEODE_IS_WINDOWS)
|
||||
bin.has("windows").into(info.m_binaryName);
|
||||
#elif defined(GEODE_IS_MACOS)
|
||||
bin.has("macos").into(info.m_binaryName);
|
||||
#elif defined(GEODE_IS_ANDROID)
|
||||
bin.has("android").into(info.m_binaryName);
|
||||
#elif defined(GEODE_IS_IOS)
|
||||
bin.has("ios").into(info.m_binaryName);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (
|
||||
root.has("binary") &&
|
||||
autoEndBinaryName &&
|
||||
!string_utils::endsWith(info.m_binaryName, GEODE_PLATFORM_EXTENSION)
|
||||
) {
|
||||
info.m_binaryName += GEODE_PLATFORM_EXTENSION;
|
||||
}
|
||||
|
||||
if (checker.isError()) {
|
||||
return Err(checker.getError());
|
||||
}
|
||||
root.checkUnknownKeys();
|
||||
|
||||
return Ok(info);
|
||||
}
|
||||
|
|
65
loader/src/load/Setting.cpp
Normal file
65
loader/src/load/Setting.cpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
#include <Geode/loader/Setting.hpp>
|
||||
#include <Geode/utils/general.hpp>
|
||||
#include <Geode/loader/SettingNode.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
#define PROPAGATE(err) \
|
||||
{ auto err__ = err; if (!err__) return Err(err__.error()); }
|
||||
|
||||
std::string Setting::getKey() const {
|
||||
return m_key;
|
||||
}
|
||||
|
||||
Result<std::shared_ptr<Setting>> Setting::parse(
|
||||
std::string const& type,
|
||||
std::string const& key,
|
||||
JsonMaybeObject& obj
|
||||
) {
|
||||
switch (hash(type.c_str())) {
|
||||
case hash("bool"): return BoolSetting::parse(key, obj);
|
||||
case hash("int"): return IntSetting::parse(key, obj);
|
||||
case hash("float"): return FloatSetting::parse(key, obj);
|
||||
case hash("string"): return StringSetting::parse(key, obj);
|
||||
default: return Err(
|
||||
"Setting \"" + key + "\" has unknown type \"" + type + "\""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Result<std::shared_ptr<Setting>> Setting::parse(
|
||||
std::string const& key,
|
||||
nlohmann::json const& rawJson
|
||||
) {
|
||||
if (rawJson.is_object()) {
|
||||
auto json = rawJson;
|
||||
JsonChecker checker(json);
|
||||
auto root = checker.root("[setting \"" + key + "\"]");
|
||||
|
||||
auto res = Setting::parse(
|
||||
root.needs("type").get<std::string>(), key, root
|
||||
);
|
||||
root.checkUnknownKeys();
|
||||
if (checker.isError()) {
|
||||
return Err(checker.getError());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
return Err("Setting value is not an object");
|
||||
}
|
||||
|
||||
SettingNode* BoolSetting::createNode(float width) {
|
||||
return BoolSettingNode::create(shared_from_this(), width);
|
||||
}
|
||||
|
||||
SettingNode* IntSetting::createNode(float width) {
|
||||
return IntSettingNode::create(shared_from_this(), width);
|
||||
}
|
||||
|
||||
SettingNode* FloatSetting::createNode(float width) {
|
||||
return FloatSettingNode::create(shared_from_this(), width);
|
||||
}
|
||||
|
||||
SettingNode* StringSetting::createNode(float width) {
|
||||
return StringSettingNode::create(shared_from_this(), width);
|
||||
}
|
46
loader/src/load/SettingNode.cpp
Normal file
46
loader/src/load/SettingNode.cpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
#include <Geode/loader/SettingNode.hpp>
|
||||
#include <Geode/utils/cocos.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
bool SettingNode::init(std::shared_ptr<Setting> setting) {
|
||||
m_setting = setting;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BoolSettingNode::setup(std::shared_ptr<BoolSetting> setting, float width) {
|
||||
auto toggle = CCMenuItemToggler::createWithStandardSprites(
|
||||
this, makeMenuSelector([this](CCMenuItemToggler* toggle) {
|
||||
m_uncommittedValue = !toggle->isToggled();
|
||||
this->valueChanged();
|
||||
}), .5f
|
||||
);
|
||||
toggle->setPosition(.0f, .0f);
|
||||
toggle->toggle(m_uncommittedValue);
|
||||
m_menu->addChild(toggle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IntSettingNode::setup(std::shared_ptr<IntSetting> setting, float width) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FloatSettingNode::setup(std::shared_ptr<FloatSetting> setting, float width) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void StringSettingNode::updateLabel() {
|
||||
m_input->setString(m_uncommittedValue);
|
||||
}
|
||||
|
||||
bool StringSettingNode::setup(std::shared_ptr<StringSetting> setting, float width) {
|
||||
m_input = InputNode::create(180.f, "Text", "chatFont.fnt");
|
||||
m_input->setPosition({ -50.f, .0f });
|
||||
m_input->setScale(.65f);
|
||||
m_menu->addChild(m_input);
|
||||
|
||||
this->updateLabel();
|
||||
|
||||
return true;
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
#include "ModInfoLayer.hpp"
|
||||
#include "../dev/HookListLayer.hpp"
|
||||
#include "../settings/ModSettingsLayer.hpp"
|
||||
#include <Geode/ui/BasedButton.hpp>
|
||||
#include <Geode/ui/MDTextArea.hpp>
|
||||
#include "../list/ModListView.hpp"
|
||||
#include <Geode/ui/Scrollbar.hpp>
|
||||
#include <Geode/utils/WackyGeodeMacros.hpp>
|
||||
// #include <settings/Setting.hpp>
|
||||
#include <Geode/ui/IconButtonSprite.hpp>
|
||||
#include "../settings/ModSettingsPopup.hpp"
|
||||
#include <InternalLoader.hpp>
|
||||
|
||||
// TODO: die
|
||||
#undef min
|
||||
|
@ -207,12 +207,12 @@ bool ModInfoLayer::init(ModObject* obj, ModListView* list) {
|
|||
);
|
||||
m_buttonMenu->addChild(settingsBtn);
|
||||
|
||||
// if (!SettingManager::with(m_mod)->hasSettings()) {
|
||||
// settingsSpr->setColor({ 150, 150, 150 });
|
||||
// settingsBtn->setTarget(
|
||||
// this, menu_selector(ModInfoLayer::onNoSettings)
|
||||
// );
|
||||
// }
|
||||
if (!m_mod->hasSettings()) {
|
||||
settingsSpr->setColor({ 150, 150, 150 });
|
||||
settingsBtn->setTarget(
|
||||
this, menu_selector(ModInfoLayer::onNoSettings)
|
||||
);
|
||||
}
|
||||
|
||||
auto devSpr = ButtonSprite::create(
|
||||
"Dev Options", "bigFont.fnt", "GJ_button_05.png", .6f
|
||||
|
@ -268,8 +268,7 @@ bool ModInfoLayer::init(ModObject* obj, ModListView* list) {
|
|||
uninstallBtn->setPosition(-85.f, 75.f);
|
||||
m_buttonMenu->addChild(uninstallBtn);
|
||||
|
||||
// api and loader should be updated through the installer
|
||||
// todo: show update button on them that invokes the installer
|
||||
// todo: show update button on loader that invokes the installer
|
||||
if (Index::get()->isUpdateAvailableForItem(m_info.m_id)) {
|
||||
m_installBtnSpr = IconButtonSprite::create(
|
||||
"GE_button_01.png"_spr,
|
||||
|
@ -350,19 +349,18 @@ bool ModInfoLayer::init(ModObject* obj, ModListView* list) {
|
|||
}
|
||||
|
||||
void ModInfoLayer::onEnableMod(CCObject* pSender) {
|
||||
// if (!APIInternal::get()->m_shownEnableWarning) {
|
||||
// APIInternal::get()->m_shownEnableWarning = true;
|
||||
// FLAlertLayer::create(
|
||||
// "Notice",
|
||||
// "<cb>Disabling</c> a <cy>mod</c> removes its hooks & patches and "
|
||||
// "calls its user-defined disable function if one exists. You may "
|
||||
// "still see some effects of the mod left however, and you may "
|
||||
// "need to <cg>restart</c> the game to have it fully unloaded.",
|
||||
// "OK"
|
||||
// )->show();
|
||||
// if (m_list) m_list->updateAllStates(nullptr);
|
||||
// return;
|
||||
// }
|
||||
if (!InternalLoader::get()->shownInfoAlert("mod-disable-vs-unload")) {
|
||||
FLAlertLayer::create(
|
||||
"Notice",
|
||||
"<cb>Disabling</c> a <cy>mod</c> removes its hooks & patches and "
|
||||
"calls its user-defined disable function if one exists. You may "
|
||||
"still see some effects of the mod left however, and you may "
|
||||
"need to <cg>restart</c> the game to have it fully unloaded.",
|
||||
"OK"
|
||||
)->show();
|
||||
if (m_list) m_list->updateAllStates(nullptr);
|
||||
return;
|
||||
}
|
||||
if (as<CCMenuItemToggler*>(pSender)->isToggled()) {
|
||||
auto res = m_mod->load();
|
||||
if (!res) {
|
||||
|
@ -603,8 +601,7 @@ void ModInfoLayer::onHooks(CCObject*) {
|
|||
}
|
||||
|
||||
void ModInfoLayer::onSettings(CCObject*) {
|
||||
//ModSettingsLayer::create(this->m_mod)->show();
|
||||
// FIXME: No settings yet
|
||||
ModSettingsPopup::create(m_mod)->show();
|
||||
}
|
||||
|
||||
void ModInfoLayer::onNoSettings(CCObject*) {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#include "../info/CategoryNode.hpp"
|
||||
|
||||
bool SearchFilterPopup::setup(ModListLayer* layer, ModListType type) {
|
||||
// todo: clean this shitty ass popup up
|
||||
m_noElasticity = true;
|
||||
m_modLayer = layer;
|
||||
|
||||
|
|
|
@ -1,760 +0,0 @@
|
|||
/*#pragma warning(disable: 4067)
|
||||
|
||||
#include "GeodeSettingNode.hpp"
|
||||
#include <general/TextRenderer.hpp>
|
||||
#include <random>
|
||||
#include <utils/WackyGeodeMacros.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
#define GEODE_GENERATE_SETTING_CREATE(_sett_, _height_) \
|
||||
_sett_##Node* _sett_##Node::create(_sett_* setting, float width) { \
|
||||
auto ret = new _sett_##Node(width, _height_); \
|
||||
if (ret && ret->init(setting)) { \
|
||||
ret->autorelease(); \
|
||||
return ret; \
|
||||
} CC_SAFE_DELETE(ret); return nullptr; }
|
||||
|
||||
template <typename T>
|
||||
std::string toStringWithPrecision(const T a_value, const size_t n = 6, bool cutZeros = true) {
|
||||
std::ostringstream out;
|
||||
out.precision(n);
|
||||
out << std::fixed << a_value;
|
||||
auto str = out.str();
|
||||
while (
|
||||
cutZeros &&
|
||||
string_utils::contains(str, '.') &&
|
||||
(str.back() == '0' || str.back() == '.')
|
||||
) {
|
||||
str = str.substr(0, str.size() - 1);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// bool
|
||||
|
||||
bool BoolSettingNode::init(BoolSetting* setting) {
|
||||
if (!GeodeSettingNode<BoolSetting>::init(setting))
|
||||
return false;
|
||||
|
||||
m_toggle = CCMenuItemToggler::createWithStandardSprites(
|
||||
this, menu_selector(BoolSettingNode::onToggle), .65f
|
||||
);
|
||||
m_toggle->setPosition(-m_toggle->m_onButton->getScaledContentSize().width / 2, 0);
|
||||
m_toggle->toggle(setting->getValue());
|
||||
m_buttonMenu->addChild(m_toggle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BoolSettingNode::onToggle(CCObject* pSender) {
|
||||
m_value = !m_toggle->isToggled();
|
||||
this->updateSettingsList();
|
||||
}
|
||||
|
||||
void BoolSettingNode::updateState() {
|
||||
m_toggle->toggle(m_value);
|
||||
}
|
||||
|
||||
// int
|
||||
|
||||
bool IntSettingNode::init(IntSetting* setting) {
|
||||
if (!GeodeSettingNode<IntSetting>::init(setting))
|
||||
return false;
|
||||
|
||||
auto controls = CCArray::create();
|
||||
CCScale9Sprite* bgSprite = nullptr;
|
||||
if (setting->hasInput()) {
|
||||
bgSprite = CCScale9Sprite::create(
|
||||
"square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }
|
||||
);
|
||||
bgSprite->setScale(.25f);
|
||||
bgSprite->setColor({ 0, 0, 0 });
|
||||
bgSprite->setOpacity(75);
|
||||
bgSprite->setContentSize({ 45.f * 4, m_height * 3 });
|
||||
bgSprite->setPosition(-20, 0);
|
||||
m_buttonMenu->addChild(bgSprite);
|
||||
controls->addObject(bgSprite);
|
||||
}
|
||||
|
||||
m_valueInput = MenuInputNode::create(45.f, m_height, "Num", "bigFont.fnt");
|
||||
m_valueInput->setPosition(-20.f, .0f);
|
||||
m_valueInput->getInput()->setAllowedChars("0123456789+-. ");
|
||||
m_valueInput->getInput()->setMaxLabelScale(.5f);
|
||||
m_valueInput->getInput()->setLabelPlaceholderColor({ 150, 150, 150 });
|
||||
m_valueInput->getInput()->setLabelPlaceholderScale(.75f);
|
||||
m_valueInput->getInput()->setDelegate(this);
|
||||
m_valueInput->setEnabled(setting->hasInput());
|
||||
m_buttonMenu->addChild(m_valueInput);
|
||||
controls->addObject(m_valueInput);
|
||||
|
||||
if (setting->hasArrows()) {
|
||||
m_valueInput->setPositionX(-30.f);
|
||||
if (bgSprite) bgSprite->setPositionX(-30.f);
|
||||
|
||||
auto decSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png");
|
||||
decSpr->setScale(.3f);
|
||||
decSpr->setFlipX(true);
|
||||
|
||||
auto decBtn = CCMenuItemSpriteExtra::create(
|
||||
decSpr, this, menu_selector(IntSettingNode::onArrow)
|
||||
);
|
||||
decBtn->setTag(-1);
|
||||
decBtn->setPosition(-60.f, 0);
|
||||
m_buttonMenu->addChild(decBtn);
|
||||
controls->addObject(decBtn);
|
||||
|
||||
auto incSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png");
|
||||
incSpr->setScale(.3f);
|
||||
|
||||
auto incBtn = CCMenuItemSpriteExtra::create(
|
||||
incSpr, this, menu_selector(IntSettingNode::onArrow)
|
||||
);
|
||||
incBtn->setTag(1);
|
||||
incBtn->setPosition(0.f, 0);
|
||||
m_buttonMenu->addChild(incBtn);
|
||||
controls->addObject(incBtn);
|
||||
}
|
||||
|
||||
if (setting->hasSlider()) {
|
||||
m_height += 20.f;
|
||||
this->setContentSize({ m_width, m_height });
|
||||
m_backgroundLayer->setContentSize(this->getContentSize());
|
||||
|
||||
m_nameLabel->setPositionY(m_nameLabel->getPositionY() + 10.f);
|
||||
if (m_descButton) {
|
||||
m_descButton->setPositionY(m_descButton->getPositionY() - 8.f);
|
||||
}
|
||||
m_buttonMenu->setPositionY(m_buttonMenu->getPositionY() + 18.f);
|
||||
|
||||
CCARRAY_FOREACH_B_TYPE(controls, node, CCNode) {
|
||||
node->setPositionX(node->getPositionX() - (setting->hasArrows() ? 35.f : 45.f));
|
||||
}
|
||||
|
||||
m_slider = Slider::create(this, menu_selector(IntSettingNode::onSlider), .65f);
|
||||
m_slider->setPosition(-65.f, -22.f);
|
||||
m_slider->setValue(
|
||||
// normalized to 0-1
|
||||
(m_value - m_setting->getMin()) /
|
||||
(m_setting->getMax() - m_setting->getMin())
|
||||
);
|
||||
m_buttonMenu->addChild(m_slider);
|
||||
}
|
||||
|
||||
this->updateValue();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void IntSettingNode::textChanged(CCTextInputNode* input) {
|
||||
try {
|
||||
m_value = std::stoi(input->getString());
|
||||
} catch(...) {}
|
||||
this->updateValue(false);
|
||||
}
|
||||
|
||||
void IntSettingNode::textInputClosed(CCTextInputNode* input) {
|
||||
try {
|
||||
m_value = std::stoi(input->getString());
|
||||
} catch(...) {}
|
||||
this->updateValue();
|
||||
}
|
||||
|
||||
void IntSettingNode::onSlider(CCObject* pSender) {
|
||||
m_value =
|
||||
as<SliderThumb*>(pSender)->getValue() *
|
||||
(m_setting->getMax() - m_setting->getMin()) +
|
||||
m_setting->getMin();
|
||||
m_valueInput->getInput()->detachWithIME();
|
||||
this->updateValue();
|
||||
}
|
||||
|
||||
void IntSettingNode::onArrow(CCObject* pSender) {
|
||||
m_value = m_value + pSender->getTag() * m_setting->getStep();
|
||||
m_valueInput->getInput()->detachWithIME();
|
||||
this->updateValue();
|
||||
}
|
||||
|
||||
void IntSettingNode::updateValue(bool updateInput) {
|
||||
if (m_value < m_setting->getMin()) {
|
||||
m_value = m_setting->getMin();
|
||||
}
|
||||
if (m_value > m_setting->getMax()) {
|
||||
m_value = m_setting->getMax();
|
||||
}
|
||||
if (updateInput) {
|
||||
m_valueInput->getInput()->setString(
|
||||
std::to_string(m_value).c_str()
|
||||
);
|
||||
}
|
||||
if (m_slider) {
|
||||
m_slider->setValue(
|
||||
static_cast<float>(m_value - m_setting->getMin()) /
|
||||
(m_setting->getMax() - m_setting->getMin())
|
||||
);
|
||||
m_slider->updateBar();
|
||||
}
|
||||
this->updateSettingsList();
|
||||
}
|
||||
|
||||
void IntSettingNode::updateState() {
|
||||
this->updateValue();
|
||||
}
|
||||
|
||||
// float
|
||||
|
||||
bool FloatSettingNode::init(FloatSetting* setting) {
|
||||
if (!GeodeSettingNode<FloatSetting>::init(setting))
|
||||
return false;
|
||||
|
||||
auto controls = CCArray::create();
|
||||
CCScale9Sprite* bgSprite = nullptr;
|
||||
if (setting->hasInput()) {
|
||||
bgSprite = CCScale9Sprite::create(
|
||||
"square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }
|
||||
);
|
||||
bgSprite->setScale(.25f);
|
||||
bgSprite->setColor({ 0, 0, 0 });
|
||||
bgSprite->setOpacity(75);
|
||||
bgSprite->setContentSize({ 45.f * 4, m_height * 3 });
|
||||
bgSprite->setPosition(-20, 0);
|
||||
m_buttonMenu->addChild(bgSprite);
|
||||
controls->addObject(bgSprite);
|
||||
}
|
||||
|
||||
m_valueInput = MenuInputNode::create(45.f, m_height, "Num", "bigFont.fnt");
|
||||
m_valueInput->setPosition(-20.f, .0f);
|
||||
m_valueInput->getInput()->setAllowedChars("0123456789+-. ");
|
||||
m_valueInput->getInput()->setMaxLabelScale(.5f);
|
||||
m_valueInput->getInput()->setLabelPlaceholderColor({ 150, 150, 150 });
|
||||
m_valueInput->getInput()->setLabelPlaceholderScale(.5f);
|
||||
m_valueInput->getInput()->setDelegate(this);
|
||||
m_valueInput->setEnabled(setting->hasInput());
|
||||
m_buttonMenu->addChild(m_valueInput);
|
||||
controls->addObject(m_valueInput);
|
||||
|
||||
CCMenuItemSpriteExtra* decBtn = nullptr;
|
||||
CCMenuItemSpriteExtra* incBtn = nullptr;
|
||||
if (setting->hasArrows()) {
|
||||
m_valueInput->setPositionX(-30.f);
|
||||
if (bgSprite) bgSprite->setPositionX(-30.f);
|
||||
|
||||
auto decSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png");
|
||||
decSpr->setScale(.3f);
|
||||
decSpr->setFlipX(true);
|
||||
|
||||
decBtn = CCMenuItemSpriteExtra::create(
|
||||
decSpr, this, menu_selector(FloatSettingNode::onArrow)
|
||||
);
|
||||
decBtn->setTag(-1);
|
||||
decBtn->setPosition(-60.f, 0);
|
||||
m_buttonMenu->addChild(decBtn);
|
||||
controls->addObject(decBtn);
|
||||
|
||||
auto incSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png");
|
||||
incSpr->setScale(.3f);
|
||||
|
||||
incBtn = CCMenuItemSpriteExtra::create(
|
||||
incSpr, this, menu_selector(FloatSettingNode::onArrow)
|
||||
);
|
||||
incBtn->setTag(1);
|
||||
incBtn->setPosition(0.f, 0);
|
||||
m_buttonMenu->addChild(incBtn);
|
||||
controls->addObject(incBtn);
|
||||
}
|
||||
|
||||
if (setting->hasSlider()) {
|
||||
m_height += 20.f;
|
||||
this->setContentSize({ m_width, m_height });
|
||||
m_backgroundLayer->setContentSize(this->getContentSize());
|
||||
|
||||
m_nameLabel->setPositionY(m_nameLabel->getPositionY() + 10.f);
|
||||
if (m_descButton) {
|
||||
m_descButton->setPositionY(m_descButton->getPositionY() - 8.f);
|
||||
}
|
||||
m_buttonMenu->setPositionY(m_buttonMenu->getPositionY() + 18.f);
|
||||
|
||||
CCARRAY_FOREACH_B_TYPE(controls, node, CCNode) {
|
||||
node->setPositionX(node->getPositionX() - (setting->hasArrows() ? 35.f : 45.f));
|
||||
}
|
||||
|
||||
m_slider = Slider::create(this, menu_selector(FloatSettingNode::onSlider), .65f);
|
||||
m_slider->setPosition(-65.f, -22.f);
|
||||
m_slider->setValue(
|
||||
// normalized to 0-1
|
||||
(m_value - m_setting->getMin()) /
|
||||
(m_setting->getMax() - m_setting->getMin())
|
||||
);
|
||||
m_buttonMenu->addChild(m_slider);
|
||||
}
|
||||
|
||||
this->updateValue();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FloatSettingNode::textChanged(CCTextInputNode* input) {
|
||||
try {
|
||||
m_value = std::stof(input->getString());
|
||||
} catch(...) {}
|
||||
this->updateValue(false);
|
||||
}
|
||||
|
||||
void FloatSettingNode::textInputClosed(CCTextInputNode* input) {
|
||||
try {
|
||||
m_value = std::stof(input->getString());
|
||||
} catch(...) {}
|
||||
this->updateValue();
|
||||
}
|
||||
|
||||
void FloatSettingNode::onSlider(CCObject* pSender) {
|
||||
m_value =
|
||||
as<SliderThumb*>(pSender)->getValue() *
|
||||
(m_setting->getMax() - m_setting->getMin()) +
|
||||
m_setting->getMin();
|
||||
m_valueInput->getInput()->detachWithIME();
|
||||
this->updateValue();
|
||||
}
|
||||
|
||||
void FloatSettingNode::onArrow(CCObject* pSender) {
|
||||
m_value = m_value + pSender->getTag() * m_setting->getStep();
|
||||
this->updateValue();
|
||||
}
|
||||
|
||||
void FloatSettingNode::updateValue(bool updateInput) {
|
||||
if (m_value < m_setting->getMin()) {
|
||||
m_value = m_setting->getMin();
|
||||
}
|
||||
if (m_value > m_setting->getMax()) {
|
||||
m_value = m_setting->getMax();
|
||||
}
|
||||
if (updateInput) {
|
||||
m_valueInput->getInput()->setString(toStringWithPrecision(
|
||||
m_value, m_setting->getPrecision()
|
||||
).c_str());
|
||||
}
|
||||
if (m_slider) {
|
||||
m_slider->setValue(
|
||||
// normalized to 0-1
|
||||
(m_value - m_setting->getMin()) /
|
||||
(m_setting->getMax() - m_setting->getMin())
|
||||
);
|
||||
m_slider->updateBar();
|
||||
}
|
||||
this->updateSettingsList();
|
||||
}
|
||||
|
||||
void FloatSettingNode::updateState() {
|
||||
this->updateValue();
|
||||
}
|
||||
|
||||
// string
|
||||
|
||||
bool StringSettingNode::init(StringSetting* setting) {
|
||||
if (!GeodeSettingNode<StringSetting>::init(setting))
|
||||
return false;
|
||||
|
||||
auto bgSprite = CCScale9Sprite::create(
|
||||
"square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }
|
||||
);
|
||||
bgSprite->setScale(.25f);
|
||||
bgSprite->setColor({ 0, 0, 0 });
|
||||
bgSprite->setOpacity(75);
|
||||
bgSprite->setContentSize({ (m_width / 2 - 30.f) * 4, m_height * 3 });
|
||||
bgSprite->setPosition(-m_width / 4 + 18.f, 0);
|
||||
m_buttonMenu->addChild(bgSprite);
|
||||
|
||||
m_input = MenuInputNode::create(
|
||||
m_width / 2 - 30.f, m_height,
|
||||
"...", "bigFont.fnt"
|
||||
);
|
||||
m_input->setPositionX(-m_width / 4 + 18.f);
|
||||
m_input->getInput()->setAllowedChars(setting->getFilter());
|
||||
m_input->getInput()->setMaxLabelScale(.5f);
|
||||
m_input->getInput()->setLabelPlaceholderColor({ 150, 150, 150 });
|
||||
m_input->getInput()->setLabelPlaceholderScale(.5f);
|
||||
m_input->getInput()->setDelegate(this);
|
||||
m_buttonMenu->addChild(m_input);
|
||||
|
||||
m_input->getInput()->setString(m_value.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void StringSettingNode::textChanged(CCTextInputNode* input) {
|
||||
m_value = input->getString();
|
||||
this->updateSettingsList();
|
||||
}
|
||||
|
||||
void StringSettingNode::updateState() {
|
||||
m_input->getInput()->setString(m_value);
|
||||
}
|
||||
|
||||
// color
|
||||
|
||||
bool ColorSettingNode::init(ColorSetting* setting) {
|
||||
if (!GeodeSettingNode<ColorSetting>::init(setting))
|
||||
return false;
|
||||
|
||||
m_colorSprite = ColorChannelSprite::create();
|
||||
m_colorSprite->setColor(setting->getValue());
|
||||
m_colorSprite->setScale(.65f);
|
||||
|
||||
auto button = CCMenuItemSpriteExtra::create(
|
||||
m_colorSprite, this, menu_selector(ColorSettingNode::onPickColor)
|
||||
);
|
||||
button->setPosition({ -m_colorSprite->getScaledContentSize().width / 2, 0 });
|
||||
m_buttonMenu->addChild(button);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ColorSettingNode::onPickColor(CCObject*) {
|
||||
ColorPickPopup::create(this)->show();
|
||||
}
|
||||
|
||||
void ColorSettingNode::updateState() {
|
||||
m_colorSprite->setColor(m_value);
|
||||
}
|
||||
|
||||
// rgba
|
||||
|
||||
bool ColorAlphaSettingNode::init(ColorAlphaSetting* setting) {
|
||||
if (!GeodeSettingNode<ColorAlphaSetting>::init(setting))
|
||||
return false;
|
||||
|
||||
m_colorSprite = ColorChannelSprite::create();
|
||||
m_colorSprite->setColor(to3B(setting->getValue()));
|
||||
m_colorSprite->updateOpacity(setting->getValue().a / 255.f);
|
||||
m_colorSprite->setScale(.65f);
|
||||
|
||||
auto button = CCMenuItemSpriteExtra::create(
|
||||
m_colorSprite, this, menu_selector(ColorAlphaSettingNode::onPickColor)
|
||||
);
|
||||
button->setPosition({ -m_colorSprite->getScaledContentSize().width / 2, 0 });
|
||||
m_buttonMenu->addChild(button);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ColorAlphaSettingNode::onPickColor(CCObject*) {
|
||||
ColorPickPopup::create(this)->show();
|
||||
}
|
||||
|
||||
void ColorAlphaSettingNode::updateState() {
|
||||
m_colorSprite->setColor(to3B(m_value));
|
||||
}
|
||||
|
||||
// path
|
||||
|
||||
bool PathSettingNode::init(PathSetting* setting) {
|
||||
if (!GeodeSettingNode<PathSetting>::init(setting))
|
||||
return false;
|
||||
|
||||
auto bgSprite = CCScale9Sprite::create(
|
||||
"square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }
|
||||
);
|
||||
bgSprite->setScale(.25f);
|
||||
bgSprite->setColor({ 0, 0, 0 });
|
||||
bgSprite->setOpacity(75);
|
||||
bgSprite->setContentSize({ (m_width / 2 - 50.f) * 4, m_height * 3 });
|
||||
bgSprite->setPosition(-m_width / 4, 0);
|
||||
m_buttonMenu->addChild(bgSprite);
|
||||
|
||||
m_input = MenuInputNode::create(
|
||||
m_width / 2 - 50.f, m_height,
|
||||
"...", "chatFont.fnt"
|
||||
);
|
||||
m_input->setPositionX(-m_width / 4);
|
||||
m_input->getInput()->setAllowedChars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,/\\_-.+%#@!';");
|
||||
m_input->getInput()->setMaxLabelScale(.7f);
|
||||
m_input->getInput()->setLabelPlaceholderColor({ 150, 150, 150 });
|
||||
m_input->getInput()->setLabelPlaceholderScale(.7f);
|
||||
m_input->getInput()->setDelegate(this);
|
||||
m_buttonMenu->addChild(m_input);
|
||||
|
||||
auto folder = CCSprite::createWithSpriteFrameName("gj_folderBtn_001.png");
|
||||
folder->setScale(.65f);
|
||||
auto button = CCMenuItemSpriteExtra::create(
|
||||
folder, this, menu_selector(PathSettingNode::onSelectFile)
|
||||
);
|
||||
button->setPosition({ -folder->getScaledContentSize().width / 2, 0 });
|
||||
m_buttonMenu->addChild(button);
|
||||
|
||||
m_input->getInput()->setString(m_value.string().c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PathSettingNode::onSelectFile(CCObject*) {
|
||||
FLAlertLayer::create("todo", "implement file picker", "OK")->show();
|
||||
this->updateSettingsList();
|
||||
}
|
||||
|
||||
void PathSettingNode::textChanged(CCTextInputNode* input) {
|
||||
m_value = input->getString();
|
||||
this->updateSettingsList();
|
||||
}
|
||||
|
||||
void PathSettingNode::updateState() {
|
||||
m_input->getInput()->setString(m_value.string());
|
||||
}
|
||||
|
||||
// string[]
|
||||
|
||||
bool StringSelectSettingNode::init(StringSelectSetting* setting) {
|
||||
if (!GeodeSettingNode<StringSelectSetting>::init(setting))
|
||||
return false;
|
||||
|
||||
m_selectedLabel = CCLabelBMFont::create(setting->getValue().c_str(), "bigFont.fnt");
|
||||
m_selectedLabel->setPosition(-m_width / 4 + 20.f, .0f);
|
||||
m_selectedLabel->limitLabelWidth(m_width / 2 - 60.f, .5f, .1f);
|
||||
m_buttonMenu->addChild(m_selectedLabel);
|
||||
|
||||
auto decSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png");
|
||||
decSpr->setScale(.3f);
|
||||
decSpr->setFlipX(true);
|
||||
|
||||
auto decBtn = CCMenuItemSpriteExtra::create(
|
||||
decSpr, this, menu_selector(StringSelectSettingNode::onChange)
|
||||
);
|
||||
decBtn->setTag(-1);
|
||||
decBtn->setPosition(-m_width / 2 + 40.f, 0);
|
||||
m_buttonMenu->addChild(decBtn);
|
||||
|
||||
auto incSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png");
|
||||
incSpr->setScale(.3f);
|
||||
|
||||
auto incBtn = CCMenuItemSpriteExtra::create(
|
||||
incSpr, this, menu_selector(StringSelectSettingNode::onChange)
|
||||
);
|
||||
incBtn->setTag(1);
|
||||
incBtn->setPosition(0.f, 0);
|
||||
m_buttonMenu->addChild(incBtn);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void StringSelectSettingNode::onChange(CCObject* pSender) {
|
||||
if (pSender) {
|
||||
long newValue = m_value + pSender->getTag();
|
||||
if (newValue < 0) m_value = m_setting->getOptions().size() - 1;
|
||||
else if (newValue > static_cast<long>(m_setting->getOptions().size() - 1)) m_value = 0;
|
||||
else m_value = newValue;
|
||||
}
|
||||
|
||||
m_selectedLabel->setString(m_setting->getValueAt(m_value).c_str());
|
||||
m_selectedLabel->limitLabelWidth(m_width / 2 - 60.f, .5f, .1f);
|
||||
this->updateSettingsList();
|
||||
}
|
||||
|
||||
void StringSelectSettingNode::updateState() {
|
||||
this->onChange(nullptr);
|
||||
}
|
||||
|
||||
// custom
|
||||
|
||||
bool CustomSettingPlaceHolderNode::init(CustomSettingPlaceHolder* setting, bool isLoaded) {
|
||||
if (!CCNode::init())
|
||||
return false;
|
||||
|
||||
auto pad = m_height;
|
||||
this->setContentSize({ m_width, m_height });
|
||||
m_backgroundLayer->setContentSize(this->getContentSize());
|
||||
|
||||
// i'm using TextRenderer instead of TextArea because
|
||||
// i couldn't get TextArea to work for some reason
|
||||
// and TextRenderer should be fast enough for short
|
||||
// static text
|
||||
|
||||
auto render = TextRenderer::create();
|
||||
|
||||
render->begin(this, CCPointZero, { m_width - pad * 2, m_height });
|
||||
|
||||
render->pushFont(
|
||||
[](int) -> TextRenderer::Label {
|
||||
return CCLabelBMFont::create("", "chatFont.fnt");
|
||||
}
|
||||
);
|
||||
render->pushHorizontalAlign(TextAlignment::Begin);
|
||||
render->pushVerticalAlign(TextAlignment::Begin);
|
||||
render->pushScale(.7f);
|
||||
auto rendered = render->renderString(
|
||||
isLoaded ?
|
||||
"This setting (id: " + setting->getKey() + ") is a "
|
||||
"custom setting which has no registered setting node. "
|
||||
"This is likely a bug in the mod; report it to the "
|
||||
"developer." :
|
||||
"This setting (id: " + setting->getKey() + ") is a "
|
||||
"custom setting, which means that you need to enable "
|
||||
"& load the mod to change its value."
|
||||
);
|
||||
|
||||
render->end(true, TextAlignment::Center, TextAlignment::Center);
|
||||
render->release();
|
||||
|
||||
m_height = this->getContentSize().height + pad / 4;
|
||||
m_width = this->getContentSize().width;
|
||||
m_width += pad * 2;
|
||||
this->setContentSize({ m_width, m_height });
|
||||
m_backgroundLayer->setContentSize(this->getContentSize());
|
||||
|
||||
for (auto& label : rendered) {
|
||||
label.m_node->setPositionX(pad * 1.5f);
|
||||
label.m_node->setPositionY(label.m_node->getPositionY() + pad / 8);
|
||||
}
|
||||
|
||||
auto bgSprite = CCScale9Sprite::create(
|
||||
"square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }
|
||||
);
|
||||
bgSprite->setScale(.5f);
|
||||
bgSprite->setColor({ 0, 0, 0 });
|
||||
bgSprite->setOpacity(75);
|
||||
bgSprite->setZOrder(-1);
|
||||
bgSprite->setContentSize(m_obContentSize * 2 - CCSize { pad, 0 });
|
||||
bgSprite->setPosition(m_obContentSize / 2);
|
||||
this->addChild(bgSprite);
|
||||
|
||||
auto iconSprite = CCSprite::createWithSpriteFrameName(
|
||||
isLoaded ? "info-warning.png"_spr : "GJ_infoIcon_001.png"
|
||||
);
|
||||
iconSprite->setPosition({ pad * .9f, m_height / 2 });
|
||||
iconSprite->setScale(.8f);
|
||||
this->addChild(iconSprite);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CustomSettingPlaceHolderNode* CustomSettingPlaceHolderNode::create(CustomSettingPlaceHolder* setting, bool isLoaded, float width) {
|
||||
auto ret = new CustomSettingPlaceHolderNode(width, 30.f);
|
||||
if (ret && ret->init(setting, isLoaded)) {
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
CC_SAFE_DELETE(ret);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ColorPickPopup::setup(
|
||||
ColorSettingNode* colorNode,
|
||||
ColorAlphaSettingNode* alphaNode
|
||||
) {
|
||||
m_noElasticity = true;
|
||||
|
||||
m_colorNode = colorNode;
|
||||
m_colorAlphaNode = alphaNode;
|
||||
|
||||
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
||||
|
||||
auto picker = CCControlColourPicker::colourPicker();
|
||||
picker->setColorValue(
|
||||
colorNode ?
|
||||
colorNode->m_value :
|
||||
to3B(alphaNode->m_value)
|
||||
);
|
||||
picker->setColorTarget(colorNode ?
|
||||
colorNode->m_colorSprite :
|
||||
alphaNode->m_colorSprite
|
||||
);
|
||||
picker->setPosition(winSize.width / 2, winSize.height / 2 + (colorNode ? 0.f : 20.f));
|
||||
picker->setDelegate(this);
|
||||
m_mainLayer->addChild(picker);
|
||||
|
||||
if (alphaNode) {
|
||||
m_alphaSlider = Slider::create(this, menu_selector(ColorPickPopup::onSlider), .75f);
|
||||
m_alphaSlider->setPosition(winSize.width / 2, winSize.height / 2 - 90.f);
|
||||
m_alphaSlider->setValue(alphaNode->m_value.a / 255.f);
|
||||
m_alphaSlider->updateBar();
|
||||
m_mainLayer->addChild(m_alphaSlider);
|
||||
|
||||
auto bgSprite = CCScale9Sprite::create(
|
||||
"square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }
|
||||
);
|
||||
bgSprite->setScale(.25f);
|
||||
bgSprite->setColor({ 0, 0, 0 });
|
||||
bgSprite->setOpacity(75);
|
||||
bgSprite->setContentSize({ 200.f, 100.f });
|
||||
bgSprite->setPosition(winSize.width / 2 + 115.f, winSize.height / 2 - 90.f);
|
||||
m_mainLayer->addChild(bgSprite);
|
||||
|
||||
m_alphaInput = MenuInputNode::create(50.f, 25.f, "...", "bigFont.fnt");
|
||||
m_alphaInput->setPosition(winSize.width / 2 + 115.f, winSize.height / 2 - 90.f);
|
||||
m_alphaInput->getInput()->setDelegate(this);
|
||||
m_alphaInput->getInput()->setAllowedChars("0123456789. ");
|
||||
m_alphaInput->getInput()->setString(
|
||||
toStringWithPrecision(alphaNode->m_value.a / 255.f, 2, false)
|
||||
);
|
||||
m_alphaInput->setScale(.8f);
|
||||
m_mainLayer->addChild(m_alphaInput);
|
||||
}
|
||||
}
|
||||
|
||||
void ColorPickPopup::onSlider(CCObject* pSender) {
|
||||
if (m_colorAlphaNode) {
|
||||
m_colorAlphaNode->m_value = to4B(
|
||||
to3B(m_colorAlphaNode->m_value),
|
||||
static_cast<GLubyte>(as<SliderThumb*>(pSender)->getValue() * 255.f)
|
||||
);
|
||||
m_alphaInput->getInput()->setString(
|
||||
toStringWithPrecision(as<SliderThumb*>(pSender)->getValue(), 2, false)
|
||||
);
|
||||
m_colorAlphaNode->m_colorSprite->updateOpacity(
|
||||
m_colorAlphaNode->m_value.a / 255.f
|
||||
);
|
||||
m_colorAlphaNode->updateSettingsList();
|
||||
}
|
||||
}
|
||||
|
||||
void ColorPickPopup::textChanged(CCTextInputNode* input) {
|
||||
try {
|
||||
m_colorAlphaNode->m_value = to4B(
|
||||
to3B(m_colorAlphaNode->m_value),
|
||||
static_cast<GLubyte>(std::stof(input->getString()) * 255.f)
|
||||
);
|
||||
m_colorAlphaNode->m_colorSprite->updateOpacity(
|
||||
m_colorAlphaNode->m_value.a / 255.f
|
||||
);
|
||||
m_alphaSlider->setValue(std::stof(input->getString()));
|
||||
m_colorAlphaNode->updateSettingsList();
|
||||
} catch(...) {}
|
||||
}
|
||||
|
||||
void ColorPickPopup::colorValueChanged(ccColor3B color) {
|
||||
if (m_colorNode) {
|
||||
m_colorNode->m_value = color;
|
||||
m_colorNode->updateSettingsList();
|
||||
} else {
|
||||
m_colorAlphaNode->m_value = to4B(color, m_colorAlphaNode->m_value.a);
|
||||
m_colorAlphaNode->updateSettingsList();
|
||||
}
|
||||
}
|
||||
|
||||
ColorPickPopup* ColorPickPopup::create(
|
||||
ColorSettingNode* colorNode,
|
||||
ColorAlphaSettingNode* colorAlphaNode
|
||||
) {
|
||||
auto ret = new ColorPickPopup;
|
||||
if (ret && ret->init(320.f, 250.f, colorNode, colorAlphaNode, "GJ_square02.png")) {
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
CC_SAFE_DELETE(ret);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ColorPickPopup* ColorPickPopup::create(ColorSettingNode* colorNode) {
|
||||
return ColorPickPopup::create(colorNode, nullptr);
|
||||
}
|
||||
|
||||
ColorPickPopup* ColorPickPopup::create(ColorAlphaSettingNode* colorNode) {
|
||||
return ColorPickPopup::create(nullptr, colorNode);
|
||||
}
|
||||
|
||||
GEODE_GENERATE_SETTING_CREATE(BoolSetting, 30.f);
|
||||
GEODE_GENERATE_SETTING_CREATE(IntSetting, 30.f);
|
||||
GEODE_GENERATE_SETTING_CREATE(FloatSetting, 30.f);
|
||||
GEODE_GENERATE_SETTING_CREATE(StringSetting, 30.f);
|
||||
GEODE_GENERATE_SETTING_CREATE(ColorSetting, 30.f);
|
||||
GEODE_GENERATE_SETTING_CREATE(ColorAlphaSetting, 30.f);
|
||||
GEODE_GENERATE_SETTING_CREATE(PathSetting, 30.f);
|
||||
GEODE_GENERATE_SETTING_CREATE(StringSelectSetting, 30.f);*/
|
|
@ -1,282 +0,0 @@
|
|||
/*#pragma once
|
||||
|
||||
#pragma warning(disable: 4067)
|
||||
|
||||
#include <Geode.hpp>
|
||||
#include <nodes/MenuInputNode.hpp>
|
||||
#include <nodes/Popup.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
namespace geode {
|
||||
class ColorPickPopup;
|
||||
|
||||
template<class SettingClass>
|
||||
class GeodeSettingNode : public SettingNode {
|
||||
protected:
|
||||
SettingClass* m_setting;
|
||||
CCMenu* m_buttonMenu;
|
||||
CCLabelBMFont* m_nameLabel;
|
||||
CCMenuItemSpriteExtra* m_descButton = nullptr;
|
||||
typename SettingClass::value_type_t m_value;
|
||||
|
||||
bool init(SettingClass* setting) {
|
||||
if (!CCNode::init())
|
||||
return false;
|
||||
|
||||
m_setting = setting;
|
||||
if constexpr (std::is_same_v<typename SettingClass::value_type_t, size_t>) {
|
||||
m_value = m_setting->getIndex();
|
||||
} else {
|
||||
m_value = m_setting->getValue();
|
||||
}
|
||||
|
||||
this->setContentSize({ m_width, m_height });
|
||||
|
||||
auto text = setting->getName().size() ? setting->getName() : setting->getKey();
|
||||
m_nameLabel = CCLabelBMFont::create(text.c_str(), "bigFont.fnt");
|
||||
m_nameLabel->setAnchorPoint({ .0f, .5f });
|
||||
m_nameLabel->setPosition(m_height / 2, m_height / 2);
|
||||
m_nameLabel->limitLabelWidth(m_width / 2 - 40.f, .5f, .1f);
|
||||
this->addChild(m_nameLabel);
|
||||
|
||||
m_buttonMenu = CCMenu::create();
|
||||
m_buttonMenu->setPosition(m_width - m_height / 2, m_height / 2);
|
||||
this->addChild(m_buttonMenu);
|
||||
|
||||
if (m_setting->getDescription().size()) {
|
||||
auto descBtnSpr = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png");
|
||||
descBtnSpr->setScale(.7f);
|
||||
m_descButton = CCMenuItemSpriteExtra::create(
|
||||
descBtnSpr, this, menu_selector(GeodeSettingNode::onDescription)
|
||||
);
|
||||
m_descButton->setPosition(
|
||||
-m_buttonMenu->getPositionX() +
|
||||
m_nameLabel->getPositionX() +
|
||||
m_nameLabel->getScaledContentSize().width +
|
||||
m_height / 2,
|
||||
.0f
|
||||
);
|
||||
m_buttonMenu->addChild(m_descButton);
|
||||
}
|
||||
|
||||
CCDirector::sharedDirector()->getTouchDispatcher()->incrementForcePrio(2);
|
||||
this->registerWithTouchDispatcher();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void onDescription(CCObject*) {
|
||||
FLAlertLayer::create(
|
||||
m_nameLabel->getString(),
|
||||
m_setting->getDescription(),
|
||||
"OK"
|
||||
)->show();
|
||||
}
|
||||
|
||||
bool hasUnsavedChanges() const override {
|
||||
if constexpr (std::is_same_v<typename SettingClass::value_type_t, size_t>) {
|
||||
return m_value != m_setting->getIndex();
|
||||
} else {
|
||||
return m_value != m_setting->getValue();
|
||||
}
|
||||
}
|
||||
|
||||
void commitChanges() override {
|
||||
if constexpr (std::is_same_v<typename SettingClass::value_type_t, size_t>) {
|
||||
m_setting->setIndex(m_value);
|
||||
} else {
|
||||
m_setting->setValue(m_value);
|
||||
}
|
||||
}
|
||||
|
||||
void resetToDefault() override {
|
||||
m_setting->resetToDefault();
|
||||
if constexpr (std::is_same_v<typename SettingClass::value_type_t, size_t>) {
|
||||
m_value = m_setting->getIndex();
|
||||
} else {
|
||||
m_value = m_setting->getValue();
|
||||
}
|
||||
this->updateState();
|
||||
}
|
||||
|
||||
virtual void updateState() = 0;
|
||||
|
||||
~GeodeSettingNode() {
|
||||
CCDirector::sharedDirector()->getTouchDispatcher()->decrementForcePrio(2);
|
||||
}
|
||||
|
||||
GeodeSettingNode(float width, float height) : SettingNode(width, height) {}
|
||||
};
|
||||
|
||||
class BoolSettingNode : public GeodeSettingNode<BoolSetting> {
|
||||
protected:
|
||||
CCMenuItemToggler* m_toggle;
|
||||
|
||||
bool init(BoolSetting* setting);
|
||||
|
||||
void onToggle(CCObject*);
|
||||
void updateState() override;
|
||||
|
||||
BoolSettingNode(float width, float height) : GeodeSettingNode<BoolSetting>(width, height) {}
|
||||
|
||||
public:
|
||||
static BoolSettingNode* create(BoolSetting* setting, float width);
|
||||
};
|
||||
|
||||
class IntSettingNode : public GeodeSettingNode<IntSetting>, public TextInputDelegate {
|
||||
protected:
|
||||
MenuInputNode* m_valueInput;
|
||||
Slider* m_slider = nullptr;
|
||||
|
||||
bool init(IntSetting* setting);
|
||||
|
||||
void updateValue(bool updateInput = true);
|
||||
void onArrow(CCObject*);
|
||||
void onSlider(CCObject*);
|
||||
void textInputClosed(CCTextInputNode* input) override;
|
||||
void textChanged(CCTextInputNode* input) override;
|
||||
void updateState() override;
|
||||
|
||||
IntSettingNode(float width, float height) : GeodeSettingNode<IntSetting>(width, height) {}
|
||||
|
||||
public:
|
||||
static IntSettingNode* create(IntSetting* setting, float width);
|
||||
};
|
||||
|
||||
class FloatSettingNode : public GeodeSettingNode<FloatSetting>, public TextInputDelegate {
|
||||
protected:
|
||||
MenuInputNode* m_valueInput;
|
||||
Slider* m_slider = nullptr;
|
||||
|
||||
bool init(FloatSetting* setting);
|
||||
|
||||
void updateValue(bool updateInput = true);
|
||||
void onArrow(CCObject*);
|
||||
void onSlider(CCObject*);
|
||||
void textInputClosed(CCTextInputNode* input) override;
|
||||
void textChanged(CCTextInputNode* input) override;
|
||||
void updateState() override;
|
||||
|
||||
FloatSettingNode(float width, float height) : GeodeSettingNode<FloatSetting>(width, height) {}
|
||||
|
||||
public:
|
||||
static FloatSettingNode* create(FloatSetting* setting, float width);
|
||||
};
|
||||
|
||||
class StringSettingNode : public GeodeSettingNode<StringSetting>, public TextInputDelegate {
|
||||
protected:
|
||||
MenuInputNode* m_input;
|
||||
|
||||
bool init(StringSetting* setting);
|
||||
void textChanged(CCTextInputNode* input) override;
|
||||
void updateState() override;
|
||||
|
||||
StringSettingNode(float width, float height) : GeodeSettingNode<StringSetting>(width, height) {}
|
||||
|
||||
public:
|
||||
static StringSettingNode* create(StringSetting* setting, float width);
|
||||
};
|
||||
|
||||
class ColorSettingNode : public GeodeSettingNode<ColorSetting> {
|
||||
protected:
|
||||
ColorChannelSprite* m_colorSprite;
|
||||
|
||||
bool init(ColorSetting* setting);
|
||||
void onPickColor(CCObject*);
|
||||
void updateState() override;
|
||||
|
||||
ColorSettingNode(float width, float height) : GeodeSettingNode<ColorSetting>(width, height) {}
|
||||
|
||||
friend class ColorPickPopup;
|
||||
|
||||
public:
|
||||
static ColorSettingNode* create(ColorSetting* setting, float width);
|
||||
};
|
||||
|
||||
class ColorAlphaSettingNode : public GeodeSettingNode<ColorAlphaSetting> {
|
||||
protected:
|
||||
ColorChannelSprite* m_colorSprite;
|
||||
|
||||
bool init(ColorAlphaSetting* setting);
|
||||
void onPickColor(CCObject*);
|
||||
void updateState() override;
|
||||
|
||||
ColorAlphaSettingNode(float width, float height) : GeodeSettingNode<ColorAlphaSetting>(width, height) {}
|
||||
|
||||
friend class ColorPickPopup;
|
||||
|
||||
|
||||
public:
|
||||
static ColorAlphaSettingNode* create(ColorAlphaSetting* setting, float width);
|
||||
};
|
||||
|
||||
class PathSettingNode : public GeodeSettingNode<PathSetting>, public TextInputDelegate {
|
||||
protected:
|
||||
MenuInputNode* m_input;
|
||||
|
||||
bool init(PathSetting* setting);
|
||||
void textChanged(CCTextInputNode* input) override;
|
||||
void onSelectFile(CCObject*);
|
||||
void updateState() override;
|
||||
|
||||
PathSettingNode(float width, float height) : GeodeSettingNode<PathSetting>(width, height) {}
|
||||
|
||||
public:
|
||||
static PathSettingNode* create(PathSetting* setting, float width);
|
||||
};
|
||||
|
||||
class StringSelectSettingNode : public GeodeSettingNode<StringSelectSetting> {
|
||||
protected:
|
||||
CCLabelBMFont* m_selectedLabel;
|
||||
|
||||
bool init(StringSelectSetting* setting);
|
||||
void onChange(CCObject* pSender);
|
||||
void updateState() override;
|
||||
|
||||
StringSelectSettingNode(float width, float height) : GeodeSettingNode<StringSelectSetting>(width, height) {}
|
||||
|
||||
public:
|
||||
static StringSelectSettingNode* create(StringSelectSetting* setting, float width);
|
||||
};
|
||||
|
||||
class CustomSettingPlaceHolderNode : public SettingNode {
|
||||
protected:
|
||||
bool init(CustomSettingPlaceHolder* setting, bool isLoaded);
|
||||
|
||||
bool hasUnsavedChanges() const override {
|
||||
return false;
|
||||
}
|
||||
void commitChanges() override {}
|
||||
void resetToDefault() override {}
|
||||
|
||||
CustomSettingPlaceHolderNode(float width, float height) : SettingNode(width, height) {}
|
||||
|
||||
public:
|
||||
static CustomSettingPlaceHolderNode* create(CustomSettingPlaceHolder* setting, bool isLoaded, float width);
|
||||
};
|
||||
|
||||
class ColorPickPopup :
|
||||
public Popup<ColorPickPopup, ColorSettingNode*, ColorAlphaSettingNode*>,
|
||||
public ColorPickerDelegate,
|
||||
public TextInputDelegate
|
||||
{
|
||||
protected:
|
||||
ColorSettingNode* m_colorNode = nullptr;
|
||||
ColorAlphaSettingNode* m_colorAlphaNode = nullptr;
|
||||
MenuInputNode* m_alphaInput = nullptr;
|
||||
Slider* m_alphaSlider = nullptr;
|
||||
|
||||
void setup(ColorSettingNode* colorNode, ColorAlphaSettingNode* alphaNode) override;
|
||||
void colorValueChanged(ccColor3B) override;
|
||||
void textChanged(CCTextInputNode* input) override;
|
||||
void onSlider(CCObject*);
|
||||
|
||||
public:
|
||||
static ColorPickPopup* create(ColorSettingNode* colorNode, ColorAlphaSettingNode* colorAlphaNode);
|
||||
static ColorPickPopup* create(ColorSettingNode* colorNode);
|
||||
static ColorPickPopup* create(ColorAlphaSettingNode* colorNode);
|
||||
};
|
||||
}
|
||||
|
||||
#pragma warning(default: 4067)*/
|
|
@ -1,194 +0,0 @@
|
|||
/*#include "ModSettingsLayer.hpp"
|
||||
#include "../settings/ModSettingsList.hpp"
|
||||
|
||||
bool ModSettingsLayer::init(Mod* mod) {
|
||||
m_noElasticity = true;
|
||||
m_mod = mod;
|
||||
|
||||
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
||||
const CCSize size { 440.f, 290.f };
|
||||
|
||||
if (!this->initWithColor({ 0, 0, 0, 105 })) return false;
|
||||
m_mainLayer = CCLayer::create();
|
||||
this->addChild(m_mainLayer);
|
||||
|
||||
auto bg = CCScale9Sprite::create("GJ_square01.png", { 0.0f, 0.0f, 80.0f, 80.0f });
|
||||
bg->setContentSize(size);
|
||||
bg->setPosition(winSize.width / 2, winSize.height / 2);
|
||||
m_mainLayer->addChild(bg);
|
||||
|
||||
m_buttonMenu = CCMenu::create();
|
||||
m_buttonMenu->setZOrder(10);
|
||||
m_mainLayer->addChild(m_buttonMenu);
|
||||
|
||||
CCDirector::sharedDirector()->getTouchDispatcher()->incrementForcePrio(2);
|
||||
this->registerWithTouchDispatcher();
|
||||
|
||||
auto nameStr = m_mod->getName() + " Settings";
|
||||
auto nameLabel = CCLabelBMFont::create(
|
||||
nameStr.c_str(), "bigFont.fnt"
|
||||
);
|
||||
nameLabel->setPosition(winSize.width / 2, winSize.height / 2 + 120.f);
|
||||
nameLabel->setScale(.7f);
|
||||
m_mainLayer->addChild(nameLabel, 2);
|
||||
|
||||
const CCSize listSize { 350.f, 200.f };
|
||||
|
||||
auto bgSprite = CCScale9Sprite::create(
|
||||
"square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }
|
||||
);
|
||||
bgSprite->setScale(.5f);
|
||||
bgSprite->setColor({ 0, 0, 0 });
|
||||
bgSprite->setOpacity(75);
|
||||
bgSprite->setContentSize(listSize * 2);
|
||||
bgSprite->setPosition(winSize.width / 2, winSize.height / 2);
|
||||
m_mainLayer->addChild(bgSprite);
|
||||
|
||||
m_list = ModSettingsList::create(mod, this, listSize.width, listSize.height);
|
||||
m_list->setPosition(winSize.width / 2 - listSize.width / 2, winSize.height / 2 - listSize.height / 2);
|
||||
m_mainLayer->addChild(m_list);
|
||||
|
||||
{
|
||||
auto topSprite = CCSprite::createWithSpriteFrameName("GJ_commentTop_001.png");
|
||||
topSprite->setPosition({ winSize.width / 2, winSize.height / 2 + listSize.height / 2 - 5.f });
|
||||
m_mainLayer->addChild(topSprite);
|
||||
|
||||
auto bottomSprite = CCSprite::createWithSpriteFrameName("GJ_commentTop_001.png");
|
||||
bottomSprite->setFlipY(true);
|
||||
bottomSprite->setPosition({ winSize.width / 2, winSize.height / 2 - listSize.height / 2 + 5.f });
|
||||
m_mainLayer->addChild(bottomSprite);
|
||||
|
||||
auto leftSprite = CCSprite::createWithSpriteFrameName("GJ_commentSide_001.png");
|
||||
leftSprite->setPosition({ winSize.width / 2 - listSize.width / 2 + 1.5f, winSize.height / 2 });
|
||||
leftSprite->setScaleY(5.7f);
|
||||
m_mainLayer->addChild(leftSprite);
|
||||
|
||||
auto rightSprite = CCSprite::createWithSpriteFrameName("GJ_commentSide_001.png");
|
||||
rightSprite->setFlipX(true);
|
||||
rightSprite->setPosition({ winSize.width / 2 + listSize.width / 2 - 1.5f, winSize.height / 2 });
|
||||
rightSprite->setScaleY(5.7f);
|
||||
m_mainLayer->addChild(rightSprite);
|
||||
}
|
||||
|
||||
m_applyBtnSpr = ButtonSprite::create("Apply", "goldFont.fnt", "GJ_button_01.png", .8f);
|
||||
m_applyBtnSpr->setScale(.8f);
|
||||
|
||||
auto applyBtn = CCMenuItemSpriteExtra::create(
|
||||
m_applyBtnSpr,
|
||||
this,
|
||||
menu_selector(ModSettingsLayer::onApply)
|
||||
);
|
||||
applyBtn->setPosition(size.width / 2 - 80.f, -size.height / 2 + 25.f);
|
||||
m_buttonMenu->addChild(applyBtn);
|
||||
|
||||
|
||||
auto resetBtnSpr = ButtonSprite::create("Reset to\nDefault", "bigFont.fnt", "GJ_button_05.png", .8f);
|
||||
resetBtnSpr->setScale(.4f);
|
||||
|
||||
auto resetBtn = CCMenuItemSpriteExtra::create(
|
||||
resetBtnSpr,
|
||||
this,
|
||||
menu_selector(ModSettingsLayer::onResetAllToDefault)
|
||||
);
|
||||
resetBtn->setPosition(size.width / 2 - 150.f, -size.height / 2 + 25.f);
|
||||
m_buttonMenu->addChild(resetBtn);
|
||||
|
||||
|
||||
auto closeSpr = CCSprite::createWithSpriteFrameName("GJ_closeBtn_001.png");
|
||||
closeSpr->setScale(.8f);
|
||||
|
||||
auto closeBtn = CCMenuItemSpriteExtra::create(
|
||||
closeSpr,
|
||||
this,
|
||||
menu_selector(ModSettingsLayer::onClose)
|
||||
);
|
||||
closeBtn->setPosition(-size.width / 2 + 3.f, size.height / 2 - 3.f);
|
||||
m_buttonMenu->addChild(closeBtn);
|
||||
|
||||
this->setKeypadEnabled(true);
|
||||
this->setTouchEnabled(true);
|
||||
|
||||
this->updateState();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModSettingsLayer::updateState() {
|
||||
if (m_list->hasUnsavedModifiedSettings()) {
|
||||
m_applyBtnSpr->setOpacity(255);
|
||||
m_applyBtnSpr->setColor({ 255, 255, 255 });
|
||||
} else {
|
||||
m_applyBtnSpr->setOpacity(155);
|
||||
m_applyBtnSpr->setColor({ 155, 155, 155 });
|
||||
}
|
||||
}
|
||||
|
||||
void ModSettingsLayer::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) {
|
||||
if (btn2) {
|
||||
switch (layer->getTag()) {
|
||||
case 0: this->close(); break;
|
||||
case 1: m_list->resetAllToDefault(); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModSettingsLayer::onResetAllToDefault(CCObject*) {
|
||||
auto layer = FLAlertLayer::create(
|
||||
this, "Reset Settings",
|
||||
"Are you sure you want to <co>reset</c> ALL settings?",
|
||||
"Cancel", "Reset", 400.f
|
||||
);
|
||||
layer->setTag(1);
|
||||
layer->show();
|
||||
}
|
||||
|
||||
void ModSettingsLayer::onApply(CCObject*) {
|
||||
if (!m_list->hasUnsavedModifiedSettings()) {
|
||||
FLAlertLayer::create(
|
||||
"No Changes",
|
||||
"No changes were made.",
|
||||
"OK"
|
||||
)->show();
|
||||
}
|
||||
m_list->applyChanges();
|
||||
this->updateState();
|
||||
}
|
||||
|
||||
void ModSettingsLayer::keyDown(enumKeyCodes key) {
|
||||
if (key == KEY_Escape)
|
||||
return this->onClose(nullptr);
|
||||
if (key == KEY_Space)
|
||||
return;
|
||||
return FLAlertLayer::keyDown(key);
|
||||
}
|
||||
|
||||
void ModSettingsLayer::onClose(CCObject*) {
|
||||
if (m_list->hasUnsavedModifiedSettings()) {
|
||||
auto layer = FLAlertLayer::create(
|
||||
this, "Unsaved Changes",
|
||||
"You have <cy>unsaved</c> settings! Are you sure "
|
||||
"you want to <cr>discard</c> your changes?",
|
||||
"Cancel", "Discard", 400.f
|
||||
);
|
||||
layer->setTag(0);
|
||||
layer->show();
|
||||
} else {
|
||||
this->close();
|
||||
}
|
||||
};
|
||||
|
||||
void ModSettingsLayer::close() {
|
||||
this->setKeyboardEnabled(false);
|
||||
this->removeFromParentAndCleanup(true);
|
||||
}
|
||||
|
||||
ModSettingsLayer* ModSettingsLayer::create(Mod* mod) {
|
||||
auto ret = new ModSettingsLayer();
|
||||
if (ret && ret->init(mod)) {
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
CC_SAFE_DELETE(ret);
|
||||
return nullptr;
|
||||
}*/
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
/*#pragma once
|
||||
|
||||
#include <Geode.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
class ModSettingsList;
|
||||
|
||||
class ModSettingsLayer : public FLAlertLayer, public FLAlertLayerProtocol {
|
||||
protected:
|
||||
Mod* m_mod;
|
||||
ModSettingsList* m_list;
|
||||
ButtonSprite* m_applyBtnSpr;
|
||||
|
||||
void FLAlert_Clicked(FLAlertLayer*, bool) override;
|
||||
|
||||
bool init(Mod* mod);
|
||||
|
||||
void onApply(CCObject*);
|
||||
void onResetAllToDefault(CCObject*);
|
||||
void keyDown(enumKeyCodes) override;
|
||||
void onClose(CCObject*);
|
||||
void close();
|
||||
|
||||
public:
|
||||
static ModSettingsLayer* create(Mod* Mod);
|
||||
|
||||
void updateState();
|
||||
};
|
||||
|
||||
*/
|
|
@ -1,109 +0,0 @@
|
|||
/*#include "ModSettingsList.hpp"
|
||||
#include <settings/SettingNodeManager.hpp>
|
||||
#include <settings/SettingNode.hpp>
|
||||
#include <utils/WackyGeodeMacros.hpp>
|
||||
#include "ModSettingsLayer.hpp"
|
||||
|
||||
bool ModSettingsList::init(Mod* mod, ModSettingsLayer* layer, float width, float height) {
|
||||
m_mod = mod;
|
||||
m_settingsLayer = layer;
|
||||
|
||||
m_scrollLayer = ScrollLayer::create({ width, height });
|
||||
this->addChild(m_scrollLayer);
|
||||
|
||||
if (mod->getSettings().size()) {
|
||||
float offset = 0.f;
|
||||
bool coloredBG = false;
|
||||
std::vector<CCNode*> gen;
|
||||
for (auto const& sett : mod->getSettings()) {
|
||||
auto node = SettingNodeManager::get()->generateNode(mod, sett, width);
|
||||
if (node) {
|
||||
m_settingNodes.push_back(node);
|
||||
node->m_list = this;
|
||||
if (coloredBG) {
|
||||
node->m_backgroundLayer->setColor({ 0, 0, 0 });
|
||||
node->m_backgroundLayer->setOpacity(50);
|
||||
}
|
||||
node->setPosition(
|
||||
0.f, offset - node->getScaledContentSize().height
|
||||
);
|
||||
m_scrollLayer->m_contentLayer->addChild(node);
|
||||
|
||||
auto separator = CCLayerColor::create({ 0, 0, 0, 50 }, width, 1.f);
|
||||
separator->setPosition(0.f, offset - .5f);
|
||||
m_scrollLayer->m_contentLayer->addChild(separator);
|
||||
gen.push_back(separator);
|
||||
|
||||
offset -= node->m_height;
|
||||
coloredBG = !coloredBG;
|
||||
gen.push_back(node);
|
||||
}
|
||||
}
|
||||
auto separator = CCLayerColor::create({ 0, 0, 0, 50 }, width, 1.f);
|
||||
separator->setPosition(0.f, offset);
|
||||
m_scrollLayer->m_contentLayer->addChild(separator);
|
||||
gen.push_back(separator);
|
||||
|
||||
offset = -offset;
|
||||
// to avoid needing to do moveToTopWithOffset,
|
||||
// just set the content size to the viewport
|
||||
// size if its less
|
||||
if (offset < height) offset = height;
|
||||
for (auto& node : gen) {
|
||||
node->setPositionY(node->getPositionY() + offset);
|
||||
}
|
||||
m_scrollLayer->m_contentLayer->setContentSize({ width, offset });
|
||||
m_scrollLayer->moveToTop();
|
||||
} else {
|
||||
auto label = CCLabelBMFont::create("This mod has no settings", "bigFont.fnt");
|
||||
label->setPosition(width / 2, height / 2);
|
||||
label->setScale(.5f);
|
||||
m_scrollLayer->m_contentLayer->addChild(label);
|
||||
m_scrollLayer->setContentSize({ width, height });
|
||||
m_scrollLayer->moveToTop();
|
||||
}
|
||||
|
||||
CCDirector::sharedDirector()->getTouchDispatcher()->incrementForcePrio(2);
|
||||
m_scrollLayer->registerWithTouchDispatcher();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModSettingsList::hasUnsavedModifiedSettings() const {
|
||||
for (auto& setting : m_settingNodes) {
|
||||
if (setting->hasUnsavedChanges()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ModSettingsList::applyChanges() {
|
||||
for (auto& setting : m_settingNodes) {
|
||||
if (setting->hasUnsavedChanges()) {
|
||||
setting->commitChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModSettingsList::updateList() {
|
||||
m_settingsLayer->updateState();
|
||||
}
|
||||
|
||||
void ModSettingsList::resetAllToDefault() {
|
||||
for (auto& setting : m_settingNodes) {
|
||||
setting->resetToDefault();
|
||||
}
|
||||
}
|
||||
|
||||
ModSettingsList* ModSettingsList::create(
|
||||
Mod* mod, ModSettingsLayer* layer, float width, float height
|
||||
) {
|
||||
auto ret = new ModSettingsList();
|
||||
if (ret && ret->init(mod, layer, width, height)) {
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
CC_SAFE_DELETE(ret);
|
||||
return nullptr;
|
||||
}*/
|
|
@ -1,29 +0,0 @@
|
|||
/*#pragma once
|
||||
|
||||
#include <Geode.hpp>
|
||||
#include <nodes/ScrollLayer.hpp>
|
||||
#include <settings/SettingNode.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
class ModSettingsLayer;
|
||||
|
||||
class ModSettingsList : public CCLayer {
|
||||
protected:
|
||||
Mod* m_mod;
|
||||
ModSettingsLayer* m_settingsLayer;
|
||||
ScrollLayer* m_scrollLayer;
|
||||
std::vector<SettingNode*> m_settingNodes;
|
||||
|
||||
bool init(Mod* mod, ModSettingsLayer* layer, float width, float height);
|
||||
|
||||
public:
|
||||
static ModSettingsList* create(
|
||||
Mod* mod, ModSettingsLayer* layer, float width, float height
|
||||
);
|
||||
|
||||
void updateList();
|
||||
void resetAllToDefault();
|
||||
bool hasUnsavedModifiedSettings() const;
|
||||
void applyChanges();
|
||||
};*/
|
126
loader/src/ui/internal/settings/ModSettingsPopup.cpp
Normal file
126
loader/src/ui/internal/settings/ModSettingsPopup.cpp
Normal file
|
@ -0,0 +1,126 @@
|
|||
#include "ModSettingsPopup.hpp"
|
||||
#include <Geode/ui/ScrollLayer.hpp>
|
||||
#include <Geode/loader/Setting.hpp>
|
||||
#include <Geode/loader/SettingNode.hpp>
|
||||
#include <Geode/utils/cocos.hpp>
|
||||
#include <Geode/utils/convert.hpp>
|
||||
|
||||
bool ModSettingsPopup::setup(Mod* mod) {
|
||||
m_noElasticity = true;
|
||||
m_mod = mod;
|
||||
|
||||
this->setTitle((mod->getName() + "'s Settings").c_str());
|
||||
|
||||
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
||||
|
||||
auto const layerSize = CCSize { 360.f, 220.f };
|
||||
|
||||
auto layerBG = CCLayerColor::create({ 0, 0, 0, 75 });
|
||||
layerBG->setContentSize(layerSize);
|
||||
layerBG->setPosition(winSize / 2 - layerSize / 2);
|
||||
m_mainLayer->addChild(layerBG);
|
||||
|
||||
auto layer = ScrollLayer::create(layerSize);
|
||||
layer->setPosition(winSize / 2 - layerSize / 2);
|
||||
|
||||
CCDirector::sharedDirector()->getTouchDispatcher()->incrementForcePrio(2);
|
||||
layer->registerWithTouchDispatcher();
|
||||
|
||||
float totalHeight = .0f;
|
||||
std::vector<CCNode*> rendered;
|
||||
bool hasBG = false;
|
||||
for (auto& [_, sett] : mod->getSettings()) {
|
||||
auto node = sett->createNode(layerSize.width);
|
||||
|
||||
totalHeight += node->getScaledContentSize().height;
|
||||
|
||||
if (hasBG) {
|
||||
auto bg = CCLayerColor::create({ 0, 0, 0, 75 });
|
||||
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);
|
||||
|
||||
rendered.push_back(node);
|
||||
m_settings.push_back(node);
|
||||
}
|
||||
if (totalHeight < layerSize.height) {
|
||||
totalHeight = layerSize.height;
|
||||
}
|
||||
for (auto& node : rendered) {
|
||||
node->setPositionY(node->getPositionY() + totalHeight);
|
||||
}
|
||||
layer->m_contentLayer->setContentSize({ layerSize.width, totalHeight });
|
||||
|
||||
m_mainLayer->addChild(layer);
|
||||
|
||||
m_applyBtnSpr = ButtonSprite::create("Apply");
|
||||
m_applyBtnSpr->setScale(.8f);
|
||||
|
||||
m_applyBtn = CCMenuItemSpriteExtra::create(
|
||||
m_applyBtnSpr, this, makeMenuSelector([this](CCObject*) {
|
||||
bool someChangesMade = false;
|
||||
for (auto& sett : m_settings) {
|
||||
if (sett->hasUncommittedChanges()) {
|
||||
sett->commit();
|
||||
someChangesMade = true;
|
||||
}
|
||||
}
|
||||
if (!someChangesMade) {
|
||||
FLAlertLayer::create(
|
||||
"Info",
|
||||
"No changes have been made.",
|
||||
"OK"
|
||||
)->show();
|
||||
}
|
||||
})
|
||||
);
|
||||
m_applyBtn->setPosition(.0f, -m_size.height / 2 + 20.f);
|
||||
m_buttonMenu->addChild(m_applyBtn);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModSettingsPopup::hasUncommitted() const {
|
||||
for (auto& sett : m_settings) {
|
||||
if (sett->hasUncommittedChanges()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ModSettingsPopup::onClose(CCObject* sender) {
|
||||
if (sender && this->hasUncommitted()) {
|
||||
return createQuickPopup(
|
||||
"Unsaved Changes",
|
||||
"You have <cr>unsaved changes</c>! Are you sure you "
|
||||
"want to exit?",
|
||||
"Cancel", "Discard",
|
||||
[this](FLAlertLayer*, bool btn2) {
|
||||
if (btn2) this->onClose(nullptr);
|
||||
}
|
||||
);
|
||||
}
|
||||
Popup<Mod*>::onClose(sender);
|
||||
}
|
||||
|
||||
ModSettingsPopup* ModSettingsPopup::create(Mod* mod) {
|
||||
auto ret = new ModSettingsPopup();
|
||||
if (ret && ret->init(440.f, 290.f, mod)) {
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
CC_SAFE_DELETE(ret);
|
||||
return nullptr;
|
||||
}
|
22
loader/src/ui/internal/settings/ModSettingsPopup.hpp
Normal file
22
loader/src/ui/internal/settings/ModSettingsPopup.hpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <Geode/ui/Popup.hpp>
|
||||
#include <Geode/utils/Ref.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
class ModSettingsPopup : public Popup<Mod*> {
|
||||
protected:
|
||||
Mod* m_mod;
|
||||
std::vector<SettingNode*> m_settings;
|
||||
CCMenuItemSpriteExtra* m_applyBtn;
|
||||
ButtonSprite* m_applyBtnSpr;
|
||||
|
||||
bool setup(Mod* mod) override;
|
||||
bool hasUncommitted() const;
|
||||
void onClose(CCObject*) override;
|
||||
|
||||
public:
|
||||
static ModSettingsPopup* create(Mod* mod);
|
||||
};
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
// #include <settings/Setting.hpp>
|
||||
// #include <unordered_map>
|
||||
|
||||
// USE_GEODE_NAMESPACE();
|
||||
|
||||
// Setting* Setting::from(std::string const& id, nlohmann::json const& json) {
|
||||
// if (json.is_object()) {
|
||||
|
||||
// } else {
|
||||
|
||||
// }
|
||||
|
||||
// auto ctrl = json["control"];
|
||||
// if (!ctrl.is_string()) {
|
||||
// FLAlertLayer::create(
|
||||
// "Failed to load settings",
|
||||
// "JSON error: 'control' key is not a string (or doesn't exist)!",
|
||||
// "OK"
|
||||
// )->show();
|
||||
// return nullptr;
|
||||
// }
|
||||
// std::string control = ctrl;
|
||||
|
||||
// Setting* out = nullptr;
|
||||
// /*EventCenter::get()->broadcast(Event(
|
||||
// events::getSetting(id),
|
||||
// &out,
|
||||
// Mod::get()
|
||||
// ));*/
|
||||
// #pragma message("Event")
|
||||
|
||||
// if (out == nullptr) {
|
||||
// FLAlertLayer::create(
|
||||
// "Failed to load settings",
|
||||
// std::string("No known setting control '") + control + "'",
|
||||
// "OK"
|
||||
// )->show();
|
||||
// }
|
||||
|
||||
// return out;
|
||||
// }
|
||||
|
||||
// bool SettingManager::hasSettings() {
|
||||
// return !this->m_settings.empty();
|
||||
// }
|
||||
|
||||
// SettingManager* SettingManager::with(Mod* m) {
|
||||
// static std::unordered_map<Mod*, SettingManager*> managers;
|
||||
// if (!managers.count(m)) {
|
||||
// managers[m] = new SettingManager(m);
|
||||
// }
|
||||
// return managers[m];
|
||||
// }
|
||||
|
||||
// SettingManager::SettingManager(Mod* m) {
|
||||
// m_mod = m;
|
||||
// auto root = m_mod->getDataStore()["settings"];
|
||||
|
||||
// if (!root.is_object()) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// for (auto [id, setting] : root.items()) {
|
||||
// m_settings[id] = Setting::from(id, setting);
|
||||
// }
|
||||
// }
|
||||
|
||||
// Setting* SettingManager::getSetting(std::string id) {
|
||||
// return m_settings[id];
|
||||
// }
|
||||
|
||||
// void SettingManager::updateSetting(std::string id) {
|
||||
// // m_mod->getDataStore()["settings"][id] = this->getSetting(id)->saveJSON();
|
||||
// }
|
||||
|
||||
// CCNode* Setting::createControl() const {
|
||||
// return nullptr;
|
||||
// }
|
||||
|
||||
// std::vector<CCNode*> SettingManager::generateSettingNodes() {
|
||||
// std::vector<CCNode*> out;
|
||||
// for (auto [k, v] : this->m_settings) {
|
||||
// out.push_back(v->createControl());
|
||||
// }
|
||||
// return out;
|
||||
// }
|
|
@ -3,28 +3,39 @@
|
|||
USE_GEODE_NAMESPACE();
|
||||
|
||||
const char* InputNode::getString() {
|
||||
return m_input->getTextField()->getString();
|
||||
return m_input->getString();
|
||||
}
|
||||
|
||||
void InputNode::setString(const char* _str) {
|
||||
m_input->getTextField()->setString(_str);
|
||||
m_input->refreshLabel();
|
||||
void InputNode::setString(std::string const& str) {
|
||||
m_input->setString(str);
|
||||
}
|
||||
|
||||
CCTextInputNode* InputNode::getInputNode() const {
|
||||
CCTextInputNode* InputNode::getInput() const {
|
||||
return m_input;
|
||||
}
|
||||
|
||||
CCScale9Sprite* InputNode::getBGSprite() const {
|
||||
CCScale9Sprite* InputNode::getBG() const {
|
||||
return m_bgSprite;
|
||||
}
|
||||
|
||||
void InputNode::activate() {
|
||||
std::cout << "steve\n";
|
||||
m_input->onClickTrackNode(true);
|
||||
}
|
||||
|
||||
void InputNode::setEnabled(bool enabled) {
|
||||
m_input->setMouseEnabled(enabled);
|
||||
m_input->setTouchEnabled(enabled);
|
||||
}
|
||||
|
||||
bool InputNode::init(float _w, float _h, const char* _phtxt, const char* _fnt, const std::string & _awc, int _cc) {
|
||||
bool InputNode::init(
|
||||
float width, float height,
|
||||
const char* placeholder, const char* font,
|
||||
std::string const& filter, int maxCharCount
|
||||
) {
|
||||
if (!CCMenuItem::init())
|
||||
return false;
|
||||
|
||||
m_bgSprite = cocos2d::extension::CCScale9Sprite::create(
|
||||
"square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }
|
||||
);
|
||||
|
@ -32,34 +43,51 @@ bool InputNode::init(float _w, float _h, const char* _phtxt, const char* _fnt, c
|
|||
m_bgSprite->setScale(.5f);
|
||||
m_bgSprite->setColor({ 0, 0, 0 });
|
||||
m_bgSprite->setOpacity(75);
|
||||
m_bgSprite->setContentSize({ _w * 2, _h * 2 });
|
||||
m_bgSprite->setContentSize({ width * 2, height * 2 });
|
||||
m_bgSprite->setPosition(width / 2, height / 2);
|
||||
|
||||
this->addChild(m_bgSprite);
|
||||
|
||||
m_input = CCTextInputNode::create(
|
||||
_w - 10.0f, 60.0f, _phtxt, _fnt
|
||||
width - 10.0f, height, placeholder, font
|
||||
);
|
||||
|
||||
m_input->setLabelPlaceholderColor({ 150, 150, 150 });
|
||||
m_input->setLabelPlaceholderScale(.75f);
|
||||
m_input->setMaxLabelScale(.8f);
|
||||
m_input->setMaxLabelWidth(_cc);
|
||||
if (_awc.length())
|
||||
m_input->setAllowedChars(_awc);
|
||||
|
||||
m_input->setMaxLabelScale(.85f);
|
||||
m_input->setMaxLabelWidth(maxCharCount);
|
||||
m_input->setPosition(width / 2, height / 2);
|
||||
if (filter.length()) {
|
||||
m_input->setAllowedChars(filter);
|
||||
}
|
||||
this->addChild(m_input);
|
||||
|
||||
this->setContentSize({ width, height });
|
||||
this->setAnchorPoint({ .5f, .5f });
|
||||
this->setEnabled(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputNode::init(float _w, const char* _phtxt, const char* _fnt, const std::string & _awc, int _cc) {
|
||||
return init(_w, 30.0f, _phtxt, _fnt, _awc, _cc);
|
||||
bool InputNode::init(
|
||||
float width,
|
||||
const char* placeholder,
|
||||
const char* font,
|
||||
std::string const& filter,
|
||||
int maxCharCount
|
||||
) {
|
||||
return init(width, 30.0f, placeholder, font, filter, maxCharCount);
|
||||
}
|
||||
|
||||
InputNode* InputNode::create(float _w, const char* _phtxt, const char* _fnt, const std::string & _awc, int _cc) {
|
||||
InputNode* InputNode::create(
|
||||
float width,
|
||||
const char* placeholder,
|
||||
const char* font,
|
||||
std::string const& filter,
|
||||
int maxCharCount
|
||||
) {
|
||||
auto pRet = new InputNode();
|
||||
|
||||
if (pRet && pRet->init(_w, _phtxt, _fnt, _awc, _cc)) {
|
||||
if (pRet && pRet->init(width, placeholder, font, filter, maxCharCount)) {
|
||||
pRet->autorelease();
|
||||
return pRet;
|
||||
}
|
||||
|
@ -68,18 +96,34 @@ InputNode* InputNode::create(float _w, const char* _phtxt, const char* _fnt, con
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
InputNode* InputNode::create(float _w, const char* _phtxt, const std::string & _awc) {
|
||||
return create(_w, _phtxt, "bigFont.fnt", _awc, 69);
|
||||
InputNode* InputNode::create(
|
||||
float width,
|
||||
const char* placeholder,
|
||||
std::string const& filter
|
||||
) {
|
||||
return create(width, placeholder, "bigFont.fnt", filter, 69);
|
||||
}
|
||||
|
||||
InputNode* InputNode::create(float _w, const char* _phtxt, const std::string & _awc, int _cc) {
|
||||
return create(_w, _phtxt, "bigFont.fnt", _awc, _cc);
|
||||
InputNode* InputNode::create(
|
||||
float width,
|
||||
const char* placeholder,
|
||||
std::string const& filter,
|
||||
int maxCharCount
|
||||
) {
|
||||
return create(width, placeholder, "bigFont.fnt", filter, maxCharCount);
|
||||
}
|
||||
|
||||
InputNode* InputNode::create(float _w, const char* _phtxt, const char* _fnt) {
|
||||
return create(_w, _phtxt, _fnt, "", 69);
|
||||
InputNode* InputNode::create(
|
||||
float width,
|
||||
const char* placeholder,
|
||||
const char* font
|
||||
) {
|
||||
return create(width, placeholder, font, "", 69);
|
||||
}
|
||||
|
||||
InputNode* InputNode::create(float _w, const char* _phtxt) {
|
||||
return create(_w, _phtxt, "bigFont.fnt");
|
||||
InputNode* InputNode::create(
|
||||
float width,
|
||||
const char* placeholder
|
||||
) {
|
||||
return create(width, placeholder, "bigFont.fnt");
|
||||
}
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
#include <Geode/ui/MenuInputNode.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
bool MenuInputNode::init(
|
||||
float width, float height, const char* placeholder, const char* fontPath, bool bg
|
||||
) {
|
||||
if (!CCMenuItem::init())
|
||||
return false;
|
||||
|
||||
if (bg) {
|
||||
m_bgSprite = cocos2d::extension::CCScale9Sprite::create(
|
||||
"square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }
|
||||
);
|
||||
m_bgSprite->setScale(.5f);
|
||||
m_bgSprite->setColor({ 0, 0, 0 });
|
||||
m_bgSprite->setOpacity(75);
|
||||
m_bgSprite->setContentSize({ width * 2, height * 2 });
|
||||
this->addChild(m_bgSprite);
|
||||
}
|
||||
|
||||
this->setContentSize({ width, height });
|
||||
this->setAnchorPoint({ .5f, .5f });
|
||||
m_input = CCTextInputNode::create(width, height, placeholder, fontPath);
|
||||
m_input->setPosition(width / 2, height / 2);
|
||||
this->addChild(m_input);
|
||||
|
||||
this->setEnabled(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MenuInputNode* MenuInputNode::create(
|
||||
float width, float height, const char* placeholder, const char* fontPath, bool bg
|
||||
) {
|
||||
auto ret = new MenuInputNode;
|
||||
if (ret && ret->init(width, height, placeholder, fontPath, bg)) {
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
CC_SAFE_DELETE(ret);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MenuInputNode::selected() {
|
||||
m_input->onClickTrackNode(true);
|
||||
}
|
||||
|
||||
CCTextInputNode* MenuInputNode::getInput() const {
|
||||
return m_input;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.3.0)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(PROJECT_NAME TestDependency)
|
||||
|
|
|
@ -5,5 +5,25 @@
|
|||
"name": "Geode Test Dependency",
|
||||
"developer": "Geode Team",
|
||||
"description": "Unit test dependency for Geode",
|
||||
"binary": "TestDependency"
|
||||
"binary": "TestDependency",
|
||||
"settings": {
|
||||
"sock": {
|
||||
"type": "string",
|
||||
"default": "Cheese"
|
||||
},
|
||||
"steve-setting": {
|
||||
"type": "string",
|
||||
"name": "Steve!!",
|
||||
"default": "ball"
|
||||
},
|
||||
"enable-gay-dreams": {
|
||||
"type": "bool",
|
||||
"default": true
|
||||
},
|
||||
"catgirl": {
|
||||
"type": "int",
|
||||
"default": 5,
|
||||
"max": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.3.0)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(PROJECT_NAME TestMod)
|
||||
|
|
Loading…
Reference in a new issue