Improved Popup class; makeMemberFunction & makeMenuSelector; one-time

info alerts; improved some UI stuff
This commit is contained in:
HJfod 2022-09-01 09:35:18 +03:00
parent 78415153b5
commit 8d7a46f6ab
13 changed files with 248 additions and 144 deletions

View file

@ -9,6 +9,14 @@ else()
endif()
function(create_geode_file proname)
message(
DEPRECATION
"create_geode_file has been deprecated, and will
be replaced by create_geode_file_v2 in the future.
Do note that create_geode_file_v2 will be *renamed*
to create_geode_file"
)
message(STATUS "Creating geode file")
if(GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND")
@ -24,6 +32,22 @@ function(create_geode_file proname)
endfunction()
function(create_geode_file_v2 proname)
message(STATUS "Creating geode file")
if(GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND")
message(WARNING "create_geode_file called, but Geode CLI was not found - You will need to manually package the .geode files")
else()
add_custom_target(${proname}_PACKAGE ALL
DEPENDS ${proname}
COMMAND ${GEODE_CLI} package new ${CMAKE_CURRENT_SOURCE_DIR} --out $<TARGET_FILE_DIR:${proname}>/${proname}.geode --install
VERBATIM USES_TERMINAL
)
endif()
endfunction()
function(package_geode_resources proname src dest prefix)
message(STATUS "Packaging resources from ${src} with prefix ${prefix} into ${dest}")

View file

@ -3,11 +3,12 @@
#include <Geode/Bindings.hpp>
namespace geode {
template<typename T, typename... InitArgs>
template<typename... InitArgs>
class GEODE_DLL Popup : public FLAlertLayer {
protected:
cocos2d::CCSize m_size;
cocos2d::extension::CCScale9Sprite* m_bgSprite;
cocos2d::CCLabelBMFont* m_title = nullptr;
bool init(
float width,
@ -16,13 +17,13 @@ namespace geode {
const char* bg = "GJ_square01.png"
) {
auto winSize = cocos2d::CCDirector::sharedDirector()->getWinSize();
m_size = cocos2d::CCSize{width, height};
m_size = cocos2d::CCSize { width, height };
if (!this->initWithColor({0, 0, 0, 105})) return false;
if (!this->initWithColor({ 0, 0, 0, 105 })) return false;
m_mainLayer = cocos2d::CCLayer::create();
this->addChild(m_mainLayer);
m_bgSprite = cocos2d::extension::CCScale9Sprite::create(bg, {0.0f, 0.0f, 80.0f, 80.0f});
m_bgSprite = cocos2d::extension::CCScale9Sprite::create(bg, { 0, 0, 80, 80 });
m_bgSprite->setContentSize(m_size);
m_bgSprite->setPosition(winSize.width / 2, winSize.height / 2);
m_mainLayer->addChild(m_bgSprite);
@ -33,13 +34,20 @@ namespace geode {
cocos2d::CCDirector::sharedDirector()->getTouchDispatcher()->incrementForcePrio(2);
this->registerWithTouchDispatcher();
this->setup(args...);
if (!setup(std::forward<InitArgs>(args)...)) {
return false;
}
auto closeSpr = cocos2d::CCSprite::createWithSpriteFrameName("GJ_closeBtn_001.png");
closeSpr->setScale(.8f);
auto closeBtn = CCMenuItemSpriteExtra::create(closeSpr, this, (cocos2d::SEL_MenuHandler)(&Popup::onClose));
closeBtn->setPosition(-m_size.width / 2 + 3.f, m_size.height / 2 - 3.f);
auto closeBtn = CCMenuItemSpriteExtra::create(
closeSpr, this, (cocos2d::SEL_MenuHandler)(&Popup::onClose)
);
closeBtn->setPosition(
-m_size.width / 2 + 3.f,
m_size.height / 2 - 3.f
);
m_buttonMenu->addChild(closeBtn);
this->setKeypadEnabled(true);
@ -48,7 +56,7 @@ namespace geode {
return true;
}
virtual void setup(InitArgs... args) = 0;
virtual bool setup(InitArgs... args) = 0;
void keyDown(cocos2d::enumKeyCodes key) {
if (key == cocos2d::enumKeyCodes::KEY_Escape) return this->onClose(nullptr);
@ -60,6 +68,21 @@ namespace geode {
this->setKeyboardEnabled(false);
this->removeFromParentAndCleanup(true);
}
void setTitle(const char* title, const char* font = "goldFont.fnt") {
if (m_title) {
m_title->setString(title);
} else {
auto winSize = cocos2d::CCDirector::sharedDirector()->getWinSize();
m_title = cocos2d::CCLabelBMFont::create(title, font);
m_title->setPosition(
winSize.width / 2,
winSize.height / 2 + m_size.height / 2 - 20.f
);
m_mainLayer->addChild(m_title, 2);
}
m_title->limitLabelWidth(m_size.width - 20.f, .7f, .1f);
}
};
void GEODE_DLL createQuickPopup(

View file

@ -56,7 +56,11 @@ namespace geode {
* structures, this class can handle either
* one universally. All relevant vtables are
* stored in-class to avoid needing to
* `dynamic_cast` everything.
* `dynamic_cast` everything. This way of
* storing vtables also means that anything
* which satisfies these 3 vtables can be used,
* even if its true UX representation is
* actually not a label.
*/
struct Label {
/**

View file

@ -372,4 +372,92 @@ namespace geode::cocos {
auto selectorFromFn(std::function<F> fn) {
return SelectorWrapper(fn);
}
// namespace for storing implementation stuff for
// inline member functions
namespace {
// class that holds the lambda (probably should've just used
// std::function but hey, this one's heap-free!)
template<class F, class Ret, class... Args>
struct LambdaHolder {
bool m_assigned = false;
// lambdas don't implement operator= so we
// gotta do this wacky union stuff
union {
F m_lambda;
};
LambdaHolder() {}
~LambdaHolder() {
if (m_assigned) {
m_lambda.~F();
}
}
Ret operator()(Args... args) {
if (m_assigned) {
return m_lambda(std::forward<Args>(args)...);
} else {
if constexpr (!std::is_void_v<Ret>) {
return Ret();
}
}
}
void assign(F&& func) {
if (!m_assigned) {
new (&m_lambda) F(func);
m_assigned = true;
}
}
};
// Extract parameters and return type from a lambda
template<class Func>
struct ExtractLambda : public ExtractLambda<decltype(&Func::operator())> {};
template<class C, class R, class... Args>
struct ExtractLambda<R(C::*)(Args...) const> {
using Ret = R;
using Params = std::tuple<Args...>;
};
// Class for storing the member function
template<class Base, class Func, class Args>
struct InlineMemberFunction;
template<class Base, class Func, class... Args>
struct InlineMemberFunction<Base, Func, std::tuple<Args...>> : public Base {
// this class isn't instantiated anywhere, and is
// just used as a proxy to redirect the member function
// to the lambda
static inline LambdaHolder<Func, typename ExtractLambda<Func>::Ret, Args...> s_selector {};
typename ExtractLambda<Func>::Ret onSelector(Args... args) {
return s_selector(std::forward<Args>(args)...);
}
};
}
/**
* Wrap a lambda into a member function pointer. Useful for creating
* callbacks that have to be members of a class without having to deal
* with all of the boilerplate associated with defining a new class
* member function.
*/
template<class Base, class Func>
static auto makeMemberFunction(Func&& function) {
InlineMemberFunction<Base, Func, typename ExtractLambda<Func>::Params>::s_selector.assign(std::move(function));
return &InlineMemberFunction<Base, Func, typename ExtractLambda<Func>::Params>::onSelector;
}
/**
* Create a SEL_MenuHandler out of a lambda with optional captures. Useful
* for adding callbacks to CCMenuItemSpriteExtras without needing to add
* the callback as a member to a class. Use the GEODE_MENU_SELECTOR class
* for even more concise code.
*/
template<class Func>
static cocos2d::SEL_MenuHandler makeMenuSelector(Func&& selector) {
return (cocos2d::SEL_MenuHandler)(makeMemberFunction<cocos2d::CCObject, Func>(std::move(selector)));
}
#define GEODE_MENU_SELECTOR(senderArg, ...) \
makeMenuSelector([=](senderArg) { __VA_ARGS__; })
}

BIN
loader/resources/sort.png Normal file

Binary file not shown.

After

(image error) Size: 3 KiB

View file

@ -70,6 +70,22 @@ bool InternalLoader::platformConsoleReady() const {
return m_platformConsoleReady;
}
bool InternalLoader::shownInfoAlert(std::string const& key) {
if (m_shownInfoAlerts.count(key)) {
return true;
}
m_shownInfoAlerts.insert(key);
return false;
}
void InternalLoader::saveInfoAlerts(nlohmann::json& json) {
json["alerts"] = m_shownInfoAlerts;
}
void InternalLoader::loadInfoAlerts(nlohmann::json& json) {
m_shownInfoAlerts = json["alerts"].get<std::unordered_set<std::string>>();
}
#if defined(GEODE_IS_WINDOWS)
void InternalLoader::platformMessageBox(const char* title, const char* info) {
MessageBoxA(nullptr, title, info, MB_OK);

View file

@ -7,6 +7,7 @@
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/Log.hpp>
#include <Geode/utils/Result.hpp>
#include <unordered_set>
USE_GEODE_NAMESPACE();
@ -20,9 +21,16 @@ protected:
std::vector<std::function<void(void)>> m_gdThreadQueue;
mutable std::mutex m_gdThreadMutex;
bool m_platformConsoleReady = false;
std::unordered_set<std::string> m_shownInfoAlerts;
void saveInfoAlerts(nlohmann::json& json);
void loadInfoAlerts(nlohmann::json& json);
InternalLoader();
~InternalLoader();
friend class Loader;
public:
static InternalLoader* get();
@ -30,6 +38,13 @@ public:
bool loadHooks();
/**
* Check if a one-time event has been shown to the user,
* and set it to true if not. Will return the previous
* state of the event before setting it to true
*/
bool shownInfoAlert(std::string const& key);
void queueInGDThread(std::function<void GEODE_CALL(void)> func);
void executeGDThreadQueue();

View file

@ -192,6 +192,7 @@ Result<> Loader::saveSettings() {
json["mods"][id] = value;
}
json["succesfully-closed"] = true;
InternalLoader::get()->saveInfoAlerts(json);
auto path = this->getGeodeSaveDirectory() / "mods.json";
return file_utils::writeString(path, json.dump(4));
}
@ -233,6 +234,7 @@ Result<> Loader::loadSettings() {
m_loadedSettings.m_mods.insert({ key, mod });
}
}
InternalLoader::get()->loadInfoAlerts(json);
return Ok();
} catch(std::exception const& e) {
return Err(e.what());

View file

@ -2,6 +2,7 @@
#include <Geode/ui/BasedButton.hpp>
#include "SearchFilterPopup.hpp"
#include <Geode/ui/Notification.hpp>
#include <optional>
static ModListType g_tab = ModListType::Installed;
static ModListLayer* g_instance = nullptr;
@ -124,17 +125,21 @@ std::tuple<CCNode*, CCTextInputNode*> ModListLayer::createSearchControl() {
auto menu = CCMenu::create();
menu->setPosition(340.f, 15.f);
// filters
auto filterSpr = EditorButtonSprite::createWithSpriteFrameName(
"filters.png"_spr, 1.0f, EditorBaseColor::Gray
);
filterSpr->setScale(.7f);
auto filterBtn = CCMenuItemSpriteExtra::create(
filterSpr, this, menu_selector(ModListLayer::onSearchFilters)
filterSpr, this, makeMenuSelector([this](CCObject*) {
SearchFilterPopup::create(this)->show();
})
);
filterBtn->setPosition(-8.f, 0.f);
filterBtn->setPosition(-10.f, 0.f);
menu->addChild(filterBtn);
// search button
auto searchSpr = CCSprite::createWithSpriteFrameName("gj_findBtn_001.png");
searchSpr->setScale(.7f);
@ -152,16 +157,17 @@ std::tuple<CCNode*, CCTextInputNode*> ModListLayer::createSearchControl() {
m_searchClearBtn->setVisible(false);
menu->addChild(m_searchClearBtn);
// search input
auto inputBG = CCScale9Sprite::create(
"square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }
);
inputBG->setColor({ 126, 59, 7 });
inputBG->setContentSize({ 530.f, 40.f });
inputBG->setPosition(153.f, 15.f);
inputBG->setContentSize({ 550.f, 40.f });
inputBG->setPosition(150.f, 15.f);
inputBG->setScale(.5f);
layer->addChild(inputBG);
auto input = CCTextInputNode::create(250.f, 20.f, "Search Mods...", "bigFont.fnt");
auto input = CCTextInputNode::create(260.f, 20.f, "Search Mods...", "bigFont.fnt");
input->setLabelPlaceholderColor({ 150, 150, 150 });
input->setLabelPlaceholderScale(.4f);
input->setMaxLabelScale(.4f);
@ -170,6 +176,7 @@ std::tuple<CCNode*, CCTextInputNode*> ModListLayer::createSearchControl() {
input->m_placeholderLabel->setAnchorPoint({ .0f, .5f });
layer->addChild(menu);
return { layer, input };
}
@ -278,7 +285,7 @@ void ModListLayer::reloadList() {
m_searchInput = std::get<1>(search);
m_searchInput->setPosition(
winSize.width / 2 - 155.f,
winSize.width / 2 - 160.f,
winSize.height / 2 + 95.f
);
m_searchInput->setZOrder(60);
@ -397,10 +404,6 @@ void ModListLayer::onTab(CCObject* pSender) {
toggleTab(m_featuredTabBtn);
}
void ModListLayer::onSearchFilters(CCObject*) {
SearchFilterPopup::create(this)->show();
}
ModListLayer* ModListLayer::create() {
// return global instance if one exists
if (g_instance) return g_instance;

View file

@ -37,7 +37,6 @@ protected:
void onResetSearch(CCObject*);
void keyDown(enumKeyCodes) override;
void onTab(CCObject*);
void onSearchFilters(CCObject*);
void textChanged(CCTextInputNode*) override;
void indexUpdateProgress(
UpdateStatus status,

View file

@ -3,6 +3,15 @@
#include <Geode/utils/WackyGeodeMacros.hpp>
#include <Index.hpp>
#include "ModListLayer.hpp"
#include <InternalLoader.hpp>
template<class T>
static bool tryOrAlert(Result<T> const& res, const char* title) {
if (!res) {
FLAlertLayer::create(title, res.error(), "OK")->show();
}
return res;
}
ModCell::ModCell(const char* name, CCSize size) :
TableViewCell(name, size.width, size.height) {}
@ -234,50 +243,24 @@ void ModCell::updateBGColor(int index) {
}
void ModCell::onEnable(CCObject* pSender) {
// if (!APIInternal::get()->m_shownEnableWarning) {
// APIInternal::get()->m_shownEnableWarning = true;
// FLAlertLayer::create(
// "Notice",
// "<cb>Disabling</c> a <cy>mod</c> removes its hooks & patches and "
// "calls its user-defined disable function if one exists. You may "
// "still see some effects of the mod left however, and you may "
// "need to <cg>restart</c> the game to have it fully unloaded.",
// "OK"
// )->show();
// m_list->updateAllStates(this);
// return;
// }
if (!InternalLoader::get()->shownInfoAlert("mod-disable-vs-unload")) {
FLAlertLayer::create(
"Notice",
"<cb>Disabling</c> a <cy>mod</c> removes its hooks & patches and "
"calls its user-defined disable function if one exists. You may "
"still see some effects of the mod left however, and you may "
"need to <cg>restart</c> the game to have it fully unloaded.",
"OK"
)->show();
m_list->updateAllStates(this);
return;
}
if (!as<CCMenuItemToggler*>(pSender)->isToggled()) {
auto res = m_obj->m_mod->load();
if (!res) {
FLAlertLayer::create(
nullptr,
"Error Loading Mod",
res.error(),
"OK", nullptr
)->show();
}
else {
auto res = m_obj->m_mod->enable();
if (!res) {
FLAlertLayer::create(
nullptr,
"Error Enabling Mod",
res.error(),
"OK", nullptr
)->show();
}
if (tryOrAlert(m_obj->m_mod->enable(), "Error enabling mod")) {
tryOrAlert(m_obj->m_mod->load(), "Error loading mod");
}
} else {
auto res = m_obj->m_mod->disable();
if (!res) {
FLAlertLayer::create(
nullptr,
"Error Disabling Mod",
res.error(),
"OK", nullptr
)->show();
}
tryOrAlert(m_obj->m_mod->disable(), "Error disabling mod");
}
m_list->updateAllStates(this);
}

View file

@ -2,98 +2,48 @@
#include "ModListLayer.hpp"
#include "ModListView.hpp"
bool SearchFilterPopup::init(ModListLayer* layer) {
this->m_noElasticity = true;
bool SearchFilterPopup::setup(ModListLayer* layer) {
// todo: clean this shitty ass popup up
m_noElasticity = true;
m_modLayer = layer;
this->m_modLayer = layer;
this->setTitle("Match Fields");
auto winSize = CCDirector::sharedDirector()->getWinSize();
CCSize size { 280.f, 250.f };
auto pos = CCPoint { winSize.width / 2 - 55.f, winSize.height / 2 + 50.f };
if (!this->initWithColor({ 0, 0, 0, 105 })) return false;
this->m_mainLayer = CCLayer::create();
this->addChild(this->m_mainLayer);
auto bg = CCScale9Sprite::create("GJ_square05.png", { 0.0f, 0.0f, 80.0f, 80.0f });
bg->setContentSize(size);
bg->setPosition(winSize.width / 2, winSize.height / 2);
this->m_mainLayer->addChild(bg);
this->m_buttonMenu = CCMenu::create();
this->m_mainLayer->addChild(this->m_buttonMenu);
auto nameLabel = CCLabelBMFont::create("Search Filters", "goldFont.fnt");
nameLabel->setPosition(winSize.width / 2, winSize.height / 2 + 100.f);
nameLabel->setScale(.7f);
this->m_mainLayer->addChild(nameLabel, 2);
this->m_pos = CCPoint { winSize.width / 2 - 45.f, 86.f };
this->addToggle("Name", ModListView::SearchFlags::Name);
this->addToggle("ID", ModListView::SearchFlags::ID);
this->addToggle("Credits", ModListView::SearchFlags::Credits);
this->addToggle("Description", ModListView::SearchFlags::Description);
this->addToggle("Details", ModListView::SearchFlags::Details);
this->addToggle("Developer", ModListView::SearchFlags::Developer);
CCDirector::sharedDirector()->getTouchDispatcher()->incrementForcePrio(2);
this->registerWithTouchDispatcher();
auto closeSpr = CCSprite::createWithSpriteFrameName("GJ_closeBtn_001.png");
closeSpr->setScale(1.0f);
auto closeBtn = CCMenuItemSpriteExtra::create(
closeSpr,
this,
(SEL_MenuHandler)&SearchFilterPopup::onClose
);
closeBtn->setUserData(reinterpret_cast<void*>(this));
this->m_buttonMenu->addChild(closeBtn);
closeBtn->setPosition( - size.width / 2, size.height / 2 );
this->setKeypadEnabled(true);
this->setTouchEnabled(true);
this->addToggle("Name", ModListView::SearchFlags::Name, pos);
this->addToggle("ID", ModListView::SearchFlags::ID, pos);
this->addToggle("Credits", ModListView::SearchFlags::Credits, pos);
this->addToggle("Description", ModListView::SearchFlags::Description, pos);
this->addToggle("Details", ModListView::SearchFlags::Details, pos);
this->addToggle("Developer", ModListView::SearchFlags::Developer, pos);
return true;
}
void SearchFilterPopup::addToggle(const char* title, int flag) {
void SearchFilterPopup::addToggle(const char* title, int flag, CCPoint& pos) {
GameToolbox::createToggleButton(
title, menu_selector(SearchFilterPopup::onToggle),
this->m_modLayer->m_searchFlags & flag,
this->m_buttonMenu, this->m_pos, this,
this->m_buttonMenu, .5f, .5f, 100.f,
m_modLayer->m_searchFlags & flag,
m_buttonMenu, pos, this,
m_buttonMenu, .5f, .5f, 100.f,
{ 10.f, .0f }, nullptr, false, flag, nullptr
)->setTag(flag);
this->m_pos.y += 25.f;
}
void SearchFilterPopup::keyDown(cocos2d::enumKeyCodes key) {
if (key == KEY_Escape)
return onClose(nullptr);
if (key == KEY_Space)
return;
return FLAlertLayer::keyDown(key);
}
void SearchFilterPopup::onClose(cocos2d::CCObject*) {
this->setKeyboardEnabled(false);
this->removeFromParentAndCleanup(true);
pos.y -= 25.f;
}
void SearchFilterPopup::onToggle(cocos2d::CCObject* pSender) {
if (as<CCMenuItemToggler*>(pSender)->isToggled()) {
this->m_modLayer->m_searchFlags &= ~pSender->getTag();
m_modLayer->m_searchFlags &= ~pSender->getTag();
} else {
this->m_modLayer->m_searchFlags |= pSender->getTag();
m_modLayer->m_searchFlags |= pSender->getTag();
}
}
SearchFilterPopup* SearchFilterPopup::create(ModListLayer* layer) {
auto ret = new SearchFilterPopup();
if (ret && ret->init(layer)) {
if (ret && ret->init(200.f, 200.f, layer, "GJ_square05.png")) {
ret->autorelease();
return ret;
}

View file

@ -1,24 +1,21 @@
#pragma once
#include <Geode/Geode.hpp>
#include <Geode/ui/Popup.hpp>
USE_GEODE_NAMESPACE();
class ModListLayer;
class SearchFilterPopup : public FLAlertLayer {
protected:
ModListLayer* m_modLayer;
CCPoint m_pos;
class SearchFilterPopup : public Popup<ModListLayer*> {
protected:
ModListLayer* m_modLayer;
bool init(ModListLayer* layer);
void addToggle(const char* title, int flag);
bool setup(ModListLayer* layer) override;
void addToggle(const char* title, int flag, CCPoint& pos);
void keyDown(cocos2d::enumKeyCodes) override;
void onClose(cocos2d::CCObject*);
void onToggle(cocos2d::CCObject*);
public:
static SearchFilterPopup* create(ModListLayer* layer);
void onToggle(cocos2d::CCObject*);
public:
static SearchFilterPopup* create(ModListLayer* layer);
};