mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-26 17:36:05 -05:00
many changes for updating Index
- events now follow a public const member pattern instead of getters - more file utils - ModInfoLayer split into LocalModInfoPopup and IndexItemInfoPopup to reduce uses of ugly variant - same with ModCell - lots of restructuring related to UI anyway - Index also got tons of redesigning - much more i've forgotten - this commit compiles, surprisingly enough, but it's not usable as installing mods through index was removed - remove duplicate LevelSearchLayer ids
This commit is contained in:
parent
08934132d8
commit
956ad1d6d5
25 changed files with 1854 additions and 1901 deletions
|
@ -93,19 +93,19 @@ namespace geode {
|
||||||
};
|
};
|
||||||
|
|
||||||
class GEODE_DLL Event {
|
class GEODE_DLL Event {
|
||||||
|
private:
|
||||||
static std::unordered_set<EventListenerProtocol*> s_listeners;
|
static std::unordered_set<EventListenerProtocol*> s_listeners;
|
||||||
Mod* m_sender;
|
|
||||||
friend EventListenerProtocol;
|
friend EventListenerProtocol;
|
||||||
public:
|
|
||||||
void postFrom(Mod* sender);
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
Mod* sender;
|
||||||
|
|
||||||
|
void postFrom(Mod* sender);
|
||||||
template<class = void>
|
template<class = void>
|
||||||
void post() {
|
void post() {
|
||||||
postFrom(Mod::get());
|
postFrom(Mod::get());
|
||||||
}
|
}
|
||||||
|
|
||||||
Mod* getSender();
|
|
||||||
|
|
||||||
virtual ~Event();
|
virtual ~Event();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "Types.hpp"
|
#include "Types.hpp"
|
||||||
#include "ModInfo.hpp"
|
#include "ModInfo.hpp"
|
||||||
#include "Event.hpp"
|
#include "Event.hpp"
|
||||||
|
#include "../utils/Result.hpp"
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace geode {
|
namespace geode {
|
||||||
|
@ -11,43 +12,12 @@ namespace geode {
|
||||||
using UpdateError = std::string;
|
using UpdateError = std::string;
|
||||||
using UpdateStatus = std::variant<UpdateFinished, UpdateProgress, UpdateError>;
|
using UpdateStatus = std::variant<UpdateFinished, UpdateProgress, UpdateError>;
|
||||||
|
|
||||||
class GEODE_DLL IndexUpdateEvent : public Event {
|
struct GEODE_DLL ModInstallEvent : public Event {
|
||||||
protected:
|
const std::string modID;
|
||||||
std::string m_sourceRepository;
|
const UpdateStatus status;
|
||||||
UpdateStatus m_status;
|
|
||||||
|
|
||||||
public:
|
|
||||||
IndexUpdateEvent(
|
|
||||||
std::string const& src,
|
|
||||||
UpdateStatus status
|
|
||||||
);
|
|
||||||
std::string getSource() const;
|
|
||||||
UpdateStatus getStatus() const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class GEODE_DLL IndexUpdateFilter : public EventFilter<IndexUpdateEvent> {
|
class GEODE_DLL ModInstallFilter : public EventFilter<ModInstallEvent> {
|
||||||
public:
|
|
||||||
using Callback = void(IndexUpdateEvent*);
|
|
||||||
|
|
||||||
ListenerResult handle(std::function<Callback> fn, IndexUpdateEvent* event);
|
|
||||||
IndexUpdateFilter();
|
|
||||||
};
|
|
||||||
|
|
||||||
class GEODE_DLL ModInstallEvent : public Event {
|
|
||||||
protected:
|
|
||||||
std::string m_id;
|
|
||||||
UpdateStatus m_status;
|
|
||||||
|
|
||||||
public:
|
|
||||||
ModInstallEvent(
|
|
||||||
std::string const& id,
|
|
||||||
UpdateStatus status
|
|
||||||
);
|
|
||||||
std::string getModID() const;
|
|
||||||
UpdateStatus getStatus() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
class GEODE_DLL ModInstallFilter : public EventFilter<IndexUpdateEvent> {
|
|
||||||
protected:
|
protected:
|
||||||
std::string m_id;
|
std::string m_id;
|
||||||
|
|
||||||
|
@ -58,6 +28,47 @@ namespace geode {
|
||||||
ModInstallFilter(std::string const& id);
|
ModInstallFilter(std::string const& id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct GEODE_DLL IndexUpdateEvent : public Event {
|
||||||
|
const UpdateStatus status;
|
||||||
|
IndexUpdateEvent(const UpdateStatus status);
|
||||||
|
};
|
||||||
|
|
||||||
|
class GEODE_DLL IndexUpdateFilter : public EventFilter<IndexUpdateEvent> {
|
||||||
|
public:
|
||||||
|
using Callback = void(IndexUpdateEvent*);
|
||||||
|
|
||||||
|
ListenerResult handle(std::function<Callback> fn, IndexUpdateEvent* event);
|
||||||
|
IndexUpdateFilter();
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace impl {
|
||||||
|
// The reason sources have private implementation events that are
|
||||||
|
// turned into the global IndexUpdateEvent is because it makes it much
|
||||||
|
// simpler to keep track of progress, what errors were received, etc.
|
||||||
|
// without having to store a ton of members
|
||||||
|
|
||||||
|
struct GEODE_DLL IndexSource final {
|
||||||
|
std::string repository;
|
||||||
|
bool isUpToDate = false;
|
||||||
|
|
||||||
|
std::string dirname() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GEODE_DLL SourceUpdateEvent : public Event {
|
||||||
|
const IndexSource& source;
|
||||||
|
const UpdateStatus status;
|
||||||
|
SourceUpdateEvent(IndexSource const& src, const UpdateStatus status);
|
||||||
|
};
|
||||||
|
|
||||||
|
class GEODE_DLL SourceUpdateFilter : public EventFilter<SourceUpdateEvent> {
|
||||||
|
public:
|
||||||
|
using Callback = void(SourceUpdateEvent*);
|
||||||
|
|
||||||
|
ListenerResult handle(std::function<Callback> fn, SourceUpdateEvent* event);
|
||||||
|
SourceUpdateFilter();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
struct GEODE_DLL IndexItem {
|
struct GEODE_DLL IndexItem {
|
||||||
std::string sourceRepository;
|
std::string sourceRepository;
|
||||||
ghc::filesystem::path path;
|
ghc::filesystem::path path;
|
||||||
|
@ -68,29 +79,34 @@ namespace geode {
|
||||||
std::unordered_set<PlatformID> platforms;
|
std::unordered_set<PlatformID> platforms;
|
||||||
} download;
|
} download;
|
||||||
bool isFeatured;
|
bool isFeatured;
|
||||||
};
|
|
||||||
|
|
||||||
struct GEODE_DLL IndexSource final {
|
/**
|
||||||
std::string repository;
|
* Create IndexItem from a directory
|
||||||
bool isUpToDate = false;
|
*/
|
||||||
|
static Result<std::shared_ptr<IndexItem>> createFromDir(
|
||||||
std::string dirname() const;
|
std::string const& sourceRepository,
|
||||||
|
ghc::filesystem::path const& dir
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
using IndexItemHandle = std::shared_ptr<IndexItem>;
|
||||||
|
|
||||||
class GEODE_DLL Index final {
|
class GEODE_DLL Index final {
|
||||||
protected:
|
protected:
|
||||||
// for once, the fact that std::map is ordered is useful (this makes
|
// for once, the fact that std::map is ordered is useful (this makes
|
||||||
// getting the latest version of a mod as easy as items.rbegin())
|
// getting the latest version of a mod as easy as items.rbegin())
|
||||||
using ItemVersions = std::map<size_t, IndexItem>;
|
using ItemVersions = std::map<size_t, IndexItemHandle>;
|
||||||
|
|
||||||
std::vector<IndexSource> m_sources;
|
std::vector<impl::IndexSource> m_sources;
|
||||||
|
std::unordered_map<std::string, UpdateStatus> m_sourceStatuses;
|
||||||
|
std::atomic<bool> m_triedToUpdate = false;
|
||||||
std::unordered_map<std::string, ItemVersions> m_items;
|
std::unordered_map<std::string, ItemVersions> m_items;
|
||||||
|
|
||||||
Index();
|
Index();
|
||||||
|
|
||||||
void checkSourceUpdates(IndexSource& src);
|
void onSourceUpdate(impl::SourceUpdateEvent* event);
|
||||||
void downloadSource(IndexSource& src);
|
void checkSourceUpdates(impl::IndexSource& src);
|
||||||
void updateSourceFromLocal(IndexSource& src);
|
void downloadSource(impl::IndexSource& src);
|
||||||
|
void updateSourceFromLocal(impl::IndexSource& src);
|
||||||
void cleanupItems();
|
void cleanupItems();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -98,12 +114,16 @@ namespace geode {
|
||||||
|
|
||||||
void addSource(std::string const& repository);
|
void addSource(std::string const& repository);
|
||||||
void removeSource(std::string const& repository);
|
void removeSource(std::string const& repository);
|
||||||
std::vector<IndexSource> getSources() const;
|
std::vector<impl::IndexSource> getSources() const;
|
||||||
|
|
||||||
std::vector<IndexItem> getItems() const;
|
std::vector<IndexItemHandle> getItems() const;
|
||||||
bool isKnownItem(std::string const& id, std::optional<size_t> version) const;
|
bool isKnownItem(std::string const& id, std::optional<size_t> version) const;
|
||||||
std::optional<IndexItem> getItem(std::string const& id, std::optional<size_t> version) const;
|
IndexItemHandle getItem(std::string const& id, std::optional<size_t> version) const;
|
||||||
|
IndexItemHandle getItem(ModInfo const& info) const;
|
||||||
|
IndexItemHandle getItem(Mod* mod) const;
|
||||||
|
bool updateAvailable(IndexItemHandle item) const;
|
||||||
|
|
||||||
|
bool hasTriedToUpdate() const;
|
||||||
bool isUpToDate() const;
|
bool isUpToDate() const;
|
||||||
void update(bool force = false);
|
void update(bool force = false);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../loader/Mod.hpp"
|
#include "../loader/Mod.hpp"
|
||||||
|
#include "../loader/Index.hpp"
|
||||||
|
|
||||||
namespace geode {
|
namespace geode {
|
||||||
/**
|
/**
|
||||||
|
@ -11,6 +12,10 @@ namespace geode {
|
||||||
* Open the info popup for a mod
|
* Open the info popup for a mod
|
||||||
*/
|
*/
|
||||||
GEODE_DLL void openInfoPopup(Mod* mod);
|
GEODE_DLL void openInfoPopup(Mod* mod);
|
||||||
|
/**
|
||||||
|
* Open the issue report popup for a mod
|
||||||
|
*/
|
||||||
|
GEODE_DLL void openIssueReportPopup(Mod* mod);
|
||||||
/**
|
/**
|
||||||
* Open the store page for a mod (if it exists)
|
* Open the store page for a mod (if it exists)
|
||||||
*/
|
*/
|
||||||
|
@ -19,4 +24,25 @@ namespace geode {
|
||||||
* Open the settings popup for a mod (if it has any settings)
|
* Open the settings popup for a mod (if it has any settings)
|
||||||
*/
|
*/
|
||||||
GEODE_DLL void openSettingsPopup(Mod* mod);
|
GEODE_DLL void openSettingsPopup(Mod* mod);
|
||||||
|
/**
|
||||||
|
* Create a default logo sprite
|
||||||
|
* @param size Size of the sprite
|
||||||
|
*/
|
||||||
|
GEODE_DLL cocos2d::CCNode* createDefaultLogo(
|
||||||
|
cocos2d::CCSize const& size
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* Create a logo sprite for a mod
|
||||||
|
* @param size Size of the sprite
|
||||||
|
*/
|
||||||
|
GEODE_DLL cocos2d::CCNode* createModLogo(
|
||||||
|
Mod* mod, cocos2d::CCSize const& size
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* Create a logo sprite for an index item
|
||||||
|
* @param size Size of the sprite
|
||||||
|
*/
|
||||||
|
GEODE_DLL cocos2d::CCNode* createIndexItemLogo(
|
||||||
|
IndexItemHandle item, cocos2d::CCSize const& size
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "Result.hpp"
|
#include "Result.hpp"
|
||||||
#include "general.hpp"
|
#include "general.hpp"
|
||||||
|
|
||||||
|
#include "../external/json/json.hpp"
|
||||||
#include <Geode/DefaultInclude.hpp>
|
#include <Geode/DefaultInclude.hpp>
|
||||||
#include <fs/filesystem.hpp>
|
#include <fs/filesystem.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -10,6 +11,7 @@
|
||||||
|
|
||||||
namespace geode::utils::file {
|
namespace geode::utils::file {
|
||||||
GEODE_DLL Result<std::string> readString(ghc::filesystem::path const& path);
|
GEODE_DLL Result<std::string> readString(ghc::filesystem::path const& path);
|
||||||
|
GEODE_DLL Result<nlohmann::json> readJson(ghc::filesystem::path const& path);
|
||||||
GEODE_DLL Result<byte_array> readBinary(ghc::filesystem::path const& path);
|
GEODE_DLL Result<byte_array> readBinary(ghc::filesystem::path const& path);
|
||||||
|
|
||||||
GEODE_DLL Result<> writeString(ghc::filesystem::path const& path, std::string const& data);
|
GEODE_DLL Result<> writeString(ghc::filesystem::path const& path, std::string const& data);
|
||||||
|
|
|
@ -41,6 +41,16 @@ namespace geode {
|
||||||
using TypeIdentityType = typename TypeIdentity<T>::type;
|
using TypeIdentityType = typename TypeIdentity<T>::type;
|
||||||
|
|
||||||
namespace utils {
|
namespace utils {
|
||||||
|
// helper for std::visit
|
||||||
|
template<class... Ts> struct makeVisitor : Ts... { using Ts::operator()...; };
|
||||||
|
template<class... Ts> makeVisitor(Ts...) -> makeVisitor<Ts...>;
|
||||||
|
|
||||||
|
template<class T, class ... Args>
|
||||||
|
constexpr T getOr(std::variant<Args...> const& variant, T const& defValue) {
|
||||||
|
return std::holds_alternative<T>(variant) ?
|
||||||
|
std::get<T>(variant) : defValue;
|
||||||
|
}
|
||||||
|
|
||||||
constexpr unsigned int hash(char const* str, int h = 0) {
|
constexpr unsigned int hash(char const* str, int h = 0) {
|
||||||
return !str[h] ? 5381 : (hash(str, h + 1) * 33) ^ str[h];
|
return !str[h] ? 5381 : (hash(str, h + 1) * 33) ^ str[h];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
#include "../ui/internal/info/ModInfoLayer.hpp"
|
|
||||||
#include "../ui/internal/list/ModListLayer.hpp"
|
|
||||||
|
|
||||||
#include <Geode/utils/cocos.hpp>
|
|
||||||
#include <Index.hpp>
|
|
||||||
#include <InternalLoader.hpp>
|
|
||||||
#include <InternalMod.hpp>
|
|
||||||
|
|
||||||
USE_GEODE_NAMESPACE();
|
|
||||||
|
|
||||||
#pragma warning(disable : 4217)
|
|
||||||
|
|
||||||
template <class T = CCNode>
|
|
||||||
requires std::is_base_of_v<CCNode, T>
|
|
||||||
T* setIDSafe(CCNode* node, int index, char const* id) {
|
|
||||||
if constexpr (std::is_same_v<CCNode, T>) {
|
|
||||||
if (auto child = getChild(node, index)) {
|
|
||||||
child->setID(id);
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (auto child = getChildOfType<T>(node, index)) {
|
|
||||||
child->setID(id);
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
#include <Geode/modify/LevelSearchLayer.hpp>
|
|
||||||
struct LevelSearchLayerIDs : Modify<LevelSearchLayerIDs, LevelSearchLayer> {
|
|
||||||
bool init() {
|
|
||||||
if (!LevelSearchLayer::init())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// set the funny ids
|
|
||||||
this->setID("creator-layer");
|
|
||||||
setIDSafe(this, 0, "creator-layer-bg");
|
|
||||||
getChildOfType<CCTextInputNode>(this, 0)->setID("search-bar");
|
|
||||||
getChildOfType<CCScale9Sprite>(this, 0)->setID("level-search-bg");
|
|
||||||
getChildOfType<CCScale9Sprite>(this, 1)->setID("level-search-bar-bg");
|
|
||||||
getChildOfType<CCScale9Sprite>(this, 2)->setID("quick-search-bg");
|
|
||||||
getChildOfType<CCScale9Sprite>(this, 3)->setID("difficulty-filters-bg");
|
|
||||||
getChildOfType<CCScale9Sprite>(this, 4)->setID("length-filters-bg");
|
|
||||||
getChildOfType<CCLabelBMFont>(this, 0)->setID("quick-search-title");
|
|
||||||
getChildOfType<CCLabelBMFont>(this, 1)->setID("filters-title");
|
|
||||||
getChildOfType<CCSprite>(this, 1)->setID("left-corner");
|
|
||||||
getChildOfType<CCSprite>(this, 2)->setID("right-corner");
|
|
||||||
|
|
||||||
if (auto filtermenu = getChildOfType<CCMenu>(this, 0)) {
|
|
||||||
filtermenu->setID("other-filter-menu");
|
|
||||||
setIDSafe(filtermenu, 0, "clear-filters-button");
|
|
||||||
setIDSafe(filtermenu, 1, "advanced-filters-button");
|
|
||||||
}
|
|
||||||
if (auto searchmenu = getChildOfType<CCMenu>(this, 1)) {
|
|
||||||
searchmenu->setID("search-button-menu");
|
|
||||||
setIDSafe(searchmenu, 0, "search-level-button");
|
|
||||||
setIDSafe(searchmenu, 1, "search-user-button");
|
|
||||||
|
|
||||||
}
|
|
||||||
if (auto quickmenu = getChildOfType<CCMenu>(this, 2)) {
|
|
||||||
quickmenu->setID("quick-search-menu");
|
|
||||||
setIDSafe(quickmenu, 0, "most-downloaded-button");
|
|
||||||
setIDSafe(quickmenu, 1, "most-liked-button");
|
|
||||||
setIDSafe(quickmenu, 2, "trending-button");
|
|
||||||
setIDSafe(quickmenu, 3, "recent-button");
|
|
||||||
setIDSafe(quickmenu, 4, "magic-button");
|
|
||||||
setIDSafe(quickmenu, 5, "awarded-button");
|
|
||||||
setIDSafe(quickmenu, 6, "followed-button");
|
|
||||||
setIDSafe(quickmenu, 7, "friends-button");
|
|
||||||
}
|
|
||||||
if (auto filtersmenu = getChildOfType<CCMenu>(this, 3)) {
|
|
||||||
filtersmenu->setID("difficulty-filter-menu");
|
|
||||||
setIDSafe(filtersmenu, 0, "na-filter-button");
|
|
||||||
setIDSafe(filtersmenu, 1, "easy-filter-button");
|
|
||||||
setIDSafe(filtersmenu, 2, "normal-filter-button");
|
|
||||||
setIDSafe(filtersmenu, 3, "hard-filter-button");
|
|
||||||
setIDSafe(filtersmenu, 4, "harder-filter-button");
|
|
||||||
setIDSafe(filtersmenu, 5, "insane-filter-button");
|
|
||||||
setIDSafe(filtersmenu, 6, "demon-filter-button");
|
|
||||||
setIDSafe(filtersmenu, 7, "auto-filter-button");
|
|
||||||
setIDSafe(filtersmenu, 8, "demon-type-filter-button");
|
|
||||||
}
|
|
||||||
if (auto filtersmenu = getChildOfType<CCMenu>(this, 4)) {
|
|
||||||
filtersmenu->setID("length-filter-menu");
|
|
||||||
setIDSafe(filtersmenu, 0, "clock-icon");
|
|
||||||
setIDSafe(filtersmenu, 1, "tiny-filter-button");
|
|
||||||
setIDSafe(filtersmenu, 2, "short-filter-button");
|
|
||||||
setIDSafe(filtersmenu, 3, "medium-filter-button");
|
|
||||||
setIDSafe(filtersmenu, 4, "long-filter-button");
|
|
||||||
setIDSafe(filtersmenu, 5, "xl-filter-button");
|
|
||||||
setIDSafe(filtersmenu, 6, "star-filter-button");
|
|
||||||
}
|
|
||||||
if (auto backmenu = getChildOfType<CCMenu>(this, 5)) {
|
|
||||||
backmenu->setID("exit-menu");
|
|
||||||
setIDSafe(backmenu, 0, "exit-button");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// clang-format on
|
|
|
@ -44,39 +44,33 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
|
||||||
|
|
||||||
void setUpdateText(std::string const& text) {
|
void setUpdateText(std::string const& text) {
|
||||||
m_textArea->setString(text.c_str());
|
m_textArea->setString(text.c_str());
|
||||||
// m_fields->m_updatingResources->setString(text.c_str());
|
|
||||||
// m_fields->m_updatingResourcesBG->setContentSize({
|
|
||||||
// m_fields->m_updatingResources->getScaledContentSize().width + 30.f,
|
|
||||||
// 50.f
|
|
||||||
// });
|
|
||||||
// m_fields->m_updatingResources->setPosition(
|
|
||||||
// m_fields->m_updatingResourcesBG->getContentSize() / 2
|
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateResourcesProgress(ResourceDownloadEvent* event) {
|
void updateResourcesProgress(ResourceDownloadEvent* event) {
|
||||||
auto status = event->getStatus();
|
std::visit(makeVisitor {
|
||||||
if (std::holds_alternative<UpdateProgress>(status)) {
|
[&](UpdateProgress const& progress) {
|
||||||
auto prog = std::get<UpdateProgress>(status);
|
this->setUpdateText(fmt::format(
|
||||||
this->setUpdateText("Downloading Resources: " + std::to_string(prog.first) + "%");
|
"Downloading Resources: {}%", progress.first
|
||||||
}
|
));
|
||||||
else if (std::holds_alternative<UpdateFinished>(status)) {
|
},
|
||||||
this->setUpdateText("Resources Downloaded");
|
[&](UpdateFinished) {
|
||||||
m_fields->m_updatingResources = false;
|
this->setUpdateText("Resources Downloaded");
|
||||||
this->loadAssets();
|
m_fields->m_updatingResources = false;
|
||||||
}
|
this->loadAssets();
|
||||||
else {
|
},
|
||||||
InternalLoader::platformMessageBox(
|
[&](UpdateError const& error) {
|
||||||
"Error updating resources",
|
InternalLoader::platformMessageBox(
|
||||||
"Unable to update Geode resources: " +
|
"Error updating resources",
|
||||||
std::get<UpdateError>(status) + ".\n"
|
"Unable to update Geode resources: " +
|
||||||
"The game will be loaded as normal, but please be aware "
|
error + ".\n"
|
||||||
"that it may very likely crash."
|
"The game will be loaded as normal, but please be aware "
|
||||||
);
|
"that it may very likely crash."
|
||||||
this->setUpdateText("Resource Download Failed");
|
);
|
||||||
m_fields->m_updatingResources = false;
|
this->setUpdateText("Resource Download Failed");
|
||||||
this->loadAssets();
|
m_fields->m_updatingResources = false;
|
||||||
}
|
this->loadAssets();
|
||||||
|
}
|
||||||
|
}, event->status);
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadAssets() {
|
void loadAssets() {
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
|
|
||||||
#include <Geode/utils/cocos.hpp>
|
#include <Geode/utils/cocos.hpp>
|
||||||
#include "../ui/internal/info/ModInfoLayer.hpp"
|
|
||||||
#include "../ui/internal/list/ModListLayer.hpp"
|
#include "../ui/internal/list/ModListLayer.hpp"
|
||||||
#include <Geode/ui/BasedButtonSprite.hpp>
|
#include <Geode/ui/BasedButtonSprite.hpp>
|
||||||
#include <Geode/ui/MDPopup.hpp>
|
|
||||||
#include <Geode/ui/Notification.hpp>
|
#include <Geode/ui/Notification.hpp>
|
||||||
|
#include <Geode/ui/GeodeUI.hpp>
|
||||||
|
#include <Geode/ui/Popup.hpp>
|
||||||
#include <Geode/utils/cocos.hpp>
|
#include <Geode/utils/cocos.hpp>
|
||||||
#include <Index.hpp>
|
#include <Geode/loader/Index.hpp>
|
||||||
#include <InternalLoader.hpp>
|
#include <InternalLoader.hpp>
|
||||||
#include "../ids/AddIDs.hpp"
|
#include "../ids/AddIDs.hpp"
|
||||||
#include <InternalMod.hpp>
|
#include <InternalMod.hpp>
|
||||||
#include <Geode/modify/Modify.hpp>
|
#include <Geode/modify/Modify.hpp>
|
||||||
|
#include <Geode/modify/MenuLayer.hpp>
|
||||||
|
|
||||||
USE_GEODE_NAMESPACE();
|
USE_GEODE_NAMESPACE();
|
||||||
|
|
||||||
|
@ -21,39 +22,6 @@ class CustomMenuLayer;
|
||||||
static Ref<Notification> g_indexUpdateNotif = nullptr;
|
static Ref<Notification> g_indexUpdateNotif = nullptr;
|
||||||
static Ref<CCSprite> g_geodeButton = nullptr;
|
static Ref<CCSprite> g_geodeButton = nullptr;
|
||||||
|
|
||||||
static void addUpdateIcon(char const* icon = "updates-available.png"_spr) {
|
|
||||||
if (g_geodeButton && Index::get()->areUpdatesAvailable()) {
|
|
||||||
auto updateIcon = CCSprite::createWithSpriteFrameName(icon);
|
|
||||||
updateIcon->setPosition(g_geodeButton->getContentSize() - CCSize { 10.f, 10.f });
|
|
||||||
updateIcon->setZOrder(99);
|
|
||||||
updateIcon->setScale(.5f);
|
|
||||||
g_geodeButton->addChild(updateIcon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void updateIndexProgress(UpdateStatus status, std::string const& info, uint8_t progress) {
|
|
||||||
if (status == UpdateStatus::Failed) {
|
|
||||||
g_indexUpdateNotif->setIcon(NotificationIcon::Error);
|
|
||||||
g_indexUpdateNotif->setString("Index update failed");
|
|
||||||
g_indexUpdateNotif->setTime(2.f);
|
|
||||||
g_indexUpdateNotif = nullptr;
|
|
||||||
addUpdateIcon("updates-failed.png"_spr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status == UpdateStatus::Finished) {
|
|
||||||
g_indexUpdateNotif->setIcon(NotificationIcon::Success);
|
|
||||||
if (Index::get()->areUpdatesAvailable()) {
|
|
||||||
g_indexUpdateNotif->setString("Updates Available");
|
|
||||||
addUpdateIcon();
|
|
||||||
} else {
|
|
||||||
g_indexUpdateNotif->setString("Everything Up-to-Date");
|
|
||||||
}
|
|
||||||
g_indexUpdateNotif->setTime(2.f);
|
|
||||||
g_indexUpdateNotif = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#include <Geode/modify/MenuLayer.hpp>
|
|
||||||
struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
|
struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
|
||||||
void destructor() {
|
void destructor() {
|
||||||
g_geodeButton = nullptr;
|
g_geodeButton = nullptr;
|
||||||
|
@ -81,8 +49,6 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
|
||||||
))
|
))
|
||||||
.orMake<ButtonSprite>("!!");
|
.orMake<ButtonSprite>("!!");
|
||||||
|
|
||||||
addUpdateIcon();
|
|
||||||
|
|
||||||
auto bottomMenu = static_cast<CCMenu*>(this->getChildByID("bottom-menu"));
|
auto bottomMenu = static_cast<CCMenu*>(this->getChildByID("bottom-menu"));
|
||||||
|
|
||||||
auto btn = CCMenuItemSpriteExtra::create(
|
auto btn = CCMenuItemSpriteExtra::create(
|
||||||
|
@ -122,9 +88,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
|
||||||
"No", "Send",
|
"No", "Send",
|
||||||
[](auto, bool btn2) {
|
[](auto, bool btn2) {
|
||||||
if (btn2) {
|
if (btn2) {
|
||||||
ModInfoLayer::showIssueReportPopup(
|
geode::openIssueReportPopup(InternalMod::get());
|
||||||
InternalMod::get()->getModInfo()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
|
@ -135,13 +99,13 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// update mods index
|
// update mods index
|
||||||
if (!g_indexUpdateNotif && !Index::get()->isIndexUpdated()) {
|
if (!g_indexUpdateNotif && !Index::get()->hasTriedToUpdate()) {
|
||||||
g_indexUpdateNotif = Notification::create(
|
g_indexUpdateNotif = Notification::create(
|
||||||
"Updating Index", NotificationIcon::Loading, 0
|
"Updating Index", NotificationIcon::Loading, 0
|
||||||
);
|
);
|
||||||
g_indexUpdateNotif->show();
|
g_indexUpdateNotif->show();
|
||||||
|
|
||||||
Index::get()->updateIndex(updateIndexProgress);
|
Index::get()->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
ResourceDownloadEvent::ResourceDownloadEvent(
|
||||||
|
UpdateStatus const& status
|
||||||
|
) : status(status) {}
|
||||||
|
|
||||||
ListenerResult ResourceDownloadFilter::handle(
|
ListenerResult ResourceDownloadFilter::handle(
|
||||||
std::function<Callback> fn,
|
std::function<Callback> fn,
|
||||||
ResourceDownloadEvent* event
|
ResourceDownloadEvent* event
|
||||||
|
|
|
@ -17,13 +17,9 @@
|
||||||
|
|
||||||
USE_GEODE_NAMESPACE();
|
USE_GEODE_NAMESPACE();
|
||||||
|
|
||||||
class ResourceDownloadEvent : public Event {
|
struct ResourceDownloadEvent : public Event {
|
||||||
protected:
|
const UpdateStatus status;
|
||||||
UpdateStatus m_status;
|
ResourceDownloadEvent(UpdateStatus const& status);
|
||||||
|
|
||||||
public:
|
|
||||||
ResourceDownloadEvent(UpdateStatus status);
|
|
||||||
UpdateStatus getStatus() const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class GEODE_DLL ResourceDownloadFilter : public EventFilter<ResourceDownloadEvent> {
|
class GEODE_DLL ResourceDownloadFilter : public EventFilter<ResourceDownloadEvent> {
|
||||||
|
|
|
@ -19,7 +19,7 @@ EventListenerProtocol::~EventListenerProtocol() {
|
||||||
Event::~Event() {}
|
Event::~Event() {}
|
||||||
|
|
||||||
void Event::postFrom(Mod* m) {
|
void Event::postFrom(Mod* m) {
|
||||||
if (m) m_sender = m;
|
if (m) this->sender = m;
|
||||||
|
|
||||||
for (auto h : Event::s_listeners) {
|
for (auto h : Event::s_listeners) {
|
||||||
if (h->passThrough(this) == ListenerResult::Stop) {
|
if (h->passThrough(this) == ListenerResult::Stop) {
|
||||||
|
@ -27,7 +27,3 @@ void Event::postFrom(Mod* m) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Mod* Event::getSender() {
|
|
||||||
return m_sender;
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <Geode/utils/map.hpp>
|
#include <Geode/utils/map.hpp>
|
||||||
|
|
||||||
USE_GEODE_NAMESPACE();
|
USE_GEODE_NAMESPACE();
|
||||||
|
using namespace geode::impl;
|
||||||
|
|
||||||
struct IndexSourceSaveData {
|
struct IndexSourceSaveData {
|
||||||
std::string downloadedCommitSHA;
|
std::string downloadedCommitSHA;
|
||||||
|
@ -22,20 +23,41 @@ std::string IndexSource::dirname() const {
|
||||||
return string::replace(this->repository, "/", "_");
|
return string::replace(this->repository, "/", "_");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SourceUpdateEvent
|
||||||
|
|
||||||
|
SourceUpdateEvent::SourceUpdateEvent(
|
||||||
|
IndexSource const& src,
|
||||||
|
const UpdateStatus status
|
||||||
|
) : source(src), status(status) {}
|
||||||
|
|
||||||
|
ListenerResult SourceUpdateFilter::handle(
|
||||||
|
std::function<Callback> fn,
|
||||||
|
SourceUpdateEvent* event
|
||||||
|
) {
|
||||||
|
fn(event);
|
||||||
|
return ListenerResult::Propagate;
|
||||||
|
}
|
||||||
|
|
||||||
|
SourceUpdateFilter::SourceUpdateFilter() {}
|
||||||
|
|
||||||
|
// ModInstallEvent
|
||||||
|
|
||||||
|
ListenerResult ModInstallFilter::handle(std::function<Callback> fn, ModInstallEvent* event) {
|
||||||
|
if (m_id == event->modID) {
|
||||||
|
fn(event);
|
||||||
|
}
|
||||||
|
return ListenerResult::Propagate;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModInstallFilter::ModInstallFilter(
|
||||||
|
std::string const& id
|
||||||
|
) : m_id(id) {}
|
||||||
|
|
||||||
// IndexUpdateEvent
|
// IndexUpdateEvent
|
||||||
|
|
||||||
IndexUpdateEvent::IndexUpdateEvent(
|
IndexUpdateEvent::IndexUpdateEvent(
|
||||||
std::string const& src,
|
const UpdateStatus status
|
||||||
UpdateStatus status
|
) : status(status) {}
|
||||||
) : m_sourceRepository(src), m_status(status) {}
|
|
||||||
|
|
||||||
std::string IndexUpdateEvent::getSource() const {
|
|
||||||
return m_sourceRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateStatus IndexUpdateEvent::getStatus() const {
|
|
||||||
return m_status;
|
|
||||||
}
|
|
||||||
|
|
||||||
ListenerResult IndexUpdateFilter::handle(
|
ListenerResult IndexUpdateFilter::handle(
|
||||||
std::function<Callback> fn,
|
std::function<Callback> fn,
|
||||||
|
@ -47,35 +69,53 @@ ListenerResult IndexUpdateFilter::handle(
|
||||||
|
|
||||||
IndexUpdateFilter::IndexUpdateFilter() {}
|
IndexUpdateFilter::IndexUpdateFilter() {}
|
||||||
|
|
||||||
// ModInstallEvent
|
// IndexItem
|
||||||
|
|
||||||
ModInstallEvent::ModInstallEvent(
|
Result<IndexItemHandle> IndexItem::createFromDir(
|
||||||
std::string const& id,
|
std::string const& sourceRepository,
|
||||||
UpdateStatus status
|
ghc::filesystem::path const& dir
|
||||||
) : m_id(id), m_status(status) {}
|
) {
|
||||||
|
GEODE_UNWRAP_INTO(
|
||||||
|
auto entry, file::readJson(dir / "entry.json")
|
||||||
|
.expect("Unable to read entry.json")
|
||||||
|
);
|
||||||
|
GEODE_UNWRAP_INTO(
|
||||||
|
auto info, ModInfo::createFromFile(dir / "mod.json")
|
||||||
|
.expect("Unable to read mod.json: {error}")
|
||||||
|
);
|
||||||
|
|
||||||
std::string ModInstallEvent::getModID() const {
|
JsonChecker checker(entry);
|
||||||
return m_id;
|
auto root = checker.root("[entry.json]").obj();
|
||||||
}
|
|
||||||
|
|
||||||
UpdateStatus ModInstallEvent::getStatus() const {
|
std::unordered_set<PlatformID> platforms;
|
||||||
return m_status;
|
for (auto& plat : root.has("platforms").iterate()) {
|
||||||
}
|
platforms.insert(PlatformID::from(plat.template get<std::string>()));
|
||||||
|
|
||||||
ListenerResult ModInstallFilter::handle(std::function<Callback> fn, ModInstallEvent* event) {
|
|
||||||
if (m_id == event->getModID()) {
|
|
||||||
fn(event);
|
|
||||||
}
|
}
|
||||||
return ListenerResult::Propagate;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModInstallFilter::ModInstallFilter(
|
auto item = std::make_shared<IndexItem>(IndexItem {
|
||||||
std::string const& id
|
.sourceRepository = sourceRepository,
|
||||||
) : m_id(id) {}
|
.path = dir,
|
||||||
|
.info = info,
|
||||||
|
.download = {
|
||||||
|
.url = root.has("mod").obj().has("download").template get<std::string>(),
|
||||||
|
.hash = root.has("mod").obj().has("hash").template get<std::string>(),
|
||||||
|
.platforms = platforms,
|
||||||
|
},
|
||||||
|
.isFeatured = root.has("is-featured").template get<bool>(),
|
||||||
|
});
|
||||||
|
if (checker.isError()) {
|
||||||
|
return Err(checker.getError());
|
||||||
|
}
|
||||||
|
return Ok(item);
|
||||||
|
}
|
||||||
|
|
||||||
// Index
|
// Index
|
||||||
|
|
||||||
Index::Index() {
|
Index::Index() {
|
||||||
|
new EventListener(
|
||||||
|
std::bind(&Index::onSourceUpdate, this, std::placeholders::_1),
|
||||||
|
SourceUpdateFilter()
|
||||||
|
);
|
||||||
this->addSource("https://github.com/geode-sdk/index-test");
|
this->addSource("https://github.com/geode-sdk/index-test");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,20 +140,74 @@ std::vector<IndexSource> Index::getSources() const {
|
||||||
return m_sources;
|
return m_sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Index::isUpToDate() const {
|
void Index::onSourceUpdate(SourceUpdateEvent* event) {
|
||||||
for (auto& source : m_sources) {
|
// save status for aggregating SourceUpdateEvents to a single global
|
||||||
if (!source.isUpToDate) {
|
// IndexUpdateEvent
|
||||||
return false;
|
m_sourceStatuses[event->source.repository] = event->status;
|
||||||
|
|
||||||
|
// figure out aggregate event
|
||||||
|
enum { Finished, Progress, Failed, } whatToPost = Finished;
|
||||||
|
for (auto& [src, status] : m_sourceStatuses) {
|
||||||
|
// if some source is still updating, post progress
|
||||||
|
if (std::holds_alternative<UpdateProgress>(status)) {
|
||||||
|
whatToPost = Progress;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
// otherwise, if some source failed, then post failed
|
||||||
|
else if (std::holds_alternative<UpdateError>(status)) {
|
||||||
|
if (whatToPost != Progress) {
|
||||||
|
whatToPost = Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// otherwise if all are finished, whatToPost is already set to that
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (whatToPost) {
|
||||||
|
case Finished: {
|
||||||
|
// clear source statuses to allow updating index again
|
||||||
|
m_sourceStatuses.clear();
|
||||||
|
// post finish event
|
||||||
|
IndexUpdateEvent(UpdateFinished()).post();
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Progress: {
|
||||||
|
// get total progress
|
||||||
|
size_t total = 0;
|
||||||
|
for (auto& [src, status] : m_sourceStatuses) {
|
||||||
|
if (std::holds_alternative<UpdateProgress>(status)) {
|
||||||
|
total += std::get<UpdateProgress>(status).first;
|
||||||
|
} else {
|
||||||
|
total += 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IndexUpdateEvent(
|
||||||
|
UpdateProgress(
|
||||||
|
static_cast<uint8_t>(total / m_sourceStatuses.size()),
|
||||||
|
"Downloading"
|
||||||
|
)
|
||||||
|
).post();
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Failed: {
|
||||||
|
std::string info = "";
|
||||||
|
for (auto& [src, status] : m_sourceStatuses) {
|
||||||
|
if (std::holds_alternative<UpdateError>(status)) {
|
||||||
|
info += src + ": " + std::get<UpdateError>(status) + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// clear source statuses to allow updating index again
|
||||||
|
m_sourceStatuses.clear();
|
||||||
|
// post finish event
|
||||||
|
IndexUpdateEvent(UpdateError(info)).post();
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Index::checkSourceUpdates(IndexSource& src) {
|
void Index::checkSourceUpdates(IndexSource& src) {
|
||||||
if (src.isUpToDate) {
|
if (src.isUpToDate) {
|
||||||
return this->updateSourceFromLocal(src);
|
return this->updateSourceFromLocal(src);
|
||||||
}
|
}
|
||||||
IndexUpdateEvent(src.repository, UpdateProgress(0, "Checking status")).post();
|
SourceUpdateEvent(src, UpdateProgress(0, "Checking status")).post();
|
||||||
auto data = Mod::get()->getSavedMutable<IndexSaveData>("index");
|
auto data = Mod::get()->getSavedMutable<IndexSaveData>("index");
|
||||||
auto oldSHA = data.sources[src.repository].downloadedCommitSHA;
|
auto oldSHA = data.sources[src.repository].downloadedCommitSHA;
|
||||||
web::AsyncWebRequest()
|
web::AsyncWebRequest()
|
||||||
|
@ -136,15 +230,15 @@ void Index::checkSourceUpdates(IndexSource& src) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.expect([&src](std::string const& err) {
|
.expect([&src](std::string const& err) {
|
||||||
IndexUpdateEvent(
|
SourceUpdateEvent(
|
||||||
src.repository,
|
src,
|
||||||
UpdateError(fmt::format("Error checking for updates: {}", err))
|
UpdateError(fmt::format("Error checking for updates: {}", err))
|
||||||
).post();
|
).post();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Index::downloadSource(IndexSource& src) {
|
void Index::downloadSource(IndexSource& src) {
|
||||||
IndexUpdateEvent(src.repository, UpdateProgress(0, "Beginning download")).post();
|
SourceUpdateEvent(src, UpdateProgress(0, "Beginning download")).post();
|
||||||
|
|
||||||
auto targetFile = dirs::getIndexDir() / fmt::format("{}.zip", src.dirname());
|
auto targetFile = dirs::getIndexDir() / fmt::format("{}.zip", src.dirname());
|
||||||
|
|
||||||
|
@ -161,18 +255,16 @@ void Index::downloadSource(IndexSource& src) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(...) {
|
catch(...) {
|
||||||
return IndexUpdateEvent(
|
return SourceUpdateEvent(
|
||||||
src.repository,
|
src, UpdateError("Unable to clear cached index")
|
||||||
UpdateError("Unable to clear cached index")
|
|
||||||
).post();
|
).post();
|
||||||
}
|
}
|
||||||
|
|
||||||
// unzip new index
|
// unzip new index
|
||||||
auto unzip = file::Unzip::intoDir(targetFile, targetDir, true);
|
auto unzip = file::Unzip::intoDir(targetFile, targetDir, true);
|
||||||
if (!unzip) {
|
if (!unzip) {
|
||||||
return IndexUpdateEvent(
|
return SourceUpdateEvent(
|
||||||
src.repository,
|
src, UpdateError("Unable to unzip new index")
|
||||||
UpdateError("Unable to unzip new index")
|
|
||||||
).post();
|
).post();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,25 +272,27 @@ void Index::downloadSource(IndexSource& src) {
|
||||||
this->updateSourceFromLocal(src);
|
this->updateSourceFromLocal(src);
|
||||||
})
|
})
|
||||||
.expect([&src](std::string const& err) {
|
.expect([&src](std::string const& err) {
|
||||||
IndexUpdateEvent(
|
SourceUpdateEvent(
|
||||||
src.repository,
|
src, UpdateError(fmt::format("Error downloading: {}", err))
|
||||||
UpdateError(fmt::format("Error downloading: {}", err))
|
|
||||||
).post();
|
).post();
|
||||||
})
|
})
|
||||||
.progress([&src](auto&, double now, double total) {
|
.progress([&src](auto&, double now, double total) {
|
||||||
IndexUpdateEvent(
|
SourceUpdateEvent(
|
||||||
src.repository,
|
src,
|
||||||
UpdateProgress(static_cast<uint8_t>(now / total * 100.0), "Downloading")
|
UpdateProgress(
|
||||||
|
static_cast<uint8_t>(now / total * 100.0),
|
||||||
|
"Downloading"
|
||||||
|
)
|
||||||
).post();
|
).post();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Index::updateSourceFromLocal(IndexSource& src) {
|
void Index::updateSourceFromLocal(IndexSource& src) {
|
||||||
IndexUpdateEvent(src.repository, UpdateProgress(100, "Updating local cache")).post();
|
SourceUpdateEvent(src, UpdateProgress(100, "Updating local cache")).post();
|
||||||
// delete old items from this url if such exist
|
// delete old items from this url if such exist
|
||||||
for (auto& [_, versions] : m_items) {
|
for (auto& [_, versions] : m_items) {
|
||||||
for (auto it = versions.begin(); it != versions.end(); ) {
|
for (auto it = versions.begin(); it != versions.end(); ) {
|
||||||
if (it->second.sourceRepository == src.repository) {
|
if (it->second->sourceRepository == src.repository) {
|
||||||
it = versions.erase(it);
|
it = versions.erase(it);
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
|
@ -207,10 +301,32 @@ void Index::updateSourceFromLocal(IndexSource& src) {
|
||||||
}
|
}
|
||||||
this->cleanupItems();
|
this->cleanupItems();
|
||||||
|
|
||||||
// todo: add shit
|
// read directory and add new items
|
||||||
|
for (auto& dir : ghc::filesystem::directory_iterator(src.dirname())) {
|
||||||
|
auto addRes = IndexItem::createFromDir(src.repository, dir);
|
||||||
|
if (!addRes) {
|
||||||
|
log::warn("Unable to add index item from {}: {}", dir, addRes.unwrapErr());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto add = addRes.unwrap();
|
||||||
|
// check if this major version of this item has already been added
|
||||||
|
if (m_items[add->info.m_id].count(add->info.m_version.getMajor())) {
|
||||||
|
log::warn(
|
||||||
|
"Item {}@{} has already been added, skipping",
|
||||||
|
add->info.m_id, add->info.m_version
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// add new major version of this item
|
||||||
|
m_items[add->info.m_id].insert({
|
||||||
|
add->info.m_version.getMajor(),
|
||||||
|
add
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark source as finished
|
||||||
src.isUpToDate = true;
|
src.isUpToDate = true;
|
||||||
IndexUpdateEvent(src.repository, UpdateFinished()).post();
|
SourceUpdateEvent(src, UpdateFinished()).post();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Index::cleanupItems() {
|
void Index::cleanupItems() {
|
||||||
|
@ -224,12 +340,33 @@ void Index::cleanupItems() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Index::isUpToDate() const {
|
||||||
|
for (auto& source : m_sources) {
|
||||||
|
if (!source.isUpToDate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Index::hasTriedToUpdate() const {
|
||||||
|
return m_triedToUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
void Index::update(bool force) {
|
void Index::update(bool force) {
|
||||||
// create index dir if it doesn't exist
|
// create index dir if it doesn't exist
|
||||||
(void)file::createDirectoryAll(dirs::getIndexDir());
|
(void)file::createDirectoryAll(dirs::getIndexDir());
|
||||||
|
|
||||||
|
m_triedToUpdate = true;
|
||||||
|
|
||||||
// update all sources in GD thread
|
// update all sources in GD thread
|
||||||
Loader::get()->queueInGDThread([force, this]() {
|
Loader::get()->queueInGDThread([force, this]() {
|
||||||
|
// check if some sources are already being updated
|
||||||
|
if (m_sourceStatuses.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update sources
|
||||||
for (auto& src : m_sources) {
|
for (auto& src : m_sources) {
|
||||||
if (force) {
|
if (force) {
|
||||||
this->downloadSource(src);
|
this->downloadSource(src);
|
||||||
|
@ -240,8 +377,8 @@ void Index::update(bool force) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<IndexItem> Index::getItems() const {
|
std::vector<IndexItemHandle> Index::getItems() const {
|
||||||
std::vector<IndexItem> res;
|
std::vector<IndexItemHandle> res;
|
||||||
for (auto& items : map::values(m_items)) {
|
for (auto& items : map::values(m_items)) {
|
||||||
if (items.size()) {
|
if (items.size()) {
|
||||||
res.push_back(items.rbegin()->second);
|
res.push_back(items.rbegin()->second);
|
||||||
|
@ -265,7 +402,7 @@ bool Index::isKnownItem(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<IndexItem> Index::getItem(
|
IndexItemHandle Index::getItem(
|
||||||
std::string const& id,
|
std::string const& id,
|
||||||
std::optional<size_t> version
|
std::optional<size_t> version
|
||||||
) const {
|
) const {
|
||||||
|
@ -281,5 +418,21 @@ std::optional<IndexItem> Index::getItem(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexItemHandle Index::getItem(ModInfo const& info) const {
|
||||||
|
return this->getItem(info.m_id, info.m_version.getMajor());
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexItemHandle Index::getItem(Mod* mod) const {
|
||||||
|
return this->getItem(mod->getID(), mod->getVersion().getMajor());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Index::updateAvailable(IndexItemHandle item) const {
|
||||||
|
auto installed = Loader::get()->getInstalledMod(item->info.m_id);
|
||||||
|
if (!installed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return item->info.m_version > installed->getVersion();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,58 @@
|
||||||
|
|
||||||
#include <Geode/loader/Index.hpp>
|
#include <Geode/loader/Index.hpp>
|
||||||
#include "info/ModInfoLayer.hpp"
|
#include <Geode/loader/Dirs.hpp>
|
||||||
|
#include "info/ModInfoPopup.hpp"
|
||||||
#include "list/ModListLayer.hpp"
|
#include "list/ModListLayer.hpp"
|
||||||
#include "settings/ModSettingsPopup.hpp"
|
#include "settings/ModSettingsPopup.hpp"
|
||||||
|
#include <Geode/ui/MDPopup.hpp>
|
||||||
#include <Geode/ui/GeodeUI.hpp>
|
#include <Geode/ui/GeodeUI.hpp>
|
||||||
|
#include <Geode/utils/web.hpp>
|
||||||
|
|
||||||
void geode::openModsList() {
|
void geode::openModsList() {
|
||||||
ModListLayer::scene();
|
ModListLayer::scene();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void geode::openIssueReportPopup(Mod* mod) {
|
||||||
|
if (mod->getModInfo().m_issues) {
|
||||||
|
MDPopup::create(
|
||||||
|
"Issue Report",
|
||||||
|
mod->getModInfo().m_issues.value().m_info +
|
||||||
|
"\n\n"
|
||||||
|
"If your issue relates to a <cr>game crash</c>, <cb>please include</c> the "
|
||||||
|
"latest crash log(s) from `" +
|
||||||
|
dirs::getCrashlogsDir().string() + "`",
|
||||||
|
"OK", (mod->getModInfo().m_issues.value().m_url ? "Open URL" : ""),
|
||||||
|
[mod](bool btn2) {
|
||||||
|
if (btn2) {
|
||||||
|
web::openLinkInBrowser(
|
||||||
|
mod->getModInfo().m_issues.value().m_url.value()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)->show();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
MDPopup::create(
|
||||||
|
"Issue Report",
|
||||||
|
"Please report your issue on the "
|
||||||
|
"[#support](https://discord.com/channels/911701438269386882/979352389985390603) "
|
||||||
|
"channnel in the [Geode Discord Server](https://discord.gg/9e43WMKzhp)\n\n"
|
||||||
|
"If your issue relates to a <cr>game crash</c>, <cb>please include</c> the "
|
||||||
|
"latest crash log(s) from `" + dirs::getCrashlogsDir().string() + "`",
|
||||||
|
"OK"
|
||||||
|
)->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void geode::openInfoPopup(Mod* mod) {
|
void geode::openInfoPopup(Mod* mod) {
|
||||||
ModInfoLayer::create(mod, nullptr)->show();
|
LocalModInfoPopup::create(mod, nullptr)->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
void geode::openIndexPopup(Mod* mod) {
|
void geode::openIndexPopup(Mod* mod) {
|
||||||
if (Index::get()->isKnownItem(mod->getID())) {
|
if (auto item = Index::get()->getItem(
|
||||||
ModInfoLayer::create(
|
mod->getID(), mod->getVersion().getMajor()
|
||||||
new ModObject(Index::get()->getKnownItem(mod->getID()))
|
)) {
|
||||||
)->show();
|
IndexItemInfoPopup::create(item, nullptr)->show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,3 +61,61 @@ void geode::openSettingsPopup(Mod* mod) {
|
||||||
ModSettingsPopup::create(mod)->show();
|
ModSettingsPopup::create(mod)->show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CCNode* geode::createDefaultLogo(CCSize const& size) {
|
||||||
|
CCNode* spr = CCSprite::createWithSpriteFrameName("no-logo.png"_spr);
|
||||||
|
if (!spr) {
|
||||||
|
spr = CCLabelBMFont::create("OwO", "goldFont.fnt");
|
||||||
|
}
|
||||||
|
limitNodeSize(spr, size, 1.f, .1f);
|
||||||
|
return spr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CCNode* geode::createModLogo(Mod* mod, CCSize const& size) {
|
||||||
|
CCNode* spr = nullptr;
|
||||||
|
if (mod == Loader::getInternalMod()) {
|
||||||
|
spr = CCSprite::createWithSpriteFrameName("geode-logo.png"_spr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
spr = CCSprite::create(
|
||||||
|
fmt::format("{}/logo.png", mod->getID()).c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!spr) spr = CCSprite::createWithSpriteFrameName("no-logo.png"_spr);
|
||||||
|
if (!spr) spr = CCLabelBMFont::create("N/A", "goldFont.fnt");
|
||||||
|
limitNodeSize(spr, size, 1.f, .1f);
|
||||||
|
return spr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CCNode* geode::createIndexItemLogo(IndexItemHandle item, CCSize const& size) {
|
||||||
|
CCNode* spr = nullptr;
|
||||||
|
auto logoPath = ghc::filesystem::absolute(item->path / "logo.png");
|
||||||
|
spr = CCSprite::create(logoPath.string().c_str());
|
||||||
|
if (!spr) {
|
||||||
|
spr = CCSprite::createWithSpriteFrameName("no-logo.png"_spr);
|
||||||
|
}
|
||||||
|
if (!spr) {
|
||||||
|
spr = CCLabelBMFont::create("N/A", "goldFont.fnt");
|
||||||
|
}
|
||||||
|
if (item->isFeatured) {
|
||||||
|
auto glowSize = size + CCSize(4.f, 4.f);
|
||||||
|
|
||||||
|
auto logoGlow = CCSprite::createWithSpriteFrameName("logo-glow.png"_spr);
|
||||||
|
logoGlow->setScaleX(glowSize.width / logoGlow->getContentSize().width);
|
||||||
|
logoGlow->setScaleY(glowSize.height / logoGlow->getContentSize().height);
|
||||||
|
|
||||||
|
// i dont know why + 1 is needed and its too late for me to figure out why
|
||||||
|
spr->setPosition(
|
||||||
|
logoGlow->getContentSize().width / 2, logoGlow->getContentSize().height / 2
|
||||||
|
);
|
||||||
|
// scary mathematics
|
||||||
|
spr->setScaleX(size.width / spr->getContentSize().width / logoGlow->getScaleX());
|
||||||
|
spr->setScaleY(size.height / spr->getContentSize().height / logoGlow->getScaleY());
|
||||||
|
logoGlow->addChild(spr);
|
||||||
|
spr = logoGlow;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
limitNodeSize(spr, size, 1.f, .1f);
|
||||||
|
}
|
||||||
|
return spr;
|
||||||
|
}
|
||||||
|
|
53
loader/src/ui/internal/info/DownloadStatusNode.cpp
Normal file
53
loader/src/ui/internal/info/DownloadStatusNode.cpp
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#include "ModInfoPopup.hpp"
|
||||||
|
#include <Geode/binding/Slider.hpp>
|
||||||
|
#include <Geode/binding/SliderTouchLogic.hpp>
|
||||||
|
#include <Geode/binding/SliderThumb.hpp>
|
||||||
|
|
||||||
|
bool DownloadStatusNode::init() {
|
||||||
|
if (!CCNode::init()) return false;
|
||||||
|
|
||||||
|
this->setContentSize({ 150.f, 25.f });
|
||||||
|
|
||||||
|
auto bg = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f });
|
||||||
|
bg->setScale(.33f);
|
||||||
|
bg->setColor({ 0, 0, 0 });
|
||||||
|
bg->setOpacity(75);
|
||||||
|
bg->setContentSize(m_obContentSize * 3);
|
||||||
|
this->addChild(bg);
|
||||||
|
|
||||||
|
m_bar = Slider::create(this, nullptr, .6f);
|
||||||
|
m_bar->setValue(.0f);
|
||||||
|
m_bar->updateBar();
|
||||||
|
m_bar->setPosition(0.f, -5.f);
|
||||||
|
m_bar->m_touchLogic->m_thumb->setVisible(false);
|
||||||
|
this->addChild(m_bar);
|
||||||
|
|
||||||
|
m_label = CCLabelBMFont::create("", "bigFont.fnt");
|
||||||
|
m_label->setAnchorPoint({ .0f, .5f });
|
||||||
|
m_label->setScale(.45f);
|
||||||
|
m_label->setPosition(-m_obContentSize.width / 2 + 15.f, 5.f);
|
||||||
|
this->addChild(m_label);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadStatusNode* DownloadStatusNode::create() {
|
||||||
|
auto ret = new DownloadStatusNode();
|
||||||
|
if (ret && ret->init()) {
|
||||||
|
ret->autorelease();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
CC_SAFE_DELETE(ret);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadStatusNode::setProgress(uint8_t progress) {
|
||||||
|
m_bar->setValue(progress / 100.f);
|
||||||
|
m_bar->updateBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadStatusNode::setStatus(std::string const& text) {
|
||||||
|
m_label->setString(text.c_str());
|
||||||
|
m_label->limitLabelWidth(m_obContentSize.width - 30.f, .5f, .1f);
|
||||||
|
}
|
||||||
|
|
|
@ -1,832 +0,0 @@
|
||||||
#include "ModInfoLayer.hpp"
|
|
||||||
|
|
||||||
#include "../dev/HookListLayer.hpp"
|
|
||||||
#include "../list/ModListView.hpp"
|
|
||||||
#include "../settings/ModSettingsPopup.hpp"
|
|
||||||
#include "../settings/AdvancedSettingsPopup.hpp"
|
|
||||||
#include <InternalLoader.hpp>
|
|
||||||
#include <Geode/loader/Dirs.hpp>
|
|
||||||
|
|
||||||
#include <Geode/binding/ButtonSprite.hpp>
|
|
||||||
#include <Geode/binding/CCTextInputNode.hpp>
|
|
||||||
#include <Geode/binding/GJListLayer.hpp>
|
|
||||||
#include <Geode/binding/Slider.hpp>
|
|
||||||
#include <Geode/binding/SliderThumb.hpp>
|
|
||||||
#include <Geode/binding/SliderTouchLogic.hpp>
|
|
||||||
#include <Geode/loader/Mod.hpp>
|
|
||||||
#include <Geode/ui/BasedButton.hpp>
|
|
||||||
#include <Geode/ui/IconButtonSprite.hpp>
|
|
||||||
#include <Geode/ui/MDPopup.hpp>
|
|
||||||
#include <Geode/utils/casts.hpp>
|
|
||||||
#include <Geode/utils/ranges.hpp>
|
|
||||||
#include <InternalLoader.hpp>
|
|
||||||
|
|
||||||
// TODO: die
|
|
||||||
#undef min
|
|
||||||
#undef max
|
|
||||||
|
|
||||||
static constexpr int const TAG_CONFIRM_UNINSTALL = 5;
|
|
||||||
static constexpr int const TAG_DELETE_SAVEDATA = 6;
|
|
||||||
|
|
||||||
bool DownloadStatusNode::init() {
|
|
||||||
if (!CCNode::init()) return false;
|
|
||||||
|
|
||||||
this->setContentSize({ 150.f, 25.f });
|
|
||||||
|
|
||||||
auto bg = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f });
|
|
||||||
bg->setScale(.33f);
|
|
||||||
bg->setColor({ 0, 0, 0 });
|
|
||||||
bg->setOpacity(75);
|
|
||||||
bg->setContentSize(m_obContentSize * 3);
|
|
||||||
this->addChild(bg);
|
|
||||||
|
|
||||||
m_bar = Slider::create(this, nullptr, .6f);
|
|
||||||
m_bar->setValue(.0f);
|
|
||||||
m_bar->updateBar();
|
|
||||||
m_bar->setPosition(0.f, -5.f);
|
|
||||||
m_bar->m_touchLogic->m_thumb->setVisible(false);
|
|
||||||
this->addChild(m_bar);
|
|
||||||
|
|
||||||
m_label = CCLabelBMFont::create("", "bigFont.fnt");
|
|
||||||
m_label->setAnchorPoint({ .0f, .5f });
|
|
||||||
m_label->setScale(.45f);
|
|
||||||
m_label->setPosition(-m_obContentSize.width / 2 + 15.f, 5.f);
|
|
||||||
this->addChild(m_label);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
DownloadStatusNode* DownloadStatusNode::create() {
|
|
||||||
auto ret = new DownloadStatusNode();
|
|
||||||
if (ret && ret->init()) {
|
|
||||||
ret->autorelease();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
CC_SAFE_DELETE(ret);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadStatusNode::setProgress(uint8_t progress) {
|
|
||||||
m_bar->setValue(progress / 100.f);
|
|
||||||
m_bar->updateBar();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadStatusNode::setStatus(std::string const& text) {
|
|
||||||
m_label->setString(text.c_str());
|
|
||||||
m_label->limitLabelWidth(m_obContentSize.width - 30.f, .5f, .1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::onChangelog(CCObject* sender) {
|
|
||||||
auto toggle = static_cast<CCMenuItemToggler*>(sender);
|
|
||||||
auto winSize = CCDirector::get()->getWinSize();
|
|
||||||
|
|
||||||
m_detailsArea->setVisible(toggle->isToggled());
|
|
||||||
// as it turns out, cocos2d is stupid and still passes touch
|
|
||||||
// events to invisible nodes
|
|
||||||
m_detailsArea->setPositionX(
|
|
||||||
toggle->isToggled() ? winSize.width / 2 - m_detailsArea->getScaledContentSize().width / 2
|
|
||||||
: -5000.f
|
|
||||||
);
|
|
||||||
|
|
||||||
m_changelogArea->setVisible(!toggle->isToggled());
|
|
||||||
// as it turns out, cocos2d is stupid and still passes touch
|
|
||||||
// events to invisible nodes
|
|
||||||
m_changelogArea->setPositionX(
|
|
||||||
!toggle->isToggled() ? winSize.width / 2 - m_changelogArea->getScaledContentSize().width / 2
|
|
||||||
: -5000.f
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModInfoLayer::init(ModObject* obj, ModListView* list) {
|
|
||||||
m_noElasticity = true;
|
|
||||||
m_list = list;
|
|
||||||
m_mod = obj->m_mod;
|
|
||||||
|
|
||||||
bool isInstalledMod;
|
|
||||||
switch (obj->m_type) {
|
|
||||||
case ModObjectType::Mod:
|
|
||||||
{
|
|
||||||
m_info = obj->m_mod->getModInfo();
|
|
||||||
isInstalledMod = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ModObjectType::Index:
|
|
||||||
{
|
|
||||||
m_info = obj->m_index.m_info;
|
|
||||||
isInstalledMod = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
|
||||||
CCSize size { 440.f, 290.f };
|
|
||||||
|
|
||||||
if (!this->initWithColor({ 0, 0, 0, 105 })) return false;
|
|
||||||
m_mainLayer = CCLayer::create();
|
|
||||||
this->addChild(m_mainLayer);
|
|
||||||
|
|
||||||
auto bg = CCScale9Sprite::create("GJ_square01.png", { 0.0f, 0.0f, 80.0f, 80.0f });
|
|
||||||
bg->setContentSize(size);
|
|
||||||
bg->setPosition(winSize.width / 2, winSize.height / 2);
|
|
||||||
bg->setZOrder(-10);
|
|
||||||
m_mainLayer->addChild(bg);
|
|
||||||
|
|
||||||
m_buttonMenu = CCMenu::create();
|
|
||||||
m_mainLayer->addChild(m_buttonMenu);
|
|
||||||
|
|
||||||
constexpr float logoSize = 40.f;
|
|
||||||
constexpr float logoOffset = 10.f;
|
|
||||||
|
|
||||||
auto nameLabel = CCLabelBMFont::create(m_info.m_name.c_str(), "bigFont.fnt");
|
|
||||||
nameLabel->setAnchorPoint({ .0f, .5f });
|
|
||||||
nameLabel->limitLabelWidth(200.f, .7f, .1f);
|
|
||||||
m_mainLayer->addChild(nameLabel, 2);
|
|
||||||
|
|
||||||
auto logoSpr = this->createLogoSpr(obj, { logoSize, logoSize });
|
|
||||||
m_mainLayer->addChild(logoSpr);
|
|
||||||
|
|
||||||
auto developerStr = "by " + m_info.m_developer;
|
|
||||||
auto developerLabel = CCLabelBMFont::create(developerStr.c_str(), "goldFont.fnt");
|
|
||||||
developerLabel->setScale(.5f);
|
|
||||||
developerLabel->setAnchorPoint({ .0f, .5f });
|
|
||||||
m_mainLayer->addChild(developerLabel);
|
|
||||||
|
|
||||||
auto logoTitleWidth =
|
|
||||||
std::max(
|
|
||||||
nameLabel->getScaledContentSize().width, developerLabel->getScaledContentSize().width
|
|
||||||
) +
|
|
||||||
logoSize + logoOffset;
|
|
||||||
|
|
||||||
nameLabel->setPosition(
|
|
||||||
winSize.width / 2 - logoTitleWidth / 2 + logoSize + logoOffset, winSize.height / 2 + 125.f
|
|
||||||
);
|
|
||||||
logoSpr->setPosition({ winSize.width / 2 - logoTitleWidth / 2 + logoSize / 2,
|
|
||||||
winSize.height / 2 + 115.f });
|
|
||||||
developerLabel->setPosition(
|
|
||||||
winSize.width / 2 - logoTitleWidth / 2 + logoSize + logoOffset, winSize.height / 2 + 105.f
|
|
||||||
);
|
|
||||||
|
|
||||||
auto versionLabel = CCLabelBMFont::create(m_info.m_version.toString().c_str(), "bigFont.fnt");
|
|
||||||
versionLabel->setAnchorPoint({ .0f, .5f });
|
|
||||||
versionLabel->setScale(.4f);
|
|
||||||
versionLabel->setPosition(
|
|
||||||
nameLabel->getPositionX() + nameLabel->getScaledContentSize().width + 5.f,
|
|
||||||
winSize.height / 2 + 125.f
|
|
||||||
);
|
|
||||||
versionLabel->setColor({ 0, 255, 0 });
|
|
||||||
m_mainLayer->addChild(versionLabel);
|
|
||||||
|
|
||||||
CCDirector::sharedDirector()->getTouchDispatcher()->incrementForcePrio(2);
|
|
||||||
this->registerWithTouchDispatcher();
|
|
||||||
|
|
||||||
m_detailsArea = MDTextArea::create(
|
|
||||||
m_info.m_details ? m_info.m_details.value() : "### No description provided.",
|
|
||||||
{ 350.f, 137.5f }
|
|
||||||
);
|
|
||||||
m_detailsArea->setPosition(
|
|
||||||
winSize.width / 2 - m_detailsArea->getScaledContentSize().width / 2,
|
|
||||||
winSize.height / 2 - m_detailsArea->getScaledContentSize().height / 2 - 20.f
|
|
||||||
);
|
|
||||||
m_mainLayer->addChild(m_detailsArea);
|
|
||||||
|
|
||||||
m_scrollbar = Scrollbar::create(m_detailsArea->getScrollLayer());
|
|
||||||
m_scrollbar->setPosition(
|
|
||||||
winSize.width / 2 + m_detailsArea->getScaledContentSize().width / 2 + 20.f,
|
|
||||||
winSize.height / 2 - 20.f
|
|
||||||
);
|
|
||||||
m_mainLayer->addChild(m_scrollbar);
|
|
||||||
|
|
||||||
// changelog
|
|
||||||
if (m_info.m_changelog) {
|
|
||||||
m_changelogArea = MDTextArea::create(m_info.m_changelog.value(), { 350.f, 137.5f });
|
|
||||||
m_changelogArea->setPosition(
|
|
||||||
-5000.f, winSize.height / 2 - m_changelogArea->getScaledContentSize().height / 2 - 20.f
|
|
||||||
);
|
|
||||||
m_changelogArea->setVisible(false);
|
|
||||||
m_mainLayer->addChild(m_changelogArea);
|
|
||||||
|
|
||||||
auto changelogBtnOffSpr = ButtonSprite::create(
|
|
||||||
CCSprite::createWithSpriteFrameName("changelog.png"_spr), 0x20, true, 32.f,
|
|
||||||
"GJ_button_01.png", 1.f
|
|
||||||
);
|
|
||||||
changelogBtnOffSpr->setScale(.65f);
|
|
||||||
|
|
||||||
auto changelogBtnOnSpr = ButtonSprite::create(
|
|
||||||
CCSprite::createWithSpriteFrameName("changelog.png"_spr), 0x20, true, 32.f,
|
|
||||||
"GJ_button_02.png", 1.f
|
|
||||||
);
|
|
||||||
changelogBtnOnSpr->setScale(.65f);
|
|
||||||
|
|
||||||
auto changelogBtn = CCMenuItemToggler::create(
|
|
||||||
changelogBtnOffSpr, changelogBtnOnSpr, this, menu_selector(ModInfoLayer::onChangelog)
|
|
||||||
);
|
|
||||||
changelogBtn->setPosition(-size.width / 2 + 21.5f, .0f);
|
|
||||||
m_buttonMenu->addChild(changelogBtn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// mod info
|
|
||||||
auto infoSpr = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png");
|
|
||||||
infoSpr->setScale(.85f);
|
|
||||||
|
|
||||||
auto infoBtn =
|
|
||||||
CCMenuItemSpriteExtra::create(infoSpr, this, menu_selector(ModInfoLayer::onInfo));
|
|
||||||
infoBtn->setPosition(size.width / 2 - 25.f, size.height / 2 - 25.f);
|
|
||||||
m_buttonMenu->addChild(infoBtn);
|
|
||||||
|
|
||||||
// issue report button
|
|
||||||
if (m_info.m_issues) {
|
|
||||||
auto issuesBtnSpr =
|
|
||||||
ButtonSprite::create("Report an Issue", "goldFont.fnt", "GJ_button_04.png", .8f);
|
|
||||||
issuesBtnSpr->setScale(.75f);
|
|
||||||
|
|
||||||
auto issuesBtn = CCMenuItemSpriteExtra::create(
|
|
||||||
issuesBtnSpr, this, menu_selector(ModInfoLayer::onIssues)
|
|
||||||
);
|
|
||||||
issuesBtn->setPosition(0.f, -size.height / 2 + 25.f);
|
|
||||||
m_buttonMenu->addChild(issuesBtn);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInstalledMod) {
|
|
||||||
// mod settings
|
|
||||||
auto settingsSpr = CCSprite::createWithSpriteFrameName(
|
|
||||||
"GJ_optionsBtn_001.png"
|
|
||||||
);
|
|
||||||
settingsSpr->setScale(.65f);
|
|
||||||
|
|
||||||
auto settingsBtn = CCMenuItemSpriteExtra::create(
|
|
||||||
settingsSpr, this, menu_selector(ModInfoLayer::onSettings)
|
|
||||||
);
|
|
||||||
settingsBtn->setPosition(-size.width / 2 + 25.f, -size.height / 2 + 25.f);
|
|
||||||
m_buttonMenu->addChild(settingsBtn);
|
|
||||||
|
|
||||||
// Check if a config directory for the mod exists
|
|
||||||
// Mod::getConfigDir auto-creates the directory for user convenience, so
|
|
||||||
// have to do it manually
|
|
||||||
if (ghc::filesystem::exists(m_mod->getConfigDir(false))) {
|
|
||||||
auto configSpr = CircleButtonSprite::createWithSpriteFrameName(
|
|
||||||
"pencil.png"_spr, 1.f, CircleBaseColor::Green, CircleBaseSize::Medium2
|
|
||||||
);
|
|
||||||
configSpr->setScale(.65f);
|
|
||||||
|
|
||||||
auto configBtn = CCMenuItemSpriteExtra::create(
|
|
||||||
configSpr, this, menu_selector(ModInfoLayer::onOpenConfigDir)
|
|
||||||
);
|
|
||||||
configBtn->setPosition(-size.width / 2 + 65.f, -size.height / 2 + 25.f);
|
|
||||||
m_buttonMenu->addChild(configBtn);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_mod->hasSettings()) {
|
|
||||||
settingsSpr->setColor({ 150, 150, 150 });
|
|
||||||
settingsBtn->setTarget(this, menu_selector(ModInfoLayer::onNoSettings));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_mod->getModInfo().m_repository) {
|
|
||||||
auto repoBtn = CCMenuItemSpriteExtra::create(
|
|
||||||
CCSprite::createWithSpriteFrameName("github.png"_spr), this,
|
|
||||||
menu_selector(ModInfoLayer::onRepository)
|
|
||||||
);
|
|
||||||
repoBtn->setPosition(size.width / 2 - 25.f, -size.height / 2 + 25.f);
|
|
||||||
m_buttonMenu->addChild(repoBtn);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_mod->getModInfo().m_supportInfo) {
|
|
||||||
auto supportBtn = CCMenuItemSpriteExtra::create(
|
|
||||||
CCSprite::createWithSpriteFrameName("gift.png"_spr), this,
|
|
||||||
menu_selector(ModInfoLayer::onSupport)
|
|
||||||
);
|
|
||||||
supportBtn->setPosition(size.width / 2 - 60.f, -size.height / 2 + 25.f);
|
|
||||||
m_buttonMenu->addChild(supportBtn);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto enableBtnSpr = ButtonSprite::create("Enable", "bigFont.fnt", "GJ_button_01.png", .6f);
|
|
||||||
enableBtnSpr->setScale(.6f);
|
|
||||||
|
|
||||||
auto disableBtnSpr =
|
|
||||||
ButtonSprite::create("Disable", "bigFont.fnt", "GJ_button_06.png", .6f);
|
|
||||||
disableBtnSpr->setScale(.6f);
|
|
||||||
|
|
||||||
auto enableBtn = CCMenuItemToggler::create(
|
|
||||||
disableBtnSpr, enableBtnSpr, this, menu_selector(ModInfoLayer::onEnableMod)
|
|
||||||
);
|
|
||||||
enableBtn->setPosition(-155.f, 75.f);
|
|
||||||
enableBtn->toggle(!obj->m_mod->isEnabled());
|
|
||||||
m_buttonMenu->addChild(enableBtn);
|
|
||||||
|
|
||||||
if (!m_info.m_supportsDisabling) {
|
|
||||||
enableBtn->setTarget(this, menu_selector(ModInfoLayer::onDisablingNotSupported));
|
|
||||||
enableBtnSpr->setColor({ 150, 150, 150 });
|
|
||||||
disableBtnSpr->setColor({ 150, 150, 150 });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
m_mod != Loader::get()->getInternalMod() &&
|
|
||||||
m_mod != Mod::get()
|
|
||||||
) {
|
|
||||||
// advanced settings
|
|
||||||
auto advSettSpr = CCSprite::createWithSpriteFrameName("GJ_optionsBtn02_001.png");
|
|
||||||
advSettSpr->setScale(.65f);
|
|
||||||
|
|
||||||
auto advSettBtn = CCMenuItemSpriteExtra::create(
|
|
||||||
advSettSpr, this, menu_selector(ModInfoLayer::onAdvancedSettings)
|
|
||||||
);
|
|
||||||
advSettBtn->setPosition(
|
|
||||||
infoBtn->getPositionX() - 30.f,
|
|
||||||
infoBtn->getPositionY()
|
|
||||||
);
|
|
||||||
m_buttonMenu->addChild(advSettBtn);
|
|
||||||
|
|
||||||
auto uninstallBtnSpr = ButtonSprite::create(
|
|
||||||
"Uninstall", "bigFont.fnt", "GJ_button_05.png", .6f
|
|
||||||
);
|
|
||||||
uninstallBtnSpr->setScale(.6f);
|
|
||||||
|
|
||||||
auto uninstallBtn = CCMenuItemSpriteExtra::create(
|
|
||||||
uninstallBtnSpr, this, menu_selector(ModInfoLayer::onUninstall)
|
|
||||||
);
|
|
||||||
uninstallBtn->setPosition(-85.f, 75.f);
|
|
||||||
m_buttonMenu->addChild(uninstallBtn);
|
|
||||||
|
|
||||||
// todo: show update button on loader that invokes the installer
|
|
||||||
if (Index::get()->isUpdateAvailableForItem(m_info.m_id)) {
|
|
||||||
m_installBtnSpr = IconButtonSprite::create(
|
|
||||||
"GE_button_01.png"_spr, CCSprite::createWithSpriteFrameName("install.png"_spr),
|
|
||||||
"Update", "bigFont.fnt"
|
|
||||||
);
|
|
||||||
m_installBtnSpr->setScale(.6f);
|
|
||||||
|
|
||||||
m_installBtn = CCMenuItemSpriteExtra::create(
|
|
||||||
m_installBtnSpr, this, menu_selector(ModInfoLayer::onInstallMod)
|
|
||||||
);
|
|
||||||
m_installBtn->setPosition(-8.0f, 75.f);
|
|
||||||
m_buttonMenu->addChild(m_installBtn);
|
|
||||||
|
|
||||||
m_installStatus = DownloadStatusNode::create();
|
|
||||||
m_installStatus->setPosition(winSize.width / 2 + 105.f, winSize.height / 2 + 75.f);
|
|
||||||
m_installStatus->setVisible(false);
|
|
||||||
m_mainLayer->addChild(m_installStatus);
|
|
||||||
|
|
||||||
auto incomingVersion =
|
|
||||||
Index::get()->getKnownItem(m_info.m_id).m_info.m_version.toString();
|
|
||||||
|
|
||||||
m_updateVersionLabel =
|
|
||||||
CCLabelBMFont::create(("Available: " + incomingVersion).c_str(), "bigFont.fnt");
|
|
||||||
m_updateVersionLabel->setScale(.35f);
|
|
||||||
m_updateVersionLabel->setAnchorPoint({ .0f, .5f });
|
|
||||||
m_updateVersionLabel->setColor({ 94, 219, 255 });
|
|
||||||
m_updateVersionLabel->setPosition(
|
|
||||||
winSize.width / 2 + 35.f, winSize.height / 2 + 75.f
|
|
||||||
);
|
|
||||||
m_mainLayer->addChild(m_updateVersionLabel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_installBtnSpr = IconButtonSprite::create(
|
|
||||||
"GE_button_01.png"_spr, CCSprite::createWithSpriteFrameName("install.png"_spr),
|
|
||||||
"Install", "bigFont.fnt"
|
|
||||||
);
|
|
||||||
m_installBtnSpr->setScale(.6f);
|
|
||||||
|
|
||||||
m_installBtn = CCMenuItemSpriteExtra::create(
|
|
||||||
m_installBtnSpr, this, menu_selector(ModInfoLayer::onInstallMod)
|
|
||||||
);
|
|
||||||
m_installBtn->setPosition(-143.0f, 75.f);
|
|
||||||
m_buttonMenu->addChild(m_installBtn);
|
|
||||||
|
|
||||||
m_installStatus = DownloadStatusNode::create();
|
|
||||||
m_installStatus->setPosition(winSize.width / 2 - 25.f, winSize.height / 2 + 75.f);
|
|
||||||
m_installStatus->setVisible(false);
|
|
||||||
m_mainLayer->addChild(m_installStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if this mod is being installed/updated, and if so, update UI
|
|
||||||
if (auto handle = Index::get()->isInstallingItem(m_info.m_id)) {
|
|
||||||
m_installation = handle;
|
|
||||||
this->install();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto closeSpr = CCSprite::createWithSpriteFrameName("GJ_closeBtn_001.png");
|
|
||||||
closeSpr->setScale(.8f);
|
|
||||||
|
|
||||||
auto closeBtn =
|
|
||||||
CCMenuItemSpriteExtra::create(closeSpr, this, menu_selector(ModInfoLayer::onClose));
|
|
||||||
closeBtn->setPosition(-size.width / 2 + 3.f, size.height / 2 - 3.f);
|
|
||||||
m_buttonMenu->addChild(closeBtn);
|
|
||||||
|
|
||||||
this->setKeypadEnabled(true);
|
|
||||||
this->setTouchEnabled(true);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::onIssues(CCObject*) {
|
|
||||||
ModInfoLayer::showIssueReportPopup(m_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::onSupport(CCObject*) {
|
|
||||||
MDPopup::create("Support " + m_mod->getName(), m_mod->getModInfo().m_supportInfo.value(), "OK")
|
|
||||||
->show();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::onEnableMod(CCObject* pSender) {
|
|
||||||
if (!InternalLoader::get()->shownInfoAlert("mod-disable-vs-unload")) {
|
|
||||||
FLAlertLayer::create(
|
|
||||||
"Notice",
|
|
||||||
"You may still see some effects of the mod left, and you may "
|
|
||||||
"need to <cg>restart</c> the game to have it fully unloaded.",
|
|
||||||
"OK"
|
|
||||||
)
|
|
||||||
->show();
|
|
||||||
if (m_list) m_list->updateAllStates(nullptr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (as<CCMenuItemToggler*>(pSender)->isToggled()) {
|
|
||||||
auto res = m_mod->loadBinary();
|
|
||||||
if (!res) {
|
|
||||||
FLAlertLayer::create(
|
|
||||||
nullptr, "Error Loading Mod",
|
|
||||||
res.unwrapErr(), "OK", nullptr
|
|
||||||
)->show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
auto res = m_mod->disable();
|
|
||||||
if (!res) {
|
|
||||||
FLAlertLayer::create(
|
|
||||||
nullptr, "Error Disabling Mod",
|
|
||||||
res.unwrapErr(), "OK", nullptr
|
|
||||||
)->show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (m_list) m_list->updateAllStates(nullptr);
|
|
||||||
as<CCMenuItemToggler*>(pSender)->toggle(m_mod->isEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::onRepository(CCObject*) {
|
|
||||||
web::openLinkInBrowser(m_mod->getModInfo().m_repository.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::onInstallMod(CCObject*) {
|
|
||||||
auto ticketRes = Index::get()->installItem(Index::get()->getKnownItem(m_info.m_id));
|
|
||||||
if (!ticketRes) {
|
|
||||||
return FLAlertLayer::create(
|
|
||||||
"Unable to install", ticketRes.unwrapErr(), "OK"
|
|
||||||
)->show();
|
|
||||||
}
|
|
||||||
m_installation = ticketRes.unwrap();
|
|
||||||
|
|
||||||
createQuickPopup(
|
|
||||||
"Install",
|
|
||||||
"The following <cb>mods</c> will be installed: " +
|
|
||||||
ranges::join(m_installation->toInstall(), ",") + ".",
|
|
||||||
"Cancel", "OK",
|
|
||||||
[this](FLAlertLayer*, bool btn2) {
|
|
||||||
if (btn2) {
|
|
||||||
this->install();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this->updateInstallStatus("", 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::onCancelInstall(CCObject*) {
|
|
||||||
m_installBtn->setEnabled(false);
|
|
||||||
m_installBtnSpr->setString("Cancelling");
|
|
||||||
m_installation->cancel();
|
|
||||||
m_installation = nullptr;
|
|
||||||
if (m_updateVersionLabel) {
|
|
||||||
m_updateVersionLabel->setVisible(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::onOpenConfigDir(CCObject*) {
|
|
||||||
file::openFolder(m_mod->getConfigDir());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::onUninstall(CCObject*) {
|
|
||||||
auto layer = FLAlertLayer::create(
|
|
||||||
this, "Confirm Uninstall",
|
|
||||||
"Are you sure you want to uninstall <cr>" + m_info.m_name + "</c>?", "Cancel", "OK"
|
|
||||||
);
|
|
||||||
layer->setTag(TAG_CONFIRM_UNINSTALL);
|
|
||||||
layer->show();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) {
|
|
||||||
switch (layer->getTag()) {
|
|
||||||
case TAG_CONFIRM_UNINSTALL:
|
|
||||||
{
|
|
||||||
if (btn2) {
|
|
||||||
this->uninstall();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TAG_DELETE_SAVEDATA:
|
|
||||||
{
|
|
||||||
if (btn2) {
|
|
||||||
if (ghc::filesystem::remove_all(m_mod->getSaveDir())) {
|
|
||||||
FLAlertLayer::create("Deleted", "The mod's save data was deleted.", "OK")
|
|
||||||
->show();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
FLAlertLayer::create(
|
|
||||||
"Error", "Unable to delete mod's save directory!", "OK"
|
|
||||||
)
|
|
||||||
->show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (m_list) m_list->refreshList();
|
|
||||||
this->onClose(nullptr);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::updateInstallStatus(std::string const& status, uint8_t progress) {
|
|
||||||
if (status.size()) {
|
|
||||||
m_installStatus->setVisible(true);
|
|
||||||
m_installStatus->setStatus(status);
|
|
||||||
m_installStatus->setProgress(progress);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_installStatus->setVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::modInstallProgress(
|
|
||||||
InstallHandle, UpdateStatus status, std::string const& info, uint8_t percentage
|
|
||||||
) {
|
|
||||||
switch (status) {
|
|
||||||
case UpdateStatus::Failed:
|
|
||||||
{
|
|
||||||
FLAlertLayer::create("Installation failed :(", info, "OK")->show();
|
|
||||||
this->updateInstallStatus("", 0);
|
|
||||||
|
|
||||||
m_installBtn->setEnabled(true);
|
|
||||||
m_installBtn->setTarget(this, menu_selector(ModInfoLayer::onInstallMod));
|
|
||||||
m_installBtnSpr->setString("Install");
|
|
||||||
m_installBtnSpr->setBG("GE_button_01.png"_spr, false);
|
|
||||||
|
|
||||||
m_installation = nullptr;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case UpdateStatus::Finished:
|
|
||||||
{
|
|
||||||
this->updateInstallStatus("", 100);
|
|
||||||
|
|
||||||
FLAlertLayer::create(
|
|
||||||
"Install complete",
|
|
||||||
"Mod succesfully installed! :) "
|
|
||||||
"(You may need to <cy>restart the game</c> "
|
|
||||||
"for the mod to take full effect)",
|
|
||||||
"OK"
|
|
||||||
)
|
|
||||||
->show();
|
|
||||||
|
|
||||||
m_installation = nullptr;
|
|
||||||
|
|
||||||
if (m_list) m_list->refreshList();
|
|
||||||
this->onClose(nullptr);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
this->updateInstallStatus(info, percentage);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::install() {
|
|
||||||
if (m_updateVersionLabel) {
|
|
||||||
m_updateVersionLabel->setVisible(false);
|
|
||||||
}
|
|
||||||
this->updateInstallStatus("Starting install", 0);
|
|
||||||
|
|
||||||
m_installBtn->setTarget(this, menu_selector(ModInfoLayer::onCancelInstall));
|
|
||||||
m_installBtnSpr->setString("Cancel");
|
|
||||||
m_installBtnSpr->setBG("GJ_button_06.png", false);
|
|
||||||
|
|
||||||
m_callbackID = m_installation->start(std::bind(
|
|
||||||
&ModInfoLayer::modInstallProgress, this, std::placeholders::_1, std::placeholders::_2,
|
|
||||||
std::placeholders::_3, std::placeholders::_4
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::uninstall() {
|
|
||||||
auto res = m_mod->uninstall();
|
|
||||||
if (!res) {
|
|
||||||
return FLAlertLayer::create(
|
|
||||||
"Uninstall failed :(", res.unwrapErr(), "OK"
|
|
||||||
)->show();
|
|
||||||
}
|
|
||||||
auto layer = FLAlertLayer::create(
|
|
||||||
this, "Uninstall complete",
|
|
||||||
"Mod was succesfully uninstalled! :) "
|
|
||||||
"(You may need to <cy>restart the game</c> "
|
|
||||||
"for the mod to take full effect). "
|
|
||||||
"<co>Would you also like to delete the mod's "
|
|
||||||
"save data?</c>",
|
|
||||||
"Cancel", "Delete", 350.f
|
|
||||||
);
|
|
||||||
layer->setTag(TAG_DELETE_SAVEDATA);
|
|
||||||
layer->show();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::onDisablingNotSupported(CCObject* pSender) {
|
|
||||||
FLAlertLayer::create("Unsupported", "<cr>Disabling</c> is not supported for this mod.", "OK")
|
|
||||||
->show();
|
|
||||||
as<CCMenuItemToggler*>(pSender)->toggle(m_mod->isEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::onHooks(CCObject*) {
|
|
||||||
auto layer = HookListLayer::create(this->m_mod);
|
|
||||||
this->addChild(layer);
|
|
||||||
layer->showLayer(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::onSettings(CCObject*) {
|
|
||||||
ModSettingsPopup::create(m_mod)->show();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::onNoSettings(CCObject*) {
|
|
||||||
FLAlertLayer::create("No Settings Found", "This mod has no customizable settings.", "OK")
|
|
||||||
->show();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::onAdvancedSettings(CCObject*) {
|
|
||||||
AdvancedSettingsPopup::create(m_mod)->show();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::onInfo(CCObject*) {
|
|
||||||
FLAlertLayer::create(
|
|
||||||
nullptr, ("About " + m_info.m_name).c_str(),
|
|
||||||
"<cr>ID: " + m_info.m_id +
|
|
||||||
"</c>\n"
|
|
||||||
"<cg>Version: " +
|
|
||||||
m_info.m_version.toString() +
|
|
||||||
"</c>\n"
|
|
||||||
"<cp>Developer: " +
|
|
||||||
m_info.m_developer +
|
|
||||||
"</c>\n"
|
|
||||||
"<cb>Path: " +
|
|
||||||
m_info.m_path.string() + "</c>\n",
|
|
||||||
"OK", nullptr, 400.f
|
|
||||||
)
|
|
||||||
->show();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::keyDown(enumKeyCodes key) {
|
|
||||||
if (key == KEY_Escape) return this->onClose(nullptr);
|
|
||||||
if (key == KEY_Space) return;
|
|
||||||
|
|
||||||
return FLAlertLayer::keyDown(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::onClose(CCObject* pSender) {
|
|
||||||
this->setKeyboardEnabled(false);
|
|
||||||
this->removeFromParentAndCleanup(true);
|
|
||||||
if (m_installation) {
|
|
||||||
m_installation->leave(m_callbackID);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ModInfoLayer* ModInfoLayer::create(Mod* mod, ModListView* list) {
|
|
||||||
auto ret = new ModInfoLayer;
|
|
||||||
if (ret && ret->init(new ModObject(mod), list)) {
|
|
||||||
ret->autorelease();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
CC_SAFE_DELETE(ret);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModInfoLayer* ModInfoLayer::create(ModObject* obj, ModListView* list) {
|
|
||||||
auto ret = new ModInfoLayer;
|
|
||||||
if (ret && ret->init(obj, list)) {
|
|
||||||
ret->autorelease();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
CC_SAFE_DELETE(ret);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
CCNode* ModInfoLayer::createLogoSpr(ModObject* modObj, CCSize const& size) {
|
|
||||||
switch (modObj->m_type) {
|
|
||||||
case ModObjectType::Mod:
|
|
||||||
{
|
|
||||||
return ModInfoLayer::createLogoSpr(modObj->m_mod, size);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ModObjectType::Index:
|
|
||||||
{
|
|
||||||
return ModInfoLayer::createLogoSpr(modObj->m_index, size);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
auto spr = CCSprite::createWithSpriteFrameName("no-logo.png"_spr);
|
|
||||||
spr->setScaleX(size.width / spr->getContentSize().width);
|
|
||||||
spr->setScaleY(size.height / spr->getContentSize().height);
|
|
||||||
if (!spr) {
|
|
||||||
return CCLabelBMFont::create("OwO", "goldFont.fnt");
|
|
||||||
}
|
|
||||||
return spr;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CCNode* ModInfoLayer::createLogoSpr(Mod* mod, CCSize const& size) {
|
|
||||||
CCNode* spr = nullptr;
|
|
||||||
if (mod == Loader::getInternalMod()) {
|
|
||||||
spr = CCSprite::createWithSpriteFrameName("geode-logo.png"_spr);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
spr = CCSprite::create(
|
|
||||||
fmt::format("{}/logo.png", mod->getID()).c_str()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!spr) spr = CCSprite::createWithSpriteFrameName("no-logo.png"_spr);
|
|
||||||
if (!spr) spr = CCLabelBMFont::create("OwO", "goldFont.fnt");
|
|
||||||
spr->setScaleX(size.width / spr->getContentSize().width);
|
|
||||||
spr->setScaleY(size.height / spr->getContentSize().height);
|
|
||||||
return spr;
|
|
||||||
}
|
|
||||||
|
|
||||||
CCNode* ModInfoLayer::createLogoSpr(IndexItem const& item, CCSize const& size) {
|
|
||||||
CCNode* spr = nullptr;
|
|
||||||
auto logoPath = ghc::filesystem::absolute(item.m_path / "logo.png");
|
|
||||||
spr = CCSprite::create(logoPath.string().c_str());
|
|
||||||
if (!spr) {
|
|
||||||
spr = CCSprite::createWithSpriteFrameName("no-logo.png"_spr);
|
|
||||||
}
|
|
||||||
if (!spr) {
|
|
||||||
spr = CCLabelBMFont::create("OwO", "goldFont.fnt");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Index::get()->isFeaturedItem(item.m_info.m_id)) {
|
|
||||||
auto glowSize = size + CCSize(4.f, 4.f);
|
|
||||||
|
|
||||||
auto logoGlow = CCSprite::createWithSpriteFrameName("logo-glow.png"_spr);
|
|
||||||
logoGlow->setScaleX(glowSize.width / logoGlow->getContentSize().width);
|
|
||||||
logoGlow->setScaleY(glowSize.height / logoGlow->getContentSize().height);
|
|
||||||
|
|
||||||
// i dont know why + 1 is needed and its too late for me to figure out why
|
|
||||||
spr->setPosition(
|
|
||||||
logoGlow->getContentSize().width / 2, logoGlow->getContentSize().height / 2
|
|
||||||
);
|
|
||||||
// scary mathematics
|
|
||||||
spr->setScaleX(size.width / spr->getContentSize().width / logoGlow->getScaleX());
|
|
||||||
spr->setScaleY(size.height / spr->getContentSize().height / logoGlow->getScaleY());
|
|
||||||
logoGlow->addChild(spr);
|
|
||||||
spr = logoGlow;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
spr->setScaleX(size.width / spr->getContentSize().width);
|
|
||||||
spr->setScaleY(size.height / spr->getContentSize().height);
|
|
||||||
}
|
|
||||||
|
|
||||||
return spr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModInfoLayer::showIssueReportPopup(ModInfo const& info) {
|
|
||||||
if (info.m_issues) {
|
|
||||||
MDPopup::create(
|
|
||||||
"Issue Report",
|
|
||||||
info.m_issues.value().m_info +
|
|
||||||
"\n\n"
|
|
||||||
"If your issue relates to a <cr>game crash</c>, <cb>please include</c> the "
|
|
||||||
"latest crash log(s) from `" +
|
|
||||||
dirs::getCrashlogsDir().string() + "`",
|
|
||||||
"OK", (info.m_issues.value().m_url ? "Open URL" : ""),
|
|
||||||
[info](bool btn2) {
|
|
||||||
if (btn2) {
|
|
||||||
web::openLinkInBrowser(info.m_issues.value().m_url.value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)->show();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
MDPopup::create(
|
|
||||||
"Issue Report",
|
|
||||||
"Please report your issue on the "
|
|
||||||
"[#support](https://discord.com/channels/911701438269386882/979352389985390603) "
|
|
||||||
"channnel in the [Geode Discord Server](https://discord.gg/9e43WMKzhp)\n\n"
|
|
||||||
"If your issue relates to a <cr>game crash</c>, <cb>please include</c> the "
|
|
||||||
"latest crash log(s) from `" + dirs::getCrashlogsDir().string() + "`",
|
|
||||||
"OK"
|
|
||||||
)
|
|
||||||
->show();
|
|
||||||
}
|
|
||||||
}
|
|
620
loader/src/ui/internal/info/ModInfoPopup.cpp
Normal file
620
loader/src/ui/internal/info/ModInfoPopup.cpp
Normal file
|
@ -0,0 +1,620 @@
|
||||||
|
#include "ModInfoPopup.hpp"
|
||||||
|
|
||||||
|
#include "../dev/HookListLayer.hpp"
|
||||||
|
#include "../list/ModListView.hpp"
|
||||||
|
#include "../settings/ModSettingsPopup.hpp"
|
||||||
|
#include "../settings/AdvancedSettingsPopup.hpp"
|
||||||
|
#include <InternalLoader.hpp>
|
||||||
|
#include <Geode/loader/Dirs.hpp>
|
||||||
|
|
||||||
|
#include <Geode/binding/ButtonSprite.hpp>
|
||||||
|
#include <Geode/binding/CCTextInputNode.hpp>
|
||||||
|
#include <Geode/binding/GJListLayer.hpp>
|
||||||
|
#include <Geode/binding/Slider.hpp>
|
||||||
|
#include <Geode/binding/SliderThumb.hpp>
|
||||||
|
#include <Geode/binding/SliderTouchLogic.hpp>
|
||||||
|
#include <Geode/loader/Mod.hpp>
|
||||||
|
#include <Geode/ui/BasedButton.hpp>
|
||||||
|
#include <Geode/ui/IconButtonSprite.hpp>
|
||||||
|
#include <Geode/ui/GeodeUI.hpp>
|
||||||
|
#include <Geode/utils/casts.hpp>
|
||||||
|
#include <Geode/utils/ranges.hpp>
|
||||||
|
#include <Geode/utils/web.hpp>
|
||||||
|
#include <InternalLoader.hpp>
|
||||||
|
|
||||||
|
// TODO: die
|
||||||
|
#undef min
|
||||||
|
#undef max
|
||||||
|
|
||||||
|
static constexpr int const TAG_CONFIRM_UNINSTALL = 5;
|
||||||
|
static constexpr int const TAG_DELETE_SAVEDATA = 6;
|
||||||
|
static const CCSize LAYER_SIZE = { 440.f, 290.f };
|
||||||
|
|
||||||
|
bool ModInfoPopup::init(ModInfo const& info, ModListView* list) {
|
||||||
|
m_noElasticity = true;
|
||||||
|
m_list = list;
|
||||||
|
|
||||||
|
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
||||||
|
|
||||||
|
if (!this->initWithColor({ 0, 0, 0, 105 })) return false;
|
||||||
|
m_mainLayer = CCLayer::create();
|
||||||
|
this->addChild(m_mainLayer);
|
||||||
|
|
||||||
|
auto bg = CCScale9Sprite::create("GJ_square01.png", { 0.0f, 0.0f, 80.0f, 80.0f });
|
||||||
|
bg->setContentSize(LAYER_SIZE);
|
||||||
|
bg->setPosition(winSize.width / 2, winSize.height / 2);
|
||||||
|
bg->setZOrder(-10);
|
||||||
|
m_mainLayer->addChild(bg);
|
||||||
|
|
||||||
|
m_buttonMenu = CCMenu::create();
|
||||||
|
m_mainLayer->addChild(m_buttonMenu);
|
||||||
|
|
||||||
|
constexpr float logoSize = 40.f;
|
||||||
|
constexpr float logoOffset = 10.f;
|
||||||
|
|
||||||
|
auto nameLabel = CCLabelBMFont::create(info.m_name.c_str(), "bigFont.fnt");
|
||||||
|
nameLabel->setAnchorPoint({ .0f, .5f });
|
||||||
|
nameLabel->limitLabelWidth(200.f, .7f, .1f);
|
||||||
|
m_mainLayer->addChild(nameLabel, 2);
|
||||||
|
|
||||||
|
auto logoSpr = this->createLogo({ logoSize, logoSize });
|
||||||
|
m_mainLayer->addChild(logoSpr);
|
||||||
|
|
||||||
|
auto developerStr = "by " + info.m_developer;
|
||||||
|
auto developerLabel = CCLabelBMFont::create(developerStr.c_str(), "goldFont.fnt");
|
||||||
|
developerLabel->setScale(.5f);
|
||||||
|
developerLabel->setAnchorPoint({ .0f, .5f });
|
||||||
|
m_mainLayer->addChild(developerLabel);
|
||||||
|
|
||||||
|
auto logoTitleWidth =
|
||||||
|
std::max(
|
||||||
|
nameLabel->getScaledContentSize().width,
|
||||||
|
developerLabel->getScaledContentSize().width
|
||||||
|
) +
|
||||||
|
logoSize + logoOffset;
|
||||||
|
|
||||||
|
nameLabel->setPosition(
|
||||||
|
winSize.width / 2 - logoTitleWidth / 2 + logoSize + logoOffset,
|
||||||
|
winSize.height / 2 + 125.f
|
||||||
|
);
|
||||||
|
logoSpr->setPosition({
|
||||||
|
winSize.width / 2 - logoTitleWidth / 2 + logoSize / 2,
|
||||||
|
winSize.height / 2 + 115.f
|
||||||
|
});
|
||||||
|
developerLabel->setPosition(
|
||||||
|
winSize.width / 2 - logoTitleWidth / 2 + logoSize + logoOffset,
|
||||||
|
winSize.height / 2 + 105.f
|
||||||
|
);
|
||||||
|
|
||||||
|
auto versionLabel = CCLabelBMFont::create(
|
||||||
|
info.m_version.toString().c_str(),
|
||||||
|
"bigFont.fnt"
|
||||||
|
);
|
||||||
|
versionLabel->setAnchorPoint({ .0f, .5f });
|
||||||
|
versionLabel->setScale(.4f);
|
||||||
|
versionLabel->setPosition(
|
||||||
|
nameLabel->getPositionX() + nameLabel->getScaledContentSize().width + 5.f,
|
||||||
|
winSize.height / 2 + 125.f
|
||||||
|
);
|
||||||
|
versionLabel->setColor({ 0, 255, 0 });
|
||||||
|
m_mainLayer->addChild(versionLabel);
|
||||||
|
|
||||||
|
CCDirector::sharedDirector()->getTouchDispatcher()->incrementForcePrio(2);
|
||||||
|
this->registerWithTouchDispatcher();
|
||||||
|
|
||||||
|
m_detailsArea = MDTextArea::create(
|
||||||
|
(info.m_details ? info.m_details.value() : "### No description provided."),
|
||||||
|
{ 350.f, 137.5f }
|
||||||
|
);
|
||||||
|
m_detailsArea->setPosition(
|
||||||
|
winSize.width / 2 - m_detailsArea->getScaledContentSize().width / 2,
|
||||||
|
winSize.height / 2 - m_detailsArea->getScaledContentSize().height / 2 - 20.f
|
||||||
|
);
|
||||||
|
m_mainLayer->addChild(m_detailsArea);
|
||||||
|
|
||||||
|
m_scrollbar = Scrollbar::create(m_detailsArea->getScrollLayer());
|
||||||
|
m_scrollbar->setPosition(
|
||||||
|
winSize.width / 2 + m_detailsArea->getScaledContentSize().width / 2 + 20.f,
|
||||||
|
winSize.height / 2 - 20.f
|
||||||
|
);
|
||||||
|
m_mainLayer->addChild(m_scrollbar);
|
||||||
|
|
||||||
|
// changelog
|
||||||
|
if (info.m_changelog) {
|
||||||
|
m_changelogArea = MDTextArea::create(info.m_changelog.value(), { 350.f, 137.5f });
|
||||||
|
m_changelogArea->setPosition(
|
||||||
|
-5000.f, winSize.height / 2 -
|
||||||
|
m_changelogArea->getScaledContentSize().height / 2 - 20.f
|
||||||
|
);
|
||||||
|
m_changelogArea->setVisible(false);
|
||||||
|
m_mainLayer->addChild(m_changelogArea);
|
||||||
|
|
||||||
|
auto changelogBtnOffSpr = ButtonSprite::create(
|
||||||
|
CCSprite::createWithSpriteFrameName("changelog.png"_spr),
|
||||||
|
0x20, true, 32.f, "GJ_button_01.png", 1.f
|
||||||
|
);
|
||||||
|
changelogBtnOffSpr->setScale(.65f);
|
||||||
|
|
||||||
|
auto changelogBtnOnSpr = ButtonSprite::create(
|
||||||
|
CCSprite::createWithSpriteFrameName("changelog.png"_spr),
|
||||||
|
0x20, true, 32.f, "GJ_button_02.png", 1.f
|
||||||
|
);
|
||||||
|
changelogBtnOnSpr->setScale(.65f);
|
||||||
|
|
||||||
|
auto changelogBtn = CCMenuItemToggler::create(
|
||||||
|
changelogBtnOffSpr, changelogBtnOnSpr,
|
||||||
|
this, menu_selector(ModInfoPopup::onChangelog)
|
||||||
|
);
|
||||||
|
changelogBtn->setPosition(-LAYER_SIZE.width / 2 + 21.5f, .0f);
|
||||||
|
m_buttonMenu->addChild(changelogBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// mod info
|
||||||
|
auto infoSpr = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png");
|
||||||
|
infoSpr->setScale(.85f);
|
||||||
|
|
||||||
|
m_infoBtn = CCMenuItemSpriteExtra::create(
|
||||||
|
infoSpr, this, menu_selector(ModInfoPopup::onInfo)
|
||||||
|
);
|
||||||
|
m_infoBtn->setPosition(
|
||||||
|
LAYER_SIZE.width / 2 - 25.f,
|
||||||
|
LAYER_SIZE.height / 2 - 25.f
|
||||||
|
);
|
||||||
|
m_buttonMenu->addChild(m_infoBtn);
|
||||||
|
|
||||||
|
// repo button
|
||||||
|
if (info.m_repository) {
|
||||||
|
auto repoBtn = CCMenuItemSpriteExtra::create(
|
||||||
|
CCSprite::createWithSpriteFrameName("github.png"_spr), this,
|
||||||
|
menu_selector(ModInfoPopup::onRepository)
|
||||||
|
);
|
||||||
|
repoBtn->setPosition(
|
||||||
|
LAYER_SIZE.width / 2 - 25.f,
|
||||||
|
-LAYER_SIZE.height / 2 + 25.f
|
||||||
|
);
|
||||||
|
m_buttonMenu->addChild(repoBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// support button
|
||||||
|
if (info.m_supportInfo) {
|
||||||
|
auto supportBtn = CCMenuItemSpriteExtra::create(
|
||||||
|
CCSprite::createWithSpriteFrameName("gift.png"_spr), this,
|
||||||
|
menu_selector(ModInfoPopup::onSupport)
|
||||||
|
);
|
||||||
|
supportBtn->setPosition(
|
||||||
|
LAYER_SIZE.width / 2 - 60.f,
|
||||||
|
-LAYER_SIZE.height / 2 + 25.f
|
||||||
|
);
|
||||||
|
m_buttonMenu->addChild(supportBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto closeSpr = CCSprite::createWithSpriteFrameName("GJ_closeBtn_001.png");
|
||||||
|
closeSpr->setScale(.8f);
|
||||||
|
|
||||||
|
auto closeBtn = CCMenuItemSpriteExtra::create(
|
||||||
|
closeSpr, this, menu_selector(ModInfoPopup::onClose)
|
||||||
|
);
|
||||||
|
closeBtn->setPosition(
|
||||||
|
-LAYER_SIZE.width / 2 + 3.f,
|
||||||
|
LAYER_SIZE.height / 2 - 3.f
|
||||||
|
);
|
||||||
|
m_buttonMenu->addChild(closeBtn);
|
||||||
|
|
||||||
|
this->setKeypadEnabled(true);
|
||||||
|
this->setTouchEnabled(true);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModInfoPopup::onSupport(CCObject*) {
|
||||||
|
MDPopup::create(
|
||||||
|
"Support " + this->getModInfo().m_name,
|
||||||
|
this->getModInfo().m_supportInfo.value(),
|
||||||
|
"OK"
|
||||||
|
)->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModInfoPopup::onRepository(CCObject*) {
|
||||||
|
web::openLinkInBrowser(this->getModInfo().m_repository.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModInfoPopup::onInfo(CCObject*) {
|
||||||
|
auto info = this->getModInfo();
|
||||||
|
FLAlertLayer::create(
|
||||||
|
nullptr,
|
||||||
|
("About " + info.m_name).c_str(),
|
||||||
|
fmt::format(
|
||||||
|
"<cr>ID: {}</c>\n"
|
||||||
|
"<cg>Version: {}</c>\n"
|
||||||
|
"<cp>Developer: {}</c>\n"
|
||||||
|
"<cb>Path: {}</c>\n",
|
||||||
|
info.m_id,
|
||||||
|
info.m_version.toString(),
|
||||||
|
info.m_developer,
|
||||||
|
info.m_path.string()
|
||||||
|
),
|
||||||
|
"OK", nullptr, 400.f
|
||||||
|
)->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModInfoPopup::onChangelog(CCObject* sender) {
|
||||||
|
auto toggle = static_cast<CCMenuItemToggler*>(sender);
|
||||||
|
auto winSize = CCDirector::get()->getWinSize();
|
||||||
|
|
||||||
|
m_detailsArea->setVisible(toggle->isToggled());
|
||||||
|
// as it turns out, cocos2d is stupid and still passes touch
|
||||||
|
// events to invisible nodes
|
||||||
|
m_detailsArea->setPositionX(
|
||||||
|
toggle->isToggled() ?
|
||||||
|
winSize.width / 2 - m_detailsArea->getScaledContentSize().width / 2 :
|
||||||
|
-5000.f
|
||||||
|
);
|
||||||
|
|
||||||
|
m_changelogArea->setVisible(!toggle->isToggled());
|
||||||
|
// as it turns out, cocos2d is stupid and still passes touch
|
||||||
|
// events to invisible nodes
|
||||||
|
m_changelogArea->setPositionX(
|
||||||
|
!toggle->isToggled() ?
|
||||||
|
winSize.width / 2 - m_changelogArea->getScaledContentSize().width / 2 :
|
||||||
|
-5000.f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModInfoPopup::keyDown(enumKeyCodes key) {
|
||||||
|
if (key == KEY_Escape) return this->onClose(nullptr);
|
||||||
|
if (key == KEY_Space) return;
|
||||||
|
|
||||||
|
return FLAlertLayer::keyDown(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModInfoPopup::onClose(CCObject* pSender) {
|
||||||
|
this->setKeyboardEnabled(false);
|
||||||
|
this->removeFromParentAndCleanup(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// LocalModInfoPopup
|
||||||
|
|
||||||
|
bool LocalModInfoPopup::init(Mod* mod, ModListView* list) {
|
||||||
|
m_mod = mod;
|
||||||
|
|
||||||
|
if (!ModInfoPopup::init(mod->getModInfo(), list))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
||||||
|
|
||||||
|
// mod settings
|
||||||
|
auto settingsSpr = CCSprite::createWithSpriteFrameName(
|
||||||
|
"GJ_optionsBtn_001.png"
|
||||||
|
);
|
||||||
|
settingsSpr->setScale(.65f);
|
||||||
|
|
||||||
|
auto settingsBtn = CCMenuItemSpriteExtra::create(
|
||||||
|
settingsSpr, this, menu_selector(LocalModInfoPopup::onSettings)
|
||||||
|
);
|
||||||
|
settingsBtn->setPosition(
|
||||||
|
-LAYER_SIZE.width / 2 + 25.f,
|
||||||
|
-LAYER_SIZE.height / 2 + 25.f
|
||||||
|
);
|
||||||
|
m_buttonMenu->addChild(settingsBtn);
|
||||||
|
|
||||||
|
// Check if a config directory for the mod exists
|
||||||
|
if (ghc::filesystem::exists(mod->getConfigDir(false))) {
|
||||||
|
auto configSpr = CircleButtonSprite::createWithSpriteFrameName(
|
||||||
|
"pencil.png"_spr, 1.f, CircleBaseColor::Green, CircleBaseSize::Medium2
|
||||||
|
);
|
||||||
|
configSpr->setScale(.65f);
|
||||||
|
|
||||||
|
auto configBtn = CCMenuItemSpriteExtra::create(
|
||||||
|
configSpr, this, menu_selector(LocalModInfoPopup::onOpenConfigDir)
|
||||||
|
);
|
||||||
|
configBtn->setPosition(
|
||||||
|
-LAYER_SIZE.width / 2 + 65.f,
|
||||||
|
-LAYER_SIZE.height / 2 + 25.f
|
||||||
|
);
|
||||||
|
m_buttonMenu->addChild(configBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mod->hasSettings()) {
|
||||||
|
settingsSpr->setColor({ 150, 150, 150 });
|
||||||
|
settingsBtn->setTarget(this, menu_selector(LocalModInfoPopup::onNoSettings));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto enableBtnSpr = ButtonSprite::create(
|
||||||
|
"Enable", "bigFont.fnt", "GJ_button_01.png", .6f
|
||||||
|
);
|
||||||
|
enableBtnSpr->setScale(.6f);
|
||||||
|
|
||||||
|
auto disableBtnSpr = ButtonSprite::create(
|
||||||
|
"Disable", "bigFont.fnt", "GJ_button_06.png", .6f
|
||||||
|
);
|
||||||
|
disableBtnSpr->setScale(.6f);
|
||||||
|
|
||||||
|
auto enableBtn = CCMenuItemToggler::create(
|
||||||
|
disableBtnSpr, enableBtnSpr,
|
||||||
|
this, menu_selector(LocalModInfoPopup::onEnableMod)
|
||||||
|
);
|
||||||
|
enableBtn->setPosition(-155.f, 75.f);
|
||||||
|
enableBtn->toggle(!mod->isEnabled());
|
||||||
|
m_buttonMenu->addChild(enableBtn);
|
||||||
|
|
||||||
|
if (!mod->supportsDisabling()) {
|
||||||
|
enableBtn->setTarget(this, menu_selector(LocalModInfoPopup::onDisablingNotSupported));
|
||||||
|
enableBtnSpr->setColor({ 150, 150, 150 });
|
||||||
|
disableBtnSpr->setColor({ 150, 150, 150 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mod != Loader::get()->getInternalMod()) {
|
||||||
|
// advanced settings
|
||||||
|
auto advSettSpr = CCSprite::createWithSpriteFrameName("GJ_optionsBtn02_001.png");
|
||||||
|
advSettSpr->setScale(.65f);
|
||||||
|
|
||||||
|
auto advSettBtn = CCMenuItemSpriteExtra::create(
|
||||||
|
advSettSpr, this, menu_selector(LocalModInfoPopup::onAdvancedSettings)
|
||||||
|
);
|
||||||
|
advSettBtn->setPosition(
|
||||||
|
m_infoBtn->getPositionX() - 30.f,
|
||||||
|
m_infoBtn->getPositionY()
|
||||||
|
);
|
||||||
|
m_buttonMenu->addChild(advSettBtn);
|
||||||
|
|
||||||
|
auto uninstallBtnSpr = ButtonSprite::create(
|
||||||
|
"Uninstall", "bigFont.fnt", "GJ_button_05.png", .6f
|
||||||
|
);
|
||||||
|
uninstallBtnSpr->setScale(.6f);
|
||||||
|
|
||||||
|
auto uninstallBtn = CCMenuItemSpriteExtra::create(
|
||||||
|
uninstallBtnSpr, this, menu_selector(LocalModInfoPopup::onUninstall)
|
||||||
|
);
|
||||||
|
uninstallBtn->setPosition(-85.f, 75.f);
|
||||||
|
m_buttonMenu->addChild(uninstallBtn);
|
||||||
|
|
||||||
|
auto indexItem = Index::get()->getItem(mod->getModInfo());
|
||||||
|
|
||||||
|
// todo: show update button on loader that invokes the installer
|
||||||
|
if (indexItem && Index::get()->updateAvailable(indexItem)) {
|
||||||
|
m_installBtnSpr = IconButtonSprite::create(
|
||||||
|
"GE_button_01.png"_spr,
|
||||||
|
CCSprite::createWithSpriteFrameName("install.png"_spr),
|
||||||
|
"Update", "bigFont.fnt"
|
||||||
|
);
|
||||||
|
m_installBtnSpr->setScale(.6f);
|
||||||
|
|
||||||
|
m_installBtn = CCMenuItemSpriteExtra::create(
|
||||||
|
m_installBtnSpr, this, nullptr
|
||||||
|
);
|
||||||
|
m_installBtn->setPosition(-8.0f, 75.f);
|
||||||
|
m_buttonMenu->addChild(m_installBtn);
|
||||||
|
|
||||||
|
m_installStatus = DownloadStatusNode::create();
|
||||||
|
m_installStatus->setPosition(
|
||||||
|
winSize.width / 2 + 105.f,
|
||||||
|
winSize.height / 2 + 75.f
|
||||||
|
);
|
||||||
|
m_installStatus->setVisible(false);
|
||||||
|
m_mainLayer->addChild(m_installStatus);
|
||||||
|
|
||||||
|
m_updateVersionLabel = CCLabelBMFont::create(
|
||||||
|
("Available: " + indexItem->info.m_version.toString()).c_str(),
|
||||||
|
"bigFont.fnt"
|
||||||
|
);
|
||||||
|
m_updateVersionLabel->setScale(.35f);
|
||||||
|
m_updateVersionLabel->setAnchorPoint({ .0f, .5f });
|
||||||
|
m_updateVersionLabel->setColor({ 94, 219, 255 });
|
||||||
|
m_updateVersionLabel->setPosition(
|
||||||
|
winSize.width / 2 + 35.f, winSize.height / 2 + 75.f
|
||||||
|
);
|
||||||
|
m_mainLayer->addChild(m_updateVersionLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// issue report button
|
||||||
|
if (mod->getModInfo().m_issues) {
|
||||||
|
auto issuesBtnSpr = ButtonSprite::create(
|
||||||
|
"Report an Issue", "goldFont.fnt", "GJ_button_04.png", .8f
|
||||||
|
);
|
||||||
|
issuesBtnSpr->setScale(.75f);
|
||||||
|
|
||||||
|
auto issuesBtn = CCMenuItemSpriteExtra::create(
|
||||||
|
issuesBtnSpr, this, menu_selector(LocalModInfoPopup::onIssues)
|
||||||
|
);
|
||||||
|
issuesBtn->setPosition(0.f, -LAYER_SIZE.height / 2 + 25.f);
|
||||||
|
m_buttonMenu->addChild(issuesBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CCNode* LocalModInfoPopup::createLogo(CCSize const& size) {
|
||||||
|
return geode::createModLogo(m_mod, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModInfo LocalModInfoPopup::getModInfo() const {
|
||||||
|
return m_mod->getModInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalModInfoPopup::onIssues(CCObject*) {
|
||||||
|
geode::openIssueReportPopup(m_mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalModInfoPopup::onUninstall(CCObject*) {
|
||||||
|
auto layer = FLAlertLayer::create(
|
||||||
|
this, "Confirm Uninstall",
|
||||||
|
fmt::format(
|
||||||
|
"Are you sure you want to uninstall <cr>{}</c>?",
|
||||||
|
m_mod->getName()
|
||||||
|
),
|
||||||
|
"Cancel", "OK"
|
||||||
|
);
|
||||||
|
layer->setTag(TAG_CONFIRM_UNINSTALL);
|
||||||
|
layer->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalModInfoPopup::onEnableMod(CCObject* sender) {
|
||||||
|
if (!InternalLoader::get()->shownInfoAlert("mod-disable-vs-unload")) {
|
||||||
|
FLAlertLayer::create(
|
||||||
|
"Notice",
|
||||||
|
"You may still see some effects of the mod left, and you may "
|
||||||
|
"need to <cg>restart</c> the game to have it fully unloaded.",
|
||||||
|
"OK"
|
||||||
|
)
|
||||||
|
->show();
|
||||||
|
if (m_list) m_list->updateAllStates(nullptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (as<CCMenuItemToggler*>(sender)->isToggled()) {
|
||||||
|
auto res = m_mod->loadBinary();
|
||||||
|
if (!res) {
|
||||||
|
FLAlertLayer::create(
|
||||||
|
nullptr, "Error Loading Mod",
|
||||||
|
res.unwrapErr(), "OK", nullptr
|
||||||
|
)->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
auto res = m_mod->disable();
|
||||||
|
if (!res) {
|
||||||
|
FLAlertLayer::create(
|
||||||
|
nullptr, "Error Disabling Mod",
|
||||||
|
res.unwrapErr(), "OK", nullptr
|
||||||
|
)->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_list) m_list->updateAllStates(nullptr);
|
||||||
|
as<CCMenuItemToggler*>(sender)->toggle(m_mod->isEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalModInfoPopup::onOpenConfigDir(CCObject*) {
|
||||||
|
file::openFolder(m_mod->getConfigDir());
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalModInfoPopup::onDisablingNotSupported(CCObject* pSender) {
|
||||||
|
FLAlertLayer::create(
|
||||||
|
"Unsupported",
|
||||||
|
"<cr>Disabling</c> is not supported for this mod.",
|
||||||
|
"OK"
|
||||||
|
)->show();
|
||||||
|
as<CCMenuItemToggler*>(pSender)->toggle(m_mod->isEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalModInfoPopup::onSettings(CCObject*) {
|
||||||
|
ModSettingsPopup::create(m_mod)->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalModInfoPopup::onNoSettings(CCObject*) {
|
||||||
|
FLAlertLayer::create("No Settings Found", "This mod has no customizable settings.", "OK")
|
||||||
|
->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalModInfoPopup::onAdvancedSettings(CCObject*) {
|
||||||
|
AdvancedSettingsPopup::create(m_mod)->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) {
|
||||||
|
switch (layer->getTag()) {
|
||||||
|
case TAG_CONFIRM_UNINSTALL: {
|
||||||
|
if (btn2) {
|
||||||
|
this->uninstall();
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case TAG_DELETE_SAVEDATA: {
|
||||||
|
if (btn2) {
|
||||||
|
if (ghc::filesystem::remove_all(m_mod->getSaveDir())) {
|
||||||
|
FLAlertLayer::create(
|
||||||
|
"Deleted", "The mod's save data was deleted.", "OK"
|
||||||
|
)->show();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
FLAlertLayer::create(
|
||||||
|
"Error", "Unable to delete mod's save directory!", "OK"
|
||||||
|
)->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_list) m_list->refreshList();
|
||||||
|
this->onClose(nullptr);
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalModInfoPopup::uninstall() {
|
||||||
|
auto res = m_mod->uninstall();
|
||||||
|
if (!res) {
|
||||||
|
return FLAlertLayer::create(
|
||||||
|
"Uninstall failed :(", res.unwrapErr(), "OK"
|
||||||
|
)->show();
|
||||||
|
}
|
||||||
|
auto layer = FLAlertLayer::create(
|
||||||
|
this, "Uninstall complete",
|
||||||
|
"Mod was succesfully uninstalled! :) "
|
||||||
|
"(You may need to <cy>restart the game</c> "
|
||||||
|
"for the mod to take full effect). "
|
||||||
|
"<co>Would you also like to delete the mod's "
|
||||||
|
"save data?</c>",
|
||||||
|
"Cancel", "Delete", 350.f
|
||||||
|
);
|
||||||
|
layer->setTag(TAG_DELETE_SAVEDATA);
|
||||||
|
layer->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalModInfoPopup* LocalModInfoPopup::create(Mod* mod, ModListView* list) {
|
||||||
|
auto ret = new LocalModInfoPopup;
|
||||||
|
if (ret && ret->init(mod, list)) {
|
||||||
|
ret->autorelease();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
CC_SAFE_DELETE(ret);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IndexItemInfoPopup
|
||||||
|
|
||||||
|
bool IndexItemInfoPopup::init(IndexItemHandle item, ModListView* list) {
|
||||||
|
m_item = item;
|
||||||
|
|
||||||
|
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
||||||
|
|
||||||
|
if (!ModInfoPopup::init(item->info, list))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_installBtnSpr = IconButtonSprite::create(
|
||||||
|
"GE_button_01.png"_spr, CCSprite::createWithSpriteFrameName("install.png"_spr),
|
||||||
|
"Install", "bigFont.fnt"
|
||||||
|
);
|
||||||
|
m_installBtnSpr->setScale(.6f);
|
||||||
|
|
||||||
|
m_installBtn = CCMenuItemSpriteExtra::create(
|
||||||
|
m_installBtnSpr, this, nullptr
|
||||||
|
);
|
||||||
|
m_installBtn->setPosition(-143.0f, 75.f);
|
||||||
|
m_buttonMenu->addChild(m_installBtn);
|
||||||
|
|
||||||
|
m_installStatus = DownloadStatusNode::create();
|
||||||
|
m_installStatus->setPosition(
|
||||||
|
winSize.width / 2 - 25.f,
|
||||||
|
winSize.height / 2 + 75.f
|
||||||
|
);
|
||||||
|
m_installStatus->setVisible(false);
|
||||||
|
m_mainLayer->addChild(m_installStatus);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CCNode* IndexItemInfoPopup::createLogo(CCSize const& size) {
|
||||||
|
return geode::createIndexItemLogo(m_item, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModInfo IndexItemInfoPopup::getModInfo() const {
|
||||||
|
return m_item->info;
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexItemInfoPopup* IndexItemInfoPopup::create(
|
||||||
|
IndexItemHandle item, ModListView* list
|
||||||
|
) {
|
||||||
|
auto ret = new IndexItemInfoPopup;
|
||||||
|
if (ret && ret->init(item, list)) {
|
||||||
|
ret->autorelease();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
CC_SAFE_DELETE(ret);
|
||||||
|
return nullptr;
|
||||||
|
}
|
|
@ -26,58 +26,66 @@ public:
|
||||||
void setStatus(std::string const& text);
|
void setStatus(std::string const& text);
|
||||||
};
|
};
|
||||||
|
|
||||||
class ModInfoLayer : public FLAlertLayer, public FLAlertLayerProtocol {
|
class ModInfoPopup : public FLAlertLayer {
|
||||||
protected:
|
protected:
|
||||||
Mod* m_mod = nullptr;
|
|
||||||
ModInfo m_info;
|
|
||||||
bool m_isIndexMod = false;
|
|
||||||
ModListView* m_list = nullptr;
|
ModListView* m_list = nullptr;
|
||||||
DownloadStatusNode* m_installStatus = nullptr;
|
DownloadStatusNode* m_installStatus = nullptr;
|
||||||
IconButtonSprite* m_installBtnSpr;
|
IconButtonSprite* m_installBtnSpr;
|
||||||
CCMenuItemSpriteExtra* m_installBtn;
|
CCMenuItemSpriteExtra* m_installBtn;
|
||||||
|
CCMenuItemSpriteExtra* m_infoBtn;
|
||||||
CCLabelBMFont* m_updateVersionLabel = nullptr;
|
CCLabelBMFont* m_updateVersionLabel = nullptr;
|
||||||
InstallHandle m_installation;
|
|
||||||
InstallItems::CallbackID m_callbackID;
|
|
||||||
MDTextArea* m_detailsArea;
|
MDTextArea* m_detailsArea;
|
||||||
MDTextArea* m_changelogArea;
|
MDTextArea* m_changelogArea;
|
||||||
Scrollbar* m_scrollbar;
|
Scrollbar* m_scrollbar;
|
||||||
|
|
||||||
void onHooks(CCObject*);
|
|
||||||
void onSettings(CCObject*);
|
|
||||||
void onNoSettings(CCObject*);
|
|
||||||
void onInfo(CCObject*);
|
|
||||||
void onEnableMod(CCObject*);
|
|
||||||
void onInstallMod(CCObject*);
|
|
||||||
void onCancelInstall(CCObject*);
|
|
||||||
void onUninstall(CCObject*);
|
|
||||||
void onDisablingNotSupported(CCObject*);
|
|
||||||
void onChangelog(CCObject*);
|
void onChangelog(CCObject*);
|
||||||
void onIssues(CCObject*);
|
|
||||||
void onRepository(CCObject*);
|
void onRepository(CCObject*);
|
||||||
void onSupport(CCObject*);
|
void onSupport(CCObject*);
|
||||||
void onOpenConfigDir(CCObject*);
|
void onInfo(CCObject*);
|
||||||
void onAdvancedSettings(CCObject*);
|
|
||||||
void install();
|
|
||||||
void uninstall();
|
|
||||||
void updateInstallStatus(std::string const& status, uint8_t progress);
|
|
||||||
|
|
||||||
void modInstallProgress(
|
bool init(ModInfo const& info, ModListView* list);
|
||||||
InstallHandle handle, UpdateStatus status, std::string const& info, uint8_t percentage
|
|
||||||
);
|
|
||||||
void FLAlert_Clicked(FLAlertLayer*, bool) override;
|
|
||||||
|
|
||||||
bool init(ModObject* obj, ModListView* list);
|
|
||||||
|
|
||||||
void keyDown(cocos2d::enumKeyCodes) override;
|
void keyDown(cocos2d::enumKeyCodes) override;
|
||||||
void onClose(cocos2d::CCObject*);
|
void onClose(cocos2d::CCObject*);
|
||||||
|
|
||||||
public:
|
virtual CCNode* createLogo(CCSize const& size) = 0;
|
||||||
static ModInfoLayer* create(Mod* mod, ModListView* list);
|
virtual ModInfo getModInfo() const = 0;
|
||||||
static ModInfoLayer* create(ModObject* obj, ModListView* list);
|
};
|
||||||
|
|
||||||
static void showIssueReportPopup(ModInfo const& info);
|
class LocalModInfoPopup : public ModInfoPopup, public FLAlertLayerProtocol {
|
||||||
|
protected:
|
||||||
static CCNode* createLogoSpr(ModObject* modObj, CCSize const& size);
|
Mod* m_mod;
|
||||||
static CCNode* createLogoSpr(Mod* mod, CCSize const& size);
|
|
||||||
static CCNode* createLogoSpr(IndexItem const& item, CCSize const& size);
|
bool init(Mod* mod, ModListView* list);
|
||||||
|
|
||||||
|
void onIssues(CCObject*);
|
||||||
|
void onSettings(CCObject*);
|
||||||
|
void onNoSettings(CCObject*);
|
||||||
|
void onDisablingNotSupported(CCObject*);
|
||||||
|
void onEnableMod(CCObject*);
|
||||||
|
void onUninstall(CCObject*);
|
||||||
|
void onOpenConfigDir(CCObject*);
|
||||||
|
void onAdvancedSettings(CCObject*);
|
||||||
|
void uninstall();
|
||||||
|
|
||||||
|
void FLAlert_Clicked(FLAlertLayer*, bool) override;
|
||||||
|
|
||||||
|
CCNode* createLogo(CCSize const& size) override;
|
||||||
|
ModInfo getModInfo() const override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static LocalModInfoPopup* create(Mod* mod, ModListView* list);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IndexItemInfoPopup : public ModInfoPopup {
|
||||||
|
protected:
|
||||||
|
IndexItemHandle m_item;
|
||||||
|
|
||||||
|
bool init(IndexItemHandle item, ModListView* list);
|
||||||
|
|
||||||
|
CCNode* createLogo(CCSize const& size) override;
|
||||||
|
ModInfo getModInfo() const override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static IndexItemInfoPopup* create(IndexItemHandle item, ModListView* list);
|
||||||
};
|
};
|
400
loader/src/ui/internal/list/ModListCell.cpp
Normal file
400
loader/src/ui/internal/list/ModListCell.cpp
Normal file
|
@ -0,0 +1,400 @@
|
||||||
|
#include "ModListCell.hpp"
|
||||||
|
#include "ModListView.hpp"
|
||||||
|
#include "../info/ModInfoPopup.hpp"
|
||||||
|
#include <Geode/binding/StatsCell.hpp>
|
||||||
|
#include <Geode/binding/FLAlertLayer.hpp>
|
||||||
|
#include <Geode/binding/ButtonSprite.hpp>
|
||||||
|
#include <Geode/binding/CCMenuItemSpriteExtra.hpp>
|
||||||
|
#include <Geode/binding/CCMenuItemToggler.hpp>
|
||||||
|
#include <Geode/ui/GeodeUI.hpp>
|
||||||
|
#include <InternalLoader.hpp>
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
static bool tryOrAlert(Result<T> const& res, char const* title) {
|
||||||
|
if (!res) {
|
||||||
|
FLAlertLayer::create(title, res.unwrapErr(), "OK")->show();
|
||||||
|
}
|
||||||
|
return res.isOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
ModListCell::ModListCell(char const* name, CCSize const& size)
|
||||||
|
: TableViewCell(name, size.width, size.height) {}
|
||||||
|
|
||||||
|
void ModListCell::draw() {
|
||||||
|
reinterpret_cast<StatsCell*>(this)->StatsCell::draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModListCell::setupInfo(ModInfo const& info, bool spaceForCategories) {
|
||||||
|
m_mainLayer->setVisible(true);
|
||||||
|
m_backgroundLayer->setOpacity(255);
|
||||||
|
|
||||||
|
m_menu = CCMenu::create();
|
||||||
|
m_menu->setPosition(m_width - 40.f, m_height / 2);
|
||||||
|
m_mainLayer->addChild(m_menu);
|
||||||
|
|
||||||
|
auto logoSize = m_height / 1.5f;
|
||||||
|
|
||||||
|
auto logoSpr = this->createLogo({ logoSize, logoSize });
|
||||||
|
logoSpr->setPosition({ logoSize / 2 + 12.f, m_height / 2 });
|
||||||
|
m_mainLayer->addChild(logoSpr);
|
||||||
|
|
||||||
|
bool hasDesc =
|
||||||
|
m_display == ModListDisplay::Expanded &&
|
||||||
|
info.m_description.has_value();
|
||||||
|
|
||||||
|
auto titleLabel = CCLabelBMFont::create(info.m_name.c_str(), "bigFont.fnt");
|
||||||
|
titleLabel->setAnchorPoint({ .0f, .5f });
|
||||||
|
titleLabel->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
|
||||||
|
if (hasDesc && spaceForCategories) {
|
||||||
|
titleLabel->setPositionY(m_height / 2 + 20.f);
|
||||||
|
}
|
||||||
|
else if (hasDesc || spaceForCategories) {
|
||||||
|
titleLabel->setPositionY(m_height / 2 + 15.f);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
titleLabel->setPositionY(m_height / 2 + 7.f);
|
||||||
|
}
|
||||||
|
titleLabel->limitLabelWidth(m_width / 2 - 40.f, .5f, .1f);
|
||||||
|
m_mainLayer->addChild(titleLabel);
|
||||||
|
|
||||||
|
auto versionLabel = CCLabelBMFont::create(info.m_version.toString().c_str(), "bigFont.fnt");
|
||||||
|
versionLabel->setAnchorPoint({ .0f, .5f });
|
||||||
|
versionLabel->setScale(.3f);
|
||||||
|
versionLabel->setPosition(
|
||||||
|
titleLabel->getPositionX() + titleLabel->getScaledContentSize().width + 5.f,
|
||||||
|
titleLabel->getPositionY() - 1.f
|
||||||
|
);
|
||||||
|
versionLabel->setColor({ 0, 255, 0 });
|
||||||
|
m_mainLayer->addChild(versionLabel);
|
||||||
|
|
||||||
|
auto creatorStr = "by " + info.m_developer;
|
||||||
|
auto creatorLabel = CCLabelBMFont::create(creatorStr.c_str(), "goldFont.fnt");
|
||||||
|
creatorLabel->setAnchorPoint({ .0f, .5f });
|
||||||
|
creatorLabel->setScale(.43f);
|
||||||
|
creatorLabel->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
|
||||||
|
if (hasDesc && spaceForCategories) {
|
||||||
|
creatorLabel->setPositionY(m_height / 2 + 7.5f);
|
||||||
|
}
|
||||||
|
else if (hasDesc || spaceForCategories) {
|
||||||
|
creatorLabel->setPositionY(m_height / 2);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
creatorLabel->setPositionY(m_height / 2 - 7.f);
|
||||||
|
}
|
||||||
|
m_mainLayer->addChild(creatorLabel);
|
||||||
|
|
||||||
|
if (hasDesc) {
|
||||||
|
auto descBG = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f });
|
||||||
|
descBG->setColor({ 0, 0, 0 });
|
||||||
|
descBG->setOpacity(90);
|
||||||
|
descBG->setContentSize({ m_width * 2, 60.f });
|
||||||
|
descBG->setAnchorPoint({ .0f, .5f });
|
||||||
|
descBG->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
|
||||||
|
if (spaceForCategories) {
|
||||||
|
descBG->setPositionY(m_height / 2 - 7.5f);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
descBG->setPositionY(m_height / 2 - 17.f);
|
||||||
|
}
|
||||||
|
descBG->setScale(.25f);
|
||||||
|
m_mainLayer->addChild(descBG);
|
||||||
|
|
||||||
|
auto descText = CCLabelBMFont::create(info.m_description.value().c_str(), "chatFont.fnt");
|
||||||
|
descText->setAnchorPoint({ .0f, .5f });
|
||||||
|
descText->setPosition(m_height / 2 + logoSize / 2 + 18.f, descBG->getPositionY());
|
||||||
|
descText->limitLabelWidth(m_width / 2 - 10.f, .5f, .1f);
|
||||||
|
m_mainLayer->addChild(descText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModListCell::updateBGColor(int index) {
|
||||||
|
if (index % 2) {
|
||||||
|
m_backgroundLayer->setColor(ccc3(0xc2, 0x72, 0x3e));
|
||||||
|
}
|
||||||
|
else m_backgroundLayer->setColor(ccc3(0xa1, 0x58, 0x2c));
|
||||||
|
m_backgroundLayer->setOpacity(0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModListCell::init(ModListView* list, ModListDisplay display) {
|
||||||
|
m_list = list;
|
||||||
|
m_display = display;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModCell
|
||||||
|
|
||||||
|
ModCell::ModCell(const char* name, CCSize const& size)
|
||||||
|
: ModListCell(name, size) {}
|
||||||
|
|
||||||
|
ModCell* ModCell::create(
|
||||||
|
ModListView* list, ModListDisplay display,
|
||||||
|
const char* key, CCSize const& size
|
||||||
|
) {
|
||||||
|
auto ret = new ModCell(key, size);
|
||||||
|
if (ret && ret->init(list, display)) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
CC_SAFE_DELETE(ret);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModCell::onEnable(CCObject* sender) {
|
||||||
|
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*>(sender)->isToggled()) {
|
||||||
|
tryOrAlert(m_mod->enable(), "Error enabling mod");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tryOrAlert(m_mod->disable(), "Error disabling mod");
|
||||||
|
}
|
||||||
|
m_list->updateAllStates(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModCell::onUnresolvedInfo(CCObject*) {
|
||||||
|
std::string info =
|
||||||
|
"This mod has the following "
|
||||||
|
"<cr>unresolved dependencies</c>: ";
|
||||||
|
for (auto const& dep : m_mod->getUnresolvedDependencies()) {
|
||||||
|
info += fmt::format(
|
||||||
|
"<cg>{}</c> (<cy>{}</c>), ",
|
||||||
|
dep.m_id, dep.m_version.toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
info.pop_back();
|
||||||
|
info.pop_back();
|
||||||
|
FLAlertLayer::create(nullptr, "Unresolved Dependencies", info, "OK", nullptr, 400.f)->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModCell::onInfo(CCObject*) {
|
||||||
|
LocalModInfoPopup::create(m_mod, m_list)->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModCell::updateState() {
|
||||||
|
bool unresolved = m_mod->hasUnresolvedDependencies();
|
||||||
|
if (m_enableToggle) {
|
||||||
|
m_enableToggle->toggle(m_mod->isEnabled());
|
||||||
|
m_enableToggle->setEnabled(!unresolved);
|
||||||
|
m_enableToggle->m_offButton->setOpacity(unresolved ? 100 : 255);
|
||||||
|
m_enableToggle->m_offButton->setColor(unresolved ? cc3x(155) : cc3x(255));
|
||||||
|
m_enableToggle->m_onButton->setOpacity(unresolved ? 100 : 255);
|
||||||
|
m_enableToggle->m_onButton->setColor(unresolved ? cc3x(155) : cc3x(255));
|
||||||
|
}
|
||||||
|
m_unresolvedExMark->setVisible(unresolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModCell::loadFromMod(Mod* mod) {
|
||||||
|
this->setupInfo(mod->getModInfo(), false);
|
||||||
|
|
||||||
|
auto viewSpr = ButtonSprite::create(
|
||||||
|
"View", "bigFont.fnt", "GJ_button_01.png", .8f
|
||||||
|
);
|
||||||
|
viewSpr->setScale(.65f);
|
||||||
|
|
||||||
|
auto viewBtn = CCMenuItemSpriteExtra::create(
|
||||||
|
viewSpr, this, menu_selector(ModCell::onInfo)
|
||||||
|
);
|
||||||
|
m_menu->addChild(viewBtn);
|
||||||
|
|
||||||
|
if (m_mod->wasSuccesfullyLoaded() && m_mod->supportsDisabling()) {
|
||||||
|
m_enableToggle = CCMenuItemToggler::createWithStandardSprites(
|
||||||
|
this, menu_selector(ModCell::onEnable), .7f
|
||||||
|
);
|
||||||
|
m_enableToggle->setPosition(-45.f, 0.f);
|
||||||
|
m_menu->addChild(m_enableToggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto exMark = CCSprite::createWithSpriteFrameName("exMark_001.png");
|
||||||
|
exMark->setScale(.5f);
|
||||||
|
|
||||||
|
m_unresolvedExMark = CCMenuItemSpriteExtra::create(
|
||||||
|
exMark, this, menu_selector(ModCell::onUnresolvedInfo)
|
||||||
|
);
|
||||||
|
m_unresolvedExMark->setPosition(-80.f, 0.f);
|
||||||
|
m_unresolvedExMark->setVisible(false);
|
||||||
|
m_menu->addChild(m_unresolvedExMark);
|
||||||
|
|
||||||
|
// if (m_mod->wasSuccesfullyLoaded()) {
|
||||||
|
// if (Index::get()->isUpdateAvailableForItem(m_obj->m_mod->getID())) {
|
||||||
|
// viewSpr->updateBGImage("GE_button_01.png"_spr);
|
||||||
|
|
||||||
|
// auto updateIcon = CCSprite::createWithSpriteFrameName("updates-available.png"_spr);
|
||||||
|
// updateIcon->setPosition(viewSpr->getContentSize() - CCSize { 2.f, 2.f });
|
||||||
|
// updateIcon->setZOrder(99);
|
||||||
|
// updateIcon->setScale(.5f);
|
||||||
|
// viewSpr->addChild(updateIcon);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
this->updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
CCNode* ModCell::createLogo(CCSize const& size) {
|
||||||
|
return geode::createModLogo(m_mod, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// IndexItemCell
|
||||||
|
|
||||||
|
IndexItemCell::IndexItemCell(char const* name, CCSize const& size)
|
||||||
|
: ModListCell(name, size) {}
|
||||||
|
|
||||||
|
void IndexItemCell::onInfo(CCObject*) {
|
||||||
|
IndexItemInfoPopup::create(m_item, m_list)->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexItemCell* IndexItemCell::create(
|
||||||
|
ModListView* list, ModListDisplay display,
|
||||||
|
const char* key, CCSize const& size
|
||||||
|
) {
|
||||||
|
auto ret = new IndexItemCell(key, size);
|
||||||
|
if (ret && ret->init(list, display)) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
CC_SAFE_DELETE(ret);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndexItemCell::loadFromItem(IndexItemHandle item) {
|
||||||
|
this->setupInfo(item->info, true);
|
||||||
|
|
||||||
|
auto viewSpr = ButtonSprite::create(
|
||||||
|
"View", "bigFont.fnt", "GJ_button_01.png", .8f
|
||||||
|
);
|
||||||
|
viewSpr->setScale(.65f);
|
||||||
|
|
||||||
|
auto viewBtn = CCMenuItemSpriteExtra::create(
|
||||||
|
viewSpr, this, menu_selector(IndexItemCell::onInfo)
|
||||||
|
);
|
||||||
|
m_menu->addChild(viewBtn);
|
||||||
|
|
||||||
|
// if (hasCategories) {
|
||||||
|
// float x = m_height / 2 + logoSize / 2 + 13.f;
|
||||||
|
// for (auto& category : modobj->m_index.m_categories) {
|
||||||
|
// auto node = CategoryNode::create(category);
|
||||||
|
// node->setAnchorPoint({ .0f, .5f });
|
||||||
|
// node->setPositionX(x);
|
||||||
|
// node->setScale(.3f);
|
||||||
|
// if (hasDesc) {
|
||||||
|
// node->setPositionY(m_height / 2 - 23.f);
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// node->setPositionY(m_height / 2 - 17.f);
|
||||||
|
// }
|
||||||
|
// m_mainLayer->addChild(node);
|
||||||
|
|
||||||
|
// x += node->getScaledContentSize().width + 5.f;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
this->updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndexItemCell::updateState() {}
|
||||||
|
|
||||||
|
CCNode* IndexItemCell::createLogo(CCSize const& size) {
|
||||||
|
return geode::createIndexItemLogo(m_item, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidGeodeFileCell
|
||||||
|
|
||||||
|
InvalidGeodeFileCell::InvalidGeodeFileCell(const char* name, CCSize const& size)
|
||||||
|
: ModListCell(name, size) {}
|
||||||
|
|
||||||
|
void InvalidGeodeFileCell::onInfo(CCObject*) {
|
||||||
|
FLAlertLayer::create(
|
||||||
|
this, "Error Info",
|
||||||
|
m_info.m_reason,
|
||||||
|
"OK", "Remove file", 360.f
|
||||||
|
)->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InvalidGeodeFileCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) {
|
||||||
|
if (btn2) {
|
||||||
|
try {
|
||||||
|
if (ghc::filesystem::remove(m_info.m_path)) {
|
||||||
|
FLAlertLayer::create(
|
||||||
|
"File removed", "Removed <cy>" + m_info.m_path.string() + "</c>", "OK"
|
||||||
|
)->show();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
FLAlertLayer::create(
|
||||||
|
"Unable to remove file",
|
||||||
|
"Unable to remove <cy>" + m_info.m_path.string() + "</c>", "OK"
|
||||||
|
)->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (std::exception& e) {
|
||||||
|
FLAlertLayer::create(
|
||||||
|
"Unable to remove file",
|
||||||
|
"Unable to remove <cy>" + m_info.m_path.string() + "</c>: <cr>" +
|
||||||
|
std::string(e.what()) + "</c>",
|
||||||
|
"OK"
|
||||||
|
)->show();
|
||||||
|
}
|
||||||
|
(void)Loader::get()->refreshModsList();
|
||||||
|
m_list->refreshList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InvalidGeodeFileCell* InvalidGeodeFileCell::create(
|
||||||
|
ModListView* list, ModListDisplay display,
|
||||||
|
char const* key, CCSize const& size
|
||||||
|
) {
|
||||||
|
auto ret = new InvalidGeodeFileCell(key, size);
|
||||||
|
if (ret && ret->init(list, display)) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
CC_SAFE_DELETE(ret);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InvalidGeodeFileCell::loadFromInfo(InvalidGeodeFile const& info) {
|
||||||
|
m_info = info;
|
||||||
|
|
||||||
|
m_mainLayer->setVisible(true);
|
||||||
|
|
||||||
|
auto menu = CCMenu::create();
|
||||||
|
menu->setPosition(m_width - m_height, m_height / 2);
|
||||||
|
m_mainLayer->addChild(menu);
|
||||||
|
|
||||||
|
auto titleLabel = CCLabelBMFont::create("Failed to Load", "bigFont.fnt");
|
||||||
|
titleLabel->setAnchorPoint({ .0f, .5f });
|
||||||
|
titleLabel->setScale(.5f);
|
||||||
|
titleLabel->setPosition(m_height / 2, m_height / 2 + 7.f);
|
||||||
|
m_mainLayer->addChild(titleLabel);
|
||||||
|
|
||||||
|
auto pathLabel = CCLabelBMFont::create(
|
||||||
|
m_info.m_path.string().c_str(),
|
||||||
|
"chatFont.fnt"
|
||||||
|
);
|
||||||
|
pathLabel->setAnchorPoint({ .0f, .5f });
|
||||||
|
pathLabel->setScale(.43f);
|
||||||
|
pathLabel->setPosition(m_height / 2, m_height / 2 - 7.f);
|
||||||
|
pathLabel->setColor({ 255, 255, 0 });
|
||||||
|
m_mainLayer->addChild(pathLabel);
|
||||||
|
|
||||||
|
auto whySpr = ButtonSprite::create(
|
||||||
|
"Info", 0, 0, "bigFont.fnt", "GJ_button_01.png", 0, .8f
|
||||||
|
);
|
||||||
|
whySpr->setScale(.65f);
|
||||||
|
|
||||||
|
auto viewBtn = CCMenuItemSpriteExtra::create(
|
||||||
|
whySpr, this, menu_selector(InvalidGeodeFileCell::onInfo)
|
||||||
|
);
|
||||||
|
menu->addChild(viewBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InvalidGeodeFileCell::updateState() {}
|
||||||
|
|
||||||
|
CCNode* InvalidGeodeFileCell::createLogo(CCSize const& size) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
91
loader/src/ui/internal/list/ModListCell.hpp
Normal file
91
loader/src/ui/internal/list/ModListCell.hpp
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Geode/binding/TableViewCell.hpp>
|
||||||
|
#include <Geode/binding/FLAlertLayerProtocol.hpp>
|
||||||
|
#include <Geode/loader/Loader.hpp>
|
||||||
|
#include <Geode/loader/ModInfo.hpp>
|
||||||
|
#include <Geode/loader/Index.hpp>
|
||||||
|
|
||||||
|
USE_GEODE_NAMESPACE();
|
||||||
|
|
||||||
|
class ModListView;
|
||||||
|
enum class ModListDisplay;
|
||||||
|
|
||||||
|
class ModListCell : public TableViewCell {
|
||||||
|
protected:
|
||||||
|
ModListView* m_list;
|
||||||
|
CCMenu* m_menu;
|
||||||
|
CCMenuItemToggler* m_enableToggle = nullptr;
|
||||||
|
CCMenuItemSpriteExtra* m_unresolvedExMark;
|
||||||
|
ModListDisplay m_display;
|
||||||
|
|
||||||
|
ModListCell(char const* name, CCSize const& size);
|
||||||
|
bool init(ModListView* list, ModListDisplay display);
|
||||||
|
void setupInfo(ModInfo const& info, bool spaceForCategories);
|
||||||
|
void draw() override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void updateBGColor(int index);
|
||||||
|
virtual void updateState() = 0;
|
||||||
|
virtual CCNode* createLogo(CCSize const& size) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ModCell : public ModListCell {
|
||||||
|
protected:
|
||||||
|
Mod* m_mod;
|
||||||
|
|
||||||
|
ModCell(char const* name, CCSize const& size);
|
||||||
|
|
||||||
|
void onInfo(CCObject*);
|
||||||
|
void onEnable(CCObject*);
|
||||||
|
void onUnresolvedInfo(CCObject*);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static ModCell* create(
|
||||||
|
ModListView* list, ModListDisplay display,
|
||||||
|
const char* key, CCSize const& size
|
||||||
|
);
|
||||||
|
|
||||||
|
void loadFromMod(Mod* mod);
|
||||||
|
void updateState() override;
|
||||||
|
CCNode* createLogo(CCSize const& size) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IndexItemCell : public ModListCell {
|
||||||
|
protected:
|
||||||
|
IndexItemHandle m_item;
|
||||||
|
|
||||||
|
IndexItemCell(char const* name, CCSize const& size);
|
||||||
|
|
||||||
|
void onInfo(CCObject*);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static IndexItemCell* create(
|
||||||
|
ModListView* list, ModListDisplay display,
|
||||||
|
const char* key, CCSize const& size
|
||||||
|
);
|
||||||
|
|
||||||
|
void loadFromItem(IndexItemHandle item);
|
||||||
|
void updateState() override;
|
||||||
|
CCNode* createLogo(CCSize const& size) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InvalidGeodeFileCell : public ModListCell, public FLAlertLayerProtocol {
|
||||||
|
protected:
|
||||||
|
InvalidGeodeFile m_info;
|
||||||
|
|
||||||
|
InvalidGeodeFileCell(char const* name, CCSize const& size);
|
||||||
|
|
||||||
|
void onInfo(CCObject*);
|
||||||
|
void FLAlert_Clicked(FLAlertLayer*, bool btn2) override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static InvalidGeodeFileCell* create(
|
||||||
|
ModListView* list, ModListDisplay display,
|
||||||
|
const char* key, CCSize const& size
|
||||||
|
);
|
||||||
|
|
||||||
|
void loadFromInfo(InvalidGeodeFile const& file);
|
||||||
|
void updateState() override;
|
||||||
|
CCNode* createLogo(CCSize const& size) override;
|
||||||
|
};
|
|
@ -205,40 +205,6 @@ void ModListLayer::createSearchControl() {
|
||||||
this->addChild(m_searchInput);
|
this->addChild(m_searchInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModListLayer::indexUpdateProgress(
|
|
||||||
UpdateStatus status, std::string const& info, uint8_t percentage
|
|
||||||
) {
|
|
||||||
// if we have a check for updates button
|
|
||||||
// visible, disable it from being clicked
|
|
||||||
// again
|
|
||||||
if (m_checkForUpdatesBtn) {
|
|
||||||
m_checkForUpdatesBtn->setEnabled(false);
|
|
||||||
as<ButtonSprite*>(m_checkForUpdatesBtn->getNormalImage())->setString("Updating Index");
|
|
||||||
}
|
|
||||||
|
|
||||||
// if finished, refresh list
|
|
||||||
if (status == UpdateStatus::Finished) {
|
|
||||||
m_indexUpdateLabel->setVisible(false);
|
|
||||||
this->reloadList();
|
|
||||||
|
|
||||||
// make sure to release global instance
|
|
||||||
// and set it back to null
|
|
||||||
CC_SAFE_RELEASE_NULL(g_instance);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_indexUpdateLabel->setVisible(true);
|
|
||||||
m_indexUpdateLabel->setString(info.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status == UpdateStatus::Failed) {
|
|
||||||
FLAlertLayer::create("Error Updating Index", info, "OK")->show();
|
|
||||||
|
|
||||||
// make sure to release global instance
|
|
||||||
// and set it back to null
|
|
||||||
CC_SAFE_RELEASE(g_instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModListLayer::reloadList() {
|
void ModListLayer::reloadList() {
|
||||||
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
||||||
|
|
||||||
|
@ -249,25 +215,21 @@ void ModListLayer::reloadList() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new list
|
// create new list
|
||||||
m_query.m_searchFilter =
|
auto list = ModListView::create(g_tab, m_display);
|
||||||
m_searchInput && m_searchInput->getString() && strlen(m_searchInput->getString())
|
|
||||||
? std::optional<std::string>(m_searchInput->getString())
|
|
||||||
: std::nullopt;
|
|
||||||
auto list = ModListView::create(g_tab, m_expandedList, 358.f, 190.f, m_query);
|
|
||||||
list->setLayer(this);
|
list->setLayer(this);
|
||||||
|
|
||||||
// set list status
|
// set list status
|
||||||
auto status = list->getStatusAsString();
|
// auto status = list->getStatusAsString();
|
||||||
if (status.size()) {
|
// if (status.size()) {
|
||||||
m_listLabel->setVisible(true);
|
// m_listLabel->setVisible(true);
|
||||||
m_listLabel->setString(status.c_str());
|
// m_listLabel->setString(status.c_str());
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
m_listLabel->setVisible(false);
|
m_listLabel->setVisible(false);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// update index if needed
|
// update index if needed
|
||||||
if (g_tab == ModListType::Download && !Index::get()->isIndexUpdated()) {
|
if (g_tab == ModListType::Download && !Index::get()->isUpToDate()) {
|
||||||
m_listLabel->setString("Updating index...");
|
m_listLabel->setString("Updating index...");
|
||||||
if (!m_loadingCircle) {
|
if (!m_loadingCircle) {
|
||||||
m_loadingCircle = LoadingCircle::create();
|
m_loadingCircle = LoadingCircle::create();
|
||||||
|
@ -311,9 +273,9 @@ void ModListLayer::reloadList() {
|
||||||
|
|
||||||
// check if the user has searched something,
|
// check if the user has searched something,
|
||||||
// and show visual indicator if so
|
// and show visual indicator if so
|
||||||
auto hasQuery = m_query.m_searchFilter.has_value();
|
// auto hasQuery = m_query.m_searchFilter.has_value();
|
||||||
m_searchBtn->setVisible(!hasQuery);
|
// m_searchBtn->setVisible(!hasQuery);
|
||||||
m_searchClearBtn->setVisible(hasQuery);
|
// m_searchClearBtn->setVisible(hasQuery);
|
||||||
|
|
||||||
// add/remove "Check for Updates" button
|
// add/remove "Check for Updates" button
|
||||||
if (
|
if (
|
||||||
|
@ -321,7 +283,7 @@ void ModListLayer::reloadList() {
|
||||||
g_tab == ModListType::Installed &&
|
g_tab == ModListType::Installed &&
|
||||||
// check if index is updated, and if not
|
// check if index is updated, and if not
|
||||||
// add button if it doesn't exist yet
|
// add button if it doesn't exist yet
|
||||||
!Index::get()->isIndexUpdated()
|
!Index::get()->isUpToDate()
|
||||||
) {
|
) {
|
||||||
if (!m_checkForUpdatesBtn) {
|
if (!m_checkForUpdatesBtn) {
|
||||||
auto checkSpr = ButtonSprite::create("Check for Updates");
|
auto checkSpr = ButtonSprite::create("Check for Updates");
|
||||||
|
@ -351,11 +313,7 @@ void ModListLayer::onCheckForUpdates(CCObject*) {
|
||||||
g_instance->retain();
|
g_instance->retain();
|
||||||
|
|
||||||
// update index
|
// update index
|
||||||
Index::get()->updateIndex(
|
Index::get()->update();
|
||||||
[](UpdateStatus status, std::string const& info, uint8_t progress) -> void {
|
|
||||||
g_instance->indexUpdateProgress(status, info, progress);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModListLayer::textChanged(CCTextInputNode* input) {
|
void ModListLayer::textChanged(CCTextInputNode* input) {
|
||||||
|
@ -374,7 +332,9 @@ void ModListLayer::onReload(CCObject*) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModListLayer::onExpand(CCObject* sender) {
|
void ModListLayer::onExpand(CCObject* sender) {
|
||||||
m_expandedList = !static_cast<CCMenuItemToggler*>(sender)->isToggled();
|
m_display = static_cast<CCMenuItemToggler*>(sender)->isToggled() ?
|
||||||
|
ModListDisplay::Concise :
|
||||||
|
ModListDisplay::Expanded;
|
||||||
this->reloadList();
|
this->reloadList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,5 +403,5 @@ ModListLayer* ModListLayer::scene() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ModListLayer::~ModListLayer() {
|
ModListLayer::~ModListLayer() {
|
||||||
removeAllChildrenWithCleanup(true);
|
this->removeAllChildrenWithCleanup(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,8 @@ protected:
|
||||||
CCNode* m_searchBG = nullptr;
|
CCNode* m_searchBG = nullptr;
|
||||||
CCTextInputNode* m_searchInput = nullptr;
|
CCTextInputNode* m_searchInput = nullptr;
|
||||||
LoadingCircle* m_loadingCircle = nullptr;
|
LoadingCircle* m_loadingCircle = nullptr;
|
||||||
ModListQuery m_query;
|
|
||||||
CCMenuItemSpriteExtra* m_filterBtn;
|
CCMenuItemSpriteExtra* m_filterBtn;
|
||||||
bool m_expandedList = false;
|
ModListDisplay m_display = ModListDisplay::Concise;
|
||||||
|
|
||||||
virtual ~ModListLayer();
|
virtual ~ModListLayer();
|
||||||
|
|
||||||
|
@ -43,7 +42,6 @@ protected:
|
||||||
void onFilters(CCObject*);
|
void onFilters(CCObject*);
|
||||||
void keyDown(enumKeyCodes) override;
|
void keyDown(enumKeyCodes) override;
|
||||||
void textChanged(CCTextInputNode*) override;
|
void textChanged(CCTextInputNode*) override;
|
||||||
void indexUpdateProgress(UpdateStatus status, std::string const& info, uint8_t percentage);
|
|
||||||
void createSearchControl();
|
void createSearchControl();
|
||||||
|
|
||||||
friend class SearchFilterPopup;
|
friend class SearchFilterPopup;
|
||||||
|
|
|
@ -1,369 +1,30 @@
|
||||||
#include "ModListView.hpp"
|
#include "ModListView.hpp"
|
||||||
|
|
||||||
#include "../info/CategoryNode.hpp"
|
#include "../info/CategoryNode.hpp"
|
||||||
#include "../info/ModInfoLayer.hpp"
|
|
||||||
#include "ModListLayer.hpp"
|
#include "ModListLayer.hpp"
|
||||||
|
#include "ModListCell.hpp"
|
||||||
|
|
||||||
#include <Geode/binding/ButtonSprite.hpp>
|
#include <Geode/binding/ButtonSprite.hpp>
|
||||||
#include <Geode/binding/CCMenuItemSpriteExtra.hpp>
|
#include <Geode/binding/CCMenuItemSpriteExtra.hpp>
|
||||||
#include <Geode/binding/StatsCell.hpp>
|
|
||||||
#include <Geode/binding/TableView.hpp>
|
#include <Geode/binding/TableView.hpp>
|
||||||
#include <Geode/binding/CCMenuItemToggler.hpp>
|
#include <Geode/binding/CCMenuItemToggler.hpp>
|
||||||
#include <Geode/loader/Mod.hpp>
|
#include <Geode/loader/Mod.hpp>
|
||||||
#include <Geode/utils/casts.hpp>
|
#include <Geode/utils/casts.hpp>
|
||||||
#include <Geode/utils/cocos.hpp>
|
#include <Geode/utils/cocos.hpp>
|
||||||
#include <Geode/utils/string.hpp>
|
#include <Geode/utils/string.hpp>
|
||||||
#include <Index.hpp>
|
#include <Geode/loader/Index.hpp>
|
||||||
#include <InternalLoader.hpp>
|
#include <InternalLoader.hpp>
|
||||||
|
|
||||||
template <class T>
|
void ModListView::updateAllStates(ModListCell* toggled) {
|
||||||
static bool tryOrAlert(Result<T> const& res, char const* title) {
|
for (auto cell : CCArrayExt<ModListCell>(m_tableView->m_cellArray)) {
|
||||||
if (!res) {
|
if (toggled != cell) {
|
||||||
FLAlertLayer::create(title, res.unwrapErr(), "OK")->show();
|
cell->updateState();
|
||||||
}
|
|
||||||
return res.isOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
ModCell::ModCell(char const* name, CCSize size) : TableViewCell(name, size.width, size.height) {}
|
|
||||||
|
|
||||||
void ModCell::draw() {
|
|
||||||
reinterpret_cast<StatsCell*>(this)->StatsCell::draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModCell::onFailedInfo(CCObject*) {
|
|
||||||
FLAlertLayer::create(
|
|
||||||
this, "Error Info",
|
|
||||||
m_obj->m_info.m_reason.size() ?
|
|
||||||
m_obj->m_info.m_reason :
|
|
||||||
"Unable to load mod",
|
|
||||||
"OK", "Remove file", 360.f
|
|
||||||
)->show();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) {
|
|
||||||
if (btn2) {
|
|
||||||
try {
|
|
||||||
if (ghc::filesystem::remove(m_obj->m_info.m_path)) {
|
|
||||||
FLAlertLayer::create(
|
|
||||||
"File removed", "Removed <cy>" + m_obj->m_info.m_path.string() + "</c>", "OK"
|
|
||||||
)->show();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
FLAlertLayer::create(
|
|
||||||
"Unable to remove file",
|
|
||||||
"Unable to remove <cy>" + m_obj->m_info.m_path.string() + "</c>", "OK"
|
|
||||||
)->show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (std::exception& e) {
|
|
||||||
FLAlertLayer::create(
|
|
||||||
"Unable to remove file",
|
|
||||||
"Unable to remove <cy>" + m_obj->m_info.m_path.string() + "</c>: <cr>" +
|
|
||||||
std::string(e.what()) + "</c>",
|
|
||||||
"OK"
|
|
||||||
)->show();
|
|
||||||
}
|
|
||||||
(void)Loader::get()->refreshModsList();
|
|
||||||
m_list->refreshList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModCell::setupUnloaded() {
|
|
||||||
m_mainLayer->setVisible(true);
|
|
||||||
|
|
||||||
auto menu = CCMenu::create();
|
|
||||||
menu->setPosition(m_width - m_height, m_height / 2);
|
|
||||||
m_mainLayer->addChild(menu);
|
|
||||||
|
|
||||||
auto titleLabel = CCLabelBMFont::create("Failed to Load", "bigFont.fnt");
|
|
||||||
titleLabel->setAnchorPoint({ .0f, .5f });
|
|
||||||
titleLabel->setScale(.5f);
|
|
||||||
titleLabel->setPosition(m_height / 2, m_height / 2 + 7.f);
|
|
||||||
m_mainLayer->addChild(titleLabel);
|
|
||||||
|
|
||||||
auto pathLabel = CCLabelBMFont::create(
|
|
||||||
m_obj->m_info.m_path.string().c_str(),
|
|
||||||
"chatFont.fnt"
|
|
||||||
);
|
|
||||||
pathLabel->setAnchorPoint({ .0f, .5f });
|
|
||||||
pathLabel->setScale(.43f);
|
|
||||||
pathLabel->setPosition(m_height / 2, m_height / 2 - 7.f);
|
|
||||||
pathLabel->setColor({ 255, 255, 0 });
|
|
||||||
m_mainLayer->addChild(pathLabel);
|
|
||||||
|
|
||||||
auto whySpr = ButtonSprite::create("Info", 0, 0, "bigFont.fnt", "GJ_button_01.png", 0, .8f);
|
|
||||||
whySpr->setScale(.65f);
|
|
||||||
|
|
||||||
auto viewBtn =
|
|
||||||
CCMenuItemSpriteExtra::create(whySpr, this, menu_selector(ModCell::onFailedInfo));
|
|
||||||
menu->addChild(viewBtn);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModCell::setupLoadedButtons() {
|
|
||||||
auto viewSpr = m_obj->m_mod->wasSuccesfullyLoaded()
|
|
||||||
? ButtonSprite::create("View", "bigFont.fnt", "GJ_button_01.png", .8f)
|
|
||||||
: ButtonSprite::create("Why", "bigFont.fnt", "GJ_button_06.png", .8f);
|
|
||||||
viewSpr->setScale(.65f);
|
|
||||||
|
|
||||||
auto viewBtn = CCMenuItemSpriteExtra::create(
|
|
||||||
viewSpr, this,
|
|
||||||
m_obj->m_mod->wasSuccesfullyLoaded() ? menu_selector(ModCell::onInfo)
|
|
||||||
: menu_selector(ModCell::onFailedInfo)
|
|
||||||
);
|
|
||||||
m_menu->addChild(viewBtn);
|
|
||||||
|
|
||||||
if (m_obj->m_mod->wasSuccesfullyLoaded() && m_obj->m_mod->supportsDisabling()) {
|
|
||||||
m_enableToggle = CCMenuItemToggler::createWithStandardSprites(
|
|
||||||
this, menu_selector(ModCell::onEnable), .7f
|
|
||||||
);
|
|
||||||
m_enableToggle->setPosition(-45.f, 0.f);
|
|
||||||
m_menu->addChild(m_enableToggle);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto exMark = CCSprite::createWithSpriteFrameName("exMark_001.png");
|
|
||||||
exMark->setScale(.5f);
|
|
||||||
|
|
||||||
m_unresolvedExMark =
|
|
||||||
CCMenuItemSpriteExtra::create(exMark, this, menu_selector(ModCell::onUnresolvedInfo));
|
|
||||||
m_unresolvedExMark->setPosition(-80.f, 0.f);
|
|
||||||
m_unresolvedExMark->setVisible(false);
|
|
||||||
m_menu->addChild(m_unresolvedExMark);
|
|
||||||
|
|
||||||
if (m_obj->m_mod->wasSuccesfullyLoaded()) {
|
|
||||||
if (Index::get()->isUpdateAvailableForItem(m_obj->m_mod->getID())) {
|
|
||||||
viewSpr->updateBGImage("GE_button_01.png"_spr);
|
|
||||||
|
|
||||||
auto updateIcon = CCSprite::createWithSpriteFrameName("updates-available.png"_spr);
|
|
||||||
updateIcon->setPosition(viewSpr->getContentSize() - CCSize { 2.f, 2.f });
|
|
||||||
updateIcon->setZOrder(99);
|
|
||||||
updateIcon->setScale(.5f);
|
|
||||||
viewSpr->addChild(updateIcon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModCell::setupIndexButtons() {
|
|
||||||
auto viewSpr = ButtonSprite::create("View", "bigFont.fnt", "GJ_button_01.png", .8f);
|
|
||||||
viewSpr->setScale(.65f);
|
|
||||||
|
|
||||||
auto viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(ModCell::onInfo));
|
|
||||||
m_menu->addChild(viewBtn);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModCell::loadFromObject(ModObject* modobj) {
|
|
||||||
m_obj = modobj;
|
|
||||||
|
|
||||||
if (modobj->m_type == ModObjectType::Unloaded) {
|
|
||||||
return this->setupUnloaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_mainLayer->setVisible(true);
|
|
||||||
m_backgroundLayer->setOpacity(255);
|
|
||||||
|
|
||||||
m_menu = CCMenu::create();
|
|
||||||
m_menu->setPosition(m_width - 40.f, m_height / 2);
|
|
||||||
m_mainLayer->addChild(m_menu);
|
|
||||||
|
|
||||||
auto logoSize = m_height / 1.5f;
|
|
||||||
|
|
||||||
auto logoSpr = ModInfoLayer::createLogoSpr(modobj, { logoSize, logoSize });
|
|
||||||
logoSpr->setPosition({ logoSize / 2 + 12.f, m_height / 2 });
|
|
||||||
m_mainLayer->addChild(logoSpr);
|
|
||||||
|
|
||||||
bool hasCategories = false;
|
|
||||||
|
|
||||||
ModInfo info;
|
|
||||||
switch (modobj->m_type) {
|
|
||||||
case ModObjectType::Mod: info = modobj->m_mod->getModInfo(); break;
|
|
||||||
|
|
||||||
case ModObjectType::Index:
|
|
||||||
info = modobj->m_index.m_info;
|
|
||||||
hasCategories = m_expanded && modobj->m_index.m_categories.size();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasDesc = m_expanded && info.m_description.has_value();
|
|
||||||
|
|
||||||
auto titleLabel = CCLabelBMFont::create(info.m_name.c_str(), "bigFont.fnt");
|
|
||||||
titleLabel->setAnchorPoint({ .0f, .5f });
|
|
||||||
titleLabel->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
|
|
||||||
if (hasDesc && hasCategories) {
|
|
||||||
titleLabel->setPositionY(m_height / 2 + 20.f);
|
|
||||||
}
|
|
||||||
else if (hasDesc || hasCategories) {
|
|
||||||
titleLabel->setPositionY(m_height / 2 + 15.f);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
titleLabel->setPositionY(m_height / 2 + 7.f);
|
|
||||||
}
|
|
||||||
titleLabel->limitLabelWidth(m_width / 2 - 40.f, .5f, .1f);
|
|
||||||
m_mainLayer->addChild(titleLabel);
|
|
||||||
|
|
||||||
auto versionLabel = CCLabelBMFont::create(info.m_version.toString().c_str(), "bigFont.fnt");
|
|
||||||
versionLabel->setAnchorPoint({ .0f, .5f });
|
|
||||||
versionLabel->setScale(.3f);
|
|
||||||
versionLabel->setPosition(
|
|
||||||
titleLabel->getPositionX() + titleLabel->getScaledContentSize().width + 5.f,
|
|
||||||
titleLabel->getPositionY() - 1.f
|
|
||||||
);
|
|
||||||
versionLabel->setColor({ 0, 255, 0 });
|
|
||||||
m_mainLayer->addChild(versionLabel);
|
|
||||||
|
|
||||||
auto creatorStr = "by " + info.m_developer;
|
|
||||||
auto creatorLabel = CCLabelBMFont::create(creatorStr.c_str(), "goldFont.fnt");
|
|
||||||
creatorLabel->setAnchorPoint({ .0f, .5f });
|
|
||||||
creatorLabel->setScale(.43f);
|
|
||||||
creatorLabel->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
|
|
||||||
if (hasDesc && hasCategories) {
|
|
||||||
creatorLabel->setPositionY(m_height / 2 + 7.5f);
|
|
||||||
}
|
|
||||||
else if (hasDesc || hasCategories) {
|
|
||||||
creatorLabel->setPositionY(m_height / 2);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
creatorLabel->setPositionY(m_height / 2 - 7.f);
|
|
||||||
}
|
|
||||||
m_mainLayer->addChild(creatorLabel);
|
|
||||||
|
|
||||||
if (hasDesc) {
|
|
||||||
auto descBG = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f });
|
|
||||||
descBG->setColor({ 0, 0, 0 });
|
|
||||||
descBG->setOpacity(90);
|
|
||||||
descBG->setContentSize({ m_width * 2, 60.f });
|
|
||||||
descBG->setAnchorPoint({ .0f, .5f });
|
|
||||||
descBG->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
|
|
||||||
if (hasCategories) {
|
|
||||||
descBG->setPositionY(m_height / 2 - 7.5f);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
descBG->setPositionY(m_height / 2 - 17.f);
|
|
||||||
}
|
|
||||||
descBG->setScale(.25f);
|
|
||||||
m_mainLayer->addChild(descBG);
|
|
||||||
|
|
||||||
auto descText = CCLabelBMFont::create(info.m_description.value().c_str(), "chatFont.fnt");
|
|
||||||
descText->setAnchorPoint({ .0f, .5f });
|
|
||||||
descText->setPosition(m_height / 2 + logoSize / 2 + 18.f, descBG->getPositionY());
|
|
||||||
descText->limitLabelWidth(m_width / 2 - 10.f, .5f, .1f);
|
|
||||||
m_mainLayer->addChild(descText);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasCategories) {
|
|
||||||
float x = m_height / 2 + logoSize / 2 + 13.f;
|
|
||||||
for (auto& category : modobj->m_index.m_categories) {
|
|
||||||
auto node = CategoryNode::create(category);
|
|
||||||
node->setAnchorPoint({ .0f, .5f });
|
|
||||||
node->setPositionX(x);
|
|
||||||
node->setScale(.3f);
|
|
||||||
if (hasDesc) {
|
|
||||||
node->setPositionY(m_height / 2 - 23.f);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
node->setPositionY(m_height / 2 - 17.f);
|
|
||||||
}
|
|
||||||
m_mainLayer->addChild(node);
|
|
||||||
|
|
||||||
x += node->getScaledContentSize().width + 5.f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (modobj->m_type) {
|
|
||||||
case ModObjectType::Mod: this->setupLoadedButtons(); break;
|
|
||||||
|
|
||||||
case ModObjectType::Index: this->setupIndexButtons(); break;
|
|
||||||
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
this->updateState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModCell::onInfo(CCObject*) {
|
|
||||||
ModInfoLayer::create(m_obj, m_list)->show();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModCell::updateBGColor(int index) {
|
|
||||||
if (index & 1) m_backgroundLayer->setColor(ccc3(0xc2, 0x72, 0x3e));
|
|
||||||
else m_backgroundLayer->setColor(ccc3(0xa1, 0x58, 0x2c));
|
|
||||||
m_backgroundLayer->setOpacity(0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModCell::onEnable(CCObject* pSender) {
|
|
||||||
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()) {
|
|
||||||
tryOrAlert(m_obj->m_mod->enable(), "Error enabling mod");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tryOrAlert(m_obj->m_mod->disable(), "Error disabling mod");
|
|
||||||
}
|
|
||||||
m_list->updateAllStates(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModCell::onUnresolvedInfo(CCObject* pSender) {
|
|
||||||
std::string info =
|
|
||||||
"This mod has the following "
|
|
||||||
"<cr>unresolved dependencies</c>: ";
|
|
||||||
for (auto const& dep : m_obj->m_mod->getUnresolvedDependencies()) {
|
|
||||||
info += "<cg>" + dep.m_id +
|
|
||||||
"</c> "
|
|
||||||
"(<cy>" +
|
|
||||||
dep.m_version.toString() + "</c>), ";
|
|
||||||
}
|
|
||||||
info.pop_back();
|
|
||||||
info.pop_back();
|
|
||||||
FLAlertLayer::create(nullptr, "Unresolved Dependencies", info, "OK", nullptr, 400.f)->show();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModCell::init(ModListView* list, bool expanded) {
|
|
||||||
m_list = list;
|
|
||||||
m_expanded = expanded;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModCell::updateState(bool invert) {
|
|
||||||
if (m_obj->m_type == ModObjectType::Mod) {
|
|
||||||
bool unresolved = m_obj->m_mod->hasUnresolvedDependencies();
|
|
||||||
if (m_enableToggle) {
|
|
||||||
m_enableToggle->toggle(m_obj->m_mod->isEnabled() ^ invert);
|
|
||||||
m_enableToggle->setEnabled(!unresolved);
|
|
||||||
m_enableToggle->m_offButton->setOpacity(unresolved ? 100 : 255);
|
|
||||||
m_enableToggle->m_offButton->setColor(unresolved ? cc3x(155) : cc3x(255));
|
|
||||||
m_enableToggle->m_onButton->setOpacity(unresolved ? 100 : 255);
|
|
||||||
m_enableToggle->m_onButton->setColor(unresolved ? cc3x(155) : cc3x(255));
|
|
||||||
}
|
|
||||||
m_unresolvedExMark->setVisible(unresolved);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ModCell* ModCell::create(ModListView* list, bool expanded, char const* key, CCSize size) {
|
|
||||||
auto pRet = new ModCell(key, size);
|
|
||||||
if (pRet && pRet->init(list, expanded)) {
|
|
||||||
return pRet;
|
|
||||||
}
|
|
||||||
CC_SAFE_DELETE(pRet);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModListView::updateAllStates(ModCell* toggled) {
|
|
||||||
for (auto cell : CCArrayExt<ModCell>(m_tableView->m_cellArray)) {
|
|
||||||
cell->updateState(toggled == cell);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModListView::setupList() {
|
void ModListView::setupList() {
|
||||||
m_itemSeparation = m_expandedList ? 60.f : 40.0f;
|
m_itemSeparation = m_display == ModListDisplay::Expanded ? 60.f : 40.0f;
|
||||||
|
|
||||||
if (!m_entries->count()) return;
|
if (!m_entries->count()) return;
|
||||||
|
|
||||||
|
@ -372,8 +33,10 @@ void ModListView::setupList() {
|
||||||
// fix content layer content size so the
|
// fix content layer content size so the
|
||||||
// list is properly aligned to the top
|
// list is properly aligned to the top
|
||||||
auto coverage = calculateChildCoverage(m_tableView->m_contentLayer);
|
auto coverage = calculateChildCoverage(m_tableView->m_contentLayer);
|
||||||
m_tableView->m_contentLayer->setContentSize({ -coverage.origin.x + coverage.size.width,
|
m_tableView->m_contentLayer->setContentSize({
|
||||||
-coverage.origin.y + coverage.size.height });
|
-coverage.origin.x + coverage.size.width,
|
||||||
|
-coverage.origin.y + coverage.size.height
|
||||||
|
});
|
||||||
|
|
||||||
if (m_entries->count() == 1) {
|
if (m_entries->count() == 1) {
|
||||||
m_tableView->moveToTopWithOffset(m_itemSeparation * 2);
|
m_tableView->moveToTopWithOffset(m_itemSeparation * 2);
|
||||||
|
@ -387,72 +50,21 @@ void ModListView::setupList() {
|
||||||
}
|
}
|
||||||
|
|
||||||
TableViewCell* ModListView::getListCell(char const* key) {
|
TableViewCell* ModListView::getListCell(char const* key) {
|
||||||
return ModCell::create(this, m_expandedList, key, { m_width, m_itemSeparation });
|
return ModCell::create(this, m_display, key, { m_width, m_itemSeparation });
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModListView::loadCell(TableViewCell* cell, unsigned int index) {
|
void ModListView::loadCell(TableViewCell* cell, unsigned int index) {
|
||||||
auto obj = as<ModObject*>(m_entries->objectAtIndex(index));
|
auto obj = m_entries->objectAtIndex(index);
|
||||||
as<ModCell*>(cell)->loadFromObject(obj);
|
if (auto mod = typeinfo_cast<ModObject*>(obj)) {
|
||||||
if (obj->m_type == ModObjectType::Mod) {
|
as<ModCell*>(cell)->loadFromMod(mod->mod);
|
||||||
if (obj->m_mod->wasSuccesfullyLoaded()) {
|
|
||||||
as<ModCell*>(cell)->updateBGColor(index);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cell->m_backgroundLayer->setOpacity(255);
|
|
||||||
cell->m_backgroundLayer->setColor({ 153, 0, 0 });
|
|
||||||
}
|
|
||||||
if (obj->m_mod->isUninstalled()) {
|
|
||||||
cell->m_backgroundLayer->setColor({ 50, 50, 50 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
if (auto mod = typeinfo_cast<IndexItemObject*>(obj)) {
|
||||||
as<ModCell*>(cell)->updateBGColor(index);
|
// as<IndexItemCell*>(cell)->loadFromItem(mod->item);
|
||||||
}
|
}
|
||||||
}
|
if (auto failed = typeinfo_cast<InvalidGeodeFileObject*>(obj)) {
|
||||||
|
as<InvalidGeodeFileCell*>(cell)->loadFromInfo(failed->info);
|
||||||
bool ModListView::filter(ModInfo const& info, ModListQuery const& query) {
|
|
||||||
// the UI for this functionality has been removed, however
|
|
||||||
// the code has been kept in case we want to add it back at
|
|
||||||
// some point.
|
|
||||||
|
|
||||||
if (!query.m_searchFilter) return true;
|
|
||||||
auto check = [query](SearchFlags flag, std::string const& name) -> bool {
|
|
||||||
if (!(query.m_searchFlags & flag)) return false;
|
|
||||||
return utils::string::contains(
|
|
||||||
utils::string::toLower(name), utils::string::toLower(query.m_searchFilter.value())
|
|
||||||
);
|
|
||||||
};
|
|
||||||
if (check(SearchFlag::Name, info.m_name)) return true;
|
|
||||||
if (check(SearchFlag::ID, info.m_id)) return true;
|
|
||||||
if (check(SearchFlag::Developer, info.m_developer)) return true;
|
|
||||||
if (check(SearchFlag::Description, info.m_description.value_or(""))) return true;
|
|
||||||
if (check(SearchFlag::Details, info.m_details.value_or(""))) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModListView::filter(IndexItem const& item, ModListQuery const& query) {
|
|
||||||
if (!query.m_showInstalled) {
|
|
||||||
if (Loader::get()->isModInstalled(item.m_info.m_id)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (query.m_categories.size()) {
|
as<ModListCell*>(cell)->updateBGColor(index);
|
||||||
bool found = false;
|
|
||||||
for (auto& cat : query.m_categories) {
|
|
||||||
if (item.m_categories.count(cat)) {
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (auto& plat : query.m_platforms) {
|
|
||||||
if (item.m_download.m_platforms.count(plat)) {
|
|
||||||
return filter(item.m_info, query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sortInstalledMods(std::vector<Mod*>& mods) {
|
static void sortInstalledMods(std::vector<Mod*>& mods) {
|
||||||
|
@ -462,16 +74,18 @@ static void sortInstalledMods(std::vector<Mod*>& mods) {
|
||||||
auto front = mods.front();
|
auto front = mods.front();
|
||||||
for (auto mod = mods.begin(); mod != mods.end(); mod++) {
|
for (auto mod = mods.begin(); mod != mods.end(); mod++) {
|
||||||
// move mods with updates to front
|
// move mods with updates to front
|
||||||
if (Index::get()->isUpdateAvailableForItem((*mod)->getID())) {
|
if (auto item = Index::get()->getItem(*mod)) {
|
||||||
// swap first object and updatable mod
|
if (Index::get()->updateAvailable(item)) {
|
||||||
// if the updatable mod is the first object,
|
// swap first object and updatable mod
|
||||||
// nothing changes
|
// if the updatable mod is the first object,
|
||||||
std::rotate(mods.begin(), mod, mod + 1);
|
// nothing changes
|
||||||
|
std::rotate(mods.begin(), mod, mod + 1);
|
||||||
|
|
||||||
// get next object at front for next mod
|
// get next object at front for next mod
|
||||||
// to sort
|
// to sort
|
||||||
frontIndex++;
|
frontIndex++;
|
||||||
front = mods[frontIndex];
|
front = mods[frontIndex];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -482,81 +96,50 @@ static std::vector<Mod*> sortedInstalledMods() {
|
||||||
return std::move(mods);
|
return std::move(mods);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModListView::init(
|
bool ModListView::init(CCArray* mods, ModListDisplay display) {
|
||||||
CCArray* mods, ModListType type, bool expanded, float width, float height, ModListQuery query
|
m_display = display;
|
||||||
) {
|
return CustomListView::init(mods, BoomListType::Default, 358.f, 190.f);
|
||||||
m_expandedList = expanded;
|
|
||||||
if (!mods) {
|
|
||||||
switch (type) {
|
|
||||||
case ModListType::Installed:
|
|
||||||
{
|
|
||||||
mods = CCArray::create();
|
|
||||||
// failed mods first
|
|
||||||
for (auto const& mod : Loader::get()->getFailedMods()) {
|
|
||||||
mods->addObject(new ModObject(mod));
|
|
||||||
}
|
|
||||||
// internal geode representation always at the top
|
|
||||||
auto imod = Loader::getInternalMod();
|
|
||||||
if (this->filter(imod->getModInfo(), query)) {
|
|
||||||
mods->addObject(new ModObject(imod));
|
|
||||||
}
|
|
||||||
// then other mods
|
|
||||||
for (auto const& mod : sortedInstalledMods()) {
|
|
||||||
// if the mod is no longer installed nor
|
|
||||||
// loaded, it's as good as not existing
|
|
||||||
// (because it doesn't)
|
|
||||||
if (mod->isUninstalled() && !mod->isLoaded()) continue;
|
|
||||||
if (this->filter(mod->getModInfo(), query)) {
|
|
||||||
mods->addObject(new ModObject(mod));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!mods->count()) {
|
|
||||||
m_status = Status::SearchEmpty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ModListType::Download:
|
|
||||||
{
|
|
||||||
mods = CCArray::create();
|
|
||||||
for (auto const& item : Index::get()->getItems()) {
|
|
||||||
if (this->filter(item, query)) {
|
|
||||||
mods->addObject(new ModObject(item));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!mods->count()) {
|
|
||||||
m_status = Status::NoModsFound;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ModListType::Featured:
|
|
||||||
{
|
|
||||||
mods = CCArray::create();
|
|
||||||
for (auto const& item : Index::get()->getFeaturedItems()) {
|
|
||||||
if (this->filter(item, query)) {
|
|
||||||
mods->addObject(new ModObject(item));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!mods->count()) {
|
|
||||||
m_status = Status::NoModsFound;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return CustomListView::init(mods, BoomListType::Default, width, height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ModListView* ModListView::create(
|
CCArray* ModListView::getModsForType(ModListType type) {
|
||||||
CCArray* mods, ModListType type, bool expanded, float width, float height,
|
auto mods = CCArray::create();
|
||||||
ModListQuery const& query
|
switch (type) {
|
||||||
) {
|
default:
|
||||||
|
case ModListType::Installed: {
|
||||||
|
// failed mods first
|
||||||
|
for (auto const& mod : Loader::get()->getFailedMods()) {
|
||||||
|
mods->addObject(new InvalidGeodeFileObject(mod));
|
||||||
|
}
|
||||||
|
// internal geode representation always at the top
|
||||||
|
auto imod = Loader::getInternalMod();
|
||||||
|
mods->addObject(new ModObject(imod));
|
||||||
|
|
||||||
|
// then other mods
|
||||||
|
for (auto const& mod : sortedInstalledMods()) {
|
||||||
|
// if the mod is no longer installed nor
|
||||||
|
// loaded, it's as good as not existing
|
||||||
|
// (because it doesn't)
|
||||||
|
if (mod->isUninstalled() && !mod->isLoaded()) continue;
|
||||||
|
mods->addObject(new ModObject(mod));
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case ModListType::Download: {
|
||||||
|
for (auto const& item : Index::get()->getItems()) {
|
||||||
|
mods->addObject(new IndexItemObject(item));
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case ModListType::Featured: {
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModListView* ModListView::create(CCArray* mods, ModListDisplay display) {
|
||||||
auto pRet = new ModListView;
|
auto pRet = new ModListView;
|
||||||
if (pRet) {
|
if (pRet) {
|
||||||
if (pRet->init(mods, type, expanded, width, height, query)) {
|
if (pRet->init(mods, display)) {
|
||||||
pRet->autorelease();
|
pRet->autorelease();
|
||||||
return pRet;
|
return pRet;
|
||||||
}
|
}
|
||||||
|
@ -565,24 +148,8 @@ ModListView* ModListView::create(
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModListView* ModListView::create(
|
ModListView* ModListView::create(ModListType type, ModListDisplay display) {
|
||||||
ModListType type, bool expanded, float width, float height, ModListQuery const& query
|
return ModListView::create(getModsForType(type), display);
|
||||||
) {
|
|
||||||
return ModListView::create(nullptr, type, expanded, width, height, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
ModListView::Status ModListView::getStatus() const {
|
|
||||||
return m_status;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ModListView::getStatusAsString() const {
|
|
||||||
switch (m_status) {
|
|
||||||
case Status::OK: return "";
|
|
||||||
case Status::Unknown: return "Unknown Issue";
|
|
||||||
case Status::NoModsFound: return "No Mods Found";
|
|
||||||
case Status::SearchEmpty: return "No Mods Match Search Query";
|
|
||||||
}
|
|
||||||
return "Unrecorded Status";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModListView::setLayer(ModListLayer* layer) {
|
void ModListView::setLayer(ModListLayer* layer) {
|
||||||
|
|
|
@ -9,143 +9,59 @@
|
||||||
|
|
||||||
USE_GEODE_NAMESPACE();
|
USE_GEODE_NAMESPACE();
|
||||||
|
|
||||||
struct ModListQuery;
|
|
||||||
|
|
||||||
enum class ModListType {
|
enum class ModListType {
|
||||||
Installed,
|
Installed,
|
||||||
Download,
|
Download,
|
||||||
Featured,
|
Featured,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ModObjectType {
|
enum class ModListDisplay {
|
||||||
Mod,
|
Concise,
|
||||||
Invalid,
|
Expanded,
|
||||||
Index,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class ModListLayer;
|
class ModListLayer;
|
||||||
|
class ModListCell;
|
||||||
|
|
||||||
|
// for passing invalid files as CCObject
|
||||||
|
struct InvalidGeodeFileObject : public CCObject {
|
||||||
|
InvalidGeodeFile info;
|
||||||
|
inline InvalidGeodeFileObject(InvalidGeodeFile const& info) : info(info) {
|
||||||
|
this->autorelease();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Wrapper so you can pass Mods in a CCArray
|
|
||||||
struct ModObject : public CCObject {
|
struct ModObject : public CCObject {
|
||||||
ModObjectType m_type;
|
Mod* mod;
|
||||||
std::variant<Mod*, InvalidGeodeFile, IndexItem> m_data;
|
inline ModObject(Mod* mod) : mod(mod) {
|
||||||
|
|
||||||
inline ModObject(Mod* mod)
|
|
||||||
: m_data(mod), m_type(ModObjectType::Mod)
|
|
||||||
{
|
|
||||||
this->autorelease();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline ModObject(InvalidGeodeFile const& info)
|
|
||||||
: m_data(info), m_type(ModObjectType::Invalid)
|
|
||||||
{
|
|
||||||
this->autorelease();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline ModObject(IndexItem const& index)
|
|
||||||
: m_data(index), m_type(ModObjectType::Index)
|
|
||||||
{
|
|
||||||
this->autorelease();
|
this->autorelease();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class ModListView;
|
struct IndexItemObject : public CCObject {
|
||||||
|
IndexItemHandle item;
|
||||||
class ModCell : public TableViewCell, public FLAlertLayerProtocol {
|
inline IndexItemObject(IndexItemHandle item) : item(item) {
|
||||||
protected:
|
this->autorelease();
|
||||||
ModListView* m_list;
|
}
|
||||||
ModObject* m_obj;
|
|
||||||
CCMenu* m_menu;
|
|
||||||
CCMenuItemToggler* m_enableToggle = nullptr;
|
|
||||||
CCMenuItemSpriteExtra* m_unresolvedExMark;
|
|
||||||
bool m_expanded;
|
|
||||||
|
|
||||||
ModCell(char const* name, CCSize size);
|
|
||||||
|
|
||||||
void draw() override;
|
|
||||||
void onInfo(CCObject*);
|
|
||||||
void onFailedInfo(CCObject*);
|
|
||||||
void onEnable(CCObject*);
|
|
||||||
void onUnresolvedInfo(CCObject*);
|
|
||||||
|
|
||||||
void setupUnloaded();
|
|
||||||
void setupLoadedButtons();
|
|
||||||
void setupIndexButtons();
|
|
||||||
|
|
||||||
void FLAlert_Clicked(FLAlertLayer*, bool btn2) override;
|
|
||||||
|
|
||||||
bool init(ModListView* list, bool expanded);
|
|
||||||
|
|
||||||
public:
|
|
||||||
void updateBGColor(int index);
|
|
||||||
void loadFromObject(ModObject*);
|
|
||||||
void updateState(bool invert = false);
|
|
||||||
|
|
||||||
static ModCell* create(ModListView* list, bool expanded, char const* key, CCSize size);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SearchFlag {
|
|
||||||
enum : int {
|
|
||||||
Name = 0b1,
|
|
||||||
ID = 0b10,
|
|
||||||
Developer = 0b100,
|
|
||||||
Credits = 0b1000,
|
|
||||||
Description = 0b10000,
|
|
||||||
Details = 0b100000,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
using SearchFlags = int;
|
|
||||||
|
|
||||||
static constexpr SearchFlags ALL_FLAGS = SearchFlag::Name | SearchFlag::ID | SearchFlag::Developer |
|
|
||||||
SearchFlag::Credits | SearchFlag::Description | SearchFlag::Details;
|
|
||||||
|
|
||||||
struct ModListQuery {
|
|
||||||
std::optional<std::string> m_searchFilter = std::nullopt;
|
|
||||||
int m_searchFlags = ALL_FLAGS;
|
|
||||||
bool m_showInstalled = false;
|
|
||||||
std::unordered_set<PlatformID> m_platforms { GEODE_PLATFORM_TARGET };
|
|
||||||
std::unordered_set<std::string> m_categories {};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class ModListView : public CustomListView {
|
class ModListView : public CustomListView {
|
||||||
protected:
|
protected:
|
||||||
enum class Status {
|
|
||||||
OK,
|
|
||||||
Unknown,
|
|
||||||
NoModsFound,
|
|
||||||
SearchEmpty,
|
|
||||||
};
|
|
||||||
|
|
||||||
Status m_status = Status::OK;
|
|
||||||
ModListLayer* m_layer = nullptr;
|
ModListLayer* m_layer = nullptr;
|
||||||
bool m_expandedList;
|
ModListDisplay m_display;
|
||||||
|
|
||||||
void setupList() override;
|
void setupList() override;
|
||||||
TableViewCell* getListCell(char const* key) override;
|
TableViewCell* getListCell(char const* key) override;
|
||||||
void loadCell(TableViewCell* cell, unsigned int index) override;
|
void loadCell(TableViewCell* cell, unsigned int index) override;
|
||||||
|
|
||||||
bool init(
|
bool init(CCArray* mods, ModListDisplay display);
|
||||||
CCArray* mods, ModListType type, bool expanded, float width, float height,
|
|
||||||
ModListQuery query
|
|
||||||
);
|
|
||||||
bool filter(ModInfo const& info, ModListQuery const& query);
|
|
||||||
bool filter(IndexItem const& item, ModListQuery const& query);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static ModListView* create(
|
static ModListView* create(CCArray* mods, ModListDisplay display);
|
||||||
CCArray* mods, ModListType type = ModListType::Installed, bool expanded = false,
|
static ModListView* create(ModListType type, ModListDisplay display);
|
||||||
float width = 358.f, float height = 220.f, ModListQuery const& query = ModListQuery()
|
static CCArray* getModsForType(ModListType type);
|
||||||
);
|
|
||||||
static ModListView* create(
|
|
||||||
ModListType type, bool expanded = false, float width = 358.f, float height = 220.f,
|
|
||||||
ModListQuery const& query = ModListQuery()
|
|
||||||
);
|
|
||||||
|
|
||||||
void updateAllStates(ModCell* toggled = nullptr);
|
void updateAllStates(ModListCell* except = nullptr);
|
||||||
void setLayer(ModListLayer* layer);
|
void setLayer(ModListLayer* layer);
|
||||||
void refreshList();
|
void refreshList();
|
||||||
|
|
||||||
Status getStatus() const;
|
|
||||||
std::string getStatusAsString() const;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "ModListView.hpp"
|
#include "ModListView.hpp"
|
||||||
|
|
||||||
#include <Geode/binding/GameToolbox.hpp>
|
#include <Geode/binding/GameToolbox.hpp>
|
||||||
|
#include <Geode/binding/CCMenuItemToggler.hpp>
|
||||||
#include <Geode/ui/SelectList.hpp>
|
#include <Geode/ui/SelectList.hpp>
|
||||||
|
|
||||||
bool SearchFilterPopup::setup(ModListLayer* layer, ModListType type) {
|
bool SearchFilterPopup::setup(ModListLayer* layer, ModListType type) {
|
||||||
|
@ -53,10 +54,10 @@ bool SearchFilterPopup::setup(ModListLayer* layer, ModListType type) {
|
||||||
|
|
||||||
pos = CCPoint { winSize.width / 2 - 140.f, winSize.height / 2 - 85.f };
|
pos = CCPoint { winSize.width / 2 - 140.f, winSize.height / 2 - 85.f };
|
||||||
|
|
||||||
this->addToggle(
|
// this->addToggle(
|
||||||
"Show Installed", menu_selector(SearchFilterPopup::onShowInstalled),
|
// "Show Installed", menu_selector(SearchFilterPopup::onShowInstalled),
|
||||||
layer->m_query.m_showInstalled, 0, pos
|
// layer->m_query.m_showInstalled, 0, pos
|
||||||
);
|
// );
|
||||||
|
|
||||||
// categories
|
// categories
|
||||||
|
|
||||||
|
@ -75,23 +76,23 @@ bool SearchFilterPopup::setup(ModListLayer* layer, ModListType type) {
|
||||||
|
|
||||||
pos = CCPoint { winSize.width / 2 + 30.f, winSize.height / 2 + 45.f };
|
pos = CCPoint { winSize.width / 2 + 30.f, winSize.height / 2 + 45.f };
|
||||||
|
|
||||||
for (auto& category : Index::get()->getCategories()) {
|
// for (auto& category : Index::get()->getCategories()) {
|
||||||
auto toggle = CCMenuItemToggler::createWithStandardSprites(
|
// auto toggle = CCMenuItemToggler::createWithStandardSprites(
|
||||||
this, menu_selector(SearchFilterPopup::onCategory), .5f
|
// this, menu_selector(SearchFilterPopup::onCategory), .5f
|
||||||
);
|
// );
|
||||||
toggle->toggle(m_modLayer->m_query.m_categories.count(category));
|
// toggle->toggle(m_modLayer->m_query.m_categories.count(category));
|
||||||
toggle->setPosition(pos - winSize / 2);
|
// toggle->setPosition(pos - winSize / 2);
|
||||||
toggle->setUserObject(CCString::create(category));
|
// toggle->setUserObject(CCString::create(category));
|
||||||
m_buttonMenu->addChild(toggle);
|
// m_buttonMenu->addChild(toggle);
|
||||||
|
|
||||||
auto label = CategoryNode::create(category, CategoryNodeStyle::Dot);
|
// auto label = CategoryNode::create(category, CategoryNodeStyle::Dot);
|
||||||
label->setScale(.4f);
|
// label->setScale(.4f);
|
||||||
label->setAnchorPoint({ .0f, .5f });
|
// label->setAnchorPoint({ .0f, .5f });
|
||||||
label->setPosition(pos.x + 10.f, pos.y);
|
// label->setPosition(pos.x + 10.f, pos.y);
|
||||||
m_mainLayer->addChild(label);
|
// m_mainLayer->addChild(label);
|
||||||
|
|
||||||
pos.y -= 22.5f;
|
// pos.y -= 22.5f;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -99,13 +100,13 @@ bool SearchFilterPopup::setup(ModListLayer* layer, ModListType type) {
|
||||||
void SearchFilterPopup::onCategory(CCObject* sender) {
|
void SearchFilterPopup::onCategory(CCObject* sender) {
|
||||||
try {
|
try {
|
||||||
auto toggle = static_cast<CCMenuItemToggler*>(sender);
|
auto toggle = static_cast<CCMenuItemToggler*>(sender);
|
||||||
auto category = static_cast<CCString*>(toggle->getUserObject())->getCString();
|
// auto category = static_cast<CCString*>(toggle->getUserObject())->getCString();
|
||||||
if (!toggle->isToggled()) {
|
// if (!toggle->isToggled()) {
|
||||||
m_modLayer->m_query.m_categories.insert(category);
|
// m_modLayer->m_query.m_categories.insert(category);
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
m_modLayer->m_query.m_categories.erase(category);
|
// m_modLayer->m_query.m_categories.erase(category);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
catch (...) {
|
catch (...) {
|
||||||
}
|
}
|
||||||
|
@ -113,7 +114,7 @@ void SearchFilterPopup::onCategory(CCObject* sender) {
|
||||||
|
|
||||||
void SearchFilterPopup::onShowInstalled(CCObject* sender) {
|
void SearchFilterPopup::onShowInstalled(CCObject* sender) {
|
||||||
auto toggle = static_cast<CCMenuItemToggler*>(sender);
|
auto toggle = static_cast<CCMenuItemToggler*>(sender);
|
||||||
m_modLayer->m_query.m_showInstalled = !toggle->isToggled();
|
// m_modLayer->m_query.m_showInstalled = !toggle->isToggled();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchFilterPopup::enable(CCMenuItemToggler* toggle, ModListType type) {
|
void SearchFilterPopup::enable(CCMenuItemToggler* toggle, ModListType type) {
|
||||||
|
@ -137,37 +138,39 @@ CCMenuItemToggler* SearchFilterPopup::addToggle(
|
||||||
}
|
}
|
||||||
|
|
||||||
CCMenuItemToggler* SearchFilterPopup::addSearchMatch(char const* title, int flag, CCPoint& pos) {
|
CCMenuItemToggler* SearchFilterPopup::addSearchMatch(char const* title, int flag, CCPoint& pos) {
|
||||||
return this->addToggle(
|
// return this->addToggle(
|
||||||
title, menu_selector(SearchFilterPopup::onSearchToggle),
|
// title, menu_selector(SearchFilterPopup::onSearchToggle),
|
||||||
m_modLayer->m_query.m_searchFlags & flag, flag, pos
|
// m_modLayer->m_query.m_searchFlags & flag, flag, pos
|
||||||
);
|
// );
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
CCMenuItemToggler* SearchFilterPopup::addPlatformToggle(
|
CCMenuItemToggler* SearchFilterPopup::addPlatformToggle(
|
||||||
char const* title, PlatformID id, CCPoint& pos
|
char const* title, PlatformID id, CCPoint& pos
|
||||||
) {
|
) {
|
||||||
return this->addToggle(
|
// return this->addToggle(
|
||||||
title, menu_selector(SearchFilterPopup::onPlatformToggle),
|
// title, menu_selector(SearchFilterPopup::onPlatformToggle),
|
||||||
m_modLayer->m_query.m_platforms.count(id), id.to<int>(), pos
|
// m_modLayer->m_query.m_platforms.count(id), id.to<int>(), pos
|
||||||
);
|
// );
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchFilterPopup::onSearchToggle(CCObject* sender) {
|
void SearchFilterPopup::onSearchToggle(CCObject* sender) {
|
||||||
if (static_cast<CCMenuItemToggler*>(sender)->isToggled()) {
|
// if (static_cast<CCMenuItemToggler*>(sender)->isToggled()) {
|
||||||
m_modLayer->m_query.m_searchFlags &= ~sender->getTag();
|
// m_modLayer->m_query.m_searchFlags &= ~sender->getTag();
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
m_modLayer->m_query.m_searchFlags |= sender->getTag();
|
// m_modLayer->m_query.m_searchFlags |= sender->getTag();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchFilterPopup::onPlatformToggle(CCObject* sender) {
|
void SearchFilterPopup::onPlatformToggle(CCObject* sender) {
|
||||||
if (static_cast<CCMenuItemToggler*>(sender)->isToggled()) {
|
// if (static_cast<CCMenuItemToggler*>(sender)->isToggled()) {
|
||||||
m_modLayer->m_query.m_platforms.erase(PlatformID::from(sender->getTag()));
|
// m_modLayer->m_query.m_platforms.erase(PlatformID::from(sender->getTag()));
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
m_modLayer->m_query.m_platforms.insert(PlatformID::from(sender->getTag()));
|
// m_modLayer->m_query.m_platforms.insert(PlatformID::from(sender->getTag()));
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchFilterPopup::onClose(CCObject* sender) {
|
void SearchFilterPopup::onClose(CCObject* sender) {
|
||||||
|
|
|
@ -28,6 +28,22 @@ Result<std::string> utils::file::readString(ghc::filesystem::path const& path) {
|
||||||
return Err("Unable to open file");
|
return Err("Unable to open file");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<nlohmann::json> utils::file::readJson(ghc::filesystem::path const& path) {
|
||||||
|
#if _WIN32
|
||||||
|
std::ifstream in(path.wstring(), std::ios::in | std::ios::binary);
|
||||||
|
#else
|
||||||
|
std::ifstream in(path.string(), std::ios::in | std::ios::binary);
|
||||||
|
#endif
|
||||||
|
if (in) {
|
||||||
|
try {
|
||||||
|
return Ok(nlohmann::json::parse(in));
|
||||||
|
} catch(std::exception const& e) {
|
||||||
|
return Err("Unable to parse JSON: " + std::string(e.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Err("Unable to open file");
|
||||||
|
}
|
||||||
|
|
||||||
Result<byte_array> utils::file::readBinary(ghc::filesystem::path const& path) {
|
Result<byte_array> utils::file::readBinary(ghc::filesystem::path const& path) {
|
||||||
#if _WIN32
|
#if _WIN32
|
||||||
std::ifstream in(path.wstring(), std::ios::in | std::ios::binary);
|
std::ifstream in(path.wstring(), std::ios::in | std::ios::binary);
|
||||||
|
|
Loading…
Reference in a new issue