Das G3-HUD ist abgenommen; der Meilenstein U3 ist damit fertig. - Prüfung (Port von verify_g3, Teile A–H plus I): 718 Prüfungen ohne Fehler. Teil I prüft die Feld-Darstellung um das HUD (U3-Zugabe aus dem Motivvergleich): Aufstellungsflächen, Grotteneingang, ruhender Boss, Namensschilder, Eigentümer-Ring, Ancient-Balken. - Feld um das HUD: Namensschilder für Helden (mit Stufe), Angriffs- champions und den eingesetzten Boss („für Blau“), Eigentümer-Ring, Ancient-Balken bei Schaden, ruhende Schlundkrone auf dem Podest, eigener Grotteneingang, Aufstellungsflächen als eigene Objekte. - Schriften korrigiert: Die Referenz deklariert Cantarell 560/700–760 und Cinzel 700, wendet die Gewichte aber nie an (variation_opentype mit dem String-Schlüssel „wght“ wird von Godot ignoriert). G3 zeigt Regular 400, der Port ebenso — Cinzel-Regular statt Bold, keine Kontur zur Aufdickung. Laufweite aus spacing_glyph umgerechnet; dunkler Saum als Schatten statt TextCore-Kontur. - Werkzeuge: HudViewsTests (14 G3-Motive), HudPlaytest (Spieltest über die echte Oberfläche per Befehlsdateien), ui_ms_per_frame im LuneBenchmark. Layout folgt den echten Panelhöhen, Kamera startet wie Godot _ready auf der eigenen Lane. - Tests: EditMode 23/23, PlayMode 13/13, Kerntests 402/402, Verify 718/0. Fingerabdrücke der Bot-Partien unverändert (DCE95C96556F3A1A, 7B8E43AEFC0D60C5). - Bilder: 14 Motive bei 1920×1080, Auflösungen 1152×720 bis 2560×1440, Prüf-Schlüsselbilder, Spieltest-Screenshots. - Leistung (macOS-Player mit HUD, 116 Einheiten): 169 fps Übersicht, 218 fps Mid nah; HUD-Skripte 0,090 bzw. 0,078 ms je Bild. - Dokumentation: U3-UI (U3D, Schriftbefund), PORT-QA (U3), PORT-STATUS (U3 fertig, U4 nächster Schritt), KNOWN-BEHAVIOR-DIFFERENCES. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
682 lines
27 KiB
C#
682 lines
27 KiB
C#
using System;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace Luneraria.UI
|
|
{
|
|
/// <summary>The plate styles of the HUD panels (Godot "panel" styleboxes of lune_ui_theme.gd and the menu theme).</summary>
|
|
public static class LunePanels
|
|
{
|
|
public static readonly LunePlateStyle Hud = LunePlateStyle.Hud(LuneUiTheme.SlateTop, LuneUiTheme.SlateBottom, LuneUiTheme.Edge, 1f, 6f, 12f, 10f);
|
|
public static readonly LunePlateStyle HudTight = LunePlateStyle.Hud(LuneUiTheme.SlateTop, LuneUiTheme.SlateBottom, LuneUiTheme.Edge, 1f, 5f, 8f, 6f);
|
|
public static readonly LunePlateStyle Modal = LunePlateStyle.Hud(new Color(.07f, .09f, .125f, .98f), new Color(.045f, .06f, .085f, .98f), LuneUiTheme.Hex("5c4a33"), 1.5f, 10f, 22f, 18f)
|
|
.WithShadow(new Color(0f, 0f, 0f, .55f), 16f);
|
|
public static readonly LunePlateStyle BannerDanger = LunePlateStyle.Hud(new Color(.16f, .075f, .05f, .94f), new Color(.11f, .05f, .035f, .95f), LuneUiTheme.Hex("a0523a"), 1.5f, 6f, 16f, 8f);
|
|
public static readonly LunePlateStyle BannerOwn = LunePlateStyle.Hud(new Color(.07f, .1f, .16f, .94f), new Color(.05f, .07f, .11f, .95f), LuneUiTheme.Hex("5f86b8"), 1.5f, 6f, 16f, 8f);
|
|
public static readonly LunePlateStyle Tooltip = LunePlateStyle.Hud(new Color(.06f, .08f, .11f, .98f), new Color(.045f, .06f, .085f, .98f), LuneUiTheme.Hex("6b5a44"), 1f, 6f, 12f, 9f)
|
|
.WithShadow(new Color(0f, 0f, 0f, .5f), 8f);
|
|
public static readonly LunePlateStyle Menu = MenuPlate();
|
|
|
|
static LunePlateStyle MenuPlate()
|
|
{
|
|
LunePlateStyle style = LunePlateStyle.Menu(new Color(.078f, .094f, .114f, .95f), new Color(.055f, .066f, .08f, .96f), LuneUiTheme.Hex("5c4a33"), 1.5f, 12f, 30f, 26f)
|
|
.WithShadow(new Color(0f, 0f, 0f, .45f), 18f);
|
|
style.Ornaments = true;
|
|
style.OrnamentColor = LuneUiTheme.Hex("8a6a3c");
|
|
return style;
|
|
}
|
|
}
|
|
|
|
/// <summary>A panel with a drawn plate background (Godot PanelContainer with a ForgedStyle).</summary>
|
|
public class LunePanel : VisualElement
|
|
{
|
|
LunePlateStyle _plate;
|
|
|
|
public LunePanel(LunePlateStyle plate, bool stopsMouse = true)
|
|
{
|
|
pickingMode = stopsMouse ? PickingMode.Position : PickingMode.Ignore;
|
|
Plate = plate;
|
|
generateVisualContent += context =>
|
|
{
|
|
if (_plate != null)
|
|
LunePlates.Draw(context, new Rect(0f, 0f, layout.width, layout.height), _plate);
|
|
};
|
|
}
|
|
|
|
public LunePlateStyle Plate
|
|
{
|
|
get { return _plate; }
|
|
set
|
|
{
|
|
if (value == _plate)
|
|
return;
|
|
_plate = value;
|
|
value?.ApplyPadding(this);
|
|
MarkDirtyRepaint();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Look of a button variation: plates per state and the text colours (Godot Button theme items).</summary>
|
|
public sealed class LuneButtonLook
|
|
{
|
|
public LunePlateStyle Normal;
|
|
public LunePlateStyle Hover;
|
|
public LunePlateStyle Pressed;
|
|
public LunePlateStyle Disabled;
|
|
public LunePlateStyle HoverPressed;
|
|
/// <summary>Flat tab look: background and bottom line per state instead of plates.</summary>
|
|
public bool Tab;
|
|
public string TextVariation = "HudText";
|
|
public float TextSize = 15f;
|
|
public Color Text = LuneUiTheme.Text;
|
|
public Color TextHover = Color.white;
|
|
public Color? TextPressed;
|
|
public Color? TextHoverPressed;
|
|
public Color TextDisabled = LuneUiTheme.Faint;
|
|
|
|
public LunePlateStyle PlateFor(bool disabled, bool pressed, bool hover)
|
|
{
|
|
if (disabled)
|
|
return Disabled ?? Normal;
|
|
if (pressed && hover)
|
|
return HoverPressed ?? Pressed ?? Normal;
|
|
if (pressed)
|
|
return Pressed ?? Normal;
|
|
if (hover)
|
|
return Hover ?? Normal;
|
|
return Normal;
|
|
}
|
|
|
|
public Color TextFor(bool disabled, bool pressed, bool hover)
|
|
{
|
|
if (disabled)
|
|
return TextDisabled;
|
|
if (pressed && hover)
|
|
return TextHoverPressed ?? TextPressed ?? TextHover;
|
|
if (pressed)
|
|
return TextPressed ?? Text;
|
|
return hover ? TextHover : Text;
|
|
}
|
|
}
|
|
|
|
/// <summary>The button variations of lune_ui_theme.gd and the menu theme.</summary>
|
|
public static class LuneButtons
|
|
{
|
|
static LunePlateStyle P(string top, string bottom, Color edge, float width, float cut, float x, float y)
|
|
{
|
|
return LunePlateStyle.Hud(LuneUiTheme.Hex(top), LuneUiTheme.Hex(bottom), edge, width, cut, x, y);
|
|
}
|
|
|
|
static LunePlateStyle P(Color top, Color bottom, Color edge, float width, float cut, float x, float y)
|
|
{
|
|
return LunePlateStyle.Hud(top, bottom, edge, width, cut, x, y);
|
|
}
|
|
|
|
static LunePlateStyle M(string top, string bottom, Color edge, float width, float cut, float x, float y)
|
|
{
|
|
return LunePlateStyle.Menu(LuneUiTheme.Hex(top), LuneUiTheme.Hex(bottom), edge, width, cut, x, y);
|
|
}
|
|
|
|
static Color H(string hex) { return LuneUiTheme.Hex(hex); }
|
|
|
|
public static readonly LuneButtonLook HudButton = new LuneButtonLook
|
|
{
|
|
Normal = P("1b232d", "131920", H("4a5a6c"), 1f, 5f, 12f, 7f), Hover = P("26303c", "1a212a", LuneUiTheme.BronzeLight, 1f, 5f, 12f, 7f),
|
|
Pressed = P("10151b", "171d25", LuneUiTheme.Bronze, 1.5f, 5f, 12f, 7f), Disabled = P("141a20", "10141a", H("2c333c"), 1f, 5f, 12f, 7f),
|
|
TextSize = 15f,
|
|
};
|
|
|
|
public static readonly LuneButtonLook HudIconButton = new LuneButtonLook
|
|
{
|
|
Normal = P("1b232d", "131920", H("3c4a5a"), 1f, 5f, 9f, 6f), Hover = P("26303c", "1a212a", LuneUiTheme.BronzeLight, 1f, 5f, 9f, 6f),
|
|
Pressed = P("10151b", "171d25", LuneUiTheme.Bronze, 1.5f, 5f, 9f, 6f), Disabled = P("141a20", "10141a", H("2c333c"), 1f, 5f, 9f, 6f),
|
|
TextSize = 15f,
|
|
};
|
|
|
|
public static readonly LuneButtonLook HudPrimary = new LuneButtonLook
|
|
{
|
|
Normal = M("e8c47e", "b98a4a", H("fff0cc"), 1.5f, 7f, 16f, 8f), Hover = M("f6d898", "cc9c56", Color.white, 1.5f, 7f, 16f, 8f),
|
|
Pressed = M("b98a4a", "d5aa66", H("fff0cc"), 2f, 7f, 16f, 8f), Disabled = M("4a4234", "3a3328", H("6a5c46"), 1f, 7f, 16f, 8f),
|
|
TextVariation = "HudPrimary", TextSize = 16f, Text = LuneUiTheme.Ink, TextHover = LuneUiTheme.Ink, TextPressed = LuneUiTheme.Ink, TextDisabled = H("9c8f78"),
|
|
};
|
|
|
|
/// <summary>Godot PrimaryButton of the menu theme (Bereit, Neue Partie).</summary>
|
|
public static readonly LuneButtonLook Primary = new LuneButtonLook
|
|
{
|
|
Normal = M("e8c47e", "b98a4a", H("fff0cc"), 1.5f, 9f, 30f, 15f), Hover = M("f6d898", "cc9c56", Color.white, 1.5f, 9f, 30f, 15f),
|
|
Pressed = M("b98a4a", "d5aa66", H("fff0cc"), 2f, 9f, 30f, 15f), Disabled = M("5a4c38", "463a2a", H("7d6a4c"), 1.5f, 9f, 30f, 15f),
|
|
TextVariation = "PrimaryButton", TextSize = 22f, Text = LuneUiTheme.Ink, TextHover = LuneUiTheme.Ink, TextPressed = LuneUiTheme.Ink, TextDisabled = H("2a2218"),
|
|
};
|
|
|
|
/// <summary>Godot Button of the menu theme (secondary).</summary>
|
|
public static readonly LuneButtonLook Secondary = new LuneButtonLook
|
|
{
|
|
Normal = M("1d232b", "141920", H("6b5a44"), 1.5f, 7f, 20f, 11f), Hover = M("27303a", "1a2028", LuneUiTheme.BronzeLight, 1.5f, 7f, 20f, 11f),
|
|
Pressed = M("10141a", "171c23", LuneUiTheme.Bronze, 2f, 7f, 20f, 11f), Disabled = M("15191e", "111418", H("3a352e"), 1.5f, 7f, 20f, 11f),
|
|
TextSize = 17f, Text = LuneUiTheme.Parchment,
|
|
};
|
|
|
|
public static readonly LuneButtonLook HudCard = Card(new LuneButtonLook
|
|
{
|
|
Normal = P("19222c", "10161d", H("34424f"), 1f, 7f, 6f, 6f), Hover = P("212c38", "141b23", LuneUiTheme.Bronze, 1.5f, 7f, 6f, 6f),
|
|
Pressed = P("2a2820", "17171a", LuneUiTheme.BronzeLight, 2f, 7f, 6f, 6f), Disabled = P("12171d", "0d1116", H("252c34"), 1f, 7f, 6f, 6f),
|
|
});
|
|
|
|
public static readonly LuneButtonLook HudChampionCard = Card(new LuneButtonLook
|
|
{
|
|
Normal = P("261c14", "15100c", H("6e4a2c"), 1f, 7f, 6f, 6f), Hover = P("302216", "1b140e", LuneUiTheme.Champion, 1.5f, 7f, 6f, 6f),
|
|
Pressed = P("3a2818", "1e150e", H("ffc88c"), 2f, 7f, 6f, 6f), Disabled = P("16110d", "0f0c0a", H("3a2c20"), 1f, 7f, 6f, 6f),
|
|
});
|
|
|
|
public static readonly LuneButtonLook HudChip = Card(new LuneButtonLook
|
|
{
|
|
Normal = P("1a232d", "11171e", H("33404d"), 1f, 4f, 2f, 2f), Hover = P("222d39", "151c24", LuneUiTheme.Bronze, 1f, 4f, 2f, 2f),
|
|
Pressed = P("2c2a22", "17171a", LuneUiTheme.BronzeLight, 2f, 4f, 2f, 2f), Disabled = P("12171d", "0d1116", H("252c34"), 1f, 4f, 2f, 2f),
|
|
});
|
|
|
|
public static readonly LuneButtonLook HudRow = new LuneButtonLook
|
|
{
|
|
Normal = P(new Color(.09f, .12f, .16f, .55f), new Color(.06f, .08f, .11f, .55f), H("2a3645"), 1f, 5f, 10f, 7f),
|
|
Hover = P(new Color(.13f, .17f, .22f, .75f), new Color(.08f, .11f, .15f, .75f), LuneUiTheme.Bronze, 1f, 5f, 10f, 7f),
|
|
Pressed = P(new Color(.16f, .15f, .12f, .8f), new Color(.09f, .09f, .1f, .8f), LuneUiTheme.BronzeLight, 1.5f, 5f, 10f, 7f),
|
|
Disabled = P(new Color(.09f, .12f, .16f, .55f), new Color(.06f, .08f, .11f, .55f), H("2a3645"), 1f, 5f, 10f, 7f),
|
|
};
|
|
|
|
public static readonly LuneButtonLook HudToggle = Toggle();
|
|
|
|
static LuneButtonLook Toggle()
|
|
{
|
|
LunePlateStyle on = P("3a3222", "262117", LuneUiTheme.BronzeLight, 1.5f, 5f, 10f, 6f).WithInner(LuneUiTheme.WithAlpha(LuneUiTheme.BronzeLight, .35f));
|
|
return new LuneButtonLook
|
|
{
|
|
Normal = P("171e27", "11161d", H("3a4756"), 1f, 5f, 10f, 6f), Hover = P("212a35", "171d25", LuneUiTheme.Bronze, 1f, 5f, 10f, 6f),
|
|
Pressed = on, HoverPressed = on, Disabled = P("11151a", "0e1115", H("242a31"), 1f, 5f, 10f, 6f),
|
|
TextVariation = "HudBadge", TextSize = 14f, Text = H("cfd5dc"), TextHover = Color.white, TextPressed = H("fff4dc"), TextHoverPressed = H("fff4dc"), TextDisabled = H("59616b"),
|
|
};
|
|
}
|
|
|
|
public static readonly LuneButtonLook HudTab = new LuneButtonLook
|
|
{
|
|
Tab = true, TextVariation = "HudCaps", TextSize = 14f, Text = LuneUiTheme.Muted, TextHover = LuneUiTheme.Text, TextPressed = LuneUiTheme.BronzeLight,
|
|
TextHoverPressed = LuneUiTheme.BronzeLight, TextDisabled = LuneUiTheme.Faint,
|
|
};
|
|
|
|
static LuneButtonLook Card(LuneButtonLook look)
|
|
{
|
|
look.HoverPressed = look.Pressed;
|
|
return look;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Button of the HUD (Godot Button with a theme variation). Draws the plate of its state;
|
|
/// `Disabled` greys it out and blocks clicks but keeps hover, so tooltips can say why (as
|
|
/// in G3). Toggle buttons keep their pressed state.
|
|
/// </summary>
|
|
public class LuneButton : VisualElement
|
|
{
|
|
public event Action Clicked;
|
|
public readonly Label TextLabel;
|
|
public bool ToggleMode;
|
|
LuneButtonLook _look;
|
|
bool _hover;
|
|
bool _down;
|
|
bool _on;
|
|
bool _disabled;
|
|
|
|
public LuneButton(string text, LuneButtonLook look, VisualElement parent = null)
|
|
{
|
|
_look = look;
|
|
focusable = false;
|
|
style.flexDirection = FlexDirection.Row;
|
|
style.alignItems = Align.Center;
|
|
style.justifyContent = Justify.Center;
|
|
if (text != null)
|
|
{
|
|
TextLabel = LuneUi.Label(text, look.TextVariation, this, look.TextSize);
|
|
TextLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
|
|
}
|
|
ApplyPadding();
|
|
generateVisualContent += Draw;
|
|
RegisterCallback<PointerEnterEvent>(_ => SetHover(true));
|
|
RegisterCallback<PointerLeaveEvent>(_ => SetHover(false));
|
|
RegisterCallback<PointerDownEvent>(OnDown);
|
|
RegisterCallback<PointerUpEvent>(OnUp);
|
|
RegisterCallback<PointerCaptureOutEvent>(_ => { _down = false; Refresh(); });
|
|
Refresh();
|
|
parent?.Add(this);
|
|
}
|
|
|
|
public LuneButtonLook Look
|
|
{
|
|
get { return _look; }
|
|
set
|
|
{
|
|
if (value == _look)
|
|
return;
|
|
_look = value;
|
|
if (TextLabel != null)
|
|
LuneUi.Apply(TextLabel, value.TextVariation, value.TextSize);
|
|
ApplyPadding();
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
public string Text
|
|
{
|
|
get { return TextLabel != null ? TextLabel.text : ""; }
|
|
set
|
|
{
|
|
if (TextLabel != null && TextLabel.text != value)
|
|
TextLabel.text = value;
|
|
}
|
|
}
|
|
|
|
public bool Disabled
|
|
{
|
|
get { return _disabled; }
|
|
set
|
|
{
|
|
if (value == _disabled)
|
|
return;
|
|
_disabled = value;
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
/// <summary>Pressed state of a toggle (Godot set_pressed_no_signal).</summary>
|
|
public bool On
|
|
{
|
|
get { return _on; }
|
|
set
|
|
{
|
|
if (value == _on)
|
|
return;
|
|
_on = value;
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
public bool Hovered { get { return _hover; } }
|
|
|
|
void ApplyPadding()
|
|
{
|
|
if (_look.Tab)
|
|
{
|
|
style.paddingLeft = 12f;
|
|
style.paddingRight = 12f;
|
|
style.paddingTop = 6f;
|
|
style.paddingBottom = 6f;
|
|
}
|
|
else
|
|
{
|
|
_look.Normal?.ApplyPadding(this);
|
|
}
|
|
}
|
|
|
|
void SetHover(bool value)
|
|
{
|
|
_hover = value;
|
|
Refresh();
|
|
}
|
|
|
|
void OnDown(PointerDownEvent evt)
|
|
{
|
|
if (evt.button != 0 || _disabled)
|
|
return;
|
|
_down = true;
|
|
this.CapturePointer(evt.pointerId);
|
|
Refresh();
|
|
evt.StopPropagation();
|
|
}
|
|
|
|
void OnUp(PointerUpEvent evt)
|
|
{
|
|
if (evt.button != 0 || !_down)
|
|
return;
|
|
_down = false;
|
|
this.ReleasePointer(evt.pointerId);
|
|
bool inside = ContainsPoint(evt.localPosition);
|
|
Refresh();
|
|
evt.StopPropagation();
|
|
if (inside && !_disabled)
|
|
Click();
|
|
}
|
|
|
|
/// <summary>Godot `pressed`: toggles first, then raises Clicked (also for tests and hotkeys).</summary>
|
|
public void Click()
|
|
{
|
|
if (_disabled)
|
|
return;
|
|
if (ToggleMode)
|
|
On = !_on;
|
|
Clicked?.Invoke();
|
|
}
|
|
|
|
void Refresh()
|
|
{
|
|
bool pressed = _down || _on;
|
|
if (TextLabel != null)
|
|
LuneUi.SetColor(TextLabel, _look.TextFor(_disabled, pressed, _hover));
|
|
MarkDirtyRepaint();
|
|
}
|
|
|
|
void Draw(MeshGenerationContext context)
|
|
{
|
|
var rect = new Rect(0f, 0f, layout.width, layout.height);
|
|
bool pressed = _down || _on;
|
|
if (_look.Tab)
|
|
{
|
|
Painter2D painter = context.painter2D;
|
|
Color fill = pressed ? LuneUiTheme.WithAlpha(LuneUiTheme.BronzeLight, .08f) : _hover && !_disabled ? new Color(1f, 1f, 1f, .04f) : Color.clear;
|
|
Color line = pressed ? LuneUiTheme.BronzeLight : _hover && !_disabled ? LuneUiTheme.WithAlpha(LuneUiTheme.Bronze, .5f) : Color.clear;
|
|
if (fill.a > 0f)
|
|
LunePlates.Polygon(painter, new[] { rect.min, new Vector2(rect.xMax, rect.yMin), rect.max, new Vector2(rect.xMin, rect.yMax) }, fill);
|
|
if (line.a > 0f)
|
|
LunePlates.Line(painter, new Vector2(rect.xMin, rect.yMax - 1f), new Vector2(rect.xMax, rect.yMax - 1f), line, 2f);
|
|
return;
|
|
}
|
|
LunePlateStyle plate = _look.PlateFor(_disabled, pressed, _hover && !_disabled);
|
|
if (plate != null)
|
|
LunePlates.Draw(context, rect, plate);
|
|
}
|
|
}
|
|
|
|
/// <summary>A LuneGlyphs symbol as an element (Godot LuneGlyph). Ignores the mouse.</summary>
|
|
public sealed class LuneGlyphElement : VisualElement
|
|
{
|
|
string _glyph;
|
|
Color _color;
|
|
public Color Accent;
|
|
|
|
public LuneGlyphElement(string glyph, Color color, float edge = 18f)
|
|
{
|
|
_glyph = glyph;
|
|
_color = color;
|
|
pickingMode = PickingMode.Ignore;
|
|
style.width = edge;
|
|
style.height = edge;
|
|
style.flexShrink = 0f;
|
|
style.alignSelf = Align.Center;
|
|
generateVisualContent += context =>
|
|
{
|
|
if (!string.IsNullOrEmpty(_glyph))
|
|
LuneGlyphs.Draw(context.painter2D, _glyph, new Rect(0f, 0f, layout.width, layout.height), _color, Accent);
|
|
};
|
|
}
|
|
|
|
public string Glyph
|
|
{
|
|
get { return _glyph; }
|
|
set
|
|
{
|
|
if (value == _glyph)
|
|
return;
|
|
_glyph = value;
|
|
MarkDirtyRepaint();
|
|
}
|
|
}
|
|
|
|
public Color Color
|
|
{
|
|
get { return _color; }
|
|
set
|
|
{
|
|
if (value == _color)
|
|
return;
|
|
_color = value;
|
|
MarkDirtyRepaint();
|
|
}
|
|
}
|
|
|
|
public float Edge
|
|
{
|
|
set
|
|
{
|
|
style.width = value;
|
|
style.height = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Thin value bar drawn directly: dark track, fill, a top highlight and optional ticks (Godot LuneHpBar).</summary>
|
|
public sealed class LuneHpBar : VisualElement
|
|
{
|
|
float _fraction = 1f;
|
|
Color _fill = LuneUiTheme.Good;
|
|
public int Ticks;
|
|
public Color Track = new Color(0f, 0f, 0f, .55f);
|
|
|
|
public LuneHpBar(float width = 60f, float height = 6f)
|
|
{
|
|
pickingMode = PickingMode.Ignore;
|
|
style.minWidth = width;
|
|
style.height = height;
|
|
style.flexShrink = 0f;
|
|
generateVisualContent += Draw;
|
|
}
|
|
|
|
public float Fraction
|
|
{
|
|
get { return _fraction; }
|
|
set
|
|
{
|
|
value = Mathf.Clamp01(value);
|
|
if (Mathf.Abs(value - _fraction) <= .0005f)
|
|
return;
|
|
_fraction = value;
|
|
MarkDirtyRepaint();
|
|
}
|
|
}
|
|
|
|
public Color Fill
|
|
{
|
|
get { return _fill; }
|
|
set
|
|
{
|
|
if (value == _fill)
|
|
return;
|
|
_fill = value;
|
|
MarkDirtyRepaint();
|
|
}
|
|
}
|
|
|
|
void Draw(MeshGenerationContext context)
|
|
{
|
|
Painter2D painter = context.painter2D;
|
|
var rect = new Rect(0f, 0f, layout.width, layout.height);
|
|
LunePlates.Polygon(painter, Corners(rect), Track);
|
|
if (_fraction > 0f)
|
|
{
|
|
var filled = new Rect(0f, 0f, Mathf.Max(1f, rect.width * _fraction), rect.height);
|
|
LunePlates.Polygon(painter, Corners(filled), _fill);
|
|
LunePlates.Line(painter, new Vector2(0f, .5f), new Vector2(filled.width, .5f), new Color(1f, 1f, 1f, .28f), 1f);
|
|
}
|
|
for (int index = 1; index < Ticks; index++)
|
|
{
|
|
float x = Mathf.Round(rect.width * index / Ticks);
|
|
LunePlates.Line(painter, new Vector2(x, 0f), new Vector2(x, rect.height), new Color(0f, 0f, 0f, .45f), 1f);
|
|
}
|
|
LunePlates.Outline(painter, Corners(new Rect(.5f, .5f, rect.width - 1f, rect.height - 1f)), new Color(1f, 1f, 1f, .12f), 1f);
|
|
}
|
|
|
|
public static Vector2[] Corners(Rect rect)
|
|
{
|
|
return new[] { rect.min, new Vector2(rect.xMax, rect.yMin), rect.max, new Vector2(rect.xMin, rect.yMax) };
|
|
}
|
|
|
|
/// <summary>Green above 60 %, amber above 30 %, red below; the team colour when given.</summary>
|
|
public static Color HealthColor(float value, Color? baseColor = null)
|
|
{
|
|
if (value > .6f)
|
|
return baseColor ?? LuneUiTheme.Good;
|
|
if (value > .3f)
|
|
return LuneUiTheme.Warn;
|
|
return LuneUiTheme.Danger;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// One building as a symbol in the team's shape language (round = Blau, angular = Rot) with its
|
|
/// HP as a bar underneath (Godot LuneTowerIndicator). Destroyed buildings remain as a hollow
|
|
/// outline with a cross.
|
|
/// </summary>
|
|
public sealed class LuneTowerIndicator : VisualElement
|
|
{
|
|
public int Team;
|
|
public string Tier = "outer";
|
|
public bool Alive = true;
|
|
public float Fraction = 1f;
|
|
public bool Attackable;
|
|
|
|
public LuneTowerIndicator(float width = 22f, float height = 27f)
|
|
{
|
|
pickingMode = PickingMode.Position;
|
|
style.width = width;
|
|
style.height = height;
|
|
style.flexShrink = 0f;
|
|
generateVisualContent += Draw;
|
|
}
|
|
|
|
public bool SetState(bool alive, float fraction, bool attackable)
|
|
{
|
|
float value = Mathf.Round(Mathf.Clamp01(fraction) * 100f) / 100f;
|
|
if (alive == Alive && Mathf.Approximately(value, Fraction) && attackable == Attackable)
|
|
return false;
|
|
Alive = alive;
|
|
Fraction = value;
|
|
Attackable = attackable;
|
|
MarkDirtyRepaint();
|
|
return true;
|
|
}
|
|
|
|
void Draw(MeshGenerationContext context)
|
|
{
|
|
Painter2D painter = context.painter2D;
|
|
float edge = Mathf.Min(layout.width, layout.height - 5f);
|
|
var box = new Rect((layout.width - edge) * .5f, 0f, edge, edge);
|
|
Color tint = LuneUiTheme.TeamColor(Team);
|
|
string shape = "shape_" + Mathf.Clamp(Team, 0, 1);
|
|
if (Alive)
|
|
{
|
|
LuneGlyphs.Draw(painter, shape, box, LuneUiTheme.Lightened(LuneUiTheme.TeamDark[Mathf.Clamp(Team, 0, 1)], .1f));
|
|
LuneGlyphs.Draw(painter, shape, Grow(box, -edge * .08f), LuneUiTheme.WithAlpha(tint, .35f));
|
|
LuneGlyphs.Draw(painter, Tier == "core" ? "ancient" : "tower", Grow(box, -edge * .24f), LuneUiTheme.Hex("f4efe4"));
|
|
var bar = new Rect(box.x + 1f, layout.height - 3f, box.width - 2f, 3f);
|
|
LunePlates.Polygon(painter, LuneHpBar.Corners(bar), new Color(0f, 0f, 0f, .6f));
|
|
if (Fraction > 0f)
|
|
LunePlates.Polygon(painter, LuneHpBar.Corners(new Rect(bar.x, bar.y, bar.width * Fraction, bar.height)), LuneHpBar.HealthColor(Fraction, tint));
|
|
}
|
|
else
|
|
{
|
|
LuneGlyphs.Draw(painter, shape, box, new Color(0f, 0f, 0f, .35f));
|
|
LuneGlyphs.Draw(painter, "close", Grow(box, -edge * .18f), new Color(.5f, .52f, .55f, .8f));
|
|
}
|
|
}
|
|
|
|
static Rect Grow(Rect rect, float amount)
|
|
{
|
|
return new Rect(rect.x - amount, rect.y - amount, rect.width + amount * 2f, rect.height + amount * 2f);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Small labelled state (RUHIG, KRITISCH, BEREIT, BOT …; Godot LuneStatusBadge). Always colour
|
|
/// plus text and, where it helps, a symbol; never colour alone.
|
|
/// </summary>
|
|
public sealed class LuneStatusBadge : VisualElement
|
|
{
|
|
readonly LuneGlyphElement _glyph;
|
|
readonly Label _label;
|
|
public string Text { get; private set; } = "";
|
|
Color _color;
|
|
bool _filled;
|
|
|
|
public LuneStatusBadge()
|
|
{
|
|
pickingMode = PickingMode.Ignore;
|
|
style.flexDirection = FlexDirection.Row;
|
|
style.alignItems = Align.Center;
|
|
style.alignSelf = Align.Center;
|
|
style.flexShrink = 0f;
|
|
style.paddingLeft = 7f;
|
|
style.paddingRight = 7f;
|
|
style.paddingTop = 1f;
|
|
style.paddingBottom = 1f;
|
|
style.borderTopLeftRadius = 4f;
|
|
style.borderTopRightRadius = 4f;
|
|
style.borderBottomLeftRadius = 4f;
|
|
style.borderBottomRightRadius = 4f;
|
|
style.borderLeftWidth = 1f;
|
|
style.borderRightWidth = 1f;
|
|
style.borderTopWidth = 1f;
|
|
style.borderBottomWidth = 1f;
|
|
_glyph = LuneUi.Glyph("", Color.white, 12f, this);
|
|
_glyph.style.marginRight = 4f;
|
|
LuneUi.Show(_glyph, false);
|
|
_label = LuneUi.Label("", "HudBadge", this);
|
|
}
|
|
|
|
public void SetState(string text, Color color, string glyph = "", bool filled = false)
|
|
{
|
|
if (text == Text && color == _color && filled == _filled && glyph == _glyph.Glyph)
|
|
return;
|
|
Text = text;
|
|
_color = color;
|
|
_filled = filled;
|
|
_label.text = text;
|
|
Color ink = filled ? LuneUiTheme.Hex("15110b") : LuneUiTheme.Lightened(color, .15f);
|
|
LuneUi.SetColor(_label, ink);
|
|
LuneUi.Show(_glyph, !string.IsNullOrEmpty(glyph));
|
|
_glyph.Glyph = glyph;
|
|
_glyph.Color = ink;
|
|
Color background = filled ? LuneUiTheme.WithAlpha(color, .92f) : LuneUiTheme.WithAlpha(LuneUiTheme.Darkened(color, .75f), .85f);
|
|
Color border = filled ? LuneUiTheme.Lightened(color, .2f) : color;
|
|
style.backgroundColor = background;
|
|
style.borderLeftColor = border;
|
|
style.borderRightColor = border;
|
|
style.borderTopColor = border;
|
|
style.borderBottomColor = border;
|
|
}
|
|
}
|
|
|
|
/// <summary>Tabs with a bronze underline for the open one (Godot LuneTabBar). Select changes only the display; TabChanged fires on input.</summary>
|
|
public sealed class LuneTabBar : VisualElement
|
|
{
|
|
public event Action<int> TabChanged;
|
|
public int Current { get; private set; }
|
|
public readonly System.Collections.Generic.List<LuneButton> Tabs = new System.Collections.Generic.List<LuneButton>();
|
|
|
|
public LuneTabBar()
|
|
{
|
|
pickingMode = PickingMode.Ignore;
|
|
style.flexDirection = FlexDirection.Row;
|
|
}
|
|
|
|
public LuneButton AddTab(string title, string tipTitle = "", string tipText = "")
|
|
{
|
|
var tab = new LuneButton(title, LuneButtons.HudTab, this);
|
|
int index = Tabs.Count;
|
|
if (index > 0)
|
|
tab.style.marginLeft = 2f;
|
|
tab.Clicked += () =>
|
|
{
|
|
Select(index);
|
|
TabChanged?.Invoke(index);
|
|
};
|
|
if (tipTitle.Length > 0)
|
|
LuneTooltip.Attach(tab, () => new LuneTip { Title = tipTitle, Text = tipText });
|
|
Tabs.Add(tab);
|
|
tab.On = index == 0;
|
|
return tab;
|
|
}
|
|
|
|
public void Select(int index)
|
|
{
|
|
Current = Mathf.Clamp(index, 0, Tabs.Count - 1);
|
|
for (int i = 0; i < Tabs.Count; i++)
|
|
Tabs[i].On = i == Current;
|
|
}
|
|
}
|
|
}
|