Use KTK/CS DragDrop again

This commit is contained in:
Zeffuro
2025-12-31 16:49:58 +01:00
parent c15ddc8342
commit 1dec5b3183
5 changed files with 5 additions and 366 deletions
@@ -1,4 +1,4 @@
using AetherBags.Interop;
using AetherBags.Inventory;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Component.GUI;
@@ -8,55 +8,8 @@ using Lumina.Text;
namespace AetherBags.Extensions;
// TODO: Remove FixedInterface when CS is merged into Dalamud.
public static unsafe class DragDropPayloadExtensions
{
public static DragDropPayload FromFixedInterface(AtkDragDropInterface* dragDropInterface)
{
// Cast to our manual fixed struct
var fixedInterface = (AtkDragDropInterfaceFixed*)dragDropInterface;
// Calls Index 12
var payloadContainer = fixedInterface->GetPayloadContainer();
return new DragDropPayload
{
Type = fixedInterface->DragDropType,
ReferenceIndex = fixedInterface->DragDropReferenceIndex,
Int1 = payloadContainer->Int1,
Int2 = payloadContainer->Int2,
Text = new ReadOnlySeString(payloadContainer->Text),
};
}
public static void ToFixedInterface(this DragDropPayload payload, AtkDragDropInterface* dragDropInterface, bool writeToPayloadContainer = true)
{
var fixedInterface = (AtkDragDropInterfaceFixed*)dragDropInterface;
fixedInterface->DragDropType = payload.Type;
fixedInterface->DragDropReferenceIndex = payload.ReferenceIndex;
if (writeToPayloadContainer)
{
// Calls Index 12
var payloadContainer = fixedInterface->GetPayloadContainer();
payloadContainer->Clear();
payloadContainer->Int1 = payload.Int1;
payloadContainer->Int2 = payload.Int2;
if (payload.Text.IsEmpty)
{
payloadContainer->Text.Clear();
}
else
{
var stringBuilder = new SeStringBuilder().Append(payload.Text);
payloadContainer->Text.SetString(stringBuilder.GetViewAsSpan());
}
}
}
extension(DragDropPayload payload)
{
public bool IsValidInventoryPayload =>
@@ -1,69 +0,0 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace AetherBags.Interop;
// Size 0x30 (48) matches the original struct
[StructLayout(LayoutKind.Explicit, Size = 48)]
public unsafe struct AtkDragDropInterfaceFixed
{
// Offset 0 is the Virtual Table Pointer (void**)
[FieldOffset(0)] public void** VirtualTable;
// Map specific fields needed for Payload logic
[FieldOffset(36)] public DragDropType DragDropType;
[FieldOffset(40)] public short DragDropReferenceIndex;
// Helper to get 'this' as a pointer
private AtkDragDropInterfaceFixed* ThisPtr => (AtkDragDropInterfaceFixed*)Unsafe.AsPointer(ref this);
// [VirtualFunction(1)]
public void GetScreenPosition(float* screenX, float* screenY)
{
var fnPtr = (delegate* unmanaged<AtkDragDropInterfaceFixed*, float*, float*, void>)VirtualTable[1];
fnPtr(ThisPtr, screenX, screenY);
}
// [VirtualFunction(3)]
public AtkComponentNode* GetComponentNode()
{
var fnPtr = (delegate* unmanaged<AtkDragDropInterfaceFixed*, AtkComponentNode*>)VirtualTable[3];
return fnPtr(ThisPtr);
}
// [VirtualFunction(5)]
public void SetComponentNode(AtkComponentNode* node)
{
var fnPtr = (delegate* unmanaged<AtkDragDropInterfaceFixed*, AtkComponentNode*, void>)VirtualTable[5];
fnPtr(ThisPtr, node);
}
// [VirtualFunction(6)]
public AtkResNode* GetActiveNode()
{
var fnPtr = (delegate* unmanaged<AtkDragDropInterfaceFixed*, AtkResNode*>)VirtualTable[6];
return fnPtr(ThisPtr);
}
// [VirtualFunction(8)]
public AtkComponentBase* GetComponent()
{
var fnPtr = (delegate* unmanaged<AtkDragDropInterfaceFixed*, AtkComponentBase*>)VirtualTable[8];
return fnPtr(ThisPtr);
}
// [VirtualFunction(9)]
public bool HandleMouseUpEvent(AtkEventData.AtkMouseData* mouseData)
{
var fnPtr = (delegate* unmanaged<AtkDragDropInterfaceFixed*, AtkEventData.AtkMouseData*, byte>)VirtualTable[9];
return fnPtr(ThisPtr, mouseData) != 0;
}
// [VirtualFunction(12)]
public AtkDragDropPayloadContainer* GetPayloadContainer()
{
var fnPtr = (delegate* unmanaged<AtkDragDropInterfaceFixed*, AtkDragDropPayloadContainer*>)VirtualTable[12];
return fnPtr(ThisPtr);
}
}
-242
View File
@@ -1,242 +0,0 @@
using System;
using System.Numerics;
using AetherBags.Interop;
using FFXIVClientStructs.FFXIV.Client.Enums;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Classes.Timelines;
using KamiToolKit.Nodes;
namespace AetherBags.Nodes;
public unsafe class DragDropNode : ComponentNode<AtkComponentDragDrop, AtkUldComponentDataDragDrop> {
// FIX: Manually expose the pointers that are 'internal' in KamiToolKit
// We access the raw AtkComponentNode* via 'this.ResNode' and cast from there.
private AtkComponentDragDrop* Component => (AtkComponentDragDrop*)Node->Component;
private AtkUldComponentDataDragDrop* Data => (AtkUldComponentDataDragDrop*)Component->UldManager.ComponentData;
public readonly ImageNode DragDropBackgroundNode;
public readonly IconNode IconNode;
public DragDropNode() {
SetInternalComponentType(ComponentType.DragDrop);
DragDropBackgroundNode = new SimpleImageNode {
NodeId = 3,
Size = new Vector2(44.0f, 44.0f),
TexturePath = "ui/uld/DragTargetA.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
TextureSize = new Vector2(44.0f, 44.0f),
WrapMode = WrapMode.Tile,
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
};
DragDropBackgroundNode.AttachNode(this);
IconNode = new IconNode {
NodeId = 2,
Size = new Vector2(44.0f, 48.0f),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
};
IconNode.AttachNode(this);
LoadTimelines();
Data->Nodes[0] = IconNode.NodeId;
AcceptedType = DragDropType.Everything;
Payload = new DragDropPayload();
// Use the fixed shadow struct for writing initial values if needed,
// though direct field access on the struct usually works for simple fields.
// However, to be safe with the VTable fix, we just set fields directly here
// as they are standard offsets, or use the pointer.
Component->AtkDragDropInterface.DragDropType = DragDropType.Everything;
Component->AtkDragDropInterface.DragDropReferenceIndex = 0;
InitializeComponentEvents();
AddEvent(AtkEventType.DragDropBegin, DragDropBeginHandler);
AddEvent(AtkEventType.DragDropInsert, DragDropInsertHandler);
AddEvent(AtkEventType.DragDropDiscard, DragDropDiscardHandler);
AddEvent(AtkEventType.DragDropClick, DragDropClickHandler);
AddEvent(AtkEventType.DragDropRollOver, DragDropRollOverHandler);
AddEvent(AtkEventType.DragDropRollOut, DragDropRollOutHandler);
}
private bool IsDragDropEndRegistered { get; set; }
public Action<DragDropNode>? OnBegin { get; set; }
public Action<DragDropNode>? OnEnd { get; set; }
public Action<DragDropNode, DragDropPayload>? OnPayloadAccepted { get; set; }
public Action<DragDropNode>? OnDiscard { get; set; }
public Action<DragDropNode>? OnClicked { get; set; }
public Action<DragDropNode>? OnRollOver { get; set; }
public Action<DragDropNode>? OnRollOut { get; set; }
public DragDropPayload Payload { get; set; }
public uint IconId {
get => IconNode.IconId;
set {
IconNode.IconId = value;
IconNode.IsVisible = value != 0;
}
}
public bool IsIconDisabled {
get => IconNode.IsIconDisabled;
set => IconNode.IsIconDisabled = value;
}
public int Quantity {
get => int.Parse(Component->GetQuantityText().ToString());
set => Component->SetQuantity(value);
}
public string QuantityString {
get => Component->GetQuantityText().ToString();
set => Component->SetQuantityText(value);
}
public DragDropType AcceptedType {
get => Component->AcceptedType;
set => Component->AcceptedType = value;
}
public AtkDragDropInterface.SoundEffectSuppression SoundEffectSuppression {
get => Component->AtkDragDropInterface.DragDropSoundEffectSuppression;
set => Component->AtkDragDropInterface.DragDropSoundEffectSuppression = value;
}
public bool IsDraggable {
get => !Component->Flags.HasFlag(DragDropFlag.Locked);
set {
if (value) {
Component->Flags &= ~DragDropFlag.Locked;
}
else {
Component->Flags |= DragDropFlag.Locked;
}
}
}
public bool IsClickable {
get => Component->Flags.HasFlag(DragDropFlag.Clickable);
set {
if (value) {
Component->Flags |= DragDropFlag.Clickable;
}
else {
Component->Flags &= ~DragDropFlag.Clickable;
}
}
}
private void DragDropBeginHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
atkEvent->SetEventIsHandled();
// FIX: Use extension method to write payload using fixed VTable
Payload.ToFixedInterface(atkEventData->DragDropData.DragDropInterface);
OnBegin?.Invoke(this);
if (!IsDragDropEndRegistered) {
AddEvent(AtkEventType.DragDropEnd, DragDropEndHandler);
IsDragDropEndRegistered = true;
}
}
private void DragDropInsertHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
atkEvent->SetEventIsHandled();
atkEvent->State.StateFlags |= AtkEventStateFlags.HasReturnFlags;
atkEvent->State.ReturnFlags = 1;
// FIX: Use extension method to read payload using fixed VTable
var payload = DragDropPayloadExtensions.FromFixedInterface(atkEventData->DragDropData.DragDropInterface);
Payload.Clear();
IconId = 0;
OnPayloadAccepted?.Invoke(this, payload);
}
private void DragDropDiscardHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
atkEvent->SetEventIsHandled();
atkEvent->State.StateFlags |= AtkEventStateFlags.HasReturnFlags;
atkEvent->State.ReturnFlags = 1;
OnDiscard?.Invoke(this);
}
private void DragDropEndHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
atkEvent->SetEventIsHandled();
// FIX: Cast to shadow struct to call the correct GetPayloadContainer (Index 12)
var fixedInterface = (AtkDragDropInterfaceFixed*)atkEventData->DragDropData.DragDropInterface;
fixedInterface->GetPayloadContainer()->Clear();
OnEnd?.Invoke(this);
if (IsDragDropEndRegistered) {
RemoveEvent(AtkEventType.DragDropEnd, DragDropEndHandler);
IsDragDropEndRegistered = false;
}
}
private void DragDropClickHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
atkEvent->SetEventIsHandled();
atkEvent->State.StateFlags |= AtkEventStateFlags.HasReturnFlags;
atkEvent->State.ReturnFlags = 1;
OnClicked?.Invoke(this);
}
private void DragDropRollOverHandler()
=> OnRollOver?.Invoke(this);
private void DragDropRollOutHandler()
=> OnRollOut?.Invoke(this);
public void Clear() {
Payload.Clear();
IconId = 0;
}
public void ShowTooltip(AtkTooltipManager.AtkTooltipType type, ActionKind actionKind) {
if (AtkStage.Instance()->DragDropManager.IsDragging) return;
// FIX: Explicitly use 'this.ResNode' and cast to (AtkResNode*) to avoid ambiguity with the class name
var addon = RaptureAtkUnitManager.Instance()->GetAddonByNode((AtkResNode*)this);
if (addon is null) return;
var tooltipArgs = new AtkTooltipManager.AtkTooltipArgs();
tooltipArgs.Ctor();
tooltipArgs.ActionArgs.Id = Payload.Int2;
tooltipArgs.ActionArgs.Kind = (DetailKind)actionKind;
AtkStage.Instance()->TooltipManager.ShowTooltip(
AtkTooltipManager.AtkTooltipType.Action,
addon->Id,
(AtkResNode*)this, // FIX: Explicit cast here as well
&tooltipArgs);
}
private void LoadTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 59)
.AddLabelPair(1, 10, 1)
.AddLabelPair(11, 19, 2)
.AddLabelPair(20, 29, 3)
.AddLabelPair(30, 39, 7)
.AddLabelPair(40, 49, 6)
.AddLabelPair(50, 59, 4)
.EndFrameSet()
.Build());
}
}
@@ -14,14 +14,13 @@ using KamiToolKit.Classes;
using KamiToolKit.Nodes;
// TODO: Switch back to CS version when Dalamud Updated
using DragDropFixedNode = AetherBags.Nodes.DragDropNode;
namespace AetherBags.Nodes.Inventory;
public class InventoryCategoryNode : SimpleComponentNode
{
private readonly TextNode _categoryNameTextNode;
private readonly HybridDirectionalFlexNode<DragDropFixedNode> _itemGridNode;
private readonly HybridDirectionalFlexNode<DragDropNode> _itemGridNode;
private const float FallbackItemSize = 46;
private const float HeaderHeight = 16;
@@ -55,7 +54,7 @@ public class InventoryCategoryNode : SimpleComponentNode
_categoryNameTextNode.AddFlags(NodeFlags.EmitsEvents | NodeFlags.HasCollision);
_categoryNameTextNode.AttachNode(this);
_itemGridNode = new HybridDirectionalFlexNode<DragDropFixedNode>
_itemGridNode = new HybridDirectionalFlexNode<DragDropNode>
{
Position = new Vector2(0, HeaderHeight),
Size = new Vector2(240, 92),
@@ -7,12 +7,10 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
// TODO: Switch back to CS version when Dalamud Updated
using DragDropFixedNode = AetherBags.Nodes.DragDropNode;
namespace AetherBags.Nodes.Inventory;
public class InventoryDragDropNode : DragDropFixedNode
public class InventoryDragDropNode : DragDropNode
{
private readonly TextNode _quantityTextNode;
public unsafe InventoryDragDropNode()