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)
|
protected override void OnSetup(AtkUnitBase* addon)
|
||||||
{
|
{
|
||||||
|
InitializeBackgroundDropTarget();
|
||||||
|
|
||||||
CategoriesNode = new WrappingGridNode<InventoryCategoryNode>
|
CategoriesNode = new WrappingGridNode<InventoryCategoryNode>
|
||||||
{
|
{
|
||||||
Position = ContentStartPosition,
|
Position = ContentStartPosition,
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ public unsafe class AddonRetainerWindow : InventoryAddonBase
|
|||||||
|
|
||||||
protected override void OnSetup(AtkUnitBase* addon)
|
protected override void OnSetup(AtkUnitBase* addon)
|
||||||
{
|
{
|
||||||
|
InitializeBackgroundDropTarget();
|
||||||
|
|
||||||
WindowNode?.AddColor = _tintColor;
|
WindowNode?.AddColor = _tintColor;
|
||||||
|
|
||||||
CategoriesNode = new WrappingGridNode<InventoryCategoryNode>
|
CategoriesNode = new WrappingGridNode<InventoryCategoryNode>
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ public unsafe class AddonSaddleBagWindow : InventoryAddonBase
|
|||||||
|
|
||||||
protected override void OnSetup(AtkUnitBase* addon)
|
protected override void OnSetup(AtkUnitBase* addon)
|
||||||
{
|
{
|
||||||
|
InitializeBackgroundDropTarget();
|
||||||
|
|
||||||
WindowNode?.AddColor = _tintColor;
|
WindowNode?.AddColor = _tintColor;
|
||||||
|
|
||||||
CategoriesNode = new WrappingGridNode<InventoryCategoryNode>
|
CategoriesNode = new WrappingGridNode<InventoryCategoryNode>
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using AetherBags.Helpers;
|
||||||
using AetherBags.Inventory;
|
using AetherBags.Inventory;
|
||||||
using AetherBags.Inventory.Categories;
|
using AetherBags.Inventory.Categories;
|
||||||
|
using AetherBags.Inventory.Context;
|
||||||
|
using AetherBags.Inventory.Scanning;
|
||||||
using AetherBags.Inventory.State;
|
using AetherBags.Inventory.State;
|
||||||
using AetherBags.Nodes.Input;
|
using AetherBags.Nodes.Input;
|
||||||
using AetherBags.Nodes.Inventory;
|
using AetherBags.Nodes.Inventory;
|
||||||
using AetherBags.Nodes.Layout;
|
using AetherBags.Nodes.Layout;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using KamiToolKit;
|
using KamiToolKit;
|
||||||
|
using KamiToolKit.Classes;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
namespace AetherBags.Addons;
|
namespace AetherBags.Addons;
|
||||||
@@ -19,6 +24,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon
|
|||||||
protected readonly InventoryCategoryPinCoordinator PinCoordinator = new();
|
protected readonly InventoryCategoryPinCoordinator PinCoordinator = new();
|
||||||
protected readonly HashSet<InventoryCategoryNode> HoverSubscribed = new();
|
protected readonly HashSet<InventoryCategoryNode> HoverSubscribed = new();
|
||||||
|
|
||||||
|
protected DragDropNode BackgroundDropTarget = null!;
|
||||||
protected WrappingGridNode<InventoryCategoryNode> CategoriesNode = null!;
|
protected WrappingGridNode<InventoryCategoryNode> CategoriesNode = null!;
|
||||||
protected TextInputWithHintNode SearchInputNode = null!;
|
protected TextInputWithHintNode SearchInputNode = null!;
|
||||||
protected InventoryFooterNode FooterNode = 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()
|
protected virtual InventoryCategoryNode CreateCategoryNode()
|
||||||
{
|
{
|
||||||
return new InventoryCategoryNode
|
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()
|
protected void WireHoverHandlers()
|
||||||
{
|
{
|
||||||
var nodes = CategoriesNode.Nodes;
|
var nodes = CategoriesNode.Nodes;
|
||||||
@@ -233,6 +291,12 @@ public abstract unsafe class InventoryAddonBase : NativeAddon
|
|||||||
protected void ResizeWindow(float width, float height, bool recalcLayout)
|
protected void ResizeWindow(float width, float height, bool recalcLayout)
|
||||||
{
|
{
|
||||||
SetWindowSize(width, height);
|
SetWindowSize(width, height);
|
||||||
|
|
||||||
|
if (BackgroundDropTarget != null)
|
||||||
|
{
|
||||||
|
BackgroundDropTarget.Size = ContentSize;
|
||||||
|
}
|
||||||
|
|
||||||
LayoutContent();
|
LayoutContent();
|
||||||
|
|
||||||
if (recalcLayout)
|
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<(int page, int slot)> EligibleSlots = new();
|
||||||
private static readonly HashSet<(InventoryType container, int slot)> BlockedSlots = 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<InventoryMappedLocation, InventoryMappedLocation> VisualLocationMap = new();
|
||||||
private static readonly Dictionary<int, Dictionary<InventoryMappedLocation, InventoryMappedLocation>> GroupedLocationMaps = new();
|
private static readonly Dictionary<int, Dictionary<InventoryMappedLocation, InventoryMappedLocation>> GroupedLocationMaps = new();
|
||||||
|
|
||||||
private static uint _lastContextId;
|
private static uint _lastContextId;
|
||||||
|
|
||||||
|
public static uint ActiveContextId => _lastContextId;
|
||||||
|
|
||||||
|
public static bool HasActiveContext => _lastContextId != 0;
|
||||||
|
|
||||||
public static void RefreshMaps()
|
public static void RefreshMaps()
|
||||||
{
|
{
|
||||||
EligibleSlots.Clear();
|
EligibleSlots.Clear();
|
||||||
@@ -134,9 +138,6 @@ public static unsafe class InventoryContextState
|
|||||||
public static bool IsSlotBlocked(InventoryType container, int slot)
|
public static bool IsSlotBlocked(InventoryType container, int slot)
|
||||||
=> BlockedSlots.Contains((container, slot));
|
=> BlockedSlots.Contains((container, slot));
|
||||||
|
|
||||||
public static bool HasActiveContext
|
|
||||||
=> _lastContextId != 0;
|
|
||||||
|
|
||||||
public static InventoryMappedLocation GetVisualLocation(InventoryType realContainer, int slot)
|
public static InventoryMappedLocation GetVisualLocation(InventoryType realContainer, int slot)
|
||||||
{
|
{
|
||||||
var key = new InventoryMappedLocation((int)realContainer, slot);
|
var key = new InventoryMappedLocation((int)realContainer, slot);
|
||||||
|
|||||||
@@ -82,13 +82,35 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!InventoryContextState.HasActiveContext)
|
uint contextId = InventoryContextState.ActiveContextId;
|
||||||
return true;
|
if (contextId == 0) return true;
|
||||||
|
|
||||||
if (!IsMainInventory)
|
bool isRetainerContext = contextId == 4;
|
||||||
return true;
|
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)
|
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
|
// Backwards compability TODO: Remove
|
||||||
public static string GetEmptyItemSlotsString()
|
public static string GetEmptyItemSlotsString()
|
||||||
@@ -184,7 +204,7 @@ public static unsafe class InventoryScanner
|
|||||||
int total = InventorySourceDefinitions.GetTotalSlots(source);
|
int total = InventorySourceDefinitions.GetTotalSlots(source);
|
||||||
uint empty = source switch
|
uint empty = source switch
|
||||||
{
|
{
|
||||||
InventorySourceType.MainBags => FFXIVClientStructs.FFXIV.Client.Game.InventoryManager.Instance()->GetEmptySlotsInBag(),
|
InventorySourceType.MainBags => InventoryManager.Instance()->GetEmptySlotsInBag(),
|
||||||
InventorySourceType.SaddleBag => GetEmptySlotsInContainer(InventorySourceDefinitions.SaddleBag),
|
InventorySourceType.SaddleBag => GetEmptySlotsInContainer(InventorySourceDefinitions.SaddleBag),
|
||||||
InventorySourceType.PremiumSaddleBag => GetEmptySlotsInContainer(InventorySourceDefinitions.PremiumSaddleBag),
|
InventorySourceType.PremiumSaddleBag => GetEmptySlotsInContainer(InventorySourceDefinitions.PremiumSaddleBag),
|
||||||
InventorySourceType.AllSaddleBags => GetEmptySlotsInContainer(InventorySourceDefinitions.AllSaddleBags),
|
InventorySourceType.AllSaddleBags => GetEmptySlotsInContainer(InventorySourceDefinitions.AllSaddleBags),
|
||||||
@@ -198,7 +218,7 @@ public static unsafe class InventoryScanner
|
|||||||
private static uint GetEmptySlotsInContainer(InventoryType[] inventories)
|
private static uint GetEmptySlotsInContainer(InventoryType[] inventories)
|
||||||
{
|
{
|
||||||
uint empty = 0;
|
uint empty = 0;
|
||||||
var inventoryManager = FFXIVClientStructs.FFXIV.Client.Game.InventoryManager.Instance();
|
var inventoryManager = InventoryManager.Instance();
|
||||||
foreach (var inv in inventories)
|
foreach (var inv in inventories)
|
||||||
{
|
{
|
||||||
var container = inventoryManager->GetInventoryContainer(inv);
|
var container = inventoryManager->GetInventoryContainer(inv);
|
||||||
|
|||||||
@@ -60,13 +60,23 @@ public static class InventorySourceDefinitions
|
|||||||
_ => MainBags,
|
_ => 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
|
public static int GetTotalSlots(InventorySourceType source) => source switch
|
||||||
{
|
{
|
||||||
InventorySourceType.MainBags => 140, // 4 * 35
|
InventorySourceType.MainBags => 140, // 4 * 35
|
||||||
InventorySourceType.SaddleBag => 70, // 2 * 35
|
InventorySourceType.SaddleBag => 70, // 2 * 35
|
||||||
InventorySourceType.PremiumSaddleBag => 70, // 2 * 35
|
InventorySourceType.PremiumSaddleBag => 70, // 2 * 35
|
||||||
InventorySourceType.AllSaddleBags => 140, // 2 * 35
|
InventorySourceType.AllSaddleBags => 140, // 2 * 35
|
||||||
InventorySourceType.Retainer => 175, // 7 * 25
|
InventorySourceType.Retainer => Retainer.Length * 35, // 7 * 25
|
||||||
_ => 140,
|
_ => 140,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -51,8 +51,6 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
|||||||
};
|
};
|
||||||
AddNode(defaultCurrencyColorNode);
|
AddNode(defaultCurrencyColorNode);
|
||||||
|
|
||||||
AddNode();
|
|
||||||
|
|
||||||
CheckboxNode cappedEnabledCheckbox = new CheckboxNode
|
CheckboxNode cappedEnabledCheckbox = new CheckboxNode
|
||||||
{
|
{
|
||||||
Size = Size with { Y = 18 },
|
Size = Size with { Y = 18 },
|
||||||
@@ -89,7 +87,7 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
|||||||
{
|
{
|
||||||
Size = Size with { Y = 18 },
|
Size = Size with { Y = 18 },
|
||||||
IsVisible = true,
|
IsVisible = true,
|
||||||
String = "Color Weekly Limit",
|
String = "Limited Currency Color",
|
||||||
IsChecked = config.ColorWhenLimited,
|
IsChecked = config.ColorWhenLimited,
|
||||||
OnClick = isChecked =>
|
OnClick = isChecked =>
|
||||||
{
|
{
|
||||||
@@ -103,7 +101,7 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
|||||||
|
|
||||||
ColorInputRow limitCurrencyColorNode = new ColorInputRow
|
ColorInputRow limitCurrencyColorNode = new ColorInputRow
|
||||||
{
|
{
|
||||||
Label = "Limit Currency Color",
|
Label = "Color Weekly Limit",
|
||||||
Size = new Vector2(300, 24),
|
Size = new Vector2(300, 24),
|
||||||
CurrentColor = config.LimitColor,
|
CurrentColor = config.LimitColor,
|
||||||
DefaultColor = new CurrencySettings().LimitColor,
|
DefaultColor = new CurrencySettings().LimitColor,
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class CurrencyNode : SimpleComponentNode
|
|||||||
|
|
||||||
_countNode.TextColor =
|
_countNode.TextColor =
|
||||||
isLimited ? config.LimitColor :
|
isLimited ? config.LimitColor :
|
||||||
isCapped ? config.CappedColor :
|
isCapped ? config.CappedColor :
|
||||||
config.DefaultColor;
|
config.DefaultColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
Submodule KamiToolKit updated: 2122482f0d...ac0f8116f6
Reference in New Issue
Block a user