acdream/docs/research/2026-07-17-retail-window-control-cursor-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

5 KiB
Raw Blame History

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

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

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:

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.