Merge remote-tracking branch 'origin/master' into dev/pie-lover
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
using AetherBags.Interop;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using Lumina.Text.ReadOnly;
|
||||
using Lumina.Text;
|
||||
|
||||
namespace AetherBags.Extensions;
|
||||
|
||||
// TODO: Remove this 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ public sealed class GeneralScrollingAreaNode : ScrollingAreaNode<VerticalListNod
|
||||
private readonly CheckboxNode _debugCheckboxNode = null!;
|
||||
private readonly LabeledDropdownNode _stackDropDown = null!;
|
||||
|
||||
public unsafe GeneralScrollingAreaNode()
|
||||
public GeneralScrollingAreaNode()
|
||||
{
|
||||
GeneralSettings config = System.Config.General;
|
||||
|
||||
@@ -21,6 +21,7 @@ public sealed class GeneralScrollingAreaNode : ScrollingAreaNode<VerticalListNod
|
||||
_stackDropDown = new LabeledDropdownNode
|
||||
{
|
||||
Size = new Vector2(300, 20),
|
||||
IsEnabled = true,
|
||||
LabelText = "Stack Mode",
|
||||
Options = Enum.GetNames(typeof(InventoryStackMode)).ToList(),
|
||||
SelectedOption = config.StackMode.ToString(),
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using AetherBags.Extensions;
|
||||
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;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
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 new AtkComponentDragDrop* Component => (AtkComponentDragDrop*)this.InternalComponentNode->Component;
|
||||
private new 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;
|
||||
}
|
||||
}
|
||||
|
||||
public override ReadOnlySeString? Tooltip {
|
||||
get;
|
||||
set {
|
||||
field = value;
|
||||
switch (value) {
|
||||
case { IsEmpty: false } when !TooltipRegistered:
|
||||
AddEvent(AtkEventType.DragDropRollOver, ShowTooltip);
|
||||
AddEvent(AtkEventType.DragDropRollOut, HideTooltip);
|
||||
|
||||
TooltipRegistered = true;
|
||||
break;
|
||||
|
||||
case null when TooltipRegistered:
|
||||
RemoveEvent(AtkEventType.DragDropRollOver, ShowTooltip);
|
||||
RemoveEvent(AtkEventType.DragDropRollOut, HideTooltip);
|
||||
|
||||
TooltipRegistered = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,15 @@ using KamiToolKit.Nodes;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
// TODO: Switch back to CS version when Dalamud Updated
|
||||
using DragDropFixedNode = AetherBags.Nodes.DragDropNode;
|
||||
|
||||
namespace AetherBags.Nodes;
|
||||
|
||||
public class InventoryCategoryNode : SimpleComponentNode
|
||||
{
|
||||
private readonly TextNode _categoryNameTextNode;
|
||||
private readonly HybridDirectionalFlexNode<DragDropNode> _itemGridNode;
|
||||
private readonly HybridDirectionalFlexNode<DragDropFixedNode> _itemGridNode;
|
||||
|
||||
private const float FallbackItemSize = 46;
|
||||
private const float HeaderHeight = 16;
|
||||
@@ -48,7 +51,7 @@ public class InventoryCategoryNode : SimpleComponentNode
|
||||
_categoryNameTextNode.AddFlags(NodeFlags.EmitsEvents | NodeFlags.HasCollision);
|
||||
_categoryNameTextNode.AttachNode(this);
|
||||
|
||||
_itemGridNode = new HybridDirectionalFlexNode<DragDropNode>
|
||||
_itemGridNode = new HybridDirectionalFlexNode<DragDropFixedNode>
|
||||
{
|
||||
Position = new Vector2(0, HeaderHeight),
|
||||
Size = new Vector2(240, 92),
|
||||
@@ -249,16 +252,20 @@ public class InventoryCategoryNode : SimpleComponentNode
|
||||
Size = new Vector2(42, 46),
|
||||
IsVisible = true,
|
||||
IconId = item.IconId,
|
||||
AcceptedType = DragDropType.Nothing,
|
||||
IsDraggable = false,
|
||||
AcceptedType = DragDropType.Item,
|
||||
IsDraggable = true,
|
||||
Payload = new DragDropPayload
|
||||
{
|
||||
Type = DragDropType.Item,
|
||||
Int1 = (int)item.Container,
|
||||
Int2 = (int)item.ItemId,
|
||||
Type = DragDropType.Inventory_Item,
|
||||
Int1 = (int)item.GetInventoryType(),
|
||||
Int2 = item.Slot,
|
||||
},
|
||||
IsClickable = true,
|
||||
|
||||
OnEnd = _ =>
|
||||
{
|
||||
System.AddonInventoryWindow.ManualInventoryRefresh();
|
||||
},
|
||||
OnPayloadAccepted = (n, p) => OnPayloadAccepted(n, p, data),
|
||||
OnRollOver = n =>
|
||||
{
|
||||
BeginHeaderHover();
|
||||
@@ -275,4 +282,14 @@ public class InventoryCategoryNode : SimpleComponentNode
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private unsafe void OnPayloadAccepted(DragDropNode node, DragDropPayload payload, ItemInfo itemInfo)
|
||||
{
|
||||
Services.Logger.Debug($"Inventory DragDropNode Payload Accepted: {payload.Type} Int1: {payload.Int1} Int2: {payload.Int2}");
|
||||
InventoryType inventoryType = (InventoryType)payload.Int1;
|
||||
ushort sourceSlot = (ushort)payload.Int2;
|
||||
System.AddonInventoryWindow.ManualInventoryRefresh();
|
||||
// Should work for swapping item but need a fake empty slot to put new items in probably.
|
||||
InventoryManager.Instance()->MoveItemSlot(inventoryType, sourceSlot, itemInfo.Item.Container, itemInfo.Item.GetSlot());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,12 @@ 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;
|
||||
|
||||
public class InventoryDragDropNode : DragDropNode
|
||||
public class InventoryDragDropNode : DragDropFixedNode
|
||||
{
|
||||
private readonly TextNode _quantityTextNode;
|
||||
public unsafe InventoryDragDropNode()
|
||||
|
||||
+1
-1
Submodule KamiToolKit updated: 8ad335a4fd...1438568d3b
Reference in New Issue
Block a user