acdream/docs/research/2026-07-17-retail-shared-main-panel-pseudocode.md
Erik 06016014bc fix(ui): port retail window control feedback
Map synthetic move and resize affordances to the exact DAT cursors, make chat top chrome movable, and replace stale primary-panel height caps with a dynamic screen-edge constraint. This keeps the retained wrapper adaptation aligned with retail Dragbar/Resizebar behavior.
2026-07-17 09:40:08 +02:00

145 lines
5.3 KiB
Markdown

# Retail shared main-panel placement — pseudocode
**Date:** 2026-07-17
**Symptom:** moving Inventory and then opening Character/Skills or Magic opens
the new panel at its own stale saved position instead of the position just used.
## Retail oracle
Named Sept-2013 retail symbols:
- `gmPanelUI::SetupChildren @ 0x004BC9E0`
- `gmPanelUI::RecvNotice_SetPanelVisibility @ 0x004BC6F0`
- `gmPanelUI::PostInit @ 0x004BD010`
- `gmPanelUI::ResizeTo @ 0x004BC6E0`
Verbatim `acclient.h` ownership:
```text
gmPanelUI : UIElement_Field
SmartArray<PanelChildInfo> m_childrenInfoArray
UIElement* m_currentlyShownPanel
UIElement* m_previouslyShownPanel
UIElement* m_pPanelPagesFrame
bool m_bStretchMode
PanelChildInfo
UIElement* child
uint panelID
```
`SetupChildren` resolves each known panel element recursively, stores the
child pointer together with its property-`0x10000029` panel ID, and initially
hides every child. It does not create independent top-level windows.
## Retail behavior
```text
SetPanelVisibility(panelId, show):
requested = find m_childrenInfoArray entry by panelId
if requested is missing:
return
if show:
if requested.child == m_currentlyShownPanel:
requested.child.SetVisible(true)
parent.SetVisible(true)
return
previous = m_currentlyShownPanel
m_currentlyShownPanel = requested.child
if previous is visible:
if requested restores previous and previous does not:
m_previouslyShownPanel = previous
else:
m_previouslyShownPanel = null
send SetPanelVisibility(previous.panelID, false)
requested.child.SetVisible(true)
parent.SetVisible(true)
return
if requested.child == m_currentlyShownPanel:
requested.child.SetVisible(false)
if m_previouslyShownPanel exists:
send SetPanelVisibility(previous.panelID, true)
m_previouslyShownPanel = null
else:
m_currentlyShownPanel = null
parent.SetVisible(false)
return
requested.child.SetVisible(false)
```
The decisive placement fact is structural: Inventory, Character/Skills, and
Magic are children of one `gmPanelUI`. The switch changes child visibility;
there is no child move and no per-panel top-level position. Dragging the parent
therefore moves the one position subsequently used by every child.
## Resize follow-up
The production end-of-retail LayoutDesc supplies the missing geometry oracle:
```text
LayoutDesc 0x2100006E
gmPanelUI host 0x100005FE 310 x 372
content parent 0x10000180 300 x 362, anchored on all edges
Inventory child 0x1000018B 300 x 362, anchored on all edges
Character child 0x1000018E 300 x 362, anchored on all edges
Magic child 0x10000190 300 x 362, anchored on all edges
top-center 0x1000065C Type 2 Dragbar, cursor 0x06006119
bottom-center 0x10000660 Type 9 Resizebar, cursor 0x06005E66
```
Every primary child therefore participates in the shared host's vertical
resize; Magic is not a fixed-height exception. Named
`gmPanelUI::ResizeTo @ 0x004BC6E0` delegates directly to
`UIElement::ResizeTo @ 0x00463C30`, whose only size limits are effective DAT
properties `0x3C..0x3F`. There is no retail 760-pixel Character limit and no
maximum captured from the host's original screen position.
acdream retains independent wrappers under IA-12, so the faithful adaptation
is to give each primary wrapper the same bottom-only vertical resize policy,
leave its maximum unbounded by an authored substitute, and calculate the
available desktop extent from its current position during the resize. The
viewport remains the physical limit; an old static maximum must not become a
smaller limit after the shared panel moves.
## acdream port shape
acdream imports these child LayoutDesc roots independently, so each currently
has its own retained wrapper. `RetailPanelUiController` already ports retail's
one-active-child and restore-previous lifecycle. Complete that logical-parent
ownership by adding one canonical placement for the primary toolbar children
(Inventory `7`, Character `11`, Magic `13`):
```text
on primary child frame moved:
canonicalPosition = moved frame position
move every other primary child frame to canonicalPosition
before showing a primary child:
if canonicalPosition exists:
move requested child frame to canonicalPosition
else:
canonicalPosition = requested child frame position
before hiding the active primary child:
capture its current position as canonicalPosition
```
The controller owns this synchronization and subscribes to typed
`RetailWindowHandle.Moved` events. Toolbar commands, keyboard commands,
restore-previous effects, persistence restore, and direct window dragging all
therefore pass through the same owner. Panel-authored size/resize policy stays
with the active child, but the three primary wrappers apply the shared host's
same vertical resize contract; only placement is synchronized between the
independent frames.
ACE contains no client UI implementation, and the in-tree WorldBuilder
reference contains no `gmPanelUI` port, so neither supplies a competing
behavioral interpretation. The named retail client and verbatim header are the
oracle for this UI ownership rule.