diff --git a/Scripts/Layout/TableLayoutGroup.cs b/Scripts/Layout/TableLayoutGroup.cs new file mode 100644 index 0000000..6c44751 --- /dev/null +++ b/Scripts/Layout/TableLayoutGroup.cs @@ -0,0 +1,290 @@ +/// Credit RahulOfTheRamanEffect +/// Sourced from - https://forum.unity3d.com/members/rahuloftheramaneffect.773241/ + +namespace UnityEngine.UI.Extensions +{ + /// + /// Arranges child objects into a non-uniform grid, with fixed column widths and flexible row heights + /// + public class TableLayoutGroup : LayoutGroup + { + public enum Corner + { + UpperLeft = 0, + UpperRight = 1, + LowerLeft = 2, + LowerRight = 3 + } + + [SerializeField] + protected Corner startCorner = Corner.UpperLeft; + /// + /// The corner starting from which the cells should be arranged + /// + public Corner StartCorner + { + get { return startCorner; } + set + { + SetProperty(ref startCorner, value); + } + } + + [SerializeField] + protected float[] columnWidths = new float[1] { 96f }; + /// + /// The widths of all the columns in the table + /// + public float[] ColumnWidths + { + get { return columnWidths; } + set + { + SetProperty(ref columnWidths, value); + } + } + + [SerializeField] + protected float minimumRowHeight = 32f; + /// + /// The minimum height for any row in the table + /// + public float MinimumRowHeight + { + get { return minimumRowHeight; } + set + { + SetProperty(ref minimumRowHeight, value); + } + } + + [SerializeField] + protected bool flexibleRowHeight = true; + /// + /// Expand rows to fit the cell with the highest preferred height? + /// + public bool FlexibleRowHeight + { + get { return flexibleRowHeight; } + set + { + SetProperty(ref flexibleRowHeight, value); + } + } + + [SerializeField] + protected float columnSpacing = 0f; + /// + /// The horizontal spacing between each cell in the table + /// + public float ColumnSpacing + { + get { return columnSpacing; } + set + { + SetProperty(ref columnSpacing, value); + } + } + + [SerializeField] + protected float rowSpacing = 0; + /// + /// The vertical spacing between each row in the table + /// + public float RowSpacing + { + get { return rowSpacing; } + set + { + SetProperty(ref rowSpacing, value); + } + } + + // Temporarily stores data generated during the execution CalculateLayoutInputVertical for use in SetLayoutVertical + private float[] preferredRowHeights; + + public override void CalculateLayoutInputHorizontal() + { + base.CalculateLayoutInputHorizontal(); + + float horizontalSize = padding.horizontal; + + // We calculate the actual cell count for cases where the number of children is lesser than the number of columns + int actualCellCount = Mathf.Min(rectChildren.Count, columnWidths.Length); + + for (int i = 0; i < actualCellCount; i++) + { + horizontalSize += columnWidths[i]; + horizontalSize += columnSpacing; + } + + horizontalSize -= columnSpacing; + + SetLayoutInputForAxis(horizontalSize, horizontalSize, 0, 0); + } + + public override void CalculateLayoutInputVertical() + { + int columnCount = columnWidths.Length; + int rowCount = Mathf.CeilToInt(rectChildren.Count / (float)columnCount); + + preferredRowHeights = new float[rowCount]; + + float totalMinHeight = padding.vertical; + float totalPreferredHeight = padding.vertical; + + if (rowCount > 1) + { + float heightFromSpacing = ((rowCount - 1) * rowSpacing); + totalMinHeight += heightFromSpacing; + totalPreferredHeight += heightFromSpacing; + } + + if (flexibleRowHeight) + { + // If flexibleRowHeight is enabled, find the max value for minimum and preferred heights in each row + + float maxMinimumHeightInRow = 0; + float maxPreferredHeightInRow = 0; + + for (int i = 0; i < rectChildren.Count; i++) + { + int currentRowIndex = i / columnCount; + int currentColumnIndex = i % columnCount; + + // If it's the first cell in the row, reset heights for the row + if (currentColumnIndex == 0) + { + maxMinimumHeightInRow = minimumRowHeight; + maxPreferredHeightInRow = minimumRowHeight; + } + + maxPreferredHeightInRow = Mathf.Max(LayoutUtility.GetPreferredHeight(rectChildren[i]), maxPreferredHeightInRow); + maxMinimumHeightInRow = Mathf.Max(LayoutUtility.GetMinHeight(rectChildren[i]), maxMinimumHeightInRow); + + // If it's the last cell in the row, or if it's the last cell and the row is incomplete, set calculated heights + if (currentColumnIndex == columnCount - 1 || (i == rectChildren.Count - 1 && currentRowIndex == rowCount - 1)) + { + totalMinHeight += maxMinimumHeightInRow; + totalPreferredHeight += maxPreferredHeightInRow; + + // Add calculated row height to a commonly accessible array for reuse in SetLayoutVertical() + preferredRowHeights[currentRowIndex] = maxPreferredHeightInRow; + } + } + } + else + { + // If flexibleRowHeight is disabled, then use the minimumRowHeight to calculate vertical layout information + for (int i = 0; i < rowCount; i++) + preferredRowHeights[i] = minimumRowHeight; + + totalMinHeight += rowCount * minimumRowHeight; + totalPreferredHeight = totalMinHeight; + } + + totalPreferredHeight = Mathf.Max(totalMinHeight, totalPreferredHeight); + SetLayoutInputForAxis(totalMinHeight, totalPreferredHeight, 1, 1); + } + + public override void SetLayoutHorizontal() + { + // If no column width is defined, then assign a reasonable default + if (columnWidths.Length == 0) + columnWidths = new float[1] { 0f }; + + int columnCount = columnWidths.Length; + int cornerX = (int)startCorner % 2; + + float startOffset = 0; + float requiredSizeWithoutPadding = 0; + + // We calculate the actual cell count for cases where the number of children is lesser than the number of columns + int actualCellCount = Mathf.Min(rectChildren.Count, columnWidths.Length); + + for (int i = 0; i < actualCellCount; i++) + { + requiredSizeWithoutPadding += columnWidths[i]; + requiredSizeWithoutPadding += columnSpacing; + } + + requiredSizeWithoutPadding -= columnSpacing; + + startOffset = GetStartOffset(0, requiredSizeWithoutPadding); + + if (cornerX == 1) + startOffset += requiredSizeWithoutPadding; + + float positionX = startOffset; + + for (int i = 0; i < rectChildren.Count; i++) + { + int currentColumnIndex = i % columnCount; + + // If it's the first cell in the row, reset positionX + if (currentColumnIndex == 0) + positionX = startOffset; + + if (cornerX == 1) + positionX -= columnWidths[currentColumnIndex]; + + SetChildAlongAxis(rectChildren[i], 0, positionX, columnWidths[currentColumnIndex]); + + if (cornerX == 1) + positionX -= columnSpacing; + else + positionX += columnWidths[currentColumnIndex] + columnSpacing; + } + } + + public override void SetLayoutVertical() + { + int columnCount = columnWidths.Length; + int rowCount = preferredRowHeights.Length; + + int cornerY = (int)startCorner / 2; + + float startOffset = 0; + float requiredSizeWithoutPadding = 0; + + for (int i = 0; i < rowCount; i++) + { + requiredSizeWithoutPadding += preferredRowHeights[i]; + requiredSizeWithoutPadding += rowSpacing; + } + + requiredSizeWithoutPadding -= rowSpacing; + + startOffset = GetStartOffset(1, requiredSizeWithoutPadding); + + if (cornerY == 1) + startOffset += requiredSizeWithoutPadding; + + float positionY = startOffset; + + for (int i = 0; i < rectChildren.Count; i++) + { + int currentRowIndex = i / columnCount; + int currentColumnIndex = i % columnCount; + + // If it's the first cell in the row and start corner is one of the bottom corners, then modify positionY appropriately + if (currentColumnIndex == 0 && cornerY == 1) + positionY -= preferredRowHeights[currentRowIndex]; + + SetChildAlongAxis(rectChildren[i], 1, positionY, preferredRowHeights[currentRowIndex]); + + // If it's the first last cell in the row, then modify positionY appropriately + if (currentColumnIndex == columnCount - 1) + { + if (cornerY == 1) + positionY -= rowSpacing; + else + positionY += preferredRowHeights[currentRowIndex] + rowSpacing; + } + } + + // Set preferredRowHeights to null to free memory + preferredRowHeights = null; + } + } +} \ No newline at end of file