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

@ -13,12 +13,12 @@ Each of the eight `SpellCastSubMenu` groups contains:
```text
favorite group
scrollbar 0x100000B5, 685x36
decrement button 0x10000071, 23x36
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
increment button 0x10000072, 23x36
Normal 0x06004CDE
Rollover 0x06004CDF
item list 0x100000B6, x=23, y=2, 639x32
scrollbar property 0x77 = increment button 0x10000072
@ -28,7 +28,12 @@ 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.
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
@ -85,8 +90,8 @@ UpdateScrollbarSize(horizontal):
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
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)
```
@ -95,6 +100,31 @@ 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:
@ -127,15 +157,23 @@ SpellCastSubMenu.SetSelected(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.
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.