using System; using System.Numerics; namespace AcDream.App.UI.Layout; /// /// Generic dat element: draws its active state's media by DrawMode (Normal=tile, /// Alphablend/Overlay=blended overlay). The fallback renderer for every element type /// without a dedicated behavioral widget (chrome corners/edges, drag bars, resize grips); /// faithful because retail's base element render is exactly "stamp the media per draw-mode". /// /// /// For Plan 1, all observed draw modes produce the same alpha-blended tiled quad — the /// sprite shader already alpha-blends, so no per-mode branch is needed here. The named /// constants document the real enum for Plan 2. /// /// /// /// DrawModeType (DatReaderWriter.Enums), stored as int in to /// keep this dat-free. See docs/research/2026-06-15-layoutdesc-format.md §6: /// Undefined=0, Normal=1, Overlay=2, Alphablend=3. There is no Stretch mode. /// /// /// /// Tiling uses UV-repeat on BOTH axes (Width/tw, Height/th) so vertical /// chrome edges (e.g. a 5×10 sprite drawn over a 5×48 rect) tile vertically too. /// sets /// GL_REPEAT on both S and T, so vertical tiling is always active. /// /// public sealed class UiDatElement : UiElement { // DrawModeType enum values from DatReaderWriter.Enums. // See docs/research/2026-06-15-layoutdesc-format.md §6. #pragma warning disable IDE0051 // private constants kept for documentation / Plan 2 private const int DrawUndefined = 0; private const int DrawNormal = 1; private const int DrawOverlay = 2; private const int DrawAlphablend = 3; #pragma warning restore IDE0051 private readonly ElementInfo _info; private readonly Func _resolve; /// Which state name to render. "" = the unnamed DirectState. /// Falls back to DirectState if the named state is absent. public string ActiveState { get; set; } = ""; /// Merged for this element. /// Dat file-id → (GL texture handle, native px width, native px height). /// Returns (0,0,0) when the texture is not yet uploaded. public UiDatElement(ElementInfo info, Func resolve) { _info = info; _resolve = resolve; ClickThrough = true; // generic decoration; behavioral widgets opt back in // Pick the initial active state: retail applies DefaultState when set; falls back // to "Normal" when the element has a Normal-state sprite (retail's implicit default // for stateful elements like tabs and buttons); else the unnamed DirectState (""). if (!string.IsNullOrEmpty(info.DefaultStateName)) ActiveState = info.DefaultStateName; else if (info.StateMedia.ContainsKey("Normal")) ActiveState = "Normal"; // else ActiveState stays "" (DirectState) } /// /// Returns the (File, DrawMode) for the current , /// falling back to the DirectState ("" key) if the named state is absent. /// Returns (0, 0) if neither exists. /// // exposed for unit testing public (uint File, int DrawMode) ActiveMedia() => _info.StateMedia.TryGetValue(ActiveState, out var m) ? m : _info.StateMedia.TryGetValue("", out var d) ? d : (0u, 0); /// Optional click handler. Set by a controller for interactive dat /// elements (e.g. the chat Send / max-min buttons). Requires /// = false to receive click events. public Action? OnClick { get; set; } public override bool OnEvent(in UiEvent e) { if (e.Type == UiEventType.Click && OnClick is not null) { OnClick(); return true; } return false; } /// Optional centered text label drawn over the sprite (e.g. the "Send" /// button face whose dat sprite is a blank frame). Null = sprite only. public string? Label { get; set; } /// Dat font for . Required for the label to draw. public UiDatFont? LabelFont { get; set; } /// Label color (default white). public Vector4 LabelColor { get; set; } = Vector4.One; protected override void OnDraw(UiRenderContext ctx) { var (file, _) = ActiveMedia(); if (file != 0) { var (tex, tw, th) = _resolve(file); if (tex != 0 && tw != 0 && th != 0) { // Normal → TILE at native size on both axes (UV-repeat; GL_REPEAT-wrapped UI // texture), matching ImgTex::TileCSI. Overlay/Alphablend use the same blit (the // sprite shader already alpha-blends). No Stretch mode exists in DrawModeType. ctx.DrawSprite(tex, 0, 0, Width, Height, 0, 0, Width / tw, Height / th, Vector4.One); } } // Centered text label over the sprite (retail draws button captions as text; // their dat sprites are blank frames). if (Label is { Length: > 0 } label && LabelFont is { } lf) { float tx = (Width - lf.MeasureWidth(label)) * 0.5f; float ty = (Height - lf.LineHeight) * 0.5f; ctx.DrawStringDat(lf, label, tx, ty, LabelColor); } } }