From fff0578b745365b6184ecdc92a491d6e97737a84 Mon Sep 17 00:00:00 2001 From: Zeffuro Date: Mon, 22 Dec 2025 16:34:01 +0100 Subject: [PATCH] Implement VERY basic configuration window and stacking --- AetherBags.sln.DotSettings.user | 2 + AetherBags/Addons/AddonConfigurationWindow.cs | 79 +++++++++++ AetherBags/Configuration/GeneralSettings.cs | 16 +++ .../Configuration/SystemConfiguration.cs | 4 +- .../Extensions/AddonLifecycleExtensions.cs | 4 +- AetherBags/Extensions/LoggerExtensions.cs | 16 +++ AetherBags/GlobalUsing.cs | 3 +- AetherBags/Helpers/BackupHelper.cs | 4 +- AetherBags/Inventory/InventoryState.cs | 133 ++++++++++++------ AetherBags/Inventory/ItemInfo.cs | 7 +- .../CategoryScrollingAreaNode.cs | 7 + .../CurrencyScrollingAreaNode.cs | 8 ++ .../Configuration/GeneralScrollingAreaNode.cs | 47 +++++++ AetherBags/Nodes/LabeledDropdownNode.cs | 70 +++++++++ AetherBags/Plugin.cs | 14 +- AetherBags/System.cs | 1 + 16 files changed, 363 insertions(+), 52 deletions(-) create mode 100644 AetherBags/Addons/AddonConfigurationWindow.cs create mode 100644 AetherBags/Configuration/GeneralSettings.cs create mode 100644 AetherBags/Extensions/LoggerExtensions.cs create mode 100644 AetherBags/Nodes/Configuration/CategoryScrollingAreaNode.cs create mode 100644 AetherBags/Nodes/Configuration/CurrencyScrollingAreaNode.cs create mode 100644 AetherBags/Nodes/Configuration/GeneralScrollingAreaNode.cs create mode 100644 AetherBags/Nodes/LabeledDropdownNode.cs diff --git a/AetherBags.sln.DotSettings.user b/AetherBags.sln.DotSettings.user index a860f90..4930879 100644 --- a/AetherBags.sln.DotSettings.user +++ b/AetherBags.sln.DotSettings.user @@ -1,3 +1,5 @@  ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded \ No newline at end of file diff --git a/AetherBags/Addons/AddonConfigurationWindow.cs b/AetherBags/Addons/AddonConfigurationWindow.cs new file mode 100644 index 0000000..aa9f970 --- /dev/null +++ b/AetherBags/Addons/AddonConfigurationWindow.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using AetherBags.Nodes.Configuration; +using FFXIVClientStructs.FFXIV.Component.GUI; +using KamiToolKit; +using KamiToolKit.Nodes; + +namespace AetherBags.Addons; + +public class AddonConfigurationWindow : NativeAddon +{ + private TabBarNode _tabBarNode = null!; + + private GeneralScrollingAreaNode _generalScrollingAreaNode = null!; + private CategoryScrollingAreaNode _categoryScrollingAreaNode = null!; + private CurrencyScrollingAreaNode _currencyScrollingAreaNode = null!; + + private readonly List _tabContent = new(); + + protected override unsafe void OnSetup(AtkUnitBase* addon) + { + var tabContentY = ContentStartPosition.Y + 40; + var tabContentHeight = ContentSize.Y - 40; + + _tabBarNode = new TabBarNode + { + Position = ContentStartPosition, + Size = ContentSize with { Y = 24 }, + IsVisible = true + }; + _tabBarNode.AttachNode(this); + + _generalScrollingAreaNode = new GeneralScrollingAreaNode + { + Position = ContentStartPosition with { Y = tabContentY }, + Size = ContentSize with { Y = tabContentHeight }, + ContentHeight = 400, + ScrollSpeed = 25, + IsVisible = true, + }; + _generalScrollingAreaNode.AttachNode(this); + + _categoryScrollingAreaNode = new CategoryScrollingAreaNode + { + Position = ContentStartPosition with { Y = tabContentY }, + Size = ContentSize with { Y = tabContentHeight }, + ContentHeight = 400, + ScrollSpeed = 25, + IsVisible = false, + }; + _categoryScrollingAreaNode.AttachNode(this); + + _currencyScrollingAreaNode = new CurrencyScrollingAreaNode + { + Position = ContentStartPosition with { Y = tabContentY }, + Size = ContentSize with { Y = tabContentHeight }, + ContentHeight = 400, + ScrollSpeed = 25, + IsVisible = false, + }; + _currencyScrollingAreaNode.AttachNode(this); + + _tabContent.Add(_generalScrollingAreaNode); + _tabContent.Add(_categoryScrollingAreaNode); + _tabContent.Add(_currencyScrollingAreaNode); + + _tabBarNode.AddTab("General", () => SwitchTab(0)); + _tabBarNode.AddTab("Categories", () => SwitchTab(1)); + _tabBarNode.AddTab("Currency", () => SwitchTab(2)); + + base.OnSetup(addon); + } + + private void SwitchTab(int index) + { + for (var i = 0; i < _tabContent.Count; i++) + _tabContent[i].IsVisible = i == index; + } +} \ No newline at end of file diff --git a/AetherBags/Configuration/GeneralSettings.cs b/AetherBags/Configuration/GeneralSettings.cs new file mode 100644 index 0000000..40589cb --- /dev/null +++ b/AetherBags/Configuration/GeneralSettings.cs @@ -0,0 +1,16 @@ +using System.Numerics; +using KamiToolKit.Classes; + +namespace AetherBags.Configuration; + +public class GeneralSettings +{ + public InventoryStackMode StackMode { get; set; } = InventoryStackMode.AggregateByItemId; + public bool DebugEnabled { get; set; } = false; +} + +public enum InventoryStackMode : byte +{ + NaturalStacks = 0, + AggregateByItemId = 1, +} \ No newline at end of file diff --git a/AetherBags/Configuration/SystemConfiguration.cs b/AetherBags/Configuration/SystemConfiguration.cs index 5cc8251..4e58599 100644 --- a/AetherBags/Configuration/SystemConfiguration.cs +++ b/AetherBags/Configuration/SystemConfiguration.cs @@ -7,6 +7,8 @@ public class SystemConfiguration { public const string FileName = "AetherBags.json"; - public CurrencySettings Currency { get; set; } = new(); + + public GeneralSettings General { get; set; } = new(); public CategorySettings Categories { get; set; } = new(); + public CurrencySettings Currency { get; set; } = new(); } \ No newline at end of file diff --git a/AetherBags/Extensions/AddonLifecycleExtensions.cs b/AetherBags/Extensions/AddonLifecycleExtensions.cs index 03324f4..0c7bf4d 100644 --- a/AetherBags/Extensions/AddonLifecycleExtensions.cs +++ b/AetherBags/Extensions/AddonLifecycleExtensions.cs @@ -43,11 +43,11 @@ public static class AddonLifecycleExtensions { private static void Logger(AddonEvent type, AddonArgs args) { switch (args) { case AddonReceiveEventArgs receiveEventArgs: - Services.Logger.Debug($"[{args.AddonName}] {(AtkEventType)receiveEventArgs.AtkEventType}: {receiveEventArgs.EventParam}"); + Services.Logger.DebugOnly($"[{args.AddonName}] {(AtkEventType)receiveEventArgs.AtkEventType}: {receiveEventArgs.EventParam}"); break; default: - Services.Logger.Debug($"{args.AddonName} called {type.ToString().Replace("Post", string.Empty)}"); + Services.Logger.DebugOnly($"{args.AddonName} called {type.ToString().Replace("Post", string.Empty)}"); break; } } diff --git a/AetherBags/Extensions/LoggerExtensions.cs b/AetherBags/Extensions/LoggerExtensions.cs new file mode 100644 index 0000000..c0a9765 --- /dev/null +++ b/AetherBags/Extensions/LoggerExtensions.cs @@ -0,0 +1,16 @@ +using System.Diagnostics; + +namespace AetherBags.Extensions; + +public static class LoggerExtensions +{ + public static void DebugOnly(this object logger, string message) + { + if(System.Config.General.DebugEnabled) Services.Logger.Debug(message); + } + + public static void DebugOnly(this object logger, string message, params object[] args) + { + if(System.Config.General.DebugEnabled) Services.Logger.Debug(message); + } +} \ No newline at end of file diff --git a/AetherBags/GlobalUsing.cs b/AetherBags/GlobalUsing.cs index 8cbabd6..b691c20 100644 --- a/AetherBags/GlobalUsing.cs +++ b/AetherBags/GlobalUsing.cs @@ -1 +1,2 @@ -global using KamiToolKit.Extensions; \ No newline at end of file +global using KamiToolKit.Extensions; +global using AetherBags.Extensions; \ No newline at end of file diff --git a/AetherBags/Helpers/BackupHelper.cs b/AetherBags/Helpers/BackupHelper.cs index 39760ec..60466cc 100644 --- a/AetherBags/Helpers/BackupHelper.cs +++ b/AetherBags/Helpers/BackupHelper.cs @@ -62,7 +62,7 @@ public static class BackupHelper { File.Move(latestFile.FullName, archivePath); moved = true; } catch (IOException ioEx) when (i < 4) { - Services.Logger.Debug($"Move failed, retrying in 100ms: {ioEx.Message}"); + Services.Logger.DebugOnly($"Move failed, retrying in 100ms: {ioEx.Message}"); global::System.Threading.Thread.Sleep(100); } } @@ -79,7 +79,7 @@ public static class BackupHelper { var allBackups = dir.GetFiles().Where(f => f.Name.StartsWith($"{Name}.2") && f.Name.EndsWith(".zip")) .OrderBy(f => f.LastWriteTime.Ticks).ToList(); if (allBackups.Count > MaxBackups) { - Services.Logger.Debug($"Removing Oldest Backup: {allBackups[0].FullName}"); + Services.Logger.DebugOnly($"Removing Oldest Backup: {allBackups[0].FullName}"); File.Delete(allBackups[0].FullName); } } catch (Exception exception) { diff --git a/AetherBags/Inventory/InventoryState.cs b/AetherBags/Inventory/InventoryState.cs index df7aaa5..be76a3a 100644 --- a/AetherBags/Inventory/InventoryState.cs +++ b/AetherBags/Inventory/InventoryState.cs @@ -1,3 +1,4 @@ +using AetherBags.Configuration; using AetherBags.Currency; using Dalamud.Game.Inventory; using FFXIVClientStructs.FFXIV.Client.Game; @@ -6,7 +7,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; -using AetherBags.Configuration; namespace AetherBags.Inventory; @@ -46,19 +46,17 @@ public static unsafe class InventoryState private static readonly Dictionary CategoryInfoCache = new(capacity: 256); - private static readonly Dictionary AggByItemId = new(capacity: 512); - private static readonly Dictionary ItemInfoByItemId = new(capacity: 512); + private static readonly Dictionary AggByKey = new(capacity: 512); + private static readonly Dictionary ItemInfoByKey = new(capacity: 512); private static readonly Dictionary BucketsByKey = new(capacity: 256); private static readonly List SortedCategoryKeys = new(capacity: 256); private static readonly List AllCategories = new(capacity: 256); - private static readonly List FilteredCategories = new(capacity: 256); private static readonly List UserCategoriesSortedScratch = new(capacity: 64); - - private static readonly List RemoveKeysScratch = new(capacity: 256); + private static readonly List RemoveKeysScratch = new(capacity: 256); private const uint UserCategoryKeyFlag = 0x8000_0000; @@ -71,6 +69,9 @@ public static unsafe class InventoryState public static bool Contains(this IReadOnlyCollection inventoryTypes, GameInventoryType type) => inventoryTypes.Contains((InventoryType)type); + private static ulong MakeNaturalSlotKey(InventoryType container, int slot) + => ((ulong)(uint)container << 32) | (uint)slot; + public static void RefreshFromGame() { InventoryManager* inventoryManager = InventoryManager.Instance(); @@ -82,41 +83,78 @@ public static unsafe class InventoryState var config = System.Config; + InventoryStackMode stackMode = config.General.StackMode; + bool userCategoriesEnabled = config.Categories.UserCategoriesEnabled; bool gameCategoriesEnabled = config.Categories.GameCategoriesEnabled; - List userCategories = config.Categories.UserCategories; - AggByItemId.Clear(); + AggByKey.Clear(); + ItemInfoByKey.Clear(); + + BucketsByKey.Clear(); + SortedCategoryKeys.Clear(); + AllCategories.Clear(); + FilteredCategories.Clear(); + + Services.Logger.DebugOnly($"RefreshFromGame StackMode={stackMode}"); + + int scannedSlots = 0; + int nonEmptySlots = 0; + int collisions = 0; for (int inventoryIndex = 0; inventoryIndex < BagInventories.Length; inventoryIndex++) { - var container = inventoryManager->GetInventoryContainer(BagInventories[inventoryIndex]); + var inventoryType = BagInventories[inventoryIndex]; + var container = inventoryManager->GetInventoryContainer(inventoryType); if (container == null) + { + Services.Logger.DebugOnly($"Container null: {inventoryType}"); continue; + } int size = container->Size; + Services.Logger.DebugOnly($"Scanning {inventoryType} Size={size}"); + for (int slot = 0; slot < size; slot++) { + scannedSlots++; + ref var item = ref container->Items[slot]; uint id = item.ItemId; if (id == 0) continue; + nonEmptySlots++; + int quantity = item.Quantity; - if (AggByItemId.TryGetValue(id, out AggregatedItem agg)) + ulong key = stackMode == InventoryStackMode.AggregateByItemId + ? id + : MakeNaturalSlotKey(inventoryType, slot); + + Services.Logger.DebugOnly($"Slot {inventoryType}[{slot}] ItemId={id} Qty={quantity} Key=0x{key:X16}"); + + if (AggByKey.TryGetValue(key, out AggregatedItem agg)) { + if (stackMode == InventoryStackMode.NaturalStacks) + { + collisions++; + Services.Logger.DebugOnly($"COLLISION Key=0x{key:X16}: existing ItemId={agg.First.ItemId} new ItemId={id}"); + } + agg.Total += quantity; - AggByItemId[id] = agg; + AggByKey[key] = agg; } else { - AggByItemId.Add(id, new AggregatedItem { First = item, Total = quantity }); + AggByKey.Add(key, new AggregatedItem { First = item, Total = quantity }); } } } + Services.Logger.DebugOnly($"ScannedSlots={scannedSlots} NonEmptySlots={nonEmptySlots} AggByKey.Count={AggByKey.Count} Collisions={collisions}"); + foreach (var kvp in BucketsByKey) { CategoryBucket bucket = kvp.Value; @@ -125,19 +163,20 @@ public static unsafe class InventoryState bucket.FilteredItems.Clear(); } - foreach (var kvp in AggByItemId) + foreach (var kvp in AggByKey) { - uint itemId = kvp.Key; + ulong key = kvp.Key; AggregatedItem agg = kvp.Value; - if (!ItemInfoByItemId.TryGetValue(itemId, out ItemInfo? info)) + if (!ItemInfoByKey.TryGetValue(key, out ItemInfo? info)) { info = new ItemInfo { + Key = key, Item = agg.First, ItemCount = agg.Total, }; - ItemInfoByItemId.Add(itemId, info); + ItemInfoByKey.Add(key, info); } else { @@ -146,8 +185,10 @@ public static unsafe class InventoryState } } + Services.Logger.DebugOnly($"ItemInfoByKey.Count={ItemInfoByKey.Count}"); + // Bucket by user category - HashSet claimedItemIds = new(capacity: ItemInfoByItemId.Count); + HashSet claimedKeys = new HashSet(capacity: ItemInfoByKey.Count); if (userCategoriesEnabled && userCategories.Count > 0) { @@ -167,13 +208,13 @@ public static unsafe class InventoryState for (int c = 0; c < UserCategoriesSortedScratch.Count; c++) { UserCategoryDefinition category = UserCategoriesSortedScratch[c]; - uint key = MakeUserCategoryKey(category.Order); + uint bucketKey = MakeUserCategoryKey(category.Order); - if (!BucketsByKey.TryGetValue(key, out CategoryBucket? bucket)) + if (!BucketsByKey.TryGetValue(bucketKey, out CategoryBucket? bucket)) { bucket = new CategoryBucket { - Key = key, + Key = bucketKey, Category = new CategoryInfo { Name = category.Name, @@ -184,7 +225,7 @@ public static unsafe class InventoryState FilteredItems = new List(capacity: 16), Used = true, }; - BucketsByKey.Add(key, bucket); + BucketsByKey.Add(bucketKey, bucket); } else { @@ -194,18 +235,18 @@ public static unsafe class InventoryState bucket.Category.Color = category.Color; } - foreach (var itemKvp in ItemInfoByItemId) + foreach (var itemKvp in ItemInfoByKey) { + ulong itemKey = itemKvp.Key; ItemInfo item = itemKvp.Value; - uint itemId = item.Item.ItemId; - if (claimedItemIds.Contains(itemId)) + if (claimedKeys.Contains(itemKey)) continue; if (UserCategoryMatcher.Matches(item, category)) { bucket.Items.Add(item); - claimedItemIds.Add(itemId); + claimedKeys.Add(itemKey); } } @@ -217,11 +258,12 @@ public static unsafe class InventoryState // Game category bucket if (gameCategoriesEnabled) { - foreach (var itemKvp in ItemInfoByItemId) + foreach (var itemKvp in ItemInfoByKey) { + ulong itemKey = itemKvp.Key; ItemInfo info = itemKvp.Value; - if (userCategoriesEnabled && claimedItemIds.Contains(info.Item.ItemId)) + if (userCategoriesEnabled && claimedKeys.Contains(itemKey)) continue; uint categoryKey = info.UiCategory.RowId; @@ -253,9 +295,9 @@ public static unsafe class InventoryState if (!BucketsByKey.TryGetValue(0u, out CategoryBucket? miscBucket)) { CategoryInfo miscInfo; - if (ItemInfoByItemId.Count > 0) + if (ItemInfoByKey.Count > 0) { - var sample = ItemInfoByItemId.Values.First(); + var sample = ItemInfoByKey.Values.First(); miscInfo = GetCategoryInfoForKeyCached(0u, sample); } else @@ -278,11 +320,12 @@ public static unsafe class InventoryState miscBucket.Used = true; } - foreach (var itemKvp in ItemInfoByItemId) + foreach (var itemKvp in ItemInfoByKey) { + ulong itemKey = itemKvp.Key; ItemInfo info = itemKvp.Value; - if (userCategoriesEnabled && claimedItemIds.Contains(info.Item.ItemId)) + if (userCategoriesEnabled && claimedKeys.Contains(itemKey)) continue; miscBucket.Items.Add(info); @@ -292,19 +335,19 @@ public static unsafe class InventoryState miscBucket.Used = false; } - if (ItemInfoByItemId.Count != AggByItemId.Count) + if (ItemInfoByKey.Count != AggByKey.Count) { RemoveKeysScratch.Clear(); - foreach (var kvp in ItemInfoByItemId) + foreach (var kvp in ItemInfoByKey) { - uint itemId = kvp.Key; - if (!AggByItemId.ContainsKey(itemId)) - RemoveKeysScratch.Add(itemId); + ulong key = kvp.Key; + if (!AggByKey.ContainsKey(key)) + RemoveKeysScratch.Add(key); } for (int i = 0; i < RemoveKeysScratch.Count; i++) - ItemInfoByItemId.Remove(RemoveKeysScratch[i]); + ItemInfoByKey.Remove(RemoveKeysScratch[i]); } SortedCategoryKeys.Clear(); @@ -336,6 +379,11 @@ public static unsafe class InventoryState CategoryBucket bucket = BucketsByKey[key]; AllCategories.Add(new CategorizedInventory(bucket.Key, bucket.Category, bucket.Items)); } + int displayed = 0; + for (int i = 0; i < AllCategories.Count; i++) + displayed += AllCategories[i].Items.Count; + + Services.Logger.DebugOnly($"AllCategories={AllCategories.Count} DisplayedItemsTotal={displayed}"); } public static IReadOnlyList GetInventoryItemCategories(string filterString = "", bool invert = false) @@ -431,7 +479,6 @@ public static unsafe class InventoryState return new CurrencyItem(itemId, isLimited); } - public static IReadOnlyList GetCurrencyInfoList(uint[] currencyIds) { if (currencyIds.Length == 0) return Array.Empty(); @@ -455,7 +502,7 @@ public static unsafe class InventoryState InventoryManager* inventoryManager = InventoryManager.Instance(); var item = Services.DataManager.GetExcelSheet().GetRow(currencyItem.ItemId); - uint amount = (uint) inventoryManager->GetInventoryItemCount(currencyItem.ItemId); + uint amount = (uint)inventoryManager->GetInventoryItemCount(currencyItem.ItemId); uint maxAmount = item.StackSize; bool isCapped = false; if (currencyItem.IsLimited) @@ -478,8 +525,8 @@ public static unsafe class InventoryState private static void ClearAll() { - AggByItemId.Clear(); - ItemInfoByItemId.Clear(); + AggByKey.Clear(); + ItemInfoByKey.Clear(); foreach (var kvp in BucketsByKey) { @@ -540,7 +587,7 @@ public static unsafe class InventoryState public int Compare(ItemInfo? x, ItemInfo? y) { if (ReferenceEquals(x, y)) return 0; - if (x is null) return 1; // nulls last + if (x is null) return 1; if (y is null) return -1; int a = x.ItemCount; @@ -562,4 +609,4 @@ public static unsafe class InventoryState } private record CurrencyItem(uint ItemId, bool IsLimited); -} +} \ No newline at end of file diff --git a/AetherBags/Inventory/ItemInfo.cs b/AetherBags/Inventory/ItemInfo.cs index cb98128..83fca5f 100644 --- a/AetherBags/Inventory/ItemInfo.cs +++ b/AetherBags/Inventory/ItemInfo.cs @@ -10,6 +10,8 @@ namespace AetherBags.Inventory; public sealed class ItemInfo : IEquatable { + public required ulong Key { get; set; } + public required InventoryItem Item { get; set; } public required int ItemCount { get; set; } @@ -44,6 +46,7 @@ public sealed class ItemInfo : IEquatable public int ItemLevel => (int)Row.LevelItem.RowId; public int Rarity => Row.Rarity; public uint VendorPrice => Row.PriceLow; + public uint StackSize => Row.StackSize; public RowRef UiCategory => Row.ItemUICategory; @@ -87,11 +90,11 @@ public sealed class ItemInfo : IEquatable => Description.Contains(value, StringComparison.OrdinalIgnoreCase); public bool Equals(ItemInfo? other) - => other is not null && Item.ItemId == other.Item.ItemId && ItemCount == other.ItemCount; + => other is not null && Key == other.Key; public override bool Equals(object? obj) => obj is ItemInfo other && Equals(other); public override int GetHashCode() - => HashCode.Combine(Item.ItemId, ItemCount); + => Key.GetHashCode(); } diff --git a/AetherBags/Nodes/Configuration/CategoryScrollingAreaNode.cs b/AetherBags/Nodes/Configuration/CategoryScrollingAreaNode.cs new file mode 100644 index 0000000..fd33e4c --- /dev/null +++ b/AetherBags/Nodes/Configuration/CategoryScrollingAreaNode.cs @@ -0,0 +1,7 @@ +using KamiToolKit.Nodes; + +namespace AetherBags.Nodes.Configuration; + +public class CategoryScrollingAreaNode : ScrollingAreaNode +{ +} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/CurrencyScrollingAreaNode.cs b/AetherBags/Nodes/Configuration/CurrencyScrollingAreaNode.cs new file mode 100644 index 0000000..c15980a --- /dev/null +++ b/AetherBags/Nodes/Configuration/CurrencyScrollingAreaNode.cs @@ -0,0 +1,8 @@ +using KamiToolKit.Nodes; + +namespace AetherBags.Nodes.Configuration; + +public class CurrencyScrollingAreaNode : ScrollingAreaNode +{ + +} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/GeneralScrollingAreaNode.cs b/AetherBags/Nodes/Configuration/GeneralScrollingAreaNode.cs new file mode 100644 index 0000000..b121e95 --- /dev/null +++ b/AetherBags/Nodes/Configuration/GeneralScrollingAreaNode.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq; +using System.Numerics; +using AetherBags.Configuration; +using KamiToolKit.Nodes; + +namespace AetherBags.Nodes.Configuration; + +public sealed class GeneralScrollingAreaNode : ScrollingAreaNode +{ + private readonly CheckboxNode _debugCheckboxNode = null!; + private readonly LabeledDropdownNode _stackDropDown = null!; + public GeneralScrollingAreaNode() + { + GeneralSettings config = System.Config.General; + + ContentNode.ItemSpacing = 32; + + _stackDropDown = new LabeledDropdownNode + { + Size = new Vector2(300, 20), + LabelText = "Stack Mode", + Options = Enum.GetNames(typeof(InventoryStackMode)).ToList(), + SelectedOption = config.StackMode.ToString(), + OnOptionSelected = selected => + { + if (Enum.TryParse(selected, out var parsed)) + config.StackMode = parsed; + RefreshInventory(); + } + }; + ContentNode.AddNode(_stackDropDown); + + _debugCheckboxNode = new CheckboxNode + { + Size = new Vector2(300, 20), + IsVisible = true, + String = "Debug Mode", + IsChecked = config.DebugEnabled, + OnClick = isChecked => { config.DebugEnabled = isChecked; } + }; + ContentNode.AddNode(_debugCheckboxNode); + + } + + private void RefreshInventory() => System.AddonInventoryWindow.ManualRefresh(); +} \ No newline at end of file diff --git a/AetherBags/Nodes/LabeledDropdownNode.cs b/AetherBags/Nodes/LabeledDropdownNode.cs new file mode 100644 index 0000000..9f285fa --- /dev/null +++ b/AetherBags/Nodes/LabeledDropdownNode.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using FFXIVClientStructs.FFXIV.Component.GUI; +using KamiToolKit.Classes; +using KamiToolKit.Nodes; + +namespace AetherBags.Nodes; + +public class LabeledDropdownNode : SimpleComponentNode { + private readonly GridNode _gridNode; + private readonly TextNode _labelNode; + private readonly TextDropDownNode _dropDownNode; + + public LabeledDropdownNode() { + _gridNode = new GridNode { + GridSize = new GridSize(2, 1), + }; + _gridNode.AttachNode(this); + + _labelNode = new TextNode { + AlignmentType = AlignmentType.Bottom, + FontType = FontType.Axis, + FontSize = 14, + LineSpacing = 14, + TextColor = ColorHelper.GetColor(8), + TextOutlineColor = ColorHelper.GetColor(7), + TextFlags = TextFlags.Edge | TextFlags.AutoAdjustNodeSize, + String = string.Empty, + }; + _labelNode.AttachNode(_gridNode[0, 0]); + + _dropDownNode = new TextDropDownNode { + Options = new List(), + }; + _dropDownNode.AttachNode(_gridNode[1, 0]); + } + + protected override void OnSizeChanged() { + base.OnSizeChanged(); + + _gridNode.Size = Size; + + _labelNode.Size = _gridNode[0, 0].Size; + _dropDownNode.Size = _gridNode[1, 0].Size; + } + + public required string LabelText + { + get => _labelNode.String; + set => _labelNode.String = value; + } + + public Action? OnOptionSelected + { + get => _dropDownNode.OnOptionSelected; + set => _dropDownNode.OnOptionSelected = value; + } + + public string? SelectedOption + { + get => _dropDownNode.SelectedOption; + set => _dropDownNode.SelectedOption = value; + } + + public required List Options + { + get => _dropDownNode.Options!; + set => _dropDownNode.Options = value; + } +} diff --git a/AetherBags/Plugin.cs b/AetherBags/Plugin.cs index 43018e9..dbc9310 100644 --- a/AetherBags/Plugin.cs +++ b/AetherBags/Plugin.cs @@ -28,6 +28,16 @@ public class Plugin : IDalamudPlugin Size = new Vector2(750, 750), }; + System.AddonConfigurationWindow = new AddonConfigurationWindow + { + InternalName = "AetherBags Config", + Title = "AetherBags Config", + Size = new Vector2(640, 512), + }; + + Services.PluginInterface.UiBuilder.OpenMainUi += System.AddonInventoryWindow.Toggle; + Services.PluginInterface.UiBuilder.OpenConfigUi += System.AddonConfigurationWindow.Toggle; + Services.CommandManager.AddHandler("/aetherbags", new CommandInfo(OnCommand) { DisplayOrder = 1, @@ -59,6 +69,7 @@ public class Plugin : IDalamudPlugin Services.CommandManager.RemoveHandler("/ab"); System.AddonInventoryWindow.Dispose(); + System.AddonConfigurationWindow.Dispose(); KamiToolKitLibrary.Dispose(); } @@ -72,7 +83,7 @@ public class Plugin : IDalamudPlugin if(args.Length == 0) System.AddonInventoryWindow.Toggle(); if(args == "config") - System.AddonInventoryWindow.Toggle(); + System.AddonConfigurationWindow.Toggle(); if (args == "import-sk") { // Manually import from SortaKinda for testing until we have a proper config window @@ -89,6 +100,7 @@ public class Plugin : IDalamudPlugin #if DEBUG System.AddonInventoryWindow.Toggle(); + System.AddonConfigurationWindow.Toggle(); #endif } diff --git a/AetherBags/System.cs b/AetherBags/System.cs index 28ff178..05dee40 100644 --- a/AetherBags/System.cs +++ b/AetherBags/System.cs @@ -6,5 +6,6 @@ namespace AetherBags; public static class System { public static AddonInventoryWindow AddonInventoryWindow { get; set; } = null!; + public static AddonConfigurationWindow AddonConfigurationWindow { get; set; } = null!; public static SystemConfiguration Config { get; set; } = null!; } \ No newline at end of file