using UnityEngine;
using UnityEngine.UIElements;
namespace ProjectM.Client
{
///
/// UI Toolkit factories for the in-game HUD — a thin extension of 's Aether palette,
/// now skinned with the curated Synty sci-fi-soldier kit (). Panels are 9-sliced Synty
/// boxes tinted into the Aether palette; bars are a skinned track + a percent-width fill; numerics use the
/// Orbitron display font and labels the Exo 2.0 body font. EVERYTHING is null-safe: if
/// (or a given sprite/font) is missing, each factory falls back to the original flat-colour look, so the HUD
/// is shippable with or without the theme asset. Every element is pickingMode = Ignore by default so
/// the HUD never eats clicks meant for the game world (only the interactive build-palette slots opt back in).
///
public static class HudUi
{
public static readonly Color Track = new(0f, 0f, 0f, 0.55f);
// ---- text (Orbitron display vs Exo body, theme-driven with a bold fallback) ----
/// A body label (Exo 2.0 when themed) — labels, captions, hints.
public static Label Text(string text, int size, Color color, TextAnchor align)
{
var l = MakeLabel(text, size, color, align);
var theme = HudTheme.Get();
if (theme != null) theme.ApplyBody(l.style);
return l;
}
/// A display label (Orbitron when themed) — numerics + phase words, the authoritative HUD voice.
public static Label Display(string text, int size, Color color, TextAnchor align)
{
var l = MakeLabel(text, size, color, align);
var theme = HudTheme.Get();
if (theme != null) theme.ApplyDisplay(l.style);
return l;
}
static Label MakeLabel(string text, int size, Color color, TextAnchor align)
{
var l = new Label(text);
l.style.fontSize = size;
l.style.color = color;
l.style.unityTextAlign = align;
l.style.unityFontStyleAndWeight = FontStyle.Bold;
l.pickingMode = PickingMode.Ignore;
return l;
}
// ---- bars ----
/// A dark rounded bar track with a percent-width fill child (returned via ).
public static VisualElement Bar(float width, float height, Color fillColor, out VisualElement fill)
{
var theme = HudTheme.Get();
var trackSpr = theme != null ? theme.BarTrack : null;
var track = new VisualElement();
track.style.width = width;
track.style.height = height;
track.style.paddingLeft = 2; track.style.paddingRight = 2;
track.style.paddingTop = 2; track.style.paddingBottom = 2;
track.style.flexDirection = FlexDirection.Row;
track.pickingMode = PickingMode.Ignore;
fill = new VisualElement();
fill.style.height = Length.Percent(100);
fill.style.width = Length.Percent(100);
fill.style.backgroundColor = fillColor;
fill.pickingMode = PickingMode.Ignore;
MenuUi.Round(fill, 3);
if (trackSpr != null)
{
// Bar_Angled ships an 80/0 border → UITK 9-slices it horizontally from the art; no style override.
track.style.backgroundImage = new StyleBackground(Background.FromSprite(trackSpr));
track.style.unityBackgroundImageTintColor = new Color(0.04f, 0.06f, 0.09f, 0.92f);
}
else
{
track.style.backgroundColor = Track;
MenuUi.Round(track, 4);
}
track.Add(fill);
return track;
}
/// Set a fill's width to a 0..1 fraction of its track.
public static void SetFill(VisualElement fill, float frac)
{
if (fill != null) fill.style.width = Length.Percent(Mathf.Clamp01(frac) * 100f);
}
// ---- panels / icons / glyphs ----
/// A grouping container (no background). Transparent, click-through.
public static VisualElement Group(Align items = Align.FlexStart)
{
var g = new VisualElement();
g.style.alignItems = items;
g.pickingMode = PickingMode.Ignore;
return g;
}
///
/// A 9-sliced Synty panel tinted into the Aether palette; falls back to a flat translucent rounded panel
/// when the theme/sprite is missing. multiplies the (light-grey) skin, so a dark
/// tint reads as panel-dark while preserving the printed bevels.
///
public static VisualElement Panel(Color tint)
{
var p = new VisualElement();
p.pickingMode = PickingMode.Ignore;
var theme = HudTheme.Get();
var box = theme != null ? theme.PanelBox : null;
if (box != null)
{
p.style.backgroundImage = new StyleBackground(Background.FromSprite(box));
p.style.unityBackgroundImageTintColor = tint;
// 9-slice uses the sprite's AUTHORED border (Box_Glass ships 25px); no style override → no
// "borders overridden by style slices" log, and the art's intended corners are preserved.
}
else
{
p.style.backgroundColor = tint;
MenuUi.Round(p, 8);
MenuUi.Border(p, new Color(1f, 1f, 1f, 0.08f), 1);
}
return p;
}
/// A fixed-size icon element backed by a Synty sprite (tinted). Returns an empty element if null.
public static VisualElement Icon(Sprite sprite, float size, Color tint)
{
var e = new VisualElement();
e.style.width = size; e.style.height = size;
e.style.flexShrink = 0;
e.pickingMode = PickingMode.Ignore;
if (sprite != null)
{
e.style.backgroundImage = new StyleBackground(Background.FromSprite(sprite));
e.style.unityBackgroundImageTintColor = tint;
e.style.backgroundSize = new StyleBackgroundSize(new BackgroundSize(BackgroundSizeType.Contain));
}
return e;
}
/// True when the theme is loaded and a sprite is present (callers choose icon vs text fallback).
///
/// An input-prompt chip: the Synty key/button glyph (left untinted to keep its printed face) when present,
/// else a bordered text keycap with (e.g. "R", "Esc").
///
public static VisualElement Glyph(Sprite sprite, string fallbackText, float size)
{
if (sprite != null) return Icon(sprite, size, Color.white);
var cap = new VisualElement();
cap.style.minWidth = size; cap.style.height = size;
cap.style.paddingLeft = 7; cap.style.paddingRight = 7;
cap.style.alignItems = Align.Center; cap.style.justifyContent = Justify.Center;
cap.style.backgroundColor = new Color(0.14f, 0.17f, 0.22f, 0.95f);
cap.style.flexShrink = 0;
cap.pickingMode = PickingMode.Ignore;
MenuUi.Round(cap, 4);
MenuUi.Border(cap, new Color(1f, 1f, 1f, 0.25f), 1);
cap.Add(Text(fallbackText, Mathf.Max(10, (int)(size * 0.5f)), MenuUi.TextCol, TextAnchor.MiddleCenter));
return cap;
}
/// Apply a uniform 9-slice (horizontal / vertical source-texture px) to a skinned element.
}
}