fix(ui): complete retail indicator detail panels

Port the authored effect row template, remaining-time and selection details, synchronize the full gmPanelUI child geometry, and route the burden indicator to Character Information panel 3.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-17 11:36:43 +02:00
parent a96767ba6d
commit d1d603105f
24 changed files with 3696 additions and 147 deletions

View file

@ -6,7 +6,8 @@ using AcDream.App.UI;
namespace AcDream.App.UI.Layout;
/// <summary>
/// Per-window controller for the Character window (LayoutDesc 0x2100002E, gmCharacterInfoUI).
/// Per-window controller for the Character Information child in the production
/// gmPanelUI layout (LayoutDesc 0x2100006E, root 0x10000183).
///
/// <para>Retail fills a single scrollable <c>UIElement_Text</c> element (id 0x1000011d,
/// <c>m_pMainText</c>) by calling a sequence of Update* methods that each
@ -32,9 +33,14 @@ namespace AcDream.App.UI.Layout;
/// </summary>
public static class CharacterController
{
public const uint LayoutId = 0x2100006Eu;
public const uint RootId = 0x10000183u;
/// <summary>Dat element id for the main report text (m_pMainText). Confirmed in
/// <c>gmCharacterInfoUI::PostInit</c> 0x004b86f0: <c>GetChildRecursive(this, 0x1000011d)</c>.</summary>
public const uint MainTextId = 0x1000011du;
public const uint ScrollbarId = 0x1000011Eu;
public const uint CloseId = 0x100000FCu;
/// <summary>White body text — matches retail's default UIElement_Text foreground.</summary>
private static readonly Vector4 BodyColor = new(1f, 1f, 1f, 1f);
@ -48,27 +54,27 @@ public static class CharacterController
/// <see cref="UiText.LinesProvider"/> so the Studio or a live session can push a fresh
/// <see cref="CharacterSheet"/> without re-binding.
///
/// <para>The element must resolve as a <see cref="UiText"/> (Type 12 in the dat).
/// If 0x1000011d is absent from the layout the bind is a no-op — partial layouts
/// (e.g. test fakes) don't cause errors.</para>
/// <para>Production data resolves the element as a <see cref="UiText"/>
/// (Type 12). A synthetic fallback is created only for deliberately reduced
/// test layouts.</para>
/// </summary>
/// <param name="layout">Imported 0x2100002E layout tree.</param>
/// <param name="layout">Imported 0x2100006E / 0x10000183 layout tree.</param>
/// <param name="data">Provider returning the current <see cref="CharacterSheet"/>.</param>
/// <param name="datFont">Retail dat font forwarded from the render stack. May be null
/// (falls back to the BitmapFont debug path).</param>
public static void Bind(
ImportedLayout layout,
Func<CharacterSheet> data,
UiDatFont? datFont = null)
UiDatFont? datFont = null,
Action? close = null)
{
// Retail's gmCharacterInfoUI CREATES m_pMainText (0x1000011d) at RUNTIME — it is NOT a static
// element in any LayoutDesc (confirmed: acdream's dat-import of 0x2100002E AND 0x2100006E both
// lack it; only a runtime UI-tree capture has it). So replicate that: build the report text
// element ourselves and place it in the panel body, exactly as the gm*UI does at runtime.
// End-of-retail data authors m_pMainText under the Character Information
// child. The fallback exists only so focused controller tests can bind a
// deliberately reduced tree without fabricating production structure.
var root = layout.Root;
if (root is null) return;
var text = new UiText
UiText text = layout.FindElement(MainTextId) as UiText ?? new UiText
{
EventId = MainTextId,
Name = "m_pMainText",
@ -81,8 +87,17 @@ public static class CharacterController
DatFont = datFont,
ClickThrough = false,
};
if (text.Parent is null)
root.AddChild(text);
else if (text.DatFont is null && datFont is not null)
text.DatFont = datFont;
text.PreserveEndOnLayout = false;
if (layout.FindElement(ScrollbarId) is UiScrollbar scrollbar)
scrollbar.Model = text.Scroll;
text.LinesProvider = () => BuildReport(data());
root.AddChild(text);
if (layout.FindElement(CloseId) is UiButton closeButton)
closeButton.OnClick = close;
}
// ── Report builder ────────────────────────────────────────────────────────