From a5b40931fbadb061acb0dda10943cbb3168903d9 Mon Sep 17 00:00:00 2001 From: The Bearodactyl Date: Sun, 15 Mar 2026 20:43:10 -0500 Subject: [PATCH 1/5] add dropdown stuff --- loader/include/Geode/ui/Dropdown.hpp | 45 +++ loader/src/ui/mods/settings/SettingNodeV3.cpp | 312 ++++++++-------- loader/src/ui/mods/settings/SettingNodeV3.hpp | 36 +- loader/src/ui/nodes/Dropdown.cpp | 335 ++++++++++++++++++ 4 files changed, 552 insertions(+), 176 deletions(-) create mode 100644 loader/include/Geode/ui/Dropdown.hpp create mode 100644 loader/src/ui/nodes/Dropdown.cpp diff --git a/loader/include/Geode/ui/Dropdown.hpp b/loader/include/Geode/ui/Dropdown.hpp new file mode 100644 index 000000000..8b249eebb --- /dev/null +++ b/loader/include/Geode/ui/Dropdown.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include +#include + +class DropdownOverlay; + +namespace geode { + class GEODE_DLL Dropdown : public cocos2d::CCNode { + class Impl; + std::unique_ptr m_impl; + + friend class ::DropdownOverlay; + + protected: + Dropdown(); + ~Dropdown() override; + + bool init( + float width, std::vector options, + Function callback + ); + + void onOpen(cocos2d::CCObject*); + + public: + static Dropdown* create( + float width, std::vector options, + Function callback + ); + + void setSelected(size_t index); + void setSelected(std::string_view value); + size_t getSelectedIndex() const; + std::string getSelectedValue() const; + + void setEnabled(bool enabled); + bool isEnabled() const; + + void setItems(std::vector options); + }; +} diff --git a/loader/src/ui/mods/settings/SettingNodeV3.cpp b/loader/src/ui/mods/settings/SettingNodeV3.cpp index 3cccbc854..b51932c04 100644 --- a/loader/src/ui/mods/settings/SettingNodeV3.cpp +++ b/loader/src/ui/mods/settings/SettingNodeV3.cpp @@ -1,11 +1,13 @@ #include "SettingNodeV3.hpp" -#include -#include + +#include "KeybindEditPopup.hpp" + #include +#include #include #include -#include -#include "KeybindEditPopup.hpp" +#include +#include class SettingNodeV3::Impl final { public: @@ -25,16 +27,15 @@ class SettingNodeV3::Impl final { }; bool SettingNodeV3::init(std::shared_ptr setting, float width) { - if (!CCNode::init()) - return false; + if (!CCNode::init()) return false; // note: setting may be null due to UnresolvedCustomSettingNodeV3 m_impl = std::make_shared(); m_impl->setting = setting; - m_impl->bg = CCLayerColor::create({ 0, 0, 0, 0 }); - m_impl->bg->setContentSize({ width, 0 }); + m_impl->bg = CCLayerColor::create({0, 0, 0, 0}); + m_impl->bg->setContentSize({width, 0}); m_impl->bg->ignoreAnchorPointForPosition(false); m_impl->bg->setAnchorPoint(ccp(.5f, .5f)); this->addChildAtPosition(m_impl->bg, Anchor::Center); @@ -42,8 +43,11 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { m_impl->nameMenu = CCMenu::create(); m_impl->nameMenu->setContentWidth(width / 2 + 25); - m_impl->nameLabel = CCLabelBMFont::create(setting ? setting->getDisplayName().c_str() : "", "bigFont.fnt"); - m_impl->nameLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .4f)->setScalePriority(1)); + m_impl->nameLabel = + CCLabelBMFont::create(setting ? setting->getDisplayName().c_str() : "", "bigFont.fnt"); + m_impl->nameLabel->setLayoutOptions( + AxisLayoutOptions::create()->setScaleLimits(.1f, .4f)->setScalePriority(1) + ); m_impl->nameMenu->addChild(m_impl->nameLabel); m_impl->statusLabel = CCLabelBMFont::create("", "bigFont.fnt"); @@ -53,29 +57,27 @@ bool SettingNodeV3::init(std::shared_ptr setting, float width) { if (setting && setting->getDescription()) { auto descSpr = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png"); descSpr->setScale(.5f); - m_impl->descButton = CCMenuItemSpriteExtra::create( - descSpr, this, menu_selector(SettingNodeV3::onDescription) - ); + m_impl->descButton = + CCMenuItemSpriteExtra::create(descSpr, this, menu_selector(SettingNodeV3::onDescription)); m_impl->nameMenu->addChild(m_impl->descButton); } auto resetSpr = CCSprite::createWithSpriteFrameName("reset-gold.png"_spr); resetSpr->setScale(.5f); - m_impl->resetButton = CCMenuItemSpriteExtra::create( - resetSpr, this, menu_selector(SettingNodeV3::onReset) - ); + m_impl->resetButton = + CCMenuItemSpriteExtra::create(resetSpr, this, menu_selector(SettingNodeV3::onReset)); m_impl->nameMenu->addChild(m_impl->resetButton); m_impl->nameMenu->setLayout(RowLayout::create()->setAxisAlignment(AxisAlignment::Start)); this->addChildAtPosition(m_impl->nameMenu, Anchor::Left, ccp(10, 0), ccp(0, .5f)); m_impl->buttonMenu = CCMenu::create(); - m_impl->buttonMenu->setContentSize({ width / 2 - 55, 30 }); + m_impl->buttonMenu->setContentSize({width / 2 - 55, 30}); m_impl->buttonMenu->setLayout(AnchorLayout::create()); this->addChildAtPosition(m_impl->buttonMenu, Anchor::Right, ccp(-10, 0), ccp(1, .5f)); - this->setAnchorPoint({ .5f, .5f }); - this->setContentSize({ width, 30 }); + this->setAnchorPoint({.5f, .5f}); + this->setContentSize({width, 30}); return true; } @@ -105,7 +107,9 @@ void SettingNodeV3::updateState(CCNode* invoker) { m_impl->bg->setOpacity(75); } - m_impl->nameMenu->setContentWidth(this->getContentWidth() - m_impl->buttonMenu->getContentWidth() - 25); + m_impl->nameMenu->setContentWidth( + this->getContentWidth() - m_impl->buttonMenu->getContentWidth() - 25 + ); m_impl->nameMenu->updateLayout(); } @@ -116,11 +120,13 @@ void SettingNodeV3::updateState2(CCNode* invoker) { void SettingNodeV3::onDescription(CCObject*) { if (!m_impl->setting) return; auto title = m_impl->setting->getDisplayName(); - MDPopup::create(true, + MDPopup::create( + true, title.c_str(), m_impl->setting->getDescription().value_or("No description provided"), "OK" - )->show(); + ) + ->show(); } void SettingNodeV3::onReset(CCObject*) { @@ -130,7 +136,8 @@ void SettingNodeV3::onReset(CCObject*) { "Are you sure you want to reset {} to default?", this->getSetting()->getDisplayName() ), - "Cancel", "Reset", + "Cancel", + "Reset", [this](auto, bool btn2) { if (btn2) { this->resetToDefault(); @@ -149,7 +156,8 @@ void SettingNodeV3::markChanged(CCNode* invoker) { SettingNodeValueChangeEventV3( m_impl->setting ? m_impl->setting->getModID() : "", m_impl->setting ? m_impl->setting->getKey() : "" - ).send(this, false); + ) + .send(this, false); } void SettingNodeV3::commit() { @@ -166,7 +174,8 @@ void SettingNodeV3::resetToDefault() { m_impl->committed = true; this->onResetToDefault(); this->updateState(nullptr); - SettingNodeValueChangeEventV3(m_impl->setting->getModID(), m_impl->setting->getKey()).send(this, false); + SettingNodeValueChangeEventV3(m_impl->setting->getModID(), m_impl->setting->getKey()) + .send(this, false); } void SettingNodeV3::overrideDescription(std::optional description) { @@ -212,8 +221,7 @@ std::shared_ptr SettingNodeV3::getSetting() const { // TitleSettingNodeV3 bool TitleSettingNodeV3::init(std::shared_ptr setting, float width) { - if (!SettingNodeV3::init(setting, width)) - return false; + if (!SettingNodeV3::init(setting, width)) return false; // note: setting may be null @@ -234,8 +242,7 @@ bool TitleSettingNodeV3::init(std::shared_ptr setting, float wid uncollapseSprBG->setScale(.2f); m_collapseToggle = CCMenuItemToggler::create( - collapseSprBG, uncollapseSprBG, - this, menu_selector(TitleSettingNodeV3::onCollapse) + collapseSprBG, uncollapseSprBG, this, menu_selector(TitleSettingNodeV3::onCollapse) ); m_collapseToggle->m_notClickable = true; this->getButtonMenu()->setContentWidth(20); @@ -290,7 +297,9 @@ TitleSettingNodeV3* TitleSettingNodeV3::create(std::shared_ptr s return nullptr; } -TitleSettingNodeV3* TitleSettingNodeV3::create(ZStringView title, std::optional description, float width) { +TitleSettingNodeV3* TitleSettingNodeV3::create( + ZStringView title, std::optional description, float width +) { auto ret = TitleSettingNodeV3::create(nullptr, width); ret->getNameLabel()->setString(title.c_str()); ret->overrideDescription(description); @@ -301,8 +310,7 @@ TitleSettingNodeV3* TitleSettingNodeV3::create(ZStringView title, std::optional< // InfoSettingNodeV3 bool InfoSettingNodeV3::init(std::shared_ptr setting, float width) { - if (!SettingNodeV3::init(setting, width)) - return false; + if (!SettingNodeV3::init(setting, width)) return false; this->setContentHeight(22); this->getNameLabel()->setVisible(false); @@ -317,8 +325,12 @@ bool InfoSettingNodeV3::init(std::shared_ptr setting, float width auto infoLabel = TextArea::create( setting->getDescription().value_or(""), - "chatFont.fnt", .65f, bg->getContentWidth() - 45, - ccp(.4999f, .4999f), 12, false + "chatFont.fnt", + .65f, + bg->getContentWidth() - 45, + ccp(.4999f, .4999f), + 12, + false ); this->setContentHeight(30 + infoLabel->getContentHeight()); @@ -355,21 +367,18 @@ InfoSettingNodeV3* InfoSettingNodeV3::create(std::shared_ptr sett // ButtonSettingNodeV3 bool ButtonSettingNodeV3::init(std::shared_ptr setting, float width) { - if (!SettingNodeV3::init(setting, width)) - return false; + if (!SettingNodeV3::init(setting, width)) return false; this->getNameLabel()->setVisible(false); - for (const auto& [k, v] : setting->getButtons()) { + for (auto const& [k, v] : setting->getButtons()) { auto spr = createGeodeButton(v); spr->setScale(.5f); spr->setCascadeColorEnabled(true); spr->setCascadeOpacityEnabled(true); - auto button = Button::createWithNode( - spr, [setting, k] (auto sender) { - ButtonSettingPressedEventV3(setting->getMod(), setting->getKey()).send(k); - } - ); + auto button = Button::createWithNode(spr, [setting, k](auto sender) { + ButtonSettingPressedEventV3(setting->getMod(), setting->getKey()).send(k); + }); button->setCascadeColorEnabled(true); button->setCascadeOpacityEnabled(true); button->setID(fmt::format("{}-button", k)); @@ -377,7 +386,7 @@ bool ButtonSettingNodeV3::init(std::shared_ptr setting, float w this->getButtonMenu()->addChild(button); } - this->getButtonMenu()->setAnchorPoint({ 0.f, .5f }); + this->getButtonMenu()->setAnchorPoint({0.f, .5f}); this->getButtonMenu()->setContentWidth(setting->getDescription() ? width - 45 : width - 20); auto buttonLayout = RowLayout::create(); @@ -405,7 +414,7 @@ bool ButtonSettingNodeV3::init(std::shared_ptr setting, float w } void ButtonSettingNodeV3::enableButtons(bool enabled) { - for (const auto& button : buttons) { + for (auto const& button : buttons) { button->setEnabled(enabled); button->setOpacity(enabled ? 255 : 175); button->setColor(enabled ? ccc3(255, 255, 255) : ccc3(166, 166, 166)); @@ -454,17 +463,16 @@ ButtonSettingNodeV3* ButtonSettingNodeV3::create(std::shared_ptr setting, float width) { - if (!SettingValueNodeV3::init(setting, width)) - return false; + if (!SettingValueNodeV3::init(setting, width)) return false; this->getButtonMenu()->setContentWidth(20); m_toggle = CCMenuItemToggler::createWithStandardSprites( this, menu_selector(BoolSettingNodeV3::onToggle), .55f ); - m_toggle->m_onButton->setContentSize({ 25, 25 }); + m_toggle->m_onButton->setContentSize({25, 25}); m_toggle->m_onButton->getNormalImage()->setPosition(ccp(25, 25) / 2); - m_toggle->m_offButton->setContentSize({ 25, 25 }); + m_toggle->m_offButton->setContentSize({25, 25}); m_toggle->m_offButton->getNormalImage()->setPosition(ccp(25, 25) / 2); m_toggle->m_notClickable = true; m_toggle->toggle(setting->getValue()); @@ -504,43 +512,28 @@ BoolSettingNodeV3* BoolSettingNodeV3::create(std::shared_ptr sett // StringSettingNodeV3 bool StringSettingNodeV3::init(std::shared_ptr setting, float width) { - if (!SettingValueNodeV3::init(setting, width)) - return false; - - m_input = TextInput::create(setting->getEnumOptions() ? width / 2 - 50 : width / 2, "Text"); - m_input->setCallback([this](auto const& str) { - this->setValue(str, m_input); - }); - m_input->setScale(.7f); - m_input->setString(this->getSetting()->getValue()); - if (auto filter = this->getSetting()->getAllowedCharacters()) { - m_input->setFilter(*filter); - } - - this->getButtonMenu()->addChildAtPosition(m_input, Anchor::Center); + if (!SettingValueNodeV3::init(setting, width)) return false; if (setting->getEnumOptions()) { - m_input->getBGSprite()->setVisible(false); - m_input->setEnabled(false); - m_input->getInputNode()->m_textLabel->setOpacity(255); - m_input->getInputNode()->m_textLabel->setColor(ccWHITE); - - m_arrowLeftSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png"); - m_arrowLeftSpr->setFlipX(true); - m_arrowLeftSpr->setScale(.4f); - auto arrowLeftBtn = CCMenuItemSpriteExtra::create( - m_arrowLeftSpr, this, menu_selector(StringSettingNodeV3::onArrow) - ); - arrowLeftBtn->setTag(-1); - this->getButtonMenu()->addChildAtPosition(arrowLeftBtn, Anchor::Left, ccp(5, 0)); - - m_arrowRightSpr = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png"); - m_arrowRightSpr->setScale(.4f); - auto arrowRightBtn = CCMenuItemSpriteExtra::create( - m_arrowRightSpr, this, menu_selector(StringSettingNodeV3::onArrow) - ); - arrowRightBtn->setTag(1); - this->getButtonMenu()->addChildAtPosition(arrowRightBtn, Anchor::Right, ccp(-5, 0)); + auto options = *setting->getEnumOptions(); + m_dropdown = Dropdown::create(width / 2, options, [this](std::string const& value, size_t) { + this->setValue(value, m_dropdown); + }); + m_dropdown->setSelected(this->getSetting()->getValue()); + m_dropdown->setScale(.7f); + this->getButtonMenu()->addChildAtPosition(m_dropdown, Anchor::Center); + } + else { + m_input = TextInput::create(width / 2, "Text"); + m_input->setCallback([this](auto const& str) { + this->setValue(str, m_input); + }); + m_input->setScale(.7f); + m_input->setString(this->getSetting()->getValue()); + if (auto filter = this->getSetting()->getAllowedCharacters()) { + m_input->setFilter(*filter); + } + this->getButtonMenu()->addChildAtPosition(m_input, Anchor::Center); } this->updateState(nullptr); @@ -551,32 +544,19 @@ bool StringSettingNodeV3::init(std::shared_ptr setting, float w void StringSettingNodeV3::updateState(CCNode* invoker) { SettingValueNodeV3::updateState(invoker); - if (invoker != m_input) { - m_input->setString(this->getValue()); - } - auto enable = this->getSetting()->shouldEnable(); - if (!this->getSetting()->getEnumOptions()) { - m_input->setEnabled(enable); - } - else { - m_arrowRightSpr->setOpacity(enable ? 255 : 155); - m_arrowRightSpr->setColor(enable ? ccWHITE : ccGRAY); - m_arrowLeftSpr->setOpacity(enable ? 255 : 155); - m_arrowLeftSpr->setColor(enable ? ccWHITE : ccGRAY); - } -} - -void StringSettingNodeV3::onArrow(CCObject* sender) { - auto options = *this->getSetting()->getEnumOptions(); - auto index = ranges::indexOf(options, this->getValue()).value_or(0); - if (sender->getTag() > 0) { - index = index < options.size() - 1 ? index + 1 : 0; + if (m_dropdown) { + if (invoker != m_dropdown) { + m_dropdown->setSelected(this->getValue()); + } + m_dropdown->setEnabled(enable); } - else { - index = index > 0 ? index - 1 : options.size() - 1; + else if (m_input) { + if (invoker != m_input) { + m_input->setString(this->getValue()); + } + m_input->setEnabled(enable); } - this->setValue(options.at(index), static_cast(sender)); } StringSettingNodeV3* StringSettingNodeV3::create(std::shared_ptr setting, float width) { @@ -592,14 +572,13 @@ StringSettingNodeV3* StringSettingNodeV3::create(std::shared_ptr setting, float width) { - if (!SettingValueNodeV3::init(setting, width)) - return false; + if (!SettingValueNodeV3::init(setting, width)) return false; - auto labelBG = NineSlice::create("square02b_001.png", { 0, 0, 80, 80 }); + auto labelBG = NineSlice::create("square02b_001.png", {0, 0, 80, 80}); labelBG->setScale(.25f); - labelBG->setColor({ 0, 0, 0 }); + labelBG->setColor({0, 0, 0}); labelBG->setOpacity(90); - labelBG->setContentSize({ 420, 80 }); + labelBG->setContentSize({420, 80}); this->getButtonMenu()->addChildAtPosition(labelBG, Anchor::Center, ccp(-10, 0)); m_fileIcon = CCSprite::create(); @@ -623,7 +602,7 @@ bool FileSettingNodeV3::init(std::shared_ptr setting, float width void FileSettingNodeV3::updateState(CCNode* invoker) { // This is because people tend to put `"default": "Please pick a good file"` // which is clever and good UX but also a hack so I also need to hack to support that - const auto isTextualDefaultValue = [this, setting = this->getSetting()]() { + auto const isTextualDefaultValue = [this, setting = this->getSetting()]() { if (this->hasNonDefaultValue()) return false; if (utils::string::pathToString(setting->getDefaultValue()).size() > 20) return false; std::error_code ec; @@ -633,16 +612,22 @@ void FileSettingNodeV3::updateState(CCNode* invoker) { }(); SettingValueNodeV3::updateState(invoker); - m_fileIcon->setDisplayFrame(CCSpriteFrameCache::get()->spriteFrameByName( - this->getSetting()->isFolder() ? "folderIcon_001.png" : "file.png"_spr - )); + m_fileIcon->setDisplayFrame( + CCSpriteFrameCache::get()->spriteFrameByName( + this->getSetting()->isFolder() ? "folderIcon_001.png" : "file.png"_spr + ) + ); limitNodeSize(m_fileIcon, ccp(10, 10), 1.f, .1f); if (this->getValue().empty() || isTextualDefaultValue) { if (isTextualDefaultValue) { - m_nameLabel->setString(utils::string::pathToString(this->getSetting()->getDefaultValue()).c_str()); + m_nameLabel->setString( + utils::string::pathToString(this->getSetting()->getDefaultValue()).c_str() + ); } else { - m_nameLabel->setString(this->getSetting()->isFolder() ? "No Folder Selected" : "No File Selected"); + m_nameLabel->setString( + this->getSetting()->isFolder() ? "No Folder Selected" : "No File Selected" + ); } m_nameLabel->setColor(ccGRAY); m_nameLabel->setOpacity(155); @@ -665,14 +650,14 @@ void FileSettingNodeV3::onPickFile(CCObject*) { m_pickListener.spawn( file::pick( - this->getSetting()->isFolder() ? - file::PickMode::OpenFolder : - this->getSetting()->useSaveDialog() ? file::PickMode::SaveFile : file::PickMode::OpenFile, - { - // Prefer opening the current path directly if possible - this->getValue().empty() || !std::filesystem::exists(this->getValue().parent_path(), ec) - ? dirs::getGameDir() : this->getValue(), - this->getSetting()->getFilters().value_or(std::vector()) + this->getSetting()->isFolder() ? file::PickMode::OpenFolder : + this->getSetting()->useSaveDialog() ? file::PickMode::SaveFile : + file::PickMode::OpenFile, + {// Prefer opening the current path directly if possible + this->getValue().empty() || !std::filesystem::exists(this->getValue().parent_path(), ec) ? + dirs::getGameDir() : + this->getValue(), + this->getSetting()->getFilters().value_or(std::vector()) } ), [this](Result> path) { @@ -681,10 +666,9 @@ void FileSettingNodeV3::onPickFile(CCObject*) { } else if (path.isErr()) { FLAlertLayer::create( - "Failed", - fmt::format("Failed to pick file: {}", path.unwrapErr()), - "Ok" - )->show(); + "Failed", fmt::format("Failed to pick file: {}", path.unwrapErr()), "Ok" + ) + ->show(); } } ); @@ -703,8 +687,7 @@ FileSettingNodeV3* FileSettingNodeV3::create(std::shared_ptr sett // Color3BSettingNodeV3 bool Color3BSettingNodeV3::init(std::shared_ptr setting, float width) { - if (!SettingValueNodeV3::init(setting, width)) - return false; + if (!SettingValueNodeV3::init(setting, width)) return false; m_colorSprite = ColorChannelSprite::create(); m_colorSprite->setScale(.65f); @@ -730,9 +713,12 @@ void Color3BSettingNodeV3::updateState(CCNode* invoker) { void Color3BSettingNodeV3::onSelectColor(CCObject*) { auto popup = ColorPickPopup::create(this->getValue()); - popup->setCallback([this](ccColor4B const& color) { this->setValue(to3B(color), nullptr); }); + popup->setCallback([this](ccColor4B const& color) { + this->setValue(to3B(color), nullptr); + }); popup->show(); } + Color3BSettingNodeV3* Color3BSettingNodeV3::create(std::shared_ptr setting, float width) { auto ret = new Color3BSettingNodeV3(); if (ret->init(setting, width)) { @@ -746,8 +732,7 @@ Color3BSettingNodeV3* Color3BSettingNodeV3::create(std::shared_ptr setting, float width) { - if (!SettingValueNodeV3::init(setting, width)) - return false; + if (!SettingValueNodeV3::init(setting, width)) return false; m_colorSprite = ColorChannelSprite::create(); m_colorSprite->setScale(.65f); @@ -774,7 +759,9 @@ void Color4BSettingNodeV3::updateState(CCNode* invoker) { void Color4BSettingNodeV3::onSelectColor(CCObject*) { auto popup = ColorPickPopup::create(this->getValue()); - popup->setCallback([this](ccColor4B const& color) { this->setValue(color, nullptr); }); + popup->setCallback([this](ccColor4B const& color) { + this->setValue(color, nullptr); + }); popup->show(); } @@ -801,14 +788,13 @@ KeybindSettingNodeV3* KeybindSettingNodeV3::create(std::shared_ptr setting, float width) { - if (!SettingNodeV3::init(setting, width)) - return false; + if (!SettingNodeV3::init(setting, width)) return false; m_currentValue = setting->getValue(); this->getButtonMenu()->setLayout(RowLayout::create()->setAxisAlignment(AxisAlignment::End)); if (auto category = setting->getCategory()) { - const char* catSpr; + char const* catSpr; switch (*category) { default: case KeybindCategory::Editor: { @@ -824,12 +810,11 @@ bool KeybindSettingNodeV3::init(std::shared_ptr setting, float } break; } auto categoryLabel = createTagLabelWithIcon( - CCSprite::createWithSpriteFrameName(catSpr), "", + CCSprite::createWithSpriteFrameName(catSpr), + "", std::make_pair(ccWHITE, "keybinds-list-category-label"_cc3b) ); - categoryLabel->setLayoutOptions( - AxisLayoutOptions::create()->setScaleLimits(.1f, .35f) - ); + categoryLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .35f)); this->getNameMenu()->addChild(categoryLabel); } @@ -852,14 +837,17 @@ void KeybindSettingNodeV3::updateState(CCNode* invoker) { for (auto& keybind : m_currentValue) { auto bspr = createKeybindButton(keybind); bspr->setScale(.5f); - auto button = CCMenuItemSpriteExtra::create(bspr, this, menu_selector(KeybindSettingNodeV3::onKeybind)); + auto button = + CCMenuItemSpriteExtra::create(bspr, this, menu_selector(KeybindSettingNodeV3::onKeybind)); button->setTag(index); buttonMenu->addChild(button); index += 1; } auto plusSprite = createGeodeButton("+", true); plusSprite->setScale(.5f); - auto plusButton = CCMenuItemSpriteExtra::create(plusSprite, this, menu_selector(KeybindSettingNodeV3::onKeybind)); + auto plusButton = CCMenuItemSpriteExtra::create( + plusSprite, this, menu_selector(KeybindSettingNodeV3::onKeybind) + ); buttonMenu->addChild(plusButton); buttonMenu->updateLayout(); @@ -871,7 +859,9 @@ void KeybindSettingNodeV3::updateState(CCNode* invoker) { auto moreSprite = createGeodeButton("...", true); moreSprite->setScale(.5f); - auto moreButton = CCMenuItemSpriteExtra::create(moreSprite, this, menu_selector(KeybindSettingNodeV3::onExtra)); + auto moreButton = CCMenuItemSpriteExtra::create( + moreSprite, this, menu_selector(KeybindSettingNodeV3::onExtra) + ); buttonMenu->addChild(moreButton); buttonMenu->updateLayout(); @@ -879,15 +869,11 @@ void KeybindSettingNodeV3::updateState(CCNode* invoker) { } void KeybindSettingNodeV3::onExtra(CCObject* sender) { - KeybindListPopup::create( - getSetting(), - m_currentValue, - [this](std::vector newKeybinds) { - if (m_currentValue == newKeybinds) return; - m_currentValue = std::move(newKeybinds); - this->markChanged(nullptr); - } - )->show(); + KeybindListPopup::create(getSetting(), m_currentValue, [this](std::vector newKeybinds) { + if (m_currentValue == newKeybinds) return; + m_currentValue = std::move(newKeybinds); + this->markChanged(nullptr); + })->show(); } void KeybindSettingNodeV3::onKeybind(CCObject* sender) { @@ -934,18 +920,16 @@ void KeybindSettingNodeV3::onResetToDefault() { // UnresolvedCustomSettingNodeV3 bool UnresolvedCustomSettingNodeV3::init(std::string_view key, Mod* mod, float width) { - if (!SettingNodeV3::init(nullptr, width)) - return false; + if (!SettingNodeV3::init(nullptr, width)) return false; m_mod = mod; this->setContentHeight(30); auto label = CCLabelBMFont::create( - (mod && mod->isLoaded() ? - fmt::format("Missing setting '{}'", key) : - fmt::format("Enable the Mod to Edit '{}'", key) - ).c_str(), + (mod && mod->isLoaded() ? fmt::format("Missing setting '{}'", key) : + fmt::format("Enable the Mod to Edit '{}'", key)) + .c_str(), "bigFont.fnt" ); label->setColor(mod && mod->isLoaded() ? "mod-list-errors-found-2"_cc3b : "mod-list-gray"_cc3b); @@ -957,7 +941,9 @@ bool UnresolvedCustomSettingNodeV3::init(std::string_view key, Mod* mod, float w void UnresolvedCustomSettingNodeV3::updateState(CCNode* invoker) { SettingNodeV3::updateState(invoker); - this->getBG()->setColor(m_mod && m_mod->isLoaded() ? "mod-list-errors-found-2"_cc3b : "mod-list-gray"_cc3b); + this->getBG()->setColor( + m_mod && m_mod->isLoaded() ? "mod-list-errors-found-2"_cc3b : "mod-list-gray"_cc3b + ); this->getBG()->setOpacity(75); } @@ -973,7 +959,9 @@ bool UnresolvedCustomSettingNodeV3::hasNonDefaultValue() const { void UnresolvedCustomSettingNodeV3::onResetToDefault() {} -UnresolvedCustomSettingNodeV3* UnresolvedCustomSettingNodeV3::create(std::string_view key, Mod* mod, float width) { +UnresolvedCustomSettingNodeV3* UnresolvedCustomSettingNodeV3::create( + std::string_view key, Mod* mod, float width +) { auto ret = new UnresolvedCustomSettingNodeV3(); if (ret->init(key, mod, width)) { ret->autorelease(); diff --git a/loader/src/ui/mods/settings/SettingNodeV3.hpp b/loader/src/ui/mods/settings/SettingNodeV3.hpp index adf1a6dc0..60a1d4a4a 100644 --- a/loader/src/ui/mods/settings/SettingNodeV3.hpp +++ b/loader/src/ui/mods/settings/SettingNodeV3.hpp @@ -1,12 +1,13 @@ #pragma once -#include #include #include #include +#include +#include #include +#include #include -#include #include using namespace geode::prelude; @@ -27,7 +28,9 @@ class TitleSettingNodeV3 : public SettingNodeV3 { public: // `setting` may be null here static TitleSettingNodeV3* create(std::shared_ptr setting, float width); - static TitleSettingNodeV3* create(ZStringView title, std::optional description, float width); + static TitleSettingNodeV3* create( + ZStringView title, std::optional description, float width + ); bool isCollapsed() const; void setCollapsed(bool collapsed); @@ -106,6 +109,7 @@ class NumberSettingNodeV3 : public SettingValueNodeV3 { auto range = max - min; return static_cast(std::clamp(static_cast(value - min) / range, 0.0, 1.0)); } + ValueType valueFromSlider(float num) { auto min = this->getSetting()->getMinValue().value_or(-100); auto max = this->getSetting()->getMaxValue().value_or(+100); @@ -119,8 +123,7 @@ class NumberSettingNodeV3 : public SettingValueNodeV3 { } bool init(std::shared_ptr setting, float width) { - if (!SettingValueNodeV3::init(setting, width)) - return false; + if (!SettingValueNodeV3::init(setting, width)) return false; m_bigArrowLeftBtnSpr = CCSprite::create(); m_bigArrowLeftBtnSpr->setCascadeColorEnabled(true); @@ -136,7 +139,9 @@ class NumberSettingNodeV3 : public SettingValueNodeV3 { m_bigArrowLeftBtn = CCMenuItemSpriteExtra::create( m_bigArrowLeftBtnSpr, this, menu_selector(NumberSettingNodeV3::onArrow) ); - m_bigArrowLeftBtn->setUserObject(ObjWrapper::create(-setting->getBigArrowStepSize())); + m_bigArrowLeftBtn->setUserObject( + ObjWrapper::create(-setting->getBigArrowStepSize()) + ); m_bigArrowLeftBtn->setVisible(setting->isBigArrowsEnabled()); this->getButtonMenu()->addChildAtPosition(m_bigArrowLeftBtn, Anchor::Left, ccp(5, 0)); @@ -151,7 +156,9 @@ class NumberSettingNodeV3 : public SettingValueNodeV3 { m_input = TextInput::create(this->getButtonMenu()->getContentWidth() - 40, "Num"); m_input->setScale(.7f); - m_input->setCommonFilter(std::is_floating_point_v ? CommonFilter::Float : CommonFilter::Int); + m_input->setCommonFilter( + std::is_floating_point_v ? CommonFilter::Float : CommonFilter::Int + ); m_input->setCallback([this, setting](auto const& str) { this->setValue(numFromString(str).unwrapOr(setting->getDefaultValue()), m_input); }); @@ -189,7 +196,9 @@ class NumberSettingNodeV3 : public SettingValueNodeV3 { m_bigArrowRightBtn = CCMenuItemSpriteExtra::create( m_bigArrowRightBtnSpr, this, menu_selector(NumberSettingNodeV3::onArrow) ); - m_bigArrowRightBtn->setUserObject(ObjWrapper::create(setting->getBigArrowStepSize())); + m_bigArrowRightBtn->setUserObject( + ObjWrapper::create(setting->getBigArrowStepSize()) + ); m_bigArrowRightBtn->setVisible(setting->isBigArrowsEnabled()); this->getButtonMenu()->addChildAtPosition(m_bigArrowRightBtn, Anchor::Right, ccp(-5, 0)); @@ -248,9 +257,9 @@ class NumberSettingNodeV3 : public SettingValueNodeV3 { } void onArrow(CCObject* sender) { - auto value = this->getValue() + static_cast*>( - static_cast(sender)->getUserObject() - )->getValue(); + auto value = this->getValue() + + static_cast*>(static_cast(sender)->getUserObject()) + ->getValue(); if (auto min = this->getSetting()->getMinValue()) { value = std::max(*min, value); } @@ -292,12 +301,10 @@ using FloatSettingNodeV3 = NumberSettingNodeV3; class StringSettingNodeV3 : public SettingValueNodeV3 { protected: TextInput* m_input; - CCSprite* m_arrowLeftSpr = nullptr; - CCSprite* m_arrowRightSpr = nullptr; + geode::Dropdown* m_dropdown = nullptr; bool init(std::shared_ptr setting, float width); void updateState(CCNode* invoker) override; - void onArrow(CCObject* sender); public: static StringSettingNodeV3* create(std::shared_ptr setting, float width); @@ -358,6 +365,7 @@ class KeybindSettingNodeV3 : public SettingNodeV3 { bool hasUncommittedChanges() const override; bool hasNonDefaultValue() const override; void onResetToDefault() override; + public: static KeybindSettingNodeV3* create(std::shared_ptr setting, float width); diff --git a/loader/src/ui/nodes/Dropdown.cpp b/loader/src/ui/nodes/Dropdown.cpp new file mode 100644 index 000000000..fe238e096 --- /dev/null +++ b/loader/src/ui/nodes/Dropdown.cpp @@ -0,0 +1,335 @@ +#include +#include +#include +#include + +using namespace geode; +using namespace cocos2d; + +class DropdownOverlay; + +class Dropdown::Impl { +public: + Dropdown* m_self; + std::vector m_options; + Function m_callback; + size_t m_selectedIndex = 0; + float m_width; + bool m_enabled = true; + + CCLabelBMFont* m_label = nullptr; + NineSlice* m_bg = nullptr; + CCSprite* m_arrow = nullptr; + CCMenuItemSpriteExtra* m_button = nullptr; + CCMenu* m_menu = nullptr; + DropdownOverlay* m_overlay = nullptr; + + bool init( + float width, std::vector options, + Function callback + ); + + void updateLabel(); + void openOverlay(); + void closeOverlay(); + void selectOption(size_t index); + void setEnabled(bool enabled); +}; + +class DropdownOverlay : public CCLayerColor { + Dropdown::Impl* m_dropdown; + + struct ItemRect { + CCRect rect; + size_t index; + }; + + std::vector m_itemRects; + +public: + static DropdownOverlay* create(Dropdown::Impl* dropdown) { + auto ret = new DropdownOverlay(); + + if (ret->init(dropdown)) { + ret->autorelease(); + return ret; + } + + delete ret; + return nullptr; + } + + bool init(Dropdown::Impl* dropdown) { + if (!CCLayerColor::initWithColor({0, 0, 0, 85})) return false; + + m_dropdown = dropdown; + this->setKeypadEnabled(true); + + auto winSize = CCDirector::get()->getWinSize(); + auto buttonWorldPos = m_dropdown->m_self->convertToWorldSpace(ccp(0, 0)); + auto buttonSize = m_dropdown->m_self->getContentSize(); + + float dropdownWidth = m_dropdown->m_width; + float itemHeight = 25.f; + float listHeight = itemHeight * m_dropdown->m_options.size(); + float panelPadding = 4.f; + float panelHeight = listHeight + panelPadding * 2; + bool openBelow = true; + float panelY = buttonWorldPos.y - panelHeight; + + if (panelY < 5.f) { + openBelow = false; + panelY = buttonWorldPos.y + buttonSize.height; + } + + float panelX = buttonWorldPos.x; + if (panelX + dropdownWidth > winSize.width - 5.f) { + panelX = winSize.width - dropdownWidth - 5.f; + } + if (panelX < 5.f) panelX = 5.f; + + auto panelBG = NineSlice::create("square02b_001.png", {0, 0, 80, 80}); + panelBG->setColor({0, 0, 0}); + panelBG->setOpacity(200); + panelBG->setContentSize({dropdownWidth / 0.25f, panelHeight / 0.25f}); + panelBG->setScale(0.25f); + panelBG->setAnchorPoint({0, 0}); + panelBG->setPosition(panelX, panelY); + this->addChild(panelBG); + + float itemWidth = dropdownWidth - panelPadding * 2; + for (size_t i = 0; i < m_dropdown->m_options.size(); i++) { + float itemY; + if (openBelow) { + itemY = panelY + panelHeight - panelPadding - itemHeight * i - itemHeight; + } + else { + itemY = panelY + panelPadding + itemHeight * (m_dropdown->m_options.size() - 1 - i); + } + float itemX = panelX + panelPadding; + + auto itemBG = NineSlice::create("square02b_001.png", {0, 0, 80, 80}); + itemBG->setScale(0.25f); + if (i == m_dropdown->m_selectedIndex) { + itemBG->setColor({80, 80, 80}); + itemBG->setOpacity(200); + } + else { + itemBG->setColor({0, 0, 0}); + itemBG->setOpacity(0); + } + itemBG->setContentSize({itemWidth / 0.25f, itemHeight / 0.25f}); + itemBG->setAnchorPoint({0, 0}); + itemBG->setPosition(itemX, itemY); + this->addChild(itemBG); + + auto label = CCLabelBMFont::create(m_dropdown->m_options[i].c_str(), "bigFont.fnt"); + label->setScale(0.4f / 0.25f); + label->setAnchorPoint({0.f, 0.5f}); + label->limitLabelWidth((itemWidth - 10.f) / 0.25f, 0.4f / 0.25f, 0.1f / 0.25f); + itemBG->addChildAtPosition(label, Anchor::Left, ccp(5.f / 0.25f, 0)); + + m_itemRects.push_back({CCRect(itemX, itemY, itemWidth, itemHeight), i}); + } + + return true; + } + + void registerWithTouchDispatcher() override { + CCTouchDispatcher::get()->addTargetedDelegate(this, -500, true); + } + + bool ccTouchBegan(CCTouch* touch, CCEvent* event) override { + auto loc = touch->getLocation(); + + for (auto const& item : m_itemRects) { + if (item.rect.containsPoint(loc)) { + m_dropdown->selectOption(item.index); + return true; + } + } + + m_dropdown->closeOverlay(); + return true; + } + + void ccTouchEnded(CCTouch*, CCEvent*) override {} + + void ccTouchCancelled(CCTouch*, CCEvent*) override {} + + void ccTouchMoved(CCTouch*, CCEvent*) override {} + + void keyBackClicked() override { + m_dropdown->closeOverlay(); + } +}; + +bool Dropdown::Impl::init( + float width, std::vector options, Function callback +) { + m_width = width; + m_options = std::move(options); + m_callback = std::move(callback); + + float height = 30.f; + m_self->setContentSize({width, height}); + m_self->setAnchorPoint({0.5f, 0.5f}); + + m_bg = NineSlice::create("square02b_001.png", {0, 0, 80, 80}); + m_bg->setColor({0, 0, 0}); + m_bg->setOpacity(90); + m_bg->setContentSize({width / 0.25f, height / 0.25f}); + m_bg->setScale(0.25f); + m_self->addChildAtPosition(m_bg, Anchor::Center); + + m_arrow = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png"); + m_arrow->setScale(0.3f); + m_arrow->setRotation(90.f); + m_self->addChildAtPosition(m_arrow, Anchor::Right, ccp(-10, 0)); + + m_label = CCLabelBMFont::create("", "bigFont.fnt"); + m_label->setScale(0.4f); + m_label->setAnchorPoint({0.f, 0.5f}); + m_self->addChildAtPosition(m_label, Anchor::Left, ccp(5, 0)); + + auto clickArea = CCSprite::create(); + clickArea->setContentSize({width, height}); + clickArea->setOpacity(0); + m_button = CCMenuItemSpriteExtra::create(clickArea, m_self, menu_selector(Dropdown::onOpen)); + m_button->setContentSize({width, height}); + + m_menu = CCMenu::create(); + m_menu->setContentSize({width, height}); + m_menu->addChildAtPosition(m_button, Anchor::Center); + m_self->addChildAtPosition(m_menu, Anchor::Center); + + if (!m_options.empty()) { + updateLabel(); + } + + return true; +} + +void Dropdown::Impl::updateLabel() { + if (m_options.empty()) { + m_label->setString(""); + return; + } + m_label->setString(m_options[m_selectedIndex].c_str()); + m_label->limitLabelWidth(m_width - 25.f, 0.4f, 0.1f); +} + +void Dropdown::Impl::openOverlay() { + if (!m_enabled || m_overlay) return; + + auto scene = CCDirector::get()->getRunningScene(); + if (!scene) return; + + m_overlay = DropdownOverlay::create(this); + if (m_overlay) { + CCTouchDispatcher::get()->registerForcePrio(m_overlay, 2); + scene->addChild(m_overlay, 9999); + } +} + +void Dropdown::Impl::closeOverlay() { + if (m_overlay) { + CCTouchDispatcher::get()->unregisterForcePrio(m_overlay); + m_overlay->removeFromParentAndCleanup(true); + m_overlay = nullptr; + } +} + +void Dropdown::Impl::selectOption(size_t index) { + if (index >= m_options.size()) return; + m_selectedIndex = index; + updateLabel(); + closeOverlay(); + if (m_callback) { + m_callback(m_options[m_selectedIndex], m_selectedIndex); + } +} + +void Dropdown::Impl::setEnabled(bool enabled) { + m_enabled = enabled; + m_button->setEnabled(enabled); + GLubyte opacity = enabled ? 255 : 155; + auto color = enabled ? ccWHITE : ccGRAY; + m_label->setOpacity(opacity); + m_label->setColor(color); + m_arrow->setOpacity(opacity); + m_arrow->setColor(color); +} + +Dropdown::Dropdown() : m_impl(std::make_unique()) { + m_impl->m_self = this; +} + +Dropdown::~Dropdown() { + if (m_impl->m_overlay) { + m_impl->closeOverlay(); + } +} + +void Dropdown::onOpen(CCObject*) { + m_impl->openOverlay(); +} + +bool Dropdown::init( + float width, std::vector options, Function callback +) { + if (!CCNode::init()) return false; + return m_impl->init(width, std::move(options), std::move(callback)); +} + +Dropdown* Dropdown::create( + float width, std::vector options, Function callback +) { + auto ret = new Dropdown(); + if (ret->init(width, std::move(options), std::move(callback))) { + ret->autorelease(); + return ret; + } + delete ret; + return nullptr; +} + +void Dropdown::setSelected(size_t index) { + if (index < m_impl->m_options.size()) { + m_impl->m_selectedIndex = index; + m_impl->updateLabel(); + } +} + +void Dropdown::setSelected(std::string_view value) { + for (size_t i = 0; i < m_impl->m_options.size(); i++) { + if (m_impl->m_options[i] == value) { + m_impl->m_selectedIndex = i; + m_impl->updateLabel(); + return; + } + } +} + +size_t Dropdown::getSelectedIndex() const { + return m_impl->m_selectedIndex; +} + +std::string Dropdown::getSelectedValue() const { + if (m_impl->m_options.empty()) return ""; + return m_impl->m_options[m_impl->m_selectedIndex]; +} + +void Dropdown::setEnabled(bool enabled) { + m_impl->setEnabled(enabled); +} + +bool Dropdown::isEnabled() const { + return m_impl->m_enabled; +} + +void Dropdown::setItems(std::vector options) { + m_impl->m_options = std::move(options); + m_impl->m_selectedIndex = 0; + m_impl->updateLabel(); +} From 622a5472ede60e5124351f3f9d63c98a58ba01f6 Mon Sep 17 00:00:00 2001 From: The Bearodactyl Date: Sun, 15 Mar 2026 21:40:54 -0500 Subject: [PATCH 2/5] make it better --- loader/src/ui/nodes/Dropdown.cpp | 162 ++++++++++++++++++++----------- 1 file changed, 108 insertions(+), 54 deletions(-) diff --git a/loader/src/ui/nodes/Dropdown.cpp b/loader/src/ui/nodes/Dropdown.cpp index fe238e096..e7a7ddf08 100644 --- a/loader/src/ui/nodes/Dropdown.cpp +++ b/loader/src/ui/nodes/Dropdown.cpp @@ -1,11 +1,19 @@ #include +#include #include #include +#include +#include #include using namespace geode; +using namespace geode::cocos; using namespace cocos2d; +static bool isGeodeTheme() { + return Mod::get()->getSettingValue("enable-geode-theme"); +} + class DropdownOverlay; class Dropdown::Impl { @@ -16,6 +24,7 @@ class Dropdown::Impl { size_t m_selectedIndex = 0; float m_width; bool m_enabled = true; + int m_savedZOrder = 0; CCLabelBMFont* m_label = nullptr; NineSlice* m_bg = nullptr; @@ -38,13 +47,18 @@ class Dropdown::Impl { class DropdownOverlay : public CCLayerColor { Dropdown::Impl* m_dropdown; + ScrollLayer* m_scrollLayer = nullptr; + CCRect m_panelRect; - struct ItemRect { - CCRect rect; + struct ItemInfo { + CCNode* node; size_t index; }; - std::vector m_itemRects; + std::vector m_items; + float m_itemHeight; + float m_itemWidth; + float m_panelPadding; public: static DropdownOverlay* create(Dropdown::Impl* dropdown) { @@ -60,78 +74,102 @@ class DropdownOverlay : public CCLayerColor { } bool init(Dropdown::Impl* dropdown) { - if (!CCLayerColor::initWithColor({0, 0, 0, 85})) return false; + if (!CCLayerColor::initWithColor({0, 0, 0, 150})) return false; m_dropdown = dropdown; + this->setTouchEnabled(true); this->setKeypadEnabled(true); + bool geodeTheme = isGeodeTheme(); + auto winSize = CCDirector::get()->getWinSize(); auto buttonWorldPos = m_dropdown->m_self->convertToWorldSpace(ccp(0, 0)); auto buttonSize = m_dropdown->m_self->getContentSize(); float dropdownWidth = m_dropdown->m_width; - float itemHeight = 25.f; - float listHeight = itemHeight * m_dropdown->m_options.size(); - float panelPadding = 4.f; - float panelHeight = listHeight + panelPadding * 2; - bool openBelow = true; - float panelY = buttonWorldPos.y - panelHeight; + m_itemHeight = 28.f; + float itemSpacing = 2.f; + float totalListHeight = m_itemHeight * m_dropdown->m_options.size() + + itemSpacing * (m_dropdown->m_options.size() - 1); + m_panelPadding = 6.f; + + float maxPanelHeight = 200.f; + float naturalPanelHeight = totalListHeight + m_panelPadding * 2; + float panelHeight = std::min(naturalPanelHeight, maxPanelHeight); + + float panelY = buttonWorldPos.y - panelHeight - 2.f; if (panelY < 5.f) { - openBelow = false; - panelY = buttonWorldPos.y + buttonSize.height; + panelY = buttonWorldPos.y + buttonSize.height + 2.f; } - float panelX = buttonWorldPos.x; + float panelX = buttonWorldPos.x + buttonSize.width - dropdownWidth; if (panelX + dropdownWidth > winSize.width - 5.f) { panelX = winSize.width - dropdownWidth - 5.f; } if (panelX < 5.f) panelX = 5.f; - auto panelBG = NineSlice::create("square02b_001.png", {0, 0, 80, 80}); - panelBG->setColor({0, 0, 0}); - panelBG->setOpacity(200); - panelBG->setContentSize({dropdownWidth / 0.25f, panelHeight / 0.25f}); - panelBG->setScale(0.25f); + m_panelRect = CCRect(panelX, panelY, dropdownWidth, panelHeight); + + auto panelBG = NineSlice::create(geodeTheme ? "GE_square02.png"_spr : "GJ_square02.png"); + panelBG->setID("dropdown-panel-bg"); + panelBG->setContentSize({dropdownWidth, panelHeight}); panelBG->setAnchorPoint({0, 0}); panelBG->setPosition(panelX, panelY); this->addChild(panelBG); - float itemWidth = dropdownWidth - panelPadding * 2; + m_itemWidth = dropdownWidth - m_panelPadding * 2; + float scrollAreaWidth = m_itemWidth; + float scrollAreaHeight = panelHeight - m_panelPadding * 2; + + m_scrollLayer = ScrollLayer::create({scrollAreaWidth, scrollAreaHeight}, true, true); + m_scrollLayer->setAnchorPoint({0, 0}); + m_scrollLayer->setPosition(panelX + m_panelPadding, panelY + m_panelPadding); + this->addChild(m_scrollLayer); + for (size_t i = 0; i < m_dropdown->m_options.size(); i++) { - float itemY; - if (openBelow) { - itemY = panelY + panelHeight - panelPadding - itemHeight * i - itemHeight; + float itemY = totalListHeight - (m_itemHeight + itemSpacing) * i - m_itemHeight; + + auto itemBG = NineSlice::createWithSpriteFrameName("tab-bg.png"_spr); + itemBG->setScale(.5f); + itemBG->setContentSize({m_itemWidth / .5f, m_itemHeight / .5f}); + itemBG->setAnchorPoint({0, 0}); + itemBG->setPosition(0, itemY); + + if (i == m_dropdown->m_selectedIndex) { + itemBG->setColor(to3B(ColorProvider::get()->color("mod-list-tab-selected-bg"_spr))); } else { - itemY = panelY + panelPadding + itemHeight * (m_dropdown->m_options.size() - 1 - i); + itemBG->setColor(to3B(ColorProvider::get()->color("mod-list-tab-deselected-bg"_spr))); } - float itemX = panelX + panelPadding; + m_scrollLayer->m_contentLayer->addChild(itemBG); + + auto label = CCLabelBMFont::create(m_dropdown->m_options[i].c_str(), "bigFont.fnt"); + label->setScale(0.35f / .5f); + label->setAnchorPoint({0.f, 0.5f}); + label->limitLabelWidth((m_itemWidth - 14.f) / .5f, 0.35f / .5f, 0.1f / .5f); - auto itemBG = NineSlice::create("square02b_001.png", {0, 0, 80, 80}); - itemBG->setScale(0.25f); if (i == m_dropdown->m_selectedIndex) { - itemBG->setColor({80, 80, 80}); - itemBG->setOpacity(200); + label->setColor(ccWHITE); } else { - itemBG->setColor({0, 0, 0}); - itemBG->setOpacity(0); + label->setColor(ccc3(200, 200, 200)); } - itemBG->setContentSize({itemWidth / 0.25f, itemHeight / 0.25f}); - itemBG->setAnchorPoint({0, 0}); - itemBG->setPosition(itemX, itemY); - this->addChild(itemBG); - auto label = CCLabelBMFont::create(m_dropdown->m_options[i].c_str(), "bigFont.fnt"); - label->setScale(0.4f / 0.25f); - label->setAnchorPoint({0.f, 0.5f}); - label->limitLabelWidth((itemWidth - 10.f) / 0.25f, 0.4f / 0.25f, 0.1f / 0.25f); - itemBG->addChildAtPosition(label, Anchor::Left, ccp(5.f / 0.25f, 0)); + itemBG->addChildAtPosition(label, Anchor::Left, ccp(8.f / .5f, 0)); - m_itemRects.push_back({CCRect(itemX, itemY, itemWidth, itemHeight), i}); + if (i == m_dropdown->m_selectedIndex) { + auto check = CCSprite::createWithSpriteFrameName("GJ_completesIcon_001.png"); + check->setScale(0.4f / .5f); + itemBG->addChildAtPosition(check, Anchor::Right, ccp(-10.f / .5f, 0)); + } + + m_items.push_back({itemBG, i}); } + m_scrollLayer->m_contentLayer->setContentSize({scrollAreaWidth, totalListHeight}); + m_scrollLayer->scrollToTop(); + return true; } @@ -142,11 +180,20 @@ class DropdownOverlay : public CCLayerColor { bool ccTouchBegan(CCTouch* touch, CCEvent* event) override { auto loc = touch->getLocation(); - for (auto const& item : m_itemRects) { - if (item.rect.containsPoint(loc)) { - m_dropdown->selectOption(item.index); - return true; + if (m_panelRect.containsPoint(loc)) { + auto localPos = m_scrollLayer->m_contentLayer->convertToNodeSpace(loc); + + for (auto const& item : m_items) { + auto itemRect = CCRect( + item.node->getPositionX(), item.node->getPositionY(), m_itemWidth, m_itemHeight + ); + if (itemRect.containsPoint(localPos)) { + m_dropdown->selectOption(item.index); + return true; + } } + + return true; } m_dropdown->closeOverlay(); @@ -171,26 +218,27 @@ bool Dropdown::Impl::init( m_options = std::move(options); m_callback = std::move(callback); + bool geodeTheme = isGeodeTheme(); + float height = 30.f; m_self->setContentSize({width, height}); m_self->setAnchorPoint({0.5f, 0.5f}); - m_bg = NineSlice::create("square02b_001.png", {0, 0, 80, 80}); - m_bg->setColor({0, 0, 0}); - m_bg->setOpacity(90); - m_bg->setContentSize({width / 0.25f, height / 0.25f}); - m_bg->setScale(0.25f); + m_bg = NineSlice::createWithSpriteFrameName("tab-bg.png"_spr); + m_bg->setScale(.5f); + m_bg->setContentSize({width / .5f, height / .5f}); + m_bg->setColor(to3B(ColorProvider::get()->color("mod-list-search-bg"_spr))); m_self->addChildAtPosition(m_bg, Anchor::Center); m_arrow = CCSprite::createWithSpriteFrameName("navArrowBtn_001.png"); - m_arrow->setScale(0.3f); + m_arrow->setScale(0.25f); m_arrow->setRotation(90.f); - m_self->addChildAtPosition(m_arrow, Anchor::Right, ccp(-10, 0)); + m_self->addChildAtPosition(m_arrow, Anchor::Right, ccp(-12, 0)); m_label = CCLabelBMFont::create("", "bigFont.fnt"); - m_label->setScale(0.4f); + m_label->setScale(0.35f); m_label->setAnchorPoint({0.f, 0.5f}); - m_self->addChildAtPosition(m_label, Anchor::Left, ccp(5, 0)); + m_self->addChildAtPosition(m_label, Anchor::Left, ccp(8, 0)); auto clickArea = CCSprite::create(); clickArea->setContentSize({width, height}); @@ -216,7 +264,7 @@ void Dropdown::Impl::updateLabel() { return; } m_label->setString(m_options[m_selectedIndex].c_str()); - m_label->limitLabelWidth(m_width - 25.f, 0.4f, 0.1f); + m_label->limitLabelWidth(m_width - 30.f, 0.35f, 0.1f); } void Dropdown::Impl::openOverlay() { @@ -225,6 +273,9 @@ void Dropdown::Impl::openOverlay() { auto scene = CCDirector::get()->getRunningScene(); if (!scene) return; + m_savedZOrder = m_self->getZOrder(); + m_self->setZOrder(9998); + m_overlay = DropdownOverlay::create(this); if (m_overlay) { CCTouchDispatcher::get()->registerForcePrio(m_overlay, 2); @@ -237,6 +288,8 @@ void Dropdown::Impl::closeOverlay() { CCTouchDispatcher::get()->unregisterForcePrio(m_overlay); m_overlay->removeFromParentAndCleanup(true); m_overlay = nullptr; + + m_self->setZOrder(m_savedZOrder); } } @@ -259,6 +312,7 @@ void Dropdown::Impl::setEnabled(bool enabled) { m_label->setColor(color); m_arrow->setOpacity(opacity); m_arrow->setColor(color); + m_bg->setOpacity(enabled ? 255 : 155); } Dropdown::Dropdown() : m_impl(std::make_unique()) { From 11246e34cc580f1be4f5da39029aaa5cddfaf2b1 Mon Sep 17 00:00:00 2001 From: The Bearodactyl Date: Sun, 15 Mar 2026 22:12:59 -0500 Subject: [PATCH 3/5] make it even better --- loader/src/ui/nodes/Dropdown.cpp | 50 +++++++++++++++----------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/loader/src/ui/nodes/Dropdown.cpp b/loader/src/ui/nodes/Dropdown.cpp index e7a7ddf08..c56d0dc4b 100644 --- a/loader/src/ui/nodes/Dropdown.cpp +++ b/loader/src/ui/nodes/Dropdown.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -50,12 +51,6 @@ class DropdownOverlay : public CCLayerColor { ScrollLayer* m_scrollLayer = nullptr; CCRect m_panelRect; - struct ItemInfo { - CCNode* node; - size_t index; - }; - - std::vector m_items; float m_itemHeight; float m_itemWidth; float m_panelPadding; @@ -74,7 +69,7 @@ class DropdownOverlay : public CCLayerColor { } bool init(Dropdown::Impl* dropdown) { - if (!CCLayerColor::initWithColor({0, 0, 0, 150})) return false; + if (!CCLayerColor::initWithColor({0, 0, 0, 0})) return false; m_dropdown = dropdown; this->setTouchEnabled(true); @@ -97,10 +92,17 @@ class DropdownOverlay : public CCLayerColor { float naturalPanelHeight = totalListHeight + m_panelPadding * 2; float panelHeight = std::min(naturalPanelHeight, maxPanelHeight); - float panelY = buttonWorldPos.y - panelHeight - 2.f; - - if (panelY < 5.f) { + float screenMidY = winSize.height / 2.f; + float panelY; + if (buttonWorldPos.y + buttonSize.height / 2.f > screenMidY) { + panelY = buttonWorldPos.y - panelHeight - 2.f; + if (panelY < 5.f) panelY = 5.f; + } + else { panelY = buttonWorldPos.y + buttonSize.height + 2.f; + if (panelY + panelHeight > winSize.height - 5.f) { + panelY = winSize.height - panelHeight - 5.f; + } } float panelX = buttonWorldPos.x + buttonSize.width - dropdownWidth; @@ -133,8 +135,6 @@ class DropdownOverlay : public CCLayerColor { auto itemBG = NineSlice::createWithSpriteFrameName("tab-bg.png"_spr); itemBG->setScale(.5f); itemBG->setContentSize({m_itemWidth / .5f, m_itemHeight / .5f}); - itemBG->setAnchorPoint({0, 0}); - itemBG->setPosition(0, itemY); if (i == m_dropdown->m_selectedIndex) { itemBG->setColor(to3B(ColorProvider::get()->color("mod-list-tab-selected-bg"_spr))); @@ -142,7 +142,6 @@ class DropdownOverlay : public CCLayerColor { else { itemBG->setColor(to3B(ColorProvider::get()->color("mod-list-tab-deselected-bg"_spr))); } - m_scrollLayer->m_contentLayer->addChild(itemBG); auto label = CCLabelBMFont::create(m_dropdown->m_options[i].c_str(), "bigFont.fnt"); label->setScale(0.35f / .5f); @@ -164,7 +163,16 @@ class DropdownOverlay : public CCLayerColor { itemBG->addChildAtPosition(check, Anchor::Right, ccp(-10.f / .5f, 0)); } - m_items.push_back({itemBG, i}); + size_t index = i; + auto btn = Button::createWithNode(itemBG, [this, index](Button*) { + m_dropdown->selectOption(index); + }); + btn->setContentSize({m_itemWidth, m_itemHeight}); + btn->setAnchorPoint({0, 0}); + btn->setPosition(0, itemY); + btn->setTouchPriority(-501); + btn->setAnimationType(Button::AnimationType::None); + m_scrollLayer->m_contentLayer->addChild(btn); } m_scrollLayer->m_contentLayer->setContentSize({scrollAreaWidth, totalListHeight}); @@ -181,19 +189,7 @@ class DropdownOverlay : public CCLayerColor { auto loc = touch->getLocation(); if (m_panelRect.containsPoint(loc)) { - auto localPos = m_scrollLayer->m_contentLayer->convertToNodeSpace(loc); - - for (auto const& item : m_items) { - auto itemRect = CCRect( - item.node->getPositionX(), item.node->getPositionY(), m_itemWidth, m_itemHeight - ); - if (itemRect.containsPoint(localPos)) { - m_dropdown->selectOption(item.index); - return true; - } - } - - return true; + return false; } m_dropdown->closeOverlay(); From ee0bb7b68384c0fa0e3285d3243ad332f1f05c80 Mon Sep 17 00:00:00 2001 From: The Bearodactyl Date: Sun, 15 Mar 2026 22:21:20 -0500 Subject: [PATCH 4/5] fuck you git --- loader/src/ui/mods/settings/SettingNodeV3.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/loader/src/ui/mods/settings/SettingNodeV3.cpp b/loader/src/ui/mods/settings/SettingNodeV3.cpp index dc9a8bcbd..b51932c04 100644 --- a/loader/src/ui/mods/settings/SettingNodeV3.cpp +++ b/loader/src/ui/mods/settings/SettingNodeV3.cpp @@ -6,14 +6,8 @@ #include #include #include -<<<<<<< HEAD #include #include -======= -#include -#include -#include "KeybindEditPopup.hpp" ->>>>>>> 2262a8e04d16be27e2eacc37efcbcd5b8d8f6171 class SettingNodeV3::Impl final { public: From 9d3d0e96b4cfcfc824cac575d64a3c821d74e5a3 Mon Sep 17 00:00:00 2001 From: The Bearodactyl Date: Sun, 15 Mar 2026 22:27:36 -0500 Subject: [PATCH 5/5] small fix --- loader/src/ui/nodes/Dropdown.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/loader/src/ui/nodes/Dropdown.cpp b/loader/src/ui/nodes/Dropdown.cpp index c56d0dc4b..f751feee2 100644 --- a/loader/src/ui/nodes/Dropdown.cpp +++ b/loader/src/ui/nodes/Dropdown.cpp @@ -1,3 +1,5 @@ +#include "ui/mods/GeodeStyle.hpp" + #include #include #include @@ -11,10 +13,6 @@ using namespace geode; using namespace geode::cocos; using namespace cocos2d; -static bool isGeodeTheme() { - return Mod::get()->getSettingValue("enable-geode-theme"); -} - class DropdownOverlay; class Dropdown::Impl {