diff --git a/loader/resources/mod.json.in b/loader/resources/mod.json.in index 0e7ef08d..f6570260 100644 --- a/loader/resources/mod.json.in +++ b/loader/resources/mod.json.in @@ -85,6 +85,10 @@ "name": "Enable Geode-Themed Colors", "description": "When enabled, the Geode menu has a Geode-themed color scheme. This does not affect any other menus!" }, + "copy-mods": { + "type": "custom:copy-mods", + "name": "" + }, "developer-title": { "type": "title", "name": "Developer Settings" diff --git a/loader/src/loader/CopyButtonSetting.cpp b/loader/src/loader/CopyButtonSetting.cpp new file mode 100644 index 00000000..834ec221 --- /dev/null +++ b/loader/src/loader/CopyButtonSetting.cpp @@ -0,0 +1,44 @@ +#include "CopyButtonSetting.hpp" +#include + +$on_mod(Loaded) { + (void)Mod::get()->registerCustomSettingType("copy-mods", &CopyButtonSetting::parse); +} + +SettingNodeV3* CopyButtonSetting::createNode(float width) { + return CopyButtonSettingNode::create(std::static_pointer_cast(shared_from_this()), width); +} + +void CopyButtonSettingNode::onCopy(CCObject*) { + auto mods = Loader::get()->getAllMods(); + if (mods.empty()) { + Notification::create("No mods installed", NotificationIcon::Info, 0.5f)->show(); + return; + } + + std::sort(mods.begin(), mods.end(), [](Mod* a, Mod* b) { + auto const s1 = a->getID(); + auto const s2 = b->getID(); + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), [](auto a, auto b) { + return std::tolower(a) < std::tolower(b); + }); + }); + + std::stringstream ss; + using namespace std::string_view_literals; + for (int i = 0; i < mods.size(); i++) { + auto& mod = mods[i]; + ss << fmt::format("{} | [{}] {}", + mod->isEnabled() ? "x"sv : + mod->hasProblems() ? "!"sv : + " "sv, + mod->getVersion().toVString(), mod->getID() + ); + if (i != mods.size() - 1) { + ss << "\n"; + } + } + clipboard::write(ss.str()); + + Notification::create("Mods list copied to clipboard", NotificationIcon::Info, 0.5f)->show(); +} diff --git a/loader/src/loader/CopyButtonSetting.hpp b/loader/src/loader/CopyButtonSetting.hpp new file mode 100644 index 00000000..81b5f5b9 --- /dev/null +++ b/loader/src/loader/CopyButtonSetting.hpp @@ -0,0 +1,76 @@ +#include +#include + +using namespace geode::prelude; + +class CopyButtonSetting : public SettingV3 { +public: + static Result> parse(std::string const& key, std::string const& modID, matjson::Value const& json) { + auto res = std::make_shared(); + auto root = checkJson(json, "CopyButtonSetting"); + + res->init(key, modID, root); + res->parseNameAndDescription(root); + + return root.ok(res); + } + + bool load(matjson::Value const& json) override { + return true; + } + bool save(matjson::Value& json) const override { + return true; + } + + bool isDefaultValue() const override { + return true; + } + void reset() override {} + + SettingNodeV3* createNode(float width) override; +}; + +class CopyButtonSettingNode : public SettingNodeV3 { +protected: + bool init(std::shared_ptr setting, float width) { + if (!SettingNodeV3::init(setting, width)) + return false; + + auto buttonSprite = createGeodeButton("Copy Mods"); + buttonSprite->setScale(.5f); + auto button = CCMenuItemSpriteExtra::create( + buttonSprite, this, menu_selector(CopyButtonSettingNode::onCopy) + ); + this->getButtonMenu()->addChildAtPosition(button, Anchor::Center); + this->getButtonMenu()->setPosition(getContentSize() / 2); + this->getButtonMenu()->setAnchorPoint({ .5f, .5f }); + this->getButtonMenu()->updateLayout(); + + this->updateState(nullptr); + + return true; + } + + void onCopy(CCObject*); + + void onCommit() override {} + void onResetToDefault() override {} + +public: + static CopyButtonSettingNode* create(std::shared_ptr setting, float width) { + auto ret = new CopyButtonSettingNode(); + if (ret && ret->init(setting, width)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; + } + + bool hasUncommittedChanges() const override { + return false; + } + bool hasNonDefaultValue() const override { + return false; + } +};