diff --git a/loader/CMakeLists.txt b/loader/CMakeLists.txt index 41cc8351..9470dfc4 100644 --- a/loader/CMakeLists.txt +++ b/loader/CMakeLists.txt @@ -66,6 +66,7 @@ file(GLOB SOURCES CONFIGURE_DEPENDS src/utils/*.cpp src/ui/*.cpp src/ui/nodes/*.cpp + src/ui/mods/*.cpp src/ui/internal/*.cpp src/ui/internal/info/*.cpp src/ui/internal/list/*.cpp diff --git a/loader/include/Geode/ui/General.hpp b/loader/include/Geode/ui/General.hpp index 941e2dbf..3214e71b 100644 --- a/loader/include/Geode/ui/General.hpp +++ b/loader/include/Geode/ui/General.hpp @@ -11,6 +11,31 @@ namespace geode { */ GEODE_DLL cocos2d::CCSprite* createLayerBG(); + enum class SideArt { + BottomLeft = 0b0001, + BottomRight = 0b0010, + TopLeft = 0b0100, + TopRight = 0b1000, + Bottom = 0b0011, + Top = 0b1100, + All = 0b1111, + }; + constexpr SideArt operator|(SideArt a, SideArt b) { + return static_cast<SideArt>(static_cast<int>(a) | static_cast<int>(b)); + } + constexpr bool operator&(SideArt a, SideArt b) { + return static_cast<bool>(static_cast<int>(a) & static_cast<int>(b)); + } + + /** + * Add side art (corner pieces) for a layer + * @param to Layer to add corner pieces to + * @param sides Which corners to populate; by default, populates all + * @param useAnchorLayout If true, `to` is given an `AnchorLayout` and the + * corners' positions are dynamically updated + */ + GEODE_DLL void addSideArt(cocos2d::CCNode* to, SideArt sides = SideArt::All, bool useAnchorLayout = false); + /** * Add the rounded comment borders to a node */ diff --git a/loader/include/Geode/ui/GeodeUI.hpp b/loader/include/Geode/ui/GeodeUI.hpp index c4552eaa..2a8fdae3 100644 --- a/loader/include/Geode/ui/GeodeUI.hpp +++ b/loader/include/Geode/ui/GeodeUI.hpp @@ -30,23 +30,14 @@ namespace geode { GEODE_DLL void openSettingsPopup(Mod* mod); /** * Create a default logo sprite - * @param size Size of the sprite */ - GEODE_DLL cocos2d::CCNode* createDefaultLogo( - cocos2d::CCSize const& size - ); + GEODE_DLL cocos2d::CCNode* createDefaultLogo(); /** * Create a logo sprite for a mod - * @param size Size of the sprite */ - GEODE_DLL cocos2d::CCNode* createModLogo( - Mod* mod, cocos2d::CCSize const& size - ); + GEODE_DLL cocos2d::CCNode* createModLogo(Mod* mod); /** * Create a logo sprite for an index item - * @param size Size of the sprite */ - GEODE_DLL cocos2d::CCNode* createIndexItemLogo( - IndexItemHandle item, cocos2d::CCSize const& size - ); + GEODE_DLL cocos2d::CCNode* createIndexItemLogo(IndexItemHandle item); } diff --git a/loader/resources/download.png b/loader/resources/download.png new file mode 100644 index 00000000..475d2b20 Binary files /dev/null and b/loader/resources/download.png differ diff --git a/loader/resources/mod.json.in b/loader/resources/mod.json.in index 0fc0bbbe..03dfe1f3 100644 --- a/loader/resources/mod.json.in +++ b/loader/resources/mod.json.in @@ -50,6 +50,9 @@ ], "BlankSheet": [ "blanks/*.png" + ], + "SwelveSheet": [ + "swelve/*.png" ] } }, diff --git a/loader/resources/mods-list-bottom.png b/loader/resources/mods-list-bottom.png new file mode 100644 index 00000000..049f94a2 Binary files /dev/null and b/loader/resources/mods-list-bottom.png differ diff --git a/loader/resources/mods-list-side.png b/loader/resources/mods-list-side.png new file mode 100644 index 00000000..e87ddbc3 Binary files /dev/null and b/loader/resources/mods-list-side.png differ diff --git a/loader/resources/mods-list-top.png b/loader/resources/mods-list-top.png new file mode 100644 index 00000000..a0cb5d61 Binary files /dev/null and b/loader/resources/mods-list-top.png differ diff --git a/loader/resources/search.png b/loader/resources/search.png new file mode 100644 index 00000000..c29bca66 Binary files /dev/null and b/loader/resources/search.png differ diff --git a/loader/resources/swelve/layer0.png b/loader/resources/swelve/layer0.png new file mode 100644 index 00000000..cbd6dc3f Binary files /dev/null and b/loader/resources/swelve/layer0.png differ diff --git a/loader/resources/swelve/layer1.png b/loader/resources/swelve/layer1.png new file mode 100644 index 00000000..8ae3edc3 Binary files /dev/null and b/loader/resources/swelve/layer1.png differ diff --git a/loader/resources/swelve/layer2.png b/loader/resources/swelve/layer2.png new file mode 100644 index 00000000..fe9221c3 Binary files /dev/null and b/loader/resources/swelve/layer2.png differ diff --git a/loader/resources/swelve/layer3.png b/loader/resources/swelve/layer3.png new file mode 100644 index 00000000..f1f6aac8 Binary files /dev/null and b/loader/resources/swelve/layer3.png differ diff --git a/loader/resources/tab-bg.png b/loader/resources/tab-bg.png new file mode 100644 index 00000000..afb22ad6 Binary files /dev/null and b/loader/resources/tab-bg.png differ diff --git a/loader/src/hooks/MenuLayer.cpp b/loader/src/hooks/MenuLayer.cpp index 2ff81663..a2a97b45 100644 --- a/loader/src/hooks/MenuLayer.cpp +++ b/loader/src/hooks/MenuLayer.cpp @@ -1,5 +1,4 @@ -#include "../ui/internal/list/ModListLayer.hpp" - +#include "../ui/mods/ModsLayer.hpp" #include <Geode/loader/Index.hpp> #include <Geode/modify/MenuLayer.hpp> #include <Geode/modify/Modify.hpp> @@ -334,6 +333,6 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> { } void onGeode(CCObject*) { - ModListLayer::scene(); + ModsLayer::scene(); } }; diff --git a/loader/src/ui/internal/GeodeUI.cpp b/loader/src/ui/internal/GeodeUI.cpp index 0115ea37..9010584d 100644 --- a/loader/src/ui/internal/GeodeUI.cpp +++ b/loader/src/ui/internal/GeodeUI.cpp @@ -66,66 +66,40 @@ void geode::openSettingsPopup(Mod* mod) { } } -CCNode* geode::createDefaultLogo(CCSize const& size) { +CCNode* geode::createDefaultLogo() { CCNode* spr = CCSprite::createWithSpriteFrameName("no-logo.png"_spr); if (!spr) { spr = CCLabelBMFont::create("OwO", "goldFont.fnt"); } - limitNodeSize(spr, size, 1.f, .01f); return spr; } -CCNode* geode::createModLogo(Mod* mod, CCSize const& size) { - CCNode* spr = mod == Mod::get() ? - CCSprite::createWithSpriteFrameName("geode-logo.png"_spr) : - CCSprite::create(fmt::format("{}/logo.png", mod->getID()).c_str()); - if (!spr) spr = CCSprite::createWithSpriteFrameName("no-logo.png"_spr); - if (!spr) spr = CCLabelBMFont::create("N/A", "goldFont.fnt"); - limitNodeSize(spr, size, 1.f, .01f); - spr->setPosition(size/2); - spr->setAnchorPoint({.5f, .5f}); - - auto node = CCNode::create(); - node->addChild(spr); - node->setContentSize(size); - return node; +CCNode* geode::createModLogo(Mod* mod) { + CCNode* ret = nullptr; + if (mod == Mod::get()) { + ret = CCSprite::createWithSpriteFrameName("geode-logo.png"_spr); + } + else { + ret = CCSprite::create(fmt::format("{}/logo.png", mod->getID()).c_str()); + } + if (!ret) { + ret = createDefaultLogo(); + } + return ret; } -CCNode* geode::createIndexItemLogo(IndexItemHandle item, CCSize const& size) { +CCNode* geode::createIndexItemLogo(IndexItemHandle item) { auto logoPath = ghc::filesystem::absolute(item->getRootPath() / "logo.png"); CCNode* spr = CCSprite::create(logoPath.string().c_str()); if (!spr) { - spr = CCSprite::createWithSpriteFrameName("no-logo.png"_spr); - } - if (!spr) { - spr = CCLabelBMFont::create("N/A", "goldFont.fnt"); + spr = createDefaultLogo(); } if (item->isFeatured()) { - auto glowSize = size + CCSize(4.f, 4.f); - auto logoGlow = CCSprite::createWithSpriteFrameName("logo-glow.png"_spr); - logoGlow->setScaleX(glowSize.width / logoGlow->getContentSize().width); - logoGlow->setScaleY(glowSize.height / logoGlow->getContentSize().height); - - // i dont know why + 1 is needed and its too late for me to figure out why - spr->setPosition( - logoGlow->getContentSize().width / 2 + 1, - logoGlow->getContentSize().height / 2 - 1 - ); - // scary mathematics - spr->setScaleX(size.width / spr->getContentSize().width / logoGlow->getScaleX()); - spr->setScaleY(size.height / spr->getContentSize().height / logoGlow->getScaleY()); - logoGlow->addChild(spr); + spr->setScaleX(logoGlow->getContentWidth() / spr->getContentWidth()); + spr->setScaleY(logoGlow->getContentHeight() / spr->getContentHeight()); + logoGlow->addChildAtPosition(spr, Anchor::Center); spr = logoGlow; } - else { - limitNodeSize(spr, size, 1.f, .01f); - } - spr->setPosition(size/2); - spr->setAnchorPoint({.5f, .5f}); - - auto node = CCNode::create(); - node->addChild(spr); - node->setContentSize(size); - return node; + return spr; } diff --git a/loader/src/ui/internal/info/ModInfoPopup.cpp b/loader/src/ui/internal/info/ModInfoPopup.cpp index 8e9740e1..735be430 100644 --- a/loader/src/ui/internal/info/ModInfoPopup.cpp +++ b/loader/src/ui/internal/info/ModInfoPopup.cpp @@ -444,7 +444,7 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) { } CCNode* LocalModInfoPopup::createLogo(CCSize const& size) { - return geode::createModLogo(m_mod, size); + return geode::createModLogo(m_mod); } ModMetadata LocalModInfoPopup::getMetadata() const { @@ -677,7 +677,7 @@ void IndexItemInfoPopup::onInstall(CCObject*) { } CCNode* IndexItemInfoPopup::createLogo(CCSize const& size) { - return geode::createIndexItemLogo(m_item, size); + return geode::createIndexItemLogo(m_item); } ModMetadata IndexItemInfoPopup::getMetadata() const { diff --git a/loader/src/ui/internal/list/InstallListCell.cpp b/loader/src/ui/internal/list/InstallListCell.cpp index 007a26d3..0eb8a0ba 100644 --- a/loader/src/ui/internal/list/InstallListCell.cpp +++ b/loader/src/ui/internal/list/InstallListCell.cpp @@ -168,7 +168,7 @@ ModInstallListCell* ModInstallListCell::create(Mod* mod, InstallListPopup* list, } CCNode* ModInstallListCell::createLogo(CCSize const& size) { - return geode::createModLogo(m_mod, size); + return geode::createModLogo(m_mod); } std::string ModInstallListCell::getID() const { return m_mod->getID(); @@ -292,7 +292,7 @@ IndexItemInstallListCell* IndexItemInstallListCell::create( } CCNode* IndexItemInstallListCell::createLogo(CCSize const& size) { - return geode::createIndexItemLogo(m_item, size); + return geode::createIndexItemLogo(m_item); } std::string IndexItemInstallListCell::getID() const { return m_item->getMetadata().getID(); @@ -354,7 +354,7 @@ UnknownInstallListCell* UnknownInstallListCell::create( } CCNode* UnknownInstallListCell::createLogo(CCSize const& size) { - return geode::createDefaultLogo(size); + return geode::createDefaultLogo(); } std::string UnknownInstallListCell::getID() const { return m_dependency.id; @@ -401,7 +401,7 @@ SelectVersionCell* SelectVersionCell::create(IndexItemHandle item, SelectVersion } CCNode* SelectVersionCell::createLogo(CCSize const& size) { - return geode::createIndexItemLogo(m_item, size); + return geode::createIndexItemLogo(m_item); } std::string SelectVersionCell::getID() const { return m_item->getMetadata().getID(); diff --git a/loader/src/ui/internal/list/ModListCell.cpp b/loader/src/ui/internal/list/ModListCell.cpp index 088f7f33..3e24d1e6 100644 --- a/loader/src/ui/internal/list/ModListCell.cpp +++ b/loader/src/ui/internal/list/ModListCell.cpp @@ -344,7 +344,7 @@ std::optional<ModMetadata> ModCell::getModMetadata() const { } CCNode* ModCell::createLogo(CCSize const& size) { - return geode::createModLogo(m_mod, size); + return geode::createModLogo(m_mod); } // IndexItemCell @@ -434,7 +434,7 @@ std::optional<ModMetadata> IndexItemCell::getModMetadata() const { } CCNode* IndexItemCell::createLogo(CCSize const& size) { - return geode::createIndexItemLogo(m_item, size); + return geode::createIndexItemLogo(m_item); } // InvalidGeodeFileCell diff --git a/loader/src/ui/mods/ModItem.cpp b/loader/src/ui/mods/ModItem.cpp new file mode 100644 index 00000000..ccd76d74 --- /dev/null +++ b/loader/src/ui/mods/ModItem.cpp @@ -0,0 +1,83 @@ +#include "ModItem.hpp" +#include <Geode/ui/GeodeUI.hpp> + +bool BaseModItem::init() { + if (!CCNode::init()) + return false; + + return true; +} + +void BaseModItem::setupCommonInfo() { + auto meta = this->getMetadata(); + + m_logo = this->createModLogo(); + this->addChild(m_logo); + + m_title = CCLabelBMFont::create(meta.getName().c_str(), "bigFont.fnt"); + m_title->setAnchorPoint({ .0f, .5f }); + this->addChild(m_title); + + auto by = "By " + ModMetadata::formatDeveloperDisplayString(meta.getDevelopers()); + auto developersBtn = CCMenuItemSpriteExtra::create( + CCLabelBMFont::create(by.c_str(), "goldFont.fnt"), + this, nullptr + ); + m_developers = CCMenu::create(); + m_developers->ignoreAnchorPointForPosition(false); + m_developers->setContentSize(developersBtn->getScaledContentSize()); + m_developers->addChildAtPosition(developersBtn, Anchor::Center); + m_developers->setAnchorPoint({ .0f, .5f }); + this->addChild(m_developers); +} + +void BaseModItem::updateSize(float width, bool big) { + this->setContentSize({ width, big ? 40.f : 25.f }); + + if (m_logo) { + auto logoSize = m_obContentSize.height - 5; + limitNodeSize(m_logo, { logoSize, logoSize }, 999, .1f); + m_logo->setPosition(m_obContentSize.height / 2 + 5, m_obContentSize.height / 2); + } + CCSize titleSpace { + m_obContentSize.width / 2 - m_obContentSize.height, + m_obContentSize.height / 2 + }; + if (m_title) { + m_title->setPosition(m_obContentSize.height + 10, m_obContentSize.height * .7f); + limitNodeSize(m_title, titleSpace, 1.f, .1f); + } + if (m_developers) { + m_developers->setPosition(m_obContentSize.height + 10, m_obContentSize.height * .3f); + limitNodeSize(m_developers, titleSpace, .6f, .1f); + } +} + +bool InstalledModItem::init(Mod* mod) { + if (!BaseModItem::init()) + return false; + + m_mod = mod; + + this->setupCommonInfo(); + + return true; +} + +InstalledModItem* InstalledModItem::create(Mod* mod) { + auto ret = new InstalledModItem(); + if (ret && ret->init(mod)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +ModMetadata InstalledModItem::getMetadata() const { + return m_mod->getMetadata(); +} + +CCNode* InstalledModItem::createModLogo() const { + return geode::createModLogo(m_mod); +} diff --git a/loader/src/ui/mods/ModItem.hpp b/loader/src/ui/mods/ModItem.hpp new file mode 100644 index 00000000..035ba1e4 --- /dev/null +++ b/loader/src/ui/mods/ModItem.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include <Geode/ui/General.hpp> + +using namespace geode::prelude; + +class BaseModItem : public CCNode { +protected: + CCNode* m_logo = nullptr; + CCNode* m_title = nullptr; + CCNode* m_developers = nullptr; + + bool init(); + + void setupCommonInfo(); + +public: + virtual ModMetadata getMetadata() const = 0; + virtual CCNode* createModLogo() const = 0; + + virtual void updateSize(float width, bool big); +}; + +class InstalledModItem : public BaseModItem { +protected: + Mod* m_mod; + + bool init(Mod* mod); + +public: + static InstalledModItem* create(Mod* mod); + + ModMetadata getMetadata() const override; + CCNode* createModLogo() const override; +}; diff --git a/loader/src/ui/mods/ModsLayer.cpp b/loader/src/ui/mods/ModsLayer.cpp new file mode 100644 index 00000000..ed889438 --- /dev/null +++ b/loader/src/ui/mods/ModsLayer.cpp @@ -0,0 +1,193 @@ +#include "ModsLayer.hpp" +#include "SwelvyBG.hpp" + +static bool BIG_VIEW = false; + +bool ModsLayer::init() { + if (!CCLayer::init()) + return false; + + auto winSize = CCDirector::get()->getWinSize(); + + this->addChild(SwelvyBG::create()); + + auto backMenu = CCMenu::create(); + backMenu->setContentWidth(100.f); + backMenu->setAnchorPoint({ .0f, .5f }); + + auto backSpr = CCSprite::createWithSpriteFrameName("GJ_arrow_03_001.png"); + auto backBtn = CCMenuItemSpriteExtra::create( + backSpr, this, menu_selector(ModsLayer::onBack) + ); + backMenu->addChild(backBtn); + backMenu->setLayout( + RowLayout::create() + ->setAxisAlignment(AxisAlignment::Start) + ); + + this->addChildAtPosition(backMenu, Anchor::TopLeft, ccp(12, -25), false); + + auto frame = CCNode::create(); + frame->setAnchorPoint({ .5f, .5f }); + frame->setContentSize({ 380, 205 }); + + auto frameBG = CCLayerColor::create({ 25, 17, 37, 255 }); + frameBG->setContentSize(frame->getContentSize()); + frameBG->ignoreAnchorPointForPosition(false); + frame->addChildAtPosition(frameBG, Anchor::Center); + + auto tabsTop = CCSprite::createWithSpriteFrameName("mods-list-top.png"_spr); + tabsTop->setAnchorPoint({ .5f, .0f }); + frame->addChildAtPosition(tabsTop, Anchor::Top, ccp(0, -2)); + + auto tabsLeft = CCSprite::createWithSpriteFrameName("mods-list-side.png"_spr); + tabsLeft->setScaleY(frame->getContentHeight() / tabsLeft->getContentHeight()); + frame->addChildAtPosition(tabsLeft, Anchor::Left, ccp(6, 0)); + + auto tabsRight = CCSprite::createWithSpriteFrameName("mods-list-side.png"_spr); + tabsRight->setFlipX(true); + tabsRight->setScaleY(frame->getContentHeight() / tabsRight->getContentHeight()); + frame->addChildAtPosition(tabsRight, Anchor::Right, ccp(-6, 0)); + + auto tabsBottom = CCSprite::createWithSpriteFrameName("mods-list-bottom.png"_spr); + tabsBottom->setAnchorPoint({ .5f, 1.f }); + frame->addChildAtPosition(tabsBottom, Anchor::Bottom, ccp(0, 2)); + + this->addChildAtPosition(frame, Anchor::Center, ccp(0, -10), false); + + m_list = ScrollLayer::create(frame->getContentSize() - ccp(24, 0)); + m_list->m_contentLayer->setLayout( + ColumnLayout::create() + ->setAxisReverse(true) + ->setAxisAlignment(AxisAlignment::End) + ->setAutoGrowAxis(frame->getContentHeight()) + ); + this->addChildAtPosition(m_list, Anchor::Center, -m_list->getScaledContentSize() / 2 - ccp(0, 10), false); + + auto mainTabs = CCMenu::create(); + mainTabs->setContentWidth(tabsTop->getContentWidth() - 45); + mainTabs->setAnchorPoint({ .5f, .0f }); + mainTabs->setPosition(frame->convertToWorldSpace(tabsTop->getPosition() + ccp(0, 10))); + + for (auto item : std::initializer_list<std::tuple<const char*, const char*, const char*>> { + { "download.png"_spr, "Installed", "installed" }, + { "GJ_bigStar_noShadow_001.png", "Featured", "featured" }, + { "GJ_sTrendingIcon_001.png", "Trending", "trending" }, + { "gj_folderBtn_001.png", "Mod Packs", "mod-packs" }, + { "search.png"_spr, "Search", "search" }, + }) { + const CCSize itemSize { 100, 40 }; + const CCSize iconSize { 20, 20 }; + + auto spr = CCNode::create(); + spr->setContentSize(itemSize); + spr->setAnchorPoint({ .5f, .5f }); + + auto disabledBG = CCScale9Sprite::createWithSpriteFrameName("tab-bg.png"_spr); + disabledBG->setContentSize(itemSize); + disabledBG->setID("disabled-bg"); + disabledBG->setColor({ 26, 24, 29 }); + spr->addChildAtPosition(disabledBG, Anchor::Center); + + auto enabledBG = CCScale9Sprite::createWithSpriteFrameName("tab-bg.png"_spr); + enabledBG->setContentSize(itemSize); + enabledBG->setID("enabled-bg"); + enabledBG->setColor({ 168, 147, 185 }); + spr->addChildAtPosition(enabledBG, Anchor::Center); + + auto icon = CCSprite::createWithSpriteFrameName(std::get<0>(item)); + limitNodeSize(icon, iconSize, 3.f, .1f); + spr->addChildAtPosition(icon, Anchor::Left, ccp(iconSize.width / 2 + 5, 0), false); + + auto title = CCLabelBMFont::create(std::get<1>(item), "bigFont.fnt"); + title->limitLabelWidth(spr->getContentWidth() - iconSize.width - 15, .55f, .1f); + title->setAnchorPoint({ .0f, .5f }); + spr->addChildAtPosition(title, Anchor::Left, ccp((iconSize.width + 10), 0), false); + + auto btn = CCMenuItemSpriteExtra::create(spr, this, menu_selector(ModsLayer::onTab)); + btn->setID(std::get<2>(item)); + mainTabs->addChild(btn); + m_tabs.push_back(btn); + } + + mainTabs->setLayout(RowLayout::create()); + this->addChild(mainTabs); + + this->gotoTab("installed"); + + this->setKeypadEnabled(true); + cocos::handleTouchPriority(this, true); + + return true; +} + +void ModsLayer::loadList(std::string const& id, bool update) { + if (m_currentList) { + m_currentList->scrollPosition = m_list->m_contentLayer->getPositionY(); + } + m_list->m_contentLayer->removeAllChildren(); + if (!m_listItemsCache.contains(id) || update) { + ListCache cache; + switch (hash(id.c_str())) { + case hash("installed"): { + for (auto mod : Loader::get()->getAllMods()) { + cache.items.push_back(InstalledModItem::create(mod)); + } + } break; + } + cache.scrollPosition = std::numeric_limits<float>::max(); + m_listItemsCache[id] = std::move(cache); + } + auto& cache = m_listItemsCache.at(id); + for (auto item : cache.items) { + m_list->m_contentLayer->addChild(item); + item->updateSize(m_list->getContentWidth(), BIG_VIEW); + } + m_list->m_contentLayer->updateLayout(); + auto listTopScrollPos = -m_list->m_contentLayer->getContentHeight() + m_list->getContentHeight(); + if (cache.scrollPosition > 0.f || cache.scrollPosition < listTopScrollPos) { + cache.scrollPosition = listTopScrollPos; + } + m_list->m_contentLayer->setPositionY(cache.scrollPosition); + m_currentList = &cache; +} + +void ModsLayer::gotoTab(std::string const& id) { + for (auto tab : m_tabs) { + auto selected = tab->getID() == id; + tab->getNormalImage()->getChildByID("disabled-bg")->setVisible(!selected); + tab->getNormalImage()->getChildByID("enabled-bg")->setVisible(selected); + tab->setEnabled(!selected); + } + this->loadList(id); +} + +void ModsLayer::onTab(CCObject* sender) { + this->gotoTab(static_cast<CCNode*>(sender)->getID()); +} + +void ModsLayer::keyBackClicked() { + this->onBack(nullptr); +} + +void ModsLayer::onBack(CCObject*) { + CCDirector::get()->replaceScene(CCTransitionFade::create(.5f, MenuLayer::scene(false))); +} + +ModsLayer* ModsLayer::create() { + auto ret = new ModsLayer(); + if (ret && ret->init()) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +ModsLayer* ModsLayer::scene() { + auto scene = CCScene::create(); + auto layer = ModsLayer::create(); + scene->addChild(layer); + CCDirector::sharedDirector()->replaceScene(CCTransitionFade::create(.5f, scene)); + return layer; +} diff --git a/loader/src/ui/mods/ModsLayer.hpp b/loader/src/ui/mods/ModsLayer.hpp new file mode 100644 index 00000000..e39254ae --- /dev/null +++ b/loader/src/ui/mods/ModsLayer.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include <Geode/ui/General.hpp> +#include <Geode/ui/ScrollLayer.hpp> +#include "ModItem.hpp" + +using namespace geode::prelude; + +struct ListCache { + std::vector<Ref<BaseModItem>> items; + float scrollPosition; +}; + +class ModsLayer : public CCLayer { +protected: + std::vector<CCMenuItemSpriteExtra*> m_tabs; + ScrollLayer* m_list; + ListCache* m_currentList = nullptr; + std::unordered_map<std::string, ListCache> m_listItemsCache; + + bool init(); + + void keyBackClicked() override; + void onTab(CCObject* sender); + void gotoTab(std::string const& id); + void loadList(std::string const& id, bool update = false); + +public: + static ModsLayer* create(); + static ModsLayer* scene(); + + void onBack(CCObject*); +}; diff --git a/loader/src/ui/mods/SwelvyBG.cpp b/loader/src/ui/mods/SwelvyBG.cpp new file mode 100644 index 00000000..806d641b --- /dev/null +++ b/loader/src/ui/mods/SwelvyBG.cpp @@ -0,0 +1,67 @@ +#include "SwelvyBG.hpp" +#include <random> + +bool SwelvyBG::init() { + if (!CCSpriteBatchNode::initWithTexture(CCTextureCache::get()->textureForKey("SwelveSheet.png"_spr), 20)) + return false; + + auto winSize = CCDirector::get()->getWinSize(); + this->setContentSize(winSize); + this->setAnchorPoint({ 0.f, 0.f }); + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> sign(0, 1); + std::uniform_real_distribution<float> dis(.05f, .15f); + + float y = m_obContentSize.height + 5; + for (auto layer : std::initializer_list<std::pair<ccColor3B, const char*>> { + { ccc3(244, 212, 142), "layer3.png"_spr }, + { ccc3(245, 174, 125), "layer0.png"_spr }, + { ccc3(236, 137, 124), "layer1.png"_spr }, + { ccc3(213, 105, 133), "layer2.png"_spr }, + { ccc3(173, 84, 146), "layer1.png"_spr }, + { ccc3(113, 74, 154), "layer0.png"_spr }, + }) { + float speed = dis(gen); + if (sign(gen) == 0) { + speed = -speed; + } + auto frame = CCSpriteFrameCache::get()->spriteFrameByName(layer.second); + auto repeatCount = static_cast<int>(floor(winSize.width / frame->getRect().size.width)) + 2; + for (int i = 0; i < repeatCount; i += 1) { + auto sprite = CCSprite::createWithSpriteFrame(frame); + sprite->setColor(layer.first); + sprite->setAnchorPoint({ (speed < 0 ? 0.f : 1.f), 1 }); + sprite->setPosition({ (i + 1) * (sprite->getContentWidth() - 1), y }); + sprite->schedule(schedule_selector(SwelvyBG::updateSpritePosition)); + sprite->setUserData(std::bit_cast<void*>(speed)); + sprite->setTag(repeatCount); + this->addChild(sprite); + } + y -= m_obContentSize.height / 6; + } + + return true; +} + +void SwelvyBG::updateSpritePosition(float dt) { + auto speed = std::bit_cast<float>(this->getUserData()); + this->setPositionX(this->getPositionX() - speed); + if (speed > 0 && this->getPositionX() < 0.f) { + this->setPositionX(this->getPositionX() + (this->getContentWidth() - 1) * this->getTag()); + } + else if (speed < 0 && this->getPositionX() > this->getParent()->getContentWidth()) { + this->setPositionX(this->getPositionX() - (this->getContentWidth() - 1) * this->getTag()); + } +} + +SwelvyBG* SwelvyBG::create() { + auto ret = new SwelvyBG(); + if (ret && ret->init()) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} diff --git a/loader/src/ui/mods/SwelvyBG.hpp b/loader/src/ui/mods/SwelvyBG.hpp new file mode 100644 index 00000000..77981742 --- /dev/null +++ b/loader/src/ui/mods/SwelvyBG.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include <Geode/ui/General.hpp> + +using namespace geode::prelude; + +class SwelvyBG : public CCSpriteBatchNode { +protected: + bool init(); + + void updateSpritePosition(float dt); + +public: + static SwelvyBG* create(); +}; diff --git a/loader/src/ui/nodes/General.cpp b/loader/src/ui/nodes/General.cpp index b5163d29..be47b48e 100644 --- a/loader/src/ui/nodes/General.cpp +++ b/loader/src/ui/nodes/General.cpp @@ -18,6 +18,29 @@ CCSprite* geode::createLayerBG() { return bg; } +void geode::addSideArt(CCNode* to, SideArt sides, bool useAnchorLayout) { + if (sides & SideArt::BottomLeft) { + auto spr = CCSprite::createWithSpriteFrameName("GJ_sideArt_001.png"); + to->addChildAtPosition(spr, Anchor::BottomLeft, ccp(35, 35), useAnchorLayout); + } + if (sides & SideArt::BottomRight) { + auto spr = CCSprite::createWithSpriteFrameName("GJ_sideArt_001.png"); + spr->setFlipX(true); + to->addChildAtPosition(spr, Anchor::BottomRight, ccp(-35, 35), useAnchorLayout); + } + if (sides & SideArt::TopLeft) { + auto spr = CCSprite::createWithSpriteFrameName("GJ_sideArt_001.png"); + spr->setFlipY(true); + to->addChildAtPosition(spr, Anchor::TopLeft, ccp(35, -35), useAnchorLayout); + } + if (sides & SideArt::TopRight) { + auto spr = CCSprite::createWithSpriteFrameName("GJ_sideArt_001.png"); + spr->setFlipX(true); + spr->setFlipY(true); + to->addChildAtPosition(spr, Anchor::TopRight, ccp(-35, -35), useAnchorLayout); + } +} + void geode::addListBorders(CCNode* to, CCPoint const& center, CCSize const& size) { // if the size is 346.f, the top aligns perfectly by default :3 if (size.width == 346.f) { diff --git a/loader/src/utils/cocos.cpp b/loader/src/utils/cocos.cpp index e7db8687..f55e779d 100644 --- a/loader/src/utils/cocos.cpp +++ b/loader/src/utils/cocos.cpp @@ -418,32 +418,16 @@ CCRect geode::cocos::calculateChildCoverage(CCNode* parent) { return calculateNodeCoverage(parent->getChildren()); } -void geode::cocos::limitNodeSize(cocos2d::CCNode* spr, cocos2d::CCSize const& size, float def, float min) { - spr->setScale(1.f); - auto [cwidth, cheight] = spr->getContentSize(); - - float scale = def; - if (size.height && size.height < cheight) { - scale = size.height / cheight; - } - if (size.width && size.width < cwidth) { - if (size.width / cwidth < scale) scale = size.width / cwidth; - } - if (def && def < scale) { - scale = def; - } - if (min && scale < min) { - scale = min; - } - spr->setScale(scale); +void geode::cocos::limitNodeSize(CCNode* spr, CCSize const& size, float def, float min) { + spr->setScale(clamp(std::min(size.height / spr->getContentHeight(), size.width / spr->getContentWidth()), min, def)); } -bool geode::cocos::nodeIsVisible(cocos2d::CCNode* node) { +bool geode::cocos::nodeIsVisible(CCNode* node) { if (!node->isVisible()) { return false; } - if (node->getParent()) { - return nodeIsVisible(node->getParent()); + if (auto parent = node->getParent()) { + return nodeIsVisible(parent); } return true; }