Add basic search

This commit is contained in:
Zeffuro
2025-12-20 10:19:16 +01:00
parent 592fa392bf
commit d18f4483bb
5 changed files with 99 additions and 43 deletions
+26 -28
View File
@@ -7,17 +7,18 @@ using AetherBags.Inventory;
using AetherBags.Nodes; using AetherBags.Nodes;
using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
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; using KamiToolKit;
using KamiToolKit.Classes; using KamiToolKit.Classes;
using Lumina.Data.Parsing.Uld;
namespace AetherBags.Addons; namespace AetherBags.Addons;
public class AddonInventoryWindow : NativeAddon public class AddonInventoryWindow : NativeAddon
{ {
private WrappingGridNode<InventoryCategoryNode> _categoriesNode; private WrappingGridNode<InventoryCategoryNode> _categoriesNode = null!;
private TextInputWithHintNode _searchInputNode = null!;
// Window constraints // Window constraints
private const float MinWindowWidth = 300; private const float MinWindowWidth = 300;
@@ -26,14 +27,12 @@ public class AddonInventoryWindow : NativeAddon
private const float MaxWindowHeight = 1000; private const float MaxWindowHeight = 1000;
// Layout settings // Layout settings
private const float CategorySpacing = 10; private const float CategorySpacing = 12;
private const float ItemSize = 40; private const float ItemSize = 40;
private const float ItemPadding = 6; 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);
addon->SubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
_categoriesNode = new WrappingGridNode<InventoryCategoryNode> _categoriesNode = new WrappingGridNode<InventoryCategoryNode>
{ {
Position = ContentStartPosition, Position = ContentStartPosition,
@@ -43,6 +42,20 @@ public class AddonInventoryWindow : NativeAddon
}; };
_categoriesNode.AttachNode(this); _categoriesNode.AttachNode(this);
var size = new Vector2(addon->Size.X / 2.0f, 28.0f);
Vector2 headerSize = new Vector2(addon->WindowHeaderCollisionNode->Width, addon->WindowHeaderCollisionNode->Height);
_searchInputNode = new TextInputWithHintNode {
Position = headerSize / 2.0f - size / 2.0f + new Vector2(25.0f, 10.0f),
Size = size,
OnInputReceived = _ => RefreshCategories(false),
};
_searchInputNode.AttachNode(this);
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, "Inventory", OnInventoryUpdate);
addon->SubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
RefreshCategories(); RefreshCategories();
} }
@@ -60,9 +73,9 @@ public class AddonInventoryWindow : NativeAddon
RefreshCategories(); RefreshCategories();
} }
private void RefreshCategories() private void RefreshCategories(bool autosize = true)
{ {
var categories = InventoryState.GetInventoryItemCategories(); var categories = InventoryState.GetInventoryItemCategories(_searchInputNode.SearchString.ExtractText());
float maxContentWidth = MaxWindowWidth - (ContentStartPosition.X * 2); float maxContentWidth = MaxWindowWidth - (ContentStartPosition.X * 2);
int maxItemsPerLine = CalculateOptimalItemsPerLine(maxContentWidth); int maxItemsPerLine = CalculateOptimalItemsPerLine(maxContentWidth);
@@ -70,39 +83,24 @@ public class AddonInventoryWindow : NativeAddon
_categoriesNode.SyncWithListData( _categoriesNode.SyncWithListData(
categories, categories,
node => node.CategorizedInventory, node => node.CategorizedInventory,
data => data => new InventoryCategoryNode
{
var node = new InventoryCategoryNode
{ {
Size = ContentSize with { Y = 120 }, Size = ContentSize with { Y = 120 },
CategorizedInventory = data CategorizedInventory = data,
}; ItemsPerLine = Math.Min(data.Items.Count, maxItemsPerLine)
UpdateItemsPerLine(node, maxItemsPerLine);
return node;
}); });
foreach (InventoryCategoryNode node in _categoriesNode.GetNodes<InventoryCategoryNode>()) foreach (InventoryCategoryNode node in _categoriesNode.GetNodes<InventoryCategoryNode>())
{ {
UpdateItemsPerLine(node, maxItemsPerLine); node.ItemsPerLine = Math.Min(node.CategorizedInventory.Items.Count, maxItemsPerLine);
} }
AutoSizeWindow(); if(autosize) 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) private int CalculateOptimalItemsPerLine(float availableWidth)
{ {
float itemWithPadding = ItemSize + ItemPadding; return Math.Clamp((int)Math.Floor((availableWidth + ItemPadding) / (ItemSize + ItemPadding)), 1, 15);
int maxItems = (int)Math.Floor((availableWidth + ItemPadding) / itemWithPadding);
return Math.Clamp(maxItems, 1, 15);
} }
private void AutoSizeWindow() private void AutoSizeWindow()
+4 -2
View File
@@ -35,9 +35,11 @@ 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() public static List<CategorizedInventory> GetInventoryItemCategories(string filterString = "", bool invert = false)
{ {
var items = GetInventoryItems(); var items = string.IsNullOrEmpty(filterString)
? GetInventoryItems()
: GetInventoryItems(filterString, invert);
return items return items
.GroupBy(GetItemUiCategoryKey) .GroupBy(GetItemUiCategoryKey)
+15 -8
View File
@@ -22,7 +22,7 @@ public class InventoryCategoryNode : SimpleComponentNode
private const float HeaderHeight = 16; private const float HeaderHeight = 16;
private const float MinWidth = 40; private const float MinWidth = 40;
private float? _fixedWidth = null; private float? _fixedWidth;
public InventoryCategoryNode() public InventoryCategoryNode()
{ {
@@ -61,17 +61,25 @@ public class InventoryCategoryNode : SimpleComponentNode
} }
} }
public void SetItemsPerLine(int itemsPerLine) public int ItemsPerLine
{ {
_itemGridNode.ItemsPerLine = itemsPerLine; get => _itemGridNode.ItemsPerLine;
set
{
_itemGridNode.ItemsPerLine = value;
RecalculateSize(); RecalculateSize();
} }
}
public void SetFixedWidth(float width) public float? FixedWidth
{ {
_fixedWidth = width; get => _fixedWidth;
set
{
_fixedWidth = value;
RecalculateSize(); RecalculateSize();
} }
}
private void RecalculateSize() private void RecalculateSize()
{ {
@@ -106,10 +114,9 @@ public class InventoryCategoryNode : SimpleComponentNode
_categoryNameTextNode.Size = _categoryNameTextNode.Size with { X = calculatedWidth }; _categoryNameTextNode.Size = _categoryNameTextNode.Size with { X = calculatedWidth };
} }
private bool UpdateItemGrid() private void UpdateItemGrid()
{ {
var listUpdated = _itemGridNode.SyncWithListData(CategorizedInventory.Items, node => node.ItemInfo, CreateInventoryDragDropNode); _itemGridNode.SyncWithListData(CategorizedInventory.Items, node => node.ItemInfo, CreateInventoryDragDropNode);
return listUpdated;
} }
private InventoryDragDropNode CreateInventoryDragDropNode(ItemInfo data) private InventoryDragDropNode CreateInventoryDragDropNode(ItemInfo data)
+51
View File
@@ -0,0 +1,51 @@
using System;
using System.Numerics;
using KamiToolKit.Nodes;
using Lumina.Text;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes;
public class TextInputWithHintNode : SimpleComponentNode {
private readonly TextInputNode textInputNode;
private readonly ImageNode helpNode;
public TextInputWithHintNode() {
textInputNode = new TextInputNode {
PlaceholderString = "Search . . .",
};
textInputNode.AttachNode(this);
helpNode = new SimpleImageNode {
TexturePath = "ui/uld/CircleButtons.tex",
TextureCoordinates = new Vector2(112.0f, 84.0f),
TextureSize = new Vector2(28.0f, 28.0f),
Tooltip = new SeStringBuilder()
.Append("Supports Regex Search")
.AppendNewLine()
.Append("Start input with '$' to search by description")
.ToReadOnlySeString(),
};
helpNode.AttachNode(this);
}
public required Action<ReadOnlySeString>? OnInputReceived {
get => textInputNode.OnInputReceived;
set => textInputNode.OnInputReceived = value;
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
helpNode.Size = new Vector2(Height, Height);
helpNode.Position = new Vector2(Width - helpNode.Width - 5.0f, 0.0f);
textInputNode.Size = new Vector2(Width - helpNode.Width - 5.0f, Height);
textInputNode.Position = new Vector2(0.0f, 0.0f);
}
public ReadOnlySeString SearchString {
get => textInputNode.SeString;
set => textInputNode.SeString = value;
}
}
-2
View File
@@ -1,7 +1,6 @@
using System.Numerics; using System.Numerics;
using AetherBags.Addons; using AetherBags.Addons;
using AetherBags.Helpers; using AetherBags.Helpers;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Game.Command; using Dalamud.Game.Command;
using KamiToolKit; using KamiToolKit;
@@ -43,7 +42,6 @@ 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()