acdream/docs/research/2026-07-13-retail-item-drag-visuals-pseudocode.md
Erik ace5880fed fix(ui): port retail item drag visuals
Keep retail's underlay-free m_pDragIcon separate from the full cell icon and reveal the authored ghost mesh on physical source cells for the complete drag lifecycle. This removes the backpack backing from the cursor and retires AP-47.

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

3.4 KiB

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

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 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

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. UiItemSlot maps that 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
  • DragDropSpineTests.ShortcutDrag_doesNotGhostSource
  • UiItemSlotTests.DefaultWaitingSprite_isRetailGhostMesh
  • Existing IconComposerTests.TwoStageWithEffect_copiesTilePixelBeforeUnderlay locks the two-stage composition order.