using System.Collections.Generic;
namespace UnityEngine.UI.Extensions
{
/// <summary>
/// Layout Group controller that arranges children in rows, fitting as many on a line until total width exceeds parent bounds
/// </summary>
[AddComponentMenu( "Layout/Extensions/Flow Layout Group" )]
public class FlowLayoutGroup : LayoutGroup
{
public float Spacing = 0f;
public bool ChildForceExpandWidth = false ;
public bool ChildForceExpandHeight = false ;
private float _layoutHeight;
public override void CalculateLayoutInputHorizontal()
{
base.CalculateLayoutInputHorizontal();
var minWidth = GetGreatestMinimumChildWidth() + padding.left + padding.right;
SetLayoutInputForAxis(minWidth, -1, -1, 0);
}
public override void SetLayoutHorizontal()
{
SetLayout(rectTransform.rect.width, 0, false );
}
public override void SetLayoutVertical()
{
SetLayout(rectTransform.rect.width, 1, false );
}
public override void CalculateLayoutInputVertical()
{
_layoutHeight = SetLayout(rectTransform.rect.width, 1, true );
}
protected bool IsCenterAlign
{
get
{
return childAlignment == TextAnchor.LowerCenter || childAlignment == TextAnchor.MiddleCenter ||
childAlignment == TextAnchor.UpperCenter;
}
}
protected bool IsRightAlign
{
get
{
return childAlignment == TextAnchor.LowerRight || childAlignment == TextAnchor.MiddleRight ||
childAlignment == TextAnchor.UpperRight;
}
}
protected bool IsMiddleAlign
{
get
{
return childAlignment == TextAnchor.MiddleLeft || childAlignment == TextAnchor.MiddleRight ||
childAlignment == TextAnchor.MiddleCenter;
}
}
protected bool IsLowerAlign
{
get
{
return childAlignment == TextAnchor.LowerLeft || childAlignment == TextAnchor.LowerRight ||
childAlignment == TextAnchor.LowerCenter;
}
}
/// <summary>
/// Holds the rects that will make up the current row being processed
/// </summary>
private readonly IList<RectTransform> _rowList = new List<RectTransform>();
/// <summary>
/// Main layout method
/// </summary>
/// <param name="width">Width to calculate the layout with</param>
/// <param name="axis">0 for horizontal axis, 1 for vertical</param>
/// <param name="layoutInput">If true, sets the layout input for the axis. If false, sets child position for axis</param>
public float SetLayout( float width, int axis, bool layoutInput)
{
var groupHeight = rectTransform.rect.height;
// Width that is available after padding is subtracted
var workingWidth = rectTransform.rect.width - padding.left - padding.right;
// Accumulates the total height of the rows, including spacing and padding.
var yOffset = IsLowerAlign ? ( float )padding.bottom : ( float )padding.top;
var currentRowWidth = 0f;
var currentRowHeight = 0f;
for (var i = 0; i < rectChildren.Count; i++) {
// LowerAlign works from back to front
var index = IsLowerAlign ? rectChildren.Count - 1 - i : i;
var child = rectChildren[index];
var childWidth = LayoutUtility.GetPreferredSize(child, 0);
var childHeight = LayoutUtility.GetPreferredSize(child, 1);
// Max child width is layout group with - padding
childWidth = Mathf.Min(childWidth, workingWidth);
// If adding this element would exceed the bounds of the row,
// go to a new line after processing the current row
if (currentRowWidth + childWidth > workingWidth) {
currentRowWidth -= Spacing;
// Process current row elements positioning
if (!layoutInput) {
var h = CalculateRowVerticalOffset(groupHeight, yOffset, currentRowHeight);
LayoutRow(_rowList, currentRowWidth, currentRowHeight, workingWidth, padding.left, h, axis);
}
// Clear existing row
_rowList.Clear();
// Add the current row height to total height accumulator, and reset to 0 for the next row
yOffset += currentRowHeight;
yOffset += Spacing;
currentRowHeight = 0;
currentRowWidth = 0;
}
currentRowWidth += childWidth;
_rowList.Add(child);
// We need the largest element height to determine the starting position of the next line
if (childHeight > currentRowHeight) {
currentRowHeight = childHeight;
}
currentRowWidth += Spacing;
}
if (!layoutInput) {
var h = CalculateRowVerticalOffset(groupHeight, yOffset, currentRowHeight);
// Layout the final row
LayoutRow(_rowList, currentRowWidth, currentRowHeight, workingWidth, padding.left, h, axis);
}
_rowList.Clear();
// Add the last rows height to the height accumulator
yOffset += currentRowHeight;
yOffset += IsLowerAlign ? padding.top : padding.bottom;
if (layoutInput) {
if (axis == 1)
SetLayoutInputForAxis(yOffset, yOffset, -1, axis);
}
return yOffset;
}
private float CalculateRowVerticalOffset( float groupHeight, float yOffset, float currentRowHeight)
{
float h;
if (IsLowerAlign) {
h = groupHeight - yOffset - currentRowHeight;
} else if (IsMiddleAlign) {
h = groupHeight*0.5f - _layoutHeight * 0.5f + yOffset;
} else {
h = yOffset;
}
return h;
}
protected void LayoutRow(IList<RectTransform> contents, float rowWidth, float rowHeight, float maxWidth, float xOffset, float yOffset, int axis)
{
var xPos = xOffset;
if (!ChildForceExpandWidth && IsCenterAlign)
xPos += (maxWidth - rowWidth) * 0.5f;
else if (!ChildForceExpandWidth && IsRightAlign)
xPos += (maxWidth - rowWidth);
var extraWidth = 0f;
if (ChildForceExpandWidth) {
extraWidth = (maxWidth - rowWidth)/_rowList.Count;
}
for (var j = 0; j < _rowList.Count; j++) {
var index = IsLowerAlign ? _rowList.Count - 1 - j : j;
var rowChild = _rowList[index];
var rowChildWidth = LayoutUtility.GetPreferredSize(rowChild, 0) + extraWidth;
var rowChildHeight = LayoutUtility.GetPreferredSize(rowChild, 1);
if (ChildForceExpandHeight)
rowChildHeight = rowHeight;
rowChildWidth = Mathf.Min(rowChildWidth, maxWidth);
var yPos = yOffset;
if (IsMiddleAlign)
yPos += (rowHeight - rowChildHeight) * 0.5f;
else if (IsLowerAlign)
yPos += (rowHeight - rowChildHeight);
if (axis == 0)
SetChildAlongAxis(rowChild, 0, xPos, rowChildWidth);
else
SetChildAlongAxis(rowChild, 1, yPos, rowChildHeight);
xPos += rowChildWidth + Spacing;
}
}
public float GetGreatestMinimumChildWidth()
{
var max = 0f;
for (var i = 0; i < rectChildren.Count; i++) {
var w = LayoutUtility.GetMinWidth(rectChildren[i]);
max = Mathf.Max(w, max);
}
return max;
}
}
}
|