WIP pinning and hoisting

This commit is contained in:
Shawrkie Williams
2025-12-29 15:36:06 -05:00
parent 17a67b8c12
commit 47d5aa3bd6
31 changed files with 682 additions and 157 deletions
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
+21 -5
View File
@@ -1,19 +1,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Numerics; using System.Numerics;
using AetherBags.Extensions;
using AetherBags.Inventory; using AetherBags.Inventory;
using AetherBags.Nodes;
using AetherBags.Nodes.Input; using AetherBags.Nodes.Input;
using AetherBags.Nodes.Inventory; using AetherBags.Nodes.Inventory;
using AetherBags.Nodes.Layout; using AetherBags.Nodes.Layout;
using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Game.Gui;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit; using KamiToolKit;
using KamiToolKit.Classes;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
namespace AetherBags.Addons; namespace AetherBags.Addons;
@@ -21,6 +17,7 @@ namespace AetherBags.Addons;
public class AddonInventoryWindow : NativeAddon public class AddonInventoryWindow : NativeAddon
{ {
private readonly InventoryCategoryHoverCoordinator _hoverCoordinator = new(); private readonly InventoryCategoryHoverCoordinator _hoverCoordinator = new();
private readonly InventoryCategoryPinCoordinator _pinCoordinator = new();
private readonly HashSet<InventoryCategoryNode> _hoverSubscribed = new(); private readonly HashSet<InventoryCategoryNode> _hoverSubscribed = new();
private InventoryNotificationNode _notificationNode = null!; private InventoryNotificationNode _notificationNode = null!;
@@ -135,6 +132,20 @@ public class AddonInventoryWindow : NativeAddon
RefreshCategoriesCore(true); RefreshCategoriesCore(true);
} }
public void UpdateLootedCategory(IReadOnlyList<LootedItemInfo> lootedItemInfos)
{
if (!Services.ClientState.IsLoggedIn) return;
_recentlyLootedCategoryNode?.CategorizedInventory.Items.AddRange(
lootedItemInfos.Select(x => new ItemInfo
{
ItemCount = x.Quantity,
Key = uint.MaxValue - 1,
Item = x.Item,
})
.ToList());
RefreshCategoriesCore(true);
}
public void ManualCurrencyRefresh() public void ManualCurrencyRefresh()
{ {
if (!Services.ClientState.IsLoggedIn) return; if (!Services.ClientState.IsLoggedIn) return;
@@ -182,6 +193,10 @@ public class AddonInventoryWindow : NativeAddon
Size = ContentSize with { Y = 120 }, Size = ContentSize with { Y = 120 },
}); });
bool pinsChanged = _pinCoordinator.ApplyPinnedStates(_categoriesNode);
if (pinsChanged)
_hoverCoordinator.ResetAll(_categoriesNode);
WireHoverHandlers(); WireHoverHandlers();
if (autosize) AutoSizeWindow(); if (autosize) AutoSizeWindow();
@@ -192,6 +207,7 @@ public class AddonInventoryWindow : NativeAddon
} }
} }
private void WireHoverHandlers() private void WireHoverHandlers()
{ {
var nodes = _categoriesNode.Nodes; var nodes = _categoriesNode.Nodes;
-2
View File
@@ -1,8 +1,6 @@
using AetherBags.Configuration; using AetherBags.Configuration;
using AetherBags.Inventory; using AetherBags.Inventory;
using Dalamud.Game.Text.SeStringHandling;
using KamiToolKit.Premade; using KamiToolKit.Premade;
using SeStringBuilder = Lumina.Text.SeStringBuilder;
namespace AetherBags.Addons; namespace AetherBags.Addons;
+1 -1
View File
@@ -4,7 +4,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<Author>Zeffuro</Author> <Author>Zeffuro, Pie Lover</Author>
<Name>AetherBags</Name> <Name>AetherBags</Name>
<InternalName>AetherBags</InternalName> <InternalName>AetherBags</InternalName>
<Punchline>Never think too hard about your bags again!</Punchline> <Punchline>Never think too hard about your bags again!</Punchline>
@@ -17,6 +17,7 @@ public class CategorySettings
public class UserCategoryDefinition public class UserCategoryDefinition
{ {
public bool Enabled { get; set; } = true; public bool Enabled { get; set; } = true;
public bool Pinned { get; set; } = false;
public string Id { get; set; } = Guid.NewGuid().ToString("N"); public string Id { get; set; } = Guid.NewGuid().ToString("N");
public string Name { get; set; } = "New Category"; public string Name { get; set; } = "New Category";
public string Description { get; set; } = string.Empty; public string Description { get; set; } = string.Empty;
@@ -1,6 +1,5 @@
using System.Numerics; using System.Numerics;
using KamiToolKit.Classes; using KamiToolKit.Classes;
using SixLabors.ImageSharp.PixelFormats;
namespace AetherBags.Configuration; namespace AetherBags.Configuration;
@@ -1,6 +1,3 @@
using System.Numerics;
using KamiToolKit.Classes;
namespace AetherBags.Configuration; namespace AetherBags.Configuration;
public class GeneralSettings public class GeneralSettings
@@ -1,6 +1,3 @@
using System.Numerics;
using KamiToolKit.Classes;
namespace AetherBags.Configuration; namespace AetherBags.Configuration;
public class SystemConfiguration public class SystemConfiguration
@@ -1,4 +1,3 @@
using System;
using AetherBags.Inventory; using AetherBags.Inventory;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Client.UI.Misc;
@@ -1,5 +1,3 @@
using System.Diagnostics;
namespace AetherBags.Extensions; namespace AetherBags.Extensions;
public static class LoggerExtensions public static class LoggerExtensions
@@ -1,8 +1,6 @@
using System.Linq;
using AetherBags.Configuration; using AetherBags.Configuration;
using AetherBags.Helpers.Import; using AetherBags.Helpers.Import;
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification;
namespace AetherBags.Helpers; namespace AetherBags.Helpers;
@@ -1,8 +1,4 @@
using AetherBags. Extensions;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component. GUI;
using ValueType = FFXIVClientStructs. FFXIV. Component.GUI.ValueType;
namespace AetherBags. Helpers; namespace AetherBags. Helpers;
-1
View File
@@ -1,7 +1,6 @@
using System; using System;
using Dalamud.Hooking; using Dalamud.Hooking;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
@@ -73,6 +73,7 @@ public static class CategoryBucketManager
Name = category.Name, Name = category.Name,
Description = category.Description, Description = category.Description,
Color = category.Color, Color = category.Color,
IsPinned = category.Pinned,
}, },
Items = new List<ItemInfo>(capacity: 16), Items = new List<ItemInfo>(capacity: 16),
FilteredItems = new List<ItemInfo>(capacity: 16), FilteredItems = new List<ItemInfo>(capacity: 16),
@@ -86,6 +87,7 @@ public static class CategoryBucketManager
bucket.Category.Name = category.Name; bucket.Category.Name = category.Name;
bucket.Category.Description = category.Description; bucket.Category.Description = category.Description;
bucket.Category.Color = category.Color; bucket.Category.Color = category.Color;
bucket.Category.IsPinned = category.Pinned;
} }
foreach (var itemKvp in itemInfoByKey) foreach (var itemKvp in itemInfoByKey)
+2 -1
View File
@@ -6,6 +6,7 @@ namespace AetherBags.Inventory;
public class CategoryInfo public class CategoryInfo
{ {
public required string Name { get; set; } public required string Name { get; set; }
public Vector4 Color { get; set; } = ColorHelper.GetColor(50); public Vector4 Color { get; set; } = ColorHelper.GetColor(2);
public string Description { get; set; } = string.Empty; public string Description { get; set; } = string.Empty;
public bool IsPinned { get; set; } = false;
} }
@@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using Lumina.Excel;
using Lumina.Excel.Sheets; using Lumina.Excel.Sheets;
using Lumina.Text.ReadOnly; using Lumina.Text.ReadOnly;
+38 -2
View File
@@ -1,10 +1,10 @@
using AetherBags.Configuration; using AetherBags.Configuration;
using AetherBags.Currency;
using Dalamud.Game.Inventory; using Dalamud.Game.Inventory;
using Dalamud.Game.Inventory.InventoryEventArgTypes;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using AetherBags.Currency;
using CurrencyManager = FFXIVClientStructs.FFXIV.Client.Game.CurrencyManager;
namespace AetherBags.Inventory; namespace AetherBags.Inventory;
@@ -21,6 +21,9 @@ public static unsafe class InventoryState
private static readonly List<UserCategoryDefinition> UserCategoriesSortedScratch = new(capacity: 64); private static readonly List<UserCategoryDefinition> UserCategoriesSortedScratch = new(capacity: 64);
private static readonly List<ulong> RemoveKeysScratch = new(capacity: 256); private static readonly List<ulong> RemoveKeysScratch = new(capacity: 256);
private static readonly HashSet<ulong> ClaimedKeys = new(capacity: 512); private static readonly HashSet<ulong> ClaimedKeys = new(capacity: 512);
private static readonly List<LootedItemInfo>? LootedItems = new(capacity: 512);
public static bool TrackLootedItems = false;
public static bool Contains(this IReadOnlyCollection<InventoryType> inventoryTypes, GameInventoryType type) public static bool Contains(this IReadOnlyCollection<InventoryType> inventoryTypes, GameInventoryType type)
=> inventoryTypes.Contains((InventoryType)type); => inventoryTypes.Contains((InventoryType)type);
@@ -135,6 +138,38 @@ public static unsafe class InventoryState
public static InventoryContainer* GetInventoryContainer(InventoryType inventoryType) public static InventoryContainer* GetInventoryContainer(InventoryType inventoryType)
=> InventoryScanner.GetInventoryContainer(inventoryType); => InventoryScanner.GetInventoryContainer(inventoryType);
internal static void OnRawItemAdded(IReadOnlyCollection<InventoryEventArgs> events)
{
if (!TrackLootedItems) return;
bool updateRequested = false;
foreach (var eventData in events)
{
if (!StandardInventories.Contains(eventData.Item.ContainerType)) continue;
if (!Services.ClientState.IsLoggedIn) return;
if (eventData is not (InventoryItemAddedArgs or InventoryItemChangedArgs)) return;
if (eventData is InventoryItemChangedArgs changedArgs && changedArgs.OldItemState.Quantity >= changedArgs.Item.Quantity) return;
var inventoryItem = (InventoryItem*)eventData.Item.Address;
var changeAmount = eventData is InventoryItemChangedArgs changed ? changed.Item.Quantity - changed.OldItemState.Quantity : eventData.Item.Quantity;
LootedItems?.Add(new LootedItemInfo(
LootedItems.Count,
*inventoryItem,
changeAmount)
);
updateRequested = true;
}
if (updateRequested)
{
System.AddonInventoryWindow?.UpdateLootedCategory(LootedItems ?? []);
}
}
private static void ClearAll() private static void ClearAll()
{ {
AggByKey.Clear(); AggByKey.Clear();
@@ -152,5 +187,6 @@ public static unsafe class InventoryState
FilteredCategories.Clear(); FilteredCategories.Clear();
RemoveKeysScratch.Clear(); RemoveKeysScratch.Clear();
ClaimedKeys.Clear(); ClaimedKeys.Clear();
LootedItems?.Clear();
} }
} }
-2
View File
@@ -1,11 +1,9 @@
using AetherBags.Extensions;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using Lumina.Excel; using Lumina.Excel;
using Lumina.Excel.Sheets; using Lumina.Excel.Sheets;
using System; using System;
using System.Numerics; using System.Numerics;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
namespace AetherBags.Inventory; namespace AetherBags.Inventory;
+5
View File
@@ -0,0 +1,5 @@
using FFXIVClientStructs.FFXIV.Client.Game;
namespace AetherBags.Inventory;
public record LootedItemInfo(int Index, InventoryItem Item, int Quantity);
@@ -1,6 +1,5 @@
using AetherBags.Configuration; using AetherBags.Configuration;
using System; using System;
using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace AetherBags.Inventory; namespace AetherBags.Inventory;
@@ -1,7 +1,5 @@
using System.Numerics; using System.Numerics;
using Dalamud.Game.Addon.Events.EventDataTypes;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
using ColorPreviewNode = AetherBags.Nodes.Color.ColorPreviewNode;
namespace AetherBags.Nodes.Color; namespace AetherBags.Nodes.Color;
@@ -1,7 +1,5 @@
using System; using System;
using System.Numerics;
using AetherBags.Addons; using AetherBags.Addons;
using AetherBags.Configuration;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
using KamiToolKit.Premade.Nodes; using KamiToolKit.Premade.Nodes;
@@ -17,6 +17,7 @@ namespace AetherBags.Nodes.Configuration.Category;
public sealed class CategoryDefinitionConfigurationNode : VerticalListNode public sealed class CategoryDefinitionConfigurationNode : VerticalListNode
{ {
private readonly CheckboxNode _enabledCheckbox; private readonly CheckboxNode _enabledCheckbox;
private readonly CheckboxNode _pinnedCheckbox;
private readonly TextInputNode _nameInputNode; private readonly TextInputNode _nameInputNode;
private readonly TextInputNode _descriptionInputNode; private readonly TextInputNode _descriptionInputNode;
private readonly ColorInputRow _colorInputNode; private readonly ColorInputRow _colorInputNode;
@@ -98,6 +99,20 @@ public sealed class CategoryDefinitionConfigurationNode : VerticalListNode
}; };
AddNode(_enabledCheckbox); AddNode(_enabledCheckbox);
_pinnedCheckbox = new CheckboxNode
{
Size = new Vector2(200, 20),
String = "Pinned",
IsChecked = CategoryDefinition.Pinned,
OnClick = isChecked =>
{
CategoryDefinition.Pinned = isChecked;
NotifyChanged();
NotifyCategoryPropertyChanged();
},
};
AddNode(_pinnedCheckbox);
AddNode(new LabelTextNode AddNode(new LabelTextNode
{ {
TextFlags = TextFlags.AutoAdjustNodeSize, TextFlags = TextFlags.AutoAdjustNodeSize,
@@ -471,6 +486,7 @@ public sealed class CategoryDefinitionConfigurationNode : VerticalListNode
if (! _isInitialized) return; if (! _isInitialized) return;
_enabledCheckbox.IsChecked = CategoryDefinition.Enabled; _enabledCheckbox.IsChecked = CategoryDefinition.Enabled;
_pinnedCheckbox.IsChecked = CategoryDefinition.Pinned;
_colorInputNode.CurrentColor = CategoryDefinition.Color; _colorInputNode.CurrentColor = CategoryDefinition.Color;
_nameInputNode.String = CategoryDefinition.Name; _nameInputNode.String = CategoryDefinition.Name;
_descriptionInputNode.String = CategoryDefinition.Description; _descriptionInputNode.String = CategoryDefinition.Description;
@@ -1,9 +1,6 @@
using System;
using System.Linq;
using System.Numerics; using System.Numerics;
using AetherBags.Configuration; using AetherBags.Configuration;
using AetherBags.Nodes.Configuration.Layout; using AetherBags.Nodes.Configuration.Layout;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Configuration.General; namespace AetherBags.Nodes.Configuration.General;
@@ -1,7 +1,6 @@
using System.Numerics; using System.Numerics;
using AetherBags.Configuration; using AetherBags.Configuration;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
using KamiToolKit.Classes;
namespace AetherBags.Nodes.Configuration.Layout; namespace AetherBags.Nodes.Configuration.Layout;
@@ -40,7 +39,7 @@ internal class LayoutConfigurationNode : TabbedVerticalListNode
var compactPackingCheckboxNode = new CheckboxNode var compactPackingCheckboxNode = new CheckboxNode
{ {
Size = Size with { Y = 18 }, Height = 18,
IsVisible = true, IsVisible = true,
String = "Use Compact Packing", String = "Use Compact Packing",
IsChecked = config.CompactPackingEnabled, IsChecked = config.CompactPackingEnabled,
@@ -58,7 +57,7 @@ internal class LayoutConfigurationNode : TabbedVerticalListNode
AddTab(1); AddTab(1);
_preferLargestFitCheckboxNode = new CheckboxNode _preferLargestFitCheckboxNode = new CheckboxNode
{ {
Size = Size with { Y = 18 }, Height = 18,
IsVisible = true, IsVisible = true,
String = "Prefer Largest Fit", String = "Prefer Largest Fit",
IsEnabled = config.CompactPackingEnabled, IsEnabled = config.CompactPackingEnabled,
@@ -73,7 +72,7 @@ internal class LayoutConfigurationNode : TabbedVerticalListNode
_useStableInsertCheckboxNode = new CheckboxNode _useStableInsertCheckboxNode = new CheckboxNode
{ {
Size = Size with { Y = 18 }, Height = 18,
IsVisible = true, IsVisible = true,
String = "Use Stable Insert", String = "Use Stable Insert",
IsEnabled = config.CompactPackingEnabled, IsEnabled = config.CompactPackingEnabled,
-2
View File
@@ -1,6 +1,5 @@
using System; using System;
using System.Numerics; using System.Numerics;
using AetherBags.Extensions;
using AetherBags.Interop; using AetherBags.Interop;
using FFXIVClientStructs.FFXIV.Client.Enums; using FFXIVClientStructs.FFXIV.Client.Enums;
using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI;
@@ -9,7 +8,6 @@ using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes; using KamiToolKit.Classes;
using KamiToolKit.Classes.Timelines; using KamiToolKit.Classes.Timelines;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes; namespace AetherBags.Nodes;
@@ -1,12 +1,10 @@
using System; using System;
using System.Numerics; using System.Numerics;
using AetherBags.Extensions;
using AetherBags.Helpers; using AetherBags.Helpers;
using AetherBags.Inventory; using AetherBags.Inventory;
using AetherBags.Nodes.Layout; using AetherBags.Nodes.Layout;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes; using KamiToolKit.Classes;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
@@ -107,6 +105,8 @@ public class InventoryCategoryNode : SimpleComponentNode
} }
} }
public bool IsPinnedInConfig => CategorizedInventory.Category?.IsPinned ?? false;
public void BeginHeaderHover() public void BeginHeaderHover()
{ {
_hoverRefs++; _hoverRefs++;
@@ -0,0 +1,45 @@
using AetherBags.Nodes.Layout;
namespace AetherBags.Nodes.Inventory;
public sealed class InventoryCategoryPinCoordinator
{
public bool ApplyPinnedStates(WrappingGridNode<InventoryCategoryNode> grid)
{
bool changed = false;
using (grid.DeferRecalculateLayout())
{
foreach (var node in grid.GetNodes<InventoryCategoryNode>())
{
bool shouldBePinned = node.IsPinnedInConfig;
bool isPinned = grid.IsPinned(node);
if (shouldBePinned)
{
if (!isPinned)
{
grid.PinNode(node);
changed = true;
}
}
else
{
if (isPinned)
{
grid.UnpinNode(node);
changed = true;
}
}
}
}
return changed;
}
public bool PrunePinnedNotInGrid(WrappingGridNode<InventoryCategoryNode> grid)
{
return false;
}
}
+533 -100
View File
@@ -1,9 +1,9 @@
using KamiToolKit;
using KamiToolKit.Nodes;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using KamiToolKit;
using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Layout; namespace AetherBags.Nodes.Layout;
@@ -36,8 +36,18 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
private bool _lastuseStableInsert; private bool _lastuseStableInsert;
private int _lastCompactLookahead; private int _lastCompactLookahead;
private int _deferRecalcDepth;
private bool _pendingRecalc;
private int[] _orderScratch = Array.Empty<int>(); private int[] _orderScratch = Array.Empty<int>();
private T? _hoistedNode;
private readonly HashSet<T> _pinned = new(ReferenceEqualityComparer<T>.Instance);
private readonly List<NodeBase> _layoutOrder = new(capacity: 256);
private readonly List<NodeBase> _pinnedScratch = new(capacity: 64);
private readonly List<NodeBase> _normalScratch = new(capacity: 256);
public WrappingGridNode() public WrappingGridNode()
{ {
_rowsView = new RowsReadOnlyView(_rows); _rowsView = new RowsReadOnlyView(_rows);
@@ -45,13 +55,61 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
public IReadOnlyList<IReadOnlyList<NodeBase>> Rows => _rowsView; public IReadOnlyList<IReadOnlyList<NodeBase>> Rows => _rowsView;
public T? HoistedNode => _hoistedNode;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetRowIndex(NodeBase node, out int rowIndex) => _rowIndex.TryGetValue(node, out rowIndex); public bool TryGetRowIndex(NodeBase node, out int rowIndex) => _rowIndex.TryGetValue(node, out rowIndex);
public void SetHoistedNode(T? node)
{
if (ReferenceEquals(_hoistedNode, node))
return;
_hoistedNode = node;
if (node is not null)
{
if (!NodeList.Contains(node))
AddNode(node);
}
RecalculateLayout();
}
public bool PinNode(T node)
{
if (_pinned.Add(node))
{
RequestRecalculateLayout();
return true;
}
return false;
}
public bool UnpinNode(T node)
{
if (_pinned.Remove(node))
{
RequestRecalculateLayout();
return true;
}
return false;
}
public void ClearPinned()
{
if (_pinned.Count == 0) return;
_pinned.Clear();
RequestRecalculateLayout();
}
public bool IsPinned(T node) => _pinned.Contains(node);
protected override void InternalRecalculateLayout() protected override void InternalRecalculateLayout()
{ {
int count = NodeList.Count; int layoutCount = BuildLayoutOrder(out int hoistedCount, out int pinnedCount);
if (count == 0)
if (layoutCount == 0)
{ {
RecycleAllRows(); RecycleAllRows();
_rowIndex.Clear(); _rowIndex.Clear();
@@ -61,9 +119,20 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
return; return;
} }
if (System.Config.General.CompactPackingEnabled) bool hasSpecials = hoistedCount != 0 || pinnedCount != 0;
bool compactEnabled = System.Config.General.CompactPackingEnabled;
if (compactEnabled)
{ {
if (_rows.Count != 0 && LayoutParamsMatchLast() && NodeSetMatchesExistingLayout(count)) if (hasSpecials)
{
FullReflowCompactSections(layoutCount, hoistedCount, pinnedCount);
_requiredHeightDirty = true;
RememberLayoutParams();
return;
}
if (_rows.Count != 0 && LayoutParamsMatchLast() && NodeSetMatchesExistingLayout(layoutCount))
{ {
RepositionExistingRows(); RepositionExistingRows();
_requiredHeightDirty = true; _requiredHeightDirty = true;
@@ -71,44 +140,108 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
return; return;
} }
FullReflowCompact(count); FullReflowCompact(layoutCount);
_requiredHeightDirty = true; _requiredHeightDirty = true;
RememberLayoutParams(); RememberLayoutParams();
return; return;
} }
if (_rows.Count != 0 && TryUpdateLayoutWithoutReflowOrTailReflow(count)) if (_rows.Count != 0 &&
NodeSetMatchesExistingLayout(layoutCount) &&
TryUpdateLayoutWithoutReflowOrTailReflow(layoutCount, hoistedCount, pinnedCount))
{ {
_requiredHeightDirty = true; _requiredHeightDirty = true;
RememberLayoutParams(); RememberLayoutParams();
return; return;
} }
FullReflow(count); FullReflowOrdered(layoutCount, hoistedCount, pinnedCount);
_requiredHeightDirty = true; _requiredHeightDirty = true;
RememberLayoutParams(); RememberLayoutParams();
} }
private bool NodeSetMatchesExistingLayout(int count) private int BuildLayoutOrder(out int hoistedCount, out int pinnedCount)
{ {
if (_rowIndex.Count != count) _layoutOrder.Clear();
_pinnedScratch.Clear();
_normalScratch.Clear();
int nodeCount = NodeList.Count;
if (nodeCount == 0)
{
_hoistedNode = null;
if (_pinned.Count != 0) _pinned.Clear();
hoistedCount = 0;
pinnedCount = 0;
return 0;
}
var present = new HashSet<T>(ReferenceEqualityComparer<T>.Instance);
bool hoistedPresent = false;
T? hoisted = _hoistedNode;
for (int i = 0; i < nodeCount; i++)
{
if (NodeList[i] is not T node)
continue;
present.Add(node);
if (hoisted != null && ReferenceEquals(node, hoisted))
{
hoistedPresent = true;
continue;
}
if (_pinned.Contains(node))
_pinnedScratch.Add(node);
else
_normalScratch.Add(node);
}
if (_pinned.Count != 0)
_pinned.RemoveWhere(n => !present.Contains(n));
if (hoisted != null && !hoistedPresent)
_hoistedNode = null;
if (hoistedPresent && hoisted != null)
_layoutOrder.Add(hoisted);
for (int i = 0; i < _pinnedScratch.Count; i++)
_layoutOrder.Add(_pinnedScratch[i]);
for (int i = 0; i < _normalScratch.Count; i++)
_layoutOrder.Add(_normalScratch[i]);
hoistedCount = (hoistedPresent && hoisted != null) ? 1 : 0;
pinnedCount = _pinnedScratch.Count;
return _layoutOrder.Count;
}
private bool NodeSetMatchesExistingLayout(int layoutCount)
{
if (_rowIndex.Count != layoutCount)
return false; return false;
for (int i = 0; i < count; i++) for (int i = 0; i < layoutCount; i++)
{ {
if (!_rowIndex.ContainsKey(NodeList[i])) if (!_rowIndex.ContainsKey(_layoutOrder[i]))
return false; return false;
} }
return true; return true;
} }
private bool TryUpdateLayoutWithoutReflowOrTailReflow(int count) private bool TryUpdateLayoutWithoutReflowOrTailReflow(int layoutCount, int hoistedCount, int pinnedCount)
{ {
if (!LayoutParamsMatchLast()) if (!LayoutParamsMatchLast())
return false; return false;
int mismatchRow = FindFirstMismatchRow(count, out int mismatchNodeIndex); int mismatchRow = FindFirstMismatchRow(layoutCount, hoistedCount, pinnedCount, out int mismatchNodeIndex);
if (mismatchRow < 0) if (mismatchRow < 0)
{ {
@@ -116,20 +249,22 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
return true; return true;
} }
TailReflowFrom(mismatchRow, mismatchNodeIndex, count); TailReflowFrom(mismatchRow, mismatchNodeIndex, layoutCount, hoistedCount, pinnedCount);
return true; return true;
} }
private int FindFirstMismatchRow(int count, out int mismatchNodeIndex) private int FindFirstMismatchRow(int layoutCount, int hoistedCount, int pinnedCount, out int mismatchNodeIndex)
{ {
float availableWidth = Width; float availableWidth = Width;
float hSpace = HorizontalSpacing; float hSpace = HorizontalSpacing;
float startX = FirstItemSpacing; float startX = FirstItemSpacing;
int normalStart = hoistedCount + pinnedCount;
int rowIdx = 0; int rowIdx = 0;
int nodeIdx = 0; int nodeIdx = 0;
while (nodeIdx < count) while (nodeIdx < layoutCount)
{ {
if (rowIdx >= _rows.Count) if (rowIdx >= _rows.Count)
{ {
@@ -146,19 +281,33 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
return rowIdx; return rowIdx;
} }
int predictedCount = 0; int predictedCount;
float currentX = startX;
while (nodeIdx + predictedCount < count) if (hoistedCount != 0 && nodeIdx == 0)
{ {
NodeBase node = NodeList[nodeIdx + predictedCount]; predictedCount = 1;
float w = node.Width; }
else
{
int sectionEnd = nodeIdx < normalStart ? normalStart : layoutCount;
if (predictedCount != 0 && (currentX + w) > availableWidth) predictedCount = 0;
break; float currentX = startX;
predictedCount++; while (nodeIdx + predictedCount < sectionEnd)
currentX += w + hSpace; {
NodeBase node = _layoutOrder[nodeIdx + predictedCount];
float w = node.Width;
if (predictedCount != 0 && (currentX + w) > availableWidth)
break;
predictedCount++;
currentX += w + hSpace;
}
if (predictedCount == 0 && nodeIdx < sectionEnd)
predictedCount = 1;
} }
if (predictedCount != existingRowCount) if (predictedCount != existingRowCount)
@@ -169,7 +318,7 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
for (int j = 0; j < existingRowCount; j++) for (int j = 0; j < existingRowCount; j++)
{ {
if (!ReferenceEquals(existingRow[j], NodeList[nodeIdx + j])) if (!ReferenceEquals(existingRow[j], _layoutOrder[nodeIdx + j]))
{ {
mismatchNodeIndex = nodeIdx; mismatchNodeIndex = nodeIdx;
return rowIdx; return rowIdx;
@@ -193,7 +342,7 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
private void RepositionExistingRows() private void RepositionExistingRows()
{ {
_rowIndex.Clear(); _rowIndex.Clear();
_rowIndex.EnsureCapacity(NodeList.Count); _rowIndex.EnsureCapacity(_layoutOrder.Count);
float hSpace = HorizontalSpacing; float hSpace = HorizontalSpacing;
float vSpace = VerticalSpacing; float vSpace = VerticalSpacing;
@@ -228,10 +377,10 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
} }
} }
private void TailReflowFrom(int startRowIndex, int startNodeIndex, int count) private void TailReflowFrom(int startRowIndex, int startNodeIndex, int layoutCount, int hoistedCount, int pinnedCount)
{ {
_rowIndex.Clear(); _rowIndex.Clear();
_rowIndex.EnsureCapacity(count); _rowIndex.EnsureCapacity(layoutCount);
float availableWidth = Width; float availableWidth = Width;
float hSpace = HorizontalSpacing; float hSpace = HorizontalSpacing;
@@ -277,109 +426,359 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
_rows.RemoveAt(i); _rows.RemoveAt(i);
} }
int currentRowIndex = startRowIndex; int normalStart = hoistedCount + pinnedCount;
float xCursor = startX;
float rowHeightTail = 0f;
List<NodeBase> currentRow = RentRowList(capacityHint: 8); int rowIndex = startRowIndex;
int idx = startNodeIndex;
for (int i = startNodeIndex; i < count; i++) while (idx < layoutCount)
{ {
NodeBase node = NodeList[i]; List<NodeBase> row = RentRowList(capacityHint: 8);
float w = node.Width;
if (currentRow.Count != 0 && (xCursor + w) > availableWidth) float x = startX;
float rowHeight = 0f;
if (hoistedCount != 0 && idx == 0)
{ {
_rows.Add(currentRow); NodeBase node = _layoutOrder[0];
currentRowIndex++;
y += rowHeightTail + vSpace; node.X = x;
xCursor = startX; node.Y = y;
rowHeightTail = 0f;
currentRow = RentRowList(capacityHint: 8); AdjustNode(node);
rowHeight = node.Height;
row.Add(node);
_rowIndex[node] = rowIndex;
idx = 1;
}
else
{
int sectionEnd = idx < normalStart ? normalStart : layoutCount;
while (idx < sectionEnd)
{
NodeBase node = _layoutOrder[idx];
float w = node.Width;
if (row.Count != 0 && (x + w) > availableWidth)
break;
node.X = x;
node.Y = y;
AdjustNode(node);
float h = node.Height;
if (h > rowHeight) rowHeight = h;
row.Add(node);
_rowIndex[node] = rowIndex;
x += w + hSpace;
idx++;
}
if (row.Count == 0 && idx < sectionEnd)
{
NodeBase node = _layoutOrder[idx];
node.X = startX;
node.Y = y;
AdjustNode(node);
rowHeight = node.Height;
row.Add(node);
_rowIndex[node] = rowIndex;
idx++;
}
} }
node.X = xCursor; if (row.Count != 0)
node.Y = y; {
_rows.Add(row);
AdjustNode(node); rowIndex++;
y += rowHeight + vSpace;
float h = node.Height; }
if (h > rowHeightTail) rowHeightTail = h; else
{
currentRow.Add(node); RecycleRow(row);
_rowIndex[node] = currentRowIndex; break;
}
xCursor += w + hSpace;
}
if (currentRow.Count != 0)
{
_rows.Add(currentRow);
}
else
{
RecycleRow(currentRow);
} }
} }
private void FullReflow(int count) private void FullReflowOrdered(int layoutCount, int hoistedCount, int pinnedCount)
{ {
RecycleAllRows(); RecycleAllRows();
_rowIndex.Clear(); _rowIndex.Clear();
_rowIndex.EnsureCapacity(count); _rowIndex.EnsureCapacity(layoutCount);
float availableWidth = Width; float availableWidth = Width;
float hSpace = HorizontalSpacing; float hSpace = HorizontalSpacing;
float vSpace = VerticalSpacing; float vSpace = VerticalSpacing;
float startX = FirstItemSpacing; float startX = FirstItemSpacing;
float currentX = startX; float y = TopPadding;
float currentY = TopPadding;
float rowHeight = 0f;
int currentRowIndex = 0; int normalStart = hoistedCount + pinnedCount;
List<NodeBase> currentRow = RentRowList(capacityHint: 8);
for (int i = 0; i < count; i++) int rowIdx = 0;
int idx = 0;
while (idx < layoutCount)
{ {
NodeBase node = NodeList[i]; List<NodeBase> row = RentRowList(capacityHint: 8);
float nodeWidth = node.Width;
if (currentRow.Count != 0 && (currentX + nodeWidth) > availableWidth) float x = startX;
float rowHeight = 0f;
if (hoistedCount != 0 && idx == 0)
{ {
_rows.Add(currentRow); NodeBase node = _layoutOrder[0];
currentRowIndex++;
currentY += rowHeight + vSpace; node.X = x;
currentX = startX; node.Y = y;
rowHeight = 0f;
currentRow = RentRowList(capacityHint: 8); AdjustNode(node);
rowHeight = node.Height;
row.Add(node);
_rowIndex[node] = rowIdx;
idx = 1;
}
else
{
int sectionEnd = idx < normalStart ? normalStart : layoutCount;
while (idx < sectionEnd)
{
NodeBase node = _layoutOrder[idx];
float w = node.Width;
if (row.Count != 0 && (x + w) > availableWidth)
break;
node.X = x;
node.Y = y;
AdjustNode(node);
float h = node.Height;
if (h > rowHeight) rowHeight = h;
row.Add(node);
_rowIndex[node] = rowIdx;
x += w + hSpace;
idx++;
}
if (row.Count == 0 && idx < sectionEnd)
{
NodeBase node = _layoutOrder[idx];
node.X = startX;
node.Y = y;
AdjustNode(node);
rowHeight = node.Height;
row.Add(node);
_rowIndex[node] = rowIdx;
idx++;
}
} }
node.X = currentX; if (row.Count != 0)
node.Y = currentY; {
_rows.Add(row);
rowIdx++;
y += rowHeight + vSpace;
}
else
{
RecycleRow(row);
break;
}
}
}
private void FullReflowCompactSections(int layoutCount, int hoistedCount, int pinnedCount)
{
RecycleAllRows();
_rowIndex.Clear();
_rowIndex.EnsureCapacity(layoutCount);
float vSpace = VerticalSpacing;
float y = TopPadding;
int rowIdx = 0;
int idx = 0;
if (hoistedCount != 0)
{
NodeBase node = _layoutOrder[0];
List<NodeBase> row = RentRowList(capacityHint: 1);
node.X = FirstItemSpacing;
node.Y = y;
AdjustNode(node); AdjustNode(node);
float nodeHeight = node.Height; row.Add(node);
if (nodeHeight > rowHeight) rowHeight = nodeHeight; _rowIndex[node] = rowIdx;
currentRow.Add(node); _rows.Add(row);
_rowIndex[node] = currentRowIndex;
currentX += nodeWidth + hSpace; y += node.Height + vSpace;
rowIdx++;
idx = 1;
} }
if (currentRow.Count != 0) int pinnedStart = idx;
int pinnedEnd = pinnedStart + pinnedCount;
if (pinnedCount > 0)
{ {
_rows.Add(currentRow); PackSectionCompact(pinnedStart, pinnedEnd, ref y, ref rowIdx);
idx = pinnedEnd;
} }
else
if (idx < layoutCount)
{ {
RecycleRow(currentRow); PackSectionCompact(idx, layoutCount, ref y, ref rowIdx);
}
}
private void PackSectionCompact(int startIndex, int endIndex, ref float y, ref int rowIdx)
{
int sectionCount = endIndex - startIndex;
if (sectionCount <= 0)
return;
float availableWidth = Width;
float hSpace = HorizontalSpacing;
float vSpace = VerticalSpacing;
float startX = FirstItemSpacing;
EnsureOrderScratch(sectionCount);
for (int i = 0; i < sectionCount; i++)
_orderScratch[i] = i;
int lookahead = System.Config.General.CompactLookahead;
if (lookahead < 0) lookahead = 0;
int p = 0;
while (p < sectionCount)
{
List<NodeBase> row = RentRowList(capacityHint: 8);
float x = startX;
float rowHeight = 0f;
while (p < sectionCount)
{
int localIdx = _orderScratch[p];
NodeBase node = _layoutOrder[startIndex + localIdx];
float w = node.Width;
if (row.Count == 0 || (x + w) <= availableWidth)
{
node.X = x;
node.Y = y;
AdjustNode(node);
float h = node.Height;
if (h > rowHeight) rowHeight = h;
row.Add(node);
_rowIndex[node] = rowIdx;
x += w + hSpace;
p++;
continue;
}
int bestPos = -1;
float bestWidth = 0f;
int end = p + lookahead;
if (end >= sectionCount) end = sectionCount - 1;
for (int s = p + 1; s <= end; s++)
{
int candLocalIdx = _orderScratch[s];
NodeBase cand = _layoutOrder[startIndex + candLocalIdx];
float cw = cand.Width;
if ((x + cw) <= availableWidth)
{
if (!System.Config.General.CompactPreferLargestFit)
{
bestPos = s;
break;
}
if (cw > bestWidth)
{
bestWidth = cw;
bestPos = s;
}
}
}
if (bestPos < 0)
break;
if (bestPos != p)
{
int chosen = _orderScratch[bestPos];
if (System.Config.General.CompactStableInsert)
{
Array.Copy(_orderScratch, p, _orderScratch, p + 1, bestPos - p);
_orderScratch[p] = chosen;
}
else
{
_orderScratch[bestPos] = _orderScratch[p];
_orderScratch[p] = chosen;
}
}
}
if (row.Count == 0)
{
int localIdx = _orderScratch[p];
NodeBase node = _layoutOrder[startIndex + localIdx];
node.X = startX;
node.Y = y;
AdjustNode(node);
rowHeight = node.Height;
row.Add(node);
_rowIndex[node] = rowIdx;
p++;
}
_rows.Add(row);
rowIdx++;
y += rowHeight + vSpace;
} }
} }
@@ -416,7 +815,7 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
while (p < count) while (p < count)
{ {
int idx = _orderScratch[p]; int idx = _orderScratch[p];
NodeBase node = NodeList[idx]; NodeBase node = _layoutOrder[idx];
float w = node.Width; float w = node.Width;
if (row.Count == 0 || (x + w) <= availableWidth) if (row.Count == 0 || (x + w) <= availableWidth)
@@ -446,7 +845,7 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
for (int s = p + 1; s <= end; s++) for (int s = p + 1; s <= end; s++)
{ {
int candIdx = _orderScratch[s]; int candIdx = _orderScratch[s];
NodeBase cand = NodeList[candIdx]; NodeBase cand = _layoutOrder[candIdx];
float cw = cand.Width; float cw = cand.Width;
if ((x + cw) <= availableWidth) if ((x + cw) <= availableWidth)
@@ -488,8 +887,7 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
if (row.Count == 0) if (row.Count == 0)
{ {
int idx = _orderScratch[p]; int idx = _orderScratch[p];
NodeBase node = NodeList[idx]; NodeBase node = _layoutOrder[idx];
float w = node.Width;
node.X = startX; node.X = startX;
node.Y = y; node.Y = y;
@@ -517,11 +915,11 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
if (!_requiredHeightDirty) return _requiredHeight; if (!_requiredHeightDirty) return _requiredHeight;
float maxBottom = 0f; float maxBottom = 0f;
int count = NodeList.Count; int count = _layoutOrder.Count;
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
NodeBase node = NodeList[i]; NodeBase node = _layoutOrder[i];
float bottom = node.Y + node.Height; float bottom = node.Y + node.Height;
if (bottom > maxBottom) maxBottom = bottom; if (bottom > maxBottom) maxBottom = bottom;
} }
@@ -615,6 +1013,34 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
_orderScratch = new int[newSize]; _orderScratch = new int[newSize];
} }
public IDisposable DeferRecalculateLayout()
{
_deferRecalcDepth++;
return new RecalcDeferToken<T>(this);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RequestRecalculateLayout()
{
if (_deferRecalcDepth > 0)
{
_pendingRecalc = true;
return;
}
RecalculateLayout();
}
private void EndDefer()
{
_deferRecalcDepth--;
if (_deferRecalcDepth == 0 && _pendingRecalc)
{
_pendingRecalc = false;
RecalculateLayout();
}
}
private sealed class RowsReadOnlyView : IReadOnlyList<IReadOnlyList<NodeBase>> private sealed class RowsReadOnlyView : IReadOnlyList<IReadOnlyList<NodeBase>>
{ {
private readonly List<List<NodeBase>> _rows; private readonly List<List<NodeBase>> _rows;
@@ -642,4 +1068,11 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetHashCode(TRef obj) => RuntimeHelpers.GetHashCode(obj); public int GetHashCode(TRef obj) => RuntimeHelpers.GetHashCode(obj);
} }
private readonly struct RecalcDeferToken<TRef>(WrappingGridNode<TRef> owner) : IDisposable
where TRef : NodeBase
{
public void Dispose() => owner.EndDefer();
}
} }
+12 -8
View File
@@ -1,14 +1,11 @@
using System;
using System.Numerics; using System.Numerics;
using AetherBags.AddonLifecycles; using AetherBags.AddonLifecycles;
using AetherBags.Addons; using AetherBags.Addons;
using AetherBags.Commands; using AetherBags.Commands;
using AetherBags.Helpers; using AetherBags.Helpers;
using AetherBags.Hooks; using AetherBags.Hooks;
using AetherBags.Inventory;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Game.Command;
using Dalamud.Hooking;
using FFXIVClientStructs.FFXIV.Client.Game;
using KamiToolKit; using KamiToolKit;
namespace AetherBags; namespace AetherBags;
@@ -50,6 +47,8 @@ public unsafe class Plugin : IDalamudPlugin
_commandHandler = new CommandHandler(); _commandHandler = new CommandHandler();
Services.GameInventory.InventoryChanged += InventoryState.OnRawItemAdded;
Services.ClientState.Login += OnLogin; Services.ClientState.Login += OnLogin;
Services.ClientState.Logout += OnLogout; Services.ClientState.Logout += OnLogout;
@@ -65,6 +64,8 @@ public unsafe class Plugin : IDalamudPlugin
{ {
Util.SaveConfig(System.Config); Util.SaveConfig(System.Config);
Services.GameInventory.InventoryChanged -= InventoryState.OnRawItemAdded;
Services.ClientState.Login -= OnLogin; Services.ClientState.Login -= OnLogin;
Services.ClientState.Logout -= OnLogout; Services.ClientState.Logout -= OnLogout;
@@ -82,16 +83,19 @@ public unsafe class Plugin : IDalamudPlugin
private void OnLogin() private void OnLogin()
{ {
System.Config = Util.LoadConfigOrDefault(); System.Config = Util.LoadConfigOrDefault();
InventoryState.TrackLootedItems = true;
#if DEBUG #if DEBUG
System.AddonInventoryWindow.Toggle(); System.AddonInventoryWindow.Toggle();
System.AddonConfigurationWindow.Toggle(); System.AddonConfigurationWindow.Toggle();
#endif #endif
} }
private void OnLogout(int type, int code) private void OnLogout(int type, int code)
{ {
Util.SaveConfig(System.Config); Util.SaveConfig(System.Config);
InventoryState.TrackLootedItems = false;
System.AddonInventoryWindow.Close(); System.AddonInventoryWindow.Close();
System.AddonConfigurationWindow.Close();
} }
} }
+1 -1
View File
@@ -1,4 +1,3 @@
using System.Diagnostics.CodeAnalysis;
using Dalamud.IoC; using Dalamud.IoC;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
@@ -15,6 +14,7 @@ public class Services
[PluginService] public static IDalamudPluginInterface PluginInterface { get; private set; } = null!; [PluginService] public static IDalamudPluginInterface PluginInterface { get; private set; } = null!;
[PluginService] public static IFramework Framework { get; private set; } = null!; [PluginService] public static IFramework Framework { get; private set; } = null!;
[PluginService] public static IGameGui GameGui { get; private set; } = null!; [PluginService] public static IGameGui GameGui { get; private set; } = null!;
[PluginService] public static IGameInventory GameInventory { get; set; } = null!;
[PluginService] public static IKeyState KeyState { get; private set; } = null!; [PluginService] public static IKeyState KeyState { get; private set; } = null!;
[PluginService] public static IPluginLog Logger { get; private set; } = null!; [PluginService] public static IPluginLog Logger { get; private set; } = null!;
[PluginService] public static INotificationManager NotificationManager { get; private set; } = null!; [PluginService] public static INotificationManager NotificationManager { get; private set; } = null!;