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.
137 lines
5 KiB
Markdown
137 lines
5 KiB
Markdown
# Retail window-control cursor pseudocode
|
||
|
||
Date: 2026-07-17
|
||
|
||
## Question
|
||
|
||
Which cursors does the end-of-retail client show while hovering or operating
|
||
window dragbars and resizebars, and how are they selected?
|
||
|
||
## Named-retail mechanism
|
||
|
||
### Widget cursor ownership
|
||
|
||
```text
|
||
MediaMachine::Update_Cursor(owner, cursorMedia) // 0x00465A80
|
||
if cursorMedia.file is valid
|
||
owner.SetCursor(file, hotspotX, hotspotY)
|
||
else
|
||
owner.UnSetCursor()
|
||
|
||
UIElement::SetCursor(file, hotspotX, hotspotY) // 0x0045FF50
|
||
m_cursorDID = file
|
||
m_cursorHotX = hotspotX
|
||
m_cursorHotY = hotspotY
|
||
UIElementManager.CheckCursor()
|
||
|
||
UIElementManager::CheckCursor() // 0x0045ABF0
|
||
if mouseCapture != null and mouseCapture.HasCursor()
|
||
apply mouseCapture cursor
|
||
else if lastEntered != null and lastEntered.HasCursor()
|
||
apply lastEntered cursor
|
||
else
|
||
apply ClientUISystem default cursor
|
||
```
|
||
|
||
The important ownership rule is that the captured widget wins. A resize or
|
||
move cursor therefore remains visible after the pointer leaves the original
|
||
five-pixel control while the button is still held.
|
||
|
||
### Window controls
|
||
|
||
```text
|
||
UIElement_Resizebar::StartMouseResizing(point) // 0x0046B7E0
|
||
parent = GetParent()
|
||
edge = authored Left/Right/Top/Bottom attributes
|
||
m_mousePressed = true
|
||
parent.StartResizing(edge, point.x, point.y)
|
||
SetState(Pressed)
|
||
|
||
UIElement_Resizebar::StopMouseResizing() // 0x0046B8C0
|
||
SetState(Normal)
|
||
m_mousePressed = false
|
||
GetParent().StopResizing()
|
||
|
||
UIElement_Dragbar::StartMouseMoving(message) // 0x0046C760
|
||
if primary mouse button and parent is not already moving/resizing
|
||
m_mousePressed = true
|
||
parent.StartMovement(message.windowX, message.windowY)
|
||
SetState(Pressed)
|
||
|
||
UIElement_Dragbar::StopMouseMoving(message) // 0x0046C7C0
|
||
if pressed and primary mouse button
|
||
SetState(Normal)
|
||
m_mousePressed = false
|
||
GetParent().StopMovement()
|
||
```
|
||
|
||
Dragbar and Resizebar do not synthesize OS cursors in these methods. Their
|
||
LayoutDesc state media supplies `MD_Data_Cursor`; the generic
|
||
`MediaMachine::Update_Cursor` path above installs it on the element.
|
||
|
||
## Installed end-of-retail DAT oracle
|
||
|
||
The production LayoutDesc records and portal DAT resolve these direct cursor
|
||
surfaces, all with hotspot `(16, 16)` pixels:
|
||
|
||
| Intent | RenderSurface DID | Observed authored use |
|
||
|---|---:|---|
|
||
| Move window | `0x06006119` | Type-2 Dragbar controls |
|
||
| Resize vertically | `0x06005E66` | horizontal Type-9 Resizebar (main chat top edge and combat bottom edge) |
|
||
| Resize horizontally | `0x06006128` | vertical Type-9 Resizebar |
|
||
| Resize NW-SE | `0x06006126` | top-left and bottom-right Type-9 corners |
|
||
| Resize NE-SW | `0x06006127` | bottom-left / opposite diagonal Type-9 corner |
|
||
|
||
All five surfaces are 32×32 `PFID_A8R8G8B8` cursor images. Representative
|
||
LayoutDesc fixtures independently preserve the same media:
|
||
|
||
- main chat `0x21000006`, element `0x1000000F`: vertical cursor
|
||
`0x06005E66` at `(16,16)`;
|
||
- combat `0x21000073`, Dragbar `0x100006AE`: move cursor
|
||
`0x06006119` at `(16,16)`;
|
||
- combat corner/edge Resizebars: `0x06006126..0x06006128`.
|
||
|
||
`DatReaderWriter` supplies these cursor records through the existing
|
||
LayoutDesc media decode. WorldBuilder's backend exposes the same architectural
|
||
boundary (`SetCursor(cursorDataId, hotspot)`) but leaves it unimplemented; it
|
||
does not provide a competing behavior interpretation.
|
||
|
||
## acdream root cause and port
|
||
|
||
`UiRoot` already computes the active or hovered resize edges and window-move
|
||
ownership. `CursorFeedbackController` already translates those states into
|
||
`WindowMove`, `ResizeHorizontal`, `ResizeVertical`, and the two diagonal
|
||
semantic kinds. However, `RetailCursorManager` renders only a valid
|
||
`UiCursorMedia` override or the `ClientUISystem` global cursor. The semantic
|
||
window-control kind was never converted to media, so synthetic wrapper borders
|
||
and whole-window drag regions retained the normal pointer.
|
||
|
||
Port shape:
|
||
|
||
```text
|
||
ResolveWindowControlCursor(kind):
|
||
WindowMove -> 0x06006119 @ (16,16)
|
||
ResizeHorizontal -> 0x06006128 @ (16,16)
|
||
ResizeVertical -> 0x06005E66 @ (16,16)
|
||
ResizeDiagonalNWSE -> 0x06006126 @ (16,16)
|
||
ResizeDiagonalNESW -> 0x06006127 @ (16,16)
|
||
|
||
UpdateCursor(root):
|
||
kind = resolve semantic interaction state
|
||
authored = captured-or-hovered element cursor, using retail precedence
|
||
|
||
if a synthetic resize edge owns the pointer or a resize/move operation owns capture
|
||
cursor = ResolveWindowControlCursor(kind)
|
||
else if authored is valid
|
||
cursor = authored
|
||
else
|
||
cursor = ResolveWindowControlCursor(kind)
|
||
|
||
render cursor; otherwise retain the ClientUISystem global cursor
|
||
```
|
||
|
||
Authored widget media therefore remains the first hover-time oracle. The
|
||
shared fallback exists for retained wrapper borders and the accepted IA-12
|
||
whole-window drag adaptation, which have no authored Dragbar/Resizebar child.
|
||
Once a synthetic operation starts, its control cursor wins just as retail's
|
||
captured Dragbar or Resizebar wins.
|