mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-14 19:15:05 -05:00
rework settings
- Settings no longer abuse templates - Custom settings are now supported (finally) - Bumped version to v1.0.0-alpha
This commit is contained in:
parent
b1e0276a7e
commit
9ffb15b616
31 changed files with 1509 additions and 1158 deletions
|
@ -1,9 +1,22 @@
|
|||
cmake_minimum_required(VERSION 3.21 FATAL_ERROR)
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build libraries static" FORCE)
|
||||
|
||||
# Read version
|
||||
file(READ VERSION GEODE_VERSION)
|
||||
string(STRIP "${GEODE_VERSION}" GEODE_VERSION)
|
||||
|
||||
# Check if version has a tag like v1.0.0-alpha
|
||||
string(FIND ${GEODE_VERSION} "-" GEODE_VERSION_HAS_TAG)
|
||||
if (GEODE_VERSION_HAS_TAG)
|
||||
string(REGEX MATCH "[a-z]+[0-9]?$" GEODE_VERSION_TAG ${GEODE_VERSION})
|
||||
string(SUBSTRING "${GEODE_VERSION}" 0 ${GEODE_VERSION_HAS_TAG} GEODE_VERSION)
|
||||
else()
|
||||
set(GEODE_VERSION_TAG "")
|
||||
endif()
|
||||
|
||||
message(STATUS "Version: ${GEODE_VERSION}, tag: ${GEODE_VERSION_TAG}")
|
||||
|
||||
project(geode-sdk VERSION ${GEODE_VERSION} LANGUAGES CXX C)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.6.1
|
||||
1.0.0-alpha
|
|
@ -89,10 +89,6 @@ namespace geode {
|
|||
this->enable();
|
||||
}
|
||||
|
||||
// todo: maybe add these?
|
||||
EventListener(EventListener const& other) = delete;
|
||||
EventListener(EventListener&& other) = delete;
|
||||
|
||||
void bind(std::function<Callback> fn) {
|
||||
m_callback = fn;
|
||||
}
|
||||
|
|
|
@ -18,19 +18,6 @@
|
|||
#include <vector>
|
||||
|
||||
namespace geode {
|
||||
template <class T>
|
||||
struct HandleToSaved : public T {
|
||||
Mod* m_mod;
|
||||
std::string m_key;
|
||||
|
||||
HandleToSaved(std::string const& key, Mod* mod, T const& value) :
|
||||
T(value), m_key(key), m_mod(mod) {}
|
||||
|
||||
HandleToSaved(HandleToSaved const&) = delete;
|
||||
HandleToSaved(HandleToSaved&&) = delete;
|
||||
~HandleToSaved();
|
||||
};
|
||||
|
||||
/**
|
||||
* @class Mod
|
||||
* Represents a Mod ingame.
|
||||
|
@ -82,6 +69,14 @@ namespace geode {
|
|||
* Saved values
|
||||
*/
|
||||
nlohmann::json m_saved;
|
||||
/**
|
||||
* Setting values
|
||||
*/
|
||||
std::unordered_map<std::string, std::unique_ptr<SettingValue>> m_settings;
|
||||
/**
|
||||
* Settings save data. Stored for efficient loading of custom settings
|
||||
*/
|
||||
nlohmann::json m_savedSettingsData;
|
||||
|
||||
/**
|
||||
* Load the platform binary
|
||||
|
@ -90,6 +85,8 @@ namespace geode {
|
|||
Result<> unloadPlatformBinary();
|
||||
Result<> createTempDir();
|
||||
|
||||
void setupSettings();
|
||||
|
||||
// no copying
|
||||
Mod(Mod const&) = delete;
|
||||
Mod operator=(Mod const&) = delete;
|
||||
|
@ -146,25 +143,31 @@ namespace geode {
|
|||
ghc::filesystem::path getConfigDir(bool create = true) const;
|
||||
|
||||
bool hasSettings() const;
|
||||
decltype(ModInfo::settings) getSettings() const;
|
||||
std::vector<std::string> getSettingKeys() const;
|
||||
bool hasSetting(std::string const& key) const;
|
||||
std::shared_ptr<Setting> getSetting(std::string const& key) const;
|
||||
std::optional<Setting> getSettingDefinition(std::string const& key) const;
|
||||
SettingValue* getSetting(std::string const& key) const;
|
||||
void registerCustomSetting(
|
||||
std::string const& key,
|
||||
std::unique_ptr<SettingValue> value
|
||||
);
|
||||
|
||||
template <class T>
|
||||
T getSettingValue(std::string const& key) const {
|
||||
if (this->hasSetting(key)) {
|
||||
return geode::getBuiltInSettingValue<T>(this->getSetting(key));
|
||||
if (auto sett = this->getSetting(key)) {
|
||||
return SettingValueSetter<T>::get(sett);
|
||||
}
|
||||
return T();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool setSettingValue(std::string const& key, T const& value) {
|
||||
if (this->hasSetting(key)) {
|
||||
geode::setBuiltInSettingValue<T>(this->getSetting(key), value);
|
||||
return true;
|
||||
T setSettingValue(std::string const& key, T const& value) {
|
||||
if (auto sett = this->getSetting(key)) {
|
||||
auto old = this->getSettingValue<T>(sett);
|
||||
SettingValueSetter<T>::set(sett, value);
|
||||
return old;
|
||||
}
|
||||
return false;
|
||||
return T();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
|
@ -194,16 +197,6 @@ namespace geode {
|
|||
return defaultValue;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
HandleToSaved<T> getSavedMutable(std::string const& key) {
|
||||
return HandleToSaved(key, this, this->getSavedValue<T>(key));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
HandleToSaved<T> getSavedMutable(std::string const& key, T const& defaultValue) {
|
||||
return HandleToSaved(key, this, this->getSavedValue<T>(key, defaultValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of an automatically saved variable. When the game is
|
||||
* closed, the value is automatically saved under the key
|
||||
|
@ -389,11 +382,6 @@ namespace geode {
|
|||
ModJson getRuntimeInfo() const;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
HandleToSaved<T>::~HandleToSaved() {
|
||||
m_mod->setSavedValue(m_key, static_cast<T>(*this));
|
||||
}
|
||||
|
||||
/**
|
||||
* To bypass the need for cyclic dependencies,
|
||||
* this function does the exact same as Mod::get()
|
||||
|
|
|
@ -10,8 +10,6 @@ namespace geode {
|
|||
class Unzip;
|
||||
}
|
||||
|
||||
using ModJson = nlohmann::ordered_json;
|
||||
|
||||
struct GEODE_DLL Dependency {
|
||||
std::string id;
|
||||
ComparableVersionInfo version;
|
||||
|
@ -111,8 +109,9 @@ namespace geode {
|
|||
std::vector<std::string> spritesheets;
|
||||
/**
|
||||
* Mod settings
|
||||
* @note Not a map because insertion order must be preserved
|
||||
*/
|
||||
std::vector<std::pair<std::string, std::shared_ptr<Setting>>> settings;
|
||||
std::vector<std::pair<std::string, Setting>> settings;
|
||||
/**
|
||||
* Whether the mod can be disabled or not
|
||||
*/
|
||||
|
|
|
@ -1,502 +1,244 @@
|
|||
#pragma once
|
||||
|
||||
#include "Types.hpp"
|
||||
#include "../DefaultInclude.hpp"
|
||||
#include "../utils/JsonValidation.hpp"
|
||||
#include "../utils/Result.hpp"
|
||||
#include "../external/json/json.hpp"
|
||||
#include "../utils/file.hpp"
|
||||
#include "../utils/ranges.hpp"
|
||||
#include "../utils/cocos.hpp"
|
||||
#include "ModInfo.hpp"
|
||||
#include "../external/json/json.hpp"
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <unordered_set>
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4275)
|
||||
|
||||
namespace geode {
|
||||
class Setting;
|
||||
class SettingNode;
|
||||
class BoolSetting;
|
||||
class IntSetting;
|
||||
class FloatSetting;
|
||||
class StringSetting;
|
||||
class SettingValue;
|
||||
|
||||
struct ModInfo;
|
||||
struct GEODE_DLL BoolSetting final {
|
||||
using ValueType = bool;
|
||||
|
||||
enum class SettingType {
|
||||
Bool,
|
||||
Int,
|
||||
Float,
|
||||
String,
|
||||
File,
|
||||
Color,
|
||||
ColorAlpha,
|
||||
User,
|
||||
std::optional<std::string> name;
|
||||
std::optional<std::string> description;
|
||||
bool defaultValue;
|
||||
|
||||
static Result<BoolSetting> parse(JsonMaybeObject<ModJson>& obj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for all settings in Geode mods. Note that for most purposes
|
||||
* you should use the built-in setting types. If you need a custom setting
|
||||
* type however, inherit from this class. Do note that you are responsible
|
||||
* for things like storing the default value, broadcasting value change
|
||||
* events, making the setting node etc.
|
||||
*/
|
||||
class Setting : public std::enable_shared_from_this<Setting> {
|
||||
struct GEODE_DLL IntSetting final {
|
||||
using ValueType = int64_t;
|
||||
|
||||
std::optional<std::string> name;
|
||||
std::optional<std::string> description;
|
||||
ValueType defaultValue;
|
||||
std::optional<ValueType> min;
|
||||
std::optional<ValueType> max;
|
||||
struct {
|
||||
bool arrows = true;
|
||||
bool bigArrows = false;
|
||||
size_t arrowStep = 1;
|
||||
size_t bigArrowStep = 5;
|
||||
bool slider = true;
|
||||
std::optional<ValueType> sliderStep = std::nullopt;
|
||||
bool input = true;
|
||||
} controls;
|
||||
|
||||
static Result<IntSetting> parse(JsonMaybeObject<ModJson>& obj);
|
||||
};
|
||||
|
||||
struct GEODE_DLL FloatSetting final {
|
||||
using ValueType = double;
|
||||
|
||||
std::optional<std::string> name;
|
||||
std::optional<std::string> description;
|
||||
ValueType defaultValue;
|
||||
std::optional<ValueType> min;
|
||||
std::optional<ValueType> max;
|
||||
struct {
|
||||
bool arrows = true;
|
||||
bool bigArrows = false;
|
||||
size_t arrowStep = 1;
|
||||
size_t bigArrowStep = 5;
|
||||
bool slider = true;
|
||||
std::optional<ValueType> sliderStep = std::nullopt;
|
||||
bool input = true;
|
||||
} controls;
|
||||
|
||||
static Result<FloatSetting> parse(JsonMaybeObject<ModJson>& obj);
|
||||
};
|
||||
|
||||
struct GEODE_DLL StringSetting final {
|
||||
using ValueType = std::string;
|
||||
|
||||
std::optional<std::string> name;
|
||||
std::optional<std::string> description;
|
||||
ValueType defaultValue;
|
||||
std::optional<std::string> match;
|
||||
|
||||
static Result<StringSetting> parse(JsonMaybeObject<ModJson>& obj);
|
||||
};
|
||||
|
||||
struct GEODE_DLL FileSetting final {
|
||||
using ValueType = ghc::filesystem::path;
|
||||
using Filter = utils::file::FilePickOptions::Filter;
|
||||
|
||||
std::optional<std::string> name;
|
||||
std::optional<std::string> description;
|
||||
ValueType defaultValue;
|
||||
struct {
|
||||
std::vector<Filter> filters;
|
||||
} controls;
|
||||
|
||||
static Result<FileSetting> parse(JsonMaybeObject<ModJson>& obj);
|
||||
};
|
||||
|
||||
struct GEODE_DLL ColorSetting final {
|
||||
using ValueType = cocos2d::ccColor3B;
|
||||
|
||||
std::optional<std::string> name;
|
||||
std::optional<std::string> description;
|
||||
ValueType defaultValue;
|
||||
|
||||
static Result<ColorSetting> parse(JsonMaybeObject<ModJson>& obj);
|
||||
};
|
||||
|
||||
struct GEODE_DLL ColorAlphaSetting final {
|
||||
using ValueType = cocos2d::ccColor4B;
|
||||
|
||||
std::optional<std::string> name;
|
||||
std::optional<std::string> description;
|
||||
ValueType defaultValue;
|
||||
|
||||
static Result<ColorAlphaSetting> parse(JsonMaybeObject<ModJson>& obj);
|
||||
};
|
||||
|
||||
struct GEODE_DLL CustomSetting final {
|
||||
ModJson json;
|
||||
};
|
||||
|
||||
using SettingKind = std::variant<
|
||||
BoolSetting,
|
||||
IntSetting,
|
||||
FloatSetting,
|
||||
StringSetting,
|
||||
FileSetting,
|
||||
ColorSetting,
|
||||
ColorAlphaSetting,
|
||||
CustomSetting
|
||||
>;
|
||||
|
||||
struct GEODE_DLL Setting final {
|
||||
private:
|
||||
std::string m_key;
|
||||
SettingKind m_kind;
|
||||
|
||||
Setting() = default;
|
||||
|
||||
public:
|
||||
static Result<Setting> parse(
|
||||
std::string const& key,
|
||||
JsonMaybeValue<ModJson>& obj
|
||||
);
|
||||
Setting(std::string const& key, SettingKind const& kind);
|
||||
|
||||
template<class T>
|
||||
std::optional<T> get() {
|
||||
if (std::holds_alternative<T>(m_kind)) {
|
||||
return std::get<T>(m_kind);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::unique_ptr<SettingValue> createDefaultValue() const;
|
||||
bool isCustom() const;
|
||||
std::string getDisplayName() const;
|
||||
std::optional<std::string> getDescription() const;
|
||||
};
|
||||
|
||||
class GEODE_DLL SettingValue {
|
||||
protected:
|
||||
std::string m_key;
|
||||
std::string m_modID;
|
||||
|
||||
friend struct ModInfo;
|
||||
|
||||
static Result<std::shared_ptr<Setting>> parse(
|
||||
std::string const& type, std::string const& key, JsonMaybeObject<ModJson>& obj
|
||||
);
|
||||
|
||||
SettingValue(std::string const& key);
|
||||
|
||||
public:
|
||||
GEODE_DLL virtual ~Setting() = default;
|
||||
|
||||
// Load from mod.json
|
||||
GEODE_DLL static Result<std::shared_ptr<Setting>> parse(
|
||||
std::string const& key, ModJson const& json
|
||||
);
|
||||
// Load value from saved settings
|
||||
virtual ~SettingValue() = default;
|
||||
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;
|
||||
|
||||
GEODE_DLL void valueChanged();
|
||||
|
||||
GEODE_DLL std::string getKey() const;
|
||||
virtual SettingType getType() const = 0;
|
||||
std::string getKey() const;
|
||||
};
|
||||
|
||||
// built-in settings' implementation details
|
||||
namespace {
|
||||
#define GEODE_INT_PARSE_SETTING_IMPL(obj, func, ...) \
|
||||
if constexpr (std::is_base_of_v<__VA_ARGS__, Class>) { \
|
||||
auto r = std::static_pointer_cast<Class>(res)->func(obj); \
|
||||
if (!r) return Err(r.unwrapErr()); \
|
||||
}
|
||||
template<class T>
|
||||
class GeodeSettingValue : public SettingValue {
|
||||
public:
|
||||
using ValueType = typename T::ValueType;
|
||||
|
||||
#define GEODE_INT_CONSTRAIN_SETTING_CAN_IMPL(func, ...) \
|
||||
if constexpr (std::is_base_of_v<__VA_ARGS__, Class>) { \
|
||||
auto res = static_cast<Class*>(this)->func(value); \
|
||||
if (!res) { \
|
||||
return res; \
|
||||
} \
|
||||
}
|
||||
protected:
|
||||
ValueType m_value;
|
||||
T m_definition;
|
||||
|
||||
template <class ValueType>
|
||||
class IMinMax;
|
||||
template <class Class, class ValueType>
|
||||
class IOneOf;
|
||||
template <class Class, class ValueType>
|
||||
class IMatch;
|
||||
using Valid = std::pair<ValueType, std::optional<std::string>>;
|
||||
|
||||
class ICArrows;
|
||||
template <class ValueType>
|
||||
class ICSlider;
|
||||
class ICInput;
|
||||
class ICFileFilters;
|
||||
GEODE_DLL Valid toValid(ValueType const& value) const;
|
||||
|
||||
template <class Class, class ValueType, SettingType Type>
|
||||
class GeodeSetting : public Setting {
|
||||
protected:
|
||||
ValueType m_default;
|
||||
ValueType m_value;
|
||||
std::optional<std::string> m_name;
|
||||
std::optional<std::string> m_description;
|
||||
bool m_canResetToDefault = true;
|
||||
public:
|
||||
GeodeSettingValue(std::string const& key, T const& definition)
|
||||
: SettingValue(key),
|
||||
m_definition(definition),
|
||||
m_value(definition.defaultValue) {}
|
||||
|
||||
friend class Setting;
|
||||
|
||||
static Result<std::shared_ptr<Class>> parse(
|
||||
std::string const& key, JsonMaybeObject<ModJson>& obj
|
||||
) {
|
||||
auto res = std::make_shared<Class>();
|
||||
|
||||
res->m_key = key;
|
||||
obj.needs("default").into(res->m_default);
|
||||
obj.has("name").intoAs<std::string>(res->m_name);
|
||||
obj.has("description").intoAs<std::string>(res->m_description);
|
||||
GEODE_INT_PARSE_SETTING_IMPL(obj, parseMinMax, IMinMax<ValueType>);
|
||||
GEODE_INT_PARSE_SETTING_IMPL(obj, parseOneOf, IOneOf<Class, ValueType>);
|
||||
GEODE_INT_PARSE_SETTING_IMPL(obj, parseMatch, IMatch<Class, ValueType>);
|
||||
res->setValue(res->m_default);
|
||||
|
||||
if (auto controls = obj.has("control").obj()) {
|
||||
// every built-in setting type has a reset button
|
||||
// by default
|
||||
controls.has("can-reset").into(res->m_canResetToDefault);
|
||||
GEODE_INT_PARSE_SETTING_IMPL(controls, parseArrows, ICArrows);
|
||||
GEODE_INT_PARSE_SETTING_IMPL(controls, parseSlider, ICSlider<ValueType>);
|
||||
GEODE_INT_PARSE_SETTING_IMPL(controls, parseInput, ICInput);
|
||||
GEODE_INT_PARSE_SETTING_IMPL(controls, parseFileFilters, ICFileFilters);
|
||||
}
|
||||
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
public:
|
||||
using value_t = ValueType;
|
||||
|
||||
std::optional<std::string> getName() const {
|
||||
return m_name;
|
||||
}
|
||||
|
||||
std::string getDisplayName() const {
|
||||
return m_name.value_or(m_key);
|
||||
}
|
||||
|
||||
std::optional<std::string> getDescription() const {
|
||||
return m_description;
|
||||
}
|
||||
|
||||
ValueType getDefault() const {
|
||||
return m_default;
|
||||
}
|
||||
|
||||
ValueType getValue() const {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void setValue(ValueType const& value) {
|
||||
m_value = value;
|
||||
if constexpr (std::is_base_of_v<IMinMax<ValueType>, Class>) {
|
||||
(void)static_cast<Class*>(this)->constrainMinMax(m_value);
|
||||
}
|
||||
if constexpr (std::is_base_of_v<IOneOf<Class, ValueType>, Class>) {
|
||||
(void)static_cast<Class*>(this)->constrainOneOf(m_value);
|
||||
}
|
||||
if constexpr (std::is_base_of_v<IMatch<Class, ValueType>, Class>) {
|
||||
(void)static_cast<Class*>(this)->constrainMatch(m_value);
|
||||
}
|
||||
this->valueChanged();
|
||||
}
|
||||
|
||||
Result<> isValidValue(ValueType value) {
|
||||
GEODE_INT_CONSTRAIN_SETTING_CAN_IMPL(constrainMinMax, IMinMax<ValueType>);
|
||||
GEODE_INT_CONSTRAIN_SETTING_CAN_IMPL(constrainOneOf, IOneOf<Class, ValueType>);
|
||||
GEODE_INT_CONSTRAIN_SETTING_CAN_IMPL(constrainMatch, IMatch<Class, ValueType>);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
bool load(nlohmann::json const& json) override {
|
||||
auto rawJson = json;
|
||||
JsonChecker(rawJson).root("[setting value]").into(m_value);
|
||||
bool load(nlohmann::json const& json) override {
|
||||
try {
|
||||
m_value = json.get<ValueType>();
|
||||
return true;
|
||||
} catch(...) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool save(nlohmann::json& json) const override {
|
||||
json = m_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canResetToDefault() const {
|
||||
return m_canResetToDefault;
|
||||
}
|
||||
|
||||
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:
|
||||
Result<> constrainMinMax(ValueType& value) {
|
||||
if (m_min && value < m_min.value()) {
|
||||
value = m_min.value();
|
||||
return Err(
|
||||
"Value must be between " + std::to_string(m_min.value()) + " and " +
|
||||
std::to_string(m_max.value())
|
||||
);
|
||||
}
|
||||
if (m_max && value > m_max.value()) {
|
||||
value = m_max.value();
|
||||
return Err(
|
||||
"Value must be between " + std::to_string(m_min.value()) + " and " +
|
||||
std::to_string(m_max.value())
|
||||
);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> parseMinMax(JsonMaybeObject<ModJson>& 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 Class, class ValueType>
|
||||
class IOneOf {
|
||||
protected:
|
||||
std::optional<std::unordered_set<ValueType>> m_oneOf = std::nullopt;
|
||||
|
||||
public:
|
||||
Result<> constrainOneOf(ValueType& value) {
|
||||
if (m_oneOf && !m_oneOf.value().count(value)) {
|
||||
value = static_cast<Class*>(this)->getDefault();
|
||||
return Err(
|
||||
"Value must be one of " + utils::ranges::join(m_oneOf.value(), ", ")
|
||||
);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> parseOneOf(JsonMaybeObject<ModJson>& 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();
|
||||
}
|
||||
|
||||
auto getOneOf() const {
|
||||
return m_oneOf;
|
||||
}
|
||||
};
|
||||
|
||||
template <class Class, class ValueType>
|
||||
class IMatch {
|
||||
protected:
|
||||
std::optional<ValueType> m_matchRegex = std::nullopt;
|
||||
|
||||
public:
|
||||
Result<> constrainMatch(ValueType& value) {
|
||||
if (m_matchRegex) {
|
||||
auto regex = std::regex(m_matchRegex.value());
|
||||
if (!std::regex_match(value, regex)) {
|
||||
value = static_cast<Class*>(this)->getDefault();
|
||||
return Err("Value must match regex " + m_matchRegex.value());
|
||||
}
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> parseMatch(JsonMaybeObject<ModJson>& obj) {
|
||||
obj.has("match").intoAs<ValueType>(m_matchRegex);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
std::optional<ValueType> getMatch() const {
|
||||
return m_matchRegex;
|
||||
}
|
||||
};
|
||||
|
||||
#define GEODE_INT_DECL_SETTING_CONTROL(Name, name, default, json) \
|
||||
class IC##Name { \
|
||||
protected: \
|
||||
bool m_##name = default; \
|
||||
\
|
||||
public: \
|
||||
Result<> parse##Name(JsonMaybeObject<ModJson>& obj) { \
|
||||
obj.has(json).into(m_##name); \
|
||||
return Ok(); \
|
||||
} \
|
||||
bool has##Name() const { return m_##name; } \
|
||||
}
|
||||
|
||||
class ICArrows {
|
||||
protected:
|
||||
bool m_hasArrows = true;
|
||||
bool m_hasBigArrows = false;
|
||||
size_t m_arrowStep = 1;
|
||||
size_t m_bigArrowStep = 5;
|
||||
|
||||
public:
|
||||
Result<> parseArrows(JsonMaybeObject<ModJson>& obj) {
|
||||
obj.has("arrows").into(m_hasArrows);
|
||||
obj.has("arrow-step").into(m_arrowStep);
|
||||
obj.has("big-arrows").into(m_hasBigArrows);
|
||||
obj.has("big-arrow-step").into(m_bigArrowStep);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
bool hasArrows() const {
|
||||
return m_hasArrows;
|
||||
}
|
||||
|
||||
bool hasBigArrows() const {
|
||||
return m_hasBigArrows;
|
||||
}
|
||||
|
||||
size_t getArrowStepSize() const {
|
||||
return m_arrowStep;
|
||||
}
|
||||
|
||||
size_t getBigArrowStepSize() const {
|
||||
return m_bigArrowStep;
|
||||
}
|
||||
};
|
||||
|
||||
template <class ValueType>
|
||||
class ICSlider {
|
||||
protected:
|
||||
bool m_hasSlider = true;
|
||||
std::optional<ValueType> m_sliderStep = std::nullopt;
|
||||
|
||||
public:
|
||||
Result<> parseSlider(JsonMaybeObject<ModJson>& obj) {
|
||||
obj.has("slider").into(m_hasSlider);
|
||||
obj.has("slider-step").intoAs<ValueType>(m_sliderStep);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
bool hasSlider() const {
|
||||
return m_hasSlider;
|
||||
}
|
||||
|
||||
std::optional<ValueType> getSliderStepSize() const {
|
||||
return m_sliderStep;
|
||||
}
|
||||
};
|
||||
|
||||
class ICFileFilters {
|
||||
protected:
|
||||
using Filter = utils::file::FilePickOptions::Filter;
|
||||
|
||||
std::optional<std::vector<Filter>> m_filters = std::nullopt;
|
||||
|
||||
public:
|
||||
Result<> parseFileFilters(JsonMaybeObject<ModJson>& obj) {
|
||||
std::vector<Filter> filters {};
|
||||
for (auto& item : obj.has("filters").iterate()) {
|
||||
if (auto iobj = item.obj()) {
|
||||
Filter filter;
|
||||
iobj.has("description").into(filter.description);
|
||||
iobj.has("files").into(filter.files);
|
||||
filters.push_back(filter);
|
||||
}
|
||||
}
|
||||
if (filters.size()) {
|
||||
m_filters = filters;
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
auto getFileFilters() const {
|
||||
return m_filters;
|
||||
}
|
||||
};
|
||||
|
||||
GEODE_INT_DECL_SETTING_CONTROL(Input, hasInput, true, "input");
|
||||
}
|
||||
|
||||
class BoolSetting : public GeodeSetting<BoolSetting, bool, SettingType::Bool> {
|
||||
public:
|
||||
GEODE_DLL SettingNode* createNode(float width) override;
|
||||
};
|
||||
|
||||
class IntSetting :
|
||||
public GeodeSetting<IntSetting, int64_t, SettingType::Int>,
|
||||
public IOneOf<IntSetting, int64_t>,
|
||||
public IMinMax<int64_t>,
|
||||
public ICArrows,
|
||||
public ICSlider<int64_t>,
|
||||
public ICInput {
|
||||
public:
|
||||
GEODE_DLL SettingNode* createNode(float width) override;
|
||||
};
|
||||
|
||||
class FloatSetting :
|
||||
public GeodeSetting<FloatSetting, double, SettingType::Float>,
|
||||
public IOneOf<FloatSetting, double>,
|
||||
public IMinMax<double>,
|
||||
public ICArrows,
|
||||
public ICSlider<double>,
|
||||
public ICInput {
|
||||
public:
|
||||
GEODE_DLL SettingNode* createNode(float width) override;
|
||||
};
|
||||
|
||||
class StringSetting :
|
||||
public GeodeSetting<StringSetting, std::string, SettingType::String>,
|
||||
public IOneOf<StringSetting, std::string>,
|
||||
public IMatch<StringSetting, std::string> {
|
||||
public:
|
||||
GEODE_DLL SettingNode* createNode(float width) override;
|
||||
};
|
||||
|
||||
class FileSetting :
|
||||
public GeodeSetting<FileSetting, ghc::filesystem::path, SettingType::File>,
|
||||
public ICFileFilters {
|
||||
public:
|
||||
GEODE_DLL SettingNode* createNode(float width) override;
|
||||
};
|
||||
|
||||
class ColorSetting : public GeodeSetting<ColorSetting, cocos2d::ccColor3B, SettingType::Color> {
|
||||
public:
|
||||
GEODE_DLL SettingNode* createNode(float width) override;
|
||||
};
|
||||
|
||||
class ColorAlphaSetting :
|
||||
public GeodeSetting<ColorAlphaSetting, cocos2d::ccColor4B, SettingType::ColorAlpha> {
|
||||
public:
|
||||
GEODE_DLL SettingNode* createNode(float width) override;
|
||||
};
|
||||
|
||||
// these can't be member functions because C++ is single-pass >:(
|
||||
|
||||
#define GEODE_INT_BUILTIN_SETTING_IF(type, action, ...) \
|
||||
if constexpr (__VA_ARGS__) { \
|
||||
if (setting->getType() == SettingType::type) { \
|
||||
return std::static_pointer_cast<type##Setting>(setting)->action; \
|
||||
} \
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
template <class T>
|
||||
T getBuiltInSettingValue(const std::shared_ptr<Setting> setting) {
|
||||
GEODE_INT_BUILTIN_SETTING_IF(Bool, getValue(), std::is_same_v<T, bool>)
|
||||
else GEODE_INT_BUILTIN_SETTING_IF(Float, getValue(), std::is_floating_point_v<T>)
|
||||
else GEODE_INT_BUILTIN_SETTING_IF(Int, getValue(), std::is_integral_v<T>)
|
||||
else GEODE_INT_BUILTIN_SETTING_IF(String, getValue(), std::is_same_v<T, std::string>)
|
||||
else GEODE_INT_BUILTIN_SETTING_IF(File, getValue(), std::is_same_v<T, ghc::filesystem::path>)
|
||||
else GEODE_INT_BUILTIN_SETTING_IF(Color, getValue(), std::is_same_v<T, cocos2d::ccColor3B>)
|
||||
else GEODE_INT_BUILTIN_SETTING_IF(ColorAlpha, getValue(), std::is_same_v<T, cocos2d::ccColor4B>)
|
||||
else {
|
||||
static_assert(!std::is_same_v<T, T>, "Unsupported type for getting setting value!");
|
||||
}
|
||||
return T();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void setBuiltInSettingValue(const std::shared_ptr<Setting> setting, T const& value) {
|
||||
GEODE_INT_BUILTIN_SETTING_IF(Bool, setValue(value), std::is_same_v<T, bool>)
|
||||
else GEODE_INT_BUILTIN_SETTING_IF(Float, setValue(value), std::is_floating_point_v<T>)
|
||||
else GEODE_INT_BUILTIN_SETTING_IF(Int, setValue(value), std::is_integral_v<T>)
|
||||
else GEODE_INT_BUILTIN_SETTING_IF(String, setValue(value), std::is_same_v<T, std::string>)
|
||||
else GEODE_INT_BUILTIN_SETTING_IF(File, setValue(value), std::is_same_v<T, ghc::filesystem::path>)
|
||||
else GEODE_INT_BUILTIN_SETTING_IF(Color, setValue(value), std::is_same_v<T, cocos2d::ccColor3B>)
|
||||
else GEODE_INT_BUILTIN_SETTING_IF(ColorAlpha, setValue(value), std::is_same_v<T, cocos2d::ccColor4B>)
|
||||
else {
|
||||
static_assert(!std::is_same_v<T, T>, "Unsupported type for getting setting value!");
|
||||
bool save(nlohmann::json& json) const {
|
||||
json = m_value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
GEODE_DLL SettingNode* createNode(float width) override;
|
||||
T castDefinition() const {
|
||||
return m_definition;
|
||||
}
|
||||
Setting getDefinition() const {
|
||||
return Setting(m_key, m_definition);
|
||||
}
|
||||
|
||||
ValueType getValue() const {
|
||||
return m_value;
|
||||
}
|
||||
void setValue(ValueType const& value) {
|
||||
m_value = this->toValid(value).first;
|
||||
}
|
||||
Result<> validate(ValueType const& value) const {
|
||||
auto reason = this->toValid(value).second;
|
||||
if (reason.has_value()) {
|
||||
return Err(static_cast<std::string>(reason.value()));
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
};
|
||||
|
||||
using BoolSettingValue = GeodeSettingValue<BoolSetting>;
|
||||
using IntSettingValue = GeodeSettingValue<IntSetting>;
|
||||
using FloatSettingValue = GeodeSettingValue<FloatSetting>;
|
||||
using StringSettingValue = GeodeSettingValue<StringSetting>;
|
||||
using FileSettingValue = GeodeSettingValue<FileSetting>;
|
||||
using ColorSettingValue = GeodeSettingValue<ColorSetting>;
|
||||
using ColorAlphaSettingValue = GeodeSettingValue<ColorAlphaSetting>;
|
||||
|
||||
template<class T>
|
||||
struct SettingValueSetter {
|
||||
static GEODE_DLL T get(SettingValue* setting);
|
||||
static GEODE_DLL void set(SettingValue* setting, T const& value);
|
||||
};
|
||||
}
|
||||
|
||||
#pragma warning(pop)
|
||||
|
|
|
@ -7,67 +7,73 @@
|
|||
#include <optional>
|
||||
|
||||
namespace geode {
|
||||
class GEODE_DLL SettingChangedEvent : public Event {
|
||||
protected:
|
||||
std::string m_modID;
|
||||
Setting* m_setting;
|
||||
struct GEODE_DLL SettingChangedEvent : public Event {
|
||||
Mod* mod;
|
||||
SettingValue* value;
|
||||
|
||||
public:
|
||||
SettingChangedEvent(std::string const& modID, Setting* setting);
|
||||
std::string getModID() const;
|
||||
Setting* getSetting() const;
|
||||
SettingChangedEvent(Mod* mod, SettingValue* value);
|
||||
};
|
||||
|
||||
template <typename T = Setting, typename = std::enable_if_t<std::is_base_of_v<Setting, T>>>
|
||||
class SettingChangedFilter : public EventFilter<SettingChangedEvent> {
|
||||
class GEODE_DLL SettingChangedFilter : public EventFilter<SettingChangedEvent> {
|
||||
protected:
|
||||
std::string m_modID;
|
||||
std::optional<std::string> m_targetKey;
|
||||
|
||||
public:
|
||||
using Callback = void(T*);
|
||||
using Event = SettingChangedEvent;
|
||||
using Callback = void(SettingValue*);
|
||||
|
||||
ListenerResult handle(std::function<Callback> fn, SettingChangedEvent* event);
|
||||
/**
|
||||
* Listen to changes on a setting, or all settings
|
||||
* @param modID Mod whose settings to listen to
|
||||
* @param settingID Setting to listen to, or all settings if nullopt
|
||||
*/
|
||||
SettingChangedFilter(
|
||||
std::string const& modID,
|
||||
std::optional<std::string> const& settingKey
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen for built-in setting changes
|
||||
*/
|
||||
template<class T>
|
||||
class GeodeSettingChangedFilter : public SettingChangedFilter {
|
||||
public:
|
||||
using Callback = void(T);
|
||||
|
||||
ListenerResult handle(std::function<Callback> fn, SettingChangedEvent* event) {
|
||||
if (m_modID == event->getModID() &&
|
||||
(!m_targetKey || m_targetKey.value() == event->getSetting()->getKey())) {
|
||||
fn(static_cast<T*>(event->getSetting()));
|
||||
if (
|
||||
m_modID == event->mod->getID() &&
|
||||
(!m_targetKey || m_targetKey.value() == event->value->getKey())
|
||||
) {
|
||||
fn(SettingValueSetter<T>::get(event->value));
|
||||
}
|
||||
return ListenerResult::Propagate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to changes on a specific setting
|
||||
*/
|
||||
SettingChangedFilter(
|
||||
std::string const& modID, std::string const& settingID
|
||||
) :
|
||||
m_modID(modID),
|
||||
m_targetKey(settingID) {}
|
||||
|
||||
/**
|
||||
* Listen to changes on all of a mods' settings
|
||||
*/
|
||||
SettingChangedFilter(std::string const& modID) :
|
||||
m_modID(modID), m_targetKey(std::nullopt) {}
|
||||
protected:
|
||||
std::string m_modID;
|
||||
std::optional<std::string> m_targetKey;
|
||||
GeodeSettingChangedFilter(
|
||||
std::string const& modID,
|
||||
std::string const& settingID
|
||||
) : SettingChangedFilter(modID, settingID) {}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
requires std::is_base_of_v<Setting, T>
|
||||
std::monostate listenForSettingChanges(
|
||||
std::string const& settingID, void (*callback)(T*)
|
||||
std::string const& settingKey, void (*callback)(T)
|
||||
) {
|
||||
Loader::get()->scheduleOnModLoad(getMod(), [=]() {
|
||||
new EventListener(
|
||||
callback, SettingChangedFilter<T>(getMod()->getID(), settingID)
|
||||
callback, GeodeSettingChangedFilter<T>(getMod()->getID(), settingKey)
|
||||
);
|
||||
});
|
||||
return std::monostate();
|
||||
}
|
||||
|
||||
static std::monostate listenForAllSettingChanges(void (*callback)(Setting*)) {
|
||||
static std::monostate listenForAllSettingChanges(void (*callback)(SettingValue*)) {
|
||||
Loader::get()->scheduleOnModLoad(getMod(), [=]() {
|
||||
new EventListener(
|
||||
callback, SettingChangedFilter(getMod()->getID())
|
||||
callback, SettingChangedFilter(getMod()->getID(), std::nullopt)
|
||||
);
|
||||
});
|
||||
return std::monostate();
|
||||
|
|
|
@ -7,17 +7,19 @@
|
|||
namespace geode {
|
||||
class SettingNode;
|
||||
|
||||
struct GEODE_DLL SettingNodeDelegate {
|
||||
virtual void settingValueChanged(SettingNode* node);
|
||||
virtual void settingValueCommitted(SettingNode* node);
|
||||
struct SettingNodeDelegate {
|
||||
virtual void settingValueChanged(SettingNode* node) {}
|
||||
virtual void settingValueCommitted(SettingNode* node) {}
|
||||
};
|
||||
|
||||
class GEODE_DLL SettingNode : public cocos2d::CCNode {
|
||||
protected:
|
||||
std::shared_ptr<Setting> m_setting;
|
||||
SettingValue* m_value;
|
||||
SettingNodeDelegate* m_delegate = nullptr;
|
||||
|
||||
bool init(std::shared_ptr<Setting> setting);
|
||||
bool init(SettingValue* value);
|
||||
void dispatchChanged();
|
||||
void dispatchCommitted();
|
||||
|
||||
public:
|
||||
void setDelegate(SettingNodeDelegate* delegate);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "../DefaultInclude.hpp"
|
||||
#include "../platform/cplatform.h"
|
||||
#include "../external/json/json.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
@ -147,6 +148,7 @@ namespace geode {
|
|||
class FieldIntermediate;
|
||||
}
|
||||
|
||||
using ModJson = nlohmann::ordered_json;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -105,6 +105,7 @@ namespace geode {
|
|||
);
|
||||
|
||||
GEODE_DLL bool isError() const;
|
||||
GEODE_DLL std::string getError() const;
|
||||
|
||||
GEODE_DLL operator bool() const;
|
||||
};
|
||||
|
|
|
@ -311,12 +311,14 @@ namespace geode {
|
|||
class EventListenerNode : public cocos2d::CCNode {
|
||||
protected:
|
||||
EventListener<Filter> m_listener;
|
||||
|
||||
EventListenerNode(EventListener<Filter>&& listener)
|
||||
: m_listener(std::move(listener)) {}
|
||||
|
||||
public:
|
||||
static EventListenerNode* create(EventListener<Filter> listener) {
|
||||
auto ret = new EventListenerNode();
|
||||
auto ret = new EventListenerNode(std::move(listener));
|
||||
if (ret && ret->init()) {
|
||||
ret->m_listener = listener;
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
|
@ -325,9 +327,8 @@ namespace geode {
|
|||
}
|
||||
|
||||
static EventListenerNode* create(typename Filter::Callback callback) {
|
||||
auto ret = new EventListenerNode();
|
||||
auto ret = new EventListenerNode(EventListener<Filter>(callback));
|
||||
if (ret && ret->init()) {
|
||||
ret->m_listener = EventListener(callback);
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
|
@ -343,9 +344,8 @@ namespace geode {
|
|||
// for some reason msvc won't let me just call EventListenerNode::create...
|
||||
// it claims no return value...
|
||||
// despite me writing return EventListenerNode::create()......
|
||||
auto ret = new EventListenerNode();
|
||||
auto ret = new EventListenerNode(EventListener<Filter>(cls, callback));
|
||||
if (ret && ret->init()) {
|
||||
ret->m_listener.bind(cls, callback);
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,12 @@
|
|||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
// allow converting ghc filesystem to json and back
|
||||
namespace ghc::filesystem {
|
||||
void GEODE_DLL to_json(nlohmann::json& json, path const& path);
|
||||
void GEODE_DLL from_json(nlohmann::json const& json, path& path);
|
||||
}
|
||||
|
||||
namespace geode::utils::file {
|
||||
GEODE_DLL Result<std::string> readString(ghc::filesystem::path const& path);
|
||||
GEODE_DLL Result<nlohmann::json> readJson(ghc::filesystem::path const& path);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
#include "LoaderImpl.hpp"
|
||||
|
||||
#include <cocos2d.h>
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <Geode/loader/IPC.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
|
@ -154,7 +154,8 @@ bool Loader::Impl::isModVersionSupported(VersionInfo const& version) {
|
|||
|
||||
Result<> Loader::Impl::saveData() {
|
||||
// save mods' data
|
||||
for (auto& [_, mod] : m_mods) {
|
||||
for (auto& [id, mod] : m_mods) {
|
||||
InternalMod::get()->setSavedValue("should-load-" + id, mod->isEnabled());
|
||||
auto r = mod->saveData();
|
||||
if (!r) {
|
||||
log::warn("Unable to save data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
|
||||
|
@ -254,10 +255,10 @@ void Loader::Impl::dispatchScheduledFunctions(Mod* mod) {
|
|||
}
|
||||
|
||||
void Loader::Impl::scheduleOnModLoad(Mod* mod, ScheduledFunction func) {
|
||||
std::lock_guard _(m_scheduledFunctionsMutex);
|
||||
if (mod) {
|
||||
return func();
|
||||
}
|
||||
std::lock_guard _(m_scheduledFunctionsMutex);
|
||||
m_scheduledFunctions.push_back(func);
|
||||
}
|
||||
|
||||
|
@ -403,9 +404,6 @@ bool Loader::Impl::didLastLaunchCrash() const {
|
|||
return crashlog::didLastLaunchCrash();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Loader::Impl::reset() {
|
||||
this->closePlatformConsole();
|
||||
|
||||
|
@ -417,6 +415,7 @@ void Loader::Impl::reset() {
|
|||
ghc::filesystem::remove_all(dirs::getModRuntimeDir());
|
||||
ghc::filesystem::remove_all(dirs::getTempDir());
|
||||
}
|
||||
|
||||
bool Loader::Impl::isReadyToHook() const {
|
||||
return m_readyToHook;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,14 @@ Mod::Mod(ModInfo const& info) {
|
|||
m_info = info;
|
||||
m_saveDirPath = dirs::getModsSaveDir() / info.id;
|
||||
ghc::filesystem::create_directories(m_saveDirPath);
|
||||
this->setupSettings();
|
||||
auto loadRes = this->loadData();
|
||||
if (!loadRes) {
|
||||
log::warn(
|
||||
"Unable to load data for \"{}\": {}",
|
||||
info.id, loadRes.unwrapErr()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Mod::~Mod() {
|
||||
|
@ -93,14 +101,6 @@ std::vector<Hook*> Mod::getHooks() const {
|
|||
return m_hooks;
|
||||
}
|
||||
|
||||
bool Mod::hasSettings() const {
|
||||
return m_info.settings.size();
|
||||
}
|
||||
|
||||
decltype(ModInfo::settings) Mod::getSettings() const {
|
||||
return m_info.settings;
|
||||
}
|
||||
|
||||
// Settings and saved values
|
||||
|
||||
Result<> Mod::loadData() {
|
||||
|
@ -117,12 +117,20 @@ Result<> Mod::loadData() {
|
|||
JsonChecker checker(json);
|
||||
auto root = checker.root("[settings.json]");
|
||||
|
||||
m_savedSettingsData = json;
|
||||
|
||||
for (auto& [key, value] : root.items()) {
|
||||
// check if this is a known setting
|
||||
if (auto setting = this->getSetting(key)) {
|
||||
// load its value
|
||||
if (!setting->load(value.json()))
|
||||
return Err("Unable to load value for setting \"" + key + "\"");
|
||||
if (!setting->load(value.json())) {
|
||||
log::log(
|
||||
Severity::Error,
|
||||
this,
|
||||
"{}: Unable to load value for setting \"{}\"",
|
||||
m_info.id, key
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
log::log(
|
||||
|
@ -158,26 +166,91 @@ Result<> Mod::loadData() {
|
|||
Result<> Mod::saveData() {
|
||||
ModStateEvent(this, ModEventType::DataSaved).post();
|
||||
|
||||
// Data saving should be fully fail-safe
|
||||
|
||||
std::unordered_set<std::string> coveredSettings;
|
||||
|
||||
// Settings
|
||||
auto json = nlohmann::json::object();
|
||||
for (auto& [key, value] : m_info.settings) {
|
||||
if (!value->save(json[key])) return Err("Unable to save setting \"" + key + "\"");
|
||||
for (auto& [key, value] : m_settings) {
|
||||
coveredSettings.insert(key);
|
||||
if (!value->save(json[key])) {
|
||||
log::error("Unable to save setting \"" + key + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
GEODE_UNWRAP(utils::file::writeString(m_saveDirPath / "settings.json", json.dump(4)));
|
||||
// if some settings weren't provided a custom settings handler (for example,
|
||||
// the mod was not loaded) then make sure to save their previous state in
|
||||
// order to not lose data
|
||||
try {
|
||||
log::debug("Check covered");
|
||||
for (auto& [key, value] : m_savedSettingsData.items()) {
|
||||
log::debug("Check if {} is saved", key);
|
||||
if (!coveredSettings.count(key)) {
|
||||
json[key] = value;
|
||||
}
|
||||
}
|
||||
} catch(...) {}
|
||||
|
||||
// Saved values
|
||||
GEODE_UNWRAP(utils::file::writeString(m_saveDirPath / "saved.json", m_saved.dump(4)));
|
||||
auto res = utils::file::writeString(m_saveDirPath / "settings.json", json.dump(4));
|
||||
if (!res) {
|
||||
log::error("Unable to save settings: {}", res.unwrapErr());
|
||||
}
|
||||
|
||||
auto res2 = utils::file::writeString(m_saveDirPath / "saved.json", m_saved.dump(4));
|
||||
if (!res2) {
|
||||
log::error("Unable to save values: {}", res2.unwrapErr());
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
std::shared_ptr<Setting> Mod::getSetting(std::string const& key) const {
|
||||
void Mod::setupSettings() {
|
||||
for (auto& [key, sett] : m_info.settings) {
|
||||
if (auto value = sett.createDefaultValue()) {
|
||||
m_settings.emplace(key, std::move(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Mod::registerCustomSetting(
|
||||
std::string const& key,
|
||||
std::unique_ptr<SettingValue> value
|
||||
) {
|
||||
if (!m_settings.count(key)) {
|
||||
// load data
|
||||
if (m_savedSettingsData.count(key)) {
|
||||
value->load(m_savedSettingsData.at(key));
|
||||
}
|
||||
m_settings.emplace(key, std::move(value));
|
||||
}
|
||||
}
|
||||
|
||||
bool Mod::hasSettings() const {
|
||||
return m_info.settings.size();
|
||||
}
|
||||
|
||||
std::vector<std::string> Mod::getSettingKeys() const {
|
||||
std::vector<std::string> keys;
|
||||
for (auto& [key, _] : m_info.settings) {
|
||||
keys.push_back(key);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
std::optional<Setting> Mod::getSettingDefinition(std::string const& key) const {
|
||||
for (auto& setting : m_info.settings) {
|
||||
if (setting.first == key) {
|
||||
return setting.second;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
SettingValue* Mod::getSetting(std::string const& key) const {
|
||||
if (m_settings.count(key)) {
|
||||
return m_settings.at(key).get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -211,11 +284,6 @@ Result<> Mod::loadBinary() {
|
|||
|
||||
ModStateEvent(this, ModEventType::Loaded).post();
|
||||
|
||||
auto loadRes = this->loadData();
|
||||
if (!loadRes) {
|
||||
log::warn("Unable to load data for \"{}\": {}", m_info.id, loadRes.unwrapErr());
|
||||
}
|
||||
|
||||
Loader::get()->updateAllDependencies();
|
||||
|
||||
GEODE_UNWRAP(this->enable());
|
||||
|
|
|
@ -70,8 +70,7 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
|
|||
}
|
||||
|
||||
for (auto& [key, value] : root.has("settings").items()) {
|
||||
GEODE_UNWRAP_INTO(auto sett, Setting::parse(key, value.json()));
|
||||
sett->m_modID = info.id;
|
||||
GEODE_UNWRAP_INTO(auto sett, Setting::parse(key, value));
|
||||
info.settings.push_back({ key, sett });
|
||||
}
|
||||
|
||||
|
|
|
@ -4,106 +4,361 @@
|
|||
#include <Geode/loader/SettingEvent.hpp>
|
||||
#include <Geode/loader/SettingNode.hpp>
|
||||
#include <Geode/utils/general.hpp>
|
||||
#include <regex>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
#define PROPAGATE(err) \
|
||||
{ \
|
||||
auto err__ = err; \
|
||||
if (!err__) return Err(err__.error()); \
|
||||
}
|
||||
template<class T>
|
||||
static void parseCommon(T& sett, JsonMaybeObject<ModJson>& obj) {
|
||||
obj.has("name").into(sett.name);
|
||||
obj.has("description").into(sett.description);
|
||||
obj.has("default").into(sett.defaultValue);
|
||||
}
|
||||
|
||||
std::string Setting::getKey() const {
|
||||
Result<BoolSetting> BoolSetting::parse(JsonMaybeObject<ModJson>& obj) {
|
||||
BoolSetting sett;
|
||||
parseCommon(sett, obj);
|
||||
return Ok(sett);
|
||||
}
|
||||
|
||||
Result<IntSetting> IntSetting::parse(JsonMaybeObject<ModJson>& obj) {
|
||||
IntSetting sett;
|
||||
parseCommon(sett, obj);
|
||||
obj.has("min").into(sett.min);
|
||||
obj.has("max").into(sett.max);
|
||||
if (auto controls = obj.has("control").obj()) {
|
||||
controls.has("arrows").into(sett.controls.arrows);
|
||||
controls.has("big-arrows").into(sett.controls.bigArrows);
|
||||
controls.has("arrow-step").into(sett.controls.arrowStep);
|
||||
controls.has("big-arrow-step").into(sett.controls.bigArrowStep);
|
||||
controls.has("slider").into(sett.controls.slider);
|
||||
controls.has("slider-step").into(sett.controls.sliderStep);
|
||||
controls.has("input").into(sett.controls.input);
|
||||
}
|
||||
return Ok(sett);
|
||||
}
|
||||
|
||||
Result<FloatSetting> FloatSetting::parse(JsonMaybeObject<ModJson>& obj) {
|
||||
FloatSetting sett;
|
||||
parseCommon(sett, obj);
|
||||
obj.has("min").into(sett.min);
|
||||
obj.has("max").into(sett.max);
|
||||
if (auto controls = obj.has("control").obj()) {
|
||||
controls.has("arrows").into(sett.controls.arrows);
|
||||
controls.has("big-arrows").into(sett.controls.bigArrows);
|
||||
controls.has("arrow-step").into(sett.controls.arrowStep);
|
||||
controls.has("big-arrow-step").into(sett.controls.bigArrowStep);
|
||||
controls.has("slider").into(sett.controls.slider);
|
||||
controls.has("slider-step").into(sett.controls.sliderStep);
|
||||
controls.has("input").into(sett.controls.input);
|
||||
}
|
||||
return Ok(sett);
|
||||
}
|
||||
|
||||
Result<StringSetting> StringSetting::parse(JsonMaybeObject<ModJson>& obj) {
|
||||
StringSetting sett;
|
||||
parseCommon(sett, obj);
|
||||
obj.has("match").into(sett.match);
|
||||
return Ok(sett);
|
||||
}
|
||||
|
||||
Result<FileSetting> FileSetting::parse(JsonMaybeObject<ModJson>& obj) {
|
||||
FileSetting sett;
|
||||
parseCommon(sett, obj);
|
||||
if (auto controls = obj.has("control").obj()) {
|
||||
for (auto& item : controls.has("filters").iterate()) {
|
||||
if (auto iobj = item.obj()) {
|
||||
Filter filter;
|
||||
iobj.has("description").into(filter.description);
|
||||
iobj.has("files").into(filter.files);
|
||||
sett.controls.filters.push_back(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(sett);
|
||||
}
|
||||
|
||||
Result<ColorSetting> ColorSetting::parse(JsonMaybeObject<ModJson>& obj) {
|
||||
ColorSetting sett;
|
||||
parseCommon(sett, obj);
|
||||
return Ok(sett);
|
||||
}
|
||||
|
||||
Result<ColorAlphaSetting> ColorAlphaSetting::parse(JsonMaybeObject<ModJson>& obj) {
|
||||
ColorAlphaSetting sett;
|
||||
parseCommon(sett, obj);
|
||||
return Ok(sett);
|
||||
}
|
||||
|
||||
Result<Setting> Setting::parse(
|
||||
std::string const& key,
|
||||
JsonMaybeValue<ModJson>& value
|
||||
) {
|
||||
auto sett = Setting();
|
||||
sett.m_key = key;
|
||||
if (auto obj = value.obj()) {
|
||||
std::string type;
|
||||
obj.needs("type").into(type);
|
||||
if (type.size()) {
|
||||
switch (hash(type.c_str())) {
|
||||
case hash("bool"): {
|
||||
GEODE_UNWRAP_INTO(sett.m_kind, BoolSetting::parse(obj));
|
||||
} break;
|
||||
|
||||
case hash("int"): {
|
||||
GEODE_UNWRAP_INTO(sett.m_kind, IntSetting::parse(obj));
|
||||
} break;
|
||||
|
||||
case hash("float"): {
|
||||
GEODE_UNWRAP_INTO(sett.m_kind, FloatSetting::parse(obj));
|
||||
} break;
|
||||
|
||||
case hash("string"): {
|
||||
GEODE_UNWRAP_INTO(sett.m_kind, StringSetting::parse(obj));
|
||||
} break;
|
||||
|
||||
case hash("rgb"): case hash("color"): {
|
||||
GEODE_UNWRAP_INTO(sett.m_kind, ColorSetting::parse(obj));
|
||||
} break;
|
||||
|
||||
case hash("rgba"): {
|
||||
GEODE_UNWRAP_INTO(sett.m_kind, ColorAlphaSetting::parse(obj));
|
||||
} break;
|
||||
|
||||
case hash("path"): case hash("file"): {
|
||||
GEODE_UNWRAP_INTO(sett.m_kind, FileSetting::parse(obj));
|
||||
} break;
|
||||
|
||||
case hash("custom"): {
|
||||
sett.m_kind = CustomSetting {
|
||||
.json = obj.json()
|
||||
};
|
||||
// skip checking unknown keys
|
||||
return Ok(sett);
|
||||
} break;
|
||||
|
||||
default: return Err("Unknown setting type \"" + type + "\"");
|
||||
}
|
||||
}
|
||||
obj.checkUnknownKeys();
|
||||
}
|
||||
// if the type wasn't an object or a string, the JsonChecker that gave the
|
||||
// JsonMaybeValue will fail eventually so we can continue on
|
||||
return Ok(sett);
|
||||
}
|
||||
|
||||
Setting::Setting(std::string const& key, SettingKind const& kind)
|
||||
: m_key(key), m_kind(kind) {}
|
||||
|
||||
std::unique_ptr<SettingValue> Setting::createDefaultValue() const {
|
||||
return std::visit(makeVisitor {
|
||||
[&](BoolSetting const& sett) -> std::unique_ptr<SettingValue> {
|
||||
return std::make_unique<BoolSettingValue>(m_key, sett);
|
||||
},
|
||||
[&](IntSetting const& sett) -> std::unique_ptr<SettingValue> {
|
||||
return std::make_unique<IntSettingValue>(m_key, sett);
|
||||
},
|
||||
[&](FloatSetting const& sett) -> std::unique_ptr<SettingValue> {
|
||||
return std::make_unique<FloatSettingValue>(m_key, sett);
|
||||
},
|
||||
[&](StringSetting const& sett) -> std::unique_ptr<SettingValue> {
|
||||
return std::make_unique<StringSettingValue>(m_key, sett);
|
||||
},
|
||||
[&](FileSetting const& sett) -> std::unique_ptr<SettingValue> {
|
||||
return std::make_unique<FileSettingValue>(m_key, sett);
|
||||
},
|
||||
[&](ColorSetting const& sett) -> std::unique_ptr<SettingValue> {
|
||||
return std::make_unique<ColorSettingValue>(m_key, sett);
|
||||
},
|
||||
[&](ColorAlphaSetting const& sett) -> std::unique_ptr<SettingValue> {
|
||||
return std::make_unique<ColorAlphaSettingValue>(m_key, sett);
|
||||
},
|
||||
[&](auto const& sett) -> std::unique_ptr<SettingValue> {
|
||||
return nullptr;
|
||||
},
|
||||
}, m_kind);
|
||||
}
|
||||
|
||||
bool Setting::isCustom() const {
|
||||
return std::holds_alternative<CustomSetting>(m_kind);
|
||||
}
|
||||
|
||||
std::string Setting::getDisplayName() const {
|
||||
return std::visit(makeVisitor {
|
||||
[&](CustomSetting const& sett) {
|
||||
return std::string();
|
||||
},
|
||||
[&](auto const& sett) {
|
||||
return sett.name.value_or(m_key);
|
||||
},
|
||||
}, m_kind);
|
||||
}
|
||||
|
||||
std::optional<std::string> Setting::getDescription() const {
|
||||
return std::visit(makeVisitor {
|
||||
[&](CustomSetting const& sett) -> std::optional<std::string> {
|
||||
return std::nullopt;
|
||||
},
|
||||
[&](auto const& sett) {
|
||||
return sett.description;
|
||||
},
|
||||
}, m_kind);
|
||||
}
|
||||
|
||||
// SettingValue
|
||||
|
||||
SettingValue::SettingValue(std::string const& key) : m_key(key) {}
|
||||
|
||||
std::string SettingValue::getKey() const {
|
||||
return m_key;
|
||||
}
|
||||
|
||||
Result<std::shared_ptr<Setting>> Setting::parse(
|
||||
std::string const& type, std::string const& key, JsonMaybeObject<ModJson>& 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);
|
||||
case hash("rgb"):
|
||||
case hash("color"): return ColorSetting::parse(key, obj);
|
||||
case hash("rgba"): return ColorAlphaSetting::parse(key, obj);
|
||||
case hash("path"):
|
||||
case hash("file"): return FileSetting::parse(key, obj);
|
||||
default: return Err("Setting \"" + key + "\" has unknown type \"" + type + "\"");
|
||||
// GeodeSettingValue & SettingValueSetter specializations
|
||||
|
||||
#define IMPL_NODE_AND_SETTERS(type_) \
|
||||
template<> \
|
||||
SettingNode* GeodeSettingValue< \
|
||||
type_##Setting \
|
||||
>::createNode(float width) { \
|
||||
return type_##SettingNode::create(this, width); \
|
||||
} \
|
||||
typename type_##Setting::ValueType SettingValueSetter< \
|
||||
typename type_##Setting::ValueType \
|
||||
>::get(SettingValue* setting) { \
|
||||
if (auto b = typeinfo_cast<type_##SettingValue*>(setting)) { \
|
||||
return b->getValue(); \
|
||||
} \
|
||||
return typename type_##Setting::ValueType(); \
|
||||
} \
|
||||
void SettingValueSetter< \
|
||||
typename type_##Setting::ValueType \
|
||||
>::set( \
|
||||
SettingValue* setting, \
|
||||
typename type_##Setting::ValueType const& value \
|
||||
) { \
|
||||
if (auto b = typeinfo_cast<type_##SettingValue*>(setting)) { \
|
||||
b->setValue(value); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define IMPL_TO_VALID(type_) \
|
||||
template<> \
|
||||
typename GeodeSettingValue<type_##Setting>::Valid \
|
||||
GeodeSettingValue<type_##Setting>::toValid( \
|
||||
typename type_##Setting::ValueType const& value \
|
||||
) const
|
||||
|
||||
// instantiate value setters
|
||||
|
||||
template struct SettingValueSetter<typename BoolSetting::ValueType>;
|
||||
template struct SettingValueSetter<typename IntSetting::ValueType>;
|
||||
template struct SettingValueSetter<typename FloatSetting::ValueType>;
|
||||
template struct SettingValueSetter<typename StringSetting::ValueType>;
|
||||
template struct SettingValueSetter<typename FileSetting::ValueType>;
|
||||
template struct SettingValueSetter<typename ColorSetting::ValueType>;
|
||||
template struct SettingValueSetter<typename ColorAlphaSetting::ValueType>;
|
||||
|
||||
// instantiate values
|
||||
|
||||
template class GeodeSettingValue<BoolSetting>;
|
||||
template class GeodeSettingValue<IntSetting>;
|
||||
template class GeodeSettingValue<FloatSetting>;
|
||||
template class GeodeSettingValue<StringSetting>;
|
||||
template class GeodeSettingValue<FileSetting>;
|
||||
template class GeodeSettingValue<ColorSetting>;
|
||||
template class GeodeSettingValue<ColorAlphaSetting>;
|
||||
|
||||
IMPL_NODE_AND_SETTERS(Bool);
|
||||
IMPL_NODE_AND_SETTERS(Int);
|
||||
IMPL_NODE_AND_SETTERS(Float);
|
||||
IMPL_NODE_AND_SETTERS(String);
|
||||
IMPL_NODE_AND_SETTERS(File);
|
||||
IMPL_NODE_AND_SETTERS(Color);
|
||||
IMPL_NODE_AND_SETTERS(ColorAlpha);
|
||||
|
||||
IMPL_TO_VALID(Bool) {
|
||||
return { value, std::nullopt };
|
||||
}
|
||||
|
||||
Result<std::shared_ptr<Setting>> Setting::parse(std::string const& key, ModJson const& rawJson) {
|
||||
if (rawJson.is_object()) {
|
||||
auto json = rawJson;
|
||||
JsonChecker checker(json);
|
||||
auto root = checker.root("[setting \"" + key + "\"]").obj();
|
||||
IMPL_TO_VALID(Int) {
|
||||
if (m_definition.min && value < m_definition.min) {
|
||||
return { m_definition.min.value(), fmt::format(
|
||||
"Value must be more than or equal to {}",
|
||||
m_definition.min.value()
|
||||
) };
|
||||
}
|
||||
if (m_definition.max && value > m_definition.max) {
|
||||
return { m_definition.max.value(), fmt::format(
|
||||
"Value must be less than or equal to {}",
|
||||
m_definition.max.value()
|
||||
) };
|
||||
}
|
||||
return { value, std::nullopt };
|
||||
}
|
||||
|
||||
auto res = Setting::parse(root.needs("type").get<std::string>(), key, root);
|
||||
root.checkUnknownKeys();
|
||||
if (checker.isError()) {
|
||||
return Err(checker.getError());
|
||||
IMPL_TO_VALID(Float) {
|
||||
if (m_definition.min && value < m_definition.min) {
|
||||
return { m_definition.min.value(), fmt::format(
|
||||
"Value must be more than or equal to {}",
|
||||
m_definition.min.value()
|
||||
) };
|
||||
}
|
||||
if (m_definition.max && value > m_definition.max) {
|
||||
return { m_definition.max.value(), fmt::format(
|
||||
"Value must be less than or equal to {}",
|
||||
m_definition.max.value()
|
||||
) };
|
||||
}
|
||||
return { value, std::nullopt };
|
||||
}
|
||||
|
||||
IMPL_TO_VALID(String) {
|
||||
if (m_definition.match) {
|
||||
auto regex = std::regex(m_definition.match.value());
|
||||
if (!std::regex_match(value, regex)) {
|
||||
return {
|
||||
m_definition.defaultValue,
|
||||
fmt::format(
|
||||
"Value must match regex {}",
|
||||
m_definition.match.value()
|
||||
)
|
||||
};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
return Err("Setting value is not an object");
|
||||
return { value, std::nullopt };
|
||||
}
|
||||
|
||||
void Setting::valueChanged() {
|
||||
SettingChangedEvent(m_modID, this).post();
|
||||
IMPL_TO_VALID(File) {
|
||||
return { value, std::nullopt };
|
||||
}
|
||||
|
||||
SettingNode* BoolSetting::createNode(float width) {
|
||||
return BoolSettingNode::create(
|
||||
std::static_pointer_cast<BoolSetting>(shared_from_this()), width
|
||||
);
|
||||
IMPL_TO_VALID(Color) {
|
||||
return { value, std::nullopt };
|
||||
}
|
||||
|
||||
SettingNode* IntSetting::createNode(float width) {
|
||||
return IntSettingNode::create(std::static_pointer_cast<IntSetting>(shared_from_this()), width);
|
||||
IMPL_TO_VALID(ColorAlpha) {
|
||||
return { value, std::nullopt };
|
||||
}
|
||||
|
||||
SettingNode* FloatSetting::createNode(float width) {
|
||||
return FloatSettingNode::create(
|
||||
std::static_pointer_cast<FloatSetting>(shared_from_this()), width
|
||||
);
|
||||
// SettingChangedEvent
|
||||
|
||||
SettingChangedEvent::SettingChangedEvent(Mod* mod, SettingValue* value)
|
||||
: mod(mod), value(value) {}
|
||||
|
||||
// SettingChangedFilter
|
||||
|
||||
ListenerResult SettingChangedFilter::handle(
|
||||
std::function<Callback> fn, SettingChangedEvent* event
|
||||
) {
|
||||
if (m_modID == event->mod->getID() &&
|
||||
(!m_targetKey || m_targetKey.value() == event->value->getKey())
|
||||
) {
|
||||
fn(event->value);
|
||||
}
|
||||
return ListenerResult::Propagate;
|
||||
}
|
||||
|
||||
SettingNode* StringSetting::createNode(float width) {
|
||||
return StringSettingNode::create(
|
||||
std::static_pointer_cast<StringSetting>(shared_from_this()), width
|
||||
);
|
||||
}
|
||||
|
||||
SettingNode* FileSetting::createNode(float width) {
|
||||
return FileSettingNode::create(
|
||||
std::static_pointer_cast<FileSetting>(shared_from_this()), width
|
||||
);
|
||||
}
|
||||
|
||||
SettingNode* ColorSetting::createNode(float width) {
|
||||
return ColorSettingNode::create(
|
||||
std::static_pointer_cast<ColorSetting>(shared_from_this()), width
|
||||
);
|
||||
}
|
||||
|
||||
SettingNode* ColorAlphaSetting::createNode(float width) {
|
||||
return ColorAlphaSettingNode::create(
|
||||
std::static_pointer_cast<ColorAlphaSetting>(shared_from_this()), width
|
||||
);
|
||||
}
|
||||
|
||||
SettingChangedEvent::SettingChangedEvent(
|
||||
std::string const& modID, Setting* setting
|
||||
) :
|
||||
m_modID(modID),
|
||||
m_setting(setting) {}
|
||||
|
||||
std::string SettingChangedEvent::getModID() const {
|
||||
return m_modID;
|
||||
}
|
||||
|
||||
Setting* SettingChangedEvent::getSetting() const {
|
||||
return m_setting;
|
||||
}
|
||||
SettingChangedFilter::SettingChangedFilter(
|
||||
std::string const& modID,
|
||||
std::optional<std::string> const& settingKey
|
||||
) : m_modID(modID), m_targetKey(settingKey) {}
|
||||
|
|
|
@ -3,12 +3,20 @@
|
|||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
void SettingNodeDelegate::settingValueChanged(SettingNode* node) {}
|
||||
void SettingNode::dispatchChanged() {
|
||||
if (m_delegate) {
|
||||
m_delegate->settingValueChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingNodeDelegate::settingValueCommitted(SettingNode* node) {}
|
||||
void SettingNode::dispatchCommitted() {
|
||||
if (m_delegate) {
|
||||
m_delegate->settingValueCommitted(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool SettingNode::init(std::shared_ptr<Setting> setting) {
|
||||
m_setting = setting;
|
||||
bool SettingNode::init(SettingValue* value) {
|
||||
m_value = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -99,50 +99,48 @@ BOOL WINAPI DllMain(HINSTANCE lib, DWORD reason, LPVOID) {
|
|||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define $_ GEODE_CONCAT(unnamedVar_, __LINE__)
|
||||
|
||||
static auto $_ =
|
||||
listenForSettingChanges<BoolSetting>("show-platform-console", [](BoolSetting* setting) {
|
||||
if (setting->getValue()) {
|
||||
$execute {
|
||||
listenForSettingChanges("show-platform-console", +[](bool value) {
|
||||
if (value) {
|
||||
Loader::get()->openPlatformConsole();
|
||||
}
|
||||
else {
|
||||
Loader::get()->closePlatformConsole();
|
||||
}
|
||||
});
|
||||
|
||||
listenForIPC("ipc-test", [](IPCEvent* event) -> nlohmann::json {
|
||||
return "Hello from Geode!";
|
||||
});
|
||||
|
||||
static auto $_ = listenForIPC("ipc-test", [](IPCEvent* event) -> nlohmann::json {
|
||||
return "Hello from Geode!";
|
||||
});
|
||||
listenForIPC("loader-info", [](IPCEvent* event) -> nlohmann::json {
|
||||
return Loader::get()->getInternalMod()->getModInfo();
|
||||
});
|
||||
|
||||
static auto $_ = listenForIPC("loader-info", [](IPCEvent* event) -> nlohmann::json {
|
||||
return Loader::get()->getInternalMod()->getModInfo();
|
||||
});
|
||||
listenForIPC("list-mods", [](IPCEvent* event) -> nlohmann::json {
|
||||
std::vector<nlohmann::json> res;
|
||||
|
||||
static auto $_ = listenForIPC("list-mods", [](IPCEvent* event) -> nlohmann::json {
|
||||
std::vector<nlohmann::json> res;
|
||||
auto args = event->messageData;
|
||||
JsonChecker checker(args);
|
||||
auto root = checker.root("").obj();
|
||||
|
||||
auto args = event->messageData;
|
||||
JsonChecker checker(args);
|
||||
auto root = checker.root("").obj();
|
||||
auto includeRunTimeInfo = root.has("include-runtime-info").template get<bool>();
|
||||
auto dontIncludeLoader = root.has("dont-include-loader").template get<bool>();
|
||||
|
||||
auto includeRunTimeInfo = root.has("include-runtime-info").template get<bool>();
|
||||
auto dontIncludeLoader = root.has("dont-include-loader").template get<bool>();
|
||||
if (!dontIncludeLoader) {
|
||||
res.push_back(
|
||||
includeRunTimeInfo ? Loader::get()->getInternalMod()->getRuntimeInfo() :
|
||||
Loader::get()->getInternalMod()->getModInfo().toJSON()
|
||||
);
|
||||
}
|
||||
|
||||
if (!dontIncludeLoader) {
|
||||
res.push_back(
|
||||
includeRunTimeInfo ? Loader::get()->getInternalMod()->getRuntimeInfo() :
|
||||
Loader::get()->getInternalMod()->getModInfo().toJSON()
|
||||
);
|
||||
}
|
||||
for (auto& mod : Loader::get()->getAllMods()) {
|
||||
res.push_back(includeRunTimeInfo ? mod->getRuntimeInfo() : mod->getModInfo().toJSON());
|
||||
}
|
||||
|
||||
for (auto& mod : Loader::get()->getAllMods()) {
|
||||
res.push_back(includeRunTimeInfo ? mod->getRuntimeInfo() : mod->getModInfo().toJSON());
|
||||
}
|
||||
|
||||
return res;
|
||||
});
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
int geodeEntry(void* platformData) {
|
||||
// setup internals
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
#include <Geode/utils/string.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
std::string expandKnownLink(std::string const& link) {
|
||||
switch (hash(utils::string::toLower(link).c_str())) {
|
||||
case hash("github"):
|
||||
if (!utils::string::contains(link, "/")) {
|
||||
return "https://github.com/" + link;
|
||||
}
|
||||
break;
|
||||
|
||||
case hash("youtube"):
|
||||
if (!utils::string::contains(link, "/")) {
|
||||
return "https://youtube.com/channel/" + link;
|
||||
}
|
||||
break;
|
||||
|
||||
case hash("twitter"):
|
||||
if (!utils::string::contains(link, "/")) {
|
||||
return "https://twitter.com/" + link;
|
||||
}
|
||||
break;
|
||||
|
||||
case hash("newgrounds"):
|
||||
if (!utils::string::contains(link, "/")) {
|
||||
return "https://" + link + ".newgrounds.com";
|
||||
}
|
||||
break;
|
||||
}
|
||||
return link;
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/ui/ListView.hpp>
|
||||
#include <Geode/utils/string.hpp>
|
||||
#include <Geode/utils/ranges.hpp>
|
||||
|
||||
#define FTS_FUZZY_MATCH_IMPLEMENTATION
|
||||
#include <Geode/external/fts/fts_fuzzy_match.h>
|
||||
|
|
|
@ -8,6 +8,107 @@
|
|||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
|
||||
// Helpers
|
||||
|
||||
template <class Num>
|
||||
Num parseNumForInput(std::string const& str) {
|
||||
try {
|
||||
if constexpr (std::is_same_v<Num, int64_t>) {
|
||||
return std::stoll(str);
|
||||
}
|
||||
else if constexpr (std::is_same_v<Num, double>) {
|
||||
return std::stod(str);
|
||||
}
|
||||
else {
|
||||
static_assert(!std::is_same_v<Num, Num>, "Impl Num for parseNumForInput");
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static float valueToSlider(T const& setting, typename T::ValueType value) {
|
||||
auto min = setting.min ? setting.min.value() : -100;
|
||||
auto max = setting.max ? setting.max.value() : +100;
|
||||
auto range = max - min;
|
||||
return static_cast<float>(clamp(static_cast<double>(value - min) / range, 0.0, 1.0));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static typename T::ValueType valueFromSlider(T const& setting, float num) {
|
||||
auto min = setting.min ? setting.min.value() : -100;
|
||||
auto max = setting.max ? setting.max.value() : +100;
|
||||
auto range = max - min;
|
||||
auto value = static_cast<typename T::ValueType>(num * range + min);
|
||||
if (auto step = setting.controls.sliderStep) {
|
||||
value = static_cast<typename T::ValueType>(
|
||||
round(value / step.value()) * step.value()
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
template<class C, class T>
|
||||
InputNode* createInput(C* node, GeodeSettingValue<T>* setting, float width) {
|
||||
auto input = InputNode::create(width / 2 - 70.f, "Text", "chatFont.fnt");
|
||||
input->setPosition({
|
||||
-(width / 2 - 70.f) / 2,
|
||||
setting->castDefinition().controls.slider ? 5.f : 0.f
|
||||
});
|
||||
input->setScale(.65f);
|
||||
input->getInput()->setDelegate(node);
|
||||
return input;
|
||||
}
|
||||
|
||||
template<class C, class T>
|
||||
Slider* createSlider(C* node, GeodeSettingValue<T>* setting, float width) {
|
||||
auto slider = Slider::create(
|
||||
node, menu_selector(C::onSlider), .5f
|
||||
);
|
||||
slider->setPosition(-50.f, -15.f);
|
||||
node->updateSlider();
|
||||
return slider;
|
||||
}
|
||||
|
||||
template<class C, class T>
|
||||
std::pair<
|
||||
CCMenuItemSpriteExtra*, CCMenuItemSpriteExtra*
|
||||
> createArrows(C* node, GeodeSettingValue<T>* setting, float width, bool big) {
|
||||
auto yPos = setting->castDefinition().controls.slider ? 5.f : 0.f;
|
||||
auto decArrowSpr = CCSprite::createWithSpriteFrameName(
|
||||
big ? "double-nav.png"_spr : "navArrowBtn_001.png"
|
||||
);
|
||||
decArrowSpr->setFlipX(true);
|
||||
decArrowSpr->setScale(.3f);
|
||||
|
||||
auto decArrow = CCMenuItemSpriteExtra::create(
|
||||
decArrowSpr, node, menu_selector(C::onArrow)
|
||||
);
|
||||
decArrow->setPosition(-width / 2 + (big ? 65.f : 80.f), yPos);
|
||||
decArrow->setTag(big ?
|
||||
-setting->castDefinition().controls.bigArrowStep :
|
||||
-setting->castDefinition().controls.arrowStep
|
||||
);
|
||||
|
||||
auto incArrowSpr = CCSprite::createWithSpriteFrameName(
|
||||
big ? "double-nav.png"_spr : "navArrowBtn_001.png"
|
||||
);
|
||||
incArrowSpr->setScale(.3f);
|
||||
|
||||
auto incArrow = CCMenuItemSpriteExtra::create(
|
||||
incArrowSpr, node, menu_selector(C::onArrow)
|
||||
);
|
||||
incArrow->setTag(big ?
|
||||
setting->castDefinition().controls.bigArrowStep :
|
||||
setting->castDefinition().controls.arrowStep
|
||||
);
|
||||
incArrow->setPosition(big ? 5.f : -10.f, yPos);
|
||||
|
||||
return { decArrow, incArrow };
|
||||
}
|
||||
|
||||
// BoolSettingNode
|
||||
|
||||
void BoolSettingNode::valueChanged(bool updateText) {
|
||||
|
@ -21,7 +122,7 @@ void BoolSettingNode::onToggle(CCObject*) {
|
|||
m_toggle->toggle(!m_uncommittedValue);
|
||||
}
|
||||
|
||||
bool BoolSettingNode::setup(std::shared_ptr<BoolSetting> setting, float width) {
|
||||
bool BoolSettingNode::setup(BoolSettingValue* setting, float width) {
|
||||
m_toggle = CCMenuItemToggler::createWithStandardSprites(
|
||||
this, menu_selector(BoolSettingNode::onToggle), .6f
|
||||
);
|
||||
|
@ -34,42 +135,172 @@ bool BoolSettingNode::setup(std::shared_ptr<BoolSetting> setting, float width) {
|
|||
|
||||
// IntSettingNode
|
||||
|
||||
float IntSettingNode::setupHeight(std::shared_ptr<IntSetting> setting) const {
|
||||
return setting->hasSlider() ? 55.f : 40.f;
|
||||
float IntSettingNode::setupHeight(IntSettingValue* setting) const {
|
||||
return setting->castDefinition().controls.slider ? 55.f : 40.f;
|
||||
}
|
||||
|
||||
bool IntSettingNode::setup(std::shared_ptr<IntSetting> setting, float width) {
|
||||
this->setupArrows(setting, width);
|
||||
this->setupInput(setting, width);
|
||||
this->setupSlider(setting, width);
|
||||
return true;
|
||||
void IntSettingNode::onSlider(CCObject* slider) {
|
||||
m_uncommittedValue = valueFromSlider(
|
||||
setting()->castDefinition(),
|
||||
static_cast<SliderThumb*>(slider)->getValue()
|
||||
);
|
||||
this->valueChanged(true);
|
||||
}
|
||||
|
||||
void IntSettingNode::valueChanged(bool updateText) {
|
||||
GeodeSettingNode::valueChanged(updateText);
|
||||
this->updateLabel(updateText);
|
||||
if (updateText) {
|
||||
this->updateLabel();
|
||||
}
|
||||
this->updateSlider();
|
||||
}
|
||||
|
||||
void IntSettingNode::updateSlider() {
|
||||
if (m_slider) {
|
||||
m_slider->setValue(valueToSlider(
|
||||
setting()->castDefinition(),
|
||||
m_uncommittedValue
|
||||
));
|
||||
m_slider->updateBar();
|
||||
}
|
||||
}
|
||||
|
||||
void IntSettingNode::updateLabel() {
|
||||
if (m_input) {
|
||||
// hacky way to make setString not called textChanged
|
||||
m_input->getInput()->setDelegate(nullptr);
|
||||
m_input->setString(numToString(m_uncommittedValue));
|
||||
m_input->getInput()->setDelegate(this);
|
||||
}
|
||||
else {
|
||||
m_label->setString(numToString(m_uncommittedValue).c_str());
|
||||
m_label->limitLabelWidth(m_width / 2 - 70.f, .5f, .1f);
|
||||
}
|
||||
}
|
||||
|
||||
void IntSettingNode::onArrow(CCObject* sender) {
|
||||
m_uncommittedValue += sender->getTag();
|
||||
this->valueChanged(true);
|
||||
}
|
||||
|
||||
void IntSettingNode::textChanged(CCTextInputNode* input) {
|
||||
m_uncommittedValue = parseNumForInput<ValueType>(input->getString());
|
||||
this->valueChanged(false);
|
||||
}
|
||||
|
||||
bool IntSettingNode::setup(IntSettingValue* setting, float width) {
|
||||
if (setting->castDefinition().controls.input) {
|
||||
m_menu->addChild(m_input = createInput(this, setting, width));
|
||||
}
|
||||
else {
|
||||
m_label = CCLabelBMFont::create("", "bigFont.fnt");
|
||||
m_label->setPosition({
|
||||
-(width / 2 - 70.f) / 2,
|
||||
setting->castDefinition().controls.slider ? 5.f : 0.f
|
||||
});
|
||||
m_label->limitLabelWidth(width / 2 - 70.f, .5f, .1f);
|
||||
m_menu->addChild(m_label);
|
||||
}
|
||||
if (setting->castDefinition().controls.slider) {
|
||||
m_menu->addChild(m_slider = createSlider(this, setting, width));
|
||||
}
|
||||
if (setting->castDefinition().controls.arrows) {
|
||||
auto [dec, inc] = createArrows(this, setting, width, false);
|
||||
m_menu->addChild(m_decArrow = dec);
|
||||
m_menu->addChild(m_incArrow = inc);
|
||||
}
|
||||
if (setting->castDefinition().controls.bigArrows) {
|
||||
auto [dec, inc] = createArrows(this, setting, width, true);
|
||||
m_menu->addChild(m_bigDecArrow = dec);
|
||||
m_menu->addChild(m_bigIncArrow = inc);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// FloatSettingNode
|
||||
|
||||
float FloatSettingNode::setupHeight(std::shared_ptr<FloatSetting> setting) const {
|
||||
return setting->hasSlider() ? 55.f : 40.f;
|
||||
float FloatSettingNode::setupHeight(FloatSettingValue* setting) const {
|
||||
return setting->castDefinition().controls.slider ? 55.f : 40.f;
|
||||
}
|
||||
|
||||
bool FloatSettingNode::setup(std::shared_ptr<FloatSetting> setting, float width) {
|
||||
this->setupArrows(setting, width);
|
||||
this->setupInput(setting, width);
|
||||
this->setupSlider(setting, width);
|
||||
return true;
|
||||
void FloatSettingNode::onSlider(CCObject* slider) {
|
||||
m_uncommittedValue = valueFromSlider(
|
||||
setting()->castDefinition(),
|
||||
static_cast<SliderThumb*>(slider)->getValue()
|
||||
);
|
||||
this->valueChanged(true);
|
||||
}
|
||||
|
||||
void FloatSettingNode::valueChanged(bool updateText) {
|
||||
GeodeSettingNode::valueChanged(updateText);
|
||||
this->updateLabel(updateText);
|
||||
if (updateText) {
|
||||
this->updateLabel();
|
||||
}
|
||||
this->updateSlider();
|
||||
}
|
||||
|
||||
void FloatSettingNode::updateSlider() {
|
||||
if (m_slider) {
|
||||
m_slider->setValue(valueToSlider(
|
||||
setting()->castDefinition(),
|
||||
m_uncommittedValue
|
||||
));
|
||||
m_slider->updateBar();
|
||||
}
|
||||
}
|
||||
|
||||
void FloatSettingNode::updateLabel() {
|
||||
if (m_input) {
|
||||
// hacky way to make setString not called textChanged
|
||||
m_input->getInput()->setDelegate(nullptr);
|
||||
m_input->setString(numToString(m_uncommittedValue));
|
||||
m_input->getInput()->setDelegate(this);
|
||||
}
|
||||
else {
|
||||
m_label->setString(numToString(m_uncommittedValue).c_str());
|
||||
m_label->limitLabelWidth(m_width / 2 - 70.f, .5f, .1f);
|
||||
}
|
||||
}
|
||||
|
||||
void FloatSettingNode::onArrow(CCObject* sender) {
|
||||
m_uncommittedValue += sender->getTag();
|
||||
this->valueChanged(true);
|
||||
}
|
||||
|
||||
void FloatSettingNode::textChanged(CCTextInputNode* input) {
|
||||
m_uncommittedValue = parseNumForInput<ValueType>(input->getString());
|
||||
this->valueChanged(false);
|
||||
}
|
||||
|
||||
bool FloatSettingNode::setup(FloatSettingValue* setting, float width) {
|
||||
if (setting->castDefinition().controls.input) {
|
||||
m_menu->addChild(m_input = createInput(this, setting, width));
|
||||
}
|
||||
else {
|
||||
m_label = CCLabelBMFont::create("", "bigFont.fnt");
|
||||
m_label->setPosition({
|
||||
-(width / 2 - 70.f) / 2,
|
||||
setting->castDefinition().controls.slider ? 5.f : 0.f
|
||||
});
|
||||
m_label->limitLabelWidth(width / 2 - 70.f, .5f, .1f);
|
||||
m_menu->addChild(m_label);
|
||||
}
|
||||
if (setting->castDefinition().controls.slider) {
|
||||
m_menu->addChild(m_slider = createSlider(this, setting, width));
|
||||
}
|
||||
if (setting->castDefinition().controls.arrows) {
|
||||
auto [dec, inc] = createArrows(this, setting, width, false);
|
||||
m_menu->addChild(m_decArrow = dec);
|
||||
m_menu->addChild(m_incArrow = inc);
|
||||
}
|
||||
if (setting->castDefinition().controls.bigArrows) {
|
||||
auto [dec, inc] = createArrows(this, setting, width, true);
|
||||
m_menu->addChild(m_bigDecArrow = dec);
|
||||
m_menu->addChild(m_bigIncArrow = inc);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// StringSettingNode
|
||||
|
||||
void StringSettingNode::updateLabel() {
|
||||
|
@ -89,7 +320,7 @@ void StringSettingNode::valueChanged(bool updateText) {
|
|||
this->updateLabel();
|
||||
}
|
||||
|
||||
bool StringSettingNode::setup(std::shared_ptr<StringSetting> setting, float width) {
|
||||
bool StringSettingNode::setup(StringSettingValue* setting, float width) {
|
||||
m_input = InputNode::create(width / 2 - 10.f, "Text", "chatFont.fnt");
|
||||
m_input->setPosition({ -(width / 2 - 70.f) / 2, .0f });
|
||||
m_input->setScale(.65f);
|
||||
|
@ -119,20 +350,19 @@ void FileSettingNode::valueChanged(bool updateText) {
|
|||
}
|
||||
|
||||
void FileSettingNode::onPickFile(CCObject*) {
|
||||
auto setting = std::static_pointer_cast<FileSetting>(m_setting);
|
||||
if (auto path = file::pickFile(
|
||||
file::PickMode::OpenFile,
|
||||
{
|
||||
dirs::getGameDir(),
|
||||
setting->getFileFilters().value_or(std::vector<file::FilePickOptions::Filter>())
|
||||
}
|
||||
)) {
|
||||
file::PickMode::OpenFile,
|
||||
{
|
||||
dirs::getGameDir(),
|
||||
setting()->castDefinition().controls.filters
|
||||
}
|
||||
)) {
|
||||
m_uncommittedValue = path.unwrap();
|
||||
this->valueChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool FileSettingNode::setup(std::shared_ptr<FileSetting> setting, float width) {
|
||||
bool FileSettingNode::setup(FileSettingValue* setting, float width) {
|
||||
m_input = InputNode::create(width / 2 - 30.f, "Path to File", "chatFont.fnt");
|
||||
m_input->setPosition({ -(width / 2 - 80.f) / 2 - 15.f, .0f });
|
||||
m_input->setScale(.65f);
|
||||
|
@ -142,8 +372,9 @@ bool FileSettingNode::setup(std::shared_ptr<FileSetting> setting, float width) {
|
|||
auto fileBtnSpr = CCSprite::createWithSpriteFrameName("gj_folderBtn_001.png");
|
||||
fileBtnSpr->setScale(.5f);
|
||||
|
||||
auto fileBtn =
|
||||
CCMenuItemSpriteExtra::create(fileBtnSpr, this, menu_selector(FileSettingNode::onPickFile));
|
||||
auto fileBtn = CCMenuItemSpriteExtra::create(
|
||||
fileBtnSpr, this, menu_selector(FileSettingNode::onPickFile)
|
||||
);
|
||||
fileBtn->setPosition(.0f, .0f);
|
||||
m_menu->addChild(fileBtn);
|
||||
|
||||
|
@ -169,7 +400,7 @@ void ColorSettingNode::onSelectColor(CCObject*) {
|
|||
popup->show();
|
||||
}
|
||||
|
||||
bool ColorSettingNode::setup(std::shared_ptr<ColorSetting> setting, float width) {
|
||||
bool ColorSettingNode::setup(ColorSettingValue* setting, float width) {
|
||||
m_colorSpr = ColorChannelSprite::create();
|
||||
m_colorSpr->setColor(m_uncommittedValue);
|
||||
m_colorSpr->setScale(.65f);
|
||||
|
@ -203,7 +434,7 @@ void ColorAlphaSettingNode::onSelectColor(CCObject*) {
|
|||
popup->show();
|
||||
}
|
||||
|
||||
bool ColorAlphaSettingNode::setup(std::shared_ptr<ColorAlphaSetting> setting, float width) {
|
||||
bool ColorAlphaSettingNode::setup(ColorAlphaSettingValue* setting, float width) {
|
||||
m_colorSpr = ColorChannelSprite::create();
|
||||
m_colorSpr->setColor(to3B(m_uncommittedValue));
|
||||
m_colorSpr->updateOpacity(m_uncommittedValue.a / 255.f);
|
||||
|
@ -217,3 +448,53 @@ bool ColorAlphaSettingNode::setup(std::shared_ptr<ColorAlphaSetting> setting, fl
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
// CustomSettingPlaceholderNode
|
||||
|
||||
void CustomSettingPlaceholderNode::commit() {
|
||||
|
||||
}
|
||||
|
||||
bool CustomSettingPlaceholderNode::hasUncommittedChanges() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CustomSettingPlaceholderNode::hasNonDefaultValue() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void CustomSettingPlaceholderNode::resetToDefault() {
|
||||
|
||||
}
|
||||
|
||||
bool CustomSettingPlaceholderNode::init(std::string const& key, float width) {
|
||||
if (!SettingNode::init(nullptr))
|
||||
return false;
|
||||
|
||||
constexpr auto sidePad = 40.f;
|
||||
|
||||
this->setContentSize({ width, 40.f });
|
||||
|
||||
auto info = CCLabelBMFont::create(
|
||||
"You need to enable the mod to modify this setting.",
|
||||
"bigFont.fnt"
|
||||
);
|
||||
info->setAnchorPoint({ .0f, .5f });
|
||||
info->limitLabelWidth(width - sidePad, .5f, .1f);
|
||||
info->setPosition({ sidePad / 2, m_obContentSize.height / 2 });
|
||||
this->addChild(info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CustomSettingPlaceholderNode* CustomSettingPlaceholderNode::create(
|
||||
std::string const& key, float width
|
||||
) {
|
||||
auto ret = new CustomSettingPlaceholderNode;
|
||||
if (ret && ret->init(key, width)) {
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
CC_SAFE_DELETE(ret);
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -15,435 +15,256 @@
|
|||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
namespace {
|
||||
template <class Num>
|
||||
Num parseNumForInput(std::string const& str) {
|
||||
try {
|
||||
if constexpr (std::is_same_v<Num, int64_t>) {
|
||||
return std::stoll(str);
|
||||
}
|
||||
else if constexpr (std::is_same_v<Num, double>) {
|
||||
return std::stod(str);
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
return 0;
|
||||
}
|
||||
#define IMPL_SETT_CREATE(type_) \
|
||||
static type_##SettingNode* create( \
|
||||
type_##SettingValue* value, float width \
|
||||
) { \
|
||||
auto ret = new type_##SettingNode; \
|
||||
if (ret && ret->init(value, width)) { \
|
||||
ret->autorelease(); \
|
||||
return ret; \
|
||||
} \
|
||||
CC_SAFE_DELETE(ret); \
|
||||
return nullptr; \
|
||||
}
|
||||
|
||||
template <class N, class T>
|
||||
class GeodeSettingNode : public SettingNode {
|
||||
public:
|
||||
using value_t = typename T::value_t;
|
||||
template <class T>
|
||||
class GeodeSettingNode : public SettingNode {
|
||||
public:
|
||||
using ValueType = typename T::ValueType;
|
||||
|
||||
protected:
|
||||
float m_width;
|
||||
float m_height;
|
||||
value_t m_uncommittedValue;
|
||||
CCMenu* m_menu;
|
||||
CCLabelBMFont* m_nameLabel;
|
||||
CCLabelBMFont* m_errorLabel;
|
||||
CCMenuItemSpriteExtra* m_resetBtn;
|
||||
protected:
|
||||
float m_width;
|
||||
float m_height;
|
||||
ValueType m_uncommittedValue;
|
||||
CCMenu* m_menu;
|
||||
CCLabelBMFont* m_nameLabel;
|
||||
CCLabelBMFont* m_errorLabel;
|
||||
CCMenuItemSpriteExtra* m_resetBtn;
|
||||
|
||||
bool init(std::shared_ptr<T> setting, float width) {
|
||||
if (!SettingNode::init(std::static_pointer_cast<Setting>(setting))) return false;
|
||||
bool init(GeodeSettingValue<T>* setting, float width) {
|
||||
if (!SettingNode::init(setting))
|
||||
return false;
|
||||
|
||||
m_width = width;
|
||||
m_height = this->setupHeight(setting);
|
||||
this->setContentSize({ width, m_height });
|
||||
m_width = width;
|
||||
m_height = this->setupHeight(setting);
|
||||
this->setContentSize({ width, m_height });
|
||||
|
||||
constexpr auto sidePad = 40.f;
|
||||
constexpr auto sidePad = 40.f;
|
||||
|
||||
m_uncommittedValue = setting->getValue();
|
||||
m_uncommittedValue = setting->getValue();
|
||||
|
||||
auto name = setting->getDisplayName();
|
||||
auto name = setting->getDefinition().getDisplayName();
|
||||
|
||||
m_nameLabel = CCLabelBMFont::create(name.c_str(), "bigFont.fnt");
|
||||
m_nameLabel->setAnchorPoint({ .0f, .5f });
|
||||
m_nameLabel->limitLabelWidth(width / 2 - 50.f, .5f, .1f);
|
||||
m_nameLabel->setPosition({ sidePad / 2, m_obContentSize.height / 2 });
|
||||
this->addChild(m_nameLabel);
|
||||
m_nameLabel = CCLabelBMFont::create(name.c_str(), "bigFont.fnt");
|
||||
m_nameLabel->setAnchorPoint({ .0f, .5f });
|
||||
m_nameLabel->limitLabelWidth(width / 2 - 50.f, .5f, .1f);
|
||||
m_nameLabel->setPosition({ sidePad / 2, m_obContentSize.height / 2 });
|
||||
this->addChild(m_nameLabel);
|
||||
|
||||
m_errorLabel = CCLabelBMFont::create("", "bigFont.fnt");
|
||||
m_errorLabel->setAnchorPoint({ .0f, .5f });
|
||||
m_errorLabel->limitLabelWidth(width / 2 - 50.f, .25f, .1f);
|
||||
m_errorLabel->setPosition({ sidePad / 2, m_obContentSize.height / 2 - 14.f });
|
||||
m_errorLabel->setColor({ 255, 100, 100 });
|
||||
m_errorLabel->setZOrder(5);
|
||||
this->addChild(m_errorLabel);
|
||||
m_errorLabel = CCLabelBMFont::create("", "bigFont.fnt");
|
||||
m_errorLabel->setAnchorPoint({ .0f, .5f });
|
||||
m_errorLabel->limitLabelWidth(width / 2 - 50.f, .25f, .1f);
|
||||
m_errorLabel->setPosition({ sidePad / 2, m_obContentSize.height / 2 - 14.f });
|
||||
m_errorLabel->setColor({ 255, 100, 100 });
|
||||
m_errorLabel->setZOrder(5);
|
||||
this->addChild(m_errorLabel);
|
||||
|
||||
m_menu = CCMenu::create();
|
||||
m_menu->setPosition({ m_obContentSize.width - sidePad / 2, m_obContentSize.height / 2 }
|
||||
m_menu = CCMenu::create();
|
||||
m_menu->setPosition({ m_obContentSize.width - sidePad / 2, m_obContentSize.height / 2 }
|
||||
);
|
||||
this->addChild(m_menu);
|
||||
|
||||
float btnPos = 15.f;
|
||||
|
||||
if (setting->castDefinition().description) {
|
||||
auto infoSpr = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png");
|
||||
infoSpr->setScale(.6f);
|
||||
|
||||
auto infoBtn = CCMenuItemSpriteExtra::create(
|
||||
infoSpr, this, menu_selector(GeodeSettingNode::onDescription)
|
||||
);
|
||||
this->addChild(m_menu);
|
||||
infoBtn->setPosition(
|
||||
-m_obContentSize.width + sidePad + m_nameLabel->getScaledContentSize().width +
|
||||
btnPos,
|
||||
0.f
|
||||
);
|
||||
m_menu->addChild(infoBtn);
|
||||
|
||||
float btnPos = 15.f;
|
||||
|
||||
if (setting->getDescription()) {
|
||||
auto infoSpr = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png");
|
||||
infoSpr->setScale(.6f);
|
||||
|
||||
auto infoBtn = CCMenuItemSpriteExtra::create(
|
||||
infoSpr, this, menu_selector(GeodeSettingNode::onDescription)
|
||||
);
|
||||
infoBtn->setPosition(
|
||||
-m_obContentSize.width + sidePad + m_nameLabel->getScaledContentSize().width +
|
||||
btnPos,
|
||||
0.f
|
||||
);
|
||||
m_menu->addChild(infoBtn);
|
||||
|
||||
btnPos += 20.f;
|
||||
}
|
||||
|
||||
if (setting->canResetToDefault()) {
|
||||
auto resetBtnSpr = CCSprite::createWithSpriteFrameName("reset-gold.png"_spr);
|
||||
resetBtnSpr->setScale(.5f);
|
||||
|
||||
m_resetBtn = CCMenuItemSpriteExtra::create(
|
||||
resetBtnSpr, this, menu_selector(GeodeSettingNode::onReset)
|
||||
);
|
||||
m_resetBtn->setPosition(
|
||||
-m_obContentSize.width + sidePad + m_nameLabel->getScaledContentSize().width +
|
||||
btnPos,
|
||||
.0f
|
||||
);
|
||||
m_menu->addChild(m_resetBtn);
|
||||
}
|
||||
|
||||
CCDirector::sharedDirector()->getTouchDispatcher()->incrementForcePrio(2);
|
||||
m_menu->registerWithTouchDispatcher();
|
||||
|
||||
if (!this->setup(setting, width)) return false;
|
||||
|
||||
this->valueChanged();
|
||||
|
||||
return true;
|
||||
btnPos += 20.f;
|
||||
}
|
||||
|
||||
void onDescription(CCObject*) {
|
||||
auto setting = std::static_pointer_cast<T>(m_setting);
|
||||
FLAlertLayer::create(
|
||||
setting->getDisplayName().c_str(), setting->getDescription().value(), "OK"
|
||||
)
|
||||
->show();
|
||||
}
|
||||
auto resetBtnSpr = CCSprite::createWithSpriteFrameName("reset-gold.png"_spr);
|
||||
resetBtnSpr->setScale(.5f);
|
||||
|
||||
void onReset(CCObject*) {
|
||||
auto setting = std::static_pointer_cast<T>(m_setting);
|
||||
createQuickPopup(
|
||||
"Reset",
|
||||
"Are you sure you want to <cr>reset</c> <cl>" + setting->getDisplayName() +
|
||||
"</c> to <cy>default</c>?",
|
||||
"Cancel", "Reset",
|
||||
[this](auto, bool btn2) {
|
||||
if (btn2) this->resetToDefault();
|
||||
m_resetBtn = CCMenuItemSpriteExtra::create(
|
||||
resetBtnSpr, this, menu_selector(GeodeSettingNode::onReset)
|
||||
);
|
||||
m_resetBtn->setPosition(
|
||||
-m_obContentSize.width + sidePad + m_nameLabel->getScaledContentSize().width +
|
||||
btnPos,
|
||||
.0f
|
||||
);
|
||||
m_menu->addChild(m_resetBtn);
|
||||
|
||||
CCDirector::sharedDirector()->getTouchDispatcher()->incrementForcePrio(2);
|
||||
m_menu->registerWithTouchDispatcher();
|
||||
|
||||
if (!this->setup(setting, width)) return false;
|
||||
|
||||
this->valueChanged();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void onDescription(CCObject*) {
|
||||
FLAlertLayer::create(
|
||||
setting()->getDefinition().getDisplayName().c_str(),
|
||||
setting()->castDefinition().description.value(),
|
||||
"OK"
|
||||
)->show();
|
||||
}
|
||||
|
||||
void onReset(CCObject*) {
|
||||
createQuickPopup(
|
||||
"Reset",
|
||||
"Are you sure you want to <cr>reset</c> <cl>" +
|
||||
setting()->getDefinition().getDisplayName() +
|
||||
"</c> to <cy>default</c>?",
|
||||
"Cancel", "Reset",
|
||||
[this](auto, bool btn2) {
|
||||
if (btn2) {
|
||||
this->resetToDefault();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
virtual float setupHeight(GeodeSettingValue<T>* setting) const {
|
||||
return 40.f;
|
||||
}
|
||||
|
||||
virtual bool setup(GeodeSettingValue<T>* setting, float width) = 0;
|
||||
|
||||
virtual void valueChanged(bool updateText = true) {
|
||||
if (this->hasUncommittedChanges()) {
|
||||
m_nameLabel->setColor(cc3x(0x1d0));
|
||||
}
|
||||
|
||||
virtual float setupHeight(std::shared_ptr<T> setting) const {
|
||||
return 40.f;
|
||||
else {
|
||||
m_nameLabel->setColor(cc3x(0xfff));
|
||||
}
|
||||
|
||||
virtual bool setup(std::shared_ptr<T> setting, float width) = 0;
|
||||
|
||||
virtual void valueChanged(bool updateText = true) {
|
||||
if (m_delegate) m_delegate->settingValueChanged(this);
|
||||
if (this->hasUncommittedChanges()) {
|
||||
m_nameLabel->setColor(cc3x(0x1d0));
|
||||
}
|
||||
else {
|
||||
m_nameLabel->setColor(cc3x(0xfff));
|
||||
}
|
||||
if (m_resetBtn) m_resetBtn->setVisible(this->hasNonDefaultValue());
|
||||
auto isValid = std::static_pointer_cast<T>(m_setting)->isValidValue(m_uncommittedValue);
|
||||
if (!isValid) {
|
||||
m_errorLabel->setVisible(true);
|
||||
m_errorLabel->setString(isValid.unwrapErr().c_str());
|
||||
}
|
||||
else {
|
||||
m_errorLabel->setVisible(false);
|
||||
}
|
||||
if (m_resetBtn) m_resetBtn->setVisible(this->hasNonDefaultValue());
|
||||
auto isValid = setting()->validate(m_uncommittedValue);
|
||||
if (!isValid) {
|
||||
m_errorLabel->setVisible(true);
|
||||
m_errorLabel->setString(isValid.unwrapErr().c_str());
|
||||
}
|
||||
|
||||
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;
|
||||
else {
|
||||
m_errorLabel->setVisible(false);
|
||||
}
|
||||
this->dispatchChanged();
|
||||
}
|
||||
|
||||
void commit() override {
|
||||
std::static_pointer_cast<T>(m_setting)->setValue(m_uncommittedValue);
|
||||
m_uncommittedValue = std::static_pointer_cast<T>(m_setting)->getValue();
|
||||
this->valueChanged();
|
||||
if (m_delegate) m_delegate->settingValueCommitted(this);
|
||||
}
|
||||
GeodeSettingValue<T>* setting() {
|
||||
return static_cast<GeodeSettingValue<T>*>(m_value);
|
||||
}
|
||||
|
||||
bool hasUncommittedChanges() override {
|
||||
return m_uncommittedValue != std::static_pointer_cast<T>(m_setting)->getValue();
|
||||
}
|
||||
public:
|
||||
void commit() override {
|
||||
setting()->setValue(m_uncommittedValue);
|
||||
m_uncommittedValue = setting()->getValue();
|
||||
this->valueChanged();
|
||||
this->dispatchCommitted();
|
||||
}
|
||||
|
||||
bool hasNonDefaultValue() override {
|
||||
return m_uncommittedValue != std::static_pointer_cast<T>(m_setting)->getDefault();
|
||||
}
|
||||
bool hasUncommittedChanges() override {
|
||||
return m_uncommittedValue != setting()->getValue();
|
||||
}
|
||||
|
||||
void resetToDefault() override {
|
||||
m_uncommittedValue = std::static_pointer_cast<T>(m_setting)->getDefault();
|
||||
this->valueChanged();
|
||||
}
|
||||
};
|
||||
bool hasNonDefaultValue() override {
|
||||
return m_uncommittedValue != setting()->castDefinition().defaultValue;
|
||||
}
|
||||
|
||||
template <class C, class T>
|
||||
class ImplInput : public TextInputDelegate {
|
||||
protected:
|
||||
InputNode* m_input = nullptr;
|
||||
CCLabelBMFont* m_label = nullptr;
|
||||
void resetToDefault() override {
|
||||
m_uncommittedValue = setting()->castDefinition().defaultValue;
|
||||
this->valueChanged();
|
||||
}
|
||||
};
|
||||
|
||||
C* self() {
|
||||
return static_cast<C*>(this);
|
||||
}
|
||||
|
||||
void setupInput(std::shared_ptr<T> setting, float width) {
|
||||
if (setting->hasInput()) {
|
||||
m_input = InputNode::create(width / 2 - 70.f, "Text", "chatFont.fnt");
|
||||
m_input->setPosition({ -(width / 2 - 70.f) / 2, setting->hasSlider() ? 5.f : 0.f });
|
||||
m_input->setScale(.65f);
|
||||
m_input->getInput()->setDelegate(this);
|
||||
self()->m_menu->addChild(m_input);
|
||||
}
|
||||
else {
|
||||
m_label = CCLabelBMFont::create("", "bigFont.fnt");
|
||||
m_label->setPosition({ -(width / 2 - 70.f) / 2, setting->hasSlider() ? 5.f : 0.f });
|
||||
m_label->limitLabelWidth(width / 2 - 70.f, .5f, .1f);
|
||||
self()->m_menu->addChild(m_label);
|
||||
}
|
||||
}
|
||||
|
||||
void updateLabel(bool updateText) {
|
||||
if (!updateText) return;
|
||||
if (m_input) {
|
||||
// hacky way to make setString not called textChanged
|
||||
m_input->getInput()->setDelegate(nullptr);
|
||||
m_input->setString(numToString(self()->m_uncommittedValue));
|
||||
m_input->getInput()->setDelegate(this);
|
||||
}
|
||||
else {
|
||||
m_label->setString(numToString(self()->m_uncommittedValue).c_str());
|
||||
m_label->limitLabelWidth(self()->m_width / 2 - 70.f, .5f, .1f);
|
||||
}
|
||||
}
|
||||
|
||||
void textChanged(CCTextInputNode* input) override {
|
||||
try {
|
||||
self()->m_uncommittedValue =
|
||||
parseNumForInput<typename C::value_t>(input->getString());
|
||||
self()->valueChanged(false);
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class C, class T>
|
||||
class ImplArrows {
|
||||
protected:
|
||||
CCMenuItemSpriteExtra* m_decArrow = nullptr;
|
||||
CCMenuItemSpriteExtra* m_incArrow = nullptr;
|
||||
CCMenuItemSpriteExtra* m_bigDecArrow = nullptr;
|
||||
CCMenuItemSpriteExtra* m_bigIncArrow = nullptr;
|
||||
|
||||
C* self() {
|
||||
return static_cast<C*>(this);
|
||||
}
|
||||
|
||||
void setupArrows(std::shared_ptr<T> setting, float width) {
|
||||
auto yPos = setting->hasSlider() ? 5.f : 0.f;
|
||||
|
||||
if (setting->hasArrows()) {
|
||||
auto decArrowSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png");
|
||||
decArrowSpr->setFlipX(true);
|
||||
decArrowSpr->setScale(.3f);
|
||||
|
||||
m_decArrow = CCMenuItemSpriteExtra::create(
|
||||
decArrowSpr, self(), menu_selector(ImplArrows::Callbacks::onDecrement)
|
||||
);
|
||||
m_decArrow->setPosition(-width / 2 + 80.f, yPos);
|
||||
self()->m_menu->addChild(m_decArrow);
|
||||
|
||||
auto incArrowSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png");
|
||||
incArrowSpr->setScale(.3f);
|
||||
|
||||
m_incArrow = CCMenuItemSpriteExtra::create(
|
||||
incArrowSpr, self(), menu_selector(ImplArrows::Callbacks::onIncrement)
|
||||
);
|
||||
m_incArrow->setPosition(-10.f, yPos);
|
||||
self()->m_menu->addChild(m_incArrow);
|
||||
}
|
||||
|
||||
if (setting->hasBigArrows()) {
|
||||
auto decArrowSpr = CCSprite::createWithSpriteFrameName("double-nav.png"_spr);
|
||||
decArrowSpr->setFlipX(true);
|
||||
decArrowSpr->setScale(.3f);
|
||||
|
||||
m_bigDecArrow = CCMenuItemSpriteExtra::create(
|
||||
decArrowSpr, self(), menu_selector(ImplArrows::Callbacks::onBigDecrement)
|
||||
);
|
||||
m_bigDecArrow->setPosition(-width / 2 + 65.f, yPos);
|
||||
self()->m_menu->addChild(m_bigDecArrow);
|
||||
|
||||
auto incArrowSpr = CCSprite::createWithSpriteFrameName("double-nav.png"_spr);
|
||||
incArrowSpr->setScale(.3f);
|
||||
|
||||
m_bigIncArrow = CCMenuItemSpriteExtra::create(
|
||||
incArrowSpr, self(), menu_selector(ImplArrows::Callbacks::onBigIncrement)
|
||||
);
|
||||
m_bigIncArrow->setPosition(5.f, yPos);
|
||||
self()->m_menu->addChild(m_bigIncArrow);
|
||||
}
|
||||
}
|
||||
|
||||
// intentionally a subclass to make it relatively safe to
|
||||
// use as callbacks for the child class, since we give the
|
||||
// child class as the target to CCMenuItemSpriteExtra
|
||||
struct Callbacks : public C {
|
||||
void onIncrement(CCObject*) {
|
||||
this->m_uncommittedValue +=
|
||||
std::static_pointer_cast<T>(this->m_setting)->getArrowStepSize();
|
||||
this->valueChanged(true);
|
||||
}
|
||||
|
||||
void onDecrement(CCObject*) {
|
||||
this->m_uncommittedValue -=
|
||||
std::static_pointer_cast<T>(this->m_setting)->getArrowStepSize();
|
||||
this->valueChanged(true);
|
||||
}
|
||||
|
||||
void onBigIncrement(CCObject*) {
|
||||
this->m_uncommittedValue +=
|
||||
std::static_pointer_cast<T>(this->m_setting)->getBigArrowStepSize();
|
||||
this->valueChanged(true);
|
||||
}
|
||||
|
||||
void onBigDecrement(CCObject*) {
|
||||
this->m_uncommittedValue -=
|
||||
std::static_pointer_cast<T>(this->m_setting)->getBigArrowStepSize();
|
||||
this->valueChanged(true);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
template <class C, class T>
|
||||
class ImplSlider {
|
||||
protected:
|
||||
Slider* m_slider = nullptr;
|
||||
|
||||
C* self() {
|
||||
return static_cast<C*>(this);
|
||||
}
|
||||
|
||||
static float valueToSlider(std::shared_ptr<T> setting, typename T::value_t num) {
|
||||
auto min = setting->getMin() ? setting->getMin().value() : -100;
|
||||
auto max = setting->getMax() ? setting->getMax().value() : +100;
|
||||
auto range = max - min;
|
||||
return static_cast<float>(clamp(static_cast<double>(num - min) / range, 0.0, 1.0));
|
||||
}
|
||||
|
||||
static typename T::value_t valueFromSlider(std::shared_ptr<T> setting, float num) {
|
||||
auto min = setting->getMin() ? setting->getMin().value() : -100;
|
||||
auto max = setting->getMax() ? setting->getMax().value() : +100;
|
||||
auto range = max - min;
|
||||
auto value = static_cast<typename T::value_t>(num * range + min);
|
||||
if (auto step = setting->getSliderStepSize()) {
|
||||
value =
|
||||
static_cast<typename T::value_t>(round(value / step.value()) * step.value());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void setupSlider(std::shared_ptr<T> setting, float width) {
|
||||
if (setting->hasSlider()) {
|
||||
m_slider =
|
||||
Slider::create(self(), menu_selector(ImplSlider::Callbacks::onSlider), .5f);
|
||||
m_slider->setPosition(-50.f, -15.f);
|
||||
self()->m_menu->addChild(m_slider);
|
||||
|
||||
this->updateSlider();
|
||||
}
|
||||
}
|
||||
|
||||
void updateSlider() {
|
||||
if (m_slider) {
|
||||
auto setting = std::static_pointer_cast<T>(self()->m_setting);
|
||||
m_slider->setValue(valueToSlider(setting, self()->m_uncommittedValue));
|
||||
m_slider->updateBar();
|
||||
}
|
||||
}
|
||||
|
||||
// intentionally a subclass to make it relatively safe to
|
||||
// use as callbacks for the child class, since we give the
|
||||
// child class as the target to CCMenuItemSpriteExtra
|
||||
struct Callbacks : public C {
|
||||
void onSlider(CCObject* slider) {
|
||||
auto setting = std::static_pointer_cast<T>(this->m_setting);
|
||||
|
||||
this->m_uncommittedValue =
|
||||
valueFromSlider(setting, static_cast<SliderThumb*>(slider)->getValue());
|
||||
this->valueChanged(true);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
class BoolSettingNode : public GeodeSettingNode<BoolSettingNode, BoolSetting> {
|
||||
class BoolSettingNode : public GeodeSettingNode<BoolSetting> {
|
||||
protected:
|
||||
CCMenuItemToggler* m_toggle;
|
||||
|
||||
void onToggle(CCObject*);
|
||||
void valueChanged(bool updateText) override;
|
||||
bool setup(std::shared_ptr<BoolSetting> setting, float width) override;
|
||||
bool setup(BoolSettingValue* setting, float width) override;
|
||||
|
||||
public:
|
||||
IMPL_SETT_CREATE(Bool);
|
||||
};
|
||||
|
||||
class IntSettingNode :
|
||||
public GeodeSettingNode<IntSettingNode, IntSetting>,
|
||||
public ImplArrows<IntSettingNode, IntSetting>,
|
||||
public ImplInput<IntSettingNode, IntSetting>,
|
||||
public ImplSlider<IntSettingNode, IntSetting> {
|
||||
public GeodeSettingNode<IntSetting>,
|
||||
public TextInputDelegate
|
||||
{
|
||||
protected:
|
||||
friend class ImplArrows<IntSettingNode, IntSetting>;
|
||||
friend class ImplInput<IntSettingNode, IntSetting>;
|
||||
friend class ImplSlider<IntSettingNode, IntSetting>;
|
||||
InputNode* m_input = nullptr;
|
||||
CCLabelBMFont* m_label = nullptr;
|
||||
Slider* m_slider = nullptr;
|
||||
CCMenuItemSpriteExtra* m_decArrow = nullptr;
|
||||
CCMenuItemSpriteExtra* m_incArrow = nullptr;
|
||||
CCMenuItemSpriteExtra* m_bigDecArrow = nullptr;
|
||||
CCMenuItemSpriteExtra* m_bigIncArrow = nullptr;
|
||||
|
||||
void valueChanged(bool updateText) override;
|
||||
void textChanged(CCTextInputNode* input) override;
|
||||
|
||||
float setupHeight(std::shared_ptr<IntSetting> setting) const override;
|
||||
bool setup(std::shared_ptr<IntSetting> setting, float width) override;
|
||||
float setupHeight(IntSettingValue* setting) const override;
|
||||
bool setup(IntSettingValue* setting, float width) override;
|
||||
|
||||
public:
|
||||
void updateSlider();
|
||||
void updateLabel();
|
||||
|
||||
void onSlider(CCObject* slider);
|
||||
void onArrow(CCObject* sender);
|
||||
|
||||
IMPL_SETT_CREATE(Int);
|
||||
};
|
||||
|
||||
class FloatSettingNode :
|
||||
public GeodeSettingNode<FloatSettingNode, FloatSetting>,
|
||||
public ImplArrows<FloatSettingNode, FloatSetting>,
|
||||
public ImplInput<FloatSettingNode, FloatSetting>,
|
||||
public ImplSlider<FloatSettingNode, FloatSetting> {
|
||||
public GeodeSettingNode<FloatSetting>,
|
||||
public TextInputDelegate
|
||||
{
|
||||
protected:
|
||||
friend class ImplArrows<FloatSettingNode, FloatSetting>;
|
||||
friend class ImplInput<FloatSettingNode, FloatSetting>;
|
||||
friend class ImplSlider<FloatSettingNode, FloatSetting>;
|
||||
InputNode* m_input = nullptr;
|
||||
CCLabelBMFont* m_label = nullptr;
|
||||
Slider* m_slider = nullptr;
|
||||
CCMenuItemSpriteExtra* m_decArrow = nullptr;
|
||||
CCMenuItemSpriteExtra* m_incArrow = nullptr;
|
||||
CCMenuItemSpriteExtra* m_bigDecArrow = nullptr;
|
||||
CCMenuItemSpriteExtra* m_bigIncArrow = nullptr;
|
||||
|
||||
void valueChanged(bool updateText) override;
|
||||
void textChanged(CCTextInputNode* input) override;
|
||||
|
||||
float setupHeight(std::shared_ptr<FloatSetting> setting) const override;
|
||||
bool setup(std::shared_ptr<FloatSetting> setting, float width) override;
|
||||
float setupHeight(FloatSettingValue* setting) const override;
|
||||
bool setup(FloatSettingValue* setting, float width) override;
|
||||
|
||||
public:
|
||||
void updateSlider();
|
||||
void updateLabel();
|
||||
|
||||
void onSlider(CCObject* slider);
|
||||
void onArrow(CCObject* sender);
|
||||
|
||||
IMPL_SETT_CREATE(Float);
|
||||
};
|
||||
|
||||
class StringSettingNode :
|
||||
public GeodeSettingNode<StringSettingNode, StringSetting>,
|
||||
public TextInputDelegate {
|
||||
public GeodeSettingNode<StringSetting>,
|
||||
public TextInputDelegate
|
||||
{
|
||||
protected:
|
||||
InputNode* m_input;
|
||||
|
||||
|
@ -451,12 +272,16 @@ protected:
|
|||
void valueChanged(bool updateText) override;
|
||||
void updateLabel();
|
||||
|
||||
bool setup(std::shared_ptr<StringSetting> setting, float width) override;
|
||||
bool setup(StringSettingValue* setting, float width) override;
|
||||
|
||||
public:
|
||||
IMPL_SETT_CREATE(String);
|
||||
};
|
||||
|
||||
class FileSettingNode :
|
||||
public GeodeSettingNode<FileSettingNode, FileSetting>,
|
||||
public TextInputDelegate {
|
||||
public GeodeSettingNode<FileSetting>,
|
||||
public TextInputDelegate
|
||||
{
|
||||
protected:
|
||||
InputNode* m_input;
|
||||
|
||||
|
@ -466,11 +291,14 @@ protected:
|
|||
|
||||
void onPickFile(CCObject*);
|
||||
|
||||
bool setup(std::shared_ptr<FileSetting> setting, float width) override;
|
||||
bool setup(FileSettingValue* setting, float width) override;
|
||||
|
||||
public:
|
||||
IMPL_SETT_CREATE(File);
|
||||
};
|
||||
|
||||
class ColorSettingNode :
|
||||
public GeodeSettingNode<ColorSettingNode, ColorSetting>,
|
||||
public GeodeSettingNode<ColorSetting>,
|
||||
public ColorPickPopupDelegate {
|
||||
protected:
|
||||
ColorChannelSprite* m_colorSpr;
|
||||
|
@ -480,11 +308,14 @@ protected:
|
|||
|
||||
void onSelectColor(CCObject*);
|
||||
|
||||
bool setup(std::shared_ptr<ColorSetting> setting, float width) override;
|
||||
bool setup(ColorSettingValue* setting, float width) override;
|
||||
|
||||
public:
|
||||
IMPL_SETT_CREATE(Color);
|
||||
};
|
||||
|
||||
class ColorAlphaSettingNode :
|
||||
public GeodeSettingNode<ColorAlphaSettingNode, ColorAlphaSetting>,
|
||||
public GeodeSettingNode<ColorAlphaSetting>,
|
||||
public ColorPickPopupDelegate {
|
||||
protected:
|
||||
ColorChannelSprite* m_colorSpr;
|
||||
|
@ -494,5 +325,23 @@ protected:
|
|||
|
||||
void onSelectColor(CCObject*);
|
||||
|
||||
bool setup(std::shared_ptr<ColorAlphaSetting> setting, float width) override;
|
||||
bool setup(ColorAlphaSettingValue* setting, float width) override;
|
||||
|
||||
public:
|
||||
IMPL_SETT_CREATE(ColorAlpha);
|
||||
};
|
||||
|
||||
class CustomSettingPlaceholderNode : public SettingNode {
|
||||
protected:
|
||||
void commit() override;
|
||||
bool hasUncommittedChanges() override;
|
||||
bool hasNonDefaultValue() override;
|
||||
void resetToDefault() override;
|
||||
|
||||
bool init(std::string const& key, float width);
|
||||
|
||||
public:
|
||||
static CustomSettingPlaceholderNode* create(
|
||||
std::string const& key, float width
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <Geode/ui/ScrollLayer.hpp>
|
||||
#include <Geode/utils/cocos.hpp>
|
||||
#include <Geode/ui/General.hpp>
|
||||
#include "GeodeSettingNode.hpp"
|
||||
|
||||
bool ModSettingsPopup::setup(Mod* mod) {
|
||||
m_noElasticity = true;
|
||||
|
@ -31,8 +32,14 @@ bool ModSettingsPopup::setup(Mod* mod) {
|
|||
float totalHeight = .0f;
|
||||
std::vector<CCNode*> rendered;
|
||||
bool hasBG = true;
|
||||
for (auto& [_, sett] : mod->getSettings()) {
|
||||
auto node = sett->createNode(layerSize.width);
|
||||
for (auto& key : mod->getSettingKeys()) {
|
||||
SettingNode* node;
|
||||
if (auto sett = mod->getSetting(key)) {
|
||||
node = sett->createNode(layerSize.width);
|
||||
}
|
||||
else {
|
||||
node = CustomSettingPlaceholderNode::create(key, layerSize.width);
|
||||
}
|
||||
node->setDelegate(this);
|
||||
|
||||
totalHeight += node->getScaledContentSize().height;
|
||||
|
@ -144,6 +151,17 @@ void ModSettingsPopup::onResetAll(CCObject*) {
|
|||
);
|
||||
}
|
||||
|
||||
void ModSettingsPopup::settingValueCommitted(SettingNode*) {
|
||||
if (this->hasUncommitted()) {
|
||||
m_applyBtnSpr->setColor(cc3x(0xf));
|
||||
m_applyBtn->setEnabled(true);
|
||||
}
|
||||
else {
|
||||
m_applyBtnSpr->setColor(cc3x(0x4));
|
||||
m_applyBtn->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ModSettingsPopup::settingValueChanged(SettingNode*) {
|
||||
if (this->hasUncommitted()) {
|
||||
m_applyBtnSpr->setColor(cc3x(0xf));
|
||||
|
|
|
@ -13,7 +13,8 @@ protected:
|
|||
CCMenuItemSpriteExtra* m_applyBtn;
|
||||
ButtonSprite* m_applyBtnSpr;
|
||||
|
||||
void settingValueChanged(SettingNode* node) override;
|
||||
void settingValueChanged(SettingNode*) override;
|
||||
void settingValueCommitted(SettingNode*) override;
|
||||
|
||||
bool setup(Mod* mod) override;
|
||||
bool hasUncommitted() const;
|
||||
|
|
|
@ -19,6 +19,11 @@ bool JsonMaybeSomething<Json>::isError() const {
|
|||
return m_checker.isError() || !m_hasValue;
|
||||
}
|
||||
|
||||
template <class Json>
|
||||
std::string JsonMaybeSomething<Json>::getError() const {
|
||||
return m_checker.getError();
|
||||
}
|
||||
|
||||
template <class Json>
|
||||
JsonMaybeSomething<Json>::operator bool() const {
|
||||
return !isError();
|
||||
|
|
|
@ -11,6 +11,14 @@
|
|||
USE_GEODE_NAMESPACE();
|
||||
using namespace geode::utils::file;
|
||||
|
||||
void ghc::filesystem::to_json(nlohmann::json& json, path const& path) {
|
||||
json = path.string();
|
||||
}
|
||||
|
||||
void ghc::filesystem::from_json(nlohmann::json const& json, path& path) {
|
||||
path = json.get<std::string>();
|
||||
}
|
||||
|
||||
Result<std::string> utils::file::readString(ghc::filesystem::path const& path) {
|
||||
#if _WIN32
|
||||
std::ifstream in(path.wstring(), std::ios::in | std::ios::binary);
|
||||
|
|
|
@ -18,4 +18,4 @@ target_link_libraries(
|
|||
geode-sdk
|
||||
)
|
||||
|
||||
create_geode_file(${PROJECT_NAME} DONT_INSTALL)
|
||||
create_geode_file(${PROJECT_NAME})
|
||||
|
|
|
@ -3,6 +3,134 @@
|
|||
USE_GEODE_NAMESPACE();
|
||||
|
||||
#include <Geode/modify/MenuLayer.hpp>
|
||||
#include <Geode/loader/SettingNode.hpp>
|
||||
|
||||
enum class Icon {
|
||||
Steve,
|
||||
Mike,
|
||||
LazarithTheDestroyerOfForsakenSouls,
|
||||
Geoff,
|
||||
};
|
||||
constexpr Icon DEFAULT_ICON = Icon::Steve;
|
||||
|
||||
class MySettingValue;
|
||||
|
||||
class MySettingValue : public SettingValue {
|
||||
protected:
|
||||
Icon m_icon;
|
||||
|
||||
public:
|
||||
MySettingValue(std::string const& key, Icon icon)
|
||||
: SettingValue(key), m_icon(icon) {}
|
||||
|
||||
bool load(nlohmann::json const& json) override {
|
||||
try {
|
||||
m_icon = static_cast<Icon>(json.get<int>());
|
||||
return true;
|
||||
} catch(...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool save(nlohmann::json& json) const override {
|
||||
json = static_cast<int>(m_icon);
|
||||
return true;
|
||||
}
|
||||
SettingNode* createNode(float width) override;
|
||||
|
||||
void setIcon(Icon icon) {
|
||||
m_icon = icon;
|
||||
}
|
||||
Icon getIcon() const {
|
||||
return m_icon;
|
||||
}
|
||||
};
|
||||
|
||||
class MySettingNode : public SettingNode {
|
||||
protected:
|
||||
Icon m_currentIcon;
|
||||
std::vector<CCSprite*> m_sprites;
|
||||
|
||||
bool init(MySettingValue* value, float width) {
|
||||
if (!SettingNode::init(value))
|
||||
return false;
|
||||
|
||||
m_currentIcon = value->getIcon();
|
||||
this->setContentSize({ width, 40.f });
|
||||
|
||||
auto menu = CCMenu::create();
|
||||
menu->setPosition(width / 2, 20.f);
|
||||
|
||||
float x = -75.f;
|
||||
|
||||
for (auto& [spr, icon] : {
|
||||
std::pair { "player_01_001.png", Icon::Steve, },
|
||||
std::pair { "player_02_001.png", Icon::Mike, },
|
||||
std::pair { "player_03_001.png", Icon::LazarithTheDestroyerOfForsakenSouls, },
|
||||
std::pair { "player_04_001.png", Icon::Geoff, },
|
||||
}) {
|
||||
auto btnSpr = CCSprite::createWithSpriteFrameName(spr);
|
||||
btnSpr->setScale(.7f);
|
||||
m_sprites.push_back(btnSpr);
|
||||
if (icon == m_currentIcon) {
|
||||
btnSpr->setColor({ 0, 255, 0 });
|
||||
} else {
|
||||
btnSpr->setColor({ 200, 200, 200 });
|
||||
}
|
||||
auto btn = CCMenuItemSpriteExtra::create(
|
||||
btnSpr, this, menu_selector(MySettingNode::onSelect)
|
||||
);
|
||||
btn->setTag(static_cast<int>(icon));
|
||||
btn->setPosition(x, 0);
|
||||
menu->addChild(btn);
|
||||
|
||||
x += 50.f;
|
||||
}
|
||||
|
||||
this->addChild(menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void onSelect(CCObject* sender) {
|
||||
for (auto& spr : m_sprites) {
|
||||
spr->setColor({ 200, 200, 200 });
|
||||
}
|
||||
m_currentIcon = static_cast<Icon>(sender->getTag());
|
||||
static_cast<CCSprite*>(
|
||||
static_cast<CCMenuItemSpriteExtra*>(sender)->getNormalImage()
|
||||
)->setColor({ 0, 255, 0 });
|
||||
this->dispatchChanged();
|
||||
}
|
||||
|
||||
public:
|
||||
void commit() override {
|
||||
static_cast<MySettingValue*>(m_value)->setIcon(m_currentIcon);
|
||||
this->dispatchCommitted();
|
||||
}
|
||||
bool hasUncommittedChanges() override {
|
||||
return m_currentIcon != static_cast<MySettingValue*>(m_value)->getIcon();
|
||||
}
|
||||
bool hasNonDefaultValue() override {
|
||||
return m_currentIcon != DEFAULT_ICON;
|
||||
}
|
||||
void resetToDefault() override {
|
||||
m_currentIcon = DEFAULT_ICON;
|
||||
}
|
||||
|
||||
static MySettingNode* create(MySettingValue* value, float width) {
|
||||
auto ret = new MySettingNode;
|
||||
if (ret && ret->init(value, width)) {
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
CC_SAFE_DELETE(ret);
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
SettingNode* MySettingValue::createNode(float width) {
|
||||
return MySettingNode::create(this, width);
|
||||
}
|
||||
|
||||
struct MyMenuLayer : Modify<MyMenuLayer, MenuLayer> {
|
||||
void onMoreGames(CCObject*) {
|
||||
|
@ -10,13 +138,21 @@ struct MyMenuLayer : Modify<MyMenuLayer, MenuLayer> {
|
|||
FLAlertLayer::create("Damn", ":(", "OK")->show();
|
||||
}
|
||||
else {
|
||||
FLAlertLayer::create("Yay", "The weather report said it wouldn't rain today :)", "OK")
|
||||
->show();
|
||||
FLAlertLayer::create(
|
||||
"Yay",
|
||||
"The weather report said it wouldn't rain today :)",
|
||||
"OK"
|
||||
)->show();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
GEODE_API bool GEODE_CALL geode_load(Mod*) {
|
||||
$on_mod(Loaded) {
|
||||
Mod::get()->registerCustomSetting(
|
||||
"overcast-skies",
|
||||
std::make_unique<MySettingValue>("overcast-skies", DEFAULT_ICON)
|
||||
);
|
||||
|
||||
// Dispatcher::get()->addFunction<void(GJGarageLayer*)>("test-garage-open", [](GJGarageLayer*
|
||||
// gl) { auto label = CCLabelBMFont::create("Dispatcher works!", "bigFont.fnt");
|
||||
// label->setPosition(100, 80);
|
||||
|
@ -24,5 +160,4 @@ GEODE_API bool GEODE_CALL geode_load(Mod*) {
|
|||
// label->setZOrder(99999);
|
||||
// gl->addChild(label);
|
||||
// });
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -76,6 +76,9 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overcast-skies": {
|
||||
"type": "custom"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,4 +16,4 @@ target_link_libraries(
|
|||
geode-sdk
|
||||
)
|
||||
|
||||
create_geode_file(${PROJECT_NAME} DONT_INSTALL)
|
||||
create_geode_file(${PROJECT_NAME})
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"dependencies": [
|
||||
{
|
||||
"id": "geode.testdep",
|
||||
"version": "1.0.*",
|
||||
"version": ">=1.0.0",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
|
|
Loading…
Reference in a new issue