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:
ConfiG 2023-08-13 14:00:18 +03:00
parent 3707418355
commit 73169fbf22
No known key found for this signature in database
GPG key ID: 44DA1983F524C11B
11 changed files with 191 additions and 41 deletions

View file

@ -243,6 +243,12 @@ namespace geode {
* Check if any of the mods on the index have updates available * Check if any of the mods on the index have updates available
*/ */
bool areUpdatesAvailable() const; 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.) * Get the list of items needed to install this item (dependencies, etc.)
* @param item Item to get the list for * @param item Item to get the list for

View file

@ -96,4 +96,14 @@ namespace geode {
char const* title, std::string const& content, char const* btn1, char const* btn2, char const* title, std::string const& content, char const* btn1, char const* btn2,
float width, utils::MiniFunction<void(FLAlertLayer*, bool)> selected, bool doShow = true 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
);
} }

View file

@ -547,19 +547,18 @@ bool Index::areUpdatesAvailable() const {
// Item installation // Item installation
Result<IndexInstallList> Index::getInstallList(IndexItemHandle item) const { Result<> Index::canInstall(IndexItemHandle item) const {
if (!item->getAvailablePlatforms().count(GEODE_PLATFORM_TARGET)) { if (!item->getAvailablePlatforms().count(GEODE_PLATFORM_TARGET)) {
return Err("Mod is not available on {}", GEODE_PLATFORM_NAME); 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()) { for (auto& dep : item->getMetadata().getDependencies()) {
// if the dep is resolved, then all its dependencies must be installed // if the dep is resolved, then all its dependencies must be installed
// already in order for that to have happened // already in order for that to have happened
if (dep.isResolved()) continue; if (dep.isResolved()) continue;
if (dep.importance != ModMetadata::Dependency::Importance::Required) continue;
// check if this dep is available in the index // check if this dep is available in the index
if (auto depItem = this->getItem(dep.id, dep.version)) { if (auto depItem = this->getItem(dep.id, dep.version)) {
if (!depItem->getAvailablePlatforms().count(GEODE_PLATFORM_TARGET)) { if (!depItem->getAvailablePlatforms().count(GEODE_PLATFORM_TARGET)) {
@ -569,12 +568,55 @@ Result<IndexInstallList> Index::getInstallList(IndexItemHandle item) const {
); );
} }
// recursively add dependencies // 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)); GEODE_UNWRAP_INTO(auto deps, this->getInstallList(depItem));
ranges::push(list.list, deps.list); ranges::push(list.list, deps.list);
} }
// otherwise user must get this dependency manually from somewhere // otherwise user must get this dependency manually from somewhere
// else
else { else {
// it's fine to not install optional dependencies
if (dep.importance != ModMetadata::Dependency::Importance::Required) continue;
return Err( return Err(
"Dependency {} version {} not found in the index! Likely " "Dependency {} version {} not found in the index! Likely "
"reason is that the version of the dependency this mod " "reason is that the version of the dependency this mod "

View file

@ -750,20 +750,47 @@ void IndexItemInfoPopup::onInstallProgress(ModInstallEvent* event) {
} }
void IndexItemInfoPopup::onInstall(CCObject*) { void IndexItemInfoPopup::onInstall(CCObject*) {
InstallListPopup::create(m_item, [&](IndexInstallList const& list) { createQuickPopup(
if (m_latestVersionLabel) { "Confirm Install",
m_latestVersionLabel->setVisible(false); "Installing this mod requires a few other mods to be installed. "
} "Would you like to continue with <cy>recommended settings</c> or "
this->setInstallStatus(UpdateProgress(0, "Starting install")); "<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( void IndexItemInfoPopup::preInstall() {
this, menu_selector(IndexItemInfoPopup::onCancel) if (m_latestVersionLabel) {
); m_latestVersionLabel->setVisible(false);
m_installBtnSpr->setString("Cancel"); }
m_installBtnSpr->setBG("GJ_button_06.png", false); this->setInstallStatus(UpdateProgress(0, "Starting install"));
Index::get()->install(list); m_installBtn->setTarget(
})->show(); this, menu_selector(IndexItemInfoPopup::onCancel)
);
m_installBtnSpr->setString("Cancel");
m_installBtnSpr->setBG("GJ_button_06.png", false);
} }
void IndexItemInfoPopup::onCancel(CCObject*) { void IndexItemInfoPopup::onCancel(CCObject*) {

View file

@ -100,6 +100,8 @@ protected:
void onInstall(CCObject*); void onInstall(CCObject*);
void onCancel(CCObject*); void onCancel(CCObject*);
void preInstall();
CCNode* createLogo(CCSize const& size) override; CCNode* createLogo(CCSize const& size) override;
ModMetadata getMetadata() const override; ModMetadata getMetadata() const override;

View file

@ -229,7 +229,7 @@ bool IndexItemInstallListCell::init(
if (importance != ModMetadata::Dependency::Importance::Required) { if (importance != ModMetadata::Dependency::Importance::Required) {
message->setCString("N/A (Optional)"); 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 }); message->setColor({ 240, 31, 31 });
if (optional) { if (optional) {
message->setCString("Missing (Optional)"); message->setCString("Missing (Optional)");
message->setColor({ 120, 15, 15 }); message->setColor({ 163, 24, 24 });
} }
this->addChild(message); this->addChild(message);
return true; return true;

View file

@ -97,8 +97,7 @@ static std::optional<int> queryMatch(ModListQuery const& query, Mod* mod) {
} }
static std::optional<int> queryMatch(ModListQuery const& query, IndexItemHandle item) { static std::optional<int> queryMatch(ModListQuery const& query, IndexItemHandle item) {
// if no force visibility was provided and item is already installed, don't // if no force visibility was provided and item is already installed, don't show it
// show it
if (!query.forceVisibility && Loader::get()->isModInstalled(item->getMetadata().getID())) { if (!query.forceVisibility && Loader::get()->isModInstalled(item->getMetadata().getID())) {
return std::nullopt; return std::nullopt;
} }
@ -114,6 +113,16 @@ static std::optional<int> queryMatch(ModListQuery const& query, IndexItemHandle
})) { })) {
return std::nullopt; 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 // otherwise match keywords
if (auto match = queryMatchKeywords(query, item->getMetadata())) { if (auto match = queryMatchKeywords(query, item->getMetadata())) {
auto weighted = match.value(); auto weighted = match.value();
@ -190,7 +199,8 @@ CCArray* ModListLayer::createModCells(ModListType type, ModListQuery const& quer
// sort the mods by match score // sort the mods by match score
std::multimap<int, IndexItemHandle> sorted; 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)) { if (auto match = queryMatch(query, item)) {
sorted.insert({ match.value(), item }); sorted.insert({ match.value(), item });
} }

View file

@ -25,10 +25,15 @@ struct ModListQuery {
*/ */
std::optional<std::string> keywords; 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) * tags (used to show installed mods on index)
*/ */
bool forceVisibility; 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 * Empty means current platform
*/ */

View file

@ -5,7 +5,9 @@
#include <Geode/binding/GameToolbox.hpp> #include <Geode/binding/GameToolbox.hpp>
#include <Geode/binding/CCMenuItemToggler.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) { bool SearchFilterPopup::setup(ModListLayer* layer, ModListType type) {
m_noElasticity = true; m_noElasticity = true;
@ -14,66 +16,77 @@ bool SearchFilterPopup::setup(ModListLayer* layer, ModListType type) {
this->setTitle("Search Filters"); this->setTitle("Search Filters");
auto winSize = CCDirector::sharedDirector()->getWinSize(); 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 // platforms
auto platformTitle = CCLabelBMFont::create("Platforms", "goldFont.fnt"); 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); platformTitle->setScale(.5f);
m_mainLayer->addChild(platformTitle); m_mainLayer->addChild(platformTitle);
auto platformBG = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }); auto platformBG = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f });
platformBG->setColor({ 0, 0, 0 }); platformBG->setColor({ 0, 0, 0 });
platformBG->setOpacity(90); platformBG->setOpacity(90);
platformBG->setContentSize({ 290.f, 205.f }); platformBG->setContentSize({ 290.f, 205.f - iosAndAndroidSize * 2.f });
platformBG->setPosition(winSize.width / 2 - 85.f, winSize.height / 2 + 11.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); platformBG->setScale(.5f);
m_mainLayer->addChild(platformBG); m_mainLayer->addChild(platformBG);
this->enable(this->addPlatformToggle("Windows", PlatformID::Windows, pos), type); this->enable(this->addPlatformToggle("Windows", PlatformID::Windows, pos), type);
this->enable(this->addPlatformToggle("macOS", PlatformID::MacOS, pos), type); this->enable(this->addPlatformToggle("macOS", PlatformID::MacOS, pos), type);
this->enable(this->addPlatformToggle("IOS", PlatformID::iOS, pos), type); //this->enable(this->addPlatformToggle("IOS", PlatformID::iOS, pos), type);
this->enable(this->addPlatformToggle("Android", PlatformID::Android, pos), type); //this->enable(this->addPlatformToggle("Android", PlatformID::Android, pos), type);
// show installed // show installed
auto installedTitle = CCLabelBMFont::create("Other", "goldFont.fnt"); 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); installedTitle->setScale(.5f);
m_mainLayer->addChild(installedTitle); m_mainLayer->addChild(installedTitle);
auto installedBG = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }); auto installedBG = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f });
installedBG->setColor({ 0, 0, 0 }); installedBG->setColor({ 0, 0, 0 });
installedBG->setOpacity(90); installedBG->setOpacity(90);
installedBG->setContentSize({ 290.f, 65.f }); installedBG->setContentSize({ 290.f, 110.f });
installedBG->setPosition(winSize.width / 2 - 85.f, winSize.height / 2 - 85.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); installedBG->setScale(.5f);
m_mainLayer->addChild(installedBG); 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( this->addToggle(
"Show Installed", menu_selector(SearchFilterPopup::onShowInstalled), "Show Installed", menu_selector(SearchFilterPopup::onShowInstalled),
m_modLayer->getQuery().forceVisibility, 0, pos m_modLayer->getQuery().forceVisibility, 0, pos
); );
this->addToggle(
"Show Invalid", menu_selector(SearchFilterPopup::onShowInvalid),
m_modLayer->getQuery().forceInvalid, 1, pos
);
// tags // tags
auto tagsTitle = CCLabelBMFont::create("Tags", "goldFont.fnt"); 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); tagsTitle->setScale(.5f);
m_mainLayer->addChild(tagsTitle); m_mainLayer->addChild(tagsTitle);
auto tagsBG = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }); auto tagsBG = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f });
tagsBG->setColor({ 0, 0, 0 }); tagsBG->setColor({ 0, 0, 0 });
tagsBG->setOpacity(90); tagsBG->setOpacity(90);
tagsBG->setContentSize({ 290.f, 328.f }); tagsBG->setContentSize({ 290.f, 328.f - iosAndAndroidSize });
tagsBG->setPosition(winSize.width / 2 + 85.f, winSize.height / 2 - 19.5f); tagsBG->setAnchorPoint({ 0.5f, 1.f });
tagsBG->setPosition(winSize.width / 2 + 85.f, winSize.height / 2 + 62.5f - iosAndAndroidSize * 0.25f);
tagsBG->setScale(.5f); tagsBG->setScale(.5f);
m_mainLayer->addChild(tagsBG); 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()) { for (auto& tag : Index::get()->getTags()) {
auto toggle = CCMenuItemToggler::createWithStandardSprites( auto toggle = CCMenuItemToggler::createWithStandardSprites(
@ -116,6 +129,11 @@ void SearchFilterPopup::onShowInstalled(CCObject* sender) {
m_modLayer->getQuery().forceVisibility = !toggle->isToggled(); 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) { void SearchFilterPopup::enable(CCMenuItemToggler* toggle, ModListType type) {
if (type == ModListType::Installed) { if (type == ModListType::Installed) {
toggle->setEnabled(false); toggle->setEnabled(false);
@ -162,7 +180,7 @@ void SearchFilterPopup::onClose(CCObject* sender) {
SearchFilterPopup* SearchFilterPopup::create(ModListLayer* layer, ModListType type) { SearchFilterPopup* SearchFilterPopup::create(ModListLayer* layer, ModListType type) {
auto ret = new SearchFilterPopup(); 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(); ret->autorelease();
return ret; return ret;
} }

View file

@ -19,6 +19,7 @@ protected:
void onPlatformToggle(CCObject*); void onPlatformToggle(CCObject*);
void onShowInstalled(CCObject*); void onShowInstalled(CCObject*);
void onShowInvalid(CCObject*);
void onTag(CCObject*); void onTag(CCObject*);
void enable(CCMenuItemToggler* toggle, ModListType type); void enable(CCMenuItemToggler* toggle, ModListType type);

View file

@ -5,8 +5,18 @@ using namespace geode::prelude;
class QuickPopup : public FLAlertLayer, public FLAlertLayerProtocol { class QuickPopup : public FLAlertLayer, public FLAlertLayerProtocol {
protected: protected:
MiniFunction<void(FLAlertLayer*, bool)> m_selected; 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 { void FLAlert_Clicked(FLAlertLayer* layer, bool btn2) override {
if (m_cancelledByEscape && m_usedEscape) {
return;
}
if (m_selected) { if (m_selected) {
m_selected(layer, btn2); m_selected(layer, btn2);
} }
@ -15,10 +25,11 @@ protected:
public: public:
static QuickPopup* create( static QuickPopup* create(
char const* title, std::string const& content, char const* btn1, char const* btn2, 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; auto inst = new QuickPopup;
inst->m_selected = selected; inst->m_selected = selected;
inst->m_cancelledByEscape = cancelledByEscape;
if (inst && inst->init(inst, title, content, btn1, btn2, width, false, .0f)) { if (inst && inst->init(inst, title, content, btn1, btn2, width, false, .0f)) {
inst->autorelease(); inst->autorelease();
return inst; return inst;
@ -32,7 +43,7 @@ FLAlertLayer* geode::createQuickPopup(
char const* title, std::string const& content, char const* btn1, char const* btn2, float width, char const* title, std::string const& content, char const* btn1, char const* btn2, float width,
MiniFunction<void(FLAlertLayer*, bool)> selected, bool doShow 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) { if (doShow) {
ret->show(); ret->show();
} }
@ -45,3 +56,21 @@ FLAlertLayer* geode::createQuickPopup(
) { ) {
return createQuickPopup(title, content, btn1, btn2, 350.f, selected, doShow); 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);
}