From 5d15eb0215dc3435776049d0ccde2e4134f65f6d Mon Sep 17 00:00:00 2001 From: altalk23 <45172705+altalk23@users.noreply.github.com> Date: Tue, 5 Sep 2023 02:22:57 +0300 Subject: [PATCH] Implement UI for multiple version downloading (very cursed) --- loader/include/Geode/loader/Index.hpp | 10 + loader/src/loader/Index.cpp | 33 ++- loader/src/ui/internal/info/ModInfoPopup.cpp | 241 +++++++----------- loader/src/ui/internal/info/ModInfoPopup.hpp | 12 +- .../src/ui/internal/list/InstallListCell.cpp | 176 +++++++++---- .../src/ui/internal/list/InstallListCell.hpp | 37 ++- .../src/ui/internal/list/InstallListPopup.cpp | 72 +++++- .../src/ui/internal/list/InstallListPopup.hpp | 21 ++ loader/src/ui/internal/list/ModListLayer.cpp | 2 +- 9 files changed, 399 insertions(+), 205 deletions(-) diff --git a/loader/include/Geode/loader/Index.hpp b/loader/include/Geode/loader/Index.hpp index 5d48430f..fda97552 100644 --- a/loader/include/Geode/loader/Index.hpp +++ b/loader/include/Geode/loader/Index.hpp @@ -178,12 +178,22 @@ namespace geode { * Get all featured index items */ std::vector<IndexItemHandle> getFeaturedItems() const; + /** + * Get all latest index items + */ + std::vector<IndexItemHandle> getLatestItems() const; /** * Get all index items by a developer */ std::vector<IndexItemHandle> getItemsByDeveloper( std::string const& name ) const; + /** + * Get all index items for a specific mod + */ + std::vector<IndexItemHandle> getItemsByModID( + std::string const& modID + ) const; /** * Check if an item with this ID is found on the index, and optionally * provide the version sought after diff --git a/loader/src/loader/Index.cpp b/loader/src/loader/Index.cpp index a8e7ea9b..d4b0519f 100644 --- a/loader/src/loader/Index.cpp +++ b/loader/src/loader/Index.cpp @@ -183,7 +183,14 @@ Result<IndexItemHandle> IndexItem::Impl::create(ghc::filesystem::path const& roo } bool IndexItem::Impl::isInstalled() const { - return ghc::filesystem::exists(dirs::getModsDir() / (m_metadata.getID() + ".geode")); + if (!Loader::get()->isModInstalled(m_metadata.getID())) { + return false; + } + auto installed = Loader::get()->getInstalledMod(m_metadata.getID()); + if (installed->getVersion() != m_metadata.getVersion()) { + return false; + } + return true; } // Helpers @@ -449,6 +456,14 @@ std::vector<IndexItemHandle> Index::getItems() const { return res; } +std::vector<IndexItemHandle> Index::getLatestItems() const { + std::vector<IndexItemHandle> res; + for (auto& [modID, versions] : m_impl->m_items) { + res.push_back(this->getMajorItem(modID)); + } + return res; +} + std::vector<IndexItemHandle> Index::getFeaturedItems() const { std::vector<IndexItemHandle> res; for (auto& items : map::values(m_impl->m_items)) { @@ -475,6 +490,18 @@ std::vector<IndexItemHandle> Index::getItemsByDeveloper( return res; } +std::vector<IndexItemHandle> Index::getItemsByModID( + std::string const& modID +) const { + std::vector<IndexItemHandle> res; + if (m_impl->m_items.count(modID)) { + for (auto& [versionStr, item] : m_impl->m_items[modID]) { + res.push_back(item); + } + } + return res; +} + bool Index::isKnownItem( std::string const& id, std::optional<VersionInfo> version @@ -758,6 +785,10 @@ void Index::cancelInstall(IndexItemHandle item) { } void Index::install(IndexInstallList const& list) { + if (list.list.empty()) { + ModInstallEvent(list.target->getMetadata().getID(), UpdateFinished()).post(); + return; + } Loader::get()->queueInMainThread([this, list]() { m_impl->installNext(0, list); }); diff --git a/loader/src/ui/internal/info/ModInfoPopup.cpp b/loader/src/ui/internal/info/ModInfoPopup.cpp index 4173072d..10f1f38d 100644 --- a/loader/src/ui/internal/info/ModInfoPopup.cpp +++ b/loader/src/ui/internal/info/ModInfoPopup.cpp @@ -277,6 +277,95 @@ void ModInfoPopup::setInstallStatus(std::optional<UpdateProgress> const& progres } } +void ModInfoPopup::popupInstallItem(IndexItemHandle item) { + auto deps = item->getMetadata().getDependencies(); + enum class DepState { + None, + HasOnlyRequired, + HasOptional + } depState = DepState::None; + for (auto const& dep : deps) { + // resolved means it's already installed, so + // no need to ask the user whether they want to install it + if (Loader::get()->isModLoaded(dep.id)) + continue; + if (dep.importance != ModMetadata::Dependency::Importance::Required) { + depState = DepState::HasOptional; + break; + } + depState = DepState::HasOnlyRequired; + } + + std::string content; + char const* btn1; + char const* btn2; + switch (depState) { + case DepState::None: + content = fmt::format( + "Are you sure you want to install <cg>{}</c>?", + item->getMetadata().getName() + ); + btn1 = "Info"; + btn2 = "Install"; + break; + case DepState::HasOnlyRequired: + content = + "Installing this mod requires other mods to be installed. " + "Would you like to <cy>proceed</c> with the installation or " + "<cb>view</c> which mods are going to be installed?"; + btn1 = "View"; + btn2 = "Proceed"; + break; + case DepState::HasOptional: + content = + "This mod recommends installing other mods alongside it. " + "Would you like to continue with <cy>recommended settings</c> or " + "<cb>customize</c> which mods to install?"; + btn1 = "Customize"; + btn2 = "Recommended"; + break; + } + + createQuickPopup("Confirm Install", content, btn1, btn2, 320.f, [&](FLAlertLayer*, bool btn2) { + if (btn2) { + auto canInstall = Index::get()->canInstall(m_item); + if (!canInstall) { + FLAlertLayer::create( + "Unable to Install", + canInstall.unwrapErr(), + "OK" + )->show(); + return; + } + this->preInstall(); + Index::get()->install(m_item); + } + else { + InstallListPopup::create(m_item, [&](IndexInstallList const& list) { + this->preInstall(); + Index::get()->install(list); + })->show(); + } + }, true, true); +} + +void ModInfoPopup::preInstall() { + if (m_latestVersionLabel) { + m_latestVersionLabel->setVisible(false); + } + this->setInstallStatus(UpdateProgress(0, "Starting install")); + + m_installBtn->setTarget( + this, menu_selector(ModInfoPopup::onCancelInstall) + ); + m_installBtnSpr->setString("Cancel"); + m_installBtnSpr->setBG("GJ_button_06.png", false); +} + +void ModInfoPopup::onCancelInstall(CCObject*) { + Index::get()->cancelInstall(m_item); +} + // LocalModInfoPopup LocalModInfoPopup::LocalModInfoPopup() : m_installListener( @@ -493,61 +582,7 @@ void LocalModInfoPopup::onUpdateProgress(ModInstallEvent* event) { } void LocalModInfoPopup::onUpdate(CCObject*) { - auto list = Index::get()->getInstallList(m_item); - if (!list) { - return FLAlertLayer::create( - "Unable to Update", - list.unwrapErr(), - "OK" - )->show(); - } - auto layer = FLAlertLayer::create( - this, - "Confirm Update", - fmt::format( - "The following mods will be updated:\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" - ); - layer->setTag(TAG_CONFIRM_UPDATE); - layer->show(); -} - -void LocalModInfoPopup::onCancel(CCObject*) { - Index::get()->cancelInstall(m_item); -} - -void LocalModInfoPopup::doUpdate() { - if (m_latestVersionLabel) { - m_latestVersionLabel->setVisible(false); - } - - if (m_minorVersionLabel) { - m_minorVersionLabel->setVisible(false); - } - this->setInstallStatus(UpdateProgress(0, "Starting update")); - - m_installBtn->setTarget( - this, menu_selector(LocalModInfoPopup::onCancel) - ); - m_installBtnSpr->setString("Cancel"); - m_installBtnSpr->setBG("GJ_button_06.png", false); - - Index::get()->install(m_item); + this->popupInstallItem(m_item); } void LocalModInfoPopup::onUninstall(CCObject*) { @@ -631,12 +666,6 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) { } this->onClose(nullptr); } break; - - case TAG_CONFIRM_UPDATE: { - if (btn2) { - this->doUpdate(); - } - } break; } } @@ -687,7 +716,8 @@ bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) { if (!ModInfoPopup::init(item->getMetadata(), list)) return false; - if (item->isInstalled()) return true; + // bruh why is this here if we are allowing for browsing already installed mods + // if (item->isInstalled()) return true; m_installBtnSpr = IconButtonSprite::create( "GE_button_01.png"_spr, @@ -750,92 +780,7 @@ void IndexItemInfoPopup::onInstallProgress(ModInstallEvent* event) { } void IndexItemInfoPopup::onInstall(CCObject*) { - auto deps = m_item->getMetadata().getDependencies(); - enum class DepState { - None, - HasOnlyRequired, - HasOptional - } depState = DepState::None; - for (auto const& item : deps) { - // resolved means it's already installed, so - // no need to ask the user whether they want to install it - if (Loader::get()->isModLoaded(item.id)) - continue; - if (item.importance != ModMetadata::Dependency::Importance::Required) { - depState = DepState::HasOptional; - break; - } - depState = DepState::HasOnlyRequired; - } - - std::string content; - char const* btn1; - char const* btn2; - switch (depState) { - case DepState::None: - content = fmt::format( - "Are you sure you want to install <cg>{}</c>?", - m_item->getMetadata().getName() - ); - btn1 = "Info"; - btn2 = "Install"; - break; - case DepState::HasOnlyRequired: - content = - "Installing this mod requires other mods to be installed. " - "Would you like to <cy>proceed</c> with the installation or " - "<cb>view</c> which mods are going to be installed?"; - btn1 = "View"; - btn2 = "Proceed"; - break; - case DepState::HasOptional: - content = - "This mod recommends installing other mods alongside it. " - "Would you like to continue with <cy>recommended settings</c> or " - "<cb>customize</c> which mods to install?"; - btn1 = "Customize"; - btn2 = "Recommended"; - break; - } - - createQuickPopup("Confirm Install", content, btn1, btn2, 320.f, [&](FLAlertLayer*, bool btn2) { - if (btn2) { - auto canInstall = Index::get()->canInstall(m_item); - if (!canInstall) { - FLAlertLayer::create( - "Unable to Install", - canInstall.unwrapErr(), - "OK" - )->show(); - return; - } - this->preInstall(); - Index::get()->install(m_item); - } - else { - InstallListPopup::create(m_item, [&](IndexInstallList const& list) { - this->preInstall(); - Index::get()->install(list); - })->show(); - } - }, true, true); -} - -void IndexItemInfoPopup::preInstall() { - 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); -} - -void IndexItemInfoPopup::onCancel(CCObject*) { - Index::get()->cancelInstall(m_item); + this->popupInstallItem(m_item); } CCNode* IndexItemInfoPopup::createLogo(CCSize const& size) { diff --git a/loader/src/ui/internal/info/ModInfoPopup.hpp b/loader/src/ui/internal/info/ModInfoPopup.hpp index 062230a0..7870dc9c 100644 --- a/loader/src/ui/internal/info/ModInfoPopup.hpp +++ b/loader/src/ui/internal/info/ModInfoPopup.hpp @@ -38,6 +38,7 @@ protected: MDTextArea* m_detailsArea; MDTextArea* m_changelogArea = nullptr; Scrollbar* m_scrollbar; + IndexItemHandle m_item; void onChangelog(CCObject*); void onRepository(CCObject*); @@ -51,13 +52,16 @@ protected: void setInstallStatus(std::optional<UpdateProgress> const& progress); + void popupInstallItem(IndexItemHandle item); + void preInstall(); + void onCancelInstall(CCObject*); + virtual CCNode* createLogo(CCSize const& size) = 0; virtual ModMetadata getMetadata() const = 0; }; class LocalModInfoPopup : public ModInfoPopup, public FLAlertLayerProtocol { protected: - IndexItemHandle m_item; EventListener<ModInstallFilter> m_installListener; Mod* m_mod; @@ -74,8 +78,6 @@ protected: void onUpdateProgress(ModInstallEvent* event); void onUpdate(CCObject*); - void onCancel(CCObject*); - void doUpdate(); void FLAlert_Clicked(FLAlertLayer*, bool) override; @@ -91,16 +93,12 @@ public: class IndexItemInfoPopup : public ModInfoPopup { protected: - IndexItemHandle m_item; EventListener<ModInstallFilter> m_installListener; bool init(IndexItemHandle item, ModListLayer* list); void onInstallProgress(ModInstallEvent* event); void onInstall(CCObject*); - void onCancel(CCObject*); - - void preInstall(); 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 index 0ba4be3e..07daca20 100644 --- a/loader/src/ui/internal/list/InstallListCell.cpp +++ b/loader/src/ui/internal/list/InstallListCell.cpp @@ -8,7 +8,6 @@ #include <Geode/ui/GeodeUI.hpp> #include <loader/LoaderImpl.hpp> #include <utility> -#include "../info/TagNode.hpp" #include "../info/DevProfilePopup.hpp" // InstallListCell @@ -27,8 +26,11 @@ void InstallListCell::setupInfo( std::variant<VersionInfo, ComparableVersionInfo> version, bool inactive ) { + m_inactive = inactive; m_menu = CCMenu::create(); - m_menu->setPosition(m_width - 10.f, m_height / 2); + m_menu->setPosition(0, 0); + m_menu->setAnchorPoint({ .0f, .0f }); + m_menu->setContentSize({m_width, m_height}); this->addChild(m_menu); auto logoSize = this->getLogoSize(); @@ -41,15 +43,15 @@ void InstallListCell::setupInfo( } 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); + m_titleLabel = CCLabelBMFont::create(name.c_str(), "bigFont.fnt"); + m_titleLabel->setAnchorPoint({ .0f, .5f }); + m_titleLabel->setPositionX(m_height / 2 + logoSize / 2 + 13.f); + m_titleLabel->setPositionY(m_height / 2); + m_titleLabel->limitLabelWidth(m_width / 2 - 70.f, .4f, .1f); if (inactive) { - titleLabel->setColor({ 163, 163, 163 }); + m_titleLabel->setColor({ 163, 163, 163 }); } - this->addChild(titleLabel); + this->addChild(m_titleLabel); m_developerBtn = nullptr; if (developer) { @@ -64,43 +66,55 @@ void InstallListCell::setupInfo( 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_titleLabel->getPositionX() + m_titleLabel->getScaledContentSize().width + 3.f + + creatorLabel->getScaledContentSize().width / 2, + m_height / 2 ); m_menu->addChild(m_developerBtn); } - auto versionLabel = CCLabelBMFont::create( + this->setupVersion(version); +} + +void InstallListCell::setupVersion(std::variant<VersionInfo, ComparableVersionInfo> version) { + if (m_versionLabel) { + m_versionLabel->removeFromParent(); + m_versionLabel = nullptr; + } + if (m_tagLabel) { + m_tagLabel->removeFromParent(); + m_tagLabel = nullptr; + } + + m_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_versionLabel->setAnchorPoint({ .0f, .5f }); + m_versionLabel->setScale(.2f); + m_versionLabel->setPosition( + m_titleLabel->getPositionX() + m_titleLabel->getScaledContentSize().width + 3.f + (m_developerBtn ? m_developerBtn->getScaledContentSize().width + 3.f : 0.f), - titleLabel->getPositionY() - 1.f + m_titleLabel->getPositionY() - 1.f ); - versionLabel->setColor({ 0, 255, 0 }); - if (inactive) { - versionLabel->setColor({ 0, 163, 0 }); + m_versionLabel->setColor({ 0, 255, 0 }); + if (m_inactive) { + m_versionLabel->setColor({ 0, 163, 0 }); } - this->addChild(versionLabel); + this->addChild(m_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() + m_tagLabel = TagNode::create(tag->toString()); + m_tagLabel->setAnchorPoint({.0f, .5f}); + m_tagLabel->setScale(.2f); + m_tagLabel->setPosition( + m_versionLabel->getPositionX() + m_versionLabel->getScaledContentSize().width + 3.f, + m_versionLabel->getPositionY() ); - this->addChild(tagLabel); + this->addChild(m_tagLabel); } } @@ -134,7 +148,7 @@ bool ModInstallListCell::init(Mod* mod, InstallListPopup* list, CCSize const& si this->setupInfo(mod->getMetadata(), true); auto message = CCLabelBMFont::create("Installed", "bigFont.fnt"); message->setAnchorPoint({ 1.f, .5f }); - message->setPositionX(m_menu->getPositionX()); + message->setPositionX(m_width - 10.0f); message->setPositionY(16.f); message->setScale(0.4f); message->setColor({ 163, 163, 163 }); @@ -174,23 +188,38 @@ bool IndexItemInstallListCell::init( 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; - } + + // TODO: show installed label properly + // if (item->isInstalled()) { + // auto message = CCLabelBMFont::create("Installed", "bigFont.fnt"); + // message->setAnchorPoint({ 1.f, .5f }); + // message->setPositionX(m_width - 10.0f); + // 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); + m_toggle->setAnchorPoint({1.f, .5f}); + m_toggle->setPosition(m_width - 5, m_height / 2); + + // recycling sprites in my Geode?? noo never + auto versionSelectSpr = EditorButtonSprite::createWithSpriteFrameName( + "filters.png"_spr, 1.0f, EditorBaseColor::Gray + ); + versionSelectSpr->setScale(.7f); + + auto versionSelectBtn = + CCMenuItemSpriteExtra::create(versionSelectSpr, this, menu_selector(IndexItemInstallListCell::onSelectVersion)); + versionSelectBtn->setAnchorPoint({1.f, .5f}); + versionSelectBtn->setPosition({m_toggle->getPositionX() - m_toggle->getContentSize().width - 5, m_height / 2}); + m_menu->addChild(versionSelectBtn); switch (importance) { case ModMetadata::Dependency::Importance::Required: @@ -207,13 +236,18 @@ bool IndexItemInstallListCell::init( break; } + if (item->isInstalled()) { + m_toggle->setClickable(false); + m_toggle->toggle(true); + } + 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->setPositionX(m_width - 5.f); message->setPositionY(16.f); message->setScale(0.4f); message->setColor({ 240, 31, 31 }); @@ -269,6 +303,15 @@ IndexItemHandle IndexItemInstallListCell::getItem() { return m_item; } +void IndexItemInstallListCell::setVersionFromItem(IndexItemHandle item) { + this->setupVersion(item->getMetadata().getVersion()); + m_item = item; +} + +void IndexItemInstallListCell::onSelectVersion(CCObject*) { + SelectVersionPopup::create(m_item->getMetadata().getID(), this)->show(); +} + // UnknownInstallListCell bool UnknownInstallListCell::init( @@ -317,3 +360,50 @@ std::string UnknownInstallListCell::getID() const { std::string UnknownInstallListCell::getDeveloper() const { return ""; } + +// SelectVersionCell + +bool SelectVersionCell::init(IndexItemHandle item, SelectVersionPopup* versionPopup, CCSize const& size) { + if (!InstallListCell::init(nullptr, size)) + return false; + m_item = item; + m_versionPopup = versionPopup; + this->setupInfo(item->getMetadata(), item->isInstalled()); + + auto selectSpr = ButtonSprite::create( + "Select", 0, 0, "bigFont.fnt", "GJ_button_01.png", 0, .6f + ); + selectSpr->setScale(.6f); + + auto selectBtn = CCMenuItemSpriteExtra::create( + selectSpr, this, menu_selector(SelectVersionCell::onSelected) + ); + selectBtn->setAnchorPoint({1.f, .5f}); + selectBtn->setPosition({m_width - 5, m_height / 2}); + m_menu->addChild(selectBtn); + + return true; +} + +void SelectVersionCell::onSelected(CCObject*) { + m_versionPopup->selectItem(m_item); +} + +SelectVersionCell* SelectVersionCell::create(IndexItemHandle item, SelectVersionPopup* versionPopup, CCSize const& size) { + auto ret = new SelectVersionCell(); + if (ret->init(item, versionPopup, size)) { + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +CCNode* SelectVersionCell::createLogo(CCSize const& size) { + return geode::createIndexItemLogo(m_item, size); +} +std::string SelectVersionCell::getID() const { + return m_item->getMetadata().getID(); +} +std::string SelectVersionCell::getDeveloper() const { + return m_item->getMetadata().getDeveloper(); +} \ No newline at end of file diff --git a/loader/src/ui/internal/list/InstallListCell.hpp b/loader/src/ui/internal/list/InstallListCell.hpp index 582ca24d..f2fd677c 100644 --- a/loader/src/ui/internal/list/InstallListCell.hpp +++ b/loader/src/ui/internal/list/InstallListCell.hpp @@ -6,6 +6,8 @@ #include <Geode/loader/ModMetadata.hpp> #include <Geode/loader/Index.hpp> +#include "../info/TagNode.hpp" + using namespace geode::prelude; class InstallListPopup; @@ -17,10 +19,14 @@ class InstallListCell : public CCLayer { protected: float m_width; float m_height; - InstallListPopup* m_layer; - CCMenu* m_menu; - CCMenuItemSpriteExtra* m_developerBtn; + InstallListPopup* m_layer = nullptr; + CCMenu* m_menu = nullptr; + CCMenuItemSpriteExtra* m_developerBtn = nullptr; + CCLabelBMFont* m_titleLabel = nullptr; + CCLabelBMFont* m_versionLabel = nullptr; + TagNode* m_tagLabel = nullptr; CCMenuItemToggler* m_toggle = nullptr; + bool m_inactive = false; void setupInfo( std::string name, @@ -29,6 +35,8 @@ protected: bool inactive ); + void setupVersion(std::variant<VersionInfo, ComparableVersionInfo> version); + bool init(InstallListPopup* list, CCSize const& size); void setupInfo(ModMetadata const& metadata, bool inactive); void draw() override; @@ -90,6 +98,9 @@ public: [[nodiscard]] std::string getDeveloper() const override; IndexItemHandle getItem(); + void setVersionFromItem(IndexItemHandle item); + + void onSelectVersion(CCObject*); }; /** @@ -112,3 +123,23 @@ public: [[nodiscard]] std::string getID() const override; [[nodiscard]] std::string getDeveloper() const override; }; + +class SelectVersionPopup; +/** + * Select version list item + */ +class SelectVersionCell : public InstallListCell { +protected: + IndexItemHandle m_item; + SelectVersionPopup* m_versionPopup; + + bool init(IndexItemHandle item, SelectVersionPopup* versionPopup, CCSize const& size); + + void onSelected(CCObject*); +public: + static SelectVersionCell* create(IndexItemHandle item, SelectVersionPopup* versionPopup, CCSize const& size); + + CCNode* createLogo(CCSize const& size) override; + [[nodiscard]] std::string getID() const override; + [[nodiscard]] std::string getDeveloper() const override; +}; \ No newline at end of file diff --git a/loader/src/ui/internal/list/InstallListPopup.cpp b/loader/src/ui/internal/list/InstallListPopup.cpp index fa639355..e561ca9b 100644 --- a/loader/src/ui/internal/list/InstallListPopup.cpp +++ b/loader/src/ui/internal/list/InstallListPopup.cpp @@ -112,7 +112,8 @@ CCArray* InstallListPopup::createCells(std::unordered_map<std::string, InstallLi queued.insert(item.id); // installed - if (item.mod && !item.mod->isUninstalled()) { + // TODO: we should be able to select a different version even if its installed + if (/*item.mod && !item.mod->isUninstalled()*/item.mod->getMetadata().getID() == "geode.loader") { bottom.push_back(ModInstallListCell::create(item.mod, this, this->getCellSize())); for (auto const& dep : item.mod->getMetadata().getDependencies()) { queue.push(dep); @@ -204,7 +205,7 @@ void InstallListPopup::onInstall(cocos2d::CCObject* obj) { 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()) + if (!itemCell || !itemCell->isIncluded() || itemCell->getItem()->isInstalled()) continue; IndexItemHandle item = itemCell->getItem(); list.list.push_back(item); @@ -227,3 +228,70 @@ InstallListPopup* InstallListPopup::create( ret->autorelease(); return ret; } + +// SelectVersionPopup + +bool SelectVersionPopup::setup(std::string const& modID, IndexItemInstallListCell* installCell) { + m_modID = modID; + m_installCell = installCell; + + this->setTitle("Select Version"); + + this->createList(); + + return true; +} + +void SelectVersionPopup::createList() { + auto winSize = CCDirector::sharedDirector()->getWinSize(); + + if (m_listParent) { + m_listParent->removeFromParent(); + } + + m_listParent = CCNode::create(); + m_mainLayer->addChild(m_listParent); + + auto items = this->createCells(); + 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); + + addListBorders(m_listParent, winSize / 2, m_list->getScaledContentSize()); +} + +CCArray* SelectVersionPopup::createCells() { + auto cells = CCArray::create(); + for (auto& item : ranges::reverse(Index::get()->getItemsByModID(m_modID))) { + cells->addObject(SelectVersionCell::create(item, this, this->getCellSize())); + } + return cells; +} + +CCSize SelectVersionPopup::getCellSize() const { + return { getListSize().width, 30.f }; +} +CCSize SelectVersionPopup::getListSize() const { + return { 340.f, 170.f }; +} + +void SelectVersionPopup::selectItem(IndexItemHandle item) { + this->onBtn2(nullptr); + + m_installCell->setVersionFromItem(item); +} + +SelectVersionPopup* SelectVersionPopup::create(std::string const& modID, IndexItemInstallListCell* installCell) { + auto ret = new SelectVersionPopup(); + if (!ret->init(380.f, 250.f, modID, installCell)) { + CC_SAFE_DELETE(ret); + return nullptr; + } + ret->autorelease(); + return ret; +} \ No newline at end of file diff --git a/loader/src/ui/internal/list/InstallListPopup.hpp b/loader/src/ui/internal/list/InstallListPopup.hpp index 7802bccb..fbab3f5c 100644 --- a/loader/src/ui/internal/list/InstallListPopup.hpp +++ b/loader/src/ui/internal/list/InstallListPopup.hpp @@ -28,3 +28,24 @@ public: static InstallListPopup* create(IndexItemHandle item, MiniFunction<void(IndexInstallList const&)> onInstall); }; + +class SelectVersionPopup : public Popup<std::string const&, IndexItemInstallListCell*> { +protected: + std::string m_modID; + CCNode* m_listParent; + ListView* m_list; + IndexItemInstallListCell* m_installCell; + + bool setup(std::string const& modID, IndexItemInstallListCell* installCell) override; + + void createList(); + CCArray* createCells(); + CCSize getCellSize() const; + CCSize getListSize() const; + + +public: + void selectItem(IndexItemHandle item); + + static SelectVersionPopup* create(std::string const& modID, IndexItemInstallListCell* installCell); +}; diff --git a/loader/src/ui/internal/list/ModListLayer.cpp b/loader/src/ui/internal/list/ModListLayer.cpp index 3ff3b930..3a5cf1d3 100644 --- a/loader/src/ui/internal/list/ModListLayer.cpp +++ b/loader/src/ui/internal/list/ModListLayer.cpp @@ -198,7 +198,7 @@ CCArray* ModListLayer::createModCells(ModListType type, ModListQuery const& quer std::multimap<int, IndexItemHandle> sorted; auto index = Index::get(); - for (auto const& item : index->getItems()) { + for (auto const& item : index->getLatestItems()) { if (auto match = queryMatch(query, item)) { sorted.insert({ match.value(), item }); }