diff --git a/AetherBags/Addons/AddonCurrencyPicker.cs b/AetherBags/Addons/AddonCurrencyPicker.cs new file mode 100644 index 0000000..0804191 --- /dev/null +++ b/AetherBags/Addons/AddonCurrencyPicker.cs @@ -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 { + public AddonCurrencyPicker() { + var allItems = Services.DataManager.GetExcelSheet(); + var obsoleteTomes = Services.DataManager.GetExcelSheet() + .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()); +} \ No newline at end of file diff --git a/AetherBags/Addons/AddonItemPicker.cs b/AetherBags/Addons/AddonItemPicker.cs new file mode 100644 index 0000000..341f350 --- /dev/null +++ b/AetherBags/Addons/AddonItemPicker.cs @@ -0,0 +1,7 @@ +using KamiToolKit.Premade.ListItemNodes; +using KamiToolKit.Premade.SearchAddons; + +namespace AetherBags.Addons; + +public class AddonItemPicker : ItemSearchAddonBase { +} \ No newline at end of file diff --git a/AetherBags/Addons/AddonUICategoryPicker.cs b/AetherBags/Addons/AddonUICategoryPicker.cs new file mode 100644 index 0000000..4f3fb91 --- /dev/null +++ b/AetherBags/Addons/AddonUICategoryPicker.cs @@ -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 { + 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); +} \ No newline at end of file diff --git a/AetherBags/Addons/InventoryAddonBase.cs b/AetherBags/Addons/InventoryAddonBase.cs index 522955b..df7ed7a 100644 --- a/AetherBags/Addons/InventoryAddonBase.cs +++ b/AetherBags/Addons/InventoryAddonBase.cs @@ -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; diff --git a/AetherBags/Configuration/CurrencySettings.cs b/AetherBags/Configuration/CurrencySettings.cs index 505ef00..a0e8236 100644 --- a/AetherBags/Configuration/CurrencySettings.cs +++ b/AetherBags/Configuration/CurrencySettings.cs @@ -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 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); diff --git a/AetherBags/Currency/CurrencyState.cs b/AetherBags/Currency/CurrencyState.cs index a357bab..40242b0 100644 --- a/AetherBags/Currency/CurrencyState.cs +++ b/AetherBags/Currency/CurrencyState.cs @@ -71,6 +71,31 @@ public static unsafe class CurrencyState return currencyInfoList; } + public static (uint Limited, uint NonLimited) GetCurrentTomestoneIds() + { + var tomestonesItemSheet = Services.DataManager.GetExcelSheet(); + 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) { diff --git a/AetherBags/Extensions/EnumExtensions.cs b/AetherBags/Extensions/EnumExtensions.cs index 8f5a0ee..258ba19 100644 --- a/AetherBags/Extensions/EnumExtensions.cs +++ b/AetherBags/Extensions/EnumExtensions.cs @@ -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(ref T flagValue) where T : unmanaged, Enum { public void SetFlags(params T[] flags) { foreach (var flag in flags) { diff --git a/AetherBags/Nodes/Configuration/Category/BasicSettingsSection.cs b/AetherBags/Nodes/Configuration/Category/BasicSettingsSection.cs new file mode 100644 index 0000000..91bea68 --- /dev/null +++ b/AetherBags/Nodes/Configuration/Category/BasicSettingsSection.cs @@ -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 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(); + } +} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/Category/CategoryDefinitionConfigurationNode.cs b/AetherBags/Nodes/Configuration/Category/CategoryDefinitionConfigurationNode.cs index 894b51e..9add9c7 100644 --- a/AetherBags/Nodes/Configuration/Category/CategoryDefinitionConfigurationNode.cs +++ b/AetherBags/Nodes/Configuration/Category/CategoryDefinitionConfigurationNode.cs @@ -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 _scrollingArea; - private readonly BasicSettingsSection _basicSettings; - private readonly RangeFiltersSection _rangeFilters; - private readonly StateFiltersSection _stateFilters; - private readonly ListFiltersSection _listFilters; + private readonly ScrollingAreaNode _scrollingArea; + private readonly List _sections = new(); public CategoryDefinitionConfigurationNode() { - _scrollingArea = new ScrollingAreaNode - { - ContentHeight = 100.0f, + _scrollingArea = new ScrollingAreaNode { 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 _getCategoryDefinition; - public Action? OnValueChanged { get; init; } + public Action? OnValueChanged { get; set; } protected UserCategoryDefinition CategoryDefinition => _getCategoryDefinition(); protected ConfigurationSection(Func 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 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 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 GetFilter)> _filters = []; - private bool _initialized; - - public StateFiltersSection(Func 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 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 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(); - } } \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/Category/CategoryGeneralConfigurationNode.cs b/AetherBags/Nodes/Configuration/Category/CategoryGeneralConfigurationNode.cs index 8f90f36..3f0fd58 100644 --- a/AetherBags/Nodes/Configuration/Category/CategoryGeneralConfigurationNode.cs +++ b/AetherBags/Nodes/Configuration/Category/CategoryGeneralConfigurationNode.cs @@ -80,7 +80,7 @@ public sealed class CategoryGeneralConfigurationNode : TabbedVerticalListNode bool bisBuddyReady = System.IPC.BisBuddy?.IsReady ?? false; - LabeledDropdownNode? bbModeDropdown = new LabeledDropdownNode + LabeledEnumDropdownNode? bbModeDropdown = new LabeledEnumDropdownNode { 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? atModeDropdown = new LabeledDropdownNode + LabeledEnumDropdownNode? atModeDropdown = new LabeledEnumDropdownNode { Size = new Vector2(500, 20), LabelText = "Filter Display Mode", diff --git a/AetherBags/Nodes/Configuration/Category/ListFiltersSection.cs b/AetherBags/Nodes/Configuration/Category/ListFiltersSection.cs new file mode 100644 index 0000000..76f0f6a --- /dev/null +++ b/AetherBags/Nodes/Configuration/Category/ListFiltersSection.cs @@ -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 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() + .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() + .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(); + } +} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/Category/RangeFiltersSection.cs b/AetherBags/Nodes/Configuration/Category/RangeFiltersSection.cs new file mode 100644 index 0000000..8b7c1ad --- /dev/null +++ b/AetherBags/Nodes/Configuration/Category/RangeFiltersSection.cs @@ -0,0 +1,77 @@ +using System; +using AetherBags.Configuration; + +namespace AetherBags.Nodes.Configuration.Category; + +public sealed class RangeFiltersSection(Func 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(); + } +} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/Category/StateFiltersSection.cs b/AetherBags/Nodes/Configuration/Category/StateFiltersSection.cs new file mode 100644 index 0000000..b9f9e11 --- /dev/null +++ b/AetherBags/Nodes/Configuration/Category/StateFiltersSection.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using AetherBags.Configuration; + +namespace AetherBags.Nodes.Configuration.Category; + +public sealed class StateFiltersSection(Func getCategoryDefinition) + : ConfigurationSection(getCategoryDefinition) +{ + private readonly List<(StateFilterRowNode Node, Func 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 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(); + } +} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/Category/UICategoryListItemNode.cs b/AetherBags/Nodes/Configuration/Category/UICategoryListItemNode.cs new file mode 100644 index 0000000..a87dc47 --- /dev/null +++ b/AetherBags/Nodes/Configuration/Category/UICategoryListItemNode.cs @@ -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 { + 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(); + } +} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/Category/UintListEditorNode.cs b/AetherBags/Nodes/Configuration/Category/UintListEditorNode.cs index 18612ed..2544f50 100644 --- a/AetherBags/Nodes/Configuration/Category/UintListEditorNode.cs +++ b/AetherBags/Nodes/Configuration/Category/UintListEditorNode.cs @@ -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 _list = []; + public List GetList() => _list.ToList(); + private readonly LabelTextNode _headerLabel; private readonly VerticalListNode _itemsContainer; private readonly NumericInputNode _addInput; + public Action? OnSearchButtonClicked { get; init; } + public Func? 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 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 { diff --git a/AetherBags/Nodes/Configuration/Currency/CurrencyGeneralConfigurationNode.cs b/AetherBags/Nodes/Configuration/Currency/CurrencyGeneralConfigurationNode.cs index b1425a9..412eab4 100644 --- a/AetherBags/Nodes/Configuration/Currency/CurrencyGeneralConfigurationNode.cs +++ b/AetherBags/Nodes/Configuration/Currency/CurrencyGeneralConfigurationNode.cs @@ -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().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 CreateColorHandler(Action 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(); + } } \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/Currency/CurrencyScrollingAreaNode.cs b/AetherBags/Nodes/Configuration/Currency/CurrencyScrollingAreaNode.cs index e8188e0..f95e1e9 100644 --- a/AetherBags/Nodes/Configuration/Currency/CurrencyScrollingAreaNode.cs +++ b/AetherBags/Nodes/Configuration/Currency/CurrencyScrollingAreaNode.cs @@ -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 }); } } \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/General/FunctionalConfigurationNode.cs b/AetherBags/Nodes/Configuration/General/FunctionalConfigurationNode.cs index cbb3d68..877d183 100644 --- a/AetherBags/Nodes/Configuration/General/FunctionalConfigurationNode.cs +++ b/AetherBags/Nodes/Configuration/General/FunctionalConfigurationNode.cs @@ -14,7 +14,7 @@ internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode private readonly CheckboxNode _hideDefaultBagsCheckboxNode; private readonly CheckboxNode _hideSaddlebagsCheckboxNode; private readonly CheckboxNode _hideRetainerbagsCheckboxNode; - private readonly LabeledDropdownNode _stackDropDown; + private readonly LabeledEnumDropdownNode _stackDropDown; public FunctionalConfigurationNode() { @@ -139,7 +139,7 @@ internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode Height = 6 }); - var searchModeDropDown = new LabeledDropdownNode + var searchModeDropDown = new LabeledEnumDropdownNode { Size = new Vector2(500, 20), LabelText = "Search Mode", @@ -154,7 +154,7 @@ internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode }; AddNode(searchModeDropDown); - _stackDropDown = new LabeledDropdownNode + _stackDropDown = new LabeledEnumDropdownNode { Size = new Vector2(500, 20), IsEnabled = true, diff --git a/AetherBags/Nodes/Input/LabeledDropdownNode.cs b/AetherBags/Nodes/Input/LabeledEnumDropdownNode.cs similarity index 87% rename from AetherBags/Nodes/Input/LabeledDropdownNode.cs rename to AetherBags/Nodes/Input/LabeledEnumDropdownNode.cs index 4c92b89..83fd449 100644 --- a/AetherBags/Nodes/Input/LabeledDropdownNode.cs +++ b/AetherBags/Nodes/Input/LabeledEnumDropdownNode.cs @@ -6,12 +6,12 @@ using Lumina.Text.ReadOnly; namespace AetherBags.Nodes.Input; -public class LabeledDropdownNode : SimpleComponentNode where T : Enum { +public class LabeledEnumDropdownNode : SimpleComponentNode where T : Enum { private readonly GridNode _gridNode; private readonly TextNode _labelNode; private readonly EnumDropDownNode _dropDownNode; - public LabeledDropdownNode() { + public LabeledEnumDropdownNode() { _gridNode = new GridNode { GridSize = new GridSize(2, 1), }; @@ -62,6 +62,12 @@ public class LabeledDropdownNode : SimpleComponentNode where T : Enum { } } + public int MaxListOptions + { + get => _dropDownNode.MaxListOptions; + set => _dropDownNode.MaxListOptions = value; + } + public required List Options { get => _dropDownNode.Options!; diff --git a/AetherBags/Nodes/Inventory/InventoryFooterNode.cs b/AetherBags/Nodes/Inventory/InventoryFooterNode.cs index ef0f8df..436c904 100644 --- a/AetherBags/Nodes/Inventory/InventoryFooterNode.cs +++ b/AetherBags/Nodes/Inventory/InventoryFooterNode.cs @@ -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 currencyInfoList = GetCurrencyInfoList([1, 28, 0xFFFF_FFFE, 0xFFFF_FFFD]); + if (!config.Enabled) return; + + //IReadOnlyList currencyInfoList = GetCurrencyInfoList([1, 28, 0xFFFF_FFFE, 0xFFFF_FFFD]); + IReadOnlyList currencyInfoList = GetCurrencyInfoList(config.DisplayedCurrencies.ToArray()); _currencyListNode.SyncWithListDataByKey( dataList: currencyInfoList, getKeyFromData: currencyInfo => currencyInfo.ItemId, diff --git a/AetherBags/Nodes/Layout/CollapsibleSectionNode.cs b/AetherBags/Nodes/Layout/CollapsibleSectionNode.cs new file mode 100644 index 0000000..2d73abe --- /dev/null +++ b/AetherBags/Nodes/Layout/CollapsibleSectionNode.cs @@ -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 nodes) => ContentNode.AddNode(nodes); + + public void AddNode(int tabIndex, NodeBase node) => ContentNode.AddNode(tabIndex, node); + + public void AddNode(int tabIndex, IEnumerable 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; } +} diff --git a/KamiToolKit b/KamiToolKit index 27a23f8..811154c 160000 --- a/KamiToolKit +++ b/KamiToolKit @@ -1 +1 @@ -Subproject commit 27a23f83cf68594831a7cd1c9ea2a7c403837cca +Subproject commit 811154c8f882d1dfbb4fa3457982ef802fbff35a