fix(ui): restore retail spell tabs and spellbook

Bind the spellbook and component-book tabs as their authored stateful text controls, propagate Closed/Open state into the retained DAT child tree, and keep authored label colors live across state changes.

Match retail UIElement_Text zero-margin construction so the Magic favorite captions I through VIII are no longer clipped. Add a real portal.dat spellbook fixture plus controller, state-propagation, and text-layout regression coverage.

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-15 15:54:11 +02:00
parent 07be994d97
commit ad30c37a48
15 changed files with 13492 additions and 38 deletions

View file

@ -71,6 +71,54 @@ public class LayoutImporterTests
Assert.NotNull(tree.FindElement(0x20000002));
}
[Fact]
public void BuildFromInfos_TextPassToChildren_PropagatesDefaultAndRuntimeState()
{
var tab = new ElementInfo
{
Id = 0x100002A9u,
Type = 12,
Width = 100,
Height = 25,
DefaultStateId = RetailUiStateIds.Closed,
DefaultStateName = "Closed",
};
tab.States[RetailUiStateIds.Closed] = new UiStateInfo
{ Id = RetailUiStateIds.Closed, Name = "Closed", PassToChildren = true };
tab.States[RetailUiStateIds.Open] = new UiStateInfo
{ Id = RetailUiStateIds.Open, Name = "Open", PassToChildren = true };
var cap = new ElementInfo { Id = 0x10000439u, Type = 3, Width = 17, Height = 25 };
cap.States[RetailUiStateIds.Closed] = new UiStateInfo
{
Id = RetailUiStateIds.Closed,
Name = "Closed",
Image = new UiImageMedia(0x06005D93u, 3),
};
cap.States[RetailUiStateIds.Open] = new UiStateInfo
{
Id = RetailUiStateIds.Open,
Name = "Open",
Image = new UiImageMedia(0x06005D92u, 3),
};
cap.StateMedia["Closed"] = (0x06005D93u, 3);
cap.StateMedia["Open"] = (0x06005D92u, 3);
tab.Children.Add(cap);
var root = new ElementInfo { Id = 1u, Type = 3, Width = 100, Height = 25 };
ImportedLayout layout = LayoutImporter.BuildFromInfos(root, [tab], NoTex, null);
UiText text = Assert.IsType<UiText>(layout.FindElement(tab.Id));
UiDatElement child = Assert.IsType<UiDatElement>(layout.FindElement(cap.Id));
Assert.Single(text.Children);
Assert.Equal(RetailUiStateIds.Closed, child.ActiveRetailStateId);
Assert.Equal(0x06005D93u, child.ActiveMedia().File);
Assert.True(text.TrySetRetailState(RetailUiStateIds.Open));
Assert.Equal(RetailUiStateIds.Open, child.ActiveRetailStateId);
Assert.Equal(0x06005D92u, child.ActiveMedia().File);
}
// ── Test 3: Meter consumes Type-3 slice children — child ids not in byId ────
/// <summary>