From cc4de3ef77e7e105e371d3feb18774bb8daac584 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 15 Jun 2026 13:29:16 +0200 Subject: [PATCH] =?UTF-8?q?feat(D.2b):=20UiDatElement=20=E2=80=94=20generi?= =?UTF-8?q?c=20per-drawmode=20element=20renderer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generic fallback widget for every LayoutDesc element type without a dedicated behavioral widget (chrome corners/edges, drag bars, resize grips). Holds an ElementInfo + active-state name; draws that state's media by tiling (UV-repeat on both S+T axes, matching ImgTex::TileCSI). DrawMode constants documented per format spec §6 (Undefined=0, Normal=1, Overlay=2, Alphablend=3 — no Stretch mode). Plan 1: all modes render as the same alpha-blended tiled quad; per-mode branches deferred to Plan 2. Co-Authored-By: Claude Sonnet 4.6 --- src/AcDream.App/UI/Layout/UiDatElement.cs | 87 +++++++++++++++++++ .../UI/Layout/UiDatElementTests.cs | 17 ++++ 2 files changed, 104 insertions(+) create mode 100644 src/AcDream.App/UI/Layout/UiDatElement.cs create mode 100644 tests/AcDream.App.Tests/UI/Layout/UiDatElementTests.cs diff --git a/src/AcDream.App/UI/Layout/UiDatElement.cs b/src/AcDream.App/UI/Layout/UiDatElement.cs new file mode 100644 index 00000000..892f053a --- /dev/null +++ b/src/AcDream.App/UI/Layout/UiDatElement.cs @@ -0,0 +1,87 @@ +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 + } + + /// + /// 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. + /// + public (uint File, int DrawMode) ActiveMedia() + => _info.StateMedia.TryGetValue(ActiveState, out var m) ? m + : _info.StateMedia.TryGetValue("", out var d) ? d + : (0u, 0); + + protected override void OnDraw(UiRenderContext ctx) + { + var (file, drawMode) = ActiveMedia(); + if (file == 0) return; + + var (tex, tw, th) = _resolve(file); + if (tex == 0 || tw == 0 || th == 0) return; + + // Normal → TILE at native size on both axes (UV-repeat; GL_REPEAT-wrapped UI texture), + // matching ImgTex::TileCSI. Overlay/Alphablend are the same blit with a blend state; the + // sprite shader already alpha-blends, so the quad is identical for all draw modes in Plan 1. + // (No Stretch mode exists in DatReaderWriter.Enums.DrawModeType.) + // drawMode is not yet branched here — Plan 2 can add per-mode behavior if needed. + _ = drawMode; // suppress unused-variable warning until Plan 2 adds per-mode branches + float u1 = Width / tw; + float v1 = Height / th; + ctx.DrawSprite(tex, 0, 0, Width, Height, 0, 0, u1, v1, Vector4.One); + } +} diff --git a/tests/AcDream.App.Tests/UI/Layout/UiDatElementTests.cs b/tests/AcDream.App.Tests/UI/Layout/UiDatElementTests.cs new file mode 100644 index 00000000..91f66d49 --- /dev/null +++ b/tests/AcDream.App.Tests/UI/Layout/UiDatElementTests.cs @@ -0,0 +1,17 @@ +using AcDream.App.UI.Layout; +namespace AcDream.App.Tests.UI.Layout; + +public class UiDatElementTests +{ + [Fact] + public void ActiveMedia_PrefersNamedStateOverDirect() + { + var info = new ElementInfo(); + info.StateMedia[""] = (0x06000001, 1); // DirectState (DrawMode Normal=1) + info.StateMedia["ShowDetail"] = (0x06000002, 3); // named (Alphablend=3) + var e = new UiDatElement(info, _ => (0, 0, 0)) { ActiveState = "ShowDetail" }; + Assert.Equal(0x06000002u, e.ActiveMedia().File); + e.ActiveState = ""; + Assert.Equal(0x06000001u, e.ActiveMedia().File); + } +}