Merge remote-tracking branch 'upstream/main'

This commit is contained in:
SabeMP 2025-03-11 10:37:25 -03:00
commit 261941fc6b
No known key found for this signature in database
18 changed files with 1372 additions and 40 deletions

View file

@ -1,7 +1,21 @@
# Geode Changelog
## v4.2.1
## v4.3.0
* Event export macro (#1243)
* See [the docs](https://docs.geode-sdk.org/mods/dependencies#event-macro) for more info
* Fix settings `enable-if` parsing (315bf46, 1542e29)
* Add environment variable for forcing terminal colors (39b1bef)
* Fix crashlog PDB search paths (#1222)
* Add utils::string::trim* overloads for specific charset (0d4dcb3, 17faf36)
* Round number settings to 5 decimal places (c9dbc4b)
* Allow number inputs to be invalid while typing and active (6c6215b)
* Allow `Task<void>`, useful for coroutines (2bfff1a, 463ea22)
* Add some coroutine utils (99cefab)
* Remove handler from function if no hooks are active (dc14d4c)
* Fix some bugs in `geode::utils::ranges` methods (#1236, #1239)
* Add patch for `CCGLProgram::compileShader` on remaining platforms (#1241)
* Update the pugixml headers to be v1.15 compatible (#1247)
* Allow auto update across major versions if running in forward compat mode (4bb17a9)
## v4.2.0
* Implement gd::set for android (#1197, #1207)

View file

@ -15,6 +15,12 @@ endif()
option(GEODE_USE_BREAKPAD "Enables the use of the Breakpad library for crash dumps." ON)
# Check if git is installed, raise a fatal error if not
find_program(GIT_EXECUTABLE git)
if (NOT GIT_EXECUTABLE)
message(FATAL_ERROR "Git not found! Please install Git and try again.\nhttps://git-scm.com/")
endif()
# Read version
file(READ VERSION GEODE_VERSION)
string(STRIP "${GEODE_VERSION}" GEODE_VERSION)
@ -238,7 +244,7 @@ endif()
set(MAT_JSON_AS_INTERFACE ON)
CPMAddPackage("gh:geode-sdk/result@1.3.3")
CPMAddPackage("gh:geode-sdk/json@3.2.1")
CPMAddPackage("gh:fmtlib/fmt#11.0.2")
CPMAddPackage("gh:fmtlib/fmt#11.1.4")
target_compile_definitions(${PROJECT_NAME} INTERFACE MAT_JSON_DYNAMIC=1)

View file

@ -17,6 +17,7 @@
#include "ui/SceneManager.hpp"
#include "ui/ScrollLayer.hpp"
#include "ui/SelectList.hpp"
#include "ui/SimpleAxisLayout.hpp"
#include "ui/Scrollbar.hpp"
#include "ui/TextArea.hpp"
#include "ui/TextRenderer.hpp"

View file

@ -233,6 +233,7 @@ class CC_DLL CCRepeatForever : public CCActionInterval
{
GEODE_FRIEND_MODIFY
public:
GEODE_CUSTOM_CONSTRUCTOR_COCOS(CCRepeatForever, CCActionInterval)
/**
* @js ctor
*/

View file

@ -19,6 +19,8 @@ public:
cocos2d::CCDictionary* addDictDS(const char* dict);
void clearCache();
public:
cocos2d::CCDictionary* m_pDictCache;
};
#endif

View file

@ -98,6 +98,9 @@ public:
* @lua NA
*/
~CCSpriteFrame(void);
inline CCSpriteFrame() {}
GEODE_CUSTOM_CONSTRUCTOR_COCOS(CCSpriteFrame, CCObject);
/**
* @js NA
* @lua NA

View file

@ -1,6 +1,7 @@
#pragma once
#include "Event.hpp"
#include "../modify/Traits.hpp"
#include <functional>
#include <string>
@ -16,11 +17,11 @@ namespace geode {
protected:
std::string m_id;
std::tuple<Args...> m_args;
public:
DispatchEvent(std::string const& id, Args... args)
: m_id(id), m_args(std::make_tuple(args...)) {}
DispatchEvent(std::string const& id, Args... args) :
m_id(id), m_args(std::make_tuple(args...)) {}
std::tuple<Args...> getArgs() const {
return m_args;
}
@ -61,6 +62,103 @@ namespace geode {
}
DispatchFilter(std::string const& id) : m_id(id) {}
DispatchFilter(DispatchFilter const&) = default;
};
}
}
// - Macros for exporting functions via events -
// You can use these to easily export functions to other mods
// without being a required depedency.
// # Example Usage:
/*
```
// (In your api distributed header file)
#pragma once
#include <Geode/loader/Dispatch.hpp>
// You must **manually** declare the mod id, as macros like GEODE_MOD_ID will not
// behave correctly to other mods using your api.
#define MY_MOD_ID "dev.my-api"
namespace api {
// Important: The function must be declared inline, and return a geode::Result,
// as it can fail if the api is not available.
inline geode::Result<int> addNumbers(int a, int b) GEODE_EVENT_EXPORT(&addNumbers, (a, b));
}
```
*/
// Then, in **one** of your source files, you must define the exported functions:
/*
```
// MUST be defined before including the header.
#define GEODE_DEFINE_EVENT_EXPORTS
#include "../include/api.hpp"
Result<int> api::addNumbers(int a, int b) {
return Ok(a + b);
}
```
*/
// once this is set in stone we should not change it ever
#define GEODE_EVENT_EXPORT_ID_FOR(fnPtrStr, callArgsStr) \
(std::string(MY_MOD_ID "/") + (fnPtrStr[0] == '&' ? &fnPtrStr[1] : fnPtrStr))
namespace geode::geode_internal {
template <class Fn>
inline auto callEventExportListener(Fn fnPtr, auto eventID) {
using StaticType = geode::modifier::AsStaticType<Fn>::type;
Fn ptr = nullptr;
geode::DispatchEvent<Fn*>(eventID, &ptr).post();
return std::function<std::remove_pointer_t<StaticType>>(ptr);
}
template <class Fn>
inline bool getEventExportListener(Fn fnPtr, auto eventID) {
new geode::EventListener(
[=](Fn* ptr) {
*ptr = fnPtr;
return geode::ListenerResult::Stop;
},
geode::DispatchFilter<Fn*>(eventID)
);
return true;
}
}
#define GEODE_EVENT_EXPORT_CALL(fnPtr, callArgs, eventID) \
{ \
static auto storage = geode::geode_internal::callEventExportListener(fnPtr, eventID); \
if (!storage) return geode::Err("Unable to call method"); \
return storage callArgs; \
}
#define GEODE_EVENT_EXPORT_DEFINE(fnPtr, callArgs, eventID) \
; \
template <auto> \
struct EventExportDefine; \
template <> \
struct EventExportDefine<geode::modifier::FunctionUUID<fnPtr>::value> { \
static inline bool val = geode::geode_internal::getEventExportListener(fnPtr, eventID); \
static inline auto nonOmitted = &val; \
};
#ifndef GEODE_DEFINE_EVENT_EXPORTS
#define GEODE_EVENT_EXPORT(fnPtr, callArgs) \
GEODE_EVENT_EXPORT_CALL(fnPtr, callArgs, GEODE_EVENT_EXPORT_ID_FOR(#fnPtr, #callArgs))
#define GEODE_EVENT_EXPORT_ID(fnPtr, callArgs, eventID) \
GEODE_EVENT_EXPORT_CALL(fnPtr, callArgs, eventID)
#else
#define GEODE_EVENT_EXPORT(fnPtr, callArgs) \
GEODE_EVENT_EXPORT_DEFINE(fnPtr, callArgs, GEODE_EVENT_EXPORT_ID_FOR(#fnPtr, #callArgs))
#define GEODE_EVENT_EXPORT_ID(fnPtr, callArgs, eventID) \
GEODE_EVENT_EXPORT_DEFINE(fnPtr, callArgs, eventID)
#endif

View file

@ -0,0 +1,239 @@
#pragma once
#include "Layout.hpp"
namespace geode {
enum class AxisScaling {
// Does not scale items
None,
// Scales items down if necessary to fit
ScaleDown,
// Scales items up/down to fit
Scale,
// Grows the layout if necessary to fit
Grow,
// Fits the layout to the items
Fit,
// Shrinks gaps if needed to fit, then scales down items
ScaleDownGaps,
};
enum class ScalingPriority {
// Scales down first
First,
// Scales down second
Early,
// Default scaling priority
Normal,
// Scales down second to last
Late,
// Scales down last
Last,
// Does not scale
Never = 128,
};
enum class MainAxisAlignment {
// Align items to the start
// |ooo......|
Start,
// All items are centered
// |...ooo...|
Center,
// Align items to the end
// |......ooo|
End,
// Each item gets the same portion from the layout (disregards gap)
// |.o..o..o.|
Even,
// Space between each item is the same (disregards gap)
// |o...o...o|
Between,
// Space around each item is the same (disregards gap)
// |.o..o..o.|
Around,
};
enum class CrossAxisAlignment {
// Align items to the start
// |ooo......|
Start,
// All items are centered
// |...ooo...|
Center,
// Align items to the end
// |......ooo|
End,
};
enum class AxisDirection {
// Items are laid out from top to bottom
TopToBottom = 1,
// Items are laid out from bottom to top
BottomToTop = 0,
// Items are laid out from left to right
LeftToRight = 0,
// Items are laid out from right to left
RightToLeft = 1,
// Items are laid out from front to back
FrontToBack = 0,
// Items are laid out from back to front
BackToFront = 1,
};
class GEODE_DLL SimpleAxisLayoutOptions : public LayoutOptions {
protected:
class Impl;
std::unique_ptr<Impl> m_impl;
SimpleAxisLayoutOptions();
public:
static SimpleAxisLayoutOptions* create();
virtual ~SimpleAxisLayoutOptions();
/**
* Sets the minimum relative scale the node can be scaled to
* if required to scale down to fit the layout
*/
SimpleAxisLayoutOptions* setMinRelativeScale(std::optional<float> scale);
/**
* Sets the maximum relative scale the node can be scaled to
* if required to scale up to fit the layout
*/
SimpleAxisLayoutOptions* setMaxRelativeScale(std::optional<float> scale);
/**
* Sets the scaling priority for the node, to specify
* when the node should be scaled compared to other nodes
* during main axis scaling
*/
SimpleAxisLayoutOptions* setScalingPriority(ScalingPriority priority);
std::optional<float> getMinRelativeScale() const;
std::optional<float> getMaxRelativeScale() const;
ScalingPriority getScalingPriority() const;
};
class GEODE_DLL SimpleAxisLayout : public Layout {
protected:
class Impl;
std::unique_ptr<Impl> m_impl;
SimpleAxisLayout(Axis);
public:
static SimpleAxisLayout* create(Axis axis);
virtual ~SimpleAxisLayout();
void apply(cocos2d::CCNode* on) override;
cocos2d::CCSize getSizeHint(cocos2d::CCNode* on) const override;
/**
* Sets the axis of the layout
*/
SimpleAxisLayout* setAxis(Axis axis);
/**
* Sets the scaling behaviour of the main axis
* The default is set to AxisScaling::None
*/
SimpleAxisLayout* setMainAxisScaling(AxisScaling scaling);
/**
* Sets the scaling behaviour of the cross axis
* The default is set to AxisScaling::None
*/
SimpleAxisLayout* setCrossAxisScaling(AxisScaling scaling);
/**
* Sets how the items are aligned on the main axis
* The default is set to MainAxisAlignment::Start
*/
SimpleAxisLayout* setMainAxisAlignment(MainAxisAlignment alignment);
/**
* Sets how the items are aligned on the cross axis
* The default is set to CrossAxisAlignment::Center
*/
SimpleAxisLayout* setCrossAxisAlignment(CrossAxisAlignment alignment);
/**
* Sets the direction of the main axis
* The default is set to AxisDirection::TopToBottom for SimpleRow
* and AxisDirection::LeftToRight for SimpleColumn
*/
SimpleAxisLayout* setMainAxisDirection(AxisDirection direction);
/**
* Sets the direction of the cross axis
* The default is set to AxisDirection::TopToBottom for SimpleRow
* and AxisDirection::LeftToRight for SimpleColumn
*/
SimpleAxisLayout* setCrossAxisDirection(AxisDirection direction);
/**
* Sets the gap between items, unless overridden by a AxisGap node
* The default is set to 0.0f
*/
SimpleAxisLayout* setGap(float gap);
/**
* Sets the minimum relative scale the node can be scaled to
* The default is set to 0.5f
*/
SimpleAxisLayout* setMinRelativeScale(std::optional<float> scale);
/**
* Sets the maximum relative scale the node can be scaled to
* The default is set to 2.0f
*/
SimpleAxisLayout* setMaxRelativeScale(std::optional<float> scale);
Axis getAxis() const;
AxisScaling getMainAxisScaling() const;
AxisScaling getCrossAxisScaling() const;
MainAxisAlignment getMainAxisAlignment() const;
CrossAxisAlignment getCrossAxisAlignment() const;
AxisDirection getMainAxisDirection() const;
AxisDirection getCrossAxisDirection() const;
float getGap() const;
std::optional<float> getMinRelativeScale() const;
std::optional<float> getMaxRelativeScale() const;
};
class GEODE_DLL SimpleRowLayout : public SimpleAxisLayout {
protected:
SimpleRowLayout();
public:
static SimpleRowLayout* create();
virtual ~SimpleRowLayout();
};
class GEODE_DLL SimpleColumnLayout : public SimpleAxisLayout {
protected:
SimpleColumnLayout();
public:
static SimpleColumnLayout* create();
virtual ~SimpleColumnLayout();
};
class GEODE_DLL AxisGap : public cocos2d::CCNode {
protected:
class Impl;
std::unique_ptr<Impl> m_impl;
AxisGap(float gap);
public:
static AxisGap* create(float gap);
virtual ~AxisGap();
/**
* Sets the gap between items in the layout
*/
AxisGap* setGap(float gap);
float getGap() const;
};
}

View file

@ -413,7 +413,7 @@ namespace geode {
* to it is freed or locked
* @param obj Object to construct the WeakRef from
*/
WeakRef(T* obj) : m_controller(WeakRefPool::get()->manage(obj)) {}
WeakRef(T* obj) : m_controller(obj ? WeakRefPool::get()->manage(obj) : nullptr) {}
WeakRef(WeakRef<T> const& other) : WeakRef(other.m_controller) {}
@ -437,7 +437,7 @@ namespace geode {
* a null Ref if the object has been freed
*/
Ref<T> lock() const {
if (m_controller->isManaged()) {
if (m_controller && m_controller->isManaged()) {
return Ref(static_cast<T*>(m_controller->get()));
}
return Ref<T>(nullptr);
@ -447,7 +447,7 @@ namespace geode {
* Check if the WeakRef points to a valid object
*/
bool valid() const {
return m_controller->isManaged();
return m_controller && m_controller->isManaged();
}
/**
@ -456,7 +456,13 @@ namespace geode {
* @param other The new object to swap to
*/
void swap(T* other) {
m_controller->swap(other);
if (m_controller) {
m_controller->swap(other);
} else if (other) {
m_controller = WeakRefPool::get()->manage(other);
} else {
m_controller = nullptr;
}
}
Ref<T> operator=(T* obj) {
@ -465,7 +471,7 @@ namespace geode {
}
WeakRef<T>& operator=(WeakRef<T> const& other) {
this->swap(static_cast<T*>(other.m_controller->get()));
this->swap(static_cast<T*>(other.m_controller ? other.m_controller->get() : nullptr));
return *this;
}
@ -479,33 +485,40 @@ namespace geode {
}
bool operator==(T* other) const {
return m_controller->get() == other;
return (m_controller && m_controller->get() == other) || (!m_controller && !other);
}
bool operator==(WeakRef<T> const& other) const {
if (!m_controller && !other.m_controller) return true;
if (!m_controller || !other.m_controller) return false;
return m_controller->get() == other.m_controller->get();
}
bool operator!=(T* other) const {
return m_controller->get() != other;
return !(*this == other);
}
bool operator!=(WeakRef<T> const& other) const {
return m_controller->get() != other.m_controller->get();
return !(*this == other);
}
// for containers
bool operator<(WeakRef<T> const& other) const {
if (!m_controller && !other.m_controller) return false;
if (!m_controller) return true;
if (!other.m_controller) return false;
return m_controller->get() < other.m_controller->get();
}
bool operator<=(WeakRef<T> const& other) const {
return m_controller->get() <= other.m_controller->get();
return !(*this > other);
}
bool operator>(WeakRef<T> const& other) const {
return m_controller->get() > other.m_controller->get();
return other < *this;
}
bool operator>=(WeakRef<T> const& other) const {
return m_controller->get() >= other.m_controller->get();
return !(*this < other);
}
};

View file

@ -115,7 +115,6 @@
"default": "info",
"name": "Console Log Level",
"description": "Sets the log level for the <cb>platform console</c>.",
"platforms": ["win", "mac"],
"one-of": ["debug", "info", "warn", "error"]
},
"file-log-level": {

View file

@ -0,0 +1,871 @@
#include <Geode/ui/SimpleAxisLayout.hpp>
#include <Geode/ui/SpacerNode.hpp>
#include <Geode/utils/cocos.hpp>
using namespace geode::prelude;
class SimpleAxisLayoutOptions::Impl {
public:
std::optional<float> m_minRelativeScale = .5f;
std::optional<float> m_maxRelativeScale = 2.f;
ScalingPriority m_scalingPriority = ScalingPriority::Normal;
};
SimpleAxisLayoutOptions::SimpleAxisLayoutOptions() : m_impl(std::make_unique<Impl>()) {}
SimpleAxisLayoutOptions::~SimpleAxisLayoutOptions() = default;
SimpleAxisLayoutOptions* SimpleAxisLayoutOptions::create() {
auto ret = new SimpleAxisLayoutOptions();
ret->autorelease();
return ret;
}
SimpleAxisLayoutOptions* SimpleAxisLayoutOptions::setMinRelativeScale(std::optional<float> scale) {
m_impl->m_minRelativeScale = scale;
return this;
}
SimpleAxisLayoutOptions* SimpleAxisLayoutOptions::setMaxRelativeScale(std::optional<float> scale) {
m_impl->m_maxRelativeScale = scale;
return this;
}
SimpleAxisLayoutOptions* SimpleAxisLayoutOptions::setScalingPriority(ScalingPriority priority) {
m_impl->m_scalingPriority = priority;
return this;
}
std::optional<float> SimpleAxisLayoutOptions::getMinRelativeScale() const {
return m_impl->m_minRelativeScale;
}
std::optional<float> SimpleAxisLayoutOptions::getMaxRelativeScale() const {
return m_impl->m_maxRelativeScale;
}
ScalingPriority SimpleAxisLayoutOptions::getScalingPriority() const {
return m_impl->m_scalingPriority;
}
class SimpleAxisLayout::Impl {
public:
Axis m_axis = Axis::Column;
AxisScaling m_mainAxisScaling = AxisScaling::ScaleDownGaps;
AxisScaling m_crossAxisScaling = AxisScaling::None;
MainAxisAlignment m_mainAxisAlignment = MainAxisAlignment::Center;
CrossAxisAlignment m_crossAxisAlignment = CrossAxisAlignment::Center;
AxisDirection m_mainAxisDirection = AxisDirection::FrontToBack;
AxisDirection m_crossAxisDirection = AxisDirection::FrontToBack;
float m_gap = 0.f;
std::optional<float> m_minRelativeScale = 0.5f;
std::optional<float> m_maxRelativeScale = 2.f;
std::unordered_map<CCNode*, float> m_originalScalesPerNode;
std::unordered_map<CCNode*, float> m_relativeScalesPerNode;
Impl(Axis axis) : m_axis(axis) {
switch (axis) {
case Axis::Column:
m_mainAxisDirection = AxisDirection::TopToBottom;
m_crossAxisDirection = AxisDirection::LeftToRight;
break;
case Axis::Row:
m_mainAxisDirection = AxisDirection::LeftToRight;
m_crossAxisDirection = AxisDirection::TopToBottom;
break;
}
}
std::unordered_map<CCNode*, float> calculateCrossScaling(CCNode* layout, std::vector<CCNode*> const& nodes);
std::unordered_map<CCNode*, float> calculateMainScaling(CCNode* layout, std::vector<CCNode*> const& nodes, float totalGap);
void applyCrossPositioning(CCNode* layout, std::vector<CCNode*> const& nodes);
void applyMainPositioning(CCNode* layout, std::vector<CCNode*> const& nodes, std::vector<SpacerNode*> const& spacers, float totalGap);
void apply(cocos2d::CCNode* on);
float getContentWidth(CCNode* on) const {
if (m_axis == Axis::Column) {
return on->getContentSize().width;
}
else {
return on->getContentSize().height;
}
}
float getContentHeight(CCNode* on) const {
if (m_axis == Axis::Column) {
return on->getContentSize().height;
}
else {
return on->getContentSize().width;
}
}
void setContentWidth(CCNode* on, float width) {
if (m_axis == Axis::Column) {
on->setContentSize({ width, on->getContentSize().height });
}
else {
on->setContentSize({ on->getContentSize().width, width });
}
}
void setContentHeight(CCNode* on, float height) {
if (m_axis == Axis::Column) {
on->setContentSize({ on->getContentSize().width, height });
}
else {
on->setContentSize({ height, on->getContentSize().height });
}
}
float getPositionX(CCNode* on) const {
if (m_axis == Axis::Column) {
return on->getPositionX();
}
else {
return on->getPositionY();
}
}
float getPositionY(CCNode* on) const {
if (m_axis == Axis::Column) {
return on->getPositionY();
}
else {
return on->getPositionX();
}
}
void setPositionX(CCNode* on, float x) {
if (m_axis == Axis::Column) {
on->setPosition(x, on->getPositionY());
}
else {
on->setPosition(on->getPositionX(), x);
}
}
void setPositionY(CCNode* on, float y) {
if (m_axis == Axis::Column) {
on->setPosition(on->getPositionX(), y);
}
else {
on->setPosition(y, on->getPositionY());
}
}
float getScale(CCNode* on) const {
return on->getScale();
}
void setScale(CCNode* on, float scale) {
on->setScale(scale);
}
SimpleAxisLayoutOptions* getLayoutOptions(CCNode* on) const {
return typeinfo_cast<SimpleAxisLayoutOptions*>(on->getLayoutOptions());
}
// get the minimum allowed scale for the node
// if the node already has a relative scale set,
// it will be taken into account
std::optional<float> getMinScale(CCNode* on) const {
auto const layoutOptions = this->getLayoutOptions(on);
auto const minScale = layoutOptions ? layoutOptions->getMinRelativeScale() : m_minRelativeScale;
if (minScale) return *minScale / m_relativeScalesPerNode.at(on);
return std::nullopt;
}
// get the maximum allowed scale for the node
// if the node already has a relative scale set,
// it will be taken into account
std::optional<float> getMaxScale(CCNode* on) const {
auto const layoutOptions = this->getLayoutOptions(on);
auto const maxScale = layoutOptions ? layoutOptions->getMaxRelativeScale() : m_maxRelativeScale;
if (maxScale) return *maxScale / m_relativeScalesPerNode.at(on);
return std::nullopt;
}
// get the maximum allowed scale for the node
// based on the layout's width and the node's width
float getMaxCrossScale(CCNode* layout, CCNode* on) const {
auto const layoutWidth = this->getContentWidth(layout);
auto const width = this->getContentWidth(on) * this->getScale(on);
auto const maxAllowedScale = layoutWidth / width;
auto const maxScale = this->getMaxScale(on);
if (maxScale) return std::min(maxAllowedScale, *maxScale);
return maxAllowedScale;
}
};
std::unordered_map<CCNode*, float> SimpleAxisLayout::Impl::calculateCrossScaling(CCNode* layout, std::vector<CCNode*> const& nodes) {
std::unordered_map<CCNode*, float> scales;
auto maxWidth = std::numeric_limits<float>::min();
auto layoutWidth = this->getContentWidth(layout);
// get the limits we are working with
for (auto node : nodes) {
auto const width = this->getContentWidth(node) * this->getScale(node);
if (width > maxWidth) {
maxWidth = width;
}
}
switch (m_crossAxisScaling) {
case AxisScaling::Grow:
// grow the layout to fit the widest node
if (maxWidth > layoutWidth) layoutWidth = maxWidth;
break;
case AxisScaling::Fit:
// fit the layout to the widest node
layoutWidth = maxWidth;
break;
default:
break;
}
this->setContentWidth(layout, layoutWidth);
// get the scales we need for current limits
for (auto node : nodes) {
switch (m_crossAxisScaling) {
case AxisScaling::ScaleDownGaps:
case AxisScaling::ScaleDown: {
auto const width = this->getContentWidth(node) * this->getScale(node);
auto const minScale = this->getMinScale(node);
// scale down if needed
if (width > layoutWidth) {
scales[node] = std::clamp(layoutWidth / width, minScale.value_or(0.f), 1.f);
}
break;
}
case AxisScaling::Scale: {
auto const width = this->getContentWidth(node) * this->getScale(node);
auto const minScale = this->getMinScale(node);
auto const maxScale = this->getMaxCrossScale(layout, node);
// scale both up and down
scales[node] = std::clamp(layoutWidth / width, minScale.value_or(0.f), maxScale);
break;
}
default:
break;
}
}
return scales;
}
// assumes scales are reverted before call
std::unordered_map<CCNode*, float> SimpleAxisLayout::Impl::calculateMainScaling(CCNode* layout, std::vector<CCNode*> const& nodes, float totalGap) {
std::unordered_map<CCNode*, float> scales;
auto totalHeight = totalGap;
auto layoutHeight = this->getContentHeight(layout);
// get the limits we are working with
for (auto node : nodes) {
auto const height = this->getContentHeight(node) * this->getScale(node);
totalHeight += height;
}
switch (m_mainAxisScaling) {
case AxisScaling::Grow:
// grow the layout to fit all the nodes
if (totalHeight > layoutHeight) layoutHeight = totalHeight;
break;
case AxisScaling::Fit:
// fit the layout to all the nodes
layoutHeight = totalHeight;
break;
case AxisScaling::ScaleDownGaps:
// remove gaps if needed to fit the layout
if (totalHeight > layoutHeight && totalHeight - totalGap <= layoutHeight) {
totalHeight = layoutHeight;
}
else if (totalHeight > layoutHeight) {
// remove as much as we can
totalHeight -= totalGap;
}
default:
break;
}
this->setContentHeight(layout, layoutHeight);
std::unordered_map<ScalingPriority, std::vector<CCNode*>> sortedNodes;
std::unordered_map<ScalingPriority, float> reducedHeightPerPriority;
std::unordered_map<ScalingPriority, float> increasedHeightPerPriority;
// calculate min max heights based on priorities
for (auto node : nodes) {
// sort the nodes by priority, so we can scale them later
// in the correct order
auto const layoutOptions = this->getLayoutOptions(node);
auto const scalingPriority = layoutOptions ? layoutOptions->getScalingPriority() : ScalingPriority::Normal;
sortedNodes[scalingPriority].push_back(node);
switch (m_mainAxisScaling) {
case AxisScaling::ScaleDownGaps:
case AxisScaling::ScaleDown: {
auto const height = this->getContentHeight(node) * this->getScale(node);
auto const minScale = this->getMinScale(node);
// scale down if needed
auto minHeight = height * minScale.value_or(0.f);
// store how much scaling reduced the height
reducedHeightPerPriority[scalingPriority] += height - minHeight;
break;
}
case AxisScaling::Scale: {
auto const height = this->getContentHeight(node) * this->getScale(node);
auto const minScale = this->getMinScale(node);
auto const maxScale = this->getMaxCrossScale(layout, node);
// scale both up and down
auto minHeight = height * minScale.value_or(0.f);
auto maxHeight = height * maxScale;
// store how much scaling reduced and increased the height
reducedHeightPerPriority[scalingPriority] += height - minHeight;
increasedHeightPerPriority[scalingPriority] += maxHeight - height;
break;
}
default:
break;
}
}
switch (m_mainAxisScaling) {
case AxisScaling::None:
case AxisScaling::Grow:
case AxisScaling::Fit:
return scales;
default:
break;
}
// sort the nodes by priority
if (totalHeight > layoutHeight) {
for (auto& [priority, sorted] : sortedNodes) {
std::sort(sorted.begin(), sorted.end(), [&](CCNode* a, CCNode* b) {
auto const prioA = this->getMinScale(a);
auto const prioB = this->getMinScale(b);
// biggest min scale will be first,
// since it is likely the target scale
// will be smaller than the allowed min scale,
// allowing us to change the target scale later
return prioA.value_or(0.f) > prioB.value_or(0.f);
});
}
}
else {
for (auto& [priority, sorted] : sortedNodes) {
std::sort(sorted.begin(), sorted.end(), [&](CCNode* a, CCNode* b) {
auto const prioA = this->getMaxCrossScale(layout, a);
auto const prioB = this->getMaxCrossScale(layout, b);
// smallest max scale will be first,
// since it is likely the target scale
// will be bigger than the allowed max scale,
// allowing us to change the target scale later
return prioA < prioB;
});
}
}
for (auto priority : {
ScalingPriority::First, ScalingPriority::Early, ScalingPriority::Normal,
ScalingPriority::Late, ScalingPriority::Last
}) {
if (totalHeight > layoutHeight) {
// scale down the nodes, we are over the limit
auto const reducedHeight = reducedHeightPerPriority[priority];
auto difference = totalHeight - layoutHeight;
if (reducedHeight > difference) {
// only partially scale down, should be the last priority to scale
auto priorityHeight = 0.f;
for (auto node : sortedNodes[priority]) {
auto const height = this->getContentHeight(node) * this->getScale(node);
priorityHeight += height;
}
// remainingHeight stores unscaled remaining height
auto remainingHeight = priorityHeight;
// set our target scale to the remaining height
// which may change if minScale is bigger than the target scale
auto targetScale = (remainingHeight - difference) / remainingHeight;
// minScales are sorted in a decreasing priority
for (auto node : sortedNodes[priority]) {
auto const height = this->getContentHeight(node) * this->getScale(node);
auto const minScale = this->getMinScale(node);
auto const scale = std::max(targetScale, minScale.value_or(0.f));
auto const minHeight = height * scale;
scales[node] = scale;
// reduce the remaining height and difference
remainingHeight -= height;
difference -= height - minHeight;
// we need to readjust the target scale if we have remaining height
targetScale = (remainingHeight - difference) / remainingHeight;
}
break;
}
else {
// scale down all the way
for (auto node : sortedNodes[priority]) {
auto const minScale = this->getMinScale(node);
scales[node] = minScale.value_or(0.f);
}
}
totalHeight -= reducedHeight;
}
else {
if (m_mainAxisScaling != AxisScaling::Scale) {
break;
}
// scale up the nodes, we are under the limit
auto const increasedHeight = increasedHeightPerPriority[priority];
auto difference = layoutHeight - totalHeight;
if (increasedHeight > difference) {
// only partially scale up, should be the last priority to scale
auto priorityHeight = 0.f;
for (auto node : sortedNodes[priority]) {
auto const height = this->getContentHeight(node) * this->getScale(node);
priorityHeight += height;
}
// remainingHeight stores unscaled remaining height
auto remainingHeight = priorityHeight;
// set our target scale to the remaining height
// which may change if maxScale is smaller than the target scale
auto targetScale = (remainingHeight + difference) / remainingHeight;
// maxScales are sorted in an increasing priority
for (auto node : sortedNodes[priority]) {
auto const height = this->getContentHeight(node) * this->getScale(node);
auto const maxScale = this->getMaxCrossScale(layout, node);
auto const scale = std::min(targetScale, maxScale);
auto const maxHeight = height * scale;
scales[node] = scale;
// reduce the remaining height and difference
remainingHeight -= height;
difference -= maxHeight - height;
// we need to readjust the target scale if we have remaining height
targetScale = (remainingHeight + difference) / remainingHeight;
}
break;
}
else {
// scale up all the way
for (auto node : sortedNodes[priority]) {
auto const maxScale = this->getMaxCrossScale(layout, node);
scales[node] = maxScale;
}
}
}
}
return scales;
}
void SimpleAxisLayout::Impl::applyCrossPositioning(CCNode* layout, std::vector<CCNode*> const& nodes) {
auto maxWidth = 0.f;
auto layoutWidth = this->getContentWidth(layout);
for (auto node : nodes) {
auto const width = this->getContentWidth(node) * this->getScale(node);
if (width > maxWidth) {
maxWidth = width;
}
}
// reapply grow/fit since main scaling may have changed the max width
switch (m_crossAxisScaling) {
case AxisScaling::Grow:
if (maxWidth > layoutWidth) layoutWidth = maxWidth;
break;
case AxisScaling::Fit:
layoutWidth = maxWidth;
break;
default:
break;
}
this->setContentWidth(layout, layoutWidth);
// cross axis direction only exists to disambiguate the alignment
CrossAxisAlignment alignment = m_crossAxisAlignment;
if (m_crossAxisDirection == AxisDirection::BackToFront) {
switch (m_crossAxisAlignment) {
case CrossAxisAlignment::Start:
alignment = CrossAxisAlignment::End;
break;
case CrossAxisAlignment::End:
alignment = CrossAxisAlignment::Start;
break;
default:
break;
}
}
for (auto node : nodes) {
auto const scale = this->getScale(node);
auto const width = this->getContentWidth(node) * scale;
auto const remainingWidth = layoutWidth - width;
node->ignoreAnchorPointForPosition(false);
node->setAnchorPoint({ 0.5f, 0.5f });
switch (alignment) {
// remainingWidth is the space left after the node is placed
// and width * .5 is added since the anchor point is in the middle
case CrossAxisAlignment::Start:
this->setPositionX(node, width * 0.5f);
break;
case CrossAxisAlignment::Center:
this->setPositionX(node, remainingWidth * 0.5f + width * 0.5f);
break;
case CrossAxisAlignment::End:
this->setPositionX(node, remainingWidth + width * 0.5f);
break;
default:
break;
}
}
}
void SimpleAxisLayout::Impl::applyMainPositioning(CCNode* layout, std::vector<CCNode*> const& nodes, std::vector<SpacerNode*> const& spacers, float totalGap) {
// get the limits we are working with
auto totalHeight = totalGap;
for (auto node : nodes) {
auto const height = this->getContentHeight(node) * this->getScale(node);
totalHeight += height;
}
auto const layoutHeight = this->getContentHeight(layout);
auto gapPercentage = 1.f;
if (m_mainAxisScaling == AxisScaling::ScaleDownGaps) {
if (totalHeight > layoutHeight && totalHeight - totalGap <= layoutHeight) {
auto const difference = totalHeight - layoutHeight;
gapPercentage = 1.f - difference / totalGap;
totalHeight = layoutHeight;
}
else if (totalHeight > layoutHeight) {
// remove as much as we can
gapPercentage = 0.f;
totalHeight -= totalGap;
}
}
auto const remainingHeight = layoutHeight - totalHeight;
auto extraGap = remainingHeight;
auto offset = 0.f;
auto spacerGap = 0.f;
if (spacers.size() > 0) {
// if there are spacer nodes, we allocate all the remaining space to them
size_t totalGrow = 0;
for (auto spacer : spacers) {
totalGrow += spacer->getGrow();
}
extraGap = 0.f;
offset = 0.f;
spacerGap = remainingHeight / totalGrow;
// apply the new height to the spacers
for (auto spacer : spacers) {
this->setScale(spacer, 1.f);
auto const height = spacer->getGrow() * spacerGap;
this->setContentHeight(spacer, height);
this->setContentWidth(spacer, this->getContentWidth(layout));
this->setPositionX(spacer, this->getContentWidth(layout) / 2);
}
}
else {
switch (m_mainAxisAlignment) {
// starts at the start of the layout
// no offset is needed
case MainAxisAlignment::Start:
extraGap = 0.f;
offset = 0.f;
break;
// starts at the center of the layout
// half of the remaining space is added to the offset
case MainAxisAlignment::Center:
extraGap = 0.f;
offset = remainingHeight / 2;
break;
// starts at the end of the layout
// all of the remaining space is added to the offset
case MainAxisAlignment::End:
extraGap = 0.f;
offset = remainingHeight;
break;
// remaining space is divided between the nodes + 1 (outside included)
// and the offset is set to the extra gap
case MainAxisAlignment::Even:
extraGap = remainingHeight / (nodes.size() + 1);
offset = extraGap;
break;
// remaining space is divided between the nodes - 1 (outside excluded)
// and the offset is set to 0
case MainAxisAlignment::Between:
extraGap = remainingHeight / (nodes.size() - 1);
offset = 0.0f;
break;
// remaining space is divided between the nodes (outside half included)
// and the offset is set to half of the extra gap
case MainAxisAlignment::Around:
extraGap = remainingHeight / nodes.size();
offset = extraGap / 2.0f;
break;
}
}
// change the offset based on the direction
if (m_mainAxisDirection == AxisDirection::BackToFront) {
offset = layoutHeight - offset;
}
CCNode* lastChild = nullptr;
for (auto node : nodes) {
// apply the gap between the nodes
if (auto gap = typeinfo_cast<AxisGap*>(node)) {
offset += gap->getGap() * gapPercentage;
lastChild = nullptr;
continue;
}
// otherwise use the default gap
if (lastChild) {
offset += m_gap * gapPercentage;
}
auto const height = this->getContentHeight(node) * this->getScale(node);
node->ignoreAnchorPointForPosition(false);
node->setAnchorPoint(ccp(0.5f, 0.5f));
switch (m_mainAxisDirection) {
// items are laid out from top to bottom
// so the center is subtracted from the offset
case AxisDirection::BackToFront:
this->setPositionY(node, offset - height / 2);
offset -= height + extraGap;
break;
// items are laid out from bottom to top
// so the center is added to the offset
case AxisDirection::FrontToBack:
this->setPositionY(node, offset + height / 2);
offset += height + extraGap;
break;
}
lastChild = node;
}
}
void SimpleAxisLayout::Impl::apply(cocos2d::CCNode* layout) {
std::vector<CCNode*> realChildren;
std::vector<CCNode*> positionChildren;
std::vector<SpacerNode*> spacers;
std::vector<AxisGap*> gaps;
float totalGap = 0.f;
CCNode* lastChild = nullptr;
for (auto child : CCArrayExt<CCNode*>(layout->getChildren())) {
if (auto spacer = typeinfo_cast<SpacerNode*>(child)) {
spacers.push_back(spacer);
positionChildren.push_back(spacer);
}
else if (auto gap = typeinfo_cast<AxisGap*>(child)) {
gaps.push_back(gap);
totalGap += gap->getGap();
// axis gaps are not used for gap ignoring alignments
switch (m_mainAxisAlignment) {
case MainAxisAlignment::Start:
case MainAxisAlignment::Center:
case MainAxisAlignment::End:
positionChildren.push_back(gap);
default:
break;
}
// we use lastChild only for gap calculation
// so we reset it here to not use default gap
// for the next child
lastChild = nullptr;
}
else {
if (lastChild) {
totalGap += m_gap;
}
realChildren.push_back(child);
positionChildren.push_back(child);
lastChild = child;
}
}
// revert back to original scale if needed
for (auto child : realChildren) {
auto const expectedScale = m_originalScalesPerNode[child] * m_relativeScalesPerNode[child];
auto const scale = this->getScale(child);
if (scale != expectedScale) {
// the scale was manually changed, so lets accept
// the new scale as the original scale
m_originalScalesPerNode[child] = scale;
}
else {
this->setScale(child, m_originalScalesPerNode[child]);
}
m_relativeScalesPerNode[child] = 1.f;
}
// calculate required cross scaling
auto crossScales = this->calculateCrossScaling(layout, realChildren);
for (auto child : realChildren) {
auto scale = 1.f;
if (crossScales.contains(child)) {
scale *= crossScales[child];
}
this->setScale(child, scale);
m_relativeScalesPerNode[child] = scale;
}
// calculate required main scaling
// since cross scaling might change the relative scales,
// minScale and maxScale functions account for this change
auto mainScales = this->calculateMainScaling(layout, realChildren, totalGap);
for (auto child : realChildren) {
auto scale = m_relativeScalesPerNode[child];
if (mainScales.contains(child)) {
scale *= mainScales[child];
}
this->setScale(child, scale);
m_relativeScalesPerNode[child] = scale;
}
// apply positions
this->applyCrossPositioning(layout, realChildren);
this->applyMainPositioning(layout, positionChildren, spacers, totalGap);
}
SimpleAxisLayout::SimpleAxisLayout(Axis axis) : m_impl(std::make_unique<Impl>(axis)) {}
SimpleAxisLayout::~SimpleAxisLayout() = default;
SimpleAxisLayout* SimpleAxisLayout::create(Axis axis) {
auto ret = new SimpleAxisLayout(axis);
ret->autorelease();
return ret;
}
cocos2d::CCSize SimpleAxisLayout::getSizeHint(cocos2d::CCNode* on) const {
return on->getContentSize();
}
void SimpleAxisLayout::apply(cocos2d::CCNode* on) {
m_impl->apply(on);
}
SimpleAxisLayout* SimpleAxisLayout::setAxis(Axis axis) {
m_impl->m_axis = axis;
return this;
}
SimpleAxisLayout* SimpleAxisLayout::setMainAxisScaling(AxisScaling scaling) {
m_impl->m_mainAxisScaling = scaling;
return this;
}
SimpleAxisLayout* SimpleAxisLayout::setCrossAxisScaling(AxisScaling scaling) {
m_impl->m_crossAxisScaling = scaling;
return this;
}
SimpleAxisLayout* SimpleAxisLayout::setMainAxisAlignment(MainAxisAlignment alignment) {
m_impl->m_mainAxisAlignment = alignment;
return this;
}
SimpleAxisLayout* SimpleAxisLayout::setCrossAxisAlignment(CrossAxisAlignment alignment) {
m_impl->m_crossAxisAlignment = alignment;
return this;
}
SimpleAxisLayout* SimpleAxisLayout::setMainAxisDirection(AxisDirection direction) {
m_impl->m_mainAxisDirection = direction;
return this;
}
SimpleAxisLayout* SimpleAxisLayout::setCrossAxisDirection(AxisDirection direction) {
m_impl->m_crossAxisDirection = direction;
return this;
}
SimpleAxisLayout* SimpleAxisLayout::setGap(float gap) {
m_impl->m_gap = gap;
return this;
}
SimpleAxisLayout* SimpleAxisLayout::setMinRelativeScale(std::optional<float> scale) {
m_impl->m_minRelativeScale = scale;
return this;
}
SimpleAxisLayout* SimpleAxisLayout::setMaxRelativeScale(std::optional<float> scale) {
m_impl->m_maxRelativeScale = scale;
return this;
}
SimpleRowLayout::SimpleRowLayout() : SimpleAxisLayout(Axis::Row) {}
SimpleRowLayout::~SimpleRowLayout() = default;
SimpleRowLayout* SimpleRowLayout::create() {
auto ret = new SimpleRowLayout();
ret->autorelease();
return ret;
}
SimpleColumnLayout::SimpleColumnLayout() : SimpleAxisLayout(Axis::Column) {}
SimpleColumnLayout::~SimpleColumnLayout() = default;
SimpleColumnLayout* SimpleColumnLayout::create() {
auto ret = new SimpleColumnLayout();
ret->autorelease();
return ret;
}
class AxisGap::Impl {
public:
float m_gap;
Impl(float gap) : m_gap(gap) {}
};
AxisGap::AxisGap(float gap) : m_impl(std::make_unique<Impl>(gap)) {}
AxisGap::~AxisGap() = default;
AxisGap* AxisGap::create(float gap) {
auto ret = new AxisGap(gap);
ret->autorelease();
return ret;
}
float AxisGap::getGap() const {
return m_impl->m_gap;
}
AxisGap* AxisGap::setGap(float gap) {
m_impl->m_gap = gap;
return this;
}

View file

@ -15,9 +15,30 @@ constexpr auto METADATA_TAG = 0xB324ABC;
struct ProxyCCNode;
static uint64_t fnv1aHash(char const* str) {
uint64_t hash = 0xcbf29ce484222325;
while (*str) {
hash ^= *str++;
hash *= 0x100000001b3;
}
return hash;
}
template <typename T>
class NoHashHasher;
template <>
class NoHashHasher<uint64_t> {
public:
size_t operator()(uint64_t key) const {
return key;
}
};
class GeodeNodeMetadata final : public cocos2d::CCObject {
private:
std::unordered_map<std::string, FieldContainer*> m_classFieldContainers;
// for performance reasons, this key is the hash of the class name
std::unordered_map<uint64_t, FieldContainer*, NoHashHasher<uint64_t>> m_classFieldContainers;
std::string m_id = "";
Ref<Layout> m_layout = nullptr;
Ref<LayoutOptions> m_layoutOptions = nullptr;
@ -63,10 +84,14 @@ public:
}
FieldContainer* getFieldContainer(char const* forClass) {
if (!m_classFieldContainers.count(forClass)) {
m_classFieldContainers[forClass] = new FieldContainer();
auto hash = fnv1aHash(forClass);
auto& container = m_classFieldContainers[hash];
if (!container) {
container = new FieldContainer();
}
return m_classFieldContainers[forClass];
return container;
}
};
@ -78,7 +103,7 @@ struct ProxyCCNode : Modify<ProxyCCNode, CCNode> {
return asNode->getUserObject("");
}
else {
// apparently this function is the same as
// apparently this function is the same as
// CCDirector::getNextScene so yeah
return m_pUserObject;
}
@ -224,7 +249,7 @@ public:
}
collectedID.push_back(c);
}
// Any other character is syntax error due to needing to reserve
// Any other character is syntax error due to needing to reserve
// stuff for possible future features
else {
return Err("Unexpected character '{}' at index {}", c, i);

View file

@ -3,6 +3,7 @@
#include <Geode/utils/cocos.hpp>
#include <Geode/utils/NodeIDs.hpp>
#include <Geode/ui/BasedButtonSprite.hpp>
#include <Geode/ui/SimpleAxisLayout.hpp>
#include <Geode/binding/GameManager.hpp>
#include <Geode/binding/PlatformToolbox.hpp>
@ -63,8 +64,9 @@ $register_ids(MenuLayer) {
if (auto pfp = setIDSafe(menu, 3, "profile-button")) {
auto profileMenu = detachAndCreateMenu(
this, "profile-menu",
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start),
SimpleRowLayout::create()
->setMainAxisAlignment(MainAxisAlignment::Start)
->setGap(5.f),
pfp
);
profileMenu->setContentSize({ 150.f, 50.f });
@ -81,9 +83,9 @@ $register_ids(MenuLayer) {
menu->setContentSize({ winSize.width - 140.f, 65.f });
menu->setLayout(
RowLayout::create()
SimpleRowLayout::create()
->setGap(18.f)
->setCrossAxisOverflow(true)
->setCrossAxisScaling(AxisScaling::Grow)
);
}
@ -110,7 +112,8 @@ $register_ids(MenuLayer) {
menu->setContentSize({ winSize.width - 220.f, 65.f });
menu->setLayout(
RowLayout::create()
SimpleRowLayout::create()
->setGap(5.f)
);
}
@ -136,8 +139,9 @@ $register_ids(MenuLayer) {
auto closeMenu = detachAndCreateMenu(
this,
"close-menu",
RowLayout::create()
->setAxisAlignment(AxisAlignment::Start),
SimpleRowLayout::create()
->setMainAxisAlignment(MainAxisAlignment::Start)
->setGap(5.f),
closeBtn
);
closeMenu->setContentSize({ 200.f, 50.f });
@ -154,9 +158,10 @@ $register_ids(MenuLayer) {
getSizeSafe(moreGamesBtn).width / 2
);
menu->setLayout(
RowLayout::create()
->setAxisAlignment(AxisAlignment::End)
->setAxisReverse(true)
SimpleRowLayout::create()
->setMainAxisAlignment(MainAxisAlignment::Start)
->setMainAxisDirection(AxisDirection::RightToLeft)
->setGap(5.f)
);
}
@ -168,9 +173,10 @@ $register_ids(MenuLayer) {
topRightMenu->setID("top-right-menu");
topRightMenu->setContentSize({ 200.f, 50.f });
topRightMenu->setLayout(
RowLayout::create()
->setAxisReverse(true)
->setAxisAlignment(AxisAlignment::End)
SimpleRowLayout::create()
->setMainAxisDirection(AxisDirection::RightToLeft)
->setMainAxisAlignment(MainAxisAlignment::Start)
->setGap(5.f)
);
this->addChild(topRightMenu);

View file

@ -18,9 +18,19 @@ void GenericContentLayer::setPosition(CCPoint const& pos) {
// all be TableViewCells
CCLayerColor::setPosition(pos);
CCSize scrollLayerSize{};
if (auto parent = this->getParent()) {
scrollLayerSize = parent->getContentSize();
}
for (auto child : CCArrayExt<CCNode*>(m_pChildren)) {
auto y = this->getPositionY() + child->getPositionY();
child->setVisible(!((m_obContentSize.height < y) || (y < -child->getContentSize().height)));
float childY = this->getPositionY() + child->getPositionY();
auto anchor = child->isIgnoreAnchorPointForPosition() ? CCPoint{ 0, 0 } : child->getAnchorPoint();
float childTop = childY + (1.f - anchor.y) * child->getScaledContentSize().height;
float childBottom = childY - child->getAnchorPoint().y * child->getScaledContentSize().height;
bool visible = childTop > 0 && childBottom < scrollLayerSize.height;
child->setVisible(visible);
}
}

View file

@ -290,6 +290,10 @@ void WeakRefPool::check(CCObject* obj) {
}
std::shared_ptr<WeakRefController> WeakRefPool::manage(CCObject* obj) {
if (!obj) {
return std::shared_ptr<WeakRefController>();
}
if (!m_pool.contains(obj)) {
CC_SAFE_RETAIN(obj);
auto controller = std::make_shared<WeakRefController>();

View file

@ -1,5 +1,6 @@
#include <Geode/Loader.hpp>
#include <Geode/modify/MenuLayer.hpp>
#define GEODE_DEFINE_EVENT_EXPORTS
#include <Geode/loader/Dispatch.hpp>
#include <Geode/Bindings.hpp>
#include "main.hpp"
@ -203,4 +204,12 @@ $on_mod(Loaded) {
gl->addChild(label);
return ListenerResult::Propagate;
}, MyDispatchFilter("geode.test/test-garage-open"));
}
Result<int> api::addNumbers(int a, int b) {
return Ok(a + b);
}
Result<int> api::Test::addNumbers(int a, int b) {
return Ok(a + b);
}

View file

@ -16,6 +16,21 @@ using namespace geode::prelude;
#define GEODE_TESTDEP_DLL
#endif
#ifdef MY_MOD_ID
#undef MY_MOD_ID
#endif
#define MY_MOD_ID "geode.testdep"
namespace api {
// Important: The function must be declared inline, and return a geode::Result,
// as it can fail if the api is not available.
inline geode::Result<int> addNumbers(int a, int b) GEODE_EVENT_EXPORT(&addNumbers, (a, b));
struct Test {
geode::Result<int> addNumbers(int a, int b) GEODE_EVENT_EXPORT(&Test::addNumbers, (this, a, b));
};
}
class GEODE_TESTDEP_DLL TestEvent : public Event {
protected:
std::string data;

View file

@ -95,6 +95,22 @@ struct $modify(MenuLayer) {
log::debug("should run second!");
if (GEODE_UNWRAP_IF_OK(val, api::addNumbers(5, 6))) {
log::info("5 + 6 = {}", val);
}
else {
log::error("Failed to API (function)");
}
api::Test test;
if (GEODE_UNWRAP_IF_OK(val, test.addNumbers(5, 6))) {
log::info("5 + 6 = {}", val);
}
else {
log::error("Failed to API (method)");
}
return true;
}
};