feat(ui): D.5.3/B.1 — drag payload + handler interface + UiItemList registration
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
24ce8a0b5e
commit
9d483468d9
4 changed files with 93 additions and 0 deletions
19
src/AcDream.App/UI/IItemListDragHandler.cs
Normal file
19
src/AcDream.App/UI/IItemListDragHandler.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
namespace AcDream.App.UI;
|
||||
|
||||
/// <summary>
|
||||
/// A panel controller implements this and registers itself on each of its
|
||||
/// <see cref="UiItemList"/>s. Port of retail's <c>m_dragHandler</c> vtable
|
||||
/// (<c>RegisterItemListDragHandler</c>, decomp 230461; confirmed acclient
|
||||
/// 0x004a539e + the gmToolbarUI block 0x004bdd89).
|
||||
/// <para><see cref="OnDragOver"/> decides the accept/reject OVERLAY only (advisory).
|
||||
/// <see cref="HandleDropRelease"/> is authoritative — it performs the action, or
|
||||
/// no-ops to reject.</para>
|
||||
/// </summary>
|
||||
public interface IItemListDragHandler
|
||||
{
|
||||
/// <summary>True ⇒ the target cell shows the accept (green) frame; false ⇒ reject (red).</summary>
|
||||
bool OnDragOver(UiItemList targetList, UiItemSlot targetCell, ItemDragPayload payload);
|
||||
|
||||
/// <summary>Perform the drop (issue the per-panel wire action), or no-op to reject.</summary>
|
||||
void HandleDropRelease(UiItemList targetList, UiItemSlot targetCell, ItemDragPayload payload);
|
||||
}
|
||||
24
src/AcDream.App/UI/ItemDragPayload.cs
Normal file
24
src/AcDream.App/UI/ItemDragPayload.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
namespace AcDream.App.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Where a dragged item came from — the retail <c>InqDropIconInfo</c> flag
|
||||
/// distinction (<c>flags & 0xE == 0</c> fresh-from-inventory vs
|
||||
/// <c>flags & 4</c> within-list reorder) expressed as a typed enum. The drop
|
||||
/// handler maps SourceKind + target back to the fresh-vs-reorder decision.
|
||||
/// Decomp anchors: gmToolbarUI 0x004bd162 / 0x004bd1af; InqDropIconInfo 230533.
|
||||
/// </summary>
|
||||
public enum ItemDragSource { Inventory, ShortcutBar, Equipment, Ground }
|
||||
|
||||
/// <summary>
|
||||
/// Snapshot of a drag-in-progress, taken at drag-begin (so a server move arriving
|
||||
/// mid-drag can't mutate it under us). Port of retail's <c>m_dragElement</c> +
|
||||
/// <c>InqDropIconInfo</c> out-params (objId/container/flags, decomp 230533).
|
||||
/// <para><c>SourceContainer</c> is intentionally NOT stored: the handler resolves the
|
||||
/// LIVE container via <c>ClientObjectTable.Get(ObjId).ContainerId</c> at drop — the
|
||||
/// same container id retail reads off the dragged element, single source of truth.</para>
|
||||
/// </summary>
|
||||
public sealed record ItemDragPayload(
|
||||
uint ObjId, // dragged weenie guid (retail itemID, +0x5FC)
|
||||
ItemDragSource SourceKind, // what kind of slot it left
|
||||
int SourceSlot, // the source cell's SlotIndex (retail m_lastShortcutNumDragged)
|
||||
UiItemSlot SourceCell); // back-ref: reorder-restore / clear source state / ghost
|
||||
|
|
@ -24,6 +24,14 @@ public sealed class UiItemList : UiElement
|
|||
|
||||
public Func<uint, (uint tex, int w, int h)>? SpriteResolve { get; set; }
|
||||
|
||||
/// <summary>The drag handler this list routes drops to (a panel controller).
|
||||
/// Null = the list accepts no drops. Retail: UIElement_ItemList::m_dragHandler.</summary>
|
||||
public IItemListDragHandler? DragHandler { get; private set; }
|
||||
|
||||
/// <summary>Register the panel's drag handler on this list. Retail:
|
||||
/// UIElement_ItemList::RegisterItemListDragHandler (decomp 230461).</summary>
|
||||
public void RegisterDragHandler(IItemListDragHandler handler) => DragHandler = handler;
|
||||
|
||||
/// <summary>Convenience for single-cell slots (the toolbar): the first cell.
|
||||
/// Valid only while the list has at least one cell; after <see cref="Flush"/>
|
||||
/// (the inventory-phase rebuild path) the list is empty until <see cref="AddItem"/>
|
||||
|
|
|
|||
42
tests/AcDream.App.Tests/UI/DragDropSpineTests.cs
Normal file
42
tests/AcDream.App.Tests/UI/DragDropSpineTests.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
using AcDream.App.UI;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.UI;
|
||||
|
||||
public class DragDropSpineTests
|
||||
{
|
||||
// A spy handler used across the spine tests.
|
||||
private sealed class SpyHandler : IItemListDragHandler
|
||||
{
|
||||
public bool AcceptResult = true;
|
||||
public (UiItemList list, UiItemSlot cell, ItemDragPayload payload)? LastOver;
|
||||
public (UiItemList list, UiItemSlot cell, ItemDragPayload payload)? LastDrop;
|
||||
|
||||
public bool OnDragOver(UiItemList list, UiItemSlot cell, ItemDragPayload p)
|
||||
{ LastOver = (list, cell, p); return AcceptResult; }
|
||||
|
||||
public void HandleDropRelease(UiItemList list, UiItemSlot cell, ItemDragPayload p)
|
||||
{ LastDrop = (list, cell, p); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Payload_holdsAllFields()
|
||||
{
|
||||
var src = new UiItemSlot();
|
||||
var p = new ItemDragPayload(0x5001u, ItemDragSource.ShortcutBar, 3, src);
|
||||
Assert.Equal(0x5001u, p.ObjId);
|
||||
Assert.Equal(ItemDragSource.ShortcutBar, p.SourceKind);
|
||||
Assert.Equal(3, p.SourceSlot);
|
||||
Assert.Same(src, p.SourceCell);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UiItemList_registerDragHandler_roundtrips()
|
||||
{
|
||||
var list = new UiItemList(_ => (0u, 0, 0));
|
||||
Assert.Null(list.DragHandler);
|
||||
var h = new SpyHandler();
|
||||
list.RegisterDragHandler(h);
|
||||
Assert.Same(h, list.DragHandler);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue