mirror of
https://github.com/geode-sdk/geode.git
synced 2025-02-17 00:30:26 -05:00
problems list ui
This commit is contained in:
parent
6ab542d51a
commit
aee84c0ffc
9 changed files with 465 additions and 32 deletions
|
@ -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();
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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()) {
|
||||
|
|
140
loader/src/ui/internal/list/ProblemsListCell.cpp
Normal file
140
loader/src/ui/internal/list/ProblemsListCell.cpp
Normal 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;
|
||||
}
|
33
loader/src/ui/internal/list/ProblemsListCell.hpp
Normal file
33
loader/src/ui/internal/list/ProblemsListCell.hpp
Normal 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);
|
||||
};
|
106
loader/src/ui/internal/list/ProblemsListPopup.cpp
Normal file
106
loader/src/ui/internal/list/ProblemsListPopup.cpp
Normal 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;
|
||||
}
|
||||
|
22
loader/src/ui/internal/list/ProblemsListPopup.hpp
Normal file
22
loader/src/ui/internal/list/ProblemsListPopup.hpp
Normal 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);
|
||||
};
|
Loading…
Reference in a new issue