mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-27 09:55:34 -05:00
Compare commits
8 commits
db9e2ccb48
...
792e61e2f5
Author | SHA1 | Date | |
---|---|---|---|
|
792e61e2f5 | ||
|
6b8dd4a1ae | ||
|
8a826065a1 | ||
|
6eb079735f | ||
|
659c168a14 | ||
|
d0eb881ebc | ||
|
444a8c198b | ||
|
a31c68f178 |
22 changed files with 354 additions and 39 deletions
|
@ -243,7 +243,7 @@ if (DEFINED GEODE_TULIPHOOK_REPO_PATH)
|
|||
message(STATUS "Using ${GEODE_TULIPHOOK_REPO_PATH} for TulipHook")
|
||||
add_subdirectory(${GEODE_TULIPHOOK_REPO_PATH} ${GEODE_TULIPHOOK_REPO_PATH}/build)
|
||||
else()
|
||||
CPMAddPackage("gh:geode-sdk/TulipHook#fbf79b4")
|
||||
CPMAddPackage("gh:geode-sdk/TulipHook#c8a445c")
|
||||
endif()
|
||||
set(CMAKE_WARN_DEPRECATED ON CACHE BOOL "" FORCE)
|
||||
|
||||
|
|
|
@ -99,6 +99,8 @@ public:
|
|||
void drawPreciseCubicBezier(cocos2d::CCPoint const&, cocos2d::CCPoint const&, cocos2d::CCPoint const&, cocos2d::CCPoint const&, unsigned int, cocos2d::_ccColor4F const&);
|
||||
bool drawLines(cocos2d::CCPoint*, unsigned int, float, cocos2d::_ccColor4F const&);
|
||||
bool drawRect(cocos2d::CCPoint const&, cocos2d::CCPoint const&, cocos2d::_ccColor4F const&, float, cocos2d::_ccColor4F const&);
|
||||
void disableDrawArea();
|
||||
void enableDrawArea(cocos2d::CCRect& rect);
|
||||
#else
|
||||
/** draw a dot at a position, with a given radius and color */
|
||||
void drawDot(const CCPoint &pos, float radius, const ccColor4F &color);
|
||||
|
|
|
@ -9,6 +9,10 @@ namespace geode {
|
|||
class Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
|
||||
friend class ::geode::SettingV3;
|
||||
|
||||
void markRestartRequired();
|
||||
|
||||
public:
|
||||
static ModSettingsManager* from(Mod* mod);
|
||||
|
||||
|
@ -42,5 +46,11 @@ namespace geode {
|
|||
std::shared_ptr<SettingV3> get(std::string_view key);
|
||||
std::shared_ptr<SettingValue> getLegacy(std::string_view key);
|
||||
std::optional<Setting> getLegacyDefinition(std::string_view key);
|
||||
|
||||
/**
|
||||
* Returns true if any setting with the `"restart-required"` attribute
|
||||
* has been altered
|
||||
*/
|
||||
bool restartRequired() const;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,18 +4,18 @@
|
|||
#include "Loader.hpp"
|
||||
#include "Setting.hpp"
|
||||
#include "Mod.hpp"
|
||||
|
||||
#include "SettingV3.hpp"
|
||||
#include <optional>
|
||||
|
||||
namespace geode {
|
||||
struct GEODE_DLL SettingChangedEvent : public Event {
|
||||
struct GEODE_DLL [[deprecated("Use SettingChangedEventV3 from SettingV3.hpp instead")]] SettingChangedEvent : public Event {
|
||||
Mod* mod;
|
||||
SettingValue* value;
|
||||
|
||||
SettingChangedEvent(Mod* mod, SettingValue* value);
|
||||
};
|
||||
|
||||
class GEODE_DLL SettingChangedFilter : public EventFilter<SettingChangedEvent> {
|
||||
class GEODE_DLL [[deprecated("Use SettingChangedEventV3 from SettingV3.hpp instead")]] SettingChangedFilter : public EventFilter<SettingChangedEvent> {
|
||||
protected:
|
||||
std::string m_modID;
|
||||
std::optional<std::string> m_targetKey;
|
||||
|
@ -40,7 +40,7 @@ namespace geode {
|
|||
* Listen for built-in setting changes
|
||||
*/
|
||||
template<class T>
|
||||
class GeodeSettingChangedFilter : public SettingChangedFilter {
|
||||
class [[deprecated("Use SettingChangedEventV3 from SettingV3.hpp instead")]] GeodeSettingChangedFilter : public SettingChangedFilter {
|
||||
public:
|
||||
using Callback = void(T);
|
||||
|
||||
|
@ -60,21 +60,4 @@ namespace geode {
|
|||
) : SettingChangedFilter(modID, settingID) {}
|
||||
GeodeSettingChangedFilter(GeodeSettingChangedFilter const&) = default;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
std::monostate listenForSettingChanges(
|
||||
std::string const& settingKey, void (*callback)(T)
|
||||
) {
|
||||
(void)new EventListener(
|
||||
callback, GeodeSettingChangedFilter<T>(getMod()->getID(), settingKey)
|
||||
);
|
||||
return std::monostate();
|
||||
}
|
||||
|
||||
static std::monostate listenForAllSettingChanges(void (*callback)(SettingValue*)) {
|
||||
(void)new EventListener(
|
||||
callback, SettingChangedFilter(getMod()->getID(), std::nullopt)
|
||||
);
|
||||
return std::monostate();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "../utils/cocos.hpp"
|
||||
// this unfortunately has to be included because of C++ templates
|
||||
#include "../utils/JsonValidation.hpp"
|
||||
#include "../utils/function.hpp"
|
||||
|
||||
// todo in v4: this can be removed as well as the friend decl in LegacyCustomSettingV3
|
||||
class LegacyCustomSettingToV3Node;
|
||||
|
@ -16,6 +17,8 @@ class ModSettingsPopup;
|
|||
namespace geode {
|
||||
class ModSettingsManager;
|
||||
class SettingNodeV3;
|
||||
// todo in v4: remove this
|
||||
class SettingValue;
|
||||
|
||||
class GEODE_DLL SettingV3 : public std::enable_shared_from_this<SettingV3> {
|
||||
private:
|
||||
|
@ -27,6 +30,14 @@ namespace geode {
|
|||
Result<> parseSharedProperties(std::string const& key, std::string const& modID, matjson::Value const& value, bool onlyNameAndDesc = false);
|
||||
void parseSharedProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value, bool onlyNameAndDesc = false);
|
||||
|
||||
/**
|
||||
* Mark that the value of this setting has changed. This should be
|
||||
* ALWAYS called on every setter that can modify the setting's state!
|
||||
*/
|
||||
void markChanged();
|
||||
|
||||
friend class ::geode::SettingValue;
|
||||
|
||||
public:
|
||||
SettingV3();
|
||||
virtual ~SettingV3();
|
||||
|
@ -120,6 +131,7 @@ namespace geode {
|
|||
}
|
||||
void setValue(V value) {
|
||||
this->getValueMut() = this->isValid(value) ? value : this->getDefaultValue();
|
||||
this->markChanged();
|
||||
}
|
||||
virtual Result<> isValid(V value) const = 0;
|
||||
|
||||
|
@ -452,6 +464,38 @@ namespace geode {
|
|||
std::shared_ptr<SettingV3> getSetting() const;
|
||||
};
|
||||
|
||||
class GEODE_DLL SettingChangedEventV3 final : public Event {
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
public:
|
||||
SettingChangedEventV3(std::shared_ptr<SettingV3> setting);
|
||||
|
||||
std::shared_ptr<SettingV3> getSetting() const;
|
||||
};
|
||||
class GEODE_DLL SettingChangedFilterV3 final : public EventFilter<SettingChangedEventV3> {
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
public:
|
||||
using Callback = void(std::shared_ptr<SettingV3>);
|
||||
|
||||
ListenerResult handle(utils::MiniFunction<Callback> fn, SettingChangedEventV3* event);
|
||||
/**
|
||||
* Listen to changes on a setting, or all settings
|
||||
* @param modID Mod whose settings to listen to
|
||||
* @param settingKey Setting to listen to, or all settings if nullopt
|
||||
*/
|
||||
SettingChangedFilterV3(
|
||||
std::string const& modID,
|
||||
std::optional<std::string> const& settingKey
|
||||
);
|
||||
SettingChangedFilterV3(Mod* mod, std::optional<std::string> const& settingKey);
|
||||
SettingChangedFilterV3(SettingChangedFilterV3 const&);
|
||||
};
|
||||
|
||||
class GEODE_DLL SettingNodeSizeChangeEventV3 : public Event {
|
||||
private:
|
||||
class Impl;
|
||||
|
@ -512,4 +556,25 @@ namespace geode {
|
|||
struct SettingTypeForValueType<cocos2d::ccColor4B> {
|
||||
using SettingType = Color4BSettingV3;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
EventListener<SettingChangedFilterV3>* listenForSettingChanges(std::string_view settingKey, auto&& callback, Mod* mod = getMod()) {
|
||||
using Ty = typename SettingTypeForValueType<T>::SettingType;
|
||||
return new EventListener(
|
||||
[callback = std::move(callback)](std::shared_ptr<SettingV3> setting) {
|
||||
if (auto ty = typeinfo_pointer_cast<Ty>(setting)) {
|
||||
callback(ty->getValue());
|
||||
}
|
||||
},
|
||||
SettingChangedFilterV3(mod, std::string(settingKey))
|
||||
);
|
||||
}
|
||||
EventListener<SettingChangedFilterV3>* listenForSettingChanges(std::string_view settingKey, auto&& callback, Mod* mod = getMod()) {
|
||||
using T = std::remove_cvref_t<utils::function::Arg<0, decltype(callback)>>;
|
||||
return listenForSettingChanges<T>(settingKey, std::move(callback), mod);
|
||||
}
|
||||
GEODE_DLL EventListener<SettingChangedFilterV3>* listenForAllSettingChanges(
|
||||
std::function<void(std::shared_ptr<SettingV3>)> const& callback,
|
||||
Mod* mod = getMod()
|
||||
);
|
||||
}
|
||||
|
|
59
loader/include/Geode/utils/function.hpp
Normal file
59
loader/include/Geode/utils/function.hpp
Normal file
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace geode::utils::function {
|
||||
namespace detail {
|
||||
template <class F>
|
||||
struct ImplExtract;
|
||||
|
||||
template <class R, class... A>
|
||||
struct ImplExtract<R(A...)> {
|
||||
using Type = R(A...);
|
||||
using Return = R;
|
||||
using Args = std::tuple<A...>;
|
||||
static constexpr std::size_t ARG_COUNT = std::tuple_size_v<Args>;
|
||||
};
|
||||
template <class R, class... A>
|
||||
struct ImplExtract<R(*)(A...)> {
|
||||
using Type = R(A...);
|
||||
using Return = R;
|
||||
using Args = std::tuple<A...>;
|
||||
static constexpr std::size_t ARG_COUNT = std::tuple_size_v<Args>;
|
||||
};
|
||||
template <class R, class C, class... A>
|
||||
struct ImplExtract<R(C::*)(A...)> {
|
||||
using Type = R(A...);
|
||||
using Class = C;
|
||||
using Return = R;
|
||||
using Args = std::tuple<A...>;
|
||||
static constexpr std::size_t ARG_COUNT = std::tuple_size_v<Args>;
|
||||
};
|
||||
template <class R, class C, class... A>
|
||||
struct ImplExtract<R(C::*)(A...) const> {
|
||||
using Type = R(A...);
|
||||
using Class = C;
|
||||
using Return = R;
|
||||
using Args = std::tuple<A...>;
|
||||
static constexpr std::size_t ARG_COUNT = std::tuple_size_v<Args>;
|
||||
};
|
||||
template <class F>
|
||||
requires requires { &F::operator(); }
|
||||
struct ImplExtract<F> : public ImplExtract<decltype(&F::operator())> {};
|
||||
|
||||
template <class F>
|
||||
using Extract = ImplExtract<std::remove_cvref_t<F>>;
|
||||
}
|
||||
|
||||
template <class F>
|
||||
using FunctionInfo = detail::Extract<F>;
|
||||
|
||||
template <class F>
|
||||
using Return = typename detail::Extract<F>::Return;
|
||||
|
||||
template <class F>
|
||||
using Args = typename detail::Extract<F>::Args;
|
||||
|
||||
template <std::size_t Ix, class F>
|
||||
using Arg = std::tuple_element_t<Ix, typename detail::Extract<F>::Args>;
|
||||
}
|
|
@ -17,13 +17,22 @@
|
|||
namespace geode {
|
||||
using ByteVector = std::vector<uint8_t>;
|
||||
|
||||
// todo in v4: remove this
|
||||
template <typename T>
|
||||
[[deprecated("Use geode::toBytes instead")]]
|
||||
ByteVector toByteArray(T const& a) {
|
||||
ByteVector out;
|
||||
out.resize(sizeof(T));
|
||||
std::memcpy(out.data(), &a, sizeof(T));
|
||||
return out;
|
||||
}
|
||||
template <typename T>
|
||||
ByteVector toBytes(T const& a) {
|
||||
ByteVector out;
|
||||
out.resize(sizeof(T));
|
||||
std::memcpy(out.data(), &a, sizeof(T));
|
||||
return out;
|
||||
}
|
||||
|
||||
namespace utils {
|
||||
// helper for std::visit
|
||||
|
|
|
@ -78,6 +78,7 @@ public:
|
|||
};
|
||||
std::string modID;
|
||||
std::unordered_map<std::string, SettingInfo> settings;
|
||||
bool restartRequired = false;
|
||||
|
||||
void createSettings() {
|
||||
for (auto& [key, setting] : settings) {
|
||||
|
@ -103,6 +104,7 @@ public:
|
|||
};
|
||||
|
||||
ModSettingsManager* ModSettingsManager::from(Mod* mod) {
|
||||
if (!mod) return nullptr;
|
||||
return ModImpl::getImpl(mod)->m_settings.get();
|
||||
}
|
||||
|
||||
|
@ -136,6 +138,10 @@ ModSettingsManager::ModSettingsManager(ModMetadata const& metadata)
|
|||
ModSettingsManager::~ModSettingsManager() {}
|
||||
ModSettingsManager::ModSettingsManager(ModSettingsManager&&) = default;
|
||||
|
||||
void ModSettingsManager::markRestartRequired() {
|
||||
m_impl->restartRequired = true;
|
||||
}
|
||||
|
||||
Result<> ModSettingsManager::registerCustomSettingType(std::string_view type, SettingGenerator generator) {
|
||||
GEODE_UNWRAP(SharedSettingTypesPool::get().add(m_impl->modID, type, generator));
|
||||
m_impl->createSettings();
|
||||
|
@ -230,3 +236,7 @@ std::optional<Setting> ModSettingsManager::getLegacyDefinition(std::string_view
|
|||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool ModSettingsManager::restartRequired() const {
|
||||
return m_impl->restartRequired;
|
||||
}
|
||||
|
|
|
@ -280,10 +280,10 @@ std::string SettingValue::getModID() const {
|
|||
}
|
||||
|
||||
void SettingValue::valueChanged() {
|
||||
// this is actually p neat because now if the mod gets disabled this wont
|
||||
// post the event so that side-effect is automatically handled :3
|
||||
if (auto mod = Loader::get()->getLoadedMod(m_modID)) {
|
||||
SettingChangedEvent(mod, this).post();
|
||||
if (auto sett = mod->getSettingV3(m_key)) {
|
||||
sett->markChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -125,7 +125,7 @@ void SettingNodeV3::updateState() {
|
|||
m_impl->errorLabel->setString(desc->c_str());
|
||||
}
|
||||
}
|
||||
if (m_impl->setting->requiresRestart() && (this->hasUncommittedChanges() || m_impl->committed)) {
|
||||
if (m_impl->setting->requiresRestart() && m_impl->committed) {
|
||||
m_impl->errorLabel->setVisible(true);
|
||||
m_impl->errorLabel->setColor("mod-list-restart-required-label"_cc3b);
|
||||
m_impl->errorLabel->setString("Restart Required");
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#include <Geode/loader/SettingV3.hpp>
|
||||
#include <Geode/loader/SettingEvent.hpp>
|
||||
#include <Geode/loader/ModSettingsManager.hpp>
|
||||
#include <Geode/utils/JsonValidation.hpp>
|
||||
#include <regex>
|
||||
#include "SettingNodeV3.hpp"
|
||||
|
@ -397,6 +399,63 @@ namespace enable_if_parsing {
|
|||
};
|
||||
}
|
||||
|
||||
class SettingChangedEventV3::Impl final {
|
||||
public:
|
||||
std::shared_ptr<SettingV3> setting;
|
||||
};
|
||||
|
||||
SettingChangedEventV3::SettingChangedEventV3(std::shared_ptr<SettingV3> setting)
|
||||
: m_impl(std::make_shared<Impl>())
|
||||
{
|
||||
m_impl->setting = setting;
|
||||
}
|
||||
|
||||
std::shared_ptr<SettingV3> SettingChangedEventV3::getSetting() const {
|
||||
return m_impl->setting;
|
||||
}
|
||||
|
||||
class SettingChangedFilterV3::Impl final {
|
||||
public:
|
||||
std::string modID;
|
||||
std::optional<std::string> settingKey;
|
||||
};
|
||||
|
||||
ListenerResult SettingChangedFilterV3::handle(utils::MiniFunction<Callback> fn, SettingChangedEventV3* event) {
|
||||
if (
|
||||
event->getSetting()->getModID() == m_impl->modID &&
|
||||
!m_impl->settingKey || event->getSetting()->getKey() == m_impl->settingKey
|
||||
) {
|
||||
fn(event->getSetting());
|
||||
}
|
||||
return ListenerResult::Propagate;
|
||||
}
|
||||
|
||||
SettingChangedFilterV3::SettingChangedFilterV3(
|
||||
std::string const& modID,
|
||||
std::optional<std::string> const& settingKey
|
||||
) : m_impl(std::make_shared<Impl>())
|
||||
{
|
||||
m_impl->modID = modID;
|
||||
m_impl->settingKey = settingKey;
|
||||
}
|
||||
|
||||
SettingChangedFilterV3::SettingChangedFilterV3(Mod* mod, std::optional<std::string> const& settingKey)
|
||||
: SettingChangedFilterV3(mod->getID(), settingKey) {}
|
||||
|
||||
SettingChangedFilterV3::SettingChangedFilterV3(SettingChangedFilterV3 const&) = default;
|
||||
|
||||
EventListener<SettingChangedFilterV3>* geode::listenForAllSettingChanges(
|
||||
std::function<void(std::shared_ptr<SettingV3>)> const& callback,
|
||||
Mod* mod
|
||||
) {
|
||||
return new EventListener(
|
||||
[callback](std::shared_ptr<SettingV3> setting) {
|
||||
callback(setting);
|
||||
},
|
||||
SettingChangedFilterV3(mod->getID(), std::nullopt)
|
||||
);
|
||||
}
|
||||
|
||||
class SettingV3::GeodeImpl {
|
||||
public:
|
||||
std::string modID;
|
||||
|
@ -485,6 +544,19 @@ Mod* SettingV3::getMod() const {
|
|||
return Loader::get()->getInstalledMod(m_impl->modID);
|
||||
}
|
||||
|
||||
void SettingV3::markChanged() {
|
||||
auto manager = ModSettingsManager::from(this->getMod());
|
||||
if (m_impl->requiresRestart) {
|
||||
manager->markRestartRequired();
|
||||
}
|
||||
SettingChangedEventV3(shared_from_this()).post();
|
||||
if (manager) {
|
||||
// Use ModSettingsManager rather than convertToLegacyValue since it
|
||||
// caches the result and we want to have that for performance
|
||||
SettingChangedEvent(this->getMod(), manager->getLegacy(this->getKey()).get()).post();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Setting> SettingV3::convertToLegacy() const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
|
|
@ -90,6 +90,11 @@ bool ModsStatusNode::init() {
|
|||
|
||||
m_downloadListener.bind([this](auto) { this->updateState(); });
|
||||
|
||||
m_settingNodeListener.bind([this](SettingNodeValueChangeEventV3* ev) {
|
||||
this->updateState();
|
||||
return ListenerResult::Propagate;
|
||||
});
|
||||
|
||||
this->updateState();
|
||||
|
||||
return true;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "sources/ModListSource.hpp"
|
||||
#include "UpdateModListState.hpp"
|
||||
#include <server/DownloadManager.hpp>
|
||||
#include <Geode/loader/SettingV3.hpp>
|
||||
|
||||
using namespace geode::prelude;
|
||||
|
||||
|
@ -39,6 +40,7 @@ protected:
|
|||
EventListener<UpdateModListStateFilter> m_updateStateListener;
|
||||
EventListener<server::ModDownloadFilter> m_downloadListener;
|
||||
DownloadState m_lastState = DownloadState::None;
|
||||
EventListener<EventFilter<SettingNodeValueChangeEventV3>> m_settingNodeListener;
|
||||
|
||||
bool init();
|
||||
void updateState();
|
||||
|
|
|
@ -283,6 +283,11 @@ bool ModItem::init(ModSource&& source) {
|
|||
m_downloadListener.bind([this](auto) { this->updateState(); });
|
||||
m_downloadListener.setFilter(server::ModDownloadFilter(m_source.getID()));
|
||||
|
||||
m_settingNodeListener.bind([this](SettingNodeValueChangeEventV3*) {
|
||||
this->updateState();
|
||||
return ListenerResult::Propagate;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ protected:
|
|||
EventListener<server::ServerRequest<std::optional<server::ServerModUpdate>>> m_checkUpdateListener;
|
||||
EventListener<server::ModDownloadFilter> m_downloadListener;
|
||||
std::optional<server::ServerModUpdate> m_availableUpdate;
|
||||
EventListener<EventFilter<SettingNodeValueChangeEventV3>> m_settingNodeListener;
|
||||
|
||||
/**
|
||||
* @warning Make sure `getMetadata` and `createModLogo` are callable
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <Geode/ui/MDTextArea.hpp>
|
||||
#include <Geode/utils/web.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/ModSettingsManager.hpp>
|
||||
#include <Geode/ui/GeodeUI.hpp>
|
||||
#include <Geode/utils/ColorProvider.hpp>
|
||||
#include "ConfirmUninstallPopup.hpp"
|
||||
|
@ -557,6 +558,13 @@ bool ModPopup::setup(ModSource&& src) {
|
|||
mainContainer->updateLayout();
|
||||
m_mainLayer->addChildAtPosition(mainContainer, Anchor::Center);
|
||||
|
||||
m_settingsBG = CCScale9Sprite::create("square02b_001.png");
|
||||
m_settingsBG->setColor({ 0, 0, 0 });
|
||||
m_settingsBG->setOpacity(75);
|
||||
m_settingsBG->setScale(.3f);
|
||||
m_settingsBG->setContentSize(ccp(35, 30) / linksBG->getScale());
|
||||
m_buttonMenu->addChildAtPosition(m_settingsBG, Anchor::BottomLeft, ccp(28, 25));
|
||||
|
||||
auto settingsSpr = createGeodeCircleButton(CCSprite::createWithSpriteFrameName("settings.png"_spr));
|
||||
settingsSpr->setScale(.6f);
|
||||
auto settingsBtn = CCMenuItemSpriteExtra::create(
|
||||
|
@ -601,15 +609,30 @@ bool ModPopup::setup(ModSource&& src) {
|
|||
m_downloadListener.bind([this](auto) { this->updateState(); });
|
||||
m_downloadListener.setFilter(m_source.getID());
|
||||
|
||||
m_settingNodeListener.bind([this](SettingNodeValueChangeEventV3*) {
|
||||
this->updateState();
|
||||
return ListenerResult::Propagate;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModPopup::updateState() {
|
||||
auto asMod = m_source.asMod();
|
||||
auto wantsRestart = m_source.wantsRestart();
|
||||
auto wantsRestartBecauseOfSettings = asMod && ModSettingsManager::from(asMod)->restartRequired();
|
||||
|
||||
m_installBG->setColor(wantsRestart ? to3B(ColorProvider::get()->color("mod-list-restart-required-label"_spr)) : ccc3(0, 0, 0));
|
||||
m_installBG->setOpacity(wantsRestart ? 40 : 75);
|
||||
m_installBG->setColor((wantsRestart && !wantsRestartBecauseOfSettings) ?
|
||||
to3B(ColorProvider::get()->color("mod-list-restart-required-label"_spr)) :
|
||||
ccBLACK
|
||||
);
|
||||
m_installBG->setOpacity((wantsRestart && !wantsRestartBecauseOfSettings) ? 40 : 75);
|
||||
m_settingsBG->setColor(wantsRestartBecauseOfSettings ?
|
||||
to3B(ColorProvider::get()->color("mod-list-restart-required-label"_spr)) :
|
||||
ccBLACK
|
||||
);
|
||||
m_settingsBG->setOpacity(wantsRestartBecauseOfSettings ? 40 : 75);
|
||||
m_settingsBG->setVisible(wantsRestartBecauseOfSettings);
|
||||
m_restartRequiredLabel->setVisible(wantsRestart);
|
||||
|
||||
if (!wantsRestart && asMod) {
|
||||
|
|
|
@ -30,6 +30,7 @@ protected:
|
|||
CCMenuItemSpriteExtra* m_cancelBtn;
|
||||
CCLabelBMFont* m_installStatusLabel;
|
||||
CCScale9Sprite* m_installBG;
|
||||
CCScale9Sprite* m_settingsBG;
|
||||
CCLabelBMFont* m_enabledStatusLabel;
|
||||
ButtonSprite* m_restartRequiredLabel;
|
||||
CCNode* m_rightColumn;
|
||||
|
@ -40,6 +41,7 @@ protected:
|
|||
EventListener<server::ServerRequest<std::optional<server::ServerModUpdate>>> m_checkUpdateListener;
|
||||
EventListener<UpdateModListStateFilter> m_updateStateListener;
|
||||
EventListener<server::ModDownloadFilter> m_downloadListener;
|
||||
EventListener<EventFilter<SettingNodeValueChangeEventV3>> m_settingNodeListener;
|
||||
|
||||
bool setup(ModSource&& src) override;
|
||||
void updateState();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <Geode/binding/ButtonSprite.hpp>
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
#include <Geode/loader/Setting.hpp>
|
||||
#include <Geode/loader/ModSettingsManager.hpp>
|
||||
#include <Geode/ui/ScrollLayer.hpp>
|
||||
#include <Geode/utils/cocos.hpp>
|
||||
#include <Geode/ui/General.hpp>
|
||||
|
@ -63,13 +64,26 @@ bool ModSettingsPopup::setup(Mod* mod) {
|
|||
|
||||
// buttons
|
||||
|
||||
m_applyMenu = CCMenu::create();
|
||||
m_applyMenu->setContentWidth(150);
|
||||
m_applyMenu->setLayout(RowLayout::create());
|
||||
m_applyMenu->getLayout()->ignoreInvisibleChildren(true);
|
||||
|
||||
auto restartBtnSpr = createGeodeButton("Restart Now", true);
|
||||
restartBtnSpr->setScale(.6f);
|
||||
m_restartBtn = CCMenuItemSpriteExtra::create(
|
||||
restartBtnSpr, this, menu_selector(ModSettingsPopup::onRestart)
|
||||
);
|
||||
m_applyMenu->addChildAtPosition(m_restartBtn, Anchor::Bottom, ccp(0, 20));
|
||||
|
||||
m_applyBtnSpr = createGeodeButton("Apply", true);
|
||||
m_applyBtnSpr->setScale(.6f);
|
||||
|
||||
m_applyBtn = CCMenuItemSpriteExtra::create(
|
||||
m_applyBtnSpr, this, menu_selector(ModSettingsPopup::onApply)
|
||||
);
|
||||
m_buttonMenu->addChildAtPosition(m_applyBtn, Anchor::Bottom, ccp(0, 20));
|
||||
m_applyMenu->addChildAtPosition(m_applyBtn, Anchor::Bottom, ccp(0, 20));
|
||||
|
||||
m_mainLayer->addChildAtPosition(m_applyMenu, Anchor::Bottom, ccp(0, 20));
|
||||
|
||||
auto resetBtnSpr = createGeodeButton("Reset All", true);
|
||||
resetBtnSpr->setScale(.6f);
|
||||
|
@ -79,13 +93,28 @@ bool ModSettingsPopup::setup(Mod* mod) {
|
|||
);
|
||||
m_buttonMenu->addChildAtPosition(resetBtn, Anchor::BottomLeft, ccp(45, 20));
|
||||
|
||||
auto openDirBtnSpr = createGeodeButton("Open Folder", true);
|
||||
openDirBtnSpr->setScale(.6f);
|
||||
auto configFolderSpr = CCSprite::createWithSpriteFrameName("folderIcon_001.png");
|
||||
m_openConfigDirBtnSpr = createGeodeButton(configFolderSpr, "");
|
||||
m_openConfigDirBtnSpr->setScale(.6f);
|
||||
m_openConfigDirBtnSpr->getIcon()->setScale(m_openConfigDirBtnSpr->getIcon()->getScale() * 1.4f);
|
||||
auto openConfigDirBtn = CCMenuItemSpriteExtra::create(
|
||||
m_openConfigDirBtnSpr, this, menu_selector(ModSettingsPopup::onOpenConfigDirectory)
|
||||
);
|
||||
m_buttonMenu->addChildAtPosition(openConfigDirBtn, Anchor::BottomRight, ccp(-50, 20));
|
||||
|
||||
auto settingFolderSpr = CCSprite::createWithSpriteFrameName("folderIcon_001.png");
|
||||
auto settingFolderSprSub = CCSprite::createWithSpriteFrameName("settings.png"_spr);
|
||||
settingFolderSprSub->setColor(ccBLACK);
|
||||
settingFolderSprSub->setOpacity(155);
|
||||
settingFolderSprSub->setScale(.55f);
|
||||
settingFolderSpr->addChildAtPosition(settingFolderSprSub, Anchor::Center, ccp(0, -3));
|
||||
auto openDirBtnSpr = createGeodeButton(settingFolderSpr, "");
|
||||
openDirBtnSpr->setScale(.6f);
|
||||
openDirBtnSpr->getIcon()->setScale(openDirBtnSpr->getIcon()->getScale() * 1.4f);
|
||||
auto openDirBtn = CCMenuItemSpriteExtra::create(
|
||||
openDirBtnSpr, this, menu_selector(ModSettingsPopup::onOpenSaveDirectory)
|
||||
);
|
||||
m_buttonMenu->addChildAtPosition(openDirBtn, Anchor::BottomRight, ccp(-53, 20));
|
||||
m_buttonMenu->addChildAtPosition(openDirBtn, Anchor::BottomRight, ccp(-20, 20));
|
||||
|
||||
m_changeListener.bind([this](auto* ev) {
|
||||
this->updateState(ev->getNode());
|
||||
|
@ -108,7 +137,21 @@ void ModSettingsPopup::onApply(CCObject*) {
|
|||
FLAlertLayer::create("Info", "No changes have been made.", "OK")->show();
|
||||
}
|
||||
}
|
||||
void ModSettingsPopup::onRestart(CCObject*) {
|
||||
// Update button state to let user know it's restarting but it might take a bit
|
||||
m_restartBtn->setEnabled(false);
|
||||
static_cast<ButtonSprite*>(m_restartBtn->getNormalImage())->setString("Restarting...");
|
||||
m_restartBtn->updateSprite();
|
||||
|
||||
// Actually restart
|
||||
Loader::get()->queueInMainThread([] {
|
||||
// Delayed by 2 frames - one is needed to render the "Restarting text"
|
||||
Loader::get()->queueInMainThread([] {
|
||||
// the other never finishes rendering because the game actually restarts at this point
|
||||
game::restart();
|
||||
});
|
||||
});
|
||||
}
|
||||
void ModSettingsPopup::onResetAll(CCObject*) {
|
||||
createQuickPopup(
|
||||
"Reset All",
|
||||
|
@ -124,8 +167,24 @@ void ModSettingsPopup::onResetAll(CCObject*) {
|
|||
}
|
||||
);
|
||||
}
|
||||
void ModSettingsPopup::onOpenSaveDirectory(CCObject*) {
|
||||
file::openFolder(m_mod->getSaveDir());
|
||||
}
|
||||
void ModSettingsPopup::onOpenConfigDirectory(CCObject*) {
|
||||
file::openFolder(m_mod->getConfigDir());
|
||||
this->updateState();
|
||||
}
|
||||
|
||||
void ModSettingsPopup::updateState(SettingNodeV3* invoker) {
|
||||
m_restartBtn->setVisible(ModSettingsManager::from(m_mod)->restartRequired());
|
||||
m_applyMenu->updateLayout();
|
||||
|
||||
auto configDirExists = std::filesystem::exists(m_mod->getConfigDir(false));
|
||||
m_openConfigDirBtnSpr->setCascadeColorEnabled(true);
|
||||
m_openConfigDirBtnSpr->setCascadeOpacityEnabled(true);
|
||||
m_openConfigDirBtnSpr->setColor(configDirExists ? ccWHITE : ccGRAY);
|
||||
m_openConfigDirBtnSpr->setOpacity(configDirExists ? 255 : 155);
|
||||
|
||||
// Update all settings with "enable-if" schemes
|
||||
for (auto& sett : m_settings) {
|
||||
// Avoid infinite loops
|
||||
|
@ -136,6 +195,7 @@ void ModSettingsPopup::updateState(SettingNodeV3* invoker) {
|
|||
sett->updateState();
|
||||
}
|
||||
}
|
||||
|
||||
m_applyBtnSpr->setCascadeColorEnabled(true);
|
||||
m_applyBtnSpr->setCascadeOpacityEnabled(true);
|
||||
if (this->hasUncommitted()) {
|
||||
|
@ -175,10 +235,6 @@ void ModSettingsPopup::onClose(CCObject* sender) {
|
|||
Popup<Mod*>::onClose(sender);
|
||||
}
|
||||
|
||||
void ModSettingsPopup::onOpenSaveDirectory(CCObject*) {
|
||||
file::openFolder(m_mod->getSaveDir());
|
||||
}
|
||||
|
||||
ModSettingsPopup* ModSettingsPopup::create(Mod* mod) {
|
||||
auto ret = new ModSettingsPopup();
|
||||
if (ret->init(440, 280, mod)) {
|
||||
|
|
|
@ -11,8 +11,11 @@ class ModSettingsPopup : public GeodePopup<Mod*> {
|
|||
protected:
|
||||
Mod* m_mod;
|
||||
std::vector<SettingNodeV3*> m_settings;
|
||||
CCMenu* m_applyMenu;
|
||||
CCMenuItemSpriteExtra* m_applyBtn;
|
||||
CCMenuItemSpriteExtra* m_restartBtn;
|
||||
ButtonSprite* m_applyBtnSpr;
|
||||
IconButtonSprite* m_openConfigDirBtnSpr;
|
||||
EventListener<EventFilter<SettingNodeValueChangeEventV3>> m_changeListener;
|
||||
|
||||
bool setup(Mod* mod) override;
|
||||
|
@ -20,8 +23,10 @@ protected:
|
|||
bool hasUncommitted() const;
|
||||
void onClose(CCObject*) override;
|
||||
void onApply(CCObject*);
|
||||
void onRestart(CCObject*);
|
||||
void onResetAll(CCObject*);
|
||||
void onOpenSaveDirectory(CCObject*);
|
||||
void onOpenConfigDirectory(CCObject*);
|
||||
|
||||
public:
|
||||
static ModSettingsPopup* create(Mod* mod);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "ModListSource.hpp"
|
||||
#include <server/DownloadManager.hpp>
|
||||
#include <Geode/loader/ModSettingsManager.hpp>
|
||||
|
||||
#define FTS_FUZZY_MATCH_IMPLEMENTATION
|
||||
#include <Geode/external/fts/fts_fuzzy_match.h>
|
||||
|
@ -90,6 +91,9 @@ bool ModListSource::isRestartRequired() {
|
|||
if (mod->getRequestedAction() != ModRequestedAction::None) {
|
||||
return true;
|
||||
}
|
||||
if (ModSettingsManager::from(mod)->restartRequired()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (server::ModDownloadManager::get()->wantsRestart()) {
|
||||
return true;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "ModSource.hpp"
|
||||
|
||||
#include <Geode/loader/ModMetadata.hpp>
|
||||
#include <Geode/loader/ModSettingsManager.hpp>
|
||||
#include <Geode/ui/GeodeUI.hpp>
|
||||
#include <server/DownloadManager.hpp>
|
||||
#include <Geode/binding/GameObject.hpp>
|
||||
|
@ -106,7 +107,8 @@ bool ModSource::wantsRestart() const {
|
|||
}
|
||||
return std::visit(makeVisitor {
|
||||
[](Mod* mod) {
|
||||
return mod->getRequestedAction() != ModRequestedAction::None;
|
||||
return mod->getRequestedAction() != ModRequestedAction::None ||
|
||||
ModSettingsManager::from(mod)->restartRequired();
|
||||
},
|
||||
[](server::ServerModMetadata const& metdata) {
|
||||
return false;
|
||||
|
|
|
@ -209,7 +209,7 @@ void SimpleTextArea::updateLinesNoWrap() {
|
|||
|
||||
void SimpleTextArea::updateLinesWordWrap(bool spaceWrap) {
|
||||
this->charIteration([this, spaceWrap](CCLabelBMFont* line, const char c, const float top) {
|
||||
static const std::string delimiters(spaceWrap ? " " : " `~!@#$%^&*()-_=+[{}];:'\",<.>/?\\|");
|
||||
const std::string_view delimiters(spaceWrap ? " " : " `~!@#$%^&*()-_=+[{}];:'\",<.>/?\\|");
|
||||
|
||||
if (delimiters.find(c) == std::string_view::npos) {
|
||||
const std::string& text = line->getString();
|
||||
|
|
Loading…
Reference in a new issue