fully implement enable-if!!!!

This commit is contained in:
HJfod 2024-08-28 22:38:07 +03:00
parent 4fb42754cb
commit db9e2ccb48
7 changed files with 608 additions and 88 deletions

View file

@ -9,8 +9,9 @@
// this unfortunately has to be included because of C++ templates
#include "../utils/JsonValidation.hpp"
// todo in v4: these can be removed as well as the friend decl in LegacyCustomSettingV3
// todo in v4: this can be removed as well as the friend decl in LegacyCustomSettingV3
class LegacyCustomSettingToV3Node;
class ModSettingsPopup;
namespace geode {
class ModSettingsManager;
@ -59,6 +60,11 @@ namespace geode {
* Get the "enable-if" scheme for this setting
*/
std::optional<std::string> getEnableIf() const;
/**
* Check if this setting should be enabled based on the "enable-if" scheme
*/
bool shouldEnable() const;
std::optional<std::string> getEnableIfDescription() const;
/**
* Whether this setting requires a restart on change
*/
@ -402,6 +408,8 @@ namespace geode {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
friend class ::ModSettingsPopup;
protected:
bool init(std::shared_ptr<SettingV3> setting, float width);
@ -453,7 +461,7 @@ namespace geode {
SettingNodeSizeChangeEventV3(SettingNodeV3* node);
virtual ~SettingNodeSizeChangeEventV3();
SettingNodeV3* getTargetNode() const;
SettingNodeV3* getNode() const;
};
class GEODE_DLL SettingNodeValueChangeEventV3 : public Event {
private:
@ -461,9 +469,10 @@ namespace geode {
std::shared_ptr<Impl> m_impl;
public:
SettingNodeValueChangeEventV3(bool commit);
SettingNodeValueChangeEventV3(SettingNodeV3* node, bool commit);
virtual ~SettingNodeValueChangeEventV3();
SettingNodeV3* getNode() const;
bool isCommit() const;
};

View file

@ -427,6 +427,19 @@ namespace geode {
}
return *this;
}
template <class T>
JsonExpectedValue& mustBe(std::string_view name, auto predicate) requires requires {
{ predicate(std::declval<T>()) } -> std::convertible_to<Result<>>;
} {
if (this->hasError()) return *this;
if (auto v = this->template tryGet<T>()) {
auto p = predicate(*v);
if (!p) {
this->setError("json value is not {}: {}", name, p.unwrapErr());
}
}
return *this;
}
// -- Dealing with objects --

View file

@ -15,22 +15,27 @@ SettingNodeSizeChangeEventV3::SettingNodeSizeChangeEventV3(SettingNodeV3* node)
}
SettingNodeSizeChangeEventV3::~SettingNodeSizeChangeEventV3() = default;
SettingNodeV3* SettingNodeSizeChangeEventV3::getTargetNode() const {
SettingNodeV3* SettingNodeSizeChangeEventV3::getNode() const {
return m_impl->node;
}
class SettingNodeValueChangeEventV3::Impl final {
public:
SettingNodeV3* node;
bool commit = false;
};
SettingNodeValueChangeEventV3::SettingNodeValueChangeEventV3(bool commit)
SettingNodeValueChangeEventV3::SettingNodeValueChangeEventV3(SettingNodeV3* node, bool commit)
: m_impl(std::make_shared<Impl>())
{
m_impl->node = node;
m_impl->commit = commit;
}
SettingNodeValueChangeEventV3::~SettingNodeValueChangeEventV3() = default;
SettingNodeV3* SettingNodeValueChangeEventV3::getNode() const {
return m_impl->node;
}
bool SettingNodeValueChangeEventV3::isCommit() const {
return m_impl->commit;
}
@ -43,7 +48,7 @@ public:
CCMenu* nameMenu;
CCMenu* buttonMenu;
CCMenuItemSpriteExtra* resetButton;
ButtonSprite* restartRequiredLabel;
CCLabelBMFont* errorLabel;
ccColor4B bgColor = ccc4(0, 0, 0, 0);
bool committed = false;
};
@ -68,15 +73,9 @@ bool SettingNodeV3::init(std::shared_ptr<SettingV3> setting, float width) {
m_impl->nameLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .4f)->setScalePriority(1));
m_impl->nameMenu->addChild(m_impl->nameLabel);
m_impl->restartRequiredLabel = createGeodeTagLabel(
"Restart Required",
{{
to3B(ColorProvider::get()->color("mod-list-restart-required-label"_spr)),
to3B(ColorProvider::get()->color("mod-list-restart-required-label-bg"_spr))
}}
);
m_impl->restartRequiredLabel->setScale(.25f);
this->addChildAtPosition(m_impl->restartRequiredLabel, Anchor::Left, ccp(10, -10), ccp(0, .5f));
m_impl->errorLabel = CCLabelBMFont::create("", "bigFont.fnt");
m_impl->errorLabel->setScale(.25f);
this->addChildAtPosition(m_impl->errorLabel, Anchor::Left, ccp(10, -10), ccp(0, .5f));
if (setting->getDescription()) {
auto descSpr = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png");
@ -110,15 +109,26 @@ bool SettingNodeV3::init(std::shared_ptr<SettingV3> setting, float width) {
}
void SettingNodeV3::updateState() {
m_impl->errorLabel->setVisible(false);
m_impl->nameLabel->setColor(this->hasUncommittedChanges() ? ccc3(17, 221, 0) : ccWHITE);
m_impl->resetButton->setVisible(this->hasNonDefaultValue());
m_impl->bg->setColor(to3B(m_impl->bgColor));
m_impl->bg->setOpacity(m_impl->bgColor.a);
m_impl->restartRequiredLabel->setVisible(false);
if (!m_impl->setting->shouldEnable()) {
if (auto desc = m_impl->setting->getEnableIfDescription()) {
m_impl->nameLabel->setColor(ccGRAY);
m_impl->errorLabel->setVisible(true);
m_impl->errorLabel->setColor("mod-list-errors-found"_cc3b);
m_impl->errorLabel->setString(desc->c_str());
}
}
if (m_impl->setting->requiresRestart() && (this->hasUncommittedChanges() || m_impl->committed)) {
m_impl->restartRequiredLabel->setVisible(true);
m_impl->errorLabel->setVisible(true);
m_impl->errorLabel->setColor("mod-list-restart-required-label"_cc3b);
m_impl->errorLabel->setString("Restart Required");
m_impl->bg->setColor("mod-list-restart-required-label-bg"_cc3b);
m_impl->bg->setOpacity(75);
}
@ -159,19 +169,19 @@ void SettingNodeV3::setBGColor(ccColor4B const& color) {
void SettingNodeV3::markChanged() {
this->updateState();
SettingNodeValueChangeEventV3(false).post();
SettingNodeValueChangeEventV3(this, false).post();
}
void SettingNodeV3::commit() {
this->onCommit();
m_impl->committed = true;
this->updateState();
SettingNodeValueChangeEventV3(true).post();
SettingNodeValueChangeEventV3(this, true).post();
}
void SettingNodeV3::resetToDefault() {
m_impl->setting->reset();
this->onResetToDefault();
this->updateState();
SettingNodeValueChangeEventV3(false).post();
SettingNodeValueChangeEventV3(this, false).post();
}
void SettingNodeV3::setContentSize(CCSize const& size) {
@ -259,6 +269,16 @@ bool BoolSettingNodeV3::init(std::shared_ptr<BoolSettingV3> setting, float width
return true;
}
void BoolSettingNodeV3::updateState() {
SettingNodeV3::updateState();
auto enable = this->getSetting()->shouldEnable();
m_toggle->setCascadeColorEnabled(true);
m_toggle->setCascadeOpacityEnabled(true);
m_toggle->setEnabled(enable);
m_toggle->setColor(enable ? ccWHITE : ccGRAY);
m_toggle->setOpacity(enable ? 255 : 155);
}
void BoolSettingNodeV3::onCommit() {
this->getSetting()->setValue(m_toggle->isToggled());
}
@ -311,19 +331,19 @@ bool StringSettingNodeV3::init(std::shared_ptr<StringSettingV3> setting, float w
m_input->getInputNode()->m_placeholderLabel->setOpacity(255);
m_input->getInputNode()->m_placeholderLabel->setColor(ccWHITE);
auto arrowLeftSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png");
arrowLeftSpr->setFlipX(true);
arrowLeftSpr->setScale(.4f);
m_arrowLeftSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png");
m_arrowLeftSpr->setFlipX(true);
m_arrowLeftSpr->setScale(.4f);
auto arrowLeftBtn = CCMenuItemSpriteExtra::create(
arrowLeftSpr, this, menu_selector(StringSettingNodeV3::onArrow)
m_arrowLeftSpr, this, menu_selector(StringSettingNodeV3::onArrow)
);
arrowLeftBtn->setTag(-1);
this->getButtonMenu()->addChildAtPosition(arrowLeftBtn, Anchor::Left, ccp(5, 0));
auto arrowRightSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png");
arrowRightSpr->setScale(.4f);
m_arrowRightSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png");
m_arrowRightSpr->setScale(.4f);
auto arrowRightBtn = CCMenuItemSpriteExtra::create(
arrowRightSpr, this, menu_selector(StringSettingNodeV3::onArrow)
m_arrowRightSpr, this, menu_selector(StringSettingNodeV3::onArrow)
);
arrowRightBtn->setTag(1);
this->getButtonMenu()->addChildAtPosition(arrowRightBtn, Anchor::Right, ccp(-5, 0));
@ -334,6 +354,20 @@ bool StringSettingNodeV3::init(std::shared_ptr<StringSettingV3> setting, float w
return true;
}
void StringSettingNodeV3::updateState() {
SettingNodeV3::updateState();
auto enable = this->getSetting()->shouldEnable();
if (!this->getSetting()->getEnumOptions()) {
m_input->setEnabled(enable);
}
else {
m_arrowRightSpr->setOpacity(enable ? 255 : 155);
m_arrowRightSpr->setColor(enable ? ccWHITE : ccGRAY);
m_arrowLeftSpr->setOpacity(enable ? 255 : 155);
m_arrowLeftSpr->setColor(enable ? ccWHITE : ccGRAY);
}
}
void StringSettingNodeV3::onArrow(CCObject* sender) {
auto options = *this->getSetting()->getEnumOptions();
auto index = ranges::indexOf(options, m_input->getString()).value_or(0);
@ -350,7 +384,6 @@ void StringSettingNodeV3::onArrow(CCObject* sender) {
void StringSettingNodeV3::onCommit() {
this->getSetting()->setValue(m_input->getString());
}
bool StringSettingNodeV3::hasUncommittedChanges() const {
return m_input->getString() != this->getSetting()->getValue();
}
@ -396,12 +429,12 @@ bool FileSettingNodeV3::init(std::shared_ptr<FileSettingV3> setting, float width
m_nameLabel = CCLabelBMFont::create("", "bigFont.fnt");
this->getButtonMenu()->addChildAtPosition(m_nameLabel, Anchor::Left, ccp(13, 0), ccp(0, .5f));
auto selectSpr = CCSprite::createWithSpriteFrameName("GJ_plus2Btn_001.png");
selectSpr->setScale(.7f);
auto selectBtn = CCMenuItemSpriteExtra::create(
selectSpr, this, menu_selector(FileSettingNodeV3::onPickFile)
m_selectBtnSpr = CCSprite::createWithSpriteFrameName("GJ_plus2Btn_001.png");
m_selectBtnSpr->setScale(.7f);
m_selectBtn = CCMenuItemSpriteExtra::create(
m_selectBtnSpr, this, menu_selector(FileSettingNodeV3::onPickFile)
);
this->getButtonMenu()->addChildAtPosition(selectBtn, Anchor::Right, ccp(-5, 0));
this->getButtonMenu()->addChildAtPosition(m_selectBtn, Anchor::Right, ccp(-5, 0));
this->updateState();
@ -425,6 +458,11 @@ void FileSettingNodeV3::updateState() {
m_nameLabel->setOpacity(255);
}
m_nameLabel->limitLabelWidth(75, .35f, .1f);
auto enable = this->getSetting()->shouldEnable();
m_selectBtnSpr->setOpacity(enable ? 255 : 155);
m_selectBtnSpr->setColor(enable ? ccWHITE : ccGRAY);
m_selectBtn->setEnabled(enable);
}
void FileSettingNodeV3::onPickFile(CCObject*) {
@ -496,10 +534,10 @@ bool Color3BSettingNodeV3::init(std::shared_ptr<Color3BSettingV3> setting, float
m_colorSprite = ColorChannelSprite::create();
m_colorSprite->setScale(.65f);
auto button = CCMenuItemSpriteExtra::create(
m_colorBtn = CCMenuItemSpriteExtra::create(
m_colorSprite, this, menu_selector(Color3BSettingNodeV3::onSelectColor)
);
this->getButtonMenu()->addChildAtPosition(button, Anchor::Right, ccp(-10, 0));
this->getButtonMenu()->addChildAtPosition(m_colorBtn, Anchor::Right, ccp(-10, 0));
this->updateState();
@ -509,6 +547,10 @@ bool Color3BSettingNodeV3::init(std::shared_ptr<Color3BSettingV3> setting, float
void Color3BSettingNodeV3::updateState() {
SettingNodeV3::updateState();
m_colorSprite->setColor(m_value);
auto enable = this->getSetting()->shouldEnable();
m_colorSprite->setOpacity(enable ? 255 : 155);
m_colorBtn->setEnabled(enable);
}
void Color3BSettingNodeV3::onSelectColor(CCObject*) {
@ -559,10 +601,10 @@ bool Color4BSettingNodeV3::init(std::shared_ptr<Color4BSettingV3> setting, float
m_colorSprite = ColorChannelSprite::create();
m_colorSprite->setScale(.65f);
auto button = CCMenuItemSpriteExtra::create(
m_colorBtn = CCMenuItemSpriteExtra::create(
m_colorSprite, this, menu_selector(Color4BSettingNodeV3::onSelectColor)
);
this->getButtonMenu()->addChildAtPosition(button, Anchor::Right, ccp(-10, 0));
this->getButtonMenu()->addChildAtPosition(m_colorBtn, Anchor::Right, ccp(-10, 0));
this->updateState();
@ -573,6 +615,10 @@ void Color4BSettingNodeV3::updateState() {
SettingNodeV3::updateState();
m_colorSprite->setColor(to3B(m_value));
m_colorSprite->updateOpacity(m_value.a / 255.f);
auto enable = this->getSetting()->shouldEnable();
m_colorSprite->setOpacity(enable ? 255 : 155);
m_colorBtn->setEnabled(enable);
}
void Color4BSettingNodeV3::onSelectColor(CCObject*) {
@ -672,10 +718,10 @@ bool LegacyCustomSettingToV3Node::init(std::shared_ptr<LegacyCustomSettingV3> or
}
void LegacyCustomSettingToV3Node::settingValueChanged(SettingNode*) {
SettingNodeValueChangeEventV3(false).post();
SettingNodeValueChangeEventV3(this, false).post();
}
void LegacyCustomSettingToV3Node::settingValueCommitted(SettingNode*) {
SettingNodeValueChangeEventV3(true).post();
SettingNodeValueChangeEventV3(this, true).post();
}
void LegacyCustomSettingToV3Node::onCommit() {

View file

@ -32,6 +32,8 @@ protected:
CCMenuItemToggler* m_toggle;
bool init(std::shared_ptr<BoolSettingV3> setting, float width);
void updateState() override;
void onCommit() override;
void onToggle(CCObject*);
@ -57,6 +59,10 @@ protected:
CCMenuItemSpriteExtra* m_bigArrowLeftBtn;
CCMenuItemSpriteExtra* m_arrowRightBtn;
CCMenuItemSpriteExtra* m_bigArrowRightBtn;
CCSprite* m_arrowLeftBtnSpr;
CCSprite* m_bigArrowLeftBtnSpr;
CCSprite* m_arrowRightBtnSpr;
CCSprite* m_bigArrowRightBtnSpr;
float valueToSlider(ValueType value) {
auto min = this->getSetting()->getMinValue().value_or(-100);
@ -79,28 +85,28 @@ protected:
if (!SettingNodeV3::init(setting, width))
return false;
auto bigArrowLeftSpr = CCSprite::create();
bigArrowLeftSpr->setCascadeColorEnabled(true);
bigArrowLeftSpr->setCascadeOpacityEnabled(true);
m_bigArrowLeftBtnSpr = CCSprite::create();
m_bigArrowLeftBtnSpr->setCascadeColorEnabled(true);
m_bigArrowLeftBtnSpr->setCascadeOpacityEnabled(true);
auto bigArrowLeftSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png");
auto bigArrowLeftSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png");
bigArrowLeftSpr->setContentSize(bigArrowLeftSpr1->getContentSize() + ccp(20, 0));
bigArrowLeftSpr->addChildAtPosition(bigArrowLeftSpr2, Anchor::Center, ccp(10, 0));
bigArrowLeftSpr->addChildAtPosition(bigArrowLeftSpr1, Anchor::Center, ccp(-10, 0));
bigArrowLeftSpr->setScale(.3f);
m_bigArrowLeftBtnSpr->setContentSize(bigArrowLeftSpr1->getContentSize() + ccp(20, 0));
m_bigArrowLeftBtnSpr->addChildAtPosition(bigArrowLeftSpr2, Anchor::Center, ccp(10, 0));
m_bigArrowLeftBtnSpr->addChildAtPosition(bigArrowLeftSpr1, Anchor::Center, ccp(-10, 0));
m_bigArrowLeftBtnSpr->setScale(.3f);
m_bigArrowLeftBtn = CCMenuItemSpriteExtra::create(
bigArrowLeftSpr, this, menu_selector(NumberSettingNodeV3::onArrow)
m_bigArrowLeftBtnSpr, this, menu_selector(NumberSettingNodeV3::onArrow)
);
m_bigArrowLeftBtn->setUserObject(ObjWrapper<ValueType>::create(-setting->getBigArrowStepSize()));
m_bigArrowLeftBtn->setVisible(setting->isBigArrowsEnabled());
this->getButtonMenu()->addChildAtPosition(m_bigArrowLeftBtn, Anchor::Left, ccp(5, 0));
auto arrowLeftSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png");
arrowLeftSpr->setScale(.5f);
m_arrowLeftBtnSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png");
m_arrowLeftBtnSpr->setScale(.5f);
m_arrowLeftBtn = CCMenuItemSpriteExtra::create(
arrowLeftSpr, this, menu_selector(NumberSettingNodeV3::onArrow)
m_arrowLeftBtnSpr, this, menu_selector(NumberSettingNodeV3::onArrow)
);
m_arrowLeftBtn->setUserObject(ObjWrapper<ValueType>::create(-setting->getArrowStepSize()));
m_arrowLeftBtn->setVisible(setting->isArrowsEnabled());
@ -119,31 +125,31 @@ protected:
}
this->getButtonMenu()->addChildAtPosition(m_input, Anchor::Center);
auto arrowRightSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png");
arrowRightSpr->setFlipX(true);
arrowRightSpr->setScale(.5f);
m_arrowRightBtnSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png");
m_arrowRightBtnSpr->setFlipX(true);
m_arrowRightBtnSpr->setScale(.5f);
m_arrowRightBtn = CCMenuItemSpriteExtra::create(
arrowRightSpr, this, menu_selector(NumberSettingNodeV3::onArrow)
m_arrowRightBtnSpr, this, menu_selector(NumberSettingNodeV3::onArrow)
);
m_arrowRightBtn->setUserObject(ObjWrapper<ValueType>::create(setting->getArrowStepSize()));
m_arrowRightBtn->setVisible(setting->isArrowsEnabled());
this->getButtonMenu()->addChildAtPosition(m_arrowRightBtn, Anchor::Right, ccp(-22, 0));
auto bigArrowRightSpr = CCSprite::create();
bigArrowRightSpr->setCascadeColorEnabled(true);
bigArrowRightSpr->setCascadeOpacityEnabled(true);
m_bigArrowRightBtnSpr = CCSprite::create();
m_bigArrowRightBtnSpr->setCascadeColorEnabled(true);
m_bigArrowRightBtnSpr->setCascadeOpacityEnabled(true);
auto bigArrowRightSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png");
bigArrowRightSpr1->setFlipX(true);
auto bigArrowRightSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png");
bigArrowRightSpr2->setFlipX(true);
bigArrowRightSpr->setContentSize(bigArrowRightSpr1->getContentSize() + ccp(20, 0));
bigArrowRightSpr->addChildAtPosition(bigArrowRightSpr1, Anchor::Center, ccp(-10, 0));
bigArrowRightSpr->addChildAtPosition(bigArrowRightSpr2, Anchor::Center, ccp(10, 0));
bigArrowRightSpr->setScale(.3f);
m_bigArrowRightBtnSpr->setContentSize(bigArrowRightSpr1->getContentSize() + ccp(20, 0));
m_bigArrowRightBtnSpr->addChildAtPosition(bigArrowRightSpr1, Anchor::Center, ccp(-10, 0));
m_bigArrowRightBtnSpr->addChildAtPosition(bigArrowRightSpr2, Anchor::Center, ccp(10, 0));
m_bigArrowRightBtnSpr->setScale(.3f);
m_bigArrowRightBtn = CCMenuItemSpriteExtra::create(
bigArrowRightSpr, this, menu_selector(NumberSettingNodeV3::onArrow)
m_bigArrowRightBtnSpr, this, menu_selector(NumberSettingNodeV3::onArrow)
);
m_bigArrowRightBtn->setUserObject(ObjWrapper<ValueType>::create(setting->getBigArrowStepSize()));
m_bigArrowRightBtn->setVisible(setting->isBigArrowsEnabled());
@ -166,27 +172,35 @@ protected:
void updateState() override {
SettingNodeV3::updateState();
auto enable = this->getSetting()->shouldEnable();
if (this->getSetting()->isInputEnabled()) {
m_input->setEnabled(enable);
}
auto min = this->getSetting()->getMinValue();
auto enableLeft = enable && (!min || this->getCurrentValue() > *min);
m_arrowLeftBtn->setEnabled(enableLeft);
m_bigArrowLeftBtn->setEnabled(enableLeft);
m_arrowLeftBtnSpr->setOpacity(enableLeft ? 255 : 155);
m_arrowLeftBtnSpr->setColor(enableLeft ? ccWHITE : ccGRAY);
m_bigArrowLeftBtnSpr->setOpacity(enableLeft ? 255 : 155);
m_bigArrowLeftBtnSpr->setColor(enableLeft ? ccWHITE : ccGRAY);
auto max = this->getSetting()->getMaxValue();
auto enableRight = enable && (!max || this->getCurrentValue() < *max);
m_arrowRightBtn->setEnabled(enableRight);
m_bigArrowRightBtn->setEnabled(enableRight);
m_arrowRightBtnSpr->setOpacity(enableRight ? 255 : 155);
m_arrowRightBtnSpr->setColor(enableRight ? ccWHITE : ccGRAY);
m_bigArrowRightBtnSpr->setOpacity(enableRight ? 255 : 155);
m_bigArrowRightBtnSpr->setColor(enableRight ? ccWHITE : ccGRAY);
if (m_slider) {
m_slider->m_touchLogic->m_thumb->setValue(this->valueToSlider(this->getCurrentValue()));
m_slider->updateBar();
}
if (auto min = this->getSetting()->getMinValue()) {
auto enable = this->getCurrentValue() > *min;
m_arrowLeftBtn->setEnabled(enable);
m_bigArrowLeftBtn->setEnabled(enable);
static_cast<CCSprite*>(m_arrowLeftBtn->getNormalImage())->setOpacity(enable ? 255 : 155);
static_cast<CCSprite*>(m_arrowLeftBtn->getNormalImage())->setColor(enable ? ccWHITE : ccGRAY);
static_cast<CCSprite*>(m_bigArrowLeftBtn->getNormalImage())->setOpacity(enable ? 255 : 155);
static_cast<CCSprite*>(m_bigArrowLeftBtn->getNormalImage())->setColor(enable ? ccWHITE : ccGRAY);
}
if (auto max = this->getSetting()->getMaxValue()) {
auto enable = this->getCurrentValue() < *max;
m_arrowRightBtn->setEnabled(enable);
m_bigArrowRightBtn->setEnabled(enable);
static_cast<CCSprite*>(m_arrowRightBtn->getNormalImage())->setOpacity(enable ? 255 : 155);
static_cast<CCSprite*>(m_arrowRightBtn->getNormalImage())->setColor(enable ? ccWHITE : ccGRAY);
static_cast<CCSprite*>(m_bigArrowRightBtn->getNormalImage())->setOpacity(enable ? 255 : 155);
static_cast<CCSprite*>(m_bigArrowRightBtn->getNormalImage())->setColor(enable ? ccWHITE : ccGRAY);
m_slider->m_sliderBar->setColor(enable ? ccWHITE : ccGRAY);
m_slider->m_touchLogic->m_thumb->setColor(enable ? ccWHITE : ccGRAY);
m_slider->m_touchLogic->m_thumb->setEnabled(enable);
}
}
@ -250,13 +264,17 @@ using FloatSettingNodeV3 = NumberSettingNodeV3<FloatSettingV3>;
class StringSettingNodeV3 : public SettingNodeV3 {
protected:
TextInput* m_input;
CCSprite* m_arrowLeftSpr = nullptr;
CCSprite* m_arrowRightSpr = nullptr;
bool init(std::shared_ptr<StringSettingV3> setting, float width);
void onCommit() override;
void updateState() override;
void onArrow(CCObject* sender);
void onCommit() override;
public:
static StringSettingNodeV3* create(std::shared_ptr<StringSettingV3> setting, float width);
@ -273,6 +291,8 @@ protected:
std::filesystem::path m_path;
CCLabelBMFont* m_nameLabel;
EventListener<Task<Result<std::filesystem::path>>> m_pickListener;
CCMenuItemSpriteExtra* m_selectBtn;
CCSprite* m_selectBtnSpr;
bool init(std::shared_ptr<FileSettingV3> setting, float width);
@ -294,6 +314,7 @@ public:
class Color3BSettingNodeV3 : public SettingNodeV3, public ColorPickPopupDelegate {
protected:
ccColor3B m_value;
CCMenuItemSpriteExtra* m_colorBtn;
ColorChannelSprite* m_colorSprite;
bool init(std::shared_ptr<Color3BSettingV3> setting, float width);
@ -317,6 +338,7 @@ public:
class Color4BSettingNodeV3 : public SettingNodeV3, public ColorPickPopupDelegate {
protected:
ccColor4B m_value;
CCMenuItemSpriteExtra* m_colorBtn;
ColorChannelSprite* m_colorSprite;
bool init(std::shared_ptr<Color4BSettingV3> setting, float width);

View file

@ -5,6 +5,398 @@
using namespace geode::prelude;
namespace enable_if_parsing {
struct Component {
virtual ~Component() = default;
virtual Result<> check() const = 0;
virtual Result<> eval(std::string const& defaultModID) const = 0;
};
struct RequireModLoaded final : public Component {
std::string modID;
RequireModLoaded(std::string const& modID)
: modID(modID) {}
Result<> check() const override {
return Ok();
}
Result<> eval(std::string const& defaultModID) const override {
if (Loader::get()->getLoadedMod(modID)) {
return Ok();
}
auto modName = modID;
if (auto mod = Loader::get()->getInstalledMod(modID)) {
modName = mod->getName();
}
return Err("Enable the mod {}", modName);
}
};
struct RequireSettingEnabled final : public Component {
std::string modID;
std::string settingID;
RequireSettingEnabled(std::string const& modID, std::string const& settingID)
: modID(modID), settingID(settingID) {}
Result<> check() const override {
if (auto mod = Loader::get()->getInstalledMod(modID)) {
if (!mod->hasSetting(settingID)) {
return Err("Mod '{}' does not have setting '{}'", mod->getName(), settingID);
}
if (!typeinfo_pointer_cast<BoolSettingV3>(mod->getSettingV3(settingID))) {
return Err("Setting '{}' in mod '{}' is not a boolean setting", settingID, mod->getName());
}
}
return Ok();
}
Result<> eval(std::string const& defaultModID) const override {
if (auto mod = Loader::get()->getLoadedMod(modID)) {
if (mod->template getSettingValue<bool>(settingID)) {
return Ok();
}
// This is an if-check just in case, even though check() should already
// make sure that getSettingV3 is guaranteed to return true
auto name = settingID;
if (auto sett = mod->getSettingV3(settingID)) {
name = sett->getDisplayName();
}
if (modID == defaultModID) {
return Err("Enable the setting '{}'", name);
}
return Err("Enable the setting '{}' from the mod {}", name, mod->getName());
}
auto modName = modID;
if (auto mod = Loader::get()->getInstalledMod(modID)) {
modName = mod->getName();
}
return Err("Enable the mod {}", modName);
}
};
struct RequireSavedValueEnabled final : public Component {
std::string modID;
std::string savedValue;
RequireSavedValueEnabled(std::string const& modID, std::string const& savedValue)
: modID(modID), savedValue(savedValue) {}
Result<> check() const override {
return Ok();
}
Result<> eval(std::string const& defaultModID) const override {
if (auto mod = Loader::get()->getLoadedMod(modID)) {
if (mod->template getSavedValue<bool>(savedValue)) {
return Ok();
}
if (modID == defaultModID) {
return Err("Enable the value '{}'", savedValue);
}
return Err("Enable the value '{}' from the mod {}", savedValue, mod->getName());
}
auto modName = modID;
if (auto mod = Loader::get()->getInstalledMod(modID)) {
modName = mod->getName();
}
return Err("Enable the mod {}", modName);
}
};
struct RequireNot final : public Component {
std::unique_ptr<Component> component;
RequireNot(std::unique_ptr<Component>&& component)
: component(std::move(component)) {}
Result<> check() const override {
return component->check();
}
Result<> eval(std::string const& defaultModID) const override {
if (auto res = component->eval(defaultModID)) {
// Surely this will never break!
auto str = res.unwrapErr();
string::replaceIP(str, "Enable", "___TEMP");
string::replaceIP(str, "Disable", "Enable");
string::replaceIP(str, "___TEMP", "Disable");
return Err(str);
}
return Ok();
}
};
struct RequireAll final : public Component {
std::vector<std::unique_ptr<Component>> components;
RequireAll(std::vector<std::unique_ptr<Component>>&& components)
: components(std::move(components)) {}
Result<> check() const override {
for (auto& comp : components) {
GEODE_UNWRAP(comp->check());
}
return Ok();
}
Result<> eval(std::string const& defaultModID) const override {
// Only print out whatever the first erroring condition is to not shit out
// "Please enable X and Y and Z and Ö and Å and"
for (auto& comp : components) {
GEODE_UNWRAP(comp->eval(defaultModID));
}
return Ok();
}
};
struct RequireSome final : public Component {
std::vector<std::unique_ptr<Component>> components;
RequireSome(std::vector<std::unique_ptr<Component>>&& components)
: components(std::move(components)) {}
Result<> check() const override {
for (auto& comp : components) {
GEODE_UNWRAP(comp->check());
}
return Ok();
}
Result<> eval(std::string const& defaultModID) const override {
Result<> err = Ok();
for (auto& comp : components) {
auto res = comp->eval(defaultModID);
if (res) {
return Ok();
}
// Only show first condition that isn't met
if (err.isOk()) {
err = Err(res.unwrapErr());
}
}
return err;
}
};
static bool isComponentStartChar(char c) {
return
('a' <= c && c <= 'z') ||
('A' <= c && c <= 'Z') ||
c == '_';
}
static bool isComponentContinueChar(char c) {
return
('a' <= c && c <= 'z') ||
('A' <= c && c <= 'Z') ||
('0' <= c && c <= '9') ||
c == '_' || c == '-' || c == '/' ||
c == '.' || c == ':';
}
class Parser final {
private:
std::string_view m_src;
size_t m_index = 0;
std::string m_defaultModID;
static bool isUnOpWord(std::string_view op) {
return op == "!";
}
static bool isBiOpWord(std::string_view op) {
return op == "&&" || op == "||";
}
Result<std::optional<std::string_view>> nextWord() {
// Skip whitespace
while (m_index < m_src.size() && std::isspace(m_src[m_index])) {
m_index += 1;
}
if (m_index == m_src.size()) {
return Ok(std::nullopt);
}
// Parentheses & single operators
if (m_src[m_index] == '(' || m_src[m_index] == ')' || m_src[m_index] == '!') {
m_index += 1;
return Ok(m_src.substr(m_index - 1, 1));
}
// Double-character operators
if (m_src[m_index] == '&' || m_src[m_index] == '|') {
// Consume first character
m_index += 1;
// Next character must be the same
if (m_index == m_src.size() || m_src[m_index - 1] != m_src[m_index]) {
return Err("Expected '{}' at index {}", m_src[m_index - 1], m_index - 1);
}
// Consume second character
m_index += 1;
return Ok(m_src.substr(m_index - 2, 2));
}
// Components
if (isComponentStartChar(m_src[m_index])) {
auto start = m_index;
m_index += 1;
while (m_index < m_src.size() && isComponentContinueChar(m_src[m_index])) {
m_index += 1;
}
return Ok(m_src.substr(start, m_index - start));
}
return Err("Unexpected character '{}' at index {}", m_src[m_index], m_index);
}
std::optional<std::string_view> peekWord() {
auto original = m_index;
auto ret = this->nextWord();
m_index = original;
return ret ? *ret : std::nullopt;
}
Result<std::unique_ptr<Component>> nextComponent() {
GEODE_UNWRAP_INTO(auto maybeWord, this->nextWord());
if (!maybeWord) {
return Err("Expected component, got end-of-enable-if-string");
}
const auto word = *maybeWord;
if (isUnOpWord(word) || isBiOpWord(word)) {
return Err("Expected component, got operator \"{}\" at index {}", word, m_index - word.size());
}
if (word == ")") {
return Err("Unexpected closing parenthesis at index {}", m_index - 1);
}
if (word == "(") {
GEODE_UNWRAP_INTO(auto op, this->next());
GEODE_UNWRAP_INTO(auto maybeClosing, this->nextWord());
if (!maybeClosing) {
return Err("Expected closing parenthesis, got end-of-enable-if-string");
}
if (maybeClosing != ")") {
return Err(
"Expected closing parenthesis, got \"{}\" at index {}",
*maybeClosing, m_index - maybeClosing->size()
);
}
return Ok(std::move(op));
}
std::string_view ty = "setting";
std::string_view value = word;
if (word.find(':') != std::string::npos) {
ty = word.substr(0, word.find(':'));
value = word.substr(word.find(':') + 1);
}
switch (hash(ty)) {
case hash("setting"): {
std::string modID = m_defaultModID;
std::string settingID = std::string(value);
// mod.id/setting-id
if (value.find('/') != std::string::npos) {
modID = value.substr(0, value.find('/'));
settingID = value.substr(value.find('/') + 1);
}
if (!ModMetadata::validateID(std::string(modID))) {
return Err("Invalid mod ID '{}'", modID);
}
return Ok(std::make_unique<RequireSettingEnabled>(modID, settingID));
} break;
case hash("saved"): {
std::string modID = m_defaultModID;
std::string savedValue = std::string(value);
// mod.id/setting-id
if (value.find('/') != std::string::npos) {
modID = value.substr(0, value.find('/'));
savedValue = value.substr(value.find('/') + 1);
}
if (!ModMetadata::validateID(std::string(modID))) {
return Err("Invalid mod ID '{}'", modID);
}
return Ok(std::make_unique<RequireSavedValueEnabled>(modID, savedValue));
} break;
case hash("loaded"): {
if (!ModMetadata::validateID(std::string(value))) {
return Err("Invalid mod ID '{}'", value);
}
return Ok(std::make_unique<RequireModLoaded>(std::string(value)));
} break;
default: {
return Err("Invalid designator '{}' at index {}", ty, m_index - word.size());
} break;
}
}
Result<std::unique_ptr<Component>> nextUnOp() {
std::string op;
if (auto peek = this->peekWord()) {
if (isUnOpWord(*peek)) {
op = *peek;
}
}
GEODE_UNWRAP_INTO(auto comp, this->nextComponent());
if (op.empty()) {
return Ok(std::move(comp));
}
switch (hash(op)) {
case hash("!"): {
return Ok(std::make_unique<RequireNot>(std::move(comp)));
} break;
default: {
return Err(
"THIS SHOULD BE UNREACHABLE!! \"{}\" was an unhandled "
"unary operator despite isUnOpWord claiming it's valid! "
"REPORT THIS BUG TO GEODE DEVELOPERS",
op
);
} break;
}
}
Result<std::unique_ptr<Component>> nextBiOp() {
GEODE_UNWRAP_INTO(auto first, this->nextUnOp());
std::string firstOp;
std::vector<std::unique_ptr<Component>> components;
while (auto peek = this->peekWord()) {
if (!isBiOpWord(*peek)) {
break;
}
GEODE_UNWRAP_INTO(auto word, this->nextWord());
auto op = *word;
if (firstOp.empty()) {
firstOp = op;
}
if (op != firstOp) {
return Err(
"Expected operator \"{}\", got operator \"{}\" - "
"parentheses are required to disambiguate operator chains",
firstOp, op
);
}
GEODE_UNWRAP_INTO(auto comp, this->nextUnOp());
components.emplace_back(std::move(comp));
}
if (components.size()) {
components.emplace(components.begin(), std::move(first));
switch (hash(firstOp)) {
case hash("&&"): {
return Ok(std::make_unique<RequireAll>(std::move(components)));
} break;
case hash("||"): {
return Ok(std::make_unique<RequireSome>(std::move(components)));
} break;
default: {
return Err(
"THIS SHOULD BE UNREACHABLE!! \"{}\" was an unhandled "
"binary operator despite isBiOpWord claiming it's valid! "
"REPORT THIS BUG TO GEODE DEVELOPERS",
firstOp
);
} break;
}
}
return Ok(std::move(first));
}
Result<std::unique_ptr<Component>> next() {
return this->nextBiOp();
}
public:
static Result<std::unique_ptr<Component>> parse(std::string_view str, std::string const& defaultModID) {
auto ret = Parser();
ret.m_src = str;
ret.m_defaultModID = defaultModID;
GEODE_UNWRAP_INTO(auto comp, ret.next());
GEODE_UNWRAP_INTO(auto shouldBeEOF, ret.nextWord());
if (shouldBeEOF) {
return Err(
"Expected end-of-enable-if-string, got \"{}\" at index {}",
*shouldBeEOF, ret.m_index - shouldBeEOF->size()
);
}
return Ok(std::move(comp));
}
};
}
class SettingV3::GeodeImpl {
public:
std::string modID;
@ -12,6 +404,8 @@ public:
std::optional<std::string> name;
std::optional<std::string> description;
std::optional<std::string> enableIf;
std::unique_ptr<enable_if_parsing::Component> enableIfTree;
std::optional<std::string> enableIfDescription;
bool requiresRestart = false;
};
@ -30,8 +424,16 @@ void SettingV3::parseSharedProperties(std::string const& key, std::string const&
value.has("name").into(m_impl->name);
value.has("description").into(m_impl->description);
if (!onlyNameAndDesc) {
value.has("enable-if").into(m_impl->enableIf);
value.has("requires-restart").into(m_impl->requiresRestart);
value.has("enable-if")
.template mustBe<std::string>("a valid \"enable-if\" scheme", [this](std::string const& str) -> Result<> {
GEODE_UNWRAP_INTO(auto tree, enable_if_parsing::Parser::parse(str, m_impl->modID));
GEODE_UNWRAP(tree->check());
m_impl->enableIfTree = std::move(tree);
return Ok();
})
.into(m_impl->enableIf);
value.has("enable-if-description").into(m_impl->enableIfDescription);
}
}
void SettingV3::init(std::string const& key, std::string const& modID) {
@ -57,6 +459,25 @@ std::optional<std::string> SettingV3::getDescription() const {
std::optional<std::string> SettingV3::getEnableIf() const {
return m_impl->enableIf;
}
bool SettingV3::shouldEnable() const {
if (m_impl->enableIfTree) {
return m_impl->enableIfTree->eval(m_impl->modID).isOk();
}
return true;
}
std::optional<std::string> SettingV3::getEnableIfDescription() const {
if (m_impl->enableIfDescription) {
return *m_impl->enableIfDescription;
}
if (!m_impl->enableIfTree) {
return std::nullopt;
}
auto res = m_impl->enableIfTree->eval(m_impl->modID);
if (res) {
return std::nullopt;
}
return res.unwrapErr();
}
bool SettingV3::requiresRestart() const {
return m_impl->requiresRestart;
}

View file

@ -87,8 +87,8 @@ bool ModSettingsPopup::setup(Mod* mod) {
);
m_buttonMenu->addChildAtPosition(openDirBtn, Anchor::BottomRight, ccp(-53, 20));
m_changeListener.bind([this](auto) {
this->updateState();
m_changeListener.bind([this](auto* ev) {
this->updateState(ev->getNode());
return ListenerResult::Propagate;
});
this->updateState();
@ -125,7 +125,17 @@ void ModSettingsPopup::onResetAll(CCObject*) {
);
}
void ModSettingsPopup::updateState() {
void ModSettingsPopup::updateState(SettingNodeV3* invoker) {
// Update all settings with "enable-if" schemes
for (auto& sett : m_settings) {
// Avoid infinite loops
if (sett == invoker) {
continue;
}
if (sett->getSetting()->getEnableIf()) {
sett->updateState();
}
}
m_applyBtnSpr->setCascadeColorEnabled(true);
m_applyBtnSpr->setCascadeOpacityEnabled(true);
if (this->hasUncommitted()) {

View file

@ -16,8 +16,7 @@ protected:
EventListener<EventFilter<SettingNodeValueChangeEventV3>> m_changeListener;
bool setup(Mod* mod) override;
void updateState();
void onChangeEvent(SettingNodeValueChangeEventV3* event);
void updateState(SettingNodeV3* invoker = nullptr);
bool hasUncommitted() const;
void onClose(CCObject*) override;
void onApply(CCObject*);