now with color settings

This commit is contained in:
HJfod 2022-09-21 14:50:23 +03:00
parent 25fb983cb4
commit 19293e2fdf
9 changed files with 525 additions and 10 deletions

View file

@ -7,6 +7,7 @@
#include "../utils/json.hpp"
#include "../utils/Result.hpp"
#include "../utils/JsonValidation.hpp"
#include "../utils/convert.hpp"
#include <regex>
namespace geode {
@ -394,6 +395,22 @@ namespace geode {
SettingNode* createNode(float width) override;
};
class GEODE_DLL ColorSetting :
public GeodeSetting<ColorSetting, cocos2d::ccColor3B, SettingType::Color>,
public std::enable_shared_from_this<ColorSetting>
{
public:
SettingNode* createNode(float width) override;
};
class GEODE_DLL ColorAlphaSetting :
public GeodeSetting<ColorAlphaSetting, cocos2d::ccColor4B, SettingType::ColorAlpha>,
public std::enable_shared_from_this<ColorAlphaSetting>
{
public:
SettingNode* createNode(float width) override;
};
// these can't be member functions because C++ is single-pass >:(
#define GEODE_INT_BUILTIN_SETTING_IF(type, action, ...) \

View file

@ -51,6 +51,9 @@ namespace geode {
}
bool jsonConvertibleTo(value_t value, value_t to) {
// if we don't know the type we're passing into,
// everything's valid
if (to == value_t::null) return true;
if (
value == value_t::number_float ||
value == value_t::number_integer ||

View file

@ -2,6 +2,15 @@
#include <cocos2d.h>
#include "general.hpp"
#include "json.hpp"
// support converting ccColor3B / ccColor4B to / from json
namespace cocos2d {
void GEODE_DLL to_json(nlohmann::json& json, cocos2d::ccColor3B const& color);
void GEODE_DLL from_json(nlohmann::json const& json, cocos2d::ccColor3B& color);
void GEODE_DLL to_json(nlohmann::json& json, cocos2d::ccColor4B const& color);
void GEODE_DLL from_json(nlohmann::json const& json, cocos2d::ccColor4B& color);
}
namespace geode::cocos {
inline void ccDrawColor4B(cocos2d::ccColor4B const& color) {
@ -81,6 +90,11 @@ namespace geode::cocos {
};
}
GEODE_DLL Result<cocos2d::ccColor3B> cc3bFromHexString(std::string const& hexValue);
GEODE_DLL Result<cocos2d::ccColor4B> cc4bFromHexString(std::string const& hexValue);
GEODE_DLL std::string cc3bToHexString(cocos2d::ccColor3B const& color);
GEODE_DLL std::string cc4bToHexString(cocos2d::ccColor4B const& color);
template<typename T,
typename = std::enable_if_t<std::is_pointer_v<T>> >
static cocos2d::CCArray* vectorToCCArray(std::vector<T> const& vec) {

View file

@ -60,5 +60,22 @@ namespace geode::utils {
return stream.str();
}
/**
* Turn a number into a string, with support for specifying precision
* (unlike std::to_string).
* @param num Number to convert to string
* @param precision Precision of the converted number
* @returns Number as string
*/
template<class Num>
std::string numToString(Num num, size_t precision = 0) {
std::stringstream ss;
if (precision) {
ss << std::fixed << std::setprecision(precision);
}
ss << num;
return ss.str();
}
GEODE_DLL std::string timePointAsString(const std::chrono::system_clock::time_point& tp);
}

View file

@ -22,6 +22,8 @@ Result<std::shared_ptr<Setting>> Setting::parse(
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("color"): return ColorSetting::parse(key, obj);
case hash("rgba"): return ColorAlphaSetting::parse(key, obj);
default: return Err(
"Setting \"" + key + "\" has unknown type \"" + type + "\""
);
@ -64,3 +66,11 @@ SettingNode* FloatSetting::createNode(float width) {
SettingNode* StringSetting::createNode(float width) {
return StringSettingNode::create(shared_from_this(), width);
}
SettingNode* ColorSetting::createNode(float width) {
return ColorSettingNode::create(shared_from_this(), width);
}
SettingNode* ColorAlphaSetting::createNode(float width) {
return ColorAlphaSettingNode::create(shared_from_this(), width);
}

View file

@ -1,5 +1,7 @@
#include "GeodeSettingNode.hpp"
// BoolSettingNode
void BoolSettingNode::valueChanged(bool updateText) {
GeodeSettingNode::valueChanged(updateText);
m_toggle->toggle(m_uncommittedValue);
@ -15,13 +17,15 @@ bool BoolSettingNode::setup(std::shared_ptr<BoolSetting> setting, float width) {
m_toggle = CCMenuItemToggler::createWithStandardSprites(
this, menu_selector(BoolSettingNode::onToggle), .6f
);
m_toggle->setPosition(-10.f, .0f);
m_toggle->toggle(m_uncommittedValue);
m_toggle->setPositionX(-10.f);
m_menu->addChild(m_toggle);
return true;
}
// IntSettingNode
float IntSettingNode::setupHeight(std::shared_ptr<IntSetting> setting) const {
return setting->hasSlider() ? 55.f : 40.f;
}
@ -39,6 +43,8 @@ void IntSettingNode::valueChanged(bool updateText) {
this->updateSlider();
}
// FloatSettingNode
float FloatSettingNode::setupHeight(std::shared_ptr<FloatSetting> setting) const {
return setting->hasSlider() ? 55.f : 40.f;
}
@ -56,6 +62,8 @@ void FloatSettingNode::valueChanged(bool updateText) {
this->updateSlider();
}
// StringSettingNode
void StringSettingNode::updateLabel() {
// hacky way to make setString not called textChanged
m_input->getInput()->setDelegate(nullptr);
@ -82,3 +90,51 @@ bool StringSettingNode::setup(std::shared_ptr<StringSetting> setting, float widt
return true;
}
// ColorSettingNode
void ColorSettingNode::valueChanged(bool updateText) {
GeodeSettingNode::valueChanged(updateText);
m_colorSpr->setColor(m_uncommittedValue);
}
bool ColorSettingNode::setup(std::shared_ptr<ColorSetting> setting, float width) {
m_colorSpr = ColorChannelSprite::create();
m_colorSpr->setColor(m_uncommittedValue);
m_colorSpr->setScale(.65f);
auto button = CCMenuItemSpriteExtra::create(
m_colorSpr, this, makeMenuSelector([this](CCObject*) {
ColorPickPopup<ColorSettingNode>::create(this)->show();
})
);
button->setPositionX(-10.f);
m_menu->addChild(button);
return true;
}
// ColorAlphaSettingNode
void ColorAlphaSettingNode::valueChanged(bool updateText) {
GeodeSettingNode::valueChanged(updateText);
m_colorSpr->setColor(to3B(m_uncommittedValue));
m_colorSpr->updateOpacity(m_uncommittedValue.a / 255.f);
}
bool ColorAlphaSettingNode::setup(std::shared_ptr<ColorAlphaSetting> setting, float width) {
m_colorSpr = ColorChannelSprite::create();
m_colorSpr->setColor(to3B(m_uncommittedValue));
m_colorSpr->updateOpacity(m_uncommittedValue.a / 255.f);
m_colorSpr->setScale(.65f);
auto button = CCMenuItemSpriteExtra::create(
m_colorSpr, this, makeMenuSelector([this](CCObject*) {
ColorPickPopup<ColorAlphaSettingNode>::create(this)->show();
})
);
button->setPositionX(-10.f);
m_menu->addChild(button);
return true;
}

View file

@ -12,6 +12,9 @@
USE_GEODE_NAMESPACE();
template<class T>
class ColorPickPopup;
namespace {
template<class Num>
Num parseNumForInput(std::string const& str) {
@ -26,13 +29,6 @@ namespace {
}
}
template<class Num>
std::string numToStringFixed(Num num) {
std::stringstream ss;
ss << num;
return ss.str();
}
template<class N, class T>
class GeodeSettingNode : public SettingNode {
public:
@ -255,12 +251,12 @@ namespace {
// hacky way to make setString not called textChanged
m_input->getInput()->setDelegate(nullptr);
m_input->setString(
numToStringFixed(self()->m_uncommittedValue)
numToString(self()->m_uncommittedValue)
);
m_input->getInput()->setDelegate(this);
} else {
m_label->setString(
numToStringFixed(self()->m_uncommittedValue).c_str()
numToString(self()->m_uncommittedValue).c_str()
);
m_label->limitLabelWidth(self()->m_width / 2 - 70.f, .5f, .1f);
}
@ -499,3 +495,157 @@ protected:
bool setup(std::shared_ptr<StringSetting> setting, float width) override;
};
class ColorSettingNode :
public GeodeSettingNode<ColorSettingNode, ColorSetting>
{
protected:
ColorChannelSprite* m_colorSpr;
friend class ColorPickPopup<ColorSettingNode>;
void valueChanged(bool updateText) override;
bool setup(std::shared_ptr<ColorSetting> setting, float width) override;
};
class ColorAlphaSettingNode :
public GeodeSettingNode<ColorAlphaSettingNode, ColorAlphaSetting>
{
protected:
ColorChannelSprite* m_colorSpr;
friend class ColorPickPopup<ColorAlphaSettingNode>;
void valueChanged(bool updateText) override;
bool setup(std::shared_ptr<ColorAlphaSetting> setting, float width) override;
};
template<class T>
class ColorPickPopup :
public Popup<T*>,
public ColorPickerDelegate,
public TextInputDelegate
{
protected:
T* m_settingNode;
Slider* m_opacitySlider = nullptr;
InputNode* m_opacityInput = nullptr;
static constexpr auto TAG_OPACITY_INPUT = 0;
static constexpr auto TAG_R_INPUT = 1;
static constexpr auto TAG_G_INPUT = 2;
static constexpr auto TAG_B_INPUT = 3;
static constexpr auto TAG_HEX_INPUT = 4;
static constexpr auto IS_RGBA = std::is_same_v<
decltype(T::m_uncommittedValue),
ccColor4B
>;
FLAlertLayer* asAlert() {
return static_cast<FLAlertLayer*>(this);
}
bool setup(T* node) override {
asAlert()->m_noElasticity = true;
m_settingNode = node;
auto winSize = CCDirector::sharedDirector()->getWinSize();
auto picker = CCControlColourPicker::colourPicker();
if constexpr (IS_RGBA) {
picker->setColorValue(to3B(node->m_uncommittedValue));
} else {
picker->setColorValue(node->m_uncommittedValue);
}
picker->setColorTarget(node->m_colorSpr);
picker->setPosition(
winSize.width / 2,
winSize.height / 2 + (IS_RGBA ? 20.f : 0.f)
);
picker->setDelegate(this);
asAlert()->m_mainLayer->addChild(picker);
if constexpr (IS_RGBA) {
m_opacitySlider = Slider::create(
this,
menu_selector(ColorPickPopup::onOpacity),
.75f
);
m_opacitySlider->setPosition(
winSize.width / 2 - 30.f,
winSize.height / 2 - 90.f
);
m_opacitySlider->setValue(node->m_uncommittedValue.a / 255.f);
m_opacitySlider->updateBar();
asAlert()->m_mainLayer->addChild(m_opacitySlider);
m_opacityInput = InputNode::create(45.f, "0.00");
m_opacityInput->setPosition(85.f, -90.f);
m_opacityInput->setString(numToString(
node->m_uncommittedValue.a / 255.f, 2
));
m_opacityInput->getInput()->setTag(TAG_OPACITY_INPUT);
m_opacityInput->getInput()->setDelegate(this);
asAlert()->m_buttonMenu->addChild(m_opacityInput);
}
return true;
}
void textChanged(CCTextInputNode* input) override {
switch (input->getTag()) {
case TAG_OPACITY_INPUT: {
if constexpr (IS_RGBA) {
try {
auto value = std::stof(input->getString());
m_settingNode->m_uncommittedValue.a = static_cast<GLubyte>(
value * 255.f
);
m_opacitySlider->setValue(value);
m_opacitySlider->updateBar();
m_settingNode->valueChanged(true);
} catch(...) {}
}
} break;
default: break;
}
}
void onOpacity(CCObject* sender) {
if constexpr (IS_RGBA) {
m_settingNode->m_uncommittedValue.a = static_cast<GLubyte>(
static_cast<SliderThumb*>(sender)->getValue() * 255.f
);
m_opacityInput->setString(numToString(
m_settingNode->m_uncommittedValue.a / 255.f, 3
));
m_settingNode->valueChanged(true);
}
}
void colorValueChanged(ccColor3B color) override {
if constexpr (IS_RGBA) {
m_settingNode->m_uncommittedValue = to4B(
color, m_settingNode->m_uncommittedValue.a
);
} else {
m_settingNode->m_uncommittedValue = color;
}
m_settingNode->valueChanged(true);
}
public:
static ColorPickPopup* create(T* node) {
auto ret = new ColorPickPopup();
if (ret && ret->init(250.f, (IS_RGBA ? 250.f : 200.f), node)) {
ret->autorelease();
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
};

View file

@ -0,0 +1,240 @@
#include <Geode/utils/convert.hpp>
USE_GEODE_NAMESPACE();
void cocos2d::to_json(nlohmann::json& json, ccColor3B const& color) {
json = nlohmann::json {
{ "r", color.r },
{ "g", color.g },
{ "b", color.b },
};
}
void cocos2d::from_json(nlohmann::json const& json, ccColor3B& color) {
// array
if (json.is_array()) {
if (json.size() == 3) {
json.at(0).get_to(color.r);
json.at(1).get_to(color.g);
json.at(2).get_to(color.b);
} else {
throw nlohmann::json::type_error::create(
0, "Expected color array to have 3 items", json
);
}
}
// object
else if (json.is_object()) {
json.at("r").get_to(color.r);
json.at("g").get_to(color.g);
json.at("b").get_to(color.b);
}
// hex string
else if (json.is_string()) {
std::string str = json;
if (str[0] == '#') {
str.erase(str.begin());
}
if (str.size() > 6) {
throw nlohmann::json::type_error::create(
0, "Hex string for color too long", json
);
}
auto c = cc3bFromHexString(str);
if (!c) {
throw nlohmann::json::type_error::create(
0, "Invalid color hex string: " + c.error(), json
);
}
color = c.value();
}
// bad
else {
throw nlohmann::json::type_error::create(
0, "Expected color to be array, object or hex string", json
);
}
}
void cocos2d::to_json(nlohmann::json& json, ccColor4B const& color) {
json = nlohmann::json {
{ "r", color.r },
{ "g", color.g },
{ "b", color.b },
{ "a", color.a },
};
}
void cocos2d::from_json(nlohmann::json const& json, ccColor4B& color) {
// array
if (json.is_array()) {
if (json.size() == 4) {
json.at(0).get_to(color.r);
json.at(1).get_to(color.g);
json.at(2).get_to(color.b);
json.at(3).get_to(color.a);
} else {
throw nlohmann::json::type_error::create(
0, "Expected color array to have 4 items", json
);
}
}
// object
else if (json.is_object()) {
json.at("r").get_to(color.r);
json.at("g").get_to(color.g);
json.at("b").get_to(color.b);
json.at("a").get_to(color.a);
}
// hex string
else if (json.is_string()) {
std::string str = json;
if (str[0] == '#') {
str.erase(str.begin());
}
if (str.size() > 8) {
throw nlohmann::json::type_error::create(
0, "Hex string for color too long", json
);
}
auto c = cc4bFromHexString(str);
if (!c) {
throw nlohmann::json::type_error::create(
0, "Invalid color hex string: " + c.error(), json
);
}
color = c.value();
}
// bad
else {
throw nlohmann::json::type_error::create(
0, "Expected color to be array, object or hex string", json
);
}
}
Result<ccColor3B> geode::cocos::cc3bFromHexString(std::string const& hexValue) {
if (hexValue.empty()) {
return Ok(ccc3(255, 255, 255));
}
if (hexValue.size() > 6) {
return Err("Hex value too large");
}
int numValue;
try {
numValue = std::stoi(hexValue, 0, 16);
} catch(...) {
return Err("Invalid hex value");
}
switch (hexValue.size()) {
case 6: {
auto r = static_cast<uint8_t>((numValue & 0xFF0000) >> 16);
auto g = static_cast<uint8_t>((numValue & 0x00FF00) >> 8);
auto b = static_cast<uint8_t>((numValue & 0x0000FF));
return Ok(ccc3(r, g, b));
} break;
case 3: {
auto r = static_cast<uint8_t>(((numValue & 0xF00) >> 8) * 17);
auto g = static_cast<uint8_t>(((numValue & 0x0F0) >> 4) * 17);
auto b = static_cast<uint8_t>(((numValue & 0x00F)) * 17);
return Ok(ccc3(r, g, b));
} break;
case 2: {
auto num = static_cast<uint8_t>(numValue);
return Ok(ccc3(num, num, num));
} break;
case 1: {
auto num = static_cast<uint8_t>(numValue) * 17;
return Ok(ccc3(num, num, num));
} break;
default: return Err("Invalid hex size, expected 1, 2, 3, or 6");
}
}
Result<ccColor4B> geode::cocos::cc4bFromHexString(std::string const& hexValue) {
if (hexValue.empty()) {
return Ok(ccc4(255, 255, 255, 255));
}
if (hexValue.size() > 8) {
return Err("Hex value too large");
}
int numValue;
try {
numValue = std::stoi(hexValue, 0, 16);
} catch(...) {
return Err("Invalid hex value");
}
switch (hexValue.size()) {
case 8: {
auto r = static_cast<uint8_t>((numValue & 0xFF000000) >> 24);
auto g = static_cast<uint8_t>((numValue & 0x00FF0000) >> 16);
auto b = static_cast<uint8_t>((numValue & 0x0000FF00) >> 8);
auto a = static_cast<uint8_t>((numValue & 0x000000FF));
return Ok(ccc4(r, g, b, a));
} break;
case 6: {
auto r = static_cast<uint8_t>((numValue & 0xFF0000) >> 16);
auto g = static_cast<uint8_t>((numValue & 0x00FF00) >> 8);
auto b = static_cast<uint8_t>((numValue & 0x0000FF));
return Ok(ccc4(r, g, b, 255));
} break;
case 4: {
auto r = static_cast<uint8_t>(((numValue & 0xF000) >> 12) * 17);
auto g = static_cast<uint8_t>(((numValue & 0x0F00) >> 8) * 17);
auto b = static_cast<uint8_t>(((numValue & 0x00F0) >> 4) * 17);
auto a = static_cast<uint8_t>(((numValue & 0x000F)) * 17);
return Ok(ccc4(r, g, b, a));
} break;
case 3: {
auto r = static_cast<uint8_t>(((numValue & 0xF00) >> 8) * 17);
auto g = static_cast<uint8_t>(((numValue & 0x0F0) >> 4) * 17);
auto b = static_cast<uint8_t>(((numValue & 0x00F)) * 17);
return Ok(ccc4(r, g, b, 255));
} break;
case 2: {
auto num = static_cast<uint8_t>(numValue);
return Ok(ccc4(num, num, num, 255));
} break;
case 1: {
auto num = static_cast<uint8_t>(numValue) * 17;
return Ok(ccc4(num, num, num, 255));
} break;
default: return Err("Invalid hex size, expected 1, 2, 3, 4, 6, or 8");
}
}
std::string geode::cocos::cc3bToHexString(ccColor3B const& color) {
static constexpr auto digits = "0123456789ABCDEF";
std::string output;
output += digits[color.r >> 4 & 0xF];
output += digits[color.r & 0xF];
output += digits[color.g >> 4 & 0xF];
output += digits[color.g & 0xF];
output += digits[color.b >> 4 & 0xF];
output += digits[color.b & 0xF];
return output;
}
std::string geode::cocos::cc4bToHexString(ccColor4B const& color) {
static constexpr auto digits = "0123456789ABCDEF";
std::string output;
output += digits[color.r >> 4 & 0xF];
output += digits[color.r & 0xF];
output += digits[color.g >> 4 & 0xF];
output += digits[color.g & 0xF];
output += digits[color.b >> 4 & 0xF];
output += digits[color.b & 0xF];
output += digits[color.a >> 4 & 0xF];
output += digits[color.a & 0xF];
return output;
}

View file

@ -52,6 +52,14 @@
"mirai toka",
"arun darou ka"
]
},
"territory-battle": {
"type": "color",
"default": "#00f"
},
"faithful-dog-hachi": {
"type": "rgba",
"default": [ 255, 150, 55, 180 ]
}
}
}