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