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.
This commit is contained in:
parent
3f3cfdac30
commit
06016014bc
16 changed files with 578 additions and 27 deletions
|
|
@ -0,0 +1,137 @@
|
|||
# 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue