Compare commits

...

6 commits

Author SHA1 Message Date
TheSillyDoggo
c06c5aff25
Merge bd6bfb661f into 7bcf50da57 2024-11-18 11:36:49 -08:00
HJfod
7bcf50da57 grid view in mod list; some minor stuff left to do
Some checks failed
Build Binaries / Build Windows (push) Has been cancelled
Build Binaries / Build macOS (push) Has been cancelled
Build Binaries / Build Android (64-bit) (push) Has been cancelled
Build Binaries / Build Android (32-bit) (push) Has been cancelled
Build Binaries / Publish (push) Has been cancelled
2024-11-17 23:35:39 +02:00
matcool
38f3385c90 add safe mode tip text to windows crashlog window 2024-11-17 15:14:34 -03:00
dankmeme01
4d5e465ade fix utils::string::replaceIP hanging if filter is empty 2024-11-17 18:13:04 +01:00
Explodingbill
bd6bfb661f asdf 2024-11-16 15:03:10 +11:00
Explodingbill
03e8220dfe Add OverlayManager 2024-11-16 13:48:59 +11:00
13 changed files with 475 additions and 143 deletions

View file

@ -0,0 +1,41 @@
#pragma once
#include "../DefaultInclude.hpp"
#include <cocos2d.h>
#include <vector>
#include <span>
#include <Geode/utils/cocos.hpp>
namespace geode
{
/*
Because cocos only allows for one notification node (a node drawn last, above the fps counter and everything),
I added this, a simple class to add nodes to a general notification node so that mods dont interfere with each other.
*/
class GEODE_DLL OverlayManager : private cocos2d::CCNode
{
private:
std::vector<cocos2d::CCNode*> nodes;
public:
/// @brief Get the overlay manager instance, and if it doesnt exist, sets the notification node to it
static OverlayManager* get();
/// @brief Adds a node to the overlay manager, overlays are sorted by ZOrder, the higher the order is the later it draws. This will retain the node
void addNode(cocos2d::CCNode* node);
/// @brief Removes a node from the overlay manager, stopping it from being drawn. This will release the node
void removeNode(cocos2d::CCNode* node);
/// @brief Util to get the highest ZOrder of all nodes
int getHighestOverlayZOrder();
/// @brief Util to get the lowest ZOrder of all nodes
int getLowestOverlayZOrder();
/// @brief Gets all the overlays
std::vector<cocos2d::CCNode*> getOverlays();
};
};

View file

@ -71,6 +71,7 @@ namespace geode::utils {
std::string m_msg;
Timer<Clock> m_timer;
// @geode-ignore(geode-alternative)
LogPerformance(std::string const& msg = "", std::ostream& out = std::cout) :
m_msg(msg), m_output(out) {
m_timer = Timer<Clock>();

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -10,11 +10,13 @@
#include <unistd.h>
void Loader::Impl::platformMessageBox(char const* title, std::string const& info) {
// @geode-ignore(geode-alternative)
std::cout << title << ": " << info << std::endl;
}
void Loader::Impl::logConsoleMessageWithSeverity(std::string const& msg, Severity severity) {
if (m_platformConsoleOpen) {
// @geode-ignore(geode-alternative)
std::cout << msg << "\n" << std::flush;
}
}

View file

@ -18,6 +18,7 @@ enum {
ID_BUTTON_OPEN_FOLDER = 103,
ID_BUTTON_COPY_CLIPBOARD = 104,
ID_BUTTON_RESTART_GAME = 105,
ID_SAFE_MODE_TIP_TEXT = 106,
};
#define TO_HMENU(x) reinterpret_cast<HMENU>(static_cast<size_t>(x))
@ -63,6 +64,25 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
DEFAULT_PITCH | FF_DONTCARE, TEXT("Consolas"));
auto guiFont = static_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
auto calculateTextSize = [&](std::string_view text) -> SIZE {
HDC hdc = GetDC(hwnd);
SelectObject(hdc, monoFont);
SIZE size;
GetTextExtentPoint32A(hdc, text.data(), text.size(), &size);
ReleaseDC(hwnd, hdc);
return size;
};
auto tipTextStr = "Tip: You can hold shift while launching the game to enter safe mode.";
auto tipTextSize = calculateTextSize(tipTextStr);
auto tipText = CreateWindowA(
"STATIC", tipTextStr,
WS_CHILD | WS_VISIBLE | SS_SIMPLE,
0, 0, tipTextSize.cx, tipTextSize.cy,
hwnd, TO_HMENU(ID_SAFE_MODE_TIP_TEXT), NULL, NULL
);
SendMessage(tipText, WM_SETFONT, WPARAM(monoFont), TRUE);
auto handleText = CreateWindowA(
"EDIT", "Crashlog text goes here", WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_LEFT | ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL | WS_BORDER,
0, 0, 100, 100,
@ -109,10 +129,20 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
RECT clientRect;
GetClientRect(hwnd, &clientRect);
RECT textRect;
GetClientRect(GetDlgItem(hwnd, ID_SAFE_MODE_TIP_TEXT), &textRect);
SetWindowPos(
GetDlgItem(hwnd, ID_SAFE_MODE_TIP_TEXT), NULL,
layout::PADDING, layout::PADDING,
0, 0,
SWP_NOZORDER | SWP_NOSIZE
);
SetWindowPos(
GetDlgItem(hwnd, ID_CRASHLOG_TEXT), NULL,
layout::PADDING, layout::PADDING,
clientRect.right - layout::PADDING * 2, clientRect.bottom - layout::BUTTON_HEIGHT - layout::PADDING * 3,
layout::PADDING, layout::PADDING * 2 + textRect.bottom,
clientRect.right - layout::PADDING * 2, clientRect.bottom - layout::BUTTON_HEIGHT - layout::PADDING * 4 - textRect.bottom,
SWP_NOZORDER
);
@ -150,6 +180,9 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
} break;
case WM_CTLCOLORSTATIC: {
auto hdc = (HDC)wParam;
// make every text have transparent background
SetBkMode(hdc, TRANSPARENT);
return (LRESULT)(COLOR_WINDOWFRAME);
} break;

View file

@ -499,28 +499,48 @@ bool ModsLayer::init() {
// Actions
auto listActionsMenu = CCMenu::create();
listActionsMenu->setID("list-actions-menu");
listActionsMenu->setContentHeight(100);
listActionsMenu->setAnchorPoint({ 1, 0 });
listActionsMenu->setScale(.65f);
auto listDisplayMenu = CCMenu::create();
listDisplayMenu->setID("list-actions-menu");
listDisplayMenu->setContentHeight(100);
listDisplayMenu->setAnchorPoint({ 1, 0 });
listDisplayMenu->setScale(.65f);
auto smallSizeBtn = CCMenuItemSpriteExtra::create(
GeodeSquareSprite::createWithSpriteFrameName("GJ_smallModeIcon_001.png"),
this, menu_selector(ModsLayer::onDisplay)
);
smallSizeBtn->setTag(static_cast<int>(ModListDisplay::SmallList));
smallSizeBtn->setID("list-normal-size-button");
listDisplayMenu->addChild(smallSizeBtn);
m_displayBtns.push_back(smallSizeBtn);
auto bigSizeBtn = CCMenuItemSpriteExtra::create(
GeodeSquareSprite::createWithSpriteFrameName("GJ_smallModeIcon_001.png", &m_bigView),
this, menu_selector(ModsLayer::onBigView)
GeodeSquareSprite::createWithSpriteFrameName("GJ_extendedIcon_001.png"),
this, menu_selector(ModsLayer::onDisplay)
);
bigSizeBtn->setTag(static_cast<int>(ModListDisplay::BigList));
bigSizeBtn->setID("list-size-button");
listActionsMenu->addChild(bigSizeBtn);
listDisplayMenu->addChild(bigSizeBtn);
m_displayBtns.push_back(bigSizeBtn);
auto gridBtn = CCMenuItemSpriteExtra::create(
GeodeSquareSprite::createWithSpriteFrameName("grid-view.png"_spr),
this, menu_selector(ModsLayer::onDisplay)
);
gridBtn->setTag(static_cast<int>(ModListDisplay::Grid));
gridBtn->setID("list-size-button");
listDisplayMenu->addChild(gridBtn);
m_displayBtns.push_back(gridBtn);
// auto searchBtn = CCMenuItemSpriteExtra::create(
// GeodeSquareSprite::createWithSpriteFrameName("search.png"_spr, &m_showSearch),
// this, menu_selector(ModsLayer::onSearch)
// );
// searchBtn->setID("search-button");
// listActionsMenu->addChild(searchBtn);
// listDisplayMenu->addChild(searchBtn);
listActionsMenu->setLayout(ColumnLayout::create());
m_frame->addChildAtPosition(listActionsMenu, Anchor::Left, ccp(-5, 25));
listDisplayMenu->setLayout(ColumnLayout::create()->setAxisReverse(true));
m_frame->addChildAtPosition(listDisplayMenu, Anchor::Left, ccp(-5, 25));
m_statusNode = ModsStatusNode::create();
m_statusNode->setZOrder(4);
@ -629,7 +649,7 @@ void ModsLayer::gotoTab(ModListSource* src) {
m_currentSource = src;
// Update the state of the current list
m_lists.at(m_currentSource)->updateSize(m_bigView);
m_lists.at(m_currentSource)->updateDisplay(m_display);
m_lists.at(m_currentSource)->activateSearch(m_showSearch);
m_lists.at(m_currentSource)->updateState();
}
@ -687,6 +707,13 @@ void ModsLayer::updateState() {
else {
m_pageMenu->setVisible(false);
}
// Update display button
for (auto btn : m_displayBtns) {
static_cast<GeodeSquareSprite*>(btn->getNormalImage())->setState(
static_cast<ModListDisplay>(btn->getTag()) == m_display
);
}
}
void ModsLayer::onTab(CCObject* sender) {
@ -716,12 +743,13 @@ void ModsLayer::onGoToPage(CCObject*) {
popup->setID("go-to-page"_spr);
popup->show();
}
void ModsLayer::onBigView(CCObject*) {
m_bigView = !m_bigView;
void ModsLayer::onDisplay(CCObject* sender) {
m_display = static_cast<ModListDisplay>(sender->getTag());
// Make sure to avoid a crash
if (m_currentSource) {
m_lists.at(m_currentSource)->updateSize(m_bigView);
m_lists.at(m_currentSource)->updateDisplay(m_display);
}
this->updateState();
}
void ModsLayer::onSearch(CCObject*) {
m_showSearch = !m_showSearch;

View file

@ -66,7 +66,8 @@ protected:
ModsStatusNode* m_statusNode;
EventListener<UpdateModListStateFilter> m_updateStateListener;
bool m_showSearch = true;
bool m_bigView = false;
ModListDisplay m_display = ModListDisplay::SmallList;
std::vector<CCMenuItemSpriteExtra*> m_displayBtns;
bool init();
@ -77,7 +78,7 @@ protected:
void onTab(CCObject* sender);
void onOpenModsFolder(CCObject*);
void onAddModFromFile(CCObject*);
void onBigView(CCObject*);
void onDisplay(CCObject*);
void onSearch(CCObject*);
void onGoToPage(CCObject*);
void onRefreshList(CCObject*);

View file

@ -29,7 +29,7 @@ bool ModItem::init(ModSource&& source) {
m_bg->ignoreAnchorPointForPosition(false);
m_bg->setAnchorPoint({ .5f, .5f });
m_bg->setScale(.7f);
this->addChild(m_bg);
this->addChildAtPosition(m_bg, Anchor::Center);
m_logo = m_source.createModLogo();
m_logo->setID("logo-sprite");
@ -39,23 +39,10 @@ bool ModItem::init(ModSource&& source) {
m_infoContainer->setID("info-container");
m_infoContainer->setScale(.4f);
m_infoContainer->setAnchorPoint({ .0f, .5f });
m_infoContainer->setLayout(
ColumnLayout::create()
->setAxisReverse(true)
->setAxisAlignment(AxisAlignment::Even)
->setCrossAxisLineAlignment(AxisAlignment::Start)
->setGap(0)
);
m_infoContainer->getLayout()->ignoreInvisibleChildren(true);
m_titleContainer = CCNode::create();
m_titleContainer->setID("title-container");
m_titleContainer->setAnchorPoint({ .0f, .5f });
m_titleContainer->setLayout(
RowLayout::create()
->setDefaultScaleLimits(.1f, 1.f)
->setAxisAlignment(AxisAlignment::Start)
);
m_titleLabel = CCLabelBMFont::create(m_source.getMetadata().getName().c_str(), "bigFont.fnt");
m_titleLabel->setID("title-label");
@ -67,14 +54,20 @@ bool ModItem::init(ModSource&& source) {
m_versionLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(std::nullopt, .7f));
m_titleContainer->addChild(m_versionLabel);
m_infoContainer->addChild(m_titleContainer);
m_titleContainer->setLayout(
RowLayout::create()
->setDefaultScaleLimits(.1f, 1.f)
->setAxisAlignment(AxisAlignment::Start)
);
m_titleContainer->getLayout()->ignoreInvisibleChildren(true);
m_infoContainer->addChildAtPosition(m_titleContainer, Anchor::Left);
m_developers = CCMenu::create();
m_developers->setID("developers-menu");
m_developers->ignoreAnchorPointForPosition(false);
m_developers->setAnchorPoint({ .0f, .5f });
auto by = "By " + m_source.formatDevelopers();
auto by = m_source.formatDevelopers();
m_developerLabel = CCLabelBMFont::create(by.c_str(), "goldFont.fnt");
m_developerLabel->setID("developers-label");
auto developersBtn = CCMenuItemSpriteExtra::create(
@ -87,7 +80,24 @@ bool ModItem::init(ModSource&& source) {
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start)
);
m_infoContainer->addChild(m_developers);
m_infoContainer->addChildAtPosition(m_developers, Anchor::Left);
m_description = CCScale9Sprite::create("square02b_001.png");
m_description->setScale(.5f);
m_description->setContentSize(ccp(450, 30) / m_description->getScale());
m_description->setColor(ccBLACK);
m_description->setOpacity(90);
auto desc = m_source.getMetadata().getDescription();
auto descLabel = CCLabelBMFont::create(
desc.value_or("[No Description Provided]").c_str(),
"chatFont.fnt"
);
descLabel->setColor(desc ? ccWHITE : ccGRAY);
limitNodeWidth(descLabel, m_description->getContentWidth() - 20, 2.f, .1f);
m_description->addChildAtPosition(descLabel, Anchor::Left, ccp(10, 0), ccp(0, .5f));
m_infoContainer->addChildAtPosition(m_description, Anchor::Left);
m_restartRequiredLabel = createTagLabel(
"Restart Required",
@ -97,19 +107,19 @@ bool ModItem::init(ModSource&& source) {
}
);
m_restartRequiredLabel->setID("restart-required-label");
m_restartRequiredLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(std::nullopt, .75f));
m_infoContainer->addChild(m_restartRequiredLabel);
m_restartRequiredLabel->setScale(.75f);
m_infoContainer->addChildAtPosition(m_restartRequiredLabel, Anchor::Left);
m_outdatedLabel = createTagLabel(
fmt::format("Outdated (GD {})", m_source.getMetadata().getGameVersion().value_or("*")),
"Outdated",
{
to3B(ColorProvider::get()->color("mod-list-outdated-label"_spr)),
to3B(ColorProvider::get()->color("mod-list-outdated-label-bg"_spr))
}
);
m_outdatedLabel->setID("outdated-label");
m_outdatedLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(std::nullopt, .75f));
m_infoContainer->addChild(m_outdatedLabel);
m_outdatedLabel->setScale(.75f);
m_infoContainer->addChildAtPosition(m_outdatedLabel, Anchor::Left);
m_downloadBarContainer = CCNode::create();
m_downloadBarContainer->setID("download-bar-container");
@ -121,7 +131,7 @@ bool ModItem::init(ModSource&& source) {
m_downloadBar->setScale(1.5f);
m_downloadBarContainer->addChildAtPosition(m_downloadBar, Anchor::Center, ccp(0, 0), ccp(0, 0));
m_infoContainer->addChild(m_downloadBarContainer);
m_infoContainer->addChildAtPosition(m_downloadBarContainer, Anchor::Left);
m_downloadWaiting = CCNode::create();
m_downloadWaiting->setID("download-waiting-container");
@ -141,13 +151,12 @@ bool ModItem::init(ModSource&& source) {
ccp(m_downloadWaiting->getContentHeight() / 2, 0)
);
m_infoContainer->addChild(m_downloadWaiting);
m_infoContainer->addChildAtPosition(m_downloadWaiting, Anchor::Left);
this->addChild(m_infoContainer);
this->addChildAtPosition(m_infoContainer, Anchor::Left);
m_viewMenu = CCMenu::create();
m_viewMenu->setID("view-menu");
m_viewMenu->setAnchorPoint({ 1.f, .5f });
m_viewMenu->setScale(.55f);
ButtonSprite* spr = nullptr;
@ -214,43 +223,38 @@ bool ModItem::init(ModSource&& source) {
}
},
[this](server::ServerModMetadata const& metadata) {
m_badgeContainer = CCNode::create();
m_badgeContainer->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .8f));
if (metadata.featured) {
auto star = CCSprite::createWithSpriteFrameName("tag-featured.png"_spr);
star->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .8f));
m_titleContainer->addChild(star);
m_badgeContainer->addChild(CCSprite::createWithSpriteFrameName("tag-featured.png"_spr));
}
if (metadata.tags.contains("paid")) {
auto paidModLabel = CCSprite::createWithSpriteFrameName("tag-paid.png"_spr);
paidModLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .8f));
m_titleContainer->addChild(paidModLabel);
m_badgeContainer->addChild(CCSprite::createWithSpriteFrameName("tag-paid.png"_spr));
}
if (metadata.tags.contains("modtober24")) {
auto modtoberLabel = CCSprite::createWithSpriteFrameName("tag-modtober.png"_spr);
modtoberLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .8f));
m_titleContainer->addChild(modtoberLabel);
m_badgeContainer->addChild(CCSprite::createWithSpriteFrameName("tag-modtober.png"_spr));
}
// Show mod download count here already so people can make informed decisions
// on which mods to install
auto downloadsContainer = CCNode::create();
m_downloadCountContainer = CCNode::create();
auto downloads = CCLabelBMFont::create(numToAbbreviatedString(metadata.downloadCount).c_str(), "bigFont.fnt");
downloads->setID("downloads-label");
downloads->setColor("mod-list-version-label"_cc3b);
downloads->limitLabelWidth(80, .5f, .1f);
downloadsContainer->addChildAtPosition(downloads, Anchor::Right, ccp(-0, 0), ccp(1, .5f));
m_downloadCountContainer->addChildAtPosition(downloads, Anchor::Right, ccp(-0, 0), ccp(1, .5f));
auto downloadsIcon = CCSprite::createWithSpriteFrameName("GJ_downloadsIcon_001.png");
downloadsIcon->setID("downloads-icon-sprite");
downloadsIcon->setScale(.75f);
downloadsContainer->addChildAtPosition(downloadsIcon, Anchor::Right, ccp(-downloads->getScaledContentWidth() - 10, 0));
m_downloadCountContainer->addChildAtPosition(downloadsIcon, Anchor::Left, ccp(5, 0));
downloadsContainer->setContentSize({
downloads->getScaledContentWidth() + 10 + downloadsIcon->getScaledContentWidth() + 10,
m_downloadCountContainer->setContentSize({
downloads->getScaledContentWidth() + downloadsIcon->getScaledContentWidth(),
25
});
downloadsContainer->updateLayout();
m_viewMenu->addChild(downloadsContainer);
m_downloadCountContainer->updateLayout();
// Check if mod is recommended by any others, only if not installed
if (!Loader::get()->isModInstalled(metadata.id)) {
@ -289,7 +293,7 @@ bool ModItem::init(ModSource&& source) {
->setDefaultScaleLimits(.1f, 1.f)
->setAxisAlignment(AxisAlignment::Start)
);
m_infoContainer->addChild(m_recommendedBy);
m_infoContainer->addChildAtPosition(m_recommendedBy, Anchor::Left);
}
}
}
@ -329,15 +333,89 @@ bool ModItem::init(ModSource&& source) {
void ModItem::updateState() {
auto wantsRestart = m_source.wantsRestart();
auto download = server::ModDownloadManager::get()->getDownload(m_source.getID());
bool isDownloading = download && download->isActive();
// Update the size of the mod cell itself
if (m_display == ModListDisplay::Grid) {
auto widthWithoutGaps = m_targetWidth - 10;
this->setContentSize(ccp(widthWithoutGaps / roundf(widthWithoutGaps / 80), 100));
m_bg->setContentSize(m_obContentSize / m_bg->getScale());
}
else {
this->setContentSize(ccp(m_targetWidth, m_display == ModListDisplay::BigList ? 40 : 30));
m_bg->setContentSize((m_obContentSize - ccp(6, 0)) / m_bg->getScale());
}
// On Grid layout the title is a direct child of info so it can be positioned
// more cleanly, while m_titleContainer is just used to position the version
// and downloads next to each other
m_titleLabel->removeFromParent();
if (m_display == ModListDisplay::Grid) {
m_infoContainer->addChildAtPosition(m_titleLabel, Anchor::Top);
}
else {
m_titleContainer->insertBefore(m_titleLabel, nullptr);
}
// Download counts go next to the version like on the website on grid view
if (m_downloadCountContainer) {
m_downloadCountContainer->removeFromParent();
if (m_display == ModListDisplay::Grid) {
m_titleContainer->insertAfter(m_downloadCountContainer, m_versionLabel);
}
else {
m_viewMenu->addChild(m_downloadCountContainer);
}
}
// Move badges to either be next to the title or in the top left corner in grid view
if (m_badgeContainer) {
m_badgeContainer->removeFromParent();
if (m_display == ModListDisplay::Grid) {
m_badgeContainer->setLayout(ColumnLayout::create()->setAutoGrowAxis(true));
m_badgeContainer->setScale(.25f);
this->addChildAtPosition(m_badgeContainer, Anchor::TopLeft, ccp(5, -5), ccp(0, 1));
}
else {
m_badgeContainer->setLayout(RowLayout::create()->setAutoGrowAxis(true));
m_titleContainer->addChild(m_badgeContainer);
}
}
// On Grid View logo has constant size
if (m_display == ModListDisplay::Grid) {
limitNodeSize(m_logo, ccp(30, 30), 999, .1f);
m_logo->setPosition(m_obContentSize.width / 2, m_obContentSize.height - 20);
}
else {
auto logoSize = m_obContentSize.height - 10;
limitNodeSize(m_logo, ccp(logoSize, logoSize), 999, .1f);
m_logo->setPosition(m_obContentSize.height / 2 + 5, m_obContentSize.height / 2);
}
// There's space to show the description only on the big list
// When we do, elements like the download progress bar should replace it
// over the developer name since it's less important
// Couldn't figure out a more concise name
m_description->setVisible(m_display == ModListDisplay::BigList);
m_developers->setVisible(true);
auto elementToReplaceWithOtherAbnormalElement =
m_display == ModListDisplay::BigList ? m_description : m_developers;
auto titleSpace = m_display == ModListDisplay::Grid ?
CCSize(m_obContentSize.width - 10, 35) :
CCSize(m_obContentSize.width / 2 - m_obContentSize.height, m_obContentSize.height - 5);
// Divide by scale of info container since that actually determines the size
// (Since the scale of m_titleContainer and m_developers is managed by its layout)
// If there is an active download ongoing, show that in place of developer name
// (or description on big view)
if (isDownloading) {
m_updateBtn->setVisible(false);
m_restartRequiredLabel->setVisible(false);
m_developers->setVisible(false);
elementToReplaceWithOtherAbnormalElement->setVisible(false);
auto status = download->getStatus();
if (auto prog = std::get_if<server::DownloadStatusDownloading>(&status)) {
@ -355,7 +433,7 @@ void ModItem::updateState() {
// Otherwise show "Restart Required" button if needed in place of dev name
else {
m_restartRequiredLabel->setVisible(wantsRestart);
m_developers->setVisible(!wantsRestart);
elementToReplaceWithOtherAbnormalElement->setVisible(!wantsRestart);
m_downloadBarContainer->setVisible(false);
m_downloadWaiting->setVisible(false);
}
@ -418,11 +496,10 @@ void ModItem::updateState() {
m_versionLabel->setColor(to3B(ColorProvider::get()->color("mod-list-version-label"_spr)));
}
m_viewMenu->updateLayout();
m_titleContainer->updateLayout();
// Hide by default
m_outdatedLabel->setVisible(false);
// If there were problems, tint the BG red
m_outdatedLabel->setVisible(false);
if (m_source.asMod()) {
std::optional<LoadProblem> targetsOutdated = m_source.asMod()->targetsOutdatedVersion();
if (m_source.asMod()->hasLoadProblems()) {
@ -430,32 +507,129 @@ void ModItem::updateState() {
m_bg->setOpacity(isGeodeTheme() ? 25 : 90);
}
if (!wantsRestart && targetsOutdated && !isDownloading) {
LoadProblem problem = targetsOutdated.value();
m_bg->setColor("mod-list-outdated-label"_cc3b);
m_bg->setOpacity(isGeodeTheme() ? 25 : 90);
std::string content;
if (
problem.type == LoadProblem::Type::UnsupportedGeodeVersion ||
problem.type == LoadProblem::Type::NeedsNewerGeodeVersion
) {
content = fmt::format(
m_outdatedLabel->setVisible(true);
elementToReplaceWithOtherAbnormalElement->setVisible(false);
if (m_display == ModListDisplay::Grid) {
m_outdatedLabel->setString("Outdated");
}
else {
m_outdatedLabel->setString((
(targetsOutdated->type == LoadProblem::Type::UnsupportedGeodeVersion ||
targetsOutdated->type == LoadProblem::Type::NeedsNewerGeodeVersion) ?
fmt::format(
"Outdated (Geode {})",
m_source.getMetadata().getGeodeVersion().toNonVString()
);
} else {
content = fmt::format(
) :
fmt::format(
"Outdated (GD {})",
m_source.getMetadata().getGameVersion().value_or("*")
);
)
).c_str());
}
m_outdatedLabel->setString(content.c_str());
m_outdatedLabel->setVisible(true);
m_developers->setVisible(false);
}
}
// Update size and direction of title
// On grid view, m_titleContainer contains the version and download count
// but not the actual title lol
m_titleContainer->setContentWidth(titleSpace.width / m_infoContainer->getScale());
if (m_display == ModListDisplay::Grid) {
static_cast<RowLayout*>(m_titleContainer->getLayout())
->setGap(10)
->setAxisAlignment(AxisAlignment::Center);
static_cast<RowLayout*>(m_developers->getLayout())
->setAxisAlignment(AxisAlignment::Center);
}
else {
static_cast<RowLayout*>(m_titleContainer->getLayout())
->setGap(5)
->setAxisAlignment(AxisAlignment::Start);
static_cast<RowLayout*>(m_developers->getLayout())
->setAxisAlignment(AxisAlignment::Start);
}
m_titleContainer->updateLayout();
m_developers->setContentWidth(titleSpace.width / m_infoContainer->getScale());
m_developers->updateLayout();
if (m_recommendedBy) {
m_recommendedBy->setContentWidth(titleSpace.width / m_infoContainer->getScale());
m_recommendedBy->updateLayout();
}
// Update positioning (jesus)
switch (m_display) {
case ModListDisplay::Grid: {
m_infoContainer->updateAnchoredPosition(Anchor::Center, ccp(0, -5), ccp(.5f, .5f));
// m_description is hidden
m_titleLabel->updateAnchoredPosition(Anchor::Top, ccp(0, -10), ccp(.5f, .5f));
limitNodeWidth(m_titleLabel, m_titleContainer->getContentWidth(), .8f, .1f);
m_titleContainer->updateAnchoredPosition(Anchor::Center, ccp(0, 0), ccp(.5f, .5f));
m_developers->updateAnchoredPosition(Anchor::Bottom, ccp(0, 10), ccp(.5f, .5f));
m_restartRequiredLabel->updateAnchoredPosition(Anchor::Bottom, ccp(0, 10), ccp(.5f, .5f));
m_outdatedLabel->updateAnchoredPosition(Anchor::Bottom, ccp(0, 10), ccp(.5f, .5f));
m_downloadBarContainer->updateAnchoredPosition(Anchor::Bottom, ccp(0, 10), ccp(.5f, .5f));
m_downloadWaiting->updateAnchoredPosition(Anchor::Bottom, ccp(0, 10), ccp(.5f, .5f));
if (m_recommendedBy) {
m_recommendedBy->updateAnchoredPosition(Anchor::Bottom, ccp(0, 10), ccp(.5f, .5f));
}
} break;
default:
case ModListDisplay::SmallList: {
m_infoContainer->updateAnchoredPosition(Anchor::Left, ccp(m_obContentSize.height + 10, 0), ccp(0, .5f));
m_titleContainer->updateAnchoredPosition(Anchor::TopLeft, ccp(0, 0), ccp(0, 1));
// m_description is hidden
m_developers->updateAnchoredPosition(Anchor::BottomLeft, ccp(0, 3), ccp(0, 0));
m_restartRequiredLabel->updateAnchoredPosition(Anchor::BottomLeft, ccp(0, 3), ccp(0, 0));
m_outdatedLabel->updateAnchoredPosition(Anchor::BottomLeft, ccp(0, 3), ccp(0, 0));
m_downloadBarContainer->updateAnchoredPosition(Anchor::BottomLeft, ccp(0, 3), ccp(0, 0));
m_downloadWaiting->updateAnchoredPosition(Anchor::BottomLeft, ccp(0, 3), ccp(0, 0));
if (m_recommendedBy) {
m_recommendedBy->updateAnchoredPosition(Anchor::BottomLeft, ccp(0, 3), ccp(0, 0));
}
} break;
case ModListDisplay::BigList: {
m_infoContainer->updateAnchoredPosition(Anchor::Left, ccp(m_obContentSize.height + 10, 0), ccp(0, .5f));
m_titleContainer->updateAnchoredPosition(Anchor::TopLeft, ccp(0, 0), ccp(0, 1));
m_developers->updateAnchoredPosition(Anchor::Left, ccp(0, 0), ccp(0, .5f));
m_description->updateAnchoredPosition(Anchor::BottomLeft, ccp(0, 0), ccp(0, 0));
m_restartRequiredLabel->updateAnchoredPosition(Anchor::BottomLeft, ccp(0, 0), ccp(0, 0));
m_outdatedLabel->updateAnchoredPosition(Anchor::BottomLeft, ccp(0, 0), ccp(0, 0));
m_downloadBarContainer->updateAnchoredPosition(Anchor::BottomLeft, ccp(0, 0), ccp(0, 0));
m_downloadWaiting->updateAnchoredPosition(Anchor::BottomLeft, ccp(0, 0), ccp(0, 0));
if (m_recommendedBy) {
m_recommendedBy->updateAnchoredPosition(Anchor::BottomLeft, ccp(0, 0), ccp(0, 0));
}
} break;
}
m_infoContainer->setContentSize(ccp(titleSpace.width, titleSpace.height) / m_infoContainer->getScale());
m_infoContainer->updateLayout();
// Update button menu state
if (m_display == ModListDisplay::Grid) {
m_viewMenu->setContentWidth(m_obContentSize.width / m_viewMenu->getScaleX());
m_viewMenu->updateAnchoredPosition(Anchor::Bottom, ccp(0, 5), ccp(.5f, 0));
m_viewMenu->setScale(.45f);
static_cast<RowLayout*>(m_viewMenu->getLayout())->setAxisAlignment(AxisAlignment::Center);
}
else {
m_viewMenu->setContentWidth(m_obContentSize.width / m_viewMenu->getScaleX() / 2 - 20);
m_viewMenu->updateAnchoredPosition(Anchor::Right, ccp(-10, 0), ccp(1, .5f));
m_viewMenu->setScale(.55f);
static_cast<RowLayout*>(m_viewMenu->getLayout())->setAxisAlignment(AxisAlignment::End);
}
m_viewMenu->updateLayout();
// Highlight item via BG if it wants to restart for extra UI attention
if (wantsRestart) {
m_bg->setColor("mod-list-restart-required-label"_cc3b);
@ -482,45 +656,15 @@ void ModItem::updateState() {
}
}
this->updateLayout();
ModItemUIEvent(std::make_unique<ModItemUIEvent::Impl>(this)).post();
}
void ModItem::updateSize(float width, bool big) {
this->setContentSize({ width, big ? 40.f : 30.f });
m_bg->setContentSize((m_obContentSize - ccp(6, 0)) / m_bg->getScale());
m_bg->setPosition(m_obContentSize / 2);
auto logoSize = m_obContentSize.height - 10;
limitNodeSize(m_logo, { logoSize, logoSize }, 999, .1f);
m_logo->setPosition(m_obContentSize.height / 2 + 5, m_obContentSize.height / 2);
CCSize titleSpace {
m_obContentSize.width / 2 - m_obContentSize.height,
logoSize + 5
};
// Divide by scale of info container since that actually determines the size
// (Since the scale of m_titleContainer and m_developers is managed by its layout)
m_titleContainer->setContentWidth(titleSpace.width / m_infoContainer->getScale());
m_titleContainer->updateLayout();
m_developers->setContentWidth(titleSpace.width / m_infoContainer->getScale());
m_developers->updateLayout();
if (m_recommendedBy) {
m_recommendedBy->setContentWidth(titleSpace.width / m_infoContainer->getScale());
m_recommendedBy->updateLayout();
}
m_infoContainer->setPosition(m_obContentSize.height + 10, m_obContentSize.height / 2);
m_infoContainer->setContentSize(ccp(titleSpace.width, titleSpace.height) / m_infoContainer->getScale());
m_infoContainer->updateLayout();
m_viewMenu->setContentWidth(m_obContentSize.width / m_viewMenu->getScaleX() / 2 - 20);
m_viewMenu->updateLayout();
this->updateLayout();
void ModItem::updateDisplay(float width, ModListDisplay display) {
m_display = display;
m_targetWidth = width;
this->updateState();
}
void ModItem::onCheckUpdates(typename server::ServerRequest<std::optional<server::ServerModUpdate>>::Event* event) {

View file

@ -12,6 +12,12 @@
using namespace geode::prelude;
enum class ModListDisplay {
SmallList,
BigList,
Grid,
};
class ModItem : public CCNode {
protected:
ModSource m_source;
@ -19,10 +25,11 @@ protected:
CCNode* m_logo;
CCNode* m_infoContainer;
CCNode* m_titleContainer;
CCLabelBMFont* m_titleLabel;
Ref<CCLabelBMFont> m_titleLabel;
CCLabelBMFont* m_versionLabel;
CCNode* m_developers;
CCNode* m_recommendedBy;
CCScale9Sprite* m_description;
CCLabelBMFont* m_developerLabel;
ButtonSprite* m_restartRequiredLabel;
ButtonSprite* m_outdatedLabel;
@ -37,6 +44,10 @@ protected:
EventListener<server::ModDownloadFilter> m_downloadListener;
std::optional<server::ServerModUpdate> m_availableUpdate;
EventListener<EventFilter<SettingNodeValueChangeEvent>> m_settingNodeListener;
Ref<CCNode> m_badgeContainer = nullptr;
Ref<CCNode> m_downloadCountContainer;
ModListDisplay m_display = ModListDisplay::SmallList;
float m_targetWidth = 300;
/**
* @warning Make sure `getMetadata` and `createModLogo` are callable
@ -57,7 +68,7 @@ protected:
public:
static ModItem* create(ModSource&& source);
void updateSize(float width, bool big);
void updateDisplay(float width, ModListDisplay display);
ModSource& getSource() &;
};

View file

@ -18,13 +18,6 @@ bool ModList::init(ModListSource* src, CCSize const& size) {
m_source->reset();
m_list = ScrollLayer::create(size);
m_list->m_contentLayer->setLayout(
ColumnLayout::create()
->setAxisReverse(true)
->setAxisAlignment(AxisAlignment::End)
->setAutoGrowAxis(size.height)
->setGap(2.5f)
);
this->addChildAtPosition(m_list, Anchor::Bottom, ccp(-m_list->getScaledContentWidth() / 2, 0));
m_topContainer = CCNode::create();
@ -410,7 +403,7 @@ void ModList::onPromise(ModListSource::PageLoadTask::Event* event) {
first = false;
m_list->m_contentLayer->addChild(item);
}
this->updateSize(m_bigSize);
this->updateDisplay(m_display);
// Scroll list to top
auto listTopScrollPos = -m_list->m_contentLayer->getContentHeight() + m_list->getContentHeight();
@ -526,12 +519,10 @@ void ModList::updateTopContainer() {
// (giving a little bit of extra padding for it, the same size as gap)
m_list->setContentHeight(
m_topContainer->getContentHeight() > 0.f ?
this->getContentHeight() - m_topContainer->getContentHeight() -
static_cast<AxisLayout*>(m_list->m_contentLayer->getLayout())->getGap() :
this->getContentHeight() - m_topContainer->getContentHeight() - 2.5f :
this->getContentHeight()
);
static_cast<ColumnLayout*>(m_list->m_contentLayer->getLayout())->setAutoGrowAxis(m_list->getContentHeight());
m_list->m_contentLayer->updateLayout();
this->updateDisplay(m_display);
// Preserve relative scroll position
m_list->m_contentLayer->setPositionY((
@ -557,14 +548,14 @@ void ModList::updateTopContainer() {
this->updateLayout();
}
void ModList::updateSize(bool big) {
m_bigSize = big;
void ModList::updateDisplay(ModListDisplay display) {
m_display = display;
// Update all BaseModItems that are children of the list
// There may be non-BaseModItems there (like separators) so gotta be type-safe
for (auto& node : CCArrayExt<CCNode*>(m_list->m_contentLayer->getChildren())) {
if (auto item = typeinfo_cast<ModItem*>(node)) {
item->updateSize(m_list->getContentWidth(), big);
item->updateDisplay(m_list->getContentWidth(), display);
}
}
@ -574,8 +565,24 @@ void ModList::updateSize(bool big) {
m_list->m_contentLayer->getPositionY() / oldPositionArea :
-1.f;
// Auto-grow the size of the list content
m_list->m_contentLayer->updateLayout();
// Update the list layout based on the display model
if (display == ModListDisplay::Grid) {
m_list->m_contentLayer->setLayout(
RowLayout::create()
->setGrowCrossAxis(true)
->setAxisAlignment(AxisAlignment::Center)
->setGap(2.5f)
);
}
else {
m_list->m_contentLayer->setLayout(
ColumnLayout::create()
->setAxisReverse(true)
->setAxisAlignment(AxisAlignment::End)
->setAutoGrowAxis(m_obContentSize.height)
->setGap(2.5f)
);
}
// Preserve relative scroll position
m_list->m_contentLayer->setPositionY((

View file

@ -52,7 +52,7 @@ protected:
EventListener<InvalidateCacheFilter> m_invalidateCacheListener;
EventListener<server::ServerRequest<std::vector<std::string>>> m_checkUpdatesListener;
EventListener<server::ModDownloadFilter> m_downloadListener;
bool m_bigSize = false;
ModListDisplay m_display = ModListDisplay::SmallList;
bool m_exiting = false;
std::atomic<size_t> m_searchInputThreads = 0;
@ -83,7 +83,7 @@ public:
void showStatus(ModListStatus status, std::string const& message, std::optional<std::string> const& details = std::nullopt);
void updateState();
void updateSize(bool big);
void updateDisplay(ModListDisplay display);
void activateSearch(bool activate);
void setIsExiting(bool exiting);
};

View file

@ -0,0 +1,62 @@
#include <Geode/ui/OverlayManager.hpp>
using namespace geode::prelude;
OverlayManager* OverlayManager::get()
{
static OverlayManager* instance;
if (!instance)
{
instance = new OverlayManager();
CCDirector::get()->setNotificationNode(instance);
}
return instance;
}
void OverlayManager::addNode(CCNode* node)
{
this->addChild(node);
nodes.push_back(node);
}
void OverlayManager::removeNode(CCNode* node)
{
this->removeChild(node);
std::erase(nodes, node);
}
int OverlayManager::getHighestOverlayZOrder()
{
int z = INT_MIN;
for (auto node : nodes)
{
if (node->getZOrder() > z)
z = node->getZOrder();
}
return z;
}
int OverlayManager::getLowestOverlayZOrder()
{
int z = INT_MAX;
for (auto node : nodes)
{
if (node->getZOrder() < z)
z = node->getZOrder();
}
return z;
}
std::vector<CCNode*> OverlayManager::getOverlays()
{
return nodes;
}

View file

@ -59,6 +59,8 @@ std::string utils::string::toUpper(std::string const& str) {
}
std::string& utils::string::replaceIP(std::string& str, std::string const& orig, std::string const& repl) {
if (orig.empty()) return str;
std::string::size_type n = 0;
while ((n = str.find(orig, n)) != std::string::npos) {
str.replace(n, orig.size(), repl);