From 35741ba41361ba21e2e5881d693b6e6b06d75379 Mon Sep 17 00:00:00 2001 From: Cheeseworks Date: Fri, 4 Sep 2026 21:48:11 -0500 Subject: [PATCH 1/6] initial gdc impl --- CMakeLists.txt | 4 +- src/ui/MenuExtras.hpp | 51 ++------- src/ui/src/Menu.cpp | 4 +- src/ui/src/MenuExtras.cpp | 228 +++++--------------------------------- 4 files changed, 36 insertions(+), 251 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba5358a7..959231a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,5 +26,5 @@ add_subdirectory($ENV{GEODE_SDK} ${CMAKE_CURRENT_BINARY_DIR}/geode) setup_geode_mod(${PROJECT_NAME}) CPMAddPackage("gh:dankmeme01/cue#main") -CPMAddPackage("gh:GlobedGD/argon@1.5.1") -target_link_libraries(${PROJECT_NAME} cue argon) +CPMAddPackage("gh:CubicCommunity/gdcord#main") +target_link_libraries(${PROJECT_NAME} cue gdcord) diff --git a/src/ui/MenuExtras.hpp b/src/ui/MenuExtras.hpp index 10b6224c..da3fab3d 100644 --- a/src/ui/MenuExtras.hpp +++ b/src/ui/MenuExtras.hpp @@ -4,6 +4,8 @@ #include +#include + #include namespace horrible { @@ -31,42 +33,17 @@ namespace horrible { static MenuSuggest* get() noexcept; }; - struct DiscordLink final { - std::string id; - std::string username; - std::string avatar; - }; - class AuthState final : public base::Singleton { private: - bool m_authorized = false; bool m_discordLinked = false; - int m_accountID; - int m_userID; - std::string m_username; - std::string m_token; - - DiscordLink m_discord; + gdc::DiscordLink m_discord; bool m_supporter = false; - protected: - void setAuthInfo(int accountID, int userID, std::string username, std::string token); - public: - void startAuth(geode::CopyableFunction)>&& callback); - - void setDiscordLinkInfo(DiscordLink discord); + void setDiscordLinkInfo(gdc::DiscordLink discord); - bool isAuthorized() const noexcept; - bool isAuthValid() const; - - int getAccountID() const noexcept; - int getUserID() const noexcept; - geode::ZStringView getUsername() const noexcept; - geode::ZStringView getToken() const noexcept; - - geode::Result getDiscord() const; + geode::Result getDiscord() const; bool isSupporter() const noexcept; }; @@ -75,20 +52,16 @@ namespace horrible { std::string normalizeAvatarURL(std::string url) const; protected: - bool init(DiscordLink const& profile); + bool init(gdc::DiscordLink const& profile); public: - static MenuDiscordCell* create(DiscordLink const& profile); + static MenuDiscordCell* create(gdc::DiscordLink const& profile); }; class MenuDiscord final : public geode::Popup { private: static MenuDiscord* s_inst; - std::string m_state; - asp::Instant m_since; - geode::async::TaskHolder m_listener; - geode::Label* m_linkLabel = nullptr; MenuDiscordCell* m_discordCell = nullptr; @@ -100,10 +73,6 @@ namespace horrible { void setupAuthInterface(); protected: - void onExit() override; - - void checkDiscordStatus(float); - bool init(geode::ZStringView theme); public: @@ -127,9 +96,3 @@ namespace horrible { static MenuKofi* get() noexcept; }; }; - -template <> -struct horrible::json::Serialize final { - static geode::Result fromJson(horrible::json::Value const& value); - static horrible::json::Value toJson(horrible::DiscordLink const& value); -}; \ No newline at end of file diff --git a/src/ui/src/Menu.cpp b/src/ui/src/Menu.cpp index 11ed1fe7..f6149c13 100644 --- a/src/ui/src/Menu.cpp +++ b/src/ui/src/Menu.cpp @@ -491,7 +491,7 @@ bool Menu::init() { if (!m_impl->hasInternet) return Notification::create("An internet connection is required.", NotificationIcon::Error)->show() #define NOTIFY_IF_LOGGED_OUT \ - if (!AuthState::get()->isAuthValid()) return Notification::create("You must be logged in!", NotificationIcon::Warning)->show() + if (!argon::signedIn()) return Notification::create("You must be logged in!", NotificationIcon::Warning)->show() auto socialBtns = std::to_array( { @@ -517,7 +517,7 @@ bool Menu::init() { [this](auto) { NOTIFY_INTERNET_IF_OFFLINE; - if (!AuthState::get()->isAuthValid()) { + if (!argon::signedIn()) { createQuickPopup( "Discord", "Join the Cubic Studios official community Discord server?", diff --git a/src/ui/src/MenuExtras.cpp b/src/ui/src/MenuExtras.cpp index 9d43dd49..c63dee33 100644 --- a/src/ui/src/MenuExtras.cpp +++ b/src/ui/src/MenuExtras.cpp @@ -2,6 +2,8 @@ #include +#include + #include #include @@ -9,40 +11,12 @@ using namespace geode::prelude; using namespace horrible::prelude; -$on_mod(Loaded) { - if (auto as = AuthState::get()) as->startAuth([](Result<> res) { if (res.isErr()) log::error("Argon authorization failed: {}", std::move(res).unwrapErr()); }); -}; - static constexpr auto g_suggestWait = 60; MenuSuggest* MenuSuggest::s_inst = nullptr; asp::Instant MenuSuggest::s_lastSuggest = asp::Instant(); -Result json::Serialize::fromJson(json::Value const& value) { - if (!value.isObject()) return Err("Expected an object"); - - GEODE_UNWRAP_INTO(std::string id, value["id"].asString()); - GEODE_UNWRAP_INTO(std::string username, value["username"].asString()); - GEODE_UNWRAP_INTO(std::string avatar, value["avatar"].asString()); - - return Ok(DiscordLink{ - std::move(id), - std::move(username), - std::move(avatar), - }); -}; - -json::Value json::Serialize::toJson(DiscordLink const& value) { - auto obj = json::Value(); - - obj["id"] = value.id; - obj["username"] = value.username; - obj["avatar"] = value.avatar; - - return obj; -}; - bool MenuSuggest::init(ZStringView theme) { auto btns = themes::getCircleBaseColor(theme); @@ -135,8 +109,9 @@ void MenuSuggest::processSuggestion(Button* sender) { if (m_loading) m_loading->setVisible(true); sender->setVisible((false)); - if (auto as = AuthState::get()) { - as->startAuth([as, self = WeakRef(this), btn = WeakRef(sender)](Result<> res) { + async::spawn( + argon::startAuth(), + [self = WeakRef(this), btn = WeakRef(sender)](Result res) { auto const toggleBack = [btn](Ref& s) { if (auto b = btn.lock()) b->setVisible(true); if (s->m_loading) s->m_loading->setVisible(false); @@ -169,10 +144,12 @@ void MenuSuggest::processSuggestion(Button* sender) { reqJson["topic"] = topic; reqJson["description"] = desc; - reqJson["account_id"] = as->getAccountID(); - reqJson["user_id"] = as->getUserID(); - reqJson["username"] = as->getUsername(); - reqJson["authtoken"] = as->getToken(); + auto const acc = argon::getGameAccountData(); + + reqJson["account_id"] = acc.accountId; + reqJson["user_id"] = acc.userId; + reqJson["username"] = acc.username; + reqJson["authtoken"] = std::move(res).unwrap(); reqJson["v"] = mod->getVersion().toVString(); @@ -198,10 +175,6 @@ void MenuSuggest::processSuggestion(Button* sender) { }; }; }); - } else { - sender->setVisible((true)); - if (m_loading) m_loading->setVisible(false); - }; }; void MenuSuggest::onExit() { @@ -225,16 +198,7 @@ MenuSuggest* MenuSuggest::create(ZStringView theme) { return nullptr; }; -void AuthState::setAuthInfo(int accountID, int userID, std::string username, std::string token) { - m_accountID = accountID; - m_userID = userID; - m_username = std::move(username); - m_token = std::move(token); - - m_authorized = !m_token.empty(); -}; - -void AuthState::setDiscordLinkInfo(DiscordLink discord) { +void AuthState::setDiscordLinkInfo(gdc::DiscordLink discord) { m_discord = std::move(discord); m_discordLinked = !m_discord.id.empty(); @@ -254,31 +218,7 @@ void AuthState::setDiscordLinkInfo(DiscordLink discord) { }; }; -bool AuthState::isAuthorized() const noexcept { - return m_authorized; -}; - -bool AuthState::isAuthValid() const { - return isAuthorized() && (getAccountID() == argon::getGameAccountData().accountId); -}; - -int AuthState::getAccountID() const noexcept { - return m_accountID; -}; - -int AuthState::getUserID() const noexcept { - return m_userID; -}; - -ZStringView AuthState::getUsername() const noexcept { - return m_username; -}; - -ZStringView AuthState::getToken() const noexcept { - return m_token; -}; - -Result AuthState::getDiscord() const { +Result AuthState::getDiscord() const { if (!m_discordLinked) return Err("Discord account not linked"); return Ok(m_discord); }; @@ -287,55 +227,6 @@ bool AuthState::isSupporter() const noexcept { return m_supporter; }; -void AuthState::startAuth(CopyableFunction)>&& callback) { - if (!argon::signedIn()) return callback(Err("Player is logged out")); - - if (isAuthValid()) return callback(Ok()); - - async::spawn( - argon::startAuth(), - [this, cb = std::move(callback)](Result res) { - if (res.isErr()) return cb(res.asErr()); - - auto const acc = argon::getGameAccountData(); - - setAuthInfo(acc.accountId, acc.userId, acc.username, std::move(res).unwrap()); - log::info("Authorized {} ({}) with Argon", acc.username, acc.accountId); - - return cb(Ok()); - }); - - if (auto gjam = GJAccountManager::sharedState()) { - auto req = web::WebRequest() - .param("id", gjam->m_accountID); - - async::spawn( - req.get("https://api.cubicstudios.xyz/breakeode/v1/discord"), - [this](web::WebResponse res) { - auto const fallback = [](std::string_view err = "") { - log::error("Discord link web request failed ({})", err); - }; - - if (res.error()) return fallback(res.errorMessage()); - - auto jsonRes = res.json(); - if (jsonRes.isErr()) return fallback(std::move(jsonRes).unwrapErr()); - - auto json = std::move(jsonRes).unwrap(); - - auto discordRes = json.as(); - if (discordRes.isErr()) return fallback(std::move(discordRes).unwrapErr()); - - setDiscordLinkInfo(std::move(discordRes).unwrap()); - - auto discordLinkRes = getDiscord(); - if (discordLinkRes.isErr()) return fallback(std::move(discordLinkRes).unwrapErr()); - - log::info("Authorized as Discord user {}", std::move(discordLinkRes).unwrap().username); - }); - }; -}; - MenuDiscord* MenuDiscord::s_inst = nullptr; void MenuDiscord::setupAuthInterface() { @@ -378,20 +269,21 @@ void MenuDiscord::setupAuthInterface() { themes::getButtonSquareSprite(mod->getSettingValue("theme")), 0.875f), [this, as](Button* sender) { - if (!as->isAuthValid()) return Notification::create("You must be logged in!", NotificationIcon::Warning)->show(); - - sender->setVisible(false); - m_loading = LoadingSpinner::create(25.f); m_loading->setPosition(sender->getPosition()); m_mainLayer->addChild(m_loading, 9); - m_state = rng::internal::generateUUID(); - m_since = asp::Instant::now(); - web::openLinkInBrowser(fmt::format("https://api.cubicstudios.xyz/breakeode/v1/discord/link/auth?state={}", m_state)); + gdc::startLink([self = WeakRef(this)](Result res) { + if (res.isErr()) return log::error("{}", std::move(res).unwrapErr()); - scheduleOnce(schedule_selector(MenuDiscord::checkDiscordStatus), 1.25f); + auto discord = std::move(res).unwrap(); + + log::info("Successfully authorized as {}", discord.username); + if (auto as = AuthState::get()) as->setDiscordLinkInfo(std::move(discord)); + + if (auto s = self.lock()) s->setupAuthInterface(); + }); }); m_linkBtn->setID("link-discord-account-btn"); m_linkBtn->setScale(0.75f); @@ -481,76 +373,6 @@ bool MenuDiscord::init(ZStringView theme) { return true; }; -void MenuDiscord::checkDiscordStatus(float) { - if (auto as = AuthState::get()) { - if (!as->isAuthValid()) return unschedule(schedule_selector(MenuDiscord::checkDiscordStatus)); - - if (asp::Instant::now().durationSince(m_since).seconds() > 30) { - if (m_linkBtn) m_linkBtn->setVisible(true); - Notification::create("Authorization flow timed out after 30s", NotificationIcon::Error)->show(); - return unschedule(schedule_selector(MenuDiscord::checkDiscordStatus)); - }; - - auto reqJson = json::Value(); - reqJson["account_id"] = as->getAccountID(); - reqJson["user_id"] = as->getUserID(); - reqJson["username"] = as->getUsername(); - reqJson["authtoken"] = as->getToken(); - reqJson["state"] = m_state; - - auto req = web::WebRequest() - .bodyJSON(reqJson); - - log::debug("Checking endpoint for Discord link status..."); - - m_listener.spawn( - req.post("https://api.cubicstudios.xyz/breakeode/v1/discord/link/check"), - [self = WeakRef(this)](web::WebResponse res) { - if (auto s = self.lock()) { - auto const fallback = [&s](std::string_view err = "") { - log::error("Discord link web request failed ({}), trying again in 1.25s", err); - s->scheduleOnce(schedule_selector(MenuDiscord::checkDiscordStatus), 1.25f); - }; - - if (res.error()) return fallback(res.errorMessage()); - - auto jsonRes = res.json(); - if (jsonRes.isErr()) return fallback(std::move(jsonRes).unwrapErr()); - - auto json = std::move(jsonRes).unwrap(); - - auto discordRes = json.as(); - if (discordRes.isErr()) return fallback(std::move(discordRes).unwrapErr()); - - auto discord = std::move(discordRes).unwrap(); - - log::info("Successfully authorized as {}", discord.username); - if (auto as = AuthState::get()) as->setDiscordLinkInfo(std::move(discord)); - - s->unschedule(schedule_selector(MenuDiscord::checkDiscordStatus)); - - s->m_listener.cancel(); - s->m_since = asp::Instant(); - s->m_state.clear(); - - s->setupAuthInterface(); - }; - }); - } else { - unschedule(schedule_selector(MenuDiscord::checkDiscordStatus)); - }; -}; - -void MenuDiscord::onExit() { - s_inst = nullptr; - - if (m_listener.isPending()) log::trace("Cancelling Discord link tasks"); - unschedule(schedule_selector(MenuDiscord::checkDiscordStatus)); - m_listener.cancel(); - - Popup::onExit(); -}; - MenuDiscord* MenuDiscord::get() noexcept { return s_inst; }; @@ -577,7 +399,7 @@ std::string MenuDiscordCell::normalizeAvatarURL(std::string url) const { return url; }; -bool MenuDiscordCell::init(DiscordLink const& profile) { +bool MenuDiscordCell::init(gdc::DiscordLink const& profile) { if (!CCNode::init()) return false; setContentSize({45.f, 40.f}); @@ -631,7 +453,7 @@ bool MenuDiscordCell::init(DiscordLink const& profile) { return true; }; -MenuDiscordCell* MenuDiscordCell::create(DiscordLink const& profile) { +MenuDiscordCell* MenuDiscordCell::create(gdc::DiscordLink const& profile) { auto ret = new MenuDiscordCell(); if (ret->init(profile)) { ret->autorelease(); @@ -704,7 +526,7 @@ bool MenuKofi::init(ZStringView theme) { [](auto) { if (auto as = AuthState::get()) { if (as->isSupporter()) { - ProfilePage::create(as->getAccountID(), false)->show(); + ProfilePage::create(argon::getGameAccountData().accountId, false)->show(); } else { createQuickPopup( "Ko-fi", From 0dc7213d4392745fc37d9aa47d9e68520a790f1b Mon Sep 17 00:00:00 2001 From: Cheeseworks Date: Fri, 4 Sep 2026 22:15:23 -0500 Subject: [PATCH 2/6] cleanup --- src/ui/MenuExtras.hpp | 10 ++- src/ui/src/MenuExtras.cpp | 127 +++++++++++++++++++++----------------- 2 files changed, 73 insertions(+), 64 deletions(-) diff --git a/src/ui/MenuExtras.hpp b/src/ui/MenuExtras.hpp index da3fab3d..ed077422 100644 --- a/src/ui/MenuExtras.hpp +++ b/src/ui/MenuExtras.hpp @@ -33,17 +33,15 @@ namespace horrible { static MenuSuggest* get() noexcept; }; - class AuthState final : public base::Singleton { - private: - bool m_discordLinked = false; + class SupporterState final : public base::Singleton { + using Callback = geode::CopyableFunction)>; - gdc::DiscordLink m_discord; + private: bool m_supporter = false; public: - void setDiscordLinkInfo(gdc::DiscordLink discord); + void validateSupporter(Callback&& cb); - geode::Result getDiscord() const; bool isSupporter() const noexcept; }; diff --git a/src/ui/src/MenuExtras.cpp b/src/ui/src/MenuExtras.cpp index c63dee33..921ddd52 100644 --- a/src/ui/src/MenuExtras.cpp +++ b/src/ui/src/MenuExtras.cpp @@ -13,6 +13,14 @@ using namespace horrible::prelude; static constexpr auto g_suggestWait = 60; +$on_mod(Loaded) { + if (auto ss = SupporterState::get()) ss->validateSupporter( + [](Result<> res) { + if (res.isErr()) return log::error("Supporter state check failed: {}", std::move(res).unwrapErr()); + log::info("User is a Ko-fi supporter!"); + }); +}; + MenuSuggest* MenuSuggest::s_inst = nullptr; asp::Instant MenuSuggest::s_lastSuggest = asp::Instant(); @@ -198,10 +206,11 @@ MenuSuggest* MenuSuggest::create(ZStringView theme) { return nullptr; }; -void AuthState::setDiscordLinkInfo(gdc::DiscordLink discord) { - m_discord = std::move(discord); - - m_discordLinked = !m_discord.id.empty(); +void SupporterState::validateSupporter(Callback&& cb) { + if (!gdc::isLinked()) { + m_supporter = false; + return cb(Err("Player is signed out or not linked with Discord")); + }; if (auto gjam = GJAccountManager::sharedState()) { log::trace("Checking Ko-fi supporter status..."); @@ -211,19 +220,16 @@ void AuthState::setDiscordLinkInfo(gdc::DiscordLink discord) { async::spawn( req.get("https://api.cubicstudios.xyz/breakeode/v1/discord/supporter"), - [this](web::WebResponse res) { + [this, cb = std::move(cb)](web::WebResponse res) { if (res.ok()) log::info("User is a supporter of Breakeode"); m_supporter = res.ok(); + + return cb(Ok()); }); }; }; -Result AuthState::getDiscord() const { - if (!m_discordLinked) return Err("Discord account not linked"); - return Ok(m_discord); -}; - -bool AuthState::isSupporter() const noexcept { +bool SupporterState::isSupporter() const noexcept { return m_supporter; }; @@ -236,67 +242,72 @@ void MenuDiscord::setupAuthInterface() { cue::resetNode(m_loading); cue::resetNode(m_label); - if (auto as = AuthState::get()) { - std::string labelTxt; + std::string labelTxt; - auto discordRes = as->getDiscord(); - if (discordRes.isOk()) { - auto const discord = std::move(discordRes).unwrap(); + auto discordRes = gdc::getDiscordLink(); + if (discordRes.isOk()) { + auto const discord = std::move(discordRes).unwrap(); - labelTxt = "Thanks for playing with Horrible Menu!"; + labelTxt = "Thanks for playing with Horrible Menu!"; - m_discordCell = MenuDiscordCell::create(discord); - m_discordCell->setScale(0.75f); - m_discordCell->setAnchorPoint({0, 0}); + m_discordCell = MenuDiscordCell::create(discord); + m_discordCell->setScale(0.75f); + m_discordCell->setAnchorPoint({0, 0}); - m_mainLayer->addChildAtPosition(m_discordCell, Anchor::BottomLeft, {10.f, 10.f}); + m_mainLayer->addChildAtPosition(m_discordCell, Anchor::BottomLeft, {10.f, 10.f}); - m_linkLabel = Label::create("Playing as...", font::chat); - m_linkLabel->setScale(0.75f); - m_linkLabel->setAnchorPoint({0, 0}); - m_linkLabel->setPosition({m_discordCell->getPositionX(), m_discordCell->getPositionY() + m_discordCell->getScaledContentHeight() + 5.f}); + m_linkLabel = Label::create("Playing as...", font::chat); + m_linkLabel->setScale(0.75f); + m_linkLabel->setAnchorPoint({0, 0}); + m_linkLabel->setPosition({m_discordCell->getPositionX(), m_discordCell->getPositionY() + m_discordCell->getScaledContentHeight() + 5.f}); - m_mainLayer->addChild(m_linkLabel, 1); - } else { - log::error("{}", std::move(discordRes).unwrapErr()); + m_mainLayer->addChild(m_linkLabel, 1); + } else { + log::error("{}", std::move(discordRes).unwrapErr()); - labelTxt = "Your Discord account is not yet linked."; + labelTxt = "Your Discord account is not yet linked."; - m_linkBtn = Button::createWithNode( - ButtonSprite::create( - "Link Account", - font::gold, - themes::getButtonSquareSprite(mod->getSettingValue("theme")), - 0.875f), - [this, as](Button* sender) { - m_loading = LoadingSpinner::create(25.f); - m_loading->setPosition(sender->getPosition()); + auto const hideBtns = [this]() { + m_linkBtn->setVisible(false); - m_mainLayer->addChild(m_loading, 9); + m_loading = LoadingSpinner::create(25.f); + m_loading->setPosition(m_linkBtn->getPosition()); + }; - gdc::startLink([self = WeakRef(this)](Result res) { - if (res.isErr()) return log::error("{}", std::move(res).unwrapErr()); + m_linkBtn = Button::createWithNode( + ButtonSprite::create( + "Link Account", + font::gold, + themes::getButtonSquareSprite(mod->getSettingValue("theme")), + 0.875f), + [this, hideBtns](auto) { + hideBtns(); - auto discord = std::move(res).unwrap(); + m_mainLayer->addChild(m_loading, 9); - log::info("Successfully authorized as {}", discord.username); - if (auto as = AuthState::get()) as->setDiscordLinkInfo(std::move(discord)); + gdc::startLink([self = WeakRef(this)](Result res) { + if (res.isErr()) return log::error("{}", std::move(res).unwrapErr()); - if (auto s = self.lock()) s->setupAuthInterface(); - }); - }); - m_linkBtn->setID("link-discord-account-btn"); - m_linkBtn->setScale(0.75f); - m_linkBtn->setPosition({75.f, 25.f}); + auto discord = std::move(res).unwrap(); - m_mainLayer->addChild(m_linkBtn, 9); - }; + log::info("Successfully authorized as {}", std::move(discord).username); - m_label = LabelArea::create(std::move(labelTxt), m_mainLayer->getScaledContentWidth() * 0.4f, 0.5f); - m_label->setAnchorPoint({1, 0}); + if (auto s = self.lock()) s->setupAuthInterface(); + }); + }); + m_linkBtn->setID("link-discord-account-btn"); + m_linkBtn->setScale(0.75f); + m_linkBtn->setPosition({75.f, 25.f}); + + m_mainLayer->addChild(m_linkBtn, 9); - m_mainLayer->addChildAtPosition(m_label, Anchor::BottomRight, {-15.f, 15.f}); + if (gdc::isLinkOngoing()) hideBtns(); }; + + m_label = LabelArea::create(std::move(labelTxt), m_mainLayer->getScaledContentWidth() * 0.4f, 0.5f); + m_label->setAnchorPoint({1, 0}); + + m_mainLayer->addChildAtPosition(m_label, Anchor::BottomRight, {-15.f, 15.f}); }; bool MenuDiscord::init(ZStringView theme) { @@ -510,7 +521,7 @@ bool MenuKofi::init(ZStringView theme) { m_mainLayer->addChild(border, -1); - auto as = AuthState::get(); + auto as = SupporterState::get(); std::string infoLabelTxt = as->isSupporter() ? "Thanks for supporting Breakeode! You can now press the badge below to see the Supporter badge on your profile. Feel free to join Breakeode's Discord server for even more perks." @@ -524,7 +535,7 @@ bool MenuKofi::init(ZStringView theme) { auto supportBtn = Button::createWithSpriteFrameName( "badge_supporter.png"_spr, [](auto) { - if (auto as = AuthState::get()) { + if (auto as = SupporterState::get()) { if (as->isSupporter()) { ProfilePage::create(argon::getGameAccountData().accountId, false)->show(); } else { @@ -544,7 +555,7 @@ bool MenuKofi::init(ZStringView theme) { m_mainLayer->addChildAtPosition(supportBtn, Anchor::Center, {0.f, -17.5f}); - auto discordRes = as->getDiscord(); + auto discordRes = gdc::getDiscordLink(); if (discordRes.isErr()) { auto linkBtn = Button::createWithNode( From cde174e33e538b36da378082293fa7401e830b20 Mon Sep 17 00:00:00 2001 From: Cheeseworks Date: Fri, 4 Sep 2026 22:57:25 -0500 Subject: [PATCH 3/6] todo: fix dangling kofi popup pointer --- src/hooks/Mock.cpp | 50 +++++++++++++---------------- src/ui/src/Menu.cpp | 26 +++++++-------- src/ui/src/MenuExtras.cpp | 66 +++++++++++++++++++++------------------ 3 files changed, 69 insertions(+), 73 deletions(-) diff --git a/src/hooks/Mock.cpp b/src/hooks/Mock.cpp index 32ae030f..ec1d0f63 100644 --- a/src/hooks/Mock.cpp +++ b/src/hooks/Mock.cpp @@ -61,46 +61,40 @@ class $modify(MockMenuLayer, MenuLayer) { ss->setAnchorPoint(anchor::center); ss->setPosition({-192.f, getScaledContentHeight() / 2.f}); - ss->setLoadCallback([self = WeakRef(this), screenshot = WeakRef(ss), percent](Result<> res) { + ss->setLoadCallback([this, ss, percent](Result<> res) { if (res.isOk()) { - if (auto s = self.lock()) { - log::info("Sprite loaded successfully from save dir PNG"); + log::info("Sprite loaded successfully from save dir PNG"); - if (auto ss = screenshot.lock()) { - auto const percLabelText = fmt::format("{}%", percent); + auto const percLabelText = fmt::format("{}%", percent); - auto percLabel = Label::create(percLabelText.c_str(), font::big); - percLabel->setID("percentage"); - percLabel->setPosition(ss->getScaledContentSize() / 2.f); - percLabel->ignoreAnchorPointForPosition(false); - percLabel->setAnchorPoint({0, 0}); - percLabel->setScale(2.5); + auto percLabel = Label::create(percLabelText.c_str(), font::big); + percLabel->setID("percentage"); + percLabel->setPosition(ss->getScaledContentSize() / 2.f); + percLabel->ignoreAnchorPointForPosition(false); + percLabel->setAnchorPoint({0, 0}); + percLabel->setScale(2.5); - ss->addChild(percLabel); + ss->addChild(percLabel); - auto rA = rng::pc(); - auto rB = rng::pc(); + auto rA = rng::pc(); + auto rB = rng::pc(); - float yA = s->getScaledContentHeight() * rB; // starting height pos - float yB = s->getScaledContentHeight() * rA; // ending height pos + float yA = getScaledContentHeight() * rB; // starting height pos + float yB = getScaledContentHeight() * rA; // ending height pos - ss->setPositionY(s->getScaledContentHeight() * yA); - ss->setRotation(360.f * (yA * yB)); // random rotation + ss->setPositionY(getScaledContentHeight() * yA); + ss->setRotation(360.f * (yA * yB)); // random rotation - auto move = CCEaseIn::create(CCMoveTo::create(10.f, {s->getScaledContentWidth() + 192.f, s->getScaledContentHeight() * yB}), 1.f); - auto rotate = CCEaseOut::create(CCRotateBy::create(12.5f, 45.f), 1.f); + auto move = CCEaseIn::create(CCMoveTo::create(10.f, {getScaledContentWidth() + 192.f, getScaledContentHeight() * yB}), 1.f); + auto rotate = CCEaseOut::create(CCRotateBy::create(12.5f, 45.f), 1.f); - auto action = CCSpawn::createWithTwoActions(move, rotate); - ss->runAction(action); + auto action = CCSpawn::createWithTwoActions(move, rotate); + ss->runAction(action); - log::info("Animated sprite successfully"); - } else { - log::error("Mocked sprite was destroyed before load callback"); - }; - }; + log::info("Animated sprite successfully"); } else { log::error("Sprite failed to load: {}", res.unwrapErr()); - if (auto ss = screenshot.lock()) ss->removeFromParent(); + ss->removeFromParent(); }; }); diff --git a/src/ui/src/Menu.cpp b/src/ui/src/Menu.cpp index f6149c13..fbd9fbc5 100644 --- a/src/ui/src/Menu.cpp +++ b/src/ui/src/Menu.cpp @@ -172,24 +172,20 @@ void Menu::setupImageBackground(fs::path const& path) { m_impl->themeBackground->setID("theme-bg"); m_impl->themeBackground->setPosition(m_bgSprite->getScaledContentSize() / 2.f); - m_impl->themeBackground->setLoadCallback([self = WeakRef(this), themeBg = WeakRef(m_impl->themeBackground)](Result<> res) { - if (auto s = self.lock()) { - if (auto bg = themeBg.lock()) { - if (res.isOk()) { - cue::rescaleToMatch(bg, s->m_bgSprite, true); - bg->setOpacity(100); - - log::debug("Successfully loaded theme background"); - } else if (res.isErr()) { - log::error("Failed to load theme background: {}", res.unwrapErr()); - } else { - log::error("Failed to load theme background for an unknown reason"); - }; + m_impl->themeBackground->setLoadCallback([this, themeBg = WeakRef(m_impl->themeBackground)](Result<> res) { + if (auto bg = themeBg.lock()) { + if (res.isOk()) { + cue::rescaleToMatch(bg, m_bgSprite, true); + bg->setOpacity(100); + + log::debug("Successfully loaded theme background"); + } else if (res.isErr()) { + log::error("Failed to load theme background: {}", res.unwrapErr()); } else { - log::error("Theme background sprite was destroyed before load callback"); + log::error("Failed to load theme background for an unknown reason"); }; } else { - log::error("Menu was destroyed before theme background load callback"); + log::error("Theme background sprite was destroyed before load callback"); }; }); diff --git a/src/ui/src/MenuExtras.cpp b/src/ui/src/MenuExtras.cpp index 921ddd52..bea5818b 100644 --- a/src/ui/src/MenuExtras.cpp +++ b/src/ui/src/MenuExtras.cpp @@ -272,6 +272,8 @@ void MenuDiscord::setupAuthInterface() { m_loading = LoadingSpinner::create(25.f); m_loading->setPosition(m_linkBtn->getPosition()); + + m_mainLayer->addChild(m_loading, 9); }; m_linkBtn = Button::createWithNode( @@ -283,8 +285,6 @@ void MenuDiscord::setupAuthInterface() { [this, hideBtns](auto) { hideBtns(); - m_mainLayer->addChild(m_loading, 9); - gdc::startLink([self = WeakRef(this)](Result res) { if (res.isErr()) return log::error("{}", std::move(res).unwrapErr()); @@ -301,7 +301,7 @@ void MenuDiscord::setupAuthInterface() { m_mainLayer->addChild(m_linkBtn, 9); - if (gdc::isLinkOngoing()) hideBtns(); + if (gdc::isLinkOngoing() || !gdc::isLinked()) hideBtns(); }; m_label = LabelArea::create(std::move(labelTxt), m_mainLayer->getScaledContentWidth() * 0.4f, 0.5f); @@ -323,6 +323,10 @@ bool MenuDiscord::init(ZStringView theme) { setupAuthInterface(); + if (!gdc::isLinked()) gdc::getLink([self = WeakRef(this)](auto) { + if (auto s = self.lock()) s->setupAuthInterface(); + }); + auto cubicLabel = LabelArea::create("Want to join other gamers and hang out?", m_mainLayer->getScaledContentWidth() * 0.875f, 0.75f); cubicLabel->setID("cubic-studios-discord-label"); @@ -436,9 +440,9 @@ bool MenuDiscordCell::init(gdc::DiscordLink const& profile) { icon->setID("profile-icon"); icon->setAutoResize(true); icon->setAnchorPoint(anchor::center); - icon->setLoadCallback([icon = WeakRef(icon)](Result<> res) { + icon->setLoadCallback([icon](Result<> res) { if (res.isErr()) return log::error("Failed to load Discord profile icon: {}", std::move(res).unwrapErr()); - if (auto i = icon.lock()) cue::rescaleToMatch(i, 40.f); + cue::rescaleToMatch(icon, 40.f); }); iconContainer->addChildAtPosition(icon, Anchor::Center); @@ -555,34 +559,36 @@ bool MenuKofi::init(ZStringView theme) { m_mainLayer->addChildAtPosition(supportBtn, Anchor::Center, {0.f, -17.5f}); - auto discordRes = gdc::getDiscordLink(); - - if (discordRes.isErr()) { - auto linkBtn = Button::createWithNode( - ButtonSprite::create( - "Link Account", - font::gold, - themes::getButtonSquareSprite(theme), - 0.875f), - [theme](auto) { - if (auto popup = MenuDiscord::create(theme)) popup->show(); - }); - linkBtn->setID("account-link-btn"); - linkBtn->setScale(0.875f); - - m_mainLayer->addChildAtPosition(linkBtn, Anchor::Bottom); - }; + gdc::getLink([self = WeakRef(this), theme = std::string{theme}](Result res) { + if (auto s = self.lock()) { + if (res.isErr()) { + auto linkBtn = Button::createWithNode( + ButtonSprite::create( + "Link Account", + font::gold, + themes::getButtonSquareSprite(theme), + 0.875f), + [theme](auto) { + if (auto popup = MenuDiscord::create(theme)) popup->show(); + }); + linkBtn->setID("account-link-btn"); + linkBtn->setScale(0.875f); + + s->m_mainLayer->addChildAtPosition(linkBtn, Anchor::Bottom); + }; - std::string linkLabelTxt = discordRes.isOk() - ? fmt::format("Discord account @{} authorized & linked!", std::move(discordRes).unwrap().username) - : "Discord account not linked.\nThis is required to receive supporter perks!"; + std::string linkLabelTxt = res.isOk() + ? fmt::format("Discord account @{} authorized & linked!", std::move(res).unwrap().username) + : "Discord account not linked.\nThis is required to receive supporter perks!"; - auto linkLabel = Label::createRich(std::move(linkLabelTxt), font::chat); - linkLabel->setScale(0.75f); - linkLabel->setAnchorPoint({0.5, 1}); - linkLabel->setAlignment(Label::Alignment::Center); + auto linkLabel = Label::createRich(std::move(linkLabelTxt), font::chat); + linkLabel->setScale(0.75f); + linkLabel->setAnchorPoint({0.5, 1}); + linkLabel->setAlignment(Label::Alignment::Center); - m_mainLayer->addChildAtPosition(linkLabel, Anchor::Bottom, {0.f, 47.5f}); + s->m_mainLayer->addChildAtPosition(linkLabel, Anchor::Bottom, {0.f, 47.5f}); + }; + }); supportBtn->runAction( CCEaseExponentialInOut::create( From ae8c8dc75e37cc9c2d668b114d1759cf54ebc2c6 Mon Sep 17 00:00:00 2001 From: Cheeseworks Date: Sat, 5 Sep 2026 10:16:56 -0500 Subject: [PATCH 4/6] refactor some stuff --- src/ui/Menu.h | 1 + src/ui/MenuCredits.hpp | 7 --- src/ui/MenuExtras.hpp | 21 +++------ src/ui/src/Menu.cpp | 16 +++++-- src/ui/src/MenuCredits.cpp | 12 ----- src/ui/src/MenuExtras.cpp | 94 ++++++++++++++++++-------------------- 6 files changed, 62 insertions(+), 89 deletions(-) diff --git a/src/ui/Menu.h b/src/ui/Menu.h index 99b6f1dd..6bcfb5f1 100644 --- a/src/ui/Menu.h +++ b/src/ui/Menu.h @@ -33,6 +33,7 @@ namespace horrible { Menu(); ~Menu(); + void onEnter() override; void onExit() override; bool init() override; diff --git a/src/ui/MenuCredits.hpp b/src/ui/MenuCredits.hpp index aecc973b..c46412ed 100644 --- a/src/ui/MenuCredits.hpp +++ b/src/ui/MenuCredits.hpp @@ -38,18 +38,11 @@ namespace horrible { geode::Button::ButtonCallback callback; }; - private: - static MenuCredits* s_inst; - protected: - void onExit() override; - bool init(geode::ZStringView theme); public: static MenuCredits* create(geode::ZStringView theme); - - static MenuCredits* get() noexcept; }; class CreditsManager final : public base::Singleton { diff --git a/src/ui/MenuExtras.hpp b/src/ui/MenuExtras.hpp index ed077422..0f08c491 100644 --- a/src/ui/MenuExtras.hpp +++ b/src/ui/MenuExtras.hpp @@ -11,8 +11,6 @@ namespace horrible { class MenuSuggest final : public geode::Popup { private: - static MenuSuggest* s_inst; - static asp::Instant s_lastSuggest; geode::TextInput* m_topicInput; @@ -21,16 +19,12 @@ namespace horrible { geode::LoadingSpinner* m_loading; protected: - void onExit() override; - void processSuggestion(geode::Button* sender); bool init(geode::ZStringView theme); public: static MenuSuggest* create(geode::ZStringView theme); - - static MenuSuggest* get() noexcept; }; class SupporterState final : public base::Singleton { @@ -58,8 +52,6 @@ namespace horrible { class MenuDiscord final : public geode::Popup { private: - static MenuDiscord* s_inst; - geode::Label* m_linkLabel = nullptr; MenuDiscordCell* m_discordCell = nullptr; @@ -75,22 +67,21 @@ namespace horrible { public: static MenuDiscord* create(geode::ZStringView theme); - - static MenuDiscord* get() noexcept; }; class MenuKofi final : public geode::Popup { private: - static MenuKofi* s_inst; + ui::LabelArea* m_infoContainer = nullptr; + geode::LoadingSpinner* m_loading = nullptr; - protected: - void onExit() override; + geode::Button* m_linkBtn = nullptr; + geode::Label* m_linkLabel = nullptr; + geode::LoadingSpinner* m_linkLoading = nullptr; + protected: bool init(geode::ZStringView theme); public: static MenuKofi* create(geode::ZStringView theme); - - static MenuKofi* get() noexcept; }; }; diff --git a/src/ui/src/Menu.cpp b/src/ui/src/Menu.cpp index fbd9fbc5..b5717063 100644 --- a/src/ui/src/Menu.cpp +++ b/src/ui/src/Menu.cpp @@ -604,11 +604,18 @@ bool Menu::init() { return true; }; +void Menu::onEnter() { + Popup::onEnter(); + s_inst = this; +}; + void Menu::onExit() { - if (auto credits = MenuCredits::get()) credits->removeFromParent(); - if (auto suggest = MenuSuggest::get()) suggest->removeFromParent(); - if (auto discord = MenuDiscord::get()) discord->removeFromParent(); - if (auto kofi = MenuKofi::get()) kofi->removeFromParent(); + if (auto scene = CCScene::get()) { + if (auto credits = scene->getChildByType()) credits->removeFromParent(); + if (auto suggest = scene->getChildByType()) suggest->removeFromParent(); + if (auto discord = scene->getChildByType()) discord->removeFromParent(); + if (auto kofi = scene->getChildByType()) kofi->removeFromParent(); + }; s_inst = nullptr; @@ -623,7 +630,6 @@ Menu* Menu::create() { auto ret = new Menu(); if (ret->init()) { ret->autorelease(); - s_inst = ret; return ret; }; diff --git a/src/ui/src/MenuCredits.cpp b/src/ui/src/MenuCredits.cpp index 8c4d8370..2d0bd1ae 100644 --- a/src/ui/src/MenuCredits.cpp +++ b/src/ui/src/MenuCredits.cpp @@ -141,8 +141,6 @@ MenuPlayer* MenuPlayer::create(ZStringView name, int account, int icon, int colo return nullptr; }; -MenuCredits* MenuCredits::s_inst = nullptr; - bool MenuCredits::init(ZStringView theme) { auto btns = themes::getCircleBaseColor(theme); @@ -378,20 +376,10 @@ bool MenuCredits::init(ZStringView theme) { return true; }; -void MenuCredits::onExit() { - s_inst = nullptr; - Popup::onExit(); -}; - -MenuCredits* MenuCredits::get() noexcept { - return s_inst; -}; - MenuCredits* MenuCredits::create(ZStringView theme) { auto ret = new MenuCredits(); if (ret->init(theme)) { ret->autorelease(); - s_inst = ret; return ret; }; diff --git a/src/ui/src/MenuExtras.cpp b/src/ui/src/MenuExtras.cpp index bea5818b..d29889e9 100644 --- a/src/ui/src/MenuExtras.cpp +++ b/src/ui/src/MenuExtras.cpp @@ -21,8 +21,6 @@ static constexpr auto g_suggestWait = 60; }); }; -MenuSuggest* MenuSuggest::s_inst = nullptr; - asp::Instant MenuSuggest::s_lastSuggest = asp::Instant(); bool MenuSuggest::init(ZStringView theme) { @@ -185,20 +183,10 @@ void MenuSuggest::processSuggestion(Button* sender) { }); }; -void MenuSuggest::onExit() { - s_inst = nullptr; - Popup::onExit(); -}; - -MenuSuggest* MenuSuggest::get() noexcept { - return s_inst; -}; - MenuSuggest* MenuSuggest::create(ZStringView theme) { auto ret = new MenuSuggest(); if (ret->init(theme)) { ret->autorelease(); - s_inst = ret; return ret; }; @@ -210,6 +198,8 @@ void SupporterState::validateSupporter(Callback&& cb) { if (!gdc::isLinked()) { m_supporter = false; return cb(Err("Player is signed out or not linked with Discord")); + } else { + if (m_supporter) return cb(Ok()); }; if (auto gjam = GJAccountManager::sharedState()) { @@ -221,10 +211,14 @@ void SupporterState::validateSupporter(Callback&& cb) { async::spawn( req.get("https://api.cubicstudios.xyz/breakeode/v1/discord/supporter"), [this, cb = std::move(cb)](web::WebResponse res) { - if (res.ok()) log::info("User is a supporter of Breakeode"); - m_supporter = res.ok(); + if (res.ok()) { + log::info("User is a supporter of Breakeode"); + m_supporter = res.ok(); - return cb(Ok()); + return cb(Ok()); + }; + + return cb(Err("User is not a Breakeode supporter")); }); }; }; @@ -233,8 +227,6 @@ bool SupporterState::isSupporter() const noexcept { return m_supporter; }; -MenuDiscord* MenuDiscord::s_inst = nullptr; - void MenuDiscord::setupAuthInterface() { cue::resetNode(m_discordCell); cue::resetNode(m_linkLabel); @@ -388,15 +380,10 @@ bool MenuDiscord::init(ZStringView theme) { return true; }; -MenuDiscord* MenuDiscord::get() noexcept { - return s_inst; -}; - MenuDiscord* MenuDiscord::create(ZStringView theme) { auto ret = new MenuDiscord(); if (ret->init(theme)) { ret->autorelease(); - s_inst = ret; return ret; }; @@ -479,8 +466,6 @@ MenuDiscordCell* MenuDiscordCell::create(gdc::DiscordLink const& profile) { return nullptr; }; -MenuKofi* MenuKofi::s_inst = nullptr; - bool MenuKofi::init(ZStringView theme) { auto btns = themes::getCircleBaseColor(theme); @@ -525,16 +510,29 @@ bool MenuKofi::init(ZStringView theme) { m_mainLayer->addChild(border, -1); - auto as = SupporterState::get(); + if (auto as = SupporterState::get()) { + m_loading = LoadingSpinner::create(37.5f); + m_loading->setZOrder(9); - std::string infoLabelTxt = as->isSupporter() - ? "Thanks for supporting Breakeode! You can now press the badge below to see the Supporter badge on your profile. Feel free to join Breakeode's Discord server for even more perks." - : "Horrible Menu couldn't be made possible without community support. Feel free to donate through Ko-fi and get cool perks such as the badge below!\nPress the badge to get started."; + m_mainLayer->addChildAtPosition(m_loading, Anchor::Center); - auto infoContainer = LabelArea::create(std::move(infoLabelTxt), m_mainLayer->getScaledContentWidth() - 15.f, 0.625f, as->isSupporter() ? colors::gold : colors::purple); - infoContainer->setID("kofi-description"); + as->validateSupporter([self = WeakRef(this)](Result<> res) { + if (auto s = self.lock()) { + std::string infoLabelTxt = res.isOk() + ? "Thanks for supporting Breakeode! You can now press the badge below to see the Supporter badge on your profile. Feel free to join Breakeode's Discord server for even more perks." + : "Horrible Menu couldn't be made possible without community support. Feel free to donate through Ko-fi and get cool perks such as the badge below!\nPress the badge to get started."; - m_mainLayer->addChildAtPosition(infoContainer, Anchor::Center, {0.f, 12.5f + infoContainer->getScaledContentHeight()}); + s->m_infoContainer = LabelArea::create(std::move(infoLabelTxt), s->m_mainLayer->getScaledContentWidth() - 15.f, 0.625f, res.isOk() ? colors::black : colors::purple); + s->m_infoContainer->setID("kofi-description"); + + s->m_mainLayer->addChildAtPosition(s->m_infoContainer, Anchor::Center, {0.f, 12.5f + s->m_infoContainer->getScaledContentHeight()}); + + if (res.isErr()) log::error("Supporter validation failed: {}", std::move(res).unwrapErr()); + + cue::resetNode(s->m_loading); + }; + }); + }; auto supportBtn = Button::createWithSpriteFrameName( "badge_supporter.png"_spr, @@ -555,14 +553,18 @@ bool MenuKofi::init(ZStringView theme) { }; }); supportBtn->setID("support-us-btn"); + supportBtn->setScaleMultiplier(1.125f); supportBtn->setScale(0.f); m_mainLayer->addChildAtPosition(supportBtn, Anchor::Center, {0.f, -17.5f}); + m_linkLoading = LoadingSpinner::create(25.f); + m_mainLayer->addChildAtPosition(m_linkLoading, Anchor::Bottom, {0.f, 32.5f}); + gdc::getLink([self = WeakRef(this), theme = std::string{theme}](Result res) { if (auto s = self.lock()) { if (res.isErr()) { - auto linkBtn = Button::createWithNode( + s->m_linkBtn = Button::createWithNode( ButtonSprite::create( "Link Account", font::gold, @@ -571,22 +573,24 @@ bool MenuKofi::init(ZStringView theme) { [theme](auto) { if (auto popup = MenuDiscord::create(theme)) popup->show(); }); - linkBtn->setID("account-link-btn"); - linkBtn->setScale(0.875f); + s->m_linkBtn->setID("account-link-btn"); + s->m_linkBtn->setScale(0.875f); - s->m_mainLayer->addChildAtPosition(linkBtn, Anchor::Bottom); + s->m_mainLayer->addChildAtPosition(s->m_linkBtn, Anchor::Bottom); }; std::string linkLabelTxt = res.isOk() ? fmt::format("Discord account @{} authorized & linked!", std::move(res).unwrap().username) : "Discord account not linked.\nThis is required to receive supporter perks!"; - auto linkLabel = Label::createRich(std::move(linkLabelTxt), font::chat); - linkLabel->setScale(0.75f); - linkLabel->setAnchorPoint({0.5, 1}); - linkLabel->setAlignment(Label::Alignment::Center); + s->m_linkLabel = Label::createRich(std::move(linkLabelTxt), font::chat); + s->m_linkLabel->setScale(0.75f); + s->m_linkLabel->setAnchorPoint({0.5, 1}); + s->m_linkLabel->setAlignment(Label::Alignment::Center); + + s->m_mainLayer->addChildAtPosition(s->m_linkLabel, Anchor::Bottom, {0.f, 47.5f}); - s->m_mainLayer->addChildAtPosition(linkLabel, Anchor::Bottom, {0.f, 47.5f}); + cue::resetNode(s->m_linkLoading); }; }); @@ -628,20 +632,10 @@ bool MenuKofi::init(ZStringView theme) { return true; }; -void MenuKofi::onExit() { - s_inst = nullptr; - Popup::onExit(); -}; - -MenuKofi* MenuKofi::get() noexcept { - return s_inst; -}; - MenuKofi* MenuKofi::create(ZStringView theme) { auto ret = new MenuKofi(); if (ret->init(theme)) { ret->autorelease(); - s_inst = ret; return ret; }; From 3c71d98e1b2cd64df304e5565f189f03fd15fff8 Mon Sep 17 00:00:00 2001 From: Cheeseworks Date: Sat, 5 Sep 2026 12:44:44 -0500 Subject: [PATCH 5/6] finish gdc impl --- src/ui/src/MenuExtras.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ui/src/MenuExtras.cpp b/src/ui/src/MenuExtras.cpp index d29889e9..a584cc15 100644 --- a/src/ui/src/MenuExtras.cpp +++ b/src/ui/src/MenuExtras.cpp @@ -277,7 +277,7 @@ void MenuDiscord::setupAuthInterface() { [this, hideBtns](auto) { hideBtns(); - gdc::startLink([self = WeakRef(this)](Result res) { + gdc::startLinkAsync([self = WeakRef(this)](gdc::LinkResult res) { if (res.isErr()) return log::error("{}", std::move(res).unwrapErr()); auto discord = std::move(res).unwrap(); @@ -315,7 +315,7 @@ bool MenuDiscord::init(ZStringView theme) { setupAuthInterface(); - if (!gdc::isLinked()) gdc::getLink([self = WeakRef(this)](auto) { + if (!gdc::isLinked()) gdc::getLinkAsync([self = WeakRef(this)](auto) { if (auto s = self.lock()) s->setupAuthInterface(); }); @@ -561,7 +561,7 @@ bool MenuKofi::init(ZStringView theme) { m_linkLoading = LoadingSpinner::create(25.f); m_mainLayer->addChildAtPosition(m_linkLoading, Anchor::Bottom, {0.f, 32.5f}); - gdc::getLink([self = WeakRef(this), theme = std::string{theme}](Result res) { + gdc::getLinkAsync([self = WeakRef(this), theme = std::string{theme}](gdc::LinkResult res) { if (auto s = self.lock()) { if (res.isErr()) { s->m_linkBtn = Button::createWithNode( From bbfea44f4b59bf8b5a30c97b73764a439d671af8 Mon Sep 17 00:00:00 2001 From: Cheeseworks Date: Sat, 5 Sep 2026 13:59:07 -0500 Subject: [PATCH 6/6] fix some quirks --- src/ui/MenuExtras.hpp | 2 +- src/ui/src/MenuExtras.cpp | 16 +++++++++------- src/util/ui/LabelArea.hpp | 5 +++++ src/util/ui/src/LabelArea.cpp | 18 +++++++++++------- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/ui/MenuExtras.hpp b/src/ui/MenuExtras.hpp index 0f08c491..1fbd65c8 100644 --- a/src/ui/MenuExtras.hpp +++ b/src/ui/MenuExtras.hpp @@ -60,7 +60,7 @@ namespace horrible { ui::LabelArea* m_label = nullptr; - void setupAuthInterface(); + void setupAuthInterface(bool forceHide = false); protected: bool init(geode::ZStringView theme); diff --git a/src/ui/src/MenuExtras.cpp b/src/ui/src/MenuExtras.cpp index a584cc15..0051963a 100644 --- a/src/ui/src/MenuExtras.cpp +++ b/src/ui/src/MenuExtras.cpp @@ -227,7 +227,7 @@ bool SupporterState::isSupporter() const noexcept { return m_supporter; }; -void MenuDiscord::setupAuthInterface() { +void MenuDiscord::setupAuthInterface(bool forceHide) { cue::resetNode(m_discordCell); cue::resetNode(m_linkLabel); cue::resetNode(m_linkBtn); @@ -236,6 +236,9 @@ void MenuDiscord::setupAuthInterface() { std::string labelTxt; + m_label = LabelArea::create("Loading...\nPlease sit tight!", m_mainLayer->getScaledContentWidth() * 0.4f, 0.5f); + m_label->setAnchorPoint({1, 0}); + auto discordRes = gdc::getDiscordLink(); if (discordRes.isOk()) { auto const discord = std::move(discordRes).unwrap(); @@ -261,6 +264,7 @@ void MenuDiscord::setupAuthInterface() { auto const hideBtns = [this]() { m_linkBtn->setVisible(false); + m_label->setVisible(false); m_loading = LoadingSpinner::create(25.f); m_loading->setPosition(m_linkBtn->getPosition()); @@ -293,11 +297,10 @@ void MenuDiscord::setupAuthInterface() { m_mainLayer->addChild(m_linkBtn, 9); - if (gdc::isLinkOngoing() || !gdc::isLinked()) hideBtns(); + if (gdc::isLinkOngoing() || (forceHide && !gdc::isLinked())) hideBtns(); }; - m_label = LabelArea::create(std::move(labelTxt), m_mainLayer->getScaledContentWidth() * 0.4f, 0.5f); - m_label->setAnchorPoint({1, 0}); + m_label->setText(std::move(labelTxt)); m_mainLayer->addChildAtPosition(m_label, Anchor::BottomRight, {-15.f, 15.f}); }; @@ -313,8 +316,7 @@ bool MenuDiscord::init(ZStringView theme) { popup::closeBtnID(m_closeBtn); - setupAuthInterface(); - + setupAuthInterface(true); if (!gdc::isLinked()) gdc::getLinkAsync([self = WeakRef(this)](auto) { if (auto s = self.lock()) s->setupAuthInterface(); }); @@ -511,7 +513,7 @@ bool MenuKofi::init(ZStringView theme) { m_mainLayer->addChild(border, -1); if (auto as = SupporterState::get()) { - m_loading = LoadingSpinner::create(37.5f); + m_loading = LoadingSpinner::create(42.5f); m_loading->setZOrder(9); m_mainLayer->addChildAtPosition(m_loading, Anchor::Center); diff --git a/src/util/ui/LabelArea.hpp b/src/util/ui/LabelArea.hpp index 7ef69e00..8df3a05c 100644 --- a/src/util/ui/LabelArea.hpp +++ b/src/util/ui/LabelArea.hpp @@ -7,11 +7,16 @@ namespace horrible { namespace ui { class LabelArea final : public cocos2d::CCNode { + private: + geode::Label* m_textLabel; + protected: bool init(std::string text, float width, float scale, cocos2d::ccColor3B const& bgColor); public: static LabelArea* create(std::string text, float width, float scale = 1.f, cocos2d::ccColor3B const& bgColor = colors::black); + + void setText(std::string text); }; }; }; \ No newline at end of file diff --git a/src/util/ui/src/LabelArea.cpp b/src/util/ui/src/LabelArea.cpp index 0c11885e..454ee8b5 100644 --- a/src/util/ui/src/LabelArea.cpp +++ b/src/util/ui/src/LabelArea.cpp @@ -13,25 +13,29 @@ bool LabelArea::init(std::string text, float width, float scale, cocos2d::ccColo setAnchorPoint(anchor::center); setContentWidth(width); - auto textLabel = Label::createRich(std::move(text), font::chat); - textLabel->setZOrder(1); - textLabel->setScale(scale); - textLabel->setAlignment(Label::Alignment::Center); - textLabel->setMaxWidth((getScaledContentWidth() - 8.75f) / scale); + m_textLabel = Label::createRich(std::move(text), font::chat); + m_textLabel->setZOrder(1); + m_textLabel->setScale(scale); + m_textLabel->setAlignment(Label::Alignment::Center); + m_textLabel->setMaxWidth((getScaledContentWidth() - 8.75f) / scale); - setContentHeight(textLabel->getScaledContentHeight() + 5.f); + setContentHeight(m_textLabel->getScaledContentHeight() + 5.f); auto textContainer = NineSlice::create(themes::square); textContainer->setColor(bgColor); textContainer->setContentSize(getScaledContentSize()); - textContainer->addChildAtPosition(textLabel, Anchor::Center, {}, false); + textContainer->addChildAtPosition(m_textLabel, Anchor::Center, {}, false); addChildAtPosition(textContainer, Anchor::Center, {}, false); return true; }; +void LabelArea::setText(std::string text) { + m_textLabel->setRichText(std::move(text)); +}; + LabelArea* LabelArea::create(std::string text, float width, float scale, cocos2d::ccColor3B const& bgColor) { auto ret = new LabelArea(); if (ret->init(std::move(text), width, scale, bgColor)) {