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 # 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) * Fix `followThunkFunction` following through into hook handlers (ad26357)
## v3.7.1 ## 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_propagate_on_move_assign()
|| __node_alloc_traits::_S_always_equal(); || __node_alloc_traits::_S_always_equal();
_M_move_assign(std::move(__ht), _M_move_assign(std::move(__ht),
integral_constant<bool, __move_storage>()); std::integral_constant<bool, __move_storage>());
return *this; return *this;
} }

View file

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

View file

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

View file

@ -11,7 +11,7 @@ class CCLightning : public CCNode, public CCRGBAProtocol {
public: public:
CCLightning(); CCLightning();
virtual ~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, CCPoint strikePoint2, float duration);
static CCLightning* lightningWithStrikePoint(CCPoint strikePoint); static CCLightning* lightningWithStrikePoint(CCPoint strikePoint);

View file

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

View file

@ -86,7 +86,7 @@ namespace geode {
public: public:
static ListBorders* create(); 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( void setSprites(
cocos2d::extension::CCScale9Sprite* top, cocos2d::extension::CCScale9Sprite* top,
cocos2d::extension::CCScale9Sprite* bottom, cocos2d::extension::CCScale9Sprite* bottom,

View file

@ -2,6 +2,7 @@
#include "../loader/Mod.hpp" #include "../loader/Mod.hpp"
#include <Geode/binding/FLAlertLayer.hpp> #include <Geode/binding/FLAlertLayer.hpp>
#include <Geode/ui/Popup.hpp>
class ModPopup; class ModPopup;
class ModItem; class ModItem;
@ -141,6 +142,16 @@ namespace geode {
* Open the settings popup for a mod (if it has any settings) * Open the settings popup for a mod (if it has any settings)
*/ */
GEODE_DLL void openSettingsPopup(Mod* mod); 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 * Create a default logo sprite
*/ */

View file

@ -6,8 +6,59 @@
#include <Geode/utils/cocos.hpp> #include <Geode/utils/cocos.hpp>
namespace geode { namespace geode {
template <typename... InitArgs> template <class... InitArgs>
class Popup : public FLAlertLayer { 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: protected:
cocos2d::CCSize m_size; cocos2d::CCSize m_size;
cocos2d::extension::CCScale9Sprite* m_bgSprite; cocos2d::extension::CCScale9Sprite* m_bgSprite;
@ -115,6 +166,7 @@ namespace geode {
} }
virtual void onClose(cocos2d::CCObject*) { virtual void onClose(cocos2d::CCObject*) {
CloseEvent(this).post();
this->setKeypadEnabled(false); this->setKeypadEnabled(false);
this->setTouchEnabled(false); this->setTouchEnabled(false);
this->removeFromParentAndCleanup(true); this->removeFromParentAndCleanup(true);
@ -158,6 +210,13 @@ namespace geode {
spr->setAnchorPoint(orig->getAnchorPoint()); spr->setAnchorPoint(orig->getAnchorPoint());
m_closeBtn->setContentSize(origSize); 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( GEODE_DLL FLAlertLayer* createQuickPopup(

View file

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

View file

@ -32,10 +32,10 @@ namespace geode::utils::file {
template <class T> template <class T>
Result<T> readFromJson(std::filesystem::path const& file) { Result<T> readFromJson(std::filesystem::path const& file) {
GEODE_UNWRAP_INTO(auto json, readJson(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 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); 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); 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); 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); 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::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 if (ec == std::errc::result_out_of_range) return Err("Number is too large to fit");
else return Err("Unknown error"); else return Err("Unknown error");

View file

@ -1,9 +1,9 @@
#pragma once #pragma once
#include <Geode/DefaultInclude.hpp> #include <string_view>
#include <functional>
#include <string> #include <string>
#include <vector> #include <vector>
#include <compare>
namespace geode::utils::string { 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 startsWith(std::string const& str, std::string const& prefix);
GEODE_DLL bool endsWith(std::string const& str, std::string const& suffix); 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": [ "BlankSheet": [
"blanks/*.png" "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 ( if (
crashlog::didLastLaunchCrash() && crashlog::didLastLaunchCrash() &&
!shownLastCrash && !shownLastCrash &&
!Mod::get()->template getSettingValue<bool>("disable-last-crashed-popup") !Mod::get()->getSettingValue<bool>("disable-last-crashed-popup")
) { ) {
shownLastCrash = true; shownLastCrash = true;

View file

@ -159,7 +159,7 @@ $register_ids(MenuLayer) {
// but prolly a place mods want to add stuff // but prolly a place mods want to add stuff
auto topRightMenu = CCMenu::create(); 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->setID("top-right-menu");
topRightMenu->setContentSize({ 200.f, 50.f }); topRightMenu->setContentSize({ 200.f, 50.f });
topRightMenu->setLayout( topRightMenu->setLayout(

View file

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

View file

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

View file

@ -57,7 +57,7 @@ bool ModMetadata::Dependency::isResolved() const {
bool ModMetadata::Incompatibility::isResolved() const { bool ModMetadata::Incompatibility::isResolved() const {
return this->importance != Importance::Breaking || 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) { 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"); return Err("[mod.json] can not have both \"developer\" and \"developers\" specified");
} }
for (auto& dev : root.needs("developers").items()) { 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 { 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 // Tags. Actual validation is done when interacting with the server in the UI
for (auto& tag : root.has("tags").items()) { 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 // 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); obj.has("description").into(sett.description);
if (auto defValue = obj.needs("default")) { if (auto defValue = obj.needs("default")) {
// Platform-specific default value // Platform-specific default value
if (defValue.template is<matjson::Object>()) { if (defValue.is<matjson::Object>()) {
auto def = defValue.obj(); auto def = defValue.obj();
if (auto plat = def.has(PlatformID::toShortString(GEODE_PLATFORM_TARGET, true))) { if (auto plat = def.has(PlatformID::toShortString(GEODE_PLATFORM_TARGET, true))) {
plat.into(sett.defaultValue); plat.into(sett.defaultValue);

View file

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

View file

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

View file

@ -1,6 +1,9 @@
#include <Geode/loader/SettingV3.hpp> #include <Geode/loader/SettingV3.hpp>
#include <Geode/loader/SettingEvent.hpp> #include <Geode/loader/SettingEvent.hpp>
#include <Geode/loader/ModSettingsManager.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 <Geode/utils/JsonValidation.hpp>
#include <regex> #include <regex>
#include "SettingNodeV3.hpp" #include "SettingNodeV3.hpp"
@ -51,7 +54,7 @@ namespace enable_if_parsing {
} }
Result<> eval(std::string const& defaultModID) const override { Result<> eval(std::string const& defaultModID) const override {
if (auto mod = Loader::get()->getLoadedMod(modID)) { if (auto mod = Loader::get()->getLoadedMod(modID)) {
if (mod->template getSettingValue<bool>(settingID)) { if (mod->getSettingValue<bool>(settingID)) {
return Ok(); return Ok();
} }
// This is an if-check just in case, even though check() should already // 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 { Result<> eval(std::string const& defaultModID) const override {
if (auto mod = Loader::get()->getLoadedMod(modID)) { if (auto mod = Loader::get()->getLoadedMod(modID)) {
if (mod->template getSavedValue<bool>(savedValue)) { if (mod->getSavedValue<bool>(savedValue)) {
return Ok(); return Ok();
} }
if (modID == defaultModID) { if (modID == defaultModID) {
@ -482,7 +485,7 @@ void SettingV3::init(std::string const& key, std::string const& modID, JsonExpec
// Keys every setting must have // Keys every setting must have
json.needs("type"); json.needs("type");
for (auto& plat : json.has("platforms").items()) { 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) { void SettingV3::parseEnableIf(JsonExpectedValue& json) {
json.has("enable-if") 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_INTO(auto tree, enable_if_parsing::Parser::parse(str, m_impl->modID));
GEODE_UNWRAP(tree->check()); GEODE_UNWRAP(tree->check());
m_impl->enableIfTree = std::move(tree); 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 // This silly code is because step size being 0 is what defines if they are enabled
// Small arrows are enabled by default // 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; 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; 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 // Disable arrows if they aren't enabled
// Small arrows are enabled by default // 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; 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; ret->m_impl->controls.bigArrowStepSize = 0;
} }

View file

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

View file

@ -115,19 +115,19 @@ void console::setup() {
path = std::string(buf, count - 1); path = std::string(buf, count - 1);
} }
// count == 0 => not a console and not a file, assume it's closed // TODO: this code causes a crash when piping game's output somewhere (and in some other cases), so it's removed for now
// wine does something weird with /dev/null? not sure tbh but it's definitely up to no good // // count == 0 => not a console and not a file, assume it's closed
// 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 // // 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"))) { // if ((count == 0 || path.ends_with("\\dev\\null"))) {
s_outHandle = nullptr; // s_outHandle = nullptr;
CloseHandle(GetStdHandle(STD_OUTPUT_HANDLE)); // CloseHandle(GetStdHandle(STD_OUTPUT_HANDLE));
CloseHandle(GetStdHandle(STD_INPUT_HANDLE)); // CloseHandle(GetStdHandle(STD_INPUT_HANDLE));
CloseHandle(GetStdHandle(STD_ERROR_HANDLE)); // CloseHandle(GetStdHandle(STD_ERROR_HANDLE));
FreeConsole(); // FreeConsole();
SetStdHandle(STD_OUTPUT_HANDLE, nullptr); // SetStdHandle(STD_OUTPUT_HANDLE, nullptr);
SetStdHandle(STD_INPUT_HANDLE, nullptr); // SetStdHandle(STD_INPUT_HANDLE, nullptr);
SetStdHandle(STD_ERROR_HANDLE, nullptr); // SetStdHandle(STD_ERROR_HANDLE, nullptr);
} // }
} }
// clion console supports escape codes but we can't query that because it's a named pipe // 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")) { if (json.is_object() && json.contains("error")) {
return ServerError( return ServerError(
error.code(), error.code(),
"{}", json.template get<std::string>("error") "{}", json.get<std::string>("error")
); );
} }
else { else {
@ -264,13 +264,13 @@ Result<ServerModVersion> ServerModVersion::parse(matjson::Value const& raw) {
auto res = ServerModVersion(); auto res = ServerModVersion();
res.metadata.setGeodeVersion(root.needs("geode").template get<VersionInfo>()); res.metadata.setGeodeVersion(root.needs("geode").get<VersionInfo>());
// Verify target GD version // Verify target GD version
auto gd_obj = root.needs("gd").obj(); auto gd_obj = root.needs("gd").obj();
std::string gd = "0.000"; std::string gd = "0.000";
if (gd_obj.has(GEODE_PLATFORM_SHORT_IDENTIFIER)) { 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 != "*") { if (gd != "*") {
@ -283,11 +283,11 @@ Result<ServerModVersion> ServerModVersion::parse(matjson::Value const& raw) {
root.needs("hash").into(res.hash); root.needs("hash").into(res.hash);
// Get mod metadata info // Get mod metadata info
res.metadata.setID(root.needs("mod_id").template get<std::string>()); res.metadata.setID(root.needs("mod_id").get<std::string>());
res.metadata.setName(root.needs("name").template get<std::string>()); res.metadata.setName(root.needs("name").get<std::string>());
res.metadata.setDescription(root.needs("description").template get<std::string>()); res.metadata.setDescription(root.needs("description").get<std::string>());
res.metadata.setVersion(root.needs("version").template get<VersionInfo>()); res.metadata.setVersion(root.needs("version").get<VersionInfo>());
res.metadata.setIsAPI(root.needs("api").template get<bool>()); res.metadata.setIsAPI(root.needs("api").get<bool>());
std::vector<ModMetadata::Dependency> dependencies {}; std::vector<ModMetadata::Dependency> dependencies {};
for (auto dep : root.has("dependencies").iterate()) { 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("changelog").into(res.changelog);
root.has("repository").into(res.repository); root.has("repository").into(res.repository);
if (root.has("created_at")) { 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")) { 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; std::vector<std::string> developerNames;
@ -470,7 +470,7 @@ Result<ServerModMetadata> ServerModMetadata::parse(matjson::Value const& raw) {
} }
for (auto item : root.has("tags").iterate()) { 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); root.needs("download_count").into(res.downloadCount);

View file

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

View file

@ -70,16 +70,21 @@ $on_mod(Loaded) {
Loader::get()->queueInMainThread([updateColors = updateColors] { Loader::get()->queueInMainThread([updateColors = updateColors] {
// this code is ran during static init, where settings aren't loaded yet, and getSettingValue will always return false. // 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. // 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) { bool isGeodeTheme(bool forceDisableTheme) {
if (!CCSprite::initWithFile(isGeodeTheme() ? "GE_button_05.png"_spr : "GJ_button_01.png")) 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; return false;
m_stateSrc = state; m_stateSrc = state;
m_topSprite = top; m_topSprite = top;
m_forceDisableTheme = forceDisableTheme;
limitNodeSize(top, m_obContentSize * .65f, 2.f, .1f); limitNodeSize(top, m_obContentSize * .65f, 2.f, .1f);
this->addChildAtPosition(top, Anchor::Center); this->addChildAtPosition(top, Anchor::Center);
@ -94,7 +99,7 @@ bool GeodeSquareSprite::init(CCSprite* top, bool* state) {
void GeodeSquareSprite::updateImage() { void GeodeSquareSprite::updateImage() {
this->setTexture(CCTextureCache::get()->addImage( 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 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(); auto ret = new GeodeSquareSprite();
if (ret->init(CCSprite::create(top), state)) { if (ret->init(CCSprite::create(top), state, forceDisableTheme)) {
ret->autorelease(); ret->autorelease();
return ret; return ret;
} }
delete ret; delete ret;
return nullptr; return nullptr;
} }
GeodeSquareSprite* GeodeSquareSprite::createWithSpriteFrameName(const char* top, bool* state) { GeodeSquareSprite* GeodeSquareSprite::createWithSpriteFrameName(const char* top, bool* state, bool forceDisableTheme) {
auto ret = new GeodeSquareSprite(); auto ret = new GeodeSquareSprite();
if (ret->init(CCSprite::createWithSpriteFrameName(top), state)) { if (ret->init(CCSprite::createWithSpriteFrameName(top), state, forceDisableTheme)) {
ret->autorelease(); ret->autorelease();
return ret; return ret;
} }
@ -142,8 +147,8 @@ CCNode* createLoadingCircle(float sideLength, const char* id) {
return spinner; return spinner;
} }
const char* getGeodeButtonSpriteName(GeodeButtonSprite spr) { const char* getGeodeButtonSpriteName(GeodeButtonSprite spr, bool forceDisableTheme) {
if (isGeodeTheme()) { if (isGeodeTheme(forceDisableTheme)) {
switch (spr) { switch (spr) {
default: default:
case GeodeButtonSprite::Default: return "GE_button_05.png"_spr; 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) { IconButtonSprite* createGeodeButton(CCNode* icon, std::string const& text, GeodeButtonSprite bg, bool forceDisableTheme) {
return IconButtonSprite::create(getGeodeButtonSpriteName(bg), icon, text.c_str(), "bigFont.fnt"); 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) { 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), 0.0f, .8f); 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) { 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), .8f); 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) { CircleButtonSprite* createGeodeCircleButton(CCSprite* top, float scale, CircleBaseSize size, bool altColor, bool forceDisableTheme) {
const auto geodeTheme = isGeodeTheme(); const auto geodeTheme = isGeodeTheme(forceDisableTheme);
auto ret = CircleButtonSprite::create( auto ret = CircleButtonSprite::create(
top, geodeTheme ? (altColor ? CircleBaseColor::DarkAqua : CircleBaseColor::DarkPurple) : CircleBaseColor::Green, size top, geodeTheme ? (altColor ? CircleBaseColor::DarkAqua : CircleBaseColor::DarkPurple) : CircleBaseColor::Green, size
); );
@ -184,21 +189,16 @@ CircleButtonSprite* createGeodeCircleButton(CCSprite* top, float scale, CircleBa
return ret; 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); auto label = ButtonSprite::create(text.c_str(), "bigFont.fnt", "white-square.png"_spr, .8f);
if (color) { label->m_label->setColor(color.first);
label->m_label->setColor(color->first); label->m_BGSprite->setColor(color.second);
label->m_BGSprite->setColor(color->second);
}
else {
auto def = geodeTagColor(text);
label->m_label->setColor(def.first);
label->m_BGSprite->setColor(def.second);
}
return label; return label;
} }
ButtonSprite* createGeodeTagLabel(std::string_view tag) {
std::pair<ccColor3B, ccColor3B> geodeTagColor(std::string_view const& text) { return createTagLabel(geodeTagName(tag), geodeTagColors(tag));
}
std::pair<ccColor3B, ccColor3B> geodeTagColors(std::string_view tag) {
static std::array TAG_COLORS { static std::array TAG_COLORS {
std::make_pair(ccc3(240, 233, 255), ccc3(130, 123, 163)), std::make_pair(ccc3(240, 233, 255), ccc3(130, 123, 163)),
std::make_pair(ccc3(234, 255, 245), ccc3(123, 163, 136)), 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, 253, 240), ccc3(163, 157, 123)),
std::make_pair(ccc3(255, 242, 240), ccc3(163, 128, 123)), std::make_pair(ccc3(255, 242, 240), ccc3(163, 128, 123)),
}; };
return TAG_COLORS[hash(text) % 5932 % TAG_COLORS.size()]; if (tag == "modtober24") {
} return std::make_pair(ccc3(225, 236, 245), ccc3(82, 139, 201));
ListBorders* createGeodeListBorders(CCSize const& size) {
auto ret = ListBorders::create();
if (isGeodeTheme()) {
ret->setSpriteFrames("geode-list-top.png"_spr, "geode-list-side.png"_spr, 2);
} }
ret->setContentSize(size); return TAG_COLORS[hash(tag) % 5932 % TAG_COLORS.size()];
return ret; }
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() { ListBorders* createGeodeListBorders(CCSize const& size, bool forceDisableTheme) {
return Mod::get()->template getSettingValue<bool>("enable-geode-theme"); 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) { bool GeodeTabSprite::init(const char* iconFrame, const char* text, float width, bool altColor) {

View file

@ -16,11 +16,16 @@ enum class GeodePopupStyle {
Alt2, Alt2,
}; };
bool isGeodeTheme(bool forceDisableTheme = false);
template <class... Args> template <class... Args>
class GeodePopup : public Popup<Args...> { class GeodePopup : public Popup<Args...> {
protected: protected:
bool m_forceDisableTheme = false;
bool init(float width, float height, Args... args, GeodePopupStyle style = GeodePopupStyle::Default, bool 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; const char* bg;
switch (style) { switch (style) {
default: default:
@ -49,16 +54,17 @@ class GeodeSquareSprite : public CCSprite {
protected: protected:
bool* m_stateSrc = nullptr; bool* m_stateSrc = nullptr;
bool m_state = false; bool m_state = false;
bool m_forceDisableTheme = false;
CCSprite* m_topSprite; CCSprite* m_topSprite;
bool init(CCSprite* top, bool* state); bool init(CCSprite* top, bool* state, bool forceDisableTheme = false);
void update(float dt) override; void update(float dt) override;
void updateImage(); void updateImage();
public: public:
static GeodeSquareSprite* create(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); static GeodeSquareSprite* createWithSpriteFrameName(const char* top, bool* state = nullptr, bool forceDisableTheme = false);
CCSprite* getTopSprite() const; CCSprite* getTopSprite() const;
void setState(bool state); void setState(bool state);
@ -73,19 +79,19 @@ enum class GeodeButtonSprite {
Enable, Enable,
Gray, Gray,
}; };
const char* getGeodeButtonSpriteName(GeodeButtonSprite spr); const char* getGeodeButtonSpriteName(GeodeButtonSprite spr, bool forceDisableTheme = false);
IconButtonSprite* createGeodeButton(CCNode* icon, std::string const& text, GeodeButtonSprite bg = GeodeButtonSprite::Default); 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); 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); 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); ButtonSprite* createTagLabel(std::string const& text, std::pair<ccColor3B, ccColor3B> const& color);
std::pair<ccColor3B, ccColor3B> geodeTagColor(std::string_view const& text); 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); ListBorders* createGeodeListBorders(CCSize const& size, bool forceDisableTheme = false);
bool isGeodeTheme();
class GeodeTabSprite : public CCNode { class GeodeTabSprite : public CCNode {
protected: protected:

View file

@ -430,14 +430,15 @@ bool ModsLayer::init() {
// Increment touch priority so the mods in the list don't override // Increment touch priority so the mods in the list don't override
mainTabs->setTouchPriority(-150); mainTabs->setTouchPriority(-150);
for (auto item : std::initializer_list<std::tuple<const char*, const char*, ModListSource*, const char*>> { 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" }, { "download.png"_spr, "Installed", InstalledModListSource::get(InstalledModListType::All), "installed-button", false },
{ "GJ_starsIcon_001.png", "Featured", ServerModListSource::get(ServerModListType::Featured), "featured-button" }, { "GJ_starsIcon_001.png", "Featured", ServerModListSource::get(ServerModListType::Featured), "featured-button", false },
{ "globe.png"_spr, "Download", ServerModListSource::get(ServerModListType::Download), "download-button" }, { "globe.png"_spr, "Download", ServerModListSource::get(ServerModListType::Download), "download-button", false },
{ "GJ_timeIcon_001.png", "Recent", ServerModListSource::get(ServerModListType::Recent), "recent-button" }, { "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( 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) this, menu_selector(ModsLayer::onTab)
); );
btn->setUserData(std::get<2>(item)); btn->setUserData(std::get<2>(item));
@ -683,7 +684,7 @@ void ModsLayer::onSearch(CCObject*) {
} }
} }
void ModsLayer::onTheme(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( createQuickPopup(
"Switch Theme", "Switch Theme",
fmt::format( fmt::format(
@ -704,7 +705,7 @@ void ModsLayer::onTheme(CCObject*) {
); );
} }
void ModsLayer::onSettings(CCObject*) { void ModsLayer::onSettings(CCObject*) {
openSettingsPopup(Mod::get()); openSettingsPopup(Mod::get(), false);
} }
ModsLayer* ModsLayer::create() { ModsLayer* ModsLayer::create() {

View file

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

View file

@ -4,6 +4,7 @@
#include "../popups/SortPopup.hpp" #include "../popups/SortPopup.hpp"
#include "../GeodeStyle.hpp" #include "../GeodeStyle.hpp"
#include "../ModsLayer.hpp" #include "../ModsLayer.hpp"
#include "../popups/ModtoberPopup.hpp"
bool ModList::init(ModListSource* src, CCSize const& size) { bool ModList::init(ModListSource* src, CCSize const& size) {
if (!CCNode::init()) if (!CCNode::init())
@ -249,6 +250,34 @@ bool ModList::init(ModListSource* src, CCSize const& size) {
m_topContainer->addChild(m_searchMenu); 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( m_topContainer->setLayout(
ColumnLayout::create() ColumnLayout::create()
->setGap(0) ->setGap(0)
@ -492,7 +521,7 @@ void ModList::updateTopContainer() {
auto oldPosition = oldPositionArea > 0.f ? auto oldPosition = oldPositionArea > 0.f ?
m_list->m_contentLayer->getPositionY() / oldPositionArea : m_list->m_contentLayer->getPositionY() / oldPositionArea :
-1.f; -1.f;
// Update list size to account for the top menu // Update list size to account for the top menu
// (giving a little bit of extra padding for it, the same size as gap) // (giving a little bit of extra padding for it, the same size as gap)
m_list->setContentHeight( m_list->setContentHeight(
@ -501,6 +530,8 @@ void ModList::updateTopContainer() {
static_cast<AxisLayout*>(m_list->m_contentLayer->getLayout())->getGap() : static_cast<AxisLayout*>(m_list->m_contentLayer->getLayout())->getGap() :
this->getContentHeight() this->getContentHeight()
); );
static_cast<ColumnLayout*>(m_list->m_contentLayer->getLayout())->setAutoGrowAxis(m_list->getContentHeight());
m_list->m_contentLayer->updateLayout();
// Preserve relative scroll position // Preserve relative scroll position
m_list->m_contentLayer->setPositionY(( m_list->m_contentLayer->setPositionY((
@ -659,6 +690,9 @@ void ModList::onToggleErrors(CCObject*) {
void ModList::onUpdateAll(CCObject*) { void ModList::onUpdateAll(CCObject*) {
server::ModDownloadManager::get()->startUpdateAll(); server::ModDownloadManager::get()->startUpdateAll();
} }
void ModList::onModtoberInfo(CCObject*) {
ModtoberPopup::create()->show();
}
size_t ModList::getPage() const { size_t ModList::getPage() const {
return m_page; return m_page;

View file

@ -71,6 +71,7 @@ protected:
void onToggleUpdates(CCObject*); void onToggleUpdates(CCObject*);
void onToggleErrors(CCObject*); void onToggleErrors(CCObject*);
void onUpdateAll(CCObject*); void onUpdateAll(CCObject*);
void onModtoberInfo(CCObject*);
public: public:
static ModList* create(ModListSource* src, CCSize const& size); 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)); 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); okSpr->setScale(.7f);
auto okBtn = CCMenuItemSpriteExtra::create( auto okBtn = CCMenuItemSpriteExtra::create(
okSpr, this, menu_selector(FiltersPopup::onClose) okSpr, this, menu_selector(FiltersPopup::onClose)

View file

@ -1,6 +1,7 @@
#include "ModPopup.hpp" #include "ModPopup.hpp"
#include <Geode/binding/ButtonSprite.hpp> #include <Geode/binding/ButtonSprite.hpp>
#include <Geode/ui/MDTextArea.hpp> #include <Geode/ui/MDTextArea.hpp>
#include <Geode/ui/TextInput.hpp>
#include <Geode/utils/web.hpp> #include <Geode/utils/web.hpp>
#include <Geode/loader/Loader.hpp> #include <Geode/loader/Loader.hpp>
#include <Geode/loader/ModSettingsManager.hpp> #include <Geode/loader/ModSettingsManager.hpp>
@ -10,6 +11,7 @@
#include "../settings/ModSettingsPopup.hpp" #include "../settings/ModSettingsPopup.hpp"
#include "../../../internal/about.hpp" #include "../../../internal/about.hpp"
#include "../../GeodeUIEvent.hpp" #include "../../GeodeUIEvent.hpp"
#include "../popups/ModtoberPopup.hpp"
class FetchTextArea : public CCNode { class FetchTextArea : public CCNode {
public: public:
@ -300,12 +302,12 @@ bool ModPopup::setup(ModSource&& src) {
manageTitle->setOpacity(195); manageTitle->setOpacity(195);
manageContainer->addChildAtPosition(manageTitle, Anchor::Left, ccp(0, 0), ccp(0, .5f)); manageContainer->addChildAtPosition(manageTitle, Anchor::Left, ccp(0, 0), ccp(0, .5f));
m_restartRequiredLabel = createGeodeTagLabel( m_restartRequiredLabel = createTagLabel(
"Restart Required", "Restart Required",
{{ {
to3B(ColorProvider::get()->color("mod-list-restart-required-label"_spr)), to3B(ColorProvider::get()->color("mod-list-restart-required-label"_spr)),
to3B(ColorProvider::get()->color("mod-list-restart-required-label-bg"_spr)) to3B(ColorProvider::get()->color("mod-list-restart-required-label-bg"_spr))
}} }
); );
m_restartRequiredLabel->setScale(.3f); m_restartRequiredLabel->setScale(.3f);
manageContainer->addChildAtPosition(m_restartRequiredLabel, Anchor::Right, ccp(0, 0), ccp(1, .5f)); manageContainer->addChildAtPosition(m_restartRequiredLabel, Anchor::Right, ccp(0, 0), ccp(1, .5f));
@ -334,7 +336,8 @@ bool ModPopup::setup(ModSource&& src) {
auto updateModSpr = createGeodeButton( auto updateModSpr = createGeodeButton(
CCSprite::createWithSpriteFrameName("update.png"_spr), CCSprite::createWithSpriteFrameName("update.png"_spr),
"Update", "Update",
GeodeButtonSprite::Install GeodeButtonSprite::Install,
m_forceDisableTheme
); );
updateModSpr->setScale(.5f); updateModSpr->setScale(.5f);
m_updateBtn = CCMenuItemSpriteExtra::create( m_updateBtn = CCMenuItemSpriteExtra::create(
@ -345,13 +348,15 @@ bool ModPopup::setup(ModSource&& src) {
auto enableModOffSpr = createGeodeButton( auto enableModOffSpr = createGeodeButton(
CCSprite::createWithSpriteFrameName("GJ_completesIcon_001.png"), CCSprite::createWithSpriteFrameName("GJ_completesIcon_001.png"),
"Enable", "Enable",
GeodeButtonSprite::Enable GeodeButtonSprite::Enable,
m_forceDisableTheme
); );
enableModOffSpr->setScale(.5f); enableModOffSpr->setScale(.5f);
auto enableModOnSpr = createGeodeButton( auto enableModOnSpr = createGeodeButton(
CCSprite::createWithSpriteFrameName("GJ_deleteIcon_001.png"), CCSprite::createWithSpriteFrameName("GJ_deleteIcon_001.png"),
"Disable", "Disable",
GeodeButtonSprite::Delete GeodeButtonSprite::Delete,
m_forceDisableTheme
); );
enableModOnSpr->setScale(.5f); enableModOnSpr->setScale(.5f);
m_enableBtn = CCMenuItemToggler::create( m_enableBtn = CCMenuItemToggler::create(
@ -364,7 +369,8 @@ bool ModPopup::setup(ModSource&& src) {
auto reenableModOffSpr = createGeodeButton( auto reenableModOffSpr = createGeodeButton(
CCSprite::createWithSpriteFrameName("reset.png"_spr), CCSprite::createWithSpriteFrameName("reset.png"_spr),
"Re-Enable", "Re-Enable",
GeodeButtonSprite::Default GeodeButtonSprite::Default,
m_forceDisableTheme
); );
reenableModOffSpr->setScale(.5f); reenableModOffSpr->setScale(.5f);
auto reenableModOnSpr = createGeodeButton( auto reenableModOnSpr = createGeodeButton(
@ -383,7 +389,8 @@ bool ModPopup::setup(ModSource&& src) {
auto installModSpr = createGeodeButton( auto installModSpr = createGeodeButton(
CCSprite::createWithSpriteFrameName("GJ_downloadsIcon_001.png"), CCSprite::createWithSpriteFrameName("GJ_downloadsIcon_001.png"),
"Install", "Install",
GeodeButtonSprite::Install GeodeButtonSprite::Install,
m_forceDisableTheme
); );
installModSpr->setScale(.5f); installModSpr->setScale(.5f);
m_installBtn = CCMenuItemSpriteExtra::create( m_installBtn = CCMenuItemSpriteExtra::create(
@ -394,7 +401,8 @@ bool ModPopup::setup(ModSource&& src) {
auto uninstallModSpr = createGeodeButton( auto uninstallModSpr = createGeodeButton(
CCSprite::createWithSpriteFrameName("delete-white.png"_spr), CCSprite::createWithSpriteFrameName("delete-white.png"_spr),
"Uninstall", "Uninstall",
GeodeButtonSprite::Default GeodeButtonSprite::Default,
m_forceDisableTheme
); );
uninstallModSpr->setScale(.5f); uninstallModSpr->setScale(.5f);
m_uninstallBtn = CCMenuItemSpriteExtra::create( m_uninstallBtn = CCMenuItemSpriteExtra::create(
@ -405,7 +413,8 @@ bool ModPopup::setup(ModSource&& src) {
auto cancelDownloadSpr = createGeodeButton( auto cancelDownloadSpr = createGeodeButton(
CCSprite::createWithSpriteFrameName("GJ_deleteIcon_001.png"), CCSprite::createWithSpriteFrameName("GJ_deleteIcon_001.png"),
"Cancel", "Cancel",
GeodeButtonSprite::Default GeodeButtonSprite::Default,
m_forceDisableTheme
); );
cancelDownloadSpr->setScale(.5f); cancelDownloadSpr->setScale(.5f);
m_cancelBtn = CCMenuItemSpriteExtra::create( m_cancelBtn = CCMenuItemSpriteExtra::create(
@ -565,7 +574,7 @@ bool ModPopup::setup(ModSource&& src) {
m_settingsBG->setContentSize(ccp(35, 30) / linksBG->getScale()); m_settingsBG->setContentSize(ccp(35, 30) / linksBG->getScale());
m_buttonMenu->addChildAtPosition(m_settingsBG, Anchor::BottomLeft, ccp(28, 25)); 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); settingsSpr->setScale(.6f);
auto settingsBtn = CCMenuItemSpriteExtra::create( auto settingsBtn = CCMenuItemSpriteExtra::create(
settingsSpr, this, menu_selector(ModPopup::onSettings) settingsSpr, this, menu_selector(ModPopup::onSettings)
@ -880,10 +889,7 @@ void ModPopup::onLoadTags(typename server::ServerRequest<std::unordered_set<std:
m_tags->removeAllChildren(); m_tags->removeAllChildren();
for (auto& tag : data) { for (auto& tag : data) {
auto readable = tag; m_tags->addChild(createGeodeTagLabel(tag));
readable[0] = std::toupper(readable[0]);
auto colors = geodeTagColor(tag);
m_tags->addChild(createGeodeTagLabel(readable));
} }
if (data.empty()) { if (data.empty()) {
@ -891,6 +897,47 @@ void ModPopup::onLoadTags(typename server::ServerRequest<std::unordered_set<std:
label->setOpacity(120); label->setOpacity(120);
m_tags->addChild(label); 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(); m_tags->updateLayout();
@ -920,12 +967,17 @@ void ModPopup::loadTab(ModPopup::Tab tab) {
btn.first->select(value == tab); btn.first->select(value == tab);
} }
float modtoberBannerHeight = 0;
if (m_modtoberBanner) {
modtoberBannerHeight = 30;
}
if (auto existing = m_tabs.at(tab).second) { if (auto existing = m_tabs.at(tab).second) {
m_currentTabPage = existing; m_currentTabPage = existing;
m_rightColumn->addChildAtPosition(existing, Anchor::Bottom); m_rightColumn->addChildAtPosition(existing, Anchor::Bottom, ccp(0, modtoberBannerHeight));
} }
else { else {
const auto size = (m_rightColumn->getContentSize() - ccp(0, 30)); const auto size = (m_rightColumn->getContentSize() - ccp(0, 30 + modtoberBannerHeight));
const float mdScale = .85f; const float mdScale = .85f;
switch (tab) { switch (tab) {
case Tab::Details: { case Tab::Details: {
@ -955,7 +1007,7 @@ void ModPopup::loadTab(ModPopup::Tab tab) {
} break; } break;
} }
m_currentTabPage->setAnchorPoint({ .5f, .0f }); 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; m_tabs.at(tab).second = m_currentTabPage;
} }
} }
@ -1030,6 +1082,12 @@ void ModPopup::onLink(CCObject* sender) {
void ModPopup::onSupport(CCObject*) { void ModPopup::onSupport(CCObject*) {
openSupportPopup(m_source.getMetadata()); 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) { ModPopup* ModPopup::create(ModSource&& src) {
auto ret = new ModPopup(); auto ret = new ModPopup();

View file

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

View file

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

View file

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

View file

@ -1,29 +1,7 @@
#include "ModListSource.hpp" #include "ModListSource.hpp"
void ServerModListSource::resetQuery() { void ServerModListSource::resetQuery() {
switch (m_type) { m_query = this->createDefaultQuery();
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;
}
} }
ServerModListSource::ProviderTask ServerModListSource::fetchPage(size_t page, size_t pageSize, bool forceUpdate) { 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) { ServerModListSource* ServerModListSource::get(ServerModListType type) {
switch (type) { switch (type) {
default: default: [[fallthrough]];
case ServerModListType::Download: { case ServerModListType::Download: {
static auto inst = new ServerModListSource(ServerModListType::Download); static auto inst = new ServerModListSource(ServerModListType::Download);
return inst; return inst;
@ -76,6 +54,11 @@ ServerModListSource* ServerModListSource::get(ServerModListType type) {
static auto inst = new ServerModListSource(ServerModListType::Recent); static auto inst = new ServerModListSource(ServerModListType::Recent);
return inst; return inst;
} break; } 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); return InvalidateQueryAfter(m_query, this);
} }
bool ServerModListSource::isDefaultQuery() const { bool ServerModListSource::isDefaultQuery() const {
return !m_query.query.has_value() && return m_query == this->createDefaultQuery();
m_query.tags.empty() && }
!m_query.developer.has_value();
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; 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( this->setSprites(
CCScale9Sprite::createWithSpriteFrameName(topAndBottom), CCScale9Sprite::createWithSpriteFrameName(topAndBottom),
CCScale9Sprite::createWithSpriteFrameName(topAndBottom), CCScale9Sprite::createWithSpriteFrameName(topAndBottom),
CCSprite::createWithSpriteFrameName(side), CCSprite::createWithSpriteFrameName(side),
CCSprite::createWithSpriteFrameName(side), CCSprite::createWithSpriteFrameName(side),
topPadding, horizontalPadding,
topPadding horizontalPadding
); );
m_bottom->setScaleY(-1); m_bottom->setScaleY(-1);
m_right->setFlipX(true); m_right->setFlipX(true);

View file

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

View file

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

View file

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

View file

@ -192,3 +192,20 @@ std::string utils::string::normalize(std::string const& str) {
auto ret = str; auto ret = str;
return utils::string::normalizeIP(ret); 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;
}