mirror of
https://github.com/geode-sdk/geode.git
synced 2025-04-15 14:34:50 -04:00
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
261941fc6b
18 changed files with 1372 additions and 40 deletions
CHANGELOG.mdCMakeLists.txt
loader
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -233,6 +233,7 @@ class CC_DLL CCRepeatForever : public CCActionInterval
|
|||
{
|
||||
GEODE_FRIEND_MODIFY
|
||||
public:
|
||||
GEODE_CUSTOM_CONSTRUCTOR_COCOS(CCRepeatForever, CCActionInterval)
|
||||
/**
|
||||
* @js ctor
|
||||
*/
|
||||
|
|
|
@ -19,6 +19,8 @@ public:
|
|||
cocos2d::CCDictionary* addDictDS(const char* dict);
|
||||
|
||||
void clearCache();
|
||||
public:
|
||||
cocos2d::CCDictionary* m_pDictCache;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -98,6 +98,9 @@ public:
|
|||
* @lua NA
|
||||
*/
|
||||
~CCSpriteFrame(void);
|
||||
inline CCSpriteFrame() {}
|
||||
GEODE_CUSTOM_CONSTRUCTOR_COCOS(CCSpriteFrame, CCObject);
|
||||
|
||||
/**
|
||||
* @js NA
|
||||
* @lua NA
|
||||
|
|
|
@ -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
|
239
loader/include/Geode/ui/SimpleAxisLayout.hpp
Normal file
239
loader/include/Geode/ui/SimpleAxisLayout.hpp
Normal 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;
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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": {
|
||||
|
|
871
loader/src/cocos2d-ext/SimpleAxisLayout.cpp
Normal file
871
loader/src/cocos2d-ext/SimpleAxisLayout.cpp
Normal 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;
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue