feat(ui): port favorite spell bar overflow
Import the retail arrow-only spell bar scrollbar from LayoutDesc, preserve authored end-button extents and HideDisabled behavior in the shared retained widget, and bind each favorite list to its sole horizontal pixel-scroll model. Match retail selection exposure for off-screen spells and pin the behavior with real-fixture conformance tests. Document the six-slice world-interaction completion program as the active pre-M4 work order. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
20e2998adb
commit
02c29e67c8
12 changed files with 582 additions and 45 deletions
|
|
@ -223,6 +223,60 @@ public class DatWidgetFactoryTests
|
|||
Assert.Equal(0u, bar.DownSprite);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Type11_HorizontalScrollbar_ImportsAuthoredArrowButtonsAndHideDisabled()
|
||||
{
|
||||
const uint DecrementId = 0x10000071u;
|
||||
const uint IncrementId = 0x10000072u;
|
||||
const uint DecrementSprite = 0x06004CDCu;
|
||||
const uint IncrementSprite = 0x06004CDEu;
|
||||
var decrement = new ElementInfo
|
||||
{
|
||||
Id = DecrementId, Type = 1u, Width = 23f, Height = 36f,
|
||||
};
|
||||
decrement.States[UiStateInfo.DirectStateId] = new UiStateInfo
|
||||
{
|
||||
Id = UiStateInfo.DirectStateId,
|
||||
Image = new UiImageMedia(DecrementSprite, 1),
|
||||
};
|
||||
var increment = new ElementInfo
|
||||
{
|
||||
Id = IncrementId, Type = 1u, Width = 23f, Height = 36f,
|
||||
};
|
||||
increment.States[UiStateInfo.DirectStateId] = new UiStateInfo
|
||||
{
|
||||
Id = UiStateInfo.DirectStateId,
|
||||
Image = new UiImageMedia(IncrementSprite, 1),
|
||||
};
|
||||
var info = new ElementInfo
|
||||
{
|
||||
Type = 11u,
|
||||
Id = SpellcastingUiController.FavoriteScrollbarId,
|
||||
Width = 685f,
|
||||
Height = 36f,
|
||||
Children = [decrement, increment],
|
||||
};
|
||||
var state = new UiStateInfo { Id = UiStateInfo.DirectStateId };
|
||||
state.Properties.Values[0x77u] = new UiPropertyValue
|
||||
{ Kind = UiPropertyKind.Enum, UnsignedValue = IncrementId };
|
||||
state.Properties.Values[0x78u] = new UiPropertyValue
|
||||
{ Kind = UiPropertyKind.Enum, UnsignedValue = DecrementId };
|
||||
state.Properties.Values[0x79u] = Bool(true);
|
||||
info.States[UiStateInfo.DirectStateId] = state;
|
||||
|
||||
var bar = Assert.IsType<UiScrollbar>(
|
||||
DatWidgetFactory.Create(info, NoTex, null));
|
||||
|
||||
Assert.True(bar.Horizontal);
|
||||
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(0u, bar.TrackSprite);
|
||||
Assert.Equal(0u, bar.ThumbSprite);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RetailToolbarFixture_buildsEditableStackEntry_andAuthoredHorizontalSlider()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -34,8 +34,19 @@ public sealed class SpellcastingUiControllerTests
|
|||
|
||||
UiElement group = layout.FindElement(0x100000AAu)!;
|
||||
UiItemList list = Descendants(group).OfType<UiItemList>().First();
|
||||
UiScrollbar scrollbar = Descendants(group).OfType<UiScrollbar>().Single(
|
||||
candidate => candidate.DatElementId == SpellcastingUiController.FavoriteScrollbarId);
|
||||
Assert.True(list.SingleRow);
|
||||
Assert.True(list.HorizontalScroll);
|
||||
Assert.True(list.FillVisibleEmptySlots);
|
||||
Assert.Same(list.Scroll, scrollbar.Model);
|
||||
Assert.True(scrollbar.Horizontal);
|
||||
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.False(scrollbar.IsPresentationVisible);
|
||||
Assert.Equal(439f, list.Width);
|
||||
Assert.Equal(13, list.GetNumUIItems());
|
||||
|
||||
|
|
@ -71,6 +82,43 @@ public sealed class SpellcastingUiControllerTests
|
|||
cast.FaceSegmentRectsForTest());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FavoriteOverflow_ArrowButtonsStepOneCell_AndSelectionScrollsIntoView()
|
||||
{
|
||||
ImportedLayout layout = LayoutImporter.Build(
|
||||
FixtureLoader.LoadCombatInfos(), NoTex, datFont: null);
|
||||
ApplyAnchors(layout.Root);
|
||||
var spellbook = new Spellbook();
|
||||
for (uint spellId = 1u; spellId <= 20u; spellId++)
|
||||
{
|
||||
spellbook.OnSpellLearned(spellId, 1f);
|
||||
spellbook.SetFavorite(0, (int)spellId - 1, spellId);
|
||||
}
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject { ObjectId = 1u, Name = "Player" });
|
||||
|
||||
using SpellcastingUiController controller = Bind(
|
||||
layout, spellbook, objects, _ => { })!;
|
||||
|
||||
UiElement group = layout.FindElement(0x100000AAu)!;
|
||||
UiItemList list = Descendants(group).OfType<UiItemList>().Single();
|
||||
UiScrollbar scrollbar = Descendants(group).OfType<UiScrollbar>().Single(
|
||||
candidate => candidate.DatElementId == SpellcastingUiController.FavoriteScrollbarId);
|
||||
Assert.True(scrollbar.IsPresentationVisible);
|
||||
Assert.Equal(201, list.Scroll.MaxScroll);
|
||||
|
||||
Assert.True(scrollbar.OnEvent(new UiEvent(
|
||||
0u, scrollbar, UiEventType.MouseDown, Data1: (int)scrollbar.Width - 1)));
|
||||
Assert.Equal(32, list.Scroll.ScrollY);
|
||||
Assert.True(scrollbar.OnEvent(new UiEvent(
|
||||
0u, scrollbar, UiEventType.MouseDown, Data1: 0)));
|
||||
Assert.Equal(0, list.Scroll.ScrollY);
|
||||
|
||||
controller.AddFavorite(20u);
|
||||
|
||||
Assert.Equal(list.Scroll.MaxScroll, list.Scroll.ScrollY);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImportedFixture_BindsAllTabs_AndTracksEquippedEndowment()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -128,6 +128,60 @@ public class UiScrollbarTests
|
|||
Assert.True(bar.OnEvent(new UiEvent(0u, bar, UiEventType.MouseUp, Data1: 144)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HorizontalModel_UsesAuthoredArrowExtentsAndOneCellStep()
|
||||
{
|
||||
var model = new UiScrollable
|
||||
{
|
||||
ContentHeight = 640,
|
||||
ViewHeight = 320,
|
||||
LineHeight = 32,
|
||||
};
|
||||
model.SetScrollY(64);
|
||||
var bar = new UiScrollbar
|
||||
{
|
||||
Width = 160f,
|
||||
Height = 36f,
|
||||
Horizontal = true,
|
||||
Model = model,
|
||||
DecrementButtonExtent = 23f,
|
||||
IncrementButtonExtent = 29f,
|
||||
};
|
||||
|
||||
Assert.True(bar.OnEvent(new UiEvent(
|
||||
0u, bar, UiEventType.MouseDown, Data1: 22)));
|
||||
Assert.Equal(32, model.ScrollY);
|
||||
|
||||
Assert.True(bar.OnEvent(new UiEvent(
|
||||
0u, bar, UiEventType.MouseDown, Data1: 131)));
|
||||
Assert.Equal(64, model.ScrollY);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModelWithoutOverflow_IsDisabledAndHideDisabledSuppressesPresentation()
|
||||
{
|
||||
var model = new UiScrollable
|
||||
{
|
||||
ContentHeight = 320,
|
||||
ViewHeight = 320,
|
||||
LineHeight = 32,
|
||||
};
|
||||
var bar = new UiScrollbar
|
||||
{
|
||||
Width = 160f,
|
||||
Height = 36f,
|
||||
Horizontal = true,
|
||||
Model = model,
|
||||
HideWhenDisabled = true,
|
||||
};
|
||||
|
||||
Assert.True(bar.IsModelDisabled);
|
||||
Assert.False(bar.IsPresentationVisible);
|
||||
Assert.False(bar.OnEvent(new UiEvent(
|
||||
0u, bar, UiEventType.MouseDown, Data1: 159)));
|
||||
Assert.Equal(0, model.ScrollY);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0f, 0f, 0f)]
|
||||
[InlineData(0.5f, 0f, 50f)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue