#if UNITY_6000_0_OR_NEWER using Unity.Properties; #endif using UnityEngine; using UnityEngine.UIElements; namespace Kamgam.UIToolkitBlurredBackground { /// /// The blurred background works by adding an additional mesh on top of the default mesh via OnGenerateVisualContent(). /// #if UNITY_6000_0_OR_NEWER [UxmlElement] #endif public partial class BlurredBackground : VisualElement { public static Color BackgroundColorDefault = new Color(0, 0, 0, 0); #if !UNITY_6000_0_OR_NEWER public new class UxmlFactory : UxmlFactory { } public new class UxmlTraits : VisualElement.UxmlTraits { UxmlFloatAttributeDescription m_BlurStrength = new UxmlFloatAttributeDescription { name = "Blur-Strength", defaultValue = 15f }; UxmlEnumAttributeDescription m_BlurQuality = new UxmlEnumAttributeDescription { name = "Blur-Quality", defaultValue = ShaderQuality.Medium }; UxmlIntAttributeDescription m_BlurIterations = new UxmlIntAttributeDescription { name = "Blur-Iterations", defaultValue = 1 }; UxmlEnumAttributeDescription m_BlurResolution = new UxmlEnumAttributeDescription { name = "Blur-Resolution", defaultValue = SquareResolution._512 }; UxmlColorAttributeDescription m_BlurTint = new UxmlColorAttributeDescription { name = "Blur-Tint", defaultValue = new Color(1f, 1f, 1f, 1f) }; UxmlFloatAttributeDescription m_BlurMeshCornerOverlap = new UxmlFloatAttributeDescription { name = "Blur-Mesh-Corner-Overlap", defaultValue = 0.3f }; UxmlIntAttributeDescription m_BlurMeshCornerSegments = new UxmlIntAttributeDescription { name = "Blur-Mesh-Corner-Segments", defaultValue = 12 }; UxmlColorAttributeDescription m_BackgroundColor = new UxmlColorAttributeDescription { name = "Background-Color", defaultValue = BackgroundColorDefault }; public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc) { base.Init(ve, bag, cc); var bg = ve as BlurredBackground; // Delay in edito to avoid "SendMessage cannot be called during Awake, CheckConsistency, or OnValidate" warnings. #if UNITY_EDITOR UnityEditor.EditorApplication.delayCall += () => { #endif bg.BlurStrength = m_BlurStrength.GetValueFromBag(bag, cc); bg.BlurQuality = m_BlurQuality.GetValueFromBag(bag, cc); bg.BlurIterations = m_BlurIterations.GetValueFromBag(bag, cc); bg.BlurResolution = m_BlurResolution.GetValueFromBag(bag, cc); bg.BlurTint = m_BlurTint.GetValueFromBag(bag, cc); bg.BlurMeshCornerOverlap = m_BlurMeshCornerOverlap.GetValueFromBag(bag, cc); bg.BlurMeshCornerSegments = m_BlurMeshCornerSegments.GetValueFromBag(bag, cc); bg.BackgroundColor = m_BackgroundColor.GetValueFromBag(bag, cc); #if UNITY_EDITOR }; #endif } } #endif // Cache to have a value to return in GET if no style is defined. [System.NonSerialized] private int? _cachedBlurIterations; #if UNITY_6000_0_OR_NEWER [UxmlAttribute("Blur-Iterations")] [CreateProperty] #endif public int BlurIterations { get { if (!_cachedBlurIterations.HasValue) _cachedBlurIterations = BlurManager.Instance.Iterations; return BlurredBackgroundStyles.ResolveStyle(BlurredBackgroundStyles.Iterations, this, _cachedBlurIterations.Value); } set { _cachedBlurIterations = value; int newValue = BlurredBackgroundStyles.ResolveStyle(BlurredBackgroundStyles.Iterations, this, value); if (newValue != BlurManager.Instance.Iterations) { if (newValue < 0) newValue = 0; BlurManager.Instance.Iterations = newValue; MarkDirtyRepaint(); } } } [System.NonSerialized] private float? _cachedBlurStrength; #if UNITY_6000_0_OR_NEWER [UxmlAttribute("Blur-Strength")] [CreateProperty] #endif public float BlurStrength { get { if (!_cachedBlurStrength.HasValue) _cachedBlurStrength = BlurManager.Instance.Offset; return BlurredBackgroundStyles.ResolveStyle(BlurredBackgroundStyles.Strength, this, _cachedBlurStrength.Value); } set { _cachedBlurStrength = value; float newValue = BlurredBackgroundStyles.ResolveStyle(BlurredBackgroundStyles.Strength, this, value); if (newValue != BlurManager.Instance.Offset) { if (newValue < 0f) newValue = 0f; BlurManager.Instance.Offset = newValue; MarkDirtyRepaint(); } } } protected Vector2Int _blurResolutionSize = new Vector2Int(512, 512); public Vector2Int BlurResolutionSize { get { return _blurResolutionSize; } set { if (value != _blurResolutionSize) { if (value.x < 2 || value.y < 2) value = new Vector2Int(2, 2); BlurManager.Instance.Resolution = value; MarkDirtyRepaint(); } } } [System.NonSerialized] private Vector2Int? _cachedBlurResolution; #if UNITY_6000_0_OR_NEWER [UxmlAttribute("Blur-Resolution")] [CreateProperty] #endif public SquareResolution BlurResolution { get { if (!_cachedBlurResolution.HasValue) _cachedBlurResolution = _blurResolutionSize; if (customStyle.TryGetValue(BlurredBackgroundStyles.Resolution, out var width)) { return SquareResolutionsUtils.FromResolution(new Vector2Int(width, width)); } else { return SquareResolutionsUtils.FromResolution(_cachedBlurResolution.Value); } } set { var newResolution = SquareResolutionsUtils.ToResolution(value); _cachedBlurResolution = newResolution; if (customStyle.TryGetValue(BlurredBackgroundStyles.Resolution, out var newWidth)) { BlurResolutionSize = new Vector2Int(newWidth, newWidth); } else { BlurResolutionSize = newResolution; } } } [System.NonSerialized] private ShaderQuality? _cachedShaderQuality; #if UNITY_6000_0_OR_NEWER [UxmlAttribute("Blur-Quality")] [CreateProperty] #endif public ShaderQuality BlurQuality { get { if (!_cachedShaderQuality.HasValue) _cachedShaderQuality = BlurManager.Instance.Quality; if (customStyle.TryGetValue(BlurredBackgroundStyles.Quality, out var qualityString)) { return ShaderQualityTools.FromString(qualityString); } else { return _cachedShaderQuality.Value; } } set { _cachedShaderQuality = value; ShaderQuality newValue = value; if (customStyle.TryGetValue(BlurredBackgroundStyles.Quality, out var qualityString)) { newValue = ShaderQualityTools.FromString(qualityString); } if (newValue != BlurManager.Instance.Quality) { BlurManager.Instance.Quality = value; MarkDirtyRepaint(); } } } protected Color _blurTint = new Color(1f, 1f, 1f, 1f); #if UNITY_6000_0_OR_NEWER [UxmlAttribute("Blur-Tint")] [CreateProperty] #endif public Color BlurTint { get { return BlurredBackgroundStyles.ResolveStyle(BlurredBackgroundStyles.Tint, this, _blurTint); } set { var newValue = BlurredBackgroundStyles.ResolveStyle(BlurredBackgroundStyles.Tint, this, value); if (newValue != _blurTint) { _blurTint = newValue; MarkDirtyRepaint(); } } } protected int _blurMeshCornerSegments = 12; #if UNITY_6000_0_OR_NEWER [UxmlAttribute("Blur-Mesh-Corner-Segments")] [CreateProperty] #endif public int BlurMeshCornerSegments { get { return BlurredBackgroundStyles.ResolveStyle(BlurredBackgroundStyles.MeshCornerSegments, this, _blurMeshCornerSegments); } set { var newValue = BlurredBackgroundStyles.ResolveStyle(BlurredBackgroundStyles.MeshCornerSegments, this, value); if (newValue != _blurMeshCornerSegments) { if (newValue < 1) newValue = 1; _blurMeshCornerSegments = newValue; MarkDirtyRepaint(); } } } protected float _blurMeshCornerOverlap = 0.3f; #if UNITY_6000_0_OR_NEWER [UxmlAttribute("Blur-Mesh-Corner-Overlap")] [CreateProperty] #endif public float BlurMeshCornerOverlap { get { return BlurredBackgroundStyles.ResolveStyle(BlurredBackgroundStyles.MeshCornerOverlap, this, _blurMeshCornerOverlap); } set { var newValue = BlurredBackgroundStyles.ResolveStyle(BlurredBackgroundStyles.MeshCornerOverlap, this, value); if (newValue != _blurMeshCornerOverlap) { if (newValue < 0f) newValue = 0f; _blurMeshCornerOverlap = newValue; MarkDirtyRepaint(); } } } protected Color _defaultBackgroundColor = BackgroundColorDefault; #if UNITY_6000_0_OR_NEWER [UxmlAttribute("Background-Color")] [CreateProperty] #endif public Color BackgroundColor { get { return BlurredBackgroundStyles.ResolveStyle(BlurredBackgroundStyles.BackgroundColor, this, _defaultBackgroundColor); } set { var newValue = BlurredBackgroundStyles.ResolveStyle(BlurredBackgroundStyles.BackgroundColor, this, value); _defaultBackgroundColor = newValue; style.backgroundColor = _defaultBackgroundColor; } } // Mesh Data Vertex[] _vertices; ushort[] _indices; protected VisualElement rootParent; public BlurredBackground() { generateVisualContent = OnGenerateVisualContent; RegisterCallback(attach); RegisterCallback(detach); } void attach(AttachToPanelEvent evt) { #if UNITY_EDITOR UnityEditor.EditorApplication.delayCall += () => { #endif BlurManager.Instance.AttachElement(this); #if UNITY_EDITOR }; #endif } void detach(DetachFromPanelEvent evt) { #if UNITY_EDITOR UnityEditor.EditorApplication.delayCall += () => { #endif BlurManager.Instance.DetachElement(this); #if UNITY_EDITOR }; #endif } public virtual void OnGenerateVisualContent(MeshGenerationContext mgc) { // Remember: "generateVisualContent is an addition to the default rendering, it's not a replacement" // See: https://forum.unity.com/threads/hp-bars-at-runtime-image-masking-or-fill.1076486/#post-6948578 if (BlurManager.Instance == null) return; // If no blur is required then do not even draw the mesh. if (BlurIterations <= 0 || BlurManager.Instance.Offset <= 0f || contentRect.width == 0 || contentRect.height == 0) return; Rect contentRectAbs = contentRect; if (contentRectAbs.width + resolvedStyle.paddingLeft + resolvedStyle.paddingRight < 0.01f || contentRectAbs.height + resolvedStyle.paddingTop + resolvedStyle.paddingBottom < 0.01f) return; // Clamp content if (resolvedStyle.borderLeftWidth < 0) contentRectAbs.xMin -= resolvedStyle.borderLeftWidth; if (resolvedStyle.borderRightWidth < 0) contentRectAbs.xMax += resolvedStyle.borderRightWidth; if (resolvedStyle.borderTopWidth < 0) contentRectAbs.yMin -= resolvedStyle.borderTopWidth; if (resolvedStyle.borderBottomWidth < 0) contentRectAbs.yMax += resolvedStyle.borderBottomWidth; // Mesh generation // clamp to positive float borderLeft = Mathf.Clamp(resolvedStyle.borderLeftWidth, 0, resolvedStyle.width * 0.5f); float borderRight = Mathf.Clamp(resolvedStyle.borderRightWidth, 0, resolvedStyle.width * 0.5f); float borderTop = Mathf.Clamp(resolvedStyle.borderTopWidth, 0, resolvedStyle.height * 0.5f); float borderBottom = Mathf.Clamp(resolvedStyle.borderBottomWidth, 0, resolvedStyle.height * 0.5f); float radiusTopLeft = Mathf.Max(0, resolvedStyle.borderTopLeftRadius); float radiusTopRight = Mathf.Max(0, resolvedStyle.borderTopRightRadius); float radiusBottomLeft = Mathf.Max(0, resolvedStyle.borderBottomLeftRadius); float radiusBottomRight = Mathf.Max(0, resolvedStyle.borderBottomRightRadius); float paddingLeft = Mathf.Max(0, resolvedStyle.paddingLeft); float paddingRight = Mathf.Max(0, resolvedStyle.paddingRight); float paddingTop = Mathf.Max(0, resolvedStyle.paddingTop); float paddingBottom = Mathf.Max(0, resolvedStyle.paddingBottom); contentRectAbs.xMin -= paddingLeft; contentRectAbs.xMax += paddingRight; contentRectAbs.yMin -= paddingTop; contentRectAbs.yMax += paddingBottom; // Calc inner rect // It only starts to curve on the inside once the radius is > the bigger border width Vector2 topLeftCornerSize = new Vector2( Mathf.Clamp(radiusTopLeft - borderLeft, 0, resolvedStyle.width * 0.5f - borderLeft), Mathf.Clamp(radiusTopLeft - borderTop, 0, resolvedStyle.height * 0.5f - borderTop) ); Vector2 topRightCornerSize = new Vector2( Mathf.Clamp(radiusTopRight - borderRight, 0, resolvedStyle.width * 0.5f - borderRight), Mathf.Clamp(radiusTopRight - borderTop, 0, resolvedStyle.height * 0.5f - borderTop) ); Vector2 bottomLeftCornerSize = new Vector2( Mathf.Clamp(radiusBottomLeft - borderLeft, 0, resolvedStyle.width * 0.5f - borderLeft), Mathf.Clamp(radiusBottomLeft - borderBottom, 0, resolvedStyle.height * 0.5f - borderBottom) ); Vector2 bottomRightCornerSize = new Vector2( Mathf.Clamp(radiusBottomRight - borderRight, 0, resolvedStyle.width * 0.5f - borderRight), Mathf.Clamp(radiusBottomRight - borderBottom, 0, resolvedStyle.height * 0.5f - borderBottom) ); // Calc inner quad with corner radius taken into account Vector2 innerTopLeft = new Vector2(contentRectAbs.xMin + topLeftCornerSize.x, contentRectAbs.yMin + topLeftCornerSize.y); Vector2 innerTopRight = new Vector2(contentRectAbs.xMax - topRightCornerSize.x, contentRectAbs.yMin + topRightCornerSize.y); Vector2 innerBottomLeft = new Vector2(contentRectAbs.xMin + bottomLeftCornerSize.x, contentRectAbs.yMax - bottomLeftCornerSize.y); Vector2 innerBottomRight = new Vector2(contentRectAbs.xMax - bottomRightCornerSize.x, contentRectAbs.yMax - bottomRightCornerSize.y); int verticesPerCorner = BlurMeshCornerSegments; // Calc total number of vertices // 4 Vertices for the inner rectangle // + verticesPerCorner + 2 for each full corner // + 1 for a corner with a radius on one side // + 0 vertices for a corner without any border radius int numVertices = 4; // <- start value // Calc total number of indices // 6 Vertices for the inner quad (2 tris) // + (verticesPerCorner + 1) * 3 for each full corner // + 0 for a corner with a radius on one side // + 0 vertices for a corner without any border radius // Sides // + see below int numIndices = 6; // <- start value // Top Left Corner if (topLeftCornerSize.x > 0 && topLeftCornerSize.y > 0) { numVertices += verticesPerCorner + 2; numIndices += (verticesPerCorner + 1) * 3; } else if (topLeftCornerSize.x > 0 || topLeftCornerSize.y > 0) { numVertices += 1; } // Top Right Corner if (topRightCornerSize.x > 0 && topRightCornerSize.y > 0) { numVertices += verticesPerCorner + 2; numIndices += (verticesPerCorner + 1) * 3; } else if (topRightCornerSize.x > 0 || topRightCornerSize.y > 0) { numVertices += 1; } // Bottom Left Corner if (bottomLeftCornerSize.x > 0 && bottomLeftCornerSize.y > 0) { numVertices += verticesPerCorner + 2; numIndices += (verticesPerCorner + 1) * 3; } else if (bottomLeftCornerSize.x > 0 || bottomLeftCornerSize.y > 0) { numVertices += 1; } // Bottom Right Corner if (bottomRightCornerSize.x > 0 && bottomRightCornerSize.y > 0) { numVertices += verticesPerCorner + 2; numIndices += (verticesPerCorner + 1) * 3; } else if (bottomRightCornerSize.x > 0 || bottomRightCornerSize.y > 0) { numVertices += 1; } // Sides (indices) // + 6 for a side where the corners form a rectangle // + 3 for a side where the corners form a triangle // + 0 for a side between two 0 vertex corners // Top if (topLeftCornerSize.y > 0 && topRightCornerSize.y > 0) numIndices += 6; else if (topLeftCornerSize.y > 0 || topRightCornerSize.y > 0) numIndices += 3; // Right if (topRightCornerSize.x > 0 && bottomRightCornerSize.x > 0) numIndices += 6; else if (topRightCornerSize.x > 0 || bottomRightCornerSize.x > 0) numIndices += 3; // Bottom if (bottomRightCornerSize.y > 0 && bottomLeftCornerSize.y > 0) numIndices += 6; else if (bottomRightCornerSize.y > 0 || bottomLeftCornerSize.y > 0) numIndices += 3; // Left if (bottomLeftCornerSize.x > 0 && topLeftCornerSize.x > 0) numIndices += 6; else if (bottomLeftCornerSize.x > 0 || topLeftCornerSize.x > 0) numIndices += 3; if (_vertices == null || _vertices.Length != numVertices) { _vertices = new Vertex[numVertices]; _indices = new ushort[numIndices]; } // keep track of indices ushort v = 0; ushort i = 0; // Center rect ushort innerBottomLeftVertex = v; _vertices[v++].position = new Vector3(innerBottomLeft.x, innerBottomLeft.y, Vertex.nearZ); ushort innerTopLeftVertex = v; _vertices[v++].position = new Vector3(innerTopLeft.x, innerTopLeft.y, Vertex.nearZ); ushort innerTopRightVertex = v; _vertices[v++].position = new Vector3(innerTopRight.x, innerTopRight.y, Vertex.nearZ); ushort innerBottomRightVertex = v; _vertices[v++].position = new Vector3(innerBottomRight.x, innerBottomRight.y, Vertex.nearZ); _indices[i++] = 0; _indices[i++] = 1; _indices[i++] = 2; _indices[i++] = 2; _indices[i++] = 3; _indices[i++] = 0; ushort bottomLeftLeftVertex, bottomLeftBottomVertex, bottomRightRightVertex, bottomRightBottomVertex, topLeftLeftVertex, topLeftTopVertex, topRightTopVertex, topRightRightVertex; // We add an overlap to make the new mesh overlap the borders a little to reduce gaps. float overlapWidth = BlurMeshCornerOverlap; // Sides (indices) // + 2 tris for a side where the corners form a rectangle // + 1 tri for a side where the corners form a triangle // Top createSide(topLeftCornerSize, topRightCornerSize, cornerSizeNotZeroY, ref v, ref i, innerTopLeftVertex, innerTopRightVertex, new Vector3(innerTopLeft.x, innerTopLeft.y - topLeftCornerSize.y - overlapWidth, Vertex.nearZ), new Vector3(innerTopRight.x, innerTopRight.y - topRightCornerSize.y - overlapWidth, Vertex.nearZ), out topLeftTopVertex, out topRightTopVertex ); // Right createSide(topRightCornerSize, bottomRightCornerSize, cornerSizeNotZeroX, ref v, ref i, innerTopRightVertex, innerBottomRightVertex, new Vector3(innerTopRight.x + topRightCornerSize.x + overlapWidth, innerTopRight.y, Vertex.nearZ), new Vector3(innerBottomRight.x + bottomRightCornerSize.x + overlapWidth, innerBottomRight.y, Vertex.nearZ), out topRightRightVertex, out bottomRightRightVertex ); // Bottom createSide(bottomRightCornerSize, bottomLeftCornerSize, cornerSizeNotZeroY, ref v, ref i, innerBottomRightVertex, innerBottomLeftVertex, new Vector3(innerBottomRight.x, innerBottomRight.y + bottomRightCornerSize.y + overlapWidth, Vertex.nearZ), new Vector3(innerBottomLeft.x, innerBottomLeft.y + bottomLeftCornerSize.y + overlapWidth, Vertex.nearZ), out bottomRightBottomVertex, out bottomLeftBottomVertex ); // Left createSide(bottomLeftCornerSize, topLeftCornerSize, cornerSizeNotZeroX, ref v, ref i, innerBottomLeftVertex, innerTopLeftVertex, new Vector3(innerBottomLeft.x - bottomLeftCornerSize.x - overlapWidth, innerBottomLeft.y, Vertex.nearZ), new Vector3(innerTopLeft.x - topLeftCornerSize.x - overlapWidth, innerTopLeft.y, Vertex.nearZ), out bottomLeftLeftVertex, out topLeftLeftVertex ); if (verticesPerCorner > 0) { createCorner(topLeftCornerSize, innerTopLeft, verticesPerCorner, ref v, ref i, innerTopLeftVertex, topLeftLeftVertex, topLeftTopVertex, 2); createCorner(topRightCornerSize, innerTopRight, verticesPerCorner, ref v, ref i, innerTopRightVertex, topRightTopVertex, topRightRightVertex, 3); createCorner(bottomRightCornerSize, innerBottomRight, verticesPerCorner, ref v, ref i, innerBottomRightVertex, bottomRightRightVertex, bottomRightBottomVertex, 0); createCorner(bottomLeftCornerSize, innerBottomLeft, verticesPerCorner, ref v, ref i, innerBottomLeftVertex, bottomLeftBottomVertex, bottomLeftLeftVertex, 1); } MeshWriteData mwd = mgc.Allocate(_vertices.Length, _indices.Length, BlurManager.Instance.GetBlurredTexture()); // UVs if (rootParent == null) { rootParent = GetDocumentRoot(this); } for (int n = 0; n < _vertices.Length; n++) { _vertices[n].tint = BlurTint; var uv = this.LocalToWorld(_vertices[n].position); uv.x /= rootParent.worldBound.width; uv.y /= rootParent.worldBound.height; uv.y = 1f - uv.y; _vertices[n].uv = uv; } mwd.SetAllVertices(_vertices); mwd.SetAllIndices(_indices); } private void createCorner(Vector2 cornerSize, Vector2 innerPos, int verticesPerCorner, ref ushort v, ref ushort i, ushort innerVertex, ushort startVertex, ushort endVertex, int quadrantOffset) { if (cornerSize.x > 0 && cornerSize.y > 0) { ushort center = innerVertex; ushort last = startVertex; float offset = Mathf.PI * 0.5f * quadrantOffset; float stepSizeInQuadrant = 1f / (verticesPerCorner + 1) * Mathf.PI * 0.5f; for (int c = 1; c < verticesPerCorner + 1; c++) { float x = Mathf.Cos(offset + stepSizeInQuadrant * c); float y = Mathf.Sin(offset + stepSizeInQuadrant * c); // We also add an overlap to make the new mesh overlap the borders a little to reduce gaps. float overlapWidth = BlurMeshCornerOverlap; _vertices[v++].position = new Vector3(innerPos.x + x * (cornerSize.x + overlapWidth), innerPos.y + y * (cornerSize.y + overlapWidth), Vertex.nearZ); _indices[i++] = center; _indices[i++] = last; _indices[i++] = (ushort)(v - 1); last = _indices[i - 1]; } // End at the existing vertex _indices[i++] = center; _indices[i++] = last; _indices[i++] = endVertex; } } void createSide( Vector2 firstCornerSize, Vector2 secondCornerSize, System.Func cornerSizeNotZeroFunc, ref ushort v, ref ushort i, ushort firstOuterVertex, ushort secondOuterVertex, Vector3 newVertexAPos, Vector3 newVertexBPos, out ushort newVertexA, out ushort newVertexB) { newVertexA = 0; newVertexB = 0; if (cornerSizeNotZeroFunc(firstCornerSize) && cornerSizeNotZeroFunc(secondCornerSize)) { newVertexA = v; _vertices[v++].position = newVertexAPos; newVertexB = v; _vertices[v++].position = newVertexBPos; _indices[i++] = newVertexA; _indices[i++] = newVertexB; _indices[i++] = firstOuterVertex; _indices[i++] = newVertexB; _indices[i++] = secondOuterVertex; _indices[i++] = firstOuterVertex; } else if (cornerSizeNotZeroFunc(firstCornerSize) || cornerSizeNotZeroFunc(secondCornerSize)) { if (cornerSizeNotZeroFunc(firstCornerSize)) { newVertexA = v; _vertices[v++].position = newVertexAPos; _indices[i++] = newVertexA; _indices[i++] = secondOuterVertex; _indices[i++] = firstOuterVertex; } else { newVertexB = v; _vertices[v++].position = newVertexBPos; _indices[i++] = newVertexB; _indices[i++] = secondOuterVertex; _indices[i++] = firstOuterVertex; } } } bool cornerSizeNotZeroX(Vector2 cornerSize) { return cornerSize.x > 0; } bool cornerSizeNotZeroY(Vector2 cornerSize) { return cornerSize.y > 0; } public VisualElement GetDocumentRoot(VisualElement ele) { while (ele.parent != null) { ele = ele.parent; } return ele; } } }