Add searching, add currencies
This commit is contained in:
@@ -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());
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -195,25 +195,22 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
|
||||
{
|
||||
var header = addon->WindowHeaderCollisionNode;
|
||||
float headerW = header->Width;
|
||||
float headerH = header->Height;
|
||||
|
||||
// Center the search bar, width is 50% of header
|
||||
float searchWidth = headerW * 0.5f;
|
||||
var searchSize = new Vector2(searchWidth, 28f);
|
||||
float settingsX = headerW - 62f;
|
||||
float itemY = header->Y + (header->Height - 28f) * 0.5f;
|
||||
|
||||
float searchWidth = headerW * 0.45f;
|
||||
float searchX = (headerW - searchWidth) * 0.5f;
|
||||
float itemY = header->Y + (headerH - 28f) * 0.5f;
|
||||
|
||||
return new HeaderLayout
|
||||
{
|
||||
SearchPosition = new Vector2(searchX, itemY),
|
||||
SearchSize = searchSize,
|
||||
SearchSize = new Vector2(searchWidth, 28f),
|
||||
HeaderWidth = headerW,
|
||||
HeaderY = itemY
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
protected void InitializeBackgroundDropTarget()
|
||||
{
|
||||
BackgroundDropTarget = new DragDropNode
|
||||
@@ -363,6 +360,11 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
|
||||
float requiredWidth = maxChildWidth + (ContentStartPosition.X * 2);
|
||||
float finalWidth = Math.Clamp(requiredWidth, MinWindowWidth, MaxWindowWidth);
|
||||
|
||||
if (SettingsButtonNode != null)
|
||||
{
|
||||
SettingsButtonNode.X = finalWidth - 62f;
|
||||
}
|
||||
|
||||
float contentWidth = finalWidth - (ContentStartPosition.X * 2);
|
||||
|
||||
float footerSpace = HasFooter || HasSlotCounter ? FooterHeight + FooterTopSpacing : 0;
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Text.Json.Serialization;
|
||||
using KamiToolKit.Classes;
|
||||
|
||||
namespace AetherBags.Configuration;
|
||||
|
||||
public class CurrencySettings
|
||||
{
|
||||
[JsonIgnore]
|
||||
public const uint LimitedTomestoneId = 0xFFFF_FFFE;
|
||||
|
||||
[JsonIgnore]
|
||||
public const uint NonLimitedTomestoneId = 0xFFFF_FFFD;
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
public List<uint> DisplayedCurrencies { get; set; } = new() { 1, LimitedTomestoneId, NonLimitedTomestoneId };
|
||||
public bool ColorWhenCapped { get; set; } = true;
|
||||
public bool ColorWhenLimited { get; set; } = true;
|
||||
public Vector4 DefaultColor { get; set; } = ColorHelper.GetColor(8);
|
||||
|
||||
@@ -71,6 +71,31 @@ public static unsafe class CurrencyState
|
||||
return currencyInfoList;
|
||||
}
|
||||
|
||||
public static (uint Limited, uint NonLimited) GetCurrentTomestoneIds()
|
||||
{
|
||||
var tomestonesItemSheet = Services.DataManager.GetExcelSheet<TomestonesItem>();
|
||||
uint limitedId = 0;
|
||||
uint nonLimitedId = 0;
|
||||
|
||||
foreach (var row in tomestonesItemSheet)
|
||||
{
|
||||
var tomeSheetRef = row.Tomestones.ValueNullable;
|
||||
|
||||
if (tomeSheetRef == null || tomeSheetRef.Value.RowId == 0) continue;
|
||||
|
||||
var itemId = row.Item.RowId;
|
||||
if (itemId == 0 || itemId == 28) continue;
|
||||
|
||||
if (tomeSheetRef.Value.WeeklyLimit > 0)
|
||||
limitedId = itemId;
|
||||
else
|
||||
nonLimitedId = itemId;
|
||||
}
|
||||
|
||||
return (limitedId, nonLimitedId);
|
||||
}
|
||||
|
||||
/*
|
||||
private static uint? GetLimitedTomestoneItemIdCached()
|
||||
{
|
||||
if (_cachedLimitedTomestoneItemId.HasValue)
|
||||
@@ -96,6 +121,13 @@ public static unsafe class CurrencyState
|
||||
_cachedNonLimitedTomestoneItemId = itemId;
|
||||
return itemId;
|
||||
}
|
||||
*/
|
||||
|
||||
private static uint? GetLimitedTomestoneItemIdCached()
|
||||
=> _cachedLimitedTomestoneItemId ??= GetCurrentTomestoneIds().Limited;
|
||||
|
||||
private static uint? GetNonLimitedTomestoneItemIdCached()
|
||||
=> _cachedNonLimitedTomestoneItemId ??= GetCurrentTomestoneIds().NonLimited;
|
||||
|
||||
private static CurrencyItem ResolveCurrencyItemIdCached(uint currencyId)
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace KamiToolKit.Extensions;
|
||||
namespace AetherBags.Extensions;
|
||||
|
||||
internal static class EnumExtensions {
|
||||
extension(Enum enumValue) {
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using AetherBags.Configuration;
|
||||
using AetherBags.Nodes.Color;
|
||||
using Dalamud.Utility;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace AetherBags.Nodes.Configuration.Category;
|
||||
|
||||
public sealed class BasicSettingsSection(Func<UserCategoryDefinition> getCategoryDefinition) : ConfigurationSection(getCategoryDefinition)
|
||||
{
|
||||
public Action? OnPropertyChanged { get; init; }
|
||||
|
||||
private CheckboxNode? _enabledCheckbox;
|
||||
private CheckboxNode? _pinnedCheckbox;
|
||||
private TextInputNode? _nameInput;
|
||||
private TextInputNode? _descriptionInput;
|
||||
private ColorInputRow? _colorInput;
|
||||
private NumericInputNode? _priorityInput;
|
||||
private NumericInputNode? _orderInput;
|
||||
|
||||
private bool _initialized;
|
||||
|
||||
private void EnsureInitialized()
|
||||
{
|
||||
if (_initialized) return;
|
||||
_initialized = true;
|
||||
|
||||
_enabledCheckbox = new CheckboxNode
|
||||
{
|
||||
Size = new Vector2(Width, 20),
|
||||
String = "Enabled",
|
||||
OnClick = isChecked =>
|
||||
{
|
||||
CategoryDefinition.Enabled = isChecked;
|
||||
OnPropertyChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_enabledCheckbox);
|
||||
|
||||
_pinnedCheckbox = new CheckboxNode
|
||||
{
|
||||
Size = new Vector2(Width, 20),
|
||||
String = "Pinned",
|
||||
OnClick = isChecked =>
|
||||
{
|
||||
CategoryDefinition.Pinned = isChecked;
|
||||
OnPropertyChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_pinnedCheckbox);
|
||||
|
||||
AddNode(CreateLabel("Name: "));
|
||||
_nameInput = new TextInputNode
|
||||
{
|
||||
Size = new Vector2(250, 28),
|
||||
PlaceholderString = "Category Name",
|
||||
OnInputReceived = input =>
|
||||
{
|
||||
CategoryDefinition.Name = input.ExtractText();
|
||||
OnPropertyChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_nameInput);
|
||||
|
||||
AddNode(CreateLabel("Description:"));
|
||||
_descriptionInput = new TextInputNode
|
||||
{
|
||||
Size = new Vector2(250, 28),
|
||||
PlaceholderString = "Optional description",
|
||||
OnInputReceived = input =>
|
||||
{
|
||||
CategoryDefinition.Description = input.ExtractText();
|
||||
OnValueChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_descriptionInput);
|
||||
|
||||
_colorInput = new ColorInputRow
|
||||
{
|
||||
Label = "Color",
|
||||
Size = new Vector2(300, 28),
|
||||
CurrentColor = new UserCategoryDefinition().Color,
|
||||
DefaultColor = new UserCategoryDefinition().Color,
|
||||
OnColorConfirmed = color => { CategoryDefinition.Color = color; OnValueChanged?.Invoke(); },
|
||||
OnColorCanceled = color => { CategoryDefinition.Color = color; OnValueChanged?.Invoke(); },
|
||||
OnColorPreviewed = color => { CategoryDefinition.Color = color; OnValueChanged?.Invoke(); },
|
||||
OnColorChange = color => { CategoryDefinition.Color = color; OnValueChanged?.Invoke(); },
|
||||
};
|
||||
AddNode(_colorInput);
|
||||
|
||||
AddNode(CreateLabel("Priority:"));
|
||||
_priorityInput = new NumericInputNode
|
||||
{
|
||||
Size = new Vector2(120, 28),
|
||||
Min = 0,
|
||||
Max = 1000,
|
||||
Step = 1,
|
||||
OnValueUpdate = value =>
|
||||
{
|
||||
CategoryDefinition.Priority = value;
|
||||
OnValueChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_priorityInput);
|
||||
|
||||
AddNode(CreateLabel("Order: "));
|
||||
_orderInput = new NumericInputNode
|
||||
{
|
||||
Size = new Vector2(120, 28),
|
||||
Min = 0,
|
||||
Max = 9999,
|
||||
Step = 1,
|
||||
OnValueUpdate = val =>
|
||||
{
|
||||
CategoryDefinition.Order = val;
|
||||
OnPropertyChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_orderInput);
|
||||
|
||||
RecalculateLayout();
|
||||
}
|
||||
|
||||
public override void Refresh()
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
_enabledCheckbox!.IsChecked = CategoryDefinition.Enabled;
|
||||
_pinnedCheckbox!.IsChecked = CategoryDefinition.Pinned;
|
||||
_nameInput!.String = CategoryDefinition.Name;
|
||||
_nameInput.PlaceholderString = CategoryDefinition.Name.IsNullOrWhitespace() ? "Category Name" : "";
|
||||
_descriptionInput!.String = CategoryDefinition.Description;
|
||||
_descriptionInput.PlaceholderString = CategoryDefinition.Description.IsNullOrWhitespace() ? "Optional description" : "";
|
||||
_colorInput!.CurrentColor = CategoryDefinition.Color;
|
||||
_priorityInput!.Value = CategoryDefinition.Priority;
|
||||
_orderInput!.Value = CategoryDefinition.Order;
|
||||
|
||||
RecalculateLayout();
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using AetherBags.Configuration;
|
||||
using AetherBags.Inventory;
|
||||
using AetherBags.Nodes.Color;
|
||||
using Dalamud.Utility;
|
||||
using AetherBags.Nodes.Layout;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Nodes;
|
||||
using Lumina.Excel;
|
||||
@@ -23,71 +22,39 @@ public sealed class CategoryDefinitionConfigurationNode : SimpleComponentNode
|
||||
|
||||
private UserCategoryDefinition _categoryDefinition = new();
|
||||
|
||||
private readonly ScrollingAreaNode<TreeListNode> _scrollingArea;
|
||||
private readonly BasicSettingsSection _basicSettings;
|
||||
private readonly RangeFiltersSection _rangeFilters;
|
||||
private readonly StateFiltersSection _stateFilters;
|
||||
private readonly ListFiltersSection _listFilters;
|
||||
private readonly ScrollingAreaNode<VerticalListNode> _scrollingArea;
|
||||
private readonly List<ConfigurationSection> _sections = new();
|
||||
|
||||
public CategoryDefinitionConfigurationNode()
|
||||
{
|
||||
_scrollingArea = new ScrollingAreaNode<TreeListNode>
|
||||
{
|
||||
ContentHeight = 100.0f,
|
||||
_scrollingArea = new ScrollingAreaNode<VerticalListNode> {
|
||||
AutoHideScrollBar = true,
|
||||
ContentHeight = 100f
|
||||
};
|
||||
_scrollingArea.AttachNode(this);
|
||||
|
||||
_scrollingArea.ContentNode.OnLayoutUpdate = newHeight =>
|
||||
{
|
||||
_scrollingArea.ContentHeight = newHeight;
|
||||
};
|
||||
var list = _scrollingArea.ContentAreaNode;
|
||||
list.FitContents = true;
|
||||
list.ItemSpacing = 4.0f;
|
||||
|
||||
_scrollingArea.ContentNode.CategoryVerticalSpacing = 4.0f;
|
||||
_sections.Add(new BasicSettingsSection(() => _categoryDefinition) {
|
||||
String = "Basic Settings", IsCollapsed = false,
|
||||
OnPropertyChanged = () => { NotifyChanged(); OnCategoryPropertyChanged?.Invoke(); }
|
||||
});
|
||||
|
||||
var treeListNode = _scrollingArea.ContentAreaNode;
|
||||
|
||||
_basicSettings = new BasicSettingsSection(() => _categoryDefinition)
|
||||
{
|
||||
String = "Basic Settings",
|
||||
IsCollapsed = false,
|
||||
OnPropertyChanged = () =>
|
||||
{
|
||||
NotifyChanged();
|
||||
NotifyCategoryPropertyChanged();
|
||||
},
|
||||
OnValueChanged = NotifyChanged,
|
||||
};
|
||||
_basicSettings.OnToggle = _ => HandleLayoutChange();
|
||||
treeListNode.AddCategoryNode(_basicSettings);
|
||||
|
||||
_rangeFilters = new RangeFiltersSection(() => _categoryDefinition)
|
||||
{
|
||||
String = "Range Filters",
|
||||
IsCollapsed = true,
|
||||
OnValueChanged = NotifyChanged,
|
||||
};
|
||||
_rangeFilters.OnToggle = _ => HandleLayoutChange();
|
||||
treeListNode.AddCategoryNode(_rangeFilters);
|
||||
|
||||
_stateFilters = new StateFiltersSection(() => _categoryDefinition)
|
||||
{
|
||||
String = "State Filters",
|
||||
IsCollapsed = true,
|
||||
OnValueChanged = NotifyChanged,
|
||||
};
|
||||
_stateFilters.OnToggle = _ => HandleLayoutChange();
|
||||
treeListNode.AddCategoryNode(_stateFilters);
|
||||
|
||||
_listFilters = new ListFiltersSection(() => _categoryDefinition)
|
||||
{
|
||||
_sections.Add(new RangeFiltersSection(() => _categoryDefinition) { String = "Range Filters" });
|
||||
_sections.Add(new StateFiltersSection(() => _categoryDefinition) { String = "State Filters" });
|
||||
_sections.Add(new ListFiltersSection(() => _categoryDefinition) {
|
||||
String = "List Filters",
|
||||
IsCollapsed = true,
|
||||
OnValueChanged = NotifyChanged,
|
||||
OnListChanged = HandleListChanged,
|
||||
};
|
||||
_listFilters.OnToggle = _ => HandleLayoutChange();
|
||||
treeListNode.AddCategoryNode(_listFilters);
|
||||
OnListChanged = HandleLayoutChange
|
||||
});
|
||||
|
||||
foreach (var section in _sections)
|
||||
{
|
||||
section.OnToggle = HandleLayoutChange;
|
||||
section.OnValueChanged = NotifyChanged;
|
||||
list.AddNode(section);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged()
|
||||
@@ -96,65 +63,52 @@ public sealed class CategoryDefinitionConfigurationNode : SimpleComponentNode
|
||||
|
||||
_scrollingArea.Size = Size;
|
||||
|
||||
foreach (var categoryNode in _scrollingArea.ContentNode.CategoryNodes)
|
||||
foreach (var section in _sections)
|
||||
{
|
||||
categoryNode.Width = Width - 16.0f;
|
||||
section.Width = Width - 16.0f;
|
||||
}
|
||||
|
||||
_scrollingArea.ContentNode.RefreshLayout();
|
||||
HandleLayoutChange();
|
||||
}
|
||||
|
||||
public void SetCategory(UserCategoryDefinition newCategory)
|
||||
{
|
||||
_categoryDefinition = newCategory;
|
||||
RefreshAllValues();
|
||||
}
|
||||
|
||||
private void RefreshAllValues()
|
||||
{
|
||||
_basicSettings.Refresh();
|
||||
_rangeFilters.Refresh();
|
||||
_stateFilters.Refresh();
|
||||
_listFilters.Refresh();
|
||||
|
||||
HandleLayoutChange();
|
||||
}
|
||||
|
||||
private void HandleListChanged()
|
||||
{
|
||||
NotifyChanged();
|
||||
foreach (var section in _sections) section.Refresh();
|
||||
HandleLayoutChange();
|
||||
}
|
||||
|
||||
private void HandleLayoutChange()
|
||||
{
|
||||
_scrollingArea.ContentNode.RefreshLayout();
|
||||
_scrollingArea.ContentAreaNode.RecalculateLayout();
|
||||
_scrollingArea.ContentHeight = _scrollingArea.ContentAreaNode.Height;
|
||||
OnLayoutChanged?.Invoke();
|
||||
}
|
||||
|
||||
private static void NotifyChanged() => InventoryOrchestrator.RefreshAll(updateMaps: true);
|
||||
|
||||
private void NotifyCategoryPropertyChanged() => OnCategoryPropertyChanged?.Invoke();
|
||||
|
||||
public static string ResolveItemName(uint itemId) => ItemSheet?.GetRow(itemId).Name.ToString() ?? "Unknown";
|
||||
|
||||
public static string ResolveUiCategoryName(uint categoryId) => UICategorySheet?.GetRow(categoryId).Name.ToString() ?? "Unknown";
|
||||
}
|
||||
|
||||
public abstract class ConfigurationSection : TreeListCategoryNode
|
||||
public abstract class ConfigurationSection : CollapsibleSectionNode
|
||||
{
|
||||
private readonly Func<UserCategoryDefinition> _getCategoryDefinition;
|
||||
|
||||
public Action? OnValueChanged { get; init; }
|
||||
public Action? OnValueChanged { get; set; }
|
||||
|
||||
protected UserCategoryDefinition CategoryDefinition => _getCategoryDefinition();
|
||||
|
||||
protected ConfigurationSection(Func<UserCategoryDefinition> getCategoryDefinition)
|
||||
{
|
||||
_getCategoryDefinition = getCategoryDefinition;
|
||||
VerticalPadding = 4.0f;
|
||||
HeaderHeight = 30.0f;
|
||||
|
||||
AddTab();
|
||||
}
|
||||
|
||||
public abstract void Refresh();
|
||||
|
||||
protected static LabelTextNode CreateLabel(string text) => new()
|
||||
{
|
||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||
@@ -162,352 +116,3 @@ public abstract class ConfigurationSection : TreeListCategoryNode
|
||||
String = text,
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class BasicSettingsSection : ConfigurationSection
|
||||
{
|
||||
public Action? OnPropertyChanged { get; init; }
|
||||
|
||||
private CheckboxNode? _enabledCheckbox;
|
||||
private CheckboxNode? _pinnedCheckbox;
|
||||
private TextInputNode? _nameInput;
|
||||
private TextInputNode? _descriptionInput;
|
||||
private ColorInputRow? _colorInput;
|
||||
private NumericInputNode? _priorityInput;
|
||||
private NumericInputNode? _orderInput;
|
||||
|
||||
private bool _initialized;
|
||||
|
||||
public BasicSettingsSection(Func<UserCategoryDefinition> getCategoryDefinition)
|
||||
: base(getCategoryDefinition)
|
||||
{
|
||||
}
|
||||
|
||||
private void EnsureInitialized()
|
||||
{
|
||||
if (_initialized) return;
|
||||
_initialized = true;
|
||||
|
||||
_enabledCheckbox = new CheckboxNode
|
||||
{
|
||||
Size = new Vector2(Width, 20),
|
||||
String = "Enabled",
|
||||
OnClick = isChecked =>
|
||||
{
|
||||
CategoryDefinition.Enabled = isChecked;
|
||||
OnPropertyChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_enabledCheckbox);
|
||||
|
||||
_pinnedCheckbox = new CheckboxNode
|
||||
{
|
||||
Size = new Vector2(Width, 20),
|
||||
String = "Pinned",
|
||||
OnClick = isChecked =>
|
||||
{
|
||||
CategoryDefinition.Pinned = isChecked;
|
||||
OnPropertyChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_pinnedCheckbox);
|
||||
|
||||
AddNode(CreateLabel("Name: "));
|
||||
_nameInput = new TextInputNode
|
||||
{
|
||||
Size = new Vector2(250, 28),
|
||||
PlaceholderString = "Category Name",
|
||||
OnInputReceived = input =>
|
||||
{
|
||||
CategoryDefinition.Name = input.ExtractText();
|
||||
OnPropertyChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_nameInput);
|
||||
|
||||
AddNode(CreateLabel("Description:"));
|
||||
_descriptionInput = new TextInputNode
|
||||
{
|
||||
Size = new Vector2(250, 28),
|
||||
PlaceholderString = "Optional description",
|
||||
OnInputReceived = input =>
|
||||
{
|
||||
CategoryDefinition.Description = input.ExtractText();
|
||||
OnValueChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_descriptionInput);
|
||||
|
||||
_colorInput = new ColorInputRow
|
||||
{
|
||||
Label = "Color",
|
||||
Size = new Vector2(300, 28),
|
||||
CurrentColor = new UserCategoryDefinition().Color,
|
||||
DefaultColor = new UserCategoryDefinition().Color,
|
||||
OnColorConfirmed = 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 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,7 +80,7 @@ public sealed class CategoryGeneralConfigurationNode : TabbedVerticalListNode
|
||||
|
||||
bool bisBuddyReady = System.IPC.BisBuddy?.IsReady ?? false;
|
||||
|
||||
LabeledDropdownNode<PluginFilterMode>? bbModeDropdown = new LabeledDropdownNode<PluginFilterMode>
|
||||
LabeledEnumDropdownNode<PluginFilterMode>? bbModeDropdown = new LabeledEnumDropdownNode<PluginFilterMode>
|
||||
{
|
||||
Size = new Vector2(500, 20),
|
||||
LabelText = "Filter Display Mode",
|
||||
@@ -118,7 +118,7 @@ public sealed class CategoryGeneralConfigurationNode : TabbedVerticalListNode
|
||||
|
||||
bool allaganReady = System.IPC.AllaganTools?.IsReady ?? false;
|
||||
|
||||
LabeledDropdownNode<PluginFilterMode>? atModeDropdown = new LabeledDropdownNode<PluginFilterMode>
|
||||
LabeledEnumDropdownNode<PluginFilterMode>? atModeDropdown = new LabeledEnumDropdownNode<PluginFilterMode>
|
||||
{
|
||||
Size = new Vector2(500, 20),
|
||||
LabelText = "Filter Display Mode",
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using AetherBags.Configuration;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
@@ -15,10 +17,14 @@ public sealed class UintListEditorNode : VerticalListNode
|
||||
|
||||
private List<uint> _list = [];
|
||||
|
||||
public List<uint> GetList() => _list.ToList();
|
||||
|
||||
private readonly LabelTextNode _headerLabel;
|
||||
private readonly VerticalListNode _itemsContainer;
|
||||
private readonly NumericInputNode _addInput;
|
||||
|
||||
public Action? OnSearchButtonClicked { get; init; }
|
||||
|
||||
public Func<uint, string>? LabelResolver { get; init; }
|
||||
public Action? OnChanged { get; set; }
|
||||
|
||||
@@ -56,6 +62,15 @@ public sealed class UintListEditorNode : VerticalListNode
|
||||
ItemSpacing = 4.0f,
|
||||
};
|
||||
|
||||
var searchButton = new CircleButtonNode
|
||||
{
|
||||
Size = new Vector2(28),
|
||||
Icon = ButtonIcon.MagnifyingGlass,
|
||||
OnClick = () => OnSearchButtonClicked?.Invoke(),
|
||||
TextTooltip = "Search the game database..."
|
||||
};
|
||||
addRow.AddNode(searchButton);
|
||||
|
||||
_addInput = new NumericInputNode
|
||||
{
|
||||
Size = new Vector2(120, RowHeight),
|
||||
@@ -72,8 +87,9 @@ public sealed class UintListEditorNode : VerticalListNode
|
||||
OnClick = AddCurrentValue,
|
||||
};
|
||||
addRow.AddNode(addButton);
|
||||
|
||||
addRow.RecalculateLayout();
|
||||
AddNode(addRow);
|
||||
RecalculateLayout();
|
||||
}
|
||||
|
||||
public void SetList(List<uint> newList)
|
||||
@@ -82,6 +98,16 @@ public sealed class UintListEditorNode : VerticalListNode
|
||||
RefreshItems();
|
||||
}
|
||||
|
||||
public void AddValue(uint value)
|
||||
{
|
||||
if (!_list.Contains(value))
|
||||
{
|
||||
_list.Add(value);
|
||||
RefreshItems();
|
||||
OnChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddCurrentValue()
|
||||
{
|
||||
var value = (uint)_addInput.Value;
|
||||
@@ -109,6 +135,7 @@ public sealed class UintListEditorNode : VerticalListNode
|
||||
|
||||
_itemsContainer.RecalculateLayout();
|
||||
RecalculateLayout();
|
||||
OnChanged?.Invoke();
|
||||
}
|
||||
|
||||
private UintListItemNode CreateItemNode(uint value) => new(value, LabelResolver)
|
||||
@@ -120,8 +147,10 @@ public sealed class UintListEditorNode : VerticalListNode
|
||||
private void RemoveValue(uint value)
|
||||
{
|
||||
_list.Remove(value);
|
||||
RefreshItems();
|
||||
OnChanged?.Invoke();
|
||||
Services.Framework.RunOnTick(() => {
|
||||
RefreshItems();
|
||||
OnChanged?.Invoke();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,9 +166,15 @@ public sealed class UintListItemNode : HorizontalListNode
|
||||
Value = value;
|
||||
ItemSpacing = 4.0f;
|
||||
|
||||
string idDisplay = value switch {
|
||||
0xFFFF_FFFE => "[Weekly]",
|
||||
0xFFFF_FFFD => "[Tome]",
|
||||
_ => value.ToString()
|
||||
};
|
||||
|
||||
var displayText = labelResolver is not null
|
||||
? $"{value} - {labelResolver(value)}"
|
||||
: value.ToString();
|
||||
? $"{idDisplay} - {labelResolver(value)}"
|
||||
: idDisplay;
|
||||
|
||||
AddNode(new LabelTextNode
|
||||
{
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using AetherBags.Addons;
|
||||
using AetherBags.Configuration;
|
||||
using AetherBags.Nodes.Color;
|
||||
using AetherBags.Nodes.Configuration.Category;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace AetherBags.Nodes.Configuration.Currency;
|
||||
|
||||
public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
||||
{
|
||||
private readonly UintListEditorNode? _currencyListEditor;
|
||||
|
||||
public CurrencyGeneralConfigurationNode()
|
||||
{
|
||||
CurrencySettings config = System.Config.Currency;
|
||||
|
||||
Width = 600;
|
||||
ItemVerticalSpacing = 2;
|
||||
|
||||
LabelTextNode titleNode = new LabelTextNode
|
||||
{
|
||||
Size = Size with { Y = 18 },
|
||||
Size = new Vector2(Width, 18),
|
||||
String = "Currency Configuration",
|
||||
TextColor = ColorHelper.GetColor(2),
|
||||
TextOutlineColor = ColorHelper.GetColor(0),
|
||||
@@ -28,7 +34,7 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
||||
|
||||
CheckboxNode currencyEnabledCheckbox = new CheckboxNode
|
||||
{
|
||||
Size = Size with { Y = 18 },
|
||||
Size = new Vector2(Width, 18),
|
||||
IsVisible = true,
|
||||
String = "Show Currency",
|
||||
IsChecked = config.Enabled,
|
||||
@@ -58,7 +64,7 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
||||
|
||||
CheckboxNode cappedEnabledCheckbox = new CheckboxNode
|
||||
{
|
||||
Size = Size with { Y = 18 },
|
||||
Size = new Vector2(Width, 18),
|
||||
IsVisible = true,
|
||||
String = "Color Weekly Cap",
|
||||
IsChecked = config.ColorWhenCapped,
|
||||
@@ -91,7 +97,7 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
||||
|
||||
CheckboxNode limitedEnabledCheckbox = new CheckboxNode
|
||||
{
|
||||
Size = Size with { Y = 18 },
|
||||
Size = new Vector2(Width, 18),
|
||||
IsVisible = true,
|
||||
String = "Color Max Capacity",
|
||||
IsChecked = config.ColorWhenLimited,
|
||||
@@ -119,6 +125,53 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
||||
OnColorPreviewed = limitColorHandler,
|
||||
};
|
||||
AddNode(limitCurrencyColorNode);
|
||||
|
||||
AddNode(new ResNode { Size = new Vector2(15) });
|
||||
|
||||
SubtractTab(2);
|
||||
|
||||
AddNode(new ResNode { Size = new Vector2(15) });
|
||||
|
||||
_currencyListEditor = new UintListEditorNode
|
||||
{
|
||||
Label = "Displayed Currencies:",
|
||||
LabelResolver = id =>
|
||||
{
|
||||
return id switch
|
||||
{
|
||||
CurrencySettings.LimitedTomestoneId => "Current Limited Tomestone",
|
||||
CurrencySettings.NonLimitedTomestoneId => "Current Non-Limited Tomestone",
|
||||
_ => Services.DataManager.GetExcelSheet<Item>().GetRow(id).Name.ToString()
|
||||
};
|
||||
},
|
||||
OnSearchButtonClicked = OpenCurrencyPicker,
|
||||
OnChanged = () => {
|
||||
System.Config.Currency.DisplayedCurrencies = _currencyListEditor!.GetList();
|
||||
RefreshCurrency();
|
||||
RecalculateLayout();
|
||||
}
|
||||
};
|
||||
_currencyListEditor.SetList(System.Config.Currency.DisplayedCurrencies);
|
||||
AddNode(_currencyListEditor);
|
||||
|
||||
var quickAddRow = new HorizontalListNode { Size = new Vector2(600, 30), ItemSpacing = 8.0f };
|
||||
|
||||
quickAddRow.AddNode(new TextButtonNode {
|
||||
String = "+ Gil", Size = new Vector2(70, 24),
|
||||
OnClick = () => _currencyListEditor?.AddValue(1)
|
||||
});
|
||||
|
||||
quickAddRow.AddNode(new TextButtonNode {
|
||||
String = "+ Limited Tomestone", Size = new Vector2(150, 24),
|
||||
OnClick = () => _currencyListEditor?.AddValue(CurrencySettings.LimitedTomestoneId)
|
||||
});
|
||||
|
||||
quickAddRow.AddNode(new TextButtonNode {
|
||||
String = "+ Non-Limited", Size = new Vector2(110, 24),
|
||||
OnClick = () => _currencyListEditor?.AddValue(CurrencySettings.NonLimitedTomestoneId)
|
||||
});
|
||||
AddNode(quickAddRow);
|
||||
RecalculateLayout();
|
||||
}
|
||||
|
||||
private Action<Vector4> CreateColorHandler(Action<Vector4> setter) => newColor =>
|
||||
@@ -128,4 +181,14 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
||||
};
|
||||
|
||||
private void RefreshCurrency() => System.AddonInventoryWindow.ManualCurrencyRefresh();
|
||||
|
||||
private void OpenCurrencyPicker() {
|
||||
var picker = new AddonCurrencyPicker
|
||||
{
|
||||
Title = "Select Currency to Add",
|
||||
InternalName = "AetherBags_CurrencyPicker",
|
||||
};
|
||||
picker.SelectionResult = item => _currencyListEditor?.AddValue(item.RowId);
|
||||
picker.Open();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Numerics;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace AetherBags.Nodes.Configuration.Currency;
|
||||
@@ -8,7 +9,7 @@ public sealed class CurrencyScrollingAreaNode : ScrollingListNode
|
||||
{
|
||||
AddNode(new CurrencyGeneralConfigurationNode
|
||||
{
|
||||
Size = Size
|
||||
Width = 600
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode
|
||||
private readonly CheckboxNode _hideDefaultBagsCheckboxNode;
|
||||
private readonly CheckboxNode _hideSaddlebagsCheckboxNode;
|
||||
private readonly CheckboxNode _hideRetainerbagsCheckboxNode;
|
||||
private readonly LabeledDropdownNode<InventoryStackMode> _stackDropDown;
|
||||
private readonly LabeledEnumDropdownNode<InventoryStackMode> _stackDropDown;
|
||||
|
||||
public FunctionalConfigurationNode()
|
||||
{
|
||||
@@ -139,7 +139,7 @@ internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode
|
||||
Height = 6
|
||||
});
|
||||
|
||||
var searchModeDropDown = new LabeledDropdownNode<SearchMode>
|
||||
var searchModeDropDown = new LabeledEnumDropdownNode<SearchMode>
|
||||
{
|
||||
Size = new Vector2(500, 20),
|
||||
LabelText = "Search Mode",
|
||||
@@ -154,7 +154,7 @@ internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode
|
||||
};
|
||||
AddNode(searchModeDropDown);
|
||||
|
||||
_stackDropDown = new LabeledDropdownNode<InventoryStackMode>
|
||||
_stackDropDown = new LabeledEnumDropdownNode<InventoryStackMode>
|
||||
{
|
||||
Size = new Vector2(500, 20),
|
||||
IsEnabled = true,
|
||||
|
||||
+8
-2
@@ -6,12 +6,12 @@ using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace AetherBags.Nodes.Input;
|
||||
|
||||
public class LabeledDropdownNode<T> : SimpleComponentNode where T : Enum {
|
||||
public class LabeledEnumDropdownNode<T> : SimpleComponentNode where T : Enum {
|
||||
private readonly GridNode _gridNode;
|
||||
private readonly TextNode _labelNode;
|
||||
private readonly EnumDropDownNode<T> _dropDownNode;
|
||||
|
||||
public LabeledDropdownNode() {
|
||||
public LabeledEnumDropdownNode() {
|
||||
_gridNode = new GridNode {
|
||||
GridSize = new GridSize(2, 1),
|
||||
};
|
||||
@@ -62,6 +62,12 @@ public class LabeledDropdownNode<T> : SimpleComponentNode where T : Enum {
|
||||
}
|
||||
}
|
||||
|
||||
public int MaxListOptions
|
||||
{
|
||||
get => _dropDownNode.MaxListOptions;
|
||||
set => _dropDownNode.MaxListOptions = value;
|
||||
}
|
||||
|
||||
public required List<T> Options
|
||||
{
|
||||
get => _dropDownNode.Options!;
|
||||
@@ -33,7 +33,8 @@ public sealed class InventoryFooterNode : SimpleComponentNode
|
||||
{
|
||||
Position = new Vector2(0, 0),
|
||||
Size = new Vector2(120, 28),
|
||||
IsVisible = System.Config.Currency.Enabled
|
||||
IsVisible = System.Config.Currency.Enabled,
|
||||
ItemSpacing = 12f,
|
||||
};
|
||||
_currencyListNode.AttachNode(this);
|
||||
|
||||
@@ -42,9 +43,13 @@ public sealed class InventoryFooterNode : SimpleComponentNode
|
||||
|
||||
public void RefreshCurrencies()
|
||||
{
|
||||
_currencyListNode.IsVisible = System.Config.Currency.Enabled;
|
||||
var config = System.Config.Currency;
|
||||
_currencyListNode.IsVisible = config.Enabled;
|
||||
|
||||
IReadOnlyList<CurrencyInfo> currencyInfoList = GetCurrencyInfoList([1, 28, 0xFFFF_FFFE, 0xFFFF_FFFD]);
|
||||
if (!config.Enabled) return;
|
||||
|
||||
//IReadOnlyList<CurrencyInfo> currencyInfoList = GetCurrencyInfoList([1, 28, 0xFFFF_FFFE, 0xFFFF_FFFD]);
|
||||
IReadOnlyList<CurrencyInfo> currencyInfoList = GetCurrencyInfoList(config.DisplayedCurrencies.ToArray());
|
||||
_currencyListNode.SyncWithListDataByKey<CurrencyInfo, CurrencyNode, uint>(
|
||||
dataList: currencyInfoList,
|
||||
getKeyFromData: currencyInfo => currencyInfo.ItemId,
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
+1
-1
Submodule KamiToolKit updated: 27a23f83cf...811154c8f8
Reference in New Issue
Block a user