index work!!

- add tag support for index
 - rename CategoryNode to TagNode
 - add them back to the UI + fix spacing issues related to them
 - deleted ModListView, now using a generic ListView with the mod cells added as children to it
This commit is contained in:
HJfod 2022-12-08 21:15:06 +02:00
parent e763e271bf
commit 84bdeb7beb
14 changed files with 339 additions and 408 deletions

View file

@ -58,6 +58,7 @@ namespace geode {
std::unordered_set<PlatformID> platforms;
} download;
bool isFeatured;
std::unordered_set<std::string> tags;
/**
* Create IndexItem from a directory

View file

@ -122,6 +122,7 @@ Result<IndexItemHandle> IndexItem::createFromDir(
.platforms = platforms,
},
.isFeatured = root.has("is-featured").template get<bool>(),
.tags = root.has("tags").template get<std::unordered_set<std::string>>()
});
if (checker.isError()) {
return Err(checker.getError());

View file

@ -1,22 +0,0 @@
#pragma once
#include <cocos2d.h>
USE_GEODE_NAMESPACE();
enum class CategoryNodeStyle {
Tag,
Dot,
};
class CategoryNode : public CCNode {
protected:
bool init(std::string const& category, CategoryNodeStyle style);
public:
static CategoryNode* create(
std::string const& category, CategoryNodeStyle style = CategoryNodeStyle::Tag
);
static ccColor3B categoryToColor(std::string const& category);
};

View file

@ -1,7 +1,7 @@
#include "ModInfoPopup.hpp"
#include "../dev/HookListLayer.hpp"
#include "../list/ModListView.hpp"
#include "../list/ModListLayer.hpp"
#include "../settings/ModSettingsPopup.hpp"
#include "../settings/AdvancedSettingsPopup.hpp"
#include <InternalLoader.hpp>
@ -17,6 +17,7 @@
#include <Geode/ui/BasedButton.hpp>
#include <Geode/ui/IconButtonSprite.hpp>
#include <Geode/ui/GeodeUI.hpp>
#include <Geode/ui/MDPopup.hpp>
#include <Geode/utils/casts.hpp>
#include <Geode/utils/ranges.hpp>
#include <Geode/utils/web.hpp>
@ -30,9 +31,9 @@ static constexpr int const TAG_CONFIRM_UNINSTALL = 5;
static constexpr int const TAG_DELETE_SAVEDATA = 6;
static const CCSize LAYER_SIZE = { 440.f, 290.f };
bool ModInfoPopup::init(ModInfo const& info, ModListView* list) {
bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
m_noElasticity = true;
m_list = list;
m_layer = list;
auto winSize = CCDirector::sharedDirector()->getWinSize();
@ -274,7 +275,7 @@ void ModInfoPopup::onClose(CCObject* pSender) {
// LocalModInfoPopup
bool LocalModInfoPopup::init(Mod* mod, ModListView* list) {
bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
m_mod = mod;
if (!ModInfoPopup::init(mod->getModInfo(), list))
@ -458,7 +459,7 @@ void LocalModInfoPopup::onEnableMod(CCObject* sender) {
"OK"
)
->show();
if (m_list) m_list->updateAllStates(nullptr);
if (m_layer) m_layer->updateAllStates(nullptr);
return;
}
if (as<CCMenuItemToggler*>(sender)->isToggled()) {
@ -479,7 +480,7 @@ void LocalModInfoPopup::onEnableMod(CCObject* sender) {
)->show();
}
}
if (m_list) m_list->updateAllStates(nullptr);
if (m_layer) m_layer->updateAllStates(nullptr);
as<CCMenuItemToggler*>(sender)->toggle(m_mod->isEnabled());
}
@ -530,7 +531,7 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) {
)->show();
}
}
if (m_list) m_list->refreshList();
if (m_layer) m_layer->reloadList();
this->onClose(nullptr);
} break;
}
@ -556,7 +557,7 @@ void LocalModInfoPopup::uninstall() {
layer->show();
}
LocalModInfoPopup* LocalModInfoPopup::create(Mod* mod, ModListView* list) {
LocalModInfoPopup* LocalModInfoPopup::create(Mod* mod, ModListLayer* list) {
auto ret = new LocalModInfoPopup;
if (ret && ret->init(mod, list)) {
ret->autorelease();
@ -568,7 +569,7 @@ LocalModInfoPopup* LocalModInfoPopup::create(Mod* mod, ModListView* list) {
// IndexItemInfoPopup
bool IndexItemInfoPopup::init(IndexItemHandle item, ModListView* list) {
bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) {
m_item = item;
auto winSize = CCDirector::sharedDirector()->getWinSize();
@ -608,7 +609,7 @@ ModInfo IndexItemInfoPopup::getModInfo() const {
}
IndexItemInfoPopup* IndexItemInfoPopup::create(
IndexItemHandle item, ModListView* list
IndexItemHandle item, ModListLayer* list
) {
auto ret = new IndexItemInfoPopup;
if (ret && ret->init(item, list)) {

View file

@ -9,7 +9,7 @@
USE_GEODE_NAMESPACE();
class ModListView;
class ModListLayer;
class ModObject;
class DownloadStatusNode : public CCNode {
@ -28,7 +28,7 @@ public:
class ModInfoPopup : public FLAlertLayer {
protected:
ModListView* m_list = nullptr;
ModListLayer* m_layer = nullptr;
DownloadStatusNode* m_installStatus = nullptr;
IconButtonSprite* m_installBtnSpr;
CCMenuItemSpriteExtra* m_installBtn;
@ -43,7 +43,7 @@ protected:
void onSupport(CCObject*);
void onInfo(CCObject*);
bool init(ModInfo const& info, ModListView* list);
bool init(ModInfo const& info, ModListLayer* list);
void keyDown(cocos2d::enumKeyCodes) override;
void onClose(cocos2d::CCObject*);
@ -56,7 +56,7 @@ class LocalModInfoPopup : public ModInfoPopup, public FLAlertLayerProtocol {
protected:
Mod* m_mod;
bool init(Mod* mod, ModListView* list);
bool init(Mod* mod, ModListLayer* list);
void onIssues(CCObject*);
void onSettings(CCObject*);
@ -74,18 +74,18 @@ protected:
ModInfo getModInfo() const override;
public:
static LocalModInfoPopup* create(Mod* mod, ModListView* list);
static LocalModInfoPopup* create(Mod* mod, ModListLayer* list);
};
class IndexItemInfoPopup : public ModInfoPopup {
protected:
IndexItemHandle m_item;
bool init(IndexItemHandle item, ModListView* list);
bool init(IndexItemHandle item, ModListLayer* list);
CCNode* createLogo(CCSize const& size) override;
ModInfo getModInfo() const override;
public:
static IndexItemInfoPopup* create(IndexItemHandle item, ModListView* list);
static IndexItemInfoPopup* create(IndexItemHandle item, ModListLayer* list);
};

View file

@ -1,6 +1,9 @@
#include "CategoryNode.hpp"
#include "TagNode.hpp"
#include <Geode/utils/general.hpp>
#include <cocos-ext.h>
#include <Geode/loader/Mod.hpp>
ccColor3B CategoryNode::categoryToColor(std::string const& category) {
ccColor3B TagNode::categoryToColor(std::string const& category) {
// all we need is to convert a string into some number
// between 0 and 360 and for that number to always be the
// same for the same string
@ -17,8 +20,8 @@ ccColor3B CategoryNode::categoryToColor(std::string const& category) {
static_cast<GLubyte>(rgb.b * 255) };
}
bool CategoryNode::init(std::string const& category, CategoryNodeStyle style) {
if (style == CategoryNodeStyle::Dot) {
bool TagNode::init(std::string const& category, TagNodeStyle style) {
if (style == TagNodeStyle::Dot) {
auto dot = CCSprite::createWithSpriteFrameName("category-dot.png"_spr);
dot->setColor(categoryToColor(category));
dot->setPosition({ 20.f, 20.f });
@ -52,8 +55,8 @@ bool CategoryNode::init(std::string const& category, CategoryNodeStyle style) {
return true;
}
CategoryNode* CategoryNode::create(std::string const& category, CategoryNodeStyle style) {
auto ret = new CategoryNode();
TagNode* TagNode::create(std::string const& category, TagNodeStyle style) {
auto ret = new TagNode();
if (ret && ret->init(category, style)) {
ret->autorelease();
return ret;

View file

@ -0,0 +1,22 @@
#pragma once
#include <cocos2d.h>
USE_GEODE_NAMESPACE();
enum class TagNodeStyle {
Tag,
Dot,
};
class TagNode : public CCNode {
protected:
bool init(std::string const& category, TagNodeStyle style);
public:
static TagNode* create(
std::string const& category, TagNodeStyle style = TagNodeStyle::Tag
);
static ccColor3B categoryToColor(std::string const& category);
};

View file

@ -1,5 +1,5 @@
#include "ModListCell.hpp"
#include "ModListView.hpp"
#include "ModListLayer.hpp"
#include "../info/ModInfoPopup.hpp"
#include <Geode/binding/StatsCell.hpp>
#include <Geode/binding/FLAlertLayer.hpp>
@ -8,6 +8,7 @@
#include <Geode/binding/CCMenuItemToggler.hpp>
#include <Geode/ui/GeodeUI.hpp>
#include <InternalLoader.hpp>
#include "../info/TagNode.hpp"
template <class T>
static bool tryOrAlert(Result<T> const& res, char const* title) {
@ -17,45 +18,46 @@ static bool tryOrAlert(Result<T> const& res, char const* title) {
return res.isOk();
}
ModListCell::ModListCell(char const* name, CCSize const& size)
: TableViewCell(name, size.width, size.height) {}
void ModListCell::draw() {
reinterpret_cast<StatsCell*>(this)->StatsCell::draw();
}
void ModListCell::setupInfo(ModInfo const& info, bool spaceForCategories) {
m_mainLayer->setVisible(true);
m_backgroundLayer->setOpacity(255);
float ModListCell::getLogoSize() const {
return m_height / 1.5f;
}
void ModListCell::setupInfo(ModInfo const& info, bool spaceForTags) {
m_menu = CCMenu::create();
m_menu->setPosition(m_width - 40.f, m_height / 2);
m_mainLayer->addChild(m_menu);
this->addChild(m_menu);
auto logoSize = m_height / 1.5f;
auto logoSize = this->getLogoSize();
auto logoSpr = this->createLogo({ logoSize, logoSize });
logoSpr->setPosition({ logoSize / 2 + 12.f, m_height / 2 });
m_mainLayer->addChild(logoSpr);
this->addChild(logoSpr);
bool hasDesc =
m_display == ModListDisplay::Expanded &&
m_layer->getDisplay() == ModListDisplay::Expanded &&
info.m_description.has_value();
auto titleLabel = CCLabelBMFont::create(info.m_name.c_str(), "bigFont.fnt");
titleLabel->setAnchorPoint({ .0f, .5f });
titleLabel->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
if (hasDesc && spaceForCategories) {
if (hasDesc && spaceForTags) {
titleLabel->setPositionY(m_height / 2 + 20.f);
}
else if (hasDesc || spaceForCategories) {
else if (spaceForTags) {
titleLabel->setPositionY(m_height / 2 + 12.f);
}
else if (hasDesc) {
titleLabel->setPositionY(m_height / 2 + 15.f);
}
else {
titleLabel->setPositionY(m_height / 2 + 7.f);
}
titleLabel->limitLabelWidth(m_width / 2 - 40.f, .5f, .1f);
m_mainLayer->addChild(titleLabel);
this->addChild(titleLabel);
auto versionLabel = CCLabelBMFont::create(info.m_version.toString().c_str(), "bigFont.fnt");
versionLabel->setAnchorPoint({ .0f, .5f });
@ -65,23 +67,23 @@ void ModListCell::setupInfo(ModInfo const& info, bool spaceForCategories) {
titleLabel->getPositionY() - 1.f
);
versionLabel->setColor({ 0, 255, 0 });
m_mainLayer->addChild(versionLabel);
this->addChild(versionLabel);
auto creatorStr = "by " + info.m_developer;
auto creatorLabel = CCLabelBMFont::create(creatorStr.c_str(), "goldFont.fnt");
creatorLabel->setAnchorPoint({ .0f, .5f });
creatorLabel->setScale(.43f);
creatorLabel->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
if (hasDesc && spaceForCategories) {
if (hasDesc && spaceForTags) {
creatorLabel->setPositionY(m_height / 2 + 7.5f);
}
else if (hasDesc || spaceForCategories) {
else if (hasDesc || spaceForTags) {
creatorLabel->setPositionY(m_height / 2);
}
else {
creatorLabel->setPositionY(m_height / 2 - 7.f);
}
m_mainLayer->addChild(creatorLabel);
this->addChild(creatorLabel);
if (hasDesc) {
auto descBG = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f });
@ -90,48 +92,41 @@ void ModListCell::setupInfo(ModInfo const& info, bool spaceForCategories) {
descBG->setContentSize({ m_width * 2, 60.f });
descBG->setAnchorPoint({ .0f, .5f });
descBG->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
if (spaceForCategories) {
if (spaceForTags) {
descBG->setPositionY(m_height / 2 - 7.5f);
}
else {
descBG->setPositionY(m_height / 2 - 17.f);
}
descBG->setScale(.25f);
m_mainLayer->addChild(descBG);
this->addChild(descBG);
auto descText = CCLabelBMFont::create(info.m_description.value().c_str(), "chatFont.fnt");
descText->setAnchorPoint({ .0f, .5f });
descText->setPosition(m_height / 2 + logoSize / 2 + 18.f, descBG->getPositionY());
descText->limitLabelWidth(m_width / 2 - 10.f, .5f, .1f);
m_mainLayer->addChild(descText);
m_description = CCLabelBMFont::create(info.m_description.value().c_str(), "chatFont.fnt");
m_description->setAnchorPoint({ .0f, .5f });
m_description->setPosition(m_height / 2 + logoSize / 2 + 18.f, descBG->getPositionY());
m_description->limitLabelWidth(m_width / 2 - 10.f, .5f, .1f);
this->addChild(m_description);
}
}
void ModListCell::updateBGColor(int index) {
if (index % 2) {
m_backgroundLayer->setColor(ccc3(0xc2, 0x72, 0x3e));
}
else m_backgroundLayer->setColor(ccc3(0xa1, 0x58, 0x2c));
m_backgroundLayer->setOpacity(0xff);
}
bool ModListCell::init(ModListView* list, ModListDisplay display) {
m_list = list;
m_display = display;
bool ModListCell::init(ModListLayer* list, CCSize const& size) {
m_width = size.width;
m_height = size.height;
m_layer = list;
this->setContentSize(size);
this->setID("mod-list-cell");
return true;
}
// ModCell
ModCell::ModCell(const char* name, CCSize const& size)
: ModListCell(name, size) {}
ModCell* ModCell::create(
ModListView* list, ModListDisplay display,
const char* key, CCSize const& size
Mod* mod,
ModListLayer* list,
CCSize const& size
) {
auto ret = new ModCell(key, size);
if (ret && ret->init(list, display)) {
auto ret = new ModCell();
if (ret && ret->init(mod, list, size)) {
return ret;
}
CC_SAFE_DELETE(ret);
@ -148,7 +143,7 @@ void ModCell::onEnable(CCObject* sender) {
"need to <cg>restart</c> the game to have it fully unloaded.",
"OK"
)->show();
m_list->updateAllStates(this);
m_layer->updateAllStates(this);
return;
}
if (!as<CCMenuItemToggler*>(sender)->isToggled()) {
@ -157,7 +152,7 @@ void ModCell::onEnable(CCObject* sender) {
else {
tryOrAlert(m_mod->disable(), "Error disabling mod");
}
m_list->updateAllStates(this);
m_layer->updateAllStates(this);
}
void ModCell::onUnresolvedInfo(CCObject*) {
@ -176,7 +171,7 @@ void ModCell::onUnresolvedInfo(CCObject*) {
}
void ModCell::onInfo(CCObject*) {
LocalModInfoPopup::create(m_mod, m_list)->show();
LocalModInfoPopup::create(m_mod, m_layer)->show();
}
void ModCell::updateState() {
@ -192,7 +187,14 @@ void ModCell::updateState() {
m_unresolvedExMark->setVisible(unresolved);
}
void ModCell::loadFromMod(Mod* mod) {
bool ModCell::init(
Mod* mod,
ModListLayer* list,
CCSize const& size
) {
if (!ModListCell::init(list, size))
return false;
m_mod = mod;
this->setupInfo(mod->getModInfo(), false);
@ -238,6 +240,8 @@ void ModCell::loadFromMod(Mod* mod) {
// }
this->updateState();
return true;
}
CCNode* ModCell::createLogo(CCSize const& size) {
@ -246,29 +250,34 @@ CCNode* ModCell::createLogo(CCSize const& size) {
// IndexItemCell
IndexItemCell::IndexItemCell(char const* name, CCSize const& size)
: ModListCell(name, size) {}
void IndexItemCell::onInfo(CCObject*) {
IndexItemInfoPopup::create(m_item, m_list)->show();
IndexItemInfoPopup::create(m_item, m_layer)->show();
}
IndexItemCell* IndexItemCell::create(
ModListView* list, ModListDisplay display,
const char* key, CCSize const& size
IndexItemHandle item,
ModListLayer* list,
CCSize const& size
) {
auto ret = new IndexItemCell(key, size);
if (ret && ret->init(list, display)) {
auto ret = new IndexItemCell();
if (ret && ret->init(item, list, size)) {
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
void IndexItemCell::loadFromItem(IndexItemHandle item) {
bool IndexItemCell::init(
IndexItemHandle item,
ModListLayer* list,
CCSize const& size
) {
if (!ModListCell::init(list, size))
return false;
m_item = item;
this->setupInfo(item->info, true);
this->setupInfo(item->info, item->tags.size());
auto viewSpr = ButtonSprite::create(
"View", "bigFont.fnt", "GJ_button_01.png", .8f
@ -280,26 +289,28 @@ void IndexItemCell::loadFromItem(IndexItemHandle item) {
);
m_menu->addChild(viewBtn);
// if (hasCategories) {
// float x = m_height / 2 + logoSize / 2 + 13.f;
// for (auto& category : modobj->m_index.m_categories) {
// auto node = CategoryNode::create(category);
// node->setAnchorPoint({ .0f, .5f });
// node->setPositionX(x);
// node->setScale(.3f);
// if (hasDesc) {
// node->setPositionY(m_height / 2 - 23.f);
// }
// else {
// node->setPositionY(m_height / 2 - 17.f);
// }
// m_mainLayer->addChild(node);
if (item->tags.size()) {
float x = m_height / 2 + this->getLogoSize() / 2 + 13.f;
for (auto& category : item->tags) {
auto node = TagNode::create(category);
node->setAnchorPoint({ .0f, .5f });
node->setPositionX(x);
node->setScale(.3f);
if (m_description) {
node->setPositionY(m_height / 2 - 23.f);
}
else {
node->setPositionY(m_height / 2 - 12.f);
}
this->addChild(node);
// x += node->getScaledContentSize().width + 5.f;
// }
// }
x += node->getScaledContentSize().width + 5.f;
}
}
this->updateState();
return true;
}
void IndexItemCell::updateState() {}
@ -310,9 +321,6 @@ CCNode* IndexItemCell::createLogo(CCSize const& size) {
// InvalidGeodeFileCell
InvalidGeodeFileCell::InvalidGeodeFileCell(const char* name, CCSize const& size)
: ModListCell(name, size) {}
void InvalidGeodeFileCell::onInfo(CCObject*) {
FLAlertLayer::create(
this, "Error Info",
@ -345,36 +353,29 @@ void InvalidGeodeFileCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) {
)->show();
}
(void)Loader::get()->refreshModsList();
m_list->refreshList();
m_layer->reloadList();
}
}
InvalidGeodeFileCell* InvalidGeodeFileCell::create(
ModListView* list, ModListDisplay display,
char const* key, CCSize const& size
bool InvalidGeodeFileCell::init(
InvalidGeodeFile const& info,
ModListLayer* list,
CCSize const& size
) {
auto ret = new InvalidGeodeFileCell(key, size);
if (ret && ret->init(list, display)) {
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
if (!ModListCell::init(list, size))
return false;
void InvalidGeodeFileCell::loadFromInfo(InvalidGeodeFile const& info) {
m_info = info;
m_mainLayer->setVisible(true);
auto menu = CCMenu::create();
menu->setPosition(m_width - m_height, m_height / 2);
m_mainLayer->addChild(menu);
this->addChild(menu);
auto titleLabel = CCLabelBMFont::create("Failed to Load", "bigFont.fnt");
titleLabel->setAnchorPoint({ .0f, .5f });
titleLabel->setScale(.5f);
titleLabel->setPosition(m_height / 2, m_height / 2 + 7.f);
m_mainLayer->addChild(titleLabel);
this->addChild(titleLabel);
auto pathLabel = CCLabelBMFont::create(
m_info.m_path.string().c_str(),
@ -384,7 +385,7 @@ void InvalidGeodeFileCell::loadFromInfo(InvalidGeodeFile const& info) {
pathLabel->setScale(.43f);
pathLabel->setPosition(m_height / 2, m_height / 2 - 7.f);
pathLabel->setColor({ 255, 255, 0 });
m_mainLayer->addChild(pathLabel);
this->addChild(pathLabel);
auto whySpr = ButtonSprite::create(
"Info", 0, 0, "bigFont.fnt", "GJ_button_01.png", 0, .8f
@ -395,6 +396,22 @@ void InvalidGeodeFileCell::loadFromInfo(InvalidGeodeFile const& info) {
whySpr, this, menu_selector(InvalidGeodeFileCell::onInfo)
);
menu->addChild(viewBtn);
return true;
}
InvalidGeodeFileCell* InvalidGeodeFileCell::create(
InvalidGeodeFile const& file,
ModListLayer* list,
CCSize const& size
) {
auto ret = new InvalidGeodeFileCell();
if (ret && ret->init(file, list, size)) {
ret->autorelease();
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
void InvalidGeodeFileCell::updateState() {}

View file

@ -8,33 +8,45 @@
USE_GEODE_NAMESPACE();
class ModListView;
class ModListLayer;
enum class ModListDisplay;
class ModListCell : public TableViewCell {
/**
* Base class for mod list items
*/
class ModListCell : public CCLayer {
protected:
ModListView* m_list;
float m_width;
float m_height;
ModListLayer* m_layer;
CCMenu* m_menu;
CCLabelBMFont* m_description;
CCMenuItemToggler* m_enableToggle = nullptr;
CCMenuItemSpriteExtra* m_unresolvedExMark;
ModListDisplay m_display;
ModListCell(char const* name, CCSize const& size);
bool init(ModListView* list, ModListDisplay display);
void setupInfo(ModInfo const& info, bool spaceForCategories);
bool init(ModListLayer* list, CCSize const& size);
void setupInfo(ModInfo const& info, bool spaceForTags);
void draw() override;
float getLogoSize() const;
public:
void updateBGColor(int index);
virtual void updateState() = 0;
virtual CCNode* createLogo(CCSize const& size) = 0;
};
/**
* Mod list item for a mod
*/
class ModCell : public ModListCell {
protected:
Mod* m_mod;
ModCell(char const* name, CCSize const& size);
bool init(
Mod* mod,
ModListLayer* list,
CCSize const& size
);
void onInfo(CCObject*);
void onEnable(CCObject*);
@ -42,50 +54,64 @@ protected:
public:
static ModCell* create(
ModListView* list, ModListDisplay display,
const char* key, CCSize const& size
Mod* mod,
ModListLayer* list,
CCSize const& size
);
void loadFromMod(Mod* mod);
void updateState() override;
CCNode* createLogo(CCSize const& size) override;
};
/**
* Mod list item for an index item
*/
class IndexItemCell : public ModListCell {
protected:
IndexItemHandle m_item;
IndexItemCell(char const* name, CCSize const& size);
bool init(
IndexItemHandle item,
ModListLayer* list,
CCSize const& size
);
void onInfo(CCObject*);
public:
static IndexItemCell* create(
ModListView* list, ModListDisplay display,
const char* key, CCSize const& size
IndexItemHandle item,
ModListLayer* list,
CCSize const& size
);
void loadFromItem(IndexItemHandle item);
void updateState() override;
CCNode* createLogo(CCSize const& size) override;
};
/**
* Mod list item for an invalid Geode package
*/
class InvalidGeodeFileCell : public ModListCell, public FLAlertLayerProtocol {
protected:
InvalidGeodeFile m_info;
InvalidGeodeFileCell(char const* name, CCSize const& size);
bool init(
InvalidGeodeFile const& file,
ModListLayer* list,
CCSize const& size
);
void onInfo(CCObject*);
void FLAlert_Clicked(FLAlertLayer*, bool btn2) override;
public:
static InvalidGeodeFileCell* create(
ModListView* list, ModListDisplay display,
const char* key, CCSize const& size
InvalidGeodeFile const& file,
ModListLayer* list,
CCSize const& size
);
void loadFromInfo(InvalidGeodeFile const& file);
void updateState() override;
CCNode* createLogo(CCSize const& size) override;
};

View file

@ -1,5 +1,5 @@
#include "ModListLayer.hpp"
#include "ModListCell.hpp"
#include "SearchFilterPopup.hpp"
#include <Geode/binding/ButtonSprite.hpp>
@ -14,11 +14,42 @@
#include <Geode/ui/Notification.hpp>
#include <Geode/utils/casts.hpp>
#include <Geode/loader/Dirs.hpp>
#include <Geode/loader/Loader.hpp>
#include <optional>
#include <Geode/ui/ListView.hpp>
static ModListType g_tab = ModListType::Installed;
static ModListLayer* g_instance = nullptr;
static void sortInstalledMods(std::vector<Mod*>& mods) {
if (!mods.size()) return;
// keep track of first object
size_t frontIndex = 0;
auto front = mods.front();
for (auto mod = mods.begin(); mod != mods.end(); mod++) {
// move mods with updates to front
if (auto item = Index::get()->getItem(*mod)) {
if (Index::get()->updateAvailable(item)) {
// swap first object and updatable mod
// if the updatable mod is the first object,
// nothing changes
std::rotate(mods.begin(), mod, mod + 1);
// get next object at front for next mod
// to sort
frontIndex++;
front = mods[frontIndex];
}
}
}
}
static std::vector<Mod*> sortedInstalledMods() {
auto mods = Loader::get()->getAllMods();
sortInstalledMods(mods);
return std::move(mods);
}
bool ModListLayer::init() {
if (!CCLayer::init()) return false;
@ -216,11 +247,15 @@ void ModListLayer::reloadList() {
m_list->removeFromParent();
}
auto items = ModListView::modsForType(g_tab);
auto items = this->createModCells(g_tab);
// create new list
auto list = ModListView::create(items, m_display);
list->setLayer(this);
auto list = ListView::create(
items,
this->getCellSize().height,
this->getListSize().width,
this->getListSize().height
);
// set list status
if (!items->count()) {
@ -306,6 +341,68 @@ void ModListLayer::reloadList() {
}
}
CCSize ModListLayer::getListSize() const {
return { 358.f, 190.f };
}
CCSize ModListLayer::getCellSize() const {
return {
getListSize().width,
m_display == ModListDisplay::Expanded ? 60.f : 40.f
};
}
ModListDisplay ModListLayer::getDisplay() const {
return m_display;
}
CCArray* ModListLayer::createModCells(ModListType type) {
auto mods = CCArray::create();
switch (type) {
default:
case ModListType::Installed: {
// failed mods first
for (auto const& mod : Loader::get()->getFailedMods()) {
mods->addObject(InvalidGeodeFileCell::create(mod, this, this->getCellSize()));
}
// internal geode representation always at the top
auto imod = Loader::getInternalMod();
mods->addObject(ModCell::create(imod, this, this->getCellSize()));
// then other mods
for (auto const& mod : sortedInstalledMods()) {
// if the mod is no longer installed nor
// loaded, it's as good as not existing
// (because it doesn't)
if (mod->isUninstalled() && !mod->isLoaded()) continue;
mods->addObject(ModCell::create(mod, this, this->getCellSize()));
}
} break;
case ModListType::Download: {
for (auto const& item : Index::get()->getItems()) {
mods->addObject(IndexItemCell::create(item, this, this->getCellSize()));
}
} break;
case ModListType::Featured: {
// todo: featured
} break;
}
return mods;
}
void ModListLayer::updateAllStates(ModListCell* toggled) {
for (auto cell : CCArrayExt<GenericListCell>(
m_list->m_listView->m_tableView->m_cellArray
)) {
auto node = static_cast<ModListCell*>(cell->getChildByID("mod-list-cell"));
if (toggled != node) {
node->updateState();
}
}
}
void ModListLayer::onCheckForUpdates(CCObject*) {
// store instance in a global so the
// layer stays in memory even if the
@ -331,10 +428,6 @@ void ModListLayer::onIndexUpdate(IndexUpdateEvent* event) {
}, event->status);
}
void ModListLayer::textChanged(CCTextInputNode* input) {
this->reloadList();
}
void ModListLayer::onExit(CCObject*) {
CCDirector::sharedDirector()->replaceScene(
CCTransitionFade::create(.5f, MenuLayer::scene(false))
@ -365,12 +458,6 @@ void ModListLayer::onResetSearch(CCObject*) {
m_searchInput->setString("");
}
void ModListLayer::keyDown(enumKeyCodes key) {
if (key == KEY_Escape) {
this->onExit(nullptr);
}
}
void ModListLayer::onTab(CCObject* pSender) {
if (pSender) {
g_tab = static_cast<ModListType>(pSender->getTag());
@ -394,6 +481,16 @@ void ModListLayer::onTab(CCObject* pSender) {
toggleTab(m_featuredTabBtn);
}
void ModListLayer::keyDown(enumKeyCodes key) {
if (key == KEY_Escape) {
this->onExit(nullptr);
}
}
void ModListLayer::textChanged(CCTextInputNode* input) {
this->reloadList();
}
ModListLayer* ModListLayer::create() {
// return global instance if one exists
if (g_instance) return g_instance;

View file

@ -1,13 +1,23 @@
#pragma once
#include "ModListView.hpp"
#include <Geode/binding/TextInputDelegate.hpp>
#include <Geode/loader/Index.hpp>
USE_GEODE_NAMESPACE();
class SearchFilterPopup;
class ModListCell;
enum class ModListType {
Installed,
Download,
Featured,
};
enum class ModListDisplay {
Concise,
Expanded,
};
class ModListLayer : public CCLayer, public TextInputDelegate {
protected:
@ -46,11 +56,18 @@ protected:
void createSearchControl();
void onIndexUpdate(IndexUpdateEvent* event);
CCArray* createModCells(ModListType type);
CCSize getCellSize() const;
CCSize getListSize() const;
friend class SearchFilterPopup;
public:
static ModListLayer* create();
static ModListLayer* scene();
void updateAllStates(ModListCell* except = nullptr);
ModListDisplay getDisplay() const;
void reloadList();
};

View file

@ -1,164 +0,0 @@
#include "ModListView.hpp"
#include "../info/CategoryNode.hpp"
#include "ModListLayer.hpp"
#include "ModListCell.hpp"
#include <Geode/binding/ButtonSprite.hpp>
#include <Geode/binding/CCMenuItemSpriteExtra.hpp>
#include <Geode/binding/TableView.hpp>
#include <Geode/binding/CCMenuItemToggler.hpp>
#include <Geode/binding/CCContentLayer.hpp>
#include <Geode/loader/Mod.hpp>
#include <Geode/utils/casts.hpp>
#include <Geode/utils/cocos.hpp>
#include <Geode/utils/string.hpp>
#include <Geode/loader/Index.hpp>
#include <InternalLoader.hpp>
void ModListView::updateAllStates(ModListCell* toggled) {
for (auto cell : CCArrayExt<ModListCell>(m_tableView->m_cellArray)) {
if (toggled != cell) {
cell->updateState();
}
}
}
void ModListView::setupList() {
m_itemSeparation = m_display == ModListDisplay::Expanded ? 60.f : 40.0f;
if (!m_entries->count()) return;
m_tableView->reloadData();
// fix content layer content size so the
// list is properly aligned to the top
auto coverage = calculateChildCoverage(m_tableView->m_contentLayer);
m_tableView->m_contentLayer->setContentSize({
-coverage.origin.x + coverage.size.width,
-coverage.origin.y + coverage.size.height
});
if (m_entries->count() == 1) {
m_tableView->moveToTopWithOffset(m_itemSeparation * 2);
}
else if (m_entries->count() == 2) {
m_tableView->moveToTopWithOffset(-m_itemSeparation);
}
else {
m_tableView->moveToTop();
}
}
TableViewCell* ModListView::getListCell(char const* key) {
return ModCell::create(this, m_display, key, { m_width, m_itemSeparation });
}
void ModListView::loadCell(TableViewCell* cell, unsigned int index) {
auto obj = m_entries->objectAtIndex(index);
if (auto mod = typeinfo_cast<ModObject*>(obj)) {
as<ModCell*>(cell)->loadFromMod(mod->mod);
}
if (auto mod = typeinfo_cast<IndexItemObject*>(obj)) {
// as<IndexItemCell*>(cell)->loadFromItem(mod->item);
}
if (auto failed = typeinfo_cast<InvalidGeodeFileObject*>(obj)) {
as<InvalidGeodeFileCell*>(cell)->loadFromInfo(failed->info);
}
as<ModListCell*>(cell)->updateBGColor(index);
}
static void sortInstalledMods(std::vector<Mod*>& mods) {
if (!mods.size()) return;
// keep track of first object
size_t frontIndex = 0;
auto front = mods.front();
for (auto mod = mods.begin(); mod != mods.end(); mod++) {
// move mods with updates to front
if (auto item = Index::get()->getItem(*mod)) {
if (Index::get()->updateAvailable(item)) {
// swap first object and updatable mod
// if the updatable mod is the first object,
// nothing changes
std::rotate(mods.begin(), mod, mod + 1);
// get next object at front for next mod
// to sort
frontIndex++;
front = mods[frontIndex];
}
}
}
}
static std::vector<Mod*> sortedInstalledMods() {
auto mods = Loader::get()->getAllMods();
sortInstalledMods(mods);
return std::move(mods);
}
bool ModListView::init(CCArray* mods, ModListDisplay display) {
m_display = display;
return CustomListView::init(mods, BoomListType::Default, 358.f, 190.f);
}
CCArray* ModListView::modsForType(ModListType type) {
auto mods = CCArray::create();
switch (type) {
default:
case ModListType::Installed: {
// failed mods first
for (auto const& mod : Loader::get()->getFailedMods()) {
mods->addObject(new InvalidGeodeFileObject(mod));
}
// internal geode representation always at the top
auto imod = Loader::getInternalMod();
mods->addObject(new ModObject(imod));
// then other mods
for (auto const& mod : sortedInstalledMods()) {
// if the mod is no longer installed nor
// loaded, it's as good as not existing
// (because it doesn't)
if (mod->isUninstalled() && !mod->isLoaded()) continue;
mods->addObject(new ModObject(mod));
}
} break;
case ModListType::Download: {
for (auto const& item : Index::get()->getItems()) {
mods->addObject(new IndexItemObject(item));
}
} break;
case ModListType::Featured: {
} break;
}
return mods;
}
ModListView* ModListView::create(CCArray* mods, ModListDisplay display) {
auto pRet = new ModListView;
if (pRet) {
if (pRet->init(mods, display)) {
pRet->autorelease();
return pRet;
}
}
CC_SAFE_DELETE(pRet);
return nullptr;
}
ModListView* ModListView::create(ModListType type, ModListDisplay display) {
return ModListView::create(modsForType(type), display);
}
void ModListView::setLayer(ModListLayer* layer) {
m_layer = layer;
}
void ModListView::refreshList() {
if (m_layer) {
m_layer->reloadList();
}
}

View file

@ -1,67 +0,0 @@
#pragma once
#include <Geode/binding/CustomListView.hpp>
#include <Geode/binding/FLAlertLayerProtocol.hpp>
#include <Geode/binding/TableViewCell.hpp>
#include <Geode/loader/Index.hpp>
#include <Geode/loader/Loader.hpp>
#include <optional>
USE_GEODE_NAMESPACE();
enum class ModListType {
Installed,
Download,
Featured,
};
enum class ModListDisplay {
Concise,
Expanded,
};
class ModListLayer;
class ModListCell;
// for passing invalid files as CCObject
struct InvalidGeodeFileObject : public CCObject {
InvalidGeodeFile info;
inline InvalidGeodeFileObject(InvalidGeodeFile const& info) : info(info) {
this->autorelease();
}
};
struct ModObject : public CCObject {
Mod* mod;
inline ModObject(Mod* mod) : mod(mod) {
this->autorelease();
}
};
struct IndexItemObject : public CCObject {
IndexItemHandle item;
inline IndexItemObject(IndexItemHandle item) : item(item) {
this->autorelease();
}
};
class ModListView : public CustomListView {
protected:
ModListLayer* m_layer = nullptr;
ModListDisplay m_display;
void setupList() override;
TableViewCell* getListCell(char const* key) override;
void loadCell(TableViewCell* cell, unsigned int index) override;
bool init(CCArray* mods, ModListDisplay display);
public:
static ModListView* create(CCArray* mods, ModListDisplay display);
static ModListView* create(ModListType type, ModListDisplay display);
static CCArray* modsForType(ModListType type);
void updateAllStates(ModListCell* except = nullptr);
void setLayer(ModListLayer* layer);
void refreshList();
};

View file

@ -1,8 +1,7 @@
#include "SearchFilterPopup.hpp"
#include "../info/CategoryNode.hpp"
#include "../info/TagNode.hpp"
#include "ModListLayer.hpp"
#include "ModListView.hpp"
#include <Geode/binding/GameToolbox.hpp>
#include <Geode/binding/CCMenuItemToggler.hpp>