problems list ui

This commit is contained in:
ConfiG 2023-08-13 22:20:53 +03:00
parent 6ab542d51a
commit aee84c0ffc
No known key found for this signature in database
GPG key ID: 44DA1983F524C11B
9 changed files with 465 additions and 32 deletions

View file

@ -301,7 +301,7 @@ void Loader::Impl::queueMods(std::vector<ModMetadata>& modQueue) {
m_problems.push_back({
LoadProblem::Type::Duplicate,
modMetadata,
"a mod with the same ID is already present"
"A mod with the same ID is already present."
});
log::error("Failed to queue: a mod with the same ID is already queued");
log::popNest();

View file

@ -11,14 +11,6 @@
#include "../info/TagNode.hpp"
#include "../info/DevProfilePopup.hpp"
template <class T>
static bool tryOrAlert(Result<T> const& res, char const* title) {
if (!res) {
FLAlertLayer::create(title, res.unwrapErr(), "OK")->show();
}
return res.isOk();
}
// InstallListCell
void InstallListCell::draw() {

View file

@ -1,4 +1,3 @@
#include "ModListCell.hpp"
#include "ModListLayer.hpp"
#include "../info/ModInfoPopup.hpp"
@ -11,6 +10,7 @@
#include <loader/LoaderImpl.hpp>
#include "../info/TagNode.hpp"
#include "../info/DevProfilePopup.hpp"
#include "ProblemsListPopup.hpp"
template <class T>
static bool tryOrAlert(Result<T> const& res, char const* title) {
@ -234,20 +234,8 @@ void ModCell::onEnable(CCObject* sender) {
});
}
// TODO: for fod maybe :3 show problems related to this mod
void ModCell::onUnresolvedInfo(CCObject*) {
std::string info =
"This mod has the following "
"<cr>unresolved dependencies</c>: ";
for (auto const& dep : m_mod->getUnresolvedDependencies()) {
info += fmt::format(
"<cg>{}</c> (<cy>{}</c>), ",
dep.id, dep.version.toString()
);
}
info.pop_back();
info.pop_back();
FLAlertLayer::create(nullptr, "Unresolved Dependencies", info, "OK", nullptr, 400.f)->show();
ProblemsListPopup::create(m_mod)->show();
}
void ModCell::onInfo(CCObject*) {
@ -258,7 +246,6 @@ void ModCell::onRestart(CCObject*) {
utils::game::restart();
}
// TODO: for fod maybe :3 check if there are any problems related to this mod
void ModCell::updateState() {
bool unresolved = m_mod->hasUnresolvedDependencies();
if (m_enableToggle) {
@ -269,7 +256,16 @@ void ModCell::updateState() {
m_enableToggle->m_onButton->setOpacity(unresolved ? 100 : 255);
m_enableToggle->m_onButton->setColor(unresolved ? cc3x(155) : cc3x(255));
}
m_unresolvedExMark->setVisible(unresolved);
bool hasProblems = false;
for (auto const& item : Loader::get()->getProblems()) {
if (!std::holds_alternative<Mod*>(item.cause) ||
std::get<Mod*>(item.cause) != m_mod ||
item.type <= LoadProblem::Type::Recommendation)
continue;
hasProblems = true;
break;
}
m_unresolvedExMark->setVisible(hasProblems);
}
bool ModCell::init(
@ -513,7 +509,7 @@ bool InvalidGeodeFileCell::init(
pathLabel->setColor({ 255, 255, 0 });
this->addChild(pathLabel);
auto whySpr = ButtonSprite::create("Info", 0, 0, "bigFont.fnt", "GJ_button_01.png", 0, .8f);
auto whySpr = ButtonSprite::create("Info", 0, false, "bigFont.fnt", "GJ_button_01.png", 0, .8f);
whySpr->setScale(.65f);
auto viewBtn =
@ -547,3 +543,112 @@ std::string InvalidGeodeFileCell::getDeveloper() const {
CCNode* InvalidGeodeFileCell::createLogo(CCSize const& size) {
return nullptr;
}
// ProblemsCell
void ProblemsCell::onInfo(CCObject*) {
ProblemsListPopup::create(nullptr)->show();
}
bool ProblemsCell::init(
ModListLayer* list,
ModListDisplay display,
CCSize const& size
) {
if (!ModListCell::init(list, size))
return false;
LoadProblem::Type problemType = LoadProblem::Type::Unknown;
// iterate problems to find the most important severity
for (auto const& problem : Loader::get()->getProblems()) {
if (problemType < problem.type)
problemType = problem.type;
// already found the most important one (error)
if (problemType > LoadProblem::Type::Conflict)
break;
}
std::string icon;
std::string title;
switch (problemType) {
case LoadProblem::Type::Unknown:
title = "?????";
break;
case LoadProblem::Type::Suggestion:
icon = "GJ_infoIcon_001.png";
title = "You have suggested mods";
m_color = { 66, 135, 245 };
break;
case LoadProblem::Type::Recommendation:
icon = "GJ_infoIcon_001.png";
title = "You have recommended mods";
m_color = { 66, 135, 245 };
break;
case LoadProblem::Type::Conflict:
icon = "info-warning.png"_spr;
title = "Some mods had warnings when loading";
m_color = { 250, 176, 37 };
break;
default:
icon = "info-alert.png"_spr;
title = "Some mods had problems loading";
m_color = { 245, 66, 66 };
break;
}
m_menu = CCMenu::create();
m_menu->setPosition(m_width - 40.f, m_height / 2);
this->addChild(m_menu);
auto logoSize = this->getLogoSize();
if (!icon.empty()) {
auto logoSpr = CCSprite::createWithSpriteFrameName(icon.c_str());
limitNodeSize(logoSpr, size, 1.f, .1f);
logoSpr->setPosition({logoSize / 2 + 12.f, m_height / 2});
this->addChild(logoSpr);
}
auto titleLabel = CCLabelBMFont::create(title.c_str(), "bigFont.fnt");
titleLabel->setAnchorPoint({ .0f, .5f });
titleLabel->setPosition(m_height / 2 + logoSize / 2 + 13.f, m_height / 2);
titleLabel->limitLabelWidth(m_width - 120.f, 1.f, .1f);
this->addChild(titleLabel);
auto viewSpr = ButtonSprite::create("View", "bigFont.fnt", "GJ_button_01.png", .8f);
viewSpr->setScale(.65f);
auto viewBtn =
CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(ProblemsCell::onInfo));
m_menu->addChild(viewBtn);
return true;
}
std::optional<ccColor3B> ProblemsCell::getColor() {
return m_color;
}
ProblemsCell* ProblemsCell::create(
ModListLayer* list,
ModListDisplay display,
CCSize const& size
) {
auto ret = new ProblemsCell();
if (ret->init(list, display, size)) {
ret->autorelease();
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
void ProblemsCell::updateState() {}
std::string ProblemsCell::getDeveloper() const {
return "";
}
CCNode* ProblemsCell::createLogo(CCSize const& size) {
return nullptr;
}

View file

@ -131,3 +131,32 @@ public:
CCNode* createLogo(CCSize const& size) override;
std::string getDeveloper() const override;
};
/**
* Mod list item for an invalid Geode package
*/
class ProblemsCell : public ModListCell {
protected:
std::optional<ccColor3B> m_color;
bool init(
ModListLayer* list,
ModListDisplay display,
CCSize const& size
);
void onInfo(CCObject*);
public:
static ProblemsCell* create(
ModListLayer* list,
ModListDisplay display,
CCSize const& size
);
std::optional<ccColor3B> getColor();
void updateState() override;
CCNode* createLogo(CCSize const& size) override;
std::string getDeveloper() const override;
};

View file

@ -156,12 +156,9 @@ CCArray* ModListLayer::createModCells(ModListType type, ModListQuery const& quer
switch (type) {
default:
case ModListType::Installed: {
// failed mods first
for (auto const& mod : Loader::get()->getFailedMods()) {
if (!queryMatch(query, mod)) continue;
mods->addObject(InvalidGeodeFileCell::create(
mod, this, m_display, this->getCellSize()
));
// problems first
if (!Loader::get()->getProblems().empty()) {
mods->addObject(ProblemsCell::create(this, m_display, this->getCellSize()));
}
// sort the mods by match score
@ -474,6 +471,15 @@ void ModListLayer::reloadList(std::optional<ModListQuery> const& query) {
this->getListSize().width,
this->getListSize().height
);
// please forgive me for this code
auto problemsCell = typeinfo_cast<ProblemsCell*>(list->m_entries->objectAtIndex(0));
if (problemsCell) {
auto cellView =
typeinfo_cast<TableViewCell*>(list->m_tableView->m_cellArray->objectAtIndex(0));
if (cellView && problemsCell->getColor()) {
cellView->m_backgroundLayer->setColor(*problemsCell->getColor());
}
}
// set list status
if (!items->count()) {

View file

@ -0,0 +1,140 @@
#include "ProblemsListCell.hpp"
#include "ProblemsListPopup.hpp"
#include <Geode/binding/ButtonSprite.hpp>
#include <Geode/binding/CCMenuItemSpriteExtra.hpp>
#include <Geode/binding/CCMenuItemToggler.hpp>
#include <Geode/binding/FLAlertLayer.hpp>
#include <Geode/binding/StatsCell.hpp>
#include <Geode/ui/GeodeUI.hpp>
#include <loader/LoaderImpl.hpp>
#include <utility>
void ProblemsListCell::draw() {
reinterpret_cast<StatsCell*>(this)->StatsCell::draw();
}
float ProblemsListCell::getLogoSize() const {
return m_height / 1.5f;
}
bool ProblemsListCell::init(LoadProblem problem, ProblemsListPopup* list, CCSize const& size) {
m_width = size.width;
m_height = size.height;
m_layer = list;
this->setContentSize(size);
this->setID("problems-list-cell");
std::string cause = "unknown";
if (std::holds_alternative<ghc::filesystem::path>(problem.cause)) {
cause = std::get<ghc::filesystem::path>(problem.cause).filename().string();
}
else if (std::holds_alternative<ModMetadata>(problem.cause)) {
cause = std::get<ModMetadata>(problem.cause).getName();
}
else if (std::holds_alternative<Mod*>(problem.cause)) {
cause = std::get<Mod*>(problem.cause)->getName();
}
std::string icon;
std::string message;
switch (problem.type) {
case LoadProblem::Type::Unknown:
message = fmt::format("Unknown error in {}", cause);
m_longMessage = problem.message;
break;
case LoadProblem::Type::Suggestion:
icon = "GJ_infoIcon_001.png";
message = fmt::format("{} suggests {}", cause, problem.message);
break;
case LoadProblem::Type::Recommendation:
icon = "GJ_infoIcon_001.png";
message = fmt::format("{} recommends {}", cause, problem.message);
break;
case LoadProblem::Type::Conflict:
icon = "info-warning.png"_spr;
message = fmt::format("{} conflicts with {}", cause, problem.message);
break;
case LoadProblem::Type::InvalidFile:
icon = "info-alert.png"_spr;
message = fmt::format("{} is an invalid .geode file", cause);
m_longMessage = problem.message;
break;
case LoadProblem::Type::Duplicate:
icon = "info-alert.png"_spr;
message = fmt::format("{} is installed more than once", cause);
m_longMessage = problem.message;
break;
case LoadProblem::Type::SetupFailed:
icon = "info-alert.png"_spr;
message = fmt::format("{} has failed setting up", cause);
m_longMessage = problem.message;
break;
case LoadProblem::Type::LoadFailed:
icon = "info-alert.png"_spr;
message = fmt::format("{} has failed loading", cause);
m_longMessage = problem.message;
break;
case LoadProblem::Type::EnableFailed:
icon = "info-alert.png"_spr;
message = fmt::format("{} has failed enabling", cause);
m_longMessage = problem.message;
break;
case LoadProblem::Type::MissingDependency:
icon = "info-alert.png"_spr;
message = fmt::format("{} depends on {}", cause, problem.message);
break;
case LoadProblem::Type::PresentIncompatibility:
icon = "info-alert.png"_spr;
message = fmt::format("{} is incompatible with {}", cause, problem.message);
break;
}
m_problem = std::move(problem);
m_menu = CCMenu::create();
m_menu->setPosition(m_width - 40.f, m_height / 2);
this->addChild(m_menu);
auto logoSize = this->getLogoSize();
if (!icon.empty()) {
auto logoSpr = CCSprite::createWithSpriteFrameName(icon.c_str());
limitNodeSize(logoSpr, size, 1.f, .1f);
logoSpr->setPosition({logoSize / 2 + 12.f, m_height / 2});
this->addChild(logoSpr);
}
auto messageLabel = CCLabelBMFont::create(message.c_str(), "bigFont.fnt");
messageLabel->setAnchorPoint({ .0f, .5f });
messageLabel->setPosition(m_height / 2 + logoSize / 2 + 13.f, m_height / 2);
messageLabel->limitLabelWidth(m_width - 120.f, 1.f, .1f);
this->addChild(messageLabel);
if (!m_longMessage.empty()) {
auto viewSpr = ButtonSprite::create("More", "bigFont.fnt", "GJ_button_01.png", .8f);
viewSpr->setScale(.65f);
auto viewBtn =
CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(ProblemsListCell::onMore));
m_menu->addChild(viewBtn);
}
return true;
}
void ProblemsListCell::onMore(cocos2d::CCObject*) {
FLAlertLayer::create("Problem Info", m_longMessage, "OK")->show();
}
LoadProblem ProblemsListCell::getProblem() const {
return m_problem;
}
ProblemsListCell* ProblemsListCell::create(LoadProblem problem, ProblemsListPopup* list, CCSize const& size) {
auto ret = new ProblemsListCell();
if (ret->init(problem, list, size)) {
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}

View file

@ -0,0 +1,33 @@
#pragma once
#include <Geode/binding/TableViewCell.hpp>
#include <Geode/binding/FLAlertLayerProtocol.hpp>
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/ModMetadata.hpp>
#include <Geode/loader/Index.hpp>
using namespace geode::prelude;
class ProblemsListPopup;
class ProblemsListCell : public CCLayer {
protected:
float m_width;
float m_height;
ProblemsListPopup* m_layer;
CCMenu* m_menu;
LoadProblem m_problem;
std::string m_longMessage;
bool init(LoadProblem problem, ProblemsListPopup* list, CCSize const& size);
void draw() override;
void onMore(CCObject*);
float getLogoSize() const;
public:
LoadProblem getProblem() const;
static ProblemsListCell* create(LoadProblem problem, ProblemsListPopup* list, CCSize const& size);
};

View file

@ -0,0 +1,106 @@
#include "ProblemsListPopup.hpp"
#include "ProblemsListCell.hpp"
#include <utility>
#include <queue>
bool ProblemsListPopup::setup(Mod* scrollTo) {
m_noElasticity = true;
this->setTitle("Problems");
this->createList(scrollTo);
return true;
}
void ProblemsListPopup::createList(Mod* scrollTo) {
auto winSize = CCDirector::sharedDirector()->getWinSize();
m_listParent = CCNode::create();
m_listParent->setPositionY(-7.f);
m_mainLayer->addChild(m_listParent);
float scroll = 0.f;
auto items = this->createCells(scrollTo, scroll);
m_list = ListView::create(
items,
this->getCellSize().height,
this->getListSize().width,
this->getListSize().height
);
m_list->setPosition(winSize / 2 - m_list->getScaledContentSize() / 2);
m_listParent->addChild(m_list);
m_list->m_tableView->m_contentLayer->setPositionY(m_list->m_tableView->m_contentLayer->getPositionY() + scroll);
addListBorders(m_listParent, winSize / 2, m_list->getScaledContentSize());
}
CCArray* ProblemsListPopup::createCells(Mod* scrollTo, float& scrollValue) {
std::vector<ProblemsListCell*> top;
std::vector<ProblemsListCell*> middle;
std::vector<ProblemsListCell*> bottom;
for (auto const& problem : Loader::get()->getProblems()) {
switch (problem.type) {
case geode::LoadProblem::Type::Suggestion:
bottom.push_back(ProblemsListCell::create(problem, this, this->getCellSize()));
break;
case geode::LoadProblem::Type::Recommendation:
middle.push_back(ProblemsListCell::create(problem, this, this->getCellSize()));
break;
default:
top.push_back(ProblemsListCell::create(problem, this, this->getCellSize()));
break;
}
}
auto final = CCArray::create();
// find the highest scrollTo element
bool scrollFound = false;
auto tryFindScroll = [&](auto const& item) {
if (!scrollTo || scrollFound ||
!std::holds_alternative<Mod*>(item->getProblem().cause) ||
std::get<Mod*>(item->getProblem().cause) != scrollTo)
return;
scrollValue = (float)final->count() * this->getCellSize().height;
scrollFound = true;
};
for (auto const& item : top) {
tryFindScroll(item);
final->addObject(item);
}
for (auto const& item : middle) {
tryFindScroll(item);
final->addObject(item);
}
for (auto const& item : bottom) {
tryFindScroll(item);
final->addObject(item);
}
return final;
}
// Getters
CCSize ProblemsListPopup::getListSize() const {
return { 340.f, 190.f };
}
CCSize ProblemsListPopup::getCellSize() const {
return { getListSize().width, 40.f };
}
// Static
ProblemsListPopup* ProblemsListPopup::create(Mod* scrollTo) {
auto ret = new ProblemsListPopup();
if (!ret->init(380.f, 250.f, scrollTo)) {
CC_SAFE_DELETE(ret);
return nullptr;
}
ret->autorelease();
return ret;
}

View file

@ -0,0 +1,22 @@
#pragma once
#include <Geode/ui/Popup.hpp>
#include <Geode/loader/Loader.hpp>
using namespace geode::prelude;
class ProblemsListPopup : public Popup<Mod*> {
protected:
CCNode* m_listParent;
ListView* m_list;
bool setup(Mod* scrollTo) override;
void createList(Mod* scrollTo);
CCArray* createCells(Mod* scrollTo, float& scrollValue);
CCSize getCellSize() const;
CCSize getListSize() const;
public:
static ProblemsListPopup* create(Mod* scrollTo);
};