using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace Amanotes.Core { /// /// An that scales a to /// fit inside or fill a area. /// /// /// fitFill = 0 – uniform fit: entire sprite is visible (letterbox). /// fitFill = 1 – uniform fill: sprite covers the full area (crops edges). /// Values in between lerp the local scale between the two extremes. /// /// /// Usage: add this component to a GameObject, assign it as a listener on a /// (or let the Widget2D editor scan it automatically), /// and set in the inspector. /// [AddComponentMenu("Widget2D/Sprite Fit Listener")] public class SpriteFitListener : MonoBehaviour, ISizeListener { [SerializeField] private SpriteRenderer _spriteRenderer; [SerializeField] [Range(0f, 1f)] [Tooltip("0 = fit (letterbox), 1 = fill (crop). Lerps the uniform scale between the two extremes.")] private float _fitFill = 0f; /// /// Fit/fill ratio. 0 = fit, 1 = fill, values in between blend the two. /// Setting this at runtime immediately re-applies the last received size. /// public float fitFill { get => _fitFill; set { _fitFill = Mathf.Clamp01(value); if (_lastSize.sqrMagnitude > 0f) ApplyScale(_lastSize); } } // Serialized so it survives domain reloads and prefab instantiation. // Set by OnResize; used to re-apply when the sprite or fitFill changes. [SerializeField] private Vector2 _lastSize; // ─── ISizeListener ──────────────────────────────────────────────────── /// public void OnResize(Vector2 size) { _lastSize = size; ApplyScale(size); } // ─── Internal ───────────────────────────────────────────────────────── private void ApplyScale(Vector2 widgetSize) { if (_spriteRenderer == null || _spriteRenderer.sprite == null) return; if (widgetSize.x <= 0f || widgetSize.y <= 0f) return; var sprite = _spriteRenderer.sprite; var rect = sprite.rect; var ppu = sprite.pixelsPerUnit; var orgSize = new Vector2(rect.width / ppu, rect.height / ppu); var sx = widgetSize.x / orgSize.x; var sy = widgetSize.y / orgSize.y; var minS = Mathf.Min(sx, sy); var maxS = Mathf.Max(sx, sy); var s = Mathf.Lerp(minS, maxS, _fitFill); _spriteRenderer.transform.localScale = Vector3.one * s; } /// /// Re-applies the fit/fill scale using the last size received from the Widget2D. /// Call this after changing the sprite externally so the scale is recalculated /// for the new sprite's aspect ratio. /// public void Refresh() { if (_lastSize.sqrMagnitude > 0f) ApplyScale(_lastSize); } /// /// Swaps the target sprite and immediately refreshes the scale. /// Convenience wrapper around assigning spriteRenderer.sprite + . /// public void SetSprite(Sprite sprite) { if (_spriteRenderer == null) return; _spriteRenderer.sprite = sprite; Refresh(); } // ─── Unity lifecycle ────────────────────────────────────────────────── private void Awake() { if (_spriteRenderer == null) _spriteRenderer = GetComponent(); } #if UNITY_EDITOR // Tracks the sprite seen last OnValidate so we can detect inspector changes. [System.NonSerialized] private Sprite _lastKnownSprite; private void OnValidate() { if (_spriteRenderer == null) _spriteRenderer = GetComponent(); if (PrefabUtility.IsPartOfPrefabAsset(gameObject)) return; EditorApplication.delayCall += () => { if (this == null) return; // Re-apply whenever fitFill is tweaked OR the sprite was swapped // in the inspector (detected by reference change). var currentSprite = _spriteRenderer != null ? _spriteRenderer.sprite : null; bool spriteChanged = currentSprite != _lastKnownSprite; _lastKnownSprite = currentSprite; if (_lastSize.sqrMagnitude > 0f) Refresh(); }; } private void Reset() { _spriteRenderer = GetComponent(); } #endif } }