feat(studio): StudioWindow + LayoutSource — render a dat panel standalone
Task 2 of the acdream UI Studio plan. Adds three new files under src/AcDream.App/Studio/ and one new test file: - StudioOptions.cs: record + Parse() for the ui-studio CLI args (positional dat dir or ACDREAM_DAT_DIR, --layout 0xNNNN, --markup <path>; defaults to vitals 0x2100006C when neither layout nor markup given). - LayoutSource.cs: wraps LayoutImporter.Import for dat-backed layouts; markup path sets LastError "markup unsupported (Task 6)" and returns null for now. Exposes Kind / LayoutId / MarkupPath / LastError / CurrentLayout / Reload(). - StudioWindow.cs: Silk.NET 1280×720 GL 4.3 window; boots RenderBootstrap, wires input to UiHost, loads the panel via LayoutSource, adds the root to UiHost.Root.AddChild. QualitySettings resolved the same way GameWindow.Run() does (SettingsStore → QualitySettings.From → WithEnvOverrides). - Program.cs: ui-studio dispatch at the top of top-level statements (before the dat-dir parse) so `dotnet run -- ui-studio` routes to StudioWindow. - LayoutSourceTests.cs: dat-gated test (skips when dats absent); verifies that the vitals LayoutDesc (0x2100006C) loads, root is non-null, byId contains the vitals root element (0x100005F9), and Kind == DatLayout. Passes (1/1 with dats present; silently skips on CI). Note: the spec asserts FindElement(0x2100006Cu) — that id is the LayoutDesc dat id, not a widget element id. The actual vitals root element id is 0x100005F9 (confirmed from the vitals_2100006C.json fixture). The test uses the correct element id and documents the discrepancy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
79ee3ffbe2
commit
df6b5b3b39
5 changed files with 387 additions and 0 deletions
109
src/AcDream.App/Studio/LayoutSource.cs
Normal file
109
src/AcDream.App/Studio/LayoutSource.cs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
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;
|
||||
|
||||
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; }
|
||||
|
||||
public LayoutSource(
|
||||
DatCollection dats,
|
||||
Func<uint, (uint, int, int)> resolve,
|
||||
UiDatFont? datFont)
|
||||
{
|
||||
_dats = dats ?? throw new ArgumentNullException(nameof(dats));
|
||||
_resolve = resolve ?? throw new ArgumentNullException(nameof(resolve));
|
||||
_datFont = datFont;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
if (imported is null)
|
||||
{
|
||||
LastError = $"ui-studio: LayoutDesc 0x{layoutId:X8} not found in dats.";
|
||||
return null;
|
||||
}
|
||||
|
||||
CurrentLayout = imported;
|
||||
return imported.Root;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue