From d2ab30e7a69bfe7efda63ae75812207377917bd3 Mon Sep 17 00:00:00 2001
From: miepzerino <o.skotnik@gmail.com>
Date: Sun, 30 Mar 2025 18:50:27 +0000
Subject: [PATCH] Merge branch 'Flexalon-UI-Layouts' into develop

---
 Assets/Flexalon/Runtime/Layouts/FlexalonGridLayout.cs |  642 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 642 insertions(+), 0 deletions(-)

diff --git a/Assets/Flexalon/Runtime/Layouts/FlexalonGridLayout.cs b/Assets/Flexalon/Runtime/Layouts/FlexalonGridLayout.cs
new file mode 100644
index 0000000..4276513
--- /dev/null
+++ b/Assets/Flexalon/Runtime/Layouts/FlexalonGridLayout.cs
@@ -0,0 +1,642 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace Flexalon
+{
+    /// <summary>
+    /// Use a grid layout to position children at fixed intervals.
+    /// Objects are placed in cells in column-row-layer order.
+    /// </summary>
+    [AddComponentMenu("Flexalon/Flexalon Grid Layout"), HelpURL("https://www.flexalon.com/docs/gridLayout")]
+    public class FlexalonGridLayout : LayoutBase
+    {
+        /// <summary> The type of cell to use on the column-row axes. </summary>
+        public enum CellTypes
+        {
+            /// <summary> A rectangular cell. </summary>
+            Rectangle,
+
+            /// <summary> A hexagonal cell. </summary>
+            Hexagonal
+        }
+
+        [SerializeField]
+        private CellTypes _cellType = CellTypes.Rectangle;
+        /// <summary> The type of cell to use on the column-row axes. </summary>
+        public CellTypes CellType
+        {
+            get { return _cellType; }
+            set { _cellType = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField, Min(1)]
+        private uint _columns = 3;
+        /// <summary> The number of columns in the grid. </summary>
+        public uint Columns
+        {
+            get { return _columns; }
+            set { _columns = System.Math.Max(value, 1); _node.MarkDirty(); }
+        }
+
+        [SerializeField, Min(1)]
+        private uint _rows = 3;
+        /// <summary> The number of rows in the grid. </summary>
+        public uint Rows
+        {
+            get { return _rows; }
+            set { _rows = System.Math.Max(value, 1); _node.MarkDirty(); }
+        }
+
+        [SerializeField, Min(1)]
+        private uint _layers = 1;
+        /// <summary> The number of layers in the grid. </summary>
+        public uint Layers
+        {
+            get { return _layers; }
+            set { _layers = System.Math.Max(value, 1); _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private Direction _columnDirection = Direction.PositiveX;
+        /// <summary> The direction of the column axis. </summary>
+        public Direction ColumnDirection
+        {
+            get { return _columnDirection; }
+            set { _columnDirection = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private Direction _rowDirection = Direction.NegativeY;
+        /// <summary> The direction of the row axis. </summary>
+        public Direction RowDirection
+        {
+            get { return _rowDirection; }
+            set { _rowDirection = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private Direction _layerDirection = Direction.PositiveZ;
+        /// <summary> The direction of the layer axis. </summary>
+        public Direction LayerDirection
+        {
+            get { return _layerDirection; }
+            set { _layerDirection = value; _node.MarkDirty(); }
+        }
+
+        /// <summary> How to determine the size of the cell. </summary>
+        public enum CellSizeTypes
+        {
+            /// <summary> The object size is divided by the number of columns. </summary>
+            Fill,
+
+            /// <summary> The cell size is fixed. </summary>
+            Fixed,
+        }
+
+        [SerializeField]
+        private CellSizeTypes _columnSizeType = CellSizeTypes.Fill;
+        /// <summary> How to determine the size of the columns, </summary>
+        public CellSizeTypes ColumnSizeType
+        {
+            get { return _columnSizeType; }
+            set { _columnSizeType = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField, Min(0)]
+        private float _columnSize = 1.0f;
+        /// <summary> The fixed size of the columns. </summary>
+        public float ColumnSize
+        {
+            get { return _columnSize; }
+            set
+            {
+                _columnSize = Mathf.Max(0, value);
+                _columnSizeType = CellSizeTypes.Fixed;
+                _node.MarkDirty();
+            }
+        }
+
+        [SerializeField]
+        private CellSizeTypes _rowSizeType = CellSizeTypes.Fill;
+        /// <summary> How to determine the size of the rows. </summary>
+        public CellSizeTypes RowSizeType
+        {
+            get { return _rowSizeType; }
+            set { _rowSizeType = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField, Min(0)]
+        private float _rowSize = 1.0f;
+        /// <summary> The fixed size of the rows. </summary>
+        public float RowSize
+        {
+            get { return _rowSize; }
+            set
+            {
+                _rowSize = Mathf.Max(0, value);
+                _rowSizeType = CellSizeTypes.Fixed;
+                _node.MarkDirty();
+            }
+        }
+
+        [SerializeField]
+        private CellSizeTypes _layerSizeType = CellSizeTypes.Fill;
+        /// <summary> How to determine the size of the layers. </summary>
+        public CellSizeTypes LayerSizeType
+        {
+            get { return _layerSizeType; }
+            set { _layerSizeType = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField, Min(0)]
+        private float _layerSize = 1.0f;
+        /// <summary> The fixed size of the layers. </summary>
+        public float LayerSizeSize
+        {
+            get { return _layerSize; }
+            set
+            {
+                _layerSize = Mathf.Max(0, value);
+                _layerSizeType = CellSizeTypes.Fixed;
+                _node.MarkDirty();
+            }
+        }
+
+        [SerializeField]
+        private float _columnSpacing = 0;
+        /// <summary> The spacing between columns. </summary>
+        public float ColumnSpacing
+        {
+            get { return _columnSpacing; }
+            set { _columnSpacing = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private float _rowSpacing = 0;
+        /// <summary> The spacing between rows. </summary>
+        public float RowSpacing
+        {
+            get { return _rowSpacing; }
+            set { _rowSpacing = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private float _layerSpacing = 0;
+        /// <summary> The spacing between layers. </summary>
+        public float LayerSpacing
+        {
+            get { return _layerSpacing; }
+            set { _layerSpacing = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private Align _horizontalAlign = Align.Center;
+        /// <summary> How to align each child in its cell horizontally. </summary>
+        public Align HorizontalAlign
+        {
+            get { return _horizontalAlign; }
+            set { _horizontalAlign = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private Align _verticalAlign = Align.Center;
+        /// <summary> How to align each child in its cell vertically. </summary>
+        public Align VerticalAlign
+        {
+            get { return _verticalAlign; }
+            set { _verticalAlign = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private Align _depthAlign = Align.Center;
+        /// <summary> How to align each child in its cell in depth. </summary>
+        public Align DepthAlign
+        {
+            get { return _depthAlign; }
+            set { _depthAlign = value; _node.MarkDirty(); }
+        }
+
+        [Serializable]
+        private class TransformList
+        {
+            public List<Transform> Items;
+        }
+
+        [Serializable]
+        private class CellDict : FlexalonDict<Vector3Int, TransformList> {}
+
+        [SerializeField, HideInInspector]
+        private CellDict _cellToChildren;
+
+        private Dictionary<Transform, Vector3Int> _childToCell;
+
+        /// <summary> Returns the first child in the cell. </summary>
+        /// <param name="column"> The column of the cell. </param>
+        /// <param name="row"> The row of the cell. </param>
+        /// <param name="layer"> The layer of the cell. </param>
+        /// <returns> The first child in the cell. </returns>
+        public Transform GetChildAt(int column, int row, int layer = 0)
+        {
+            if (_cellToChildren != null)
+            {
+                if (_cellToChildren.TryGetValue(new Vector3Int(column, row, layer), out var children))
+                {
+                    return children.Items[0];
+                }
+            }
+
+            return null;
+        }
+
+        /// <summary> Returns all children in the cell. </summary>
+        /// <param name="column"> The column of the cell. </param>
+        /// <param name="row"> The row of the cell. </param>
+        /// <param name="layer"> The layer of the cell. </param>
+        /// <returns> A list of children in the cell. </returns>
+        public Transform[] GetChildrenAt(int column, int row, int layer = 0)
+        {
+            if (_cellToChildren != null)
+            {
+                if (_cellToChildren.TryGetValue(new Vector3Int(column, row, layer), out var children))
+                {
+                    return children.Items.ToArray();
+                }
+            }
+
+            return new Transform[0];
+        }
+
+        private void SetCell(Vector3Int cell, Transform child)
+        {
+            if (!_cellToChildren.TryGetValue(cell, out var list))
+            {
+                list = new TransformList { Items = new List<Transform>() };
+                _cellToChildren.Add(cell, list);
+            }
+
+            list.Items.Add(child);
+            _childToCell.Add(child, cell);
+        }
+
+        private void UpdateCells()
+        {
+            if (_cellToChildren == null)
+            {
+                _cellToChildren = new CellDict();
+            }
+
+            if (_childToCell == null)
+            {
+                _childToCell = new Dictionary<Transform, Vector3Int>();
+            }
+
+            _cellToChildren.Clear();
+            _childToCell.Clear();
+
+            Vector3Int nextCell = Vector3Int.zero;
+            foreach (var child in Node.Children)
+            {
+                var childTransform = child.GameObject.transform;
+                if (child.GameObject.TryGetComponent<FlexalonGridCell>(out var cellComponent))
+                {
+                    SetCell(cellComponent.Cell, childTransform);
+                }
+                else
+                {
+                    SetCell(nextCell, childTransform);
+                    nextCell[0]++;
+                    if (nextCell[0] >= _columns)
+                    {
+                        nextCell[0] = 0;
+                        nextCell[1]++;
+                        if (nextCell[1] >= _rows)
+                        {
+                            nextCell[1] = 0;
+                            nextCell[2]++;
+                        }
+                    }
+                }
+            }
+        }
+
+        private Vector3 GetCellSize(Vector3Int axes, Vector3 layoutSize)
+        {
+            var cellSize = layoutSize;
+            cellSize[axes[0]] = GetColumnSize(layoutSize[axes[0]]);
+            cellSize[axes[1]] = GetRowSize(layoutSize[axes[1]]);
+            cellSize[axes[2]] = GetLayerSize(layoutSize[axes[2]]);
+            return cellSize;
+        }
+
+        private Vector3 GetGridSize(Vector3Int axes, Vector3 cellSize)
+        {
+            int columnAxis = axes[0];
+            int rowAxis = axes[1];
+            int layerAxis = axes[2];
+
+            Vector3 gridSize = Vector3.zero;
+            if (_cellType == CellTypes.Hexagonal && _rows > 1)
+            {
+                gridSize[rowAxis] = cellSize[rowAxis] + (0.75f * cellSize[rowAxis] + _rowSpacing) * (_rows - 1);
+            }
+            else
+            {
+                gridSize[rowAxis] = cellSize[rowAxis] * _rows + (_rowSpacing * (_rows - 1));
+            }
+
+            gridSize[columnAxis] = cellSize[columnAxis] * _columns + (_columnSpacing * (_columns - 1));
+            if (_cellType == CellTypes.Hexagonal && _rows > 1)
+            {
+                gridSize[columnAxis] += cellSize[columnAxis] * 0.5f;
+            }
+
+            gridSize[layerAxis] = cellSize[layerAxis] * _layers + (_layerSpacing * (_layers - 1));
+            return gridSize;
+        }
+
+        private Vector3Int GetAxes()
+        {
+            var columnAxis = (int) Math.GetAxisFromDirection(_columnDirection);
+            var rowAxis = (int) Math.GetAxisFromDirection(_rowDirection);
+            var layerAxis = (int) Math.GetAxisFromDirection(_layerDirection);
+
+            var otherAxes = Math.GetOtherAxes(columnAxis);
+            if (columnAxis == rowAxis)
+            {
+                rowAxis = (layerAxis == otherAxes.Item1) ? otherAxes.Item2 : otherAxes.Item1;
+            }
+
+            if (columnAxis == layerAxis)
+            {
+                layerAxis = (rowAxis == otherAxes.Item1) ? otherAxes.Item2 : otherAxes.Item1;
+            }
+
+            if (rowAxis == layerAxis)
+            {
+                layerAxis = Math.GetThirdAxis(columnAxis, rowAxis);
+            }
+
+            return new Vector3Int(columnAxis, rowAxis, layerAxis);
+        }
+
+        private CellSizeTypes[] GetCellSizeTypes()
+        {
+            return new CellSizeTypes[3] {
+                _columnSizeType,
+                _rowSizeType,
+                _layerSizeType
+            };
+        }
+
+        /// <inheritdoc />
+        public override Bounds Measure(FlexalonNode node, Vector3 size, Vector3 min, Vector3 max)
+        {
+            FlexalonLog.Log("GridMeasure | Size", node, size, min, max);
+
+            var axes = GetAxes();
+            var cellSize = GetCellSize(axes, size);
+            var sizeTypes = GetCellSizeTypes();
+
+            foreach (var child in node.Children)
+            {
+                var childSize = child.GetMeasureSize(size);
+                for (int i = 0; i < 3; i++)
+                {
+                    if (node.GetSizeType(i) == SizeType.Layout && sizeTypes[i] == CellSizeTypes.Fill)
+                    {
+                        cellSize[i] = Mathf.Max(childSize[i], cellSize[i]);
+                    }
+                }
+            }
+
+            var minCellSize = GetCellSize(axes, min);
+            var maxCellSize = GetCellSize(axes, max);
+            cellSize = Math.Clamp(cellSize, minCellSize, maxCellSize);
+
+            var gridSize = GetGridSize(axes, cellSize);
+            for (int i = 0; i < 3; i++)
+            {
+                if (node.GetSizeType(i) == SizeType.Layout)
+                {
+                    size[i] = gridSize[i];
+                }
+            }
+
+            SetChildrenFillShrinkSize(node, cellSize, size);
+            return new Bounds(Vector3.zero, size);
+        }
+
+        private float GetRowSize(float availableRowSize)
+        {
+            if (_rowSizeType == CellSizeTypes.Fixed)
+            {
+                return _rowSize;
+            }
+
+            if (_cellType == CellTypes.Rectangle)
+            {
+                return (availableRowSize - _rowSpacing * (_rows - 1)) / _rows;
+            }
+            else
+            {
+                return (availableRowSize - _rowSpacing * (_rows - 1)) / (1 + (_rows - 1) * 0.75f);
+            }
+        }
+
+        private float GetColumnSize(float availableColumnSize)
+        {
+            if (_columnSizeType == CellSizeTypes.Fixed)
+            {
+                return _columnSize;
+            }
+
+            if (_cellType == CellTypes.Rectangle)
+            {
+                return (availableColumnSize - _columnSpacing * (_columns - 1)) / _columns;
+            }
+            else
+            {
+                var sz = (availableColumnSize - _columnSpacing * (_columns - 1)) / _columns;
+                if (_rows > 1)
+                {
+                    sz *= _columns / (_columns + 0.5f);
+                }
+
+                return sz;
+            }
+        }
+
+        private float GetLayerSize(float availableColumnSize)
+        {
+            if (_layerSizeType == CellSizeTypes.Fixed)
+            {
+                return _layerSize;
+            }
+
+            return (availableColumnSize - _layerSpacing * (_layers - 1)) / _layers;
+        }
+
+        private Vector3 GetPosition(Vector3Int cell, Vector3Int axes, Vector3 cellSize, Vector3 gridSize)
+        {
+            var columnAxis = axes[0];
+            var rowAxis = axes[1];
+            var layerAxis = axes[2];
+
+            var columnSize = cellSize[axes[0]];
+            var rowSize = cellSize[axes[1]];
+            var layerSize = cellSize[axes[2]];
+
+            var position = -gridSize / 2;
+
+            if (_cellType == CellTypes.Rectangle)
+            {
+                position[rowAxis] += rowSize * cell[1] + _rowSpacing * cell[1] + rowSize / 2;
+                position[columnAxis] += columnSize * cell[0] + _columnSpacing * cell[0] + columnSize / 2;
+            }
+            else
+            {
+                bool rowEven = (cell[1] % 2) == 0;
+                position[axes[1]] += rowSize * 0.75f * cell[1] + _rowSpacing * cell[1] + rowSize / 2;
+                position[columnAxis] += columnSize * cell[0] + columnSize / 2 + _columnSpacing * cell[0] + (rowEven ? 0 : columnSize / 2);
+            }
+
+            position[layerAxis] += layerSize * cell[2] + _layerSpacing * cell[2] + layerSize / 2;
+
+            position[rowAxis] *= Math.GetPositiveFromDirection(_rowDirection);
+            position[columnAxis] *= Math.GetPositiveFromDirection(_columnDirection);
+            position[layerAxis] *= Math.GetPositiveFromDirection(_layerDirection);
+            return position;
+        }
+
+        private void PositionChild(FlexalonNode child, Vector3Int cell, Vector3Int axes, Vector3 cellSize, Vector3 gridSize)
+        {
+            Vector3 position;
+            position = GetPosition(cell, axes, cellSize, gridSize);
+            var aligned = Math.Align(child.GetArrangeSize(), cellSize, _horizontalAlign, _verticalAlign, _depthAlign);
+            child.SetPositionResult(position + aligned);
+        }
+
+        /// <inheritdoc />
+        public override void Arrange(FlexalonNode node, Vector3 layoutSize)
+        {
+            FlexalonLog.Log("GridArrange | LayoutSize", node, layoutSize);
+
+            var axes = GetAxes();
+            var cellSize = GetCellSize(axes, layoutSize);
+            var gridSize = GetGridSize(axes, cellSize);
+
+            UpdateCells();
+
+            foreach (var child in node.Children)
+            {
+                if (_childToCell.TryGetValue(child.GameObject.transform, out var cell))
+                {
+                    PositionChild(child, cell, axes, cellSize, gridSize);
+                }
+            }
+        }
+
+        void OnDrawGizmosSelected()
+        {
+            if (_node != null)
+            {
+                var axes = GetAxes();
+                var sz = _node.Result.AdapterBounds.size - _node.Padding.Size;
+                var cellSize = GetCellSize(axes, sz);
+                var gridSize = GetGridSize(axes, cellSize);
+
+                Gizmos.color = new Color(1, 1, 0, 0.5f);
+                var scale = _node.GetWorldBoxScale(true);
+                Gizmos.matrix = Matrix4x4.TRS(_node.GetWorldBoxPosition(scale, true), transform.rotation, scale);
+
+                for (int r = 0; r < _rows; r++)
+                {
+                    for (int c = 0; c < _columns; c++)
+                    {
+                        for (int l = 0; l < _layers; l++)
+                        {
+                            var position = GetPosition(new Vector3Int(c, r, l), axes, cellSize, gridSize);
+                            if (_cellType == CellTypes.Rectangle)
+                            {
+                                DrawRectangle(position, axes, cellSize);
+                            }
+                            else
+                            {
+                                DrawHexagon(position, axes, cellSize);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        void DrawRectangle(Vector3 position, Vector3Int axes, Vector3 cellSize)
+        {
+            var columnAxis = axes[0];
+            var rowAxis = axes[1];
+
+            var columnSize = cellSize[axes[0]];
+            var rowSize = cellSize[axes[1]];
+
+            var p1 = new Vector3(); // top right
+            p1[rowAxis] = rowSize / 2;
+            p1[columnAxis] = columnSize / 2;
+
+            var p2 = new Vector3(); // bottom right
+            p2[rowAxis] = -rowSize / 2;
+            p2[columnAxis] = columnSize / 2;
+
+            var p3 = new Vector3(); // bottom left
+            p3[rowAxis] = -rowSize / 2;
+            p3[columnAxis] = -columnSize / 2;
+
+            var p4 = new Vector3(); // top left
+            p4[rowAxis] = rowSize / 2;
+            p4[columnAxis] = -columnSize / 2;
+
+            Gizmos.DrawLine(position + p1, position + p2);
+            Gizmos.DrawLine(position + p2, position + p3);
+            Gizmos.DrawLine(position + p3, position + p4);
+            Gizmos.DrawLine(position + p4, position + p1);
+        }
+
+        void DrawHexagon(Vector3 position, Vector3Int axes, Vector3 cellSize)
+        {
+            var columnAxis = axes[0];
+            var rowAxis = axes[1];
+
+            var columnSize = cellSize[axes[0]];
+            var rowSize = cellSize[axes[1]];
+
+            var p1 = new Vector3(); // top
+            p1[rowAxis] = rowSize / 2;
+
+            var p2 = new Vector3(); // top right
+            p2[rowAxis] = rowSize / 4;
+            p2[columnAxis] = columnSize / 2;
+
+            var p3 = new Vector3(); // bottom right
+            p3[rowAxis] = -rowSize / 4;
+            p3[columnAxis] = columnSize / 2;
+
+            var p4 = new Vector3(); // bottom
+            p4[rowAxis] = -rowSize / 2;
+
+            var p5 = new Vector3(); // bottom left
+            p5[rowAxis] = -rowSize / 4;
+            p5[columnAxis] = -columnSize / 2;
+
+            var p6 = new Vector3(); // top left
+            p6[rowAxis] = rowSize / 4;
+            p6[columnAxis] = -columnSize / 2;
+
+            Gizmos.DrawLine(position + p1, position + p2);
+            Gizmos.DrawLine(position + p2, position + p3);
+            Gizmos.DrawLine(position + p3, position + p4);
+            Gizmos.DrawLine(position + p4, position + p5);
+            Gizmos.DrawLine(position + p5, position + p6);
+            Gizmos.DrawLine(position + p6, position + p1);
+        }
+    }
+}
\ No newline at end of file

--
Gitblit v1.9.3