add tags to ModPopup

This commit is contained in:
HJfod 2024-03-24 12:05:50 +02:00
parent c8a603cdbb
commit 1876af8124
7 changed files with 131 additions and 57 deletions

View file

@ -1,5 +1,6 @@
#include "GeodeStyle.hpp"
#include <Geode/utils/cocos.hpp>
#include <Geode/utils/ColorProvider.hpp>
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<ccColor3B, ccColor3B> 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;

View file

@ -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<ccColor3B, ccColor3B> geodeTagColor(std::string_view const& text);
class GeodeTabSprite : public CCNode {
protected:
CCScale9Sprite* m_deselectedBG;

View file

@ -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);

View file

@ -2,6 +2,7 @@
#include <Geode/ui/MDTextArea.hpp>
#include <Geode/utils/web.hpp>
#include <Geode/ui/GeodeUI.hpp>
#include <Geode/utils/ColorProvider.hpp>
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<std::tuple<
const char*, const char*, SEL_MenuHandler
>> {
{ "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::ServerModMetadata, server::
}
}
void ModPopup::onLoadTags(PromiseEvent<std::unordered_set<std::string>, 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) {

View file

@ -17,10 +17,12 @@ protected:
ModSource m_source;
CCNode* m_stats;
CCNode* m_tags;
CCNode* m_rightColumn;
CCNode* m_currentTabPage = nullptr;
std::unordered_map<Tab, std::pair<GeodeTabSprite*, Ref<CCNode>>> m_tabs;
EventListener<PromiseEventFilter<server::ServerModMetadata, server::ServerError>> m_statsListener;
EventListener<PromiseEventFilter<std::unordered_set<std::string>, server::ServerError>> m_tagsListener;
bool setup(ModSource&& src) override;
@ -29,6 +31,7 @@ protected:
void setStatValue(CCNode* stat, std::optional<std::string> const& value);
void onLoadServerInfo(PromiseEvent<server::ServerModMetadata, server::ServerError>* event);
void onLoadTags(PromiseEvent<std::unordered_set<std::string>, server::ServerError>* event);
void loadTab(Tab tab);
void onTab(CCObject* sender);

View file

@ -57,3 +57,28 @@ server::ServerPromise<server::ServerModMetadata> ModSource::fetchServerInfo() co
}
}, m_value);
}
server::ServerPromise<std::unordered_set<std::string>> ModSource::fetchValidTags() const {
return std::visit(makeVisitor {
[](Mod* mod) {
return server::ServerResultCache<&server::getTags>::shared().get()
.then<std::unordered_set<std::string>>([mod](std::unordered_set<std::string> validTags) {
// Filter out invalid tags
auto modTags = mod->getMetadata().getTags();
auto finalTags = std::unordered_set<std::string>();
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<std::unordered_set<std::string>>([&metadata](auto resolve, auto) {
resolve(metadata.tags);
});
}
}, m_value);
}

View file

@ -35,4 +35,5 @@ public:
* for that
*/
server::ServerPromise<server::ServerModMetadata> fetchServerInfo() const;
server::ServerPromise<std::unordered_set<std::string>> fetchValidTags() const;
};