new install list ui

This commit is contained in:
ConfiG 2023-08-13 04:37:33 +03:00
parent 0e1d639002
commit 3707418355
No known key found for this signature in database
GPG key ID: 44DA1983F524C11B
6 changed files with 716 additions and 55 deletions

View file

@ -20,6 +20,7 @@
#include <Geode/utils/ranges.hpp> #include <Geode/utils/ranges.hpp>
#include <Geode/utils/web.hpp> #include <Geode/utils/web.hpp>
#include <loader/LoaderImpl.hpp> #include <loader/LoaderImpl.hpp>
#include <ui/internal/list/InstallListPopup.hpp>
static constexpr int const TAG_CONFIRM_UNINSTALL = 5; static constexpr int const TAG_CONFIRM_UNINSTALL = 5;
static constexpr int const TAG_CONFIRM_UPDATE = 6; static constexpr int const TAG_CONFIRM_UPDATE = 6;
@ -749,43 +750,7 @@ void IndexItemInfoPopup::onInstallProgress(ModInstallEvent* event) {
} }
void IndexItemInfoPopup::onInstall(CCObject*) { void IndexItemInfoPopup::onInstall(CCObject*) {
auto list = Index::get()->getInstallList(m_item); InstallListPopup::create(m_item, [&](IndexInstallList const& list) {
if (!list) {
return FLAlertLayer::create(
"Unable to Install",
list.unwrapErr(),
"OK"
)->show();
}
FLAlertLayer::create(
this,
"Confirm Install",
fmt::format(
"The following mods will be installed:\n {}",
// le nest
ranges::join(
ranges::map<std::vector<std::string>>(
list.unwrap().list,
[](IndexItemHandle handle) {
return fmt::format(
" - <cr>{}</c> (<cy>{}</c>)",
handle->getMetadata().getName(),
handle->getMetadata().getID()
);
}
),
"\n "
)
),
"Cancel", "OK"
)->show();
}
void IndexItemInfoPopup::onCancel(CCObject*) {
Index::get()->cancelInstall(m_item);
}
void IndexItemInfoPopup::doInstall() {
if (m_latestVersionLabel) { if (m_latestVersionLabel) {
m_latestVersionLabel->setVisible(false); m_latestVersionLabel->setVisible(false);
} }
@ -797,13 +762,12 @@ void IndexItemInfoPopup::doInstall() {
m_installBtnSpr->setString("Cancel"); m_installBtnSpr->setString("Cancel");
m_installBtnSpr->setBG("GJ_button_06.png", false); m_installBtnSpr->setBG("GJ_button_06.png", false);
Index::get()->install(m_item); Index::get()->install(list);
})->show();
} }
void IndexItemInfoPopup::FLAlert_Clicked(FLAlertLayer*, bool btn2) { void IndexItemInfoPopup::onCancel(CCObject*) {
if (btn2) { Index::get()->cancelInstall(m_item);
this->doInstall();
}
} }
CCNode* IndexItemInfoPopup::createLogo(CCSize const& size) { CCNode* IndexItemInfoPopup::createLogo(CCSize const& size) {

View file

@ -89,7 +89,7 @@ public:
static LocalModInfoPopup* create(Mod* mod, ModListLayer* list); static LocalModInfoPopup* create(Mod* mod, ModListLayer* list);
}; };
class IndexItemInfoPopup : public ModInfoPopup, public FLAlertLayerProtocol { class IndexItemInfoPopup : public ModInfoPopup {
protected: protected:
IndexItemHandle m_item; IndexItemHandle m_item;
EventListener<ModInstallFilter> m_installListener; EventListener<ModInstallFilter> m_installListener;
@ -99,9 +99,6 @@ protected:
void onInstallProgress(ModInstallEvent* event); void onInstallProgress(ModInstallEvent* event);
void onInstall(CCObject*); void onInstall(CCObject*);
void onCancel(CCObject*); void onCancel(CCObject*);
void doInstall();
void FLAlert_Clicked(FLAlertLayer*, bool) override;
CCNode* createLogo(CCSize const& size) override; CCNode* createLogo(CCSize const& size) override;
ModMetadata getMetadata() const override; ModMetadata getMetadata() const override;

View file

@ -0,0 +1,327 @@
#include "InstallListCell.hpp"
#include "InstallListPopup.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>
#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() {
reinterpret_cast<StatsCell*>(this)->StatsCell::draw();
}
float InstallListCell::getLogoSize() const {
return m_height / 1.5f;
}
void InstallListCell::setupInfo(
std::string name,
std::optional<std::string> developer,
std::variant<VersionInfo, ComparableVersionInfo> version,
bool inactive
) {
m_menu = CCMenu::create();
m_menu->setPosition(m_width - 10.f, m_height / 2);
this->addChild(m_menu);
auto logoSize = this->getLogoSize();
auto logoSpr = this->createLogo({ logoSize, logoSize });
logoSpr->setPosition({ logoSize / 2 + 12.f, m_height / 2 });
auto logoSprColor = typeinfo_cast<CCRGBAProtocol*>(logoSpr);
if (inactive && logoSprColor) {
logoSprColor->setColor({ 163, 163, 163 });
}
this->addChild(logoSpr);
auto titleLabel = CCLabelBMFont::create(name.c_str(), "bigFont.fnt");
titleLabel->setAnchorPoint({ .0f, .5f });
titleLabel->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
titleLabel->setPositionY(m_height / 2);
titleLabel->limitLabelWidth(m_width / 2 - 70.f, .4f, .1f);
if (inactive) {
titleLabel->setColor({ 163, 163, 163 });
}
this->addChild(titleLabel);
m_developerBtn = nullptr;
if (developer) {
auto creatorStr = "by " + *developer;
auto creatorLabel = CCLabelBMFont::create(creatorStr.c_str(), "goldFont.fnt");
creatorLabel->setScale(.34f);
if (inactive) {
creatorLabel->setColor({ 163, 163, 163 });
}
m_developerBtn = CCMenuItemSpriteExtra::create(
creatorLabel, this, menu_selector(InstallListCell::onViewDev)
);
m_developerBtn->setPosition(
titleLabel->getPositionX() + titleLabel->getScaledContentSize().width + 3.f +
creatorLabel->getScaledContentSize().width / 2 -
m_menu->getPositionX(),
-0.5f
);
m_menu->addChild(m_developerBtn);
}
auto versionLabel = CCLabelBMFont::create(
std::holds_alternative<VersionInfo>(version) ?
std::get<VersionInfo>(version).toString(false).c_str() :
std::get<ComparableVersionInfo>(version).toString().c_str(),
"bigFont.fnt"
);
versionLabel->setAnchorPoint({ .0f, .5f });
versionLabel->setScale(.2f);
versionLabel->setPosition(
titleLabel->getPositionX() + titleLabel->getScaledContentSize().width + 3.f +
(m_developerBtn ? m_developerBtn->getScaledContentSize().width + 3.f : 0.f),
titleLabel->getPositionY() - 1.f
);
versionLabel->setColor({ 0, 255, 0 });
if (inactive) {
versionLabel->setColor({ 0, 163, 0 });
}
this->addChild(versionLabel);
if (!std::holds_alternative<VersionInfo>(version)) return;
if (auto tag = std::get<VersionInfo>(version).getTag()) {
auto tagLabel = TagNode::create(tag->toString());
tagLabel->setAnchorPoint({.0f, .5f});
tagLabel->setScale(.2f);
tagLabel->setPosition(
versionLabel->getPositionX() + versionLabel->getScaledContentSize().width + 3.f,
versionLabel->getPositionY()
);
this->addChild(tagLabel);
}
}
void InstallListCell::setupInfo(ModMetadata const& metadata, bool inactive) {
this->setupInfo(metadata.getName(), metadata.getDeveloper(), metadata.getVersion(), inactive);
}
void InstallListCell::onViewDev(CCObject*) {
DevProfilePopup::create(getDeveloper())->show();
}
bool InstallListCell::init(InstallListPopup* list, CCSize const& size) {
m_width = size.width;
m_height = size.height;
m_layer = list;
this->setContentSize(size);
this->setID("install-list-cell");
return true;
}
bool InstallListCell::isIncluded() {
return m_toggle && m_toggle->isOn();
}
// ModInstallListCell
bool ModInstallListCell::init(Mod* mod, InstallListPopup* list, CCSize const& size) {
if (!InstallListCell::init(list, size))
return false;
m_mod = mod;
this->setupInfo(mod->getMetadata(), true);
auto message = CCLabelBMFont::create("Installed", "bigFont.fnt");
message->setAnchorPoint({ 1.f, .5f });
message->setPositionX(m_menu->getPositionX());
message->setPositionY(16.f);
message->setScale(0.4f);
message->setColor({ 163, 163, 163 });
this->addChild(message);
return true;
}
ModInstallListCell* ModInstallListCell::create(Mod* mod, InstallListPopup* list, CCSize const& size) {
auto ret = new ModInstallListCell();
if (ret->init(mod, list, size)) {
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
CCNode* ModInstallListCell::createLogo(CCSize const& size) {
return geode::createModLogo(m_mod, size);
}
std::string ModInstallListCell::getID() const {
return m_mod->getID();
}
std::string ModInstallListCell::getDeveloper() const {
return m_mod->getDeveloper();
}
// IndexItemInstallListCell
bool IndexItemInstallListCell::init(
IndexItemHandle item,
ModMetadata::Dependency::Importance importance,
InstallListPopup* list,
CCSize const& size,
std::optional<bool> selected
) {
if (!InstallListCell::init(list, size))
return false;
m_item = item;
this->setupInfo(item->getMetadata(), item->isInstalled());
if (item->isInstalled()) {
auto message = CCLabelBMFont::create("Installed", "bigFont.fnt");
message->setAnchorPoint({ 1.f, .5f });
message->setPositionX(m_menu->getPositionX());
message->setPositionY(16.f);
message->setScale(0.4f);
message->setColor({ 163, 163, 163 });
this->addChild(message);
return true;
}
m_toggle = CCMenuItemToggler::createWithStandardSprites(
m_layer,
menu_selector(InstallListPopup::onCellToggle),
.6f
);
m_toggle->setPosition(-m_toggle->getScaledContentSize().width / 2, 0.f);
switch (importance) {
case ModMetadata::Dependency::Importance::Required:
m_toggle->setClickable(false);
m_toggle->toggle(true);
break;
case ModMetadata::Dependency::Importance::Recommended:
m_toggle->setClickable(true);
m_toggle->toggle(true);
break;
case ModMetadata::Dependency::Importance::Suggested:
m_toggle->setClickable(true);
m_toggle->toggle(false);
break;
}
if (m_item->getAvailablePlatforms().count(GEODE_PLATFORM_TARGET) == 0) {
m_toggle->setClickable(false);
m_toggle->toggle(false);
auto message = CCLabelBMFont::create("N/A", "bigFont.fnt");
message->setAnchorPoint({ 1.f, .5f });
message->setPositionX(m_menu->getPositionX() - m_toggle->getScaledContentSize().width - 5.f);
message->setPositionY(16.f);
message->setScale(0.4f);
message->setColor({ 240, 31, 31 });
this->addChild(message);
if (importance != ModMetadata::Dependency::Importance::Required) {
message->setCString("N/A (Optional)");
message->setColor({ 120, 15, 15 });
}
}
if (m_toggle->m_notClickable) {
m_toggle->m_offButton->setOpacity(100);
m_toggle->m_offButton->setColor(cc3x(155));
m_toggle->m_onButton->setOpacity(100);
m_toggle->m_onButton->setColor(cc3x(155));
}
if (!m_toggle->m_notClickable && selected) {
m_toggle->toggle(*selected);
}
m_menu->addChild(m_toggle);
return true;
}
IndexItemInstallListCell* IndexItemInstallListCell::create(
IndexItemHandle item,
ModMetadata::Dependency::Importance importance,
InstallListPopup* list,
CCSize const& size,
std::optional<bool> selected
) {
auto ret = new IndexItemInstallListCell();
if (ret->init(std::move(item), importance, list, size, selected)) {
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
CCNode* IndexItemInstallListCell::createLogo(CCSize const& size) {
return geode::createIndexItemLogo(m_item, size);
}
std::string IndexItemInstallListCell::getID() const {
return m_item->getMetadata().getID();
}
std::string IndexItemInstallListCell::getDeveloper() const {
return m_item->getMetadata().getDeveloper();
}
IndexItemHandle IndexItemInstallListCell::getItem() {
return m_item;
}
// UnknownInstallListCell
bool UnknownInstallListCell::init(
ModMetadata::Dependency const& dependency,
InstallListPopup* list,
CCSize const& size
) {
if (!InstallListCell::init(list, size))
return false;
m_dependency = dependency;
bool optional = dependency.importance != ModMetadata::Dependency::Importance::Required;
this->setupInfo(dependency.id, std::nullopt, dependency.version, optional);
auto message = CCLabelBMFont::create("Missing", "bigFont.fnt");
message->setAnchorPoint({ 1.f, .5f });
message->setPositionX(m_menu->getPositionX());
message->setPositionY(16.f);
message->setScale(0.4f);
message->setColor({ 240, 31, 31 });
if (optional) {
message->setCString("Missing (Optional)");
message->setColor({ 120, 15, 15 });
}
this->addChild(message);
return true;
}
UnknownInstallListCell* UnknownInstallListCell::create(
ModMetadata::Dependency const& dependency,
InstallListPopup* list,
CCSize const& size
) {
auto ret = new UnknownInstallListCell();
if (ret->init(dependency, list, size)) {
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
CCNode* UnknownInstallListCell::createLogo(CCSize const& size) {
return geode::createDefaultLogo(size);
}
std::string UnknownInstallListCell::getID() const {
return m_dependency.id;
}
std::string UnknownInstallListCell::getDeveloper() const {
return "";
}

View file

@ -0,0 +1,114 @@
#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 InstallListPopup;
/**
* Base class for install list items
*/
class InstallListCell : public CCLayer {
protected:
float m_width;
float m_height;
InstallListPopup* m_layer;
CCMenu* m_menu;
CCMenuItemSpriteExtra* m_developerBtn;
CCMenuItemToggler* m_toggle = nullptr;
void setupInfo(
std::string name,
std::optional<std::string> developer,
std::variant<VersionInfo, ComparableVersionInfo> version,
bool inactive
);
bool init(InstallListPopup* list, CCSize const& size);
void setupInfo(ModMetadata const& metadata, bool inactive);
void draw() override;
float getLogoSize() const;
void onViewDev(CCObject*);
public:
bool isIncluded();
virtual CCNode* createLogo(CCSize const& size) = 0;
[[nodiscard]] virtual std::string getID() const = 0;
[[nodiscard]] virtual std::string getDeveloper() const = 0;
};
/**
* Install list item for a mod
*/
class ModInstallListCell : public InstallListCell {
protected:
Mod* m_mod;
bool init(Mod* mod, InstallListPopup* list, CCSize const& size);
public:
static ModInstallListCell* create(Mod* mod, InstallListPopup* list, CCSize const& size);
CCNode* createLogo(CCSize const& size) override;
[[nodiscard]] std::string getID() const override;
[[nodiscard]] std::string getDeveloper() const override;
};
/**
* Install list item for an index item
*/
class IndexItemInstallListCell : public InstallListCell {
protected:
IndexItemHandle m_item;
bool init(
IndexItemHandle item,
ModMetadata::Dependency::Importance importance,
InstallListPopup* list,
CCSize const& size,
std::optional<bool> selected
);
public:
static IndexItemInstallListCell* create(
IndexItemHandle item,
ModMetadata::Dependency::Importance importance,
InstallListPopup* list,
CCSize const& size,
std::optional<bool> selected
);
CCNode* createLogo(CCSize const& size) override;
[[nodiscard]] std::string getID() const override;
[[nodiscard]] std::string getDeveloper() const override;
IndexItemHandle getItem();
};
/**
* Install list item for an unknown item
*/
class UnknownInstallListCell : public InstallListCell {
protected:
ModMetadata::Dependency m_dependency;
bool init(ModMetadata::Dependency const& dependency, InstallListPopup* list, CCSize const& size);
public:
static UnknownInstallListCell* create(
ModMetadata::Dependency const& dependency,
InstallListPopup* list,
CCSize const& size
);
CCNode* createLogo(CCSize const& size) override;
[[nodiscard]] std::string getID() const override;
[[nodiscard]] std::string getDeveloper() const override;
};

View file

@ -0,0 +1,229 @@
#include "InstallListPopup.hpp"
#include "InstallListCell.hpp"
#include <utility>
#include <queue>
bool InstallListPopup::setup(IndexItemHandle item, MiniFunction<void(IndexInstallList const&)> callback) {
m_noElasticity = true;
m_item = item;
m_callback = callback;
this->setTitle("Select Mods to Install");
this->createList();
auto installBtnSpr = IconButtonSprite::create(
"GE_button_01.png"_spr,
CCSprite::createWithSpriteFrameName("install.png"_spr),
"Install",
"bigFont.fnt"
);
installBtnSpr->setScale(.6f);
auto installBtn = CCMenuItemSpriteExtra::create(
installBtnSpr,
this,
menu_selector(InstallListPopup::onInstall)
);
installBtn->setPositionY(-m_bgSprite->getScaledContentSize().height / 2 + 22.f);
m_buttonMenu->addChild(installBtn);
return true;
}
void InstallListPopup::createList() {
auto winSize = CCDirector::sharedDirector()->getWinSize();
std::unordered_map<std::string, InstallListCell*> oldCells;
bool oldScrollAtBottom;
std::optional<float> oldScroll;
if (m_list) {
CCArray* oldEntries = m_list->m_entries;
for (size_t i = 0; i < oldEntries->count(); i++) {
auto* itemCell = typeinfo_cast<InstallListCell*>(oldEntries->objectAtIndex(i));
oldCells[itemCell->getID()] = itemCell;
}
auto content = m_list->m_tableView->m_contentLayer;
oldScroll = content->getPositionY();
oldScrollAtBottom = oldScroll >= 0.f;
if (!oldScrollAtBottom)
*oldScroll += content->getScaledContentSize().height;
m_list->removeFromParent();
}
if (m_listParent) {
m_listParent->removeFromParent();
}
m_listParent = CCNode::create();
m_mainLayer->addChild(m_listParent);
auto items = this->createCells(oldCells);
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);
// restore scroll on list recreation
// it's stored from the top unless was scrolled all the way to the bottom
if (oldScroll) {
auto content = m_list->m_tableView->m_contentLayer;
if (oldScrollAtBottom)
content->setPositionY(*oldScroll);
else
content->setPositionY(*oldScroll - content->getScaledContentSize().height);
}
addListBorders(m_listParent, winSize / 2, m_list->getScaledContentSize());
}
CCArray* InstallListPopup::createCells(std::unordered_map<std::string, InstallListCell*> const& oldCells) {
std::vector<InstallListCell*> top;
std::vector<InstallListCell*> middle;
std::vector<InstallListCell*> bottom;
std::queue<ModMetadata::Dependency> queue;
std::unordered_set<std::string> queued;
auto id = m_item->getMetadata().getID();
middle.push_back(IndexItemInstallListCell::create(
m_item,
ModMetadata::Dependency::Importance::Required,
this,
this->getCellSize(),
oldCells.contains(id) ? std::make_optional(oldCells.at(id)->isIncluded()) : std::nullopt
));
for (auto const& dep : m_item->getMetadata().getDependencies()) {
queue.push(dep);
}
auto index = Index::get();
while (!queue.empty()) {
auto const& item = queue.front();
if (queued.contains(item.id)) {
queue.pop();
continue;
}
queued.insert(item.id);
// installed
if (item.mod && !item.mod->isUninstalled()) {
bottom.push_back(ModInstallListCell::create(item.mod, this, this->getCellSize()));
for (auto const& dep : item.mod->getMetadata().getDependencies()) {
queue.push(dep);
}
queue.pop();
continue;
}
// on index
if (auto depItem = index->getItem(item.id, item.version)) {
auto cell = IndexItemInstallListCell::create(
depItem,
item.importance,
this,
this->getCellSize(),
oldCells.contains(item.id) ?
std::make_optional(oldCells.at(item.id)->isIncluded()) :
std::nullopt
);
// put missing dependencies at the top
if (depItem->getAvailablePlatforms().count(GEODE_PLATFORM_TARGET) == 0)
top.push_back(cell);
// put installed dependencies at the bottom
else if (depItem->isInstalled())
bottom.push_back(cell);
else
middle.push_back(cell);
if (!cell->isIncluded()) {
queue.pop();
continue;
}
for (auto const& dep : depItem->getMetadata().getDependencies()) {
queue.push(dep);
}
queue.pop();
continue;
}
// unknown (aka not installed and missing from index)
auto unknownCell = UnknownInstallListCell::create(item, this, this->getCellSize());
top.push_back(unknownCell);
queue.pop();
}
auto mods = CCArray::create();
for (auto const& item : top) {
mods->addObject(item);
}
for (auto const& item : middle) {
mods->addObject(item);
}
for (auto const& item : bottom) {
mods->addObject(item);
}
return mods;
}
// Getters
CCSize InstallListPopup::getListSize() const {
return { 340.f, 170.f };
}
CCSize InstallListPopup::getCellSize() const {
return { getListSize().width, 30.f };
}
// Callbacks
void InstallListPopup::onCellToggle(cocos2d::CCObject* obj) {
auto* toggler = typeinfo_cast<CCMenuItemToggler*>(obj);
if (toggler && !toggler->m_notClickable)
toggler->toggle(!toggler->isOn());
this->createList();
}
void InstallListPopup::onInstall(cocos2d::CCObject* obj) {
this->onBtn2(obj);
if (!m_callback)
return;
IndexInstallList list;
list.target = m_item;
CCArray* entries = m_list->m_entries;
for (size_t i = entries->count(); i > 0; i--) {
auto* itemCell = typeinfo_cast<IndexItemInstallListCell*>(entries->objectAtIndex(i - 1));
if (!itemCell || !itemCell->isIncluded())
continue;
IndexItemHandle item = itemCell->getItem();
list.list.push_back(item);
}
m_callback(list);
}
// Static
InstallListPopup* InstallListPopup::create(
IndexItemHandle item,
MiniFunction<void(IndexInstallList const&)> onInstall
) {
auto ret = new InstallListPopup();
if (!ret->init(380.f, 250.f, std::move(item), std::move(onInstall))) {
CC_SAFE_DELETE(ret);
return nullptr;
}
ret->autorelease();
return ret;
}

View file

@ -0,0 +1,30 @@
#pragma once
#include <Geode/ui/Popup.hpp>
#include <Geode/loader/Index.hpp>
#include "InstallListCell.hpp"
using namespace geode::prelude;
class InstallListPopup : public Popup<IndexItemHandle, MiniFunction<void(IndexInstallList const&)>> {
protected:
IndexItemHandle m_item;
CCNode* m_listParent;
ListView* m_list;
MiniFunction<void(IndexInstallList const&)> m_callback;
bool setup(IndexItemHandle item, MiniFunction<void(IndexInstallList const&)> callback) override;
void createList();
CCArray* createCells(std::unordered_map<std::string, InstallListCell*> const& oldCells);
CCSize getCellSize() const;
CCSize getListSize() const;
void onInstall(CCObject* obj);
public:
void onCellToggle(CCObject* obj);
static InstallListPopup* create(IndexItemHandle item, MiniFunction<void(IndexInstallList const&)> onInstall);
};