acdream/docs/research/2026-07-13-retail-item-drag-visuals-pseudocode.md
Erik 609ed8bfc2 fix(ui): select items before drag mesh
Port ItemList_BeginDrag's select-before-waiting order so a direct press-drag shows the selection indicator on its first frame across inventory, paperdoll, and toolbar item lists.

Co-Authored-By: Codex <codex@openai.com>
2026-07-13 09:28:28 +02:00

121 lines
5 KiB
Markdown

# Retail item drag visuals — pseudocode
Date: 2026-07-13
Scope: the cursor graphic and source-cell visual while dragging an item from an
item list. This is a focused continuation of
`2026-06-16-ui-item-slot-icon-dragdrop-spine-deep-dive.md`.
## Retail anchors
- `IconData::RenderIcons` @ `0x0058d180`
(`named-retail/acclient_2013_pseudo_c.txt:407524`)
- `UIElement_ItemList::PrepareDragIcon` @ `0x004e2a50`
(`named-retail/acclient_2013_pseudo_c.txt:230765`)
- `UIElement_ItemList::ItemList_BeginDrag` @ `0x004e32d0`
(`named-retail/acclient_2013_pseudo_c.txt:231350`)
- `UIElement_UIItem::SetWaitingState` @ `0x004e11b0`
(`named-retail/acclient_2013_pseudo_c.txt:229190`)
- `ACCWeenieObject::SetWaitingState` @ `0x0058c0d0`
(`named-retail/acclient_2013_pseudo_c.txt:406260`)
- `UIElement_UIItem` layout child `m_elem_Icon_Ghosted = 0x10000349`;
the shared UIItem catalog `0x21000037` gives it DirectState surface
`0x0600109A`.
The live DAT catalog and the named retail code agree. The in-tree WorldBuilder
reference has no retained UI/item-list implementation to compare for this path.
## Literal pseudocode
```text
IconData.RenderIcons(item):
destroy old m_pIcon and m_pDragIcon
dragSurface = transparent 32 x 32
blit item.baseIcon onto dragSurface using normal blit
blit item.customOverlay onto dragSurface using alpha blit
replace pure-white dragSurface pixels from the effect-color surface
m_pDragIcon = Graphic(dragSurface)
cellSurface = transparent 32 x 32
blit typeDefaultUnderlay onto cellSurface using normal blit
blit item.customUnderlay onto cellSurface using alpha blit
blit dragSurface onto cellSurface using alpha blit
m_pIcon = Graphic(cellSurface)
ItemList.PrepareDragIcon(cell):
clear cell.m_dragIcon image
object = GetWeenieObject(cell.itemID)
set cell.m_dragIcon image to object.GetDragIcon() # m_pDragIcon, not m_pIcon
ItemList.ItemList_BeginDrag(cell):
if PrepareDragIcon(cell) failed:
return
if cell is not selected:
SetSelectedObject(cell.itemID, false)
if list is not vendor, salvage, or shortcut list:
cell.SetWaitingState(true)
StartDragAndDrop(cell.m_dragIcon, hotspot = 16,16)
UIItem.SetWaitingState(waiting):
object.SetWaitingState(waiting)
if cell is not unghostable:
set m_elem_Icon_Ghosted visible = waiting
on drag release/cancel:
clear source waiting state
hide m_elem_Icon_Ghosted
```
## Destination feedback and source overlay order
`UIElement_ItemList::ItemList_DragOver @ 0x004e3400` distinguishes an ordinary
slot insertion from dropping into a container:
```text
if target list is a container selector
and target cell is occupied by a container
and that container has an empty item slot:
target.SetDragAcceptState(0x10000046) # ItemSlot_DragOver_DropIn
# 0x060011F7 green arrow
else if target accepts an ordinary item-list placement:
target.SetDragAcceptState(0x10000041) # ItemSlot_DragOver_Accept
# 0x060011F9 green circle
else:
target.SetDragAcceptState(0x10000040) # 0x060011F8 reject
```
Therefore the backpack contents grid uses the green circle; the side-bag column
and main-pack container cell use the green drop-in arrow. The selected/open
indicators remain visible while `m_elem_Icon_Ghosted` is active, so the
procedural leaf draws the waiting mesh before those persistent indicators and
keeps drag-accept feedback topmost.
## Port mapping
- `IconComposer` caches the underlay-free composite separately and exposes
`GetDragIcon`; the ordinary `GetIcon` still layers the two underlays beneath
the same cached drag pixels.
- `UiItemSlot` stores `IconTexture` and `DragIconTexture` separately.
`GetDragGhost` returns the latter, while `OnDraw` keeps the former in the
source cell.
- `UiRoot` owns the generic source-active lifecycle, including release outside
UI and source-subtree removal. It dispatches `DragBegin` before enabling the
source-active mesh, matching retail's select-then-waiting order. The inventory,
paperdoll, and toolbar handlers select an unselected dragged item synchronously.
`UiItemSlot` maps the lifecycle to the retail waiting visual for physical lists;
shortcut aliases remain unghosted.
- `UiItemSlot.WaitingSprite` is the authored `0x0600109A` mesh, not a procedural
tint.
## Conformance coverage
- `DragDropSpineTests.GetDragGhost_prefersDedicatedUnderlayFreeTexture`
- `DragDropSpineTests.InventoryDrag_ghostsSourceUntilRelease`
- `InventoryControllerTests.OnDragLift_selectsItem_butKeepsItUntilServerConfirms`
- `PaperdollControllerTests.DragLift_selectsEquippedItem_beforeWaitingVisual`
- `DragDropSpineTests.ShortcutDrag_doesNotGhostSource`
- `UiItemSlotTests.DefaultWaitingSprite_isRetailGhostMesh`
- `InventoryControllerTests.Empty_sprites_and_drop_feedback_are_applied_per_list_role`
- Existing `IconComposerTests.TwoStageWithEffect_copiesTilePixelBeforeUnderlay`
locks the two-stage composition order.