From 3707418355da8adb31182ad829031db1125de8e1 Mon Sep 17 00:00:00 2001 From: ConfiG Date: Sun, 13 Aug 2023 04:37:33 +0300 Subject: [PATCH] new install list ui --- loader/src/ui/internal/info/ModInfoPopup.cpp | 66 +--- loader/src/ui/internal/info/ModInfoPopup.hpp | 5 +- .../src/ui/internal/list/InstallListCell.cpp | 327 ++++++++++++++++++ .../src/ui/internal/list/InstallListCell.hpp | 114 ++++++ .../src/ui/internal/list/InstallListPopup.cpp | 229 ++++++++++++ .../src/ui/internal/list/InstallListPopup.hpp | 30 ++ 6 files changed, 716 insertions(+), 55 deletions(-) create mode 100644 loader/src/ui/internal/list/InstallListCell.cpp create mode 100644 loader/src/ui/internal/list/InstallListCell.hpp create mode 100644 loader/src/ui/internal/list/InstallListPopup.cpp create mode 100644 loader/src/ui/internal/list/InstallListPopup.hpp diff --git a/loader/src/ui/internal/info/ModInfoPopup.cpp b/loader/src/ui/internal/info/ModInfoPopup.cpp index ea77de18..f9a69f04 100644 --- a/loader/src/ui/internal/info/ModInfoPopup.cpp +++ b/loader/src/ui/internal/info/ModInfoPopup.cpp @@ -20,6 +20,7 @@ #include #include #include +#include static constexpr int const TAG_CONFIRM_UNINSTALL = 5; static constexpr int const TAG_CONFIRM_UPDATE = 6; @@ -749,63 +750,26 @@ void IndexItemInfoPopup::onInstallProgress(ModInstallEvent* event) { } void IndexItemInfoPopup::onInstall(CCObject*) { - auto list = Index::get()->getInstallList(m_item); - 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>( - list.unwrap().list, - [](IndexItemHandle handle) { - return fmt::format( - " - {} ({})", - handle->getMetadata().getName(), - handle->getMetadata().getID() - ); - } - ), - "\n " - ) - ), - "Cancel", "OK" - )->show(); + InstallListPopup::create(m_item, [&](IndexInstallList const& list) { + if (m_latestVersionLabel) { + m_latestVersionLabel->setVisible(false); + } + this->setInstallStatus(UpdateProgress(0, "Starting install")); + + m_installBtn->setTarget( + this, menu_selector(IndexItemInfoPopup::onCancel) + ); + m_installBtnSpr->setString("Cancel"); + m_installBtnSpr->setBG("GJ_button_06.png", false); + + Index::get()->install(list); + })->show(); } void IndexItemInfoPopup::onCancel(CCObject*) { Index::get()->cancelInstall(m_item); } -void IndexItemInfoPopup::doInstall() { - if (m_latestVersionLabel) { - m_latestVersionLabel->setVisible(false); - } - this->setInstallStatus(UpdateProgress(0, "Starting install")); - - m_installBtn->setTarget( - this, menu_selector(IndexItemInfoPopup::onCancel) - ); - m_installBtnSpr->setString("Cancel"); - m_installBtnSpr->setBG("GJ_button_06.png", false); - - Index::get()->install(m_item); -} - -void IndexItemInfoPopup::FLAlert_Clicked(FLAlertLayer*, bool btn2) { - if (btn2) { - this->doInstall(); - } -} - CCNode* IndexItemInfoPopup::createLogo(CCSize const& size) { return geode::createIndexItemLogo(m_item, size); } diff --git a/loader/src/ui/internal/info/ModInfoPopup.hpp b/loader/src/ui/internal/info/ModInfoPopup.hpp index 773c41b2..e5bd0aec 100644 --- a/loader/src/ui/internal/info/ModInfoPopup.hpp +++ b/loader/src/ui/internal/info/ModInfoPopup.hpp @@ -89,7 +89,7 @@ public: static LocalModInfoPopup* create(Mod* mod, ModListLayer* list); }; -class IndexItemInfoPopup : public ModInfoPopup, public FLAlertLayerProtocol { +class IndexItemInfoPopup : public ModInfoPopup { protected: IndexItemHandle m_item; EventListener m_installListener; @@ -99,9 +99,6 @@ protected: void onInstallProgress(ModInstallEvent* event); void onInstall(CCObject*); void onCancel(CCObject*); - void doInstall(); - - void FLAlert_Clicked(FLAlertLayer*, bool) override; CCNode* createLogo(CCSize const& size) override; ModMetadata getMetadata() const override; diff --git a/loader/src/ui/internal/list/InstallListCell.cpp b/loader/src/ui/internal/list/InstallListCell.cpp new file mode 100644 index 00000000..92803a61 --- /dev/null +++ b/loader/src/ui/internal/list/InstallListCell.cpp @@ -0,0 +1,327 @@ +#include "InstallListCell.hpp" +#include "InstallListPopup.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include "../info/TagNode.hpp" +#include "../info/DevProfilePopup.hpp" + +template +static bool tryOrAlert(Result const& res, char const* title) { + if (!res) { + FLAlertLayer::create(title, res.unwrapErr(), "OK")->show(); + } + return res.isOk(); +} + +// InstallListCell + +void InstallListCell::draw() { + reinterpret_cast(this)->StatsCell::draw(); +} + +float InstallListCell::getLogoSize() const { + return m_height / 1.5f; +} + +void InstallListCell::setupInfo( + std::string name, + std::optional developer, + std::variant 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(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(version) ? + std::get(version).toString(false).c_str() : + std::get(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(version)) return; + if (auto tag = std::get(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 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 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 ""; +} diff --git a/loader/src/ui/internal/list/InstallListCell.hpp b/loader/src/ui/internal/list/InstallListCell.hpp new file mode 100644 index 00000000..582ca24d --- /dev/null +++ b/loader/src/ui/internal/list/InstallListCell.hpp @@ -0,0 +1,114 @@ +#pragma once + +#include +#include +#include +#include +#include + +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 developer, + std::variant 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 selected + ); + +public: + static IndexItemInstallListCell* create( + IndexItemHandle item, + ModMetadata::Dependency::Importance importance, + InstallListPopup* list, + CCSize const& size, + std::optional 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; +}; diff --git a/loader/src/ui/internal/list/InstallListPopup.cpp b/loader/src/ui/internal/list/InstallListPopup.cpp new file mode 100644 index 00000000..fa639355 --- /dev/null +++ b/loader/src/ui/internal/list/InstallListPopup.cpp @@ -0,0 +1,229 @@ +#include "InstallListPopup.hpp" +#include "InstallListCell.hpp" + +#include +#include + +bool InstallListPopup::setup(IndexItemHandle item, MiniFunction 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 oldCells; + bool oldScrollAtBottom; + std::optional oldScroll; + if (m_list) { + CCArray* oldEntries = m_list->m_entries; + for (size_t i = 0; i < oldEntries->count(); i++) { + auto* itemCell = typeinfo_cast(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 const& oldCells) { + std::vector top; + std::vector middle; + std::vector bottom; + + std::queue queue; + std::unordered_set 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(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(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 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; +} diff --git a/loader/src/ui/internal/list/InstallListPopup.hpp b/loader/src/ui/internal/list/InstallListPopup.hpp new file mode 100644 index 00000000..7802bccb --- /dev/null +++ b/loader/src/ui/internal/list/InstallListPopup.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "InstallListCell.hpp" + +using namespace geode::prelude; + +class InstallListPopup : public Popup> { +protected: + IndexItemHandle m_item; + CCNode* m_listParent; + ListView* m_list; + MiniFunction m_callback; + + bool setup(IndexItemHandle item, MiniFunction callback) override; + + void createList(); + CCArray* createCells(std::unordered_map const& oldCells); + CCSize getCellSize() const; + CCSize getListSize() const; + + void onInstall(CCObject* obj); + +public: + void onCellToggle(CCObject* obj); + + static InstallListPopup* create(IndexItemHandle item, MiniFunction onInstall); +};