feat(D.2b): LayoutImporter — read layout + resolve inheritance + build tree
Implements Task 5 of the LayoutDesc Importer (Plan 1 — vitals conformance). Pure layer (BuildFromInfos / Build): - ImportedLayout result type: UiElement root + O(1) FindElement(uint id) lookup - BuildWidget dispatches via DatWidgetFactory.Create; skips Type-12 prototypes (null) - Meters consume their children (DatWidgetFactory already extracted slice ids — adding the dat children as UiElement nodes would duplicate geometry) - All other element types recurse children generically via AddChild Dat shell (Import): - Loads LayoutDesc from dats; null-safe if layout is absent - Resolves each top-level ElementDesc to ElementInfo via Resolve(): BaseElement/BaseLayoutId chain with (layoutId,elementId) cycle guard - ToInfo(): reads ElementDesc scalar fields (uint → float cast) + DirectState + named States (UIStateId.ToString() as key) - ReadState(): extracts first MediaDescImage (File + DrawMode) per state + font DID from Properties[0x1A] → ArrayBaseProperty → DataIdBaseProperty.Value - Each sibling element gets a fresh base-chain set (siblings don't share guards) DRW API: all members confirmed from VitalsLayoutDump.cs usings — no adjustments needed: LayoutDesc in DBObjs; ElementDesc/StateDesc/MediaDescImage/ ArrayBaseProperty/DataIdBaseProperty in Types; DrawModeType/UIStateId in Enums. Tests (3/3 green): - BuildFromInfos_HealthMeter_IsUiMeterAtRect — Type-7 child → UiMeter, Left=5, Width=150 - BuildFromInfos_Type12Child_IsSkipped_Type3Present — prototype absent, container present - BuildFromInfos_MeterWithChildren_MeterPresent_ChildrenNotInTree — meter findable, both dat-children absent, UiMeter.Children empty Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
fc79fd519d
commit
bd01a29eb2
2 changed files with 383 additions and 0 deletions
105
tests/AcDream.App.Tests/UI/Layout/LayoutImporterTests.cs
Normal file
105
tests/AcDream.App.Tests/UI/Layout/LayoutImporterTests.cs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// Pure unit tests for <see cref="LayoutImporter.BuildFromInfos"/> — no dats, no GL.
|
||||
/// Verifies the tree-builder: widget dispatch, Type-12 skipping, and meter child consumption.
|
||||
/// </summary>
|
||||
public class LayoutImporterTests
|
||||
{
|
||||
private static (uint, int, int) NoTex(uint _) => (0, 0, 0);
|
||||
|
||||
// ── Test 1: Health meter element → UiMeter with correct rect ─────────────
|
||||
|
||||
/// <summary>
|
||||
/// A Type-7 (meter) child element with X=5,Y=5,W=150,H=16 must produce a UiMeter
|
||||
/// that is findable by its id, positioned at Left=5, Width=150.
|
||||
/// The resolve lambda is a 1-arg Func<uint,(uint,int,int)>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void BuildFromInfos_HealthMeter_IsUiMeterAtRect()
|
||||
{
|
||||
var root = new ElementInfo { Id = 0x100005F9, Type = 3, Width = 160, Height = 58 };
|
||||
var health = new ElementInfo { Id = 0x100000E6, Type = 7, X = 5, Y = 5, Width = 150, Height = 16 };
|
||||
|
||||
var tree = LayoutImporter.BuildFromInfos(root, new[] { health }, NoTex, null);
|
||||
|
||||
var found = tree.FindElement(0x100000E6);
|
||||
Assert.IsType<UiMeter>(found);
|
||||
Assert.Equal(5f, found!.Left);
|
||||
Assert.Equal(150f, found.Width);
|
||||
}
|
||||
|
||||
// ── Test 2: Type-12 child is skipped; Type-3 sibling is present ──────────
|
||||
|
||||
/// <summary>
|
||||
/// A root with two children: one Type-12 style prototype and one Type-3 container.
|
||||
/// The Type-12 must be absent from the tree (FindElement returns null);
|
||||
/// the Type-3 must be present.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void BuildFromInfos_Type12Child_IsSkipped_Type3Present()
|
||||
{
|
||||
var root = new ElementInfo { Id = 0x10000001, Type = 3, Width = 160, Height = 58 };
|
||||
var prototype = new ElementInfo { Id = 0x20000001, Type = 12, Width = 0, Height = 0 };
|
||||
var container = new ElementInfo { Id = 0x20000002, Type = 3, Width = 100, Height = 20 };
|
||||
|
||||
var tree = LayoutImporter.BuildFromInfos(root, new[] { prototype, container }, NoTex, null);
|
||||
|
||||
// Type-12 must be absent.
|
||||
Assert.Null(tree.FindElement(0x20000001));
|
||||
// Type-3 must be present.
|
||||
Assert.NotNull(tree.FindElement(0x20000002));
|
||||
}
|
||||
|
||||
// ── Test 3: Meter consumes its children — child ids not in byId ──────────
|
||||
|
||||
/// <summary>
|
||||
/// A meter (Type 7) whose children are the 3-slice back/front containers.
|
||||
/// The meter itself must be findable; its direct children must NOT appear as
|
||||
/// separate nodes in the tree (meters own their children, not the generic tree).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void BuildFromInfos_MeterWithChildren_MeterPresent_ChildrenNotInTree()
|
||||
{
|
||||
const uint MeterId = 0x100000E6u;
|
||||
const uint BackLayerId = 0x100000E7u;
|
||||
const uint FrontLayerId = 0x00000002u;
|
||||
|
||||
// Build a minimal meter with back + front containers, each with 3 slice children.
|
||||
var backContainer = BuildSliceContainer(BackLayerId, ReadOrder: 0,
|
||||
l: 0x0600747Eu, t: 0x0600747Fu, r: 0x06007480u);
|
||||
var frontContainer = BuildSliceContainer(FrontLayerId, ReadOrder: 1,
|
||||
l: 0x06007481u, t: 0x06007482u, r: 0x06007483u);
|
||||
|
||||
var meter = new ElementInfo { Id = MeterId, Type = 7, Width = 150, Height = 16 };
|
||||
meter.Children.Add(backContainer);
|
||||
meter.Children.Add(frontContainer);
|
||||
|
||||
var root = new ElementInfo { Id = 0x100005F9, Type = 3, Width = 160, Height = 58 };
|
||||
|
||||
var tree = LayoutImporter.BuildFromInfos(root, new[] { meter }, NoTex, null);
|
||||
|
||||
// The meter widget is present.
|
||||
Assert.IsType<UiMeter>(tree.FindElement(MeterId));
|
||||
// The meter's dat-children are NOT separate UiElement nodes.
|
||||
Assert.Null(tree.FindElement(BackLayerId));
|
||||
Assert.Null(tree.FindElement(FrontLayerId));
|
||||
// The UiMeter itself has no Ui children (meters consume their children internally).
|
||||
var uiMeter = (UiMeter)tree.FindElement(MeterId)!;
|
||||
Assert.Empty(uiMeter.Children);
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
private static ElementInfo BuildSliceContainer(uint id, uint ReadOrder, uint l, uint t, uint r)
|
||||
{
|
||||
var c = new ElementInfo { Id = id, Type = 3, ReadOrder = ReadOrder };
|
||||
c.Children.Add(new ElementInfo { X = 0, StateMedia = { [""] = (l, 1) } });
|
||||
c.Children.Add(new ElementInfo { X = 10, StateMedia = { [""] = (t, 1) } });
|
||||
c.Children.Add(new ElementInfo { X = 140, StateMedia = { [""] = (r, 1) } });
|
||||
return c;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue