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:
HJfod 2022-09-13 00:37:25 +03:00
parent a30f5063e7
commit 0ab32b3e25
32 changed files with 1221 additions and 2012 deletions

View file

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

View file

@ -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"

View file

@ -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"

View file

@ -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,

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

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

View file

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

View file

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

View 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);
}
};
}

View file

@ -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 + "\"");
}
}
}
};

View file

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

View file

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

View 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);
}

View file

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

View 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);
}

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

View file

@ -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*) {

View file

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

View file

@ -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);*/

View file

@ -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)*/

View file

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

View file

@ -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();
};
*/

View file

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

View file

@ -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();
};*/

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

View 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);
};

View file

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

View file

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

View file

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

View file

@ -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)

View file

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

View file

@ -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)