Merge remote-tracking branch 'origin/master' into dev/pie-lover

This commit is contained in:
Shawrkie Williams
2026-01-25 03:59:49 -05:00
50 changed files with 1306 additions and 767 deletions
@@ -14,7 +14,7 @@ namespace AetherBags.Addons;
public class AddonCategoryConfigurationWindow : NativeAddon
{
private ModifyListNode<CategoryWrapper>? _selectionListNode;
private ModifyListNode<CategoryWrapper, CategoryListItemNode>? _selectionListNode;
private VerticalLineNode? _separatorLine;
private CategoryConfigurationNode? _configNode;
private TextNode? _nothingSelectedTextNode;
@@ -28,21 +28,24 @@ public class AddonCategoryConfigurationWindow : NativeAddon
{
_categoryWrappers = CreateCategoryWrappers();
_selectionListNode = new ModifyListNode<CategoryWrapper>
_selectionListNode = new ModifyListNode<CategoryWrapper, CategoryListItemNode>
{
Position = ContentStartPosition,
Size = new Vector2(250.0f, ContentSize.Y),
SelectionOptions = _categoryWrappers,
OnOptionChanged = OnOptionChanged,
Size = ContentSize with { X = 250.0f },
Options = _categoryWrappers,
SelectionChanged = OnOptionChanged,
AddNewEntry = OnAddNewCategory,
RemoveEntry = OnRemoveCategory,
SortOptions = [ "Order" ],
ItemComparer = (left, right, mode) => left.Compare(right, mode),
IsSearchMatch = (data, search) => data.GetLabel().Contains(search, global::System.StringComparison.OrdinalIgnoreCase)
};
_selectionListNode.AttachNode(this);
_separatorLine = new VerticalLineNode
{
Position = ContentStartPosition + new Vector2(250.0f + 8.0f, 0.0f),
Size = new Vector2(4.0f, ContentSize.Y),
Size = ContentSize with { X = 4.0f },
};
_separatorLine.AttachNode(this);
@@ -78,6 +81,24 @@ public class AddonCategoryConfigurationWindow : NativeAddon
.ToList();
}
private void OnAddNewCategory()
{
var newCategory = new UserCategoryDefinition
{
Name = $"New Category {System.Config.Categories.UserCategories.Count + 1}",
Order = System.Config.Categories.UserCategories.Count,
};
System.Config.Categories.UserCategories.Add(newCategory);
var newWrapper = new CategoryWrapper(newCategory);
_categoryWrappers.Add(newWrapper);
RefreshSelectionList();
_selectionListNode?.RefreshList();
InventoryOrchestrator.RefreshAll(updateMaps: true);
}
private void OnOptionChanged(CategoryWrapper? newOption)
{
if (_configNode is null) return;
@@ -99,29 +120,11 @@ public class AddonCategoryConfigurationWindow : NativeAddon
if (_pendingSelectionListRefresh)
{
_pendingSelectionListRefresh = false;
_selectionListNode?.UpdateList();
_selectionListNode?.RefreshList();
}
}
}
private void OnAddNewCategory(ModifyListNode<CategoryWrapper> listNode)
{
var newCategory = new UserCategoryDefinition
{
Name = $"New Category {System.Config.Categories.UserCategories.Count + 1}",
Order = System.Config.Categories.UserCategories.Count,
};
System.Config.Categories.UserCategories.Add(newCategory);
var newWrapper = new CategoryWrapper(newCategory);
_categoryWrappers.Add(newWrapper);
listNode.AddOption(newWrapper);
RefreshSelectionList();
InventoryOrchestrator.RefreshAll(updateMaps: true);
}
private void OnRemoveCategory(CategoryWrapper categoryWrapper)
{
if (categoryWrapper.CategoryDefinition is null) return;
@@ -146,6 +149,15 @@ public class AddonCategoryConfigurationWindow : NativeAddon
return;
}
_selectionListNode?.UpdateList();
_selectionListNode?.RefreshList();
}
protected override unsafe void OnFinalize(AtkUnitBase* addon)
{
_selectionListNode = null;
_configNode = null;
_separatorLine = null;
_nothingSelectedTextNode = null;
base.OnFinalize(addon);
}
}
+17 -4
View File
@@ -10,11 +10,11 @@ namespace AetherBags.Addons;
public class AddonConfigurationWindow : NativeAddon
{
private TabBarNode _tabBarNode = null!;
private TabBarNode? _tabBarNode;
private GeneralScrollingAreaNode _generalScrollingAreaNode = null!;
private CategoryScrollingAreaNode _categoryScrollingAreaNode = null!;
private CurrencyScrollingAreaNode _currencyScrollingAreaNode = null!;
private GeneralScrollingAreaNode? _generalScrollingAreaNode;
private CategoryScrollingAreaNode? _categoryScrollingAreaNode;
private CurrencyScrollingAreaNode? _currencyScrollingAreaNode;
private readonly List<NodeBase> _tabContent = new();
@@ -73,4 +73,17 @@ public class AddonConfigurationWindow : NativeAddon
for (var i = 0; i < _tabContent.Count; i++)
_tabContent[i].IsVisible = i == index;
}
protected override unsafe void OnFinalize(AtkUnitBase* addon)
{
_tabBarNode?.Dispose();
_tabBarNode = null;
_generalScrollingAreaNode?.Dispose();
_generalScrollingAreaNode = null;
_categoryScrollingAreaNode?.Dispose();
_categoryScrollingAreaNode = null;
_currencyScrollingAreaNode?.Dispose();
_currencyScrollingAreaNode = null;
base.OnFinalize(addon);
}
}
+28
View File
@@ -0,0 +1,28 @@
using System;
using System.Linq;
using AetherBags.Currency;
using KamiToolKit.Premade.ListItemNodes;
using KamiToolKit.Premade.SearchAddons;
using Lumina.Excel.Sheets;
namespace AetherBags.Addons;
public class AddonCurrencyPicker : BaseSearchAddon<Item, ItemListItemNode> {
public AddonCurrencyPicker() {
var allItems = Services.DataManager.GetExcelSheet<Item>();
var obsoleteTomes = Services.DataManager.GetExcelSheet<TomestonesItem>()
.Where(t => t.Tomestones.RowId == 0)
.Select(t => t.Item.RowId).ToHashSet();
var currentTomestones = CurrencyState.GetCurrentTomestoneIds();
SearchOptions = allItems
.Where(i => (i.ItemUICategory.RowId == 100 || (i.RowId >= 1 && i.RowId < 100)) && !i.Name.IsEmpty)
.Where(i => !obsoleteTomes.Contains(i.RowId))
.Where(i => i.RowId != currentTomestones.Limited && i.RowId != currentTomestones.NonLimited)
.ToList();
}
protected override bool IsMatch(Item item, string search) => item.Name.ToString().Contains(search, StringComparison.OrdinalIgnoreCase);
protected override int Comparer(Item l, Item r, string s, bool rev) => string.CompareOrdinal(l.Name.ToString(), r.Name.ToString());
}
+2 -2
View File
@@ -79,7 +79,7 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
LayoutContent();
//addon->SubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
addon->SubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
System.LootedItemsTracker.OnLootedItemsChanged += OnLootedItemsChanged;
@@ -139,6 +139,7 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
private void OnClearAllLootedItems()
{
System.LootedItemsTracker.Clear();
System.LootedItemsTracker.FlushPendingChanges();
}
public void ManualCurrencyRefresh()
@@ -155,7 +156,6 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
}, delayTicks: 3);
}
protected override void OnFinalize(AtkUnitBase* addon)
{
System.LootedItemsTracker.OnLootedItemsChanged -= OnLootedItemsChanged;
+7
View File
@@ -0,0 +1,7 @@
using KamiToolKit.Premade.ListItemNodes;
using KamiToolKit.Premade.SearchAddons;
namespace AetherBags.Addons;
public class AddonItemPicker : ItemSearchAddonBase<ItemListItemNode> {
}
@@ -0,0 +1,14 @@
using System.Diagnostics.CodeAnalysis;
using AetherBags.Nodes.Configuration.Category;
using KamiToolKit.Premade.SearchAddons;
using Lumina.Excel.Sheets;
namespace AetherBags.Addons;
public class AddonUICategoryPicker : BaseSearchAddon<ItemUICategory, UICategoryListItemNode> {
protected override int Comparer(ItemUICategory left, ItemUICategory right, string sort, bool rev)
=> string.CompareOrdinal(left.Name.ToString(), right.Name.ToString());
protected override bool IsMatch(ItemUICategory item, string search)
=> item.Name.ToString().Contains(search, global::System.StringComparison.OrdinalIgnoreCase);
}
+14
View File
@@ -0,0 +1,14 @@
using KamiToolKit.Premade.GenericListItemNodes;
namespace AetherBags.Addons;
public class CategoryListItemNode : GenericListItemNode<CategoryWrapper>
{
protected override uint GetIconId(CategoryWrapper data) => data.GetIconId() ?? 0;
protected override string GetLabelText(CategoryWrapper data) => data.GetLabel();
protected override string GetSubLabelText(CategoryWrapper data) => data.GetSubLabel();
protected override uint? GetId(CategoryWrapper data) => data.GetId();
}
+6 -16
View File
@@ -1,17 +1,14 @@
using AetherBags.Configuration;
using AetherBags.Inventory.Categories;
using KamiToolKit.Premade;
namespace AetherBags.Addons;
public class CategoryWrapper(UserCategoryDefinition categoryDefinition) : IInfoNodeData
// Removed IInfoNodeData implementation
public class CategoryWrapper(UserCategoryDefinition categoryDefinition)
{
public UserCategoryDefinition? CategoryDefinition { get; } = categoryDefinition;
public string GetLabel() {
return CategoryDefinition!.Name;
}
public string GetLabel() => CategoryDefinition!.Name;
public string GetSubLabel() {
if(UserCategoryMatcher.IsCatchAll(CategoryDefinition!)) return " No valid rules!";
@@ -20,16 +17,9 @@ public class CategoryWrapper(UserCategoryDefinition categoryDefinition) : IInfoN
public uint? GetId() => null;
public uint? GetIconId() {
return 0;
}
public uint? GetIconId() => 0;
public string? GetTexturePath()
=> null;
public int Compare(IInfoNodeData other, string sortingMode) {
if (other is not CategoryWrapper otherWrapper) return 0;
return CategoryDefinition!.Order.CompareTo(otherWrapper.CategoryDefinition!.Order);
public int Compare(CategoryWrapper other, string sortingMode) {
return CategoryDefinition!.Order.CompareTo(other.CategoryDefinition!.Order);
}
}
+22 -46
View File
@@ -9,6 +9,7 @@ using AetherBags.Inventory.Context;
using AetherBags.Inventory.Items;
using AetherBags.Inventory.Scanning;
using AetherBags.Inventory.State;
using AetherBags.Monitoring;
using AetherBags.Nodes.Input;
using AetherBags.Nodes.Inventory;
using AetherBags.Nodes.Layout;
@@ -16,7 +17,7 @@ using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit;
using KamiToolKit.Classes;
using KamiToolKit.Classes.ContextMenu;
using KamiToolKit.ContextMenu;
using KamiToolKit.Nodes;
namespace AetherBags.Addons;
@@ -65,25 +66,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
private int _refreshFromLifecycleCount;
private long _lastLogTick;
public void ManualRefresh()
{
if (!IsOpen) return;
if (!Services.ClientState.IsLoggedIn) return;
if (_isRefreshing) return;
if (!IsSetupComplete) return;
try
{
_isRefreshing = true;
InventoryState.RefreshFromGame();
RefreshCategoriesCore(autosize: true);
}
finally
{
_isRefreshing = false;
}
}
public void ManualRefresh() => ExecuteRefresh(true);
public string GetSearchText() => SearchInputNode?.SearchString.ExtractText() ?? string.Empty;
@@ -98,21 +81,16 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
}, delayTicks: 3);
}
public void RefreshFromLifecycle()
private void ExecuteRefresh(bool autosize)
{
if (!IsSetupComplete) return;
if (!IsOpen) return;
if (_isRefreshing) return;
if (!IsSetupComplete || !IsOpen || _isRefreshing) return;
try
{
_isRefreshing = true;
_refreshFromLifecycleCount++;
LogRefreshStats();
InventoryState.RefreshFromGame();
RefreshCategoriesCore(autosize: true);
System.LootedItemsTracker.FlushPendingChanges();
RefreshCategoriesCore(autosize);
}
finally
{
@@ -120,6 +98,8 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
}
}
public void RefreshFromLifecycle() => ExecuteRefresh(autosize: true);
protected virtual void RefreshCategoriesCore(bool autosize)
{
if (!IsSetupComplete)
@@ -215,25 +195,22 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
{
var header = addon->WindowHeaderCollisionNode;
float headerW = header->Width;
float headerH = header->Height;
// Center the search bar, width is 50% of header
float searchWidth = headerW * 0.5f;
var searchSize = new Vector2(searchWidth, 28f);
float settingsX = headerW - 62f;
float itemY = header->Y + (header->Height - 28f) * 0.5f;
float searchWidth = headerW * 0.45f;
float searchX = (headerW - searchWidth) * 0.5f;
float itemY = header->Y + (headerH - 28f) * 0.5f;
return new HeaderLayout
{
SearchPosition = new Vector2(searchX, itemY),
SearchSize = searchSize,
SearchSize = new Vector2(searchWidth, 28f),
HeaderWidth = headerW,
HeaderY = itemY
};
}
protected void InitializeBackgroundDropTarget()
{
BackgroundDropTarget = new DragDropNode
@@ -383,6 +360,11 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
float requiredWidth = maxChildWidth + (ContentStartPosition.X * 2);
float finalWidth = Math.Clamp(requiredWidth, MinWindowWidth, MaxWindowWidth);
if (SettingsButtonNode != null)
{
SettingsButtonNode.X = finalWidth - 62f;
}
float contentWidth = finalWidth - (ContentStartPosition.X * 2);
float footerSpace = HasFooter || HasSlotCounter ? FooterHeight + FooterTopSpacing : 0;
@@ -442,21 +424,15 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
}
}
/*
protected override void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
{
_requestedUpdateCount++;
LogRefreshStats();
base.OnRequestedUpdate(addon, numberArrayData, stringArrayData);
if (DragDropState.IsDragging)
return;
InventoryState.RefreshFromGame();
RefreshCategoriesCore(autosize: true);
if (DragDropState.IsDragging) return;
ExecuteRefresh(autosize: true);
}
*/
protected override void OnSetup(AtkUnitBase* addon)
{
@@ -2,7 +2,7 @@ using AetherBags.Configuration;
using AetherBags.Inventory;
using AetherBags.Inventory.Context;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using KamiToolKit.Classes.ContextMenu;
using KamiToolKit.ContextMenu;
namespace AetherBags.Addons;
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Numerics;
using System.Text.Json.Serialization;
using KamiToolKit.Classes;
@@ -84,6 +85,9 @@ public enum ToggleFilterState
public enum PluginFilterMode
{
[Description("Create New Categories")]
Categorize = 0,
[Description("Apply Highlight Only")]
Highlight = 1,
}
@@ -1,11 +1,20 @@
using System.Collections.Generic;
using System.Numerics;
using System.Text.Json.Serialization;
using KamiToolKit.Classes;
namespace AetherBags.Configuration;
public class CurrencySettings
{
[JsonIgnore]
public const uint LimitedTomestoneId = 0xFFFF_FFFE;
[JsonIgnore]
public const uint NonLimitedTomestoneId = 0xFFFF_FFFD;
public bool Enabled { get; set; } = true;
public List<uint> DisplayedCurrencies { get; set; } = new() { 1, LimitedTomestoneId, NonLimitedTomestoneId };
public bool ColorWhenCapped { get; set; } = true;
public bool ColorWhenLimited { get; set; } = true;
public Vector4 DefaultColor { get; set; } = ColorHelper.GetColor(8);
@@ -1,3 +1,5 @@
using System.ComponentModel;
namespace AetherBags.Configuration;
public class GeneralSettings
@@ -21,12 +23,18 @@ public class GeneralSettings
public enum InventoryStackMode : byte
{
[Description("Split Stacks (Game Default)")]
NaturalStacks = 0,
[Description("Merge Stacks (By Item ID)")]
AggregateByItemId = 1,
}
public enum SearchMode : byte
{
[Description("Filter (Hide non-matches)")]
Filter = 0,
[Description("Highlight (Dim non-matches)")]
Highlight = 1,
}
+32
View File
@@ -71,6 +71,31 @@ public static unsafe class CurrencyState
return currencyInfoList;
}
public static (uint Limited, uint NonLimited) GetCurrentTomestoneIds()
{
var tomestonesItemSheet = Services.DataManager.GetExcelSheet<TomestonesItem>();
uint limitedId = 0;
uint nonLimitedId = 0;
foreach (var row in tomestonesItemSheet)
{
var tomeSheetRef = row.Tomestones.ValueNullable;
if (tomeSheetRef == null || tomeSheetRef.Value.RowId == 0) continue;
var itemId = row.Item.RowId;
if (itemId == 0 || itemId == 28) continue;
if (tomeSheetRef.Value.WeeklyLimit > 0)
limitedId = itemId;
else
nonLimitedId = itemId;
}
return (limitedId, nonLimitedId);
}
/*
private static uint? GetLimitedTomestoneItemIdCached()
{
if (_cachedLimitedTomestoneItemId.HasValue)
@@ -96,6 +121,13 @@ public static unsafe class CurrencyState
_cachedNonLimitedTomestoneItemId = itemId;
return itemId;
}
*/
private static uint? GetLimitedTomestoneItemIdCached()
=> _cachedLimitedTomestoneItemId ??= GetCurrentTomestoneIds().Limited;
private static uint? GetNonLimitedTomestoneItemIdCached()
=> _cachedNonLimitedTomestoneItemId ??= GetCurrentTomestoneIds().NonLimited;
private static CurrencyItem ResolveCurrencyItemIdCached(uint currencyId)
{
+52
View File
@@ -0,0 +1,52 @@
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
using Dalamud.Utility;
namespace AetherBags.Extensions;
internal static class EnumExtensions {
extension(Enum enumValue) {
public string Description => enumValue.GetDescription();
private string GetDescription() {
var attribute = enumValue.GetAttribute<DescriptionAttribute>();
return attribute?.Description ?? enumValue.ToString();
}
}
extension<T>(ref T flagValue) where T : unmanaged, Enum {
public void SetFlags(params T[] flags) {
foreach (var flag in flags) {
flagValue.SetFlag(flag, true);
}
}
public void ClearFlags(params T[] flags) {
foreach (var flag in flags) {
flagValue.SetFlag(flag, false);
}
}
private unsafe void SetFlag(T flag, bool enable) {
switch (sizeof(T)) {
case 1: flagValue.SetFlag<T, byte>(flag, enable); break;
case 2: flagValue.SetFlag<T, ushort>(flag, enable); break;
case 4: flagValue.SetFlag<T, uint>(flag, enable); break;
case 8: flagValue.SetFlag<T, ulong>(flag, enable); break;
default: throw new NotSupportedException("Unsupported enum size");
}
}
private void SetFlag<TUnderlying>(T flag, bool enable) where TUnderlying : unmanaged, IBinaryInteger<TUnderlying> {
ref var value = ref Unsafe.As<T, TUnderlying>(ref flagValue);
var mask = Unsafe.As<T, TUnderlying>(ref flag);
if (enable)
value |= mask;
else
value &= ~mask;
}
}
}
+8 -3
View File
@@ -1,22 +1,27 @@
using System.Diagnostics;
using Dalamud.Plugin.Services;
namespace AetherBags.Extensions;
public static class LoggerExtensions
{
extension(object logger)
extension(IPluginLog logger)
{
[Conditional("DEBUG")]
public void DebugOnly(string message)
{
if (System.Config?.General?.DebugEnabled == true)
{
Services.Logger.Debug(message);
logger.Debug(message);
}
}
[Conditional("DEBUG")]
public void DebugOnly(string message, params object[] args)
{
if (System.Config?.General?.DebugEnabled == true)
{
Services.Logger.Debug(message, args);
logger.Debug(message, args);
}
}
}
@@ -18,6 +18,7 @@ public abstract class InventoryStateBase
protected readonly List<CategorizedInventory> AllCategories = new(capacity: 256);
protected readonly List<CategorizedInventory> FilteredCategories = new(capacity: 256);
protected readonly List<UserCategoryDefinition> UserCategoriesSortedScratch = new(capacity: 64);
protected readonly List<UserCategoryDefinition> EnabledUserCategoriesScratch = new(capacity: 64);
protected readonly List<ulong> RemoveKeysScratch = new(capacity: 256);
protected readonly HashSet<ulong> ClaimedKeys = new(capacity: 512);
@@ -68,18 +69,22 @@ public abstract class InventoryStateBase
bool allaganCategoriesEnabled = config.Categories.AllaganToolsCategoriesEnabled && categoriesEnabled;
bool bisCategoriesEnabled = config.Categories.BisBuddyEnabled && categoriesEnabled;
// TODO: Cache this when config changes
UserCategoriesSortedScratch.Clear();
EnabledUserCategoriesScratch.Clear();
foreach (var cat in config.Categories.UserCategories)
{
if (cat.Enabled)
UserCategoriesSortedScratch.Add(cat);
EnabledUserCategoriesScratch.Add(cat);
}
var userCategories = UserCategoriesSortedScratch;
if (userCategoriesEnabled && userCategories.Count > 0)
if (userCategoriesEnabled && EnabledUserCategoriesScratch.Count > 0)
{
CategoryBucketManager.BucketByUserCategories(
ItemInfoByKey, userCategories, BucketsByKey, ClaimedKeys, UserCategoriesSortedScratch);
ItemInfoByKey,
EnabledUserCategoriesScratch,
BucketsByKey,
ClaimedKeys,
UserCategoriesSortedScratch
);
}
if (allaganCategoriesEnabled)
@@ -1,18 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AetherBags.Configuration;
using AetherBags.Inventory.Context;
using AetherBags.Inventory.Scanning;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Inventory.InventoryEventArgTypes;
using Dalamud.Game.NativeWrapper;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Lumina.Text.ReadOnly;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
namespace AetherBags.AddonLifecycles;
namespace AetherBags.Monitoring;
public static unsafe class DragDropState
{
@@ -22,10 +26,10 @@ public static unsafe class DragDropState
public static bool IsDragging => AtkStage.Instance()->DragDropManager.IsDragging;
}
public class InventoryLifecycles : IDisposable
public class InventoryMonitor : IDisposable
{
public InventoryLifecycles()
public InventoryMonitor()
{
var bags = new[] { "Inventory", "InventoryLarge", "InventoryExpansion" };
var saddle = new[] { "InventoryBuddy" };
@@ -45,8 +49,8 @@ public class InventoryLifecycles : IDisposable
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, "InventoryBuddy", OnSaddleBagUpdate);
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, retainer, OnRetainerInventoryUpdate);
// PreShow
Services.AddonLifecycle.RegisterListener(AddonEvent.PreOpen, "InventoryBuddy", OnSaddleBagOpen);
// Dalamud raw event for raw inventory changes (scans once per frame)
Services.GameInventory.InventoryChangedRaw += OnInventoryChangedRaw;
Services.Logger.Verbose("InventoryLifecycles initialized");
}
@@ -122,6 +126,32 @@ public class InventoryLifecycles : IDisposable
values[7] = can use Saddlebags (Agent InventoryBuddy IsActivatable)
*/
private void OnInventoryChangedRaw(IReadOnlyCollection<InventoryEventArgs> events)
{
bool needsRefresh = false;
foreach (var inventoryEventArgs in events)
{
if (InventoryScanner.StandardInventories.Contains((InventoryType)inventoryEventArgs.Item.ContainerType))
{
needsRefresh = true;
break;
}
}
if (needsRefresh)
{
Services.Framework.RunOnTick(() =>
{
if (IsInUnsafeState() || DragDropState.IsDragging) return;
System.LootedItemsTracker.FlushPendingChanges();
System.AddonInventoryWindow?.RefreshFromLifecycle();
System.AddonSaddleBagWindow?.RefreshFromLifecycle();
System.AddonRetainerWindow?.RefreshFromLifecycle();
});
}
}
private unsafe void InventoryPreRefreshHandler(AddonEvent type, AddonArgs args)
{
if (args is not AddonRefreshArgs refreshArgs)
@@ -165,25 +195,6 @@ public class InventoryLifecycles : IDisposable
}
}
// TODO: Inventory/Retainers are not perma open, need some way to close it too.
private void InventoryBuddyPreRefreshHandler(AddonEvent type, AddonArgs args)
{
if (args is not AddonRefreshArgs refreshArgs)
return;
if (IsInUnsafeState())
return;
GeneralSettings config = System.Config.General;
if (config.HideGameSaddleBags) refreshArgs.AtkValueCount = 0;
if (config.OpenSaddleBagsWithGameInventory)
{
System.AddonSaddleBagWindow.Toggle();
}
}
private void OnInventoryUpdate(AddonEvent type, AddonArgs args)
{
if (IsInUnsafeState())
@@ -204,6 +215,7 @@ public class InventoryLifecycles : IDisposable
if (DragDropState.IsDragging)
return;
System.LootedItemsTracker.FlushPendingChanges();
System.AddonSaddleBagWindow?.RefreshFromLifecycle();
}
@@ -215,17 +227,13 @@ public class InventoryLifecycles : IDisposable
if (DragDropState.IsDragging)
return;
System.LootedItemsTracker.FlushPendingChanges();
System.AddonRetainerWindow?.RefreshFromLifecycle();
}
private void OnSaddleBagOpen(AddonEvent type, AddonArgs args)
{
if (args is not AddonShowArgs showArgs)
return;
}
public void Dispose()
{
Services.AddonLifecycle.UnregisterListener(OnPostSetup, OnPreFinalize, OnInventoryUpdate, OnSaddleBagUpdate, OnRetainerInventoryUpdate, OnSaddleBagOpen);
Services.GameInventory.InventoryChangedRaw -= OnInventoryChangedRaw;
Services.AddonLifecycle.UnregisterListener(OnPostSetup, OnPreFinalize, OnInventoryUpdate, OnSaddleBagUpdate, OnRetainerInventoryUpdate);
}
}
@@ -9,7 +9,7 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using Lumina.Excel.Sheets;
namespace AetherBags.Inventory;
namespace AetherBags.Monitoring;
public sealed unsafe class LootedItemsTracker : IDisposable
{
@@ -32,6 +32,8 @@ public sealed unsafe class LootedItemsTracker : IDisposable
public bool HasPendingChanges => _pendingChanges.Count > 0 || _hasPendingRemoval;
private int GetNextIndex() => _lootedItems.Count > 0 ? _lootedItems.Max(x => x.Index) + 1 : 0;
public void Enable()
{
if (_isEnabled) return;
@@ -81,6 +83,8 @@ public sealed unsafe class LootedItemsTracker : IDisposable
{
if (_pendingChanges.Count == 0 && !_hasPendingRemoval) return;
ProcessPendingChanges();
_hasPendingRemoval = false;
OnLootedItemsChanged?.Invoke(_lootedItems);
}
@@ -90,12 +94,40 @@ public sealed unsafe class LootedItemsTracker : IDisposable
Disable();
}
private void ProcessPendingChanges()
{
if (_pendingChanges.Count == 0) return;
foreach (var ((itemId, isHq), (item, delta)) in _pendingChanges)
{
int existingIndex = _lootedItems.FindIndex(x =>
x.Item.ItemId == itemId &&
x.Item.Flags.HasFlag(InventoryItem.ItemFlags.HighQuality) == isHq);
if (existingIndex >= 0)
{
var current = _lootedItems[existingIndex];
int newQty = current.Quantity + delta;
if (newQty <= 0)
_lootedItems.RemoveAt(existingIndex);
else
_lootedItems[existingIndex] = current with { Quantity = newQty };
}
else if (delta > 0)
{
_lootedItems.Add(new LootedItemInfo(GetNextIndex(), item, delta));
}
}
_pendingChanges.Clear();
}
private void OnInventoryChangedRaw(IReadOnlyCollection<InventoryEventArgs> events)
{
if (!_isEnabled) return;
if (!Services.ClientState.IsLoggedIn) return;
if (!_isEnabled || !Services.ClientState.IsLoggedIn) return;
bool anyAdded = false;
bool anyChanged = false;
foreach (var eventData in events)
{
@@ -105,38 +137,42 @@ public sealed unsafe class LootedItemsTracker : IDisposable
if (eventData.Item.ContainerType == GameInventoryType.DamagedGear)
continue;
if (eventData is not (InventoryItemAddedArgs or InventoryItemChangedArgs))
continue;
if (eventData is InventoryItemChangedArgs changedArgs &&
changedArgs.OldItemState.Quantity >= changedArgs.Item.Quantity)
int changeAmount = eventData switch
{
continue;
}
InventoryItemAddedArgs added => added.Item.Quantity,
InventoryItemRemovedArgs removed => -removed.Item.Quantity,
InventoryItemChangedArgs changed => changed.Item.Quantity - changed.OldItemState.Quantity,
_ => 0
};
if (changeAmount == 0) continue;
if (ShouldFilterItem(eventData.Item.ItemId))
continue;
var inventoryItem = *(InventoryItem*)eventData.Item.Address;
var changeAmount = eventData is InventoryItemChangedArgs changed
? changed.Item.Quantity - changed.OldItemState.Quantity
: eventData.Item.Quantity;
var key = (inventoryItem.ItemId, IsHq: inventoryItem.Flags.HasFlag(InventoryItem.ItemFlags.HighQuality));
uint itemId = eventData.Item.ItemId;
bool isHq = eventData.Item.IsHq;
var key = (itemId, isHq);
if (_pendingChanges.TryGetValue(key, out var existing))
{
_pendingChanges[key] = (inventoryItem, existing.Quantity + changeAmount);
_pendingChanges[key] = (existing.Item, existing.Quantity + changeAmount);
}
else
{
_pendingChanges[key] = (inventoryItem, changeAmount);
InventoryItem itemStruct = default;
if (changeAmount > 0)
{
itemStruct = *(InventoryItem*)eventData.Item.Address;
}
anyAdded = true;
_pendingChanges[key] = (itemStruct, changeAmount);
}
if (anyAdded && _batchStartTick == 0)
anyChanged = true;
}
if (anyChanged && _batchStartTick == 0)
{
_batchStartTick = Environment.TickCount64;
}
@@ -152,23 +188,7 @@ public sealed unsafe class LootedItemsTracker : IDisposable
_batchStartTick = 0;
if (_pendingChanges.Count == 0)
return;
foreach (var ((itemId, isHq), (item, quantity)) in _pendingChanges)
{
if (quantity <= 0)
continue;
_lootedItems.Add(new LootedItemInfo(
_lootedItems.Count,
item,
quantity));
}
_pendingChanges.Clear();
OnLootedItemsChanged?.Invoke(_lootedItems);
FlushPendingChanges();
}
private static bool ShouldFilterItem(uint itemId)
+39 -30
View File
@@ -3,6 +3,7 @@ using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Nodes;
using KamiToolKit.Premade.Addons;
using KamiToolKit.Premade.Color;
namespace AetherBags.Nodes.Color;
@@ -16,41 +17,49 @@ public class ColorInputRow : HorizontalListNode
{
InitializeColorPicker();
var initialColor = CurrentColor;
_colorPreview = new ColorPreviewButtonNode
{
Size = new Vector2(28),
Color = CurrentColor,
OnClick = () =>
{
_colorPickerAddon?.InitialColor = CurrentColor;
_colorPickerAddon?.DefaultColor = DefaultColor;
_colorPickerAddon?.Toggle();
_colorPickerAddon?.OnColorConfirmed = color =>
{
CurrentColor = color;
_colorPreview?.Color = color;
initialColor = color;
OnColorConfirmed?.Invoke(color);
};
_colorPickerAddon?.OnColorPreviewed = color =>
{
_colorPreview?.Color = color;
OnColorChange?.Invoke(color);
};
_colorPickerAddon?.OnColorCancelled = () => OnColorCanceled?.Invoke(initialColor);
}
};
_colorPreview.AttachNode(this);
_colorPreview = new ColorPreviewButtonNode { Size = new Vector2(28) };
_labelTextNode = new LabelTextNode
{
TextFlags = TextFlags.AutoAdjustNodeSize,
Position = new Vector2(28, 0),
Height = 24,
String = Label ?? string.Empty,
Height = 28,
};
var node = _colorPreview;
node.OnClick = () =>
{
var snapshot = CurrentColor;
if (_colorPickerAddon is not null)
{
_colorPickerAddon.InitialColor = snapshot;
_colorPickerAddon.DefaultColor = DefaultColor;
_colorPickerAddon.Toggle();
_colorPickerAddon.OnColorConfirmed = color =>
{
CurrentColor = color;
node.Color = color;
OnColorConfirmed?.Invoke(color);
};
_colorPickerAddon.OnColorPreviewed = color =>
{
node.Color = color;
OnColorPreviewed?.Invoke(color);
};
_colorPickerAddon.OnColorCancelled = () =>
{
CurrentColor = snapshot;
node.Color = snapshot;
OnColorCanceled?.Invoke(snapshot);
};
}
};
_colorPreview.AttachNode(this);
_labelTextNode.AttachNode(this);
}
+1 -1
View File
@@ -2,7 +2,7 @@ using System.Drawing;
using System.IO;
using System.Numerics;
using Dalamud.Interface;
using KamiToolKit.Classes;
using KamiToolKit.Enums;
using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Color;
@@ -0,0 +1,141 @@
using System;
using System.Numerics;
using AetherBags.Configuration;
using AetherBags.Nodes.Color;
using Dalamud.Utility;
using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Configuration.Category;
public sealed class BasicSettingsSection(Func<UserCategoryDefinition> getCategoryDefinition) : ConfigurationSection(getCategoryDefinition)
{
public Action? OnPropertyChanged { get; init; }
private CheckboxNode? _enabledCheckbox;
private CheckboxNode? _pinnedCheckbox;
private TextInputNode? _nameInput;
private TextInputNode? _descriptionInput;
private ColorInputRow? _colorInput;
private NumericInputNode? _priorityInput;
private NumericInputNode? _orderInput;
private bool _initialized;
private void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
_enabledCheckbox = new CheckboxNode
{
Size = new Vector2(Width, 20),
String = "Enabled",
OnClick = isChecked =>
{
CategoryDefinition.Enabled = isChecked;
OnPropertyChanged?.Invoke();
},
};
AddNode(_enabledCheckbox);
_pinnedCheckbox = new CheckboxNode
{
Size = new Vector2(Width, 20),
String = "Pinned",
OnClick = isChecked =>
{
CategoryDefinition.Pinned = isChecked;
OnPropertyChanged?.Invoke();
},
};
AddNode(_pinnedCheckbox);
AddNode(CreateLabel("Name: "));
_nameInput = new TextInputNode
{
Size = new Vector2(250, 28),
PlaceholderString = "Category Name",
OnInputReceived = input =>
{
CategoryDefinition.Name = input.ExtractText();
OnPropertyChanged?.Invoke();
},
};
AddNode(_nameInput);
AddNode(CreateLabel("Description:"));
_descriptionInput = new TextInputNode
{
Size = new Vector2(250, 28),
PlaceholderString = "Optional description",
OnInputReceived = input =>
{
CategoryDefinition.Description = input.ExtractText();
OnValueChanged?.Invoke();
},
};
AddNode(_descriptionInput);
_colorInput = new ColorInputRow
{
Label = "Color",
Size = new Vector2(300, 28),
CurrentColor = new UserCategoryDefinition().Color,
DefaultColor = new UserCategoryDefinition().Color,
OnColorConfirmed = color => { CategoryDefinition.Color = color; OnValueChanged?.Invoke(); },
OnColorCanceled = color => { CategoryDefinition.Color = color; OnValueChanged?.Invoke(); },
OnColorPreviewed = color => { CategoryDefinition.Color = color; OnValueChanged?.Invoke(); },
OnColorChange = color => { CategoryDefinition.Color = color; OnValueChanged?.Invoke(); },
};
AddNode(_colorInput);
AddNode(CreateLabel("Priority:"));
_priorityInput = new NumericInputNode
{
Size = new Vector2(120, 28),
Min = 0,
Max = 1000,
Step = 1,
OnValueUpdate = value =>
{
CategoryDefinition.Priority = value;
OnValueChanged?.Invoke();
},
};
AddNode(_priorityInput);
AddNode(CreateLabel("Order: "));
_orderInput = new NumericInputNode
{
Size = new Vector2(120, 28),
Min = 0,
Max = 9999,
Step = 1,
OnValueUpdate = val =>
{
CategoryDefinition.Order = val;
OnPropertyChanged?.Invoke();
},
};
AddNode(_orderInput);
RecalculateLayout();
}
public override void Refresh()
{
EnsureInitialized();
_enabledCheckbox!.IsChecked = CategoryDefinition.Enabled;
_pinnedCheckbox!.IsChecked = CategoryDefinition.Pinned;
_nameInput!.String = CategoryDefinition.Name;
_nameInput.PlaceholderString = CategoryDefinition.Name.IsNullOrWhitespace() ? "Category Name" : "";
_descriptionInput!.String = CategoryDefinition.Description;
_descriptionInput.PlaceholderString = CategoryDefinition.Description.IsNullOrWhitespace() ? "Optional description" : "";
_colorInput!.CurrentColor = CategoryDefinition.Color;
_priorityInput!.Value = CategoryDefinition.Priority;
_orderInput!.Value = CategoryDefinition.Order;
RecalculateLayout();
}
}
@@ -3,8 +3,7 @@ using System.Collections.Generic;
using System.Numerics;
using AetherBags.Configuration;
using AetherBags.Inventory;
using AetherBags.Nodes.Color;
using Dalamud.Utility;
using AetherBags.Nodes.Layout;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Nodes;
using Lumina.Excel;
@@ -23,71 +22,39 @@ public sealed class CategoryDefinitionConfigurationNode : SimpleComponentNode
private UserCategoryDefinition _categoryDefinition = new();
private readonly ScrollingAreaNode<TreeListNode> _scrollingArea;
private readonly BasicSettingsSection _basicSettings;
private readonly RangeFiltersSection _rangeFilters;
private readonly StateFiltersSection _stateFilters;
private readonly ListFiltersSection _listFilters;
private readonly ScrollingAreaNode<VerticalListNode> _scrollingArea;
private readonly List<ConfigurationSection> _sections = new();
public CategoryDefinitionConfigurationNode()
{
_scrollingArea = new ScrollingAreaNode<TreeListNode>
{
ContentHeight = 100.0f,
_scrollingArea = new ScrollingAreaNode<VerticalListNode> {
AutoHideScrollBar = true,
ContentHeight = 100f
};
_scrollingArea.AttachNode(this);
_scrollingArea.ContentNode.OnLayoutUpdate = newHeight =>
{
_scrollingArea.ContentHeight = newHeight;
};
var list = _scrollingArea.ContentAreaNode;
list.FitContents = true;
list.ItemSpacing = 4.0f;
_scrollingArea.ContentNode.CategoryVerticalSpacing = 4.0f;
_sections.Add(new BasicSettingsSection(() => _categoryDefinition) {
String = "Basic Settings", IsCollapsed = false,
OnPropertyChanged = () => { NotifyChanged(); OnCategoryPropertyChanged?.Invoke(); }
});
var treeListNode = _scrollingArea.ContentAreaNode;
_basicSettings = new BasicSettingsSection(() => _categoryDefinition)
{
String = "Basic Settings",
IsCollapsed = false,
OnPropertyChanged = () =>
{
NotifyChanged();
NotifyCategoryPropertyChanged();
},
OnValueChanged = NotifyChanged,
};
_basicSettings.OnToggle = _ => HandleLayoutChange();
treeListNode.AddCategoryNode(_basicSettings);
_rangeFilters = new RangeFiltersSection(() => _categoryDefinition)
{
String = "Range Filters",
IsCollapsed = true,
OnValueChanged = NotifyChanged,
};
_rangeFilters.OnToggle = _ => HandleLayoutChange();
treeListNode.AddCategoryNode(_rangeFilters);
_stateFilters = new StateFiltersSection(() => _categoryDefinition)
{
String = "State Filters",
IsCollapsed = true,
OnValueChanged = NotifyChanged,
};
_stateFilters.OnToggle = _ => HandleLayoutChange();
treeListNode.AddCategoryNode(_stateFilters);
_listFilters = new ListFiltersSection(() => _categoryDefinition)
{
_sections.Add(new RangeFiltersSection(() => _categoryDefinition) { String = "Range Filters" });
_sections.Add(new StateFiltersSection(() => _categoryDefinition) { String = "State Filters" });
_sections.Add(new ListFiltersSection(() => _categoryDefinition) {
String = "List Filters",
IsCollapsed = true,
OnValueChanged = NotifyChanged,
OnListChanged = HandleListChanged,
};
_listFilters.OnToggle = _ => HandleLayoutChange();
treeListNode.AddCategoryNode(_listFilters);
OnListChanged = HandleLayoutChange
});
foreach (var section in _sections)
{
section.OnToggle = HandleLayoutChange;
section.OnValueChanged = NotifyChanged;
list.AddNode(section);
}
}
protected override void OnSizeChanged()
@@ -96,65 +63,52 @@ public sealed class CategoryDefinitionConfigurationNode : SimpleComponentNode
_scrollingArea.Size = Size;
foreach (var categoryNode in _scrollingArea.ContentNode.CategoryNodes)
foreach (var section in _sections)
{
categoryNode.Width = Width - 16.0f;
section.Width = Width - 16.0f;
}
_scrollingArea.ContentNode.RefreshLayout();
HandleLayoutChange();
}
public void SetCategory(UserCategoryDefinition newCategory)
{
_categoryDefinition = newCategory;
RefreshAllValues();
}
private void RefreshAllValues()
{
_basicSettings.Refresh();
_rangeFilters.Refresh();
_stateFilters.Refresh();
_listFilters.Refresh();
HandleLayoutChange();
}
private void HandleListChanged()
{
NotifyChanged();
foreach (var section in _sections) section.Refresh();
HandleLayoutChange();
}
private void HandleLayoutChange()
{
_scrollingArea.ContentNode.RefreshLayout();
_scrollingArea.ContentAreaNode.RecalculateLayout();
_scrollingArea.ContentHeight = _scrollingArea.ContentAreaNode.Height;
OnLayoutChanged?.Invoke();
}
private static void NotifyChanged() => InventoryOrchestrator.RefreshAll(updateMaps: true);
private void NotifyCategoryPropertyChanged() => OnCategoryPropertyChanged?.Invoke();
public static string ResolveItemName(uint itemId) => ItemSheet?.GetRow(itemId).Name.ToString() ?? "Unknown";
public static string ResolveUiCategoryName(uint categoryId) => UICategorySheet?.GetRow(categoryId).Name.ToString() ?? "Unknown";
}
public abstract class ConfigurationSection : TreeListCategoryNode
public abstract class ConfigurationSection : CollapsibleSectionNode
{
private readonly Func<UserCategoryDefinition> _getCategoryDefinition;
public Action? OnValueChanged { get; init; }
public Action? OnValueChanged { get; set; }
protected UserCategoryDefinition CategoryDefinition => _getCategoryDefinition();
protected ConfigurationSection(Func<UserCategoryDefinition> getCategoryDefinition)
{
_getCategoryDefinition = getCategoryDefinition;
VerticalPadding = 4.0f;
HeaderHeight = 30.0f;
AddTab();
}
public abstract void Refresh();
protected static LabelTextNode CreateLabel(string text) => new()
{
TextFlags = TextFlags.AutoAdjustNodeSize,
@@ -162,351 +116,3 @@ public abstract class ConfigurationSection : TreeListCategoryNode
String = text,
};
}
public sealed class BasicSettingsSection : ConfigurationSection
{
public Action? OnPropertyChanged { get; init; }
private CheckboxNode? _enabledCheckbox;
private CheckboxNode? _pinnedCheckbox;
private TextInputNode? _nameInput;
private TextInputNode? _descriptionInput;
private ColorInputRow? _colorInput;
private NumericInputNode? _priorityInput;
private NumericInputNode? _orderInput;
private bool _initialized;
public BasicSettingsSection(Func<UserCategoryDefinition> getCategoryDefinition)
: base(getCategoryDefinition)
{
}
private void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
_enabledCheckbox = new CheckboxNode
{
Size = new Vector2(Width, 20),
String = "Enabled",
OnClick = isChecked =>
{
CategoryDefinition.Enabled = isChecked;
OnPropertyChanged?.Invoke();
},
};
AddNode(_enabledCheckbox);
_pinnedCheckbox = new CheckboxNode
{
Size = new Vector2(Width, 20),
String = "Pinned",
OnClick = isChecked =>
{
CategoryDefinition.Pinned = isChecked;
OnPropertyChanged?.Invoke();
},
};
AddNode(_pinnedCheckbox);
AddNode(CreateLabel("Name: "));
_nameInput = new TextInputNode
{
Size = new Vector2(250, 28),
PlaceholderString = "Category Name",
OnInputReceived = input =>
{
CategoryDefinition.Name = input.ExtractText();
OnPropertyChanged?.Invoke();
},
};
AddNode(_nameInput);
AddNode(CreateLabel("Description:"));
_descriptionInput = new TextInputNode
{
Size = new Vector2(250, 28),
PlaceholderString = "Optional description",
OnInputReceived = input =>
{
CategoryDefinition.Description = input.ExtractText();
OnValueChanged?.Invoke();
},
};
AddNode(_descriptionInput);
_colorInput = new ColorInputRow
{
Label = "Color",
Size = new Vector2(300, 28),
CurrentColor = new UserCategoryDefinition().Color,
DefaultColor = new UserCategoryDefinition().Color,
OnColorConfirmed = c => { CategoryDefinition.Color = c; OnValueChanged?.Invoke(); },
OnColorCanceled = c => { CategoryDefinition.Color = c; OnValueChanged?.Invoke(); },
OnColorPreviewed = c => { CategoryDefinition.Color = c; OnValueChanged?.Invoke(); },
};
AddNode(_colorInput);
AddNode(CreateLabel("Priority:"));
_priorityInput = new NumericInputNode
{
Size = new Vector2(120, 28),
Min = 0,
Max = 1000,
Step = 1,
OnValueUpdate = val =>
{
CategoryDefinition.Priority = val;
OnValueChanged?.Invoke();
},
};
AddNode(_priorityInput);
AddNode(CreateLabel("Order: "));
_orderInput = new NumericInputNode
{
Size = new Vector2(120, 28),
Min = 0,
Max = 9999,
Step = 1,
OnValueUpdate = val =>
{
CategoryDefinition.Order = val;
OnPropertyChanged?.Invoke();
},
};
AddNode(_orderInput);
RecalculateLayout();
}
public void Refresh()
{
EnsureInitialized();
_enabledCheckbox!.IsChecked = CategoryDefinition.Enabled;
_pinnedCheckbox!.IsChecked = CategoryDefinition.Pinned;
_nameInput!.String = CategoryDefinition.Name;
_nameInput.PlaceholderString = CategoryDefinition.Name.IsNullOrWhitespace() ? "Category Name" : "";
_descriptionInput!.String = CategoryDefinition.Description;
_descriptionInput.PlaceholderString = CategoryDefinition.Description.IsNullOrWhitespace() ? "Optional description" : "";
_colorInput!.CurrentColor = CategoryDefinition.Color;
_priorityInput!.Value = CategoryDefinition.Priority;
_orderInput!.Value = CategoryDefinition.Order;
RecalculateLayout();
ParentTreeListNode?.RefreshLayout();
}
}
public sealed class RangeFiltersSection : ConfigurationSection
{
private RangeFilterRow? _levelFilter;
private RangeFilterRow? _itemLevelFilter;
private RangeFilterRowUint? _vendorPriceFilter;
private bool _initialized;
public RangeFiltersSection(Func<UserCategoryDefinition> getCategoryDefinition)
: base(getCategoryDefinition)
{
}
private void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
_levelFilter = new RangeFilterRow
{
Label = "Level",
MinBound = 0,
MaxBound = 200,
OnFilterChanged = (enabled, min, max) =>
{
CategoryDefinition.Rules.Level.Enabled = enabled;
CategoryDefinition.Rules.Level.Min = min;
CategoryDefinition.Rules.Level.Max = max;
OnValueChanged?.Invoke();
},
};
AddNode(_levelFilter);
_itemLevelFilter = new RangeFilterRow
{
Label = "Item Level",
MinBound = 0,
MaxBound = 2000,
OnFilterChanged = (enabled, min, max) =>
{
CategoryDefinition.Rules.ItemLevel.Enabled = enabled;
CategoryDefinition.Rules.ItemLevel.Min = min;
CategoryDefinition.Rules.ItemLevel.Max = max;
OnValueChanged?.Invoke();
},
};
AddNode(_itemLevelFilter);
_vendorPriceFilter = new RangeFilterRowUint
{
Label = "Vendor Price",
MinBound = 0,
MaxBound = 9_999_999,
OnFilterChanged = (enabled, min, max) =>
{
CategoryDefinition.Rules.VendorPrice.Enabled = enabled;
CategoryDefinition.Rules.VendorPrice.Min = min;
CategoryDefinition.Rules.VendorPrice.Max = max;
OnValueChanged?.Invoke();
},
};
AddNode(_vendorPriceFilter);
RecalculateLayout();
}
public void Refresh()
{
EnsureInitialized();
_levelFilter!.SetFilter(CategoryDefinition.Rules.Level);
_itemLevelFilter!.SetFilter(CategoryDefinition.Rules.ItemLevel);
_vendorPriceFilter!.SetFilter(CategoryDefinition.Rules.VendorPrice);
RecalculateLayout();
ParentTreeListNode?.RefreshLayout();
}
}
public sealed class StateFiltersSection : ConfigurationSection
{
private readonly List<(StateFilterRowNode Node, Func<UserCategoryDefinition, StateFilter> GetFilter)> _filters = [];
private bool _initialized;
public StateFiltersSection(Func<UserCategoryDefinition> getCategoryDefinition)
: base(getCategoryDefinition)
{
}
private void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
AddFilter("Untradable", def => def.Rules.Untradable);
AddFilter("Unique", def => def.Rules.Unique);
AddFilter("Collectable", def => def.Rules.Collectable);
AddFilter("Dyeable", def => def.Rules.Dyeable);
AddFilter("Repairable", def => def.Rules.Repairable);
AddFilter("High Quality", def => def.Rules.HighQuality);
AddFilter("Desynthesizable", def => def.Rules.Desynthesizable);
AddFilter("Glamourable", def => def.Rules.Glamourable);
AddFilter("Spiritbonded", def => def.Rules.FullySpiritbonded);
RecalculateLayout();
}
private void AddFilter(string label, Func<UserCategoryDefinition, StateFilter> getFilter)
{
var node = new StateFilterRowNode(label, new StateFilter(), () => OnValueChanged?.Invoke());
_filters.Add((node, getFilter));
AddNode(node);
}
public void Refresh()
{
EnsureInitialized();
foreach (var (node, getFilter) in _filters)
{
node.SetState(getFilter(CategoryDefinition));
}
RecalculateLayout();
ParentTreeListNode?.RefreshLayout();
}
}
public sealed class ListFiltersSection : ConfigurationSection
{
public Action? OnListChanged { get; init; }
private UintListEditorNode? _itemIdsEditor;
private StringListEditorNode? _namePatternsEditor;
private UintListEditorNode? _uiCategoriesEditor;
private RarityEditorNode? _raritiesEditor;
private bool _initialized;
public ListFiltersSection(Func<UserCategoryDefinition> getCategoryDefinition)
: base(getCategoryDefinition)
{
}
private void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
_itemIdsEditor = new UintListEditorNode
{
Label = "Allowed Item IDs:",
LabelResolver = CategoryDefinitionConfigurationNode.ResolveItemName,
OnChanged = () =>
{
OnListChanged?.Invoke();
RecalculateLayout();
ParentTreeListNode?.RefreshLayout();
},
};
AddNode(_itemIdsEditor);
_namePatternsEditor = new StringListEditorNode
{
Label = "Name Patterns (Regex):",
OnChanged = () =>
{
OnListChanged?.Invoke();
RecalculateLayout();
ParentTreeListNode?.RefreshLayout();
},
};
AddNode(_namePatternsEditor);
_uiCategoriesEditor = new UintListEditorNode
{
Label = "UI Categories:",
LabelResolver = CategoryDefinitionConfigurationNode.ResolveUiCategoryName,
OnChanged = () =>
{
OnListChanged?.Invoke();
RecalculateLayout();
ParentTreeListNode?.RefreshLayout();
},
};
AddNode(_uiCategoriesEditor);
_raritiesEditor = new RarityEditorNode
{
OnChanged = () => OnValueChanged?.Invoke(),
};
AddNode(_raritiesEditor);
RecalculateLayout();
}
public void Refresh()
{
EnsureInitialized();
_itemIdsEditor!.SetList(CategoryDefinition.Rules.AllowedItemIds);
_namePatternsEditor!.SetList(CategoryDefinition.Rules.AllowedItemNamePatterns);
_uiCategoriesEditor!.SetList(CategoryDefinition.Rules.AllowedUiCategoryIds);
_raritiesEditor!.SetList(CategoryDefinition.Rules.AllowedRarities);
RecalculateLayout();
ParentTreeListNode?.RefreshLayout();
}
}
@@ -80,25 +80,22 @@ public sealed class CategoryGeneralConfigurationNode : TabbedVerticalListNode
bool bisBuddyReady = System.IPC.BisBuddy?.IsReady ?? false;
LabeledDropdownNode? bbModeDropdown = new LabeledDropdownNode
LabeledEnumDropdownNode<PluginFilterMode>? bbModeDropdown = new LabeledEnumDropdownNode<PluginFilterMode>
{
Size = new Vector2(300, 20),
Size = new Vector2(500, 20),
LabelText = "Filter Display Mode",
LabelTextFlags = TextFlags.AutoAdjustNodeSize,
IsEnabled = config.BisBuddyEnabled && bisBuddyReady,
Options = Enum.GetNames(typeof(PluginFilterMode)).ToList(),
SelectedOption = config.BisBuddyMode.ToString(),
Options = Enum.GetValues<PluginFilterMode>().ToList(),
SelectedOption = config.BisBuddyMode,
OnOptionSelected = selected =>
{
if (Enum.TryParse<PluginFilterMode>(selected, out var parsed))
{
config.BisBuddyMode = parsed;
if (parsed == PluginFilterMode.Categorize)
config.BisBuddyMode = selected;
if (selected == PluginFilterMode.Categorize)
HighlightState.ClearFilter(HighlightSource.AllaganTools);
RefreshInventory();
}
}
};
CheckboxNode bisBuddyEnabled = new CheckboxNode
@@ -121,25 +118,24 @@ public sealed class CategoryGeneralConfigurationNode : TabbedVerticalListNode
bool allaganReady = System.IPC.AllaganTools?.IsReady ?? false;
LabeledDropdownNode? atModeDropdown = new LabeledDropdownNode
LabeledEnumDropdownNode<PluginFilterMode>? atModeDropdown = new LabeledEnumDropdownNode<PluginFilterMode>
{
Size = new Vector2(300, 20),
Size = new Vector2(500, 20),
LabelText = "Filter Display Mode",
LabelTextFlags = TextFlags.AutoAdjustNodeSize,
IsEnabled = config.AllaganToolsCategoriesEnabled && allaganReady,
Options = Enum.GetNames(typeof(PluginFilterMode)).ToList(),
SelectedOption = config.AllaganToolsFilterMode.ToString(),
Options = Enum.GetValues<PluginFilterMode>().ToList(),
SelectedOption = config.AllaganToolsFilterMode,
OnOptionSelected = selected =>
{
if (Enum.TryParse<PluginFilterMode>(selected, out var parsed))
config.AllaganToolsFilterMode = selected;
if (selected == PluginFilterMode.Categorize)
{
config.AllaganToolsFilterMode = parsed;
if (parsed == PluginFilterMode.Categorize)
HighlightState.ClearFilter(HighlightSource.AllaganTools);
}
RefreshInventory();
}
}
};
_allaganToolsCheckbox = new CheckboxNode
@@ -7,7 +7,6 @@ namespace AetherBags.Nodes.Configuration.Category;
public sealed class CategoryScrollingAreaNode : ScrollingListNode
{
private AddonCategoryConfigurationWindow? _categoryConfigurationAddon;
private readonly TextButtonNode _categoryConfigurationButtonNode;
public CategoryScrollingAreaNode()
{
@@ -15,13 +14,13 @@ public sealed class CategoryScrollingAreaNode : ScrollingListNode
AddNode(new CategoryGeneralConfigurationNode());
_categoryConfigurationButtonNode = new TextButtonNode
var categoryConfigurationButtonNode = new TextButtonNode
{
Size = new Vector2(300, 28),
String = "Configure Categories",
OnClick = () => _categoryConfigurationAddon?.Toggle(),
};
AddNode(_categoryConfigurationButtonNode);
AddNode(categoryConfigurationButtonNode);
}
private void InitializeCategoryAddon() {
@@ -33,4 +32,18 @@ public sealed class CategoryScrollingAreaNode : ScrollingListNode
Title = "Category Configuration Window",
};
}
protected override void Dispose(bool disposing, bool isNativeDestructor)
{
if (disposing)
{
if (_categoryConfigurationAddon != null)
{
_categoryConfigurationAddon.Close();
_categoryConfigurationAddon = null;
}
}
base.Dispose(disposing, isNativeDestructor);
}
}
@@ -0,0 +1,114 @@
using System;
using System.Linq;
using AetherBags.Addons;
using AetherBags.Configuration;
using Lumina.Excel.Sheets;
using Action = System.Action;
namespace AetherBags.Nodes.Configuration.Category;
public sealed class ListFiltersSection(Func<UserCategoryDefinition> getCategoryDefinition) : ConfigurationSection(getCategoryDefinition)
{
public Action? OnListChanged { get; init; }
private UintListEditorNode? _itemIdsEditor;
private StringListEditorNode? _namePatternsEditor;
private UintListEditorNode? _uiCategoriesEditor;
private RarityEditorNode? _raritiesEditor;
private bool _initialized;
private AddonItemPicker? _itemPicker;
private AddonUICategoryPicker? _categoryPicker;
private void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
_itemIdsEditor = new UintListEditorNode
{
Label = "Allowed Item IDs:",
LabelResolver = CategoryDefinitionConfigurationNode.ResolveItemName,
OnSearchButtonClicked = OpenItemPicker,
OnChanged = () =>
{
OnListChanged?.Invoke();
RefreshLayout();
},
};
AddNode(_itemIdsEditor);
_namePatternsEditor = new StringListEditorNode
{
Label = "Name Patterns (Regex):",
OnChanged = () =>
{
OnListChanged?.Invoke();
RefreshLayout();
},
};
AddNode(_namePatternsEditor);
_uiCategoriesEditor = new UintListEditorNode
{
Label = "UI Categories:",
LabelResolver = CategoryDefinitionConfigurationNode.ResolveUiCategoryName,
OnSearchButtonClicked = OpenCategoryPicker,
OnChanged = () =>
{
OnListChanged?.Invoke();
RefreshLayout();
},
};
AddNode(_uiCategoriesEditor);
_raritiesEditor = new RarityEditorNode
{
OnChanged = () => OnValueChanged?.Invoke(),
};
AddNode(_raritiesEditor);
RecalculateLayout();
}
private void OpenItemPicker() {
_itemPicker ??= new AddonItemPicker
{
Title = "Select Items to Add",
InternalName = "Aetherbags_ItemPicker",
SearchOptions = Services.DataManager.GetExcelSheet<Item>()
.Where(i => i.RowId > 0 && !i.Name.IsEmpty)
.ToList(),
SortingOptions = ["Alphabetical", "Id"],
ItemSpacing = 3.0f,
};
_itemPicker.SelectionResult = item => _itemIdsEditor?.AddValue(item.RowId);
_itemPicker.Open();
}
private void OpenCategoryPicker() {
_categoryPicker ??= new AddonUICategoryPicker {
Title = "Select Categories to Add",
InternalName = "Aetherbags_CategoryPicker",
SearchOptions = Services.DataManager.GetExcelSheet<ItemUICategory>()
.Where(i => i.RowId > 0)
.ToList()
};
_categoryPicker.SelectionResult = cat => _uiCategoriesEditor?.AddValue(cat.RowId);
_categoryPicker.Open();
}
public override void Refresh()
{
EnsureInitialized();
_itemIdsEditor!.SetList(CategoryDefinition.Rules.AllowedItemIds);
_namePatternsEditor!.SetList(CategoryDefinition.Rules.AllowedItemNamePatterns);
_uiCategoriesEditor!.SetList(CategoryDefinition.Rules.AllowedUiCategoryIds);
_raritiesEditor!.SetList(CategoryDefinition.Rules.AllowedRarities);
RecalculateLayout();
}
}
@@ -3,6 +3,7 @@ using System.Numerics;
using AetherBags.Configuration;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes.Configuration.Category;
@@ -14,9 +15,9 @@ public sealed class RangeFilterRow : VerticalListNode
public Action<bool, int, int>? OnFilterChanged { get; set; }
public required string Label
public required ReadOnlySeString Label
{
get => _enabledCheckbox.String.Replace(" Filter", "");
get => _enabledCheckbox.String.ExtractText().Replace(" Filter", "");
init => _enabledCheckbox.String = $"{value} Filter";
}
@@ -113,9 +114,9 @@ public sealed class RangeFilterRowUint : VerticalListNode
public Action<bool, uint, uint>? OnFilterChanged { get; set; }
public required string Label
public required ReadOnlySeString Label
{
get => _enabledCheckbox.String.Replace(" Filter", "");
get => _enabledCheckbox.String.ExtractText().Replace(" Filter", "");
init => _enabledCheckbox.String = $"{value} Filter";
}
@@ -0,0 +1,77 @@
using System;
using AetherBags.Configuration;
namespace AetherBags.Nodes.Configuration.Category;
public sealed class RangeFiltersSection(Func<UserCategoryDefinition> getCategoryDefinition) : ConfigurationSection(getCategoryDefinition)
{
private RangeFilterRow? _levelFilter;
private RangeFilterRow? _itemLevelFilter;
private RangeFilterRowUint? _vendorPriceFilter;
private bool _initialized;
private void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
_levelFilter = new RangeFilterRow
{
Label = "Level",
MinBound = 0,
MaxBound = 200,
OnFilterChanged = (enabled, min, max) =>
{
CategoryDefinition.Rules.Level.Enabled = enabled;
CategoryDefinition.Rules.Level.Min = min;
CategoryDefinition.Rules.Level.Max = max;
OnValueChanged?.Invoke();
},
};
AddNode(_levelFilter);
_itemLevelFilter = new RangeFilterRow
{
Label = "Item Level",
MinBound = 0,
MaxBound = 2000,
OnFilterChanged = (enabled, min, max) =>
{
CategoryDefinition.Rules.ItemLevel.Enabled = enabled;
CategoryDefinition.Rules.ItemLevel.Min = min;
CategoryDefinition.Rules.ItemLevel.Max = max;
OnValueChanged?.Invoke();
},
};
AddNode(_itemLevelFilter);
_vendorPriceFilter = new RangeFilterRowUint
{
Label = "Vendor Price",
MinBound = 0,
MaxBound = 9_999_999,
OnFilterChanged = (enabled, min, max) =>
{
CategoryDefinition.Rules.VendorPrice.Enabled = enabled;
CategoryDefinition.Rules.VendorPrice.Min = min;
CategoryDefinition.Rules.VendorPrice.Max = max;
OnValueChanged?.Invoke();
},
};
AddNode(_vendorPriceFilter);
RecalculateLayout();
}
public override void Refresh()
{
EnsureInitialized();
_levelFilter!.SetFilter(CategoryDefinition.Rules.Level);
_itemLevelFilter!.SetFilter(CategoryDefinition.Rules.ItemLevel);
_vendorPriceFilter!.SetFilter(CategoryDefinition.Rules.VendorPrice);
RecalculateLayout();
}
}
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using AetherBags.Configuration;
namespace AetherBags.Nodes.Configuration.Category;
public sealed class StateFiltersSection(Func<UserCategoryDefinition> getCategoryDefinition)
: ConfigurationSection(getCategoryDefinition)
{
private readonly List<(StateFilterRowNode Node, Func<UserCategoryDefinition, StateFilter> GetFilter)> _filters = [];
private bool _initialized;
private void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
AddFilter("Untradable", def => def.Rules.Untradable);
AddFilter("Unique", def => def.Rules.Unique);
AddFilter("Collectable", def => def.Rules.Collectable);
AddFilter("Dyeable", def => def.Rules.Dyeable);
AddFilter("Repairable", def => def.Rules.Repairable);
AddFilter("High Quality", def => def.Rules.HighQuality);
AddFilter("Desynthesizable", def => def.Rules.Desynthesizable);
AddFilter("Glamourable", def => def.Rules.Glamourable);
AddFilter("Spiritbonded", def => def.Rules.FullySpiritbonded);
RecalculateLayout();
}
private void AddFilter(string label, Func<UserCategoryDefinition, StateFilter> getFilter)
{
var node = new StateFilterRowNode(label, new StateFilter(), () => OnValueChanged?.Invoke());
_filters.Add((node, getFilter));
AddNode(node);
}
public override void Refresh()
{
EnsureInitialized();
foreach (var (node, getFilter) in _filters)
{
node.SetState(getFilter(CategoryDefinition));
}
RecalculateLayout();
}
}
@@ -4,6 +4,7 @@ using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes.Configuration.Category;
@@ -16,12 +17,11 @@ public sealed class StringListEditorNode : VerticalListNode
private readonly LabelTextNode _headerLabel;
private readonly VerticalListNode _itemsContainer;
private readonly HorizontalListNode _addRow;
private readonly TextInputNode _addInput;
public Action? OnChanged { get; set; }
public required string Label
public required ReadOnlySeString Label
{
get => _headerLabel.String;
init => _headerLabel.String = value;
@@ -49,7 +49,7 @@ public sealed class StringListEditorNode : VerticalListNode
};
AddNode(_itemsContainer);
_addRow = new HorizontalListNode
var addRow = new HorizontalListNode
{
Size = new Vector2(LabelWidth + 40f, RowHeight),
ItemSpacing = 4.0f,
@@ -61,7 +61,7 @@ public sealed class StringListEditorNode : VerticalListNode
PlaceholderString = "Add new...",
OnInputComplete = _ => AddCurrentValue(),
};
_addRow.AddNode(_addInput);
addRow.AddNode(_addInput);
var addButton = new TextButtonNode
{
@@ -69,9 +69,9 @@ public sealed class StringListEditorNode : VerticalListNode
String = "Add",
OnClick = AddCurrentValue,
};
_addRow.AddNode(addButton);
addRow.AddNode(addButton);
AddNode(_addRow);
AddNode(addRow);
}
public void SetList(List<string> newList)
@@ -82,7 +82,7 @@ public sealed class StringListEditorNode : VerticalListNode
private void AddCurrentValue()
{
var value = _addInput.String;
var value = _addInput.String.ExtractText();
if (!string.IsNullOrWhiteSpace(value) && !_list.Contains(value))
{
_list.Add(value);
@@ -0,0 +1,31 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
using Lumina.Excel.Sheets;
namespace AetherBags.Nodes.Configuration.Category;
public class UICategoryListItemNode : ListItemNode<ItemUICategory> {
public override float ItemHeight => 30.0f;
protected readonly TextNode LabelTextNode;
public UICategoryListItemNode() {
LabelTextNode = new TextNode {
FontSize = 14,
AlignmentType = AlignmentType.Left,
TextColor = ColorHelper.GetColor(8),
};
LabelTextNode.AttachNode(this);
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
LabelTextNode.Size = Size with { X = Width - 10 };
LabelTextNode.Position = new Vector2(5, 0);
}
protected override void SetNodeData(ItemUICategory data) {
LabelTextNode.String = data.Name.ToString();
}
}
@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using AetherBags.Configuration;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes.Configuration.Category;
@@ -14,15 +17,18 @@ public sealed class UintListEditorNode : VerticalListNode
private List<uint> _list = [];
public List<uint> GetList() => _list.ToList();
private readonly LabelTextNode _headerLabel;
private readonly VerticalListNode _itemsContainer;
private readonly HorizontalListNode _addRow;
private readonly NumericInputNode _addInput;
public Action? OnSearchButtonClicked { get; init; }
public Func<uint, string>? LabelResolver { get; init; }
public Action? OnChanged { get; set; }
public required string Label
public required ReadOnlySeString Label
{
get => _headerLabel.String;
init => _headerLabel.String = value;
@@ -50,12 +56,21 @@ public sealed class UintListEditorNode : VerticalListNode
};
AddNode(_itemsContainer);
_addRow = new HorizontalListNode
var addRow = new HorizontalListNode
{
Size = new Vector2(LabelWidth + 40f, RowHeight),
ItemSpacing = 4.0f,
};
var searchButton = new CircleButtonNode
{
Size = new Vector2(28),
Icon = ButtonIcon.MagnifyingGlass,
OnClick = () => OnSearchButtonClicked?.Invoke(),
TextTooltip = "Search the game database..."
};
addRow.AddNode(searchButton);
_addInput = new NumericInputNode
{
Size = new Vector2(120, RowHeight),
@@ -63,7 +78,7 @@ public sealed class UintListEditorNode : VerticalListNode
Max = int.MaxValue,
Value = 0,
};
_addRow.AddNode(_addInput);
addRow.AddNode(_addInput);
var addButton = new TextButtonNode
{
@@ -71,9 +86,10 @@ public sealed class UintListEditorNode : VerticalListNode
String = "Add",
OnClick = AddCurrentValue,
};
_addRow.AddNode(addButton);
AddNode(_addRow);
addRow.AddNode(addButton);
addRow.RecalculateLayout();
AddNode(addRow);
RecalculateLayout();
}
public void SetList(List<uint> newList)
@@ -82,6 +98,16 @@ public sealed class UintListEditorNode : VerticalListNode
RefreshItems();
}
public void AddValue(uint value)
{
if (!_list.Contains(value))
{
_list.Add(value);
RefreshItems();
OnChanged?.Invoke();
}
}
private void AddCurrentValue()
{
var value = (uint)_addInput.Value;
@@ -109,6 +135,7 @@ public sealed class UintListEditorNode : VerticalListNode
_itemsContainer.RecalculateLayout();
RecalculateLayout();
OnChanged?.Invoke();
}
private UintListItemNode CreateItemNode(uint value) => new(value, LabelResolver)
@@ -120,8 +147,10 @@ public sealed class UintListEditorNode : VerticalListNode
private void RemoveValue(uint value)
{
_list.Remove(value);
Services.Framework.RunOnTick(() => {
RefreshItems();
OnChanged?.Invoke();
});
}
}
@@ -137,9 +166,15 @@ public sealed class UintListItemNode : HorizontalListNode
Value = value;
ItemSpacing = 4.0f;
string idDisplay = value switch {
0xFFFF_FFFE => "[Weekly]",
0xFFFF_FFFD => "[Tome]",
_ => value.ToString()
};
var displayText = labelResolver is not null
? $"{value} - {labelResolver(value)}"
: value.ToString();
? $"{idDisplay} - {labelResolver(value)}"
: idDisplay;
AddNode(new LabelTextNode
{
@@ -1,22 +1,29 @@
using System;
using System.Numerics;
using AetherBags.Addons;
using AetherBags.Configuration;
using AetherBags.Nodes.Color;
using AetherBags.Nodes.Configuration.Category;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
using Lumina.Excel.Sheets;
namespace AetherBags.Nodes.Configuration.Currency;
public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
{
private readonly UintListEditorNode? _currencyListEditor;
public CurrencyGeneralConfigurationNode()
{
CurrencySettings config = System.Config.Currency;
Width = 600;
ItemVerticalSpacing = 2;
LabelTextNode titleNode = new LabelTextNode
{
Size = Size with { Y = 18 },
Size = new Vector2(Width, 18),
String = "Currency Configuration",
TextColor = ColorHelper.GetColor(2),
TextOutlineColor = ColorHelper.GetColor(0),
@@ -27,7 +34,7 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
CheckboxNode currencyEnabledCheckbox = new CheckboxNode
{
Size = Size with { Y = 18 },
Size = new Vector2(Width, 18),
IsVisible = true,
String = "Show Currency",
IsChecked = config.Enabled,
@@ -41,21 +48,23 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
AddTab(1);
var defaultColorHandler = CreateColorHandler(color => config.DefaultColor = color);
ColorInputRow defaultCurrencyColorNode = new ColorInputRow
{
Label = "Default Currency Color",
Size = new Vector2(300, 24),
CurrentColor = config.DefaultColor,
DefaultColor = new CurrencySettings().DefaultColor,
OnColorConfirmed = ApplyColorChange,
OnColorChange = ApplyColorChange,
OnColorCanceled = ApplyColorChange,
OnColorConfirmed = defaultColorHandler,
OnColorChange = defaultColorHandler,
OnColorCanceled = defaultColorHandler,
OnColorPreviewed = defaultColorHandler,
};
AddNode(defaultCurrencyColorNode);
CheckboxNode cappedEnabledCheckbox = new CheckboxNode
{
Size = Size with { Y = 18 },
Size = new Vector2(Width, 18),
IsVisible = true,
String = "Color Weekly Cap",
IsChecked = config.ColorWhenCapped,
@@ -70,18 +79,17 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
AddTab(1);
var cappedColorHandler = CreateColorHandler(color => config.CappedColor = color);
ColorInputRow cappedCurrencyColorNode = new ColorInputRow
{
Label = "Weekly Cap Color",
Size = new Vector2(300, 24),
CurrentColor = config.CappedColor,
DefaultColor = new CurrencySettings().CappedColor,
OnColorConfirmed = color =>
{
config.CappedColor = color;
RefreshCurrency();
},
OnColorConfirmed = cappedColorHandler,
OnColorChange = cappedColorHandler,
OnColorCanceled = cappedColorHandler,
OnColorPreviewed = cappedColorHandler,
};
AddNode(cappedCurrencyColorNode);
@@ -89,7 +97,7 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
CheckboxNode limitedEnabledCheckbox = new CheckboxNode
{
Size = Size with { Y = 18 },
Size = new Vector2(Width, 18),
IsVisible = true,
String = "Color Max Capacity",
IsChecked = config.ColorWhenLimited,
@@ -104,28 +112,83 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
AddTab(1);
var limitColorHandler = CreateColorHandler(color => config.LimitColor = color);
ColorInputRow limitCurrencyColorNode = new ColorInputRow
{
Label = "Max Capacity Color",
Size = new Vector2(300, 24),
CurrentColor = config.LimitColor,
DefaultColor = new CurrencySettings().LimitColor,
OnColorConfirmed = color =>
{
config.LimitColor = color;
RefreshCurrency();
},
OnColorConfirmed = limitColorHandler,
OnColorChange = limitColorHandler,
OnColorCanceled = limitColorHandler,
OnColorPreviewed = limitColorHandler,
};
AddNode(limitCurrencyColorNode);
return;
AddNode(new ResNode { Size = new Vector2(15) });
void ApplyColorChange(Vector4 color)
SubtractTab(2);
AddNode(new ResNode { Size = new Vector2(15) });
_currencyListEditor = new UintListEditorNode
{
config.DefaultColor = color;
Label = "Displayed Currencies:",
LabelResolver = id =>
{
return id switch
{
CurrencySettings.LimitedTomestoneId => "Current Limited Tomestone",
CurrencySettings.NonLimitedTomestoneId => "Current Non-Limited Tomestone",
_ => Services.DataManager.GetExcelSheet<Item>().GetRow(id).Name.ToString()
};
},
OnSearchButtonClicked = OpenCurrencyPicker,
OnChanged = () => {
System.Config.Currency.DisplayedCurrencies = _currencyListEditor!.GetList();
RefreshCurrency();
RecalculateLayout();
}
};
_currencyListEditor.SetList(System.Config.Currency.DisplayedCurrencies);
AddNode(_currencyListEditor);
var quickAddRow = new HorizontalListNode { Size = new Vector2(600, 30), ItemSpacing = 8.0f };
quickAddRow.AddNode(new TextButtonNode {
String = "+ Gil", Size = new Vector2(70, 24),
OnClick = () => _currencyListEditor?.AddValue(1)
});
quickAddRow.AddNode(new TextButtonNode {
String = "+ Limited Tomestone", Size = new Vector2(150, 24),
OnClick = () => _currencyListEditor?.AddValue(CurrencySettings.LimitedTomestoneId)
});
quickAddRow.AddNode(new TextButtonNode {
String = "+ Non-Limited", Size = new Vector2(110, 24),
OnClick = () => _currencyListEditor?.AddValue(CurrencySettings.NonLimitedTomestoneId)
});
AddNode(quickAddRow);
RecalculateLayout();
}
private Action<Vector4> CreateColorHandler(Action<Vector4> setter) => newColor =>
{
setter(newColor);
RefreshCurrency();
};
private void RefreshCurrency() => System.AddonInventoryWindow.ManualCurrencyRefresh();
private void OpenCurrencyPicker() {
var picker = new AddonCurrencyPicker
{
Title = "Select Currency to Add",
InternalName = "AetherBags_CurrencyPicker",
};
picker.SelectionResult = item => _currencyListEditor?.AddValue(item.RowId);
picker.Open();
}
}
@@ -1,3 +1,4 @@
using System.Numerics;
using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Configuration.Currency;
@@ -8,7 +9,7 @@ public sealed class CurrencyScrollingAreaNode : ScrollingListNode
{
AddNode(new CurrencyGeneralConfigurationNode
{
Size = Size
Width = 600
});
}
}
@@ -14,7 +14,7 @@ internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode
private readonly CheckboxNode _hideDefaultBagsCheckboxNode;
private readonly CheckboxNode _hideSaddlebagsCheckboxNode;
private readonly CheckboxNode _hideRetainerbagsCheckboxNode;
private readonly LabeledDropdownNode _stackDropDown;
private readonly LabeledEnumDropdownNode<InventoryStackMode> _stackDropDown;
public FunctionalConfigurationNode()
{
@@ -139,40 +139,34 @@ internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode
Height = 6
});
var searchModeDropDown = new LabeledDropdownNode
var searchModeDropDown = new LabeledEnumDropdownNode<SearchMode>
{
Size = new Vector2(300, 20),
Size = new Vector2(500, 20),
LabelText = "Search Mode",
LabelTextFlags = TextFlags.AutoAdjustNodeSize,
Options = Enum.GetNames(typeof(SearchMode)).ToList(),
SelectedOption = config.SearchMode.ToString(),
Options = Enum.GetValues<SearchMode>().ToList(),
SelectedOption = config.SearchMode,
OnOptionSelected = selected =>
{
if (Enum.TryParse<SearchMode>(selected, out var parsed))
{
config.SearchMode = parsed;
config.SearchMode = selected;
InventoryOrchestrator.RefreshAll(updateMaps: false);
}
}
};
AddNode(searchModeDropDown);
_stackDropDown = new LabeledDropdownNode
_stackDropDown = new LabeledEnumDropdownNode<InventoryStackMode>
{
Size = new Vector2(300, 20),
Size = new Vector2(500, 20),
IsEnabled = true,
LabelText = "Stack Mode",
LabelTextFlags = TextFlags.AutoAdjustNodeSize,
Options = Enum.GetNames(typeof(InventoryStackMode)).ToList(),
SelectedOption = config.StackMode.ToString(),
Options = Enum.GetValues<InventoryStackMode>().ToList(),
SelectedOption = config.StackMode,
OnOptionSelected = selected =>
{
if (Enum.TryParse<InventoryStackMode>(selected, out var parsed))
{
config.StackMode = parsed;
config.StackMode = selected;
InventoryOrchestrator.RefreshAll(updateMaps: true);
}
}
};
AddNode(_stackDropDown);
}
@@ -3,6 +3,7 @@ using AetherBags.Helpers;
using AetherBags.Inventory;
using Dalamud.Game.ClientState.Keys;
using KamiToolKit.Classes;
using KamiToolKit.Enums;
using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Configuration.General;
@@ -1,9 +1,9 @@
using AetherBags.Configuration;
using KamiToolKit.Classes.Timelines;
using KamiToolKit.Nodes;
using System.Numerics;
using AetherBags.Inventory;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Timelines;
namespace AetherBags.Nodes.Configuration.Layout;
@@ -2,27 +2,28 @@ using System;
using System.Collections.Generic;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes.Input;
public class LabeledDropdownNode : SimpleComponentNode {
public class LabeledEnumDropdownNode<T> : SimpleComponentNode where T : Enum {
private readonly GridNode _gridNode;
private readonly TextNode _labelNode;
private readonly TextDropDownNode _dropDownNode;
private readonly EnumDropDownNode<T> _dropDownNode;
public LabeledDropdownNode() {
public LabeledEnumDropdownNode() {
_gridNode = new GridNode {
GridSize = new GridSize(2, 1),
};
_gridNode.AttachNode(this);
_labelNode = new LabelTextNode {
String = String.Empty,
String = string.Empty,
};
_labelNode.AttachNode(_gridNode[0, 0]);
_dropDownNode = new TextDropDownNode {
Options = new List<string>(),
_dropDownNode = new EnumDropDownNode<T> {
Options = new List<T>(),
};
_dropDownNode.AttachNode(_gridNode[1, 0]);
}
@@ -36,25 +37,38 @@ public class LabeledDropdownNode : SimpleComponentNode {
_dropDownNode.Size = _gridNode[1, 0].Size;
}
public required string LabelText
public required ReadOnlySeString LabelText
{
get => _labelNode.String;
set => _labelNode.String = value;
}
public Action<string>? OnOptionSelected
public Action<T>? OnOptionSelected
{
get => _dropDownNode.OnOptionSelected;
set => _dropDownNode.OnOptionSelected = value;
}
public string? SelectedOption
public T? SelectedOption
{
get => _dropDownNode.SelectedOption;
set => _dropDownNode.SelectedOption = value;
get => _dropDownNode.OptionListNode.SelectedOption;
set
{
_dropDownNode.OptionListNode.SelectedOption = value;
if (value != null)
{
_dropDownNode.LabelNode.String = value.Description;
}
}
}
public required List<string> Options
public int MaxListOptions
{
get => _dropDownNode.MaxListOptions;
set => _dropDownNode.MaxListOptions = value;
}
public required List<T> Options
{
get => _dropDownNode.Options!;
set => _dropDownNode.Options = value;
@@ -49,7 +49,7 @@ public class TextInputWithButtonNode : SimpleComponentNode {
}
public ReadOnlySeString SearchString {
get => _textInputNode.SeString;
set => _textInputNode.SeString = value;
get => _textInputNode.String;
set => _textInputNode.String = value;
}
}
@@ -61,7 +61,7 @@ public class InventoryCategoryNode : InventoryCategoryNodeBase
_categoryNameTextNode.TextFlags |= TextFlags.OverflowHidden | TextFlags.Ellipsis;
_categoryNameTextNode.TextFlags &= ~(TextFlags.WordWrap | TextFlags.MultiLine);
_categoryNameTextNode.AddFlags(NodeFlags.EmitsEvents | NodeFlags.HasCollision);
_categoryNameTextNode.AddNodeFlags(NodeFlags.EmitsEvents | NodeFlags.HasCollision);
_categoryNameTextNode.AttachNode(this);
_itemGridNode = new HybridDirectionalFlexNode<DragDropNode>
@@ -18,7 +18,7 @@ public class InventoryDragDropNode : DragDropNode
Size = new Vector2(40.0f, 12.0f),
Position = new Vector2(4.0f, 34.0f),
NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents,
Color = ColorHelper.GetColor(50),
TextColor = ColorHelper.GetColor(50),
TextOutlineColor = ColorHelper.GetColor(51),
TextFlags = TextFlags.Edge,
AlignmentType = AlignmentType.Right,
@@ -5,7 +5,7 @@ using AetherBags.Nodes.Currency;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
using static AetherBags.Inventory.State.InventoryStateBase;
namespace AetherBags.Nodes.Inventory;
@@ -33,7 +33,8 @@ public sealed class InventoryFooterNode : SimpleComponentNode
{
Position = new Vector2(0, 0),
Size = new Vector2(120, 28),
IsVisible = System.Config.Currency.Enabled
IsVisible = System.Config.Currency.Enabled,
ItemSpacing = 12f,
};
_currencyListNode.AttachNode(this);
@@ -42,9 +43,13 @@ public sealed class InventoryFooterNode : SimpleComponentNode
public void RefreshCurrencies()
{
_currencyListNode.IsVisible = System.Config.Currency.Enabled;
var config = System.Config.Currency;
_currencyListNode.IsVisible = config.Enabled;
IReadOnlyList<CurrencyInfo> currencyInfoList = GetCurrencyInfoList([1, 28, 0xFFFF_FFFE, 0xFFFF_FFFD]);
if (!config.Enabled) return;
//IReadOnlyList<CurrencyInfo> currencyInfoList = GetCurrencyInfoList([1, 28, 0xFFFF_FFFE, 0xFFFF_FFFD]);
IReadOnlyList<CurrencyInfo> currencyInfoList = GetCurrencyInfoList(config.DisplayedCurrencies.ToArray());
_currencyListNode.SyncWithListDataByKey<CurrencyInfo, CurrencyNode, uint>(
dataList: currencyInfoList,
getKeyFromData: currencyInfo => currencyInfo.ItemId,
@@ -60,7 +65,7 @@ public sealed class InventoryFooterNode : SimpleComponentNode
});
}
public string SlotAmountText
public ReadOnlySeString SlotAmountText
{
get => _slotAmountTextNode.String;
set => _slotAmountTextNode.String = value;
@@ -2,8 +2,8 @@ using System.Numerics;
using AetherBags.Inventory.Context;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Classes.Timelines;
using KamiToolKit.Nodes;
using KamiToolKit.Timelines;
namespace AetherBags.Nodes.Inventory;
@@ -13,8 +13,6 @@ public sealed class InventoryNotificationNode : SimpleComponentNode
private readonly TextNode titleTextNode;
private readonly TextNode messageTextNode;
private static readonly InventoryNotificationState NotificationState = new();
public InventoryNotificationNode()
{
AddTimeline(ParentLabels);
@@ -76,8 +74,8 @@ public sealed class InventoryNotificationNode : SimpleComponentNode
{
field = value;
titleTextNode.SeString = value.Title;
messageTextNode.SeString = value.Message;
titleTextNode.String = value.Title;
messageTextNode.String = value.Message;
if (value.Title.IsEmpty && value.Message.IsEmpty)
{
@@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using AetherBags.Inventory.Items;
using AetherBags.Nodes.Layout;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Inventory;
@@ -62,7 +63,7 @@ public class LootedItemsCategoryNode : InventoryCategoryNodeBase
AlignmentType = AlignmentType.Left,
String = "Recently Looted",
TextFlags = TextFlags.OverflowHidden | TextFlags.Ellipsis,
TextColor = new Vector4(0.9f, 0.8f, 0.5f, 1.0f), // Gold-ish color
TextColor = ColorHelper.GetColor(26), // Gold-ish color
};
_headerTextNode.AddEvent(AtkEventType.MouseOver, BeginHeaderHover);
@@ -71,7 +72,7 @@ public class LootedItemsCategoryNode : InventoryCategoryNodeBase
_headerTextNode.TextFlags |= TextFlags.OverflowHidden | TextFlags.Ellipsis;
_headerTextNode.TextFlags &= ~(TextFlags.WordWrap | TextFlags.MultiLine);
_headerTextNode.AddFlags(NodeFlags.EmitsEvents | NodeFlags.HasCollision);
_headerTextNode.AddNodeFlags(NodeFlags.EmitsEvents | NodeFlags.HasCollision);
_headerTextNode.AttachNode(this);
_clearButton = new CircleButtonNode
@@ -197,7 +198,7 @@ public class LootedItemsCategoryNode : InventoryCategoryNodeBase
private void SyncItemGrid()
{
_itemGridNode.SyncWithListDataByKey<LootedItemInfo, LootedItemDisplayNode, int>(
_itemGridNode.SyncWithListDataByKey(
dataList: _lootedItems,
getKeyFromData: item => item.Index,
getKeyFromNode: node => node.LootedItem?.Index ?? -1,
@@ -221,6 +222,7 @@ public class LootedItemsCategoryNode : InventoryCategoryNodeBase
private void OnItemDismissed(LootedItemDisplayNode node)
{
if(node.LootedItem is null) return;
int index = node.LootedItem.Index;
OnDismissItem?.Invoke(index);
}
@@ -1,6 +1,7 @@
using System. Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes.Inventory;
@@ -23,7 +24,7 @@ public class SaddleBagFooterNode : SimpleComponentNode
_slotCounterNode.AttachNode(this);
}
public string SlotAmountText
public ReadOnlySeString SlotAmountText
{
get => _slotCounterNode.String;
set => _slotCounterNode.String = $"Slots: {value}";
@@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes.Layout;
public class CollapsibleSectionNode : VerticalListNode
{
protected readonly NineGridNode BackgroundNode;
protected readonly ImageNode ArrowNode;
protected readonly TextNode LabelNode;
protected new readonly CollisionNode CollisionNode;
protected readonly TabbedVerticalListNode ContentNode;
protected readonly SimpleComponentNode HeaderNode;
private bool isCollapsed = true;
private float headerHeight = 28.0f;
public Action? OnToggle;
public TabbedVerticalListNode CollapsibleContent => ContentNode;
public bool IsCollapsed
{
get => isCollapsed;
set { isCollapsed = value; UpdateState(); }
}
public float HeaderHeight
{
get => headerHeight;
set
{
headerHeight = value;
HeaderNode.Height = value;
BackgroundNode.Height = value;
CollisionNode.Height = value;
ArrowNode.Y = (value - ArrowNode.Height) / 2.0f;
LabelNode.Height = value;
RecalculateLayout();
}
}
public uint FontSize { get => LabelNode.FontSize; set => LabelNode.FontSize = value; }
public float TabSize
{
get => ContentNode.TabSize;
set => ContentNode.TabSize = value;
}
public int TabStep
{
get => ContentNode.TabStep;
set => ContentNode.TabStep = value;
}
public bool FitChildWidth
{
get => ContentNode.FitWidth;
set => ContentNode.FitWidth = value;
}
public float NestingIndent
{
get;
set
{
field = value;
ArrowNode.X = value + 4.0f;
LabelNode.X = value + 23.0f;
ContentNode.X = value + 10.0f;
}
}
public CollapsibleSectionNode()
{
FitContents = true;
ItemSpacing = 0.0f;
HeaderNode = new SimpleComponentNode
{
Size = new Vector2(Width, headerHeight)
};
BackgroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/ListItemB.tex",
TextureSize = new Vector2(48.0f, 28.0f),
TextureCoordinates = new Vector2(0.0f, 24.0f),
Size = new Vector2(Width, headerHeight),
TopOffset = 10, LeftOffset = 12, RightOffset = 12, BottomOffset = 12,
Color = new Vector4(0.9f, 0.9f, 0.9f, 1.0f)
};
BackgroundNode.AttachNode(HeaderNode);
ArrowNode = new ImageNode { Position = new Vector2(4.0f, 2.0f), Size = new Vector2(24.0f, 24.0f) };
ArrowNode.AddPart(
new Part { TexturePath = "ui/uld/ListItemB.tex", TextureCoordinates = new Vector2(0, 0), Size = new Vector2(24, 24), Id = 0 },
new Part { TexturePath = "ui/uld/ListItemB.tex", TextureCoordinates = new Vector2(24, 0), Size = new Vector2(24, 24), Id = 1 }
);
ArrowNode.AttachNode(HeaderNode);
LabelNode = new TextNode {
Position = new Vector2(30.0f, 0.0f),
Size = new Vector2(Width - 23, headerHeight),
FontSize = 12,
FontType = FontType.Axis,
AlignmentType = AlignmentType.Left,
TextColor = ColorHelper.GetColor(50),
};
LabelNode.AttachNode(HeaderNode);
CollisionNode = new CollisionNode
{
Size = new Vector2(Width, headerHeight),
ShowClickableCursor = true
};
CollisionNode.AddEvent(AtkEventType.MouseClick, () => {
IsCollapsed = !IsCollapsed;
OnToggle?.Invoke();
});
CollisionNode.AttachNode(HeaderNode);
ContentNode = new TabbedVerticalListNode {
IsVisible = false,
X = 18.0f,
ItemVerticalSpacing = 4.0f,
TabSize = 18.0f,
FitWidth = true,
};
base.AddNode([HeaderNode, ContentNode]);
UpdateState();
}
public void RefreshLayout()
{
ContentNode.RecalculateLayout();
RecalculateLayout();
OnToggle?.Invoke();
}
private void UpdateState()
{
ContentNode.IsVisible = !isCollapsed;
ArrowNode.PartId = isCollapsed ? 0u : 1u;
if (!isCollapsed)
{
ContentNode.Width = Math.Max(0, Width - ContentNode.X);
ContentNode.RecalculateLayout();
}
RecalculateLayout();
OnToggle?.Invoke();
}
public void AddTab(int tabAmount = 1) => ContentNode.AddTab(tabAmount);
public void SubtractTab(int tabAmount = 1) => ContentNode.SubtractTab(tabAmount);
public new void AddNode(NodeBase node) => ContentNode.AddNode(node);
public new void AddNode(IEnumerable<NodeBase> nodes) => ContentNode.AddNode(nodes);
public void AddNode(int tabIndex, NodeBase node) => ContentNode.AddNode(tabIndex, node);
public void AddNode(int tabIndex, IEnumerable<NodeBase> nodes) => ContentNode.AddNode(tabIndex, nodes);
public new void RemoveNode(NodeBase node) => ContentNode.RemoveNode(node);
public new void Clear() => ContentNode.Clear();
protected override void OnSizeChanged()
{
base.OnSizeChanged();
if (BackgroundNode == null || LabelNode == null || CollisionNode == null) return;
HeaderNode.Width = Width;
BackgroundNode.Width = Width;
LabelNode.Width = Math.Max(0, Width - LabelNode.X);
CollisionNode.Width = Width;
ContentNode.Width = Math.Max(0, Width - ContentNode.X);
}
public ReadOnlySeString String { get => LabelNode.String; set => LabelNode.String = value; }
}
@@ -49,9 +49,9 @@ public abstract class DeferrableLayoutListNode : SimpleComponentNode
set
{
if (value)
AddFlags(NodeFlags.Clip);
AddNodeFlags(NodeFlags.Clip);
else
RemoveFlags(NodeFlags.Clip);
RemoveNodeFlags(NodeFlags.Clip);
}
}
+6 -8
View File
@@ -1,5 +1,4 @@
using System.Numerics;
using AetherBags.AddonLifecycles;
using AetherBags.Addons;
using AetherBags.Commands;
using AetherBags.Helpers;
@@ -7,6 +6,7 @@ using AetherBags.Hooks;
using AetherBags.Inventory;
using AetherBags.Inventory.Context;
using AetherBags.IPC;
using AetherBags.Monitoring;
using Dalamud.Plugin;
using KamiToolKit;
@@ -16,7 +16,7 @@ public class Plugin : IDalamudPlugin
{
private readonly CommandHandler _commandHandler;
private readonly InventoryHooks _inventoryHooks;
private readonly InventoryLifecycles _inventoryLifecycles;
private readonly InventoryMonitor inventoryMonitor;
public Plugin(IDalamudPluginInterface pluginInterface)
{
@@ -72,14 +72,14 @@ public class Plugin : IDalamudPlugin
}
_inventoryHooks = new InventoryHooks();
_inventoryLifecycles = new InventoryLifecycles();
inventoryMonitor = new InventoryMonitor();
}
public void Dispose()
{
InventoryAddonContextMenu.Close();
_inventoryHooks.Dispose();
_inventoryLifecycles.Dispose();
inventoryMonitor.Dispose();
System.LootedItemsTracker.Dispose();
System.IPC.Dispose();
@@ -99,10 +99,8 @@ public class Plugin : IDalamudPlugin
System.Config = Util.LoadConfigOrDefault();
System.LootedItemsTracker.Enable();
#if DEBUG
System.AddonInventoryWindow.Toggle();
System.AddonConfigurationWindow.Toggle();
#endif
System.AddonInventoryWindow.DebugOpen();
System.AddonConfigurationWindow.DebugOpen();
}
private void OnLogout(int type, int code)
+1
View File
@@ -2,6 +2,7 @@ using AetherBags.Addons;
using AetherBags.Configuration;
using AetherBags.Inventory;
using AetherBags.IPC;
using AetherBags.Monitoring;
namespace AetherBags;