Code/Core/PanelTransform.cs
using System;
using System.Collections.Immutable;
using Sandbox;
using Sandbox.UI;
using EngineEntry = Sandbox.UI.PanelTransform.Entry;
using EngineEntryType = Sandbox.UI.PanelTransform.EntryType;
namespace Goo;
/// <summary>Typed wrapper around Sandbox.UI.PanelTransform adding fluent chaining and structural equality (the engine struct's Add* methods do not chain and its equality is reference-based, which would emit redundant per-Build ops).</summary>
public readonly struct PanelTransform : IEquatable<PanelTransform>
{
internal readonly ImmutableList<EngineEntry>? _entries;
private PanelTransform(ImmutableList<EngineEntry> entries) => _entries = entries;
public bool IsEmpty => _entries is null || _entries.Count == 0;
public bool Equals(PanelTransform other) => EntriesEqual(_entries, other._entries);
public override bool Equals(object? obj) => obj is PanelTransform o && Equals(o);
public override int GetHashCode() => ComputeEntriesHash(_entries);
internal static int ComputeEntriesHash(ImmutableList<EngineEntry>? entries)
{
if (entries is null || entries.Count == 0) return 0;
var h = new HashCode();
h.Add(entries.Count);
foreach (var e in entries) h.Add(EntryHash(e));
return h.ToHashCode();
}
public static bool operator ==(PanelTransform a, PanelTransform b) => a.Equals(b);
public static bool operator !=(PanelTransform a, PanelTransform b) => !a.Equals(b);
internal static bool EntriesEqual(ImmutableList<EngineEntry>? a, ImmutableList<EngineEntry>? b)
{
if (ReferenceEquals(a, b)) return true;
var aCount = a?.Count ?? 0;
var bCount = b?.Count ?? 0;
if (aCount != bCount) return false;
if (aCount == 0) return true;
for (int i = 0; i < aCount; i++)
if (!EntryEqual(a![i], b![i])) return false;
return true;
}
static bool EntryEqual(in EngineEntry a, in EngineEntry b)
=> a.Type == b.Type
&& a.Data == b.Data
&& a.Matrix == b.Matrix
&& a.X.Equals(b.X)
&& a.Y.Equals(b.Y)
&& a.Z.Equals(b.Z);
static int EntryHash(in EngineEntry e)
=> HashCode.Combine(e.Type, e.Data, e.Matrix, e.X, e.Y, e.Z);
// ---- Static factories ----
public static PanelTransform Translate(Length x, Length y)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Translate, X = x, Y = y }));
public static PanelTransform Translate(Length x, Length y, Length z)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Translate, X = x, Y = y, Z = z }));
public static PanelTransform TranslateX(Length x)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Translate, X = x }));
public static PanelTransform TranslateY(Length y)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Translate, Y = y }));
public static PanelTransform TranslateZ(Length z)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Translate, Z = z }));
public static PanelTransform Rotate(float zDegrees)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Rotation, Data = new Vector3(0f, 0f, zDegrees) }));
public static PanelTransform Rotate(float xDeg, float yDeg, float zDeg)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Rotation, Data = new Vector3(xDeg, yDeg, zDeg) }));
public static PanelTransform RotateX(float deg)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Rotation, Data = new Vector3(deg, 0f, 0f) }));
public static PanelTransform RotateY(float deg)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Rotation, Data = new Vector3(0f, deg, 0f) }));
public static PanelTransform RotateZ(float deg)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Rotation, Data = new Vector3(0f, 0f, deg) }));
public static PanelTransform Scale(float uniform)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Scale, Data = new Vector3(uniform, uniform, uniform) }));
public static PanelTransform Scale(Vector3 scale)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Scale, Data = scale }));
public static PanelTransform ScaleX(float s)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Scale, Data = new Vector3(s, 1f, 1f) }));
public static PanelTransform ScaleY(float s)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Scale, Data = new Vector3(1f, s, 1f) }));
public static PanelTransform ScaleZ(float s)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Scale, Data = new Vector3(1f, 1f, s) }));
public static PanelTransform Skew(float xDeg, float yDeg)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Skew, Data = new Vector3(xDeg, yDeg, 0f) }));
public static PanelTransform SkewX(float deg)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Skew, Data = new Vector3(deg, 0f, 0f) }));
public static PanelTransform SkewY(float deg)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Skew, Data = new Vector3(0f, deg, 0f) }));
public static PanelTransform Perspective(Length d)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Perspective, X = d }));
public static PanelTransform Matrix3D(Matrix m)
=> new(ImmutableList.Create(new EngineEntry { Type = EngineEntryType.Matrix, Matrix = m }));
// ---- Internal append helper used by extension fluent methods ----
internal PanelTransform Append(in EngineEntry entry)
=> new((_entries ?? ImmutableList<EngineEntry>.Empty).Add(entry));
}