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:
Erik 2026-07-23 07:01:01 +02:00
parent 02c29e67c8
commit 0134122c28
17 changed files with 424 additions and 59 deletions

View file

@ -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]