IPCs, Improved AT support, BISBuddy support
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -6,5 +6,6 @@ public interface IInventoryWindow
|
||||
void Toggle();
|
||||
void Close();
|
||||
void ManualRefresh();
|
||||
void ItemRefresh();
|
||||
void SetSearchText(string searchText);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user