From d18f4483bb3af9ac4b03fdafce302713fdb47791 Mon Sep 17 00:00:00 2001 From: Zeffuro Date: Sat, 20 Dec 2025 10:19:16 +0100 Subject: [PATCH] Add basic search --- AetherBags/Addons/AddonInventoryWindow.cs | 56 +++++++++++------------ AetherBags/Inventory/InventoryState.cs | 6 ++- AetherBags/Nodes/InventoryCategoryNode.cs | 27 +++++++---- AetherBags/Nodes/TextInputWithHintNode.cs | 51 +++++++++++++++++++++ AetherBags/Plugin.cs | 2 - 5 files changed, 99 insertions(+), 43 deletions(-) create mode 100644 AetherBags/Nodes/TextInputWithHintNode.cs diff --git a/AetherBags/Addons/AddonInventoryWindow.cs b/AetherBags/Addons/AddonInventoryWindow.cs index de9ea4d..51492f1 100644 --- a/AetherBags/Addons/AddonInventoryWindow.cs +++ b/AetherBags/Addons/AddonInventoryWindow.cs @@ -7,17 +7,18 @@ using AetherBags.Inventory; using AetherBags.Nodes; using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; -using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Component.GUI; using KamiToolKit; using KamiToolKit.Classes; +using Lumina.Data.Parsing.Uld; namespace AetherBags.Addons; public class AddonInventoryWindow : NativeAddon { - private WrappingGridNode _categoriesNode; + private WrappingGridNode _categoriesNode = null!; + private TextInputWithHintNode _searchInputNode = null!; // Window constraints private const float MinWindowWidth = 300; @@ -26,14 +27,12 @@ public class AddonInventoryWindow : NativeAddon private const float MaxWindowHeight = 1000; // Layout settings - private const float CategorySpacing = 10; + private const float CategorySpacing = 12; private const float ItemSize = 40; private const float ItemPadding = 6; protected override unsafe void OnSetup(AtkUnitBase* addon) { - Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, "Inventory", OnInventoryUpdate); - addon->SubscribeAtkArrayData(1, (int)NumberArrayType.Inventory); _categoriesNode = new WrappingGridNode { Position = ContentStartPosition, @@ -43,6 +42,20 @@ public class AddonInventoryWindow : NativeAddon }; _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(); } @@ -60,9 +73,9 @@ public class AddonInventoryWindow : NativeAddon 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); int maxItemsPerLine = CalculateOptimalItemsPerLine(maxContentWidth); @@ -70,39 +83,24 @@ public class AddonInventoryWindow : NativeAddon _categoriesNode.SyncWithListData( categories, node => node.CategorizedInventory, - data => + data => new InventoryCategoryNode { - var node = new InventoryCategoryNode - { - Size = ContentSize with { Y = 120 }, - CategorizedInventory = data - }; - - UpdateItemsPerLine(node, maxItemsPerLine); - return node; + Size = ContentSize with { Y = 120 }, + CategorizedInventory = data, + ItemsPerLine = Math.Min(data.Items.Count, maxItemsPerLine) }); foreach (InventoryCategoryNode node in _categoriesNode.GetNodes()) { - UpdateItemsPerLine(node, maxItemsPerLine); + node.ItemsPerLine = Math.Min(node.CategorizedInventory.Items.Count, 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); + if(autosize) AutoSizeWindow(); } private int CalculateOptimalItemsPerLine(float availableWidth) { - float itemWithPadding = ItemSize + ItemPadding; - int maxItems = (int)Math.Floor((availableWidth + ItemPadding) / itemWithPadding); - - return Math.Clamp(maxItems, 1, 15); + return Math.Clamp((int)Math.Floor((availableWidth + ItemPadding) / (ItemSize + ItemPadding)), 1, 15); } private void AutoSizeWindow() diff --git a/AetherBags/Inventory/InventoryState.cs b/AetherBags/Inventory/InventoryState.cs index 0b97f8d..f6ff412 100644 --- a/AetherBags/Inventory/InventoryState.cs +++ b/AetherBags/Inventory/InventoryState.cs @@ -35,9 +35,11 @@ public static unsafe class InventoryState public static bool Contains(this List inventoryTypes, GameInventoryType type) => inventoryTypes.Contains((InventoryType)type); - public static List GetInventoryItemCategories() + public static List GetInventoryItemCategories(string filterString = "", bool invert = false) { - var items = GetInventoryItems(); + var items = string.IsNullOrEmpty(filterString) + ? GetInventoryItems() + : GetInventoryItems(filterString, invert); return items .GroupBy(GetItemUiCategoryKey) diff --git a/AetherBags/Nodes/InventoryCategoryNode.cs b/AetherBags/Nodes/InventoryCategoryNode.cs index b682e80..389b9ef 100644 --- a/AetherBags/Nodes/InventoryCategoryNode.cs +++ b/AetherBags/Nodes/InventoryCategoryNode.cs @@ -22,7 +22,7 @@ public class InventoryCategoryNode : SimpleComponentNode private const float HeaderHeight = 16; private const float MinWidth = 40; - private float? _fixedWidth = null; + private float? _fixedWidth; public InventoryCategoryNode() { @@ -61,16 +61,24 @@ public class InventoryCategoryNode : SimpleComponentNode } } - public void SetItemsPerLine(int itemsPerLine) + public int ItemsPerLine { - _itemGridNode.ItemsPerLine = itemsPerLine; - RecalculateSize(); + get => _itemGridNode.ItemsPerLine; + set + { + _itemGridNode.ItemsPerLine = value; + RecalculateSize(); + } } - public void SetFixedWidth(float width) + public float? FixedWidth { - _fixedWidth = width; - RecalculateSize(); + get => _fixedWidth; + set + { + _fixedWidth = value; + RecalculateSize(); + } } private void RecalculateSize() @@ -106,10 +114,9 @@ public class InventoryCategoryNode : SimpleComponentNode _categoryNameTextNode.Size = _categoryNameTextNode.Size with { X = calculatedWidth }; } - private bool UpdateItemGrid() + private void UpdateItemGrid() { - var listUpdated = _itemGridNode.SyncWithListData(CategorizedInventory.Items, node => node.ItemInfo, CreateInventoryDragDropNode); - return listUpdated; + _itemGridNode.SyncWithListData(CategorizedInventory.Items, node => node.ItemInfo, CreateInventoryDragDropNode); } private InventoryDragDropNode CreateInventoryDragDropNode(ItemInfo data) diff --git a/AetherBags/Nodes/TextInputWithHintNode.cs b/AetherBags/Nodes/TextInputWithHintNode.cs new file mode 100644 index 0000000..e29434a --- /dev/null +++ b/AetherBags/Nodes/TextInputWithHintNode.cs @@ -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? 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; + } +} \ No newline at end of file diff --git a/AetherBags/Plugin.cs b/AetherBags/Plugin.cs index 5673c41..fd42690 100644 --- a/AetherBags/Plugin.cs +++ b/AetherBags/Plugin.cs @@ -1,7 +1,6 @@ using System.Numerics; using AetherBags.Addons; using AetherBags.Helpers; -using Dalamud.Game.Addon.Lifecycle; using Dalamud.Plugin; using Dalamud.Game.Command; using KamiToolKit; @@ -43,7 +42,6 @@ public class Plugin : IDalamudPlugin if (Services.ClientState.IsLoggedIn) { Services.Framework.RunOnFrameworkThread(OnLogin); } - Services.AddonLifecycle.LogAddon("Inventory"); } public void Dispose()