feat(ui): port retained widget foundations

This commit is contained in:
Erik 2026-07-10 17:55:41 +02:00
parent 44f9ec13d9
commit d825572e31
44 changed files with 84813 additions and 292 deletions

View file

@ -22,4 +22,140 @@ public class UiButtonTests
var b = new UiButton(new ElementInfo { Type = 1 }, NoTex);
Assert.False(b.ClickThrough);
}
[Fact]
public void PointerTransitions_UseRetailNormalStates()
{
var b = ButtonWithStates("Normal", "Normal_rollover", "Normal_pressed");
b.OnEvent(new UiEvent(0, b, UiEventType.HoverEnter));
Assert.Equal("Normal_rollover", b.ActiveState);
b.OnEvent(new UiEvent(0, b, UiEventType.MouseDown, Data1: 5, Data2: 5));
Assert.Equal("Normal_pressed", b.ActiveState);
b.OnEvent(new UiEvent(0, b, UiEventType.MouseMove, Data1: 50, Data2: 50));
Assert.Equal("Normal_rollover", b.ActiveState);
b.OnEvent(new UiEvent(0, b, UiEventType.MouseMove, Data1: 5, Data2: 5));
Assert.Equal("Normal_pressed", b.ActiveState);
b.OnEvent(new UiEvent(0, b, UiEventType.MouseUp, Data1: 5, Data2: 5));
Assert.Equal("Normal_rollover", b.ActiveState);
}
[Fact]
public void ToggleRelease_SelectsHighlightState()
{
var info = ButtonInfo("Normal", "Highlight");
AddBoolProperty(info, 0x0Bu, true);
var b = CreateButton(info);
b.OnEvent(new UiEvent(0, b, UiEventType.MouseDown, Data1: 5, Data2: 5));
b.OnEvent(new UiEvent(0, b, UiEventType.MouseUp, Data1: 5, Data2: 5));
Assert.True(b.Selected);
Assert.Equal("Highlight", b.ActiveState);
}
[Fact]
public void DisabledProperty_SelectsGhostedAndSuppressesClick()
{
var info = ButtonInfo("Normal", "Ghosted");
AddBoolProperty(info, 0x0Du, true);
var b = CreateButton(info);
b.OnClick = () => _clicked = true;
b.OnEvent(new UiEvent(0, b, UiEventType.Click));
Assert.False(b.Enabled);
Assert.Equal("Ghosted", b.ActiveState);
Assert.False(_clicked);
}
[Fact]
public void MissingStandardState_PreservesCustomSemanticState()
{
var info = ButtonInfo("LockedUI");
info.DefaultStateName = "LockedUI";
var b = CreateButton(info);
b.OnEvent(new UiEvent(0, b, UiEventType.HoverEnter));
b.OnEvent(new UiEvent(0, b, UiEventType.MouseDown, Data1: 5, Data2: 5));
Assert.Equal("LockedUI", b.ActiveState);
}
[Fact]
public void HotClick_FiresImmediatelyRepeatsAndSuppressesReleaseClick()
{
var info = ButtonInfo("Normal");
AddBoolProperty(info, 0x0Fu, true);
AddFloatProperty(info, 0x10u, 0.10f);
AddFloatProperty(info, 0x11u, 0.05f);
int clicks = 0;
var b = CreateButton(info);
b.OnClick = () => clicks++;
b.OnEvent(new UiEvent(0, b, UiEventType.MouseDown, Data1: 5, Data2: 5));
Assert.Equal(1, clicks);
b.OnGlobalUiTime(1.00);
b.OnGlobalUiTime(1.09);
Assert.Equal(1, clicks);
b.OnGlobalUiTime(1.11);
Assert.Equal(2, clicks);
b.OnEvent(new UiEvent(0, b, UiEventType.MouseUp, Data1: 5, Data2: 5));
b.OnEvent(new UiEvent(0, b, UiEventType.Click, Data1: 5, Data2: 5));
Assert.Equal(2, clicks);
}
private static UiButton ButtonWithStates(params string[] states)
{
var info = ButtonInfo(states);
AddBoolProperty(info, 0x13u, true);
return CreateButton(info);
}
private static ElementInfo ButtonInfo(params string[] states)
{
var info = new ElementInfo { Type = 1, Width = 20, Height = 20 };
uint file = 1;
foreach (string state in states)
info.StateMedia[state] = (file++, 1);
if (states.Length > 0)
info.DefaultStateName = states[0];
return info;
}
private static void AddBoolProperty(ElementInfo info, uint id, bool value)
{
if (!info.States.TryGetValue(UiStateInfo.DirectStateId, out var state))
{
state = new UiStateInfo { Id = UiStateInfo.DirectStateId };
info.States[UiStateInfo.DirectStateId] = state;
}
state.Properties.Values[id] = new UiPropertyValue
{
Kind = UiPropertyKind.Bool,
BoolValue = value,
};
}
private static void AddFloatProperty(ElementInfo info, uint id, float value)
{
if (!info.States.TryGetValue(UiStateInfo.DirectStateId, out var state))
{
state = new UiStateInfo { Id = UiStateInfo.DirectStateId };
info.States[UiStateInfo.DirectStateId] = state;
}
state.Properties.Values[id] = new UiPropertyValue
{
Kind = UiPropertyKind.Float,
FloatValue = value,
};
}
private static UiButton CreateButton(ElementInfo info)
=> new(info, NoTex) { Width = info.Width, Height = info.Height };
}