#if ENABLE_UGUI using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.U2D; using UnityEngine.UI; using Sprites = UnityEngine.Sprites; // Credit: https://bitbucket.org/Unity-Technologies/ui/src/2018.4/UnityEngine.UI/UI/Core/Image.cs namespace UnityEngine.UI { [RequireComponent( typeof( CanvasRenderer ) )] [AddComponentMenu( "UI/Sliced Filled Image", 11 )] public class SlicedFilledImage : MaskableGraphic, ISerializationCallbackReceiver, ILayoutElement, ICanvasRaycastFilter { private static class SetPropertyUtility { public static bool SetStruct( ref T currentValue, T newValue ) where T : struct { if( EqualityComparer.Default.Equals( currentValue, newValue ) ) return false; currentValue = newValue; return true; } public static bool SetClass( ref T currentValue, T newValue ) where T : class { if( ( currentValue == null && newValue == null ) || ( currentValue != null && currentValue.Equals( newValue ) ) ) return false; currentValue = newValue; return true; } } public enum FillDirection { Right = 0, Left = 1, Up = 2, Down = 3 } private static readonly Vector3[] s_Vertices = new Vector3[4]; private static readonly Vector2[] s_UVs = new Vector2[4]; private static readonly Vector2[] s_SlicedVertices = new Vector2[4]; private static readonly Vector2[] s_SlicedUVs = new Vector2[4]; #pragma warning disable 1692 #pragma warning disable IDE1006 // Suppress 'Naming rule violation' warnings #pragma warning disable 0649 [SerializeField] private Sprite m_Sprite; public Sprite sprite { get { return m_Sprite; } set { if( SetPropertyUtility.SetClass( ref m_Sprite, value ) ) { SetAllDirty(); TrackImage(); } } } [SerializeField] private FillDirection m_FillDirection; public FillDirection fillDirection { get { return m_FillDirection; } set { if( SetPropertyUtility.SetStruct( ref m_FillDirection, value ) ) SetVerticesDirty(); } } [Range( 0, 1 )] [SerializeField] private float m_FillAmount = 1f; public float fillAmount { get { return m_FillAmount; } set { if( SetPropertyUtility.SetStruct( ref m_FillAmount, Mathf.Clamp01( value ) ) ) SetVerticesDirty(); } } [SerializeField] private bool m_FillCenter = true; public bool fillCenter { get { return m_FillCenter; } set { if( SetPropertyUtility.SetStruct( ref m_FillCenter, value ) ) SetVerticesDirty(); } } [SerializeField] private float m_PixelsPerUnitMultiplier = 1f; public float pixelsPerUnitMultiplier { get { return m_PixelsPerUnitMultiplier; } set { m_PixelsPerUnitMultiplier = Mathf.Max( 0.01f, value ); } } public float pixelsPerUnit { get { float spritePixelsPerUnit = 100; if( activeSprite ) spritePixelsPerUnit = activeSprite.pixelsPerUnit; float referencePixelsPerUnit = 100; if( canvas ) referencePixelsPerUnit = canvas.referencePixelsPerUnit; return m_PixelsPerUnitMultiplier * spritePixelsPerUnit / referencePixelsPerUnit; } } #pragma warning restore 0649 [NonSerialized] private Sprite m_OverrideSprite; public Sprite overrideSprite { get { return activeSprite; } set { if( SetPropertyUtility.SetClass( ref m_OverrideSprite, value ) ) { SetAllDirty(); TrackImage(); } } } private Sprite activeSprite { get { return m_OverrideSprite != null ? m_OverrideSprite : m_Sprite; } } public override Texture mainTexture { get { if( activeSprite != null ) return activeSprite.texture; return material != null && material.mainTexture != null ? material.mainTexture : s_WhiteTexture; } } public bool hasBorder { get { if( activeSprite != null ) { Vector4 v = activeSprite.border; return v.sqrMagnitude > 0f; } return false; } } public override Material material { get { if( m_Material != null ) return m_Material; if( activeSprite && activeSprite.associatedAlphaSplitTexture != null ) { #if UNITY_EDITOR if( Application.isPlaying ) #endif return Image.defaultETC1GraphicMaterial; } return defaultMaterial; } set { base.material = value; } } public float alphaHitTestMinimumThreshold { get; set; } #pragma warning restore IDE1006 #pragma warning restore 1692 protected SlicedFilledImage() { useLegacyMeshGeneration = false; } protected override void OnEnable() { base.OnEnable(); TrackImage(); } protected override void OnDisable() { base.OnDisable(); if( m_Tracked ) UnTrackImage(); } #if UNITY_EDITOR protected override void OnValidate() { base.OnValidate(); m_PixelsPerUnitMultiplier = Mathf.Max( 0.01f, m_PixelsPerUnitMultiplier ); } #endif protected override void OnPopulateMesh( VertexHelper vh ) { if( activeSprite == null ) { base.OnPopulateMesh( vh ); return; } GenerateSlicedFilledSprite( vh ); } /// /// Update the renderer's material. /// protected override void UpdateMaterial() { base.UpdateMaterial(); // Check if this sprite has an associated alpha texture (generated when splitting RGBA = RGB + A as two textures without alpha) if( activeSprite == null ) { canvasRenderer.SetAlphaTexture( null ); return; } Texture2D alphaTex = activeSprite.associatedAlphaSplitTexture; if( alphaTex != null ) canvasRenderer.SetAlphaTexture( alphaTex ); } private void GenerateSlicedFilledSprite( VertexHelper vh ) { vh.Clear(); if( m_FillAmount < 0.001f ) return; Rect rect = GetPixelAdjustedRect(); Vector4 outer = Sprites.DataUtility.GetOuterUV( activeSprite ); Vector4 padding = Sprites.DataUtility.GetPadding( activeSprite ); if( !hasBorder ) { Vector2 size = activeSprite.rect.size; int spriteW = Mathf.RoundToInt( size.x ); int spriteH = Mathf.RoundToInt( size.y ); // Image's dimensions used for drawing. X = left, Y = bottom, Z = right, W = top. Vector4 vertices = new Vector4( rect.x + rect.width * ( padding.x / spriteW ), rect.y + rect.height * ( padding.y / spriteH ), rect.x + rect.width * ( ( spriteW - padding.z ) / spriteW ), rect.y + rect.height * ( ( spriteH - padding.w ) / spriteH ) ); GenerateFilledSprite( vh, vertices, outer, m_FillAmount ); return; } Vector4 inner = Sprites.DataUtility.GetInnerUV( activeSprite ); Vector4 border = GetAdjustedBorders( activeSprite.border / pixelsPerUnit, rect ); padding = padding / pixelsPerUnit; s_SlicedVertices[0] = new Vector2( padding.x, padding.y ); s_SlicedVertices[3] = new Vector2( rect.width - padding.z, rect.height - padding.w ); s_SlicedVertices[1].x = border.x; s_SlicedVertices[1].y = border.y; s_SlicedVertices[2].x = rect.width - border.z; s_SlicedVertices[2].y = rect.height - border.w; for( int i = 0; i < 4; ++i ) { s_SlicedVertices[i].x += rect.x; s_SlicedVertices[i].y += rect.y; } s_SlicedUVs[0] = new Vector2( outer.x, outer.y ); s_SlicedUVs[1] = new Vector2( inner.x, inner.y ); s_SlicedUVs[2] = new Vector2( inner.z, inner.w ); s_SlicedUVs[3] = new Vector2( outer.z, outer.w ); float rectStartPos; float _1OverTotalSize; if( m_FillDirection == FillDirection.Left || m_FillDirection == FillDirection.Right ) { rectStartPos = s_SlicedVertices[0].x; float totalSize = ( s_SlicedVertices[3].x - s_SlicedVertices[0].x ); _1OverTotalSize = totalSize > 0f ? 1f / totalSize : 1f; } else { rectStartPos = s_SlicedVertices[0].y; float totalSize = ( s_SlicedVertices[3].y - s_SlicedVertices[0].y ); _1OverTotalSize = totalSize > 0f ? 1f / totalSize : 1f; } for( int x = 0; x < 3; x++ ) { int x2 = x + 1; for( int y = 0; y < 3; y++ ) { if( !m_FillCenter && x == 1 && y == 1 ) continue; int y2 = y + 1; float sliceStart, sliceEnd; switch( m_FillDirection ) { case FillDirection.Right: sliceStart = ( s_SlicedVertices[x].x - rectStartPos ) * _1OverTotalSize; sliceEnd = ( s_SlicedVertices[x2].x - rectStartPos ) * _1OverTotalSize; break; case FillDirection.Up: sliceStart = ( s_SlicedVertices[y].y - rectStartPos ) * _1OverTotalSize; sliceEnd = ( s_SlicedVertices[y2].y - rectStartPos ) * _1OverTotalSize; break; case FillDirection.Left: sliceStart = 1f - ( s_SlicedVertices[x2].x - rectStartPos ) * _1OverTotalSize; sliceEnd = 1f - ( s_SlicedVertices[x].x - rectStartPos ) * _1OverTotalSize; break; case FillDirection.Down: sliceStart = 1f - ( s_SlicedVertices[y2].y - rectStartPos ) * _1OverTotalSize; sliceEnd = 1f - ( s_SlicedVertices[y].y - rectStartPos ) * _1OverTotalSize; break; default: // Just there to get rid of the "Use of unassigned local variable" compiler error sliceStart = sliceEnd = 0f; break; } if( sliceStart >= m_FillAmount ) continue; Vector4 vertices = new Vector4( s_SlicedVertices[x].x, s_SlicedVertices[y].y, s_SlicedVertices[x2].x, s_SlicedVertices[y2].y ); Vector4 uvs = new Vector4( s_SlicedUVs[x].x, s_SlicedUVs[y].y, s_SlicedUVs[x2].x, s_SlicedUVs[y2].y ); float fillAmount = ( m_FillAmount - sliceStart ) / ( sliceEnd - sliceStart ); GenerateFilledSprite( vh, vertices, uvs, fillAmount ); } } } private Vector4 GetAdjustedBorders( Vector4 border, Rect adjustedRect ) { Rect originalRect = rectTransform.rect; for( int axis = 0; axis <= 1; axis++ ) { float borderScaleRatio; // The adjusted rect (adjusted for pixel correctness) may be slightly larger than the original rect. // Adjust the border to match the adjustedRect to avoid small gaps between borders (case 833201). if( originalRect.size[axis] != 0 ) { borderScaleRatio = adjustedRect.size[axis] / originalRect.size[axis]; border[axis] *= borderScaleRatio; border[axis + 2] *= borderScaleRatio; } // If the rect is smaller than the combined borders, then there's not room for the borders at their normal size. // In order to avoid artefacts with overlapping borders, we scale the borders down to fit. float combinedBorders = border[axis] + border[axis + 2]; if( adjustedRect.size[axis] < combinedBorders && combinedBorders != 0 ) { borderScaleRatio = adjustedRect.size[axis] / combinedBorders; border[axis] *= borderScaleRatio; border[axis + 2] *= borderScaleRatio; } } return border; } private void GenerateFilledSprite( VertexHelper vh, Vector4 vertices, Vector4 uvs, float fillAmount ) { if( m_FillAmount < 0.001f ) return; float uvLeft = uvs.x; float uvBottom = uvs.y; float uvRight = uvs.z; float uvTop = uvs.w; if( fillAmount < 1f ) { if( m_FillDirection == FillDirection.Left || m_FillDirection == FillDirection.Right ) { if( m_FillDirection == FillDirection.Left ) { vertices.x = vertices.z - ( vertices.z - vertices.x ) * fillAmount; uvLeft = uvRight - ( uvRight - uvLeft ) * fillAmount; } else { vertices.z = vertices.x + ( vertices.z - vertices.x ) * fillAmount; uvRight = uvLeft + ( uvRight - uvLeft ) * fillAmount; } } else { if( m_FillDirection == FillDirection.Down ) { vertices.y = vertices.w - ( vertices.w - vertices.y ) * fillAmount; uvBottom = uvTop - ( uvTop - uvBottom ) * fillAmount; } else { vertices.w = vertices.y + ( vertices.w - vertices.y ) * fillAmount; uvTop = uvBottom + ( uvTop - uvBottom ) * fillAmount; } } } s_Vertices[0] = new Vector3( vertices.x, vertices.y ); s_Vertices[1] = new Vector3( vertices.x, vertices.w ); s_Vertices[2] = new Vector3( vertices.z, vertices.w ); s_Vertices[3] = new Vector3( vertices.z, vertices.y ); s_UVs[0] = new Vector2( uvLeft, uvBottom ); s_UVs[1] = new Vector2( uvLeft, uvTop ); s_UVs[2] = new Vector2( uvRight, uvTop ); s_UVs[3] = new Vector2( uvRight, uvBottom ); int startIndex = vh.currentVertCount; for( int i = 0; i < 4; i++ ) vh.AddVert( s_Vertices[i], color, s_UVs[i] ); vh.AddTriangle( startIndex, startIndex + 1, startIndex + 2 ); vh.AddTriangle( startIndex + 2, startIndex + 3, startIndex ); } int ILayoutElement.layoutPriority { get { return 0; } } float ILayoutElement.minWidth { get { return 0; } } float ILayoutElement.minHeight { get { return 0; } } float ILayoutElement.flexibleWidth { get { return -1; } } float ILayoutElement.flexibleHeight { get { return -1; } } float ILayoutElement.preferredWidth { get { if( activeSprite == null ) return 0; return Sprites.DataUtility.GetMinSize( activeSprite ).x / pixelsPerUnit; } } float ILayoutElement.preferredHeight { get { if( activeSprite == null ) return 0; return Sprites.DataUtility.GetMinSize( activeSprite ).y / pixelsPerUnit; } } void ILayoutElement.CalculateLayoutInputHorizontal() { } void ILayoutElement.CalculateLayoutInputVertical() { } bool ICanvasRaycastFilter.IsRaycastLocationValid( Vector2 screenPoint, Camera eventCamera ) { if( alphaHitTestMinimumThreshold <= 0 ) return true; if( alphaHitTestMinimumThreshold > 1 ) return false; if( activeSprite == null ) return true; Vector2 local; if( !RectTransformUtility.ScreenPointToLocalPointInRectangle( rectTransform, screenPoint, eventCamera, out local ) ) return false; Rect rect = GetPixelAdjustedRect(); // Convert to have lower left corner as reference point. local.x += rectTransform.pivot.x * rect.width; local.y += rectTransform.pivot.y * rect.height; Rect spriteRect = activeSprite.rect; Vector4 border = activeSprite.border; Vector4 adjustedBorder = GetAdjustedBorders( border / pixelsPerUnit, rect ); for( int i = 0; i < 2; i++ ) { if( local[i] <= adjustedBorder[i] ) continue; if( rect.size[i] - local[i] <= adjustedBorder[i + 2] ) { local[i] -= ( rect.size[i] - spriteRect.size[i] ); continue; } float lerp = Mathf.InverseLerp( adjustedBorder[i], rect.size[i] - adjustedBorder[i + 2], local[i] ); local[i] = Mathf.Lerp( border[i], spriteRect.size[i] - border[i + 2], lerp ); } // Normalize local coordinates. Rect textureRect = activeSprite.textureRect; Vector2 normalized = new Vector2( local.x / textureRect.width, local.y / textureRect.height ); // Convert to texture space. float x = Mathf.Lerp( textureRect.x, textureRect.xMax, normalized.x ) / activeSprite.texture.width; float y = Mathf.Lerp( textureRect.y, textureRect.yMax, normalized.y ) / activeSprite.texture.height; switch( m_FillDirection ) { case FillDirection.Right: if( x > m_FillAmount ) return false; break; case FillDirection.Left: if( 1f - x > m_FillAmount ) return false; break; case FillDirection.Up: if( y > m_FillAmount ) return false; break; case FillDirection.Down: if( 1f - y > m_FillAmount ) return false; break; } try { return activeSprite.texture.GetPixelBilinear( x, y ).a >= alphaHitTestMinimumThreshold; } catch( UnityException e ) { Debug.LogError( "Using alphaHitTestMinimumThreshold greater than 0 on Image whose sprite texture cannot be read. " + e.Message + " Also make sure to disable sprite packing for this sprite.", this ); return true; } } void ISerializationCallbackReceiver.OnBeforeSerialize() { } void ISerializationCallbackReceiver.OnAfterDeserialize() { m_FillAmount = Mathf.Clamp01( m_FillAmount ); } // Whether this is being tracked for Atlas Binding private bool m_Tracked = false; private static List m_TrackedTexturelessImages = new List(); private static bool s_Initialized; private void TrackImage() { if( activeSprite != null && activeSprite.texture == null ) { if( !s_Initialized ) { SpriteAtlasManager.atlasRegistered += RebuildImage; s_Initialized = true; } m_TrackedTexturelessImages.Add( this ); m_Tracked = true; } } private void UnTrackImage() { m_TrackedTexturelessImages.Remove( this ); m_Tracked = false; } private static void RebuildImage( SpriteAtlas spriteAtlas ) { for( int i = m_TrackedTexturelessImages.Count - 1; i >= 0; i-- ) { SlicedFilledImage image = m_TrackedTexturelessImages[i]; if( spriteAtlas.CanBindTo( image.activeSprite ) ) { image.SetAllDirty(); m_TrackedTexturelessImages.RemoveAt( i ); } } } } } #endif