Merge branch 'dev/zeffuro'
This commit is contained in:
@@ -0,0 +1,116 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using AetherBags.Configuration;
|
||||||
|
using AetherBags.Nodes.Configuration.Category;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using KamiToolKit;
|
||||||
|
using KamiToolKit.Classes;
|
||||||
|
using KamiToolKit.Nodes;
|
||||||
|
using KamiToolKit.Premade.Nodes;
|
||||||
|
|
||||||
|
namespace AetherBags.Addons;
|
||||||
|
|
||||||
|
public class AddonCategoryConfigurationWindow : NativeAddon
|
||||||
|
{
|
||||||
|
private ModifyListNode<CategoryWrapper>? _selectionListNode;
|
||||||
|
private VerticalLineNode? _separatorLine;
|
||||||
|
private CategoryConfigurationNode? _configNode;
|
||||||
|
private TextNode? _nothingSelectedTextNode;
|
||||||
|
|
||||||
|
private List<CategoryWrapper> _categoryWrappers = new();
|
||||||
|
|
||||||
|
protected override unsafe void OnSetup(AtkUnitBase* addon)
|
||||||
|
{
|
||||||
|
_categoryWrappers = CreateCategoryWrappers();
|
||||||
|
|
||||||
|
_selectionListNode = new ModifyListNode<CategoryWrapper>
|
||||||
|
{
|
||||||
|
Position = ContentStartPosition,
|
||||||
|
Size = new Vector2(250.0f, ContentSize.Y),
|
||||||
|
SelectionOptions = _categoryWrappers,
|
||||||
|
OnOptionChanged = OnOptionChanged,
|
||||||
|
AddNewEntry = OnAddNewCategory,
|
||||||
|
RemoveEntry = OnRemoveCategory,
|
||||||
|
};
|
||||||
|
_selectionListNode.AttachNode(this);
|
||||||
|
|
||||||
|
_separatorLine = new VerticalLineNode
|
||||||
|
{
|
||||||
|
Position = ContentStartPosition + new Vector2(250.0f + 8.0f, 0.0f),
|
||||||
|
Size = new Vector2(4.0f, ContentSize.Y),
|
||||||
|
};
|
||||||
|
_separatorLine.AttachNode(this);
|
||||||
|
|
||||||
|
_nothingSelectedTextNode = new TextNode
|
||||||
|
{
|
||||||
|
Position = ContentStartPosition + new Vector2(250.0f + 16.0f, 0.0f),
|
||||||
|
Size = ContentSize - new Vector2(250.0f + 16.0f, 0.0f),
|
||||||
|
AlignmentType = AlignmentType.Center,
|
||||||
|
TextFlags = TextFlags.WordWrap | TextFlags.MultiLine,
|
||||||
|
FontSize = 14,
|
||||||
|
LineSpacing = 22,
|
||||||
|
FontType = FontType.Axis,
|
||||||
|
String = "Please select a category on the left or add one.",
|
||||||
|
TextColor = ColorHelper.GetColor(1),
|
||||||
|
};
|
||||||
|
_nothingSelectedTextNode.AttachNode(this);
|
||||||
|
|
||||||
|
_configNode = new CategoryConfigurationNode
|
||||||
|
{
|
||||||
|
Position = ContentStartPosition + new Vector2(250.0f + 16.0f, 0.0f),
|
||||||
|
Size = ContentSize - new Vector2(250.0f + 16.0f, 0.0f),
|
||||||
|
IsVisible = false,
|
||||||
|
OnCategoryChanged = RefreshSelectionList,
|
||||||
|
};
|
||||||
|
|
||||||
|
_configNode.AttachNode(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<CategoryWrapper> CreateCategoryWrappers()
|
||||||
|
{
|
||||||
|
return System.Config.Categories.UserCategories
|
||||||
|
.Select(categoryDefinition => new CategoryWrapper(categoryDefinition))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnOptionChanged(CategoryWrapper? newOption)
|
||||||
|
{
|
||||||
|
if (_configNode is null) return;
|
||||||
|
|
||||||
|
_configNode.IsVisible = newOption is not null;
|
||||||
|
if (_nothingSelectedTextNode is not null)
|
||||||
|
_nothingSelectedTextNode.IsVisible = newOption is null;
|
||||||
|
|
||||||
|
_configNode.ConfigurationOption = newOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRemoveCategory(CategoryWrapper categoryWrapper)
|
||||||
|
{
|
||||||
|
if (categoryWrapper.CategoryDefinition is null) return;
|
||||||
|
|
||||||
|
System.Config.Categories.UserCategories.Remove(categoryWrapper.CategoryDefinition);
|
||||||
|
_categoryWrappers.Remove(categoryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshSelectionList()
|
||||||
|
{
|
||||||
|
_selectionListNode?.UpdateList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AetherBags.Nodes.Configuration;
|
using AetherBags.Nodes.Configuration;
|
||||||
|
using AetherBags.Nodes.Configuration.Category;
|
||||||
|
using AetherBags.Nodes.Configuration.Currency;
|
||||||
|
using AetherBags.Nodes.Configuration.General;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using KamiToolKit;
|
using KamiToolKit;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
|||||||
@@ -122,12 +122,14 @@ public class AddonInventoryWindow : NativeAddon
|
|||||||
|
|
||||||
public void ManualInventoryRefresh()
|
public void ManualInventoryRefresh()
|
||||||
{
|
{
|
||||||
|
if (!Services.ClientState.IsLoggedIn) return;
|
||||||
InventoryState.RefreshFromGame();
|
InventoryState.RefreshFromGame();
|
||||||
RefreshCategoriesCore(true);
|
RefreshCategoriesCore(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ManualCurrencyRefresh()
|
public void ManualCurrencyRefresh()
|
||||||
{
|
{
|
||||||
|
if (!Services.ClientState.IsLoggedIn) return;
|
||||||
_footerNode.RefreshCurrencies();
|
_footerNode.RefreshCurrencies();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using AetherBags.Configuration;
|
||||||
|
using KamiToolKit.Premade;
|
||||||
|
|
||||||
|
namespace AetherBags.Addons;
|
||||||
|
|
||||||
|
public class CategoryWrapper(UserCategoryDefinition categoryDefinition) : IInfoNodeData
|
||||||
|
{
|
||||||
|
public UserCategoryDefinition? CategoryDefinition { get; } = categoryDefinition;
|
||||||
|
|
||||||
|
public string GetLabel() {
|
||||||
|
|
||||||
|
return CategoryDefinition!.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSubLabel() {
|
||||||
|
return CategoryDefinition!.Enabled ? "Enabled" : "Disabled";
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint? GetId() => null;
|
||||||
|
|
||||||
|
public uint? GetIconId() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? GetTexturePath()
|
||||||
|
=> null;
|
||||||
|
|
||||||
|
public int Compare(IInfoNodeData other, string sortingMode) {
|
||||||
|
if (other is not CategoryWrapper otherWrapper) return 0;
|
||||||
|
|
||||||
|
return CategoryDefinition!.Order.CompareTo(otherWrapper.CategoryDefinition!.Order);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ public class CategorySettings
|
|||||||
|
|
||||||
public class UserCategoryDefinition
|
public class UserCategoryDefinition
|
||||||
{
|
{
|
||||||
|
public bool Enabled { get; set; } = true;
|
||||||
public string Id { get; set; } = Guid.NewGuid().ToString("N");
|
public string Id { get; set; } = Guid.NewGuid().ToString("N");
|
||||||
public string Name { get; set; } = "New Category";
|
public string Name { get; set; } = "New Category";
|
||||||
public string Description { get; set; } = string.Empty;
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|||||||
@@ -71,12 +71,12 @@ public static unsafe class CurrencyState
|
|||||||
return currencyInfoList;
|
return currencyInfoList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static uint? GetLimitedTomestoneItemIdCached()
|
private static uint? GetLimitedTomestoneItemIdCached()
|
||||||
{
|
{
|
||||||
if (_cachedLimitedTomestoneItemId.HasValue)
|
if (_cachedLimitedTomestoneItemId.HasValue)
|
||||||
return _cachedLimitedTomestoneItemId.Value;
|
return _cachedLimitedTomestoneItemId.Value;
|
||||||
|
|
||||||
uint? itemId = Services.DataManager.GetExcelSheet<TomestonesItem>()
|
uint? itemId = Services.DataManager.GetExcelSheet<TomestonesItem>()
|
||||||
.FirstOrDefault(t => t.Tomestones.RowId == 3)
|
.FirstOrDefault(t => t.Tomestones.RowId == 3)
|
||||||
.Item.RowId;
|
.Item.RowId;
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ public static unsafe class CurrencyState
|
|||||||
return itemId;
|
return itemId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static uint? GetNonLimitedTomestoneItemIdCached()
|
private static uint? GetNonLimitedTomestoneItemIdCached()
|
||||||
{
|
{
|
||||||
if (_cachedNonLimitedTomestoneItemId.HasValue)
|
if (_cachedNonLimitedTomestoneItemId.HasValue)
|
||||||
return _cachedNonLimitedTomestoneItemId.Value;
|
return _cachedNonLimitedTomestoneItemId.Value;
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ public static unsafe class InventoryTypeExtensions
|
|||||||
{
|
{
|
||||||
_ when inventoryType.IsMainInventory => InventoryType.Inventory1,
|
_ when inventoryType.IsMainInventory => InventoryType.Inventory1,
|
||||||
_ when inventoryType.IsSaddleBag => inventoryType is InventoryType. SaddleBag1 or InventoryType.SaddleBag2
|
_ when inventoryType.IsSaddleBag => inventoryType is InventoryType. SaddleBag1 or InventoryType.SaddleBag2
|
||||||
? InventoryType. SaddleBag1
|
? InventoryType. SaddleBag1
|
||||||
: InventoryType.PremiumSaddleBag1,
|
: InventoryType.PremiumSaddleBag1,
|
||||||
_ => inventoryType,
|
_ => inventoryType,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ public sealed unsafe class InventoryHooks : IDisposable
|
|||||||
ushort dstSlot,
|
ushort dstSlot,
|
||||||
bool unk);
|
bool unk);
|
||||||
|
|
||||||
private readonly Hook<MoveItemSlotDelegate>? _moveItemSlotHook;
|
private readonly Hook<MoveItemSlotDelegate>? _moveItemSlotHook;
|
||||||
|
|
||||||
public InventoryHooks()
|
public InventoryHooks()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_moveItemSlotHook = Services.GameInteropProvider.HookFromSignature<MoveItemSlotDelegate>(
|
_moveItemSlotHook = Services.GameInteropProvider.HookFromSignature<MoveItemSlotDelegate>(
|
||||||
"E8 ?? ?? ?? ?? 48 8B 03 66 FF C5",
|
"E8 ?? ?? ?? ?? 48 8B 03 66 FF C5",
|
||||||
MoveItemSlotDetour);
|
MoveItemSlotDetour);
|
||||||
_moveItemSlotHook.Enable();
|
_moveItemSlotHook.Enable();
|
||||||
|
|
||||||
@@ -36,8 +36,7 @@ public sealed unsafe class InventoryHooks : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int MoveItemSlotDetour(
|
private int MoveItemSlotDetour(InventoryManager* manager,
|
||||||
InventoryManager* manager,
|
|
||||||
InventoryType srcType,
|
InventoryType srcType,
|
||||||
ushort srcSlot,
|
ushort srcSlot,
|
||||||
InventoryType dstType,
|
InventoryType dstType,
|
||||||
@@ -47,8 +46,7 @@ public sealed unsafe class InventoryHooks : IDisposable
|
|||||||
InventoryItem* sourceItem = InventoryManager.Instance()->GetInventorySlot(srcType, srcSlot);
|
InventoryItem* sourceItem = InventoryManager.Instance()->GetInventorySlot(srcType, srcSlot);
|
||||||
InventoryItem* destItem = InventoryManager.Instance()->GetInventorySlot(dstType, dstSlot);
|
InventoryItem* destItem = InventoryManager.Instance()->GetInventorySlot(dstType, dstSlot);
|
||||||
|
|
||||||
Services.Logger.Info(
|
Services.Logger.Debug($"[MoveItemSlot] Moving {srcType}@{srcSlot} ID:{sourceItem->ItemId} -> {dstType}@{dstSlot} ID:{destItem->ItemId} Unk: {unk}");
|
||||||
$"[MoveItemSlot] Moving {srcType}@{srcSlot} ID:{sourceItem->ItemId} -> {dstType}@{dstSlot} ID:{destItem->ItemId} Unk: {unk}");
|
|
||||||
|
|
||||||
return _moveItemSlotHook!.Original(manager, srcType, srcSlot, dstType, dstSlot, unk);
|
return _moveItemSlotHook!.Original(manager, srcType, srcSlot, dstType, dstSlot, unk);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public static class CategoryBucketManager
|
|||||||
UserCategoryDefinition category = sortedScratch[i];
|
UserCategoryDefinition category = sortedScratch[i];
|
||||||
uint bucketKey = MakeUserCategoryKey(category.Order);
|
uint bucketKey = MakeUserCategoryKey(category.Order);
|
||||||
|
|
||||||
if (!bucketsByKey.TryGetValue(bucketKey, out CategoryBucket? bucket))
|
if (!bucketsByKey.TryGetValue(bucketKey, out CategoryBucket? bucket))
|
||||||
{
|
{
|
||||||
bucket = new CategoryBucket
|
bucket = new CategoryBucket
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ public static unsafe class InventoryScanner
|
|||||||
bool isHq = (item.Flags & InventoryItem.ItemFlags.HighQuality) != 0;
|
bool isHq = (item.Flags & InventoryItem.ItemFlags.HighQuality) != 0;
|
||||||
|
|
||||||
ulong key = stackMode == InventoryStackMode.AggregateByItemId
|
ulong key = stackMode == InventoryStackMode.AggregateByItemId
|
||||||
? MakeAggregatedItemKey(id, isHq)
|
? MakeAggregatedItemKey(id, isHq)
|
||||||
: MakeNaturalSlotKey(inventoryType, slot);
|
: MakeNaturalSlotKey(inventoryType, slot);
|
||||||
|
|
||||||
Services.Logger.DebugOnly($"Slot {inventoryType}[{slot}] ItemId={id} Qty={quantity} Key=0x{key: X16}");
|
Services.Logger.DebugOnly($"Slot {inventoryType}[{slot}] ItemId={id} Qty={quantity} Key=0x{key: X16}");
|
||||||
@@ -120,7 +120,7 @@ public static unsafe class InventoryScanner
|
|||||||
ulong key = kvp.Key;
|
ulong key = kvp.Key;
|
||||||
AggregatedItem agg = kvp.Value;
|
AggregatedItem agg = kvp.Value;
|
||||||
|
|
||||||
if (!itemInfoByKey.TryGetValue(key, out ItemInfo? info))
|
if (!itemInfoByKey.TryGetValue(key, out ItemInfo? info))
|
||||||
{
|
{
|
||||||
info = new ItemInfo
|
info = new ItemInfo
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public static unsafe class InventoryState
|
|||||||
InventoryStackMode stackMode = config.General.StackMode;
|
InventoryStackMode stackMode = config.General.StackMode;
|
||||||
bool userCategoriesEnabled = config.Categories.UserCategoriesEnabled;
|
bool userCategoriesEnabled = config.Categories.UserCategoriesEnabled;
|
||||||
bool gameCategoriesEnabled = config.Categories.GameCategoriesEnabled;
|
bool gameCategoriesEnabled = config.Categories.GameCategoriesEnabled;
|
||||||
List<UserCategoryDefinition> userCategories = config.Categories.UserCategories;
|
List<UserCategoryDefinition> userCategories = config.Categories.UserCategories.Where(category => category.Enabled).ToList();
|
||||||
|
|
||||||
Services.Logger.DebugOnly($"RefreshFromGame StackMode={stackMode}");
|
Services.Logger.DebugOnly($"RefreshFromGame StackMode={stackMode}");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using AetherBags.Addons;
|
||||||
|
using AetherBags.Configuration;
|
||||||
|
using KamiToolKit.Nodes;
|
||||||
|
using KamiToolKit.Premade.Nodes;
|
||||||
|
|
||||||
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
|
public class CategoryConfigurationNode : ConfigNode<CategoryWrapper>
|
||||||
|
{
|
||||||
|
private readonly ScrollingAreaNode<VerticalListNode> _categoryList;
|
||||||
|
private CategoryDefinitionConfigurationNode? _activeNode;
|
||||||
|
|
||||||
|
public Action? OnCategoryChanged { get; set; }
|
||||||
|
|
||||||
|
public CategoryConfigurationNode()
|
||||||
|
{
|
||||||
|
_categoryList = new ScrollingAreaNode<VerticalListNode>
|
||||||
|
{
|
||||||
|
ContentHeight = 100.0f,
|
||||||
|
AutoHideScrollBar = true,
|
||||||
|
};
|
||||||
|
_categoryList.ContentNode.FitContents = true;
|
||||||
|
_categoryList.AttachNode(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OptionChanged(CategoryWrapper? option)
|
||||||
|
{
|
||||||
|
if (option?.CategoryDefinition is null)
|
||||||
|
{
|
||||||
|
_categoryList.IsVisible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_categoryList.IsVisible = true;
|
||||||
|
|
||||||
|
if (_activeNode is null)
|
||||||
|
{
|
||||||
|
_activeNode = new CategoryDefinitionConfigurationNode(option.CategoryDefinition)
|
||||||
|
{
|
||||||
|
Size = _categoryList.ContentNode.Size,
|
||||||
|
OnLayoutChanged = UpdateScrollHeight,
|
||||||
|
OnCategoryPropertyChanged = OnCategoryChanged,
|
||||||
|
};
|
||||||
|
_categoryList.ContentNode.AddNode(_activeNode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_activeNode.SetCategory(option.CategoryDefinition);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateScrollHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateScrollHeight()
|
||||||
|
{
|
||||||
|
_categoryList.ContentNode.RecalculateLayout();
|
||||||
|
_categoryList.ContentHeight = _categoryList.ContentNode.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSizeChanged()
|
||||||
|
{
|
||||||
|
base.OnSizeChanged();
|
||||||
|
_categoryList.Size = Size;
|
||||||
|
_categoryList.ContentNode.Width = Width;
|
||||||
|
|
||||||
|
foreach (var node in _categoryList.ContentNode.GetNodes<CategoryDefinitionConfigurationNode>())
|
||||||
|
{
|
||||||
|
node.Width = Width;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateScrollHeight();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,467 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using AetherBags.Configuration;
|
||||||
|
using AetherBags.Nodes.Color;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
using KamiToolKit.Classes;
|
||||||
|
using KamiToolKit.Nodes;
|
||||||
|
using Lumina.Excel;
|
||||||
|
using Lumina.Excel.Sheets;
|
||||||
|
using Action = System.Action;
|
||||||
|
|
||||||
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
|
public sealed class CategoryDefinitionConfigurationNode : VerticalListNode
|
||||||
|
{
|
||||||
|
private readonly CheckboxNode _enabledCheckbox;
|
||||||
|
private readonly TextInputNode _nameInputNode;
|
||||||
|
private readonly TextInputNode _descriptionInputNode;
|
||||||
|
private readonly ColorInputRow _colorInputNode;
|
||||||
|
private readonly NumericInputNode _priorityInputNode;
|
||||||
|
private readonly NumericInputNode _orderInputNode;
|
||||||
|
|
||||||
|
private readonly CheckboxNode _levelEnabledCheckbox;
|
||||||
|
private readonly NumericInputNode _levelMinNode;
|
||||||
|
private readonly NumericInputNode _levelMaxNode;
|
||||||
|
|
||||||
|
private readonly CheckboxNode _itemLevelEnabledCheckbox;
|
||||||
|
private readonly NumericInputNode _itemLevelMinNode;
|
||||||
|
private readonly NumericInputNode _itemLevelMaxNode;
|
||||||
|
|
||||||
|
private readonly CheckboxNode _vendorPriceEnabledCheckbox;
|
||||||
|
private readonly NumericInputNode _vendorPriceMinNode;
|
||||||
|
private readonly NumericInputNode _vendorPriceMaxNode;
|
||||||
|
|
||||||
|
private readonly StateFilterRowNode _untradableFilter;
|
||||||
|
private readonly StateFilterRowNode _uniqueFilter;
|
||||||
|
private readonly StateFilterRowNode _collectableFilter;
|
||||||
|
private readonly StateFilterRowNode _dyeableFilter;
|
||||||
|
private readonly StateFilterRowNode _repairableFilter;
|
||||||
|
|
||||||
|
private readonly UintListEditorNode _allowedItemIdsEditor;
|
||||||
|
private readonly StringListEditorNode _allowedNamePatternsEditor;
|
||||||
|
private readonly UintListEditorNode _allowedUiCategoriesEditor;
|
||||||
|
private readonly RarityEditorNode _allowedRaritiesEditor;
|
||||||
|
|
||||||
|
private bool _isInitialized;
|
||||||
|
|
||||||
|
private static ExcelSheet<Item>? _sItemSheet;
|
||||||
|
private static ExcelSheet<ItemUICategory>? _sUICategorySheet;
|
||||||
|
|
||||||
|
public Action? OnLayoutChanged { get; set; }
|
||||||
|
|
||||||
|
public Action? OnCategoryPropertyChanged { get; set; }
|
||||||
|
|
||||||
|
private UserCategoryDefinition CategoryDefinition { get; set; }
|
||||||
|
|
||||||
|
public CategoryDefinitionConfigurationNode(UserCategoryDefinition categoryDefinition)
|
||||||
|
{
|
||||||
|
CategoryDefinition = categoryDefinition;
|
||||||
|
|
||||||
|
_sItemSheet ??= Services.DataManager.GetExcelSheet<Item>();
|
||||||
|
_sUICategorySheet ??= Services.DataManager.GetExcelSheet<ItemUICategory>();
|
||||||
|
|
||||||
|
FitContents = true;
|
||||||
|
ItemSpacing = 4.0f;
|
||||||
|
|
||||||
|
AddNode(CreateSectionHeader("Basic Settings"));
|
||||||
|
|
||||||
|
_enabledCheckbox = new CheckboxNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(200, 20),
|
||||||
|
String = "Enabled",
|
||||||
|
IsChecked = CategoryDefinition.Enabled,
|
||||||
|
OnClick = isChecked =>
|
||||||
|
{
|
||||||
|
CategoryDefinition.Enabled = isChecked;
|
||||||
|
NotifyChanged();
|
||||||
|
NotifyCategoryPropertyChanged();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
AddNode(_enabledCheckbox);
|
||||||
|
|
||||||
|
AddNode(new LabelTextNode { Size = new Vector2(80, 20), String = "Name:" });
|
||||||
|
_nameInputNode = new TextInputNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(250, 28),
|
||||||
|
String = CategoryDefinition.Name,
|
||||||
|
PlaceholderString = CategoryDefinition.Name.IsNullOrWhitespace() ? "Category Name" : "",
|
||||||
|
OnInputReceived = name =>
|
||||||
|
{
|
||||||
|
CategoryDefinition.Name = name.ExtractText();
|
||||||
|
NotifyChanged();
|
||||||
|
NotifyCategoryPropertyChanged();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
AddNode(_nameInputNode);
|
||||||
|
|
||||||
|
AddNode(new LabelTextNode { Size = new Vector2(80, 20), String = "Description:" });
|
||||||
|
_descriptionInputNode = new TextInputNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(250, 28),
|
||||||
|
String = CategoryDefinition.Description,
|
||||||
|
PlaceholderString = CategoryDefinition.Description.IsNullOrWhitespace() ? "Optional description" : "",
|
||||||
|
OnInputReceived = desc =>
|
||||||
|
{
|
||||||
|
CategoryDefinition.Description = desc.ExtractText();
|
||||||
|
NotifyChanged();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
AddNode(_descriptionInputNode);
|
||||||
|
|
||||||
|
_colorInputNode = new ColorInputRow
|
||||||
|
{
|
||||||
|
Label = "Color",
|
||||||
|
Size = new Vector2(300, 28),
|
||||||
|
CurrentColor = CategoryDefinition.Color,
|
||||||
|
DefaultColor = new UserCategoryDefinition().Color,
|
||||||
|
OnColorConfirmed = color =>
|
||||||
|
{
|
||||||
|
CategoryDefinition.Color = color;
|
||||||
|
NotifyChanged();
|
||||||
|
},
|
||||||
|
OnColorCanceled = color =>
|
||||||
|
{
|
||||||
|
CategoryDefinition.Color = color;
|
||||||
|
NotifyChanged();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
AddNode(_colorInputNode);
|
||||||
|
|
||||||
|
AddNode(new LabelTextNode { Size = new Vector2(80, 20), String = "Priority:" });
|
||||||
|
_priorityInputNode = new NumericInputNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(120, 28),
|
||||||
|
Min = 0,
|
||||||
|
Max = 1000,
|
||||||
|
Step = 1,
|
||||||
|
Value = CategoryDefinition.Priority,
|
||||||
|
OnValueUpdate = val =>
|
||||||
|
{
|
||||||
|
CategoryDefinition.Priority = val;
|
||||||
|
NotifyChanged();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
AddNode(_priorityInputNode);
|
||||||
|
|
||||||
|
AddNode(new LabelTextNode { Size = new Vector2(80, 20), String = "Order:" });
|
||||||
|
_orderInputNode = new NumericInputNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(120, 28),
|
||||||
|
Min = 0,
|
||||||
|
Max = 9999,
|
||||||
|
Step = 1,
|
||||||
|
Value = CategoryDefinition.Order,
|
||||||
|
OnValueUpdate = val =>
|
||||||
|
{
|
||||||
|
CategoryDefinition.Order = val;
|
||||||
|
NotifyChanged();
|
||||||
|
NotifyCategoryPropertyChanged();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
AddNode(_orderInputNode);
|
||||||
|
|
||||||
|
AddNode(CreateSectionHeader("Range Filters"));
|
||||||
|
|
||||||
|
(_levelEnabledCheckbox, _levelMinNode, _levelMaxNode) = CreateRangeFilter(
|
||||||
|
"Level",
|
||||||
|
CategoryDefinition.Rules.Level,
|
||||||
|
0, 200,
|
||||||
|
(enabled, min, max) =>
|
||||||
|
{
|
||||||
|
CategoryDefinition.Rules.Level.Enabled = enabled;
|
||||||
|
CategoryDefinition.Rules.Level.Min = min;
|
||||||
|
CategoryDefinition.Rules.Level.Max = max;
|
||||||
|
NotifyChanged();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
(_itemLevelEnabledCheckbox, _itemLevelMinNode, _itemLevelMaxNode) = CreateRangeFilter(
|
||||||
|
"Item Level",
|
||||||
|
CategoryDefinition.Rules.ItemLevel,
|
||||||
|
0, 2000,
|
||||||
|
(enabled, min, max) =>
|
||||||
|
{
|
||||||
|
CategoryDefinition.Rules.ItemLevel.Enabled = enabled;
|
||||||
|
CategoryDefinition.Rules.ItemLevel.Min = min;
|
||||||
|
CategoryDefinition.Rules.ItemLevel.Max = max;
|
||||||
|
NotifyChanged();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
(_vendorPriceEnabledCheckbox, _vendorPriceMinNode, _vendorPriceMaxNode) = CreateRangeFilterUint(
|
||||||
|
"Vendor Price",
|
||||||
|
CategoryDefinition.Rules.VendorPrice,
|
||||||
|
0, 9_999_999
|
||||||
|
);
|
||||||
|
|
||||||
|
AddNode(CreateSectionHeader("State Filters"));
|
||||||
|
|
||||||
|
_untradableFilter = new StateFilterRowNode("Untradable", CategoryDefinition.Rules.Untradable, NotifyChanged);
|
||||||
|
AddNode(_untradableFilter);
|
||||||
|
|
||||||
|
_uniqueFilter = new StateFilterRowNode("Unique", CategoryDefinition.Rules.Unique, NotifyChanged);
|
||||||
|
AddNode(_uniqueFilter);
|
||||||
|
|
||||||
|
_collectableFilter = new StateFilterRowNode("Collectable", CategoryDefinition.Rules.Collectable, NotifyChanged);
|
||||||
|
AddNode(_collectableFilter);
|
||||||
|
|
||||||
|
_dyeableFilter = new StateFilterRowNode("Dyeable", CategoryDefinition.Rules.Dyeable, NotifyChanged);
|
||||||
|
AddNode(_dyeableFilter);
|
||||||
|
|
||||||
|
_repairableFilter = new StateFilterRowNode("Repairable", CategoryDefinition.Rules.Repairable, NotifyChanged);
|
||||||
|
AddNode(_repairableFilter);
|
||||||
|
|
||||||
|
AddNode(CreateSectionHeader("List Filters"));
|
||||||
|
|
||||||
|
_allowedItemIdsEditor = new UintListEditorNode(
|
||||||
|
"Allowed Item IDs:",
|
||||||
|
CategoryDefinition.Rules.AllowedItemIds,
|
||||||
|
OnListChanged,
|
||||||
|
ResolveItemName
|
||||||
|
);
|
||||||
|
AddNode(_allowedItemIdsEditor);
|
||||||
|
|
||||||
|
_allowedNamePatternsEditor = new StringListEditorNode(
|
||||||
|
"Name Patterns (Regex):",
|
||||||
|
CategoryDefinition.Rules.AllowedItemNamePatterns,
|
||||||
|
OnListChanged
|
||||||
|
);
|
||||||
|
AddNode(_allowedNamePatternsEditor);
|
||||||
|
|
||||||
|
_allowedUiCategoriesEditor = new UintListEditorNode(
|
||||||
|
"UI Categories:",
|
||||||
|
CategoryDefinition.Rules.AllowedUiCategoryIds,
|
||||||
|
OnListChanged,
|
||||||
|
ResolveUiCategoryName
|
||||||
|
);
|
||||||
|
AddNode(_allowedUiCategoriesEditor);
|
||||||
|
|
||||||
|
_allowedRaritiesEditor = new RarityEditorNode(
|
||||||
|
CategoryDefinition.Rules.AllowedRarities,
|
||||||
|
NotifyChanged
|
||||||
|
);
|
||||||
|
AddNode(_allowedRaritiesEditor);
|
||||||
|
|
||||||
|
_isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnListChanged()
|
||||||
|
{
|
||||||
|
NotifyChanged();
|
||||||
|
RecalculateLayout();
|
||||||
|
OnLayoutChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveItemName(uint itemId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var item = _sItemSheet?.GetRow(itemId);
|
||||||
|
return item?.Name.ToString() ?? "Unknown";
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveUiCategoryName(uint categoryId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var category = _sUICategorySheet?.GetRow(categoryId);
|
||||||
|
return category?.Name.ToString() ?? "Unknown";
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void NotifyChanged()
|
||||||
|
{
|
||||||
|
System.AddonInventoryWindow?.ManualInventoryRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NotifyCategoryPropertyChanged()
|
||||||
|
{
|
||||||
|
OnCategoryPropertyChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LabelTextNode CreateSectionHeader(string text)
|
||||||
|
{
|
||||||
|
return new LabelTextNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(300, 22),
|
||||||
|
String = text,
|
||||||
|
TextColor = ColorHelper.GetColor(2),
|
||||||
|
TextOutlineColor = ColorHelper.GetColor(0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private (CheckboxNode enabled, NumericInputNode min, NumericInputNode max) CreateRangeFilter(
|
||||||
|
string label,
|
||||||
|
RangeFilter<int> filter,
|
||||||
|
int minBound,
|
||||||
|
int maxBound,
|
||||||
|
Action<bool, int, int> onUpdate)
|
||||||
|
{
|
||||||
|
var enabledCheckbox = new CheckboxNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(200, 20),
|
||||||
|
String = $"{label} Filter",
|
||||||
|
IsChecked = filter.Enabled,
|
||||||
|
};
|
||||||
|
AddNode(enabledCheckbox);
|
||||||
|
|
||||||
|
var minNode = new NumericInputNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(120, 28),
|
||||||
|
Min = minBound,
|
||||||
|
Max = maxBound,
|
||||||
|
Value = filter.Min,
|
||||||
|
IsEnabled = filter.Enabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
var maxNode = new NumericInputNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(120, 28),
|
||||||
|
Min = minBound,
|
||||||
|
Max = maxBound,
|
||||||
|
Value = filter.Max,
|
||||||
|
IsEnabled = filter.Enabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
var rangeRow = new HorizontalListNode { Size = new Vector2(300, 28), ItemSpacing = 8.0f };
|
||||||
|
rangeRow.AddNode(new LabelTextNode { Size = new Vector2(30, 28), String = "Min:" });
|
||||||
|
rangeRow.AddNode(minNode);
|
||||||
|
rangeRow.AddNode(new LabelTextNode { Size = new Vector2(30, 28), String = "Max:" });
|
||||||
|
rangeRow.AddNode(maxNode);
|
||||||
|
AddNode(rangeRow);
|
||||||
|
|
||||||
|
enabledCheckbox.OnClick = isChecked =>
|
||||||
|
{
|
||||||
|
minNode.IsEnabled = isChecked;
|
||||||
|
maxNode.IsEnabled = isChecked;
|
||||||
|
onUpdate(isChecked, minNode.Value, maxNode.Value);
|
||||||
|
};
|
||||||
|
|
||||||
|
minNode.OnValueUpdate = val => onUpdate(enabledCheckbox.IsChecked, val, maxNode.Value);
|
||||||
|
maxNode.OnValueUpdate = val => onUpdate(enabledCheckbox.IsChecked, minNode.Value, val);
|
||||||
|
|
||||||
|
return (enabledCheckbox, minNode, maxNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private (CheckboxNode enabled, NumericInputNode min, NumericInputNode max) CreateRangeFilterUint(
|
||||||
|
string label,
|
||||||
|
RangeFilter<uint> filter,
|
||||||
|
int minBound,
|
||||||
|
int maxBound)
|
||||||
|
{
|
||||||
|
var enabledCheckbox = new CheckboxNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(200, 20),
|
||||||
|
String = $"{label} Filter",
|
||||||
|
IsChecked = filter.Enabled,
|
||||||
|
};
|
||||||
|
AddNode(enabledCheckbox);
|
||||||
|
|
||||||
|
var minNode = new NumericInputNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(120, 28),
|
||||||
|
Min = minBound,
|
||||||
|
Max = maxBound,
|
||||||
|
Value = (int)filter.Min,
|
||||||
|
IsEnabled = filter.Enabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
var maxNode = new NumericInputNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(120, 28),
|
||||||
|
Min = minBound,
|
||||||
|
Max = maxBound,
|
||||||
|
Value = (int)Math.Min(filter.Max, maxBound),
|
||||||
|
IsEnabled = filter.Enabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
var rangeRow = new HorizontalListNode { Size = new Vector2(300, 28), ItemSpacing = 8.0f };
|
||||||
|
rangeRow.AddNode(new LabelTextNode { Size = new Vector2(30, 28), String = "Min:" });
|
||||||
|
rangeRow.AddNode(minNode);
|
||||||
|
rangeRow.AddNode(new LabelTextNode { Size = new Vector2(30, 28), String = "Max:" });
|
||||||
|
rangeRow.AddNode(maxNode);
|
||||||
|
AddNode(rangeRow);
|
||||||
|
|
||||||
|
enabledCheckbox.OnClick = isChecked =>
|
||||||
|
{
|
||||||
|
minNode.IsEnabled = isChecked;
|
||||||
|
maxNode.IsEnabled = isChecked;
|
||||||
|
CategoryDefinition.Rules.VendorPrice.Enabled = isChecked;
|
||||||
|
NotifyChanged();
|
||||||
|
};
|
||||||
|
|
||||||
|
minNode.OnValueUpdate = value =>
|
||||||
|
{
|
||||||
|
CategoryDefinition.Rules.VendorPrice.Min = (uint)value;
|
||||||
|
NotifyChanged();
|
||||||
|
};
|
||||||
|
|
||||||
|
maxNode.OnValueUpdate = value =>
|
||||||
|
{
|
||||||
|
CategoryDefinition.Rules.VendorPrice.Max = (uint)value;
|
||||||
|
NotifyChanged();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (enabledCheckbox, minNode, maxNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCategory(UserCategoryDefinition newCategory)
|
||||||
|
{
|
||||||
|
CategoryDefinition = newCategory;
|
||||||
|
RefreshValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshValues()
|
||||||
|
{
|
||||||
|
if (! _isInitialized) return;
|
||||||
|
|
||||||
|
_enabledCheckbox.IsChecked = CategoryDefinition.Enabled;
|
||||||
|
_colorInputNode.CurrentColor = CategoryDefinition.Color;
|
||||||
|
_nameInputNode.String = CategoryDefinition.Name;
|
||||||
|
_descriptionInputNode.String = CategoryDefinition.Description;
|
||||||
|
_priorityInputNode.Value = CategoryDefinition.Priority;
|
||||||
|
_orderInputNode.Value = CategoryDefinition.Order;
|
||||||
|
|
||||||
|
RefreshRangeFilter(_levelEnabledCheckbox, _levelMinNode, _levelMaxNode, CategoryDefinition.Rules.Level);
|
||||||
|
RefreshRangeFilter(_itemLevelEnabledCheckbox, _itemLevelMinNode, _itemLevelMaxNode, CategoryDefinition.Rules.ItemLevel);
|
||||||
|
|
||||||
|
_vendorPriceEnabledCheckbox.IsChecked = CategoryDefinition.Rules.VendorPrice.Enabled;
|
||||||
|
_vendorPriceMinNode.Value = (int)CategoryDefinition.Rules.VendorPrice.Min;
|
||||||
|
_vendorPriceMaxNode.Value = (int)Math.Min(CategoryDefinition.Rules.VendorPrice.Max, int.MaxValue);
|
||||||
|
_vendorPriceMinNode.IsEnabled = CategoryDefinition.Rules.VendorPrice.Enabled;
|
||||||
|
_vendorPriceMaxNode.IsEnabled = CategoryDefinition.Rules.VendorPrice.Enabled;
|
||||||
|
|
||||||
|
_untradableFilter.SetState(CategoryDefinition.Rules.Untradable);
|
||||||
|
_uniqueFilter.SetState(CategoryDefinition.Rules.Unique);
|
||||||
|
_collectableFilter.SetState(CategoryDefinition.Rules.Collectable);
|
||||||
|
_dyeableFilter.SetState(CategoryDefinition.Rules.Dyeable);
|
||||||
|
_repairableFilter.SetState(CategoryDefinition.Rules.Repairable);
|
||||||
|
|
||||||
|
_allowedItemIdsEditor.SetList(CategoryDefinition.Rules.AllowedItemIds);
|
||||||
|
_allowedNamePatternsEditor.SetList(CategoryDefinition.Rules.AllowedItemNamePatterns);
|
||||||
|
_allowedUiCategoriesEditor.SetList(CategoryDefinition.Rules.AllowedUiCategoryIds);
|
||||||
|
_allowedRaritiesEditor.SetList(CategoryDefinition.Rules.AllowedRarities);
|
||||||
|
|
||||||
|
RecalculateLayout();
|
||||||
|
OnLayoutChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RefreshRangeFilter(CheckboxNode enabled, NumericInputNode min, NumericInputNode max, RangeFilter<int> filter)
|
||||||
|
{
|
||||||
|
enabled.IsChecked = filter.Enabled;
|
||||||
|
min.Value = filter.Min;
|
||||||
|
max.Value = filter.Max;
|
||||||
|
min.IsEnabled = filter.Enabled;
|
||||||
|
max.IsEnabled = filter.Enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using AetherBags.Addons;
|
||||||
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
|
public class CategoryScrollingAreaNode : ScrollingAreaNode<VerticalListNode>
|
||||||
|
{
|
||||||
|
private AddonCategoryConfigurationWindow? _categoryConfigurationAddon;
|
||||||
|
private readonly TextButtonNode _categoryConfigurationButtonNode;
|
||||||
|
|
||||||
|
public CategoryScrollingAreaNode()
|
||||||
|
{
|
||||||
|
InitializeCategoryAddon();
|
||||||
|
|
||||||
|
_categoryConfigurationButtonNode = new TextButtonNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(300, 28),
|
||||||
|
String = "Configure Categories",
|
||||||
|
OnClick = () => _categoryConfigurationAddon?.Toggle(),
|
||||||
|
};
|
||||||
|
_categoryConfigurationButtonNode.AttachNode(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeCategoryAddon() {
|
||||||
|
if (_categoryConfigurationAddon is not null) return;
|
||||||
|
|
||||||
|
_categoryConfigurationAddon = new AddonCategoryConfigurationWindow {
|
||||||
|
Size = new Vector2(700.0f, 500.0f),
|
||||||
|
InternalName = "AetherBags_CategoryConfig",
|
||||||
|
Title = "Category Configuration Window",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using KamiToolKit.Classes;
|
||||||
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
|
public sealed class RarityEditorNode : VerticalListNode
|
||||||
|
{
|
||||||
|
private static readonly string[] RarityNames = { "Common (White)", "Uncommon (Green)", "Rare (Blue)", "Relic (Purple)", "Aetherial (Pink)" };
|
||||||
|
|
||||||
|
private List<int> _list;
|
||||||
|
private readonly List<CheckboxNode> _checkboxes = new();
|
||||||
|
private readonly Action? _onChanged;
|
||||||
|
|
||||||
|
public RarityEditorNode(List<int> list, Action? onChanged = null)
|
||||||
|
{
|
||||||
|
_list = list;
|
||||||
|
_onChanged = onChanged;
|
||||||
|
|
||||||
|
FitContents = true;
|
||||||
|
ItemSpacing = 2.0f;
|
||||||
|
|
||||||
|
var headerLabel = new LabelTextNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(280, 18),
|
||||||
|
String = "Allowed Rarities:",
|
||||||
|
TextColor = ColorHelper.GetColor(8),
|
||||||
|
};
|
||||||
|
AddNode(headerLabel);
|
||||||
|
|
||||||
|
for (int i = 0; i < RarityNames.Length; i++)
|
||||||
|
{
|
||||||
|
var rarity = i;
|
||||||
|
var checkbox = new CheckboxNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(200, 20),
|
||||||
|
String = RarityNames[i],
|
||||||
|
IsChecked = _list.Contains(i),
|
||||||
|
OnClick = isChecked =>
|
||||||
|
{
|
||||||
|
if (isChecked && !_list.Contains(rarity))
|
||||||
|
{
|
||||||
|
_list.Add(rarity);
|
||||||
|
_list.Sort();
|
||||||
|
}
|
||||||
|
else if (!isChecked && _list.Contains(rarity))
|
||||||
|
{
|
||||||
|
_list.Remove(rarity);
|
||||||
|
}
|
||||||
|
_onChanged?.Invoke();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
_checkboxes.Add(checkbox);
|
||||||
|
AddNode(checkbox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetList(List<int> newList)
|
||||||
|
{
|
||||||
|
_list = newList;
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _checkboxes.Count; i++)
|
||||||
|
{
|
||||||
|
_checkboxes[i].IsChecked = _list.Contains(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using AetherBags.Configuration;
|
||||||
|
using KamiToolKit.Classes;
|
||||||
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
|
public sealed class StateFilterRowNode : HorizontalListNode
|
||||||
|
{
|
||||||
|
private readonly LabelTextNode _labelNode;
|
||||||
|
private readonly TextButtonNode _stateButton;
|
||||||
|
private readonly Action? _onChanged;
|
||||||
|
private StateFilter _filter;
|
||||||
|
|
||||||
|
private static readonly string[] StateLabels = { "Ignored", "Allow", "Disallow" };
|
||||||
|
|
||||||
|
public StateFilterRowNode(string label, StateFilter filter, Action? onChanged = null)
|
||||||
|
{
|
||||||
|
_filter = filter;
|
||||||
|
_onChanged = onChanged;
|
||||||
|
Size = new Vector2(280, 24);
|
||||||
|
ItemSpacing = 8.0f;
|
||||||
|
|
||||||
|
_labelNode = new LabelTextNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(100, 24),
|
||||||
|
String = $"{label}:",
|
||||||
|
TextColor = ColorHelper.GetColor(8),
|
||||||
|
};
|
||||||
|
AddNode(_labelNode);
|
||||||
|
|
||||||
|
_stateButton = new TextButtonNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(100, 24),
|
||||||
|
String = StateLabels[_filter.State],
|
||||||
|
OnClick = CycleState,
|
||||||
|
};
|
||||||
|
AddNode(_stateButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CycleState()
|
||||||
|
{
|
||||||
|
_filter.State = (_filter.State + 1) % 3;
|
||||||
|
_stateButton.String = StateLabels[_filter.State];
|
||||||
|
_onChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetState(StateFilter newFilter)
|
||||||
|
{
|
||||||
|
_filter = newFilter;
|
||||||
|
_stateButton.String = StateLabels[_filter.State];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using KamiToolKit.Classes;
|
||||||
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
|
public sealed class StringListEditorNode : VerticalListNode
|
||||||
|
{
|
||||||
|
private List<string> _list;
|
||||||
|
private readonly TextInputNode _addInput;
|
||||||
|
private readonly VerticalListNode _itemsContainer;
|
||||||
|
private readonly Action? _onChanged;
|
||||||
|
|
||||||
|
public StringListEditorNode(string label, List<string> list, Action? onChanged = null)
|
||||||
|
{
|
||||||
|
_list = list;
|
||||||
|
_onChanged = onChanged;
|
||||||
|
|
||||||
|
FitContents = true;
|
||||||
|
ItemSpacing = 4.0f;
|
||||||
|
|
||||||
|
var headerLabel = new LabelTextNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(280, 18),
|
||||||
|
String = label,
|
||||||
|
TextColor = ColorHelper.GetColor(8),
|
||||||
|
};
|
||||||
|
AddNode(headerLabel);
|
||||||
|
|
||||||
|
_itemsContainer = new VerticalListNode
|
||||||
|
{
|
||||||
|
FitContents = true,
|
||||||
|
ItemSpacing = 2.0f,
|
||||||
|
};
|
||||||
|
AddNode(_itemsContainer);
|
||||||
|
|
||||||
|
var addRow = new HorizontalListNode { Size = new Vector2(300, 28), ItemSpacing = 4.0f };
|
||||||
|
|
||||||
|
_addInput = new TextInputNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(200, 28),
|
||||||
|
PlaceholderString = "Add new...",
|
||||||
|
OnInputComplete = text =>
|
||||||
|
{
|
||||||
|
var value = text.ExtractText();
|
||||||
|
if (!string.IsNullOrWhiteSpace(value) && ! _list.Contains(value))
|
||||||
|
{
|
||||||
|
_list.Add(value);
|
||||||
|
_addInput.String = "";
|
||||||
|
RefreshItems();
|
||||||
|
_onChanged?.Invoke();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
addRow.AddNode(_addInput);
|
||||||
|
|
||||||
|
var addButton = new TextButtonNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(60, 28),
|
||||||
|
String = "Add",
|
||||||
|
OnClick = () =>
|
||||||
|
{
|
||||||
|
var value = _addInput.String;
|
||||||
|
if (!string.IsNullOrWhiteSpace(value) && !_list.Contains(value))
|
||||||
|
{
|
||||||
|
_list.Add(value);
|
||||||
|
_addInput.String = "";
|
||||||
|
RefreshItems();
|
||||||
|
_onChanged?.Invoke();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
addRow.AddNode(addButton);
|
||||||
|
|
||||||
|
AddNode(addRow);
|
||||||
|
|
||||||
|
RefreshItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetList(List<string> newList)
|
||||||
|
{
|
||||||
|
_list = newList;
|
||||||
|
RefreshItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshItems()
|
||||||
|
{
|
||||||
|
_itemsContainer.SyncWithListData(
|
||||||
|
_list,
|
||||||
|
node => node.Value,
|
||||||
|
value => new StringListItemNode(value)
|
||||||
|
{
|
||||||
|
Size = new Vector2(280, 22),
|
||||||
|
OnRemove = () =>
|
||||||
|
{
|
||||||
|
_list.Remove(value);
|
||||||
|
RefreshItems();
|
||||||
|
_onChanged?.Invoke();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
_itemsContainer.RecalculateLayout();
|
||||||
|
RecalculateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
RefreshItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class StringListItemNode : HorizontalListNode
|
||||||
|
{
|
||||||
|
public string Value { get; }
|
||||||
|
public Action? OnRemove { get; init; }
|
||||||
|
|
||||||
|
public StringListItemNode(string value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
ItemSpacing = 4.0f;
|
||||||
|
|
||||||
|
var itemLabel = new LabelTextNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(220, 22),
|
||||||
|
String = value,
|
||||||
|
TextColor = ColorHelper.GetColor(3),
|
||||||
|
};
|
||||||
|
AddNode(itemLabel);
|
||||||
|
|
||||||
|
var removeButton = new TextButtonNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(50, 22),
|
||||||
|
String = "X",
|
||||||
|
OnClick = () => OnRemove?.Invoke(),
|
||||||
|
};
|
||||||
|
AddNode(removeButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using KamiToolKit.Classes;
|
||||||
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
|
public sealed class UintListEditorNode : VerticalListNode
|
||||||
|
{
|
||||||
|
private List<uint> _list;
|
||||||
|
private readonly NumericInputNode _addInput;
|
||||||
|
private readonly VerticalListNode _itemsContainer;
|
||||||
|
private readonly Action? _onChanged;
|
||||||
|
private readonly Func<uint, string>? _labelResolver;
|
||||||
|
|
||||||
|
public UintListEditorNode(string label, List<uint> list, Action? onChanged = null, Func<uint, string>? labelResolver = null)
|
||||||
|
{
|
||||||
|
_list = list;
|
||||||
|
_onChanged = onChanged;
|
||||||
|
_labelResolver = labelResolver;
|
||||||
|
|
||||||
|
FitContents = true;
|
||||||
|
ItemSpacing = 4.0f;
|
||||||
|
|
||||||
|
var headerLabel = new LabelTextNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(280, 18),
|
||||||
|
String = label,
|
||||||
|
TextColor = ColorHelper.GetColor(8),
|
||||||
|
};
|
||||||
|
AddNode(headerLabel);
|
||||||
|
|
||||||
|
_itemsContainer = new VerticalListNode
|
||||||
|
{
|
||||||
|
FitContents = true,
|
||||||
|
ItemSpacing = 2.0f,
|
||||||
|
};
|
||||||
|
AddNode(_itemsContainer);
|
||||||
|
|
||||||
|
var addRow = new HorizontalListNode { Size = new Vector2(300, 28), ItemSpacing = 4.0f };
|
||||||
|
|
||||||
|
_addInput = new NumericInputNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(120, 28),
|
||||||
|
Min = 0,
|
||||||
|
Max = int.MaxValue,
|
||||||
|
Value = 0,
|
||||||
|
};
|
||||||
|
addRow.AddNode(_addInput);
|
||||||
|
|
||||||
|
var addButton = new TextButtonNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(60, 28),
|
||||||
|
String = "Add",
|
||||||
|
OnClick = () =>
|
||||||
|
{
|
||||||
|
var value = (uint)_addInput.Value;
|
||||||
|
if (! _list.Contains(value))
|
||||||
|
{
|
||||||
|
_list.Add(value);
|
||||||
|
RefreshItems();
|
||||||
|
_onChanged?.Invoke();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
addRow.AddNode(addButton);
|
||||||
|
|
||||||
|
AddNode(addRow);
|
||||||
|
|
||||||
|
RefreshItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetList(List<uint> newList)
|
||||||
|
{
|
||||||
|
_list = newList;
|
||||||
|
RefreshItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshItems()
|
||||||
|
{
|
||||||
|
_itemsContainer.SyncWithListData(
|
||||||
|
_list,
|
||||||
|
node => node.Value,
|
||||||
|
value => new UintListItemNode(value, _labelResolver)
|
||||||
|
{
|
||||||
|
Size = new Vector2(280, 22),
|
||||||
|
OnRemove = () =>
|
||||||
|
{
|
||||||
|
_list.Remove(value);
|
||||||
|
RefreshItems();
|
||||||
|
_onChanged?.Invoke();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
_itemsContainer.RecalculateLayout();
|
||||||
|
RecalculateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
RefreshItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class UintListItemNode : HorizontalListNode
|
||||||
|
{
|
||||||
|
public uint Value { get; }
|
||||||
|
public Action? OnRemove { get; init; }
|
||||||
|
|
||||||
|
public UintListItemNode(uint value, Func<uint, string>? labelResolver = null)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
ItemSpacing = 4.0f;
|
||||||
|
|
||||||
|
var displayText = labelResolver != null ? $"{value} - {labelResolver(value)}" : value.ToString();
|
||||||
|
var itemLabel = new LabelTextNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(220, 22),
|
||||||
|
String = displayText,
|
||||||
|
TextColor = ColorHelper.GetColor(3),
|
||||||
|
};
|
||||||
|
AddNode(itemLabel);
|
||||||
|
|
||||||
|
var removeButton = new TextButtonNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(50, 22),
|
||||||
|
String = "X",
|
||||||
|
OnClick = () => OnRemove?.Invoke(),
|
||||||
|
};
|
||||||
|
AddNode(removeButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
using KamiToolKit.Nodes;
|
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration;
|
|
||||||
|
|
||||||
public class CategoryScrollingAreaNode : ScrollingAreaNode<VerticalListNode>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
+2
-2
@@ -6,9 +6,9 @@ using KamiToolKit.Nodes;
|
|||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration.Currency;
|
namespace AetherBags.Nodes.Configuration.Currency;
|
||||||
|
|
||||||
public sealed class CurrencyConfigurationNode : TabbedVerticalListNode
|
public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
||||||
{
|
{
|
||||||
public CurrencyConfigurationNode()
|
public CurrencyGeneralConfigurationNode()
|
||||||
{
|
{
|
||||||
CurrencySettings config = System.Config.Currency;
|
CurrencySettings config = System.Config.Currency;
|
||||||
|
|
||||||
+2
-3
@@ -1,13 +1,12 @@
|
|||||||
using AetherBags.Nodes.Configuration.Currency;
|
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration;
|
namespace AetherBags.Nodes.Configuration.Currency;
|
||||||
|
|
||||||
public sealed class CurrencyScrollingAreaNode : ScrollingAreaNode<VerticalListNode>
|
public sealed class CurrencyScrollingAreaNode : ScrollingAreaNode<VerticalListNode>
|
||||||
{
|
{
|
||||||
public CurrencyScrollingAreaNode()
|
public CurrencyScrollingAreaNode()
|
||||||
{
|
{
|
||||||
ContentNode.AddNode(new CurrencyConfigurationNode
|
ContentNode.AddNode(new CurrencyGeneralConfigurationNode
|
||||||
{
|
{
|
||||||
Size = Size
|
Size = Size
|
||||||
});
|
});
|
||||||
+7
-5
@@ -1,11 +1,11 @@
|
|||||||
using AetherBags.Configuration;
|
|
||||||
using AetherBags.Nodes.Configuration.Layout;
|
|
||||||
using KamiToolKit.Nodes;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using AetherBags.Configuration;
|
||||||
|
using AetherBags.Nodes.Configuration.Layout;
|
||||||
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration;
|
namespace AetherBags.Nodes.Configuration.General;
|
||||||
|
|
||||||
public sealed class GeneralScrollingAreaNode : ScrollingAreaNode<VerticalListNode>
|
public sealed class GeneralScrollingAreaNode : ScrollingAreaNode<VerticalListNode>
|
||||||
{
|
{
|
||||||
@@ -28,8 +28,10 @@ public sealed class GeneralScrollingAreaNode : ScrollingAreaNode<VerticalListNod
|
|||||||
OnOptionSelected = selected =>
|
OnOptionSelected = selected =>
|
||||||
{
|
{
|
||||||
if (Enum.TryParse<InventoryStackMode>(selected, out var parsed))
|
if (Enum.TryParse<InventoryStackMode>(selected, out var parsed))
|
||||||
|
{
|
||||||
config.StackMode = parsed;
|
config.StackMode = parsed;
|
||||||
RefreshInventory();
|
System.AddonInventoryWindow.ManualInventoryRefresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ContentNode.AddNode(_stackDropDown);
|
ContentNode.AddNode(_stackDropDown);
|
||||||
@@ -23,7 +23,7 @@ public class InventoryCategoryNode : SimpleComponentNode
|
|||||||
private const float HeaderHeight = 16;
|
private const float HeaderHeight = 16;
|
||||||
private const float MinWidth = 40;
|
private const float MinWidth = 40;
|
||||||
|
|
||||||
private float? _fixedWidth;
|
private float? _fixedWidth;
|
||||||
private int _hoverRefs;
|
private int _hoverRefs;
|
||||||
private bool _headerSuppressed;
|
private bool _headerSuppressed;
|
||||||
private bool _headerExpanded;
|
private bool _headerExpanded;
|
||||||
@@ -92,7 +92,7 @@ public class InventoryCategoryNode : SimpleComponentNode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public float? FixedWidth
|
public float? FixedWidth
|
||||||
{
|
{
|
||||||
get => _fixedWidth;
|
get => _fixedWidth;
|
||||||
set
|
set
|
||||||
@@ -184,8 +184,8 @@ public class InventoryCategoryNode : SimpleComponentNode
|
|||||||
int rows = (itemCount + itemsPerLine - 1) / itemsPerLine;
|
int rows = (itemCount + itemsPerLine - 1) / itemsPerLine;
|
||||||
int actualColumns = Math.Min(itemCount, itemsPerLine);
|
int actualColumns = Math.Min(itemCount, itemsPerLine);
|
||||||
|
|
||||||
float cellW = _itemGridNode.Nodes.Count > 0 ? _itemGridNode.Nodes[0].Width : FallbackItemSize;
|
float cellW = _itemGridNode.Nodes.Count > 0 ? _itemGridNode.Nodes[0].Width : FallbackItemSize;
|
||||||
float cellH = _itemGridNode.Nodes.Count > 0 ? _itemGridNode.Nodes[0].Height : FallbackItemSize;
|
float cellH = _itemGridNode.Nodes.Count > 0 ? _itemGridNode.Nodes[0].Height : FallbackItemSize;
|
||||||
|
|
||||||
float hPad = _itemGridNode.HorizontalPadding;
|
float hPad = _itemGridNode.HorizontalPadding;
|
||||||
float vPad = _itemGridNode.VerticalPadding;
|
float vPad = _itemGridNode.VerticalPadding;
|
||||||
@@ -259,7 +259,7 @@ public class InventoryCategoryNode : SimpleComponentNode
|
|||||||
InventoryType targetContainer = targetItemInfo.Item.Container;
|
InventoryType targetContainer = targetItemInfo.Item.Container;
|
||||||
ushort targetSlot = (ushort)targetItemInfo.Item.Slot;
|
ushort targetSlot = (ushort)targetItemInfo.Item.Slot;
|
||||||
|
|
||||||
Services.Logger.Info($"[OnPayload] Moving {sourceContainer}@{sourceSlot} -> {targetContainer}@{targetSlot}");
|
Services.Logger.Debug($"[OnPayload] Moving {sourceContainer}@{sourceSlot} -> {targetContainer}@{targetSlot}");
|
||||||
|
|
||||||
InventoryMoveHelper.MoveItem(sourceContainer, sourceSlot, targetContainer, targetSlot);
|
InventoryMoveHelper.MoveItem(sourceContainer, sourceSlot, targetContainer, targetSlot);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user