IPCs, Improved AT support, BISBuddy support

This commit is contained in:
Zeffuro
2026-01-02 23:23:49 +01:00
parent a7bfd7ea66
commit aec704dece
26 changed files with 556 additions and 130 deletions
+3 -2
View File
@@ -42,11 +42,12 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
};
_notificationNode.AttachNode(this);
SearchInputNode = new TextInputWithHintNode
SearchInputNode = new TextInputWithButtonNode
{
Position = header.SearchPosition,
Size = header.SearchSize,
OnInputReceived = _ => RefreshCategoriesCore(autosize: false),
OnInputReceived = _ => ItemRefresh(),
OnButtonClicked = () => InventoryAddonContextMenu.OpenMain(this)
};
SearchInputNode.AttachNode(this);
+3 -2
View File
@@ -51,11 +51,12 @@ public unsafe class AddonRetainerWindow : InventoryAddonBase
var header = CalculateHeaderLayout(addon);
SearchInputNode = new TextInputWithHintNode
SearchInputNode = new TextInputWithButtonNode
{
Position = header.SearchPosition,
Size = header.SearchSize,
OnInputReceived = _ => RefreshCategoriesCore(autosize: false),
OnInputReceived = _ => ItemRefresh(),
OnButtonClicked = () => InventoryAddonContextMenu.OpenMain(this)
};
SearchInputNode.AttachNode(this);
+3 -2
View File
@@ -44,11 +44,12 @@ public unsafe class AddonSaddleBagWindow : InventoryAddonBase
var header = CalculateHeaderLayout(addon);
SearchInputNode = new TextInputWithHintNode
SearchInputNode = new TextInputWithButtonNode
{
Position = header.SearchPosition,
Size = header.SearchSize,
OnInputReceived = _ => RefreshCategoriesCore(autosize: false),
OnInputReceived = _ => ItemRefresh(),
OnButtonClicked = () => InventoryAddonContextMenu.OpenMain(this)
};
SearchInputNode.AttachNode(this);
+1
View File
@@ -6,5 +6,6 @@ public interface IInventoryWindow
void Toggle();
void Close();
void ManualRefresh();
void ItemRefresh();
void SetSearchText(string searchText);
}
+71 -10
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using AetherBags.Configuration;
using AetherBags.Helpers;
using AetherBags.Inventory;
using AetherBags.Inventory.Categories;
@@ -14,6 +15,7 @@ using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit;
using KamiToolKit.Classes;
using KamiToolKit.Classes.ContextMenu;
using KamiToolKit.Nodes;
namespace AetherBags.Addons;
@@ -26,11 +28,13 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected DragDropNode BackgroundDropTarget = null!;
protected WrappingGridNode<InventoryCategoryNode> CategoriesNode = null!;
protected TextInputWithHintNode SearchInputNode = null!;
protected TextInputWithButtonNode SearchInputNode = null!;
protected InventoryFooterNode FooterNode = null!;
protected TextNode? SlotCounterNode { get; set; }
protected CircleButtonNode SettingsButtonNode = null!;
internal ContextMenu ContextMenu = null!;
protected virtual float MinWindowWidth => 600;
protected virtual float MaxWindowWidth => 800;
protected virtual float MinWindowHeight => 200;
@@ -54,6 +58,8 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected virtual bool HasPinning => true;
protected virtual bool HasSlotCounter => false;
private readonly HashSet<uint> _searchMatchScratch = new();
public void ManualRefresh()
{
if (!IsOpen) return;
@@ -73,6 +79,9 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
}
}
public string GetSearchText() => SearchInputNode?.SearchString.ExtractText() ?? string.Empty;
public virtual void SetSearchText(string searchText)
{
Services.Framework.RunOnTick(() =>
@@ -105,14 +114,52 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
if (!_isSetupComplete)
return;
var config = System.Config.General;
string searchText = SearchInputNode.SearchString.ExtractText();
bool isSearching = !string.IsNullOrWhiteSpace(searchText);
if (config.SearchMode == SearchMode.Highlight && isSearching)
{
_searchMatchScratch.Clear();
var allData = InventoryState.GetCategories(string.Empty);
for (int i = 0; i < allData.Count; i++)
{
var cat = allData[i];
for (int j = 0; j < cat.Items.Count; j++)
{
var item = cat.Items[j];
if (item.IsRegexMatch(searchText))
{
_searchMatchScratch.Add(item.Item.ItemId);
}
}
}
HighlightState.SetFilter(HighlightSource.Search, _searchMatchScratch);
}
else
{
HighlightState.ClearFilter(HighlightSource.Search);
}
if (SearchInputNode != null)
{
bool atActive = !string.IsNullOrEmpty(HighlightState.SelectedAllaganToolsFilterKey);
bool filterModeActive = System.Config.General.SearchMode == SearchMode.Filter;
SearchInputNode.HintAddColor = (atActive || filterModeActive)
? new Vector3(0.0f, 0.3f, 0.3f)
: Vector3.Zero;
}
if (HasFooter)
{
FooterNode.SlotAmountText = InventoryState.GetEmptySlotsString();
FooterNode.RefreshCurrencies();
}
string filter = SearchInputNode.SearchString.ExtractText();
var categories = InventoryState.GetCategories(filter);
string dataFilter = config.SearchMode == SearchMode.Filter ? searchText : string.Empty;
var categories = InventoryState.GetCategories(dataFilter);
float maxContentWidth = MaxWindowWidth - (ContentStartPosition.X * 2);
int maxItemsPerLine = CalculateOptimalItemsPerLine(maxContentWidth);
@@ -125,6 +172,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
{
node.CategorizedInventory = data;
node.ItemsPerLine = Math.Min(data.Items.Count, maxItemsPerLine);
node.RefreshNodeVisuals();
},
createNodeMethod: _ => CreateCategoryNode());
@@ -155,24 +203,27 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected HeaderLayout CalculateHeaderLayout(AtkUnitBase* addon)
{
var size = new Vector2(addon->Size.X / 2.0f, 28.0f);
var header = addon->WindowHeaderCollisionNode;
float headerW = header->Width;
float headerH = header->Height;
float x = header->X + (headerW - size.X) * 0.5f;
float y = header->Y + (headerH - size.Y) * 0.5f;
// Center the search bar, width is 50% of header
float searchWidth = headerW * 0.5f;
var searchSize = new Vector2(searchWidth, 28f);
float searchX = (headerW - searchWidth) * 0.5f;
float itemY = header->Y + (headerH - 28f) * 0.5f;
return new HeaderLayout
{
SearchPosition = new Vector2(x, y),
SearchSize = size,
SearchPosition = new Vector2(searchX, itemY),
SearchSize = searchSize,
HeaderWidth = headerW,
HeaderY = y,
HeaderY = itemY
};
}
protected void InitializeBackgroundDropTarget()
{
BackgroundDropTarget = new DragDropNode
@@ -344,6 +395,8 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected void ResizeWindow(float width, float height)
=> ResizeWindow(width, height, recalcLayout: true);
public void ItemRefresh() => RefreshCategoriesCore(false);
protected override void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
{
base.OnRequestedUpdate(addon, numberArrayData, stringArrayData);
@@ -352,6 +405,13 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
RefreshCategoriesCore(autosize: true);
}
protected override void OnSetup(AtkUnitBase* addon)
{
ContextMenu = new ContextMenu();
base.OnSetup(addon);
}
protected override void OnUpdate(AtkUnitBase* addon)
{
if (RefreshQueued)
@@ -368,6 +428,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected override void OnFinalize(AtkUnitBase* addon)
{
ContextMenu?.Dispose();
HoverSubscribed.Clear();
RefreshQueued = false;
RefreshAutosizeQueued = false;
@@ -0,0 +1,74 @@
using System;
using AetherBags. Configuration;
using AetherBags. Inventory;
using AetherBags.Inventory.Context;
using KamiToolKit.Classes. ContextMenu;
namespace AetherBags.Addons;
public static class InventoryAddonContextMenu
{
private static ContextMenuItem Separator => new()
{
Name = "---------------------------",
IsEnabled = false,
OnClick = () => { }
};
public static void OpenMain(InventoryAddonBase parent)
{
if (parent?.ContextMenu == null || System.Config == null) return;
var menu = parent.ContextMenu;
menu.Clear();
bool hasActiveAtFilter = !string.IsNullOrEmpty(HighlightState.SelectedAllaganToolsFilterKey);
string searchText = parent.GetSearchText();
if (HighlightState. IsFilterActive || hasActiveAtFilter || ! string.IsNullOrEmpty(searchText))
{
menu.AddItem("Clear All Filters", () =>
{
HighlightState.ClearAll();
parent. SetSearchText(string.Empty);
InventoryOrchestrator.RefreshAll(updateMaps: false);
});
menu.AddItem(Separator);
}
var currentMode = System. Config.General.SearchMode;
string modeLabel = currentMode == SearchMode. Filter ? "Mode: Hide Non-Matches" : "Mode: Fade Non-Matches";
menu.AddItem(modeLabel, () =>
{
System.Config.General.SearchMode = currentMode == SearchMode. Filter ? SearchMode. Highlight : SearchMode.Filter;
parent.ManualRefresh();
});
if (System.IPC.AllaganTools is { IsReady: true } && System. Config.Categories.AllaganToolsCategoriesEnabled)
{
var atFilters = System.IPC.AllaganTools.GetSearchFilters();
if (atFilters is { Count: > 0 })
{
var subMenu = new ContextMenuSubItem
{
Name = "Allagan Tools Filters...",
OnClick = () => { }
};
foreach (var (key, name) in atFilters)
{
var capturedKey = key;
bool isActive = HighlightState.SelectedAllaganToolsFilterKey == key;
subMenu.AddItem(isActive ? $"✓ {name}" : $" {name}", () =>
{
HighlightState.SelectedAllaganToolsFilterKey = isActive ? string.Empty : capturedKey;
InventoryOrchestrator.RefreshAll(updateMaps: false);
});
}
menu.AddItem(subMenu);
}
}
menu.Open();
}
}