Merge branch 'geode-sdk:main' into copy-mods

This commit is contained in:
Justin 2024-10-12 16:15:56 -04:00 committed by GitHub
commit a68a631a4d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 534 additions and 210 deletions

View file

@ -1,6 +1,15 @@
# Geode Changelog
## v3.7.2
## v3.8.1
* Fix CCLightning header
* Fix server query default value (8be97b7)
* Fix importance resolving in disabled mods (d40ba6d)
## v3.8.0
* Add Modtober integration! For more about Modtober, join [the GDP Discord server](https://discord.gg/gd-programming-646101505417674758) (964624b)
* Add `Popup::CloseEvent` (6270e1c)
* Add `openSettingsPopup` overload that returns the created `Popup` (dc8d271)
* Fix `CCNode::querySelector` logspamming (b53759f)
* Fix `followThunkFunction` following through into hook handlers (ad26357)
## v3.7.1

View file

@ -1 +1 @@
3.7.2
3.8.1

View file

@ -450,7 +450,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__node_alloc_traits::_S_propagate_on_move_assign()
|| __node_alloc_traits::_S_always_equal();
_M_move_assign(std::move(__ht),
integral_constant<bool, __move_storage>());
std::integral_constant<bool, __move_storage>());
return *this;
}

View file

@ -1115,7 +1115,7 @@ public:
geode::utils::MiniFunction<typename Filter::Callback> callback,
Args&&... args
) {
return this->template addEventListener<Filter, Args...>(
return this->addEventListener<Filter, Args...>(
"", callback, std::forward<Args>(args)...
);
}

View file

@ -181,6 +181,7 @@ public:
* @lua NA
*/
CCDictionary();
GEODE_CUSTOM_CONSTRUCTOR_COCOS(CCDictionary, CCObject);
/**
* The destructor of CCDictionary

View file

@ -11,7 +11,7 @@ class CCLightning : public CCNode, public CCRGBAProtocol {
public:
CCLightning();
virtual ~CCLightning();
GEODE_CUSTOM_CONSTRUCTOR_GD(CCLightning, CCNode);
GEODE_CUTOFF_CONSTRUCTOR_GD(CCLightning, CCNode);
static CCLightning* lightningWithStrikePoint(CCPoint strikePoint, CCPoint strikePoint2, float duration);
static CCLightning* lightningWithStrikePoint(CCPoint strikePoint);

View file

@ -324,8 +324,8 @@ namespace geode {
}
bool load(matjson::Value const& json) override {
if (json.template is<T>()) {
m_impl->value = json.template as<T>();
if (json.is<T>()) {
m_impl->value = json.as<T>();
return true;
}
return false;

View file

@ -86,7 +86,7 @@ namespace geode {
public:
static ListBorders* create();
void setSpriteFrames(const char* topAndBottom, const char* sides, float topPadding = 7.5f);
void setSpriteFrames(const char* topAndBottom, const char* sides, float horizontalPadding = 7.5f);
void setSprites(
cocos2d::extension::CCScale9Sprite* top,
cocos2d::extension::CCScale9Sprite* bottom,

View file

@ -2,6 +2,7 @@
#include "../loader/Mod.hpp"
#include <Geode/binding/FLAlertLayer.hpp>
#include <Geode/ui/Popup.hpp>
class ModPopup;
class ModItem;
@ -141,6 +142,16 @@ namespace geode {
* Open the settings popup for a mod (if it has any settings)
*/
GEODE_DLL void openSettingsPopup(Mod* mod);
/**
* Open the settings popup for a mod (if it has any settings)
* @param mod Mod the open the popup for
* @param disableGeodeTheme If false, the popup follows the user's chosen
* theme options. If true, the popup is always in the GD theme (not Geode's
* dark purple colors)
* @returns A pointer to the created Popup, or null if the mod has no
* settings
*/
GEODE_DLL Popup<Mod*>* openSettingsPopup(Mod* mod, bool disableGeodeTheme);
/**
* Create a default logo sprite
*/

View file

@ -6,8 +6,59 @@
#include <Geode/utils/cocos.hpp>
namespace geode {
template <typename... InitArgs>
template <class... InitArgs>
class Popup : public FLAlertLayer {
public:
/**
* Event posted when this popup is being closed
*/
class CloseEvent final : public ::geode::Event {
private:
class Impl final {
private:
Popup* popup;
friend class CloseEvent;
};
std::shared_ptr<Impl> m_impl;
friend class Popup;
CloseEvent(Popup* popup) : m_impl(std::make_shared<Impl>()) {
m_impl->popup = popup;
}
public:
Popup* getPopup() const {
return m_impl->popup;
}
};
class CloseEventFilter final : public ::geode::EventFilter<CloseEvent> {
public:
using Callback = void(CloseEvent*);
private:
class Impl final {
private:
Popup* popup;
friend class CloseEventFilter;
};
std::shared_ptr<Impl> m_impl;
friend class Popup;
CloseEventFilter(Popup* popup) : m_impl(std::make_shared<Impl>()) {
m_impl->popup = popup;
}
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, CloseEvent* event) {
if (event->getPopup() == m_impl->popup) {
fn(event);
}
return ListenerResult::Propagate;
}
};
protected:
cocos2d::CCSize m_size;
cocos2d::extension::CCScale9Sprite* m_bgSprite;
@ -115,6 +166,7 @@ namespace geode {
}
virtual void onClose(cocos2d::CCObject*) {
CloseEvent(this).post();
this->setKeypadEnabled(false);
this->setTouchEnabled(false);
this->removeFromParentAndCleanup(true);
@ -158,6 +210,13 @@ namespace geode {
spr->setAnchorPoint(orig->getAnchorPoint());
m_closeBtn->setContentSize(origSize);
}
/**
* Returns an event filter that listens for when this popup is closed
*/
CloseEventFilter listenForClose() {
return CloseEventFilter(this);
}
};
GEODE_DLL FLAlertLayer* createQuickPopup(

View file

@ -5,6 +5,7 @@
#include <set>
#include <variant>
#include <Geode/utils/MiniFunction.hpp>
#include <Geode/utils/Result.hpp>
namespace geode {
struct JsonChecker;
@ -147,14 +148,14 @@ namespace geode {
template <class T>
bool is() {
if (this->isError()) return false;
return self().m_json.template is<T>();
return self().m_json.is<T>();
}
template <class T>
JsonMaybeValue& validate(JsonValueValidator<T> validator) {
if (this->isError()) return *this;
if (self().m_json.template is<T>()) {
if (!validator(self().m_json.template as<T>())) {
if (self().m_json.is<T>()) {
if (!validator(self().m_json.as<T>())) {
this->setError(self().m_hierarchy + ": Invalid value format");
}
}
@ -195,9 +196,9 @@ namespace geode {
this->inferType<A>();
if (this->isError()) return *this;
if (self().m_json.template is<A>()) {
if (self().m_json.is<A>()) {
try {
target = self().m_json.template as<A>();
target = self().m_json.as<A>();
}
catch(matjson::JsonException const& e) {
this->setError(
@ -219,8 +220,8 @@ namespace geode {
T get() {
this->inferType<T>();
if (this->isError()) return T();
if (self().m_json.template is<T>()) {
return self().m_json.template as<T>();
if (self().m_json.is<T>()) {
return self().m_json.as<T>();
}
return T();
}
@ -325,8 +326,8 @@ namespace geode {
}
else {
try {
if (this->getJSONRef().template is<T>()) {
return this->getJSONRef().template as<T>();
if (this->getJSONRef().is<T>()) {
return this->getJSONRef().as<T>();
}
else {
this->setError(
@ -396,21 +397,21 @@ namespace geode {
template <class T>
T get(T const& defaultValue = T()) {
if (auto v = this->template tryGet<T>()) {
if (auto v = this->tryGet<T>()) {
return *std::move(v);
}
return defaultValue;
}
template <class T>
JsonExpectedValue& into(T& value) {
if (auto v = this->template tryGet<T>()) {
if (auto v = this->tryGet<T>()) {
value = *std::move(v);
}
return *this;
}
template <class T>
JsonExpectedValue& into(std::optional<T>& value) {
if (auto v = this->template tryGet<T>()) {
if (auto v = this->tryGet<T>()) {
value.emplace(*std::move(v));
}
return *this;
@ -420,7 +421,7 @@ namespace geode {
{ predicate(std::declval<T>()) } -> std::convertible_to<bool>;
} {
if (this->hasError()) return *this;
if (auto v = this->template tryGet<T>()) {
if (auto v = this->tryGet<T>()) {
if (!predicate(*v)) {
this->setError("json value is not {}", name);
}
@ -432,7 +433,7 @@ namespace geode {
{ predicate(std::declval<T>()) } -> std::convertible_to<Result<>>;
} {
if (this->hasError()) return *this;
if (auto v = this->template tryGet<T>()) {
if (auto v = this->tryGet<T>()) {
auto p = predicate(*v);
if (!p) {
this->setError("json value is not {}: {}", name, p.unwrapErr());

View file

@ -32,10 +32,10 @@ namespace geode::utils::file {
template <class T>
Result<T> readFromJson(std::filesystem::path const& file) {
GEODE_UNWRAP_INTO(auto json, readJson(file));
if (!json.template is<T>()) {
if (!json.is<T>()) {
return Err("JSON is not of type {}", typeid(T).name());
}
return Ok(json.template as<T>());
return Ok(json.as<T>());
}
GEODE_DLL Result<> writeString(std::filesystem::path const& path, std::string const& data);

View file

@ -137,8 +137,9 @@ namespace geode {
if constexpr (std::is_floating_point_v<Num>) res = std::from_chars(str.data(), str.data() + str.size(), result);
else res = std::from_chars(str.data(), str.data() + str.size(), result, base);
auto [_, ec] = res;
auto [ptr, ec] = res;
if (ec == std::errc()) return Ok(result);
else if (ptr != str.data() + str.size()) return Err("String contains trailing extra data");
else if (ec == std::errc::invalid_argument) return Err("String is not a number");
else if (ec == std::errc::result_out_of_range) return Err("Number is too large to fit");
else return Err("Unknown error");

View file

@ -1,9 +1,9 @@
#pragma once
#include <Geode/DefaultInclude.hpp>
#include <functional>
#include <string_view>
#include <string>
#include <vector>
#include <compare>
namespace geode::utils::string {
/**
@ -64,4 +64,10 @@ namespace geode::utils::string {
GEODE_DLL bool startsWith(std::string const& str, std::string const& prefix);
GEODE_DLL bool endsWith(std::string const& str, std::string const& suffix);
/**
* Similar to strcmp, but case insensitive.
* Uses std::tolower, but could change in the future for better locale support
*/
GEODE_DLL std::strong_ordering caseInsensitiveCompare(std::string_view a, std::string_view b);
}

View file

@ -60,6 +60,9 @@
],
"BlankSheet": [
"blanks/*.png"
],
"EventSheet": [
"modtober/*.png"
]
}
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View file

@ -144,7 +144,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
if (
crashlog::didLastLaunchCrash() &&
!shownLastCrash &&
!Mod::get()->template getSettingValue<bool>("disable-last-crashed-popup")
!Mod::get()->getSettingValue<bool>("disable-last-crashed-popup")
) {
shownLastCrash = true;

View file

@ -159,7 +159,7 @@ $register_ids(MenuLayer) {
// but prolly a place mods want to add stuff
auto topRightMenu = CCMenu::create();
topRightMenu->setPosition(winSize.width - 200.f / 2, winSize.height - 50.f / 2);
topRightMenu->setPosition(winSize.width - 210.f / 2, winSize.height - 50.f / 2);
topRightMenu->setID("top-right-menu");
topRightMenu->setContentSize({ 200.f, 50.f });
topRightMenu->setLayout(

View file

@ -33,8 +33,8 @@ $on_mod(Loaded) {
JsonChecker checker(args);
auto root = checker.root("[ipc/list-mods]").obj();
auto includeRunTimeInfo = root.has("include-runtime-info").template get<bool>();
auto dontIncludeLoader = root.has("dont-include-loader").template get<bool>();
auto includeRunTimeInfo = root.has("include-runtime-info").get<bool>();
auto dontIncludeLoader = root.has("dont-include-loader").get<bool>();
if (!dontIncludeLoader) {
res.push_back(

View file

@ -535,7 +535,7 @@ void Loader::Impl::findProblems() {
switch(dep.importance) {
case ModMetadata::Dependency::Importance::Suggested:
if (!Mod::get()->template getSavedValue<bool>(dismissKey)) {
if (!Mod::get()->getSavedValue<bool>(dismissKey)) {
this->addProblem({
LoadProblem::Type::Suggestion,
mod,
@ -548,7 +548,7 @@ void Loader::Impl::findProblems() {
}
break;
case ModMetadata::Dependency::Importance::Recommended:
if (!Mod::get()->template getSavedValue<bool>(dismissKey)) {
if (!Mod::get()->getSavedValue<bool>(dismissKey)) {
this->addProblem({
LoadProblem::Type::Recommendation,
mod,

View file

@ -57,7 +57,7 @@ bool ModMetadata::Dependency::isResolved() const {
bool ModMetadata::Incompatibility::isResolved() const {
return this->importance != Importance::Breaking ||
(!this->mod || !this->version.compare(this->mod->getVersion()));
(!this->mod || !this->mod->isEnabled() || !this->version.compare(this->mod->getVersion()));
}
static std::string sanitizeDetailsData(std::string const& str) {
@ -170,7 +170,7 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
return Err("[mod.json] can not have both \"developer\" and \"developers\" specified");
}
for (auto& dev : root.needs("developers").items()) {
impl->m_developers.push_back(dev.template get<std::string>());
impl->m_developers.push_back(dev.get<std::string>());
}
}
else {
@ -290,7 +290,7 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
// Tags. Actual validation is done when interacting with the server in the UI
for (auto& tag : root.has("tags").items()) {
impl->m_tags.insert(tag.template get<std::string>());
impl->m_tags.insert(tag.get<std::string>());
}
// with new cli, binary name is always mod id

View file

@ -16,7 +16,7 @@ static void parseCommon(T& sett, JsonMaybeObject& obj) {
obj.has("description").into(sett.description);
if (auto defValue = obj.needs("default")) {
// Platform-specific default value
if (defValue.template is<matjson::Object>()) {
if (defValue.is<matjson::Object>()) {
auto def = defValue.obj();
if (auto plat = def.has(PlatformID::toShortString(GEODE_PLATFORM_TARGET, true))) {
plat.into(sett.defaultValue);

View file

@ -1,6 +1,8 @@
#include "SettingNodeV3.hpp"
#include <Geode/loader/SettingNode.hpp>
#include <Geode/utils/ColorProvider.hpp>
#include <Geode/utils/ranges.hpp>
#include <Geode/loader/Dirs.hpp>
#include <ui/mods/GeodeStyle.hpp>
class SettingNodeSizeChangeEventV3::Impl final {
@ -349,6 +351,10 @@ bool StringSettingNodeV3::init(std::shared_ptr<StringSettingV3> setting, float w
});
m_input->setScale(.7f);
m_input->setString(this->getSetting()->getValue());
if (auto filter = this->getSetting()->getAllowedCharacters()) {
m_input->setFilter(*filter);
}
this->getButtonMenu()->addChildAtPosition(m_input, Anchor::Center);
if (setting->getEnumOptions()) {

View file

@ -3,6 +3,8 @@
#include <Geode/loader/SettingV3.hpp>
#include <Geode/loader/SettingNode.hpp>
#include <Geode/binding/CCMenuItemToggler.hpp>
#include <Geode/binding/ColorChannelSprite.hpp>
#include <Geode/binding/Slider.hpp>
#include <Geode/ui/ColorPickPopup.hpp>
using namespace geode::prelude;

View file

@ -1,6 +1,9 @@
#include <Geode/loader/SettingV3.hpp>
#include <Geode/loader/SettingEvent.hpp>
#include <Geode/loader/ModSettingsManager.hpp>
#include <Geode/utils/ranges.hpp>
#include <Geode/utils/string.hpp>
#include <Geode/loader/Dirs.hpp>
#include <Geode/utils/JsonValidation.hpp>
#include <regex>
#include "SettingNodeV3.hpp"
@ -51,7 +54,7 @@ namespace enable_if_parsing {
}
Result<> eval(std::string const& defaultModID) const override {
if (auto mod = Loader::get()->getLoadedMod(modID)) {
if (mod->template getSettingValue<bool>(settingID)) {
if (mod->getSettingValue<bool>(settingID)) {
return Ok();
}
// This is an if-check just in case, even though check() should already
@ -83,7 +86,7 @@ namespace enable_if_parsing {
}
Result<> eval(std::string const& defaultModID) const override {
if (auto mod = Loader::get()->getLoadedMod(modID)) {
if (mod->template getSavedValue<bool>(savedValue)) {
if (mod->getSavedValue<bool>(savedValue)) {
return Ok();
}
if (modID == defaultModID) {
@ -482,7 +485,7 @@ void SettingV3::init(std::string const& key, std::string const& modID, JsonExpec
// Keys every setting must have
json.needs("type");
for (auto& plat : json.has("platforms").items()) {
ranges::push(m_impl->platforms, PlatformID::getCovered(plat.template get<std::string>()));
ranges::push(m_impl->platforms, PlatformID::getCovered(plat.get<std::string>()));
}
}
@ -492,7 +495,7 @@ void SettingV3::parseNameAndDescription(JsonExpectedValue& json) {
}
void SettingV3::parseEnableIf(JsonExpectedValue& json) {
json.has("enable-if")
.template mustBe<std::string>("a valid \"enable-if\" scheme", [this](std::string const& str) -> Result<> {
.mustBe<std::string>("a valid \"enable-if\" scheme", [this](std::string const& str) -> Result<> {
GEODE_UNWRAP_INTO(auto tree, enable_if_parsing::Parser::parse(str, m_impl->modID));
GEODE_UNWRAP(tree->check());
m_impl->enableIfTree = std::move(tree);
@ -745,10 +748,10 @@ Result<std::shared_ptr<IntSettingV3>> IntSettingV3::parse(std::string const& key
// This silly code is because step size being 0 is what defines if they are enabled
// Small arrows are enabled by default
if (!root.has("control").has("arrows").template get<bool>(true)) {
if (!root.has("control").has("arrows").get<bool>(true)) {
ret->m_impl->controls.arrowStepSize = 0;
}
if (!root.has("control").has("big-arrows").template get<bool>()) {
if (!root.has("control").has("big-arrows").get<bool>()) {
ret->m_impl->controls.bigArrowStepSize = 0;
}
@ -876,10 +879,10 @@ Result<std::shared_ptr<FloatSettingV3>> FloatSettingV3::parse(std::string const&
// Disable arrows if they aren't enabled
// Small arrows are enabled by default
if (!root.has("control").has("arrows").template get<bool>(true)) {
if (!root.has("control").has("arrows").get<bool>(true)) {
ret->m_impl->controls.arrowStepSize = 0;
}
if (!root.has("control").has("big-arrows").template get<bool>()) {
if (!root.has("control").has("big-arrows").get<bool>()) {
ret->m_impl->controls.bigArrowStepSize = 0;
}

View file

@ -109,9 +109,9 @@ void updater::downloadLatestLoaderResources() {
// find release asset
for (auto asset : root.needs("assets").iterate()) {
auto obj = asset.obj();
if (obj.needs("name").template get<std::string>() == "resources.zip") {
if (obj.needs("name").get<std::string>() == "resources.zip") {
updater::tryDownloadLoaderResources(
obj.needs("browser_download_url").template get<std::string>(),
obj.needs("browser_download_url").get<std::string>(),
false
);
return;
@ -213,9 +213,9 @@ void updater::downloadLoaderResources(bool useLatestRelease) {
// find release asset
for (auto asset : root.needs("assets").iterate()) {
auto obj = asset.obj();
if (obj.needs("name").template get<std::string>() == "resources.zip") {
if (obj.needs("name").get<std::string>() == "resources.zip") {
updater::tryDownloadLoaderResources(
obj.needs("browser_download_url").template get<std::string>(),
obj.needs("browser_download_url").get<std::string>(),
false
);
return *response;
@ -391,11 +391,11 @@ void updater::checkForLoaderUpdates() {
for (auto asset : root.needs("assets").iterate()) {
auto obj = asset.obj();
if (string::endsWith(
obj.needs("name").template get<std::string>(),
obj.needs("name").get<std::string>(),
fmt::format("{}.zip", PlatformID::toShortString(GEODE_PLATFORM_TARGET, true))
)) {
updater::downloadLoaderUpdate(
obj.needs("browser_download_url").template get<std::string>()
obj.needs("browser_download_url").get<std::string>()
);
return;
}

View file

@ -115,19 +115,19 @@ void console::setup() {
path = std::string(buf, count - 1);
}
// count == 0 => not a console and not a file, assume it's closed
// wine does something weird with /dev/null? not sure tbh but it's definitely up to no good
// TODO: the isWine check is pretty hacky but without it the game does not launch at all and i cba to figure it out rn
if ((count == 0 || path.ends_with("\\dev\\null"))) {
s_outHandle = nullptr;
CloseHandle(GetStdHandle(STD_OUTPUT_HANDLE));
CloseHandle(GetStdHandle(STD_INPUT_HANDLE));
CloseHandle(GetStdHandle(STD_ERROR_HANDLE));
FreeConsole();
SetStdHandle(STD_OUTPUT_HANDLE, nullptr);
SetStdHandle(STD_INPUT_HANDLE, nullptr);
SetStdHandle(STD_ERROR_HANDLE, nullptr);
}
// TODO: this code causes a crash when piping game's output somewhere (and in some other cases), so it's removed for now
// // count == 0 => not a console and not a file, assume it's closed
// // wine does something weird with /dev/null? not sure tbh but it's definitely up to no good
// if ((count == 0 || path.ends_with("\\dev\\null"))) {
// s_outHandle = nullptr;
// CloseHandle(GetStdHandle(STD_OUTPUT_HANDLE));
// CloseHandle(GetStdHandle(STD_INPUT_HANDLE));
// CloseHandle(GetStdHandle(STD_ERROR_HANDLE));
// FreeConsole();
// SetStdHandle(STD_OUTPUT_HANDLE, nullptr);
// SetStdHandle(STD_INPUT_HANDLE, nullptr);
// SetStdHandle(STD_ERROR_HANDLE, nullptr);
// }
}
// clion console supports escape codes but we can't query that because it's a named pipe

View file

@ -189,7 +189,7 @@ static ServerError parseServerError(web::WebResponse const& error) {
if (json.is_object() && json.contains("error")) {
return ServerError(
error.code(),
"{}", json.template get<std::string>("error")
"{}", json.get<std::string>("error")
);
}
else {
@ -264,13 +264,13 @@ Result<ServerModVersion> ServerModVersion::parse(matjson::Value const& raw) {
auto res = ServerModVersion();
res.metadata.setGeodeVersion(root.needs("geode").template get<VersionInfo>());
res.metadata.setGeodeVersion(root.needs("geode").get<VersionInfo>());
// Verify target GD version
auto gd_obj = root.needs("gd").obj();
std::string gd = "0.000";
if (gd_obj.has(GEODE_PLATFORM_SHORT_IDENTIFIER)) {
gd = gd_obj.has(GEODE_PLATFORM_SHORT_IDENTIFIER).template get<std::string>();
gd = gd_obj.has(GEODE_PLATFORM_SHORT_IDENTIFIER). get<std::string>();
}
if (gd != "*") {
@ -283,11 +283,11 @@ Result<ServerModVersion> ServerModVersion::parse(matjson::Value const& raw) {
root.needs("hash").into(res.hash);
// Get mod metadata info
res.metadata.setID(root.needs("mod_id").template get<std::string>());
res.metadata.setName(root.needs("name").template get<std::string>());
res.metadata.setDescription(root.needs("description").template get<std::string>());
res.metadata.setVersion(root.needs("version").template get<VersionInfo>());
res.metadata.setIsAPI(root.needs("api").template get<bool>());
res.metadata.setID(root.needs("mod_id").get<std::string>());
res.metadata.setName(root.needs("name").get<std::string>());
res.metadata.setDescription(root.needs("description").get<std::string>());
res.metadata.setVersion(root.needs("version").get<VersionInfo>());
res.metadata.setIsAPI(root.needs("api").get<bool>());
std::vector<ModMetadata::Dependency> dependencies {};
for (auto dep : root.has("dependencies").iterate()) {
@ -433,10 +433,10 @@ Result<ServerModMetadata> ServerModMetadata::parse(matjson::Value const& raw) {
root.has("changelog").into(res.changelog);
root.has("repository").into(res.repository);
if (root.has("created_at")) {
GEODE_UNWRAP_INTO(res.createdAt, ServerDateTime::parse(root.has("created_at").template get<std::string>()));
GEODE_UNWRAP_INTO(res.createdAt, ServerDateTime::parse(root.has("created_at").get<std::string>()));
}
if (root.has("updated_at")) {
GEODE_UNWRAP_INTO(res.updatedAt, ServerDateTime::parse(root.has("updated_at").template get<std::string>()));
GEODE_UNWRAP_INTO(res.updatedAt, ServerDateTime::parse(root.has("updated_at").get<std::string>()));
}
std::vector<std::string> developerNames;
@ -470,7 +470,7 @@ Result<ServerModMetadata> ServerModMetadata::parse(matjson::Value const& raw) {
}
for (auto item : root.has("tags").iterate()) {
res.tags.insert(item.template get<std::string>());
res.tags.insert(item.get<std::string>());
}
root.needs("download_count").into(res.downloadCount);

View file

@ -159,9 +159,15 @@ void geode::openChangelogPopup(Mod* mod) {
}
void geode::openSettingsPopup(Mod* mod) {
openSettingsPopup(mod, true);
}
Popup<Mod*>* geode::openSettingsPopup(Mod* mod, bool disableGeodeTheme) {
if (mod->hasSettings()) {
ModSettingsPopup::create(mod)->show();
auto popup = ModSettingsPopup::create(mod, disableGeodeTheme);
popup->show();
return popup;
}
return nullptr;
}
class ModLogoSprite : public CCNode {

View file

@ -70,16 +70,21 @@ $on_mod(Loaded) {
Loader::get()->queueInMainThread([updateColors = updateColors] {
// this code is ran during static init, where settings aren't loaded yet, and getSettingValue will always return false.
// because of that, we have to delay it until next frame.
updateColors(Mod::get()->template getSettingValue<bool>("enable-geode-theme"));
updateColors(Mod::get()->getSettingValue<bool>("enable-geode-theme"));
});
}
bool GeodeSquareSprite::init(CCSprite* top, bool* state) {
if (!CCSprite::initWithFile(isGeodeTheme() ? "GE_button_05.png"_spr : "GJ_button_01.png"))
bool isGeodeTheme(bool forceDisableTheme) {
return !forceDisableTheme && Mod::get()->getSettingValue<bool>("enable-geode-theme");
}
bool GeodeSquareSprite::init(CCSprite* top, bool* state, bool forceDisableTheme) {
if (!CCSprite::initWithFile(isGeodeTheme(forceDisableTheme) ? "GE_button_05.png"_spr : "GJ_button_01.png"))
return false;
m_stateSrc = state;
m_topSprite = top;
m_forceDisableTheme = forceDisableTheme;
limitNodeSize(top, m_obContentSize * .65f, 2.f, .1f);
this->addChildAtPosition(top, Anchor::Center);
@ -94,7 +99,7 @@ bool GeodeSquareSprite::init(CCSprite* top, bool* state) {
void GeodeSquareSprite::updateImage() {
this->setTexture(CCTextureCache::get()->addImage(
(m_state ? "GJ_button_02.png" : (isGeodeTheme() ? "GE_button_05.png"_spr : "GJ_button_01.png")),
(m_state ? "GJ_button_02.png" : (isGeodeTheme(m_forceDisableTheme) ? "GE_button_05.png"_spr : "GJ_button_01.png")),
false
));
}
@ -106,18 +111,18 @@ void GeodeSquareSprite::update(float dt) {
}
}
GeodeSquareSprite* GeodeSquareSprite::create(const char* top, bool* state) {
GeodeSquareSprite* GeodeSquareSprite::create(const char* top, bool* state, bool forceDisableTheme) {
auto ret = new GeodeSquareSprite();
if (ret->init(CCSprite::create(top), state)) {
if (ret->init(CCSprite::create(top), state, forceDisableTheme)) {
ret->autorelease();
return ret;
}
delete ret;
return nullptr;
}
GeodeSquareSprite* GeodeSquareSprite::createWithSpriteFrameName(const char* top, bool* state) {
GeodeSquareSprite* GeodeSquareSprite::createWithSpriteFrameName(const char* top, bool* state, bool forceDisableTheme) {
auto ret = new GeodeSquareSprite();
if (ret->init(CCSprite::createWithSpriteFrameName(top), state)) {
if (ret->init(CCSprite::createWithSpriteFrameName(top), state, forceDisableTheme)) {
ret->autorelease();
return ret;
}
@ -142,8 +147,8 @@ CCNode* createLoadingCircle(float sideLength, const char* id) {
return spinner;
}
const char* getGeodeButtonSpriteName(GeodeButtonSprite spr) {
if (isGeodeTheme()) {
const char* getGeodeButtonSpriteName(GeodeButtonSprite spr, bool forceDisableTheme) {
if (isGeodeTheme(forceDisableTheme)) {
switch (spr) {
default:
case GeodeButtonSprite::Default: return "GE_button_05.png"_spr;
@ -165,18 +170,18 @@ const char* getGeodeButtonSpriteName(GeodeButtonSprite spr) {
}
}
IconButtonSprite* createGeodeButton(CCNode* icon, std::string const& text, GeodeButtonSprite bg) {
return IconButtonSprite::create(getGeodeButtonSpriteName(bg), icon, text.c_str(), "bigFont.fnt");
IconButtonSprite* createGeodeButton(CCNode* icon, std::string const& text, GeodeButtonSprite bg, bool forceDisableTheme) {
return IconButtonSprite::create(getGeodeButtonSpriteName(bg, forceDisableTheme), icon, text.c_str(), "bigFont.fnt");
}
ButtonSprite* createGeodeButton(std::string const& text, int width, bool gold, bool absolute, GeodeButtonSprite bg) {
return ButtonSprite::create(text.c_str(), width, absolute, gold ? "goldFont.fnt" : "bigFont.fnt", getGeodeButtonSpriteName(bg), 0.0f, .8f);
ButtonSprite* createGeodeButton(std::string const& text, int width, bool gold, bool absolute, GeodeButtonSprite bg, bool forceDisableTheme) {
return ButtonSprite::create(text.c_str(), width, absolute, gold ? "goldFont.fnt" : "bigFont.fnt", getGeodeButtonSpriteName(bg, forceDisableTheme), 0.0f, .8f);
}
ButtonSprite* createGeodeButton(std::string const& text, bool gold, GeodeButtonSprite bg) {
return ButtonSprite::create(text.c_str(), gold ? "goldFont.fnt" : "bigFont.fnt", getGeodeButtonSpriteName(bg), .8f);
ButtonSprite* createGeodeButton(std::string const& text, bool gold, GeodeButtonSprite bg, bool forceDisableTheme) {
return ButtonSprite::create(text.c_str(), gold ? "goldFont.fnt" : "bigFont.fnt", getGeodeButtonSpriteName(bg, forceDisableTheme), .8f);
}
CircleButtonSprite* createGeodeCircleButton(CCSprite* top, float scale, CircleBaseSize size, bool altColor) {
const auto geodeTheme = isGeodeTheme();
CircleButtonSprite* createGeodeCircleButton(CCSprite* top, float scale, CircleBaseSize size, bool altColor, bool forceDisableTheme) {
const auto geodeTheme = isGeodeTheme(forceDisableTheme);
auto ret = CircleButtonSprite::create(
top, geodeTheme ? (altColor ? CircleBaseColor::DarkAqua : CircleBaseColor::DarkPurple) : CircleBaseColor::Green, size
);
@ -184,21 +189,16 @@ CircleButtonSprite* createGeodeCircleButton(CCSprite* top, float scale, CircleBa
return ret;
}
ButtonSprite* createGeodeTagLabel(std::string const& text, std::optional<std::pair<ccColor3B, ccColor3B>> const& color) {
ButtonSprite* createTagLabel(std::string const& text, std::pair<ccColor3B, ccColor3B> const& color) {
auto label = ButtonSprite::create(text.c_str(), "bigFont.fnt", "white-square.png"_spr, .8f);
if (color) {
label->m_label->setColor(color->first);
label->m_BGSprite->setColor(color->second);
}
else {
auto def = geodeTagColor(text);
label->m_label->setColor(def.first);
label->m_BGSprite->setColor(def.second);
}
label->m_label->setColor(color.first);
label->m_BGSprite->setColor(color.second);
return label;
}
std::pair<ccColor3B, ccColor3B> geodeTagColor(std::string_view const& text) {
ButtonSprite* createGeodeTagLabel(std::string_view tag) {
return createTagLabel(geodeTagName(tag), geodeTagColors(tag));
}
std::pair<ccColor3B, ccColor3B> geodeTagColors(std::string_view tag) {
static std::array TAG_COLORS {
std::make_pair(ccc3(240, 233, 255), ccc3(130, 123, 163)),
std::make_pair(ccc3(234, 255, 245), ccc3(123, 163, 136)),
@ -206,20 +206,32 @@ std::pair<ccColor3B, ccColor3B> geodeTagColor(std::string_view const& text) {
std::make_pair(ccc3(255, 253, 240), ccc3(163, 157, 123)),
std::make_pair(ccc3(255, 242, 240), ccc3(163, 128, 123)),
};
return TAG_COLORS[hash(text) % 5932 % TAG_COLORS.size()];
}
ListBorders* createGeodeListBorders(CCSize const& size) {
auto ret = ListBorders::create();
if (isGeodeTheme()) {
ret->setSpriteFrames("geode-list-top.png"_spr, "geode-list-side.png"_spr, 2);
if (tag == "modtober24") {
return std::make_pair(ccc3(225, 236, 245), ccc3(82, 139, 201));
}
ret->setContentSize(size);
return ret;
return TAG_COLORS[hash(tag) % 5932 % TAG_COLORS.size()];
}
std::string geodeTagName(std::string_view tag) {
// todo in v4: rework tags to use a server-provided display name instead
if (tag == "modtober24") {
return "Modtober 2024";
}
// Everything else just capitalize and that's it
auto readable = std::string(tag);
readable[0] = std::toupper(readable[0]);
return readable;
}
bool isGeodeTheme() {
return Mod::get()->template getSettingValue<bool>("enable-geode-theme");
ListBorders* createGeodeListBorders(CCSize const& size, bool forceDisableTheme) {
auto ret = ListBorders::create();
const bool geodeTheme = isGeodeTheme(forceDisableTheme);
if (geodeTheme) {
ret->setSpriteFrames("geode-list-top.png"_spr, "geode-list-side.png"_spr, 2);
ret->setContentSize(size);
} else {
ret->setContentSize(size + ccp(5, 5));
}
return ret;
}
bool GeodeTabSprite::init(const char* iconFrame, const char* text, float width, bool altColor) {

View file

@ -16,11 +16,16 @@ enum class GeodePopupStyle {
Alt2,
};
bool isGeodeTheme(bool forceDisableTheme = false);
template <class... Args>
class GeodePopup : public Popup<Args...> {
protected:
bool m_forceDisableTheme = false;
bool init(float width, float height, Args... args, GeodePopupStyle style = GeodePopupStyle::Default, bool forceDisableTheme = false) {
const bool geodeTheme = !forceDisableTheme && Mod::get()->template getSettingValue<bool>("enable-geode-theme");
m_forceDisableTheme = forceDisableTheme;
const bool geodeTheme = isGeodeTheme(forceDisableTheme);
const char* bg;
switch (style) {
default:
@ -49,16 +54,17 @@ class GeodeSquareSprite : public CCSprite {
protected:
bool* m_stateSrc = nullptr;
bool m_state = false;
bool m_forceDisableTheme = false;
CCSprite* m_topSprite;
bool init(CCSprite* top, bool* state);
bool init(CCSprite* top, bool* state, bool forceDisableTheme = false);
void update(float dt) override;
void updateImage();
public:
static GeodeSquareSprite* create(const char* top, bool* state = nullptr);
static GeodeSquareSprite* createWithSpriteFrameName(const char* top, bool* state = nullptr);
static GeodeSquareSprite* create(const char* top, bool* state = nullptr, bool forceDisableTheme = false);
static GeodeSquareSprite* createWithSpriteFrameName(const char* top, bool* state = nullptr, bool forceDisableTheme = false);
CCSprite* getTopSprite() const;
void setState(bool state);
@ -73,19 +79,19 @@ enum class GeodeButtonSprite {
Enable,
Gray,
};
const char* getGeodeButtonSpriteName(GeodeButtonSprite spr);
IconButtonSprite* createGeodeButton(CCNode* icon, std::string const& text, GeodeButtonSprite bg = GeodeButtonSprite::Default);
ButtonSprite* createGeodeButton(std::string const& text, int width, bool absolute = false, bool gold = false, GeodeButtonSprite bg = GeodeButtonSprite::Default);
ButtonSprite* createGeodeButton(std::string const& text, bool gold = false, GeodeButtonSprite bg = GeodeButtonSprite::Default);
const char* getGeodeButtonSpriteName(GeodeButtonSprite spr, bool forceDisableTheme = false);
IconButtonSprite* createGeodeButton(CCNode* icon, std::string const& text, GeodeButtonSprite bg = GeodeButtonSprite::Default, bool forceDisableTheme = false);
ButtonSprite* createGeodeButton(std::string const& text, int width, bool absolute = false, bool gold = false, GeodeButtonSprite bg = GeodeButtonSprite::Default, bool forceDisableTheme = false);
ButtonSprite* createGeodeButton(std::string const& text, bool gold = false, GeodeButtonSprite bg = GeodeButtonSprite::Default, bool forceDisableTheme = false);
CircleButtonSprite* createGeodeCircleButton(CCSprite* top, float scale = 1.f, CircleBaseSize size = CircleBaseSize::Medium, bool altColor = false);
CircleButtonSprite* createGeodeCircleButton(CCSprite* top, float scale = 1.f, CircleBaseSize size = CircleBaseSize::Medium, bool altColor = false, bool forceDisableTheme = false);
ButtonSprite* createGeodeTagLabel(std::string const& text, std::optional<std::pair<ccColor3B, ccColor3B>> const& color = std::nullopt);
std::pair<ccColor3B, ccColor3B> geodeTagColor(std::string_view const& text);
ButtonSprite* createTagLabel(std::string const& text, std::pair<ccColor3B, ccColor3B> const& color);
ButtonSprite* createGeodeTagLabel(std::string_view tag);
std::pair<ccColor3B, ccColor3B> geodeTagColors(std::string_view tag);
std::string geodeTagName(std::string_view tag);
ListBorders* createGeodeListBorders(CCSize const& size);
bool isGeodeTheme();
ListBorders* createGeodeListBorders(CCSize const& size, bool forceDisableTheme = false);
class GeodeTabSprite : public CCNode {
protected:

View file

@ -430,14 +430,15 @@ bool ModsLayer::init() {
// Increment touch priority so the mods in the list don't override
mainTabs->setTouchPriority(-150);
for (auto item : std::initializer_list<std::tuple<const char*, const char*, ModListSource*, const char*>> {
{ "download.png"_spr, "Installed", InstalledModListSource::get(InstalledModListType::All), "installed-button" },
{ "GJ_starsIcon_001.png", "Featured", ServerModListSource::get(ServerModListType::Featured), "featured-button" },
{ "globe.png"_spr, "Download", ServerModListSource::get(ServerModListType::Download), "download-button" },
{ "GJ_timeIcon_001.png", "Recent", ServerModListSource::get(ServerModListType::Recent), "recent-button" },
for (auto item : std::initializer_list<std::tuple<const char*, const char*, ModListSource*, const char*, bool>> {
{ "download.png"_spr, "Installed", InstalledModListSource::get(InstalledModListType::All), "installed-button", false },
{ "GJ_starsIcon_001.png", "Featured", ServerModListSource::get(ServerModListType::Featured), "featured-button", false },
{ "globe.png"_spr, "Download", ServerModListSource::get(ServerModListType::Download), "download-button", false },
{ "GJ_timeIcon_001.png", "Recent", ServerModListSource::get(ServerModListType::Recent), "recent-button", false },
{ "d_artCloud_03_001.png", "Modtober", ServerModListSource::get(ServerModListType::Modtober24), "modtober-button", true },
}) {
auto btn = CCMenuItemSpriteExtra::create(
GeodeTabSprite::create(std::get<0>(item), std::get<1>(item), 120),
GeodeTabSprite::create(std::get<0>(item), std::get<1>(item), 100, std::get<4>(item)),
this, menu_selector(ModsLayer::onTab)
);
btn->setUserData(std::get<2>(item));
@ -683,7 +684,7 @@ void ModsLayer::onSearch(CCObject*) {
}
}
void ModsLayer::onTheme(CCObject*) {
auto old = Mod::get()->template getSettingValue<bool>("enable-geode-theme");
auto old = Mod::get()->getSettingValue<bool>("enable-geode-theme");
createQuickPopup(
"Switch Theme",
fmt::format(
@ -704,7 +705,7 @@ void ModsLayer::onTheme(CCObject*) {
);
}
void ModsLayer::onSettings(CCObject*) {
openSettingsPopup(Mod::get());
openSettingsPopup(Mod::get(), false);
}
ModsLayer* ModsLayer::create() {

View file

@ -84,12 +84,12 @@ bool ModItem::init(ModSource&& source) {
);
m_infoContainer->addChild(m_developers);
m_restartRequiredLabel = createGeodeTagLabel(
m_restartRequiredLabel = createTagLabel(
"Restart Required",
{{
{
to3B(ColorProvider::get()->color("mod-list-restart-required-label"_spr)),
to3B(ColorProvider::get()->color("mod-list-restart-required-label-bg"_spr))
}}
}
);
m_restartRequiredLabel->setID("restart-required-label");
m_restartRequiredLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(std::nullopt, .75f));
@ -208,6 +208,11 @@ bool ModItem::init(ModSource&& source) {
paidModLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .8f));
m_titleContainer->addChild(paidModLabel);
}
if (metadata.tags.contains("modtober24")) {
auto modtoberLabel = CCSprite::createWithSpriteFrameName("tag-modtober.png"_spr);
modtoberLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(.1f, .8f));
m_titleContainer->addChild(modtoberLabel);
}
// Show mod download count here already so people can make informed decisions
// on which mods to install
@ -363,7 +368,10 @@ void ModItem::updateState() {
m_bg->setColor("mod-list-paid-color"_cc3b);
m_bg->setOpacity(55);
}
if (metadata.tags.contains("modtober24")) {
m_bg->setColor(ccc3(63, 91, 138));
m_bg->setOpacity(85);
}
if (isGeodeTheme() && metadata.featured) {
m_bg->setColor("mod-list-featured-color"_cc3b);
m_bg->setOpacity(65);

View file

@ -4,6 +4,7 @@
#include "../popups/SortPopup.hpp"
#include "../GeodeStyle.hpp"
#include "../ModsLayer.hpp"
#include "../popups/ModtoberPopup.hpp"
bool ModList::init(ModListSource* src, CCSize const& size) {
if (!CCNode::init())
@ -249,6 +250,34 @@ bool ModList::init(ModListSource* src, CCSize const& size) {
m_topContainer->addChild(m_searchMenu);
// Modtober banner; this can be removed after Modtober 2024 is over!
if (
auto src = typeinfo_cast<ServerModListSource*>(m_source);
src && src->getType() == ServerModListType::Modtober24
) {
auto menu = CCMenu::create();
menu->setID("modtober-banner");
menu->ignoreAnchorPointForPosition(false);
menu->setContentSize({ size.width, 30 });
auto banner = CCSprite::createWithSpriteFrameName("modtober24-banner.png"_spr);
limitNodeWidth(banner, size.width, 1.f, .1f);
menu->addChildAtPosition(banner, Anchor::Center);
auto label = CCLabelBMFont::create("Modtober 2024 is Here!", "bigFont.fnt");
label->setScale(.5f);
menu->addChildAtPosition(label, Anchor::Left, ccp(10, 0), ccp(0, .5f));
auto aboutSpr = createGeodeButton("About");
aboutSpr->setScale(.5f);
auto aboutBtn = CCMenuItemSpriteExtra::create(
aboutSpr, this, menu_selector(ModList::onModtoberInfo)
);
menu->addChildAtPosition(aboutBtn, Anchor::Right, ccp(-35, 0));
m_topContainer->addChild(menu);
}
m_topContainer->setLayout(
ColumnLayout::create()
->setGap(0)
@ -492,7 +521,7 @@ void ModList::updateTopContainer() {
auto oldPosition = oldPositionArea > 0.f ?
m_list->m_contentLayer->getPositionY() / oldPositionArea :
-1.f;
// Update list size to account for the top menu
// (giving a little bit of extra padding for it, the same size as gap)
m_list->setContentHeight(
@ -501,6 +530,8 @@ void ModList::updateTopContainer() {
static_cast<AxisLayout*>(m_list->m_contentLayer->getLayout())->getGap() :
this->getContentHeight()
);
static_cast<ColumnLayout*>(m_list->m_contentLayer->getLayout())->setAutoGrowAxis(m_list->getContentHeight());
m_list->m_contentLayer->updateLayout();
// Preserve relative scroll position
m_list->m_contentLayer->setPositionY((
@ -659,6 +690,9 @@ void ModList::onToggleErrors(CCObject*) {
void ModList::onUpdateAll(CCObject*) {
server::ModDownloadManager::get()->startUpdateAll();
}
void ModList::onModtoberInfo(CCObject*) {
ModtoberPopup::create()->show();
}
size_t ModList::getPage() const {
return m_page;

View file

@ -71,6 +71,7 @@ protected:
void onToggleUpdates(CCObject*);
void onToggleErrors(CCObject*);
void onUpdateAll(CCObject*);
void onModtoberInfo(CCObject*);
public:
static ModList* create(ModListSource* src, CCSize const& size);

View file

@ -131,7 +131,7 @@ bool FiltersPopup::setup(ModListSource* src) {
m_mainLayer->addChildAtPosition(inputContainer, Anchor::Bottom, ccp(0, 60), ccp(.5f, .5f));
}
auto okSpr = createGeodeButton("OK");
auto okSpr = createGeodeButton("OK", false, GeodeButtonSprite::Default, m_forceDisableTheme);
okSpr->setScale(.7f);
auto okBtn = CCMenuItemSpriteExtra::create(
okSpr, this, menu_selector(FiltersPopup::onClose)

View file

@ -1,6 +1,7 @@
#include "ModPopup.hpp"
#include <Geode/binding/ButtonSprite.hpp>
#include <Geode/ui/MDTextArea.hpp>
#include <Geode/ui/TextInput.hpp>
#include <Geode/utils/web.hpp>
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/ModSettingsManager.hpp>
@ -10,6 +11,7 @@
#include "../settings/ModSettingsPopup.hpp"
#include "../../../internal/about.hpp"
#include "../../GeodeUIEvent.hpp"
#include "../popups/ModtoberPopup.hpp"
class FetchTextArea : public CCNode {
public:
@ -300,12 +302,12 @@ bool ModPopup::setup(ModSource&& src) {
manageTitle->setOpacity(195);
manageContainer->addChildAtPosition(manageTitle, Anchor::Left, ccp(0, 0), ccp(0, .5f));
m_restartRequiredLabel = createGeodeTagLabel(
m_restartRequiredLabel = createTagLabel(
"Restart Required",
{{
{
to3B(ColorProvider::get()->color("mod-list-restart-required-label"_spr)),
to3B(ColorProvider::get()->color("mod-list-restart-required-label-bg"_spr))
}}
}
);
m_restartRequiredLabel->setScale(.3f);
manageContainer->addChildAtPosition(m_restartRequiredLabel, Anchor::Right, ccp(0, 0), ccp(1, .5f));
@ -334,7 +336,8 @@ bool ModPopup::setup(ModSource&& src) {
auto updateModSpr = createGeodeButton(
CCSprite::createWithSpriteFrameName("update.png"_spr),
"Update",
GeodeButtonSprite::Install
GeodeButtonSprite::Install,
m_forceDisableTheme
);
updateModSpr->setScale(.5f);
m_updateBtn = CCMenuItemSpriteExtra::create(
@ -345,13 +348,15 @@ bool ModPopup::setup(ModSource&& src) {
auto enableModOffSpr = createGeodeButton(
CCSprite::createWithSpriteFrameName("GJ_completesIcon_001.png"),
"Enable",
GeodeButtonSprite::Enable
GeodeButtonSprite::Enable,
m_forceDisableTheme
);
enableModOffSpr->setScale(.5f);
auto enableModOnSpr = createGeodeButton(
CCSprite::createWithSpriteFrameName("GJ_deleteIcon_001.png"),
"Disable",
GeodeButtonSprite::Delete
GeodeButtonSprite::Delete,
m_forceDisableTheme
);
enableModOnSpr->setScale(.5f);
m_enableBtn = CCMenuItemToggler::create(
@ -364,7 +369,8 @@ bool ModPopup::setup(ModSource&& src) {
auto reenableModOffSpr = createGeodeButton(
CCSprite::createWithSpriteFrameName("reset.png"_spr),
"Re-Enable",
GeodeButtonSprite::Default
GeodeButtonSprite::Default,
m_forceDisableTheme
);
reenableModOffSpr->setScale(.5f);
auto reenableModOnSpr = createGeodeButton(
@ -383,7 +389,8 @@ bool ModPopup::setup(ModSource&& src) {
auto installModSpr = createGeodeButton(
CCSprite::createWithSpriteFrameName("GJ_downloadsIcon_001.png"),
"Install",
GeodeButtonSprite::Install
GeodeButtonSprite::Install,
m_forceDisableTheme
);
installModSpr->setScale(.5f);
m_installBtn = CCMenuItemSpriteExtra::create(
@ -394,7 +401,8 @@ bool ModPopup::setup(ModSource&& src) {
auto uninstallModSpr = createGeodeButton(
CCSprite::createWithSpriteFrameName("delete-white.png"_spr),
"Uninstall",
GeodeButtonSprite::Default
GeodeButtonSprite::Default,
m_forceDisableTheme
);
uninstallModSpr->setScale(.5f);
m_uninstallBtn = CCMenuItemSpriteExtra::create(
@ -405,7 +413,8 @@ bool ModPopup::setup(ModSource&& src) {
auto cancelDownloadSpr = createGeodeButton(
CCSprite::createWithSpriteFrameName("GJ_deleteIcon_001.png"),
"Cancel",
GeodeButtonSprite::Default
GeodeButtonSprite::Default,
m_forceDisableTheme
);
cancelDownloadSpr->setScale(.5f);
m_cancelBtn = CCMenuItemSpriteExtra::create(
@ -565,7 +574,7 @@ bool ModPopup::setup(ModSource&& src) {
m_settingsBG->setContentSize(ccp(35, 30) / linksBG->getScale());
m_buttonMenu->addChildAtPosition(m_settingsBG, Anchor::BottomLeft, ccp(28, 25));
auto settingsSpr = createGeodeCircleButton(CCSprite::createWithSpriteFrameName("settings.png"_spr));
auto settingsSpr = createGeodeCircleButton(CCSprite::createWithSpriteFrameName("settings.png"_spr), 1.f, CircleBaseSize::Medium, false, m_forceDisableTheme);
settingsSpr->setScale(.6f);
auto settingsBtn = CCMenuItemSpriteExtra::create(
settingsSpr, this, menu_selector(ModPopup::onSettings)
@ -880,10 +889,7 @@ void ModPopup::onLoadTags(typename server::ServerRequest<std::unordered_set<std:
m_tags->removeAllChildren();
for (auto& tag : data) {
auto readable = tag;
readable[0] = std::toupper(readable[0]);
auto colors = geodeTagColor(tag);
m_tags->addChild(createGeodeTagLabel(readable));
m_tags->addChild(createGeodeTagLabel(tag));
}
if (data.empty()) {
@ -891,6 +897,47 @@ void ModPopup::onLoadTags(typename server::ServerRequest<std::unordered_set<std:
label->setOpacity(120);
m_tags->addChild(label);
}
// This should probably be kept even after modtober ends,
// so the banner sprite must be kept
// If the build times from the cool popup become too long then we can
// probably move that to a normal FLAlert that explains "Modtober was
// this contest blah blah this mod was made for it"
else if (data.contains("modtober24")) {
auto menu = CCMenu::create();
menu->setID("modtober-banner");
menu->ignoreAnchorPointForPosition(false);
menu->setContentSize({ m_rightColumn->getContentWidth(), 25 });
auto banner = CCSprite::createWithSpriteFrameName("modtober24-banner-2.png"_spr);
limitNodeWidth(banner, m_rightColumn->getContentWidth(), 1.f, .1f);
menu->addChildAtPosition(banner, Anchor::Center);
auto label = CCLabelBMFont::create("Entry for Modtober 2024", "bigFont.fnt");
label->setScale(.35f);
menu->addChildAtPosition(label, Anchor::Left, ccp(10, 0), ccp(0, .5f));
auto aboutSpr = createGeodeButton("About", false, GeodeButtonSprite::Default, m_forceDisableTheme);
aboutSpr->setScale(.35f);
auto aboutBtn = CCMenuItemSpriteExtra::create(
aboutSpr, this, menu_selector(ModPopup::onModtoberInfo)
);
menu->addChildAtPosition(aboutBtn, Anchor::Right, ccp(-25, 0));
m_rightColumn->addChildAtPosition(menu, Anchor::Bottom, ccp(0, 0), ccp(.5f, 0));
m_modtoberBanner = menu;
// Force reload of all the tabs since otherwise their contents will overflow
for (auto& [_, tab] : m_tabs) {
if (tab.second && tab.second->getParent()) {
tab.second->removeFromParent();
}
tab.second = nullptr;
}
// This might cause a minor inconvenience to someone who opens the popup and
// immediately switches to changelog but is then forced back into details
this->loadTab(Tab::Details);
}
m_tags->updateLayout();
@ -920,12 +967,17 @@ void ModPopup::loadTab(ModPopup::Tab tab) {
btn.first->select(value == tab);
}
float modtoberBannerHeight = 0;
if (m_modtoberBanner) {
modtoberBannerHeight = 30;
}
if (auto existing = m_tabs.at(tab).second) {
m_currentTabPage = existing;
m_rightColumn->addChildAtPosition(existing, Anchor::Bottom);
m_rightColumn->addChildAtPosition(existing, Anchor::Bottom, ccp(0, modtoberBannerHeight));
}
else {
const auto size = (m_rightColumn->getContentSize() - ccp(0, 30));
const auto size = (m_rightColumn->getContentSize() - ccp(0, 30 + modtoberBannerHeight));
const float mdScale = .85f;
switch (tab) {
case Tab::Details: {
@ -955,7 +1007,7 @@ void ModPopup::loadTab(ModPopup::Tab tab) {
} break;
}
m_currentTabPage->setAnchorPoint({ .5f, .0f });
m_rightColumn->addChildAtPosition(m_currentTabPage, Anchor::Bottom);
m_rightColumn->addChildAtPosition(m_currentTabPage, Anchor::Bottom, ccp(0, modtoberBannerHeight));
m_tabs.at(tab).second = m_currentTabPage;
}
}
@ -1030,6 +1082,12 @@ void ModPopup::onLink(CCObject* sender) {
void ModPopup::onSupport(CCObject*) {
openSupportPopup(m_source.getMetadata());
}
void ModPopup::onModtoberInfo(CCObject*) {
// todo: if we want to get rid of the modtober popup sprite (because it's fucking massive)
// then we can just replace this with a normal FLAlert explaining
// "this mod was an entry for modtober 2024 blah blah blah"
ModtoberPopup::create()->show();
}
ModPopup* ModPopup::create(ModSource&& src) {
auto ret = new ModPopup();

View file

@ -35,6 +35,7 @@ protected:
ButtonSprite* m_restartRequiredLabel;
CCNode* m_rightColumn;
CCNode* m_currentTabPage = nullptr;
CCNode* m_modtoberBanner = nullptr;
std::unordered_map<Tab, std::pair<GeodeTabSprite*, Ref<CCNode>>> m_tabs;
EventListener<server::ServerRequest<server::ServerModMetadata>> m_statsListener;
EventListener<server::ServerRequest<std::unordered_set<std::string>>> m_tagsListener;
@ -63,6 +64,7 @@ protected:
void onSettings(CCObject*);
void onLink(CCObject*);
void onSupport(CCObject*);
void onModtoberInfo(CCObject*);
public:
void loadTab(Tab tab);

View file

@ -0,0 +1,44 @@
#include "ModtoberPopup.hpp"
#include <Geode/utils/web.hpp>
#include <Geode/loader/Mod.hpp>
#include <Geode/binding/ButtonSprite.hpp>
bool ModtoberPopup::setup() {
m_bgSprite->setVisible(false);
auto bg = CCSprite::createWithSpriteFrameName("modtober24-popup.png"_spr);
m_mainLayer->addChildAtPosition(bg, Anchor::Center);
auto supportSpr = createGeodeButton("Join", false, GeodeButtonSprite::Default, m_forceDisableTheme);
supportSpr->setScale(.8f);
auto supportBtn = CCMenuItemSpriteExtra::create(
supportSpr, this, menu_selector(ModtoberPopup::onDiscord)
);
m_buttonMenu->addChildAtPosition(supportBtn, Anchor::BottomRight, ccp(-65, 50));
return true;
}
void ModtoberPopup::onDiscord(CCObject*) {
createQuickPopup(
"Join Modtober",
"<cf>Modtober</c> is being hosted on the <cg>GD Programming</c> <ca>Discord Server</c>.\n"
"To participate, join GDP and read the rules for the contest in <co>#modtober-2024</c>",
"Cancel", "Join Discord",
[](auto, bool btn2) {
if (btn2) {
web::openLinkInBrowser("https://discord.gg/gd-programming-646101505417674758");
}
}
);
}
ModtoberPopup* ModtoberPopup::create() {
auto ret = new ModtoberPopup();
if (ret && ret->init(410, 270)) {
ret->autorelease();
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}

View file

@ -0,0 +1,16 @@
#pragma once
#include <Geode/ui/Popup.hpp>
#include "../GeodeStyle.hpp"
using namespace geode::prelude;
class ModtoberPopup : public GeodePopup<> {
protected:
bool setup() override;
void onDiscord(CCObject*);
public:
static ModtoberPopup* create();
};

View file

@ -6,6 +6,7 @@
#include <Geode/ui/ScrollLayer.hpp>
#include <Geode/utils/cocos.hpp>
#include <Geode/ui/General.hpp>
#include <Geode/ui/Scrollbar.hpp>
#include <loader/SettingNodeV3.hpp>
// needed for weightedFuzzyMatch
#include <ui/mods/sources/ModListSource.hpp>
@ -25,10 +26,7 @@ static bool matchSearch(SettingNodeV3* node, std::string const& query) {
else {
addToList |= weightedFuzzyMatch(setting->getKey(), query, 1, weighted);
}
if (auto desc = setting->getDescription()) {
addToList |= weightedFuzzyMatch(*desc, query, 0.02, weighted);
}
if (weighted < 2) {
if (weighted < 60 + 10 * query.size()) {
addToList = false;
}
return addToList;
@ -62,7 +60,7 @@ bool ModSettingsPopup::setup(Mod* mod) {
m_searchInput->setID("search-input");
searchContainer->addChildAtPosition(m_searchInput, Anchor::Left, ccp(7.5f, 0), ccp(0, .5f));
auto searchClearSpr = GeodeSquareSprite::createWithSpriteFrameName("GJ_deleteIcon_001.png");
auto searchClearSpr = GeodeSquareSprite::createWithSpriteFrameName("GJ_deleteIcon_001.png", nullptr, m_forceDisableTheme);
searchClearSpr->setScale(.45f);
m_searchClearBtn = CCMenuItemSpriteExtra::create(
searchClearSpr, this, menu_selector(ModSettingsPopup::onClearSearch)
@ -90,7 +88,7 @@ bool ModSettingsPopup::setup(Mod* mod) {
m_list->m_contentLayer->setLayout(
ColumnLayout::create()
->setAxisReverse(true)
->setAutoGrowAxis(layerSize.height)
->setAutoGrowAxis(m_list->getContentHeight())
->setCrossAxisOverflow(false)
->setAxisAlignment(AxisAlignment::End)
->setGap(0)
@ -106,7 +104,7 @@ bool ModSettingsPopup::setup(Mod* mod) {
// layer borders
m_mainLayer->addChildAtPosition(createGeodeListBorders(layerSize), Anchor::Center);
m_mainLayer->addChildAtPosition(createGeodeListBorders(layerSize, m_forceDisableTheme), Anchor::Center);
auto scrollBar = Scrollbar::create(m_list);
m_mainLayer->addChildAtPosition(
@ -121,14 +119,14 @@ bool ModSettingsPopup::setup(Mod* mod) {
m_applyMenu->getLayout()->ignoreInvisibleChildren(true);
m_applyMenu->setTouchPriority(buttonPriority);
auto restartBtnSpr = createGeodeButton("Restart Now", true);
auto restartBtnSpr = createGeodeButton("Restart Now", true, GeodeButtonSprite::Default, m_forceDisableTheme);
restartBtnSpr->setScale(.6f);
m_restartBtn = CCMenuItemSpriteExtra::create(
restartBtnSpr, this, menu_selector(ModSettingsPopup::onRestart)
);
m_applyMenu->addChildAtPosition(m_restartBtn, Anchor::Bottom, ccp(0, 20));
m_applyBtnSpr = createGeodeButton("Apply", true);
m_applyBtnSpr = createGeodeButton("Apply", true, GeodeButtonSprite::Default, m_forceDisableTheme);
m_applyBtnSpr->setScale(.6f);
m_applyBtn = CCMenuItemSpriteExtra::create(
m_applyBtnSpr, this, menu_selector(ModSettingsPopup::onApply)
@ -137,7 +135,7 @@ bool ModSettingsPopup::setup(Mod* mod) {
m_mainLayer->addChildAtPosition(m_applyMenu, Anchor::Bottom, ccp(0, 20));
auto resetBtnSpr = createGeodeButton("Reset All", true);
auto resetBtnSpr = createGeodeButton("Reset All", true, GeodeButtonSprite::Default, m_forceDisableTheme);
resetBtnSpr->setScale(.6f);
auto resetBtn = CCMenuItemSpriteExtra::create(
@ -162,7 +160,7 @@ bool ModSettingsPopup::setup(Mod* mod) {
folderSprSub->setOpacity(155);
folderSprSub->setScale(.55f);
folderSpr->addChildAtPosition(folderSprSub, Anchor::Center, ccp(0, -3));
auto buttonSpr = createGeodeButton(folderSpr, "");
auto buttonSpr = createGeodeButton(folderSpr, "", GeodeButtonSprite::Default, m_forceDisableTheme);
buttonSpr->setScale(.6f);
buttonSpr->getIcon()->setScale(buttonSpr->getIcon()->getScale() * 1.4f);
auto folderBtn = CCMenuItemSpriteExtra::create(
@ -345,9 +343,9 @@ void ModSettingsPopup::onClose(CCObject* sender) {
GeodePopup::onClose(sender);
}
ModSettingsPopup* ModSettingsPopup::create(Mod* mod) {
ModSettingsPopup* ModSettingsPopup::create(Mod* mod, bool forceDisableTheme) {
auto ret = new ModSettingsPopup();
if (ret->init(440, 280, mod)) {
if (ret->init(440, 280, mod, GeodePopupStyle::Default, forceDisableTheme)) {
ret->autorelease();
return ret;
}

View file

@ -3,6 +3,9 @@
#include <Geode/loader/SettingV3.hpp>
#include <Geode/ui/Popup.hpp>
#include <Geode/utils/cocos.hpp>
#include <Geode/ui/ScrollLayer.hpp>
#include <Geode/ui/TextInput.hpp>
#include "../GeodeStyle.hpp"
using namespace geode::prelude;
@ -33,5 +36,5 @@ protected:
void onClearSearch(CCObject*);
public:
static ModSettingsPopup* create(Mod* mod);
static ModSettingsPopup* create(Mod* mod, bool forceDisableTheme = false);
};

View file

@ -1,6 +1,7 @@
#pragma once
#include <Geode/utils/cocos.hpp>
#include <Geode/utils/string.hpp>
#include <server/Server.hpp>
#include "../list/ModItem.hpp"
@ -143,6 +144,7 @@ enum class ServerModListType {
Featured,
Trending,
Recent,
Modtober24,
};
class ServerModListSource : public ModListSource {
@ -165,6 +167,8 @@ public:
server::ModsQuery const& getQuery() const;
InvalidateQueryAfter<server::ModsQuery> getQueryMut();
bool isDefaultQuery() const override;
server::ModsQuery createDefaultQuery() const;
ServerModListType getType() const;
};
class ModPackListSource : public ModListSource {
@ -191,6 +195,7 @@ void filterModsWithLocalQuery(ModListSource::ProvidedMods& mods, Query const& qu
std::vector<std::pair<ModSource, double>> filtered;
// Filter installed mods based on query
// TODO: maybe skip fuzzy matching altogether if query is empty?
for (auto& src : mods.mods) {
double weighted = 0;
bool addToList = true;
@ -223,7 +228,10 @@ void filterModsWithLocalQuery(ModListSource::ProvidedMods& mods, Query const& qu
return a.second > b.second;
}
// Sort secondarily alphabetically
return a.first.getMetadata().getName() < b.first.getMetadata().getName();
return utils::string::caseInsensitiveCompare(
a.first.getMetadata().getName(),
b.first.getMetadata().getName()
) == std::strong_ordering::less;
});
mods.mods.clear();

View file

@ -1,29 +1,7 @@
#include "ModListSource.hpp"
void ServerModListSource::resetQuery() {
switch (m_type) {
case ServerModListType::Download: {
m_query = server::ModsQuery {};
} break;
case ServerModListType::Featured: {
m_query = server::ModsQuery {
.featured = true,
};
} break;
case ServerModListType::Trending: {
m_query = server::ModsQuery {
.sorting = server::ModsSort::RecentlyUpdated,
};
} break;
case ServerModListType::Recent: {
m_query = server::ModsQuery {
.sorting = server::ModsSort::RecentlyPublished,
};
} break;
}
m_query = this->createDefaultQuery();
}
ServerModListSource::ProviderTask ServerModListSource::fetchPage(size_t page, size_t pageSize, bool forceUpdate) {
@ -56,7 +34,7 @@ ServerModListSource::ServerModListSource(ServerModListType type)
ServerModListSource* ServerModListSource::get(ServerModListType type) {
switch (type) {
default:
default: [[fallthrough]];
case ServerModListType::Download: {
static auto inst = new ServerModListSource(ServerModListType::Download);
return inst;
@ -76,6 +54,11 @@ ServerModListSource* ServerModListSource::get(ServerModListType type) {
static auto inst = new ServerModListSource(ServerModListType::Recent);
return inst;
} break;
case ServerModListType::Modtober24: {
static auto inst = new ServerModListSource(ServerModListType::Modtober24);
return inst;
} break;
}
}
@ -104,7 +87,31 @@ InvalidateQueryAfter<server::ModsQuery> ServerModListSource::getQueryMut() {
return InvalidateQueryAfter(m_query, this);
}
bool ServerModListSource::isDefaultQuery() const {
return !m_query.query.has_value() &&
m_query.tags.empty() &&
!m_query.developer.has_value();
return m_query == this->createDefaultQuery();
}
server::ModsQuery ServerModListSource::createDefaultQuery() const {
switch (m_type) {
case ServerModListType::Download: return server::ModsQuery {};
case ServerModListType::Featured: return server::ModsQuery {
.featured = true,
};
case ServerModListType::Trending: return server::ModsQuery {
.sorting = server::ModsSort::RecentlyUpdated,
};
case ServerModListType::Recent: return server::ModsQuery {
.sorting = server::ModsSort::RecentlyPublished,
};
case ServerModListType::Modtober24: return server::ModsQuery {
.tags = { "modtober24" },
};
}
}
ServerModListType ServerModListSource::getType() const {
return m_type;
}

View file

@ -151,14 +151,14 @@ ListBorders* ListBorders::create() {
return nullptr;
}
void ListBorders::setSpriteFrames(const char* topAndBottom, const char* side, float topPadding) {
void ListBorders::setSpriteFrames(const char* topAndBottom, const char* side, float horizontalPadding) {
this->setSprites(
CCScale9Sprite::createWithSpriteFrameName(topAndBottom),
CCScale9Sprite::createWithSpriteFrameName(topAndBottom),
CCSprite::createWithSpriteFrameName(side),
CCSprite::createWithSpriteFrameName(side),
topPadding,
topPadding
horizontalPadding,
horizontalPadding
);
m_bottom->setScaleY(-1);
m_right->setFlipX(true);

View file

@ -151,7 +151,6 @@ void TextInput::setCallback(std::function<void(std::string const&)> onInput) {
m_onInput = onInput;
}
void TextInput::setEnabled(bool enabled) {
m_input->setMouseEnabled(enabled);
m_input->setTouchEnabled(enabled);
m_input->m_placeholderLabel->setOpacity(enabled ? 255 : 150);
}

View file

@ -1,4 +1,5 @@
#include <Geode/utils/JsonValidation.hpp>
#include <Geode/utils/ranges.hpp>
using namespace geode::prelude;

View file

@ -1,6 +1,7 @@
#include <Geode/platform/platform.hpp>
#include <Geode/utils/general.hpp>
#include <Geode/utils/ranges.hpp>
using namespace geode::prelude;

View file

@ -192,3 +192,20 @@ std::string utils::string::normalize(std::string const& str) {
auto ret = str;
return utils::string::normalizeIP(ret);
}
std::strong_ordering utils::string::caseInsensitiveCompare(std::string_view str1, std::string_view str2) {
for (size_t i = 0; i < str1.size() && i < str2.size(); i++) {
auto const a = std::tolower(str1[i]);
auto const b = std::tolower(str2[i]);
if (a < b) {
return std::strong_ordering::less;
} else if (a > b) {
return std::strong_ordering::greater;
}
}
if (str1.size() < str2.size())
return std::strong_ordering::less;
else if (str1.size() > str2.size())
return std::strong_ordering::greater;
return std::strong_ordering::equal;
}