From 50cf18bcc40eacfc556168f091642b5789dc7547 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Mon, 6 Feb 2023 21:36:08 +0200 Subject: [PATCH] attempting to add grid layout impl --- .../include/Geode/cocos/base_nodes/CCNode.h | 15 + .../include/Geode/cocos/base_nodes/Layout.hpp | 148 +++---- loader/include/Geode/cocos/cocoa/CCArray.h | 19 +- loader/src/cocos2d-ext/CCArray.cpp | 6 + loader/src/cocos2d-ext/Layout.cpp | 400 ++++++++++-------- loader/src/hooks/GeodeNodeMetadata.cpp | 12 + loader/src/ids/CreatorLayer.cpp | 38 +- loader/src/ids/EditLevelLayer.cpp | 11 +- loader/src/ids/EditorUI.cpp | 56 ++- loader/src/ids/LevelBrowserLayer.cpp | 32 +- loader/src/ids/LevelInfoLayer.cpp | 10 +- loader/src/ids/LevelSettingsLayer.cpp | 9 +- loader/src/ids/MenuLayer.cpp | 38 +- 13 files changed, 476 insertions(+), 318 deletions(-) diff --git a/loader/include/Geode/cocos/base_nodes/CCNode.h b/loader/include/Geode/cocos/base_nodes/CCNode.h index cc091cbb..4253b265 100644 --- a/loader/include/Geode/cocos/base_nodes/CCNode.h +++ b/loader/include/Geode/cocos/base_nodes/CCNode.h @@ -941,6 +941,21 @@ public: * @note Geode addition */ GEODE_DLL void updateLayout(); + /** + * Set the layout options for this node. Layout options can be used to + * control how this node is positioned in its parent's Layout, for example + * setting the grow size for a flex layout + * @param options The layout options + * @param apply Whether to update the layout of the parent node + * @note Geode addition + */ + GEODE_DLL void setLayoutOptions(LayoutOptions* options, bool apply = true); + /** + * Get the layout options for this node + * @returns The current layout options, or nullptr if no options are set + * @note Geode addition + */ + GEODE_DLL LayoutOptions* getLayoutOptions(); /** * Give a hint to the current Layout about where this node should be diff --git a/loader/include/Geode/cocos/base_nodes/Layout.hpp b/loader/include/Geode/cocos/base_nodes/Layout.hpp index db1a3376..28bb724d 100644 --- a/loader/include/Geode/cocos/base_nodes/Layout.hpp +++ b/loader/include/Geode/cocos/base_nodes/Layout.hpp @@ -32,6 +32,13 @@ public: * have their PositionHint marked as absolute */ virtual void apply(CCNode* on) = 0; + + virtual ~Layout() = default; +}; + +class GEODE_DLL LayoutOptions { +public: + virtual ~LayoutOptions() = default; }; /** @@ -47,31 +54,49 @@ enum class PositionHint { Absolute, }; +enum class Axis { + Row, + Column, +}; + /** * Specifies the alignment of something */ -enum class Alignment { - Begin, +enum class AxisAlignment { + // 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, }; +/** + * Layout for arranging nodes along an axis. Used to implement row, column, and + * grid layouts + */ class GEODE_DLL AxisLayout : public Layout { -public: - enum Axis : bool { - Row, - Column, - }; - protected: Axis m_axis; - Alignment m_axisAlignment = Alignment::Center; - Alignment m_crossAlignment = Alignment::Center; + AxisAlignment m_axisAlignment = AxisAlignment::Center; + AxisAlignment m_crossAlignment = AxisAlignment::Center; float m_gap = 5.f; bool m_autoScale = true; - bool m_shrinkCrossAxis = true; - bool m_reverse = false; - bool m_fitInside = false; + bool m_axisReverse = false; + bool m_crossReverse = false; + bool m_allowCrossAxisOverflow = true; + bool m_growCrossAxis = false; + + struct Row; + + Row* fitInRow(CCNode* on, CCArray* nodes, float scale, float squish) const; + void tryFitLayout(CCNode* on, CCArray* nodes, float scale, float squish) const; AxisLayout(Axis); @@ -79,45 +104,44 @@ public: void apply(CCNode* on) override; /** - * Sets where to align nodes on the Y-axis. If nullopt, the - * nodes' Y-position will not be affected, and the height of the node this - * layout applies to isn't altered. If an alignment is given, the height - * of the node this layout applies to is shrunk to fit the height of the - * nodes and no more. Any nodes that don't fit inside this space are - * aligned based on the value - * @param align Value - * @returns The same RowLayout this was applied on + * Sets where to align the target node's children on the cross-axis (Y for + * Row, X for Column) */ - AxisLayout* setCrossAxisAlignment(Alignment align); - AxisLayout* setAxisAlignment(Alignment align); + AxisLayout* setCrossAxisAlignment(AxisAlignment align); + /** + * Sets where to align the target node's children on the main axis (X for + * Row, Y for Column) + */ + AxisLayout* setAxisAlignment(AxisAlignment align); /** * The spacing between the children of the node this layout applies to. - * Measured as the space between their edges, not centres + * Measured as the space between their edges, not centres. Does not apply + * on the main / cross axis if their alignment is AxisAlignment::Even */ AxisLayout* setGap(float gap); /** * Whether to reverse the direction of the children in this layout or not */ - AxisLayout* setReverse(bool reverse); + AxisLayout* setAxisReverse(bool reverse); /** - * If a value is provided, then the node this layout applies to may be - * automatically rescaled to fit its contents better. By default the value - * is nullopt, which means that the layout doesn't affect the node's scale - * in any way, and any nodes that might overflow will be squished using - * other methods. If the value is set, the layout assumes that the scaled - * content size of the target node is what the content should be fit - * inside of, and scales to fit that space. If the value is nullopt, the - * unscaled content size is used instead + * Whether to reverse the direction of the rows on the cross-axis or not + */ + AxisLayout* setCrossAxisReverse(bool reverse); + /** + * If enabled, then the layout may scale the target's children if they are + * about to overflow. Assumes that all the childrens' intended scale is 1 */ AxisLayout* setAutoScale(bool enable); /** - * If true, the children of the node this layout is applied to will be - * contained entirely within the bounds of the node's content size. If - * false, the children's positions will be within the bounds, but they may - * visually overflow depending on their anchor point + * If true, if the main axis overflows extra nodes will be placed on new + * rows/columns on the cross-axis */ - AxisLayout* setFitInside(bool fit); - AxisLayout* setShrinkCrossAxis(bool shrink); + AxisLayout* setGrowCrossAxis(bool expand); + /** + * If true, the cross-axis content size of the target node will be + * automatically adjusted to fit the children + */ + AxisLayout* setCrossAxisOverflow(bool allow); }; /** @@ -156,50 +180,4 @@ public: static ColumnLayout* create(); }; -/** - * Grid direction; which direction the grid should add its next row to if the - * current row is full - */ -enum class GridDirection { - // Downward - Column, - // Upward - ReverseColumn, - // Right - Row, - // Left - ReverseRow, -}; - -/** - * Grid alignment; same as normal Alignment but also features the "Stretch" - * option which will stretch the row out to be the same size as the others -*/ -enum class GridAlignment { - Begin, - Center, - Stretch, - End, -}; - -class GEODE_DLL GridLayout : public Layout { -protected: - GridDirection m_direction = GridDirection::Column; - GridAlignment m_alignment = GridAlignment::Center; - std::optional m_rowSize; - -public: - void apply(CCNode* on) override; - - static GridLayout* create( - std::optional rowSize, - GridAlignment alignment = GridAlignment::Center, - GridDirection direction = GridDirection::Column - ); - - GridLayout* setDirection(GridDirection direction); - GridLayout* setAlignment(GridAlignment alignment); - GridLayout* setRowSize(std::optional rowSize); -}; - NS_CC_END diff --git a/loader/include/Geode/cocos/cocoa/CCArray.h b/loader/include/Geode/cocos/cocoa/CCArray.h index 8ce4f710..f6d53a8b 100644 --- a/loader/include/Geode/cocos/cocoa/CCArray.h +++ b/loader/include/Geode/cocos/cocoa/CCArray.h @@ -234,13 +234,11 @@ public: void fastRemoveObject(CCObject* object); /** Fast way to remove an element with a certain index */ void fastRemoveObjectAtIndex(unsigned int index); - - RT_ADD( - /** - * Rob modification - * Fast way to remove an element with a certain index */ - void fastRemoveObjectAtIndexNew(unsigned int index); - ); + /** + * Fast way to remove an element with a certain index + * @note RobTop addition + */ + void fastRemoveObjectAtIndexNew(unsigned int index); // Rearranging Content @@ -263,6 +261,13 @@ public: */ virtual CCObject* copyWithZone(CCZone* pZone); + /** + * Creates a shallow copy of this array, aka only clones the pointers to + * the array members and not the members themselves + * @returns New array with same members + */ + GEODE_DLL CCArray* shallowCopy(); + /* override functions */ virtual void acceptVisitor(CCDataVisitor &visitor); diff --git a/loader/src/cocos2d-ext/CCArray.cpp b/loader/src/cocos2d-ext/CCArray.cpp index b863faf3..f288dfb4 100644 --- a/loader/src/cocos2d-ext/CCArray.cpp +++ b/loader/src/cocos2d-ext/CCArray.cpp @@ -16,4 +16,10 @@ void CCArray::removeFirstObject(bool bReleaseObj) { this->removeObjectAtIndex(0, bReleaseObj); } +CCArray* CCArray::shallowCopy() { + auto r = CCArray::createWithCapacity(this->capacity()); + r->addObjectsFromArray(this); + return r; +} + #pragma warning(pop) diff --git a/loader/src/cocos2d-ext/Layout.cpp b/loader/src/cocos2d-ext/Layout.cpp index f338797b..38e097f6 100644 --- a/loader/src/cocos2d-ext/Layout.cpp +++ b/loader/src/cocos2d-ext/Layout.cpp @@ -22,189 +22,268 @@ CCArray* Layout::getNodesToPosition(CCNode* on) { return filtered; } -void AxisLayout::apply(CCNode* on) { - auto nodes = getNodesToPosition(on); - if (m_reverse) { - nodes->reverseObjects(); - } +static constexpr float AXIS_MIN_SCALE = 0.65f; - float availableAxisLength; - float originalCrossHeight; +struct AxisPosition { + float axisLength; + float axisAnchor; + float crossLength; + float crossAnchor; +}; - if (m_axis == Axis::Row) { - availableAxisLength = on->getContentSize().width; - originalCrossHeight = on->getContentSize().height; +static AxisPosition nodeAxis(CCNode* node, Axis axis, float scale) { + auto scaledSize = node->getScaledContentSize() * scale; + auto anchor = node->getAnchorPoint(); + if (axis == Axis::Row) { + return AxisPosition { + .axisLength = scaledSize.width, + .axisAnchor = anchor.x, + .crossLength = scaledSize.height, + .crossAnchor = anchor.y, + }; } else { - availableAxisLength = on->getContentSize().height; - originalCrossHeight = on->getContentSize().width; + return AxisPosition { + .axisLength = scaledSize.height, + .axisAnchor = anchor.y, + .crossLength = scaledSize.width, + .crossAnchor = anchor.x, + }; } +} +struct AxisLayout::Row : public CCObject { + float nextOverflowScaleDownFactor; + float nextOverflowSquichFactor; + float axisLength; + float crossLength; + Ref nodes; + + Row( + float scaleFactor, + float squishFactor, + float axisLength, + float crossLength, + CCArray* nodes + ) : nextOverflowScaleDownFactor(scaleFactor), + nextOverflowSquichFactor(squishFactor), + axisLength(axisLength), + crossLength(crossLength), + nodes(nodes) + { + this->autorelease(); + } +}; + +AxisLayout::Row* AxisLayout::fitInRow(CCNode* on, CCArray* nodes, float scale, float squish) const { + float nextAxisLength = 0.f; + float axisLength; + float crossLength = 0.f; + auto res = CCArray::create(); + + auto available = nodeAxis(on, m_axis, 1.f); size_t ix = 0; - float totalAxisLength = .0f; - float maxCrossLength = 0.f; for (auto& node : CCArrayExt(nodes)) { - float axisLength; - float axisAnchor; - float crossLength; - if (m_axis == Axis::Row) { - axisLength = node->getScaledContentSize().width; - axisAnchor = node->getAnchorPoint().x; - crossLength = node->getScaledContentSize().height; - } - else { - axisLength = node->getScaledContentSize().height; - axisAnchor = node->getAnchorPoint().y; - crossLength = node->getScaledContentSize().width; - } - // if no need to fit fully inside and only one item exists, the total - // width taken up is 0 - if (nodes->count() == 1 && !m_fitInside) { - totalAxisLength = 0; - } - // if no need to fit fully inside, figure out what part may overflow - // for first item - else if (ix == 0 && !m_fitInside) { - totalAxisLength += axisLength * (1.f - axisAnchor); - } - // if no need to fit fully inside, figure out what part may overflow - // for last item - else if (ix == nodes->count() - 1 && !m_fitInside) { - totalAxisLength += axisLength * axisAnchor; - } - // otherwise either we need to fit fully inside or this node is not - // at the start or end - else { - totalAxisLength += axisLength; + auto pos = nodeAxis(node, m_axis, scale * squish); + nextAxisLength += pos.axisLength; + // if multiple rows are allowed and this row is full, time for the + // next row + if (m_growCrossAxis && nextAxisLength > available.axisLength) { + break; } + res->addObject(node); + nodes->removeFirstObject(); if (ix) { - totalAxisLength += m_gap; + nextAxisLength += m_gap * squish; + axisLength += m_gap * squish; } - if (crossLength > maxCrossLength) { - maxCrossLength = crossLength; + axisLength += pos.axisLength; + if (pos.crossLength > crossLength) { + crossLength = pos.crossLength; } ix++; } - const auto minScale = .65f; + // reverse row if needed + if (m_axisReverse) { + res->reverseObjects(); + } - // assume intended scale is 1x - auto setScale = 1.f; - auto squeeze = 1.f; + return new Row( + // how much should the nodes be scaled down to fit the next row + available.axisLength / ( + nextAxisLength - m_gap * (res->count() - 1) + ) * scale * squish, + available.axisLength / nextAxisLength * scale * squish, + axisLength, + crossLength, + res + ); +} - // check for overflow - // first try to make the node smaller - if (totalAxisLength > availableAxisLength && m_autoScale) { - setScale = availableAxisLength / totalAxisLength; - if (setScale < minScale) { - setScale = minScale; +void AxisLayout::tryFitLayout(CCNode* on, CCArray* nodes, float scale, float squish) const { + auto rows = CCArray::create(); + float totalRowCrossLength = 0.f; + float crossScaleDownFactor = AXIS_MIN_SCALE; + float squishFactor = 1.f; + size_t ix = 0; + + // fit everything into rows while possible + auto newNodes = nodes->shallowCopy(); + while (newNodes->count()) { + auto row = this->fitInRow(on, newNodes, scale, squish); + rows->addObject(row); + if ( + row->nextOverflowScaleDownFactor > crossScaleDownFactor && + crossScaleDownFactor < 1.f + ) { + crossScaleDownFactor = row->nextOverflowScaleDownFactor; + } + if (row->nextOverflowSquichFactor < squishFactor) { + squishFactor = row->nextOverflowSquichFactor; + } + totalRowCrossLength += row->crossLength; + if (ix) { + totalRowCrossLength += m_gap; + } + ix++; + } + newNodes->release(); + + auto available = nodeAxis(on, m_axis, 1.f); + + // if cross axis overflow not allowed, try to scale down layout + if (!m_allowCrossAxisOverflow && totalRowCrossLength > available.crossLength) { + if (m_autoScale && scale > AXIS_MIN_SCALE) { + rows->release(); + return this->tryFitLayout(on, nodes, crossScaleDownFactor, squish); } - totalAxisLength *= setScale; } // if we're still overflowing, squeeze nodes closer together - if (totalAxisLength > availableAxisLength) { - squeeze = availableAxisLength / totalAxisLength; - totalAxisLength = availableAxisLength; + if (totalRowCrossLength > available.crossLength) { + // if squishing rows would take less squishing that squishing columns, + // then squish rows + if (totalRowCrossLength / available.crossLength < squishFactor) { + rows->release(); + return this->tryFitLayout(on, nodes, scale, squishFactor); + } } - // resize target to match settings - if (m_shrinkCrossAxis) { + // if we're here, the nodes are ready to be positioned + + if (m_crossReverse) { + rows->reverseObjects(); + } + + // resize cross axis if needed + if (m_allowCrossAxisOverflow) { + available.crossLength = totalRowCrossLength; if (m_axis == Axis::Row) { + log::debug("axisLength: {}, totalRowCrossLength: {}", available.axisLength, totalRowCrossLength); on->setContentSize({ - availableAxisLength, - maxCrossLength, + available.axisLength, + totalRowCrossLength, }); } else { on->setContentSize({ - maxCrossLength, - availableAxisLength, + totalRowCrossLength, + available.axisLength, }); } } - float pos; - switch (m_axisAlignment) { - case Alignment::Begin: { - pos = 0.f; + float crossPos; + switch (m_crossAlignment) { + case AxisAlignment::Start: { + crossPos = 0.f; } break; - case Alignment::Center: { - pos = availableAxisLength / 2 - totalAxisLength / 2; + case AxisAlignment::Center: { + crossPos = available.crossLength / 2 - totalRowCrossLength / 2; } break; - case Alignment::End: { - pos = availableAxisLength - totalAxisLength; + case AxisAlignment::End: { + crossPos = available.crossLength - totalRowCrossLength; + } break; + + case AxisAlignment::Even: { + crossPos = 0.f; } break; } - ix = 0; - for (auto& node : CCArrayExt(nodes)) { - // rescale node if overflowing - if (m_autoScale) { - // CCMenuItemSpriteExtra is quirky af - if (auto btn = typeinfo_cast(node)) { - btn->m_baseScale = setScale; + + for (auto row : CCArrayExt(rows)) { + float rowAxisPos; + switch (m_axisAlignment) { + case AxisAlignment::Start: { + rowAxisPos = 0.f; + } break; + + case AxisAlignment::Even: { + rowAxisPos = 0.f; + } break; + + case AxisAlignment::Center: { + rowAxisPos = available.axisLength / 2 - row->axisLength / 2; + } break; + + case AxisAlignment::End: { + rowAxisPos = available.axisLength - row->axisLength; + } break; + } + + for (auto& node : CCArrayExt(row->nodes)) { + // rescale node if overflowing + if (m_autoScale) { + // CCMenuItemSpriteExtra is quirky af + if (auto btn = typeinfo_cast(node)) { + btn->m_baseScale = scale; + } + node->setScale(scale); } - node->setScale(setScale); - } - float axisLength; - float axisAnchor; - float crossLength; - float crossAnchor; - if (m_axis == Axis::Row) { - axisLength = node->getScaledContentSize().width; - axisAnchor = node->getAnchorPoint().x; - crossLength = node->getScaledContentSize().height; - crossAnchor = node->getAnchorPoint().y; - } - else { - axisLength = node->getScaledContentSize().height; - axisAnchor = node->getAnchorPoint().y; - crossLength = node->getScaledContentSize().width; - crossAnchor = node->getAnchorPoint().x; - } - float axisPos; - if (ix == 0 && !m_fitInside) { - axisPos = pos; - pos += (axisLength * (1.f - axisAnchor) + m_gap * setScale) * squeeze; - } - else { - axisPos = pos + axisLength * axisAnchor * squeeze; - pos += (axisLength + m_gap * setScale) * squeeze; - } - float crossPos; - switch (m_crossAlignment) { - case Alignment::Begin: { - crossPos = crossLength * crossAnchor; - } break; + auto pos = nodeAxis(node, m_axis, scale * squish); + float axisPos = rowAxisPos + pos.axisLength * pos.axisAnchor; + float crossPos; + switch (m_crossAlignment) { + case AxisAlignment::Start: { + crossPos = pos.crossLength * pos.crossAnchor; + } break; - case Alignment::Center: { - crossPos = maxCrossLength / 2 - crossLength * (.5f - crossAnchor); - } break; + case AxisAlignment::Center: case AxisAlignment::Even: { + crossPos = row->crossLength / 2 - pos.crossLength * (.5f - pos.crossAnchor); + } break; - case Alignment::End: { - crossPos = maxCrossLength - crossLength * (1.f - crossAnchor); - } break; + case AxisAlignment::End: { + crossPos = row->crossLength - pos.crossLength * (1.f - pos.crossAnchor); + } break; + } + log::debug("axisPos: {}", axisPos); + log::debug("crossPos: {}", crossPos); + if (m_axis == Axis::Row) { + node->setPosition(axisPos, crossPos); + } + else { + node->setPosition(crossPos, axisPos); + } + rowAxisPos += pos.axisLength + m_gap * squish; } - if (m_axis == Axis::Row) { - node->setPosition(axisPos, crossPos); - } - else { - node->setPosition(crossPos, axisPos); - } - ix++; } } +void AxisLayout::apply(CCNode* on) { + auto nodes = getNodesToPosition(on); + this->tryFitLayout(on, nodes, 1.f, 1.f); +} + AxisLayout::AxisLayout(Axis axis) : m_axis(axis) {} -AxisLayout* AxisLayout::setCrossAxisAlignment(Alignment align) { +AxisLayout* AxisLayout::setCrossAxisAlignment(AxisAlignment align) { m_crossAlignment = align; return this; } -AxisLayout* AxisLayout::setAxisAlignment(Alignment align) { +AxisLayout* AxisLayout::setAxisAlignment(AxisAlignment align) { m_axisAlignment = align; return this; } @@ -214,8 +293,18 @@ AxisLayout* AxisLayout::setGap(float gap) { return this; } -AxisLayout* AxisLayout::setReverse(bool reverse) { - m_reverse = reverse; +AxisLayout* AxisLayout::setAxisReverse(bool reverse) { + m_axisReverse = reverse; + return this; +} + +AxisLayout* AxisLayout::setCrossAxisReverse(bool reverse) { + m_crossReverse = reverse; + return this; +} + +AxisLayout* AxisLayout::setCrossAxisOverflow(bool fit) { + m_allowCrossAxisOverflow = fit; return this; } @@ -224,56 +313,19 @@ AxisLayout* AxisLayout::setAutoScale(bool scale) { return this; } -AxisLayout* AxisLayout::setFitInside(bool fit) { - m_fitInside = fit; +AxisLayout* AxisLayout::setGrowCrossAxis(bool shrink) { + m_growCrossAxis = shrink; return this; } -AxisLayout* AxisLayout::setShrinkCrossAxis(bool shrink) { - m_shrinkCrossAxis = shrink; - return this; -} - -RowLayout::RowLayout() : AxisLayout(AxisLayout::Row) {} +RowLayout::RowLayout() : AxisLayout(Axis::Row) {} RowLayout* RowLayout::create() { return new RowLayout(); } -ColumnLayout::ColumnLayout() : AxisLayout(AxisLayout::Column) {} +ColumnLayout::ColumnLayout() : AxisLayout(Axis::Column) {} ColumnLayout* ColumnLayout::create() { return new ColumnLayout(); } - -void GridLayout::apply(CCNode* on) { - // todo -} - -GridLayout* GridLayout::create( - std::optional rowSize, - GridAlignment alignment, - GridDirection direction -) { - auto ret = new GridLayout; - ret->m_rowSize = rowSize; - ret->m_alignment = alignment; - ret->m_direction = direction; - return ret; -} - -GridLayout* GridLayout::setDirection(GridDirection direction) { - m_direction = direction; - return this; -} - -GridLayout* GridLayout::setAlignment(GridAlignment alignment) { - m_alignment = alignment; - return this; -} - -GridLayout* GridLayout::setRowSize(std::optional rowSize) { - m_rowSize = rowSize; - return this; -} - diff --git a/loader/src/hooks/GeodeNodeMetadata.cpp b/loader/src/hooks/GeodeNodeMetadata.cpp index dfb707cc..7254a00a 100644 --- a/loader/src/hooks/GeodeNodeMetadata.cpp +++ b/loader/src/hooks/GeodeNodeMetadata.cpp @@ -20,6 +20,7 @@ private: Ref m_userObject; std::string m_id = ""; std::unique_ptr m_layout = nullptr; + std::unique_ptr m_layoutOptions = nullptr; PositionHint m_positionHint = PositionHint::Default; std::unordered_map m_attributes; @@ -135,6 +136,17 @@ Layout* CCNode::getLayout() { return GeodeNodeMetadata::set(this)->m_layout.get(); } +void CCNode::setLayoutOptions(LayoutOptions* options, bool apply) { + GeodeNodeMetadata::set(this)->m_layoutOptions.reset(options); + if (apply && m_pParent) { + m_pParent->updateLayout(); + } +} + +LayoutOptions* CCNode::getLayoutOptions() { + return GeodeNodeMetadata::set(this)->m_layoutOptions.get(); +} + void CCNode::updateLayout() { if (auto layout = GeodeNodeMetadata::set(this)->m_layout.get()) { layout->apply(this); diff --git a/loader/src/ids/CreatorLayer.cpp b/loader/src/ids/CreatorLayer.cpp index 4938e1d4..db1ba2dc 100644 --- a/loader/src/ids/CreatorLayer.cpp +++ b/loader/src/ids/CreatorLayer.cpp @@ -26,33 +26,55 @@ $register_ids(CreatorLayer) { // move vault button to its own menu if (auto lockBtn = setIDSafe(menu, -2, "vault-button")) { - detachAndCreateMenu( + auto menu = detachAndCreateMenu( this, "top-right-menu", ColumnLayout::create() - ->setFitInside(false) - ->setAxisAlignment(Alignment::Begin), + ->setAxisAlignment(AxisAlignment::End), lockBtn - )->setAnchorPoint({ .5f, 0.f }); + ); + menu->setPositionY(menu->getPositionY() - 125.f / 2); + menu->setContentSize({ 60.f, 125.f }); + menu->updateLayout(); } // move treasure room button to its own menu if (auto roomBtn = setIDSafe(menu, -1, "treasure-room-button")) { - detachAndCreateMenu( + auto menu = detachAndCreateMenu( this, "bottom-right-menu", ColumnLayout::create() - ->setFitInside(false) - ->setAxisAlignment(Alignment::End), + ->setAxisAlignment(AxisAlignment::Start), roomBtn - )->setAnchorPoint({ .5f, 1.f }); + ); + menu->setPositionY(menu->getPositionY() + 125.f / 2); + menu->setContentSize({ 60.f, 125.f }); + menu->updateLayout(); } } if (auto menu = getChildOfType(this, 1)) { menu->setID("exit-menu"); setIDSafe(menu, 0, "exit-button"); + menu->setPositionY(menu->getPositionY() - 125.f / 2); + menu->setContentSize({ 60.f, 125.f }); + menu->setLayout( + ColumnLayout::create() + ->setAxisAlignment(AxisAlignment::End) + ); } + + // add a menu to the bottom left corner that is empty but prolly a place mods + // want to add stuff to + auto menu = CCMenu::create(); + menu->setPosition(24.f, 24.f + 125.f / 2); + menu->setID("bottom-left-menu"); + menu->setContentSize({ 60.f, 125.f }); + menu->setLayout( + ColumnLayout::create() + ->setAxisAlignment(AxisAlignment::Start) + ); + this->addChild(menu); } struct CreatorLayerIDs : Modify { diff --git a/loader/src/ids/EditLevelLayer.cpp b/loader/src/ids/EditLevelLayer.cpp index b8dabc44..e3182ad1 100644 --- a/loader/src/ids/EditLevelLayer.cpp +++ b/loader/src/ids/EditLevelLayer.cpp @@ -30,6 +30,8 @@ $register_ids(EditLevelLayer) { "info-button-menu" ); + auto winSize = CCDirector::get()->getWinSize(); + if (auto menu = this->getChildByID("level-action-menu")) { setIDs(menu, 0, "edit-button", "play-button", "share-button"); } @@ -52,16 +54,15 @@ $register_ids(EditLevelLayer) { menu->getPositionX() + static_cast( menu->getChildren()->firstObject() )->getPositionX(), - 285.f + winSize.height / 2 ); + menu->setContentSize({ 60.f, winSize.height - 50.f }); menu->setLayout( ColumnLayout::create() ->setGap(7.f) - ->setFitInside(false) - ->setAxisAlignment(Alignment::Begin) - ->setReverse(true) + ->setAxisAlignment(AxisAlignment::Start) + ->setAxisReverse(true) ); - menu->setAnchorPoint({ .5f, 0.f }); menu->setZOrder(1); for (int i = 0; i < rand() % 4; i++) { diff --git a/loader/src/ids/EditorUI.cpp b/loader/src/ids/EditorUI.cpp index d283ed5b..19d5595f 100644 --- a/loader/src/ids/EditorUI.cpp +++ b/loader/src/ids/EditorUI.cpp @@ -42,15 +42,23 @@ $register_ids(EditorUI) { "unlink-button" ); - detachAndCreateMenu( + auto toolbarTogglesMenu = detachAndCreateMenu( this, "toolbar-toggles-menu", - GridLayout::create(2, GridAlignment::Begin, GridDirection::Column), + ColumnLayout::create() + ->setCrossAxisOverflow(false) + ->setAxisAlignment(AxisAlignment::Even) + ->setCrossAxisAlignment(AxisAlignment::Even), menu->getChildByID("swipe-button"), menu->getChildByID("free-move-button"), menu->getChildByID("snap-button"), menu->getChildByID("rotate-button") ); + toolbarTogglesMenu->setPosition( + toolbarTogglesMenu->getPosition() - CCPoint { 50.f, 50.f } + ); + toolbarTogglesMenu->setContentSize({ 100.f, 100.f }); + toolbarTogglesMenu->updateLayout(); detachAndCreateMenu( this, @@ -108,24 +116,40 @@ $register_ids(EditorUI) { "delete-help-icon" ); - detachAndCreateMenu( + auto deleteButtonMenu = detachAndCreateMenu( menu, "delete-button-menu", - GridLayout::create(2, GridAlignment::Begin, GridDirection::Column), + ColumnLayout::create() + ->setCrossAxisOverflow(false) + ->setAxisAlignment(AxisAlignment::Even) + ->setCrossAxisAlignment(AxisAlignment::Even), menu->getChildByID("delete-button"), menu->getChildByID("delete-all-of-button"), menu->getChildByID("delete-startpos-button") ); + deleteButtonMenu->setPosition( + deleteButtonMenu->getPosition() - CCPoint { 50.f, 50.f } + ); + deleteButtonMenu->setContentSize({ 100.f, 100.f }); + deleteButtonMenu->updateLayout(); - detachAndCreateMenu( + auto deleteFilterMenu = detachAndCreateMenu( menu, "delete-filter-menu", - GridLayout::create(2, GridAlignment::Begin, GridDirection::Column), + ColumnLayout::create() + ->setCrossAxisOverflow(false) + ->setAxisAlignment(AxisAlignment::Even) + ->setCrossAxisAlignment(AxisAlignment::Even), menu->getChildByID("delete-filter-none"), menu->getChildByID("delete-filter-static"), menu->getChildByID("delete-filter-detail"), menu->getChildByID("delete-filter-custom") ); + deleteFilterMenu->setPosition( + deleteFilterMenu->getPosition() - CCPoint { 50.f, 50.f } + ); + deleteFilterMenu->setContentSize({ 100.f, 100.f }); + deleteFilterMenu->updateLayout(); } if (auto menu = getChildOfType(this, 2)) { @@ -174,20 +198,25 @@ $register_ids(EditorUI) { "all-layers-button" ); - detachAndCreateMenu( + auto topRightMenu = detachAndCreateMenu( this, "top-right-menu", RowLayout::create() - ->setFitInside(false) - ->setAxisAlignment(Alignment::End), + ->setAxisAlignment(AxisAlignment::End), menu->getChildByID("pause-button"), menu->getChildByID("settings-button") - )->setAnchorPoint({ 1.f, .5f }); + ); + topRightMenu->setContentSize({ 60.f, 125.f }); + topRightMenu->setPositionX(topRightMenu->getPositionX() - 125.f / 2); + topRightMenu->updateLayout(); - detachAndCreateMenu( + auto rightMenu = detachAndCreateMenu( this, "editor-buttons-menu", - GridLayout::create(4, GridAlignment::End, GridDirection::Column), + ColumnLayout::create() + ->setAxisAlignment(AxisAlignment::Even) + ->setCrossAxisAlignment(AxisAlignment::End) + ->setCrossAxisReverse(true), menu->getChildByID("copy-paste-button"), menu->getChildByID("edit-object-button"), menu->getChildByID("paste-color-button"), @@ -201,6 +230,9 @@ $register_ids(EditorUI) { menu->getChildByID("copy-values-button"), menu->getChildByID("hsv-button") ); + rightMenu->setContentSize({ 125.f, 125.f }); + rightMenu->setPosition(rightMenu->getPosition() - CCPoint { 125.f, 125.f }); + rightMenu->updateLayout(); detachAndCreateMenu( this, diff --git a/loader/src/ids/LevelBrowserLayer.cpp b/loader/src/ids/LevelBrowserLayer.cpp index a88a1fc2..81f8a7fd 100644 --- a/loader/src/ids/LevelBrowserLayer.cpp +++ b/loader/src/ids/LevelBrowserLayer.cpp @@ -18,22 +18,40 @@ $register_ids(LevelBrowserLayer) { setIDSafe(menu, 0, "new-level-button"); if (auto myLevelsBtn = setIDSafe(menu, 1, "my-levels-button")) { - detachAndCreateMenu( + auto menu = detachAndCreateMenu( this, "my-levels-menu", ColumnLayout::create() - ->setFitInside(false) - ->setAxisAlignment(Alignment::End), + ->setAxisAlignment(AxisAlignment::Start), myLevelsBtn - )->setAnchorPoint({ .5f, 1.f }); + ); + menu->setPositionY(menu->getPositionY() + 100.f / 2); + menu->setContentSize({ 50.f, 100.f }); + menu->updateLayout(); } menu->setLayout( ColumnLayout::create() - ->setFitInside(false) - ->setAxisAlignment(Alignment::End) + ->setAxisAlignment(AxisAlignment::Start) ); - menu->setAnchorPoint({ .5f, 1.f }); + menu->setPositionY(menu->getPositionY() + 150.f / 2); + menu->setContentSize({ 50.f, 150.f }); + menu->updateLayout(); + } + + if (auto menu = getChildOfType(this, 1)) { + if (auto searchBtn = setIDSafe(menu, 5, "search-button")) { + auto menu = detachAndCreateMenu( + this, + "search-menu", + ColumnLayout::create() + ->setAxisAlignment(AxisAlignment::Start), + searchBtn + ); + menu->setPositionY(menu->getPositionY() + 50.f / 2); + menu->setContentSize({ 50.f, 50.f }); + menu->updateLayout(); + } } } } diff --git a/loader/src/ids/LevelInfoLayer.cpp b/loader/src/ids/LevelInfoLayer.cpp index a424a733..3d4a3716 100644 --- a/loader/src/ids/LevelInfoLayer.cpp +++ b/loader/src/ids/LevelInfoLayer.cpp @@ -43,14 +43,16 @@ $register_ids(LevelInfoLayer) { menu->setID("right-side-menu"); if (auto name = setIDSafe(menu, 0, "creator-name")) { - detachAndCreateMenu( + auto menu = detachAndCreateMenu( this, "creator-info-menu", ColumnLayout::create() - ->setFitInside(false) - ->setAxisAlignment(Alignment::Begin), + ->setAxisAlignment(AxisAlignment::Start), name - )->setAnchorPoint({ .5f, 0.f }); + ); + menu->setPositionY(menu->getPositionY() + 100.f / 2); + menu->setContentSize({ 60.f, 100.f }); + menu->updateLayout(); } auto leftSideMenu = CCMenu::create(); diff --git a/loader/src/ids/LevelSettingsLayer.cpp b/loader/src/ids/LevelSettingsLayer.cpp index 0a52fac9..a898d013 100644 --- a/loader/src/ids/LevelSettingsLayer.cpp +++ b/loader/src/ids/LevelSettingsLayer.cpp @@ -162,14 +162,15 @@ $register_ids(LevelSettingsLayer) { menu->getChildByID("2-player-toggle") ); - detachAndCreateMenu( + auto fontButtonMenu = detachAndCreateMenu( this, "font-button-menu", RowLayout::create() - ->setFitInside(false) - ->setAxisAlignment(Alignment::End), + ->setAxisAlignment(AxisAlignment::End), menu->getChildByID("font-button") - )->setAnchorPoint({ .5f, 1.f }); + ); + fontButtonMenu->setPositionY(fontButtonMenu->getPositionY() - 100.f / 2); + fontButtonMenu->setContentSize({ 50.f, 100.f }); } } diff --git a/loader/src/ids/MenuLayer.cpp b/loader/src/ids/MenuLayer.cpp index 8d913ecd..09d548d2 100644 --- a/loader/src/ids/MenuLayer.cpp +++ b/loader/src/ids/MenuLayer.cpp @@ -41,18 +41,27 @@ $register_ids(MenuLayer) { setIDSafe(menu, 2, "editor-button"); if (auto pfp = setIDSafe(menu, 3, "profile-button")) { - detachAndCreateMenu( + auto profileMenu = detachAndCreateMenu( this, "profile-menu", ColumnLayout::create() - ->setFitInside(false) - ->setAxisAlignment(Alignment::Begin) - ->setReverse(true), + ->setAxisAlignment(AxisAlignment::Start) + ->setAxisReverse(true), pfp - )->setAnchorPoint({ .5f, .0f }); + ); + profileMenu->setContentSize({ 50.f, 200.f }); + profileMenu->setPositionY( + profileMenu->getPositionY() + 200.f / 2 - + pfp->getScaledContentSize().height / 2 + ); + profileMenu->updateLayout(); } menu->setContentSize({ 400.f, 65.f }); - menu->setLayout(RowLayout::create()->setGap(18.f)); + menu->setLayout( + RowLayout::create() + ->setGap(18.f) + ->setCrossAxisOverflow(true) + ); } // bottom menu if (auto menu = getChildOfType(this, 1)) { @@ -68,7 +77,7 @@ $register_ids(MenuLayer) { auto menu = detachAndCreateMenu( this, "right-side-menu", - ColumnLayout::create()->setFitInside(true), + ColumnLayout::create(), dailyChest ); menu->setContentSize({ 65.f, 180.f }); @@ -76,7 +85,7 @@ $register_ids(MenuLayer) { } menu->setContentSize({ 360.f, 65.f }); - menu->setLayout(RowLayout::create()->setFitInside(true)); + menu->setLayout(RowLayout::create()); } // social media menu if (auto menu = getChildOfType(this, 2)) { @@ -94,14 +103,19 @@ $register_ids(MenuLayer) { // move close button to its own menu if (auto closeBtn = setIDSafe(menu, 1, "close-button")) { - detachAndCreateMenu( + auto closeMenu = detachAndCreateMenu( this, "close-menu", RowLayout::create() - ->setFitInside(false) - ->setAxisAlignment(Alignment::Begin), + ->setAxisAlignment(AxisAlignment::Start), closeBtn - )->setAnchorPoint({ 0.f, .5f }); + ); + closeMenu->setContentSize({ 200.f, 50.f }); + closeMenu->setPositionX( + closeMenu->getPositionX() + 200.f / 2 - + closeBtn->getScaledContentSize().width / 2 + ); + closeMenu->updateLayout(); } } }