Skip to content
Merged

Themes #2009

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions loader/include/Geode/utils/ColorProvider.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,25 @@ namespace geode {
*/
cocos2d::ccColor3B color3b(std::string_view id) const;
};

class ThemeIDProvidingEvent final : public Event<ThemeIDProvidingEvent, bool(std::string& idOut)> {
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<NodeProvidingEvent, bool(std::string_view id, cocos2d::CCNode*& nodeOut, std::string_view theme), bool(cocos2d::CCNode*& nodeOut, std::string_view theme), std::string> {
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) {
Expand Down
Binary file added loader/resources/images/sapphire-bg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added loader/resources/logos/sapphire-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 11 additions & 5 deletions loader/resources/mod.json.in
Original file line number Diff line number Diff line change
Expand Up @@ -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 <ca>Geode-themed color scheme</c>. <cy>This does not affect any other menus!</c>"
"used-theme": {
"type": "string",
"name": "Used Theme",
"description": "The theme used for Geode menu. <cy>This does not affect any other menus!</c>",
"default": "Geode",
"one-of": [
"Geode",
"Geometry Dash",
"Rainbow",
"Sapphire"
]
},
"infinite-local-mods-list": {
"type": "bool",
Expand Down
Binary file added loader/resources/mods-list-bottom-sapphire.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added loader/resources/mods-list-side-sapphire.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added loader/resources/mods-list-top-sapphire.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 39 additions & 9 deletions loader/src/hooks/MenuLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <loader/updater.hpp>
#include <Geode/binding/ButtonSprite.hpp>
#include <Geode/modify/LevelSelectLayer.hpp>
#include <Geode/utils/ColorProvider.hpp>

using namespace geode::prelude;

Expand All @@ -36,7 +37,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {

struct Fields {
bool m_menuDisabled = false;
CCSprite* m_geodeButton = nullptr;
CCNode* m_geodeButton = nullptr;
async::TaskHolder<Result<InstalledModsUpdateCheck, server::ServerError>> m_updateCheckTask;
};

Expand All @@ -53,16 +54,45 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {

// 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<CCSprite*>(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<CCMenu*>(this->getChildByID("bottom-menu"));
Expand Down
25 changes: 21 additions & 4 deletions loader/src/ui/GeodeUI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <Geode/ui/MDPopup.hpp>
#include <Geode/ui/LoadingSpinner.hpp>
#include <Geode/ui/LazySprite.hpp>
#include <Geode/utils/ColorProvider.hpp>
#include <Geode/utils/web.hpp>
#include <server/Server.hpp>
#include "mods/GeodeStyle.hpp"
Expand Down Expand Up @@ -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<LazySprite*>(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);
}
}
Expand Down
93 changes: 66 additions & 27 deletions loader/src/ui/mods/GeodeStyle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand All @@ -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<bool>("enable-geode-theme", updateColors);
listenForSettingChanges<std::string_view>("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<bool>("enable-geode-theme"));
updateColors(Mod::get()->getSettingValue<std::string_view>("used-theme"));

if (Mod::get()->getSettingValue<bool>("enable-geode-theme")) {
Mod::get()->setSettingValue<std::string_view>("used-theme", "Geode");
Mod::get()->setSettingValue<bool>("enable-geode-theme", false);
}
});

ThemeIDProvidingEvent().listen([](std::string& idOut) {
if (!idOut.empty()) return; // someone overrode it already

auto theme = Mod::get()->getSettingValue<std::string_view>("used-theme");
static std::unordered_map<std::string_view, std::string> 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<bool>("enable-geode-theme");
if (forceDisableTheme) return false;
if (Mod::get()->getSettingValue<bool>("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) {
Expand Down
84 changes: 68 additions & 16 deletions loader/src/ui/mods/ModsLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ModListDisplay>("mod-list-display-type");
Expand Down Expand Up @@ -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<CCSprite*>(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));

Expand Down
Loading
Loading