mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-27 09:55:34 -05:00
Merge branch 'geode-sdk:main' into copy-mods
This commit is contained in:
commit
a68a631a4d
52 changed files with 534 additions and 210 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -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
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
3.7.2
|
3.8.1
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)...
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,9 @@
|
||||||
],
|
],
|
||||||
"BlankSheet": [
|
"BlankSheet": [
|
||||||
"blanks/*.png"
|
"blanks/*.png"
|
||||||
|
],
|
||||||
|
"EventSheet": [
|
||||||
|
"modtober/*.png"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
BIN
loader/resources/modtober/modtober24-banner-2.png
Normal file
BIN
loader/resources/modtober/modtober24-banner-2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 101 KiB |
BIN
loader/resources/modtober/modtober24-banner.png
Normal file
BIN
loader/resources/modtober/modtober24-banner.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 83 KiB |
BIN
loader/resources/modtober/modtober24-popup.png
Normal file
BIN
loader/resources/modtober/modtober24-popup.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1 MiB |
BIN
loader/resources/tag-modtober.png
Normal file
BIN
loader/resources/tag-modtober.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
44
loader/src/ui/mods/popups/ModtoberPopup.cpp
Normal file
44
loader/src/ui/mods/popups/ModtoberPopup.cpp
Normal 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;
|
||||||
|
}
|
16
loader/src/ui/mods/popups/ModtoberPopup.hpp
Normal file
16
loader/src/ui/mods/popups/ModtoberPopup.hpp
Normal 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();
|
||||||
|
};
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in a new issue