Last active
May 25, 2025 19:01
-
-
Save camila314/cb7b86919dcb894af03f05b91b5bc7e5 to your computer and use it in GitHub Desktop.
Fully functional recreation of ShareLevelLayer using UIBuilder and Signals
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <Geode/Geode.hpp> | |
| #include <Geode/utils/base64.hpp> | |
| #include <Geode/utils/Signals.hpp> | |
| #include <Geode/ui/SimpleAxisLayout.hpp> | |
| #include <UIBuilder.hpp> | |
| using namespace geode::prelude; | |
| class UploadLevelPopup : public CCLayerColor { | |
| Signal<int> m_stars = 0; | |
| Signal<bool> m_unlisted = false; | |
| Signal<bool> m_friendsOnly = false; | |
| GJGameLevel* m_level; | |
| auto requestStarsUI() { | |
| auto ui = Build<CCNode>::create() | |
| .id("request-stars") | |
| .contentSize(225, 65) | |
| .anchorPoint({0.5, 0.5}) | |
| .layout(RowLayout::create()->setGrowCrossAxis(true)->setAxisAlignment(AxisAlignment::Between)->setAutoScale(false)); | |
| for (int i = 1; i <= 10; ++i) { | |
| auto container = Build<CCMenu>::create() | |
| .id("star-menu") | |
| .contentSize(46, 30) | |
| .scale(0.8) | |
| .parent(ui); | |
| auto offBtn = Build<ButtonSprite>::create(std::to_string(i).c_str(), 25, true, "bigFont.fnt", "GJ_button_04.png", 30, 0.7) | |
| .intoMenuItem([&stars = m_stars, i] { stars = i; }) | |
| .id("enabled-btn") | |
| .parent(container) | |
| .center(); | |
| auto onBtn = Build<ButtonSprite>::create(std::to_string(i).c_str(), 25, true, "bigFont.fnt", "GJ_button_01.png", 30, 0.7) | |
| .intoMenuItem([]{}) | |
| .id("disabled-btn") | |
| .parent(container) | |
| .center(); | |
| reactToChanges([onBtn = onBtn.collect(), offBtn = offBtn.collect(), this, i] { | |
| offBtn->setVisible(m_stars != i); | |
| onBtn->setVisible(m_stars == i); | |
| }); | |
| } | |
| return ui.updateLayout(); | |
| } | |
| auto difficultyIcon() { | |
| auto icon = Build<CCSprite>::createSpriteName("difficulty_00_btn_001.png") | |
| .id("difficulty-icon") | |
| .center() | |
| .move(155, -41); | |
| reactToChanges([icon = icon.collect(), this] { | |
| char const* frame; | |
| switch (*m_stars) { | |
| default: | |
| frame = "difficulty_00_btn_001.png"; break; | |
| case 1: | |
| frame = "difficulty_auto_btn_001.png"; break; | |
| case 2: | |
| frame = "difficulty_01_btn_001.png"; break; | |
| case 3: | |
| frame = "difficulty_02_btn_001.png"; break; | |
| case 4: | |
| case 5: | |
| frame = "difficulty_03_btn_001.png"; break; | |
| case 6: | |
| case 7: | |
| frame = "difficulty_04_btn_001.png"; break; | |
| case 8: | |
| case 9: | |
| frame = "difficulty_05_btn_001.png"; break; | |
| case 10: | |
| frame = "difficulty_06_btn_001.png"; break; | |
| } | |
| icon->setDisplayFrame(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(frame)); | |
| }); | |
| return icon; | |
| } | |
| public: | |
| static UploadLevelPopup* create(GJGameLevel* gl) { | |
| auto layer = new UploadLevelPopup(); | |
| if (layer && layer->init(gl)) { | |
| layer->autorelease(); | |
| return layer; | |
| } | |
| CC_SAFE_DELETE(layer); | |
| return nullptr; | |
| } | |
| bool init(GJGameLevel* gl) { | |
| CCLayerColor::initWithColor({0, 0, 0, 150}); | |
| setID("level-upload"); | |
| setContentSize(CCDirector::sharedDirector()->getWinSize()); | |
| setKeypadEnabled(true); | |
| Build(this).initTouch(); | |
| m_stars = gl->m_isUploaded ? gl->m_starsRequested : 0; | |
| m_unlisted = gl->m_unlisted; | |
| m_friendsOnly = gl->m_friendsOnly; | |
| m_level = gl; | |
| std::string desc = gl->m_levelDesc.empty() ? "(No description provided)" : base64::decodeString(gl->m_levelDesc).unwrap(); | |
| Build<CCNode>::create().id("container").parent(this).children( | |
| Build<CCScale9Sprite>::create("GJ_square01.png") | |
| .id("bg-sprite") | |
| .contentSize(420, 300) | |
| .center(), | |
| Build<CCMenu>::create() | |
| .id("options-menu") | |
| .center() | |
| .move(184, 124) | |
| .child( | |
| Build<CCSprite>::createSpriteName("GJ_optionsBtn02_001.png") | |
| .id("options-btn") | |
| .scale(0.8) | |
| .intoMenuItem(this, &UploadLevelPopup::onOptions) | |
| ), | |
| Build<CCNode>::create() | |
| .id("content") | |
| .center() | |
| .anchorPoint({0.5, 0.5}) | |
| .contentSize(400, 285) | |
| .layout(SimpleColumnLayout::create() | |
| ->setMainAxisAlignment(MainAxisAlignment::Between) | |
| ->setMainAxisDirection(AxisDirection::TopToBottom) | |
| ->setMainAxisScaling(AxisScaling::None)) | |
| .children( | |
| Build<CCLabelBMFont>::create(gl->m_levelName.c_str(), "goldFont.fnt") | |
| .id("level-name"), | |
| Build<CCLabelBMFont>::create("You cannot change the name once it is shared.", "chatFont.fnt") | |
| .id("warn-label") | |
| .scale(0.8) | |
| .color(ccc3(255, 255, 0)), | |
| Build<CCScale9Sprite>::create("square02b_001.png") | |
| .id("description") | |
| .contentSize(380, 50) | |
| .color(ccc3(130, 64, 33)) | |
| .intoNewChild( | |
| Build<SimpleTextArea>::create(desc, "chatFont.fnt", 0.8, 380) | |
| .id("description-text") | |
| .with([](SimpleTextArea* area) { | |
| area->setAlignment(cocos2d::kCCTextAlignmentCenter); | |
| }) | |
| ).center().intoParent(), | |
| Build<CCLabelBMFont>::create("Request Stars", "bigFont.fnt") | |
| .id("request-label") | |
| .scale(0.5), | |
| requestStarsUI(), | |
| Build<CCLabelBMFont>::create("By sharing a level you give the rights to use the " | |
| "level\n(in any way) to the creators of Geometry Dash", "chatFont.fnt") | |
| .id("rights-label") | |
| .scale(0.65) | |
| .alignment(cocos2d::kCCTextAlignmentCenter), | |
| Build<CCMenu>::create() | |
| .id("bottom-menu") | |
| .width(300) | |
| .height(35) | |
| .layout(SimpleRowLayout::create()->setMainAxisAlignment(MainAxisAlignment::Between)) | |
| .anchorPoint({0.5, 0.5}) | |
| .children( | |
| Build<ButtonSprite>::create("Cancel") | |
| .intoMenuItem([this] { removeFromParentAndCleanup(true); }) | |
| .id("cancel-btn"), | |
| Build<ButtonSprite>::create("Share Level", "goldFont.fnt", m_level->m_isUploaded ? "GJ_button_04.png" : "GJ_button_01.png") | |
| .intoMenuItem(this, &UploadLevelPopup::onUpload) | |
| .id("upload-btn") | |
| ).updateLayout() | |
| ).updateLayout(), | |
| difficultyIcon() | |
| ); | |
| return true; | |
| } | |
| void onOptions(CCObject*) { | |
| auto popup = new CCLayerColor; | |
| popup->initWithColor({0, 0, 0, 150}); | |
| popup->autorelease(); | |
| Build(popup).initTouch().id("settings-popup"); | |
| char const* helpText = "<cg>Unlisted</c> makes it so that your level can only be found by " | |
| "<cy>searching for the exact ID</c>. It will <cr>not</c> be visible " | |
| "on your profile, from search, or anywhere else. Unlisted levels do " | |
| "<cr>not</c> have to be <cp>verified</c> to be uploaded.\n<co>Friends " | |
| "Only</c> makes it so that only <cl>friends</c> who search the exact " | |
| "ID can find it."; | |
| auto makeToggle = [](auto& signal, std::string id, char const* label) { | |
| return Build<CCMenu>::create() | |
| .pos(0, 0) | |
| .id(id + "-menu") | |
| .layout(SimpleRowLayout::create()->setMainAxisScaling(AxisScaling::Grow)->setGap(5)) | |
| .height(25) | |
| .scale(0.8) | |
| .anchorPoint({0, 0}) | |
| .cascadeColor(true) | |
| .children( | |
| Build<CCMenuItemToggler>::createToggle([&](auto t) { signal = t->isToggled(); }) | |
| .id(id + "-toggle") | |
| .cascadeColor(true) | |
| .toggle(!signal), | |
| Build<CCLabelBMFont>::create(label, "bigFont.fnt") | |
| .id(id + "-label") | |
| .scale(0.56) | |
| ).updateLayout(); | |
| }; | |
| Build(popup).children( | |
| Build<CCScale9Sprite>::create("GJ_square01.png") | |
| .id("bg-sprite") | |
| .contentSize(360, 220) | |
| .center(), | |
| Build<CCLabelBMFont>::create("Settings", "bigFont.fnt") | |
| .id("title") | |
| .center() | |
| .move(0, 90), | |
| Build<CCMenu>::create() | |
| .id("close-menu") | |
| .center() | |
| .move(-175, 105) | |
| .child( | |
| Build<CCSprite>::createSpriteName("GJ_closeBtn_001.png") | |
| .scale(0.8) | |
| .intoMenuItem([popup] { popup->removeFromParentAndCleanup(true); }) | |
| .id("close-btn") | |
| ), | |
| Build<CCMenu>::create() | |
| .id("info-menu") | |
| .center() | |
| .move(160, 90) | |
| .child(InfoAlertButton::create("Help", helpText, 1.0)), | |
| Build<CCMenu>::create() | |
| .id("toggle-menu") | |
| .center() | |
| .move(0, 45) | |
| .contentSize(302, 50) | |
| .layout(SimpleRowLayout::create() | |
| ->setMainAxisAlignment(MainAxisAlignment::Between) | |
| ->setMainAxisScaling(AxisScaling::None)) | |
| .children( | |
| makeToggle(m_unlisted, "unlisted", "Unlisted"), | |
| makeToggle(m_friendsOnly, "friends-only", "Friends Only") | |
| ).updateLayout(), | |
| Build<CCLabelBMFont>::create("Starting in 2.2, all new levels are free to copy.\n" | |
| "(Older levels retain any copy settings.)", "chatFont.fnt") | |
| .id("copy-label") | |
| .center() | |
| .move(0, -80) | |
| .scale(0.65) | |
| .alignment(cocos2d::kCCTextAlignmentCenter) | |
| ); | |
| auto friendsOnly = Build(popup).intoChildRecurseID<CCMenu>("friends-only-menu"); | |
| auto unlisted = Build(popup).intoChildRecurseID<CCMenu>("unlisted-menu"); | |
| if (m_level->m_isUploaded || m_level->m_levelID != 0) { | |
| friendsOnly.color(ccc3(166, 166, 166)); | |
| friendsOnly.intoChild<CCMenuItemToggler>(0).enabled(false); | |
| unlisted.color(ccc3(166, 166, 166)); | |
| unlisted.intoChild<CCMenuItemToggler>(0).enabled(false); | |
| } else { | |
| popup->reactToChanges([friendsOnly = friendsOnly.collect(), this] { | |
| Build(friendsOnly).intoChild<CCMenuItemToggler>(0).enabled(*m_unlisted); | |
| Build(friendsOnly).color(*m_unlisted ? ccc3(255, 255, 255) : ccc3(166, 166, 166)); | |
| }); | |
| } | |
| addChild(popup, 999); | |
| } | |
| void onUpload(CCObject*) { | |
| if (!m_level->m_isVerified && !m_unlisted) { | |
| FLAlertLayer::create("Unverified", "You cannot share a level until you have <cg>completed</c> it.\nComplete the level in <cl>Normal</c> mode to verify that it can be beaten!\nA level cannot be verified if it has a <cy>Start Pos</c>.\n<cp>Unlisted</c> levels do not need to be verified.", "Ok")->show(); | |
| } else if (m_level->m_isUploaded) { | |
| FLAlertLayer::create("Uploaded", "This version has already been submitted", "Ok")->show(); | |
| } else { | |
| m_level->m_unlisted = *m_unlisted; | |
| m_level->m_friendsOnly = *m_friendsOnly; | |
| auto upload = new UploadPopup; | |
| upload->autorelease(); | |
| upload->init(m_level); | |
| upload->show(); | |
| } | |
| } | |
| void keyBackClicked() override { | |
| if (auto popup = getChildByID("settings-popup")) | |
| popup->removeFromParentAndCleanup(true); | |
| else | |
| removeFromParentAndCleanup(true); | |
| } | |
| void show() { | |
| auto scene = CCDirector::sharedDirector()->getRunningScene(); | |
| setZOrder(scene->getHighestChildZ() + 1); | |
| setOpacity(0); | |
| runAction(CCFadeTo::create(0.2, 150)); | |
| auto cont = getChildByID("container"); | |
| cont->setScale(0); | |
| cont->setAnchorPoint({0.5, 0.5}); | |
| cont->setContentSize(CCDirector::sharedDirector()->getWinSize()); | |
| Build(cont).center(); | |
| cont->runAction(CCEaseElasticOut::create(CCScaleTo::create(0.5, 1.0), 0.5)); | |
| scene->addChild(this); | |
| } | |
| }; | |
| #include <Geode/modify/EditLevelLayer.hpp> | |
| class $modify(EditLevelLayer) { | |
| void onShare(cocos2d::CCObject* sender) { | |
| UploadLevelPopup::create(m_level)->show(); | |
| } | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
aww man i wanted one that brought back the passcode requirement :(