Code/Core/Shadows.cs
using System;
using Sandbox;
using Sandbox.UI;
namespace Goo;
/// <summary>An ordered stack of shadows for BoxShadow and TextShadow. Converts implicitly from a single Shadow or a Shadow[]. Assign <see cref="None"/> to declare "no shadows", which cancels an inherited TextShadow.</summary>
public readonly struct Shadows : IEquatable<Shadows>
{
readonly Shadow _single;
readonly Shadow[]? _items;
readonly int _count;
public Shadows(Shadow item)
{
_single = item;
_items = null;
_count = 1;
}
public Shadows(params Shadow[]? items)
{
if (items is null || items.Length == 0)
{
_single = default;
_items = null;
_count = 0;
}
else if (items.Length == 1)
{
_single = items[0];
_items = null;
_count = 1;
}
else
{
_single = default;
_items = new Shadow[items.Length];
for (int i = 0; i < items.Length; i++)
_items[i] = items[i];
_count = items.Length;
}
}
/// <summary>Declares "no shadows", cancelling a TextShadow inherited from a parent.</summary>
public static readonly Shadows None = new();
public int Count => _count;
public Shadow this[int i]
{
get
{
if ((uint)i >= (uint)_count) throw new IndexOutOfRangeException();
return _items is null ? _single : _items[i];
}
}
public static implicit operator Shadows(Shadow s) => new(s);
public static implicit operator Shadows(Shadow[] s) => new(s);
// Shadow has no Equals override, so the default one boxes and walks fields by reflection.
internal static bool Same(in Shadow a, in Shadow b)
=> a.OffsetX == b.OffsetX && a.OffsetY == b.OffsetY && a.Blur == b.Blur
&& a.Spread == b.Spread && a.Inset == b.Inset && a.Color == b.Color;
public bool Equals(Shadows other)
{
if (Count != other.Count) return false;
for (int i = 0; i < Count; i++)
if (!Same(this[i], other[i])) return false;
return true;
}
public override bool Equals(object? obj) => obj is Shadows s && Equals(s);
public static bool operator ==(Shadows a, Shadows b) => a.Equals(b);
public static bool operator !=(Shadows a, Shadows b) => !a.Equals(b);
public override int GetHashCode()
{
var hash = Count;
for (int i = 0; i < Count; i++)
{
var shadow = this[i];
hash = HashCode.Combine(
hash,
shadow.OffsetX,
shadow.OffsetY,
shadow.Blur,
shadow.Spread,
shadow.Inset,
shadow.Color);
}
return hash;
}
}
internal static class ShadowListApplier
{
static readonly Shadow EmptyTextShadow = new() { Color = Color.Transparent };
static bool _warnedUnsupportedInsetShadow;
internal static bool Apply(
ShadowList list,
Shadows? value,
bool allowInset = true,
bool cancelInheritance = false)
{
var shadows = value ?? default;
int sourceCount = shadows.Count;
int count = 0;
for (int i = 0; i < sourceCount; i++)
if (allowInset || !shadows[i].Inset)
count++;
bool none = value.HasValue && sourceCount == 0;
bool useEmptySentinel = none && cancelInheritance;
int appliedCount = count + (useEmptySentinel ? 1 : 0);
bool isNone = none && !useEmptySentinel;
if (!allowInset && count != sourceCount && !_warnedUnsupportedInsetShadow)
{
_warnedUnsupportedInsetShadow = true;
Log.Warning("Goo: BoxShadow inset is unsupported and was ignored.");
}
if (list.Count == appliedCount && list.IsNone == isNone)
{
bool same = !useEmptySentinel || Shadows.Same(list[0], EmptyTextShadow);
int applied = 0;
for (int i = 0; same && i < sourceCount; i++)
{
var shadow = shadows[i];
if (!allowInset && shadow.Inset) continue;
if (Shadows.Same(list[applied++], shadow)) continue;
same = false;
break;
}
if (same) return false;
}
list.Clear();
list.IsNone = isNone;
if (useEmptySentinel)
{
list.Add(EmptyTextShadow);
}
else
{
for (int i = 0; i < sourceCount; i++)
{
var shadow = shadows[i];
if (allowInset || !shadow.Inset)
list.Add(shadow);
}
}
return true;
}
}