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; }
|
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.
|
/// <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"/>
|
/// 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"/>
|
/// (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