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

@ -482,6 +482,50 @@ public class DatWidgetFactoryTests
Assert.Equal(gold, t.DefaultColor);
}
[Fact]
public void BuildText_RetailConstructorDefaultsToZeroMargins()
{
var info = new ElementInfo { Type = 12, Width = 100, Height = 20 };
var text = Assert.IsType<UiText>(DatWidgetFactory.Create(info, NoTex, null));
Assert.Equal(0f, text.Padding);
Assert.False(text.OneLine);
}
[Fact]
public void BuildText_AuthoredLineTracksStateFontColor()
{
var info = new ElementInfo
{
Type = 12,
Width = 100,
Height = 20,
DefaultStateId = RetailUiStateIds.Closed,
DefaultStateName = "Closed",
};
var direct = new UiStateInfo { Id = UiStateInfo.DirectStateId };
direct.Properties.Values[0x17u] = new UiPropertyValue
{
Kind = UiPropertyKind.StringInfo,
StringInfoValue = new UiStringInfoValue(0, 1, 2, 0, 1, 0),
};
var closed = new UiStateInfo { Id = RetailUiStateIds.Closed, Name = "Closed" };
closed.Properties.Values[0x1Bu] = Color(127, 127, 127);
var open = new UiStateInfo { Id = RetailUiStateIds.Open, Name = "Open" };
open.Properties.Values[0x1Bu] = Color(204, 204, 204);
info.States[UiStateInfo.DirectStateId] = direct;
info.States[RetailUiStateIds.Closed] = closed;
info.States[RetailUiStateIds.Open] = open;
var text = Assert.IsType<UiText>(DatWidgetFactory.Create(
info, NoTex, null, stringResolve: _ => "Spells"));
Assert.Equal(127f / 255f, text.LinesProvider()[0].Color.X, 5);
Assert.True(text.TrySetRetailState(RetailUiStateIds.Open));
Assert.Equal(204f / 255f, text.LinesProvider()[0].Color.X, 5);
}
[Fact]
public void HorizontalScrollbar_PreservesNestedCombatMeterFillSprite()
{
@ -560,6 +604,13 @@ public class DatWidgetFactoryTests
private static UiPropertyValue Integer(int value)
=> new() { Kind = UiPropertyKind.Integer, IntegerValue = value };
private static UiPropertyValue Color(byte red, byte green, byte blue)
=> new()
{
Kind = UiPropertyKind.Color,
ColorValue = new UiColorValue(blue, green, red, 255),
};
/// <summary>
/// When ElementInfo.FontColor is null (dat carried no 0x1B), DefaultColor must
/// stay at white (Vector4.One) — the backward-compatible default.