Add searching, add currencies

This commit is contained in:
Zeffuro
2026-01-23 18:46:45 +01:00
parent 723cb727ad
commit 324b47bab5
22 changed files with 873 additions and 462 deletions
+28
View File
@@ -0,0 +1,28 @@
using System;
using System.Linq;
using AetherBags.Currency;
using KamiToolKit.Premade.ListItemNodes;
using KamiToolKit.Premade.SearchAddons;
using Lumina.Excel.Sheets;
namespace AetherBags.Addons;
public class AddonCurrencyPicker : BaseSearchAddon<Item, ItemListItemNode> {
public AddonCurrencyPicker() {
var allItems = Services.DataManager.GetExcelSheet<Item>();
var obsoleteTomes = Services.DataManager.GetExcelSheet<TomestonesItem>()
.Where(t => t.Tomestones.RowId == 0)
.Select(t => t.Item.RowId).ToHashSet();
var currentTomestones = CurrencyState.GetCurrentTomestoneIds();
SearchOptions = allItems
.Where(i => (i.ItemUICategory.RowId == 100 || (i.RowId >= 1 && i.RowId < 100)) && !i.Name.IsEmpty)
.Where(i => !obsoleteTomes.Contains(i.RowId))
.Where(i => i.RowId != currentTomestones.Limited && i.RowId != currentTomestones.NonLimited)
.ToList();
}
protected override bool IsMatch(Item item, string search) => item.Name.ToString().Contains(search, StringComparison.OrdinalIgnoreCase);
protected override int Comparer(Item l, Item r, string s, bool rev) => string.CompareOrdinal(l.Name.ToString(), r.Name.ToString());
}
+7
View File
@@ -0,0 +1,7 @@
using KamiToolKit.Premade.ListItemNodes;
using KamiToolKit.Premade.SearchAddons;
namespace AetherBags.Addons;
public class AddonItemPicker : ItemSearchAddonBase<ItemListItemNode> {
}
@@ -0,0 +1,14 @@
using System.Diagnostics.CodeAnalysis;
using AetherBags.Nodes.Configuration.Category;
using KamiToolKit.Premade.SearchAddons;
using Lumina.Excel.Sheets;
namespace AetherBags.Addons;
public class AddonUICategoryPicker : BaseSearchAddon<ItemUICategory, UICategoryListItemNode> {
protected override int Comparer(ItemUICategory left, ItemUICategory right, string sort, bool rev)
=> string.CompareOrdinal(left.Name.ToString(), right.Name.ToString());
protected override bool IsMatch(ItemUICategory item, string search)
=> item.Name.ToString().Contains(search, global::System.StringComparison.OrdinalIgnoreCase);
}
+9 -7
View File
@@ -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);
+32
View File
@@ -71,6 +71,31 @@ public static unsafe class CurrencyState
return currencyInfoList;
}
public static (uint Limited, uint NonLimited) GetCurrentTomestoneIds()
{
var tomestonesItemSheet = Services.DataManager.GetExcelSheet<TomestonesItem>();
uint limitedId = 0;
uint nonLimitedId = 0;
foreach (var row in tomestonesItemSheet)
{
var tomeSheetRef = row.Tomestones.ValueNullable;
if (tomeSheetRef == null || tomeSheetRef.Value.RowId == 0) continue;
var itemId = row.Item.RowId;
if (itemId == 0 || itemId == 28) continue;
if (tomeSheetRef.Value.WeeklyLimit > 0)
limitedId = itemId;
else
nonLimitedId = itemId;
}
return (limitedId, nonLimitedId);
}
/*
private static uint? GetLimitedTomestoneItemIdCached()
{
if (_cachedLimitedTomestoneItemId.HasValue)
@@ -96,6 +121,13 @@ public static unsafe class CurrencyState
_cachedNonLimitedTomestoneItemId = itemId;
return itemId;
}
*/
private static uint? GetLimitedTomestoneItemIdCached()
=> _cachedLimitedTomestoneItemId ??= GetCurrentTomestoneIds().Limited;
private static uint? GetNonLimitedTomestoneItemIdCached()
=> _cachedNonLimitedTomestoneItemId ??= GetCurrentTomestoneIds().NonLimited;
private static CurrencyItem ResolveCurrencyItemIdCached(uint currencyId)
{
+2 -2
View File
@@ -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) {
@@ -15,7 +15,7 @@ internal static class EnumExtensions {
return attribute?.Description ?? enumValue.ToString();
}
}
extension<T>(ref T flagValue) where T : unmanaged, Enum {
public void SetFlags(params T[] flags) {
foreach (var flag in flags) {
@@ -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,418 +63,56 @@ 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,
Size = new Vector2(80, 20),
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,
@@ -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; }
}