Improve layout
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAgentSatisfactionSupply_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb2cd0663609440e590f52980cafc1ba3822648_003F28_003Ffa48b62e_003FAgentSatisfactionSupply_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAgentSatisfactionSupply_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb2cd0663609440e590f52980cafc1ba3822648_003F28_003Ffa48b62e_003FAgentSatisfactionSupply_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AItem_002Eg_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F8ec7cc8a18dbb6a6f3c21f8adcb4e2661dc7979_003FItem_002Eg_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using AetherBags.Extensions;
|
using AetherBags.Extensions;
|
||||||
@@ -15,58 +17,33 @@ namespace AetherBags.Addons;
|
|||||||
|
|
||||||
public class AddonInventoryWindow : NativeAddon
|
public class AddonInventoryWindow : NativeAddon
|
||||||
{
|
{
|
||||||
private InventoryCategoryNode _categoryNode;
|
private WrappingGridNode<InventoryCategoryNode> _categoriesNode;
|
||||||
private InventoryDragDropNode _dragDropNode;
|
|
||||||
|
// Window constraints
|
||||||
|
private const float MinWindowWidth = 300;
|
||||||
|
private const float MaxWindowWidth = 800;
|
||||||
|
private const float MinWindowHeight = 200;
|
||||||
|
private const float MaxWindowHeight = 1000;
|
||||||
|
|
||||||
|
// Layout settings
|
||||||
|
private const float CategorySpacing = 10;
|
||||||
|
private const float ItemSize = 40;
|
||||||
|
private const float ItemPadding = 6;
|
||||||
|
|
||||||
protected override unsafe void OnSetup(AtkUnitBase* addon)
|
protected override unsafe void OnSetup(AtkUnitBase* addon)
|
||||||
{
|
{
|
||||||
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, "Inventory", OnInventoryUpdate);
|
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, "Inventory", OnInventoryUpdate);
|
||||||
_categoryNode = new InventoryCategoryNode
|
addon->SubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
|
||||||
|
_categoriesNode = new WrappingGridNode<InventoryCategoryNode>
|
||||||
{
|
{
|
||||||
Position = ContentStartPosition,
|
Position = ContentStartPosition,
|
||||||
Size = ContentSize,
|
Size = ContentSize,
|
||||||
Category = new CategoryInfo
|
HorizontalSpacing = CategorySpacing,
|
||||||
{
|
VerticalSpacing = CategorySpacing
|
||||||
Name = "AetherBags",
|
|
||||||
},
|
|
||||||
Items = InventoryState.GetInventoryItems()
|
|
||||||
};
|
};
|
||||||
_categoryNode.AttachNode(this);
|
_categoriesNode.AttachNode(this);
|
||||||
/*
|
|
||||||
var data = InventoryState.GetInventoryItems().Find(item => item.Name.Contains("Cookie"));
|
|
||||||
|
|
||||||
|
|
||||||
if (data != null)
|
|
||||||
{
|
|
||||||
var item = data.Item;
|
|
||||||
_dragDropNode = new InventoryDragDropNode
|
|
||||||
{
|
|
||||||
Size = new Vector2(48),
|
|
||||||
IsVisible = true,
|
|
||||||
IconId = data.IconId,
|
|
||||||
AcceptedType = DragDropType.Nothing,
|
|
||||||
IsDraggable = false,
|
|
||||||
Payload = new DragDropPayload
|
|
||||||
{
|
|
||||||
Type = DragDropType.Item,
|
|
||||||
Int1 = (int)data.Item.Container,
|
|
||||||
Int2 = (int)data.Item.ItemId,
|
|
||||||
},
|
|
||||||
IsClickable = true,
|
|
||||||
OnRollOver = node => node.ShowInventoryItemTooltip(data.Item.Container, data.Item.Slot),
|
|
||||||
OnRollOut = node => node.HideTooltip(),
|
|
||||||
OnClicked = _ =>
|
|
||||||
{
|
|
||||||
|
|
||||||
AgentInventoryContext* context = AgentInventoryContext.Instance();
|
|
||||||
context->OpenForItemSlot(data.Item.Container, data.Item.Slot, 0, context->AddonId);
|
|
||||||
//item.UseItem();
|
|
||||||
},
|
|
||||||
ItemInfo = data
|
|
||||||
};
|
|
||||||
_dragDropNode.AttachNode(this);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
RefreshCategories();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override unsafe void OnUpdate(AtkUnitBase* addon)
|
protected override unsafe void OnUpdate(AtkUnitBase* addon)
|
||||||
@@ -76,11 +53,94 @@ public class AddonInventoryWindow : NativeAddon
|
|||||||
|
|
||||||
private void OnInventoryUpdate(AddonEvent type, AddonArgs args)
|
private void OnInventoryUpdate(AddonEvent type, AddonArgs args)
|
||||||
{
|
{
|
||||||
|
RefreshCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override unsafe void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) {
|
||||||
|
RefreshCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshCategories()
|
||||||
|
{
|
||||||
|
var categories = InventoryState.GetInventoryItemCategories();
|
||||||
|
|
||||||
|
float maxContentWidth = MaxWindowWidth - (ContentStartPosition.X * 2);
|
||||||
|
int maxItemsPerLine = CalculateOptimalItemsPerLine(maxContentWidth);
|
||||||
|
|
||||||
|
_categoriesNode.SyncWithListData(
|
||||||
|
categories,
|
||||||
|
node => node.CategorizedInventory,
|
||||||
|
data =>
|
||||||
|
{
|
||||||
|
var node = new InventoryCategoryNode
|
||||||
|
{
|
||||||
|
Size = ContentSize with { Y = 120 },
|
||||||
|
CategorizedInventory = data
|
||||||
|
};
|
||||||
|
|
||||||
|
UpdateItemsPerLine(node, maxItemsPerLine);
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (InventoryCategoryNode node in _categoriesNode.GetNodes<InventoryCategoryNode>())
|
||||||
|
{
|
||||||
|
UpdateItemsPerLine(node, maxItemsPerLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoSizeWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdateItemsPerLine(InventoryCategoryNode node, int maxItemsPerLine)
|
||||||
|
{
|
||||||
|
int itemCount = node.CategorizedInventory.Items.Count;
|
||||||
|
int itemsPerLine = Math.Min(itemCount, maxItemsPerLine);
|
||||||
|
node.SetItemsPerLine(itemsPerLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int CalculateOptimalItemsPerLine(float availableWidth)
|
||||||
|
{
|
||||||
|
float itemWithPadding = ItemSize + ItemPadding;
|
||||||
|
int maxItems = (int)Math.Floor((availableWidth + ItemPadding) / itemWithPadding);
|
||||||
|
|
||||||
|
return Math.Clamp(maxItems, 1, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AutoSizeWindow()
|
||||||
|
{
|
||||||
|
List<InventoryCategoryNode> childNodes = _categoriesNode.GetNodes<InventoryCategoryNode>().ToList();
|
||||||
|
if (childNodes.Count == 0)
|
||||||
|
{
|
||||||
|
ResizeWindow(MinWindowWidth, MinWindowHeight);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float requiredWidth = childNodes.Max(node => node. Width);
|
||||||
|
requiredWidth += ContentStartPosition.X * 2;
|
||||||
|
float finalWidth = Math.Clamp(requiredWidth, MinWindowWidth, MaxWindowWidth);
|
||||||
|
|
||||||
|
float contentWidth = finalWidth - (ContentStartPosition.X * 2);
|
||||||
|
_categoriesNode.Size = new Vector2(contentWidth, MaxWindowHeight);
|
||||||
|
|
||||||
|
_categoriesNode.RecalculateLayout();
|
||||||
|
|
||||||
|
float requiredHeight = _categoriesNode.GetRequiredHeight();
|
||||||
|
requiredHeight += ContentStartPosition.Y + ContentStartPosition.X;
|
||||||
|
|
||||||
|
float finalHeight = Math.Clamp(requiredHeight, MinWindowHeight, MaxWindowHeight);
|
||||||
|
|
||||||
|
ResizeWindow(finalWidth, finalHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResizeWindow(float width, float height)
|
||||||
|
{
|
||||||
|
SetWindowSize(width, height);
|
||||||
|
_categoriesNode.Size = ContentSize;
|
||||||
|
_categoriesNode.RecalculateLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override unsafe void OnFinalize(AtkUnitBase* addon)
|
protected override unsafe void OnFinalize(AtkUnitBase* addon)
|
||||||
{
|
{
|
||||||
Services.AddonLifecycle.UnregisterListener(OnInventoryUpdate);
|
Services.AddonLifecycle.UnregisterListener(OnInventoryUpdate);
|
||||||
|
addon->UnsubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AetherBags;
|
||||||
|
using Dalamud.Game.Addon.Lifecycle;
|
||||||
|
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
public static class AddonLifecycleExtensions {
|
||||||
|
extension(IAddonLifecycle addonLifecycle) {
|
||||||
|
public void LogAddon(string addonName, params AddonEvent[] loggedModules) {
|
||||||
|
if (loggedModules.Length is 0) {
|
||||||
|
loggedModules = [
|
||||||
|
AddonEvent.PostSetup,
|
||||||
|
AddonEvent.PostOpen,
|
||||||
|
AddonEvent.PostClose,
|
||||||
|
AddonEvent.PostShow,
|
||||||
|
AddonEvent.PostHide,
|
||||||
|
AddonEvent.PostRefresh,
|
||||||
|
AddonEvent.PostRequestedUpdate,
|
||||||
|
AddonEvent.PreFinalize,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveLoggers.TryAdd(addonName, loggedModules.ToList());
|
||||||
|
foreach (var loggedModule in loggedModules) {
|
||||||
|
addonLifecycle.RegisterListener(loggedModule, addonName, Logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnLogAddon(string addonName) {
|
||||||
|
if (!ActiveLoggers.TryGetValue(addonName, out var loggedModules)) return;
|
||||||
|
|
||||||
|
foreach (var loggedModule in loggedModules) {
|
||||||
|
addonLifecycle.UnregisterListener(loggedModule, addonName, Logger);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Dictionary<string, List<AddonEvent>> ActiveLoggers = [];
|
||||||
|
|
||||||
|
private static void Logger(AddonEvent type, AddonArgs args) {
|
||||||
|
switch (args) {
|
||||||
|
case AddonReceiveEventArgs receiveEventArgs:
|
||||||
|
Services.Logger.Debug($"[{args.AddonName}] {(AtkEventType)receiveEventArgs.AtkEventType}: {receiveEventArgs.EventParam}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Services.Logger.Debug($"{args.AddonName} called {type.ToString().Replace("Post", string.Empty)}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace AetherBags.Inventory;
|
||||||
|
|
||||||
|
public readonly record struct CategorizedInventory(CategoryInfo Category, List<ItemInfo> Items);
|
||||||
@@ -3,6 +3,7 @@ using System.Linq;
|
|||||||
using Dalamud.Game.Inventory;
|
using Dalamud.Game.Inventory;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using Lumina.Excel.Sheets;
|
||||||
|
|
||||||
namespace AetherBags.Inventory;
|
namespace AetherBags.Inventory;
|
||||||
|
|
||||||
@@ -34,6 +35,22 @@ public static unsafe class InventoryState
|
|||||||
public static bool Contains(this List<InventoryType> inventoryTypes, GameInventoryType type)
|
public static bool Contains(this List<InventoryType> inventoryTypes, GameInventoryType type)
|
||||||
=> inventoryTypes.Contains((InventoryType)type);
|
=> inventoryTypes.Contains((InventoryType)type);
|
||||||
|
|
||||||
|
public static List<CategorizedInventory> GetInventoryItemCategories()
|
||||||
|
{
|
||||||
|
var items = GetInventoryItems();
|
||||||
|
|
||||||
|
return items
|
||||||
|
.GroupBy(GetItemUiCategoryKey)
|
||||||
|
.OrderBy(g => g.Key)
|
||||||
|
.Select(g =>
|
||||||
|
{
|
||||||
|
var category = GetCategoryInfoForKey(g.Key, g.FirstOrDefault());
|
||||||
|
var list = g.OrderByDescending(i => i.ItemCount).ToList();
|
||||||
|
return new CategorizedInventory(category, list);
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
public static List<ItemInfo> GetInventoryItems() {
|
public static List<ItemInfo> GetInventoryItems() {
|
||||||
List<InventoryType> inventories = [ InventoryType.Inventory1, InventoryType.Inventory2, InventoryType.Inventory3, InventoryType.Inventory4 ];
|
List<InventoryType> inventories = [ InventoryType.Inventory1, InventoryType.Inventory2, InventoryType.Inventory3, InventoryType.Inventory4 ];
|
||||||
List<InventoryItem> items = [];
|
List<InventoryItem> items = [];
|
||||||
@@ -60,4 +77,35 @@ public static unsafe class InventoryState
|
|||||||
|
|
||||||
return itemInfos;
|
return itemInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<ItemInfo> GetInventoryItems(string filterString, bool invert = false)
|
||||||
|
=> GetInventoryItems().Where(item => item.IsRegexMatch(filterString) != invert).ToList();
|
||||||
|
|
||||||
|
private static uint GetItemUiCategoryKey(ItemInfo info)
|
||||||
|
=> info.UiCategory.RowId;
|
||||||
|
|
||||||
|
private static CategoryInfo GetCategoryInfoForKey(uint key, ItemInfo? sample)
|
||||||
|
{
|
||||||
|
if (key == 0)
|
||||||
|
{
|
||||||
|
return new CategoryInfo
|
||||||
|
{
|
||||||
|
Name = "Misc",
|
||||||
|
Description = "Uncategorized items",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var uiCat = sample?.UiCategory.Value;
|
||||||
|
var name = uiCat?.Name.ToString();
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
name = $"Category\\ {key}";
|
||||||
|
|
||||||
|
return new CategoryInfo
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ using System.Numerics;
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using AetherBags.Extensions;
|
using AetherBags.Extensions;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using Lumina.Excel;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
|
||||||
namespace AetherBags.Inventory;
|
namespace AetherBags.Inventory;
|
||||||
@@ -25,7 +26,7 @@ public class ItemInfo : IEquatable<ItemInfo> {
|
|||||||
|
|
||||||
public int Rarity => ItemData.Rarity;
|
public int Rarity => ItemData.Rarity;
|
||||||
|
|
||||||
public int UiCategory => (int) ItemData.ItemUICategory.RowId;
|
public RowRef<ItemUICategory> UiCategory => ItemData.ItemUICategory;
|
||||||
|
|
||||||
private string Description => ItemData.Description.ToString();
|
private string Description => ItemData.Description.ToString();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
using System;
|
||||||
|
using KamiToolKit;
|
||||||
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
|
namespace AetherBags.Nodes
|
||||||
|
{
|
||||||
|
public class HybridDirectionalStackNode<T> : LayoutListNode where T : NodeBase
|
||||||
|
{
|
||||||
|
public FlexGrowDirection GrowDirection
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
RecalculateLayout();
|
||||||
|
}
|
||||||
|
} = FlexGrowDirection.DownRight;
|
||||||
|
|
||||||
|
public bool Vertical
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
RecalculateLayout();
|
||||||
|
}
|
||||||
|
} = true;
|
||||||
|
|
||||||
|
public float Spacing
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
RecalculateLayout();
|
||||||
|
}
|
||||||
|
} = 1f;
|
||||||
|
|
||||||
|
public bool StretchCrossAxis
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
RecalculateLayout();
|
||||||
|
}
|
||||||
|
} = true;
|
||||||
|
|
||||||
|
protected override void InternalRecalculateLayout()
|
||||||
|
{
|
||||||
|
if (NodeList.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool alignRight = GrowDirection is FlexGrowDirection.DownLeft or FlexGrowDirection.UpLeft;
|
||||||
|
bool alignBottom = GrowDirection is FlexGrowDirection.UpRight or FlexGrowDirection.UpLeft;
|
||||||
|
|
||||||
|
float startX = alignRight ? Width : 0f;
|
||||||
|
float startY = alignBottom ? Height : 0f;
|
||||||
|
|
||||||
|
float cursor = 0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < NodeList.Count; i++)
|
||||||
|
{
|
||||||
|
var node = NodeList[i];
|
||||||
|
|
||||||
|
if (StretchCrossAxis)
|
||||||
|
{
|
||||||
|
if (Vertical)
|
||||||
|
node.Width = Width;
|
||||||
|
else
|
||||||
|
node.Height = Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
float x, y;
|
||||||
|
if (Vertical)
|
||||||
|
{
|
||||||
|
x = alignRight ? startX - node.Width : startX;
|
||||||
|
y = alignBottom ? startY - node.Height - cursor : startY + cursor;
|
||||||
|
cursor += node.Height + Spacing;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
x = alignRight ? startX - node.Width - cursor : startX + cursor;
|
||||||
|
y = alignBottom ? startY - node.Height : startY;
|
||||||
|
cursor += node.Width + Spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.X = x;
|
||||||
|
node.Y = y;
|
||||||
|
AdjustNode(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using AetherBags.Extensions;
|
using AetherBags.Extensions;
|
||||||
using AetherBags.Inventory;
|
using AetherBags.Inventory;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using KamiToolKit.Classes;
|
using KamiToolKit.Classes;
|
||||||
@@ -14,6 +15,15 @@ public class InventoryCategoryNode : SimpleComponentNode
|
|||||||
{
|
{
|
||||||
private readonly TextNode _categoryNameTextNode;
|
private readonly TextNode _categoryNameTextNode;
|
||||||
private readonly HybridDirectionalFlexNode<DragDropNode> _itemGridNode;
|
private readonly HybridDirectionalFlexNode<DragDropNode> _itemGridNode;
|
||||||
|
|
||||||
|
private const float ItemSize = 40;
|
||||||
|
private const float ItemHorizontalPadding = 6;
|
||||||
|
private const float ItemVerticalPadding = 6;
|
||||||
|
private const float HeaderHeight = 16;
|
||||||
|
private const float MinWidth = 40;
|
||||||
|
|
||||||
|
private float? _fixedWidth = null;
|
||||||
|
|
||||||
public InventoryCategoryNode()
|
public InventoryCategoryNode()
|
||||||
{
|
{
|
||||||
_categoryNameTextNode = new TextNode
|
_categoryNameTextNode = new TextNode
|
||||||
@@ -25,63 +35,101 @@ public class InventoryCategoryNode : SimpleComponentNode
|
|||||||
|
|
||||||
_itemGridNode = new HybridDirectionalFlexNode<DragDropNode>
|
_itemGridNode = new HybridDirectionalFlexNode<DragDropNode>
|
||||||
{
|
{
|
||||||
Position = new Vector2(0, 16),
|
Position = new Vector2(0, HeaderHeight),
|
||||||
Size = new Vector2(240, 100),
|
Size = new Vector2(240, 100),
|
||||||
FillRowsFirst = true,
|
FillRowsFirst = true,
|
||||||
ItemsPerLine = 10,
|
ItemsPerLine = 10,
|
||||||
HorizontalPadding = 6,
|
HorizontalPadding = ItemHorizontalPadding,
|
||||||
VerticalPadding = 6,
|
VerticalPadding = ItemVerticalPadding,
|
||||||
};
|
};
|
||||||
_itemGridNode.AttachNode(this);
|
_itemGridNode.AttachNode(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public required CategoryInfo Category
|
public required CategorizedInventory CategorizedInventory
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
field = value;
|
field = value;
|
||||||
|
|
||||||
_categoryNameTextNode.String = value.Name;
|
_categoryNameTextNode.String = value.Category.Name;
|
||||||
_categoryNameTextNode.TextColor = value.Color;
|
_categoryNameTextNode.TextColor = value.Category.Color;
|
||||||
_categoryNameTextNode.TooltipString = value.Description;
|
_categoryNameTextNode.TooltipString = value.Category.Description;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public required List<ItemInfo> Items
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
field = value;
|
|
||||||
|
|
||||||
UpdateItemGrid();
|
UpdateItemGrid();
|
||||||
|
RecalculateSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetItemsPerLine(int itemsPerLine)
|
||||||
|
{
|
||||||
|
_itemGridNode.ItemsPerLine = itemsPerLine;
|
||||||
|
RecalculateSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetFixedWidth(float width)
|
||||||
|
{
|
||||||
|
_fixedWidth = width;
|
||||||
|
RecalculateSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecalculateSize()
|
||||||
|
{
|
||||||
|
int itemCount = CategorizedInventory.Items.Count;
|
||||||
|
if (itemCount == 0)
|
||||||
|
{
|
||||||
|
float width = _fixedWidth ?? MinWidth;
|
||||||
|
Size = new Vector2(width, HeaderHeight);
|
||||||
|
_categoryNameTextNode.Size = _categoryNameTextNode.Size with { X = width };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int itemsPerLine = Math.Max(1, _itemGridNode.ItemsPerLine);
|
||||||
|
int rows = (int)Math.Ceiling((float)itemCount / itemsPerLine);
|
||||||
|
|
||||||
|
float calculatedWidth;
|
||||||
|
if (_fixedWidth. HasValue)
|
||||||
|
{
|
||||||
|
calculatedWidth = _fixedWidth.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int actualColumns = Math.Min(itemCount, itemsPerLine);
|
||||||
|
calculatedWidth = actualColumns * ItemSize + (actualColumns - 1) * ItemHorizontalPadding;
|
||||||
|
calculatedWidth = Math.Max(calculatedWidth, MinWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
float height = HeaderHeight + rows * ItemSize + (rows - 1) * ItemVerticalPadding;
|
||||||
|
|
||||||
|
Size = new Vector2(calculatedWidth, height);
|
||||||
|
_itemGridNode.Size = new Vector2(calculatedWidth, height - HeaderHeight);
|
||||||
|
_categoryNameTextNode.Size = _categoryNameTextNode.Size with { X = calculatedWidth };
|
||||||
|
}
|
||||||
|
|
||||||
private bool UpdateItemGrid()
|
private bool UpdateItemGrid()
|
||||||
{
|
{
|
||||||
var listUpdated = _itemGridNode.SyncWithListData(Items, node => node.ItemInfo, data => CreateInventoryDragDropNode(data));
|
var listUpdated = _itemGridNode.SyncWithListData(CategorizedInventory.Items, node => node.ItemInfo, CreateInventoryDragDropNode);
|
||||||
return listUpdated;
|
return listUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe InventoryDragDropNode CreateInventoryDragDropNode(ItemInfo data)
|
private InventoryDragDropNode CreateInventoryDragDropNode(ItemInfo data)
|
||||||
{
|
{
|
||||||
|
InventoryItem item = data.Item;
|
||||||
InventoryDragDropNode node = new InventoryDragDropNode
|
InventoryDragDropNode node = new InventoryDragDropNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(40),
|
Size = new Vector2(40),
|
||||||
IsVisible = true,
|
IsVisible = true,
|
||||||
IconId = data.IconId,
|
IconId = item.IconId,
|
||||||
AcceptedType = DragDropType.Nothing,
|
AcceptedType = DragDropType.Nothing,
|
||||||
IsDraggable = false,
|
IsDraggable = false,
|
||||||
Payload = new DragDropPayload
|
Payload = new DragDropPayload
|
||||||
{
|
{
|
||||||
Type = DragDropType.Item,
|
Type = DragDropType.Item,
|
||||||
Int1 = (int)data.Item.Container,
|
Int1 = (int)item.Container,
|
||||||
Int2 = (int)data.Item.ItemId,
|
Int2 = (int)item.ItemId,
|
||||||
},
|
},
|
||||||
IsClickable = true,
|
IsClickable = true,
|
||||||
OnRollOver = node => node.ShowInventoryItemTooltip(data.Item.Container, data.Item.Slot),
|
OnRollOver = node => node.ShowInventoryItemTooltip(item.Container, item.Slot),
|
||||||
OnRollOut = node => node.HideTooltip(),
|
OnRollOut = node => node.HideTooltip(),
|
||||||
ItemInfo = data
|
ItemInfo = data
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using KamiToolKit;
|
||||||
|
using KamiToolKit. Nodes;
|
||||||
|
|
||||||
|
namespace AetherBags.Nodes
|
||||||
|
{
|
||||||
|
public class WrappingGridNode<T> : LayoutListNode where T : NodeBase
|
||||||
|
{
|
||||||
|
public float HorizontalSpacing { get; set; } = 10;
|
||||||
|
public float VerticalSpacing { get; set; } = 10;
|
||||||
|
|
||||||
|
private List<List<NodeBase>> _rows = new();
|
||||||
|
|
||||||
|
protected override void InternalRecalculateLayout()
|
||||||
|
{
|
||||||
|
if (NodeList. Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_rows.Clear();
|
||||||
|
|
||||||
|
float availableWidth = Width;
|
||||||
|
float currentX = 0f;
|
||||||
|
float currentY = 0f;
|
||||||
|
float rowHeight = 0f;
|
||||||
|
List<NodeBase> currentRow = new();
|
||||||
|
|
||||||
|
foreach (var node in NodeList)
|
||||||
|
{
|
||||||
|
float nodeWidth = node.Width;
|
||||||
|
float nodeHeight = node.Height;
|
||||||
|
|
||||||
|
if (currentX + nodeWidth > availableWidth && currentRow.Count > 0)
|
||||||
|
{
|
||||||
|
_rows.Add(currentRow);
|
||||||
|
currentRow = new();
|
||||||
|
currentY += rowHeight + VerticalSpacing;
|
||||||
|
currentX = 0f;
|
||||||
|
rowHeight = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.X = currentX;
|
||||||
|
node. Y = currentY;
|
||||||
|
AdjustNode(node);
|
||||||
|
|
||||||
|
currentX += nodeWidth + HorizontalSpacing;
|
||||||
|
rowHeight = Math.Max(rowHeight, nodeHeight);
|
||||||
|
currentRow.Add(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentRow.Count > 0)
|
||||||
|
{
|
||||||
|
_rows.Add(currentRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetRequiredHeight()
|
||||||
|
{
|
||||||
|
if (NodeList.Count == 0)
|
||||||
|
return 0f;
|
||||||
|
|
||||||
|
float maxY = 0f;
|
||||||
|
foreach (var node in NodeList)
|
||||||
|
{
|
||||||
|
float nodeBottom = node.Y + node.Height;
|
||||||
|
maxY = Math. Max(maxY, nodeBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,6 +43,7 @@ public class Plugin : IDalamudPlugin
|
|||||||
if (Services.ClientState.IsLoggedIn) {
|
if (Services.ClientState.IsLoggedIn) {
|
||||||
Services.Framework.RunOnFrameworkThread(OnLogin);
|
Services.Framework.RunOnFrameworkThread(OnLogin);
|
||||||
}
|
}
|
||||||
|
Services.AddonLifecycle.LogAddon("Inventory");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
Reference in New Issue
Block a user