namespace UnityHelpers.Utils
{
using System.Collections.Generic;
using System.Linq;
using Core.Attributes;
using UnityEngine;
///
/// Keeps stack-like track of Colors and Materials of SpriteRenderers
///
[DisallowMultipleComponent]
public sealed class SpriteRendererMetadata : MonoBehaviour
{
private bool Enabled => enabled && gameObject.activeInHierarchy;
private readonly List<(Component component, Color color)> _colorStack = new();
private readonly List<(Component component, Material material)> _materialStack = new();
private readonly List<(Component component, Color color)> _colorStackCache = new();
private readonly List<(Component component, Material material)> _materialStackCache = new();
public Color OriginalColor => _colorStack[0].color;
public Color CurrentColor => _colorStack[^1].color;
public Material OriginalMaterial => _materialStack[0].material;
public Material CurrentMaterial => _materialStack[^1].material;
public IEnumerable Materials =>
_materialStack.Select(entry => entry.material).Reverse();
public IEnumerable Colors => _colorStack.Select(entry => entry.color).Reverse();
[SiblingComponent]
[SerializeField]
private SpriteRenderer _spriteRenderer;
private bool _enabled;
public void PushColor(Component component, Color color, bool force = false)
{
if (component == this)
{
return;
}
if (!force && !Enabled)
{
return;
}
InternalPushColor(component, color);
}
private void InternalPushColor(Component component, Color color)
{
RemoveColor(component);
_colorStack.Add((component, color));
_spriteRenderer.color = CurrentColor;
}
public void PushBackColor(Component component, Color color, bool force = false)
{
if (component == this)
{
return;
}
if (!force && !Enabled)
{
return;
}
RemoveColor(component);
_colorStack.Insert(1, (component, color));
_spriteRenderer.color = CurrentColor;
}
public void PopColor(Component component)
{
RemoveColor(component);
_spriteRenderer.color = CurrentColor;
}
public bool TryGetColor(Component component, out Color color)
{
foreach ((Component component, Color color) entry in _colorStack)
{
if (entry.component == component)
{
color = entry.color;
return true;
}
}
color = default;
return false;
}
///
/// Inserts a material as "first in the queue".
///
/// Component that owns the material.
/// Material to use.
/// If true, overrides the enabled check.
/// The instanced material, if possible.
public Material PushMaterial(Component component, Material material, bool force = false)
{
if (component == this)
{
return null;
}
if (!force && !Enabled)
{
return null;
}
#if UNITY_EDITOR
if (!Application.isPlaying)
{
return null;
}
#endif
return InternalPushMaterial(component, material);
}
private Material InternalPushMaterial(Component component, Material material)
{
RemoveMaterial(component);
_spriteRenderer.material = material;
Material instanced = _spriteRenderer.material;
_materialStack.Add((component, instanced));
return instanced;
}
///
/// Inserts a material as "last in the queue".
///
/// Component that owns the material.
/// Material to use.
/// If true, overrides the enabled check.
/// The instanced material, if possible.
public Material PushBackMaterial(Component component, Material material, bool force = false)
{
if (component == this)
{
return null;
}
if (!force && !Enabled)
{
return null;
}
#if UNITY_EDITOR
if (!Application.isPlaying)
{
return null;
}
#endif
RemoveMaterial(component);
Material instanced = material;
if (_materialStack.Count <= 1)
{
_spriteRenderer.material = material;
instanced = _spriteRenderer.material;
}
_materialStack.Insert(1, (component, instanced));
return instanced;
}
public void PopMaterial(Component component)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
return;
}
#endif
RemoveMaterial(component);
_spriteRenderer.material = CurrentMaterial;
Material instanced = _spriteRenderer.material;
Component currentComponent = _materialStack[^1].component;
_materialStack[^1] = (currentComponent, instanced);
}
public bool TryGetMaterial(Component component, out Material material)
{
foreach ((Component component, Material material) entry in _materialStack)
{
if (entry.component == component)
{
material = entry.material;
return true;
}
}
material = default;
return false;
}
private void Awake()
{
if (_spriteRenderer == null)
{
this.AssignSiblingComponents();
}
InternalPushColor(this, _spriteRenderer.color);
foreach ((Component component, Color color) entry in _colorStack)
{
_colorStackCache.Add(entry);
}
_ = InternalPushMaterial(this, _spriteRenderer.material);
foreach ((Component component, Material material) entry in _materialStack)
{
_materialStackCache.Add(entry);
}
}
private void OnEnable()
{
// Ignore the OnEnable call from when the object is first initialized
if (!_enabled)
{
_enabled = true;
return;
}
_colorStack.Clear();
if (0 < _colorStackCache.Count)
{
_colorStack.Add(_colorStackCache[0]);
}
List<(Component component, Color color)> colorBuffer = Buffers<(
Component component,
Color color
)>.List;
colorBuffer.Clear();
foreach ((Component component, Color color) entry in _colorStackCache)
{
colorBuffer.Add(entry);
}
for (int i = 1; i < colorBuffer.Count; ++i)
{
(Component component, Color color) entry = colorBuffer[i];
PushColor(entry.component, entry.color, force: true);
}
_materialStack.Clear();
if (0 < _materialStackCache.Count)
{
_materialStack.Add(_materialStackCache[0]);
}
List<(Component component, Material material)> materialBuffer = Buffers<(
Component component,
Material material
)>.List;
materialBuffer.Clear();
foreach ((Component component, Material material) entry in _materialStackCache)
{
materialBuffer.Add(entry);
}
for (int i = 1; i < materialBuffer.Count; ++i)
{
(Component component, Material material) entry = materialBuffer[i];
PushMaterial(entry.component, entry.material, force: true);
}
}
private void OnDisable()
{
List<(Component component, Color color)> colorBuffer = Buffers<(
Component component,
Color color
)>.List;
colorBuffer.Clear();
foreach ((Component component, Color color) entry in _colorStack)
{
colorBuffer.Add(entry);
}
for (int i = colorBuffer.Count - 1; 1 <= i; --i)
{
PopColor(colorBuffer[i].component);
}
_colorStackCache.Clear();
foreach ((Component component, Color color) entry in colorBuffer)
{
_colorStackCache.Add(entry);
}
List<(Component component, Material material)> materialBuffer = Buffers<(
Component component,
Material material
)>.List;
materialBuffer.Clear();
foreach ((Component component, Material material) entry in _materialStack)
{
materialBuffer.Add(entry);
}
for (int i = materialBuffer.Count - 1; 1 <= i; --i)
{
PopMaterial(materialBuffer[i].component);
}
_materialStackCache.Clear();
foreach ((Component component, Material material) entry in materialBuffer)
{
_materialStackCache.Add(entry);
}
}
private void RemoveColor(Component component)
{
if (component == this)
{
return;
}
for (int i = _colorStack.Count - 1; 0 <= i; --i)
{
(Component component, Color color) stackEntry = _colorStack[i];
if (stackEntry.component == component || stackEntry.component == null)
{
_colorStack.RemoveAt(i);
}
}
for (int i = _colorStackCache.Count - 1; 0 <= i; --i)
{
(Component component, Color color) stackEntry = _colorStackCache[i];
if (stackEntry.component == component || stackEntry.component == null)
{
_colorStackCache.RemoveAt(i);
}
}
}
private void RemoveMaterial(Component component)
{
if (component == this)
{
return;
}
for (int i = _materialStack.Count - 1; 0 <= i; --i)
{
(Component component, Material material) stackEntry = _materialStack[i];
if (stackEntry.component == component || stackEntry.component == null)
{
_materialStack.RemoveAt(i);
}
}
for (int i = _materialStackCache.Count - 1; 0 <= i; --i)
{
(Component component, Material material) stackEntry = _materialStackCache[i];
if (stackEntry.component == component || stackEntry.component == null)
{
_materialStackCache.RemoveAt(i);
}
}
}
}
}