Skip to content

Instantly share code, notes, and snippets.

@camila314
Last active May 25, 2025 19:01
Show Gist options
  • Select an option

  • Save camila314/cb7b86919dcb894af03f05b91b5bc7e5 to your computer and use it in GitHub Desktop.

Select an option

Save camila314/cb7b86919dcb894af03f05b91b5bc7e5 to your computer and use it in GitHub Desktop.
Fully functional recreation of ShareLevelLayer using UIBuilder and Signals
#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();
}
};
@RayDeeUx
Copy link

aww man i wanted one that brought back the passcode requirement :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment