From 1876af81241e3deb9ffab759c213a8472624d2e7 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sun, 24 Mar 2024 12:05:50 +0200 Subject: [PATCH] add tags to ModPopup --- loader/src/ui/mods/GeodeStyle.cpp | 19 ++++ loader/src/ui/mods/GeodeStyle.hpp | 3 + loader/src/ui/mods/list/ModItem.cpp | 13 +-- loader/src/ui/mods/popups/ModPopup.cpp | 124 ++++++++++++++--------- loader/src/ui/mods/popups/ModPopup.hpp | 3 + loader/src/ui/mods/sources/ModSource.cpp | 25 +++++ loader/src/ui/mods/sources/ModSource.hpp | 1 + 7 files changed, 131 insertions(+), 57 deletions(-) diff --git a/loader/src/ui/mods/GeodeStyle.cpp b/loader/src/ui/mods/GeodeStyle.cpp index 4d56a536..74b01722 100644 --- a/loader/src/ui/mods/GeodeStyle.cpp +++ b/loader/src/ui/mods/GeodeStyle.cpp @@ -1,5 +1,6 @@ #include "GeodeStyle.hpp" #include +#include bool GeodeSquareSprite::init(CCSprite* top, bool* state) { if (!CCSprite::initWithFile("GE_button_05.png"_spr)) @@ -74,6 +75,24 @@ CircleButtonSprite* createGeodeCircleButton(const char* topFrameName) { return CircleButtonSprite::createWithSpriteFrameName(topFrameName, 1.f, CircleBaseColor::DarkPurple); } +ButtonSprite* createGeodeTagLabel(std::string const& text, ccColor3B color, ccColor3B bg) { + auto label = ButtonSprite::create(text.c_str(), "bigFont.fnt", "white-square.png"_spr, .8f); + label->m_label->setColor(color); + label->m_BGSprite->setColor(bg); + return label; +} + +std::pair geodeTagColor(std::string_view const& text) { + static std::array TAG_COLORS { + std::make_pair(ccc3(240, 233, 255), ccc3(130, 123, 163)), + std::make_pair(ccc3(234, 255, 245), ccc3(123, 163, 136)), + std::make_pair(ccc3(240, 252, 255), ccc3(123, 152, 163)), + std::make_pair(ccc3(255, 253, 240), ccc3(163, 157, 123)), + std::make_pair(ccc3(255, 242, 240), ccc3(163, 128, 123)), + }; + return TAG_COLORS[hash(text) % 5932 % TAG_COLORS.size()]; +} + bool GeodeTabSprite::init(const char* iconFrame, const char* text, float width) { if (!CCNode::init()) return false; diff --git a/loader/src/ui/mods/GeodeStyle.hpp b/loader/src/ui/mods/GeodeStyle.hpp index 0e0ad8a9..3a11c89f 100644 --- a/loader/src/ui/mods/GeodeStyle.hpp +++ b/loader/src/ui/mods/GeodeStyle.hpp @@ -47,6 +47,9 @@ ButtonSprite* createGeodeButton(std::string const& text, std::string const& bg = CircleButtonSprite* createGeodeCircleButton(const char* topFrameName); +ButtonSprite* createGeodeTagLabel(std::string const& text, ccColor3B color, ccColor3B bg); +std::pair geodeTagColor(std::string_view const& text); + class GeodeTabSprite : public CCNode { protected: CCScale9Sprite* m_deselectedBG; diff --git a/loader/src/ui/mods/list/ModItem.cpp b/loader/src/ui/mods/list/ModItem.cpp index 886a4408..02fd36e6 100644 --- a/loader/src/ui/mods/list/ModItem.cpp +++ b/loader/src/ui/mods/list/ModItem.cpp @@ -72,17 +72,12 @@ bool ModItem::init(ModSource&& source) { ); m_infoContainer->addChild(m_developers); - m_restartRequiredLabel = ButtonSprite::create("Restart Required", "bigFont.fnt", "white-square.png"_spr, .8f); - m_restartRequiredLabel->m_label->setColor( - ColorProvider::get()->define("mod-list-restart-required-label"_spr, ccc3(153, 245, 245)) - ); - m_restartRequiredLabel->m_BGSprite->setColor( + m_restartRequiredLabel = createGeodeTagLabel( + "Restart Required", + ColorProvider::get()->define("mod-list-restart-required-label"_spr, ccc3(153, 245, 245)), ColorProvider::get()->define("mod-list-restart-required-label-bg"_spr, ccc3(123, 156, 163)) ); - m_restartRequiredLabel->setLayoutOptions( - AxisLayoutOptions::create() - ->setMaxScale(.75f) - ); + m_restartRequiredLabel->setLayoutOptions(AxisLayoutOptions::create()->setMaxScale(.75f)); m_infoContainer->addChild(m_restartRequiredLabel); this->addChild(m_infoContainer); diff --git a/loader/src/ui/mods/popups/ModPopup.cpp b/loader/src/ui/mods/popups/ModPopup.cpp index fb1dce37..4794156f 100644 --- a/loader/src/ui/mods/popups/ModPopup.cpp +++ b/loader/src/ui/mods/popups/ModPopup.cpp @@ -2,6 +2,7 @@ #include #include #include +#include bool ModPopup::setup(ModSource&& src) { m_source = std::move(src); @@ -56,10 +57,6 @@ bool ModPopup::setup(ModSource&& src) { idLabel->setOpacity(140); leftColumn->addChild(idLabel); - auto gap = CCNode::create(); - gap->setContentHeight(6); - leftColumn->addChild(gap); - auto statsContainer = CCNode::create(); statsContainer->setContentSize({ leftColumn->getContentWidth(), 80 }); statsContainer->setAnchorPoint({ .5f, .5f }); @@ -146,6 +143,49 @@ bool ModPopup::setup(ModSource&& src) { leftColumn->addChild(statsContainer); + // Tags + + auto tagsContainer = CCNode::create(); + tagsContainer->setContentSize({ leftColumn->getContentWidth(), 37.5f }); + tagsContainer->setAnchorPoint({ .5f, .5f }); + + auto tagsBG = CCScale9Sprite::create("square02b_001.png"); + tagsBG->setColor({ 0, 0, 0 }); + tagsBG->setOpacity(75); + tagsBG->setScale(.3f); + tagsBG->setContentSize(tagsContainer->getContentSize() / tagsBG->getScale()); + tagsContainer->addChildAtPosition(tagsBG, Anchor::Center); + + m_tags = CCNode::create(); + m_tags->ignoreAnchorPointForPosition(false); + m_tags->setContentSize(tagsContainer->getContentSize() - ccp(10, 10)); + m_tags->setAnchorPoint({ .5f, .5f }); + + // todo: refactor these spinners into a reusable class that's not the ass LoadingCircle is + auto tagsSpinnerContainer = CCNode::create(); + tagsSpinnerContainer->setContentSize({ 50, 50 }); + tagsSpinnerContainer->setID("loading-spinner"); + + auto tagsSpinner = CCSprite::create("loadingCircle.png"); + tagsSpinner->setBlendFunc({ GL_ONE, GL_ONE }); + tagsSpinner->runAction(CCRepeatForever::create(CCRotateBy::create(1.f, 360.f))); + limitNodeSize(tagsSpinner, tagsSpinnerContainer->getContentSize(), 1.f, .1f); + tagsSpinnerContainer->addChildAtPosition(tagsSpinner, Anchor::Center); + + m_tags->addChild(tagsSpinnerContainer); + + m_tags->setLayout( + RowLayout::create() + ->setDefaultScaleLimits(.1f, .3f) + ->setGrowCrossAxis(true) + ->setCrossAxisOverflow(false) + ->setAxisAlignment(AxisAlignment::Start) + ->setGap(2) + ); + tagsContainer->addChildAtPosition(m_tags, Anchor::Center); + + leftColumn->addChild(tagsContainer); + // Installing auto installContainer = CCNode::create(); @@ -190,50 +230,6 @@ bool ModPopup::setup(ModSource&& src) { leftColumn->addChild(installContainer); - // Options - - auto optionsContainer = CCNode::create(); - optionsContainer->setContentSize({ leftColumn->getContentWidth(), 25 }); - optionsContainer->setAnchorPoint({ .5f, .5f }); - - auto optionsBG = CCScale9Sprite::create("square02b_001.png"); - optionsBG->setColor({ 0, 0, 0 }); - optionsBG->setOpacity(75); - optionsBG->setScale(.3f); - optionsBG->setContentSize(optionsContainer->getContentSize() / optionsBG->getScale()); - optionsContainer->addChildAtPosition(optionsBG, Anchor::Center); - - auto optionsMenu = CCMenu::create(); - optionsMenu->ignoreAnchorPointForPosition(false); - optionsMenu->setContentSize(optionsContainer->getContentSize() - ccp(10, 10)); - optionsMenu->setAnchorPoint({ .5f, .5f }); - - for (auto stat : std::initializer_list> { - { "folderIcon_001.png", "Config", nullptr }, - { "folderIcon_001.png", "Save Data", nullptr }, - }) { - auto spr = createGeodeButton( - CCSprite::createWithSpriteFrameName(std::get<0>(stat)), - std::get<1>(stat) - ); - spr->setScale(.5f); - auto btn = CCMenuItemSpriteExtra::create( - spr, this, std::get<2>(stat) - ); - optionsMenu->addChild(btn); - } - - optionsMenu->setLayout( - RowLayout::create() - ->setDefaultScaleLimits(.1f, 1) - ->setAxisAlignment(AxisAlignment::Center) - ); - optionsContainer->addChildAtPosition(optionsMenu, Anchor::Center); - - leftColumn->addChild(optionsContainer); - // Links auto linksContainer = CCNode::create(); @@ -361,6 +357,8 @@ bool ModPopup::setup(ModSource&& src) { // Load stats from server (or just from the source if it already has them) m_statsListener.bind(this, &ModPopup::onLoadServerInfo); m_statsListener.setFilter(m_source.fetchServerInfo().listen()); + m_tagsListener.bind(this, &ModPopup::onLoadTags); + m_tagsListener.setFilter(m_source.fetchValidTags().listen()); return true; } @@ -457,6 +455,36 @@ void ModPopup::onLoadServerInfo(PromiseEvent, server::ServerError>* event) { + if (auto data = event->getResolve()) { + m_tags->removeAllChildren(); + + for (auto& tag : *data) { + auto readable = tag; + readable[0] = std::toupper(readable[0]); + auto colors = geodeTagColor(tag); + m_tags->addChild(createGeodeTagLabel(readable, colors.first, colors.second)); + } + + if (data->empty()) { + auto label = CCLabelBMFont::create("No tags found", "bigFont.fnt"); + label->setOpacity(120); + m_tags->addChild(label); + } + + m_tags->updateLayout(); + } + else if (auto err = event->getReject()) { + m_tags->removeAllChildren(); + + auto label = CCLabelBMFont::create("No tags found", "bigFont.fnt"); + label->setOpacity(120); + m_tags->addChild(label); + + m_tags->updateLayout(); + } +} + void ModPopup::loadTab(ModPopup::Tab tab) { // Remove current page if (m_currentTabPage) { diff --git a/loader/src/ui/mods/popups/ModPopup.hpp b/loader/src/ui/mods/popups/ModPopup.hpp index 1468f6cd..12620ada 100644 --- a/loader/src/ui/mods/popups/ModPopup.hpp +++ b/loader/src/ui/mods/popups/ModPopup.hpp @@ -17,10 +17,12 @@ protected: ModSource m_source; CCNode* m_stats; + CCNode* m_tags; CCNode* m_rightColumn; CCNode* m_currentTabPage = nullptr; std::unordered_map>> m_tabs; EventListener> m_statsListener; + EventListener, server::ServerError>> m_tagsListener; bool setup(ModSource&& src) override; @@ -29,6 +31,7 @@ protected: void setStatValue(CCNode* stat, std::optional const& value); void onLoadServerInfo(PromiseEvent* event); + void onLoadTags(PromiseEvent, server::ServerError>* event); void loadTab(Tab tab); void onTab(CCObject* sender); diff --git a/loader/src/ui/mods/sources/ModSource.cpp b/loader/src/ui/mods/sources/ModSource.cpp index a316d51e..108e020a 100644 --- a/loader/src/ui/mods/sources/ModSource.cpp +++ b/loader/src/ui/mods/sources/ModSource.cpp @@ -57,3 +57,28 @@ server::ServerPromise ModSource::fetchServerInfo() co } }, m_value); } + +server::ServerPromise> ModSource::fetchValidTags() const { + return std::visit(makeVisitor { + [](Mod* mod) { + return server::ServerResultCache<&server::getTags>::shared().get() + .then>([mod](std::unordered_set validTags) { + // Filter out invalid tags + auto modTags = mod->getMetadata().getTags(); + auto finalTags = std::unordered_set(); + for (auto& tag : modTags) { + if (validTags.contains(tag)) { + finalTags.insert(tag); + } + } + return finalTags; + }); + }, + [](server::ServerModMetadata const& metadata) { + // Server info tags are always certain to be valid since the server has already validated them + return server::ServerPromise>([&metadata](auto resolve, auto) { + resolve(metadata.tags); + }); + } + }, m_value); +} diff --git a/loader/src/ui/mods/sources/ModSource.hpp b/loader/src/ui/mods/sources/ModSource.hpp index 47bb1787..d57de601 100644 --- a/loader/src/ui/mods/sources/ModSource.hpp +++ b/loader/src/ui/mods/sources/ModSource.hpp @@ -35,4 +35,5 @@ public: * for that */ server::ServerPromise fetchServerInfo() const; + server::ServerPromise> fetchValidTags() const; };