diff --git a/loader/include/Geode/cocos/base_nodes/Layout.hpp b/loader/include/Geode/cocos/base_nodes/Layout.hpp index c78cb76a..7d8ab0f6 100644 --- a/loader/include/Geode/cocos/base_nodes/Layout.hpp +++ b/loader/include/Geode/cocos/base_nodes/Layout.hpp @@ -23,7 +23,7 @@ class CCNode; */ class GEODE_DLL Layout : public CCObject { protected: - CCArray* getNodesToPosition(CCNode* forNode); + CCArray* getNodesToPosition(CCNode* forNode) const; bool m_ignoreInvisibleChildren = false; @@ -37,6 +37,11 @@ public: */ virtual void apply(CCNode* on) = 0; + /** + * Get how much space this layout would like to take up for a given target + */ + virtual CCSize getSizeHint(CCNode* on) const = 0; + void ignoreInvisibleChildren(bool ignore); bool isIgnoreInvisibleChildren() const; @@ -262,6 +267,7 @@ public: static AxisLayout* create(Axis axis = Axis::Row); void apply(CCNode* on) override; + CCSize getSizeHint(CCNode* on) const override; Axis getAxis() const; AxisAlignment getAxisAlignment() const; diff --git a/loader/include/Geode/cocos/base_nodes/SpacerNode.hpp b/loader/include/Geode/cocos/base_nodes/SpacerNode.hpp new file mode 100644 index 00000000..2e153611 --- /dev/null +++ b/loader/include/Geode/cocos/base_nodes/SpacerNode.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "CCNode.h" + +NS_CC_BEGIN + +#pragma warning(push) +#pragma warning(disable: 4275) + +/** + * A node for controlling spacing in Layouts. When a Layout is applied, if + * space is left over, the remaining space is divided among all SpacerNodes in + * the Layout. The space each node gets is the proprotion between its growth + * factor and the sum of all the SpacerNodes' growth factors in the Layout + * @example + * node->addChild(SpacerNode::create(1)); + * node->addChild(SpacerNode::create(2)); + * node->addChild(SpacerNode::create(1)); + * node->updateLayout(); + * // Total SpacerNode growth sum is 1 + 2 + 1 = 4 + * // So s1 and s3 get 1/4 of the remaining space and s2 gets 2/4 + * @note If you want to specify a minimum width for a SpacerNode, add + * AxisLayoutOptions for it and use setLength + */ +class GEODE_DLL SpacerNode : public CCNode { +protected: + size_t m_grow; + + bool init(size_t grow); + +public: + /** + * Create a new spacer node. When the layout is applied, + * if there is space left over the remaining space is distributed among + * all spacer nodes in proportion to the sum of all the spacers' grow + * factors (akin to CSS flew grow) + * @param grow The grow factor for this node. Default is 1 + */ + static SpacerNode* create(size_t grow = 1); + + /** + * Set the grow factor for this spacer node. When the layout is applied, + * if there is space left over the remaining space is distributed among + * all spacer nodes in proportion to the sum of all the spacers' grow + * factors (akin to CSS flew grow) + * @param grow The new grow factor for this node. Default is 1 + * @note Make sure to call updateLayout on the spacer's parent afterwards + */ + void setGrow(size_t grow); + + /** + * Get the grow factor for this spacer node + */ + size_t getGrow() const; +}; + +#pragma warning(pop) + +NS_CC_END diff --git a/loader/include/Geode/cocos/include/cocos2d.h b/loader/include/Geode/cocos/include/cocos2d.h index b4303995..f984a172 100644 --- a/loader/include/Geode/cocos/include/cocos2d.h +++ b/loader/include/Geode/cocos/include/cocos2d.h @@ -59,6 +59,7 @@ THE SOFTWARE. // base_nodes #include "../base_nodes/CCNode.h" #include "../base_nodes/CCAtlasNode.h" +#include "../base_nodes/SpacerNode.hpp" // cocoa #include "../cocoa/CCAffineTransform.h" diff --git a/loader/include/Geode/loader/Dispatch.hpp b/loader/include/Geode/loader/Dispatch.hpp index dec81ba5..9f8a4c39 100644 --- a/loader/include/Geode/loader/Dispatch.hpp +++ b/loader/include/Geode/loader/Dispatch.hpp @@ -9,42 +9,41 @@ namespace geode { // Mod interoperability - // todo: update to new event system + template + class DispatchEvent : public Event { + protected: + std::string m_id; + std::tuple m_args; + + public: + DispatchEvent(std::string const& id, Args&&... args) + : m_id(id), m_args(std::make_tuple(args...)) {} + + std::tuple getArgs() const { + return m_args; + } - // template - // class DispatchEvent : public Event { - // std::string m_selector; - // std::tuple m_args; + std::string getID() const { + return m_id; + } + }; - // public: - // DispatchEvent(std::string const& name, Args... args) : - // m_selector(name), m_args(std::make_tuple(args...)) {} + template + class DispatchFilter : public EventFilter> { + protected: + std::string m_id; - // std::string const& selector() { - // return m_selector; - // } - // }; + public: + using Ev = DispatchEvent; + using Callback = ListenerResult(Args...); - // template - // class DispatchHandler : public EventHandler> { - // std::string m_selector; - // utils::MiniFunction m_callback; + ListenerResult handle(utils::MiniFunction fn, Ev* event) { + if (event->getID() == m_id) { + return std::apply(fn, event->getArgs()); + } + return ListenerResult::Propagate; + } - // DispatchHandler(std::string const& name, utils::MiniFunction callback) : - // m_selector(name), m_callback(callback) {} - - // public: - // bool handle(DispatchEvent* ev) { - // if (ev->name() == m_selector) { - // std::apply(m_callback, ev->m_args); - // } - // return true; - // } - - // static DispatchHandler* create( - // std::string const& name, utils::MiniFunction callback - // ) { - // return new DispatchHandler(name, callback); - // } - // }; + DispatchFilter(std::string const& id) : m_id(id) {} + }; } \ No newline at end of file diff --git a/loader/src/cocos2d-ext/Layout.cpp b/loader/src/cocos2d-ext/Layout.cpp index efa61493..f0358121 100644 --- a/loader/src/cocos2d-ext/Layout.cpp +++ b/loader/src/cocos2d-ext/Layout.cpp @@ -50,7 +50,7 @@ bool CCNode::hasAncestor(CCNode* ancestor) { return false; } -CCArray* Layout::getNodesToPosition(CCNode* on) { +CCArray* Layout::getNodesToPosition(CCNode* on) const { auto arr = CCArray::create(); for (auto child : CCArrayExt(on->getChildren())) { if (!m_ignoreInvisibleChildren || child->isVisible()) { @@ -176,6 +176,32 @@ struct AxisLayout::Row : public CCObject { { this->autorelease(); } + + void accountSpacers(Axis axis, float availableLength) { + std::vector spacers; + for (auto& node : CCArrayExt(nodes)) { + if (auto spacer = typeinfo_cast(node)) { + spacers.push_back(spacer); + } + } + if (spacers.size()) { + auto unusedSpace = availableLength - this->axisLength; + size_t sum = 0; + for (auto& spacer : spacers) { + sum += spacer->getGrow(); + } + for (auto& spacer : spacers) { + auto size = unusedSpace * spacer->getGrow() / static_cast(sum); + if (axis == Axis::Row) { + spacer->setContentSize({ size, this->crossLength }); + } + else { + spacer->setContentSize({ this->crossLength, size }); + } + } + this->axisLength = availableLength; + } + } }; struct AxisPosition { @@ -195,6 +221,9 @@ static AxisPosition nodeAxis(CCNode* node, Axis axis, float scale) { if (auto toggle = typeinfo_cast(node)) { scaledSize = toggle->m_offButton->getScaledContentSize(); } + if (auto spacer = typeinfo_cast(node)) { + scaledSize = CCSizeZero; + } auto anchor = node->getAnchorPoint(); if (axis == Axis::Row) { return AxisPosition { @@ -611,6 +640,8 @@ void AxisLayout::tryFitLayout( float rowEvenSpace = available.crossLength / rows->count(); for (auto row : CCArrayExt(rows)) { + row->accountSpacers(m_axis, available.axisLength); + if (m_crossAlignment == AxisAlignment::Even) { rowCrossPos -= rowEvenSpace / 2 + row->crossLength / 2; } @@ -756,6 +787,24 @@ void AxisLayout::apply(CCNode* on) { ); } +CCSize AxisLayout::getSizeHint(CCNode* on) const { + // Ideal is single row / column with no scaling + auto nodes = getNodesToPosition(on); + float length = 0.f; + float cross = 0.f; + for (auto& node : CCArrayExt(nodes)) { + auto axis = nodeAxis(node, m_axis, 1.f); + length += axis.axisLength; + cross += axis.crossLength; + } + if (m_axis == Axis::Row) { + return { length, cross }; + } + else { + return { cross, length }; + } +} + AxisLayout::AxisLayout(Axis axis) : m_axis(axis) {} Axis AxisLayout::getAxis() const { @@ -969,3 +1018,30 @@ AxisLayoutOptions* AxisLayoutOptions::setScalePriority(int priority) { m_scalePriority = priority; return this; } + +bool SpacerNode::init(size_t grow) { + if (!CCNode::init()) + return false; + + m_grow = grow; + + return true; +} + +SpacerNode* SpacerNode::create(size_t grow) { + auto ret = new SpacerNode; + if (ret && ret->init(grow)) { + ret->autorelease(); + return ret; + } + CC_SAFE_DELETE(ret); + return nullptr; +} + +void SpacerNode::setGrow(size_t grow) { + m_grow = grow; +} + +size_t SpacerNode::getGrow() const { + return m_grow; +}