mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-27 01:45:35 -05:00
improve index related ui
- hide install list behind a popup - remove android and ios filters - don't show not installable mods - allow quick popups to be completely cancelled by esc
This commit is contained in:
parent
3707418355
commit
73169fbf22
11 changed files with 191 additions and 41 deletions
|
@ -243,6 +243,12 @@ namespace geode {
|
|||
* Check if any of the mods on the index have updates available
|
||||
*/
|
||||
bool areUpdatesAvailable() const;
|
||||
/**
|
||||
* Checks if the mod and its required dependencies can be installed
|
||||
* @param item Item to get the list for
|
||||
* @returns Success if the mod and its required dependencies can be installed, an error otherwise
|
||||
*/
|
||||
Result<> canInstall(IndexItemHandle item) const;
|
||||
/**
|
||||
* Get the list of items needed to install this item (dependencies, etc.)
|
||||
* @param item Item to get the list for
|
||||
|
|
|
@ -96,4 +96,14 @@ namespace geode {
|
|||
char const* title, std::string const& content, char const* btn1, char const* btn2,
|
||||
float width, utils::MiniFunction<void(FLAlertLayer*, bool)> selected, bool doShow = true
|
||||
);
|
||||
|
||||
GEODE_DLL FLAlertLayer* createQuickPopup(
|
||||
char const* title, std::string const& content, char const* btn1, char const* btn2,
|
||||
utils::MiniFunction<void(FLAlertLayer*, bool)> selected, bool doShow, bool cancelledByEscape
|
||||
);
|
||||
|
||||
GEODE_DLL FLAlertLayer* createQuickPopup(
|
||||
char const* title, std::string const& content, char const* btn1, char const* btn2,
|
||||
float width, utils::MiniFunction<void(FLAlertLayer*, bool)> selected, bool doShow, bool cancelledByEscape
|
||||
);
|
||||
}
|
||||
|
|
|
@ -547,19 +547,18 @@ bool Index::areUpdatesAvailable() const {
|
|||
|
||||
// Item installation
|
||||
|
||||
Result<IndexInstallList> Index::getInstallList(IndexItemHandle item) const {
|
||||
Result<> Index::canInstall(IndexItemHandle item) const {
|
||||
if (!item->getAvailablePlatforms().count(GEODE_PLATFORM_TARGET)) {
|
||||
return Err("Mod is not available on {}", GEODE_PLATFORM_NAME);
|
||||
}
|
||||
|
||||
IndexInstallList list;
|
||||
list.target = item;
|
||||
// TODO: ui for picking recommended and suggested mods
|
||||
for (auto& dep : item->getMetadata().getDependencies()) {
|
||||
// if the dep is resolved, then all its dependencies must be installed
|
||||
// already in order for that to have happened
|
||||
if (dep.isResolved()) continue;
|
||||
|
||||
if (dep.importance != ModMetadata::Dependency::Importance::Required) continue;
|
||||
|
||||
// check if this dep is available in the index
|
||||
if (auto depItem = this->getItem(dep.id, dep.version)) {
|
||||
if (!depItem->getAvailablePlatforms().count(GEODE_PLATFORM_TARGET)) {
|
||||
|
@ -569,12 +568,55 @@ Result<IndexInstallList> Index::getInstallList(IndexItemHandle item) const {
|
|||
);
|
||||
}
|
||||
// recursively add dependencies
|
||||
GEODE_UNWRAP_INTO(auto deps, this->canInstall(depItem));
|
||||
}
|
||||
// otherwise user must get this dependency manually from somewhere
|
||||
else {
|
||||
return Err(
|
||||
"Dependency {} version {} not found in the index! Likely "
|
||||
"reason is that the version of the dependency this mod "
|
||||
"depends on is not available. Please let the developer "
|
||||
"of the mod ({}) know!",
|
||||
dep.id, dep.version.toString(), item->getMetadata().getDeveloper()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<IndexInstallList> Index::getInstallList(IndexItemHandle item) const {
|
||||
if (!item->getAvailablePlatforms().count(GEODE_PLATFORM_TARGET)) {
|
||||
return Err("Mod is not available on {}", GEODE_PLATFORM_NAME);
|
||||
}
|
||||
|
||||
IndexInstallList list;
|
||||
list.target = item;
|
||||
for (auto& dep : item->getMetadata().getDependencies()) {
|
||||
// if the dep is resolved, then all its dependencies must be installed
|
||||
// already in order for that to have happened
|
||||
if (dep.isResolved()) continue;
|
||||
|
||||
if (dep.importance == ModMetadata::Dependency::Importance::Suggested) continue;
|
||||
|
||||
// check if this dep is available in the index
|
||||
if (auto depItem = this->getItem(dep.id, dep.version)) {
|
||||
if (!depItem->getAvailablePlatforms().count(GEODE_PLATFORM_TARGET)) {
|
||||
// it's fine to not install optional dependencies
|
||||
if (dep.importance != ModMetadata::Dependency::Importance::Required) continue;
|
||||
return Err(
|
||||
"Dependency {} is not available on {}",
|
||||
dep.id, GEODE_PLATFORM_NAME
|
||||
);
|
||||
}
|
||||
// recursively add dependencies
|
||||
GEODE_UNWRAP_INTO(auto deps, this->getInstallList(depItem));
|
||||
ranges::push(list.list, deps.list);
|
||||
}
|
||||
// otherwise user must get this dependency manually from somewhere
|
||||
// else
|
||||
else {
|
||||
// it's fine to not install optional dependencies
|
||||
if (dep.importance != ModMetadata::Dependency::Importance::Required) continue;
|
||||
return Err(
|
||||
"Dependency {} version {} not found in the index! Likely "
|
||||
"reason is that the version of the dependency this mod "
|
||||
|
|
|
@ -750,20 +750,47 @@ void IndexItemInfoPopup::onInstallProgress(ModInstallEvent* event) {
|
|||
}
|
||||
|
||||
void IndexItemInfoPopup::onInstall(CCObject*) {
|
||||
InstallListPopup::create(m_item, [&](IndexInstallList const& list) {
|
||||
if (m_latestVersionLabel) {
|
||||
m_latestVersionLabel->setVisible(false);
|
||||
}
|
||||
this->setInstallStatus(UpdateProgress(0, "Starting install"));
|
||||
createQuickPopup(
|
||||
"Confirm Install",
|
||||
"Installing this mod requires a few other mods to be installed. "
|
||||
"Would you like to continue with <cy>recommended settings</c> or "
|
||||
"<cb>customize</c> which mods to install?",
|
||||
"Recommended", "Customize", 320.f,
|
||||
[&](FLAlertLayer*, bool btn2) {
|
||||
if (!btn2) {
|
||||
auto canInstall = Index::get()->canInstall(m_item);
|
||||
if (!canInstall) {
|
||||
FLAlertLayer::create(
|
||||
"Unable to Install",
|
||||
canInstall.unwrapErr(),
|
||||
"OK"
|
||||
)->show();
|
||||
return;
|
||||
}
|
||||
this->preInstall();
|
||||
Index::get()->install(m_item);
|
||||
}
|
||||
else {
|
||||
InstallListPopup::create(m_item, [&](IndexInstallList const& list) {
|
||||
this->preInstall();
|
||||
Index::get()->install(list);
|
||||
})->show();
|
||||
}
|
||||
}, true, true
|
||||
);
|
||||
}
|
||||
|
||||
m_installBtn->setTarget(
|
||||
this, menu_selector(IndexItemInfoPopup::onCancel)
|
||||
);
|
||||
m_installBtnSpr->setString("Cancel");
|
||||
m_installBtnSpr->setBG("GJ_button_06.png", false);
|
||||
void IndexItemInfoPopup::preInstall() {
|
||||
if (m_latestVersionLabel) {
|
||||
m_latestVersionLabel->setVisible(false);
|
||||
}
|
||||
this->setInstallStatus(UpdateProgress(0, "Starting install"));
|
||||
|
||||
Index::get()->install(list);
|
||||
})->show();
|
||||
m_installBtn->setTarget(
|
||||
this, menu_selector(IndexItemInfoPopup::onCancel)
|
||||
);
|
||||
m_installBtnSpr->setString("Cancel");
|
||||
m_installBtnSpr->setBG("GJ_button_06.png", false);
|
||||
}
|
||||
|
||||
void IndexItemInfoPopup::onCancel(CCObject*) {
|
||||
|
|
|
@ -100,6 +100,8 @@ protected:
|
|||
void onInstall(CCObject*);
|
||||
void onCancel(CCObject*);
|
||||
|
||||
void preInstall();
|
||||
|
||||
CCNode* createLogo(CCSize const& size) override;
|
||||
ModMetadata getMetadata() const override;
|
||||
|
||||
|
|
|
@ -229,7 +229,7 @@ bool IndexItemInstallListCell::init(
|
|||
|
||||
if (importance != ModMetadata::Dependency::Importance::Required) {
|
||||
message->setCString("N/A (Optional)");
|
||||
message->setColor({ 120, 15, 15 });
|
||||
message->setColor({ 163, 24, 24 });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -297,7 +297,7 @@ bool UnknownInstallListCell::init(
|
|||
message->setColor({ 240, 31, 31 });
|
||||
if (optional) {
|
||||
message->setCString("Missing (Optional)");
|
||||
message->setColor({ 120, 15, 15 });
|
||||
message->setColor({ 163, 24, 24 });
|
||||
}
|
||||
this->addChild(message);
|
||||
return true;
|
||||
|
|
|
@ -97,8 +97,7 @@ static std::optional<int> queryMatch(ModListQuery const& query, Mod* mod) {
|
|||
}
|
||||
|
||||
static std::optional<int> queryMatch(ModListQuery const& query, IndexItemHandle item) {
|
||||
// if no force visibility was provided and item is already installed, don't
|
||||
// show it
|
||||
// if no force visibility was provided and item is already installed, don't show it
|
||||
if (!query.forceVisibility && Loader::get()->isModInstalled(item->getMetadata().getID())) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
@ -114,6 +113,16 @@ static std::optional<int> queryMatch(ModListQuery const& query, IndexItemHandle
|
|||
})) {
|
||||
return std::nullopt;
|
||||
}
|
||||
// if no force visibility was provided and item is already installed, don't show it
|
||||
auto canInstall = Index::get()->canInstall(item);
|
||||
if (!query.forceInvalid && !canInstall) {
|
||||
log::warn(
|
||||
"Removing {} from the list because it cannot be installed: {}",
|
||||
item->getMetadata().getID(),
|
||||
canInstall.unwrapErr()
|
||||
);
|
||||
return std::nullopt;
|
||||
}
|
||||
// otherwise match keywords
|
||||
if (auto match = queryMatchKeywords(query, item->getMetadata())) {
|
||||
auto weighted = match.value();
|
||||
|
@ -190,7 +199,8 @@ CCArray* ModListLayer::createModCells(ModListType type, ModListQuery const& quer
|
|||
// sort the mods by match score
|
||||
std::multimap<int, IndexItemHandle> sorted;
|
||||
|
||||
for (auto const& item : Index::get()->getItems()) {
|
||||
auto index = Index::get();
|
||||
for (auto const& item : index->getItems()) {
|
||||
if (auto match = queryMatch(query, item)) {
|
||||
sorted.insert({ match.value(), item });
|
||||
}
|
||||
|
|
|
@ -25,10 +25,15 @@ struct ModListQuery {
|
|||
*/
|
||||
std::optional<std::string> keywords;
|
||||
/**
|
||||
* Force mods to be shown on the list unless they explicitly mismatch some
|
||||
* Force already installed mods to be shown on the list unless they explicitly mismatch some
|
||||
* tags (used to show installed mods on index)
|
||||
*/
|
||||
bool forceVisibility;
|
||||
/**
|
||||
* Force not installable mods to be shown on the list unless they explicitly mismatch some
|
||||
* tags (used to show installed mods on index)
|
||||
*/
|
||||
bool forceInvalid;
|
||||
/**
|
||||
* Empty means current platform
|
||||
*/
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
|
||||
#include <Geode/binding/GameToolbox.hpp>
|
||||
#include <Geode/binding/CCMenuItemToggler.hpp>
|
||||
#include <Geode/ui/SelectList.hpp>
|
||||
|
||||
// re-add when we actually add the platforms
|
||||
const float iosAndAndroidSize = 45.f;
|
||||
|
||||
bool SearchFilterPopup::setup(ModListLayer* layer, ModListType type) {
|
||||
m_noElasticity = true;
|
||||
|
@ -14,66 +16,77 @@ bool SearchFilterPopup::setup(ModListLayer* layer, ModListType type) {
|
|||
this->setTitle("Search Filters");
|
||||
|
||||
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
||||
auto pos = CCPoint { winSize.width / 2 - 140.f, winSize.height / 2 + 45.f };
|
||||
auto pos = CCPoint { winSize.width / 2 - 140.f, winSize.height / 2 + 45.f - iosAndAndroidSize * 0.25f };
|
||||
|
||||
// platforms
|
||||
|
||||
auto platformTitle = CCLabelBMFont::create("Platforms", "goldFont.fnt");
|
||||
platformTitle->setPosition(winSize.width / 2 - 85.f, winSize.height / 2 + 75.f);
|
||||
platformTitle->setAnchorPoint({ 0.5f, 1.f });
|
||||
platformTitle->setPosition(winSize.width / 2 - 85.f, winSize.height / 2 + 81.5f - iosAndAndroidSize * 0.25f);
|
||||
platformTitle->setScale(.5f);
|
||||
m_mainLayer->addChild(platformTitle);
|
||||
|
||||
auto platformBG = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f });
|
||||
platformBG->setColor({ 0, 0, 0 });
|
||||
platformBG->setOpacity(90);
|
||||
platformBG->setContentSize({ 290.f, 205.f });
|
||||
platformBG->setPosition(winSize.width / 2 - 85.f, winSize.height / 2 + 11.f);
|
||||
platformBG->setContentSize({ 290.f, 205.f - iosAndAndroidSize * 2.f });
|
||||
platformBG->setAnchorPoint({ 0.5f, 1.f });
|
||||
platformBG->setPosition(winSize.width / 2 - 85.f, winSize.height / 2 + 62.25f - iosAndAndroidSize * 0.25f);
|
||||
platformBG->setScale(.5f);
|
||||
m_mainLayer->addChild(platformBG);
|
||||
|
||||
this->enable(this->addPlatformToggle("Windows", PlatformID::Windows, pos), type);
|
||||
this->enable(this->addPlatformToggle("macOS", PlatformID::MacOS, pos), type);
|
||||
this->enable(this->addPlatformToggle("IOS", PlatformID::iOS, pos), type);
|
||||
this->enable(this->addPlatformToggle("Android", PlatformID::Android, pos), type);
|
||||
//this->enable(this->addPlatformToggle("IOS", PlatformID::iOS, pos), type);
|
||||
//this->enable(this->addPlatformToggle("Android", PlatformID::Android, pos), type);
|
||||
|
||||
// show installed
|
||||
|
||||
auto installedTitle = CCLabelBMFont::create("Other", "goldFont.fnt");
|
||||
installedTitle->setPosition(winSize.width / 2 - 85.f, winSize.height / 2 - 57.f);
|
||||
installedTitle->setAnchorPoint({ 0.5f, 1.f });
|
||||
installedTitle->setPosition(winSize.width / 2 - 85.f, winSize.height / 2 - 50.5f + iosAndAndroidSize - iosAndAndroidSize * 0.25f);
|
||||
installedTitle->setScale(.5f);
|
||||
m_mainLayer->addChild(installedTitle);
|
||||
|
||||
auto installedBG = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f });
|
||||
installedBG->setColor({ 0, 0, 0 });
|
||||
installedBG->setOpacity(90);
|
||||
installedBG->setContentSize({ 290.f, 65.f });
|
||||
installedBG->setPosition(winSize.width / 2 - 85.f, winSize.height / 2 - 85.f);
|
||||
installedBG->setContentSize({ 290.f, 110.f });
|
||||
installedBG->setAnchorPoint({ 0.5f, 1.f });
|
||||
installedBG->setPosition(winSize.width / 2 - 85.f, winSize.height / 2 - 68.75f + iosAndAndroidSize - iosAndAndroidSize * 0.25f);
|
||||
installedBG->setScale(.5f);
|
||||
m_mainLayer->addChild(installedBG);
|
||||
|
||||
pos = CCPoint { winSize.width / 2 - 140.f, winSize.height / 2 - 85.f };
|
||||
pos = CCPoint { winSize.width / 2 - 140.f, winSize.height / 2 - 85.f + iosAndAndroidSize - iosAndAndroidSize * 0.25f };
|
||||
|
||||
this->addToggle(
|
||||
"Show Installed", menu_selector(SearchFilterPopup::onShowInstalled),
|
||||
m_modLayer->getQuery().forceVisibility, 0, pos
|
||||
);
|
||||
|
||||
this->addToggle(
|
||||
"Show Invalid", menu_selector(SearchFilterPopup::onShowInvalid),
|
||||
m_modLayer->getQuery().forceInvalid, 1, pos
|
||||
);
|
||||
|
||||
// tags
|
||||
|
||||
auto tagsTitle = CCLabelBMFont::create("Tags", "goldFont.fnt");
|
||||
tagsTitle->setPosition(winSize.width / 2 + 85.f, winSize.height / 2 + 75.f);
|
||||
tagsTitle->setAnchorPoint({ 0.5f, 1.f });
|
||||
tagsTitle->setPosition(winSize.width / 2 + 85.f, winSize.height / 2 + 81.5f - iosAndAndroidSize * 0.25f);
|
||||
tagsTitle->setScale(.5f);
|
||||
m_mainLayer->addChild(tagsTitle);
|
||||
|
||||
auto tagsBG = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f });
|
||||
tagsBG->setColor({ 0, 0, 0 });
|
||||
tagsBG->setOpacity(90);
|
||||
tagsBG->setContentSize({ 290.f, 328.f });
|
||||
tagsBG->setPosition(winSize.width / 2 + 85.f, winSize.height / 2 - 19.5f);
|
||||
tagsBG->setContentSize({ 290.f, 328.f - iosAndAndroidSize });
|
||||
tagsBG->setAnchorPoint({ 0.5f, 1.f });
|
||||
tagsBG->setPosition(winSize.width / 2 + 85.f, winSize.height / 2 + 62.5f - iosAndAndroidSize * 0.25f);
|
||||
tagsBG->setScale(.5f);
|
||||
m_mainLayer->addChild(tagsBG);
|
||||
|
||||
pos = CCPoint { winSize.width / 2 + 30.f, winSize.height / 2 + 45.f };
|
||||
pos = CCPoint { winSize.width / 2 + 30.f, winSize.height / 2 + 45.f - iosAndAndroidSize * 0.25f };
|
||||
|
||||
for (auto& tag : Index::get()->getTags()) {
|
||||
auto toggle = CCMenuItemToggler::createWithStandardSprites(
|
||||
|
@ -116,6 +129,11 @@ void SearchFilterPopup::onShowInstalled(CCObject* sender) {
|
|||
m_modLayer->getQuery().forceVisibility = !toggle->isToggled();
|
||||
}
|
||||
|
||||
void SearchFilterPopup::onShowInvalid(CCObject* sender) {
|
||||
auto toggle = static_cast<CCMenuItemToggler*>(sender);
|
||||
m_modLayer->getQuery().forceInvalid = !toggle->isToggled();
|
||||
}
|
||||
|
||||
void SearchFilterPopup::enable(CCMenuItemToggler* toggle, ModListType type) {
|
||||
if (type == ModListType::Installed) {
|
||||
toggle->setEnabled(false);
|
||||
|
@ -162,7 +180,7 @@ void SearchFilterPopup::onClose(CCObject* sender) {
|
|||
|
||||
SearchFilterPopup* SearchFilterPopup::create(ModListLayer* layer, ModListType type) {
|
||||
auto ret = new SearchFilterPopup();
|
||||
if (ret && ret->init(350.f, 240.f, layer, type)) {
|
||||
if (ret && ret->init(350.f, 240.f - iosAndAndroidSize * 0.5f, layer, type)) {
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ protected:
|
|||
|
||||
void onPlatformToggle(CCObject*);
|
||||
void onShowInstalled(CCObject*);
|
||||
void onShowInvalid(CCObject*);
|
||||
void onTag(CCObject*);
|
||||
|
||||
void enable(CCMenuItemToggler* toggle, ModListType type);
|
||||
|
|
|
@ -5,8 +5,18 @@ using namespace geode::prelude;
|
|||
class QuickPopup : public FLAlertLayer, public FLAlertLayerProtocol {
|
||||
protected:
|
||||
MiniFunction<void(FLAlertLayer*, bool)> m_selected;
|
||||
bool m_cancelledByEscape;
|
||||
bool m_usedEscape = false;
|
||||
|
||||
void keyBackClicked() override {
|
||||
m_usedEscape = true;
|
||||
FLAlertLayer::keyBackClicked();
|
||||
}
|
||||
|
||||
void FLAlert_Clicked(FLAlertLayer* layer, bool btn2) override {
|
||||
if (m_cancelledByEscape && m_usedEscape) {
|
||||
return;
|
||||
}
|
||||
if (m_selected) {
|
||||
m_selected(layer, btn2);
|
||||
}
|
||||
|
@ -15,10 +25,11 @@ protected:
|
|||
public:
|
||||
static QuickPopup* create(
|
||||
char const* title, std::string const& content, char const* btn1, char const* btn2,
|
||||
float width, MiniFunction<void(FLAlertLayer*, bool)> selected
|
||||
float width, MiniFunction<void(FLAlertLayer*, bool)> selected, bool cancelledByEscape
|
||||
) {
|
||||
auto inst = new QuickPopup;
|
||||
inst->m_selected = selected;
|
||||
inst->m_cancelledByEscape = cancelledByEscape;
|
||||
if (inst && inst->init(inst, title, content, btn1, btn2, width, false, .0f)) {
|
||||
inst->autorelease();
|
||||
return inst;
|
||||
|
@ -32,7 +43,7 @@ FLAlertLayer* geode::createQuickPopup(
|
|||
char const* title, std::string const& content, char const* btn1, char const* btn2, float width,
|
||||
MiniFunction<void(FLAlertLayer*, bool)> selected, bool doShow
|
||||
) {
|
||||
auto ret = QuickPopup::create(title, content, btn1, btn2, width, selected);
|
||||
auto ret = QuickPopup::create(title, content, btn1, btn2, width, selected, false);
|
||||
if (doShow) {
|
||||
ret->show();
|
||||
}
|
||||
|
@ -45,3 +56,21 @@ FLAlertLayer* geode::createQuickPopup(
|
|||
) {
|
||||
return createQuickPopup(title, content, btn1, btn2, 350.f, selected, doShow);
|
||||
}
|
||||
|
||||
FLAlertLayer* geode::createQuickPopup(
|
||||
char const* title, std::string const& content, char const* btn1, char const* btn2, float width,
|
||||
MiniFunction<void(FLAlertLayer*, bool)> selected, bool doShow, bool cancelledByEscape
|
||||
) {
|
||||
auto ret = QuickPopup::create(title, content, btn1, btn2, width, selected, cancelledByEscape);
|
||||
if (doShow) {
|
||||
ret->show();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
FLAlertLayer* geode::createQuickPopup(
|
||||
char const* title, std::string const& content, char const* btn1, char const* btn2,
|
||||
MiniFunction<void(FLAlertLayer*, bool)> selected, bool doShow, bool cancelledByEscape
|
||||
) {
|
||||
return createQuickPopup(title, content, btn1, btn2, 350.f, selected, doShow, cancelledByEscape);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue