using UnityEngine; namespace Flexalon { /// To control the size of an object, add a Flexalon Object /// component to it and edit the width, height, or depth properties. [DisallowMultipleComponent, AddComponentMenu("Flexalon/Flexalon Object"), HelpURL("https://www.flexalon.com/docs/flexalonObject")] public class FlexalonObject : FlexalonComponent { /// The fixed size of the object. public Vector3 Size { get => new Vector3(_width, _height, _depth); set { Width = value.x; Height = value.y; Depth = value.z; } } /// The relative size of the object. public Vector3 SizeOfParent { get => new Vector3(_widthOfParent, _heightOfParent, _depthOfParent); set { WidthOfParent = value.x; HeightOfParent = value.y; DepthOfParent = value.z; } } [SerializeField] private SizeType _widthType = SizeType.Component; /// The width type of the object. public SizeType WidthType { get { return _widthType; } set { _widthType = value; MarkDirty(); } } [SerializeField] private float _width = 1; /// The fixed width of the object. public float Width { get { return _width; } set { _width = Mathf.Max(value, 0); _widthType = SizeType.Fixed; MarkDirty(); } } [SerializeField] private float _widthOfParent = 1; /// The relative width of the object. public float WidthOfParent { get { return _widthOfParent; } set { _widthOfParent = Mathf.Max(value, 0); _widthType = SizeType.Fill; MarkDirty(); } } [SerializeField] private SizeType _heightType = SizeType.Component; /// The height type of the object. public SizeType HeightType { get { return _heightType; } set { _heightType = value; MarkDirty(); } } [SerializeField] private float _height = 1; /// The fixed height of the object. public float Height { get { return _height; } set { _height = Mathf.Max(value, 0); _heightType = SizeType.Fixed; MarkDirty(); } } [SerializeField] private float _heightOfParent = 1; /// The relative height of the object. public float HeightOfParent { get { return _heightOfParent; } set { _heightOfParent = Mathf.Max(value, 0); _heightType = SizeType.Fill; MarkDirty(); } } [SerializeField] private SizeType _depthType = SizeType.Component; /// The depth type of the object. public SizeType DepthType { get { return _depthType; } set { _depthType = value; MarkDirty(); } } [SerializeField] private float _depth = 1; /// The fixed depth of the object. public float Depth { get { return _depth; } set { _depth = Mathf.Max(value, 0); _depthType = SizeType.Fixed; MarkDirty(); } } [SerializeField] private float _depthOfParent = 1; /// The relative depth of the object. public float DepthOfParent { get { return _depthOfParent; } set { _depthOfParent = Mathf.Max(value, 0); _depthType = SizeType.Fill; MarkDirty(); } } /// The min fixed size of the object. public Vector3 MinSize { get => new Vector3(_minWidth, _minHeight, _minDepth); set { MinWidth = value.x; MinHeight = value.y; MinDepth = value.z; } } /// The min relative size of the object. public Vector3 MinSizeOfParent { get => new Vector3(_minWidthOfParent, _minHeightOfParent, _minDepthOfParent); set { MinWidthOfParent = value.x; MinHeightOfParent = value.y; MinDepthOfParent = value.z; } } [SerializeField] private MinMaxSizeType _minWidthType = MinMaxSizeType.None; /// The min width type of the object. public MinMaxSizeType MinWidthType { get { return _minWidthType; } set { _minWidthType = value; MarkDirty(); } } [SerializeField] private float _minWidth = 0; /// The min fixed min width of the object. public float MinWidth { get { return _minWidth; } set { _minWidth = Mathf.Max(value, 0); _minWidthType = MinMaxSizeType.Fixed; MarkDirty(); } } [SerializeField] private float _minWidthOfParent = 0; /// The min relative width of the object. public float MinWidthOfParent { get { return _minWidthOfParent; } set { _minWidthOfParent = Mathf.Max(value, 0); _minWidthType = MinMaxSizeType.Fill; MarkDirty(); } } [SerializeField] private MinMaxSizeType _minHeightType = MinMaxSizeType.None; /// The min height type of the object. public MinMaxSizeType MinHeightType { get { return _minHeightType; } set { _minHeightType = value; MarkDirty(); } } [SerializeField] private float _minHeight = 0; /// The min fixed height of the object. public float MinHeight { get { return _minHeight; } set { _minHeight = Mathf.Max(value, 0); _minHeightType = MinMaxSizeType.Fixed; MarkDirty(); } } [SerializeField] private float _minHeightOfParent = 0; /// The min relative height of the object. public float MinHeightOfParent { get { return _minHeightOfParent; } set { _minHeightOfParent = Mathf.Max(value, 0); _minHeightType = MinMaxSizeType.Fill; MarkDirty(); } } [SerializeField] private MinMaxSizeType _minDepthType = MinMaxSizeType.None; /// The min depth type of the object. public MinMaxSizeType MinDepthType { get { return _minDepthType; } set { _minDepthType = value; MarkDirty(); } } [SerializeField] private float _minDepth = 0; /// The min fixed depth of the object. public float MinDepth { get { return _minDepth; } set { _minDepth = Mathf.Max(value, 0); _minDepthType = MinMaxSizeType.Fixed; MarkDirty(); } } [SerializeField] private float _minDepthOfParent = 0; /// The min relative depth of the object. public float MinDepthOfParent { get { return _minDepthOfParent; } set { _minDepthOfParent = Mathf.Max(value, 0); _minDepthType = MinMaxSizeType.Fill; MarkDirty(); } } /// The max fixed size of the object. public Vector3 MaxSize { get => new Vector3(_maxWidth, _maxHeight, _maxDepth); set { MaxWidth = value.x; MaxHeight = value.y; MaxDepth = value.z; } } /// The max relative size of the object. public Vector3 MaxSizeOfParent { get => new Vector3(_maxWidthOfParent, _maxHeightOfParent, _maxDepthOfParent); set { MaxWidthOfParent = value.x; MaxHeightOfParent = value.y; MaxDepthOfParent = value.z; } } [SerializeField] private MinMaxSizeType _maxWidthType = MinMaxSizeType.None; /// The max width type of the object. public MinMaxSizeType MaxWidthType { get { return _maxWidthType; } set { _maxWidthType = value; MarkDirty(); } } [SerializeField] private float _maxWidth = 1; /// The max fixed max width of the object. public float MaxWidth { get { return _maxWidth; } set { _maxWidth = Mathf.Max(value, 0); _maxWidthType = MinMaxSizeType.Fixed; MarkDirty(); } } [SerializeField] private float _maxWidthOfParent = 1; /// The max relative width of the object. public float MaxWidthOfParent { get { return _maxWidthOfParent; } set { _maxWidthOfParent = Mathf.Max(value, 0); _maxWidthType = MinMaxSizeType.Fill; MarkDirty(); } } [SerializeField] private MinMaxSizeType _maxHeightType = MinMaxSizeType.None; /// The max height type of the object. public MinMaxSizeType MaxHeightType { get { return _maxHeightType; } set { _maxHeightType = value; MarkDirty(); } } [SerializeField] private float _maxHeight = 1; /// The max fixed height of the object. public float MaxHeight { get { return _maxHeight; } set { _maxHeight = Mathf.Max(value, 0); _maxHeightType = MinMaxSizeType.Fixed; MarkDirty(); } } [SerializeField] private float _maxHeightOfParent = 1; /// The max relative height of the object. public float MaxHeightOfParent { get { return _maxHeightOfParent; } set { _maxHeightOfParent = Mathf.Max(value, 0); _maxHeightType = MinMaxSizeType.Fill; MarkDirty(); } } [SerializeField] private MinMaxSizeType _maxDepthType = MinMaxSizeType.None; /// The max depth type of the object. public MinMaxSizeType MaxDepthType { get { return _maxDepthType; } set { _maxDepthType = value; MarkDirty(); } } [SerializeField] private float _maxDepth = 1; /// The max fixed depth of the object. public float MaxDepth { get { return _maxDepth; } set { _maxDepth = Mathf.Max(value, 0); _maxDepthType = MinMaxSizeType.Fixed; MarkDirty(); } } [SerializeField] private float _maxDepthOfParent = 1; /// The max relative depth of the object. public float MaxDepthOfParent { get { return _maxDepthOfParent; } set { _maxDepthOfParent = Mathf.Max(value, 0); _maxDepthType = MinMaxSizeType.Fill; MarkDirty(); } } [SerializeField] private Vector3 _offset = Vector3.zero; /// Use offset to add an offset to the final position of the gameObject after layout is complete. public Vector3 Offset { get { return _offset; } set { _offset = value; MarkDirty(); } } [SerializeField] private Vector3 _scale = Vector3.one; /// Use rotation to scale the size of the gameObject before layout runs. /// This will generate a new size to encapsulate the scaled object. public Vector3 Scale { get { return _scale; } set { _scale = value; MarkDirty(); } } [SerializeField] private Quaternion _rotation = Quaternion.identity; /// Use rotation to set the rotation of the gameObject before layout runs. /// This will generate a new size to encapsulate the rotated object. public Quaternion Rotation { get { return _rotation; } set { _rotation = value; MarkDirty(); } } [SerializeField] private float _marginLeft; /// Margin to add additional space around a gameObject. public float MarginLeft { get { return _marginLeft; } set { _marginLeft = value; MarkDirty(); } } [SerializeField] private float _marginRight; /// Margin to add additional space around a gameObject. public float MarginRight { get { return _marginRight; } set { _marginRight = value; MarkDirty(); } } [SerializeField] private float _marginTop; /// Margin to add additional space around a gameObject. public float MarginTop { get { return _marginTop; } set { _marginTop = value; MarkDirty(); } } [SerializeField] private float _marginBottom; /// Margin to add additional space around a gameObject. public float MarginBottom { get { return _marginBottom; } set { _marginBottom = value; MarkDirty(); } } [SerializeField] private float _marginFront; /// Margin to add additional space around a gameObject. public float MarginFront { get { return _marginFront; } set { _marginFront = value; MarkDirty(); } } [SerializeField] private float _marginBack; /// Margin to add additional space around a gameObject. public float MarginBack { get { return _marginBack; } set { _marginBack = value; MarkDirty(); } } /// Margin to add additional space around a gameObject. public Directions Margin { get => new Directions(new float[] { _marginRight, _marginLeft, _marginTop, _marginBottom, _marginBack, _marginFront}); set { _marginRight = value[0]; _marginLeft = value[1]; _marginTop = value[2]; _marginBottom = value[3]; _marginBack = value[4]; _marginFront = value[5]; MarkDirty(); } } [SerializeField] private float _paddingLeft; /// Padding to reduce available space inside a layout. public float PaddingLeft { get { return _paddingLeft; } set { _paddingLeft = value; MarkDirty(); } } [SerializeField] private float _paddingRight; /// Padding to reduce available space inside a layout. public float PaddingRight { get { return _paddingRight; } set { _paddingRight = value; MarkDirty(); } } [SerializeField] private float _paddingTop; /// Padding to reduce available space inside a layout. public float PaddingTop { get { return _paddingTop; } set { _paddingTop = value; MarkDirty(); } } [SerializeField] private float _paddingBottom; /// Padding to reduce available space inside a layout. public float PaddingBottom { get { return _paddingBottom; } set { _paddingBottom = value; MarkDirty(); } } [SerializeField] private float _paddingFront; /// Padding to reduce available space inside a layout. public float PaddingFront { get { return _paddingFront; } set { _paddingFront = value; MarkDirty(); } } [SerializeField] private float _paddingBack; /// Padding to reduce available space inside a layout. public float PaddingBack { get { return _paddingBack; } set { _paddingBack = value; MarkDirty(); } } /// Padding to reduce available space inside a layout. public Directions Padding { get => new Directions(new float[] { _paddingRight, _paddingLeft, _paddingTop, _paddingBottom, _paddingBack, _paddingFront}); set { _paddingRight = value[0]; _paddingLeft = value[1]; _paddingTop = value[2]; _paddingBottom = value[3]; _paddingBack = value[4]; _paddingFront = value[5]; MarkDirty(); } } [SerializeField] private bool _skipLayout; /// Skip layout for this object. public bool SkipLayout { get => _skipLayout; set { _skipLayout = value; MarkDirty(); } } /// protected override void ResetProperties() { _node.SetFlexalonObject(null); } /// protected override void UpdateProperties() { _node.SetFlexalonObject(this); } #if false private Transform _lastParent; /// public override void DoUpdate() { if (Application.isPlaying || Node.Dirty) { return; } // Don't update prefab instances if (UnityEditor.PrefabUtility.IsPartOfPrefabInstance(gameObject)) { return; } var result = _node.Result; // Don't do any of this if the parent changed. if (_lastParent != transform.parent) { _lastParent = transform.parent; result.TargetScale = transform.localScale; result.TransformScale = transform.localScale; result.TargetRotation = transform.localRotation; result.TransformRotation = transform.localRotation; result.TargetPosition = transform.localPosition; result.TransformPosition = transform.localPosition; } // Detect changes to the object's position, rotation, scale, and rect size which may happen // when the developer uses the transform control, enters new values in the // inspector, or various other scenarios. Maintain those edits // by modifying the offset, rotation, and scale on the FlexalonObject. bool isRectTransform = false; if (transform is RectTransform rectTransform) { if (_widthType == SizeType.Fixed) { if (rectTransform.rect.size.x != _width) { // Avoid recording changes here to avoid screen size changes causing edits. _width = rectTransform.rect.size.x; } } if (_heightType == SizeType.Fixed) { if (rectTransform.rect.size.y != _height) { // Avoid recording changes here to avoid screen size changes causing edits. _height = rectTransform.rect.size.y; } } isRectTransform = true; } bool shouldScale = _node.Adapter.TryGetScale(_node, out var s); if (shouldScale && result.TransformScale != transform.localScale) { UnityEditor.Undo.RecordObject(this, "Scale change"); UnityEditor.PrefabUtility.RecordPrefabInstancePropertyModifications(this); UnityEditor.Undo.RecordObject(result, "Scale change"); UnityEditor.PrefabUtility.RecordPrefabInstancePropertyModifications(result); Flexalon.RecordFrameChanges = true; _scale = Math.Mul(Scale, Math.Div(transform.localScale, result.TransformScale)); if (float.IsNaN(_scale.x) || Mathf.Abs(_scale.x) < 1e-5f) _scale.x = 0; if (float.IsNaN(_scale.y) || Mathf.Abs(_scale.y) < 1e-5f) _scale.y = 0; if (float.IsNaN(_scale.z) || Mathf.Abs(_scale.z) < 1e-5f) _scale.z = 0; if (Mathf.Abs(1f - _scale.x) < 1e-5f) _scale.x = 1; if (Mathf.Abs(1f - _scale.y) < 1e-5f) _scale.y = 1; if (Mathf.Abs(1f - _scale.z) < 1e-5f) _scale.z = 1; result.TargetScale = transform.localScale; result.TransformScale = transform.localScale; _node.Parent?.MarkDirty(); if (_node.Constraint != null) { _node.MarkDirty(); } else { _node.ApplyScaleAndRotation(); } // The scale and rect transform controls affect both position and scale, // That's not expected in a layout, so early out here to avoid setting the position. return; } bool inLayoutOrConstraint = (_node.Parent != null && !_node.Parent.Dirty && transform.parent == _node.Parent.GameObject.transform) || (_node.Constraint != null && _node.Constraint.Target != null); if (inLayoutOrConstraint) { if (!isRectTransform && result.TransformPosition != transform.localPosition) { UnityEditor.Undo.RecordObject(this, "Offset change"); UnityEditor.PrefabUtility.RecordPrefabInstancePropertyModifications(this); UnityEditor.Undo.RecordObject(result, "Offset change"); UnityEditor.PrefabUtility.RecordPrefabInstancePropertyModifications(result); if (transform is RectTransform offsetRectTransform) { } if (Node.Constraint != null && Node.Constraint.Target != null) { _offset += Quaternion.Inverse(Node.Constraint.Target.transform.rotation) * (transform.localPosition - result.TransformPosition); } else { _offset += Math.Mul(Node.Parent.Result?.ComponentScale ?? Vector3.one, (transform.localPosition - result.TransformPosition)); } if (float.IsNaN(_offset.x) || Mathf.Abs(_offset.x) < 1e-5f) _offset.x = 0; if (float.IsNaN(_offset.y) || Mathf.Abs(_offset.y) < 1e-5f) _offset.y = 0; if (float.IsNaN(_offset.z) || Mathf.Abs(_offset.z) < 1e-5f) _offset.z = 0; result.TargetPosition = transform.localPosition; result.TransformPosition = transform.localPosition; } if (result.TransformRotation != transform.localRotation) { UnityEditor.Undo.RecordObject(this, "Rotation change"); UnityEditor.PrefabUtility.RecordPrefabInstancePropertyModifications(this); UnityEditor.Undo.RecordObject(result, "Rotation change"); UnityEditor.PrefabUtility.RecordPrefabInstancePropertyModifications(result); Flexalon.RecordFrameChanges = true; if (Node.Constraint != null && Node.Constraint.Target != null) { _rotation = Quaternion.Inverse(Node.Constraint.Target.transform.rotation) * transform.rotation; } else { _rotation *= transform.localRotation * Quaternion.Inverse(result.TransformRotation); } if (float.IsNaN(_rotation.x) || Mathf.Abs(_rotation.x) < 1e-5f) _rotation.x = 0; if (float.IsNaN(_rotation.y) || Mathf.Abs(_rotation.y) < 1e-5f) _rotation.y = 0; if (float.IsNaN(_rotation.z) || Mathf.Abs(_rotation.z) < 1e-5f) _rotation.z = 0; if (float.IsNaN(_rotation.w) || Mathf.Abs(1 - _rotation.w) < 1e-5f) _rotation.w = 1; _rotation.Normalize(); result.TargetRotation = transform.localRotation; result.TransformRotation = transform.localRotation; _node.Parent?.MarkDirty(); if (_node.Constraint != null) { _node.MarkDirty(); } else { _node.ApplyScaleAndRotation(); } } } else { if (result.TransformRotation != transform.localRotation) { UnityEditor.Undo.RecordObject(result, "Rotation change"); UnityEditor.PrefabUtility.RecordPrefabInstancePropertyModifications(result); result.TargetRotation = transform.localRotation; result.TransformRotation = transform.localRotation; _node.ApplyScaleAndRotation(); } } } #endif protected override void Initialize() { base.Initialize(); if (transform is RectTransform || (transform.parent && transform.parent is RectTransform)) { _width = 100; _height = 100; _maxWidth = 100; _maxHeight = 100; } } protected override void Upgrade(int fromVersion) { #if UNITY_UI // UPGRADE FIX: In v4.0 canvas no longer scales to fit layout size. // Instead, scale needs to be set on the FlexalonObject. if (fromVersion < 4 && TryGetComponent(out var canvas)) { _widthType = SizeType.Component; _heightType = SizeType.Component; if (canvas.renderMode == RenderMode.WorldSpace) { _scale = canvas.transform.localScale; _node.Result.AdapterBounds = new Bounds(Vector3.zero, (transform as RectTransform).rect.size); } } #endif } } }