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
|
}
|