refactor to move all ModItems to one monolithic class rather than subclasses

This commit is contained in:
HJfod 2024-03-01 15:01:22 +02:00
parent 5e8306ac65
commit 65f6158774
9 changed files with 231 additions and 198 deletions

View file

@ -2,12 +2,13 @@
#include <Geode/ui/GeodeUI.hpp>
#include <Geode/utils/ColorProvider.hpp>
#include "GeodeStyle.hpp"
#include "ModPopup.hpp"
bool BaseModItem::init() {
bool ModItem::init(ModSource&& source) {
if (!CCNode::init())
return false;
auto meta = this->getMetadata();
m_source = std::move(source);
m_bg = CCScale9Sprite::create("square02b_small.png");
m_bg->setOpacity(0);
@ -16,7 +17,7 @@ bool BaseModItem::init() {
m_bg->setScale(.7f);
this->addChild(m_bg);
m_logo = this->createModLogo();
m_logo = m_source.createModLogo();
this->addChild(m_logo);
m_infoContainer = CCNode::create();
@ -38,7 +39,7 @@ bool BaseModItem::init() {
->setAxisAlignment(AxisAlignment::Start)
);
m_titleLabel = CCLabelBMFont::create(meta.getName().c_str(), "bigFont.fnt");
m_titleLabel = CCLabelBMFont::create(m_source.getMetadata().getName().c_str(), "bigFont.fnt");
m_titleLabel->setAnchorPoint({ .0f, .5f });
m_titleLabel->setLayoutOptions(
AxisLayoutOptions::create()
@ -52,7 +53,7 @@ bool BaseModItem::init() {
m_developers->ignoreAnchorPointForPosition(false);
m_developers->setAnchorPoint({ .0f, .5f });
auto by = "By " + ModMetadata::formatDeveloperDisplayString(this->getMetadata().getDevelopers());
auto by = "By " + ModMetadata::formatDeveloperDisplayString(m_source.getMetadata().getDevelopers());
m_developerLabel = CCLabelBMFont::create(by.c_str(), "goldFont.fnt");
auto developersBtn = CCMenuItemSpriteExtra::create(
m_developerLabel, this, nullptr
@ -83,7 +84,8 @@ bool BaseModItem::init() {
m_viewMenu->setScale(.55f);
auto viewBtn = CCMenuItemSpriteExtra::create(
createGeodeButton("View"), this, nullptr
createGeodeButton("View"),
this, menu_selector(ModItem::onView)
);
m_viewMenu->addChild(viewBtn);
@ -95,19 +97,64 @@ bool BaseModItem::init() {
);
this->addChildAtPosition(m_viewMenu, Anchor::Right, ccp(-10, 0));
// Handle source-specific stuff
m_source.visit(makeVisitor {
[this](Mod* mod) {
// Add an enable button if the mod is enablable
if (!mod->isInternal()) {
m_enableToggle = CCMenuItemToggler::createWithStandardSprites(
this, menu_selector(ModItem::onEnable), 1.f
);
// Manually handle toggle state
m_enableToggle->m_notClickable = true;
m_viewMenu->addChild(m_enableToggle);
m_viewMenu->updateLayout();
}
},
[this](server::ServerModMetadata const& metadata) {
if (metadata.featured) {
m_checkmark = CCScale9Sprite::createWithSpriteFrameName("GJ_colorBtn_001.png");
m_checkmark->setContentSize({ 50, 38 });
m_checkmark->setColor({ 255, 255, 120 });
m_checkmark->setOpacity(45);
auto tick = CCSprite::createWithSpriteFrameName("GJ_starsIcon_001.png");
m_checkmark->addChildAtPosition(tick, Anchor::Center);
m_titleContainer->addChild(m_checkmark);
}
},
});
this->updateState();
return true;
}
void BaseModItem::updateState() {
auto wantsRestart = this->wantsRestart();
void ModItem::updateState() {
auto wantsRestart = m_source.wantsRestart();
m_restartRequiredLabel->setVisible(wantsRestart);
m_developers->setVisible(!wantsRestart);
m_infoContainer->updateLayout();
// Set default color to BG to start off with
// Set default colors based on source to start off with
// (possibly overriding later based on state)
m_bg->setColor(to3B(m_defaultBG));
m_bg->setOpacity(m_defaultBG.a);
m_source.visit(makeVisitor {
[this](Mod* mod) {
m_bg->setColor({ 255, 255, 255 });
m_bg->setOpacity(mod->isOrWillBeEnabled() ? 25 : 10);
m_titleLabel->setOpacity(mod->isOrWillBeEnabled() ? 255 : 155);
m_developerLabel->setOpacity(mod->isOrWillBeEnabled() ? 255 : 155);
},
[this](server::ServerModMetadata const& metadata) {
m_bg->setColor({ 255, 255, 255 });
m_bg->setOpacity(25);
if (metadata.featured && m_checkmark) {
m_bg->setColor(m_checkmark->getColor());
m_bg->setOpacity(40);
}
},
});
// Highlight item via BG if it wants to restart for extra UI attention
if (wantsRestart) {
@ -119,9 +166,14 @@ void BaseModItem::updateState() {
if (m_updateParentState) {
m_updateParentState();
}
// Update enable toggle state
if (m_enableToggle && m_source.asMod()) {
m_enableToggle->toggle(m_source.asMod()->isOrWillBeEnabled());
}
}
void BaseModItem::updateSize(float width, bool big) {
void ModItem::updateSize(float width, bool big) {
this->setContentSize({ width, big ? 40.f : 30.f });
m_bg->setContentSize((m_obContentSize - ccp(6, 0)) / m_bg->getScale());
@ -154,135 +206,37 @@ void BaseModItem::updateSize(float width, bool big) {
this->updateLayout();
}
void BaseModItem::onUpdateParentState(MiniFunction<void()> listener) {
void ModItem::onUpdateParentState(MiniFunction<void()> listener) {
m_updateParentState = listener;
}
bool InstalledModItem::init(Mod* mod) {
m_mod = mod;
if (!BaseModItem::init())
return false;
// Add an enable button if the mod is enablable
if (!mod->isInternal()) {
m_enableToggle = CCMenuItemToggler::createWithStandardSprites(
this, menu_selector(InstalledModItem::onEnable), 1.f
);
// Manually handle toggle state
m_enableToggle->m_notClickable = true;
m_viewMenu->addChild(m_enableToggle);
m_viewMenu->updateLayout();
ModItem* ModItem::create(ModSource&& source) {
auto ret = new ModItem();
if (ret && ret->init(std::move(source))) {
ret->autorelease();
return ret;
}
this->updateState();
return true;
CC_SAFE_DELETE(ret);
return nullptr;
}
void InstalledModItem::updateState() {
m_defaultBG.a = m_mod->isOrWillBeEnabled() ? 25 : 10;
m_titleLabel->setOpacity(m_mod->isOrWillBeEnabled() ? 255 : 155);
m_developerLabel->setOpacity(m_mod->isOrWillBeEnabled() ? 255 : 155);
BaseModItem::updateState();
// Update enable toggle state
if (m_enableToggle) {
m_enableToggle->toggle(m_mod->isOrWillBeEnabled());
}
void ModItem::onView(CCObject*) {
ModPopup::create(ModSource(m_source))->show();
}
void InstalledModItem::onEnable(CCObject*) {
// Toggle the mod state
auto res = m_mod->isOrWillBeEnabled() ? m_mod->disable() : m_mod->enable();
if (!res) {
FLAlertLayer::create(
"Error Toggling Mod",
res.unwrapErr(),
"OK"
)->show();
void ModItem::onEnable(CCObject*) {
if (auto mod = m_source.asMod()) {
// Toggle the mod state
auto res = mod->isOrWillBeEnabled() ? mod->disable() : mod->enable();
if (!res) {
FLAlertLayer::create(
"Error Toggling Mod",
res.unwrapErr(),
"OK"
)->show();
}
}
// Update whole state of the mod item
this->updateState();
}
InstalledModItem* InstalledModItem::create(Mod* mod) {
auto ret = new InstalledModItem();
if (ret && ret->init(mod)) {
ret->autorelease();
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
ModMetadata InstalledModItem::getMetadata() const {
return m_mod->getMetadata();
}
CCNode* InstalledModItem::createModLogo() const {
return geode::createModLogo(m_mod);
}
bool InstalledModItem::wantsRestart() const {
return m_mod->getRequestedAction() != ModRequestedAction::None;
}
bool ServerModItem::init(server::ServerModMetadata const& metadata) {
m_metadata = metadata;
if (!BaseModItem::init())
return false;
if (metadata.featured) {
m_checkmark = CCScale9Sprite::createWithSpriteFrameName("GJ_colorBtn_001.png");
m_checkmark->setContentSize({ 50, 38 });
m_checkmark->setColor({ 255, 255, 120 });
m_checkmark->setOpacity(45);
auto tick = CCSprite::createWithSpriteFrameName("GJ_starsIcon_001.png");
m_checkmark->addChildAtPosition(tick, Anchor::Center);
m_titleContainer->addChild(m_checkmark);
}
this->updateState();
return true;
}
void ServerModItem::updateState() {
BaseModItem::updateState();
// Update BG color unless we want to restart (more important color to show)
// and if the mod is featured
if (!this->wantsRestart() && m_metadata.featured && m_checkmark) {
m_bg->setColor(m_checkmark->getColor());
m_bg->setOpacity(40);
}
}
ServerModItem* ServerModItem::create(server::ServerModMetadata const& metadata) {
auto ret = new ServerModItem();
if (ret && ret->init(metadata)) {
ret->autorelease();
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
ModMetadata ServerModItem::getMetadata() const {
return m_metadata.versions.front().metadata;
}
CCNode* ServerModItem::createModLogo() const {
return createServerModLogo(m_metadata.id);
}
bool ServerModItem::wantsRestart() const {
// todo: request restart after install
return false;
}

View file

@ -2,13 +2,14 @@
#include <Geode/ui/General.hpp>
#include <server/Server.hpp>
#include "ModSource.hpp"
using namespace geode::prelude;
class BaseModItem : public CCNode {
class ModItem : public CCNode {
protected:
ModSource m_source;
CCScale9Sprite* m_bg;
ccColor4B m_defaultBG = { 255, 255, 255, 25 };
CCNode* m_logo;
CCNode* m_infoContainer;
CCNode* m_titleContainer;
@ -18,65 +19,26 @@ protected:
ButtonSprite* m_restartRequiredLabel = nullptr;
CCMenu* m_viewMenu;
MiniFunction<void()> m_updateParentState = nullptr;
CCMenuItemToggler* m_enableToggle = nullptr;
CCScale9Sprite* m_checkmark = nullptr;
/**
* @warning Make sure `getMetadata` and `createModLogo` are callable
* before calling `init`!
*/
bool init();
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
virtual void updateState();
void updateState();
void onEnable(CCObject*);
void onView(CCObject*);
public:
virtual ModMetadata getMetadata() const = 0;
virtual CCNode* createModLogo() const = 0;
virtual bool wantsRestart() const = 0;
static ModItem* create(ModSource&& source);
virtual void updateSize(float width, bool big);
void updateSize(float width, bool big);
void onUpdateParentState(MiniFunction<void()> listener);
};
class InstalledModItem : public BaseModItem {
protected:
Mod* m_mod;
CCMenuItemToggler* m_enableToggle = nullptr;
bool init(Mod* mod);
void onEnable(CCObject*);
void updateState() override;
public:
/**
* @note Make sure to call `updateSize` afterwards
*/
static InstalledModItem* create(Mod* mod);
ModMetadata getMetadata() const override;
CCNode* createModLogo() const override;
bool wantsRestart() const override;
};
class ServerModItem : public BaseModItem {
protected:
server::ServerModMetadata m_metadata;
CCScale9Sprite* m_checkmark = nullptr;
bool init(server::ServerModMetadata const& metadata);
void updateState() override;
public:
/**
* @note Make sure to call `updateSize` afterwards
*/
static ServerModItem* create(server::ServerModMetadata const& metadata);
ModMetadata getMetadata() const override;
CCNode* createModLogo() const override;
bool wantsRestart() const override;
};

View file

@ -294,7 +294,7 @@ void ModList::updateSize(bool big) {
// Update all BaseModItems that are children of the list
// There may be non-BaseModItems there (like separators) so gotta be type-safe
for (auto& node : CCArrayExt<CCNode*>(m_list->m_contentLayer->getChildren())) {
if (auto item = typeinfo_cast<BaseModItem*>(node)) {
if (auto item = typeinfo_cast<ModItem*>(node)) {
item->updateSize(m_list->getContentWidth(), big);
}
}

View file

@ -70,19 +70,20 @@ static std::pair<std::vector<Mod*>, size_t> getModsWithQuery(server::ModsQuery c
) {
result.push_back(mods.at(i).first);
}
// Return paged mods & total count of matching mods
// Return paged mods and total count of matches
return { result, mods.size() };
}
static auto loadInstalledModsPage(server::ModsQuery&& query) {
return ModListSource::ProviderPromise([query = std::move(query)](auto resolve, auto, auto, auto const&) {
Loader::get()->queueInMainThread([query = std::move(query), resolve = std::move(resolve)] {
auto content = ModListSource::Page();
auto mods = getModsWithQuery(query);
for (auto& mod : mods.first) {
content.push_back(InstalledModItem::create(mod));
auto content = ModListSource::ProvidedMods();
auto paged = getModsWithQuery(query);
for (auto& mod : std::move(paged.first)) {
content.mods.push_back(ModSource(mod));
}
resolve({ content, mods.second });
content.totalModCount = paged.second;
resolve(content);
});
});
}
@ -91,11 +92,12 @@ static auto loadServerModsPage(server::ModsQuery&& query) {
return ModListSource::ProviderPromise([query = std::move(query)](auto resolve, auto reject, auto progress, auto cancelled) {
server::getMods(query)
.then([resolve, reject](server::ServerModsList list) {
auto content = ModListSource::Page();
for (auto& mod : list.mods) {
content.push_back(ServerModItem::create(mod));
auto content = ModListSource::ProvidedMods();
for (auto&& mod : std::move(list.mods)) {
content.mods.push_back(ModSource(std::move(mod)));
}
resolve({ content, list.totalModCount });
content.totalModCount = list.totalModCount;
resolve(content);
})
.expect([reject](auto error) {
reject(ModListSource::LoadPageError("Error loading mods", error.details));
@ -130,12 +132,16 @@ typename ModListSource::PagePromise ModListSource::loadPage(size_t page, bool up
.pageSize = PER_PAGE,
})
.then([page, this, resolve, reject](auto data) {
if (data.second == 0 || data.first.empty()) {
if (data.totalModCount == 0 || data.mods.empty()) {
return reject(ModListSource::LoadPageError("No mods found :("));
}
m_cachedItemCount = data.second;
m_cachedPages.insert({ page, data.first });
resolve(data.first);
auto pageData = Page();
for (auto mod : std::move(data.mods)) {
pageData.push_back(ModItem::create(std::move(mod)));
}
m_cachedItemCount = data.totalModCount;
m_cachedPages.insert({ page, pageData });
resolve(pageData);
})
.expect([this, reject = reject](auto error) {
reject(error);

View file

@ -28,13 +28,17 @@ public:
LoadPageError(auto msg, auto details) : message(msg), details(details) {}
};
using Page = std::vector<Ref<BaseModItem>>;
using Page = std::vector<Ref<ModItem>>;
using PageLoadEvent = PromiseEvent<Page, LoadPageError, std::optional<uint8_t>>;
using PageLoadEventFilter = PromiseEventFilter<Page, LoadPageError, std::optional<uint8_t>>;
using PageLoadEventListener = EventListener<PageLoadEventFilter>;
using PagePromise = Promise<Page, LoadPageError, std::optional<uint8_t>>;
using ProviderPromise = Promise<std::pair<Page, size_t>, LoadPageError, std::optional<uint8_t>>;
struct ProvidedMods {
std::vector<ModSource> mods;
size_t totalModCount;
};
using ProviderPromise = Promise<ProvidedMods, LoadPageError, std::optional<uint8_t>>;
struct Provider {
ProviderPromise(*get)(server::ModsQuery&& query) = nullptr;

View file

@ -0,0 +1,18 @@
#include "ModPopup.hpp"
bool ModPopup::setup(ModSource&& src) {
m_source = std::move(src);
m_noElasticity = true;
return true;
}
ModPopup* ModPopup::create(ModSource&& src) {
auto ret = new ModPopup();
if (ret && ret->init(358.f, 250.f, std::move(src))) {
ret->autorelease();
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}

View file

@ -0,0 +1,16 @@
#pragma once
#include <Geode/ui/Popup.hpp>
#include "ModSource.hpp"
using namespace geode::prelude;
class ModPopup : public Popup<ModSource&&> {
protected:
ModSource m_source;
bool setup(ModSource&& src) override;
public:
static ModPopup* create(ModSource&& src);
};

View file

@ -0,0 +1,46 @@
#include "ModSource.hpp"
#include <Geode/ui/GeodeUI.hpp>
ModSource::ModSource(Mod* mod) : m_value(mod) {}
ModSource::ModSource(server::ServerModMetadata&& metadata) : m_value(metadata) {}
ModMetadata ModSource::getMetadata() const {
return std::visit(makeVisitor {
[](Mod* mod) {
return mod->getMetadata();
},
[](server::ServerModMetadata const& metadata) {
// Versions should be guaranteed to have at least one item
return metadata.versions.front().metadata;
}
}, m_value);
}
CCNode* ModSource::createModLogo() const {
return std::visit(makeVisitor {
[](Mod* mod) {
return geode::createModLogo(mod);
},
[](server::ServerModMetadata const& metadata) {
return createServerModLogo(metadata.id);
}
}, m_value);
}
bool ModSource::wantsRestart() const {
return std::visit(makeVisitor {
[](Mod* mod) {
return mod->getRequestedAction() != ModRequestedAction::None;
},
[](server::ServerModMetadata const& metdata) {
// todo: check if the mod has been installed
return false;
}
}, m_value);
}
Mod* ModSource::asMod() const {
auto mod = std::get_if<Mod*>(&m_value);
return mod ? *mod : nullptr;
}
server::ServerModMetadata const* ModSource::asServer() const {
return std::get_if<server::ServerModMetadata>(&m_value);
}

View file

@ -0,0 +1,27 @@
#pragma once
#include <Geode/loader/Mod.hpp>
#include <server/Server.hpp>
using namespace geode::prelude;
class ModSource final {
private:
std::variant<Mod*, server::ServerModMetadata> m_value;
public:
ModSource() = default;
ModSource(Mod* mod);
ModSource(server::ServerModMetadata&& metadata);
ModMetadata getMetadata() const;
CCNode* createModLogo() const;
bool wantsRestart() const;
auto visit(auto&& func) {
return std::visit(func, m_value);
}
Mod* asMod() const;
server::ServerModMetadata const* asServer() const;
};