Merge branch 'dev/pie-lover'

This commit is contained in:
Shawrkie Williams
2025-12-29 15:38:51 -05:00
31 changed files with 682 additions and 157 deletions
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
+21 -5
View File
@@ -1,19 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using AetherBags.Extensions;
using AetherBags.Inventory;
using AetherBags.Nodes;
using AetherBags.Nodes.Input;
using AetherBags.Nodes.Inventory;
using AetherBags.Nodes.Layout;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Game.Gui;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
namespace AetherBags.Addons;
@@ -21,6 +17,7 @@ namespace AetherBags.Addons;
public class AddonInventoryWindow : NativeAddon
{
private readonly InventoryCategoryHoverCoordinator _hoverCoordinator = new();
private readonly InventoryCategoryPinCoordinator _pinCoordinator = new();
private readonly HashSet<InventoryCategoryNode> _hoverSubscribed = new();
private InventoryNotificationNode _notificationNode = null!;
@@ -135,6 +132,20 @@ public class AddonInventoryWindow : NativeAddon
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()
{
if (!Services.ClientState.IsLoggedIn) return;
@@ -182,6 +193,10 @@ public class AddonInventoryWindow : NativeAddon
Size = ContentSize with { Y = 120 },
});
bool pinsChanged = _pinCoordinator.ApplyPinnedStates(_categoriesNode);
if (pinsChanged)
_hoverCoordinator.ResetAll(_categoriesNode);
WireHoverHandlers();
if (autosize) AutoSizeWindow();
@@ -192,6 +207,7 @@ public class AddonInventoryWindow : NativeAddon
}
}
private void WireHoverHandlers()
{
var nodes = _categoriesNode.Nodes;
-2
View File
@@ -1,8 +1,6 @@
using AetherBags.Configuration;
using AetherBags.Inventory;
using Dalamud.Game.Text.SeStringHandling;
using KamiToolKit.Premade;
using SeStringBuilder = Lumina.Text.SeStringBuilder;
namespace AetherBags.Addons;
+1 -1
View File
@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<Author>Zeffuro</Author>
<Author>Zeffuro, Pie Lover</Author>
<Name>AetherBags</Name>
<InternalName>AetherBags</InternalName>
<Punchline>Never think too hard about your bags again!</Punchline>
@@ -17,6 +17,7 @@ public class CategorySettings
public class UserCategoryDefinition
{
public bool Enabled { get; set; } = true;
public bool Pinned { get; set; } = false;
public string Id { get; set; } = Guid.NewGuid().ToString("N");
public string Name { get; set; } = "New Category";
public string Description { get; set; } = string.Empty;
@@ -1,6 +1,5 @@
using System.Numerics;
using KamiToolKit.Classes;
using SixLabors.ImageSharp.PixelFormats;
namespace AetherBags.Configuration;
@@ -1,6 +1,3 @@
using System.Numerics;
using KamiToolKit.Classes;
namespace AetherBags.Configuration;
public class GeneralSettings
@@ -1,6 +1,3 @@
using System.Numerics;
using KamiToolKit.Classes;
namespace AetherBags.Configuration;
public class SystemConfiguration
@@ -1,4 +1,3 @@
using System;
using AetherBags.Inventory;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
@@ -1,5 +1,3 @@
using System.Diagnostics;
namespace AetherBags.Extensions;
public static class LoggerExtensions
@@ -1,8 +1,6 @@
using System.Linq;
using AetherBags.Configuration;
using AetherBags.Helpers.Import;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Interface.ImGuiNotification;
namespace AetherBags.Helpers;
@@ -1,8 +1,4 @@
using AetherBags. Extensions;
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;
-1
View File
@@ -1,7 +1,6 @@
using System;
using Dalamud.Hooking;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI;
@@ -73,6 +73,7 @@ public static class CategoryBucketManager
Name = category.Name,
Description = category.Description,
Color = category.Color,
IsPinned = category.Pinned,
},
Items = 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.Description = category.Description;
bucket.Category.Color = category.Color;
bucket.Category.IsPinned = category.Pinned;
}
foreach (var itemKvp in itemInfoByKey)
+2 -1
View File
@@ -6,6 +6,7 @@ namespace AetherBags.Inventory;
public class CategoryInfo
{
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 bool IsPinned { get; set; } = false;
}
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using Lumina.Text.ReadOnly;
+38 -2
View File
@@ -1,10 +1,10 @@
using AetherBags.Configuration;
using AetherBags.Currency;
using Dalamud.Game.Inventory;
using Dalamud.Game.Inventory.InventoryEventArgTypes;
using FFXIVClientStructs.FFXIV.Client.Game;
using System.Collections.Generic;
using System.Linq;
using AetherBags.Currency;
using CurrencyManager = FFXIVClientStructs.FFXIV.Client.Game.CurrencyManager;
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<ulong> RemoveKeysScratch = new(capacity: 256);
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)
=> inventoryTypes.Contains((InventoryType)type);
@@ -135,6 +138,38 @@ public static unsafe class InventoryState
public static InventoryContainer* GetInventoryContainer(InventoryType 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()
{
AggByKey.Clear();
@@ -152,5 +187,6 @@ public static unsafe class InventoryState
FilteredCategories.Clear();
RemoveKeysScratch.Clear();
ClaimedKeys.Clear();
LootedItems?.Clear();
}
}
-2
View File
@@ -1,11 +1,9 @@
using AetherBags.Extensions;
using FFXIVClientStructs.FFXIV.Client.Game;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using System;
using System.Numerics;
using System.Text.RegularExpressions;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
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 System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace AetherBags.Inventory;
@@ -1,7 +1,5 @@
using System.Numerics;
using Dalamud.Game.Addon.Events.EventDataTypes;
using KamiToolKit.Nodes;
using ColorPreviewNode = AetherBags.Nodes.Color.ColorPreviewNode;
namespace AetherBags.Nodes.Color;
@@ -1,7 +1,5 @@
using System;
using System.Numerics;
using AetherBags.Addons;
using AetherBags.Configuration;
using KamiToolKit.Nodes;
using KamiToolKit.Premade.Nodes;
@@ -17,6 +17,7 @@ namespace AetherBags.Nodes.Configuration.Category;
public sealed class CategoryDefinitionConfigurationNode : VerticalListNode
{
private readonly CheckboxNode _enabledCheckbox;
private readonly CheckboxNode _pinnedCheckbox;
private readonly TextInputNode _nameInputNode;
private readonly TextInputNode _descriptionInputNode;
private readonly ColorInputRow _colorInputNode;
@@ -98,6 +99,20 @@ public sealed class CategoryDefinitionConfigurationNode : VerticalListNode
};
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
{
TextFlags = TextFlags.AutoAdjustNodeSize,
@@ -471,6 +486,7 @@ public sealed class CategoryDefinitionConfigurationNode : VerticalListNode
if (! _isInitialized) return;
_enabledCheckbox.IsChecked = CategoryDefinition.Enabled;
_pinnedCheckbox.IsChecked = CategoryDefinition.Pinned;
_colorInputNode.CurrentColor = CategoryDefinition.Color;
_nameInputNode.String = CategoryDefinition.Name;
_descriptionInputNode.String = CategoryDefinition.Description;
@@ -1,9 +1,6 @@
using System;
using System.Linq;
using System.Numerics;
using AetherBags.Configuration;
using AetherBags.Nodes.Configuration.Layout;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Configuration.General;
@@ -1,7 +1,6 @@
using System.Numerics;
using AetherBags.Configuration;
using KamiToolKit.Nodes;
using KamiToolKit.Classes;
namespace AetherBags.Nodes.Configuration.Layout;
@@ -40,7 +39,7 @@ internal class LayoutConfigurationNode : TabbedVerticalListNode
var compactPackingCheckboxNode = new CheckboxNode
{
Size = Size with { Y = 18 },
Height = 18,
IsVisible = true,
String = "Use Compact Packing",
IsChecked = config.CompactPackingEnabled,
@@ -58,7 +57,7 @@ internal class LayoutConfigurationNode : TabbedVerticalListNode
AddTab(1);
_preferLargestFitCheckboxNode = new CheckboxNode
{
Size = Size with { Y = 18 },
Height = 18,
IsVisible = true,
String = "Prefer Largest Fit",
IsEnabled = config.CompactPackingEnabled,
@@ -73,7 +72,7 @@ internal class LayoutConfigurationNode : TabbedVerticalListNode
_useStableInsertCheckboxNode = new CheckboxNode
{
Size = Size with { Y = 18 },
Height = 18,
IsVisible = true,
String = "Use Stable Insert",
IsEnabled = config.CompactPackingEnabled,
-2
View File
@@ -1,6 +1,5 @@
using System;
using System.Numerics;
using AetherBags.Extensions;
using AetherBags.Interop;
using FFXIVClientStructs.FFXIV.Client.Enums;
using FFXIVClientStructs.FFXIV.Client.UI;
@@ -9,7 +8,6 @@ using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Classes.Timelines;
using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes;
@@ -1,12 +1,10 @@
using System;
using System.Numerics;
using AetherBags.Extensions;
using AetherBags.Helpers;
using AetherBags.Inventory;
using AetherBags.Nodes.Layout;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
@@ -107,6 +105,8 @@ public class InventoryCategoryNode : SimpleComponentNode
}
}
public bool IsPinnedInConfig => CategorizedInventory.Category?.IsPinned ?? false;
public void BeginHeaderHover()
{
_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.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using KamiToolKit;
using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Layout;
@@ -36,8 +36,18 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
private bool _lastuseStableInsert;
private int _lastCompactLookahead;
private int _deferRecalcDepth;
private bool _pendingRecalc;
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()
{
_rowsView = new RowsReadOnlyView(_rows);
@@ -45,13 +55,61 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
public IReadOnlyList<IReadOnlyList<NodeBase>> Rows => _rowsView;
public T? HoistedNode => _hoistedNode;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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()
{
int count = NodeList.Count;
if (count == 0)
int layoutCount = BuildLayoutOrder(out int hoistedCount, out int pinnedCount);
if (layoutCount == 0)
{
RecycleAllRows();
_rowIndex.Clear();
@@ -61,9 +119,20 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
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();
_requiredHeightDirty = true;
@@ -71,44 +140,108 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
return;
}
FullReflowCompact(count);
FullReflowCompact(layoutCount);
_requiredHeightDirty = true;
RememberLayoutParams();
return;
}
if (_rows.Count != 0 && TryUpdateLayoutWithoutReflowOrTailReflow(count))
if (_rows.Count != 0 &&
NodeSetMatchesExistingLayout(layoutCount) &&
TryUpdateLayoutWithoutReflowOrTailReflow(layoutCount, hoistedCount, pinnedCount))
{
_requiredHeightDirty = true;
RememberLayoutParams();
return;
}
FullReflow(count);
FullReflowOrdered(layoutCount, hoistedCount, pinnedCount);
_requiredHeightDirty = true;
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;
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 true;
}
private bool TryUpdateLayoutWithoutReflowOrTailReflow(int count)
private bool TryUpdateLayoutWithoutReflowOrTailReflow(int layoutCount, int hoistedCount, int pinnedCount)
{
if (!LayoutParamsMatchLast())
return false;
int mismatchRow = FindFirstMismatchRow(count, out int mismatchNodeIndex);
int mismatchRow = FindFirstMismatchRow(layoutCount, hoistedCount, pinnedCount, out int mismatchNodeIndex);
if (mismatchRow < 0)
{
@@ -116,20 +249,22 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
return true;
}
TailReflowFrom(mismatchRow, mismatchNodeIndex, count);
TailReflowFrom(mismatchRow, mismatchNodeIndex, layoutCount, hoistedCount, pinnedCount);
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 hSpace = HorizontalSpacing;
float startX = FirstItemSpacing;
int normalStart = hoistedCount + pinnedCount;
int rowIdx = 0;
int nodeIdx = 0;
while (nodeIdx < count)
while (nodeIdx < layoutCount)
{
if (rowIdx >= _rows.Count)
{
@@ -146,19 +281,33 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
return rowIdx;
}
int predictedCount = 0;
float currentX = startX;
int predictedCount;
while (nodeIdx + predictedCount < count)
if (hoistedCount != 0 && nodeIdx == 0)
{
NodeBase node = NodeList[nodeIdx + predictedCount];
float w = node.Width;
predictedCount = 1;
}
else
{
int sectionEnd = nodeIdx < normalStart ? normalStart : layoutCount;
if (predictedCount != 0 && (currentX + w) > availableWidth)
break;
predictedCount = 0;
float currentX = startX;
predictedCount++;
currentX += w + hSpace;
while (nodeIdx + predictedCount < sectionEnd)
{
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)
@@ -169,7 +318,7 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
for (int j = 0; j < existingRowCount; j++)
{
if (!ReferenceEquals(existingRow[j], NodeList[nodeIdx + j]))
if (!ReferenceEquals(existingRow[j], _layoutOrder[nodeIdx + j]))
{
mismatchNodeIndex = nodeIdx;
return rowIdx;
@@ -193,7 +342,7 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
private void RepositionExistingRows()
{
_rowIndex.Clear();
_rowIndex.EnsureCapacity(NodeList.Count);
_rowIndex.EnsureCapacity(_layoutOrder.Count);
float hSpace = HorizontalSpacing;
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.EnsureCapacity(count);
_rowIndex.EnsureCapacity(layoutCount);
float availableWidth = Width;
float hSpace = HorizontalSpacing;
@@ -277,109 +426,359 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
_rows.RemoveAt(i);
}
int currentRowIndex = startRowIndex;
float xCursor = startX;
float rowHeightTail = 0f;
int normalStart = hoistedCount + pinnedCount;
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];
float w = node.Width;
List<NodeBase> row = RentRowList(capacityHint: 8);
if (currentRow.Count != 0 && (xCursor + w) > availableWidth)
float x = startX;
float rowHeight = 0f;
if (hoistedCount != 0 && idx == 0)
{
_rows.Add(currentRow);
currentRowIndex++;
NodeBase node = _layoutOrder[0];
y += rowHeightTail + vSpace;
xCursor = startX;
rowHeightTail = 0f;
node.X = x;
node.Y = y;
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;
node.Y = y;
AdjustNode(node);
float h = node.Height;
if (h > rowHeightTail) rowHeightTail = h;
currentRow.Add(node);
_rowIndex[node] = currentRowIndex;
xCursor += w + hSpace;
}
if (currentRow.Count != 0)
{
_rows.Add(currentRow);
}
else
{
RecycleRow(currentRow);
if (row.Count != 0)
{
_rows.Add(row);
rowIndex++;
y += rowHeight + vSpace;
}
else
{
RecycleRow(row);
break;
}
}
}
private void FullReflow(int count)
private void FullReflowOrdered(int layoutCount, int hoistedCount, int pinnedCount)
{
RecycleAllRows();
_rowIndex.Clear();
_rowIndex.EnsureCapacity(count);
_rowIndex.EnsureCapacity(layoutCount);
float availableWidth = Width;
float hSpace = HorizontalSpacing;
float vSpace = VerticalSpacing;
float startX = FirstItemSpacing;
float currentX = startX;
float currentY = TopPadding;
float rowHeight = 0f;
float y = TopPadding;
int currentRowIndex = 0;
List<NodeBase> currentRow = RentRowList(capacityHint: 8);
int normalStart = hoistedCount + pinnedCount;
for (int i = 0; i < count; i++)
int rowIdx = 0;
int idx = 0;
while (idx < layoutCount)
{
NodeBase node = NodeList[i];
float nodeWidth = node.Width;
List<NodeBase> row = RentRowList(capacityHint: 8);
if (currentRow.Count != 0 && (currentX + nodeWidth) > availableWidth)
float x = startX;
float rowHeight = 0f;
if (hoistedCount != 0 && idx == 0)
{
_rows.Add(currentRow);
currentRowIndex++;
NodeBase node = _layoutOrder[0];
currentY += rowHeight + vSpace;
currentX = startX;
rowHeight = 0f;
node.X = x;
node.Y = y;
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;
node.Y = currentY;
if (row.Count != 0)
{
_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);
float nodeHeight = node.Height;
if (nodeHeight > rowHeight) rowHeight = nodeHeight;
row.Add(node);
_rowIndex[node] = rowIdx;
currentRow.Add(node);
_rowIndex[node] = currentRowIndex;
_rows.Add(row);
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)
{
int idx = _orderScratch[p];
NodeBase node = NodeList[idx];
NodeBase node = _layoutOrder[idx];
float w = node.Width;
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++)
{
int candIdx = _orderScratch[s];
NodeBase cand = NodeList[candIdx];
NodeBase cand = _layoutOrder[candIdx];
float cw = cand.Width;
if ((x + cw) <= availableWidth)
@@ -488,8 +887,7 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
if (row.Count == 0)
{
int idx = _orderScratch[p];
NodeBase node = NodeList[idx];
float w = node.Width;
NodeBase node = _layoutOrder[idx];
node.X = startX;
node.Y = y;
@@ -517,11 +915,11 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
if (!_requiredHeightDirty) return _requiredHeight;
float maxBottom = 0f;
int count = NodeList.Count;
int count = _layoutOrder.Count;
for (int i = 0; i < count; i++)
{
NodeBase node = NodeList[i];
NodeBase node = _layoutOrder[i];
float bottom = node.Y + node.Height;
if (bottom > maxBottom) maxBottom = bottom;
}
@@ -615,6 +1013,34 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
_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 readonly List<List<NodeBase>> _rows;
@@ -642,4 +1068,11 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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 AetherBags.AddonLifecycles;
using AetherBags.Addons;
using AetherBags.Commands;
using AetherBags.Helpers;
using AetherBags.Hooks;
using AetherBags.Inventory;
using Dalamud.Plugin;
using Dalamud.Game.Command;
using Dalamud.Hooking;
using FFXIVClientStructs.FFXIV.Client.Game;
using KamiToolKit;
namespace AetherBags;
@@ -50,6 +47,8 @@ public unsafe class Plugin : IDalamudPlugin
_commandHandler = new CommandHandler();
Services.GameInventory.InventoryChanged += InventoryState.OnRawItemAdded;
Services.ClientState.Login += OnLogin;
Services.ClientState.Logout += OnLogout;
@@ -65,6 +64,8 @@ public unsafe class Plugin : IDalamudPlugin
{
Util.SaveConfig(System.Config);
Services.GameInventory.InventoryChanged -= InventoryState.OnRawItemAdded;
Services.ClientState.Login -= OnLogin;
Services.ClientState.Logout -= OnLogout;
@@ -82,16 +83,19 @@ public unsafe class Plugin : IDalamudPlugin
private void OnLogin()
{
System.Config = Util.LoadConfigOrDefault();
InventoryState.TrackLootedItems = true;
#if DEBUG
System.AddonInventoryWindow.Toggle();
System.AddonConfigurationWindow.Toggle();
#endif
#if DEBUG
System.AddonInventoryWindow.Toggle();
System.AddonConfigurationWindow.Toggle();
#endif
}
private void OnLogout(int type, int code)
{
Util.SaveConfig(System.Config);
InventoryState.TrackLootedItems = false;
System.AddonInventoryWindow.Close();
System.AddonConfigurationWindow.Close();
}
}
+1 -1
View File
@@ -1,4 +1,3 @@
using System.Diagnostics.CodeAnalysis;
using Dalamud.IoC;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
@@ -15,6 +14,7 @@ public class Services
[PluginService] public static IDalamudPluginInterface PluginInterface { 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 IGameInventory GameInventory { get; set; } = null!;
[PluginService] public static IKeyState KeyState { get; private set; } = null!;
[PluginService] public static IPluginLog Logger { get; private set; } = null!;
[PluginService] public static INotificationManager NotificationManager { get; private set; } = null!;