using UnityEngine; namespace Flexalon { /// /// Adapters determine how Flexalon measures other Unity components. /// See [adapters](/docs/adapters) documentation. /// public interface Adapter { /// Measure the size of this node. /// The node to measure. /// The size set by the Flexalon Object Component. The adapter should update any axis set to SizeType.Component. /// The maximum size, determined by the MinSizeType. /// The maximum size, determined by the MaxSizeType and the parent layout. /// The measured bounds to use in layout. Bounds Measure(FlexalonNode node, Vector3 size, Vector3 min, Vector3 max); /// /// Return what the gameObject's scale should be in local space. /// /// The node to update. /// The desired scale. /// True if the scale should be modified. bool TryGetScale(FlexalonNode node, out Vector3 scale); /// Return what the rect transform size should be. /// The node to update. /// The desired rect size. /// True if the rect size should be modified. 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(out var text)) { _adapter = new TextAdapter(text); } else #endif #if UNITY_UI if (gameObject.TryGetComponent(out var canvas)) { _adapter = new CanvasAdapter(canvas); } else if (gameObject.TryGetComponent(out var image)) { _adapter = new ImageAdapter(image); } else #endif if (gameObject.TryGetComponent(out var rectTransform)) { _adapter = new RectTransformAdapter(rectTransform); } else if (gameObject.TryGetComponent(out var spriteRenderer)) { _adapter = new SpriteRendererAdapter(spriteRenderer); } else if (gameObject.TryGetComponent(out var renderer) && gameObject.TryGetComponent(out var meshFilter) && meshFilter.sharedMesh) { _adapter = new MeshRendererAdapter(renderer, meshFilter); } #if UNITY_PHYSICS else if (gameObject.TryGetComponent(out var collider)) { _adapter = new ColliderAdapter(collider); } #endif #if UNITY_PHYSICS_2D else if (gameObject.TryGetComponent(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 }