A small immutable wrapper around Sandbox.UI.PanelTransform.Entry that provides fluent construction helpers, structural equality, hashing, and factory methods for common transforms (translate, rotate, scale, skew, perspective, matrix). It stores an ImmutableList of engine entries and can append entries.
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));
}