diff --git a/loader/include/Geode/platform/platform.hpp b/loader/include/Geode/platform/platform.hpp index 7da0eabc..6e257901 100644 --- a/loader/include/Geode/platform/platform.hpp +++ b/loader/include/Geode/platform/platform.hpp @@ -31,9 +31,13 @@ namespace geode { operator int() const { return m_value; } template<class T> - static Type cast(T t) { + static PlatformID from(T t) { return static_cast<Type>(t); } + template<class T> + T to() const { + return static_cast<T>(m_value); + } static constexpr const char* toString(Type lp) { switch (lp) { diff --git a/loader/src/index/Index.cpp b/loader/src/index/Index.cpp index 5388833f..76cc47b5 100644 --- a/loader/src/index/Index.cpp +++ b/loader/src/index/Index.cpp @@ -383,12 +383,25 @@ std::vector<IndexItem> const& Index::getItems() const { return m_items; } -std::vector<IndexItem> Index::getUninstalledItems() const { +std::vector<IndexItem> Index::getNoninstalledItems( + std::optional<std::unordered_set<PlatformID>> const& platforms +) const { std::vector<IndexItem> items; for (auto& item : m_items) { if (!Loader::get()->isModInstalled(item.m_info.m_id)) { - if (item.m_download.m_platforms.count(GEODE_PLATFORM_TARGET)) { - items.push_back(item); + // return whatever is available on requested platforms + if (platforms) { + for (auto& plat : platforms.value()) { + if (item.m_download.m_platforms.count(plat)) { + items.push_back(item); + } + } + } + // otherwise just return whatever is available on current platform + else { + if (item.m_download.m_platforms.count(GEODE_PLATFORM_TARGET)) { + items.push_back(item); + } } } } diff --git a/loader/src/index/Index.hpp b/loader/src/index/Index.hpp index e9021295..cb376748 100644 --- a/loader/src/index/Index.hpp +++ b/loader/src/index/Index.hpp @@ -2,6 +2,7 @@ #include <Geode/Geode.hpp> #include <mutex> +#include <optional> USE_GEODE_NAMESPACE(); @@ -132,7 +133,9 @@ public: static Index* get(); std::vector<IndexItem> const& getItems() const; - std::vector<IndexItem> getUninstalledItems() const; + std::vector<IndexItem> getNoninstalledItems( + std::optional<std::unordered_set<PlatformID>> const& platforms + ) const; bool isKnownItem(std::string const& id) const; IndexItem getKnownItem(std::string const& id) const; Result<InstallTicket*> installItems( diff --git a/loader/src/ui/internal/list/ModListLayer.cpp b/loader/src/ui/internal/list/ModListLayer.cpp index af40f5ae..c7d34ae9 100644 --- a/loader/src/ui/internal/list/ModListLayer.cpp +++ b/loader/src/ui/internal/list/ModListLayer.cpp @@ -233,8 +233,12 @@ void ModListLayer::reloadList() { } // create new list - const char* filter = m_searchInput ? m_searchInput->getString() : nullptr; - auto list = ModListView::create(g_tab, 358.f, 190.f, filter, m_searchFlags); + m_query.m_searchFilter = + m_searchInput && + m_searchInput->getString() && + strlen(m_searchInput->getString()) ? + std::optional<std::string>(m_searchInput->getString()) : std::nullopt; + auto list = ModListView::create(g_tab, 358.f, 190.f, m_query); list->setLayer(this); // set list status @@ -299,7 +303,7 @@ void ModListLayer::reloadList() { // check if the user has searched something, // and show visual indicator if so - auto hasQuery = filter && strlen(filter); + auto hasQuery = m_query.m_searchFilter.has_value(); m_searchBtn->setVisible(!hasQuery); m_searchClearBtn->setVisible(hasQuery); diff --git a/loader/src/ui/internal/list/ModListLayer.hpp b/loader/src/ui/internal/list/ModListLayer.hpp index eb9d17bc..995fe505 100644 --- a/loader/src/ui/internal/list/ModListLayer.hpp +++ b/loader/src/ui/internal/list/ModListLayer.hpp @@ -24,7 +24,7 @@ protected: CCNode* m_searchBG = nullptr; CCTextInputNode* m_searchInput = nullptr; LoadingCircle* m_loadingCircle = nullptr; - int m_searchFlags = ModListView::s_allFlags; + ModListQuery m_query; virtual ~ModListLayer(); diff --git a/loader/src/ui/internal/list/ModListView.cpp b/loader/src/ui/internal/list/ModListView.cpp index 30106b57..cec2b45a 100644 --- a/loader/src/ui/internal/list/ModListView.cpp +++ b/loader/src/ui/internal/list/ModListView.cpp @@ -367,20 +367,24 @@ void ModListView::loadCell(TableViewCell* cell, unsigned int index) { } } -bool ModListView::filter(ModInfo const& info, const char* searchFilter, int searchFlags) { - if (!searchFilter || !strlen(searchFilter)) return true; +bool ModListView::filter( + ModInfo const& info, + std::optional<std::string> const& searchFilter, + int searchFlags +) { + if (!searchFilter) return true; auto check = [searchFlags, searchFilter](SearchFlags flag, std::string const& name) -> bool { if (!(searchFlags & flag)) return false; return string_utils::contains( string_utils::toLower(name), - string_utils::toLower(searchFilter) + string_utils::toLower(searchFilter.value()) ); }; - if (check(SearchFlags::Name, info.m_name)) return true; - if (check(SearchFlags::ID, info.m_id)) return true; - if (check(SearchFlags::Developer, info.m_developer)) return true; - if (check(SearchFlags::Description, info.m_description)) return true; - if (check(SearchFlags::Details, info.m_details)) return true; + if (check(SearchFlag::Name, info.m_name)) return true; + if (check(SearchFlag::ID, info.m_id)) return true; + if (check(SearchFlag::Developer, info.m_developer)) return true; + if (check(SearchFlag::Description, info.m_description)) return true; + if (check(SearchFlag::Details, info.m_details)) return true; return false; } @@ -416,8 +420,7 @@ bool ModListView::init( ModListType type, float width, float height, - const char* searchFilter, - int searchFlags + ModListQuery query ) { if (!mods) { switch (type) { @@ -429,7 +432,7 @@ bool ModListView::init( } // internal geode representation always at the top auto imod = Loader::getInternalMod(); - if (this->filter(imod->getModInfo(), searchFilter, searchFlags)) { + if (this->filter(imod->getModInfo(), query.m_searchFilter, query.m_searchFlags)) { mods->addObject(new ModObject(imod)); } // then other mods @@ -438,7 +441,7 @@ bool ModListView::init( // loaded, it's as good as not existing // (because it doesn't) if (mod->isUninstalled() && !mod->isLoaded()) continue; - if (this->filter(mod->getModInfo(), searchFilter, searchFlags)) { + if (this->filter(mod->getModInfo(), query.m_searchFilter, query.m_searchFlags)) { mods->addObject(new ModObject(mod)); } } @@ -449,8 +452,10 @@ bool ModListView::init( case ModListType::Download: { mods = CCArray::create(); - for (auto const& item : Index::get()->getUninstalledItems()) { - mods->addObject(new ModObject(item)); + for (auto const& item : Index::get()->getNoninstalledItems(query.m_platforms)) { + if (this->filter(item.m_info, query.m_searchFilter, query.m_searchFlags)) { + mods->addObject(new ModObject(item)); + } } if (!mods->count()) { m_status = Status::NoModsFound; @@ -473,12 +478,11 @@ ModListView* ModListView::create( ModListType type, float width, float height, - const char* searchFilter, - int searchFlags + ModListQuery query ) { auto pRet = new ModListView; if (pRet) { - if (pRet->init(mods, type, width, height, searchFilter, searchFlags)) { + if (pRet->init(mods, type, width, height, query)) { pRet->autorelease(); return pRet; } @@ -491,10 +495,9 @@ ModListView* ModListView::create( ModListType type, float width, float height, - const char* searchFilter, - int searchFlags + ModListQuery query ) { - return ModListView::create(nullptr, type, width, height, searchFilter, searchFlags); + return ModListView::create(nullptr, type, width, height, query); } ModListView::Status ModListView::getStatus() const { diff --git a/loader/src/ui/internal/list/ModListView.hpp b/loader/src/ui/internal/list/ModListView.hpp index c7fb6973..e3623444 100644 --- a/loader/src/ui/internal/list/ModListView.hpp +++ b/loader/src/ui/internal/list/ModListView.hpp @@ -2,9 +2,12 @@ #include <Geode/Geode.hpp> #include <Index.hpp> +#include <optional> USE_GEODE_NAMESPACE(); +struct ModListQuery; + enum class ModListType { Installed, Download, @@ -74,10 +77,8 @@ public: static ModCell* create(ModListView* list, const char* key, CCSize size); }; -class ModListView : public CustomListView { -public: - // this is not enum class so | works - enum SearchFlags { +struct SearchFlag { + enum : int { Name = 0b1, ID = 0b10, Developer = 0b100, @@ -85,14 +86,24 @@ public: Description = 0b10000, Details = 0b100000, }; - static constexpr int s_allFlags = - SearchFlags::Name | - SearchFlags::ID | - SearchFlags::Developer | - SearchFlags::Credits | - SearchFlags::Description | - SearchFlags::Details; +}; +using SearchFlags = int; +static constexpr SearchFlags ALL_FLAGS = + SearchFlag::Name | + SearchFlag::ID | + SearchFlag::Developer | + SearchFlag::Credits | + SearchFlag::Description | + SearchFlag::Details; + +struct ModListQuery { + std::optional<std::string> m_searchFilter = std::nullopt; + int m_searchFlags = ALL_FLAGS; + std::unordered_set<PlatformID> m_platforms { GEODE_PLATFORM_TARGET }; +}; + +class ModListView : public CustomListView { protected: enum class Status { OK, @@ -113,10 +124,13 @@ protected: ModListType type, float width, float height, - const char* searchFilter, - int searchFlags + ModListQuery query + ); + bool filter( + ModInfo const& info, + std::optional<std::string> const& searchFilter, + SearchFlags searchFlags ); - bool filter(ModInfo const& info, const char* searchFilter, int searchFlags); public: static ModListView* create( @@ -124,15 +138,13 @@ public: ModListType type = ModListType::Installed, float width = 358.f, float height = 220.f, - const char* searchFilter = nullptr, - int searchFlags = 0 + ModListQuery query = ModListQuery() ); static ModListView* create( ModListType type, float width = 358.f, float height = 220.f, - const char* searchFilter = nullptr, - int searchFlags = 0 + ModListQuery query = ModListQuery() ); void updateAllStates(ModCell* toggled = nullptr); diff --git a/loader/src/ui/internal/list/SearchFilterPopup.cpp b/loader/src/ui/internal/list/SearchFilterPopup.cpp index e413150a..81d32af7 100644 --- a/loader/src/ui/internal/list/SearchFilterPopup.cpp +++ b/loader/src/ui/internal/list/SearchFilterPopup.cpp @@ -12,7 +12,7 @@ bool SearchFilterPopup::setup(ModListLayer* layer, ModListType type) { auto winSize = CCDirector::sharedDirector()->getWinSize(); auto pos = CCPoint { winSize.width / 2 - 145.f, winSize.height / 2 + 35.f }; - auto matchTitle = CCLabelBMFont::create("Search fields", "goldFont.fnt"); + auto matchTitle = CCLabelBMFont::create("Match fields", "goldFont.fnt"); matchTitle->setPosition(winSize.width / 2 - 90.f, winSize.height / 2 + 65.f); matchTitle->setScale(.5f); m_mainLayer->addChild(matchTitle); @@ -27,13 +27,12 @@ bool SearchFilterPopup::setup(ModListLayer* layer, ModListType type) { matchBG->setScale(.5f); m_mainLayer->addChild(matchBG); - this->addSearchMatch("Name", ModListView::SearchFlags::Name, pos); - this->addSearchMatch("ID", ModListView::SearchFlags::ID, pos); - this->addSearchMatch("Credits", ModListView::SearchFlags::Credits, pos); - this->addSearchMatch("Description", ModListView::SearchFlags::Description, pos); - this->addSearchMatch("Details", ModListView::SearchFlags::Details, pos); - this->addSearchMatch("Developer", ModListView::SearchFlags::Developer, pos); - + this->addSearchMatch("Name", SearchFlag::Name, pos); + this->addSearchMatch("ID", SearchFlag::ID, pos); + this->addSearchMatch("Credits", SearchFlag::Credits, pos); + this->addSearchMatch("Description", SearchFlag::Description, pos); + this->addSearchMatch("Details", SearchFlag::Details, pos); + this->addSearchMatch("Developer", SearchFlag::Developer, pos); auto line = CCSprite::createWithSpriteFrameName("edit_vLine_001.png"); line->setPosition({ winSize.width / 2, winSize.height / 2 - 20.f }); @@ -41,13 +40,35 @@ bool SearchFilterPopup::setup(ModListLayer* layer, ModListType type) { line->setOpacity(100); m_mainLayer->addChild(line); + auto platformTitle = CCLabelBMFont::create("Platforms", "goldFont.fnt"); + platformTitle->setPosition(winSize.width / 2 + 90.f, winSize.height / 2 + 65.f); + platformTitle->setScale(.5f); + m_mainLayer->addChild(platformTitle); + + auto platformBG = CCScale9Sprite::create( + "square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f } + ); + platformBG->setColor({ 0, 0, 0 }); + platformBG->setOpacity(90); + platformBG->setContentSize({ 290.f, 300.f }); + platformBG->setPosition(winSize.width / 2 + 90.f, winSize.height / 2 - 21.f); + platformBG->setScale(.5f); + m_mainLayer->addChild(platformBG); + + pos = CCPoint { winSize.width / 2 + 45.f, winSize.height / 2 + 35.f }; + + this->addPlatformToggle("Windows", PlatformID::Windows, pos); + this->addPlatformToggle("MacOS", PlatformID::MacOS, pos); + this->addPlatformToggle("iOS", PlatformID::iOS, pos); + this->addPlatformToggle("Android", PlatformID::Android, pos); + return true; } void SearchFilterPopup::addSearchMatch(const char* title, int flag, CCPoint& pos) { GameToolbox::createToggleButton( - title, menu_selector(SearchFilterPopup::onToggle), - m_modLayer->m_searchFlags & flag, + title, menu_selector(SearchFilterPopup::onSearchToggle), + m_modLayer->m_query.m_searchFlags & flag, m_buttonMenu, pos, this, m_buttonMenu, .5f, .5f, 100.f, { 10.f, .0f }, nullptr, false, flag, nullptr @@ -55,14 +76,46 @@ void SearchFilterPopup::addSearchMatch(const char* title, int flag, CCPoint& pos pos.y -= 22.5f; } -void SearchFilterPopup::onToggle(cocos2d::CCObject* pSender) { - if (as<CCMenuItemToggler*>(pSender)->isToggled()) { - m_modLayer->m_searchFlags &= ~pSender->getTag(); +void SearchFilterPopup::addPlatformToggle( + const char* title, + PlatformID id, + CCPoint& pos +) { + GameToolbox::createToggleButton( + title, menu_selector(SearchFilterPopup::onPlatformToggle), + m_modLayer->m_query.m_platforms.count(id), + m_buttonMenu, pos, this, + m_buttonMenu, .5f, .5f, 100.f, + { 10.f, .0f }, nullptr, false, id.to<int>(), nullptr + )->setTag(id.to<int>()); + pos.y -= 22.5f; +} + +void SearchFilterPopup::onSearchToggle(CCObject* sender) { + if (as<CCMenuItemToggler*>(sender)->isToggled()) { + m_modLayer->m_query.m_searchFlags &= ~sender->getTag(); } else { - m_modLayer->m_searchFlags |= pSender->getTag(); + m_modLayer->m_query.m_searchFlags |= sender->getTag(); } } +void SearchFilterPopup::onPlatformToggle(CCObject* sender) { + if (as<CCMenuItemToggler*>(sender)->isToggled()) { + m_modLayer->m_query.m_platforms.erase( + PlatformID::from(sender->getTag()) + ); + } else { + m_modLayer->m_query.m_platforms.insert( + PlatformID::from(sender->getTag()) + ); + } +} + +void SearchFilterPopup::onClose(CCObject* sender) { + Popup::onClose(sender); + m_modLayer->reloadList(); +} + SearchFilterPopup* SearchFilterPopup::create(ModListLayer* layer, ModListType type) { auto ret = new SearchFilterPopup(); if (ret && ret->init(350.f, 220.f, layer, type)) { diff --git a/loader/src/ui/internal/list/SearchFilterPopup.hpp b/loader/src/ui/internal/list/SearchFilterPopup.hpp index 713394f1..773f7a7f 100644 --- a/loader/src/ui/internal/list/SearchFilterPopup.hpp +++ b/loader/src/ui/internal/list/SearchFilterPopup.hpp @@ -13,8 +13,12 @@ protected: bool setup(ModListLayer* layer, ModListType type) override; void addSearchMatch(const char* title, int flag, CCPoint& pos); + void addPlatformToggle(const char* title, PlatformID id, CCPoint& pos); - void onToggle(cocos2d::CCObject*); + void onSearchToggle(CCObject*); + void onPlatformToggle(CCObject*); + + void onClose(CCObject*) override; public: static SearchFilterPopup* create(ModListLayer* layer, ModListType type);