mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-27 01:45:35 -05:00
make mod list state updating use events instead of poor man's delegates
This commit is contained in:
parent
e13091115b
commit
54e9763631
14 changed files with 100 additions and 64 deletions
|
@ -167,6 +167,10 @@ bool ModsLayer::init() {
|
||||||
|
|
||||||
this->updateState();
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +194,6 @@ void ModsLayer::gotoTab(ModListSourceType type) {
|
||||||
// Lazily create new list and add it to UI
|
// Lazily create new list and add it to UI
|
||||||
if (!m_lists.contains(src)) {
|
if (!m_lists.contains(src)) {
|
||||||
auto list = ModList::create(src, m_frame->getContentSize() - ccp(30, 0));
|
auto list = ModList::create(src, m_frame->getContentSize() - ccp(30, 0));
|
||||||
list->onUpdateParentState(std::bind(&ModsLayer::updateState, this));
|
|
||||||
list->setPosition(m_frame->getPosition());
|
list->setPosition(m_frame->getPosition());
|
||||||
this->addChild(list);
|
this->addChild(list);
|
||||||
m_lists.emplace(src, list);
|
m_lists.emplace(src, list);
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "list/ModItem.hpp"
|
#include "list/ModItem.hpp"
|
||||||
#include "list/ModList.hpp"
|
#include "list/ModList.hpp"
|
||||||
#include "sources/ModListSource.hpp"
|
#include "sources/ModListSource.hpp"
|
||||||
|
#include "UpdateModListState.hpp"
|
||||||
|
|
||||||
using namespace geode::prelude;
|
using namespace geode::prelude;
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ protected:
|
||||||
CCLabelBMFont* m_pageLabel;
|
CCLabelBMFont* m_pageLabel;
|
||||||
CCMenuItemSpriteExtra* m_goToPageBtn;
|
CCMenuItemSpriteExtra* m_goToPageBtn;
|
||||||
CCMenuItemSpriteExtra* m_restartBtn;
|
CCMenuItemSpriteExtra* m_restartBtn;
|
||||||
|
EventListener<UpdateModListStateFilter> m_updateStateListener;
|
||||||
bool m_showSearch = false;
|
bool m_showSearch = false;
|
||||||
bool m_bigView = false;
|
bool m_bigView = false;
|
||||||
|
|
||||||
|
|
20
loader/src/ui/mods/UpdateModListState.cpp
Normal file
20
loader/src/ui/mods/UpdateModListState.cpp
Normal 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) {}
|
39
loader/src/ui/mods/UpdateModListState.hpp
Normal file
39
loader/src/ui/mods/UpdateModListState.hpp
Normal 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);
|
||||||
|
};
|
|
@ -126,6 +126,10 @@ bool ModItem::init(ModSource&& source) {
|
||||||
|
|
||||||
this->updateState();
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,11 +165,6 @@ void ModItem::updateState() {
|
||||||
m_bg->setOpacity(40);
|
m_bg->setOpacity(40);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Propagate update up the chain
|
|
||||||
if (m_updateParentState) {
|
|
||||||
m_updateParentState();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update enable toggle state
|
// Update enable toggle state
|
||||||
if (m_enableToggle && m_source.asMod()) {
|
if (m_enableToggle && m_source.asMod()) {
|
||||||
m_enableToggle->toggle(m_source.asMod()->isOrWillBeEnabled());
|
m_enableToggle->toggle(m_source.asMod()->isOrWillBeEnabled());
|
||||||
|
@ -216,10 +215,6 @@ void ModItem::updateSize(float width, bool big) {
|
||||||
this->updateLayout();
|
this->updateLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModItem::onUpdateParentState(MiniFunction<void()> listener) {
|
|
||||||
m_updateParentState = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModItem* ModItem::create(ModSource&& source) {
|
ModItem* ModItem::create(ModSource&& source) {
|
||||||
auto ret = new ModItem();
|
auto ret = new ModItem();
|
||||||
if (ret && ret->init(std::move(source))) {
|
if (ret && ret->init(std::move(source))) {
|
||||||
|
@ -232,11 +227,7 @@ ModItem* ModItem::create(ModSource&& source) {
|
||||||
|
|
||||||
void ModItem::onView(CCObject*) {
|
void ModItem::onView(CCObject*) {
|
||||||
// Always open up the popup for the installed mod page if that is possible
|
// Always open up the popup for the installed mod page if that is possible
|
||||||
auto popup = ModPopup::create(m_source.tryConvertToMod());
|
ModPopup::create(m_source.tryConvertToMod())->show();
|
||||||
popup->onUpdateParentState([this]() {
|
|
||||||
this->updateState();
|
|
||||||
});
|
|
||||||
popup->show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModItem::onEnable(CCObject*) {
|
void ModItem::onEnable(CCObject*) {
|
||||||
|
@ -252,6 +243,6 @@ void ModItem::onEnable(CCObject*) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update whole state of the mod item
|
// Update state of the mod item
|
||||||
this->updateState();
|
UpdateModListStateEvent(UpdateModState(m_source.getID())).post();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <Geode/ui/General.hpp>
|
#include <Geode/ui/General.hpp>
|
||||||
#include <server/Server.hpp>
|
#include <server/Server.hpp>
|
||||||
#include "../sources/ModSource.hpp"
|
#include "../sources/ModSource.hpp"
|
||||||
|
#include "../UpdateModListState.hpp"
|
||||||
|
|
||||||
using namespace geode::prelude;
|
using namespace geode::prelude;
|
||||||
|
|
||||||
|
@ -19,9 +20,9 @@ protected:
|
||||||
CCLabelBMFont* m_developerLabel;
|
CCLabelBMFont* m_developerLabel;
|
||||||
ButtonSprite* m_restartRequiredLabel = nullptr;
|
ButtonSprite* m_restartRequiredLabel = nullptr;
|
||||||
CCMenu* m_viewMenu;
|
CCMenu* m_viewMenu;
|
||||||
MiniFunction<void()> m_updateParentState = nullptr;
|
|
||||||
CCMenuItemToggler* m_enableToggle = nullptr;
|
CCMenuItemToggler* m_enableToggle = nullptr;
|
||||||
CCScale9Sprite* m_checkmark = nullptr;
|
CCScale9Sprite* m_checkmark = nullptr;
|
||||||
|
EventListener<UpdateModListStateFilter> m_updateStateListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @warning Make sure `getMetadata` and `createModLogo` are callable
|
* @warning Make sure `getMetadata` and `createModLogo` are callable
|
||||||
|
@ -29,8 +30,6 @@ protected:
|
||||||
*/
|
*/
|
||||||
bool init(ModSource&& source);
|
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 updateState();
|
||||||
|
|
||||||
void onEnable(CCObject*);
|
void onEnable(CCObject*);
|
||||||
|
@ -40,6 +39,4 @@ public:
|
||||||
static ModItem* create(ModSource&& source);
|
static ModItem* create(ModSource&& source);
|
||||||
|
|
||||||
void updateSize(float width, bool big);
|
void updateSize(float width, bool big);
|
||||||
|
|
||||||
void onUpdateParentState(MiniFunction<void()> listener);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -194,7 +194,6 @@ void ModList::onPromise(typename ModListSource::PageLoadEvent* event) {
|
||||||
}
|
}
|
||||||
first = false;
|
first = false;
|
||||||
m_list->m_contentLayer->addChild(item);
|
m_list->m_contentLayer->addChild(item);
|
||||||
item->onUpdateParentState(m_updateParentState);
|
|
||||||
}
|
}
|
||||||
this->updateSize(m_bigSize);
|
this->updateSize(m_bigSize);
|
||||||
|
|
||||||
|
@ -319,10 +318,8 @@ void ModList::updatePageNumber() {
|
||||||
m_pagePrevBtn->setVisible(pageCount && m_page > 0);
|
m_pagePrevBtn->setVisible(pageCount && m_page > 0);
|
||||||
m_pageNextBtn->setVisible(pageCount && m_page < pageCount.value() - 1);
|
m_pageNextBtn->setVisible(pageCount && m_page < pageCount.value() - 1);
|
||||||
|
|
||||||
// Notify container about page count update
|
// Post the update page number event
|
||||||
if (m_updateParentState) {
|
UpdateModListStateEvent(UpdatePageNumberState()).post();
|
||||||
m_updateParentState();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModList::reloadPage() {
|
void ModList::reloadPage() {
|
||||||
|
@ -373,10 +370,6 @@ void ModList::showStatus(ModListStatus status, std::string const& message, std::
|
||||||
m_statusContainer->updateLayout();
|
m_statusContainer->updateLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModList::onUpdateParentState(MiniFunction<void()> listener) {
|
|
||||||
m_updateParentState = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModList::onFilters(CCObject*) {
|
void ModList::onFilters(CCObject*) {
|
||||||
TagsPopup::create(m_source, [this]() {
|
TagsPopup::create(m_source, [this]() {
|
||||||
this->gotoPage(0);
|
this->gotoPage(0);
|
||||||
|
|
|
@ -32,7 +32,7 @@ protected:
|
||||||
CCMenuItemSpriteExtra* m_pageNextBtn;
|
CCMenuItemSpriteExtra* m_pageNextBtn;
|
||||||
Ref<CCNode> m_searchMenu;
|
Ref<CCNode> m_searchMenu;
|
||||||
TextInput* m_searchInput;
|
TextInput* m_searchInput;
|
||||||
MiniFunction<void()> m_updateParentState = nullptr;
|
EventListener<UpdateModListStateFilter> m_updateStateListener;
|
||||||
bool m_bigSize = false;
|
bool m_bigSize = false;
|
||||||
std::atomic<size_t> m_searchInputThreads = 0;
|
std::atomic<size_t> m_searchInputThreads = 0;
|
||||||
|
|
||||||
|
@ -47,8 +47,6 @@ protected:
|
||||||
public:
|
public:
|
||||||
static ModList* create(ModListSource* src, CCSize const& size);
|
static ModList* create(ModListSource* src, CCSize const& size);
|
||||||
|
|
||||||
// poor man's delegate
|
|
||||||
void onUpdateParentState(MiniFunction<void()> listener);
|
|
||||||
size_t getPage() const;
|
size_t getPage() const;
|
||||||
|
|
||||||
void reloadPage();
|
void reloadPage();
|
||||||
|
|
|
@ -55,9 +55,7 @@ void ConfirmUninstallPopup::onUninstall(CCObject*) {
|
||||||
)->show();
|
)->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_updateParentState) {
|
UpdateModListStateEvent(UpdateModState(m_mod->getID())).post();
|
||||||
m_updateParentState();
|
|
||||||
}
|
|
||||||
|
|
||||||
this->onClose(nullptr);
|
this->onClose(nullptr);
|
||||||
}
|
}
|
||||||
|
@ -71,7 +69,3 @@ ConfirmUninstallPopup* ConfirmUninstallPopup::create(Mod* mod) {
|
||||||
CC_SAFE_DELETE(ret);
|
CC_SAFE_DELETE(ret);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfirmUninstallPopup::onUpdateParentState(MiniFunction<void()> listener) {
|
|
||||||
m_updateParentState = listener;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Geode/ui/Popup.hpp>
|
#include <Geode/ui/Popup.hpp>
|
||||||
|
#include "../UpdateModListState.hpp"
|
||||||
|
|
||||||
using namespace geode::prelude;
|
using namespace geode::prelude;
|
||||||
|
|
||||||
|
@ -8,7 +9,7 @@ class ConfirmUninstallPopup : public Popup<Mod*> {
|
||||||
protected:
|
protected:
|
||||||
Mod* m_mod;
|
Mod* m_mod;
|
||||||
CCMenuItemToggler* m_deleteDataToggle;
|
CCMenuItemToggler* m_deleteDataToggle;
|
||||||
MiniFunction<void()> m_updateParentState = nullptr;
|
EventListener<UpdateModListStateFilter> m_updateStateListener;
|
||||||
|
|
||||||
bool setup(Mod* mod) override;
|
bool setup(Mod* mod) override;
|
||||||
|
|
||||||
|
@ -16,7 +17,4 @@ protected:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static ConfirmUninstallPopup* create(Mod* mod);
|
static ConfirmUninstallPopup* create(Mod* mod);
|
||||||
|
|
||||||
// todo: replace all of these with a single event
|
|
||||||
void onUpdateParentState(MiniFunction<void()> listener);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -434,6 +434,10 @@ bool ModPopup::setup(ModSource&& src) {
|
||||||
m_tagsListener.bind(this, &ModPopup::onLoadTags);
|
m_tagsListener.bind(this, &ModPopup::onLoadTags);
|
||||||
m_tagsListener.setFilter(m_source.fetchValidTags().listen());
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,11 +467,6 @@ void ModPopup::updateState() {
|
||||||
}
|
}
|
||||||
|
|
||||||
m_installMenu->updateLayout();
|
m_installMenu->updateLayout();
|
||||||
|
|
||||||
// Propagate update up the chain
|
|
||||||
if (m_updateParentState) {
|
|
||||||
m_updateParentState();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModPopup::setStatIcon(CCNode* stat, const char* spr) {
|
void ModPopup::setStatIcon(CCNode* stat, const char* spr) {
|
||||||
|
@ -653,17 +652,12 @@ void ModPopup::onEnable(CCObject*) {
|
||||||
else {
|
else {
|
||||||
FLAlertLayer::create("Error Toggling Mod", "This mod can not be toggled!", "OK")->show();
|
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*) {
|
void ModPopup::onUninstall(CCObject*) {
|
||||||
if (auto mod = m_source.asMod()) {
|
if (auto mod = m_source.asMod()) {
|
||||||
auto popup = ConfirmUninstallPopup::create(mod);
|
ConfirmUninstallPopup::create(mod)->show();
|
||||||
popup->onUpdateParentState([this] {
|
|
||||||
this->updateState();
|
|
||||||
this->onClose(nullptr);
|
|
||||||
});
|
|
||||||
popup->show();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
FLAlertLayer::create(
|
FLAlertLayer::create(
|
||||||
|
@ -693,7 +687,3 @@ ModPopup* ModPopup::create(ModSource&& src) {
|
||||||
CC_SAFE_DELETE(ret);
|
CC_SAFE_DELETE(ret);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModPopup::onUpdateParentState(MiniFunction<void()> listener) {
|
|
||||||
m_updateParentState = listener;
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <Geode/ui/MDTextArea.hpp>
|
#include <Geode/ui/MDTextArea.hpp>
|
||||||
#include "../sources/ModSource.hpp"
|
#include "../sources/ModSource.hpp"
|
||||||
#include "../GeodeStyle.hpp"
|
#include "../GeodeStyle.hpp"
|
||||||
|
#include "../UpdateModListState.hpp"
|
||||||
|
|
||||||
using namespace geode::prelude;
|
using namespace geode::prelude;
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ protected:
|
||||||
std::unordered_map<Tab, std::pair<GeodeTabSprite*, Ref<CCNode>>> m_tabs;
|
std::unordered_map<Tab, std::pair<GeodeTabSprite*, Ref<CCNode>>> m_tabs;
|
||||||
EventListener<PromiseEventFilter<server::ServerModMetadata, server::ServerError>> m_statsListener;
|
EventListener<PromiseEventFilter<server::ServerModMetadata, server::ServerError>> m_statsListener;
|
||||||
EventListener<PromiseEventFilter<std::unordered_set<std::string>, server::ServerError>> m_tagsListener;
|
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;
|
bool setup(ModSource&& src) override;
|
||||||
void updateState();
|
void updateState();
|
||||||
|
@ -52,6 +53,4 @@ protected:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static ModPopup* create(ModSource&& src);
|
static ModPopup* create(ModSource&& src);
|
||||||
|
|
||||||
void onUpdateParentState(MiniFunction<void()> listener);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,6 +4,17 @@
|
||||||
ModSource::ModSource(Mod* mod) : m_value(mod) {}
|
ModSource::ModSource(Mod* mod) : m_value(mod) {}
|
||||||
ModSource::ModSource(server::ServerModMetadata&& metadata) : m_value(metadata) {}
|
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 {
|
ModMetadata ModSource::getMetadata() const {
|
||||||
return std::visit(makeVisitor {
|
return std::visit(makeVisitor {
|
||||||
[](Mod* mod) {
|
[](Mod* mod) {
|
||||||
|
|
|
@ -14,6 +14,7 @@ public:
|
||||||
ModSource(Mod* mod);
|
ModSource(Mod* mod);
|
||||||
ModSource(server::ServerModMetadata&& metadata);
|
ModSource(server::ServerModMetadata&& metadata);
|
||||||
|
|
||||||
|
std::string getID() const;
|
||||||
ModMetadata getMetadata() const;
|
ModMetadata getMetadata() const;
|
||||||
std::optional<std::string> getAbout() const;
|
std::optional<std::string> getAbout() const;
|
||||||
std::optional<std::string> getChangelog() const;
|
std::optional<std::string> getChangelog() const;
|
||||||
|
|
Loading…
Reference in a new issue