Merge remote-tracking branch 'origin/master' into dev/pie-lover
This commit is contained in:
@@ -14,7 +14,7 @@ namespace AetherBags.Addons;
|
|||||||
|
|
||||||
public class AddonCategoryConfigurationWindow : NativeAddon
|
public class AddonCategoryConfigurationWindow : NativeAddon
|
||||||
{
|
{
|
||||||
private ModifyListNode<CategoryWrapper>? _selectionListNode;
|
private ModifyListNode<CategoryWrapper, CategoryListItemNode>? _selectionListNode;
|
||||||
private VerticalLineNode? _separatorLine;
|
private VerticalLineNode? _separatorLine;
|
||||||
private CategoryConfigurationNode? _configNode;
|
private CategoryConfigurationNode? _configNode;
|
||||||
private TextNode? _nothingSelectedTextNode;
|
private TextNode? _nothingSelectedTextNode;
|
||||||
@@ -28,21 +28,24 @@ public class AddonCategoryConfigurationWindow : NativeAddon
|
|||||||
{
|
{
|
||||||
_categoryWrappers = CreateCategoryWrappers();
|
_categoryWrappers = CreateCategoryWrappers();
|
||||||
|
|
||||||
_selectionListNode = new ModifyListNode<CategoryWrapper>
|
_selectionListNode = new ModifyListNode<CategoryWrapper, CategoryListItemNode>
|
||||||
{
|
{
|
||||||
Position = ContentStartPosition,
|
Position = ContentStartPosition,
|
||||||
Size = new Vector2(250.0f, ContentSize.Y),
|
Size = ContentSize with { X = 250.0f },
|
||||||
SelectionOptions = _categoryWrappers,
|
Options = _categoryWrappers,
|
||||||
OnOptionChanged = OnOptionChanged,
|
SelectionChanged = OnOptionChanged,
|
||||||
AddNewEntry = OnAddNewCategory,
|
AddNewEntry = OnAddNewCategory,
|
||||||
RemoveEntry = OnRemoveCategory,
|
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);
|
_selectionListNode.AttachNode(this);
|
||||||
|
|
||||||
_separatorLine = new VerticalLineNode
|
_separatorLine = new VerticalLineNode
|
||||||
{
|
{
|
||||||
Position = ContentStartPosition + new Vector2(250.0f + 8.0f, 0.0f),
|
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);
|
_separatorLine.AttachNode(this);
|
||||||
|
|
||||||
@@ -78,6 +81,24 @@ public class AddonCategoryConfigurationWindow : NativeAddon
|
|||||||
.ToList();
|
.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)
|
private void OnOptionChanged(CategoryWrapper? newOption)
|
||||||
{
|
{
|
||||||
if (_configNode is null) return;
|
if (_configNode is null) return;
|
||||||
@@ -99,29 +120,11 @@ public class AddonCategoryConfigurationWindow : NativeAddon
|
|||||||
if (_pendingSelectionListRefresh)
|
if (_pendingSelectionListRefresh)
|
||||||
{
|
{
|
||||||
_pendingSelectionListRefresh = false;
|
_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)
|
private void OnRemoveCategory(CategoryWrapper categoryWrapper)
|
||||||
{
|
{
|
||||||
if (categoryWrapper.CategoryDefinition is null) return;
|
if (categoryWrapper.CategoryDefinition is null) return;
|
||||||
@@ -146,6 +149,15 @@ public class AddonCategoryConfigurationWindow : NativeAddon
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_selectionListNode?.UpdateList();
|
_selectionListNode?.RefreshList();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override unsafe void OnFinalize(AtkUnitBase* addon)
|
||||||
|
{
|
||||||
|
_selectionListNode = null;
|
||||||
|
_configNode = null;
|
||||||
|
_separatorLine = null;
|
||||||
|
_nothingSelectedTextNode = null;
|
||||||
|
base.OnFinalize(addon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,11 +10,11 @@ namespace AetherBags.Addons;
|
|||||||
|
|
||||||
public class AddonConfigurationWindow : NativeAddon
|
public class AddonConfigurationWindow : NativeAddon
|
||||||
{
|
{
|
||||||
private TabBarNode _tabBarNode = null!;
|
private TabBarNode? _tabBarNode;
|
||||||
|
|
||||||
private GeneralScrollingAreaNode _generalScrollingAreaNode = null!;
|
private GeneralScrollingAreaNode? _generalScrollingAreaNode;
|
||||||
private CategoryScrollingAreaNode _categoryScrollingAreaNode = null!;
|
private CategoryScrollingAreaNode? _categoryScrollingAreaNode;
|
||||||
private CurrencyScrollingAreaNode _currencyScrollingAreaNode = null!;
|
private CurrencyScrollingAreaNode? _currencyScrollingAreaNode;
|
||||||
|
|
||||||
private readonly List<NodeBase> _tabContent = new();
|
private readonly List<NodeBase> _tabContent = new();
|
||||||
|
|
||||||
@@ -73,4 +73,17 @@ public class AddonConfigurationWindow : NativeAddon
|
|||||||
for (var i = 0; i < _tabContent.Count; i++)
|
for (var i = 0; i < _tabContent.Count; i++)
|
||||||
_tabContent[i].IsVisible = i == index;
|
_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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
@@ -79,7 +79,7 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
|
|||||||
|
|
||||||
LayoutContent();
|
LayoutContent();
|
||||||
|
|
||||||
//addon->SubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
|
addon->SubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
|
||||||
|
|
||||||
System.LootedItemsTracker.OnLootedItemsChanged += OnLootedItemsChanged;
|
System.LootedItemsTracker.OnLootedItemsChanged += OnLootedItemsChanged;
|
||||||
|
|
||||||
@@ -139,6 +139,7 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
|
|||||||
private void OnClearAllLootedItems()
|
private void OnClearAllLootedItems()
|
||||||
{
|
{
|
||||||
System.LootedItemsTracker.Clear();
|
System.LootedItemsTracker.Clear();
|
||||||
|
System.LootedItemsTracker.FlushPendingChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ManualCurrencyRefresh()
|
public void ManualCurrencyRefresh()
|
||||||
@@ -155,7 +156,6 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
|
|||||||
}, delayTicks: 3);
|
}, delayTicks: 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected override void OnFinalize(AtkUnitBase* addon)
|
protected override void OnFinalize(AtkUnitBase* addon)
|
||||||
{
|
{
|
||||||
System.LootedItemsTracker.OnLootedItemsChanged -= OnLootedItemsChanged;
|
System.LootedItemsTracker.OnLootedItemsChanged -= OnLootedItemsChanged;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -1,17 +1,14 @@
|
|||||||
using AetherBags.Configuration;
|
using AetherBags.Configuration;
|
||||||
using AetherBags.Inventory.Categories;
|
using AetherBags.Inventory.Categories;
|
||||||
using KamiToolKit.Premade;
|
|
||||||
|
|
||||||
namespace AetherBags.Addons;
|
namespace AetherBags.Addons;
|
||||||
|
|
||||||
public class CategoryWrapper(UserCategoryDefinition categoryDefinition) : IInfoNodeData
|
// Removed IInfoNodeData implementation
|
||||||
|
public class CategoryWrapper(UserCategoryDefinition categoryDefinition)
|
||||||
{
|
{
|
||||||
public UserCategoryDefinition? CategoryDefinition { get; } = categoryDefinition;
|
public UserCategoryDefinition? CategoryDefinition { get; } = categoryDefinition;
|
||||||
|
|
||||||
public string GetLabel() {
|
public string GetLabel() => CategoryDefinition!.Name;
|
||||||
|
|
||||||
return CategoryDefinition!.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetSubLabel() {
|
public string GetSubLabel() {
|
||||||
if(UserCategoryMatcher.IsCatchAll(CategoryDefinition!)) return " No valid rules!";
|
if(UserCategoryMatcher.IsCatchAll(CategoryDefinition!)) return " No valid rules!";
|
||||||
@@ -20,16 +17,9 @@ public class CategoryWrapper(UserCategoryDefinition categoryDefinition) : IInfoN
|
|||||||
|
|
||||||
public uint? GetId() => null;
|
public uint? GetId() => null;
|
||||||
|
|
||||||
public uint? GetIconId() {
|
public uint? GetIconId() => 0;
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string? GetTexturePath()
|
public int Compare(CategoryWrapper other, string sortingMode) {
|
||||||
=> null;
|
return CategoryDefinition!.Order.CompareTo(other.CategoryDefinition!.Order);
|
||||||
|
|
||||||
public int Compare(IInfoNodeData other, string sortingMode) {
|
|
||||||
if (other is not CategoryWrapper otherWrapper) return 0;
|
|
||||||
|
|
||||||
return CategoryDefinition!.Order.CompareTo(otherWrapper.CategoryDefinition!.Order);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ using AetherBags.Inventory.Context;
|
|||||||
using AetherBags.Inventory.Items;
|
using AetherBags.Inventory.Items;
|
||||||
using AetherBags.Inventory.Scanning;
|
using AetherBags.Inventory.Scanning;
|
||||||
using AetherBags.Inventory.State;
|
using AetherBags.Inventory.State;
|
||||||
|
using AetherBags.Monitoring;
|
||||||
using AetherBags.Nodes.Input;
|
using AetherBags.Nodes.Input;
|
||||||
using AetherBags.Nodes.Inventory;
|
using AetherBags.Nodes.Inventory;
|
||||||
using AetherBags.Nodes.Layout;
|
using AetherBags.Nodes.Layout;
|
||||||
@@ -16,7 +17,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.ContextMenu;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
namespace AetherBags.Addons;
|
namespace AetherBags.Addons;
|
||||||
@@ -65,25 +66,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
|
|||||||
private int _refreshFromLifecycleCount;
|
private int _refreshFromLifecycleCount;
|
||||||
private long _lastLogTick;
|
private long _lastLogTick;
|
||||||
|
|
||||||
public void ManualRefresh()
|
public void ManualRefresh() => ExecuteRefresh(true);
|
||||||
{
|
|
||||||
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 string GetSearchText() => SearchInputNode?.SearchString.ExtractText() ?? string.Empty;
|
public string GetSearchText() => SearchInputNode?.SearchString.ExtractText() ?? string.Empty;
|
||||||
|
|
||||||
@@ -98,21 +81,16 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
|
|||||||
}, delayTicks: 3);
|
}, delayTicks: 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshFromLifecycle()
|
private void ExecuteRefresh(bool autosize)
|
||||||
{
|
{
|
||||||
if (!IsSetupComplete) return;
|
if (!IsSetupComplete || !IsOpen || _isRefreshing) return;
|
||||||
if (!IsOpen) return;
|
|
||||||
if (_isRefreshing) return;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_isRefreshing = true;
|
_isRefreshing = true;
|
||||||
|
|
||||||
_refreshFromLifecycleCount++;
|
|
||||||
LogRefreshStats();
|
|
||||||
|
|
||||||
InventoryState.RefreshFromGame();
|
InventoryState.RefreshFromGame();
|
||||||
RefreshCategoriesCore(autosize: true);
|
System.LootedItemsTracker.FlushPendingChanges();
|
||||||
|
RefreshCategoriesCore(autosize);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -120,6 +98,8 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RefreshFromLifecycle() => ExecuteRefresh(autosize: true);
|
||||||
|
|
||||||
protected virtual void RefreshCategoriesCore(bool autosize)
|
protected virtual void RefreshCategoriesCore(bool autosize)
|
||||||
{
|
{
|
||||||
if (!IsSetupComplete)
|
if (!IsSetupComplete)
|
||||||
@@ -215,25 +195,22 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
|
|||||||
{
|
{
|
||||||
var header = addon->WindowHeaderCollisionNode;
|
var header = addon->WindowHeaderCollisionNode;
|
||||||
float headerW = header->Width;
|
float headerW = header->Width;
|
||||||
float headerH = header->Height;
|
|
||||||
|
|
||||||
// Center the search bar, width is 50% of header
|
float settingsX = headerW - 62f;
|
||||||
float searchWidth = headerW * 0.5f;
|
float itemY = header->Y + (header->Height - 28f) * 0.5f;
|
||||||
var searchSize = new Vector2(searchWidth, 28f);
|
|
||||||
|
|
||||||
|
float searchWidth = headerW * 0.45f;
|
||||||
float searchX = (headerW - searchWidth) * 0.5f;
|
float searchX = (headerW - searchWidth) * 0.5f;
|
||||||
float itemY = header->Y + (headerH - 28f) * 0.5f;
|
|
||||||
|
|
||||||
return new HeaderLayout
|
return new HeaderLayout
|
||||||
{
|
{
|
||||||
SearchPosition = new Vector2(searchX, itemY),
|
SearchPosition = new Vector2(searchX, itemY),
|
||||||
SearchSize = searchSize,
|
SearchSize = new Vector2(searchWidth, 28f),
|
||||||
HeaderWidth = headerW,
|
HeaderWidth = headerW,
|
||||||
HeaderY = itemY
|
HeaderY = itemY
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void InitializeBackgroundDropTarget()
|
protected void InitializeBackgroundDropTarget()
|
||||||
{
|
{
|
||||||
BackgroundDropTarget = new DragDropNode
|
BackgroundDropTarget = new DragDropNode
|
||||||
@@ -383,6 +360,11 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
|
|||||||
float requiredWidth = maxChildWidth + (ContentStartPosition.X * 2);
|
float requiredWidth = maxChildWidth + (ContentStartPosition.X * 2);
|
||||||
float finalWidth = Math.Clamp(requiredWidth, MinWindowWidth, MaxWindowWidth);
|
float finalWidth = Math.Clamp(requiredWidth, MinWindowWidth, MaxWindowWidth);
|
||||||
|
|
||||||
|
if (SettingsButtonNode != null)
|
||||||
|
{
|
||||||
|
SettingsButtonNode.X = finalWidth - 62f;
|
||||||
|
}
|
||||||
|
|
||||||
float contentWidth = finalWidth - (ContentStartPosition.X * 2);
|
float contentWidth = finalWidth - (ContentStartPosition.X * 2);
|
||||||
|
|
||||||
float footerSpace = HasFooter || HasSlotCounter ? FooterHeight + FooterTopSpacing : 0;
|
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)
|
protected override void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||||
{
|
{
|
||||||
_requestedUpdateCount++;
|
|
||||||
LogRefreshStats();
|
|
||||||
|
|
||||||
base.OnRequestedUpdate(addon, numberArrayData, stringArrayData);
|
base.OnRequestedUpdate(addon, numberArrayData, stringArrayData);
|
||||||
|
|
||||||
if (DragDropState.IsDragging)
|
if (DragDropState.IsDragging) return;
|
||||||
return;
|
ExecuteRefresh(autosize: true);
|
||||||
|
|
||||||
InventoryState.RefreshFromGame();
|
|
||||||
RefreshCategoriesCore(autosize: true);
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
protected override void OnSetup(AtkUnitBase* addon)
|
protected override void OnSetup(AtkUnitBase* addon)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using AetherBags.Configuration;
|
|||||||
using AetherBags.Inventory;
|
using AetherBags.Inventory;
|
||||||
using AetherBags.Inventory.Context;
|
using AetherBags.Inventory.Context;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
using KamiToolKit.Classes.ContextMenu;
|
using KamiToolKit.ContextMenu;
|
||||||
|
|
||||||
namespace AetherBags.Addons;
|
namespace AetherBags.Addons;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using KamiToolKit.Classes;
|
using KamiToolKit.Classes;
|
||||||
@@ -84,6 +85,9 @@ public enum ToggleFilterState
|
|||||||
|
|
||||||
public enum PluginFilterMode
|
public enum PluginFilterMode
|
||||||
{
|
{
|
||||||
|
[Description("Create New Categories")]
|
||||||
Categorize = 0,
|
Categorize = 0,
|
||||||
|
|
||||||
|
[Description("Apply Highlight Only")]
|
||||||
Highlight = 1,
|
Highlight = 1,
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,20 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using KamiToolKit.Classes;
|
using KamiToolKit.Classes;
|
||||||
|
|
||||||
namespace AetherBags.Configuration;
|
namespace AetherBags.Configuration;
|
||||||
|
|
||||||
public class CurrencySettings
|
public class CurrencySettings
|
||||||
{
|
{
|
||||||
|
[JsonIgnore]
|
||||||
|
public const uint LimitedTomestoneId = 0xFFFF_FFFE;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public const uint NonLimitedTomestoneId = 0xFFFF_FFFD;
|
||||||
|
|
||||||
public bool Enabled { get; set; } = true;
|
public bool Enabled { get; set; } = true;
|
||||||
|
public List<uint> DisplayedCurrencies { get; set; } = new() { 1, LimitedTomestoneId, NonLimitedTomestoneId };
|
||||||
public bool ColorWhenCapped { get; set; } = true;
|
public bool ColorWhenCapped { get; set; } = true;
|
||||||
public bool ColorWhenLimited { get; set; } = true;
|
public bool ColorWhenLimited { get; set; } = true;
|
||||||
public Vector4 DefaultColor { get; set; } = ColorHelper.GetColor(8);
|
public Vector4 DefaultColor { get; set; } = ColorHelper.GetColor(8);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace AetherBags.Configuration;
|
namespace AetherBags.Configuration;
|
||||||
|
|
||||||
public class GeneralSettings
|
public class GeneralSettings
|
||||||
@@ -21,12 +23,18 @@ public class GeneralSettings
|
|||||||
|
|
||||||
public enum InventoryStackMode : byte
|
public enum InventoryStackMode : byte
|
||||||
{
|
{
|
||||||
|
[Description("Split Stacks (Game Default)")]
|
||||||
NaturalStacks = 0,
|
NaturalStacks = 0,
|
||||||
|
|
||||||
|
[Description("Merge Stacks (By Item ID)")]
|
||||||
AggregateByItemId = 1,
|
AggregateByItemId = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SearchMode : byte
|
public enum SearchMode : byte
|
||||||
{
|
{
|
||||||
|
[Description("Filter (Hide non-matches)")]
|
||||||
Filter = 0,
|
Filter = 0,
|
||||||
|
|
||||||
|
[Description("Highlight (Dim non-matches)")]
|
||||||
Highlight = 1,
|
Highlight = 1,
|
||||||
}
|
}
|
||||||
@@ -71,6 +71,31 @@ public static unsafe class CurrencyState
|
|||||||
return currencyInfoList;
|
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()
|
private static uint? GetLimitedTomestoneItemIdCached()
|
||||||
{
|
{
|
||||||
if (_cachedLimitedTomestoneItemId.HasValue)
|
if (_cachedLimitedTomestoneItemId.HasValue)
|
||||||
@@ -96,6 +121,13 @@ public static unsafe class CurrencyState
|
|||||||
_cachedNonLimitedTomestoneItemId = itemId;
|
_cachedNonLimitedTomestoneItemId = itemId;
|
||||||
return itemId;
|
return itemId;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static uint? GetLimitedTomestoneItemIdCached()
|
||||||
|
=> _cachedLimitedTomestoneItemId ??= GetCurrentTomestoneIds().Limited;
|
||||||
|
|
||||||
|
private static uint? GetNonLimitedTomestoneItemIdCached()
|
||||||
|
=> _cachedNonLimitedTomestoneItemId ??= GetCurrentTomestoneIds().NonLimited;
|
||||||
|
|
||||||
private static CurrencyItem ResolveCurrencyItemIdCached(uint currencyId)
|
private static CurrencyItem ResolveCurrencyItemIdCached(uint currencyId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,27 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
namespace AetherBags.Extensions;
|
namespace AetherBags.Extensions;
|
||||||
|
|
||||||
public static class LoggerExtensions
|
public static class LoggerExtensions
|
||||||
{
|
{
|
||||||
extension(object logger)
|
extension(IPluginLog logger)
|
||||||
{
|
{
|
||||||
|
[Conditional("DEBUG")]
|
||||||
public void DebugOnly(string message)
|
public void DebugOnly(string message)
|
||||||
{
|
{
|
||||||
if (System.Config?.General?.DebugEnabled == true)
|
if (System.Config?.General?.DebugEnabled == true)
|
||||||
{
|
{
|
||||||
Services.Logger.Debug(message);
|
logger.Debug(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Conditional("DEBUG")]
|
||||||
public void DebugOnly(string message, params object[] args)
|
public void DebugOnly(string message, params object[] args)
|
||||||
{
|
{
|
||||||
if (System.Config?.General?.DebugEnabled == true)
|
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> AllCategories = new(capacity: 256);
|
||||||
protected readonly List<CategorizedInventory> FilteredCategories = new(capacity: 256);
|
protected readonly List<CategorizedInventory> FilteredCategories = new(capacity: 256);
|
||||||
protected readonly List<UserCategoryDefinition> UserCategoriesSortedScratch = new(capacity: 64);
|
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 List<ulong> RemoveKeysScratch = new(capacity: 256);
|
||||||
protected readonly HashSet<ulong> ClaimedKeys = new(capacity: 512);
|
protected readonly HashSet<ulong> ClaimedKeys = new(capacity: 512);
|
||||||
|
|
||||||
@@ -68,18 +69,22 @@ public abstract class InventoryStateBase
|
|||||||
bool allaganCategoriesEnabled = config.Categories.AllaganToolsCategoriesEnabled && categoriesEnabled;
|
bool allaganCategoriesEnabled = config.Categories.AllaganToolsCategoriesEnabled && categoriesEnabled;
|
||||||
bool bisCategoriesEnabled = config.Categories.BisBuddyEnabled && categoriesEnabled;
|
bool bisCategoriesEnabled = config.Categories.BisBuddyEnabled && categoriesEnabled;
|
||||||
// TODO: Cache this when config changes
|
// TODO: Cache this when config changes
|
||||||
UserCategoriesSortedScratch.Clear();
|
EnabledUserCategoriesScratch.Clear();
|
||||||
foreach (var cat in config.Categories.UserCategories)
|
foreach (var cat in config.Categories.UserCategories)
|
||||||
{
|
{
|
||||||
if (cat.Enabled)
|
if (cat.Enabled)
|
||||||
UserCategoriesSortedScratch.Add(cat);
|
EnabledUserCategoriesScratch.Add(cat);
|
||||||
}
|
}
|
||||||
var userCategories = UserCategoriesSortedScratch;
|
|
||||||
|
|
||||||
if (userCategoriesEnabled && userCategories.Count > 0)
|
if (userCategoriesEnabled && EnabledUserCategoriesScratch.Count > 0)
|
||||||
{
|
{
|
||||||
CategoryBucketManager.BucketByUserCategories(
|
CategoryBucketManager.BucketByUserCategories(
|
||||||
ItemInfoByKey, userCategories, BucketsByKey, ClaimedKeys, UserCategoriesSortedScratch);
|
ItemInfoByKey,
|
||||||
|
EnabledUserCategoriesScratch,
|
||||||
|
BucketsByKey,
|
||||||
|
ClaimedKeys,
|
||||||
|
UserCategoriesSortedScratch
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allaganCategoriesEnabled)
|
if (allaganCategoriesEnabled)
|
||||||
|
|||||||
+39
-31
@@ -1,18 +1,22 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AetherBags.Configuration;
|
using AetherBags.Configuration;
|
||||||
using AetherBags.Inventory.Context;
|
using AetherBags.Inventory.Context;
|
||||||
|
using AetherBags.Inventory.Scanning;
|
||||||
using Dalamud.Game.Addon.Lifecycle;
|
using Dalamud.Game.Addon.Lifecycle;
|
||||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Game.Inventory.InventoryEventArgTypes;
|
||||||
using Dalamud.Game.NativeWrapper;
|
using Dalamud.Game.NativeWrapper;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using Lumina.Text.ReadOnly;
|
using Lumina.Text.ReadOnly;
|
||||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||||
|
|
||||||
namespace AetherBags.AddonLifecycles;
|
namespace AetherBags.Monitoring;
|
||||||
|
|
||||||
public static unsafe class DragDropState
|
public static unsafe class DragDropState
|
||||||
{
|
{
|
||||||
@@ -22,10 +26,10 @@ public static unsafe class DragDropState
|
|||||||
public static bool IsDragging => AtkStage.Instance()->DragDropManager.IsDragging;
|
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 bags = new[] { "Inventory", "InventoryLarge", "InventoryExpansion" };
|
||||||
var saddle = new[] { "InventoryBuddy" };
|
var saddle = new[] { "InventoryBuddy" };
|
||||||
@@ -45,8 +49,8 @@ public class InventoryLifecycles : IDisposable
|
|||||||
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, "InventoryBuddy", OnSaddleBagUpdate);
|
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, "InventoryBuddy", OnSaddleBagUpdate);
|
||||||
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, retainer, OnRetainerInventoryUpdate);
|
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, retainer, OnRetainerInventoryUpdate);
|
||||||
|
|
||||||
// PreShow
|
// Dalamud raw event for raw inventory changes (scans once per frame)
|
||||||
Services.AddonLifecycle.RegisterListener(AddonEvent.PreOpen, "InventoryBuddy", OnSaddleBagOpen);
|
Services.GameInventory.InventoryChangedRaw += OnInventoryChangedRaw;
|
||||||
|
|
||||||
Services.Logger.Verbose("InventoryLifecycles initialized");
|
Services.Logger.Verbose("InventoryLifecycles initialized");
|
||||||
}
|
}
|
||||||
@@ -122,6 +126,32 @@ public class InventoryLifecycles : IDisposable
|
|||||||
values[7] = can use Saddlebags (Agent InventoryBuddy IsActivatable)
|
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)
|
private unsafe void InventoryPreRefreshHandler(AddonEvent type, AddonArgs args)
|
||||||
{
|
{
|
||||||
if (args is not AddonRefreshArgs refreshArgs)
|
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)
|
private void OnInventoryUpdate(AddonEvent type, AddonArgs args)
|
||||||
{
|
{
|
||||||
if (IsInUnsafeState())
|
if (IsInUnsafeState())
|
||||||
@@ -204,6 +215,7 @@ public class InventoryLifecycles : IDisposable
|
|||||||
if (DragDropState.IsDragging)
|
if (DragDropState.IsDragging)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
System.LootedItemsTracker.FlushPendingChanges();
|
||||||
System.AddonSaddleBagWindow?.RefreshFromLifecycle();
|
System.AddonSaddleBagWindow?.RefreshFromLifecycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,17 +227,13 @@ public class InventoryLifecycles : IDisposable
|
|||||||
if (DragDropState.IsDragging)
|
if (DragDropState.IsDragging)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
System.LootedItemsTracker.FlushPendingChanges();
|
||||||
System.AddonRetainerWindow?.RefreshFromLifecycle();
|
System.AddonRetainerWindow?.RefreshFromLifecycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSaddleBagOpen(AddonEvent type, AddonArgs args)
|
|
||||||
{
|
|
||||||
if (args is not AddonShowArgs showArgs)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Services.AddonLifecycle.UnregisterListener(OnPostSetup, OnPreFinalize, OnInventoryUpdate, OnSaddleBagUpdate, OnRetainerInventoryUpdate, OnSaddleBagOpen);
|
Services.GameInventory.InventoryChangedRaw -= OnInventoryChangedRaw;
|
||||||
|
Services.AddonLifecycle.UnregisterListener(OnPostSetup, OnPreFinalize, OnInventoryUpdate, OnSaddleBagUpdate, OnRetainerInventoryUpdate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+58
-38
@@ -9,7 +9,7 @@ using Dalamud.Plugin.Services;
|
|||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
|
||||||
namespace AetherBags.Inventory;
|
namespace AetherBags.Monitoring;
|
||||||
|
|
||||||
public sealed unsafe class LootedItemsTracker : IDisposable
|
public sealed unsafe class LootedItemsTracker : IDisposable
|
||||||
{
|
{
|
||||||
@@ -32,6 +32,8 @@ public sealed unsafe class LootedItemsTracker : IDisposable
|
|||||||
|
|
||||||
public bool HasPendingChanges => _pendingChanges.Count > 0 || _hasPendingRemoval;
|
public bool HasPendingChanges => _pendingChanges.Count > 0 || _hasPendingRemoval;
|
||||||
|
|
||||||
|
private int GetNextIndex() => _lootedItems.Count > 0 ? _lootedItems.Max(x => x.Index) + 1 : 0;
|
||||||
|
|
||||||
public void Enable()
|
public void Enable()
|
||||||
{
|
{
|
||||||
if (_isEnabled) return;
|
if (_isEnabled) return;
|
||||||
@@ -81,6 +83,8 @@ public sealed unsafe class LootedItemsTracker : IDisposable
|
|||||||
{
|
{
|
||||||
if (_pendingChanges.Count == 0 && !_hasPendingRemoval) return;
|
if (_pendingChanges.Count == 0 && !_hasPendingRemoval) return;
|
||||||
|
|
||||||
|
ProcessPendingChanges();
|
||||||
|
|
||||||
_hasPendingRemoval = false;
|
_hasPendingRemoval = false;
|
||||||
OnLootedItemsChanged?.Invoke(_lootedItems);
|
OnLootedItemsChanged?.Invoke(_lootedItems);
|
||||||
}
|
}
|
||||||
@@ -90,12 +94,40 @@ public sealed unsafe class LootedItemsTracker : IDisposable
|
|||||||
Disable();
|
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)
|
private void OnInventoryChangedRaw(IReadOnlyCollection<InventoryEventArgs> events)
|
||||||
{
|
{
|
||||||
if (!_isEnabled) return;
|
if (!_isEnabled || !Services.ClientState.IsLoggedIn) return;
|
||||||
if (!Services.ClientState.IsLoggedIn) return;
|
|
||||||
|
|
||||||
bool anyAdded = false;
|
bool anyChanged = false;
|
||||||
|
|
||||||
foreach (var eventData in events)
|
foreach (var eventData in events)
|
||||||
{
|
{
|
||||||
@@ -105,38 +137,42 @@ public sealed unsafe class LootedItemsTracker : IDisposable
|
|||||||
if (eventData.Item.ContainerType == GameInventoryType.DamagedGear)
|
if (eventData.Item.ContainerType == GameInventoryType.DamagedGear)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (eventData is not (InventoryItemAddedArgs or InventoryItemChangedArgs))
|
int changeAmount = eventData switch
|
||||||
continue;
|
|
||||||
|
|
||||||
if (eventData is InventoryItemChangedArgs changedArgs &&
|
|
||||||
changedArgs.OldItemState.Quantity >= changedArgs.Item.Quantity)
|
|
||||||
{
|
{
|
||||||
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))
|
if (ShouldFilterItem(eventData.Item.ItemId))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var inventoryItem = *(InventoryItem*)eventData.Item.Address;
|
uint itemId = eventData.Item.ItemId;
|
||||||
var changeAmount = eventData is InventoryItemChangedArgs changed
|
bool isHq = eventData.Item.IsHq;
|
||||||
? changed.Item.Quantity - changed.OldItemState.Quantity
|
var key = (itemId, isHq);
|
||||||
: eventData.Item.Quantity;
|
|
||||||
|
|
||||||
var key = (inventoryItem.ItemId, IsHq: inventoryItem.Flags.HasFlag(InventoryItem.ItemFlags.HighQuality));
|
|
||||||
|
|
||||||
if (_pendingChanges.TryGetValue(key, out var existing))
|
if (_pendingChanges.TryGetValue(key, out var existing))
|
||||||
{
|
{
|
||||||
_pendingChanges[key] = (inventoryItem, existing.Quantity + changeAmount);
|
_pendingChanges[key] = (existing.Item, existing.Quantity + changeAmount);
|
||||||
}
|
}
|
||||||
else
|
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;
|
_batchStartTick = Environment.TickCount64;
|
||||||
}
|
}
|
||||||
@@ -152,23 +188,7 @@ public sealed unsafe class LootedItemsTracker : IDisposable
|
|||||||
|
|
||||||
_batchStartTick = 0;
|
_batchStartTick = 0;
|
||||||
|
|
||||||
if (_pendingChanges.Count == 0)
|
FlushPendingChanges();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ShouldFilterItem(uint itemId)
|
private static bool ShouldFilterItem(uint itemId)
|
||||||
@@ -3,6 +3,7 @@ using System.Numerics;
|
|||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
using KamiToolKit.Premade.Addons;
|
using KamiToolKit.Premade.Addons;
|
||||||
|
using KamiToolKit.Premade.Color;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Color;
|
namespace AetherBags.Nodes.Color;
|
||||||
|
|
||||||
@@ -16,41 +17,49 @@ public class ColorInputRow : HorizontalListNode
|
|||||||
{
|
{
|
||||||
InitializeColorPicker();
|
InitializeColorPicker();
|
||||||
|
|
||||||
var initialColor = CurrentColor;
|
_colorPreview = new ColorPreviewButtonNode { Size = new Vector2(28) };
|
||||||
|
|
||||||
_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);
|
|
||||||
|
|
||||||
_labelTextNode = new LabelTextNode
|
_labelTextNode = new LabelTextNode
|
||||||
{
|
{
|
||||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||||
Position = new Vector2(28, 0),
|
Position = new Vector2(28, 0),
|
||||||
Height = 24,
|
Height = 28,
|
||||||
String = Label ?? string.Empty,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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);
|
_labelTextNode.AttachNode(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using System.Drawing;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using KamiToolKit.Classes;
|
using KamiToolKit.Enums;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Color;
|
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 System.Numerics;
|
||||||
using AetherBags.Configuration;
|
using AetherBags.Configuration;
|
||||||
using AetherBags.Inventory;
|
using AetherBags.Inventory;
|
||||||
using AetherBags.Nodes.Color;
|
using AetherBags.Nodes.Layout;
|
||||||
using Dalamud.Utility;
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
@@ -23,71 +22,39 @@ public sealed class CategoryDefinitionConfigurationNode : SimpleComponentNode
|
|||||||
|
|
||||||
private UserCategoryDefinition _categoryDefinition = new();
|
private UserCategoryDefinition _categoryDefinition = new();
|
||||||
|
|
||||||
private readonly ScrollingAreaNode<TreeListNode> _scrollingArea;
|
private readonly ScrollingAreaNode<VerticalListNode> _scrollingArea;
|
||||||
private readonly BasicSettingsSection _basicSettings;
|
private readonly List<ConfigurationSection> _sections = new();
|
||||||
private readonly RangeFiltersSection _rangeFilters;
|
|
||||||
private readonly StateFiltersSection _stateFilters;
|
|
||||||
private readonly ListFiltersSection _listFilters;
|
|
||||||
|
|
||||||
public CategoryDefinitionConfigurationNode()
|
public CategoryDefinitionConfigurationNode()
|
||||||
{
|
{
|
||||||
_scrollingArea = new ScrollingAreaNode<TreeListNode>
|
_scrollingArea = new ScrollingAreaNode<VerticalListNode> {
|
||||||
{
|
|
||||||
ContentHeight = 100.0f,
|
|
||||||
AutoHideScrollBar = true,
|
AutoHideScrollBar = true,
|
||||||
|
ContentHeight = 100f
|
||||||
};
|
};
|
||||||
_scrollingArea.AttachNode(this);
|
_scrollingArea.AttachNode(this);
|
||||||
|
|
||||||
_scrollingArea.ContentNode.OnLayoutUpdate = newHeight =>
|
var list = _scrollingArea.ContentAreaNode;
|
||||||
{
|
list.FitContents = true;
|
||||||
_scrollingArea.ContentHeight = newHeight;
|
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;
|
_sections.Add(new RangeFiltersSection(() => _categoryDefinition) { String = "Range Filters" });
|
||||||
|
_sections.Add(new StateFiltersSection(() => _categoryDefinition) { String = "State Filters" });
|
||||||
_basicSettings = new BasicSettingsSection(() => _categoryDefinition)
|
_sections.Add(new ListFiltersSection(() => _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)
|
|
||||||
{
|
|
||||||
String = "List Filters",
|
String = "List Filters",
|
||||||
IsCollapsed = true,
|
OnListChanged = HandleLayoutChange
|
||||||
OnValueChanged = NotifyChanged,
|
});
|
||||||
OnListChanged = HandleListChanged,
|
|
||||||
};
|
foreach (var section in _sections)
|
||||||
_listFilters.OnToggle = _ => HandleLayoutChange();
|
{
|
||||||
treeListNode.AddCategoryNode(_listFilters);
|
section.OnToggle = HandleLayoutChange;
|
||||||
|
section.OnValueChanged = NotifyChanged;
|
||||||
|
list.AddNode(section);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnSizeChanged()
|
protected override void OnSizeChanged()
|
||||||
@@ -96,65 +63,52 @@ public sealed class CategoryDefinitionConfigurationNode : SimpleComponentNode
|
|||||||
|
|
||||||
_scrollingArea.Size = Size;
|
_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;
|
||||||
}
|
}
|
||||||
|
HandleLayoutChange();
|
||||||
_scrollingArea.ContentNode.RefreshLayout();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCategory(UserCategoryDefinition newCategory)
|
public void SetCategory(UserCategoryDefinition newCategory)
|
||||||
{
|
{
|
||||||
_categoryDefinition = newCategory;
|
_categoryDefinition = newCategory;
|
||||||
RefreshAllValues();
|
foreach (var section in _sections) section.Refresh();
|
||||||
}
|
|
||||||
|
|
||||||
private void RefreshAllValues()
|
|
||||||
{
|
|
||||||
_basicSettings.Refresh();
|
|
||||||
_rangeFilters.Refresh();
|
|
||||||
_stateFilters.Refresh();
|
|
||||||
_listFilters.Refresh();
|
|
||||||
|
|
||||||
HandleLayoutChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleListChanged()
|
|
||||||
{
|
|
||||||
NotifyChanged();
|
|
||||||
HandleLayoutChange();
|
HandleLayoutChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleLayoutChange()
|
private void HandleLayoutChange()
|
||||||
{
|
{
|
||||||
_scrollingArea.ContentNode.RefreshLayout();
|
_scrollingArea.ContentAreaNode.RecalculateLayout();
|
||||||
|
_scrollingArea.ContentHeight = _scrollingArea.ContentAreaNode.Height;
|
||||||
OnLayoutChanged?.Invoke();
|
OnLayoutChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void NotifyChanged() => InventoryOrchestrator.RefreshAll(updateMaps: true);
|
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 ResolveItemName(uint itemId) => ItemSheet?.GetRow(itemId).Name.ToString() ?? "Unknown";
|
||||||
|
|
||||||
public static string ResolveUiCategoryName(uint categoryId) => UICategorySheet?.GetRow(categoryId).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;
|
private readonly Func<UserCategoryDefinition> _getCategoryDefinition;
|
||||||
|
|
||||||
public Action? OnValueChanged { get; init; }
|
public Action? OnValueChanged { get; set; }
|
||||||
|
|
||||||
protected UserCategoryDefinition CategoryDefinition => _getCategoryDefinition();
|
protected UserCategoryDefinition CategoryDefinition => _getCategoryDefinition();
|
||||||
|
|
||||||
protected ConfigurationSection(Func<UserCategoryDefinition> getCategoryDefinition)
|
protected ConfigurationSection(Func<UserCategoryDefinition> getCategoryDefinition)
|
||||||
{
|
{
|
||||||
_getCategoryDefinition = getCategoryDefinition;
|
_getCategoryDefinition = getCategoryDefinition;
|
||||||
VerticalPadding = 4.0f;
|
HeaderHeight = 30.0f;
|
||||||
|
|
||||||
|
AddTab();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract void Refresh();
|
||||||
|
|
||||||
protected static LabelTextNode CreateLabel(string text) => new()
|
protected static LabelTextNode CreateLabel(string text) => new()
|
||||||
{
|
{
|
||||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||||
@@ -162,351 +116,3 @@ public abstract class ConfigurationSection : TreeListCategoryNode
|
|||||||
String = text,
|
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;
|
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",
|
LabelText = "Filter Display Mode",
|
||||||
LabelTextFlags = TextFlags.AutoAdjustNodeSize,
|
LabelTextFlags = TextFlags.AutoAdjustNodeSize,
|
||||||
IsEnabled = config.BisBuddyEnabled && bisBuddyReady,
|
IsEnabled = config.BisBuddyEnabled && bisBuddyReady,
|
||||||
Options = Enum.GetNames(typeof(PluginFilterMode)).ToList(),
|
Options = Enum.GetValues<PluginFilterMode>().ToList(),
|
||||||
SelectedOption = config.BisBuddyMode.ToString(),
|
SelectedOption = config.BisBuddyMode,
|
||||||
OnOptionSelected = selected =>
|
OnOptionSelected = selected =>
|
||||||
{
|
{
|
||||||
if (Enum.TryParse<PluginFilterMode>(selected, out var parsed))
|
config.BisBuddyMode = selected;
|
||||||
{
|
if (selected == PluginFilterMode.Categorize)
|
||||||
config.BisBuddyMode = parsed;
|
|
||||||
if (parsed == PluginFilterMode.Categorize)
|
|
||||||
HighlightState.ClearFilter(HighlightSource.AllaganTools);
|
HighlightState.ClearFilter(HighlightSource.AllaganTools);
|
||||||
|
|
||||||
RefreshInventory();
|
RefreshInventory();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CheckboxNode bisBuddyEnabled = new CheckboxNode
|
CheckboxNode bisBuddyEnabled = new CheckboxNode
|
||||||
@@ -121,25 +118,24 @@ public sealed class CategoryGeneralConfigurationNode : TabbedVerticalListNode
|
|||||||
|
|
||||||
bool allaganReady = System.IPC.AllaganTools?.IsReady ?? false;
|
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",
|
LabelText = "Filter Display Mode",
|
||||||
LabelTextFlags = TextFlags.AutoAdjustNodeSize,
|
LabelTextFlags = TextFlags.AutoAdjustNodeSize,
|
||||||
IsEnabled = config.AllaganToolsCategoriesEnabled && allaganReady,
|
IsEnabled = config.AllaganToolsCategoriesEnabled && allaganReady,
|
||||||
Options = Enum.GetNames(typeof(PluginFilterMode)).ToList(),
|
Options = Enum.GetValues<PluginFilterMode>().ToList(),
|
||||||
SelectedOption = config.AllaganToolsFilterMode.ToString(),
|
SelectedOption = config.AllaganToolsFilterMode,
|
||||||
OnOptionSelected = selected =>
|
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);
|
HighlightState.ClearFilter(HighlightSource.AllaganTools);
|
||||||
|
}
|
||||||
|
|
||||||
RefreshInventory();
|
RefreshInventory();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_allaganToolsCheckbox = new CheckboxNode
|
_allaganToolsCheckbox = new CheckboxNode
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ namespace AetherBags.Nodes.Configuration.Category;
|
|||||||
public sealed class CategoryScrollingAreaNode : ScrollingListNode
|
public sealed class CategoryScrollingAreaNode : ScrollingListNode
|
||||||
{
|
{
|
||||||
private AddonCategoryConfigurationWindow? _categoryConfigurationAddon;
|
private AddonCategoryConfigurationWindow? _categoryConfigurationAddon;
|
||||||
private readonly TextButtonNode _categoryConfigurationButtonNode;
|
|
||||||
|
|
||||||
public CategoryScrollingAreaNode()
|
public CategoryScrollingAreaNode()
|
||||||
{
|
{
|
||||||
@@ -15,13 +14,13 @@ public sealed class CategoryScrollingAreaNode : ScrollingListNode
|
|||||||
|
|
||||||
AddNode(new CategoryGeneralConfigurationNode());
|
AddNode(new CategoryGeneralConfigurationNode());
|
||||||
|
|
||||||
_categoryConfigurationButtonNode = new TextButtonNode
|
var categoryConfigurationButtonNode = new TextButtonNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(300, 28),
|
Size = new Vector2(300, 28),
|
||||||
String = "Configure Categories",
|
String = "Configure Categories",
|
||||||
OnClick = () => _categoryConfigurationAddon?.Toggle(),
|
OnClick = () => _categoryConfigurationAddon?.Toggle(),
|
||||||
};
|
};
|
||||||
AddNode(_categoryConfigurationButtonNode);
|
AddNode(categoryConfigurationButtonNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeCategoryAddon() {
|
private void InitializeCategoryAddon() {
|
||||||
@@ -33,4 +32,18 @@ public sealed class CategoryScrollingAreaNode : ScrollingListNode
|
|||||||
Title = "Category Configuration Window",
|
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 AetherBags.Configuration;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration.Category;
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
@@ -14,9 +15,9 @@ public sealed class RangeFilterRow : VerticalListNode
|
|||||||
|
|
||||||
public Action<bool, int, int>? OnFilterChanged { get; set; }
|
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";
|
init => _enabledCheckbox.String = $"{value} Filter";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,9 +114,9 @@ public sealed class RangeFilterRowUint : VerticalListNode
|
|||||||
|
|
||||||
public Action<bool, uint, uint>? OnFilterChanged { get; set; }
|
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";
|
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 FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using KamiToolKit.Classes;
|
using KamiToolKit.Classes;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration.Category;
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
@@ -16,12 +17,11 @@ public sealed class StringListEditorNode : VerticalListNode
|
|||||||
|
|
||||||
private readonly LabelTextNode _headerLabel;
|
private readonly LabelTextNode _headerLabel;
|
||||||
private readonly VerticalListNode _itemsContainer;
|
private readonly VerticalListNode _itemsContainer;
|
||||||
private readonly HorizontalListNode _addRow;
|
|
||||||
private readonly TextInputNode _addInput;
|
private readonly TextInputNode _addInput;
|
||||||
|
|
||||||
public Action? OnChanged { get; set; }
|
public Action? OnChanged { get; set; }
|
||||||
|
|
||||||
public required string Label
|
public required ReadOnlySeString Label
|
||||||
{
|
{
|
||||||
get => _headerLabel.String;
|
get => _headerLabel.String;
|
||||||
init => _headerLabel.String = value;
|
init => _headerLabel.String = value;
|
||||||
@@ -49,7 +49,7 @@ public sealed class StringListEditorNode : VerticalListNode
|
|||||||
};
|
};
|
||||||
AddNode(_itemsContainer);
|
AddNode(_itemsContainer);
|
||||||
|
|
||||||
_addRow = new HorizontalListNode
|
var addRow = new HorizontalListNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(LabelWidth + 40f, RowHeight),
|
Size = new Vector2(LabelWidth + 40f, RowHeight),
|
||||||
ItemSpacing = 4.0f,
|
ItemSpacing = 4.0f,
|
||||||
@@ -61,7 +61,7 @@ public sealed class StringListEditorNode : VerticalListNode
|
|||||||
PlaceholderString = "Add new...",
|
PlaceholderString = "Add new...",
|
||||||
OnInputComplete = _ => AddCurrentValue(),
|
OnInputComplete = _ => AddCurrentValue(),
|
||||||
};
|
};
|
||||||
_addRow.AddNode(_addInput);
|
addRow.AddNode(_addInput);
|
||||||
|
|
||||||
var addButton = new TextButtonNode
|
var addButton = new TextButtonNode
|
||||||
{
|
{
|
||||||
@@ -69,9 +69,9 @@ public sealed class StringListEditorNode : VerticalListNode
|
|||||||
String = "Add",
|
String = "Add",
|
||||||
OnClick = AddCurrentValue,
|
OnClick = AddCurrentValue,
|
||||||
};
|
};
|
||||||
_addRow.AddNode(addButton);
|
addRow.AddNode(addButton);
|
||||||
|
|
||||||
AddNode(_addRow);
|
AddNode(addRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetList(List<string> newList)
|
public void SetList(List<string> newList)
|
||||||
@@ -82,7 +82,7 @@ public sealed class StringListEditorNode : VerticalListNode
|
|||||||
|
|
||||||
private void AddCurrentValue()
|
private void AddCurrentValue()
|
||||||
{
|
{
|
||||||
var value = _addInput.String;
|
var value = _addInput.String.ExtractText();
|
||||||
if (!string.IsNullOrWhiteSpace(value) && !_list.Contains(value))
|
if (!string.IsNullOrWhiteSpace(value) && !_list.Contains(value))
|
||||||
{
|
{
|
||||||
_list.Add(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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using AetherBags.Configuration;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using KamiToolKit.Classes;
|
using KamiToolKit.Classes;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration.Category;
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
@@ -14,15 +17,18 @@ public sealed class UintListEditorNode : VerticalListNode
|
|||||||
|
|
||||||
private List<uint> _list = [];
|
private List<uint> _list = [];
|
||||||
|
|
||||||
|
public List<uint> GetList() => _list.ToList();
|
||||||
|
|
||||||
private readonly LabelTextNode _headerLabel;
|
private readonly LabelTextNode _headerLabel;
|
||||||
private readonly VerticalListNode _itemsContainer;
|
private readonly VerticalListNode _itemsContainer;
|
||||||
private readonly HorizontalListNode _addRow;
|
|
||||||
private readonly NumericInputNode _addInput;
|
private readonly NumericInputNode _addInput;
|
||||||
|
|
||||||
|
public Action? OnSearchButtonClicked { get; init; }
|
||||||
|
|
||||||
public Func<uint, string>? LabelResolver { get; init; }
|
public Func<uint, string>? LabelResolver { get; init; }
|
||||||
public Action? OnChanged { get; set; }
|
public Action? OnChanged { get; set; }
|
||||||
|
|
||||||
public required string Label
|
public required ReadOnlySeString Label
|
||||||
{
|
{
|
||||||
get => _headerLabel.String;
|
get => _headerLabel.String;
|
||||||
init => _headerLabel.String = value;
|
init => _headerLabel.String = value;
|
||||||
@@ -50,12 +56,21 @@ public sealed class UintListEditorNode : VerticalListNode
|
|||||||
};
|
};
|
||||||
AddNode(_itemsContainer);
|
AddNode(_itemsContainer);
|
||||||
|
|
||||||
_addRow = new HorizontalListNode
|
var addRow = new HorizontalListNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(LabelWidth + 40f, RowHeight),
|
Size = new Vector2(LabelWidth + 40f, RowHeight),
|
||||||
ItemSpacing = 4.0f,
|
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
|
_addInput = new NumericInputNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(120, RowHeight),
|
Size = new Vector2(120, RowHeight),
|
||||||
@@ -63,7 +78,7 @@ public sealed class UintListEditorNode : VerticalListNode
|
|||||||
Max = int.MaxValue,
|
Max = int.MaxValue,
|
||||||
Value = 0,
|
Value = 0,
|
||||||
};
|
};
|
||||||
_addRow.AddNode(_addInput);
|
addRow.AddNode(_addInput);
|
||||||
|
|
||||||
var addButton = new TextButtonNode
|
var addButton = new TextButtonNode
|
||||||
{
|
{
|
||||||
@@ -71,9 +86,10 @@ public sealed class UintListEditorNode : VerticalListNode
|
|||||||
String = "Add",
|
String = "Add",
|
||||||
OnClick = AddCurrentValue,
|
OnClick = AddCurrentValue,
|
||||||
};
|
};
|
||||||
_addRow.AddNode(addButton);
|
addRow.AddNode(addButton);
|
||||||
|
addRow.RecalculateLayout();
|
||||||
AddNode(_addRow);
|
AddNode(addRow);
|
||||||
|
RecalculateLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetList(List<uint> newList)
|
public void SetList(List<uint> newList)
|
||||||
@@ -82,6 +98,16 @@ public sealed class UintListEditorNode : VerticalListNode
|
|||||||
RefreshItems();
|
RefreshItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddValue(uint value)
|
||||||
|
{
|
||||||
|
if (!_list.Contains(value))
|
||||||
|
{
|
||||||
|
_list.Add(value);
|
||||||
|
RefreshItems();
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void AddCurrentValue()
|
private void AddCurrentValue()
|
||||||
{
|
{
|
||||||
var value = (uint)_addInput.Value;
|
var value = (uint)_addInput.Value;
|
||||||
@@ -109,6 +135,7 @@ public sealed class UintListEditorNode : VerticalListNode
|
|||||||
|
|
||||||
_itemsContainer.RecalculateLayout();
|
_itemsContainer.RecalculateLayout();
|
||||||
RecalculateLayout();
|
RecalculateLayout();
|
||||||
|
OnChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
private UintListItemNode CreateItemNode(uint value) => new(value, LabelResolver)
|
private UintListItemNode CreateItemNode(uint value) => new(value, LabelResolver)
|
||||||
@@ -120,8 +147,10 @@ public sealed class UintListEditorNode : VerticalListNode
|
|||||||
private void RemoveValue(uint value)
|
private void RemoveValue(uint value)
|
||||||
{
|
{
|
||||||
_list.Remove(value);
|
_list.Remove(value);
|
||||||
|
Services.Framework.RunOnTick(() => {
|
||||||
RefreshItems();
|
RefreshItems();
|
||||||
OnChanged?.Invoke();
|
OnChanged?.Invoke();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,9 +166,15 @@ public sealed class UintListItemNode : HorizontalListNode
|
|||||||
Value = value;
|
Value = value;
|
||||||
ItemSpacing = 4.0f;
|
ItemSpacing = 4.0f;
|
||||||
|
|
||||||
|
string idDisplay = value switch {
|
||||||
|
0xFFFF_FFFE => "[Weekly]",
|
||||||
|
0xFFFF_FFFD => "[Tome]",
|
||||||
|
_ => value.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
var displayText = labelResolver is not null
|
var displayText = labelResolver is not null
|
||||||
? $"{value} - {labelResolver(value)}"
|
? $"{idDisplay} - {labelResolver(value)}"
|
||||||
: value.ToString();
|
: idDisplay;
|
||||||
|
|
||||||
AddNode(new LabelTextNode
|
AddNode(new LabelTextNode
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,22 +1,29 @@
|
|||||||
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using AetherBags.Addons;
|
||||||
using AetherBags.Configuration;
|
using AetherBags.Configuration;
|
||||||
using AetherBags.Nodes.Color;
|
using AetherBags.Nodes.Color;
|
||||||
|
using AetherBags.Nodes.Configuration.Category;
|
||||||
using KamiToolKit.Classes;
|
using KamiToolKit.Classes;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
using Lumina.Excel.Sheets;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration.Currency;
|
namespace AetherBags.Nodes.Configuration.Currency;
|
||||||
|
|
||||||
public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
||||||
{
|
{
|
||||||
|
private readonly UintListEditorNode? _currencyListEditor;
|
||||||
|
|
||||||
public CurrencyGeneralConfigurationNode()
|
public CurrencyGeneralConfigurationNode()
|
||||||
{
|
{
|
||||||
CurrencySettings config = System.Config.Currency;
|
CurrencySettings config = System.Config.Currency;
|
||||||
|
|
||||||
|
Width = 600;
|
||||||
ItemVerticalSpacing = 2;
|
ItemVerticalSpacing = 2;
|
||||||
|
|
||||||
LabelTextNode titleNode = new LabelTextNode
|
LabelTextNode titleNode = new LabelTextNode
|
||||||
{
|
{
|
||||||
Size = Size with { Y = 18 },
|
Size = new Vector2(Width, 18),
|
||||||
String = "Currency Configuration",
|
String = "Currency Configuration",
|
||||||
TextColor = ColorHelper.GetColor(2),
|
TextColor = ColorHelper.GetColor(2),
|
||||||
TextOutlineColor = ColorHelper.GetColor(0),
|
TextOutlineColor = ColorHelper.GetColor(0),
|
||||||
@@ -27,7 +34,7 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
|||||||
|
|
||||||
CheckboxNode currencyEnabledCheckbox = new CheckboxNode
|
CheckboxNode currencyEnabledCheckbox = new CheckboxNode
|
||||||
{
|
{
|
||||||
Size = Size with { Y = 18 },
|
Size = new Vector2(Width, 18),
|
||||||
IsVisible = true,
|
IsVisible = true,
|
||||||
String = "Show Currency",
|
String = "Show Currency",
|
||||||
IsChecked = config.Enabled,
|
IsChecked = config.Enabled,
|
||||||
@@ -41,21 +48,23 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
|||||||
|
|
||||||
AddTab(1);
|
AddTab(1);
|
||||||
|
|
||||||
|
var defaultColorHandler = CreateColorHandler(color => config.DefaultColor = color);
|
||||||
ColorInputRow defaultCurrencyColorNode = new ColorInputRow
|
ColorInputRow defaultCurrencyColorNode = new ColorInputRow
|
||||||
{
|
{
|
||||||
Label = "Default Currency Color",
|
Label = "Default Currency Color",
|
||||||
Size = new Vector2(300, 24),
|
Size = new Vector2(300, 24),
|
||||||
CurrentColor = config.DefaultColor,
|
CurrentColor = config.DefaultColor,
|
||||||
DefaultColor = new CurrencySettings().DefaultColor,
|
DefaultColor = new CurrencySettings().DefaultColor,
|
||||||
OnColorConfirmed = ApplyColorChange,
|
OnColorConfirmed = defaultColorHandler,
|
||||||
OnColorChange = ApplyColorChange,
|
OnColorChange = defaultColorHandler,
|
||||||
OnColorCanceled = ApplyColorChange,
|
OnColorCanceled = defaultColorHandler,
|
||||||
|
OnColorPreviewed = defaultColorHandler,
|
||||||
};
|
};
|
||||||
AddNode(defaultCurrencyColorNode);
|
AddNode(defaultCurrencyColorNode);
|
||||||
|
|
||||||
CheckboxNode cappedEnabledCheckbox = new CheckboxNode
|
CheckboxNode cappedEnabledCheckbox = new CheckboxNode
|
||||||
{
|
{
|
||||||
Size = Size with { Y = 18 },
|
Size = new Vector2(Width, 18),
|
||||||
IsVisible = true,
|
IsVisible = true,
|
||||||
String = "Color Weekly Cap",
|
String = "Color Weekly Cap",
|
||||||
IsChecked = config.ColorWhenCapped,
|
IsChecked = config.ColorWhenCapped,
|
||||||
@@ -70,18 +79,17 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
|||||||
|
|
||||||
AddTab(1);
|
AddTab(1);
|
||||||
|
|
||||||
|
var cappedColorHandler = CreateColorHandler(color => config.CappedColor = color);
|
||||||
ColorInputRow cappedCurrencyColorNode = new ColorInputRow
|
ColorInputRow cappedCurrencyColorNode = new ColorInputRow
|
||||||
{
|
{
|
||||||
Label = "Weekly Cap Color",
|
Label = "Weekly Cap Color",
|
||||||
Size = new Vector2(300, 24),
|
Size = new Vector2(300, 24),
|
||||||
CurrentColor = config.CappedColor,
|
CurrentColor = config.CappedColor,
|
||||||
DefaultColor = new CurrencySettings().CappedColor,
|
DefaultColor = new CurrencySettings().CappedColor,
|
||||||
OnColorConfirmed = color =>
|
OnColorConfirmed = cappedColorHandler,
|
||||||
{
|
OnColorChange = cappedColorHandler,
|
||||||
config.CappedColor = color;
|
OnColorCanceled = cappedColorHandler,
|
||||||
RefreshCurrency();
|
OnColorPreviewed = cappedColorHandler,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
AddNode(cappedCurrencyColorNode);
|
AddNode(cappedCurrencyColorNode);
|
||||||
|
|
||||||
@@ -89,7 +97,7 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
|||||||
|
|
||||||
CheckboxNode limitedEnabledCheckbox = new CheckboxNode
|
CheckboxNode limitedEnabledCheckbox = new CheckboxNode
|
||||||
{
|
{
|
||||||
Size = Size with { Y = 18 },
|
Size = new Vector2(Width, 18),
|
||||||
IsVisible = true,
|
IsVisible = true,
|
||||||
String = "Color Max Capacity",
|
String = "Color Max Capacity",
|
||||||
IsChecked = config.ColorWhenLimited,
|
IsChecked = config.ColorWhenLimited,
|
||||||
@@ -104,28 +112,83 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
|||||||
|
|
||||||
AddTab(1);
|
AddTab(1);
|
||||||
|
|
||||||
|
var limitColorHandler = CreateColorHandler(color => config.LimitColor = color);
|
||||||
ColorInputRow limitCurrencyColorNode = new ColorInputRow
|
ColorInputRow limitCurrencyColorNode = new ColorInputRow
|
||||||
{
|
{
|
||||||
Label = "Max Capacity Color",
|
Label = "Max Capacity Color",
|
||||||
Size = new Vector2(300, 24),
|
Size = new Vector2(300, 24),
|
||||||
CurrentColor = config.LimitColor,
|
CurrentColor = config.LimitColor,
|
||||||
DefaultColor = new CurrencySettings().LimitColor,
|
DefaultColor = new CurrencySettings().LimitColor,
|
||||||
OnColorConfirmed = color =>
|
OnColorConfirmed = limitColorHandler,
|
||||||
{
|
OnColorChange = limitColorHandler,
|
||||||
config.LimitColor = color;
|
OnColorCanceled = limitColorHandler,
|
||||||
RefreshCurrency();
|
OnColorPreviewed = limitColorHandler,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
AddNode(limitCurrencyColorNode);
|
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();
|
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 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;
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration.Currency;
|
namespace AetherBags.Nodes.Configuration.Currency;
|
||||||
@@ -8,7 +9,7 @@ public sealed class CurrencyScrollingAreaNode : ScrollingListNode
|
|||||||
{
|
{
|
||||||
AddNode(new CurrencyGeneralConfigurationNode
|
AddNode(new CurrencyGeneralConfigurationNode
|
||||||
{
|
{
|
||||||
Size = Size
|
Width = 600
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@ internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode
|
|||||||
private readonly CheckboxNode _hideDefaultBagsCheckboxNode;
|
private readonly CheckboxNode _hideDefaultBagsCheckboxNode;
|
||||||
private readonly CheckboxNode _hideSaddlebagsCheckboxNode;
|
private readonly CheckboxNode _hideSaddlebagsCheckboxNode;
|
||||||
private readonly CheckboxNode _hideRetainerbagsCheckboxNode;
|
private readonly CheckboxNode _hideRetainerbagsCheckboxNode;
|
||||||
private readonly LabeledDropdownNode _stackDropDown;
|
private readonly LabeledEnumDropdownNode<InventoryStackMode> _stackDropDown;
|
||||||
|
|
||||||
public FunctionalConfigurationNode()
|
public FunctionalConfigurationNode()
|
||||||
{
|
{
|
||||||
@@ -139,40 +139,34 @@ internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode
|
|||||||
Height = 6
|
Height = 6
|
||||||
});
|
});
|
||||||
|
|
||||||
var searchModeDropDown = new LabeledDropdownNode
|
var searchModeDropDown = new LabeledEnumDropdownNode<SearchMode>
|
||||||
{
|
{
|
||||||
Size = new Vector2(300, 20),
|
Size = new Vector2(500, 20),
|
||||||
LabelText = "Search Mode",
|
LabelText = "Search Mode",
|
||||||
LabelTextFlags = TextFlags.AutoAdjustNodeSize,
|
LabelTextFlags = TextFlags.AutoAdjustNodeSize,
|
||||||
Options = Enum.GetNames(typeof(SearchMode)).ToList(),
|
Options = Enum.GetValues<SearchMode>().ToList(),
|
||||||
SelectedOption = config.SearchMode.ToString(),
|
SelectedOption = config.SearchMode,
|
||||||
OnOptionSelected = selected =>
|
OnOptionSelected = selected =>
|
||||||
{
|
{
|
||||||
if (Enum.TryParse<SearchMode>(selected, out var parsed))
|
config.SearchMode = selected;
|
||||||
{
|
|
||||||
config.SearchMode = parsed;
|
|
||||||
InventoryOrchestrator.RefreshAll(updateMaps: false);
|
InventoryOrchestrator.RefreshAll(updateMaps: false);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
AddNode(searchModeDropDown);
|
AddNode(searchModeDropDown);
|
||||||
|
|
||||||
_stackDropDown = new LabeledDropdownNode
|
_stackDropDown = new LabeledEnumDropdownNode<InventoryStackMode>
|
||||||
{
|
{
|
||||||
Size = new Vector2(300, 20),
|
Size = new Vector2(500, 20),
|
||||||
IsEnabled = true,
|
IsEnabled = true,
|
||||||
LabelText = "Stack Mode",
|
LabelText = "Stack Mode",
|
||||||
LabelTextFlags = TextFlags.AutoAdjustNodeSize,
|
LabelTextFlags = TextFlags.AutoAdjustNodeSize,
|
||||||
Options = Enum.GetNames(typeof(InventoryStackMode)).ToList(),
|
Options = Enum.GetValues<InventoryStackMode>().ToList(),
|
||||||
SelectedOption = config.StackMode.ToString(),
|
SelectedOption = config.StackMode,
|
||||||
OnOptionSelected = selected =>
|
OnOptionSelected = selected =>
|
||||||
{
|
{
|
||||||
if (Enum.TryParse<InventoryStackMode>(selected, out var parsed))
|
config.StackMode = selected;
|
||||||
{
|
|
||||||
config.StackMode = parsed;
|
|
||||||
InventoryOrchestrator.RefreshAll(updateMaps: true);
|
InventoryOrchestrator.RefreshAll(updateMaps: true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
AddNode(_stackDropDown);
|
AddNode(_stackDropDown);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using AetherBags.Helpers;
|
|||||||
using AetherBags.Inventory;
|
using AetherBags.Inventory;
|
||||||
using Dalamud.Game.ClientState.Keys;
|
using Dalamud.Game.ClientState.Keys;
|
||||||
using KamiToolKit.Classes;
|
using KamiToolKit.Classes;
|
||||||
|
using KamiToolKit.Enums;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration.General;
|
namespace AetherBags.Nodes.Configuration.General;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using AetherBags.Configuration;
|
using AetherBags.Configuration;
|
||||||
using KamiToolKit.Classes.Timelines;
|
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using AetherBags.Inventory;
|
using AetherBags.Inventory;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using KamiToolKit.Timelines;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration.Layout;
|
namespace AetherBags.Nodes.Configuration.Layout;
|
||||||
|
|
||||||
|
|||||||
+26
-12
@@ -2,27 +2,28 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Input;
|
namespace AetherBags.Nodes.Input;
|
||||||
|
|
||||||
public class LabeledDropdownNode : SimpleComponentNode {
|
public class LabeledEnumDropdownNode<T> : SimpleComponentNode where T : Enum {
|
||||||
private readonly GridNode _gridNode;
|
private readonly GridNode _gridNode;
|
||||||
private readonly TextNode _labelNode;
|
private readonly TextNode _labelNode;
|
||||||
private readonly TextDropDownNode _dropDownNode;
|
private readonly EnumDropDownNode<T> _dropDownNode;
|
||||||
|
|
||||||
public LabeledDropdownNode() {
|
public LabeledEnumDropdownNode() {
|
||||||
_gridNode = new GridNode {
|
_gridNode = new GridNode {
|
||||||
GridSize = new GridSize(2, 1),
|
GridSize = new GridSize(2, 1),
|
||||||
};
|
};
|
||||||
_gridNode.AttachNode(this);
|
_gridNode.AttachNode(this);
|
||||||
|
|
||||||
_labelNode = new LabelTextNode {
|
_labelNode = new LabelTextNode {
|
||||||
String = String.Empty,
|
String = string.Empty,
|
||||||
};
|
};
|
||||||
_labelNode.AttachNode(_gridNode[0, 0]);
|
_labelNode.AttachNode(_gridNode[0, 0]);
|
||||||
|
|
||||||
_dropDownNode = new TextDropDownNode {
|
_dropDownNode = new EnumDropDownNode<T> {
|
||||||
Options = new List<string>(),
|
Options = new List<T>(),
|
||||||
};
|
};
|
||||||
_dropDownNode.AttachNode(_gridNode[1, 0]);
|
_dropDownNode.AttachNode(_gridNode[1, 0]);
|
||||||
}
|
}
|
||||||
@@ -36,25 +37,38 @@ public class LabeledDropdownNode : SimpleComponentNode {
|
|||||||
_dropDownNode.Size = _gridNode[1, 0].Size;
|
_dropDownNode.Size = _gridNode[1, 0].Size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public required string LabelText
|
public required ReadOnlySeString LabelText
|
||||||
{
|
{
|
||||||
get => _labelNode.String;
|
get => _labelNode.String;
|
||||||
set => _labelNode.String = value;
|
set => _labelNode.String = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Action<string>? OnOptionSelected
|
public Action<T>? OnOptionSelected
|
||||||
{
|
{
|
||||||
get => _dropDownNode.OnOptionSelected;
|
get => _dropDownNode.OnOptionSelected;
|
||||||
set => _dropDownNode.OnOptionSelected = value;
|
set => _dropDownNode.OnOptionSelected = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string? SelectedOption
|
public T? SelectedOption
|
||||||
{
|
{
|
||||||
get => _dropDownNode.SelectedOption;
|
get => _dropDownNode.OptionListNode.SelectedOption;
|
||||||
set => _dropDownNode.SelectedOption = value;
|
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!;
|
get => _dropDownNode.Options!;
|
||||||
set => _dropDownNode.Options = value;
|
set => _dropDownNode.Options = value;
|
||||||
@@ -49,7 +49,7 @@ public class TextInputWithButtonNode : SimpleComponentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySeString SearchString {
|
public ReadOnlySeString SearchString {
|
||||||
get => _textInputNode.SeString;
|
get => _textInputNode.String;
|
||||||
set => _textInputNode.SeString = value;
|
set => _textInputNode.String = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ public class InventoryCategoryNode : InventoryCategoryNodeBase
|
|||||||
_categoryNameTextNode.TextFlags |= TextFlags.OverflowHidden | TextFlags.Ellipsis;
|
_categoryNameTextNode.TextFlags |= TextFlags.OverflowHidden | TextFlags.Ellipsis;
|
||||||
_categoryNameTextNode.TextFlags &= ~(TextFlags.WordWrap | TextFlags.MultiLine);
|
_categoryNameTextNode.TextFlags &= ~(TextFlags.WordWrap | TextFlags.MultiLine);
|
||||||
|
|
||||||
_categoryNameTextNode.AddFlags(NodeFlags.EmitsEvents | NodeFlags.HasCollision);
|
_categoryNameTextNode.AddNodeFlags(NodeFlags.EmitsEvents | NodeFlags.HasCollision);
|
||||||
_categoryNameTextNode.AttachNode(this);
|
_categoryNameTextNode.AttachNode(this);
|
||||||
|
|
||||||
_itemGridNode = new HybridDirectionalFlexNode<DragDropNode>
|
_itemGridNode = new HybridDirectionalFlexNode<DragDropNode>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class InventoryDragDropNode : DragDropNode
|
|||||||
Size = new Vector2(40.0f, 12.0f),
|
Size = new Vector2(40.0f, 12.0f),
|
||||||
Position = new Vector2(4.0f, 34.0f),
|
Position = new Vector2(4.0f, 34.0f),
|
||||||
NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents,
|
NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents,
|
||||||
Color = ColorHelper.GetColor(50),
|
TextColor = ColorHelper.GetColor(50),
|
||||||
TextOutlineColor = ColorHelper.GetColor(51),
|
TextOutlineColor = ColorHelper.GetColor(51),
|
||||||
TextFlags = TextFlags.Edge,
|
TextFlags = TextFlags.Edge,
|
||||||
AlignmentType = AlignmentType.Right,
|
AlignmentType = AlignmentType.Right,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using AetherBags.Nodes.Currency;
|
|||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using KamiToolKit.Classes;
|
using KamiToolKit.Classes;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
using static AetherBags.Inventory.State.InventoryStateBase;
|
using static AetherBags.Inventory.State.InventoryStateBase;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Inventory;
|
namespace AetherBags.Nodes.Inventory;
|
||||||
@@ -33,7 +33,8 @@ public sealed class InventoryFooterNode : SimpleComponentNode
|
|||||||
{
|
{
|
||||||
Position = new Vector2(0, 0),
|
Position = new Vector2(0, 0),
|
||||||
Size = new Vector2(120, 28),
|
Size = new Vector2(120, 28),
|
||||||
IsVisible = System.Config.Currency.Enabled
|
IsVisible = System.Config.Currency.Enabled,
|
||||||
|
ItemSpacing = 12f,
|
||||||
};
|
};
|
||||||
_currencyListNode.AttachNode(this);
|
_currencyListNode.AttachNode(this);
|
||||||
|
|
||||||
@@ -42,9 +43,13 @@ public sealed class InventoryFooterNode : SimpleComponentNode
|
|||||||
|
|
||||||
public void RefreshCurrencies()
|
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>(
|
_currencyListNode.SyncWithListDataByKey<CurrencyInfo, CurrencyNode, uint>(
|
||||||
dataList: currencyInfoList,
|
dataList: currencyInfoList,
|
||||||
getKeyFromData: currencyInfo => currencyInfo.ItemId,
|
getKeyFromData: currencyInfo => currencyInfo.ItemId,
|
||||||
@@ -60,7 +65,7 @@ public sealed class InventoryFooterNode : SimpleComponentNode
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SlotAmountText
|
public ReadOnlySeString SlotAmountText
|
||||||
{
|
{
|
||||||
get => _slotAmountTextNode.String;
|
get => _slotAmountTextNode.String;
|
||||||
set => _slotAmountTextNode.String = value;
|
set => _slotAmountTextNode.String = value;
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ using System.Numerics;
|
|||||||
using AetherBags.Inventory.Context;
|
using AetherBags.Inventory.Context;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using KamiToolKit.Classes;
|
using KamiToolKit.Classes;
|
||||||
using KamiToolKit.Classes.Timelines;
|
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
using KamiToolKit.Timelines;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Inventory;
|
namespace AetherBags.Nodes.Inventory;
|
||||||
|
|
||||||
@@ -13,8 +13,6 @@ public sealed class InventoryNotificationNode : SimpleComponentNode
|
|||||||
private readonly TextNode titleTextNode;
|
private readonly TextNode titleTextNode;
|
||||||
private readonly TextNode messageTextNode;
|
private readonly TextNode messageTextNode;
|
||||||
|
|
||||||
private static readonly InventoryNotificationState NotificationState = new();
|
|
||||||
|
|
||||||
public InventoryNotificationNode()
|
public InventoryNotificationNode()
|
||||||
{
|
{
|
||||||
AddTimeline(ParentLabels);
|
AddTimeline(ParentLabels);
|
||||||
@@ -76,8 +74,8 @@ public sealed class InventoryNotificationNode : SimpleComponentNode
|
|||||||
{
|
{
|
||||||
field = value;
|
field = value;
|
||||||
|
|
||||||
titleTextNode.SeString = value.Title;
|
titleTextNode.String = value.Title;
|
||||||
messageTextNode.SeString = value.Message;
|
messageTextNode.String = value.Message;
|
||||||
|
|
||||||
if (value.Title.IsEmpty && value.Message.IsEmpty)
|
if (value.Title.IsEmpty && value.Message.IsEmpty)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
|
|||||||
using AetherBags.Inventory.Items;
|
using AetherBags.Inventory.Items;
|
||||||
using AetherBags.Nodes.Layout;
|
using AetherBags.Nodes.Layout;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using KamiToolKit.Classes;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Inventory;
|
namespace AetherBags.Nodes.Inventory;
|
||||||
@@ -62,7 +63,7 @@ public class LootedItemsCategoryNode : InventoryCategoryNodeBase
|
|||||||
AlignmentType = AlignmentType.Left,
|
AlignmentType = AlignmentType.Left,
|
||||||
String = "Recently Looted",
|
String = "Recently Looted",
|
||||||
TextFlags = TextFlags.OverflowHidden | TextFlags.Ellipsis,
|
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);
|
_headerTextNode.AddEvent(AtkEventType.MouseOver, BeginHeaderHover);
|
||||||
@@ -71,7 +72,7 @@ public class LootedItemsCategoryNode : InventoryCategoryNodeBase
|
|||||||
_headerTextNode.TextFlags |= TextFlags.OverflowHidden | TextFlags.Ellipsis;
|
_headerTextNode.TextFlags |= TextFlags.OverflowHidden | TextFlags.Ellipsis;
|
||||||
_headerTextNode.TextFlags &= ~(TextFlags.WordWrap | TextFlags.MultiLine);
|
_headerTextNode.TextFlags &= ~(TextFlags.WordWrap | TextFlags.MultiLine);
|
||||||
|
|
||||||
_headerTextNode.AddFlags(NodeFlags.EmitsEvents | NodeFlags.HasCollision);
|
_headerTextNode.AddNodeFlags(NodeFlags.EmitsEvents | NodeFlags.HasCollision);
|
||||||
_headerTextNode.AttachNode(this);
|
_headerTextNode.AttachNode(this);
|
||||||
|
|
||||||
_clearButton = new CircleButtonNode
|
_clearButton = new CircleButtonNode
|
||||||
@@ -197,7 +198,7 @@ public class LootedItemsCategoryNode : InventoryCategoryNodeBase
|
|||||||
|
|
||||||
private void SyncItemGrid()
|
private void SyncItemGrid()
|
||||||
{
|
{
|
||||||
_itemGridNode.SyncWithListDataByKey<LootedItemInfo, LootedItemDisplayNode, int>(
|
_itemGridNode.SyncWithListDataByKey(
|
||||||
dataList: _lootedItems,
|
dataList: _lootedItems,
|
||||||
getKeyFromData: item => item.Index,
|
getKeyFromData: item => item.Index,
|
||||||
getKeyFromNode: node => node.LootedItem?.Index ?? -1,
|
getKeyFromNode: node => node.LootedItem?.Index ?? -1,
|
||||||
@@ -221,6 +222,7 @@ public class LootedItemsCategoryNode : InventoryCategoryNodeBase
|
|||||||
|
|
||||||
private void OnItemDismissed(LootedItemDisplayNode node)
|
private void OnItemDismissed(LootedItemDisplayNode node)
|
||||||
{
|
{
|
||||||
|
if(node.LootedItem is null) return;
|
||||||
int index = node.LootedItem.Index;
|
int index = node.LootedItem.Index;
|
||||||
OnDismissItem?.Invoke(index);
|
OnDismissItem?.Invoke(index);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System. Numerics;
|
using System. Numerics;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Inventory;
|
namespace AetherBags.Nodes.Inventory;
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ public class SaddleBagFooterNode : SimpleComponentNode
|
|||||||
_slotCounterNode.AttachNode(this);
|
_slotCounterNode.AttachNode(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SlotAmountText
|
public ReadOnlySeString SlotAmountText
|
||||||
{
|
{
|
||||||
get => _slotCounterNode.String;
|
get => _slotCounterNode.String;
|
||||||
set => _slotCounterNode.String = $"Slots: {value}";
|
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
|
set
|
||||||
{
|
{
|
||||||
if (value)
|
if (value)
|
||||||
AddFlags(NodeFlags.Clip);
|
AddNodeFlags(NodeFlags.Clip);
|
||||||
else
|
else
|
||||||
RemoveFlags(NodeFlags.Clip);
|
RemoveNodeFlags(NodeFlags.Clip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using AetherBags.AddonLifecycles;
|
|
||||||
using AetherBags.Addons;
|
using AetherBags.Addons;
|
||||||
using AetherBags.Commands;
|
using AetherBags.Commands;
|
||||||
using AetherBags.Helpers;
|
using AetherBags.Helpers;
|
||||||
@@ -7,6 +6,7 @@ using AetherBags.Hooks;
|
|||||||
using AetherBags.Inventory;
|
using AetherBags.Inventory;
|
||||||
using AetherBags.Inventory.Context;
|
using AetherBags.Inventory.Context;
|
||||||
using AetherBags.IPC;
|
using AetherBags.IPC;
|
||||||
|
using AetherBags.Monitoring;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using KamiToolKit;
|
using KamiToolKit;
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ public class Plugin : IDalamudPlugin
|
|||||||
{
|
{
|
||||||
private readonly CommandHandler _commandHandler;
|
private readonly CommandHandler _commandHandler;
|
||||||
private readonly InventoryHooks _inventoryHooks;
|
private readonly InventoryHooks _inventoryHooks;
|
||||||
private readonly InventoryLifecycles _inventoryLifecycles;
|
private readonly InventoryMonitor inventoryMonitor;
|
||||||
|
|
||||||
public Plugin(IDalamudPluginInterface pluginInterface)
|
public Plugin(IDalamudPluginInterface pluginInterface)
|
||||||
{
|
{
|
||||||
@@ -72,14 +72,14 @@ public class Plugin : IDalamudPlugin
|
|||||||
}
|
}
|
||||||
|
|
||||||
_inventoryHooks = new InventoryHooks();
|
_inventoryHooks = new InventoryHooks();
|
||||||
_inventoryLifecycles = new InventoryLifecycles();
|
inventoryMonitor = new InventoryMonitor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
InventoryAddonContextMenu.Close();
|
InventoryAddonContextMenu.Close();
|
||||||
_inventoryHooks.Dispose();
|
_inventoryHooks.Dispose();
|
||||||
_inventoryLifecycles.Dispose();
|
inventoryMonitor.Dispose();
|
||||||
|
|
||||||
System.LootedItemsTracker.Dispose();
|
System.LootedItemsTracker.Dispose();
|
||||||
System.IPC.Dispose();
|
System.IPC.Dispose();
|
||||||
@@ -99,10 +99,8 @@ public class Plugin : IDalamudPlugin
|
|||||||
System.Config = Util.LoadConfigOrDefault();
|
System.Config = Util.LoadConfigOrDefault();
|
||||||
System.LootedItemsTracker.Enable();
|
System.LootedItemsTracker.Enable();
|
||||||
|
|
||||||
#if DEBUG
|
System.AddonInventoryWindow.DebugOpen();
|
||||||
System.AddonInventoryWindow.Toggle();
|
System.AddonConfigurationWindow.DebugOpen();
|
||||||
System.AddonConfigurationWindow.Toggle();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLogout(int type, int code)
|
private void OnLogout(int type, int code)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using AetherBags.Addons;
|
|||||||
using AetherBags.Configuration;
|
using AetherBags.Configuration;
|
||||||
using AetherBags.Inventory;
|
using AetherBags.Inventory;
|
||||||
using AetherBags.IPC;
|
using AetherBags.IPC;
|
||||||
|
using AetherBags.Monitoring;
|
||||||
|
|
||||||
namespace AetherBags;
|
namespace AetherBags;
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule KamiToolKit updated: 5886600a04...811154c8f8
Reference in New Issue
Block a user