feat(ui): the spell-bar drop ring — retail's authored drag-accept state, and the ring exposed a real drop off-by-one
Some checks failed
Headless portability / portable-headless (ubuntu-latest) (push) Has been cancelled
Headless portability / portable-headless (windows-latest) (push) Has been cancelled
Headless portability / linux-graphical (push) Has been cancelled
Headless portability / linux-vulkan (push) Has been cancelled
Some checks failed
Headless portability / portable-headless (ubuntu-latest) (push) Has been cancelled
Headless portability / portable-headless (windows-latest) (push) Has been cancelled
Headless portability / linux-graphical (push) Has been cancelled
Headless portability / linux-vulkan (push) Has been cancelled
The green ring is retail's own art: every UIItem cell carries an authored DragAccept child (catalog 0x21000037, child 0x1000045A), and the spell bar's drag-over handler (SpellCastSubMenu::OnItemListDragOver @0x004C5990) flips it to the Accept state (0x10000040 -> surface 0x060011F9) for any spell payload. Ported through a per-slot SetDragAcceptVisual seam + a catalog DragOverAcceptance hook; other lists are untouched (null acceptance = neutral). A polarity error in our older docs (Accept/Reject state ids swapped) was corrected against three independent sources; the shipped art was always right, only the labels lied. The ring shares ONE landing computation with the drop (FavoriteDropIndex) — and that requirement exposed a genuine #354 off-by-one: the empty-tail path double-applied the -1 adjustment (retail gates it on the lift's removal @0x004C7157), landing a reordered spell second-to-last instead of last. Fixed; discriminator-verified both ways. AP-172 narrowed + its false empty-tail claim corrected. Clean-room complete solution: 11,545 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
81a9d85a1d
commit
6bb4cfa795
9 changed files with 434 additions and 34 deletions
|
|
@ -36,6 +36,19 @@ public sealed class UiCatalogSlot : UiItemSlot
|
|||
public Action<object>? DragEnded { get; init; }
|
||||
public Action<object>? Dropped { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Per-panel drag-over acceptance — the catalog-cell port of retail's
|
||||
/// per-list drag handler deciding the rollover state
|
||||
/// (<c>UIElement_ItemList::ItemList_DragOver</c> @ 0x004E3400 →
|
||||
/// <c>m_dragHandler->OnItemListDragOver</c>; the spell bar's is
|
||||
/// <c>SpellCastSubMenu::OnItemListDragOver</c> @ 0x004C5990). While a drag
|
||||
/// hovers this cell, the returned acceptance drives the authored
|
||||
/// DragAccept frame (accept 0x060011F9 / reject 0x060011F8). Null (the
|
||||
/// default for every list that never set one) keeps the cell neutral —
|
||||
/// no overlay, exactly the pre-existing presentation.
|
||||
/// </summary>
|
||||
public Func<object, ItemDragAcceptance>? DragOverAcceptance { get; init; }
|
||||
|
||||
protected override bool IsShortcutOccupied => EntryId != 0u;
|
||||
public override bool IsEmptySlot => EntryId == 0u;
|
||||
|
||||
|
|
@ -77,9 +90,25 @@ public sealed class UiCatalogSlot : UiItemSlot
|
|||
if (e.Payload is not null) DragBegan?.Invoke(e.Payload);
|
||||
return true;
|
||||
case UiEventType.DragEnter:
|
||||
// Pointer entered mid-drag: ask the panel's acceptance seam and show
|
||||
// the authored frame (retail ItemList_DragOver → the list handler's
|
||||
// SetDragAcceptState on the hovered UIItem's 0x1000045A child).
|
||||
SetDragAcceptVisual(e.Payload is { } enterPayload
|
||||
? DragOverAcceptance?.Invoke(enterPayload) switch
|
||||
{
|
||||
ItemDragAcceptance.Accept => DragAcceptState.Accept,
|
||||
ItemDragAcceptance.Reject => DragAcceptState.Reject,
|
||||
_ => DragAcceptState.None,
|
||||
}
|
||||
: DragAcceptState.None);
|
||||
return true;
|
||||
case UiEventType.DragOver:
|
||||
// UiRoot fires DragOver on LEAVE — retail's dwParam1 == 0 reset to
|
||||
// ItemSlot_DragOver_Normal (0x1000003F) @ 0x004E1438-0x004E1456.
|
||||
SetDragAcceptVisual(DragAcceptState.None);
|
||||
return true;
|
||||
case UiEventType.DropReleased:
|
||||
SetDragAcceptVisual(DragAcceptState.None);
|
||||
if (e.Payload is not null) Dropped?.Invoke(e.Payload);
|
||||
return true;
|
||||
default:
|
||||
|
|
@ -141,6 +170,10 @@ public sealed class UiCatalogSlot : UiItemSlot
|
|||
if (!string.IsNullOrWhiteSpace(Detail))
|
||||
ctx.DrawString(Detail!, MathF.Max(labelLeft, Width - 48f), 3f, Vector4.One);
|
||||
}
|
||||
|
||||
// Drag-rollover frame last — same layering as the physical UIItem draw
|
||||
// (accept/reject above selection; catalog cells have no cooldown layer).
|
||||
DrawDragAcceptOverlay(ctx);
|
||||
}
|
||||
|
||||
private void DrawSelection(UiRenderContext ctx)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue