geode/loader/include/Geode/cocos/base_nodes/Layout.hpp

339 lines
11 KiB
C++
Raw Normal View History

2022-10-10 13:58:47 -04:00
#pragma once
#include "../include/ccMacros.h"
#include "../cocoa/CCAffineTransform.h"
#include "../cocoa/CCArray.h"
2022-10-10 13:58:47 -04:00
#include <Geode/platform/platform.hpp>
#include <optional>
#include <unordered_map>
NS_CC_BEGIN
class CCNode;
/**
* Layouts automatically handle the positioning of nodes. Use CCNode::setLayout
* to apply a layout to a node, and then use CCNode::updateLayout to apply
* the layout's positioning. Geode comes with a few default layouts like
* RowLayout, ColumnLayout, and GridLayout, but if you need a different kind
* of layout you can inherit from the Layout class.
*/
2023-02-02 10:08:13 -05:00
class GEODE_DLL Layout {
protected:
static CCArray* getNodesToPosition(CCNode* forNode);
2022-10-10 13:58:47 -04:00
public:
/**
* Automatically apply the layout's positioning on a set of nodes
2023-02-02 10:08:13 -05:00
* @param on Node to apply the layout on. Position's the node's children
* according to the layout. The content size of the node should be
* respected as a boundary the layout shouldn't overflow. The node may be
* rescaled to better fit its contents
2022-10-10 13:58:47 -04:00
*/
2023-02-02 10:08:13 -05:00
virtual void apply(CCNode* on) = 0;
2023-02-06 14:36:08 -05:00
virtual ~Layout() = default;
};
class GEODE_DLL LayoutOptions {
public:
virtual ~LayoutOptions() = default;
2022-10-10 13:58:47 -04:00
};
/**
* The direction of an AxisLayout
2022-10-10 13:58:47 -04:00
*/
2023-02-06 14:36:08 -05:00
enum class Axis {
Row,
Column,
};
2022-10-10 13:58:47 -04:00
/**
* Specifies the alignment of something in an AxisLayout
2022-10-10 13:58:47 -04:00
*/
2023-02-06 14:36:08 -05:00
enum class AxisAlignment {
// Align items to the start
// |ooo......|
Start,
// All items are centered
// |...ooo...|
2022-10-10 13:58:47 -04:00
Center,
2023-02-06 14:36:08 -05:00
// Align items to the end
// |......ooo|
2022-10-10 13:58:47 -04:00
End,
2023-02-06 14:36:08 -05:00
// Each item gets the same portion from the layout (disregards gap)
// |.o..o..o.|
Even,
2022-10-10 13:58:47 -04:00
};
constexpr float AXISLAYOUT_DEFAULT_MIN_SCALE = 0.65f;
constexpr int AXISLAYOUT_DEFAULT_PRIORITY = 0;
/**
* Options for controlling the behaviour of individual nodes in an AxisLayout
* @example
* auto node = CCNode::create();
* // this node will have 10 units of spacing between it and the next one
* node->setLayoutOptions(
* AxisLayoutOptions::create()
* ->setNextGap(10.f)
* );
* someNodeWithALayout->addChild(node);
*/
class GEODE_DLL AxisLayoutOptions : public LayoutOptions {
protected:
std::optional<bool> m_autoScale = std::nullopt;
float m_maxScale = 1.f;
float m_minScale = AXISLAYOUT_DEFAULT_MIN_SCALE;
float m_relativeScale = 1.f;
std::optional<float> m_length = std::nullopt;
std::optional<float> m_nextGap = std::nullopt;
std::optional<float> m_prevGap = std::nullopt;
bool m_breakLine = false;
bool m_sameLine = false;
int m_scalePriority = AXISLAYOUT_DEFAULT_PRIORITY;
public:
static AxisLayoutOptions* create();
std::optional<bool> getAutoScale() const;
float getMaxScale() const;
float getMinScale() const;
float getRelativeScale() const;
std::optional<float> getLength() const;
std::optional<float> getPrevGap() const;
std::optional<float> getNextGap() const;
bool getBreakLine() const;
bool getSameLine() const;
int getScalePriority() const;
/**
* Set the maximum scale this node can be if it's contained in an
* auto-scaled layout. Default is 1
*/
AxisLayoutOptions* setMaxScale(float scale);
/**
* Set the minimum scale this node can be if it's contained in an
* auto-scaled layout. Default is AXISLAYOUT_DEFAULT_MIN_SCALE
*/
AxisLayoutOptions* setMinScale(float scale);
/**
* Set the relative scale of this node compared to other nodes if it's
* contained in an auto-scaled layout. Default is 1
*/
AxisLayoutOptions* setRelativeScale(float scale);
/**
* Set auto-scaling for this node, overriding the layout's auto-scale
* setting. If nullopt, the layout's auto-scale options will be used
*/
AxisLayoutOptions* setAutoScale(std::optional<bool> enabled);
/**
* Set an absolute length for this node. If nullopt, the length will be
* dynamically calculated based on content size
*/
AxisLayoutOptions* setLength(std::optional<float> length);
/**
* Override the default gap in the layout between this node and the
* previous one. If nullopt, the default gap of the layout will be used
*/
AxisLayoutOptions* setPrevGap(std::optional<float> gap);
/**
* Override the default gap in the layout between this node and the next
* one. If nullopt, the default gap of the layout will be used
*/
AxisLayoutOptions* setNextGap(std::optional<float> gap);
/**
* If enabled, the node will always cause a growable axis layout to break
* into a new line even if the current line could've fit the next node
*/
AxisLayoutOptions* setBreakLine(bool enable);
/**
* If enabled, the node will be forced to be on the same line as the
* previous node even if doing this would overflow
*/
AxisLayoutOptions* setSameLine(bool enable);
/**
* Set the scale priority of this node. Nodes with higher priority will be
* scaled down first before nodes with lower priority when an auto-scaled
* layout attempts to fit its contents. Default is
* AXISLAYOUT_DEFAULT_PRIORITY
* @note For optimal performance, the priorities should all be close to
* each other with no gaps
*/
AxisLayoutOptions* setScalePriority(int priority);
};
2023-02-06 14:36:08 -05:00
/**
* A multi-purpose dynamic layout for arranging nodes along an axis. Can be
* used to arrange nodes in a single line, a grid, or a flex layout. The
* RowLayout and ColumnLayout classes function as simple thin wrappers over
* AxisLayout. The positioning of individual nodes in the layout can be
* further controlled using AxisLayoutOptions
* @warning Calculating layouts can get increasingly expensive for large
* amounts of child nodes being fit into a small space - while this should
* never prove a real performance concern as most layouts only have a few
* hundred children at the very most, be aware that you probably shouldn't
* call CCNode::updateLayout every frame for a menu with thousands of children
* @example
* auto menu = CCMenu::create();
* // The menu's children will be arranged horizontally, unless they overflow
* // the content size width in which case a new line will be inserted and
* // aligned to the left. The menu automatically will automatically grow in
* // height to fit all the rows
* menu->setLayout(
* RowLayout::create()
* ->setGap(10.f)
* ->setGrowCrossAxis(true)
* ->setAxisAlignment(AxisAlignment::Start)
* );
* menu->setContentSize({ 200.f, 0.f });
* menu->addChild(...);
* menu->updateLayout();
2023-02-06 14:36:08 -05:00
*/
2023-02-04 08:58:10 -05:00
class GEODE_DLL AxisLayout : public Layout {
2022-10-10 13:58:47 -04:00
protected:
2023-02-04 08:58:10 -05:00
Axis m_axis;
2023-02-06 14:36:08 -05:00
AxisAlignment m_axisAlignment = AxisAlignment::Center;
AxisAlignment m_crossAlignment = AxisAlignment::Center;
2023-02-04 08:58:10 -05:00
float m_gap = 5.f;
bool m_autoScale = true;
2023-02-06 14:36:08 -05:00
bool m_axisReverse = false;
bool m_crossReverse = false;
bool m_allowCrossAxisOverflow = true;
bool m_growCrossAxis = false;
struct Row;
2023-02-15 14:25:12 -05:00
bool shouldAutoScale(AxisLayoutOptions* opts) const;
float nextGap(AxisLayoutOptions* now, AxisLayoutOptions* next) const;
Row* fitInRow(
CCNode* on, CCArray* nodes,
std::pair<float, float> const& minMaxScales,
std::pair<int, int> const& minMaxPrios,
bool doAutoScale,
float scale, float squish, int prio
) const;
void tryFitLayout(
CCNode* on, CCArray* nodes,
std::pair<float, float> const& minMaxScales,
std::pair<int, int> const& minMaxPrios,
bool doAutoScale,
float scale, float squish, int prio
) const;
2022-10-10 13:58:47 -04:00
2023-02-04 08:58:10 -05:00
AxisLayout(Axis);
2022-10-10 13:58:47 -04:00
public:
2023-02-11 10:50:14 -05:00
/**
* Create a new AxisLayout. Note that this class is not automatically
* managed by default, so you must assign it to a CCNode or manually
* manage the memory yourself. See the chainable setters on AxisLayout for
* what options you can customize for the layout
* @param axis The direction of the layout
* @note For convenience, you can use the RowLayout and ColumnLayout
* classes, which are just thin wrappers over AxisLayout
* @returns Created AxisLayout
*/
static AxisLayout* create(Axis axis = Axis::Row);
2023-02-02 10:08:13 -05:00
void apply(CCNode* on) override;
2022-10-10 13:58:47 -04:00
2023-02-11 05:06:01 -05:00
Axis getAxis() const;
AxisAlignment getAxisAlignment() const;
AxisAlignment getCrossAxisAlignment() const;
float getGap() const;
bool getAxisReverse() const;
bool getCrossAxisReverse() const;
bool getAutoScale() const;
bool getGrowCrossAxis() const;
bool getCrossAxisOverflow() const;
2023-02-11 10:50:14 -05:00
AxisLayout* setAxis(Axis axis);
2023-02-06 14:36:08 -05:00
/**
* Sets where to align the target node's children on the main axis (X for
* Row, Y for Column)
2023-02-02 10:08:13 -05:00
*/
2023-02-06 14:36:08 -05:00
AxisLayout* setAxisAlignment(AxisAlignment align);
2023-02-11 05:06:01 -05:00
/**
* Sets where to align the target node's children on the cross-axis (Y for
* Row, X for Column)
*/
AxisLayout* setCrossAxisAlignment(AxisAlignment align);
2023-02-02 10:08:13 -05:00
/**
* The spacing between the children of the node this layout applies to.
2023-02-06 14:36:08 -05:00
* Measured as the space between their edges, not centres. Does not apply
* on the main / cross axis if their alignment is AxisAlignment::Even
2023-02-02 10:08:13 -05:00
*/
2023-02-04 08:58:10 -05:00
AxisLayout* setGap(float gap);
2023-02-02 10:08:13 -05:00
/**
* Whether to reverse the direction of the children in this layout or not
*/
2023-02-06 14:36:08 -05:00
AxisLayout* setAxisReverse(bool reverse);
/**
* Whether to reverse the direction of the rows on the cross-axis or not
*/
AxisLayout* setCrossAxisReverse(bool reverse);
2023-02-02 10:08:13 -05:00
/**
2023-02-06 14:36:08 -05:00
* 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
2023-02-02 10:08:13 -05:00
*/
2023-02-04 08:58:10 -05:00
AxisLayout* setAutoScale(bool enable);
2023-02-02 10:08:13 -05:00
/**
2023-02-06 14:36:08 -05:00
* If true, if the main axis overflows extra nodes will be placed on new
* rows/columns on the cross-axis
*/
AxisLayout* setGrowCrossAxis(bool expand);
/**
* If true, the cross-axis content size of the target node will be
* automatically adjusted to fit the children
2023-02-02 10:08:13 -05:00
*/
2023-02-06 14:36:08 -05:00
AxisLayout* setCrossAxisOverflow(bool allow);
2022-10-10 13:58:47 -04:00
};
/**
2023-02-04 08:58:10 -05:00
* Simple layout for arranging nodes in a row (horizontal line)
2022-10-10 13:58:47 -04:00
*/
2023-02-04 08:58:10 -05:00
class GEODE_DLL RowLayout : public AxisLayout {
2022-10-10 13:58:47 -04:00
protected:
2023-02-04 08:58:10 -05:00
RowLayout();
2022-10-10 13:58:47 -04:00
public:
2023-02-04 08:58:10 -05:00
/**
* Create a new RowLayout. Note that this class is not automatically
* managed by default, so you must assign it to a CCNode or manually
* manage the memory yourself. See the chainable setters on RowLayout for
* what options you can customize for the layout
* @returns Created RowLayout
*/
static RowLayout* create();
};
2022-10-10 13:58:47 -04:00
2023-02-04 08:58:10 -05:00
/**
* Simple layout for arranging nodes in a column (vertical line)
*/
class GEODE_DLL ColumnLayout : public AxisLayout {
protected:
ColumnLayout();
public:
2023-02-02 10:08:13 -05:00
/**
* Create a new ColumnLayout. Note that this class is not automatically
* managed by default, so you must assign it to a CCNode or manually
* manage the memory yourself. See the chainable setters on RowLayout for
* what options you can customize for the layout
* @returns Created ColumnLayout
*/
static ColumnLayout* create();
2022-10-10 13:58:47 -04:00
};
NS_CC_END