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/FlexalonFlexibleLayout.cs |  560 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 560 insertions(+), 0 deletions(-)

diff --git a/Assets/Flexalon/Runtime/Layouts/FlexalonFlexibleLayout.cs b/Assets/Flexalon/Runtime/Layouts/FlexalonFlexibleLayout.cs
new file mode 100644
index 0000000..dd21d93
--- /dev/null
+++ b/Assets/Flexalon/Runtime/Layouts/FlexalonFlexibleLayout.cs
@@ -0,0 +1,560 @@
+using UnityEngine;
+using System.Collections.Generic;
+
+namespace Flexalon
+{
+    /// <summary>
+    /// Use a flexible layout to position children linearly along the x, y, or z axis.
+    /// The sizes of the children are considered so that they are evenly spaced.
+    /// </summary>
+    [AddComponentMenu("Flexalon/Flexalon Flexible Layout"), HelpURL("https://www.flexalon.com/docs/flexibleLayout")]
+    public class FlexalonFlexibleLayout : LayoutBase
+    {
+        /// <summary> Determines how the space between children is distributed. </summary>
+        public enum GapOptions
+        {
+            /// <summary> The Gap/WrapGap property determines the space between children. </summary>
+            Fixed,
+
+            /// <summary> Space is added between children to fill the available space. </summary>
+            SpaceBetween
+        }
+
+        [SerializeField]
+        private Direction _direction = Direction.PositiveX;
+        /// <summary> The direction in which objects are placed, one after the other. </summary>
+        public Direction Direction
+        {
+            get { return _direction; }
+            set { _direction = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private bool _wrap;
+        /// <summary> If set, then the flexible layout will attempt to position children in a line
+        /// along the Direction axis until it runs out of space. Then it will start the next line by
+        /// following the wrap direction. Wrapping will only occur if the size of the Direction axis is
+        /// set to any value other than "Layout". </summary>
+        public bool Wrap
+        {
+            get { return _wrap; }
+            set { _wrap = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private Direction _wrapDirection = Direction.NegativeY;
+        /// <summary> The direction to start a new line when wrapping. </summary>
+        public Direction WrapDirection
+        {
+            get { return _wrapDirection; }
+            set { _wrapDirection = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private Align _horizontalAlign = Align.Center;
+        /// <summary> Determines how the entire layout horizontally aligns to the parent's box. </summary>
+        public Align HorizontalAlign
+        {
+            get { return _horizontalAlign; }
+            set { _horizontalAlign = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private Align _verticalAlign = Align.Center;
+        /// /// <summary> Determines how the entire layout vertically aligns to the parent's box. </summary>
+        public Align VerticalAlign
+        {
+            get { return _verticalAlign; }
+            set { _verticalAlign = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private Align _depthAlign = Align.Center;
+        /// <summary> Determines how the entire layout aligns to the parent's box in depth. </summary>
+        public Align DepthAlign
+        {
+            get { return _depthAlign; }
+            set { _depthAlign = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private Align _horizontalInnerAlign = Align.Center;
+        /// <summary> The inner align property along the Direction axis will change how wrapped lines align
+        /// with each other. The inner align property along the other two axes will change how each object lines
+        /// up with all other objects. </summary>
+        public Align HorizontalInnerAlign
+        {
+            get { return _horizontalInnerAlign; }
+            set { _horizontalInnerAlign = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private Align _verticalInnerAlign = Align.Center;
+        /// <summary> The inner align property along the Direction axis will change how wrapped lines align
+        /// with each other. The inner align property along the other two axes will change how each object lines
+        /// up with all other objects. </summary>
+        public Align VerticalInnerAlign
+        {
+            get { return _verticalInnerAlign; }
+            set { _verticalInnerAlign = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private Align _depthInnerAlign = Align.Center;
+        /// <summary> The inner align property along the Direction axis will change how wrapped lines align
+        /// with each other. The inner align property along the other two axes will change how each object lines
+        /// up with all other objects. </summary>
+        public Align DepthInnerAlign
+        {
+            get { return _depthInnerAlign; }
+            set { _depthInnerAlign = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private GapOptions _gapType = GapOptions.Fixed;
+        /// <summary> Determines how the space between children is distributed. </summary>
+        public GapOptions GapType
+        {
+            get { return _gapType; }
+            set { _gapType = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private float _gap;
+        /// <summary> Adds a gap between objects on the Direction axis. </summary>
+        public float Gap
+        {
+            get { return _gap; }
+            set
+            {
+                _gap = value;
+                _gapType = GapOptions.Fixed;
+                _node.MarkDirty();
+            }
+        }
+
+        [SerializeField]
+        private GapOptions _wrapGapType = GapOptions.Fixed;
+        /// <summary> Determines how the space between lines is distributed. </summary>
+        public GapOptions WrapGapType
+        {
+            get { return _wrapGapType; }
+            set { _wrapGapType = value; _node.MarkDirty(); }
+        }
+
+        [SerializeField]
+        private float _wrapGap;
+        /// <summary> Adds a gap between objects on the Wrap Direction axis. </summary>
+        public float WrapGap
+        {
+            get { return _wrapGap; }
+            set
+            {
+                _wrapGap = value;
+                _wrapGapType = GapOptions.Fixed;
+                _node.MarkDirty();
+            }
+        }
+
+        private class Line
+        {
+            public Vector3 Size = Vector3.zero;
+            public Vector3 Position = Vector3.zero;
+            public List<FlexalonNode> Children = new List<FlexalonNode>();
+            public List<Vector3> ChildSizes = new List<Vector3>();
+            public List<Vector3> ChildPositions = new List<Vector3>();
+        }
+
+        private List<Line> _lines = new List<Line>();
+        private List<FlexItem> _flexItems = new List<FlexItem>();
+
+        private void CreateLines(FlexalonNode node, int flexAxis, int wrapAxis, int thirdAxis, bool wrap, Vector3 size, float maxLineSize, bool measure)
+        {
+            _lines.Clear();
+            if (node.Children.Count == 0)
+            {
+                return;
+            }
+
+            // Divide children into lines considering: size, child sizes.
+            var line = new Line();
+            _lines.Add(line);
+            bool addGap = false;
+            int i = 0;
+            foreach (var child in node.Children)
+            {
+                var gap = (addGap && _gapType == GapOptions.Fixed ? _gap : 0);
+                var childSize = measure ? child.GetMeasureSize(size) : child.GetArrangeSize();
+                if (line.ChildSizes.Count > 0 && wrap &&
+                    line.Size[flexAxis] + childSize[flexAxis] + gap > maxLineSize)
+                {
+                    line = new Line();
+                    _lines.Add(line);
+                    addGap = false;
+                    gap = 0;
+                    i++;
+                }
+
+                FlexalonLog.Log("FlexMeasure | Add child to line", child, i);
+                FlexalonLog.Log("FlexMeasure | Child Size", child, childSize);
+                line.ChildSizes.Add(childSize);
+                line.Size[flexAxis] += childSize[flexAxis] + gap;
+                line.Size[wrapAxis] = Mathf.Max(line.Size[wrapAxis], childSize[wrapAxis]);
+                line.Size[thirdAxis] = Mathf.Max(line.Size[thirdAxis], childSize[thirdAxis]);
+                line.Children.Add(child);
+                addGap = true;
+            }
+        }
+
+        private Vector3 MeasureTotalLineSize(bool wrap, int flexAxis, int wrapAxis, int thirdAxis)
+        {
+            Vector3 layoutSize = Vector3.zero;
+            foreach (var line in _lines)
+            {
+                if (wrap)
+                {
+                    layoutSize[flexAxis] = Mathf.Max(layoutSize[flexAxis], line.Size[flexAxis]);
+                    layoutSize[wrapAxis] += line.Size[wrapAxis];
+                    layoutSize[thirdAxis] = Mathf.Max(layoutSize[thirdAxis], line.Size[thirdAxis]);
+                }
+                else
+                {
+                    for (int axis = 0; axis < 3; axis++)
+                    {
+                        layoutSize[axis] = Mathf.Max(layoutSize[axis], line.Size[axis]);
+                    }
+                }
+            }
+
+            if (wrap && _wrapGapType == GapOptions.Fixed)
+            {
+                layoutSize[wrapAxis] += _wrapGap * (_lines.Count - 1);
+            }
+
+            return layoutSize;
+        }
+
+        private Vector3 MeasureLayoutSize(FlexalonNode node, bool wrap, int flexAxis, int wrapAxis, int thirdAxis, Vector3 size, Vector3 min, Vector3 max)
+        {
+            var layoutSize = MeasureTotalLineSize(wrap, flexAxis, wrapAxis, thirdAxis);
+
+            for (int axis = 0; axis < 3; axis++)
+            {
+                if (node.GetSizeType((Axis)axis) == SizeType.Layout)
+                {
+                    layoutSize[axis] = Mathf.Clamp(layoutSize[axis], min[axis], max[axis]);
+                }
+                else
+                {
+                    layoutSize[axis] = size[axis];
+                }
+            }
+
+            return layoutSize;
+        }
+
+        private void SetChildSize(Line line, int index, int axis, float size, float layoutSize)
+        {
+            var childSize = line.ChildSizes[index];
+            line.Children[index].SetShrinkFillSize(axis, size, layoutSize, true);
+            childSize[axis] = size;
+            line.ChildSizes[index] = childSize;
+        }
+
+        private void FillFlexAxis(float size, int flexAxis)
+        {
+            var gap = _gapType == GapOptions.Fixed ? _gap : 0;
+
+            foreach (var line in _lines)
+            {
+                var remainingSpace = size - line.Size[flexAxis];
+                if (Mathf.Abs(remainingSpace) <= 1e-6f)
+                {
+                    continue;
+                }
+
+                _flexItems.Clear();
+                for (int i = 0; i < line.Children.Count; i++)
+                {
+                    _flexItems.Add(Flex.CreateFlexItem(
+                        line.Children[i], flexAxis, line.ChildSizes[i][flexAxis], line.Size[flexAxis], size));
+                }
+
+                Flex.GrowOrShrink(_flexItems, line.Size[flexAxis], size, gap);
+
+                for (int i = 0; i < line.Children.Count; i++)
+                {
+                    SetChildSize(line, i, flexAxis, _flexItems[i].FinalSize, size);
+                }
+            }
+        }
+
+        private void FillWrapAxis(float size, int wrapAxis)
+        {
+            _flexItems.Clear();
+            float remainingSpace = size;
+            var gap = _wrapGapType == GapOptions.Fixed ? _wrapGap : 0;
+
+            foreach (var line in _lines)
+            {
+                var item = new FlexItem();
+                item.StartSize = line.Size[wrapAxis];
+                item.MaxSize = line.Children[0].GetMaxSize(wrapAxis, size);
+                item.ShrinkFactor = line.Size[wrapAxis] / size;
+                item.FinalSize = line.Size[wrapAxis];
+
+                for (int i = 0; i < line.Children.Count; i++)
+                {
+                    var child = line.Children[i];
+                    if (child.CanShrink(wrapAxis))
+                    {
+                        item.MinSize = Mathf.Max(child.GetMinSize(wrapAxis, size), item.MinSize);
+                    }
+                    else
+                    {
+                        item.MinSize = Mathf.Max(line.ChildSizes[i][wrapAxis], item.MinSize);
+                    }
+
+                    item.MaxSize = Mathf.Max(child.GetMaxSize(wrapAxis, size), item.MaxSize);
+
+                    if (child.GetSizeType(wrapAxis) == SizeType.Fill)
+                    {
+                        item.GrowFactor = Mathf.Max(child.SizeOfParent[wrapAxis], item.GrowFactor);
+                    }
+                }
+
+                remainingSpace -= line.Size[wrapAxis];
+                _flexItems.Add(item);
+            }
+
+            if (Mathf.Abs(remainingSpace) > 1e-6)
+            {
+                Flex.GrowOrShrink(_flexItems, size - remainingSpace, size, gap);
+            }
+
+            for (int l = 0; l < _lines.Count; l++)
+            {
+                var line = _lines[l];
+                var item = _flexItems[l];
+
+                for (int i = 0; i < line.Children.Count; i++)
+                {
+                    var child = line.Children[i];
+                    if (child.GetSizeType(wrapAxis) == SizeType.Fill)
+                    {
+                        var newSize = child.SizeOfParent[wrapAxis] * size;
+                        var minSize = child.GetMinSize(wrapAxis, size);
+                        var maxSize = child.GetMaxSize(wrapAxis, size);
+                        maxSize = Mathf.Min(maxSize, item.FinalSize);
+                        newSize = Mathf.Clamp(newSize, minSize, maxSize);
+                        SetChildSize(line, i, wrapAxis, newSize, size);
+                    }
+                    else
+                    {
+                        SetChildSize(line, i, wrapAxis, item.FinalSize, size);
+                    }
+                }
+            }
+        }
+
+        private void FillThirdAxis(float size, int thirdAxis)
+        {
+            foreach (var child in _node.Children)
+            {
+                child.SetShrinkFillSize(thirdAxis, size, size);
+            }
+        }
+
+        private void UpdateFillSizes(Vector3 size, int flexAxis, int wrapAxis, int thirdAxis)
+        {
+            FillFlexAxis(size[flexAxis], flexAxis);
+            FillWrapAxis(size[wrapAxis], wrapAxis);
+            FillThirdAxis(size[thirdAxis], thirdAxis);
+        }
+
+        /// <inheritdoc />
+        public override Bounds Measure(FlexalonNode node, Vector3 size, Vector3 min, Vector3 max)
+        {
+            FlexalonLog.Log("FlexMeasure | Size", node, size);
+
+            // Gather useful data
+            var flexAxis = (int) Math.GetAxisFromDirection(_direction);
+            var otherAxes = Math.GetOtherAxes(flexAxis);
+            bool childrenSizeFlexAxis = node.GetSizeType(flexAxis) == SizeType.Layout;
+            var wrapAxis = (int) Math.GetAxisFromDirection(_wrapDirection);
+            if (wrapAxis == flexAxis)
+            {
+                wrapAxis = otherAxes.Item1;
+            }
+
+            var thirdAxis = (wrapAxis == otherAxes.Item1 ? otherAxes.Item2 : otherAxes.Item1);
+            bool wrap = (flexAxis != wrapAxis) && _wrap;
+            var maxLineSize = childrenSizeFlexAxis ? max[flexAxis] : size[flexAxis];
+
+            FlexalonLog.Log("FlexMeasure | Flex Axis", node,  flexAxis);
+            FlexalonLog.Log("FlexMeasure | Wrap Axis", node,  wrapAxis);
+            FlexalonLog.Log("FlexMeasure | Third Axis", node,  thirdAxis);
+            FlexalonLog.Log("FlexMeasure | Wrap", node, wrap);
+
+            CreateLines(node, flexAxis, wrapAxis, thirdAxis, wrap, size, maxLineSize, true);
+            for (int i = 0; i < _lines.Count; i++)
+            {
+                FlexalonLog.Log("FlexMeasure | Line size " + i + " " + _lines[i].Size);
+            }
+
+            Vector3 layoutSize = MeasureLayoutSize(node, wrap, flexAxis, wrapAxis, thirdAxis, size, min, max);
+            FlexalonLog.Log("FlexMeasure | Total Layout Size", node, layoutSize);
+
+            UpdateFillSizes(layoutSize, flexAxis, wrapAxis, thirdAxis);
+
+            return new Bounds(Vector3.zero, layoutSize);
+        }
+
+        /// <inheritdoc />
+        public override void Arrange(FlexalonNode node, Vector3 layoutSize)
+        {
+            FlexalonLog.Log("FlexArrange | LayoutSize", node, layoutSize);
+
+            // Gather useful data
+            var flexAxis = (int) Math.GetAxisFromDirection(_direction);
+            bool childrenSizeFlexAxis = node.GetSizeType(flexAxis) == SizeType.Layout;
+            var otherAxes = Math.GetOtherAxes(flexAxis);
+            var wrapAxis = (int) Math.GetAxisFromDirection(_wrapDirection);
+            if (wrapAxis == flexAxis)
+            {
+                wrapAxis = otherAxes.Item1;
+            }
+
+            var thirdAxis = (wrapAxis == otherAxes.Item1 ? otherAxes.Item2 : otherAxes.Item1);
+            bool wrap = (flexAxis != wrapAxis) && _wrap;
+            var flexDirection = Math.GetPositiveFromDirection(_direction);
+            var wrapDirection = Math.GetPositiveFromDirection(_wrapDirection);
+            var align = new Align[] { _horizontalAlign, _verticalAlign, _depthAlign };
+            var innerAlign = new Align[] { _horizontalInnerAlign, _verticalInnerAlign, _depthInnerAlign };
+
+            FlexalonLog.Log("FlexArrange | Flex Direction", node, _direction);
+            FlexalonLog.Log("FlexArrange | Wrap Direction", node, _wrapDirection);
+            FlexalonLog.Log("FlexArrange | Third Axis", node, thirdAxis);
+            FlexalonLog.Log("FlexArrange | Wrap", node, wrap);
+
+            CreateLines(node, flexAxis, wrapAxis, thirdAxis, wrap, layoutSize, layoutSize[flexAxis], false);
+
+            // Position children within _lines. Consider: line size, child size, flexInnerAlign
+            {
+                foreach (var line in _lines)
+                {
+                    float lineGap = 0;
+                    if (line.Children.Count > 1)
+                    {
+                        switch (_gapType)
+                        {
+                            case GapOptions.Fixed:
+                                lineGap = _gap;
+                                break;
+                            case GapOptions.SpaceBetween:
+                                lineGap = (layoutSize[flexAxis] - line.Size[flexAxis]) / (line.Children.Count - 1);
+                                line.Size[flexAxis] = layoutSize[flexAxis];
+                                break;
+                        }
+                    }
+
+                    float nextChildPosition = flexDirection * -line.Size[flexAxis] / 2;
+                    foreach (var childSize in line.ChildSizes)
+                    {
+                        Vector3 childPosition = Vector3.zero;
+                        childPosition[flexAxis] = nextChildPosition + flexDirection * childSize[flexAxis] / 2;
+                        childPosition[otherAxes.Item1] = Math.Align(
+                            childSize, line.Size, otherAxes.Item1, innerAlign[otherAxes.Item1]);
+                        childPosition[otherAxes.Item2] = Math.Align(
+                            childSize, line.Size, otherAxes.Item2, innerAlign[otherAxes.Item2]);
+                        line.ChildPositions.Add(childPosition);
+                        nextChildPosition += flexDirection * (childSize[flexAxis] + lineGap);
+                    }
+                }
+            }
+
+            for (int i = 0; i < _lines.Count; i++)
+            {
+                for (int j = 0; j < _lines[i].ChildPositions.Count; j++)
+                {
+                    FlexalonLog.Log("FlexArrange | Child Size", _lines[i].Children[j], _lines[i].ChildSizes[j]);
+                    FlexalonLog.Log("FlexArrange | Child Position", _lines[i].Children[j], _lines[i].ChildPositions[j]);
+                }
+            }
+
+            Vector3 totalLineSize = MeasureTotalLineSize(wrap, flexAxis, wrapAxis, thirdAxis);
+            FlexalonLog.Log("FlexArrange | Total Line Size", node, totalLineSize);
+
+            // Position lines in total line size, consider: totalLineSize, innerAlign
+            {
+                if (wrap)
+                {
+                    float wrapGap = 0;
+                    if (_lines.Count > 1)
+                    {
+                        switch (_wrapGapType)
+                        {
+                            case GapOptions.Fixed:
+                                wrapGap = _wrapGap;
+                                break;
+                            case GapOptions.SpaceBetween:
+                                wrapGap = (layoutSize[wrapAxis] - totalLineSize[wrapAxis]) / (_lines.Count - 1);
+                                totalLineSize[wrapAxis] = layoutSize[wrapAxis];
+                                break;
+                        }
+                    }
+
+                    float nextLinePosition = wrapDirection * -totalLineSize[wrapAxis] / 2;
+                    foreach (var line in _lines)
+                    {
+                        line.Position[wrapAxis] = nextLinePosition + wrapDirection * line.Size[wrapAxis] / 2;
+                        line.Position[flexAxis] = Math.Align(
+                            line.Size, totalLineSize, flexAxis, innerAlign[flexAxis]);
+                        line.Position[thirdAxis] = Math.Align(
+                            line.Size, totalLineSize, thirdAxis, innerAlign[thirdAxis]);
+                        nextLinePosition += wrapDirection * line.Size[wrapAxis] + wrapGap * wrapDirection;
+                    }
+                }
+                else
+                {
+                    for (int axis = 0; axis < 3; axis++)
+                    {
+                        _lines[0].Position[axis] = Math.Align(
+                            _lines[0].Size, totalLineSize, axis, innerAlign[axis]);
+                    }
+                }
+            }
+
+            for (int i = 0; i < _lines.Count; i++)
+            {
+                FlexalonLog.Log("FlexArrange | Line position " + i + " " + _lines[i].Position);
+            }
+
+            // Align the total line size within the size
+            Vector3 alignOffset = Vector3.zero;
+            for (int axis = 0; axis < 3; axis++)
+            {
+                alignOffset[axis] = Math.Align(totalLineSize, layoutSize, axis, align[axis]);
+            }
+
+            FlexalonLog.Log("FlexArrange | alignOffset", node, alignOffset);
+
+            // Assign final child positions
+            int childIndex = 0;
+            foreach (var line in _lines)
+            {
+                foreach (var childPosition in line.ChildPositions)
+                {
+                    var child = node.Children[childIndex];
+                    var result = alignOffset + line.Position + childPosition;
+                    child.SetPositionResult(result);
+                    child.SetRotationResult(Quaternion.identity);
+                    FlexalonLog.Log("FlexArrange | FinalChildPosition", child, result);
+                    childIndex++;
+                }
+            }
+
+            _lines.Clear();
+        }
+    }
+}
\ No newline at end of file

--
Gitblit v1.9.3