diff --git a/loader/include/Geode/utils/ColorProvider.hpp b/loader/include/Geode/utils/ColorProvider.hpp index ff98c472e..5b26dd913 100644 --- a/loader/include/Geode/utils/ColorProvider.hpp +++ b/loader/include/Geode/utils/ColorProvider.hpp @@ -104,6 +104,25 @@ namespace geode { */ cocos2d::ccColor3B color3b(std::string_view id) const; }; + + class ThemeIDProvidingEvent final : public Event { + public: + // listener params idOut + using Event::Event; + }; + + /** + * An event that gets posted for someone to request a node for a specific ID. This is + * used in Geode themes to provide custom nodes. + */ + class NodeProvidingEvent final : public GlobalEvent { + public: + // listener params node + // filter params id + using GlobalEvent::GlobalEvent; + + bool send(cocos2d::CCNode*& nodeOut); + }; } inline cocos2d::ccColor4B operator""_cc4b_gd(const char* str, size_t) { diff --git a/loader/resources/images/sapphire-bg.png b/loader/resources/images/sapphire-bg.png new file mode 100644 index 000000000..54d28acc8 Binary files /dev/null and b/loader/resources/images/sapphire-bg.png differ diff --git a/loader/resources/logos/sapphire-logo-outline-gold.png b/loader/resources/logos/sapphire-logo-outline-gold.png new file mode 100644 index 000000000..6e7f59603 Binary files /dev/null and b/loader/resources/logos/sapphire-logo-outline-gold.png differ diff --git a/loader/resources/logos/sapphire-logo.png b/loader/resources/logos/sapphire-logo.png new file mode 100644 index 000000000..e04884a50 Binary files /dev/null and b/loader/resources/logos/sapphire-logo.png differ diff --git a/loader/resources/mod.json.in b/loader/resources/mod.json.in index 56e88ee46..69569d4fb 100644 --- a/loader/resources/mod.json.in +++ b/loader/resources/mod.json.in @@ -92,11 +92,17 @@ "name": "Disable Crash Popup", "description": "Disables the popup at startup asking if you'd like to send a bug report; intended for developers" }, - "enable-geode-theme": { - "type": "bool", - "default": true, - "name": "Enable Geode-Themed Colors", - "description": "When enabled, the Geode menu has a Geode-themed color scheme. This does not affect any other menus!" + "used-theme": { + "type": "string", + "name": "Used Theme", + "description": "The theme used for Geode menu. This does not affect any other menus!", + "default": "Geode", + "one-of": [ + "Geode", + "Geometry Dash", + "Rainbow", + "Sapphire" + ] }, "infinite-local-mods-list": { "type": "bool", diff --git a/loader/resources/mods-list-bottom-sapphire.png b/loader/resources/mods-list-bottom-sapphire.png new file mode 100644 index 000000000..927a60a0d Binary files /dev/null and b/loader/resources/mods-list-bottom-sapphire.png differ diff --git a/loader/resources/mods-list-side-sapphire.png b/loader/resources/mods-list-side-sapphire.png new file mode 100644 index 000000000..478d7998a Binary files /dev/null and b/loader/resources/mods-list-side-sapphire.png differ diff --git a/loader/resources/mods-list-top-sapphire.png b/loader/resources/mods-list-top-sapphire.png new file mode 100644 index 000000000..4b727233b Binary files /dev/null and b/loader/resources/mods-list-top-sapphire.png differ diff --git a/loader/src/hooks/MenuLayer.cpp b/loader/src/hooks/MenuLayer.cpp index e8c483039..4dd34a7c9 100644 --- a/loader/src/hooks/MenuLayer.cpp +++ b/loader/src/hooks/MenuLayer.cpp @@ -15,6 +15,7 @@ #include #include #include +#include using namespace geode::prelude; @@ -36,7 +37,7 @@ struct CustomMenuLayer : Modify { struct Fields { bool m_menuDisabled = false; - CCSprite* m_geodeButton = nullptr; + CCNode* m_geodeButton = nullptr; async::TaskHolder> m_updateCheckTask; }; @@ -53,16 +54,45 @@ struct CustomMenuLayer : Modify { // add geode button if (!m_fields->m_menuDisabled) { - m_fields->m_geodeButton = CircleButtonSprite::createWithSpriteFrameName( - "geode-logo-outline-gold.png"_spr, - .95f, - CircleBaseColor::Green, - CircleBaseSize::MediumAlt - ); + auto listener = NodeProvidingEvent("geode-button-sprite"_spr).listen([this](cocos2d::CCNode*& nodeOut, std::string_view theme) -> void { + if (nodeOut) return; // someone overrode it already + CCSprite* geodeButton; + if (theme == "sapphire") { + geodeButton = CircleButtonSprite::createWithSpriteFrameName( + "sapphire-logo-outline-gold.png"_spr, + .95f, + CircleBaseColor::Green, + CircleBaseSize::MediumAlt + ); + } + else { + geodeButton = CircleButtonSprite::createWithSpriteFrameName( + "geode-logo-outline-gold.png"_spr, + .95f, + CircleBaseColor::Green, + CircleBaseSize::MediumAlt + ); + } + + if (!geodeButton || geodeButton->isUsingFallback()) { + nodeOut = nullptr; + } + else { + nodeOut = geodeButton; + } + }, Priority::Last); + + cocos2d::CCNode* geodeBtn = nullptr; + NodeProvidingEvent("geode-button-sprite"_spr).send(geodeBtn); + auto geodeBtnSelector = &CustomMenuLayer::onGeode; - if (!m_fields->m_geodeButton || m_fields->m_geodeButton->isUsingFallback()) { - geodeBtnSelector = &CustomMenuLayer::onMissingTextures; + if (geodeBtn) { + m_fields->m_geodeButton = static_cast(geodeBtn); + } + else { + log::warn("No node provided for geode-button-sprite, using fallback instead"); m_fields->m_geodeButton = ButtonSprite::create("!!"); + geodeBtnSelector = &CustomMenuLayer::onMissingTextures; } auto bottomMenu = static_cast(this->getChildByID("bottom-menu")); diff --git a/loader/src/ui/GeodeUI.cpp b/loader/src/ui/GeodeUI.cpp index 8d5beb7e7..375177c55 100644 --- a/loader/src/ui/GeodeUI.cpp +++ b/loader/src/ui/GeodeUI.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include "mods/GeodeStyle.hpp" @@ -264,10 +265,26 @@ class ModLogoSprite : public CCNodeRGBA { if (!mod->isInternal()) { m_sprite->loadFromFile(dirs::getModRuntimeDir() / mod->getID() / "logo.png"); } else { - if (Mod::get()->getSavedValue("alternate-geode-style", false)) { - m_sprite->initWithSpriteFrameName("geode-logo-alternate.png"_spr); - } - else { + // We only support lazySprite for this because i have no idea how to better integrate this + auto listener = NodeProvidingEvent("geode-mod-logo-sprite"_spr).listen([this](cocos2d::CCNode*& nodeOut, std::string_view theme) -> void { + auto sprite = typeinfo_cast(nodeOut); + if (!sprite) return; // someone overrode it, which will probably break maybe + + if (theme == "rainbow") { + m_sprite->initWithSpriteFrameName("geode-logo-alternate.png"_spr); + } + else if (theme == "sapphire") { + m_sprite->initWithSpriteFrameName("sapphire-logo.png"_spr); + } + else { + m_sprite->initWithSpriteFrameName("geode-logo.png"_spr); + } + }, Priority::Last); + CCNode* ptr = m_sprite; + NodeProvidingEvent("geode-mod-logo-sprite"_spr).send(ptr); + + // fallback + if (ptr != m_sprite) { m_sprite->initWithSpriteFrameName("geode-logo.png"_spr); } } diff --git a/loader/src/ui/mods/GeodeStyle.cpp b/loader/src/ui/mods/GeodeStyle.cpp index 2ff19290f..f297aee1b 100644 --- a/loader/src/ui/mods/GeodeStyle.cpp +++ b/loader/src/ui/mods/GeodeStyle.cpp @@ -49,30 +49,10 @@ ColorProvider::get()->define("swelvy-bg-4"_spr, { 173, 84, 146, 255 }); ColorProvider::get()->define("swelvy-bg-5"_spr, { 113, 74, 154, 255 }); - if (Mod::get()->getSavedValue("alternate-geode-style", false)) { - ColorProvider::get()->override("swelvy-bg-0"_spr, { 216, 132, 132, 255 }); - ColorProvider::get()->override("swelvy-bg-1"_spr, { 210, 189, 119, 255 }); - ColorProvider::get()->override("swelvy-bg-2"_spr, { 195, 212, 136, 255 }); - ColorProvider::get()->override("swelvy-bg-3"_spr, { 95, 184, 134, 255 }); - ColorProvider::get()->override("swelvy-bg-4"_spr, { 100, 174, 189, 255 }); - ColorProvider::get()->override("swelvy-bg-5"_spr, { 118, 90, 148, 255 }); - } - - auto updateColors = +[](bool enabled) { - if (enabled) { - ColorProvider::get()->reset("mod-list-bg"_spr); - ColorProvider::get()->reset("mod-list-version-bg-updates-available"_spr); - ColorProvider::get()->reset("mod-list-search-bg"_spr); - ColorProvider::get()->reset("mod-list-tab-deselected-bg"_spr); - ColorProvider::get()->reset("mod-list-tab-selected-bg"_spr); - ColorProvider::get()->reset("mod-list-tab-selected-bg-alt"_spr); - ColorProvider::get()->reset("mod-list-restart-required-label"_spr); - ColorProvider::get()->reset("mod-list-restart-required-label-bg"_spr); - ColorProvider::get()->reset("mod-problems-item-bg"_spr); - ColorProvider::get()->reset("mod-developer-item-bg"_spr); - ColorProvider::get()->reset("keybinds-list-category-label"_spr); - } - else { + auto updateColors = +[](std::string_view themeName) { + std::string theme; + ThemeIDProvidingEvent().send(theme); + if (theme == "geometry-dash") { ColorProvider::get()->override("mod-list-bg"_spr, { 168, 85, 44, 255 }); ColorProvider::get()->override("mod-list-version-bg-updates-available"_spr, { 220, 190, 0, 120 }); ColorProvider::get()->override("mod-list-search-bg"_spr, { 114, 63, 31, 255 }); @@ -87,20 +67,79 @@ ColorProvider::get()->override("mod-developer-item-bg"_spr, { 0, 0, 0, 75 }); ColorProvider::get()->override("keybinds-list-category-label"_spr, ccc3(156, 185, 147)); } + else { + if (theme == "rainbow") { + ColorProvider::get()->override("swelvy-bg-0"_spr, { 216, 132, 132, 255 }); + ColorProvider::get()->override("swelvy-bg-1"_spr, { 210, 189, 119, 255 }); + ColorProvider::get()->override("swelvy-bg-2"_spr, { 195, 212, 136, 255 }); + ColorProvider::get()->override("swelvy-bg-3"_spr, { 95, 184, 134, 255 }); + ColorProvider::get()->override("swelvy-bg-4"_spr, { 100, 174, 189, 255 }); + ColorProvider::get()->override("swelvy-bg-5"_spr, { 118, 90, 148, 255 }); + } + else if (theme == "geode") { + ColorProvider::get()->reset("swelvy-bg-0"_spr); + ColorProvider::get()->reset("swelvy-bg-1"_spr); + ColorProvider::get()->reset("swelvy-bg-2"_spr); + ColorProvider::get()->reset("swelvy-bg-3"_spr); + ColorProvider::get()->reset("swelvy-bg-4"_spr); + ColorProvider::get()->reset("swelvy-bg-5"_spr); + } + ColorProvider::get()->reset("mod-list-bg"_spr); + ColorProvider::get()->reset("mod-list-version-bg-updates-available"_spr); + ColorProvider::get()->reset("mod-list-search-bg"_spr); + ColorProvider::get()->reset("mod-list-tab-deselected-bg"_spr); + ColorProvider::get()->reset("mod-list-tab-selected-bg"_spr); + ColorProvider::get()->reset("mod-list-tab-selected-bg-alt"_spr); + ColorProvider::get()->reset("mod-list-restart-required-label"_spr); + ColorProvider::get()->reset("mod-list-restart-required-label-bg"_spr); + ColorProvider::get()->reset("mod-problems-item-bg"_spr); + ColorProvider::get()->reset("mod-developer-item-bg"_spr); + ColorProvider::get()->reset("keybinds-list-category-label"_spr); + } }; // Update colors when the theme is changed - listenForSettingChanges("enable-geode-theme", updateColors); + listenForSettingChanges("used-theme", updateColors); Loader::get()->queueInMainThread([updateColors = updateColors] { // this code is ran during static init, where settings aren't loaded yet, and getSettingValue will always return false. // because of that, we have to delay it until next frame. - updateColors(Mod::get()->getSettingValue("enable-geode-theme")); + updateColors(Mod::get()->getSettingValue("used-theme")); + + if (Mod::get()->getSettingValue("enable-geode-theme")) { + Mod::get()->setSettingValue("used-theme", "Geode"); + Mod::get()->setSettingValue("enable-geode-theme", false); + } }); + + ThemeIDProvidingEvent().listen([](std::string& idOut) { + if (!idOut.empty()) return; // someone overrode it already + + auto theme = Mod::get()->getSettingValue("used-theme"); + static std::unordered_map themeMap = { + { "Geode", "geode" }, + { "Geometry Dash", "geometry-dash" }, + { "Rainbow", "rainbow" }, + { "Sapphire", "sapphire" }, + }; + if (themeMap.contains(theme)) { + idOut = themeMap[theme]; + } + else { + // default fallback provided by us + idOut = "geode"; + } + return; + + }, Priority::Last).leak(); } bool isGeodeTheme(bool forceDisableTheme) { - return !forceDisableTheme && Mod::get()->getSettingValue("enable-geode-theme"); + if (forceDisableTheme) return false; + if (Mod::get()->getSettingValue("enable-geode-theme")) return true; + std::string theme; + ThemeIDProvidingEvent().send(theme); + return theme != "geometry-dash"; } bool GeodePopup::init(float width, float height, GeodePopupStyle style, bool forceDisableTheme) { diff --git a/loader/src/ui/mods/ModsLayer.cpp b/loader/src/ui/mods/ModsLayer.cpp index 1f5f03701..5241a4e1c 100644 --- a/loader/src/ui/mods/ModsLayer.cpp +++ b/loader/src/ui/mods/ModsLayer.cpp @@ -320,13 +320,29 @@ bool ModsLayer::init() { const bool geodeTheme = isGeodeTheme(); if (!isSafeMode) { - if (geodeTheme) { - this->addChild(SwelvyBG::create()); - } - else { - this->addChild(createLayerBG()); - addSideArt(this); - } + auto listener = NodeProvidingEvent("geode-background"_spr).listen([this](cocos2d::CCNode*& nodeOut, std::string_view theme) -> void { + if (nodeOut) return; // someone overrode it already + auto winSize = CCDirector::get()->getWinSize(); + CCNode* geodeBackground; + if (theme == "sapphire") { + geodeBackground = CCSprite::create("sapphire-bg.png"_spr); + geodeBackground->setScaleX((winSize.width) / geodeBackground->getContentSize().width); + geodeBackground->setScaleY((winSize.height) / geodeBackground->getContentSize().height); + geodeBackground->setPosition({winSize.width / 2, winSize.height / 2}); + } + else if (theme == "geometry-dash") { + geodeBackground = createLayerBG(); + addSideArt(this); + } + else { + geodeBackground = SwelvyBG::create(); + } + nodeOut = geodeBackground; + }, Priority::Last); + + cocos2d::CCNode* geodeBackground = nullptr; + NodeProvidingEvent("geode-background"_spr).send(geodeBackground); + this->addChild(geodeBackground); } m_modListDisplay = Mod::get()->getSavedValue("mod-list-display-type"); @@ -447,26 +463,62 @@ bool ModsLayer::init() { frameBG->ignoreAnchorPointForPosition(false); m_frame->addChildAtPosition(frameBG, Anchor::Center); - auto tabsTop = CCSprite::createWithSpriteFrameName(geodeTheme ? "mods-list-top.png"_spr : "mods-list-top-gd.png"_spr); + auto topListener = NodeProvidingEvent("geode-mods-list-top"_spr).listen([this](cocos2d::CCNode*& nodeOut, std::string_view theme) -> void { + if (nodeOut) return; // someone overrode it already + + if (theme == "sapphire") nodeOut = CCSprite::createWithSpriteFrameName("mods-list-top-sapphire.png"_spr); + else if (theme == "geometry-dash") nodeOut = CCSprite::createWithSpriteFrameName("mods-list-top-gd.png"_spr); + else nodeOut = CCSprite::createWithSpriteFrameName("mods-list-top.png"_spr); + nodeOut->setAnchorPoint({ .5f, .0f }); + }, Priority::Last); + + CCNode* tabsTop = nullptr; + NodeProvidingEvent("geode-mods-list-top"_spr).send(tabsTop); tabsTop->setID("frame-top-sprite"); - tabsTop->setAnchorPoint({ .5f, .0f }); tabsTop->setZOrder(1); m_frame->addChildAtPosition(tabsTop, Anchor::Top, ccp(0, -2)); - auto tabsLeft = CCSprite::createWithSpriteFrameName(geodeTheme ? "mods-list-side.png"_spr : "mods-list-side-gd.png"_spr); + auto leftListener = NodeProvidingEvent("geode-mods-list-left"_spr).listen([this](cocos2d::CCNode*& nodeOut, std::string_view theme) -> void { + if (nodeOut) return; // someone overrode it already + + if (theme == "sapphire") nodeOut = CCSprite::createWithSpriteFrameName("mods-list-side-sapphire.png"_spr); + else if (theme == "geometry-dash") nodeOut = CCSprite::createWithSpriteFrameName("mods-list-side-gd.png"_spr); + else nodeOut = CCSprite::createWithSpriteFrameName("mods-list-side.png"_spr); + nodeOut->setScaleY(m_frame->getContentHeight() / nodeOut->getContentHeight()); + }, Priority::Last); + + CCNode* tabsLeft = nullptr; + NodeProvidingEvent("geode-mods-list-left"_spr).send(tabsLeft); tabsLeft->setID("frame-left-sprite"); - tabsLeft->setScaleY(m_frame->getContentHeight() / tabsLeft->getContentHeight()); m_frame->addChildAtPosition(tabsLeft, Anchor::Left, ccp(6.5f, 1)); - auto tabsRight = CCSprite::createWithSpriteFrameName(geodeTheme ? "mods-list-side.png"_spr : "mods-list-side-gd.png"_spr); + auto rightListener = NodeProvidingEvent("geode-mods-list-right"_spr).listen([this](cocos2d::CCNode*& nodeOut, std::string_view theme) -> void { + if (nodeOut) return; // someone overrode it already + + if (theme == "sapphire") nodeOut = CCSprite::createWithSpriteFrameName("mods-list-side-sapphire.png"_spr); + else if (theme == "geometry-dash") nodeOut = CCSprite::createWithSpriteFrameName("mods-list-side-gd.png"_spr); + else nodeOut = CCSprite::createWithSpriteFrameName("mods-list-side.png"_spr); + nodeOut->setScaleY(m_frame->getContentHeight() / nodeOut->getContentHeight()); + static_cast(nodeOut)->setFlipX(true); + }, Priority::Last); + + CCNode* tabsRight = nullptr; + NodeProvidingEvent("geode-mods-list-right"_spr).send(tabsRight); tabsRight->setID("frame-right-sprite"); - tabsRight->setFlipX(true); - tabsRight->setScaleY(m_frame->getContentHeight() / tabsRight->getContentHeight()); m_frame->addChildAtPosition(tabsRight, Anchor::Right, ccp(-6.5f, 1)); - auto tabsBottom = CCSprite::createWithSpriteFrameName(geodeTheme ? "mods-list-bottom.png"_spr : "mods-list-bottom-gd.png"_spr); + auto bottomListener = NodeProvidingEvent("geode-mods-list-bottom"_spr).listen([this](cocos2d::CCNode*& nodeOut, std::string_view theme) -> void { + if (nodeOut) return; // someone overrode it already + + if (theme == "sapphire") nodeOut = CCSprite::createWithSpriteFrameName("mods-list-bottom-sapphire.png"_spr); + else if (theme == "geometry-dash") nodeOut = CCSprite::createWithSpriteFrameName("mods-list-bottom-gd.png"_spr); + else nodeOut = CCSprite::createWithSpriteFrameName("mods-list-bottom.png"_spr); + nodeOut->setAnchorPoint({ .5f, 1.f }); + }, Priority::Last); + + CCNode* tabsBottom = nullptr; + NodeProvidingEvent("geode-mods-list-bottom"_spr).send(tabsBottom); tabsBottom->setID("frame-bottom-sprite"); - tabsBottom->setAnchorPoint({ .5f, 1.f }); tabsBottom->setZOrder(1); m_frame->addChildAtPosition(tabsBottom, Anchor::Bottom, ccp(0, 3)); diff --git a/loader/src/ui/mods/popups/ModPopup.cpp b/loader/src/ui/mods/popups/ModPopup.cpp index aa5f28661..726e670e1 100644 --- a/loader/src/ui/mods/popups/ModPopup.cpp +++ b/loader/src/ui/mods/popups/ModPopup.cpp @@ -137,18 +137,9 @@ bool ModPopup::init(ModSource&& src) { counter++; if (counter % 6 == 3) { Mod::get()->setSavedValue("alternate-geode-style", true); - ColorProvider::get()->override("swelvy-bg-0"_spr, { 216, 132, 132, 255 }); - ColorProvider::get()->override("swelvy-bg-1"_spr, { 210, 189, 119, 255 }); - ColorProvider::get()->override("swelvy-bg-2"_spr, { 195, 212, 136, 255 }); - ColorProvider::get()->override("swelvy-bg-3"_spr, { 95, 184, 134, 255 }); - ColorProvider::get()->override("swelvy-bg-4"_spr, { 100, 174, 189, 255 }); - ColorProvider::get()->override("swelvy-bg-5"_spr, { 118, 90, 148, 255 }); } else if (counter % 6 == 0) { Mod::get()->getSaveContainer().erase("alternate-geode-style"); - for (int i = 0; i < 6; i++) { - ColorProvider::get()->reset(fmt::format("swelvy-bg-{}"_spr, i)); - } } }); m_titleContainer->addChildAtPosition( diff --git a/loader/src/utils/ColorProvider.cpp b/loader/src/utils/ColorProvider.cpp index f3b9d819f..02076f518 100644 --- a/loader/src/utils/ColorProvider.cpp +++ b/loader/src/utils/ColorProvider.cpp @@ -69,3 +69,9 @@ ccColor4B ColorProvider::color(std::string_view id) const { ccColor3B ColorProvider::color3b(std::string_view id) const { return to3B(this->color(id)); } + +bool NodeProvidingEvent::send(cocos2d::CCNode*& nodeOut) { + std::string theme; + ThemeIDProvidingEvent().send(theme); + return this->GlobalEvent::send(nodeOut, theme); +} \ No newline at end of file