geode/loader/include/Geode/utils/cocos.hpp

624 lines
18 KiB
C++
Raw Normal View History

2022-07-30 12:24:03 -04:00
#pragma once
2022-10-30 14:59:20 -04:00
#include "Ref.hpp"
2022-11-19 00:15:01 -05:00
#include "casts.hpp"
2022-10-30 14:59:20 -04:00
2022-07-30 12:24:03 -04:00
#include <Geode/DefaultInclude.hpp>
#include <cocos2d.h>
#include <functional>
#include <type_traits>
namespace geode::cocos {
/**
2022-10-30 14:59:20 -04:00
* Get child at index. Checks bounds. A negative
2022-07-30 12:24:03 -04:00
* index will get the child starting from the end
2022-10-30 14:59:20 -04:00
* @returns Child at index cast to the given type,
2022-07-30 12:24:03 -04:00
* or nullptr if index exceeds bounds
*/
2022-10-30 14:59:20 -04:00
template <class T = cocos2d::CCNode>
static T* getChild(cocos2d::CCNode* x, int i) {
2022-07-30 12:24:03 -04:00
// start from end for negative index
if (i < 0) i = x->getChildrenCount() + i;
// check if backwards index is out of bounds
if (i < 0) return nullptr;
// check if forwards index is out of bounds
if (static_cast<int>(x->getChildrenCount()) <= i) return nullptr;
return static_cast<T*>(x->getChildren()->objectAtIndex(i));
2022-07-30 12:24:03 -04:00
}
/**
2022-10-30 14:59:20 -04:00
* Get nth child that is a given type. Checks bounds.
* @returns Child at index cast to the given type,
2022-07-30 12:24:03 -04:00
* or nullptr if index exceeds bounds
*/
2022-10-30 14:59:20 -04:00
template <class Type = cocos2d::CCNode>
static Type* getChildOfType(cocos2d::CCNode* node, size_t index) {
2022-10-30 14:59:20 -04:00
size_t indexCounter = 0;
for (size_t i = 0; i < node->getChildrenCount(); ++i) {
auto obj = cast::typeinfo_cast<Type*>(node->getChildren()->objectAtIndex(i));
if (obj != nullptr) {
if (indexCounter == index) {
return obj;
}
++indexCounter;
}
}
return nullptr;
2022-07-30 12:24:03 -04:00
}
/**
2022-10-30 14:59:20 -04:00
* Return a node, or create a default one if it's
* nullptr. Syntactic sugar function
*/
2022-10-30 14:59:20 -04:00
template <class T, class... Args>
static T* nodeOrDefault(T* node, Args... args) {
return node ? node : T::create(args...);
}
2022-10-30 14:59:20 -04:00
template <class T = cocos2d::CCNode>
struct SafeCreate final {
T* result;
2022-10-30 14:59:20 -04:00
SafeCreate<T>& with(T* node) {
result = node;
return *this;
}
2022-10-30 14:59:20 -04:00
template <class... Args>
SafeCreate<T>& make(Args... args) {
result = T::create(args...);
return *this;
}
2022-10-30 14:59:20 -04:00
// convenience for CCSprite
2022-10-30 14:59:20 -04:00
template <class... Args>
SafeCreate<T>& makeWithFrame(Args... args) {
result = T::createWithSpriteFrameName(args...);
return *this;
}
2022-10-30 14:59:20 -04:00
template <class... Args>
SafeCreate<T>& makeUsing(T* (*func)(Args...), Args... args) {
2022-10-01 04:20:11 -04:00
result = func(args...);
return *this;
}
2022-10-30 14:59:20 -04:00
template <class O = T, class... Args>
T* orMakeUsing(O* (*func)(Args...), Args... args) {
if (result) return result;
return func(args...);
}
2022-10-30 14:59:20 -04:00
template <class O = T, class... Args>
T* orMake(Args... args) {
if (result) return result;
return O::create(args...);
}
2022-10-30 14:59:20 -04:00
template <class O = T, class... Args>
T* orMakeWithFrame(Args... args) {
if (result) return result;
return O::createWithSpriteFrameName(args...);
}
};
2022-07-30 12:24:03 -04:00
/**
2022-10-30 14:59:20 -04:00
* Get bounds for a set of nodes. Based on content
2022-07-30 12:24:03 -04:00
* size
* @param nodes Nodes to calculate coverage of
2022-10-30 14:59:20 -04:00
* @returns Rectangle fitting all nodes. Origin
2022-07-30 12:24:03 -04:00
* will be <= 0 and size will be >= 0
*/
GEODE_DLL cocos2d::CCRect calculateNodeCoverage(std::vector<cocos2d::CCNode*> const& nodes);
/**
2022-10-30 14:59:20 -04:00
* Get bounds for a set of nodes. Based on content
2022-07-30 12:24:03 -04:00
* size
* @param nodes Nodes to calculate coverage of
2022-10-30 14:59:20 -04:00
* @returns Rectangle fitting all nodes. Origin
2022-07-30 12:24:03 -04:00
* will be <= 0 and size will be >= 0
*/
GEODE_DLL cocos2d::CCRect calculateNodeCoverage(cocos2d::CCArray* nodes);
/**
2022-10-30 14:59:20 -04:00
* Get bounds for a set of nodes. Based on content
2022-07-30 12:24:03 -04:00
* size
* @param parent Parent whose children to calculate
* coverage of
* @returns Rectangle fitting all the parent's children.
* Origin will be <= 0 and size will be >= 0
*/
GEODE_DLL cocos2d::CCRect calculateChildCoverage(cocos2d::CCNode* parent);
/**
2022-10-30 14:59:20 -04:00
* Create a CCScene from a layer and switch to it with the default fade
* transition
* @param layer Layer to create a scene from
* @returns Created scene (not the fade transition)
*/
GEODE_DLL cocos2d::CCScene* switchToScene(cocos2d::CCLayer* layer);
2022-07-30 12:24:03 -04:00
/**
* Rescale node to fit inside given size
* @param node Node to rescale
* @param size Size to fit inside
* @param def Default size
* @param min Minimum size
*/
GEODE_DLL void limitNodeSize(
2022-10-30 14:59:20 -04:00
cocos2d::CCNode* node, cocos2d::CCSize const& size, float def, float min
2022-07-30 12:24:03 -04:00
);
/**
2022-10-30 14:59:20 -04:00
* Checks if a node is visible (recursively
2022-07-30 12:24:03 -04:00
* checks parent visibility)
* @param node Node to check if visible
2022-10-30 14:59:20 -04:00
* @returns True if node is visibile. Does
2022-07-30 12:24:03 -04:00
* not take into account if node is off-screen
*/
GEODE_DLL bool nodeIsVisible(cocos2d::CCNode* node);
/**
* Gets a node by tag by traversing
* children recursively
2022-10-30 14:59:20 -04:00
*
2022-07-30 12:24:03 -04:00
* @param node Parent node
* @param tag Target tag
* @return Child node with specified tag, or
* null if there is none
*/
GEODE_DLL cocos2d::CCNode* getChildByTagRecursive(cocos2d::CCNode* node, int tag);
/**
* Get first node that conforms to the predicate
* by traversing children recursively
*
* @param node Parent node
* @param predicate Predicate used to evaluate nodes
* @return Child node if one is found, or null if
* there is none
*/
template <class Type = cocos2d::CCNode>
Type* findFirstChildRecursive(cocos2d::CCNode* node, std::function<bool(Type*)> predicate) {
2022-11-19 00:18:37 -05:00
if (cast::safe_cast<Type*>(node) && predicate(static_cast<Type*>(node)))
2022-11-19 00:15:01 -05:00
return node;
auto children = node->getChildren();
if (!children) return nullptr;
for (int i = 0; i < children->count(); ++i) {
2022-11-19 00:15:01 -05:00
auto newParent = static_cast<cocos2d::CCNode*>(children->objectAtIndex(i));
auto child = findFirstChildRecursive(newParent, predicate);
if (child)
return child;
}
return nullptr;
}
2022-07-30 12:24:03 -04:00
/**
2022-10-30 14:59:20 -04:00
* Checks if a given file exists in CCFileUtils
* search paths.
2022-07-30 12:24:03 -04:00
* @param filename File to check
* @returns True if file exists
* @example if (fileExistsInSearchPaths("mySprite.png"_spr)) {
* CCSprite::create("mySprite.png"_spr);
* } else {
* CCSprite::create("fallback.png");
* }
*/
2022-10-30 14:59:20 -04:00
GEODE_DLL bool fileExistsInSearchPaths(char const* filename);
2022-07-30 12:24:03 -04:00
template <typename T>
struct CCArrayIterator {
2022-07-30 12:24:03 -04:00
public:
CCArrayIterator(T* p) : m_ptr(p) {}
2022-10-30 14:59:20 -04:00
2022-07-30 12:24:03 -04:00
T* m_ptr;
auto& operator*() {
return *m_ptr;
}
2022-10-30 14:59:20 -04:00
auto& operator*() const {
return *m_ptr;
}
2022-10-30 14:59:20 -04:00
auto operator->() {
return m_ptr;
}
auto operator->() const {
return m_ptr;
}
2022-07-30 12:24:03 -04:00
auto& operator++() {
++m_ptr;
return *this;
}
auto& operator--() {
--m_ptr;
return *this;
}
auto& operator+=(size_t val) {
m_ptr += val;
return *this;
}
auto& operator-=(size_t val) {
m_ptr -= val;
return *this;
}
auto operator+(size_t val) const {
return CCArrayIterator<T>(m_ptr + val);
}
auto operator-(size_t val) const {
return CCArrayIterator<T>(m_ptr - val);
}
auto operator-(CCArrayIterator<T> const& other) const {
return m_ptr - other.m_ptr;
}
bool operator<(CCArrayIterator<T> const& other) const {
return m_ptr < other.m_ptr;
}
bool operator>(CCArrayIterator<T> const& other) const {
return m_ptr > other.m_ptr;
}
bool operator<=(CCArrayIterator<T> const& other) const {
return m_ptr <= other.m_ptr;
}
bool operator>=(CCArrayIterator<T> const& other) const {
return m_ptr >= other.m_ptr;
}
bool operator==(CCArrayIterator<T> const& other) const {
return m_ptr == other.m_ptr;
}
2022-10-30 14:59:20 -04:00
bool operator!=(CCArrayIterator<T> const& other) const {
return m_ptr != other.m_ptr;
}
};
2022-10-13 05:56:23 -04:00
}
namespace std {
template <typename T>
2022-10-13 05:56:23 -04:00
struct iterator_traits<geode::cocos::CCArrayIterator<T>> {
using difference_type = ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
2022-10-30 14:59:20 -04:00
using iterator_category =
std::random_access_iterator_tag; // its random access but im too lazy to implement it
};
2022-10-13 05:56:23 -04:00
}
namespace geode::cocos {
struct GEODE_DLL CCArrayInserter {
public:
CCArrayInserter(cocos2d::CCArray* p) : m_array(p) {}
2022-10-30 14:59:20 -04:00
cocos2d::CCArray* m_array;
auto& operator=(cocos2d::CCObject* value) {
m_array->addObject(value);
return *this;
}
auto& operator*() {
return *this;
}
auto& operator++() {
return *this;
}
2022-07-30 12:24:03 -04:00
};
template <typename _Type>
class CCArrayExt {
protected:
2022-10-14 10:02:53 -04:00
Ref<cocos2d::CCArray> m_arr;
2022-07-30 12:24:03 -04:00
using T = std::remove_pointer_t<_Type>;
2022-10-30 14:59:20 -04:00
public:
2022-10-14 10:02:53 -04:00
CCArrayExt() : m_arr(cocos2d::CCArray::create()) {}
2022-10-30 14:59:20 -04:00
2022-10-14 10:02:53 -04:00
CCArrayExt(cocos2d::CCArray* arr) : m_arr(arr) {}
2022-10-30 14:59:20 -04:00
2022-10-14 10:02:53 -04:00
CCArrayExt(CCArrayExt const& a) : m_arr(a.m_arr) {}
2022-10-30 14:59:20 -04:00
2022-07-30 12:24:03 -04:00
CCArrayExt(CCArrayExt&& a) : m_arr(a.m_arr) {
a.m_arr = nullptr;
}
2022-10-30 14:59:20 -04:00
2022-10-14 10:02:53 -04:00
~CCArrayExt() {}
2022-07-30 12:24:03 -04:00
auto begin() {
if (!m_arr) {
return CCArrayIterator<T*>(nullptr);
}
2022-07-30 12:24:03 -04:00
return CCArrayIterator<T*>(reinterpret_cast<T**>(m_arr->data->arr));
}
2022-10-30 14:59:20 -04:00
2022-07-30 12:24:03 -04:00
auto end() {
if (!m_arr) {
return CCArrayIterator<T*>(nullptr);
}
2022-07-30 12:24:03 -04:00
return CCArrayIterator<T*>(reinterpret_cast<T**>(m_arr->data->arr) + m_arr->count());
}
size_t size() const {
return m_arr ? m_arr->count() : 0;
2022-07-30 12:24:03 -04:00
}
2022-10-30 14:59:20 -04:00
2022-07-30 12:24:03 -04:00
T operator[](size_t index) {
return static_cast<T*>(m_arr->objectAtIndex(index));
2022-07-30 12:24:03 -04:00
}
2022-10-30 14:59:20 -04:00
2022-07-30 12:24:03 -04:00
void push_back(T* item) {
m_arr->addObject(item);
}
2022-10-30 14:59:20 -04:00
T* pop_back() {
2022-07-30 12:24:03 -04:00
T ret = m_arr->lastObject();
m_arr->removeLastObject();
return ret;
}
2022-10-30 14:59:20 -04:00
2022-07-30 12:24:03 -04:00
cocos2d::CCArray* inner() {
return m_arr;
}
};
template <typename K, typename T>
struct CCDictIterator {
public:
CCDictIterator(cocos2d::CCDictElement* p) : m_ptr(p) {}
2022-10-30 14:59:20 -04:00
2022-07-30 12:24:03 -04:00
cocos2d::CCDictElement* m_ptr;
std::pair<K, T> operator*() {
if constexpr (std::is_same<K, std::string>::value) {
return { m_ptr->getStrKey(), static_cast<T>(m_ptr->getObject()) };
2022-10-30 14:59:20 -04:00
}
else {
return { m_ptr->getIntKey(), static_cast<T>(m_ptr->getObject()) };
2022-07-30 12:24:03 -04:00
}
}
auto& operator++() {
m_ptr = static_cast<decltype(m_ptr)>(m_ptr->hh.next);
2022-07-30 12:24:03 -04:00
return *this;
}
2022-10-30 14:59:20 -04:00
friend bool operator==(CCDictIterator<K, T> const& a, CCDictIterator<K, T> const& b) {
return a.m_ptr == b.m_ptr;
};
friend bool operator!=(CCDictIterator<K, T> const& a, CCDictIterator<K, T> const& b) {
return a.m_ptr != b.m_ptr;
};
bool operator!=(int b) {
return m_ptr != nullptr;
}
2022-07-30 12:24:03 -04:00
};
template <typename K, typename T>
struct CCDictEntry {
K m_key;
cocos2d::CCDictionary* m_dict;
CCDictEntry(K key, cocos2d::CCDictionary* dict) : m_key(key), m_dict(dict) {}
T operator->() {
return static_cast<T>(m_dict->objectForKey(m_key));
2022-07-30 12:24:03 -04:00
}
operator T() {
return static_cast<T>(m_dict->objectForKey(m_key));
2022-07-30 12:24:03 -04:00
}
CCDictEntry& operator=(T f) {
m_dict->setObject(f, m_key);
return *this;
}
};
template <typename K, typename T>
struct CCDictionaryExt {
2022-10-30 14:59:20 -04:00
protected:
2022-07-30 12:24:03 -04:00
cocos2d::CCDictionary* m_dict;
2022-10-30 14:59:20 -04:00
2022-07-30 12:24:03 -04:00
public:
CCDictionaryExt() : m_dict(cocos2d::CCDictionary::create()) {
m_dict->retain();
2022-10-30 14:59:20 -04:00
}
2022-07-30 12:24:03 -04:00
CCDictionaryExt(cocos2d::CCDictionary* dict) : m_dict(dict) {
m_dict->retain();
}
2022-10-30 14:59:20 -04:00
2022-07-30 12:24:03 -04:00
CCDictionaryExt(CCDictionaryExt const& d) : m_dict(d.m_dict) {
m_dict->retain();
}
2022-10-30 14:59:20 -04:00
2022-07-30 12:24:03 -04:00
CCDictionaryExt(CCDictionaryExt&& d) : m_dict(d.m_dict) {
d.m_dict = nullptr;
}
2022-10-30 14:59:20 -04:00
2022-07-30 12:24:03 -04:00
~CCDictionaryExt() {
2022-10-30 14:59:20 -04:00
if (m_dict) m_dict->release();
2022-07-30 12:24:03 -04:00
}
CCDictionaryExt const& operator=(cocos2d::CCDictionary* d) {
m_dict->release();
m_dict = d;
m_dict->retain();
}
auto begin() {
return CCDictIterator<K, T*>(m_dict->m_pElements);
}
2022-10-30 14:59:20 -04:00
2022-07-30 12:24:03 -04:00
// do not use this
auto end() {
return nullptr;
}
2022-10-30 14:59:20 -04:00
size_t size() {
return m_dict->count();
}
2022-07-30 12:24:03 -04:00
auto operator[](K key) {
auto ret = static_cast<T*>(m_dict->objectForKey(key));
2022-10-30 14:59:20 -04:00
if (!ret) m_dict->setObject(cocos2d::CCNode::create(), key);
2022-07-30 12:24:03 -04:00
return CCDictEntry<K, T*>(key, m_dict);
}
2022-10-30 14:59:20 -04:00
2022-07-30 12:24:03 -04:00
size_t count(K key) {
return m_dict->allKeys(key)->count();
}
};
2022-10-30 14:59:20 -04:00
// namespace for storing implementation stuff for
// inline member functions
namespace {
2022-10-30 14:59:20 -04:00
// class that holds the lambda (probably should've just used
// std::function but hey, this one's heap-free!)
2022-10-30 14:59:20 -04:00
template <class F, class Ret, class... Args>
struct LambdaHolder {
bool m_assigned = false;
2022-10-30 14:59:20 -04:00
// lambdas don't implement operator= so we
// gotta do this wacky union stuff
union {
F m_lambda;
};
2022-10-30 14:59:20 -04:00
LambdaHolder() {}
2022-10-30 14:59:20 -04:00
~LambdaHolder() {
if (m_assigned) {
m_lambda.~F();
}
}
2022-10-30 14:59:20 -04:00
LambdaHolder(F&& func) {
this->assign(std::forward<F>(func));
}
2022-10-30 14:59:20 -04:00
Ret operator()(Args... args) {
if (m_assigned) {
return m_lambda(std::forward<Args>(args)...);
2022-10-30 14:59:20 -04:00
}
else {
return Ret();
}
}
2022-10-30 14:59:20 -04:00
void assign(F&& func) {
if (m_assigned) {
m_lambda.~F();
}
new (&m_lambda) F(func);
m_assigned = true;
}
};
// Extract parameters and return type from a lambda
2022-10-30 14:59:20 -04:00
template <class Func>
struct ExtractLambda : public ExtractLambda<decltype(&Func::operator())> {};
2022-10-30 14:59:20 -04:00
template <class C, class R, class... Args>
struct ExtractLambda<R (C::*)(Args...) const> {
using Ret = R;
using Params = std::tuple<Args...>;
};
// Class for storing the member function
2022-10-30 14:59:20 -04:00
template <class Base, class Func, class Args>
struct InlineMemberFunction;
2022-10-30 14:59:20 -04:00
template <class Base, class Func, class... Args>
struct InlineMemberFunction<Base, Func, std::tuple<Args...>> : public Base {
using Ret = typename ExtractLambda<Func>::Ret;
2022-10-30 14:59:20 -04:00
using Selector = Ret (Base::*)(Args...);
using Holder = LambdaHolder<Func, Ret, Args...>;
static inline Holder s_selector {};
2022-10-30 14:59:20 -04:00
Ret selector(Args... args) {
return s_selector(std::forward<Args>(args)...);
}
2022-10-30 14:59:20 -04:00
static Selector get(Func&& function) {
s_selector.assign(std::move(function));
return static_cast<Selector>(&InlineMemberFunction::selector);
}
};
}
/**
2022-10-30 14:59:20 -04:00
* Wrap a lambda into a member function pointer. Useful for creating
* callbacks that have to be members of a class without having to deal
2022-10-30 14:59:20 -04:00
* with all of the boilerplate associated with defining a new class
* member function.
2022-10-30 14:59:20 -04:00
*
* Do note that due to implementation problems, captures may have
* unexpected side-effects. In practice, lambda member functions with
* captures do not work properly in loops. If you assign the same
* member lambda to multiple different targets, they will share the
* same captured values.
*/
2022-10-30 14:59:20 -04:00
template <class Base, class Func>
[[deprecated(
"Due to too many implementation problems, "
"makeMemberFunction will be removed in the future."
2022-10-30 14:59:20 -04:00
)]] static auto
makeMemberFunction(Func&& function) {
return InlineMemberFunction<Base, Func, typename ExtractLambda<Func>::Params>::get(
std::move(function)
);
}
/**
2022-10-30 14:59:20 -04:00
* Create a SEL_MenuHandler out of a lambda with optional captures. Useful
* for adding callbacks to CCMenuItemSpriteExtras without needing to add
* the callback as a member to a class. Use the GEODE_MENU_SELECTOR class
* for even more concise code.
2022-10-30 14:59:20 -04:00
*
* Do note that due to implementation problems, captures may have
* unexpected side-effects. In practice, **you should not expect to be able
* to pass any more information than you can pass to a normal menu selector
* through captures**. If you assign the same member lambda to multiple
* different targets, they will share the same captured values.
*/
2022-10-30 14:59:20 -04:00
template <class Func>
[[deprecated(
"Due to too many implementation problems, "
"makeMenuSelector will be removed in the future."
2022-10-30 14:59:20 -04:00
)]] static cocos2d::SEL_MenuHandler
makeMenuSelector(Func&& selector) {
return reinterpret_cast<cocos2d::SEL_MenuHandler>(
makeMemberFunction<cocos2d::CCObject, Func>(std::move(selector))
);
}
2022-10-30 14:59:20 -04:00
#define GEODE_MENU_SELECTOR(senderArg, ...) \
makeMenuSelector([this](senderArg) { \
__VA_ARGS__; \
})
2022-07-30 12:24:03 -04:00
}