fix(D.2b): arrow swap, centered menu text, scrollbar-to-top, Send caption - scroll arrows: native sprites are opposite (0x06004C6C up / 0x06004C69 down) per live visual — swap the assignment, drop the V-flip. - menu labels centered vertically in each 17px row (was top-aligned, looked corrupt). - scrollbar pulled up to the panel top so the top arrow meets the window border and the max/min button lines up with it (the 6px dat offset left a gap after the resize-bar reclaim). - Send button: the dat sprite 0x06001915 is a blank gold frame (export-confirmed), so add a generic optional Label/LabelFont to UiDatElement and draw "Send" centered on it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
122 lines
5.5 KiB
C#
122 lines
5.5 KiB
C#
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
|
||
|
||
// 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)
|
||
}
|
||
|
||
/// <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>
|
||
// 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);
|
||
|
||
/// <summary>Optional click handler. Set by a controller for interactive dat
|
||
/// elements (e.g. the chat Send / max-min buttons). Requires
|
||
/// <see cref="UiElement.ClickThrough"/> = false to receive click events.</summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>Optional centered text label drawn over the sprite (e.g. the "Send"
|
||
/// button face whose dat sprite is a blank frame). Null = sprite only.</summary>
|
||
public string? Label { get; set; }
|
||
/// <summary>Dat font for <see cref="Label"/>. Required for the label to draw.</summary>
|
||
public UiDatFont? LabelFont { get; set; }
|
||
/// <summary>Label color (default white).</summary>
|
||
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);
|
||
}
|
||
}
|
||
}
|