fix(ui): match retail spell bar controls
Place favorite-bar arrows by their authored sides, import rollover and pressed media through the shared scrollbar, and preserve manual offsets across passive refreshes. Carry the mixed-parent DAT anchor chain to a fixed 18-cell favorite viewport so overflow controls and the Cast button remain inside the retail-sized combat frame. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
02c29e67c8
commit
0134122c28
17 changed files with 424 additions and 59 deletions
|
|
@ -230,24 +230,32 @@ public class DatWidgetFactoryTests
|
|||
const uint IncrementId = 0x10000072u;
|
||||
const uint DecrementSprite = 0x06004CDCu;
|
||||
const uint IncrementSprite = 0x06004CDEu;
|
||||
const uint DecrementRollover = 0x06004CDDu;
|
||||
const uint IncrementRollover = 0x06004CDFu;
|
||||
var decrement = new ElementInfo
|
||||
{
|
||||
Id = DecrementId, Type = 1u, Width = 23f, Height = 36f,
|
||||
Id = DecrementId, Type = 1u, X = 63f, Width = 23f, Height = 36f,
|
||||
};
|
||||
decrement.States[UiStateInfo.DirectStateId] = new UiStateInfo
|
||||
{
|
||||
Id = UiStateInfo.DirectStateId,
|
||||
Image = new UiImageMedia(DecrementSprite, 1),
|
||||
};
|
||||
decrement.StateMedia["Normal"] = (DecrementSprite, 1);
|
||||
decrement.StateMedia["Normal_rollover"] = (DecrementRollover, 1);
|
||||
decrement.StateMedia["Normal_pressed"] = (DecrementSprite, 1);
|
||||
var increment = new ElementInfo
|
||||
{
|
||||
Id = IncrementId, Type = 1u, Width = 23f, Height = 36f,
|
||||
Id = IncrementId, Type = 1u, X = 0f, Width = 23f, Height = 36f,
|
||||
};
|
||||
increment.States[UiStateInfo.DirectStateId] = new UiStateInfo
|
||||
{
|
||||
Id = UiStateInfo.DirectStateId,
|
||||
Image = new UiImageMedia(IncrementSprite, 1),
|
||||
};
|
||||
increment.StateMedia["Normal"] = (IncrementSprite, 1);
|
||||
increment.StateMedia["Normal_rollover"] = (IncrementRollover, 1);
|
||||
increment.StateMedia["Normal_pressed"] = (IncrementSprite, 1);
|
||||
var info = new ElementInfo
|
||||
{
|
||||
Type = 11u,
|
||||
|
|
@ -271,8 +279,12 @@ public class DatWidgetFactoryTests
|
|||
Assert.True(bar.HideWhenDisabled);
|
||||
Assert.Equal(23f, bar.DecrementButtonExtent);
|
||||
Assert.Equal(23f, bar.IncrementButtonExtent);
|
||||
Assert.Equal(DecrementSprite, bar.UpSprite);
|
||||
Assert.Equal(IncrementSprite, bar.DownSprite);
|
||||
Assert.Equal(IncrementSprite, bar.UpSprite);
|
||||
Assert.Equal(IncrementRollover, bar.UpRolloverSprite);
|
||||
Assert.Equal(IncrementSprite, bar.UpPressedSprite);
|
||||
Assert.Equal(DecrementSprite, bar.DownSprite);
|
||||
Assert.Equal(DecrementRollover, bar.DownRolloverSprite);
|
||||
Assert.Equal(DecrementSprite, bar.DownPressedSprite);
|
||||
Assert.Equal(0u, bar.TrackSprite);
|
||||
Assert.Equal(0u, bar.ThumbSprite);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,43 @@ public sealed class RetailWindowFrameTests
|
|||
{
|
||||
private static (uint, int, int) NoTex(uint _) => (0u, 0, 0);
|
||||
|
||||
[Theory]
|
||||
[InlineData(9, 459f, 288f)]
|
||||
[InlineData(13, 587f, 416f)]
|
||||
[InlineData(18, 747f, 576f)]
|
||||
public void CombatRoot_FitsRequestedFavoriteSlotCapacity(
|
||||
int visibleSlots,
|
||||
float expectedRootWidth,
|
||||
float expectedListWidth)
|
||||
{
|
||||
ImportedLayout combat = LayoutImporter.Build(
|
||||
FixtureLoader.LoadCombatInfos(), NoTex, datFont: null);
|
||||
|
||||
float width = RetailCombatLayout.FitFavoriteSlots(combat, visibleSlots);
|
||||
|
||||
Assert.Equal(expectedRootWidth, width);
|
||||
Assert.Equal(expectedListWidth, combat.FindElement(
|
||||
SpellcastingUiController.FavoriteListId)!.Width);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CombatRoot_EighteenSlotsKeepArrowsAndCastInsideFrame()
|
||||
{
|
||||
ImportedLayout combat = LayoutImporter.Build(
|
||||
FixtureLoader.LoadCombatInfos(), NoTex, datFont: null);
|
||||
|
||||
RetailCombatLayout.FitFavoriteSlots(combat);
|
||||
|
||||
UiElement group = combat.FindElement(0x100000AAu)!;
|
||||
UiElement scrollbar = combat.FindElement(SpellcastingUiController.FavoriteScrollbarId)!;
|
||||
UiElement cast = combat.FindElement(SpellcastingUiController.CastButtonId)!;
|
||||
Assert.Equal(747f, combat.Root.Width);
|
||||
Assert.Equal((40f, 622f), (group.Left, group.Width));
|
||||
Assert.Equal(622f, scrollbar.Width);
|
||||
Assert.Equal((662f, 75f), (cast.Left, cast.Width));
|
||||
Assert.Equal(737f, cast.Left + cast.Width);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImportedChrome_MountsRootWithoutASecondFrame()
|
||||
{
|
||||
|
|
@ -41,6 +78,30 @@ public sealed class RetailWindowFrameTests
|
|||
Assert.Equal(40f, content.MinWidth);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImportedChrome_CanPreserveFixedHudDesktopAnchors()
|
||||
{
|
||||
var root = new UiRoot { Width = 1920, Height = 1080 };
|
||||
var content = new UiPanel { Width = 1730, Height = 90 };
|
||||
|
||||
RetailWindowHandle handle = RetailWindowFrame.Mount(
|
||||
root,
|
||||
content,
|
||||
NoTex,
|
||||
new RetailWindowFrame.Options
|
||||
{
|
||||
WindowName = "combat",
|
||||
Chrome = RetailWindowChrome.Imported,
|
||||
Left = 0f,
|
||||
Top = 980f,
|
||||
OuterAnchors = AnchorEdges.Left | AnchorEdges.Bottom,
|
||||
});
|
||||
|
||||
Assert.Equal(
|
||||
AnchorEdges.Left | AnchorEdges.Bottom,
|
||||
handle.OuterFrame.Anchors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NineSlice_UsesCroppedContentAndDatHeightConstraints()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public sealed class SpellcastingUiControllerTests
|
|||
{
|
||||
ImportedLayout layout = LayoutImporter.Build(
|
||||
FixtureLoader.LoadCombatInfos(), NoTex, datFont: null);
|
||||
ApplyAnchors(layout.Root);
|
||||
RetailCombatLayout.FitFavoriteSlots(layout);
|
||||
var spellbook = new Spellbook();
|
||||
spellbook.OnSpellLearned(42u, 1f);
|
||||
spellbook.SetFavorite(0, 0, 42u);
|
||||
|
|
@ -44,11 +44,13 @@ public sealed class SpellcastingUiControllerTests
|
|||
Assert.True(scrollbar.HideWhenDisabled);
|
||||
Assert.Equal(23f, scrollbar.DecrementButtonExtent);
|
||||
Assert.Equal(23f, scrollbar.IncrementButtonExtent);
|
||||
Assert.Equal(0x06004CDCu, scrollbar.UpSprite);
|
||||
Assert.Equal(0x06004CDEu, scrollbar.DownSprite);
|
||||
Assert.Equal(0x06004CDEu, scrollbar.UpSprite);
|
||||
Assert.Equal(0x06004CDFu, scrollbar.UpRolloverSprite);
|
||||
Assert.Equal(0x06004CDCu, scrollbar.DownSprite);
|
||||
Assert.Equal(0x06004CDDu, scrollbar.DownRolloverSprite);
|
||||
Assert.False(scrollbar.IsPresentationVisible);
|
||||
Assert.Equal(439f, list.Width);
|
||||
Assert.Equal(13, list.GetNumUIItems());
|
||||
Assert.Equal(576f, list.Width);
|
||||
Assert.Equal(18, list.GetNumUIItems());
|
||||
|
||||
UiCatalogSlot favorite = Assert.IsType<UiCatalogSlot>(list.GetItem(0));
|
||||
Assert.Equal(42u, favorite.EntryId);
|
||||
|
|
@ -63,7 +65,7 @@ public sealed class SpellcastingUiControllerTests
|
|||
Assert.Equal(i, slot.ShortcutNum);
|
||||
Assert.Same(empty, slot.ActiveDigitArray());
|
||||
}
|
||||
for (int i = 9; i < 13; i++)
|
||||
for (int i = 9; i < 18; i++)
|
||||
{
|
||||
Assert.Equal(0x06001A97u, list.GetItem(i)!.EmptySprite);
|
||||
Assert.Equal(-1, list.GetItem(i)!.ShortcutNum);
|
||||
|
|
@ -71,7 +73,7 @@ public sealed class SpellcastingUiControllerTests
|
|||
|
||||
var cast = Assert.IsType<UiButton>(
|
||||
layout.FindElement(SpellcastingUiController.CastButtonId));
|
||||
Assert.Equal((525f, 75f), (cast.Left, cast.Width));
|
||||
Assert.Equal((662f, 75f), (cast.Left, cast.Width));
|
||||
Assert.Equal(3, cast.FaceSegmentCount);
|
||||
Assert.Equal(
|
||||
[
|
||||
|
|
@ -87,7 +89,7 @@ public sealed class SpellcastingUiControllerTests
|
|||
{
|
||||
ImportedLayout layout = LayoutImporter.Build(
|
||||
FixtureLoader.LoadCombatInfos(), NoTex, datFont: null);
|
||||
ApplyAnchors(layout.Root);
|
||||
RetailCombatLayout.FitFavoriteSlots(layout);
|
||||
var spellbook = new Spellbook();
|
||||
for (uint spellId = 1u; spellId <= 20u; spellId++)
|
||||
{
|
||||
|
|
@ -105,7 +107,7 @@ public sealed class SpellcastingUiControllerTests
|
|||
UiScrollbar scrollbar = Descendants(group).OfType<UiScrollbar>().Single(
|
||||
candidate => candidate.DatElementId == SpellcastingUiController.FavoriteScrollbarId);
|
||||
Assert.True(scrollbar.IsPresentationVisible);
|
||||
Assert.Equal(201, list.Scroll.MaxScroll);
|
||||
Assert.Equal(64, list.Scroll.MaxScroll);
|
||||
|
||||
Assert.True(scrollbar.OnEvent(new UiEvent(
|
||||
0u, scrollbar, UiEventType.MouseDown, Data1: (int)scrollbar.Width - 1)));
|
||||
|
|
@ -117,6 +119,14 @@ public sealed class SpellcastingUiControllerTests
|
|||
controller.AddFavorite(20u);
|
||||
|
||||
Assert.Equal(list.Scroll.MaxScroll, list.Scroll.ScrollY);
|
||||
|
||||
Assert.True(scrollbar.OnEvent(new UiEvent(
|
||||
0u, scrollbar, UiEventType.MouseDown, Data1: 0)));
|
||||
int manualOffset = list.Scroll.ScrollY;
|
||||
objects.AddOrUpdate(new ClientObject { ObjectId = 99u, Name = "Unrelated" });
|
||||
controller.Tick();
|
||||
|
||||
Assert.Equal(manualOffset, list.Scroll.ScrollY);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -157,6 +157,47 @@ public class UiScrollbarTests
|
|||
Assert.Equal(64, model.ScrollY);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HorizontalModel_UsesIndependentRolloverAndPressedArrowMedia()
|
||||
{
|
||||
var root = new UiRoot { Width = 300f, Height = 100f };
|
||||
var model = new UiScrollable
|
||||
{
|
||||
ContentHeight = 640,
|
||||
ViewHeight = 320,
|
||||
LineHeight = 32,
|
||||
};
|
||||
var bar = new UiScrollbar
|
||||
{
|
||||
Width = 160f,
|
||||
Height = 36f,
|
||||
Horizontal = true,
|
||||
Model = model,
|
||||
UpSprite = 1u,
|
||||
UpRolloverSprite = 2u,
|
||||
UpPressedSprite = 3u,
|
||||
DownSprite = 4u,
|
||||
DownRolloverSprite = 5u,
|
||||
DownPressedSprite = 6u,
|
||||
};
|
||||
root.AddChild(bar);
|
||||
|
||||
root.OnMouseMove(5, 10);
|
||||
Assert.Equal(2u, bar.ActiveStartSpriteForTest);
|
||||
Assert.Equal(4u, bar.ActiveEndSpriteForTest);
|
||||
|
||||
// Moving between two regions of the same procedural scrollbar must
|
||||
// refresh its sub-control hover, not wait for a whole-widget leave.
|
||||
root.OnMouseMove(155, 10);
|
||||
Assert.Equal(1u, bar.ActiveStartSpriteForTest);
|
||||
Assert.Equal(5u, bar.ActiveEndSpriteForTest);
|
||||
|
||||
root.OnMouseDown(UiMouseButton.Left, 155, 10);
|
||||
Assert.Equal(6u, bar.ActiveEndSpriteForTest);
|
||||
root.OnMouseUp(UiMouseButton.Left, 155, 10);
|
||||
Assert.Equal(5u, bar.ActiveEndSpriteForTest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModelWithoutOverflow_IsDisabledAndHideDisabledSuppressesPresentation()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue