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
141
docs/research/2026-07-23-retail-spellbar-overflow-pseudocode.md
Normal file
141
docs/research/2026-07-23-retail-spellbar-overflow-pseudocode.md
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
# Retail favorite spell-bar overflow pseudocode
|
||||
|
||||
This note is the implementation oracle for world-interaction completion Slice
|
||||
1. Addresses refer to the September 2013 EoR named retail client. The exact
|
||||
element graph and inherited properties were verified against combat LayoutDesc
|
||||
`0x21000073` and the existing imported fixture
|
||||
`combat_21000073.json`.
|
||||
|
||||
## Authored element graph
|
||||
|
||||
Each of the eight `SpellCastSubMenu` groups contains:
|
||||
|
||||
```text
|
||||
favorite group
|
||||
scrollbar 0x100000B5, 685x36
|
||||
decrement button 0x10000071, 23x36
|
||||
Normal 0x06004CDC
|
||||
Rollover 0x06004CDD
|
||||
increment button 0x10000072, 23x36
|
||||
Normal 0x06004CDE
|
||||
Rollover 0x06004CDF
|
||||
item list 0x100000B6, x=23, y=2, 639x32
|
||||
|
||||
scrollbar property 0x77 = increment button 0x10000072
|
||||
scrollbar property 0x78 = decrement button 0x10000071
|
||||
scrollbar property 0x79 = HideDisabled true
|
||||
item-list property 0x71 = horizontal scrollbar 0x100000B5
|
||||
```
|
||||
|
||||
The spell-bar scrollbar has no authored track or thumb. Its 23-pixel buttons
|
||||
occupy the space immediately outside the 639-pixel item-list viewport.
|
||||
|
||||
## Initialization and ownership
|
||||
|
||||
Retail:
|
||||
|
||||
- `SpellCastSubMenu::Init @ 0x004C5A90`
|
||||
- `UIElement_Scrollable::PostInit @ 0x004744B0`
|
||||
- `UIElement_Scrollable::OnSetAttribute @ 0x00474630`
|
||||
|
||||
```text
|
||||
SpellCastSubMenu.Init(parent, groupId, tabId, tabIndex):
|
||||
group = parent.GetChildRecursive(groupId)
|
||||
spellItemList = group.GetChildRecursive(0x100000B6)
|
||||
register this as the item-list drag handler
|
||||
spellTabElement = parent.GetChildRecursive(tabId)
|
||||
|
||||
UIElement_Scrollable.OnSetAttribute(property):
|
||||
if property id == 0x71:
|
||||
horizontalScrollbarId = property element id
|
||||
|
||||
UIElement_Scrollable.PostInit():
|
||||
horizontalScrollbar = resolve horizontalScrollbarId
|
||||
register for scrollbar messages
|
||||
UpdateScrollbarSize(horizontal=true)
|
||||
```
|
||||
|
||||
The controller does not synthesize arrow buttons. LayoutDesc owns them; the
|
||||
ordinary scrollable/listbox relationship owns the offset.
|
||||
|
||||
## Size, disabled state, and visibility
|
||||
|
||||
Retail:
|
||||
|
||||
- `UIElement_Scrollable::UpdateScrollbarSize_ @ 0x004741A0`
|
||||
- `UIElement_Scrollbar::SetDisabled @ 0x004703D0`
|
||||
- `UIElement_Scrollbar::SetHideDisabled @ 0x00470400`
|
||||
- `UIElement_Scrollbar::UpdateLayout @ 0x004710D0`
|
||||
|
||||
```text
|
||||
UpdateScrollbarSize(horizontal):
|
||||
content = scrollableWidth
|
||||
viewport = element.Width
|
||||
|
||||
if content <= viewport:
|
||||
content = viewport
|
||||
scrollOffsetX = 0
|
||||
disabled = true
|
||||
else:
|
||||
disabled = false
|
||||
|
||||
scrollbar.Horizontal = true
|
||||
scrollbar.Disabled = disabled
|
||||
scrollbar.Proportion = min(1, viewport / content)
|
||||
|
||||
Scrollbar.UpdateLayout():
|
||||
scrollingArea = own rectangle
|
||||
position decrement button at the start and subtract its authored width
|
||||
position increment button at the end and subtract its authored width
|
||||
update thumb/track geometry, when those children exist
|
||||
Visible = NOT (Disabled AND HideDisabled)
|
||||
```
|
||||
|
||||
For the favorite bar, disabled plus HideDisabled removes both arrows when every
|
||||
favorite/empty cell fits. With overflow, the arrows remain visible and the
|
||||
item list clips the horizontally translated cells.
|
||||
|
||||
## Scroll delta and selection exposure
|
||||
|
||||
Retail:
|
||||
|
||||
- `UIElement_Scrollable::HandleScrollbarMessage_ @ 0x00474370`
|
||||
- `UIElement_ListBox::InqScrollDelta @ 0x0046DC10`
|
||||
- `SpellCastSubMenu::SetSelected @ 0x004C5B00`
|
||||
|
||||
```text
|
||||
on decrement:
|
||||
delta = -itemList.InqScrollDelta(horizontal, line)
|
||||
set horizontal pixel offset += delta
|
||||
|
||||
on increment:
|
||||
delta = +itemList.InqScrollDelta(horizontal, line)
|
||||
set horizontal pixel offset += delta
|
||||
|
||||
ItemList.InqScrollDelta(horizontal, line):
|
||||
if column count is nonzero:
|
||||
return min(viewportWidth, scrollableWidth / columnCount)
|
||||
return 0
|
||||
|
||||
SpellCastSubMenu.SetSelected(spellId):
|
||||
for each item in list order:
|
||||
item.Selected = item.spellId == spellId
|
||||
if selected:
|
||||
endowmentSelected = false
|
||||
itemList.ScrollToView(itemIndex)
|
||||
selectedSpellId = spellId
|
||||
```
|
||||
|
||||
The favorite cells are 32 pixels wide, so a line arrow moves exactly 32
|
||||
pixels. Programmatic or keyboard selection exposes the selected cell even when
|
||||
it was outside the prior viewport.
|
||||
|
||||
## Port constraints
|
||||
|
||||
- Preserve the imported `0x71`/`0x77`/`0x78` relationships.
|
||||
- Preserve each authored button's dimension; do not assume the 16-pixel chat
|
||||
scrollbar button size.
|
||||
- Do not fabricate a track/thumb for this arrow-only control.
|
||||
- Derive disabled state from the shared model's content and viewport extents.
|
||||
- Hide disabled controls from both drawing and hit-testing.
|
||||
- Keep every favorite tab's scroll offset independent.
|
||||
Loading…
Add table
Add a link
Reference in a new issue