make mod list state updating use events instead of poor man's delegates

This commit is contained in:
HJfod 2024-03-25 15:17:20 +02:00
parent e13091115b
commit 54e9763631
14 changed files with 100 additions and 64 deletions

View file

@ -167,6 +167,10 @@ bool ModsLayer::init() {
this->updateState();
// The overall mods layer only cares about page number updates
m_updateStateListener.setFilter(UpdateModListStateFilter(UpdatePageNumberState()));
m_updateStateListener.bind([this](auto) { this->updateState(); });
return true;
}
@ -190,7 +194,6 @@ void ModsLayer::gotoTab(ModListSourceType type) {
// Lazily create new list and add it to UI
if (!m_lists.contains(src)) {
auto list = ModList::create(src, m_frame->getContentSize() - ccp(30, 0));
list->onUpdateParentState(std::bind(&ModsLayer::updateState, this));
list->setPosition(m_frame->getPosition());
this->addChild(list);
m_lists.emplace(src, list);

View file

@ -6,6 +6,7 @@
#include "list/ModItem.hpp"
#include "list/ModList.hpp"
#include "sources/ModListSource.hpp"
#include "UpdateModListState.hpp"
using namespace geode::prelude;
@ -19,6 +20,7 @@ protected:
CCLabelBMFont* m_pageLabel;
CCMenuItemSpriteExtra* m_goToPageBtn;
CCMenuItemSpriteExtra* m_restartBtn;
EventListener<UpdateModListStateFilter> m_updateStateListener;
bool m_showSearch = false;
bool m_bigView = false;

View file

@ -0,0 +1,20 @@
#include "UpdateModListState.hpp"
UpdateModListStateEvent::UpdateModListStateEvent(UpdateState&& target) : target(target) {}
ListenerResult UpdateModListStateFilter::handle(MiniFunction<Callback> fn, UpdateModListStateEvent* event) {
if (
// If the listener wants to hear all state updates then let it
std::holds_alternative<UpdateWholeState>(m_target) ||
// If the event is update everything then update everything
std::holds_alternative<UpdateWholeState>(event->target) ||
// Otherwise only run if the event is what is asked for
m_target == event->target
) {
fn(event);
}
return ListenerResult::Propagate;
}
UpdateModListStateFilter::UpdateModListStateFilter() : m_target(UpdateWholeState()) {}
UpdateModListStateFilter::UpdateModListStateFilter(UpdateState&& target) : m_target(target) {}

View file

@ -0,0 +1,39 @@
#pragma once
#include <Geode/loader/Event.hpp>
#include "sources/ModSource.hpp"
using namespace geode::prelude;
struct UpdatePageNumberState final {
constexpr bool operator==(UpdatePageNumberState const&) const = default;
};
struct UpdateWholeState final {
constexpr bool operator==(UpdateWholeState const&) const = default;
};
struct UpdateModState final {
std::string modID;
inline explicit UpdateModState(std::string const& modID) : modID(modID) {};
constexpr bool operator==(UpdateModState const&) const = default;
};
using UpdateState = std::variant<UpdatePageNumberState, UpdateWholeState, UpdateModState>;
struct UpdateModListStateEvent : public Event {
UpdateState target;
UpdateModListStateEvent(UpdateState&& target);
};
class UpdateModListStateFilter : public EventFilter<UpdateModListStateEvent> {
public:
using Callback = void(UpdateModListStateEvent*);
protected:
UpdateState m_target;
public:
ListenerResult handle(MiniFunction<Callback> fn, UpdateModListStateEvent* event);
UpdateModListStateFilter();
UpdateModListStateFilter(UpdateState&& target);
};

View file

@ -126,6 +126,10 @@ bool ModItem::init(ModSource&& source) {
this->updateState();
// Only listen for updates on this mod specifically
m_updateStateListener.setFilter(UpdateModListStateFilter(UpdateModState(m_source.getID())));
m_updateStateListener.bind([this](auto) { this->updateState(); });
return true;
}
@ -161,11 +165,6 @@ void ModItem::updateState() {
m_bg->setOpacity(40);
}
// Propagate update up the chain
if (m_updateParentState) {
m_updateParentState();
}
// Update enable toggle state
if (m_enableToggle && m_source.asMod()) {
m_enableToggle->toggle(m_source.asMod()->isOrWillBeEnabled());
@ -216,10 +215,6 @@ void ModItem::updateSize(float width, bool big) {
this->updateLayout();
}
void ModItem::onUpdateParentState(MiniFunction<void()> listener) {
m_updateParentState = listener;
}
ModItem* ModItem::create(ModSource&& source) {
auto ret = new ModItem();
if (ret && ret->init(std::move(source))) {
@ -232,11 +227,7 @@ ModItem* ModItem::create(ModSource&& source) {
void ModItem::onView(CCObject*) {
// Always open up the popup for the installed mod page if that is possible
auto popup = ModPopup::create(m_source.tryConvertToMod());
popup->onUpdateParentState([this]() {
this->updateState();
});
popup->show();
ModPopup::create(m_source.tryConvertToMod())->show();
}
void ModItem::onEnable(CCObject*) {
@ -252,6 +243,6 @@ void ModItem::onEnable(CCObject*) {
}
}
// Update whole state of the mod item
this->updateState();
// Update state of the mod item
UpdateModListStateEvent(UpdateModState(m_source.getID())).post();
}

View file

@ -3,6 +3,7 @@
#include <Geode/ui/General.hpp>
#include <server/Server.hpp>
#include "../sources/ModSource.hpp"
#include "../UpdateModListState.hpp"
using namespace geode::prelude;
@ -19,9 +20,9 @@ protected:
CCLabelBMFont* m_developerLabel;
ButtonSprite* m_restartRequiredLabel = nullptr;
CCMenu* m_viewMenu;
MiniFunction<void()> m_updateParentState = nullptr;
CCMenuItemToggler* m_enableToggle = nullptr;
CCScale9Sprite* m_checkmark = nullptr;
EventListener<UpdateModListStateFilter> m_updateStateListener;
/**
* @warning Make sure `getMetadata` and `createModLogo` are callable
@ -29,8 +30,6 @@ protected:
*/
bool init(ModSource&& source);
// This should never be exposed outside, so the parent can't call this and
// cause an infinite loop during state updating
void updateState();
void onEnable(CCObject*);
@ -40,6 +39,4 @@ public:
static ModItem* create(ModSource&& source);
void updateSize(float width, bool big);
void onUpdateParentState(MiniFunction<void()> listener);
};

View file

@ -194,7 +194,6 @@ void ModList::onPromise(typename ModListSource::PageLoadEvent* event) {
}
first = false;
m_list->m_contentLayer->addChild(item);
item->onUpdateParentState(m_updateParentState);
}
this->updateSize(m_bigSize);
@ -319,10 +318,8 @@ void ModList::updatePageNumber() {
m_pagePrevBtn->setVisible(pageCount && m_page > 0);
m_pageNextBtn->setVisible(pageCount && m_page < pageCount.value() - 1);
// Notify container about page count update
if (m_updateParentState) {
m_updateParentState();
}
// Post the update page number event
UpdateModListStateEvent(UpdatePageNumberState()).post();
}
void ModList::reloadPage() {
@ -373,10 +370,6 @@ void ModList::showStatus(ModListStatus status, std::string const& message, std::
m_statusContainer->updateLayout();
}
void ModList::onUpdateParentState(MiniFunction<void()> listener) {
m_updateParentState = listener;
}
void ModList::onFilters(CCObject*) {
TagsPopup::create(m_source, [this]() {
this->gotoPage(0);

View file

@ -32,7 +32,7 @@ protected:
CCMenuItemSpriteExtra* m_pageNextBtn;
Ref<CCNode> m_searchMenu;
TextInput* m_searchInput;
MiniFunction<void()> m_updateParentState = nullptr;
EventListener<UpdateModListStateFilter> m_updateStateListener;
bool m_bigSize = false;
std::atomic<size_t> m_searchInputThreads = 0;
@ -47,8 +47,6 @@ protected:
public:
static ModList* create(ModListSource* src, CCSize const& size);
// poor man's delegate
void onUpdateParentState(MiniFunction<void()> listener);
size_t getPage() const;
void reloadPage();

View file

@ -55,9 +55,7 @@ void ConfirmUninstallPopup::onUninstall(CCObject*) {
)->show();
}
if (m_updateParentState) {
m_updateParentState();
}
UpdateModListStateEvent(UpdateModState(m_mod->getID())).post();
this->onClose(nullptr);
}
@ -71,7 +69,3 @@ ConfirmUninstallPopup* ConfirmUninstallPopup::create(Mod* mod) {
CC_SAFE_DELETE(ret);
return nullptr;
}
void ConfirmUninstallPopup::onUpdateParentState(MiniFunction<void()> listener) {
m_updateParentState = listener;
}

View file

@ -1,6 +1,7 @@
#pragma once
#include <Geode/ui/Popup.hpp>
#include "../UpdateModListState.hpp"
using namespace geode::prelude;
@ -8,7 +9,7 @@ class ConfirmUninstallPopup : public Popup<Mod*> {
protected:
Mod* m_mod;
CCMenuItemToggler* m_deleteDataToggle;
MiniFunction<void()> m_updateParentState = nullptr;
EventListener<UpdateModListStateFilter> m_updateStateListener;
bool setup(Mod* mod) override;
@ -16,7 +17,4 @@ protected:
public:
static ConfirmUninstallPopup* create(Mod* mod);
// todo: replace all of these with a single event
void onUpdateParentState(MiniFunction<void()> listener);
};

View file

@ -434,6 +434,10 @@ bool ModPopup::setup(ModSource&& src) {
m_tagsListener.bind(this, &ModPopup::onLoadTags);
m_tagsListener.setFilter(m_source.fetchValidTags().listen());
// Only listen for updates on this mod specifically
m_updateStateListener.setFilter(UpdateModListStateFilter(UpdateModState(m_source.getID())));
m_updateStateListener.bind([this](auto) { this->updateState(); });
return true;
}
@ -463,11 +467,6 @@ void ModPopup::updateState() {
}
m_installMenu->updateLayout();
// Propagate update up the chain
if (m_updateParentState) {
m_updateParentState();
}
}
void ModPopup::setStatIcon(CCNode* stat, const char* spr) {
@ -653,17 +652,12 @@ void ModPopup::onEnable(CCObject*) {
else {
FLAlertLayer::create("Error Toggling Mod", "This mod can not be toggled!", "OK")->show();
}
this->updateState();
UpdateModListStateEvent(UpdateModState(m_source.getID())).post();
}
void ModPopup::onUninstall(CCObject*) {
if (auto mod = m_source.asMod()) {
auto popup = ConfirmUninstallPopup::create(mod);
popup->onUpdateParentState([this] {
this->updateState();
this->onClose(nullptr);
});
popup->show();
ConfirmUninstallPopup::create(mod)->show();
}
else {
FLAlertLayer::create(
@ -693,7 +687,3 @@ ModPopup* ModPopup::create(ModSource&& src) {
CC_SAFE_DELETE(ret);
return nullptr;
}
void ModPopup::onUpdateParentState(MiniFunction<void()> listener) {
m_updateParentState = listener;
}

View file

@ -4,6 +4,7 @@
#include <Geode/ui/MDTextArea.hpp>
#include "../sources/ModSource.hpp"
#include "../GeodeStyle.hpp"
#include "../UpdateModListState.hpp"
using namespace geode::prelude;
@ -31,7 +32,7 @@ protected:
std::unordered_map<Tab, std::pair<GeodeTabSprite*, Ref<CCNode>>> m_tabs;
EventListener<PromiseEventFilter<server::ServerModMetadata, server::ServerError>> m_statsListener;
EventListener<PromiseEventFilter<std::unordered_set<std::string>, server::ServerError>> m_tagsListener;
MiniFunction<void()> m_updateParentState = nullptr;
EventListener<UpdateModListStateFilter> m_updateStateListener;
bool setup(ModSource&& src) override;
void updateState();
@ -52,6 +53,4 @@ protected:
public:
static ModPopup* create(ModSource&& src);
void onUpdateParentState(MiniFunction<void()> listener);
};

View file

@ -4,6 +4,17 @@
ModSource::ModSource(Mod* mod) : m_value(mod) {}
ModSource::ModSource(server::ServerModMetadata&& metadata) : m_value(metadata) {}
std::string ModSource::getID() const {
return std::visit(makeVisitor {
[](Mod* mod) {
return mod->getID();
},
[](server::ServerModMetadata const& metadata) {
// Versions should be guaranteed to have at least one item
return metadata.id;
}
}, m_value);
}
ModMetadata ModSource::getMetadata() const {
return std::visit(makeVisitor {
[](Mod* mod) {

View file

@ -14,6 +14,7 @@ public:
ModSource(Mod* mod);
ModSource(server::ServerModMetadata&& metadata);
std::string getID() const;
ModMetadata getMetadata() const;
std::optional<std::string> getAbout() const;
std::optional<std::string> getChangelog() const;