diff --git a/Runtime/Scripts/Primitives/UISquircle.cs b/Runtime/Scripts/Primitives/UISquircle.cs index 507716c..c950a31 100644 --- a/Runtime/Scripts/Primitives/UISquircle.cs +++ b/Runtime/Scripts/Primitives/UISquircle.cs @@ -1,16 +1,14 @@ -/// Credit Soprachev Andrei +/// Credit Soprachev Andrei and Shikhar Srivastava using System.Collections.Generic; -using System.Linq; using UnityEditor; - namespace UnityEngine.UI.Extensions { [AddComponentMenu("UI/Extensions/Primitives/Squircle")] - public class UISquircle : UIPrimitiveBase + public class UISquircle : MaskableGraphic { - const float C = 1.0f; + const float c = 1.0f; public enum Type { Classic, @@ -19,14 +17,19 @@ namespace UnityEngine.UI.Extensions [Space] public Type squircleType = Type.Scaled; - [Range(1, 40)] - public float n = 4; + [Range(0, 64)] + public float segments = 4; [Min(0.1f)] public float delta = 1f; + [Range(0.1f, 40f)] public float quality = 0.1f; [Min(0)] public float radius = 32; - public Corners corners; + + [HideInInspector] public bool fillCenter = true; + [HideInInspector] public float borderWidth = 9f; + + [HideInInspector] public Corners corners; private float a, b; private List vert = new List(); @@ -34,9 +37,9 @@ namespace UnityEngine.UI.Extensions private float SquircleFunc(float t, bool xByY) { if (xByY) - return (float)System.Math.Pow(C - System.Math.Pow(t / a, n), 1f / n) * b; + return (float)System.Math.Pow(c - System.Math.Pow(t / a, segments), 1f / segments) * b; - return (float)System.Math.Pow(C - System.Math.Pow(t / b, n), 1f / n) * a; + return (float)System.Math.Pow(c - System.Math.Pow(t / b, segments), 1f / segments) * a; } protected override void OnPopulateMesh(VertexHelper vh) @@ -44,28 +47,25 @@ namespace UnityEngine.UI.Extensions float dx = 0; float dy = 0; - float width = rectTransform.rect.width / 2; - float height = rectTransform.rect.height / 2; + float quadrantWidth = rectTransform.rect.width / 2; + float quadrantHeight = rectTransform.rect.height / 2; - // Adjust vertices based on pivot offset - Vector2 pivotOffset = new Vector2(rectTransform.pivot.x - 0.5f, rectTransform.pivot.y - 0.5f); - float pivotOffsetX = pivotOffset.x * rectTransform.rect.width; - float pivotOffsetXTimesTwo = pivotOffsetX * 2; - float pivotOffsetY = pivotOffset.y * rectTransform.rect.height; - float pivotOffsetYTimesTwo = pivotOffsetY * 2; + Vector2 pivotOffset = new Vector2((rectTransform.pivot.x - 0.5f) * rectTransform.rect.width, (rectTransform.pivot.y - 0.5f) * rectTransform.rect.height); + Vector2 pivotOffsetTimesTwo = pivotOffset * 2; + Vector2 centerPoint = new Vector2(-pivotOffset.x, -pivotOffset.y); if (squircleType == Type.Classic) { - a = width; - b = height; + a = quadrantWidth; + b = quadrantHeight; } else { - a = Mathf.Min(width, height, radius); + a = Mathf.Min(quadrantWidth, quadrantHeight, radius); b = a; - dx = width - a; - dy = height - a; + dx = quadrantWidth - a; + dy = quadrantHeight - a; } float x = 0; @@ -74,17 +74,17 @@ namespace UnityEngine.UI.Extensions //create curved vert 1st quadrant List _topRightCurvedVert = new List { - new Vector2(-pivotOffsetX, height - pivotOffsetY) + new Vector2(-pivotOffset.x, quadrantHeight - pivotOffset.y) }; while (x < y) { y = SquircleFunc(x, true); - _topRightCurvedVert.Add(new Vector2(dx + x - pivotOffsetX, dy + y - pivotOffsetY)); + _topRightCurvedVert.Add(new Vector2(dx + x - pivotOffset.x, dy + y - pivotOffset.y)); x += delta; } - if (float.IsNaN(_topRightCurvedVert.Last().y)) + if (float.IsNaN(_topRightCurvedVert[_topRightCurvedVert.Count - 1].y)) { _topRightCurvedVert.RemoveAt(_topRightCurvedVert.Count - 1); } @@ -92,15 +92,13 @@ namespace UnityEngine.UI.Extensions while (y > 0) { x = SquircleFunc(y, false); - _topRightCurvedVert.Add(new Vector2(dx + x - pivotOffsetX, dy + y - pivotOffsetY)); + _topRightCurvedVert.Add(new Vector2(dx + x - pivotOffset.x, dy + y - pivotOffset.y)); y -= delta; } - _topRightCurvedVert.Add(new Vector2(width - pivotOffsetX, -pivotOffsetY)); - for (int i = 1; i < _topRightCurvedVert.Count - 1; i++) { - if (_topRightCurvedVert[i].x < _topRightCurvedVert[i].y) + if (_topRightCurvedVert[i].x + pivotOffset.x < _topRightCurvedVert[i].y + pivotOffset.y) { if (_topRightCurvedVert[i - 1].y - _topRightCurvedVert[i].y < quality) { @@ -118,19 +116,16 @@ namespace UnityEngine.UI.Extensions } } - //create flat vert 1st quadrant List _topRightFlatVert = null; //Atleast one corner flat if (corners.topRight == false || corners.bottomRight == false || corners.bottomLeft == false || corners.topLeft == false) { - _topRightFlatVert = new List { _topRightCurvedVert[0], - new Vector2(width - pivotOffsetX, height - pivotOffsetY), - new Vector2(width - pivotOffsetX, 0 - pivotOffsetY), - new Vector2(0 - pivotOffsetX, 0 - pivotOffsetY) + new Vector2(quadrantWidth - pivotOffset.x, quadrantHeight - pivotOffset.y), + new Vector2(quadrantWidth - pivotOffset.x, - pivotOffset.y), }; } @@ -144,96 +139,160 @@ namespace UnityEngine.UI.Extensions vert.AddRange(_topRightFlatVert); } - - //The .Reverse().Select() operation can be optimized. The given line of code creates a new list of Vector2 by reversing _topRightCurvedVert and applying a transformation to each element. However, the operation involves iterating through each element of _topRightCurvedVert twice (once for reversing and once for selecting), which is inefficient. if (corners.bottomRight) { - //vert.AddRange(_topRightCurvedVert.AsEnumerable().Reverse().Select(t => new Vector2(t.x, -t.y - pivotOffsetYTimesTwo))); for (int i = _topRightCurvedVert.Count - 1; i >= 0; i--) { Vector2 reversedVector = _topRightCurvedVert[i]; - reversedVector.y = -reversedVector.y - pivotOffsetYTimesTwo; - //reversedVector.x = reversedVector.x + pivotOffsetXTimesTwo; + reversedVector.y = -reversedVector.y - pivotOffsetTimesTwo.y; vert.Add(reversedVector); } } else { - //vert.AddRange(_topRightFlatVert.AsEnumerable().Reverse().Select(t => new Vector2(t.x, -t.y - pivotOffsetYTimesTwo))); for (int i = _topRightFlatVert.Count - 1; i >= 0; i--) { Vector2 reversedVector = _topRightFlatVert[i]; - reversedVector.y = -reversedVector.y - pivotOffsetYTimesTwo; + reversedVector.y = -reversedVector.y - pivotOffsetTimesTwo.y; vert.Add(reversedVector); } } - //Reset the vertex pointer to center - vert.Add(new Vector2(-pivotOffsetX, -pivotOffsetY)); - if (corners.bottomLeft) { - //vert.AddRange(_topRightCurvedVert.AsEnumerable().Reverse().Select(t => new Vector2(-t.x - pivotOffsetXTimesTwo, -t.y - pivotOffsetYTimesTwo))); - - for (int i = _topRightCurvedVert.Count - 1; i >= 0; i--) + for (int i = 0; i < _topRightCurvedVert.Count; i++) { Vector2 reversedVector = _topRightCurvedVert[i]; - reversedVector.x = -reversedVector.x - pivotOffsetXTimesTwo; - reversedVector.y = -reversedVector.y - pivotOffsetYTimesTwo; + reversedVector.x = -reversedVector.x - pivotOffsetTimesTwo.x; + reversedVector.y = -reversedVector.y - pivotOffsetTimesTwo.y; vert.Add(reversedVector); } } else { - //vert.AddRange(_topRightFlatVert.AsEnumerable().Reverse().Select(t => new Vector2(-t.x - pivotOffsetXTimesTwo, -t.y - pivotOffsetYTimesTwo))); - for (int i = _topRightFlatVert.Count - 1; i >= 0; i--) + for (int i = 0; i < _topRightFlatVert.Count; i++) { Vector2 reversedVector = _topRightFlatVert[i]; - reversedVector.x = -reversedVector.x - pivotOffsetXTimesTwo; - reversedVector.y = -reversedVector.y - pivotOffsetYTimesTwo; + reversedVector.y = -reversedVector.y - pivotOffsetTimesTwo.y; + reversedVector.x = -reversedVector.x - pivotOffsetTimesTwo.x; vert.Add(reversedVector); } } - //Reset the vertex pointer to center - vert.Add(new Vector2(-pivotOffsetX, -pivotOffsetY)); - if (corners.topLeft) { - //vert.AddRange(_topRightCurvedVert.AsEnumerable().Reverse().Select(t => new Vector2(-t.x - pivotOffsetXTimesTwo, t.y))); for (int i = _topRightCurvedVert.Count - 1; i >= 0; i--) { - Vector2 reversedVector = _topRightCurvedVert[i]; - reversedVector.x = -reversedVector.x - pivotOffsetXTimesTwo; - vert.Add(reversedVector); + Vector2 reversedVector2 = _topRightCurvedVert[i]; + reversedVector2.x = -reversedVector2.x - pivotOffsetTimesTwo.x; + vert.Add(reversedVector2); } } else { - //vert.AddRange(_topRightFlatVert.AsEnumerable().Reverse().Select(t => new Vector2(-t.x - pivotOffsetXTimesTwo, t.y))); for (int i = _topRightFlatVert.Count - 1; i >= 0; i--) { Vector2 reversedVector = _topRightFlatVert[i]; - reversedVector.x = -reversedVector.x - pivotOffsetXTimesTwo; + reversedVector.x = -reversedVector.x - pivotOffsetTimesTwo.x; vert.Add(reversedVector); } } - //vert.AddRange(vert.AsEnumerable().Reverse().Select(t => new Vector2(-t.x - pivotOffsetXTimesTwo, t.y))); - + int predefinedLength = vert.Count * 2; + Vector2[] vertFinal = new Vector2[predefinedLength]; + for (int i = vert.Count - 1; i >= 0; i--) + { + int timesTwo = i * 2; + vertFinal[timesTwo] = vert[i]; + vertFinal[timesTwo + 1] = getInsidePointForAGivenOuterPoint(vert[i], pivotOffset, centerPoint); + } vh.Clear(); - int count = vert.Count - 1; - for (int i = 0; i < count; i++) + for (int i = predefinedLength - 3; i >= 0; i--) { - vh.AddVert(vert[i], color, Vector2.zero); - vh.AddVert(vert[i + 1], color, Vector2.zero); - vh.AddVert(new Vector2(-pivotOffsetX, -pivotOffsetY), color, Vector2.zero); + vh.AddVert(vertFinal[i + 2], color, Vector2.zero); + vh.AddVert(vertFinal[i + 1], color, Vector2.zero); + vh.AddVert(vertFinal[i], color, Vector2.zero); int timesThree = i * 3; vh.AddTriangle(timesThree, timesThree + 1, timesThree + 2); } } + Vector2 getInsidePointForAGivenOuterPoint(Vector2 outerPoint, Vector2 pivotOffset, Vector2 centerPoint) + { + if (fillCenter == false) + { + Vector2 insidePoint; + float insidePointX; + float insidePointY; + if (outerPoint.x > -pivotOffset.x) + { + if (outerPoint.y > -pivotOffset.y) + { + insidePointX = Mathf.Clamp(outerPoint.x - borderWidth, -pivotOffset.x, outerPoint.x); + insidePointY = Mathf.Clamp(outerPoint.y - borderWidth, -pivotOffset.y, outerPoint.y); + } + else if (outerPoint.y < -pivotOffset.y) + { + insidePointX = Mathf.Clamp(outerPoint.x - borderWidth, -pivotOffset.x, outerPoint.x); + insidePointY = Mathf.Clamp(outerPoint.y + borderWidth, outerPoint.y, -pivotOffset.y); + } + else + { + insidePointX = Mathf.Clamp(outerPoint.x - borderWidth, -pivotOffset.x, outerPoint.x); + insidePointY = outerPoint.y; + } + + insidePoint = new Vector2(insidePointX, insidePointY); + } + else if (outerPoint.x < -pivotOffset.x) + { + if (outerPoint.y > -pivotOffset.y) + { + insidePointX = Mathf.Clamp(outerPoint.x + borderWidth, outerPoint.x, -pivotOffset.x); + insidePointY = Mathf.Clamp(outerPoint.y - borderWidth, -pivotOffset.y, outerPoint.y); + } + else if (outerPoint.y < -pivotOffset.y) + { + insidePointX = Mathf.Clamp(outerPoint.x + borderWidth, outerPoint.x, -pivotOffset.x); + insidePointY = Mathf.Clamp(outerPoint.y + borderWidth, outerPoint.y, -pivotOffset.y); + } + else + { + insidePointX = Mathf.Clamp(outerPoint.x + borderWidth, outerPoint.x, -pivotOffset.x); + insidePointY = outerPoint.y; + } + + insidePoint = new Vector2(insidePointX, insidePointY); + } + else + { + if (outerPoint.y > -pivotOffset.y) + { + insidePointX = outerPoint.x; + insidePointY = Mathf.Clamp(outerPoint.y - borderWidth, -pivotOffset.y, outerPoint.y); + } + else if (outerPoint.y < -pivotOffset.y) + { + insidePointX = outerPoint.x; + insidePointY = Mathf.Clamp(outerPoint.y + borderWidth, outerPoint.y, -pivotOffset.y); + } + else + { + insidePointX = outerPoint.x; + insidePointY = outerPoint.y; + } + insidePoint = new Vector2(insidePointX, insidePointY); + } + return insidePoint; + } + else + { + return centerPoint; + } + } + + [System.Serializable] public class Corners { @@ -247,12 +306,62 @@ namespace UnityEngine.UI.Extensions [CustomEditor(typeof(UISquircle))] public class UISquircleEditor : Editor { + SerializedProperty _fillCenter; + SerializedProperty _borderWidth; + private void OnEnable() + { + // This links the _phase SerializedProperty to the according actual field + _fillCenter = serializedObject.FindProperty("fillCenter"); + _borderWidth = serializedObject.FindProperty("borderWidth"); + } + public override void OnInspectorGUI() { + // Draw the default inspector DrawDefaultInspector(); - UISquircle script = (UISquircle)target; + EditorGUILayout.Space(); + + SerializedProperty cornersProp = serializedObject.FindProperty("corners"); + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(" ", GUILayout.Width(60)); // Placeholder for top-left corner + EditorGUILayout.LabelField("Left", GUILayout.Width(60)); + EditorGUILayout.LabelField("Right", GUILayout.Width(60)); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Top", GUILayout.Width(67.5f)); + cornersProp.FindPropertyRelative("topLeft").boolValue = EditorGUILayout.Toggle(cornersProp.FindPropertyRelative("topLeft").boolValue, GUILayout.Width(60)); + cornersProp.FindPropertyRelative("topRight").boolValue = EditorGUILayout.Toggle(cornersProp.FindPropertyRelative("topRight").boolValue, GUILayout.Width(60)); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Bottom", GUILayout.Width(67.5f)); + cornersProp.FindPropertyRelative("bottomLeft").boolValue = EditorGUILayout.Toggle(cornersProp.FindPropertyRelative("bottomLeft").boolValue, GUILayout.Width(60)); + cornersProp.FindPropertyRelative("bottomRight").boolValue = EditorGUILayout.Toggle(cornersProp.FindPropertyRelative("bottomRight").boolValue, GUILayout.Width(60)); + EditorGUILayout.EndHorizontal(); + + // Cast the target object + UISquircle squircle = (UISquircle)target; + + // Get the RectTransform component + RectTransform rectTransform = squircle.GetComponent(); + + // Get the width of the RectTransform + float maxWidth = rectTransform.rect.width; + // Draw the fillCenter property field + _fillCenter.boolValue = EditorGUILayout.Toggle("Fill Center", squircle.fillCenter); + + // If fillCenter is false, draw the borderWidth property field + if (!_fillCenter.boolValue) + { + // Draw the borderWidth property field with a range from 0.1f to maxWidth + _borderWidth.floatValue = EditorGUILayout.Slider("Border Width", squircle.borderWidth, 0.1f, maxWidth / 2f); + } + serializedObject.ApplyModifiedProperties(); GUILayout.Space(10f); - GUILayout.Label("Vertex count: " + script.vert.Count().ToString()); + GUILayout.Label("Vertex count: " + squircle.vert.Count.ToString()); + } } #endif