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

diff --git a/Assets/Flexalon/Runtime/Core/FlexalonAdapter.cs b/Assets/Flexalon/Runtime/Core/FlexalonAdapter.cs
new file mode 100644
index 0000000..e771523
--- /dev/null
+++ b/Assets/Flexalon/Runtime/Core/FlexalonAdapter.cs
@@ -0,0 +1,698 @@
+using UnityEngine;
+
+namespace Flexalon
+{
+    /// <summary>
+    /// Adapters determine how Flexalon measures other Unity components.
+    /// See [adapters](/docs/adapters) documentation.
+    /// </summary>
+    public interface Adapter
+    {
+        /// <summary> Measure the size of this node. </summary>
+        /// <param name="node"> The node to measure. </param>
+        /// <param name="size"> The size set by the Flexalon Object Component. The adapter should update any axis set to SizeType.Component. </param>
+        /// <param name="min"> The maximum size, determined by the MinSizeType. </param>
+        /// <param name="max"> The maximum size, determined by the MaxSizeType and the parent layout. </param>
+        /// <returns> The measured bounds to use in layout. </returns>
+        Bounds Measure(FlexalonNode node, Vector3 size, Vector3 min, Vector3 max);
+
+        /// <summary>
+        /// Return what the gameObject's scale should be in local space.
+        /// </summary>
+        /// <param name="node"> The node to update. </param>
+        /// <param name="scale"> The desired scale. </param>
+        /// <returns> True if the scale should be modified. </returns>
+        bool TryGetScale(FlexalonNode node, out Vector3 scale);
+
+        /// <summary> Return what the rect transform size should be. </summary>
+        /// <param name="node"> The node to update. </param>
+        /// <param name="rectSize"> The desired rect size. </param>
+        /// <returns> True if the rect size should be modified. </returns>
+        bool TryGetRectSize(FlexalonNode node, out Vector2 rectSize);
+    }
+
+    internal interface InternalAdapter : Adapter
+    {
+        bool IsValid();
+        bool SizeChanged();
+    }
+
+    internal class DefaultAdapter : Adapter
+    {
+        private InternalAdapter _adapter;
+
+        public DefaultAdapter(GameObject gameObject)
+        {
+            CheckComponent(gameObject);
+
+            // Prevent detecting a change immediately after creating the adapter.
+            _adapter?.SizeChanged();
+        }
+
+        public bool CheckComponent(GameObject gameObject)
+        {
+            if (_adapter == null)
+            {
+                CreateAdapter(gameObject);
+                return _adapter != null;
+            }
+
+            if (!_adapter.IsValid())
+            {
+                CreateAdapter(gameObject);
+                return true;
+            }
+
+            if (_adapter != null && _adapter.SizeChanged())
+            {
+                return true;
+            }
+
+            return false;
+        }
+
+        public void CreateAdapter(GameObject gameObject)
+        {
+            _adapter = null;
+
+#if UNITY_TMPRO
+            if (gameObject.TryGetComponent<TMPro.TMP_Text>(out var text))
+            {
+                _adapter = new TextAdapter(text);
+            } else
+#endif
+#if UNITY_UI
+            if (gameObject.TryGetComponent<Canvas>(out var canvas))
+            {
+                _adapter = new CanvasAdapter(canvas);
+            }
+            else if (gameObject.TryGetComponent<UnityEngine.UI.Image>(out var image))
+            {
+                _adapter = new ImageAdapter(image);
+            } else
+#endif
+            if (gameObject.TryGetComponent<RectTransform>(out var rectTransform))
+            {
+                _adapter = new RectTransformAdapter(rectTransform);
+            }
+            else if (gameObject.TryGetComponent<SpriteRenderer>(out var spriteRenderer))
+            {
+                _adapter = new SpriteRendererAdapter(spriteRenderer);
+            }
+            else if (gameObject.TryGetComponent<MeshRenderer>(out var renderer) && gameObject.TryGetComponent<MeshFilter>(out var meshFilter) && meshFilter.sharedMesh)
+            {
+                _adapter = new MeshRendererAdapter(renderer, meshFilter);
+            }
+#if UNITY_PHYSICS
+            else if (gameObject.TryGetComponent<Collider>(out var collider))
+            {
+                _adapter = new ColliderAdapter(collider);
+            }
+#endif
+#if UNITY_PHYSICS_2D
+            else if (gameObject.TryGetComponent<Collider2D>(out var collider2d))
+            {
+                _adapter = new Collider2DAdapter(collider2d);
+            }
+#endif
+        }
+
+        public Bounds Measure(FlexalonNode node, Vector3 size, Vector3 min, Vector3 max)
+        {
+            if (_adapter != null)
+            {
+                return _adapter.Measure(node, size, min, max);
+            }
+            else
+            {
+                return new Bounds(Vector3.zero, Math.Clamp(size, min, max));
+            }
+        }
+
+        public bool TryGetScale(FlexalonNode node, out Vector3 scale)
+        {
+            if (_adapter != null)
+            {
+                return _adapter.TryGetScale(node, out scale);
+            }
+            else
+            {
+                scale = Vector3.one;
+                return true;
+            }
+        }
+
+        public bool TryGetRectSize(FlexalonNode node, out Vector2 rectSize)
+        {
+            if (_adapter != null)
+            {
+                return _adapter.TryGetRectSize(node, out rectSize);
+            }
+            else
+            {
+                rectSize = Vector2.zero;
+                return false;
+            }
+        }
+    }
+
+    internal class SpriteRendererAdapter : InternalAdapter
+    {
+        private SpriteRenderer _renderer;
+        private Bounds _lastRendererBounds;
+
+        public SpriteRendererAdapter(SpriteRenderer renderer)
+        {
+            _renderer = renderer;
+        }
+
+        public bool IsValid()
+        {
+            return _renderer;
+        }
+
+        public bool SizeChanged()
+        {
+            var spriteBounds = GetBounds();
+            if (_lastRendererBounds != spriteBounds)
+            {
+                _lastRendererBounds = spriteBounds;
+                return true;
+            }
+
+            return false;
+        }
+
+        private Bounds GetBounds()
+        {
+            return _renderer.sprite?.bounds ?? new Bounds();
+        }
+
+        public Bounds Measure(FlexalonNode node, Vector3 size, Vector3 min, Vector3 max)
+        {
+            return Math.MeasureComponentBounds2D(GetBounds(), node, size, min, max);
+        }
+
+        public bool TryGetScale(FlexalonNode node, out Vector3 scale)
+        {
+            var bounds = GetBounds();
+            if (bounds.size == Vector3.zero) // Invalid bounds
+            {
+                scale = Vector3.one;
+                return true;
+            }
+
+            var r = node.Result;
+            scale = new Vector3(
+                r.AdapterBounds.size.x / bounds.size.x,
+                r.AdapterBounds.size.y / bounds.size.y,
+                1);
+            return true;
+        }
+
+        public bool TryGetRectSize(FlexalonNode node, out Vector2 rectSize)
+        {
+            rectSize = Vector2.zero;
+            return false;
+        }
+    }
+
+    internal class MeshRendererAdapter : InternalAdapter
+    {
+        private MeshRenderer _renderer;
+        private MeshFilter _meshFilter;
+        private Bounds _lastRendererBounds;
+
+        public MeshRendererAdapter(MeshRenderer renderer, MeshFilter meshFilter)
+        {
+            _renderer = renderer;
+            _meshFilter = meshFilter;
+        }
+
+        public bool IsValid()
+        {
+            return _renderer && _meshFilter && _meshFilter.sharedMesh;
+        }
+
+        public bool SizeChanged()
+        {
+            if (_lastRendererBounds != _meshFilter.sharedMesh.bounds)
+            {
+                _lastRendererBounds = _meshFilter.sharedMesh.bounds;
+                return true;
+            }
+
+            return false;
+        }
+
+        public Bounds Measure(FlexalonNode node, Vector3 size, Vector3 min, Vector3 max)
+        {
+            return Math.MeasureComponentBounds(_meshFilter.sharedMesh.bounds, node, size, min, max);
+        }
+
+        public bool TryGetScale(FlexalonNode node, out Vector3 scale)
+        {
+            var bounds = _meshFilter.sharedMesh.bounds;
+            if (bounds.size == Vector3.zero) // Invalid bounds
+            {
+                scale = Vector3.one;
+                return true;
+            }
+
+            var r = node.Result;
+            scale = Math.Div(r.AdapterBounds.size, bounds.size);
+            scale.x = scale.x > 100000f ? 1 : scale.x;
+            scale.y = scale.y > 100000f ? 1 : scale.y;
+            scale.z = scale.z > 100000f ? 1 : scale.z;
+            return true;
+        }
+
+        public bool TryGetRectSize(FlexalonNode node, out Vector2 rectSize)
+        {
+            rectSize = Vector2.zero;
+            return false;
+        }
+    }
+
+    internal class RectTransformAdapter : InternalAdapter
+    {
+        private RectTransform _rectTransform;
+
+        public RectTransformAdapter(RectTransform rectTransform)
+        {
+            _rectTransform = rectTransform;
+        }
+
+        public bool IsValid()
+        {
+            return _rectTransform;
+        }
+
+        public bool SizeChanged()
+        {
+            return false;
+        }
+
+        public Bounds Measure(FlexalonNode node, Vector3 size, Vector3 min, Vector3 max)
+        {
+            bool componentX = node.GetSizeType(Axis.X) == SizeType.Component;
+            bool componentY = node.GetSizeType(Axis.Y) == SizeType.Component;
+            bool componentZ = node.GetSizeType(Axis.Z) == SizeType.Component;
+
+            var measureSize = new Vector3(
+                componentX ? _rectTransform.rect.size.x : size.x,
+                componentY ? _rectTransform.rect.size.y : size.y,
+                componentZ ? 0 : size.z);
+
+            measureSize = Math.Clamp(measureSize, min, max);
+
+            var center = new Vector3((0.5f - _rectTransform.pivot.x) * measureSize.x, (0.5f - _rectTransform.pivot.y) * measureSize.y, 0);
+            return new Bounds(center, measureSize);
+        }
+
+        public bool TryGetScale(FlexalonNode node, out Vector3 scale)
+        {
+            scale = Vector3.one;
+            return true;
+        }
+
+        public bool TryGetRectSize(FlexalonNode node, out Vector2 rectSize)
+        {
+            rectSize = node.Result.AdapterBounds.size;
+            return true;
+        }
+    }
+
+#if UNITY_TMPRO
+    internal class TextAdapter : InternalAdapter, Adapter
+    {
+        private TMPro.TMP_Text _text;
+        private string _lastFont;
+        private TMPro.FontWeight _lastFontWeight;
+        private float _lastFontSize;
+        private TMPro.FontStyles _lastFontStyle;
+        private string _lastText;
+
+        public TextAdapter(TMPro.TMP_Text text)
+        {
+            _text = text;
+        }
+
+        public bool IsValid()
+        {
+            return _text;
+        }
+
+        public bool SizeChanged()
+        {
+            if (_lastFont != _text.font?.ToString() ||
+                _lastFontWeight != _text.fontWeight ||
+                _lastFontSize != _text.fontSize ||
+                _lastFontStyle != _text.fontStyle ||
+                _lastText != _text.text)
+            {
+                _lastFont = _text.font?.ToString();
+                _lastFontWeight = _text.fontWeight;
+                _lastFontSize = _text.fontSize;
+                _lastFontStyle = _text.fontStyle;
+                _lastText = _text.text;
+                return true;
+            }
+
+            return false;
+        }
+
+        public Bounds Measure(FlexalonNode node, Vector3 size, Vector3 min, Vector3 max)
+        {
+            bool componentX = node.GetSizeType(Axis.X) == SizeType.Component;
+            bool componentY = node.GetSizeType(Axis.Y) == SizeType.Component;
+            bool componentZ = node.GetSizeType(Axis.Z) == SizeType.Component;
+
+            size = Math.Clamp(size, min, max);
+
+            if (componentX && componentY)
+            {
+                size = Math.Clamp(_text.GetPreferredValues(max.x, 0), min, max);
+            }
+            else if (componentX && !componentY)
+            {
+                size.x = Mathf.Clamp(_text.GetPreferredValues(0, size.y).x, min.x, max.x);
+            }
+            else if (!componentX && componentY)
+            {
+                size.y = Mathf.Clamp(_text.GetPreferredValues(size.x, 0).y, min.y, max.y);
+            }
+
+            var bounds = new Bounds();
+            bounds.size = size;
+            return bounds;
+        }
+
+        public bool TryGetScale(FlexalonNode node, out Vector3 scale)
+        {
+            scale = Vector3.one;
+            return true;
+        }
+
+        public bool TryGetRectSize(FlexalonNode node, out Vector2 rectSize)
+        {
+            rectSize = node.Result.AdapterBounds.size;
+            return true;
+        }
+    }
+#endif
+
+#if UNITY_UI
+    internal class CanvasAdapter : InternalAdapter
+    {
+        private Canvas _canvas;
+        private RenderMode _lastRenderMode;
+        private float _lastScaleFactor;
+        private RectTransformAdapter _rectTransformAdapter;
+
+        public CanvasAdapter(Canvas canvas)
+        {
+            _canvas = canvas;
+            _rectTransformAdapter = new RectTransformAdapter(canvas.transform as RectTransform);
+        }
+
+        public bool IsValid()
+        {
+            return _canvas;
+        }
+
+        public bool SizeChanged()
+        {
+            bool renderModeChanged = false;
+            if (_lastRenderMode != _canvas.renderMode)
+            {
+                _lastRenderMode = _canvas.renderMode;
+                renderModeChanged = true;
+            }
+
+            bool rectChanged = _rectTransformAdapter.SizeChanged();
+            return renderModeChanged || rectChanged;
+        }
+
+        public Bounds Measure(FlexalonNode node, Vector3 size, Vector3 min, Vector3 max)
+        {
+            if (!_canvas.isRootCanvas || _canvas.renderMode == RenderMode.WorldSpace)
+            {
+                return _rectTransformAdapter.Measure(node, size, min, max);
+            }
+            else
+            {
+                Vector3 canvasSize = (_canvas.transform as RectTransform).rect.size;
+                canvasSize.z = Mathf.Clamp(size.z, min.z, max.z); // Canvas XY size can't be changed
+                return new Bounds(Vector3.zero, canvasSize);
+            }
+        }
+
+        public bool TryGetScale(FlexalonNode node, out Vector3 scale)
+        {
+            if (_canvas.renderMode == RenderMode.WorldSpace)
+            {
+                return _rectTransformAdapter.TryGetScale(node, out scale);
+            }
+
+            scale = Vector3.one;
+            return false;
+        }
+
+        public bool TryGetRectSize(FlexalonNode node, out Vector2 rectSize)
+        {
+            if (_canvas.renderMode == RenderMode.WorldSpace)
+            {
+                return _rectTransformAdapter.TryGetRectSize(node, out rectSize);
+            }
+
+            rectSize = Vector2.zero;
+            return false;
+        }
+    }
+
+    internal class ImageAdapter : InternalAdapter
+    {
+        private UnityEngine.UI.Image _image;
+        private Vector2 _lastImageSize;
+        private bool _lastPreserveAspect;
+        private RectTransformAdapter _rectTransformAdapter;
+
+        public ImageAdapter(UnityEngine.UI.Image image)
+        {
+            _rectTransformAdapter = new RectTransformAdapter(image.transform as RectTransform);
+            _image = image;
+        }
+
+        public bool IsValid()
+        {
+            return _image;
+        }
+
+        public bool SizeChanged()
+        {
+            var spriteSize = Vector2.zero;
+            if (_image.sprite)
+            {
+                spriteSize = _image.sprite.rect.size;
+            }
+
+            bool rectSizeChanged = _rectTransformAdapter.SizeChanged();
+            if (_lastImageSize != spriteSize || _lastPreserveAspect != _image.preserveAspect || rectSizeChanged)
+            {
+                _lastImageSize = spriteSize;
+                _lastPreserveAspect = _image.preserveAspect;
+                return true;
+            }
+
+            return false;
+        }
+
+        public Bounds Measure(FlexalonNode node, Vector3 size, Vector3 min, Vector3 max)
+        {
+            bool componentX = node.GetSizeType(Axis.X) == SizeType.Component;
+            bool componentY = node.GetSizeType(Axis.Y) == SizeType.Component;
+
+            if (!_image.preserveAspect || (componentX && componentY))
+            {
+                return _rectTransformAdapter.Measure(node, size, min, max);
+            }
+
+            var spriteSize = _image.sprite != null ? _image.sprite.rect.size : Vector2.one;
+            return Math.MeasureComponentBounds2D(new Bounds(Vector3.zero, spriteSize), node, size, min, max);
+        }
+
+        public bool TryGetScale(FlexalonNode node, out Vector3 scale)
+        {
+            return _rectTransformAdapter.TryGetScale(node, out scale);
+        }
+
+        public bool TryGetRectSize(FlexalonNode node, out Vector2 rectSize)
+        {
+            return _rectTransformAdapter.TryGetRectSize(node, out rectSize);
+        }
+    }
+#endif
+
+#if UNITY_PHYSICS
+    internal class ColliderAdapter : InternalAdapter
+    {
+        private Collider _collider;
+        private Bounds _lastBounds;
+
+        public ColliderAdapter(Collider collider)
+        {
+            _collider = collider;
+        }
+
+        public bool IsValid()
+        {
+            return _collider;
+        }
+
+        public Bounds GetBounds()
+        {
+            if (_collider is BoxCollider)
+            {
+                var box = _collider as BoxCollider;
+                return new Bounds(box.center, box.size);
+            }
+            else if (_collider is SphereCollider)
+            {
+                var sphere = _collider as SphereCollider;
+                return new Bounds(sphere.center, Vector3.one * sphere.radius * 2);
+            }
+            else if (_collider is CapsuleCollider)
+            {
+                var capsule = _collider as CapsuleCollider;
+                var size = Vector3.one * capsule.radius;
+                size[capsule.direction] = capsule.height;
+                return new Bounds(capsule.center, size);
+            }
+            else if (_collider is MeshCollider)
+            {
+                var mesh = _collider as MeshCollider;
+                return mesh.sharedMesh?.bounds ?? new Bounds(Vector3.zero, Vector3.zero);
+            }
+
+            return new Bounds(Vector3.zero, Vector3.zero);
+        }
+
+        public bool SizeChanged()
+        {
+            var bounds = GetBounds();
+            if (_lastBounds != bounds)
+            {
+                _lastBounds = bounds;
+                return true;
+            }
+
+            return false;
+        }
+
+        public Bounds Measure(FlexalonNode node, Vector3 size, Vector3 min, Vector3 max)
+        {
+            return Math.MeasureComponentBounds(GetBounds(), node, size, min, max);
+        }
+
+        public bool TryGetScale(FlexalonNode node, out Vector3 scale)
+        {
+            var bounds = GetBounds();
+            if (bounds.size == Vector3.zero) // Invalid bounds
+            {
+                scale = Vector3.one;
+                return true;
+            }
+
+            var r = node.Result;
+            scale = Math.Div(r.AdapterBounds.size, bounds.size);
+            return true;
+        }
+
+        public bool TryGetRectSize(FlexalonNode node, out Vector2 rectSize)
+        {
+            rectSize = Vector2.zero;
+            return false;
+        }
+    }
+#endif
+
+#if UNITY_PHYSICS_2D
+    internal class Collider2DAdapter : InternalAdapter
+    {
+        private Collider2D _collider;
+        private Bounds _lastBounds;
+
+        public Collider2DAdapter(Collider2D collider)
+        {
+            _collider = collider;
+        }
+
+        public bool IsValid()
+        {
+            return _collider;
+        }
+
+        public Bounds GetBounds()
+        {
+            if (_collider is BoxCollider2D)
+            {
+                var box = _collider as BoxCollider2D;
+                return new Bounds(box.offset, box.size + Vector2.one * box.edgeRadius);
+            }
+            else if (_collider is CircleCollider2D)
+            {
+                var circle = _collider as CircleCollider2D;
+                return new Bounds(circle.offset, Vector2.one * circle.radius * 2);
+            }
+            else if (_collider is CapsuleCollider2D)
+            {
+                var capsule = _collider as CapsuleCollider2D;
+                return new Bounds(capsule.offset, capsule.size);
+            }
+
+            return new Bounds(Vector3.zero, Vector3.zero);
+        }
+
+        public bool SizeChanged()
+        {
+            var bounds = GetBounds();
+            if (_lastBounds != bounds)
+            {
+                _lastBounds = bounds;
+                return true;
+            }
+
+            return false;
+        }
+
+        public Bounds Measure(FlexalonNode node, Vector3 size, Vector3 min, Vector3 max)
+        {
+            return Math.MeasureComponentBounds2D(GetBounds(), node, size, min, max);
+        }
+
+        public bool TryGetScale(FlexalonNode node, out Vector3 scale)
+        {
+            var bounds = GetBounds();
+            if (bounds.size == Vector3.zero) // Invalid bounds
+            {
+                scale = Vector3.one;
+                return true;
+            }
+
+            var r = node.Result;
+            scale = new Vector3(
+                r.AdapterBounds.size.x / bounds.size.x,
+                r.AdapterBounds.size.y / bounds.size.y,
+                1);
+            return true;
+        }
+
+        public bool TryGetRectSize(FlexalonNode node, out Vector2 rectSize)
+        {
+            rectSize = Vector2.zero;
+            return false;
+        }
+    }
+#endif
+}
\ No newline at end of file

--
Gitblit v1.9.3