Merge branch 'dev/pie-lover'
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,12 +281,22 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
|
||||
return rowIdx;
|
||||
}
|
||||
|
||||
int predictedCount = 0;
|
||||
int predictedCount;
|
||||
|
||||
if (hoistedCount != 0 && nodeIdx == 0)
|
||||
{
|
||||
predictedCount = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
int sectionEnd = nodeIdx < normalStart ? normalStart : layoutCount;
|
||||
|
||||
predictedCount = 0;
|
||||
float currentX = startX;
|
||||
|
||||
while (nodeIdx + predictedCount < count)
|
||||
while (nodeIdx + predictedCount < sectionEnd)
|
||||
{
|
||||
NodeBase node = NodeList[nodeIdx + predictedCount];
|
||||
NodeBase node = _layoutOrder[nodeIdx + predictedCount];
|
||||
float w = node.Width;
|
||||
|
||||
if (predictedCount != 0 && (currentX + w) > availableWidth)
|
||||
@@ -161,6 +306,10 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
|
||||
currentX += w + hSpace;
|
||||
}
|
||||
|
||||
if (predictedCount == 0 && nodeIdx < sectionEnd)
|
||||
predictedCount = 1;
|
||||
}
|
||||
|
||||
if (predictedCount != existingRowCount)
|
||||
{
|
||||
mismatchNodeIndex = nodeIdx;
|
||||
@@ -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];
|
||||
List<NodeBase> row = RentRowList(capacityHint: 8);
|
||||
|
||||
float x = startX;
|
||||
float rowHeight = 0f;
|
||||
|
||||
if (hoistedCount != 0 && idx == 0)
|
||||
{
|
||||
NodeBase node = _layoutOrder[0];
|
||||
|
||||
node.X = x;
|
||||
node.Y = y;
|
||||
|
||||
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 (currentRow.Count != 0 && (xCursor + w) > availableWidth)
|
||||
{
|
||||
_rows.Add(currentRow);
|
||||
currentRowIndex++;
|
||||
if (row.Count != 0 && (x + w) > availableWidth)
|
||||
break;
|
||||
|
||||
y += rowHeightTail + vSpace;
|
||||
xCursor = startX;
|
||||
rowHeightTail = 0f;
|
||||
|
||||
currentRow = RentRowList(capacityHint: 8);
|
||||
}
|
||||
|
||||
node.X = xCursor;
|
||||
node.X = x;
|
||||
node.Y = y;
|
||||
|
||||
AdjustNode(node);
|
||||
|
||||
float h = node.Height;
|
||||
if (h > rowHeightTail) rowHeightTail = h;
|
||||
if (h > rowHeight) rowHeight = h;
|
||||
|
||||
currentRow.Add(node);
|
||||
_rowIndex[node] = currentRowIndex;
|
||||
row.Add(node);
|
||||
_rowIndex[node] = rowIndex;
|
||||
|
||||
xCursor += w + hSpace;
|
||||
x += w + hSpace;
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (currentRow.Count != 0)
|
||||
if (row.Count == 0 && idx < sectionEnd)
|
||||
{
|
||||
_rows.Add(currentRow);
|
||||
NodeBase node = _layoutOrder[idx];
|
||||
|
||||
node.X = startX;
|
||||
node.Y = y;
|
||||
|
||||
AdjustNode(node);
|
||||
|
||||
rowHeight = node.Height;
|
||||
|
||||
row.Add(node);
|
||||
_rowIndex[node] = rowIndex;
|
||||
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
if (row.Count != 0)
|
||||
{
|
||||
_rows.Add(row);
|
||||
rowIndex++;
|
||||
y += rowHeight + vSpace;
|
||||
}
|
||||
else
|
||||
{
|
||||
RecycleRow(currentRow);
|
||||
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 y = TopPadding;
|
||||
|
||||
int normalStart = hoistedCount + pinnedCount;
|
||||
|
||||
int rowIdx = 0;
|
||||
int idx = 0;
|
||||
|
||||
while (idx < layoutCount)
|
||||
{
|
||||
List<NodeBase> row = RentRowList(capacityHint: 8);
|
||||
|
||||
float x = startX;
|
||||
float rowHeight = 0f;
|
||||
|
||||
int currentRowIndex = 0;
|
||||
List<NodeBase> currentRow = RentRowList(capacityHint: 8);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
if (hoistedCount != 0 && idx == 0)
|
||||
{
|
||||
NodeBase node = NodeList[i];
|
||||
float nodeWidth = node.Width;
|
||||
NodeBase node = _layoutOrder[0];
|
||||
|
||||
if (currentRow.Count != 0 && (currentX + nodeWidth) > availableWidth)
|
||||
{
|
||||
_rows.Add(currentRow);
|
||||
currentRowIndex++;
|
||||
|
||||
currentY += rowHeight + vSpace;
|
||||
currentX = startX;
|
||||
rowHeight = 0f;
|
||||
|
||||
currentRow = RentRowList(capacityHint: 8);
|
||||
}
|
||||
|
||||
node.X = currentX;
|
||||
node.Y = currentY;
|
||||
node.X = x;
|
||||
node.Y = y;
|
||||
|
||||
AdjustNode(node);
|
||||
|
||||
float nodeHeight = node.Height;
|
||||
if (nodeHeight > rowHeight) rowHeight = nodeHeight;
|
||||
rowHeight = node.Height;
|
||||
|
||||
currentRow.Add(node);
|
||||
_rowIndex[node] = currentRowIndex;
|
||||
row.Add(node);
|
||||
_rowIndex[node] = rowIdx;
|
||||
|
||||
currentX += nodeWidth + hSpace;
|
||||
}
|
||||
|
||||
if (currentRow.Count != 0)
|
||||
{
|
||||
_rows.Add(currentRow);
|
||||
idx = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
RecycleRow(currentRow);
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
row.Add(node);
|
||||
_rowIndex[node] = rowIdx;
|
||||
|
||||
_rows.Add(row);
|
||||
|
||||
y += node.Height + vSpace;
|
||||
rowIdx++;
|
||||
idx = 1;
|
||||
}
|
||||
|
||||
int pinnedStart = idx;
|
||||
int pinnedEnd = pinnedStart + pinnedCount;
|
||||
if (pinnedCount > 0)
|
||||
{
|
||||
PackSectionCompact(pinnedStart, pinnedEnd, ref y, ref rowIdx);
|
||||
idx = pinnedEnd;
|
||||
}
|
||||
|
||||
if (idx < layoutCount)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
+10
-6
@@ -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
|
||||
#if DEBUG
|
||||
System.AddonInventoryWindow.Toggle();
|
||||
System.AddonConfigurationWindow.Toggle();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnLogout(int type, int code)
|
||||
{
|
||||
Util.SaveConfig(System.Config);
|
||||
InventoryState.TrackLootedItems = false;
|
||||
System.AddonInventoryWindow.Close();
|
||||
System.AddonConfigurationWindow.Close();
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
|
||||
Reference in New Issue
Block a user