DatWidgetFactory.Create now accepts an optional fontResolve: Func<uint,UiDatFont?> parameter. When supplied and the element has a non-zero FontDid, the element receives its own dat font instead of the shared global datFont fallback. Null = original single-font behavior (the live GameWindow path passes null — provably unchanged). LayoutImporter.Build/BuildFromInfos/Import all thread the optional resolver down to the factory. RenderStack gains a lazy font cache (ConcurrentDictionary, pre-seeded with VitalsDatFont + LargeDatFont) and a ResolveDatFont(uint) method. StudioWindow wires stack.ResolveDatFont into LayoutSource so every studio import gets per-element fonts. GameWindow import calls left passing null (follow-up todo). CharacterStatController font-hack cleanup (diagnosed via one-shot console dump then removed): - Name (0x10000231): dat FontDid = 18px font — remove datFont override (null) - Heritage/PkStatus: dat FontDid = 14px fonts — remove override - LevelCaption: dat FontDid = 16px — remove override (same font, no visual change) - Level (0x1000023B): dat FontDid = 36px (the big retail gold font) — was forced to rowDatFont/LargeDatFont (18px); now drops to null so the dat 36px font drives - TotalXpLabel/TotalXp: dat FontDid = 16px — remove override - FooterTitle (0x1000024E): dat FontDid = 20px — remove datFont override - KEEP: synthesized elements (XP meter overlays, 9 attribute rows, tab sprites) still use datFont directly since they have no dat origin All Label/LabelTwoLine/LabelLeft/LabelProvider helpers updated: null = keep build-time dat font; non-null = controller explicit override (backward-compat). 8 new tests in DatWidgetFactoryFontResolveTests: - null resolver → DatFont == global datFont - FontDid=0 → resolver not called - resolver returns null → fallback to global datFont - resolver called with element's FontDid - controller DatFont override wins after build - LayoutImporter.Build threads fontResolve to factory - meter element fires resolver for non-zero FontDid - BuildFromInfos without fontResolve param = original behavior Build + all 710 App tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
123 lines
4.5 KiB
C#
123 lines
4.5 KiB
C#
using AcDream.App.UI;
|
|
using AcDream.App.UI.Layout;
|
|
using DatReaderWriter;
|
|
|
|
namespace AcDream.App.Studio;
|
|
|
|
/// <summary>Which kind of source the studio is currently previewing.</summary>
|
|
public enum LayoutSourceKind { DatLayout, Markup }
|
|
|
|
/// <summary>
|
|
/// Wraps the two ways the UI Studio can load a panel to preview:
|
|
/// a LayoutDesc dat id, or a KSML markup file path (Task 6 — unsupported now).
|
|
///
|
|
/// <para>Call <see cref="Load"/> with the current <see cref="StudioOptions"/> to
|
|
/// import the layout and get the root <see cref="UiElement"/>. The result is also
|
|
/// cached in <see cref="CurrentLayout"/> so <see cref="Reload"/> can re-run the same
|
|
/// source without re-reading the options.</para>
|
|
/// </summary>
|
|
public sealed class LayoutSource
|
|
{
|
|
private readonly DatCollection _dats;
|
|
private readonly Func<uint, (uint, int, int)> _resolve;
|
|
private readonly UiDatFont? _datFont;
|
|
private readonly Func<uint, UiDatFont?>? _fontResolve;
|
|
|
|
public LayoutSourceKind Kind { get; private set; }
|
|
public uint? LayoutId { get; private set; }
|
|
public string? MarkupPath { get; private set; }
|
|
public string? LastError { get; private set; }
|
|
public ImportedLayout? CurrentLayout { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Create a LayoutSource.
|
|
/// </summary>
|
|
/// <param name="fontResolve">Optional per-element font resolver: FontDid →
|
|
/// <see cref="UiDatFont"/> (null when the font isn't in the dats). When supplied,
|
|
/// elements with a non-zero FontDid receive their own dat font at build time
|
|
/// instead of the shared <paramref name="datFont"/> global. Controllers that
|
|
/// explicitly set <see cref="UiText.DatFont"/> after
|
|
/// <see cref="ImportedLayout.FindElement"/> still override the build-time value.
|
|
/// Pass null (default) for the original single-font behavior — the live
|
|
/// <see cref="GameWindow"/> path passes null so it is provably unchanged.</param>
|
|
public LayoutSource(
|
|
DatCollection dats,
|
|
Func<uint, (uint, int, int)> resolve,
|
|
UiDatFont? datFont,
|
|
Func<uint, UiDatFont?>? fontResolve = null)
|
|
{
|
|
_dats = dats ?? throw new ArgumentNullException(nameof(dats));
|
|
_resolve = resolve ?? throw new ArgumentNullException(nameof(resolve));
|
|
_datFont = datFont;
|
|
_fontResolve = fontResolve;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load the layout described by <paramref name="opts"/>. For a dat layout
|
|
/// (<see cref="StudioOptions.LayoutId"/> is non-null) calls
|
|
/// <see cref="LayoutImporter.Import"/>. For a markup path sets
|
|
/// <see cref="LastError"/> and returns null (Task 6, not yet implemented).
|
|
///
|
|
/// Returns the root <see cref="UiElement"/> on success, or null on failure
|
|
/// (check <see cref="LastError"/>).
|
|
/// </summary>
|
|
public UiElement? Load(StudioOptions opts)
|
|
{
|
|
LastError = null;
|
|
CurrentLayout = null;
|
|
|
|
if (opts.MarkupPath is not null)
|
|
{
|
|
Kind = LayoutSourceKind.Markup;
|
|
MarkupPath = opts.MarkupPath;
|
|
LastError = "markup unsupported (Task 6)";
|
|
return null;
|
|
}
|
|
|
|
if (opts.LayoutId is null)
|
|
{
|
|
LastError = "ui-studio: no layout id or markup path specified.";
|
|
return null;
|
|
}
|
|
|
|
Kind = LayoutSourceKind.DatLayout;
|
|
LayoutId = opts.LayoutId;
|
|
return LoadDat(opts.LayoutId.Value);
|
|
}
|
|
|
|
/// <summary>Re-run the most-recently-configured source without re-reading options.</summary>
|
|
public UiElement? Reload()
|
|
{
|
|
LastError = null;
|
|
CurrentLayout = null;
|
|
|
|
if (Kind == LayoutSourceKind.Markup)
|
|
{
|
|
LastError = "markup unsupported (Task 6)";
|
|
return null;
|
|
}
|
|
|
|
if (LayoutId is null)
|
|
{
|
|
LastError = "ui-studio: no layout id to reload.";
|
|
return null;
|
|
}
|
|
|
|
return LoadDat(LayoutId.Value);
|
|
}
|
|
|
|
// ── Private ──────────────────────────────────────────────────────────────────
|
|
|
|
private UiElement? LoadDat(uint layoutId)
|
|
{
|
|
var imported = LayoutImporter.Import(_dats, layoutId, _resolve, _datFont, _fontResolve);
|
|
if (imported is null)
|
|
{
|
|
LastError = $"ui-studio: LayoutDesc 0x{layoutId:X8} not found in dats.";
|
|
return null;
|
|
}
|
|
|
|
CurrentLayout = imported;
|
|
return imported.Root;
|
|
}
|
|
}
|