Add dragging between windows on background
This commit is contained in:
@@ -22,6 +22,8 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
|
||||
|
||||
protected override void OnSetup(AtkUnitBase* addon)
|
||||
{
|
||||
InitializeBackgroundDropTarget();
|
||||
|
||||
CategoriesNode = new WrappingGridNode<InventoryCategoryNode>
|
||||
{
|
||||
Position = ContentStartPosition,
|
||||
|
||||
@@ -33,6 +33,8 @@ public unsafe class AddonRetainerWindow : InventoryAddonBase
|
||||
|
||||
protected override void OnSetup(AtkUnitBase* addon)
|
||||
{
|
||||
InitializeBackgroundDropTarget();
|
||||
|
||||
WindowNode?.AddColor = _tintColor;
|
||||
|
||||
CategoriesNode = new WrappingGridNode<InventoryCategoryNode>
|
||||
|
||||
@@ -26,6 +26,8 @@ public unsafe class AddonSaddleBagWindow : InventoryAddonBase
|
||||
|
||||
protected override void OnSetup(AtkUnitBase* addon)
|
||||
{
|
||||
InitializeBackgroundDropTarget();
|
||||
|
||||
WindowNode?.AddColor = _tintColor;
|
||||
|
||||
CategoriesNode = new WrappingGridNode<InventoryCategoryNode>
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using AetherBags.Helpers;
|
||||
using AetherBags.Inventory;
|
||||
using AetherBags.Inventory.Categories;
|
||||
using AetherBags.Inventory.Context;
|
||||
using AetherBags.Inventory.Scanning;
|
||||
using AetherBags.Inventory.State;
|
||||
using AetherBags.Nodes.Input;
|
||||
using AetherBags.Nodes.Inventory;
|
||||
using AetherBags.Nodes.Layout;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace AetherBags.Addons;
|
||||
@@ -19,6 +24,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon
|
||||
protected readonly InventoryCategoryPinCoordinator PinCoordinator = new();
|
||||
protected readonly HashSet<InventoryCategoryNode> HoverSubscribed = new();
|
||||
|
||||
protected DragDropNode BackgroundDropTarget = null!;
|
||||
protected WrappingGridNode<InventoryCategoryNode> CategoriesNode = null!;
|
||||
protected TextInputWithHintNode SearchInputNode = null!;
|
||||
protected InventoryFooterNode FooterNode = null!;
|
||||
@@ -129,6 +135,26 @@ public abstract unsafe class InventoryAddonBase : NativeAddon
|
||||
}
|
||||
}
|
||||
|
||||
protected void InitializeBackgroundDropTarget()
|
||||
{
|
||||
BackgroundDropTarget = new DragDropNode
|
||||
{
|
||||
Position = ContentStartPosition,
|
||||
Size = ContentSize,
|
||||
IconId = 0,
|
||||
IsDraggable = false,
|
||||
IsClickable = false,
|
||||
AcceptedType = DragDropType.Item,
|
||||
};
|
||||
|
||||
BackgroundDropTarget.DragDropBackgroundNode.IsVisible = false;
|
||||
BackgroundDropTarget.IconNode.IsVisible = false;
|
||||
|
||||
BackgroundDropTarget.OnPayloadAccepted = OnBackgroundPayloadAccepted;
|
||||
|
||||
BackgroundDropTarget.AttachNode(this);
|
||||
}
|
||||
|
||||
protected virtual InventoryCategoryNode CreateCategoryNode()
|
||||
{
|
||||
return new InventoryCategoryNode
|
||||
@@ -139,6 +165,38 @@ public abstract unsafe class InventoryAddonBase : NativeAddon
|
||||
};
|
||||
}
|
||||
|
||||
private void OnBackgroundPayloadAccepted(DragDropNode node, DragDropPayload acceptedPayload)
|
||||
{
|
||||
if (!acceptedPayload.IsValidInventoryPayload) return;
|
||||
|
||||
InventoryLocation emptyLocation = InventoryScanner.GetFirstEmptySlot(InventoryState.SourceType);
|
||||
|
||||
if (!emptyLocation.IsValid)
|
||||
{
|
||||
Services.Logger.Error("No empty slots available to receive drop.");
|
||||
return;
|
||||
}
|
||||
|
||||
InventoryMappedLocation visualLocation = InventoryContextState.GetVisualLocation(emptyLocation.Container, emptyLocation.Slot);
|
||||
|
||||
var visualInvType = InventoryType.GetInventoryTypeFromContainerId(visualLocation.Container);
|
||||
int absoluteIndex = visualInvType.GetInventoryStartIndex + visualLocation.Slot;
|
||||
|
||||
var targetPayload = new DragDropPayload
|
||||
{
|
||||
Type = DragDropType.Item,
|
||||
Int1 = visualLocation.Container,
|
||||
Int2 = visualLocation.Slot,
|
||||
ReferenceIndex = (short)absoluteIndex
|
||||
};
|
||||
|
||||
Services.Logger.Debug($"[BackgroundDrop] Target: {emptyLocation} -> Visual: {visualLocation} (Ref: {absoluteIndex})");
|
||||
|
||||
InventoryMoveHelper.HandleItemMovePayload(acceptedPayload, targetPayload);
|
||||
|
||||
ManualRefresh();
|
||||
}
|
||||
|
||||
protected void WireHoverHandlers()
|
||||
{
|
||||
var nodes = CategoriesNode.Nodes;
|
||||
@@ -233,6 +291,12 @@ public abstract unsafe class InventoryAddonBase : NativeAddon
|
||||
protected void ResizeWindow(float width, float height, bool recalcLayout)
|
||||
{
|
||||
SetWindowSize(width, height);
|
||||
|
||||
if (BackgroundDropTarget != null)
|
||||
{
|
||||
BackgroundDropTarget.Size = ContentSize;
|
||||
}
|
||||
|
||||
LayoutContent();
|
||||
|
||||
if (recalcLayout)
|
||||
|
||||
@@ -10,12 +10,16 @@ public static unsafe class InventoryContextState
|
||||
{
|
||||
private static readonly HashSet<(int page, int slot)> EligibleSlots = new();
|
||||
private static readonly HashSet<(InventoryType container, int slot)> BlockedSlots = new();
|
||||
// map from real (containerId, slot) -> visual (containerId, slot)
|
||||
|
||||
private static readonly Dictionary<InventoryMappedLocation, InventoryMappedLocation> VisualLocationMap = new();
|
||||
private static readonly Dictionary<int, Dictionary<InventoryMappedLocation, InventoryMappedLocation>> GroupedLocationMaps = new();
|
||||
|
||||
private static uint _lastContextId;
|
||||
|
||||
public static uint ActiveContextId => _lastContextId;
|
||||
|
||||
public static bool HasActiveContext => _lastContextId != 0;
|
||||
|
||||
public static void RefreshMaps()
|
||||
{
|
||||
EligibleSlots.Clear();
|
||||
@@ -134,9 +138,6 @@ public static unsafe class InventoryContextState
|
||||
public static bool IsSlotBlocked(InventoryType container, int slot)
|
||||
=> BlockedSlots.Contains((container, slot));
|
||||
|
||||
public static bool HasActiveContext
|
||||
=> _lastContextId != 0;
|
||||
|
||||
public static InventoryMappedLocation GetVisualLocation(InventoryType realContainer, int slot)
|
||||
{
|
||||
var key = new InventoryMappedLocation((int)realContainer, slot);
|
||||
|
||||
@@ -82,13 +82,35 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!InventoryContextState.HasActiveContext)
|
||||
return true;
|
||||
uint contextId = InventoryContextState.ActiveContextId;
|
||||
if (contextId == 0) return true;
|
||||
|
||||
if (!IsMainInventory)
|
||||
return true;
|
||||
bool isRetainerContext = contextId == 4;
|
||||
bool isSaddlebagContext = contextId == 29;
|
||||
bool isMainContext = !isRetainerContext && isSaddlebagContext == false;
|
||||
|
||||
return InventoryContextState.IsEligible(InventoryPage, Item.Slot);
|
||||
if (IsMainInventory)
|
||||
{
|
||||
if (!isMainContext) return true;
|
||||
|
||||
return InventoryContextState.IsEligible(InventoryPage, Item.Slot);
|
||||
}
|
||||
|
||||
if (Item.Container.IsRetainer)
|
||||
{
|
||||
// ...but the context isn't for Retainers, don't dim it.
|
||||
if (!isRetainerContext)
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. If we are looking at a Saddlebag item...
|
||||
if (Item.Container.IsSaddleBag)
|
||||
{
|
||||
if (!isSaddlebagContext)
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -173,7 +173,27 @@ public static unsafe class InventoryScanner
|
||||
}
|
||||
|
||||
public static InventoryContainer* GetInventoryContainer(InventoryType inventoryType)
|
||||
=> FFXIVClientStructs.FFXIV.Client.Game.InventoryManager.Instance()->GetInventoryContainer(inventoryType);
|
||||
=> InventoryManager.Instance()->GetInventoryContainer(inventoryType);
|
||||
|
||||
public static InventoryLocation GetFirstEmptySlot(InventorySourceType source)
|
||||
{
|
||||
var manager = InventoryManager.Instance();
|
||||
var containers = InventorySourceDefinitions.GetContainersForSource(source);
|
||||
|
||||
foreach (var type in containers)
|
||||
{
|
||||
var container = manager->GetInventoryContainer(type);
|
||||
if (container == null || container->Size == 0) continue;
|
||||
|
||||
for (int i = 0; i < container->Size; i++)
|
||||
{
|
||||
if (container->Items[i].ItemId == 0)
|
||||
return new InventoryLocation(type, (ushort)i);
|
||||
}
|
||||
}
|
||||
|
||||
return InventoryLocation.Invalid;
|
||||
}
|
||||
|
||||
// Backwards compability TODO: Remove
|
||||
public static string GetEmptyItemSlotsString()
|
||||
@@ -184,7 +204,7 @@ public static unsafe class InventoryScanner
|
||||
int total = InventorySourceDefinitions.GetTotalSlots(source);
|
||||
uint empty = source switch
|
||||
{
|
||||
InventorySourceType.MainBags => FFXIVClientStructs.FFXIV.Client.Game.InventoryManager.Instance()->GetEmptySlotsInBag(),
|
||||
InventorySourceType.MainBags => InventoryManager.Instance()->GetEmptySlotsInBag(),
|
||||
InventorySourceType.SaddleBag => GetEmptySlotsInContainer(InventorySourceDefinitions.SaddleBag),
|
||||
InventorySourceType.PremiumSaddleBag => GetEmptySlotsInContainer(InventorySourceDefinitions.PremiumSaddleBag),
|
||||
InventorySourceType.AllSaddleBags => GetEmptySlotsInContainer(InventorySourceDefinitions.AllSaddleBags),
|
||||
@@ -198,7 +218,7 @@ public static unsafe class InventoryScanner
|
||||
private static uint GetEmptySlotsInContainer(InventoryType[] inventories)
|
||||
{
|
||||
uint empty = 0;
|
||||
var inventoryManager = FFXIVClientStructs.FFXIV.Client.Game.InventoryManager.Instance();
|
||||
var inventoryManager = InventoryManager.Instance();
|
||||
foreach (var inv in inventories)
|
||||
{
|
||||
var container = inventoryManager->GetInventoryContainer(inv);
|
||||
|
||||
@@ -60,13 +60,23 @@ public static class InventorySourceDefinitions
|
||||
_ => MainBags,
|
||||
};
|
||||
|
||||
public static InventoryType[] GetContainersForSource(InventorySourceType source) => source switch
|
||||
{
|
||||
InventorySourceType.MainBags => MainBags,
|
||||
InventorySourceType.SaddleBag => SaddleBag,
|
||||
InventorySourceType.PremiumSaddleBag => PremiumSaddleBag,
|
||||
InventorySourceType.AllSaddleBags => AllSaddleBags,
|
||||
InventorySourceType.Retainer => Retainer,
|
||||
_ => MainBags,
|
||||
};
|
||||
|
||||
public static int GetTotalSlots(InventorySourceType source) => source switch
|
||||
{
|
||||
InventorySourceType.MainBags => 140, // 4 * 35
|
||||
InventorySourceType.SaddleBag => 70, // 2 * 35
|
||||
InventorySourceType.PremiumSaddleBag => 70, // 2 * 35
|
||||
InventorySourceType.AllSaddleBags => 140, // 2 * 35
|
||||
InventorySourceType.Retainer => 175, // 7 * 25
|
||||
InventorySourceType.MainBags => 140, // 4 * 35
|
||||
InventorySourceType.SaddleBag => 70, // 2 * 35
|
||||
InventorySourceType.PremiumSaddleBag => 70, // 2 * 35
|
||||
InventorySourceType.AllSaddleBags => 140, // 2 * 35
|
||||
InventorySourceType.Retainer => Retainer.Length * 35, // 7 * 25
|
||||
_ => 140,
|
||||
};
|
||||
}
|
||||
@@ -51,8 +51,6 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
||||
};
|
||||
AddNode(defaultCurrencyColorNode);
|
||||
|
||||
AddNode();
|
||||
|
||||
CheckboxNode cappedEnabledCheckbox = new CheckboxNode
|
||||
{
|
||||
Size = Size with { Y = 18 },
|
||||
@@ -89,7 +87,7 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
||||
{
|
||||
Size = Size with { Y = 18 },
|
||||
IsVisible = true,
|
||||
String = "Color Weekly Limit",
|
||||
String = "Limited Currency Color",
|
||||
IsChecked = config.ColorWhenLimited,
|
||||
OnClick = isChecked =>
|
||||
{
|
||||
@@ -103,7 +101,7 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
||||
|
||||
ColorInputRow limitCurrencyColorNode = new ColorInputRow
|
||||
{
|
||||
Label = "Limit Currency Color",
|
||||
Label = "Color Weekly Limit",
|
||||
Size = new Vector2(300, 24),
|
||||
CurrentColor = config.LimitColor,
|
||||
DefaultColor = new CurrencySettings().LimitColor,
|
||||
|
||||
@@ -51,7 +51,7 @@ public class CurrencyNode : SimpleComponentNode
|
||||
|
||||
_countNode.TextColor =
|
||||
isLimited ? config.LimitColor :
|
||||
isCapped ? config.CappedColor :
|
||||
isCapped ? config.CappedColor :
|
||||
config.DefaultColor;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
Submodule KamiToolKit updated: 2122482f0d...ac0f8116f6
Reference in New Issue
Block a user