235 lines
7.0 KiB
C#
235 lines
7.0 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
[AddComponentMenu("Layout/Flow Layout Group")]
|
|
public class FlowLayoutGroup : LayoutGroup
|
|
{
|
|
[SerializeField] private float spacingX = 10f;
|
|
[SerializeField] private float spacingY = 10f;
|
|
[SerializeField] private bool expandChildHeight = false;
|
|
[SerializeField] private float forcedChildHeight = 120f;
|
|
|
|
private readonly List<RowInfo> rows = new List<RowInfo>();
|
|
private float calculatedPreferredHeight;
|
|
|
|
private struct RowInfo
|
|
{
|
|
public int startIndex;
|
|
public int endIndex;
|
|
public float width;
|
|
public float height;
|
|
}
|
|
|
|
public float SpacingX
|
|
{
|
|
get => spacingX;
|
|
set => SetProperty(ref spacingX, value);
|
|
}
|
|
|
|
public float SpacingY
|
|
{
|
|
get => spacingY;
|
|
set => SetProperty(ref spacingY, value);
|
|
}
|
|
|
|
public bool ExpandChildHeight
|
|
{
|
|
get => expandChildHeight;
|
|
set => SetProperty(ref expandChildHeight, value);
|
|
}
|
|
|
|
public float ForcedChildHeight
|
|
{
|
|
get => forcedChildHeight;
|
|
set => SetProperty(ref forcedChildHeight, value);
|
|
}
|
|
|
|
public override void CalculateLayoutInputHorizontal()
|
|
{
|
|
base.CalculateLayoutInputHorizontal();
|
|
CalculateRows();
|
|
float minWidth = padding.horizontal;
|
|
float preferredWidth = rectTransform.rect.width > 0f ? rectTransform.rect.width : minWidth;
|
|
SetLayoutInputForAxis(minWidth, preferredWidth, -1f, 0);
|
|
}
|
|
|
|
public override void CalculateLayoutInputVertical()
|
|
{
|
|
CalculateRows();
|
|
SetLayoutInputForAxis(calculatedPreferredHeight, calculatedPreferredHeight, -1f, 1);
|
|
}
|
|
|
|
public override void SetLayoutHorizontal()
|
|
{
|
|
CalculateRows();
|
|
SetChildrenAlongAxis();
|
|
}
|
|
|
|
public override void SetLayoutVertical()
|
|
{
|
|
CalculateRows();
|
|
SetChildrenAlongAxis();
|
|
}
|
|
|
|
private void CalculateRows()
|
|
{
|
|
rows.Clear();
|
|
|
|
float availableWidth = GetAvailableWidth();
|
|
|
|
float currentRowWidth = 0f;
|
|
float currentRowHeight = 0f;
|
|
int currentRowStart = 0;
|
|
bool hasRow = false;
|
|
|
|
for (int i = 0; i < rectChildren.Count; i++)
|
|
{
|
|
RectTransform child = rectChildren[i];
|
|
if (child == null) continue;
|
|
|
|
float childWidth = Mathf.Max(LayoutUtility.GetPreferredSize(child, 0), LayoutUtility.GetMinSize(child, 0), child.rect.width);
|
|
float childHeight = expandChildHeight
|
|
? forcedChildHeight
|
|
: Mathf.Max(LayoutUtility.GetPreferredSize(child, 1), LayoutUtility.GetMinSize(child, 1), child.rect.height);
|
|
|
|
float requiredWidth = hasRow ? currentRowWidth + spacingX + childWidth : childWidth;
|
|
bool shouldWrap = hasRow && requiredWidth > availableWidth;
|
|
|
|
if (shouldWrap)
|
|
{
|
|
rows.Add(new RowInfo
|
|
{
|
|
startIndex = currentRowStart,
|
|
endIndex = i - 1,
|
|
width = currentRowWidth,
|
|
height = currentRowHeight
|
|
});
|
|
|
|
currentRowStart = i;
|
|
currentRowWidth = childWidth;
|
|
currentRowHeight = childHeight;
|
|
}
|
|
else
|
|
{
|
|
currentRowWidth = hasRow ? requiredWidth : childWidth;
|
|
currentRowHeight = Mathf.Max(currentRowHeight, childHeight);
|
|
hasRow = true;
|
|
}
|
|
}
|
|
|
|
if (hasRow)
|
|
{
|
|
rows.Add(new RowInfo
|
|
{
|
|
startIndex = currentRowStart,
|
|
endIndex = rectChildren.Count - 1,
|
|
width = currentRowWidth,
|
|
height = currentRowHeight
|
|
});
|
|
}
|
|
|
|
calculatedPreferredHeight = padding.vertical;
|
|
for (int i = 0; i < rows.Count; i++)
|
|
{
|
|
calculatedPreferredHeight += rows[i].height;
|
|
if (i < rows.Count - 1)
|
|
{
|
|
calculatedPreferredHeight += spacingY;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SetChildrenAlongAxis()
|
|
{
|
|
float availableWidth = GetAvailableWidth();
|
|
float y = padding.top;
|
|
|
|
for (int rowIndex = 0; rowIndex < rows.Count; rowIndex++)
|
|
{
|
|
RowInfo row = rows[rowIndex];
|
|
float startX = GetRowStartX(availableWidth, row.width);
|
|
float x = startX;
|
|
|
|
for (int i = row.startIndex; i <= row.endIndex; i++)
|
|
{
|
|
RectTransform child = rectChildren[i];
|
|
if (child == null) continue;
|
|
|
|
float childWidth = Mathf.Max(LayoutUtility.GetPreferredSize(child, 0), LayoutUtility.GetMinSize(child, 0), child.rect.width);
|
|
float childHeight = expandChildHeight
|
|
? forcedChildHeight
|
|
: Mathf.Max(LayoutUtility.GetPreferredSize(child, 1), LayoutUtility.GetMinSize(child, 1), child.rect.height);
|
|
|
|
float offsetY = GetChildVerticalOffset(row.height, childHeight);
|
|
SetChildAlongAxis(child, 0, x, childWidth);
|
|
SetChildAlongAxis(child, 1, y + offsetY, childHeight);
|
|
x += childWidth + spacingX;
|
|
}
|
|
|
|
y += row.height + spacingY;
|
|
}
|
|
}
|
|
|
|
private float GetRowStartX(float availableWidth, float rowWidth)
|
|
{
|
|
TextAnchor anchor = childAlignment;
|
|
switch (anchor)
|
|
{
|
|
case TextAnchor.UpperCenter:
|
|
case TextAnchor.MiddleCenter:
|
|
case TextAnchor.LowerCenter:
|
|
return padding.left + Mathf.Max(0f, (availableWidth - rowWidth) * 0.5f);
|
|
|
|
case TextAnchor.UpperRight:
|
|
case TextAnchor.MiddleRight:
|
|
case TextAnchor.LowerRight:
|
|
return padding.left + Mathf.Max(0f, availableWidth - rowWidth);
|
|
|
|
default:
|
|
return padding.left;
|
|
}
|
|
}
|
|
|
|
private float GetChildVerticalOffset(float rowHeight, float childHeight)
|
|
{
|
|
TextAnchor anchor = childAlignment;
|
|
switch (anchor)
|
|
{
|
|
case TextAnchor.MiddleLeft:
|
|
case TextAnchor.MiddleCenter:
|
|
case TextAnchor.MiddleRight:
|
|
return Mathf.Max(0f, (rowHeight - childHeight) * 0.5f);
|
|
|
|
case TextAnchor.LowerLeft:
|
|
case TextAnchor.LowerCenter:
|
|
case TextAnchor.LowerRight:
|
|
return Mathf.Max(0f, rowHeight - childHeight);
|
|
|
|
default:
|
|
return 0f;
|
|
}
|
|
}
|
|
|
|
private float GetAvailableWidth()
|
|
{
|
|
float availableWidth = rectTransform.rect.width - padding.horizontal;
|
|
if (availableWidth > 0f)
|
|
{
|
|
return availableWidth;
|
|
}
|
|
|
|
RectTransform parentRect = rectTransform.parent as RectTransform;
|
|
if (parentRect != null)
|
|
{
|
|
availableWidth = parentRect.rect.width - padding.horizontal;
|
|
if (availableWidth > 0f)
|
|
{
|
|
return availableWidth;
|
|
}
|
|
}
|
|
|
|
return float.PositiveInfinity;
|
|
}
|
|
}
|