IPCs, Improved AT support, BISBuddy support
This commit is contained in:
@@ -42,11 +42,12 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
|
|||||||
};
|
};
|
||||||
_notificationNode.AttachNode(this);
|
_notificationNode.AttachNode(this);
|
||||||
|
|
||||||
SearchInputNode = new TextInputWithHintNode
|
SearchInputNode = new TextInputWithButtonNode
|
||||||
{
|
{
|
||||||
Position = header.SearchPosition,
|
Position = header.SearchPosition,
|
||||||
Size = header.SearchSize,
|
Size = header.SearchSize,
|
||||||
OnInputReceived = _ => RefreshCategoriesCore(autosize: false),
|
OnInputReceived = _ => ItemRefresh(),
|
||||||
|
OnButtonClicked = () => InventoryAddonContextMenu.OpenMain(this)
|
||||||
};
|
};
|
||||||
SearchInputNode.AttachNode(this);
|
SearchInputNode.AttachNode(this);
|
||||||
|
|
||||||
|
|||||||
@@ -51,11 +51,12 @@ public unsafe class AddonRetainerWindow : InventoryAddonBase
|
|||||||
|
|
||||||
var header = CalculateHeaderLayout(addon);
|
var header = CalculateHeaderLayout(addon);
|
||||||
|
|
||||||
SearchInputNode = new TextInputWithHintNode
|
SearchInputNode = new TextInputWithButtonNode
|
||||||
{
|
{
|
||||||
Position = header.SearchPosition,
|
Position = header.SearchPosition,
|
||||||
Size = header.SearchSize,
|
Size = header.SearchSize,
|
||||||
OnInputReceived = _ => RefreshCategoriesCore(autosize: false),
|
OnInputReceived = _ => ItemRefresh(),
|
||||||
|
OnButtonClicked = () => InventoryAddonContextMenu.OpenMain(this)
|
||||||
};
|
};
|
||||||
SearchInputNode.AttachNode(this);
|
SearchInputNode.AttachNode(this);
|
||||||
|
|
||||||
|
|||||||
@@ -44,11 +44,12 @@ public unsafe class AddonSaddleBagWindow : InventoryAddonBase
|
|||||||
|
|
||||||
var header = CalculateHeaderLayout(addon);
|
var header = CalculateHeaderLayout(addon);
|
||||||
|
|
||||||
SearchInputNode = new TextInputWithHintNode
|
SearchInputNode = new TextInputWithButtonNode
|
||||||
{
|
{
|
||||||
Position = header.SearchPosition,
|
Position = header.SearchPosition,
|
||||||
Size = header.SearchSize,
|
Size = header.SearchSize,
|
||||||
OnInputReceived = _ => RefreshCategoriesCore(autosize: false),
|
OnInputReceived = _ => ItemRefresh(),
|
||||||
|
OnButtonClicked = () => InventoryAddonContextMenu.OpenMain(this)
|
||||||
};
|
};
|
||||||
SearchInputNode.AttachNode(this);
|
SearchInputNode.AttachNode(this);
|
||||||
|
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ public interface IInventoryWindow
|
|||||||
void Toggle();
|
void Toggle();
|
||||||
void Close();
|
void Close();
|
||||||
void ManualRefresh();
|
void ManualRefresh();
|
||||||
|
void ItemRefresh();
|
||||||
void SetSearchText(string searchText);
|
void SetSearchText(string searchText);
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using AetherBags.Configuration;
|
||||||
using AetherBags.Helpers;
|
using AetherBags.Helpers;
|
||||||
using AetherBags.Inventory;
|
using AetherBags.Inventory;
|
||||||
using AetherBags.Inventory.Categories;
|
using AetherBags.Inventory.Categories;
|
||||||
@@ -14,6 +15,7 @@ using FFXIVClientStructs.FFXIV.Client.Game;
|
|||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using KamiToolKit;
|
using KamiToolKit;
|
||||||
using KamiToolKit.Classes;
|
using KamiToolKit.Classes;
|
||||||
|
using KamiToolKit.Classes.ContextMenu;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
namespace AetherBags.Addons;
|
namespace AetherBags.Addons;
|
||||||
@@ -26,11 +28,13 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
|
|||||||
|
|
||||||
protected DragDropNode BackgroundDropTarget = null!;
|
protected DragDropNode BackgroundDropTarget = null!;
|
||||||
protected WrappingGridNode<InventoryCategoryNode> CategoriesNode = null!;
|
protected WrappingGridNode<InventoryCategoryNode> CategoriesNode = null!;
|
||||||
protected TextInputWithHintNode SearchInputNode = null!;
|
protected TextInputWithButtonNode SearchInputNode = null!;
|
||||||
protected InventoryFooterNode FooterNode = null!;
|
protected InventoryFooterNode FooterNode = null!;
|
||||||
protected TextNode? SlotCounterNode { get; set; }
|
protected TextNode? SlotCounterNode { get; set; }
|
||||||
protected CircleButtonNode SettingsButtonNode = null!;
|
protected CircleButtonNode SettingsButtonNode = null!;
|
||||||
|
|
||||||
|
internal ContextMenu ContextMenu = null!;
|
||||||
|
|
||||||
protected virtual float MinWindowWidth => 600;
|
protected virtual float MinWindowWidth => 600;
|
||||||
protected virtual float MaxWindowWidth => 800;
|
protected virtual float MaxWindowWidth => 800;
|
||||||
protected virtual float MinWindowHeight => 200;
|
protected virtual float MinWindowHeight => 200;
|
||||||
@@ -54,6 +58,8 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
|
|||||||
protected virtual bool HasPinning => true;
|
protected virtual bool HasPinning => true;
|
||||||
protected virtual bool HasSlotCounter => false;
|
protected virtual bool HasSlotCounter => false;
|
||||||
|
|
||||||
|
private readonly HashSet<uint> _searchMatchScratch = new();
|
||||||
|
|
||||||
public void ManualRefresh()
|
public void ManualRefresh()
|
||||||
{
|
{
|
||||||
if (!IsOpen) return;
|
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)
|
public virtual void SetSearchText(string searchText)
|
||||||
{
|
{
|
||||||
Services.Framework.RunOnTick(() =>
|
Services.Framework.RunOnTick(() =>
|
||||||
@@ -105,14 +114,52 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
|
|||||||
if (!_isSetupComplete)
|
if (!_isSetupComplete)
|
||||||
return;
|
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)
|
if (HasFooter)
|
||||||
{
|
{
|
||||||
FooterNode.SlotAmountText = InventoryState.GetEmptySlotsString();
|
FooterNode.SlotAmountText = InventoryState.GetEmptySlotsString();
|
||||||
FooterNode.RefreshCurrencies();
|
FooterNode.RefreshCurrencies();
|
||||||
}
|
}
|
||||||
|
|
||||||
string filter = SearchInputNode.SearchString.ExtractText();
|
string dataFilter = config.SearchMode == SearchMode.Filter ? searchText : string.Empty;
|
||||||
var categories = InventoryState.GetCategories(filter);
|
var categories = InventoryState.GetCategories(dataFilter);
|
||||||
|
|
||||||
float maxContentWidth = MaxWindowWidth - (ContentStartPosition.X * 2);
|
float maxContentWidth = MaxWindowWidth - (ContentStartPosition.X * 2);
|
||||||
int maxItemsPerLine = CalculateOptimalItemsPerLine(maxContentWidth);
|
int maxItemsPerLine = CalculateOptimalItemsPerLine(maxContentWidth);
|
||||||
@@ -125,6 +172,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
|
|||||||
{
|
{
|
||||||
node.CategorizedInventory = data;
|
node.CategorizedInventory = data;
|
||||||
node.ItemsPerLine = Math.Min(data.Items.Count, maxItemsPerLine);
|
node.ItemsPerLine = Math.Min(data.Items.Count, maxItemsPerLine);
|
||||||
|
node.RefreshNodeVisuals();
|
||||||
},
|
},
|
||||||
createNodeMethod: _ => CreateCategoryNode());
|
createNodeMethod: _ => CreateCategoryNode());
|
||||||
|
|
||||||
@@ -155,24 +203,27 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
|
|||||||
|
|
||||||
protected HeaderLayout CalculateHeaderLayout(AtkUnitBase* addon)
|
protected HeaderLayout CalculateHeaderLayout(AtkUnitBase* addon)
|
||||||
{
|
{
|
||||||
var size = new Vector2(addon->Size.X / 2.0f, 28.0f);
|
|
||||||
var header = addon->WindowHeaderCollisionNode;
|
var header = addon->WindowHeaderCollisionNode;
|
||||||
|
|
||||||
float headerW = header->Width;
|
float headerW = header->Width;
|
||||||
float headerH = header->Height;
|
float headerH = header->Height;
|
||||||
|
|
||||||
float x = header->X + (headerW - size.X) * 0.5f;
|
// Center the search bar, width is 50% of header
|
||||||
float y = header->Y + (headerH - size.Y) * 0.5f;
|
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
|
return new HeaderLayout
|
||||||
{
|
{
|
||||||
SearchPosition = new Vector2(x, y),
|
SearchPosition = new Vector2(searchX, itemY),
|
||||||
SearchSize = size,
|
SearchSize = searchSize,
|
||||||
HeaderWidth = headerW,
|
HeaderWidth = headerW,
|
||||||
HeaderY = y,
|
HeaderY = itemY
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void InitializeBackgroundDropTarget()
|
protected void InitializeBackgroundDropTarget()
|
||||||
{
|
{
|
||||||
BackgroundDropTarget = new DragDropNode
|
BackgroundDropTarget = new DragDropNode
|
||||||
@@ -344,6 +395,8 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
|
|||||||
protected void ResizeWindow(float width, float height)
|
protected void ResizeWindow(float width, float height)
|
||||||
=> ResizeWindow(width, height, recalcLayout: true);
|
=> ResizeWindow(width, height, recalcLayout: true);
|
||||||
|
|
||||||
|
public void ItemRefresh() => RefreshCategoriesCore(false);
|
||||||
|
|
||||||
protected override void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
protected override void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||||
{
|
{
|
||||||
base.OnRequestedUpdate(addon, numberArrayData, stringArrayData);
|
base.OnRequestedUpdate(addon, numberArrayData, stringArrayData);
|
||||||
@@ -352,6 +405,13 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
|
|||||||
RefreshCategoriesCore(autosize: true);
|
RefreshCategoriesCore(autosize: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnSetup(AtkUnitBase* addon)
|
||||||
|
{
|
||||||
|
ContextMenu = new ContextMenu();
|
||||||
|
|
||||||
|
base.OnSetup(addon);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnUpdate(AtkUnitBase* addon)
|
protected override void OnUpdate(AtkUnitBase* addon)
|
||||||
{
|
{
|
||||||
if (RefreshQueued)
|
if (RefreshQueued)
|
||||||
@@ -368,6 +428,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
|
|||||||
|
|
||||||
protected override void OnFinalize(AtkUnitBase* addon)
|
protected override void OnFinalize(AtkUnitBase* addon)
|
||||||
{
|
{
|
||||||
|
ContextMenu?.Dispose();
|
||||||
HoverSubscribed.Clear();
|
HoverSubscribed.Clear();
|
||||||
RefreshQueued = false;
|
RefreshQueued = false;
|
||||||
RefreshAutosizeQueued = 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,9 @@ public class CategorySettings
|
|||||||
public bool CategoriesEnabled { get; set; } = true;
|
public bool CategoriesEnabled { get; set; } = true;
|
||||||
public bool GameCategoriesEnabled { get; set; } = true;
|
public bool GameCategoriesEnabled { get; set; } = true;
|
||||||
public bool UserCategoriesEnabled { get; set; } = true;
|
public bool UserCategoriesEnabled { get; set; } = true;
|
||||||
|
public bool BisBuddyEnabled { get; set; } = true;
|
||||||
public bool AllaganToolsCategoriesEnabled { get; set; } = false;
|
public bool AllaganToolsCategoriesEnabled { get; set; } = false;
|
||||||
|
public AllaganToolsFilterMode AllaganToolsMode { get; set; } = AllaganToolsFilterMode.Highlight;
|
||||||
|
|
||||||
public List<UserCategoryDefinition> UserCategories { get; set; } = new();
|
public List<UserCategoryDefinition> UserCategories { get; set; } = new();
|
||||||
}
|
}
|
||||||
@@ -78,3 +80,9 @@ public enum ToggleFilterState
|
|||||||
Allow = 1,
|
Allow = 1,
|
||||||
Disallow = 2,
|
Disallow = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum AllaganToolsFilterMode
|
||||||
|
{
|
||||||
|
Categorize = 0,
|
||||||
|
Highlight = 1,
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ namespace AetherBags.Configuration;
|
|||||||
public class GeneralSettings
|
public class GeneralSettings
|
||||||
{
|
{
|
||||||
public InventoryStackMode StackMode { get; set; } = InventoryStackMode.AggregateByItemId;
|
public InventoryStackMode StackMode { get; set; } = InventoryStackMode.AggregateByItemId;
|
||||||
|
public SearchMode SearchMode { get; set; } = SearchMode.Highlight;
|
||||||
public bool DebugEnabled { get; set; } = false;
|
public bool DebugEnabled { get; set; } = false;
|
||||||
public bool CompactPackingEnabled { get; set; } = true;
|
public bool CompactPackingEnabled { get; set; } = true;
|
||||||
public int CompactLookahead { get; set; } = 24;
|
public int CompactLookahead { get; set; } = 24;
|
||||||
@@ -23,3 +24,9 @@ public enum InventoryStackMode : byte
|
|||||||
NaturalStacks = 0,
|
NaturalStacks = 0,
|
||||||
AggregateByItemId = 1,
|
AggregateByItemId = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum SearchMode : byte
|
||||||
|
{
|
||||||
|
Filter = 0,
|
||||||
|
Highlight = 1,
|
||||||
|
}
|
||||||
@@ -2,13 +2,16 @@ namespace AetherBags.Extensions;
|
|||||||
|
|
||||||
public static class LoggerExtensions
|
public static class LoggerExtensions
|
||||||
{
|
{
|
||||||
public static void DebugOnly(this object logger, string message)
|
extension(object logger)
|
||||||
{
|
{
|
||||||
if(System.Config.General.DebugEnabled) Services.Logger.DebugOnly(message);
|
public void DebugOnly(string message)
|
||||||
}
|
{
|
||||||
|
if (System.Config?.General?.DebugEnabled == true)
|
||||||
|
{
|
||||||
|
Services.Logger.DebugOnly(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void DebugOnly(this object logger, string message, params object[] args)
|
public void DebugOnly(string message, params object[] args) => DebugOnly(logger, string.Format(message, args));
|
||||||
{
|
|
||||||
if(System.Config.General.DebugEnabled) Services.Logger.DebugOnly(message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using AetherBags.Inventory;
|
||||||
|
using AetherBags.Inventory.Context;
|
||||||
using Dalamud.Plugin.Ipc;
|
using Dalamud.Plugin.Ipc;
|
||||||
|
|
||||||
namespace AetherBags.IPC;
|
namespace AetherBags.IPC;
|
||||||
@@ -46,7 +48,6 @@ public class AllaganToolsIPC : IDisposable
|
|||||||
|
|
||||||
_initialized.Subscribe(OnAllaganInitialized);
|
_initialized.Subscribe(OnAllaganInitialized);
|
||||||
|
|
||||||
// Check if already initialized
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IsReady = _isInitialized.InvokeFunc();
|
IsReady = _isInitialized.InvokeFunc();
|
||||||
@@ -181,6 +182,12 @@ public class AllaganToolsIPC : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SelectFilter(string filterKey)
|
||||||
|
{
|
||||||
|
HighlightState.SelectedAllaganToolsFilterKey = filterKey;
|
||||||
|
InventoryOrchestrator.RefreshHighlights();
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_initialized?.Unsubscribe(OnAllaganInitialized);
|
_initialized?.Unsubscribe(OnAllaganInitialized);
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using AetherBags.Inventory;
|
||||||
|
using AetherBags.Inventory.Context;
|
||||||
|
using Dalamud.Plugin.Ipc;
|
||||||
|
|
||||||
|
namespace AetherBags.IPC;
|
||||||
|
|
||||||
|
public class BisBuddyIPC : IDisposable
|
||||||
|
{
|
||||||
|
private ICallGateSubscriber<bool>? _isInitialized;
|
||||||
|
private ICallGateSubscriber<bool, bool>? _initialized;
|
||||||
|
private ICallGateSubscriber<List<uint>>? _getBisItems;
|
||||||
|
private ICallGateSubscriber<List<uint>, bool>? _bisItemsChanged;
|
||||||
|
|
||||||
|
public bool IsReady { get; private set; }
|
||||||
|
private static readonly Vector3 BisColor = new(0.0f, 0.3f, 0.0f);
|
||||||
|
|
||||||
|
public BisBuddyIPC()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_isInitialized = Services.PluginInterface.GetIpcSubscriber<bool>("BisBuddy.IsInitialized");
|
||||||
|
_initialized = Services.PluginInterface.GetIpcSubscriber<bool, bool>("BisBuddy.Initialized");
|
||||||
|
_getBisItems = Services.PluginInterface.GetIpcSubscriber<List<uint>>("BisBuddy.GetBisItems");
|
||||||
|
_bisItemsChanged = Services.PluginInterface.GetIpcSubscriber<List<uint>, bool>("BisBuddy.BisItemsChanged");
|
||||||
|
|
||||||
|
_initialized.Subscribe(OnInitialized);
|
||||||
|
_bisItemsChanged.Subscribe(UpdateHighlights);
|
||||||
|
|
||||||
|
try { IsReady = _isInitialized.InvokeFunc(); } catch { IsReady = false; }
|
||||||
|
|
||||||
|
if (IsReady) RequestUpdate();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Services.Logger.DebugOnly($"BisBuddy not available: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInitialized(bool ready)
|
||||||
|
{
|
||||||
|
IsReady = ready;
|
||||||
|
if (ready) RequestUpdate();
|
||||||
|
else HighlightState.ClearLabel(HighlightSource.BiSBuddy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestUpdate()
|
||||||
|
{
|
||||||
|
if (!IsReady) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var items = _getBisItems?.InvokeFunc();
|
||||||
|
if (items != null) UpdateHighlights(items);
|
||||||
|
}
|
||||||
|
catch { IsReady = false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateHighlights(List<uint>? itemIds)
|
||||||
|
{
|
||||||
|
if (!System.Config.Categories.BisBuddyEnabled || itemIds == null || itemIds.Count == 0)
|
||||||
|
{
|
||||||
|
HighlightState.ClearLabel(HighlightSource.BiSBuddy);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HighlightState.SetLabel(HighlightSource.BiSBuddy, itemIds, BisColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
InventoryOrchestrator.RefreshHighlights();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_initialized?.Unsubscribe(OnInitialized);
|
||||||
|
_bisItemsChanged?.Unsubscribe(UpdateHighlights);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,18 +6,13 @@ namespace AetherBags.IPC;
|
|||||||
|
|
||||||
public class IPCService : IDisposable
|
public class IPCService : IDisposable
|
||||||
{
|
{
|
||||||
public AllaganToolsIPC AllaganTools { get; }
|
public AllaganToolsIPC AllaganTools { get; } = new();
|
||||||
public WotsItIPC WotsIt { get; }
|
public WotsItIPC WotsIt { get; } = new();
|
||||||
// Future: public BiSBuddyIPC BiSBuddy { get; }
|
public BisBuddyIPC BisBuddy { get; } = new();
|
||||||
|
|
||||||
public IPCService()
|
|
||||||
{
|
|
||||||
AllaganTools = new AllaganToolsIPC();
|
|
||||||
WotsIt = new WotsItIPC();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
AllaganTools.Dispose();
|
AllaganTools.Dispose();
|
||||||
|
WotsIt.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,7 +165,7 @@ public static class CategoryBucketManager
|
|||||||
bool allaganCategoriesEnabled)
|
bool allaganCategoriesEnabled)
|
||||||
{
|
{
|
||||||
if (!allaganCategoriesEnabled) return;
|
if (!allaganCategoriesEnabled) return;
|
||||||
if (! System.IPC.AllaganTools.IsReady) return;
|
if (!System.IPC.AllaganTools.IsReady) return;
|
||||||
|
|
||||||
var filters = System.IPC.AllaganTools.CachedSearchFilters;
|
var filters = System.IPC.AllaganTools.CachedSearchFilters;
|
||||||
var filterItems = System.IPC.AllaganTools.CachedFilterItems;
|
var filterItems = System.IPC.AllaganTools.CachedFilterItems;
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace AetherBags.Inventory.Context;
|
||||||
|
|
||||||
|
public enum HighlightSource
|
||||||
|
{
|
||||||
|
Search,
|
||||||
|
AllaganTools,
|
||||||
|
BiSBuddy,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HighlightState
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<HighlightSource, HashSet<uint>> Filters = new();
|
||||||
|
private static readonly Dictionary<HighlightSource, (HashSet<uint> ids, Vector3 color)> Labels = new();
|
||||||
|
|
||||||
|
public static string? SelectedAllaganToolsFilterKey { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public static bool IsFilterActive => Filters.Count > 0;
|
||||||
|
|
||||||
|
public static void SetFilter(HighlightSource source, IEnumerable<uint> ids)
|
||||||
|
=> Filters[source] = new HashSet<uint>(ids);
|
||||||
|
|
||||||
|
public static bool IsInActiveFilters(uint itemId)
|
||||||
|
{
|
||||||
|
if (Filters.Count == 0) return true;
|
||||||
|
foreach (var filter in Filters.Values)
|
||||||
|
if (filter.Contains(itemId)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector3? GetLabelColor(uint itemId)
|
||||||
|
{
|
||||||
|
foreach (var label in Labels.Values)
|
||||||
|
if (label.ids.Contains(itemId)) return label.color;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetLabel(HighlightSource source, IEnumerable<uint> ids, Vector3 color)
|
||||||
|
=> Labels[source] = (new HashSet<uint>(ids), color);
|
||||||
|
|
||||||
|
|
||||||
|
public static void ClearAll()
|
||||||
|
{
|
||||||
|
Filters.Clear();
|
||||||
|
Labels.Clear();
|
||||||
|
SelectedAllaganToolsFilterKey = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ClearFilter(HighlightSource source) => Filters.Remove(source);
|
||||||
|
public static void ClearLabel(HighlightSource source) => Labels.Remove(source);
|
||||||
|
}
|
||||||
@@ -42,6 +42,17 @@ public static unsafe class InventoryOrchestrator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void RefreshHighlights()
|
||||||
|
{
|
||||||
|
Services.Framework.RunOnTick(() =>
|
||||||
|
{
|
||||||
|
foreach (var window in GetAllWindows())
|
||||||
|
{
|
||||||
|
window.ItemRefresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static IEnumerable<IInventoryWindow> GetAllWindows()
|
private static IEnumerable<IInventoryWindow> GetAllWindows()
|
||||||
{
|
{
|
||||||
yield return System.AddonInventoryWindow;
|
yield return System.AddonInventoryWindow;
|
||||||
|
|||||||
@@ -82,38 +82,55 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
uint contextId = InventoryContextState.ActiveContextId;
|
if (IsSlotBlocked) return false;
|
||||||
if (contextId == 0) return true;
|
if (!CheckNativeContextEligibility()) return false;
|
||||||
|
if (!HighlightState.IsInActiveFilters(Item.ItemId)) return false;
|
||||||
bool isRetainerContext = contextId == 4;
|
|
||||||
bool isSaddlebagContext = contextId == 29;
|
|
||||||
bool isMainContext = !isRetainerContext && isSaddlebagContext == false;
|
|
||||||
|
|
||||||
if (IsMainInventory)
|
|
||||||
{
|
|
||||||
if (!isMainContext) return true;
|
|
||||||
|
|
||||||
return InventoryContextState.IsEligible(InventoryPage, Item.Slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Item.Container.IsRetainer)
|
|
||||||
{
|
|
||||||
// ...but the context isn't for Retainers, don't dim it.
|
|
||||||
if (!isRetainerContext)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. If we are looking at a Saddlebag item...
|
|
||||||
if (Item.Container.IsSaddleBag)
|
|
||||||
{
|
|
||||||
if (!isSaddlebagContext)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float VisualAlpha => IsEligibleForContext ? 1.0f : 0.4f;
|
||||||
|
|
||||||
|
public Vector3 HighlightOverlayColor
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!System.Config.Categories.BisBuddyEnabled)
|
||||||
|
return Vector3.Zero;
|
||||||
|
|
||||||
|
return HighlightState.GetLabelColor(Item.ItemId) ?? Vector3.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckNativeContextEligibility()
|
||||||
|
{
|
||||||
|
uint contextId = InventoryContextState.ActiveContextId;
|
||||||
|
if (contextId == 0) return true;
|
||||||
|
|
||||||
|
bool isRetainerContext = contextId == 4;
|
||||||
|
bool isSaddlebagContext = contextId == 29;
|
||||||
|
bool isMainContext = !isRetainerContext && isSaddlebagContext == false;
|
||||||
|
|
||||||
|
if (IsMainInventory)
|
||||||
|
{
|
||||||
|
if (!isMainContext) return true;
|
||||||
|
return InventoryContextState.IsEligible(InventoryPage, Item.Slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Item.Container.IsRetainer)
|
||||||
|
{
|
||||||
|
if (!isRetainerContext) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Item.Container.IsSaddleBag)
|
||||||
|
{
|
||||||
|
if (!isSaddlebagContext) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsMainInventory => InventoryPage >= 0;
|
public bool IsMainInventory => InventoryPage >= 0;
|
||||||
|
|
||||||
public bool IsRegexMatch(string searchTerms)
|
public bool IsRegexMatch(string searchTerms)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AetherBags.Configuration;
|
using AetherBags.Configuration;
|
||||||
using AetherBags.Inventory.Categories;
|
using AetherBags.Inventory.Categories;
|
||||||
|
using AetherBags.Inventory.Context;
|
||||||
using AetherBags.Inventory.Items;
|
using AetherBags.Inventory.Items;
|
||||||
using AetherBags.Inventory.Scanning;
|
using AetherBags.Inventory.Scanning;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
@@ -76,8 +77,19 @@ public abstract class InventoryStateBase
|
|||||||
|
|
||||||
if (allaganCategoriesEnabled)
|
if (allaganCategoriesEnabled)
|
||||||
{
|
{
|
||||||
CategoryBucketManager.BucketByAllaganFilters(
|
if (config.Categories.AllaganToolsMode == AllaganToolsFilterMode.Categorize)
|
||||||
ItemInfoByKey, BucketsByKey, ClaimedKeys, allaganCategoriesEnabled);
|
{
|
||||||
|
CategoryBucketManager.BucketByAllaganFilters(ItemInfoByKey, BucketsByKey, ClaimedKeys, true);
|
||||||
|
HighlightState.ClearFilter(HighlightSource.AllaganTools);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UpdateAllaganHighlight(HighlightState.SelectedAllaganToolsFilterKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HighlightState.ClearFilter(HighlightSource.AllaganTools);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameCategoriesEnabled)
|
if (gameCategoriesEnabled)
|
||||||
@@ -92,6 +104,25 @@ public abstract class InventoryStateBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateAllaganHighlight(string? filterKey)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filterKey) || !System.IPC.AllaganTools.IsReady)
|
||||||
|
{
|
||||||
|
HighlightState.ClearFilter(HighlightSource.AllaganTools);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var filterItems = System.IPC.AllaganTools.GetFilterItems(filterKey);
|
||||||
|
if (filterItems != null)
|
||||||
|
{
|
||||||
|
HighlightState.SetFilter(HighlightSource.AllaganTools, filterItems.Keys);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HighlightState.ClearFilter(HighlightSource.AllaganTools);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public IReadOnlyList<CategorizedInventory> GetCategories(string filter = "", bool invert = false)
|
public IReadOnlyList<CategorizedInventory> GetCategories(string filter = "", bool invert = false)
|
||||||
=> InventoryFilter.FilterCategories(AllCategories, BucketsByKey, FilteredCategories, filter, invert);
|
=> InventoryFilter.FilterCategories(AllCategories, BucketsByKey, FilteredCategories, filter, invert);
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class CategoryConfigurationNode : ConfigNode<CategoryWrapper>
|
|||||||
{
|
{
|
||||||
_categoryList = new ScrollingListNode
|
_categoryList = new ScrollingListNode
|
||||||
{
|
{
|
||||||
AutoHideScrollbar = true,
|
AutoHideScrollBar = true,
|
||||||
};
|
};
|
||||||
_categoryList.FitContents = true;
|
_categoryList.FitContents = true;
|
||||||
_categoryList.AttachNode(this);
|
_categoryList.AttachNode(this);
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using AetherBags.Configuration;
|
using AetherBags.Configuration;
|
||||||
using AetherBags.Inventory;
|
using AetherBags.Inventory;
|
||||||
|
using AetherBags.Inventory.Context;
|
||||||
using AetherBags.Nodes.Color;
|
using AetherBags.Nodes.Color;
|
||||||
|
using AetherBags.Nodes.Input;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using KamiToolKit.Classes;
|
using KamiToolKit.Classes;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
@@ -71,7 +76,47 @@ public sealed class CategoryGeneralConfigurationNode : TabbedVerticalListNode
|
|||||||
};
|
};
|
||||||
AddNode(userCategoriesEnabled);
|
AddNode(userCategoriesEnabled);
|
||||||
|
|
||||||
|
bool bisBuddyReady = System.IPC.BisBuddy?.IsReady ?? false;
|
||||||
|
|
||||||
|
CheckboxNode bisBuddyEnabled = new CheckboxNode
|
||||||
|
{
|
||||||
|
Size = Size with { Y = 18 },
|
||||||
|
IsVisible = true,
|
||||||
|
String = bisBuddyReady ? "BISBuddy" : "BISBuddy (Not Available)",
|
||||||
|
IsChecked = config.BisBuddyEnabled,
|
||||||
|
TextTooltip = "Allow BISBuddy to highlight items.",
|
||||||
|
OnClick = isChecked =>
|
||||||
|
{
|
||||||
|
config.BisBuddyEnabled = isChecked;
|
||||||
|
System.IPC.BisBuddy?.RequestUpdate();
|
||||||
|
RefreshInventory();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AddNode(bisBuddyEnabled);
|
||||||
|
|
||||||
bool allaganReady = System.IPC.AllaganTools?.IsReady ?? false;
|
bool allaganReady = System.IPC.AllaganTools?.IsReady ?? false;
|
||||||
|
|
||||||
|
LabeledDropdownNode? atModeDropdown = new LabeledDropdownNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(300, 20),
|
||||||
|
LabelText = "Filter Display Mode",
|
||||||
|
LabelTextFlags = TextFlags.AutoAdjustNodeSize,
|
||||||
|
IsEnabled = config.AllaganToolsCategoriesEnabled && allaganReady,
|
||||||
|
Options = Enum.GetNames(typeof(AllaganToolsFilterMode)).ToList(),
|
||||||
|
SelectedOption = config.AllaganToolsMode.ToString(),
|
||||||
|
OnOptionSelected = selected =>
|
||||||
|
{
|
||||||
|
if (Enum.TryParse<AllaganToolsFilterMode>(selected, out var parsed))
|
||||||
|
{
|
||||||
|
config.AllaganToolsMode = parsed;
|
||||||
|
if (parsed == AllaganToolsFilterMode.Categorize)
|
||||||
|
HighlightState.ClearFilter(HighlightSource.AllaganTools);
|
||||||
|
|
||||||
|
RefreshInventory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
_allaganToolsCheckbox = new CheckboxNode
|
_allaganToolsCheckbox = new CheckboxNode
|
||||||
{
|
{
|
||||||
Size = Size with { Y = 18 },
|
Size = Size with { Y = 18 },
|
||||||
@@ -85,14 +130,16 @@ public sealed class CategoryGeneralConfigurationNode : TabbedVerticalListNode
|
|||||||
OnClick = isChecked =>
|
OnClick = isChecked =>
|
||||||
{
|
{
|
||||||
config.AllaganToolsCategoriesEnabled = isChecked;
|
config.AllaganToolsCategoriesEnabled = isChecked;
|
||||||
if (isChecked)
|
if (atModeDropdown != null) atModeDropdown.IsEnabled = isChecked;
|
||||||
{
|
if (isChecked) System.IPC?.AllaganTools?.RefreshFilters();
|
||||||
System.IPC?.AllaganTools?.RefreshFilters();
|
|
||||||
}
|
|
||||||
RefreshInventory();
|
RefreshInventory();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
AddNode(_allaganToolsCheckbox);
|
AddNode(_allaganToolsCheckbox);
|
||||||
|
|
||||||
|
AddTab(1);
|
||||||
|
AddNode(atModeDropdown);
|
||||||
|
SubtractTab(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshInventory() => InventoryOrchestrator.RefreshAll(updateMaps: true);
|
private void RefreshInventory() => InventoryOrchestrator.RefreshAll(updateMaps: true);
|
||||||
|
|||||||
@@ -132,6 +132,24 @@ internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode
|
|||||||
};
|
};
|
||||||
AddNode(linkItemCheckBox);
|
AddNode(linkItemCheckBox);
|
||||||
|
|
||||||
|
var searchModeDropDown = new LabeledDropdownNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(300, 20),
|
||||||
|
LabelText = "Search Mode",
|
||||||
|
LabelTextFlags = TextFlags.AutoAdjustNodeSize,
|
||||||
|
Options = Enum.GetNames(typeof(SearchMode)).ToList(),
|
||||||
|
SelectedOption = config.SearchMode.ToString(),
|
||||||
|
OnOptionSelected = selected =>
|
||||||
|
{
|
||||||
|
if (Enum.TryParse<SearchMode>(selected, out var parsed))
|
||||||
|
{
|
||||||
|
config.SearchMode = parsed;
|
||||||
|
InventoryOrchestrator.RefreshAll(updateMaps: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AddNode(searchModeDropDown);
|
||||||
|
|
||||||
_stackDropDown = new LabeledDropdownNode
|
_stackDropDown = new LabeledDropdownNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(300, 20),
|
Size = new Vector2(300, 20),
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ namespace AetherBags.Nodes.Configuration.General;
|
|||||||
|
|
||||||
public sealed class GeneralScrollingAreaNode : ScrollingListNode
|
public sealed class GeneralScrollingAreaNode : ScrollingListNode
|
||||||
{
|
{
|
||||||
private readonly CheckboxNode _debugCheckboxNode = null!;
|
|
||||||
|
|
||||||
public GeneralScrollingAreaNode()
|
public GeneralScrollingAreaNode()
|
||||||
{
|
{
|
||||||
GeneralSettings config = System.Config.General;
|
GeneralSettings config = System.Config.General;
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using KamiToolKit.Classes;
|
||||||
|
using KamiToolKit.Nodes;
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
|
namespace AetherBags.Nodes.Input;
|
||||||
|
|
||||||
|
public class TextInputWithButtonNode : SimpleComponentNode {
|
||||||
|
private readonly TextInputNode _textInputNode;
|
||||||
|
private readonly CircleButtonNode _contextButton;
|
||||||
|
|
||||||
|
public Action? OnButtonClicked {
|
||||||
|
get => _contextButton.OnClick;
|
||||||
|
set => _contextButton.OnClick = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextInputWithButtonNode() {
|
||||||
|
_textInputNode = new TextInputNode {
|
||||||
|
PlaceholderString = "Search . . .",
|
||||||
|
};
|
||||||
|
_textInputNode.AttachNode(this);
|
||||||
|
|
||||||
|
_contextButton = new CircleButtonNode {
|
||||||
|
Icon = ButtonIcon.Filter,
|
||||||
|
Size = new Vector2(28f),
|
||||||
|
};
|
||||||
|
_contextButton.AttachNode(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 HintAddColor {
|
||||||
|
get => _contextButton.AddColor;
|
||||||
|
set => _contextButton.AddColor = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public required Action<ReadOnlySeString>? OnInputReceived {
|
||||||
|
get => _textInputNode.OnInputReceived;
|
||||||
|
set => _textInputNode.OnInputReceived = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSizeChanged() {
|
||||||
|
base.OnSizeChanged();
|
||||||
|
|
||||||
|
_contextButton.Size = new Vector2(Height, Height);
|
||||||
|
_contextButton.Position = new Vector2(Width - _contextButton.Width, 0.0f);
|
||||||
|
|
||||||
|
_textInputNode.Size = new Vector2(Width - _contextButton.Width - 5.0f, Height);
|
||||||
|
_textInputNode.Position = new Vector2(0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySeString SearchString {
|
||||||
|
get => _textInputNode.SeString;
|
||||||
|
set => _textInputNode.SeString = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Numerics;
|
|
||||||
using KamiToolKit.Nodes;
|
|
||||||
using Lumina.Text;
|
|
||||||
using Lumina.Text.ReadOnly;
|
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Input;
|
|
||||||
|
|
||||||
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),
|
|
||||||
TextTooltip = 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,8 +14,6 @@ using FFXIVClientStructs.FFXIV.Component.GUI;
|
|||||||
using KamiToolKit.Classes;
|
using KamiToolKit.Classes;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
// TODO: Switch back to CS version when Dalamud Updated
|
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Inventory;
|
namespace AetherBags.Nodes.Inventory;
|
||||||
|
|
||||||
public class InventoryCategoryNode : SimpleComponentNode
|
public class InventoryCategoryNode : SimpleComponentNode
|
||||||
@@ -237,17 +235,15 @@ public class InventoryCategoryNode : SimpleComponentNode
|
|||||||
ReferenceIndex = (short)absoluteIndex
|
ReferenceIndex = (short)absoluteIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isSlotBlocked = item.Container.IsMainInventory && data.IsSlotBlocked;
|
|
||||||
float alpha = !isSlotBlocked && data.IsEligibleForContext ? 1.0f : 0.4f;
|
|
||||||
|
|
||||||
return new InventoryDragDropNode
|
return new InventoryDragDropNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(42, 46),
|
Size = new Vector2(42, 46),
|
||||||
Alpha = alpha,
|
Alpha = data.VisualAlpha,
|
||||||
|
AddColor = data.HighlightOverlayColor,
|
||||||
|
IsDraggable = !data.IsSlotBlocked,
|
||||||
IsVisible = true,
|
IsVisible = true,
|
||||||
IconId = item.IconId,
|
IconId = item.IconId,
|
||||||
AcceptedType = DragDropType.Item,
|
AcceptedType = DragDropType.Item,
|
||||||
IsDraggable = !data.IsSlotBlocked,
|
|
||||||
Payload = nodePayload,
|
Payload = nodePayload,
|
||||||
IsClickable = true,
|
IsClickable = true,
|
||||||
OnDiscard = node => OnDiscard(node, data),
|
OnDiscard = node => OnDiscard(node, data),
|
||||||
@@ -269,6 +265,18 @@ public class InventoryCategoryNode : SimpleComponentNode
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RefreshNodeVisuals()
|
||||||
|
{
|
||||||
|
foreach (var node in _itemGridNode.Nodes)
|
||||||
|
{
|
||||||
|
if (node is not InventoryDragDropNode itemNode || itemNode.ItemInfo == null) continue;
|
||||||
|
|
||||||
|
itemNode.Alpha = itemNode.ItemInfo.VisualAlpha;
|
||||||
|
itemNode.AddColor = itemNode.ItemInfo.HighlightOverlayColor;
|
||||||
|
itemNode.IsDraggable = !itemNode.ItemInfo.IsSlotBlocked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private unsafe void OnDiscard(DragDropNode node, ItemInfo item)
|
private unsafe void OnDiscard(DragDropNode node, ItemInfo item)
|
||||||
{
|
{
|
||||||
uint addonId = RaptureAtkUnitManager.Instance()->GetAddonByNode(node)->Id;
|
uint addonId = RaptureAtkUnitManager.Instance()->GetAddonByNode(node)->Id;
|
||||||
@@ -299,7 +307,7 @@ public class InventoryCategoryNode : SimpleComponentNode
|
|||||||
|
|
||||||
if (acceptedPayload.IsSameBaseContainer(nodePayload))
|
if (acceptedPayload.IsSameBaseContainer(nodePayload))
|
||||||
{
|
{
|
||||||
Services.Logger.Information("[OnPayload] Source and target are in the same base container, skipping move.");
|
Services.Logger.DebugOnly("[OnPayload] Source and target are in the same base container, skipping move.");
|
||||||
node.IconId = targetItemInfo.IconId;
|
node.IconId = targetItemInfo.IconId;
|
||||||
node.Payload = nodePayload;
|
node.Payload = nodePayload;
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -24,10 +24,11 @@ public unsafe class Plugin : IDalamudPlugin
|
|||||||
{
|
{
|
||||||
pluginInterface.Create<Services>();
|
pluginInterface.Create<Services>();
|
||||||
|
|
||||||
|
System.Config = Util.LoadConfigOrDefault();
|
||||||
|
|
||||||
BackupHelper.DoConfigBackup(pluginInterface);
|
BackupHelper.DoConfigBackup(pluginInterface);
|
||||||
|
|
||||||
KamiToolKitLibrary.Initialize(pluginInterface);
|
KamiToolKitLibrary.Initialize(pluginInterface);
|
||||||
System.Config = Util.LoadConfigOrDefault();
|
|
||||||
|
|
||||||
System.IPC = new IPCService();
|
System.IPC = new IPCService();
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule KamiToolKit updated: 21b3f2b44c...e6ff0f781b
Reference in New Issue
Block a user