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. } }