From 766b71dfbdd7cf5f2fca0fcb0e6fe28c124bcb3d Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sat, 24 Aug 2024 00:33:38 +0300 Subject: [PATCH] path settings done --- loader/include/Geode/loader/SettingV3.hpp | 12 +- loader/src/loader/SettingNodeV3.cpp | 91 +++++++++++-- loader/src/loader/SettingNodeV3.hpp | 77 ++++++++--- loader/src/loader/SettingV3.cpp | 156 ++++++++++++++-------- 4 files changed, 252 insertions(+), 84 deletions(-) diff --git a/loader/include/Geode/loader/SettingV3.hpp b/loader/include/Geode/loader/SettingV3.hpp index d75665b2..8f17c193 100644 --- a/loader/include/Geode/loader/SettingV3.hpp +++ b/loader/include/Geode/loader/SettingV3.hpp @@ -267,8 +267,8 @@ namespace geode { bool isArrowsEnabled() const; bool isBigArrowsEnabled() const; - size_t getArrowStepSize() const; - size_t getBigArrowStepSize() const; + double getArrowStepSize() const; + double getBigArrowStepSize() const; bool isSliderEnabled() const; std::optional getSliderSnap() const; bool isInputEnabled() const; @@ -328,9 +328,17 @@ namespace geode { FileSettingV3(PrivateMarker); static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json); + enum class FileType { + Any = 0, + File = 1, + Folder = 2, + }; + std::filesystem::path getDefaultValue() const override; Result<> isValid(std::filesystem::path const& value) const override; + FileType getFileType() const; + std::optional> getFilters() const; bool load(matjson::Value const& json) override; diff --git a/loader/src/loader/SettingNodeV3.cpp b/loader/src/loader/SettingNodeV3.cpp index 58973ef9..c9b80726 100644 --- a/loader/src/loader/SettingNodeV3.cpp +++ b/loader/src/loader/SettingNodeV3.cpp @@ -73,6 +73,7 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { m_impl->nameMenu->addChild(m_impl->resetButton); m_impl->nameMenu->setLayout(RowLayout::create()->setAxisAlignment(AxisAlignment::Start)); + m_impl->nameMenu->getLayout()->ignoreInvisibleChildren(true); this->addChildAtPosition(m_impl->nameMenu, Anchor::Left, ccp(10, 0), ccp(0, .5f)); m_impl->buttonMenu = CCMenu::create(); @@ -197,6 +198,10 @@ TitleSettingNodeV3* TitleSettingNodeV3::create(std::shared_ptr s bool BoolSettingNodeV3::init(std::shared_ptr setting, float width) { if (!SettingNodeV3::init(setting, width)) return false; + + this->getButtonMenu()->setContentWidth(20); + this->getNameMenu()->setContentWidth(width - 50); + this->getNameMenu()->updateLayout(); m_toggle = CCMenuItemToggler::createWithStandardSprites( this, menu_selector(BoolSettingNodeV3::onToggle), .55f @@ -209,9 +214,6 @@ bool BoolSettingNodeV3::init(std::shared_ptr setting, float width m_toggle->toggle(setting->getValue()); this->getButtonMenu()->addChildAtPosition(m_toggle, Anchor::Right, ccp(-10, 0)); - this->getNameMenu()->setContentWidth(width - 50); - this->getNameMenu()->updateLayout(); - this->updateState(); return true; @@ -255,7 +257,7 @@ bool StringSettingNodeV3::init(std::shared_ptr setting, float w if (!SettingNodeV3::init(setting, width)) return false; - m_input = TextInput::create(width / 2 - 50, "Num"); + m_input = TextInput::create(setting->getEnumOptions() ? width / 2 - 50 : width / 2, "Text"); m_input->setCallback([this](auto const&) { this->markChanged(); }); @@ -339,20 +341,91 @@ bool FileSettingNodeV3::init(std::shared_ptr setting, float width if (!SettingNodeV3::init(setting, width)) return false; - // todo + m_path = setting->getValue(); + + auto labelBG = extension::CCScale9Sprite::create("square02b_001.png", { 0, 0, 80, 80 }); + labelBG->setScale(.25f); + labelBG->setColor({ 0, 0, 0 }); + labelBG->setOpacity(90); + labelBG->setContentSize({ 400, 80 }); + this->getButtonMenu()->addChildAtPosition(labelBG, Anchor::Center, ccp(-15, 0)); + + m_fileIcon = CCSprite::create(); + this->getButtonMenu()->addChildAtPosition(m_fileIcon, Anchor::Left, ccp(3, 0)); + + m_nameLabel = CCLabelBMFont::create("", "bigFont.fnt"); + this->getButtonMenu()->addChildAtPosition(m_nameLabel, Anchor::Left, ccp(11, 0), ccp(0, .5f)); + + auto selectSpr = CCSprite::createWithSpriteFrameName("GJ_plus2Btn_001.png"); + selectSpr->setScale(.75f); + auto selectBtn = CCMenuItemSpriteExtra::create( + selectSpr, this, menu_selector(FileSettingNodeV3::onPickFile) + ); + this->getButtonMenu()->addChildAtPosition(selectBtn, Anchor::Right, ccp(-5, 0)); + + this->updateState(); return true; } -void FileSettingNodeV3::onCommit() {} +void FileSettingNodeV3::updateState() { + SettingNodeV3::updateState(); + auto ty = this->getSetting()->getFileType(); + if (ty == FileSettingV3::FileType::Any) { + ty = std::filesystem::is_directory(m_path) ? + FileSettingV3::FileType::Folder : + FileSettingV3::FileType::File; + } + m_fileIcon->setDisplayFrame(CCSpriteFrameCache::get()->spriteFrameByName( + ty == FileSettingV3::FileType::File ? "file.png"_spr : "folderIcon_001.png" + )); + limitNodeSize(m_fileIcon, ccp(10, 10), 1.f, .1f); + m_nameLabel->setString(m_path.filename().string().c_str()); + m_nameLabel->limitLabelWidth(75, .4f, .1f); +} + +void FileSettingNodeV3::onCommit() { + this->getSetting()->setValue(m_path); +} + +void FileSettingNodeV3::onPickFile(CCObject*) { + m_pickListener.bind([this](auto* event) { + auto value = event->getValue(); + if (!value) { + return; + } + if (value->isOk()) { + m_path = value->unwrap().string(); + this->markChanged(); + } + else { + FLAlertLayer::create( + "Failed", + fmt::format("Failed to pick file: {}", value->unwrapErr()), + "Ok" + )->show(); + } + }); + m_pickListener.setFilter(file::pick( + this->getSetting()->getFileType() == FileSettingV3::FileType::Folder ? + file::PickMode::OpenFolder : + file::PickMode::OpenFile, + { + dirs::getGameDir(), + this->getSetting()->getFilters().value_or(std::vector()) + } + )); +} bool FileSettingNodeV3::hasUncommittedChanges() const { - return false; + return m_path != this->getSetting()->getValue(); } bool FileSettingNodeV3::hasNonDefaultValue() const { - return false; + return m_path != this->getSetting()->getDefaultValue(); +} +void FileSettingNodeV3::onResetToDefault() { + m_path = this->getSetting()->getDefaultValue(); } -void FileSettingNodeV3::onResetToDefault() {} std::shared_ptr FileSettingNodeV3::getSetting() const { return std::static_pointer_cast(SettingNodeV3::getSetting()); diff --git a/loader/src/loader/SettingNodeV3.hpp b/loader/src/loader/SettingNodeV3.hpp index 09458b9a..34e6e32e 100644 --- a/loader/src/loader/SettingNodeV3.hpp +++ b/loader/src/loader/SettingNodeV3.hpp @@ -52,6 +52,10 @@ protected: TextInput* m_input; Slider* m_slider; + CCMenuItemSpriteExtra* m_arrowLeftBtn; + CCMenuItemSpriteExtra* m_bigArrowLeftBtn; + CCMenuItemSpriteExtra* m_arrowRightBtn; + CCMenuItemSpriteExtra* m_bigArrowRightBtn; float valueToSlider(ValueType value) { auto min = this->getSetting()->getMinValue().value_or(-100); @@ -75,6 +79,8 @@ protected: return false; auto bigArrowLeftSpr = CCSprite::create(); + bigArrowLeftSpr->setCascadeColorEnabled(true); + bigArrowLeftSpr->setCascadeOpacityEnabled(true); auto bigArrowLeftSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); auto bigArrowLeftSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); @@ -83,21 +89,21 @@ protected: bigArrowLeftSpr->addChildAtPosition(bigArrowLeftSpr1, Anchor::Center, ccp(-10, 0)); bigArrowLeftSpr->setScale(.3f); - auto bigArrowLeftBtn = CCMenuItemSpriteExtra::create( + m_bigArrowLeftBtn = CCMenuItemSpriteExtra::create( bigArrowLeftSpr, this, menu_selector(NumberSettingNodeV3::onArrow) ); - bigArrowLeftBtn->setTag(-setting->getBigArrowStepSize()); - bigArrowLeftBtn->setVisible(setting->isBigArrowsEnabled()); - this->getButtonMenu()->addChildAtPosition(bigArrowLeftBtn, Anchor::Left, ccp(5, 0)); + m_bigArrowLeftBtn->setUserObject(ObjWrapper::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); - auto arrowLeftBtn = CCMenuItemSpriteExtra::create( + m_arrowLeftBtn = CCMenuItemSpriteExtra::create( arrowLeftSpr, this, menu_selector(NumberSettingNodeV3::onArrow) ); - arrowLeftBtn->setTag(-setting->getArrowStepSize()); - arrowLeftBtn->setVisible(setting->isArrowsEnabled()); - this->getButtonMenu()->addChildAtPosition(arrowLeftBtn, Anchor::Left, ccp(22, 0)); + m_arrowLeftBtn->setUserObject(ObjWrapper::create(-setting->getArrowStepSize())); + m_arrowLeftBtn->setVisible(setting->isArrowsEnabled()); + this->getButtonMenu()->addChildAtPosition(m_arrowLeftBtn, Anchor::Left, ccp(22, 0)); m_input = TextInput::create(this->getButtonMenu()->getContentWidth() - 40, "Num"); m_input->setScale(.7f); @@ -115,14 +121,16 @@ protected: auto arrowRightSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_01_001.png"); arrowRightSpr->setFlipX(true); arrowRightSpr->setScale(.5f); - auto arrowRightBtn = CCMenuItemSpriteExtra::create( + m_arrowRightBtn = CCMenuItemSpriteExtra::create( arrowRightSpr, this, menu_selector(NumberSettingNodeV3::onArrow) ); - arrowRightBtn->setTag(setting->getArrowStepSize()); - arrowRightBtn->setVisible(setting->isArrowsEnabled()); - this->getButtonMenu()->addChildAtPosition(arrowRightBtn, Anchor::Right, ccp(-22, 0)); + m_arrowRightBtn->setUserObject(ObjWrapper::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); auto bigArrowRightSpr1 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); bigArrowRightSpr1->setFlipX(true); auto bigArrowRightSpr2 = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); @@ -133,12 +141,12 @@ protected: bigArrowRightSpr->addChildAtPosition(bigArrowRightSpr2, Anchor::Center, ccp(10, 0)); bigArrowRightSpr->setScale(.3f); - auto bigArrowRightBtn = CCMenuItemSpriteExtra::create( + m_bigArrowRightBtn = CCMenuItemSpriteExtra::create( bigArrowRightSpr, this, menu_selector(NumberSettingNodeV3::onArrow) ); - bigArrowRightBtn->setTag(setting->getBigArrowStepSize()); - bigArrowRightBtn->setVisible(setting->isBigArrowsEnabled()); - this->getButtonMenu()->addChildAtPosition(bigArrowRightBtn, Anchor::Right, ccp(-5, 0)); + m_bigArrowRightBtn->setUserObject(ObjWrapper::create(setting->getBigArrowStepSize())); + m_bigArrowRightBtn->setVisible(setting->isBigArrowsEnabled()); + this->getButtonMenu()->addChildAtPosition(m_bigArrowRightBtn, Anchor::Right, ccp(-5, 0)); if (setting->isSliderEnabled()) { this->setContentHeight(45); @@ -161,13 +169,40 @@ protected: 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(m_arrowLeftBtn->getNormalImage())->setOpacity(enable ? 255 : 155); + static_cast(m_arrowLeftBtn->getNormalImage())->setColor(enable ? ccWHITE : ccGRAY); + static_cast(m_bigArrowLeftBtn->getNormalImage())->setOpacity(enable ? 255 : 155); + static_cast(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(m_arrowRightBtn->getNormalImage())->setOpacity(enable ? 255 : 155); + static_cast(m_arrowRightBtn->getNormalImage())->setColor(enable ? ccWHITE : ccGRAY); + static_cast(m_bigArrowRightBtn->getNormalImage())->setOpacity(enable ? 255 : 155); + static_cast(m_bigArrowRightBtn->getNormalImage())->setColor(enable ? ccWHITE : ccGRAY); + } } void onCommit() override { this->getSetting()->setValue(this->getCurrentValue()); } void onArrow(CCObject* sender) { - this->setCurrentValue(this->getCurrentValue() + sender->getTag()); + auto value = this->getCurrentValue() + static_cast*>( + static_cast(sender)->getUserObject() + )->getValue(); + if (auto min = this->getSetting()->getMinValue()) { + value = std::max(*min, value); + } + if (auto max = this->getSetting()->getMaxValue()) { + value = std::min(*max, value); + } + this->setCurrentValue(value); } void onSlider(CCObject*) { this->setCurrentValue(this->valueFromSlider(m_slider->m_touchLogic->m_thumb->getValue())); @@ -233,9 +268,17 @@ public: class FileSettingNodeV3 : public SettingNodeV3 { protected: + CCSprite* m_fileIcon; + std::filesystem::path m_path; + CCLabelBMFont* m_nameLabel; + EventListener>> m_pickListener; + bool init(std::shared_ptr setting, float width); + void updateState() override; + void onCommit() override; + void onPickFile(CCObject*); public: static FileSettingNodeV3* create(std::shared_ptr setting, float width); diff --git a/loader/src/loader/SettingV3.cpp b/loader/src/loader/SettingV3.cpp index 6c7390f1..b3b096c5 100644 --- a/loader/src/loader/SettingV3.cpp +++ b/loader/src/loader/SettingV3.cpp @@ -26,6 +26,7 @@ Result<> SettingV3::parseSharedProperties(std::string const& key, std::string co void SettingV3::parseSharedProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value, bool onlyNameAndDesc) { this->init(key, modID); value.needs("type"); + value.has("platforms"); value.has("name").into(m_impl->name); value.has("description").into(m_impl->description); if (!onlyNameAndDesc) { @@ -239,31 +240,39 @@ Result> IntSettingV3::parse(std::string const& key root.has("min").into(ret->m_impl->minValue); root.has("max").into(ret->m_impl->maxValue); if (auto controls = root.has("control")) { + controls.has("arrows"); + controls.has("big-arrows"); controls.has("arrow-step").into(ret->m_impl->controls.arrowStepSize); - if (!controls.has("arrows").template get()) { - ret->m_impl->controls.arrowStepSize = 0; - } controls.has("big-arrow-step").into(ret->m_impl->controls.bigArrowStepSize); - if (!controls.has("big-arrows").template get()) { - ret->m_impl->controls.bigArrowStepSize = 0; - } controls.has("slider").into(ret->m_impl->controls.sliderEnabled); controls.has("slider-step").into(ret->m_impl->controls.sliderSnap); controls.has("input").into(ret->m_impl->controls.textInputEnabled); - // Without "min" or "max" slider makes no sense - if (!ret->m_impl->minValue || !ret->m_impl->maxValue) { - if (ret->m_impl->controls.sliderEnabled) { - log::warn( - "Setting '{}' has \"controls.slider\" enabled but doesn't " - "have both \"min\" and \"max\" defined - the slider has " - "been force-disabled!", - key - ); - } - ret->m_impl->controls.sliderEnabled = false; - } controls.checkUnknownKeys(); } + + // Disable arrows if they aren't enabled + // This silly code is because step size being 0 is what defines if they are enabled + + // Small arrows are enabled by default + if (!root.has("control").has("arrows").template get(true)) { + ret->m_impl->controls.arrowStepSize = 0; + } + if (!root.has("control").has("big-arrows").template get()) { + ret->m_impl->controls.bigArrowStepSize = 0; + } + + // Without "min" or "max" slider makes no sense + if (!ret->m_impl->minValue || !ret->m_impl->maxValue) { + if (ret->m_impl->controls.sliderEnabled && root.has("control").has("slider")) { + log::warn( + "Setting '{}' has \"controls.slider\" enabled but doesn't " + "have both \"min\" and \"max\" defined - the slider has " + "been force-disabled!", + key + ); + } + ret->m_impl->controls.sliderEnabled = false; + } root.checkUnknownKeys(); return root.ok(ret); @@ -279,10 +288,10 @@ int64_t IntSettingV3::getDefaultValue() const { } Result<> IntSettingV3::isValid(int64_t value) const { if (m_impl->minValue && value < *m_impl->minValue) { - return Err("value must be at least {}", *m_impl->minValue); + return Err("Value must be at least {}", *m_impl->minValue); } if (m_impl->maxValue && value > *m_impl->maxValue) { - return Err("value must be at most {}", *m_impl->maxValue); + return Err("Value must be at most {}", *m_impl->maxValue); } return Ok(); } @@ -364,8 +373,8 @@ public: struct { // 0 means not enabled - size_t arrowStepSize = 1; - size_t bigArrowStepSize = 5; + double arrowStepSize = 1; + double bigArrowStepSize = 5; bool sliderEnabled = true; std::optional sliderSnap; bool textInputEnabled = true; @@ -385,32 +394,38 @@ Result> FloatSettingV3::parse(std::string const& root.has("min").into(ret->m_impl->minValue); root.has("max").into(ret->m_impl->maxValue); if (auto controls = root.has("control")) { + controls.has("arrows"); + controls.has("big-arrows"); controls.has("arrow-step").into(ret->m_impl->controls.arrowStepSize); - if (!controls.has("arrows").template get()) { - ret->m_impl->controls.arrowStepSize = 0; - } controls.has("big-arrow-step").into(ret->m_impl->controls.bigArrowStepSize); - if (!controls.has("big-arrows").template get()) { - ret->m_impl->controls.bigArrowStepSize = 0; - } controls.has("slider").into(ret->m_impl->controls.sliderEnabled); controls.has("slider-step").into(ret->m_impl->controls.sliderSnap); controls.has("input").into(ret->m_impl->controls.textInputEnabled); - // Without "min" or "max" slider makes no sense - if (!ret->m_impl->minValue || !ret->m_impl->maxValue) { - if (ret->m_impl->controls.sliderEnabled) { - log::warn( - "Setting '{}' has \"controls.slider\" enabled but doesn't " - "have both \"min\" and \"max\" defined - the slider has " - "been force-disabled!", - key - ); - } - ret->m_impl->controls.sliderEnabled = false; - } controls.checkUnknownKeys(); } + // Disable arrows if they aren't enabled + // Small arrows are enabled by default + if (!root.has("control").has("arrows").template get(true)) { + ret->m_impl->controls.arrowStepSize = 0; + } + if (!root.has("control").has("big-arrows").template get()) { + ret->m_impl->controls.bigArrowStepSize = 0; + } + + // Without "min" or "max" slider makes no sense + if (!ret->m_impl->minValue || !ret->m_impl->maxValue) { + if (ret->m_impl->controls.sliderEnabled && root.has("control").has("slider")) { + log::warn( + "Setting '{}' has \"controls.slider\" enabled but doesn't " + "have both \"min\" and \"max\" defined - the slider has " + "been force-disabled!", + key + ); + } + ret->m_impl->controls.sliderEnabled = false; + } + root.checkUnknownKeys(); return root.ok(ret); } @@ -423,10 +438,10 @@ double FloatSettingV3::getDefaultValue() const { } Result<> FloatSettingV3::isValid(double value) const { if (m_impl->minValue && value < *m_impl->minValue) { - return Err("value must be at least {}", *m_impl->minValue); + return Err("Value must be at least {}", *m_impl->minValue); } if (m_impl->maxValue && value > *m_impl->maxValue) { - return Err("value must be at most {}", *m_impl->maxValue); + return Err("Value must be at most {}", *m_impl->maxValue); } return Ok(); } @@ -444,10 +459,10 @@ bool FloatSettingV3::isArrowsEnabled() const { bool FloatSettingV3::isBigArrowsEnabled() const { return m_impl->controls.bigArrowStepSize > 0; } -size_t FloatSettingV3::getArrowStepSize() const { +double FloatSettingV3::getArrowStepSize() const { return m_impl->controls.arrowStepSize; } -size_t FloatSettingV3::getBigArrowStepSize() const { +double FloatSettingV3::getBigArrowStepSize() const { return m_impl->controls.bigArrowStepSize; } bool FloatSettingV3::isSliderEnabled() const { @@ -487,8 +502,8 @@ std::optional FloatSettingV3::convertToLegacy() const { .controls = { .arrows = this->isArrowsEnabled(), .bigArrows = this->isBigArrowsEnabled(), - .arrowStep = this->getArrowStepSize(), - .bigArrowStep = this->getBigArrowStepSize(), + .arrowStep = static_cast(this->getArrowStepSize()), + .bigArrowStep = static_cast(this->getBigArrowStepSize()), .slider = this->isSliderEnabled(), .sliderStep = this->getSliderSnap(), .input = this->isInputEnabled(), @@ -538,12 +553,12 @@ std::string StringSettingV3::getDefaultValue() const { Result<> StringSettingV3::isValid(std::string_view value) const { if (m_impl->match) { if (!std::regex_match(std::string(value), std::regex(*m_impl->match))) { - return Err("value must match regex {}", *m_impl->match); + return Err("Value must match regex {}", *m_impl->match); } } else if (m_impl->oneOf) { if (!ranges::contains(*m_impl->oneOf, std::string(value))) { - return Err("value must be one of {}", fmt::join(*m_impl->oneOf, ", ")); + return Err("Value must be one of {}", fmt::join(*m_impl->oneOf, ", ")); } } return Ok(); @@ -594,6 +609,7 @@ class FileSettingV3::Impl final { public: std::filesystem::path value; std::filesystem::path defaultValue; + FileType fileType; std::optional> filters; }; @@ -610,19 +626,32 @@ Result> FileSettingV3::parse(std::string const& k // Replace known paths like `{gd-save-dir}/` try { ret->m_impl->defaultValue = fmt::format( - fmt::runtime(ret->m_impl->defaultValue.string()), - fmt::arg("gd-save-dir", dirs::getSaveDir()), - fmt::arg("gd-game-dir", dirs::getGameDir()), - fmt::arg("mod-config-dir", dirs::getModConfigDir() / modID), - fmt::arg("mod-save-dir", dirs::getModsSaveDir() / modID), - fmt::arg("temp-dir", dirs::getTempDir()) + fmt::runtime(ret->m_impl->defaultValue.string()), + fmt::arg("gd_dir", dirs::getGameDir()), + fmt::arg("gd_save_dir", dirs::getSaveDir()), + fmt::arg("mod_config_dir", dirs::getModConfigDir() / modID), + fmt::arg("mod_save_dir", dirs::getModsSaveDir() / modID), + fmt::arg("temp_dir", dirs::getTempDir()) ); } - catch(fmt::format_error const&) { - return Err("Invalid format string for file setting path"); + catch(fmt::format_error const& e) { + return Err("Invalid format string for file setting path: {}", e.what()); } ret->m_impl->value = ret->m_impl->defaultValue; + if (auto ty = root.has("filetype")) { + ty.assertIsString(); + switch (hash(ty.template get())) { + case hash("any"): ret->m_impl->fileType = FileType::Any; break; + case hash("file"): ret->m_impl->fileType = FileType::File; break; + case hash("folder"): ret->m_impl->fileType = FileType::Folder; break; + default: return Err( + "Setting '{}' in mod {}: Invalid filetype \"{}\"", + key, modID, ty.template get() + ); + } + } + if (auto controls = root.has("control")) { auto filters = std::vector(); for (auto& item : controls.has("filters").items()) { @@ -647,9 +676,24 @@ std::filesystem::path FileSettingV3::getDefaultValue() const { return m_impl->defaultValue; } Result<> FileSettingV3::isValid(std::filesystem::path const& value) const { + if (m_impl->fileType != FileType::Any) { + if (!std::filesystem::exists(value)) { + return Err("{} must exist", m_impl->fileType == FileType::File ? "File" : "Folder"); + } + if (m_impl->fileType == FileType::File && !std::filesystem::is_regular_file(value)) { + return Err("Value must be a file"); + } + if (m_impl->fileType == FileType::Folder && !std::filesystem::is_directory(value)) { + return Err("Value must be a folder"); + } + } return Ok(); } +FileSettingV3::FileType FileSettingV3::getFileType() const { + return m_impl->fileType; +} + std::optional> FileSettingV3::getFilters() const { return m_impl->filters; }