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
*/
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

View file

@ -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
);
}

View file

@ -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 "

View file

@ -750,7 +750,37 @@ void IndexItemInfoPopup::onInstallProgress(ModInstallEvent* event) {
}
void IndexItemInfoPopup::onInstall(CCObject*) {
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
);
}
void IndexItemInfoPopup::preInstall() {
if (m_latestVersionLabel) {
m_latestVersionLabel->setVisible(false);
}
@ -761,9 +791,6 @@ void IndexItemInfoPopup::onInstall(CCObject*) {
);
m_installBtnSpr->setString("Cancel");
m_installBtnSpr->setBG("GJ_button_06.png", false);
Index::get()->install(list);
})->show();
}
void IndexItemInfoPopup::onCancel(CCObject*) {

View file

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

View file

@ -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;

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) {
// 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 });
}

View file

@ -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
*/

View file

@ -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;
}

View file

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

View file

@ -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);
}