feat(D.2b): UiDatElement — generic per-drawmode element renderer
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 <noreply@anthropic.com>
This commit is contained in:
parent
55239575e6
commit
cc4de3ef77
2 changed files with 104 additions and 0 deletions
87
src/AcDream.App/UI/Layout/UiDatElement.cs
Normal file
87
src/AcDream.App/UI/Layout/UiDatElement.cs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace AcDream.App.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// 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".
|
||||
///
|
||||
/// <para>
|
||||
/// 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.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// DrawModeType (DatReaderWriter.Enums), stored as int in <see cref="ElementInfo"/> to
|
||||
/// keep this dat-free. See docs/research/2026-06-15-layoutdesc-format.md §6:
|
||||
/// <c>Undefined=0, Normal=1, Overlay=2, Alphablend=3</c>. There is no Stretch mode.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Tiling uses UV-repeat on BOTH axes (<c>Width/tw</c>, <c>Height/th</c>) so vertical
|
||||
/// chrome edges (e.g. a 5×10 sprite drawn over a 5×48 rect) tile vertically too.
|
||||
/// <see cref="AcDream.App.Rendering.TextureCache.UploadRgba8"/> sets
|
||||
/// <c>GL_REPEAT</c> on both S and T, so vertical tiling is always active.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
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<uint, (uint tex, int w, int h)> _resolve;
|
||||
|
||||
/// <summary>Which state name to render. <c>""</c> = the unnamed DirectState.
|
||||
/// Falls back to DirectState if the named state is absent.</summary>
|
||||
public string ActiveState { get; set; } = "";
|
||||
|
||||
/// <param name="info">Merged <see cref="ElementInfo"/> for this element.</param>
|
||||
/// <param name="resolve">Dat file-id → (GL texture handle, native px width, native px height).
|
||||
/// Returns (0,0,0) when the texture is not yet uploaded.</param>
|
||||
public UiDatElement(ElementInfo info, Func<uint, (uint tex, int w, int h)> resolve)
|
||||
{
|
||||
_info = info;
|
||||
_resolve = resolve;
|
||||
ClickThrough = true; // generic decoration; behavioral widgets opt back in
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the (File, DrawMode) for the current <see cref="ActiveState"/>,
|
||||
/// falling back to the DirectState (<c>""</c> key) if the named state is absent.
|
||||
/// Returns (0, 0) if neither exists.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
17
tests/AcDream.App.Tests/UI/Layout/UiDatElementTests.cs
Normal file
17
tests/AcDream.App.Tests/UI/Layout/UiDatElementTests.cs
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue