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>
179 lines
6.6 KiB
Markdown
179 lines
6.6 KiB
Markdown
# 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
|
|
increment reference 0x10000072, authored x=0, 23x36
|
|
Normal 0x06004CDE (left-facing arrow)
|
|
Rollover 0x06004CDF
|
|
decrement reference 0x10000071, authored x=63, 23x36
|
|
Normal 0x06004CDC
|
|
Rollover 0x06004CDD
|
|
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. The
|
|
property names do not determine their drawn side:
|
|
`UIElement_Scrollbar::UpdateScrollingArea @ 0x00470AA0` places the 0x77
|
|
increment reference at the leading/top-left edge and the 0x78 decrement
|
|
reference at the trailing/bottom-right edge. For this horizontal fixture that
|
|
means `0x06004CDE` on the left and `0x06004CDC` on the right.
|
|
|
|
## 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 property-0x77 button at the start and subtract its authored width
|
|
position property-0x78 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.
|
|
|
|
The combat LayoutDesc deliberately mixes design extents: its outer combat root
|
|
is 610 pixels wide, while the magic page's children are authored against an
|
|
800-pixel parent. Applying retail
|
|
`UIElement::UpdateForParentSizeChange @ 0x00462640` through that complete
|
|
raw-edge chain gives a 439-pixel favorite viewport at the 610-pixel root—the
|
|
13-slot short bar seen before this slice.
|
|
|
|
The retail presentation exposes 18 favorite cells: nine shortcut-numbered
|
|
cells plus nine unnumbered cells. At 32 pixels per cell the target viewport is
|
|
576 pixels. Because every horizontal raw-edge stage carries the parent delta
|
|
one-for-one, solving the imported tree for that viewport yields:
|
|
|
|
```text
|
|
outer combat root = 747 px
|
|
magic page = 737 px (5 px inset on each side)
|
|
favorite group = 622 px (x=40, followed by Cast)
|
|
scrollbar = 622 px (23 + 576 + 23)
|
|
favorite list = 576 px (18 * 32)
|
|
Cast = x=662, width=75 inside the magic page
|
|
```
|
|
|
|
This is a fixed visible-capacity HUD, not a desktop-wide strip. Stretching it
|
|
to the display width makes the favorite content fit, which legitimately
|
|
disables and hides the `HideDisabled` overflow arrows.
|
|
|
|
## 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. An actual programmatic, keyboard, or pointer selection exposes the
|
|
selected cell even when it was outside the prior viewport. Passive endowment
|
|
or object-table refresh does not re-run `SetSelected`; doing so would undo a
|
|
manual scrollbar offset.
|
|
|
|
## 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.
|
|
- Preserve each authored button's Normal, Normal_rollover, and Normal_pressed
|
|
media through the shared scrollbar presentation path.
|
|
- 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.
|
|
- Carry the DAT raw-edge policies through the full imported combat tree and
|
|
solve the root width for an 18-cell (576-pixel) favorite viewport.
|
|
- Keep the resulting 747-pixel HUD width fixed across desktop resizes; do not
|
|
turn the favorite bar into a desktop-wide strip.
|