Compare commits

...

7 commits

Author SHA1 Message Date
Justin
d5fc1945e3
Merge 4a40835f71 into c0514b1915 2024-11-18 22:52:51 -05:00
mat
c0514b1915
bump to beta.2
Some checks are pending
Build Binaries / Build Windows (push) Waiting to run
Build Binaries / Build macOS (push) Waiting to run
Build Binaries / Build Android (64-bit) (push) Waiting to run
Build Binaries / Build Android (32-bit) (push) Waiting to run
Build Binaries / Publish (push) Blocked by required conditions
Check CHANGELOG.md / Check CHANGELOG.md (push) Waiting to run
2024-11-19 00:37:04 -03:00
Justin
dd1d83558f
Add a button that copies the mod list (#1039)
* New image arguments format

* add ampersand support whoops

* first moves oh lord

* it's finished holy moly

* made it slightly better

* almost forgot

* texture revamp by @Alphalaneous

* why is it not loading

* whoops

* FINALLY

* targetsOutdatedVersion

* i love abi breaks

* how's this

* this thing

* this is driving me nuts
2024-11-18 18:30:58 -07:00
HJfod
1ff24f09c6 finish grid view 2024-11-19 00:18:23 +02:00
HJfod
6e86b38990 fix vv version 2024-11-18 21:31:17 +02:00
Justin
4a40835f71
Change approach 2024-11-08 12:23:18 -05:00
Justin
74d0924bcb
Part 2: Geode SDK 2024-11-08 10:20:30 -05:00
22 changed files with 272 additions and 35 deletions

View file

@ -1,5 +1,21 @@
# Geode Changelog
## v4.0.0-beta.2
* Add grid view to mod list (7bcf50d, 1ff24f0)
* Add safe mode tip to windows crashlog window (38f3385)
* Disable enabled button on outdated mods (302eea1)
* Add a button to copy list of mods to clipboard (#1039)
* Fix VersionInfo toJson (f6c2322)
* Add `GEODE_DESKTOP(...)` and `GEODE_MOBILE(...)` macros (d6f0c59)
* Fix CCCallFuncExt (b9fb2f6)
* Fix `utils::string::replaceIP` when filter is empty (4d5e465)
* Fix more log nesting issues (2221095)
* Fix new before/after priority system (17bf772)
* Added European Portuguese translation (#1160)
* Add missing CCHttpRequest methods and members (#1161)
* Fix downloading many mods at once causing the UI to lag (c94a533)
* Fix vv version (6e86b38)
## v4.0.0-beta.1
* Button to manually install mods from files (e881dc5)
* Add `ModRequestedAction::Update` (e881dc5)

View file

@ -1 +1 @@
4.0.0-beta.1
4.0.0-beta.2

View file

@ -164,7 +164,27 @@ function(setup_geode_mod proname)
set(HAS_HEADERS Off)
endif()
if (HAS_HEADERS AND WIN32)
if (GEODE_BUNDLE_PDB AND WIN32 AND (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo"))
if (HAS_HEADERS)
add_custom_target(${proname}_PACKAGE ALL
DEPENDS ${proname} ${CMAKE_CURRENT_SOURCE_DIR}/mod.json
COMMAND ${GEODE_CLI} package new ${CMAKE_CURRENT_SOURCE_DIR}
--binary $<TARGET_FILE:${proname}> $<TARGET_LINKER_FILE:${proname}> $<TARGET_PDB_FILE:${proname}>
--output ${CMAKE_CURRENT_BINARY_DIR}/${MOD_ID}.geode
${INSTALL_ARG} ${PDB_ARG}
VERBATIM USES_TERMINAL
)
else()
add_custom_target(${proname}_PACKAGE ALL
DEPENDS ${proname} ${CMAKE_CURRENT_SOURCE_DIR}/mod.json
COMMAND ${GEODE_CLI} package new ${CMAKE_CURRENT_SOURCE_DIR}
--binary $<TARGET_FILE:${proname}> $<TARGET_PDB_FILE:${proname}>
--output ${CMAKE_CURRENT_BINARY_DIR}/${MOD_ID}.geode
${INSTALL_ARG} ${PDB_ARG}
VERBATIM USES_TERMINAL
)
endif()
elseif (HAS_HEADERS AND WIN32)
# this adds the .lib file on windows, which is needed for linking with the headers
add_custom_target(${proname}_PACKAGE ALL
DEPENDS ${proname} ${CMAKE_CURRENT_SOURCE_DIR}/mod.json

View file

@ -91,6 +91,10 @@
"name": "Expand Installed Mods List",
"description": "Make the installed mods list a single infinite scrollable list instead of having pages"
},
"copy-mods": {
"type": "custom:copy-mods",
"name": ""
},
"developer-title": {
"type": "title",
"name": "Developer Settings"

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -0,0 +1,43 @@
#include "CopyButtonSetting.hpp"
#include <Geode/loader/Mod.hpp>
$on_mod(Loaded) {
(void)Mod::get()->registerCustomSettingType("copy-mods", &CopyButtonSetting::parse);
}
SettingNodeV3* CopyButtonSetting::createNode(float width) {
return CopyButtonSettingNode::create(std::static_pointer_cast<CopyButtonSetting>(shared_from_this()), width);
}
void CopyButtonSettingNode::onCopy(CCObject*) {
auto mods = Loader::get()->getAllMods();
if (mods.empty()) {
Notification::create("No mods installed", NotificationIcon::Info, 0.5f)->show();
return;
}
std::sort(mods.begin(), mods.end(), [](Mod* a, Mod* b) {
auto const s1 = a->getID();
auto const s2 = b->getID();
return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), [](auto a, auto b) {
return std::tolower(a) < std::tolower(b);
});
});
std::string modsList;
using namespace std::string_view_literals;
for (int i = 0; i < mods.size(); i++) {
auto& mod = mods[i];
modsList += fmt::format("{} | [{}] {}{}",
mod->isEnabled() ? "x"sv :
mod->hasLoadProblems() ? "!"sv :
mod->targetsOutdatedVersion() ? "*"sv :
" "sv,
mod->getVersion().toVString(), mod->getID(),
i < mods.size() ? "\n" : ""
);
}
clipboard::write(modsList);
Notification::create("Mods list copied to clipboard", NotificationIcon::Info, 0.5f)->show();
}

View file

@ -0,0 +1,76 @@
#include <Geode/loader/SettingV3.hpp>
#include <ui/mods/GeodeStyle.hpp>
using namespace geode::prelude;
class CopyButtonSetting : public SettingV3 {
public:
static Result<std::shared_ptr<SettingV3>> parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto res = std::make_shared<CopyButtonSetting>();
auto root = checkJson(json, "CopyButtonSetting");
res->init(key, modID, root);
res->parseNameAndDescription(root);
return root.ok(std::static_pointer_cast<SettingV3>(res));
}
bool load(matjson::Value const& json) override {
return true;
}
bool save(matjson::Value& json) const override {
return true;
}
bool isDefaultValue() const override {
return true;
}
void reset() override {}
SettingNodeV3* createNode(float width) override;
};
class CopyButtonSettingNode : public SettingNodeV3 {
protected:
bool init(std::shared_ptr<CopyButtonSetting> setting, float width) {
if (!SettingNodeV3::init(setting, width))
return false;
auto buttonSprite = createGeodeButton("Copy Mods");
buttonSprite->setScale(.5f);
auto button = CCMenuItemSpriteExtra::create(
buttonSprite, this, menu_selector(CopyButtonSettingNode::onCopy)
);
this->getButtonMenu()->addChildAtPosition(button, Anchor::Center);
this->getButtonMenu()->setPosition(getContentSize() / 2);
this->getButtonMenu()->setAnchorPoint({ .5f, .5f });
this->getButtonMenu()->updateLayout();
this->updateState(nullptr);
return true;
}
void onCopy(CCObject*);
void onCommit() override {}
void onResetToDefault() override {}
public:
static CopyButtonSettingNode* create(std::shared_ptr<CopyButtonSetting> setting, float width) {
auto ret = new CopyButtonSettingNode();
if (ret && ret->init(setting, width)) {
ret->autorelease();
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
bool hasUncommittedChanges() const override {
return false;
}
bool hasNonDefaultValue() const override {
return false;
}
};

View file

@ -1079,7 +1079,7 @@ void Loader::Impl::installModManuallyFromFile(std::filesystem::path const& path,
createQuickPopup(
"Already Installed",
fmt::format(
"The mod <cy>{}</c> <cj>v{}</c> has already been installed "
"The mod <cy>{}</c> <cj>{}</c> has already been installed "
"as version <cl>{}</c>. Do you want to <co>replace the "
"installed version with the file</c>?",
meta.getID(), meta.getVersion(),

View file

@ -19,6 +19,8 @@
#include "ui/mods/sources/ModListSource.hpp"
#include <loader/LoaderImpl.hpp>
static ModListDisplay MOD_LIST_DISPLAY = ModListDisplay::SmallList;
bool ModsStatusNode::init() {
if (!CCNode::init())
return false;
@ -649,7 +651,7 @@ void ModsLayer::gotoTab(ModListSource* src) {
m_currentSource = src;
// Update the state of the current list
m_lists.at(m_currentSource)->updateDisplay(m_display);
m_lists.at(m_currentSource)->updateDisplay(MOD_LIST_DISPLAY);
m_lists.at(m_currentSource)->activateSearch(m_showSearch);
m_lists.at(m_currentSource)->updateState();
}
@ -711,7 +713,7 @@ void ModsLayer::updateState() {
// Update display button
for (auto btn : m_displayBtns) {
static_cast<GeodeSquareSprite*>(btn->getNormalImage())->setState(
static_cast<ModListDisplay>(btn->getTag()) == m_display
static_cast<ModListDisplay>(btn->getTag()) == MOD_LIST_DISPLAY
);
}
}
@ -744,10 +746,11 @@ void ModsLayer::onGoToPage(CCObject*) {
popup->show();
}
void ModsLayer::onDisplay(CCObject* sender) {
m_display = static_cast<ModListDisplay>(sender->getTag());
MOD_LIST_DISPLAY = static_cast<ModListDisplay>(sender->getTag());
// Make sure to avoid a crash
if (m_currentSource) {
m_lists.at(m_currentSource)->updateDisplay(m_display);
m_lists.at(m_currentSource)->updateDisplay(MOD_LIST_DISPLAY);
m_lists.at(m_currentSource)->reloadPage();
}
this->updateState();
}

View file

@ -66,7 +66,6 @@ protected:
ModsStatusNode* m_statusNode;
EventListener<UpdateModListStateFilter> m_updateStateListener;
bool m_showSearch = true;
ModListDisplay m_display = ModListDisplay::SmallList;
std::vector<CCMenuItemSpriteExtra*> m_displayBtns;
bool init();

View file

@ -54,6 +54,10 @@ bool ModItem::init(ModSource&& source) {
m_versionLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(std::nullopt, .7f));
m_titleContainer->addChild(m_versionLabel);
m_versionDownloadSeparator = CCLabelBMFont::create("", "bigFont.fnt");
m_versionDownloadSeparator->setOpacity(155);
m_titleContainer->addChild(m_versionDownloadSeparator);
m_titleContainer->setLayout(
RowLayout::create()
->setDefaultScaleLimits(.1f, 1.f)
@ -180,10 +184,7 @@ bool ModItem::init(ModSource&& source) {
}
}
auto viewBtn = CCMenuItemSpriteExtra::create(
spr,
this, menu_selector(ModItem::onView)
);
auto viewBtn = CCMenuItemSpriteExtra::create(spr, this, menu_selector(ModItem::onView));
viewBtn->setID("view-button");
m_viewMenu->addChild(viewBtn);
@ -229,10 +230,23 @@ bool ModItem::init(ModSource&& source) {
m_badgeContainer->addChild(CCSprite::createWithSpriteFrameName("tag-featured.png"_spr));
}
if (metadata.tags.contains("paid")) {
m_badgeContainer->addChild(CCSprite::createWithSpriteFrameName("tag-paid.png"_spr));
auto shortVer = CCSprite::createWithSpriteFrameName("tag-paid.png"_spr);
shortVer->setTag(1);
m_badgeContainer->addChild(shortVer);
auto longVer = CCSprite::createWithSpriteFrameName("tag-paid-long.png"_spr);
longVer->setTag(2);
m_badgeContainer->addChild(longVer);
}
if (metadata.tags.contains("joke")) {
m_badgeContainer->addChild(CCSprite::createWithSpriteFrameName("tag-joke.png"_spr));
}
if (metadata.tags.contains("modtober24")) {
m_badgeContainer->addChild(CCSprite::createWithSpriteFrameName("tag-modtober.png"_spr));
auto shortVer = CCSprite::createWithSpriteFrameName("tag-modtober.png"_spr);
shortVer->setTag(1);
m_badgeContainer->addChild(shortVer);
auto longVer = CCSprite::createWithSpriteFrameName("tag-modtober-long.png"_spr);
longVer->setTag(2);
m_badgeContainer->addChild(longVer);
}
// Show mod download count here already so people can make informed decisions
@ -242,17 +256,18 @@ bool ModItem::init(ModSource&& source) {
auto downloads = CCLabelBMFont::create(numToAbbreviatedString(metadata.downloadCount).c_str(), "bigFont.fnt");
downloads->setID("downloads-label");
downloads->setColor("mod-list-version-label"_cc3b);
downloads->limitLabelWidth(80, .5f, .1f);
downloads->limitLabelWidth(125, 1.f, .1f);
m_downloadCountContainer->addChildAtPosition(downloads, Anchor::Right, ccp(-0, 0), ccp(1, .5f));
auto downloadsIcon = CCSprite::createWithSpriteFrameName("GJ_downloadsIcon_001.png");
downloadsIcon->setID("downloads-icon-sprite");
downloadsIcon->setScale(.75f);
m_downloadCountContainer->addChildAtPosition(downloadsIcon, Anchor::Left, ccp(5, 0));
downloadsIcon->setScale(1.2f);
m_downloadCountContainer->addChildAtPosition(downloadsIcon, Anchor::Left, ccp(8, 0));
// m_downloadCountContainer scale is controlled in updateState
m_downloadCountContainer->setContentSize({
downloads->getScaledContentWidth() + downloadsIcon->getScaledContentWidth(),
25
30
});
m_downloadCountContainer->updateLayout();
@ -338,7 +353,7 @@ void ModItem::updateState() {
// Update the size of the mod cell itself
if (m_display == ModListDisplay::Grid) {
auto widthWithoutGaps = m_targetWidth - 10;
auto widthWithoutGaps = m_targetWidth - 7.5f;
this->setContentSize(ccp(widthWithoutGaps / roundf(widthWithoutGaps / 80), 100));
m_bg->setContentSize(m_obContentSize / m_bg->getScale());
}
@ -358,14 +373,19 @@ void ModItem::updateState() {
m_titleContainer->insertBefore(m_titleLabel, nullptr);
}
// Show download separator if there is something to separate and we're in grid view
m_versionDownloadSeparator->setVisible(m_downloadCountContainer && m_display == ModListDisplay::Grid);
// Download counts go next to the version like on the website on grid view
if (m_downloadCountContainer) {
m_downloadCountContainer->removeFromParent();
if (m_display == ModListDisplay::Grid) {
m_titleContainer->insertAfter(m_downloadCountContainer, m_versionLabel);
m_titleContainer->insertAfter(m_downloadCountContainer, m_versionDownloadSeparator);
m_downloadCountContainer->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .7f));
}
else {
m_viewMenu->addChild(m_downloadCountContainer);
m_downloadCountContainer->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .6f));
}
}
@ -373,14 +393,31 @@ void ModItem::updateState() {
if (m_badgeContainer) {
m_badgeContainer->removeFromParent();
if (m_display == ModListDisplay::Grid) {
m_badgeContainer->setLayout(ColumnLayout::create()->setAutoGrowAxis(true));
m_badgeContainer->setScale(.25f);
this->addChildAtPosition(m_badgeContainer, Anchor::TopLeft, ccp(5, -5), ccp(0, 1));
m_badgeContainer->setLayout(
ColumnLayout::create()
->setAxisReverse(true)
->setAutoGrowAxis(true)
->setAxisAlignment(AxisAlignment::Start)
);
m_badgeContainer->getLayout()->ignoreInvisibleChildren(true);
m_badgeContainer->setScale(.3f);
this->addChildAtPosition(m_badgeContainer, Anchor::TopLeft, ccp(5, -2), ccp(0, 1));
}
else {
m_badgeContainer->setLayout(RowLayout::create()->setAutoGrowAxis(true));
m_badgeContainer->setLayout(
RowLayout::create()
->setAutoGrowAxis(true)
);
m_badgeContainer->getLayout()->ignoreInvisibleChildren(true);
m_titleContainer->addChild(m_badgeContainer);
}
// Long tags don't fit in the grid UI
for (auto child : CCArrayExt<CCNode*>(m_badgeContainer->getChildren())) {
if (child->getTag() > 0) {
child->setVisible(child->getTag() == (m_display == ModListDisplay::Grid ? 1 : 2));
}
}
m_badgeContainer->updateLayout();
}
// On Grid View logo has constant size
@ -558,6 +595,9 @@ void ModItem::updateState() {
m_recommendedBy->updateLayout();
}
limitNodeWidth(m_downloadWaiting, m_titleContainer->getContentWidth(), 1.f, .1f);
limitNodeWidth(m_downloadBarContainer, m_titleContainer->getContentWidth(), 1.f, .1f);
// Update positioning (jesus)
switch (m_display) {
case ModListDisplay::Grid: {

View file

@ -48,6 +48,7 @@ protected:
Ref<CCNode> m_downloadCountContainer;
ModListDisplay m_display = ModListDisplay::SmallList;
float m_targetWidth = 300;
CCLabelBMFont* m_versionDownloadSeparator;
/**
* @warning Make sure `getMetadata` and `createModLogo` are callable

View file

@ -6,6 +6,22 @@
#include "../ModsLayer.hpp"
#include "../popups/ModtoberPopup.hpp"
static size_t getDisplayPageSize(ModListSource* src, ModListDisplay display) {
if (src->isLocalModsOnly() && Mod::get()->template getSettingValue<bool>("infinite-local-mods-list")) {
return std::numeric_limits<size_t>::max();
}
return display == ModListDisplay::Grid ? 16 : 10;
}
$execute {
listenForSettingChanges("infinite-local-mods-list", [](bool value) {
InstalledModListSource::get(InstalledModListType::All)->reset();
InstalledModListSource::get(InstalledModListType::OnlyErrors)->reset();
InstalledModListSource::get(InstalledModListType::OnlyOutdated)->reset();
// Updates is technically a server mod list :-) So I left it out here
});
}
bool ModList::init(ModListSource* src, CCSize const& size) {
if (!CCNode::init())
return false;
@ -179,7 +195,7 @@ bool ModList::init(ModListSource* src, CCSize const& size) {
m_searchInput->setCallback([this](auto const&) {
// If the source is already in memory, we can immediately update the
// search query
if (typeinfo_cast<InstalledModListSource*>(m_source)) {
if (m_source->isLocalModsOnly()) {
m_source->search(m_searchInput->getString());
return;
}
@ -550,6 +566,7 @@ void ModList::updateTopContainer() {
void ModList::updateDisplay(ModListDisplay display) {
m_display = display;
m_source->setPageSize(getDisplayPageSize(m_source, m_display));
// Update all BaseModItems that are children of the list
// There may be non-BaseModItems there (like separators) so gotta be type-safe
@ -570,7 +587,7 @@ void ModList::updateDisplay(ModListDisplay display) {
m_list->m_contentLayer->setLayout(
RowLayout::create()
->setGrowCrossAxis(true)
->setAxisAlignment(AxisAlignment::Center)
->setAxisAlignment(AxisAlignment::Start)
->setGap(2.5f)
);
}
@ -635,6 +652,9 @@ void ModList::gotoPage(size_t page, bool update) {
m_list->m_contentLayer->removeAllChildren();
m_page = page;
// Update page size (if needed)
m_source->setPageSize(getDisplayPageSize(m_source, m_display));
// Start loading new page with generic loading message
this->showStatus(ModListUnkProgressStatus(), "Loading...");
m_listener.setFilter(m_source->loadPage(page, update));

View file

@ -137,11 +137,8 @@ bool InstalledModListSource::isDefaultQuery() const {
return m_query.isDefault();
}
$execute {
listenForSettingChanges("infinite-local-mods-list", [](bool value) {
auto size = value ? std::numeric_limits<size_t>::max() : 10;
InstalledModListSource::get(InstalledModListType::All)->setPageSize(size);
InstalledModListSource::get(InstalledModListType::OnlyErrors)->setPageSize(size);
// Updates is technically a server mod list :-) So I left it out here
});
bool InstalledModListSource::isLocalModsOnly() const {
return m_type == InstalledModListType::All ||
m_type == InstalledModListType::OnlyErrors ||
m_type == InstalledModListType::OnlyOutdated;
}

View file

@ -62,9 +62,11 @@ std::optional<size_t> ModListSource::getItemCount() const {
return m_cachedItemCount;
}
void ModListSource::setPageSize(size_t size) {
if (m_pageSize != size) {
m_pageSize = size;
this->reset();
}
}
void ModListSource::reset() {
this->resetQuery();

View file

@ -78,6 +78,8 @@ public:
std::optional<size_t> getItemCount() const;
void setPageSize(size_t size);
virtual bool isLocalModsOnly() const = 0;
static void clearAllCaches();
};
@ -139,6 +141,8 @@ public:
InstalledModsQuery const& getQuery() const;
InvalidateQueryAfter<InstalledModsQuery> getQueryMut();
bool isDefaultQuery() const override;
bool isLocalModsOnly() const override;
};
enum class ServerModListType {
@ -171,6 +175,8 @@ public:
bool isDefaultQuery() const override;
server::ModsQuery createDefaultQuery() const;
ServerModListType getType() const;
bool isLocalModsOnly() const override;
};
class ModPackListSource : public ModListSource {
@ -187,6 +193,8 @@ public:
std::unordered_set<std::string> getModTags() const override;
void setModTags(std::unordered_set<std::string> const& tags) override;
bool isDefaultQuery() const override;
bool isLocalModsOnly() const override;
};
bool weightedFuzzyMatch(std::string const& str, std::string const& kw, double weight, double& out);

View file

@ -21,3 +21,7 @@ void ModPackListSource::setModTags(std::unordered_set<std::string> const& set) {
bool ModPackListSource::isDefaultQuery() const {
return true;
}
bool ModPackListSource::isLocalModsOnly() const {
return false;
}

View file

@ -115,3 +115,7 @@ server::ModsQuery ServerModListSource::createDefaultQuery() const {
ServerModListType ServerModListSource::getType() const {
return m_type;
}
bool ServerModListSource::isLocalModsOnly() const {
return false;
}