Merge pull request #4 from Seeker1437/master
Reduce reflows, improve search semantics, and small caching for item info
This commit is contained in:
@@ -1,16 +1,18 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using AetherBags.Currency;
|
using AetherBags.Currency;
|
||||||
using Dalamud.Game.Inventory;
|
using Dalamud.Game.Inventory;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace AetherBags.Inventory;
|
namespace AetherBags.Inventory;
|
||||||
|
|
||||||
public static unsafe class InventoryState
|
public static unsafe class InventoryState
|
||||||
{
|
{
|
||||||
public static List<InventoryType> StandardInventories => [
|
public static readonly InventoryType[] StandardInventories =
|
||||||
|
[
|
||||||
InventoryType.Inventory1,
|
InventoryType.Inventory1,
|
||||||
InventoryType.Inventory2,
|
InventoryType.Inventory2,
|
||||||
InventoryType.Inventory3,
|
InventoryType.Inventory3,
|
||||||
@@ -33,61 +35,151 @@ public static unsafe class InventoryState
|
|||||||
InventoryType.ArmorySoulCrystal,
|
InventoryType.ArmorySoulCrystal,
|
||||||
];
|
];
|
||||||
|
|
||||||
public static bool Contains(this List<InventoryType> inventoryTypes, GameInventoryType type)
|
private static readonly InventoryType[] BagInventories =
|
||||||
|
[
|
||||||
|
InventoryType.Inventory1,
|
||||||
|
InventoryType.Inventory2,
|
||||||
|
InventoryType.Inventory3,
|
||||||
|
InventoryType.Inventory4,
|
||||||
|
];
|
||||||
|
|
||||||
|
public static bool Contains(this IReadOnlyCollection<InventoryType> inventoryTypes, GameInventoryType type)
|
||||||
=> inventoryTypes.Contains((InventoryType)type);
|
=> inventoryTypes.Contains((InventoryType)type);
|
||||||
|
|
||||||
|
private static readonly Dictionary<uint, CategoryInfo> CategoryInfoCache = new(capacity: 256);
|
||||||
|
|
||||||
public static List<CategorizedInventory> GetInventoryItemCategories(string filterString = "", bool invert = false)
|
public static List<CategorizedInventory> GetInventoryItemCategories(string filterString = "", bool invert = false)
|
||||||
{
|
{
|
||||||
var items = string.IsNullOrEmpty(filterString)
|
List<ItemInfo> items = string.IsNullOrEmpty(filterString)
|
||||||
? GetInventoryItems()
|
? GetInventoryItems()
|
||||||
: GetInventoryItems(filterString, invert);
|
: GetInventoryItems(filterString, invert);
|
||||||
|
|
||||||
return items
|
if (items.Count == 0)
|
||||||
.GroupBy(GetItemUiCategoryKey)
|
return new List<CategorizedInventory>(0);
|
||||||
.OrderBy(g => g.Key)
|
|
||||||
.Select(g =>
|
var buckets = new Dictionary<uint, CategoryBucket>(capacity: Math.Min(128, items.Count));
|
||||||
|
|
||||||
|
for (int i = 0; i < items.Count; i++)
|
||||||
{
|
{
|
||||||
var category = GetCategoryInfoForKey(g.Key, g.FirstOrDefault());
|
ItemInfo info = items[i];
|
||||||
var list = g.OrderByDescending(i => i.ItemCount).ToList();
|
uint catKey = info.UiCategory.RowId;
|
||||||
return new CategorizedInventory(category, list);
|
|
||||||
})
|
if (!buckets.TryGetValue(catKey, out CategoryBucket? bucket))
|
||||||
.ToList();
|
{
|
||||||
|
bucket = new CategoryBucket
|
||||||
|
{
|
||||||
|
Key = catKey,
|
||||||
|
Category = GetCategoryInfoForKeyCached(catKey, info),
|
||||||
|
Items = new List<ItemInfo>(capacity: 16),
|
||||||
|
};
|
||||||
|
buckets.Add(catKey, bucket);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<ItemInfo> GetInventoryItems() {
|
bucket.Items.Add(info);
|
||||||
List<InventoryType> inventories = [ InventoryType.Inventory1, InventoryType.Inventory2, InventoryType.Inventory3, InventoryType.Inventory4 ];
|
}
|
||||||
List<InventoryItem> items = [];
|
|
||||||
|
|
||||||
foreach (var inventory in inventories) {
|
uint[] keys = new uint[buckets.Count];
|
||||||
var container = InventoryManager.Instance()->GetInventoryContainer(inventory);
|
int k = 0;
|
||||||
|
foreach (var key in buckets.Keys)
|
||||||
|
keys[k++] = key;
|
||||||
|
Array.Sort(keys);
|
||||||
|
|
||||||
for (var index = 0; index < container->Size; ++index) {
|
var result = new List<CategorizedInventory>(keys.Length);
|
||||||
ref var item = ref container->Items[index];
|
for (int i = 0; i < keys.Length; i++)
|
||||||
if (item.ItemId is 0) continue;
|
{
|
||||||
|
CategoryBucket bucket = buckets[keys[i]];
|
||||||
|
bucket.Items.Sort(ItemCountDescComparer.Instance);
|
||||||
|
result.Add(new CategorizedInventory(bucket.Category, bucket.Items));
|
||||||
|
}
|
||||||
|
|
||||||
items.Add(item);
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ItemInfo> GetInventoryItems()
|
||||||
|
{
|
||||||
|
var dict = new Dictionary<uint, AggregatedItem>(capacity: 128);
|
||||||
|
|
||||||
|
InventoryManager* mgr = InventoryManager.Instance();
|
||||||
|
if (mgr == null)
|
||||||
|
return new List<ItemInfo>(0);
|
||||||
|
|
||||||
|
for (int invIndex = 0; invIndex < BagInventories.Length; invIndex++)
|
||||||
|
{
|
||||||
|
var container = mgr->GetInventoryContainer(BagInventories[invIndex]);
|
||||||
|
if (container == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int size = container->Size;
|
||||||
|
for (int slot = 0; slot < size; slot++)
|
||||||
|
{
|
||||||
|
ref var item = ref container->Items[slot];
|
||||||
|
uint id = item.ItemId;
|
||||||
|
if (id == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int qty = item.Quantity;
|
||||||
|
|
||||||
|
if (dict.TryGetValue(id, out AggregatedItem agg))
|
||||||
|
{
|
||||||
|
agg.Total += qty;
|
||||||
|
dict[id] = agg;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dict.Add(id, new AggregatedItem { First = item, Total = qty });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ItemInfo> itemInfos = [];
|
if (dict.Count == 0)
|
||||||
itemInfos.AddRange(from itemGroups in items.GroupBy(item => item.ItemId)
|
return new List<ItemInfo>(0);
|
||||||
where itemGroups.Key is not 0
|
|
||||||
let item = itemGroups.First()
|
var list = new List<ItemInfo>(dict.Count);
|
||||||
let itemCount = itemGroups.Sum(duplicateItem => duplicateItem.Quantity)
|
foreach (var kvp in dict)
|
||||||
select new ItemInfo {
|
{
|
||||||
Item = item, ItemCount = itemCount,
|
AggregatedItem agg = kvp.Value;
|
||||||
|
|
||||||
|
list.Add(new ItemInfo
|
||||||
|
{
|
||||||
|
Item = agg.First,
|
||||||
|
ItemCount = agg.Total,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return itemInfos;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<ItemInfo> GetInventoryItems(string filterString, bool invert = false)
|
public static List<ItemInfo> GetInventoryItems(string filterString, bool invert = false)
|
||||||
=> GetInventoryItems().Where(item => item.IsRegexMatch(filterString) != invert).ToList();
|
{
|
||||||
|
List<ItemInfo> all = GetInventoryItems();
|
||||||
|
if (all.Count == 0)
|
||||||
|
return all;
|
||||||
|
|
||||||
private static uint GetItemUiCategoryKey(ItemInfo info)
|
var filtered = new List<ItemInfo>(all.Count);
|
||||||
=> info.UiCategory.RowId;
|
var re = new Regex(filterString, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||||
|
for (int i = 0; i < all.Count; i++)
|
||||||
|
{
|
||||||
|
ItemInfo info = all[i];
|
||||||
|
|
||||||
private static CategoryInfo GetCategoryInfoForKey(uint key, ItemInfo? sample)
|
bool isMatch = info.IsRegexMatch(re);
|
||||||
|
if (isMatch != invert)
|
||||||
|
filtered.Add(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CategoryInfo GetCategoryInfoForKeyCached(uint key, ItemInfo sample)
|
||||||
|
{
|
||||||
|
if (CategoryInfoCache.TryGetValue(key, out var cached))
|
||||||
|
return cached;
|
||||||
|
|
||||||
|
CategoryInfo info = GetCategoryInfoForKeySlow(key, sample);
|
||||||
|
CategoryInfoCache[key] = info;
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CategoryInfo GetCategoryInfoForKeySlow(uint key, ItemInfo sample)
|
||||||
{
|
{
|
||||||
if (key == 0)
|
if (key == 0)
|
||||||
{
|
{
|
||||||
@@ -98,8 +190,9 @@ public static unsafe class InventoryState
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var uiCat = sample?.UiCategory.Value;
|
var uiCat = sample.UiCategory.Value;
|
||||||
var name = uiCat?.Name.ToString();
|
string? name = uiCat.Name.ToString();
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
name = $"Category\\ {key}";
|
name = $"Category\\ {key}";
|
||||||
|
|
||||||
@@ -124,4 +217,29 @@ public static unsafe class InventoryState
|
|||||||
IconId = Services.DataManager.GetExcelSheet<Item>().GetRow(itemId).Icon
|
IconId = Services.DataManager.GetExcelSheet<Item>().GetRow(itemId).Icon
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct AggregatedItem
|
||||||
|
{
|
||||||
|
public InventoryItem First;
|
||||||
|
public int Total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ItemCountDescComparer : IComparer<ItemInfo>
|
||||||
|
{
|
||||||
|
public static readonly ItemCountDescComparer Instance = new();
|
||||||
|
|
||||||
|
public int Compare(ItemInfo x, ItemInfo y)
|
||||||
|
{
|
||||||
|
if (x.ItemCount > y.ItemCount) return -1;
|
||||||
|
if (x.ItemCount < y.ItemCount) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class CategoryBucket
|
||||||
|
{
|
||||||
|
public uint Key;
|
||||||
|
public CategoryInfo Category = null!;
|
||||||
|
public List<ItemInfo> Items = null!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,61 +1,87 @@
|
|||||||
using System;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using AetherBags.Extensions;
|
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.Numerics;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace AetherBags.Inventory;
|
namespace AetherBags.Inventory;
|
||||||
|
|
||||||
public class ItemInfo : IEquatable<ItemInfo> {
|
public sealed class ItemInfo : IEquatable<ItemInfo>
|
||||||
|
{
|
||||||
public required InventoryItem Item { get; set; }
|
public required InventoryItem Item { get; set; }
|
||||||
public required int ItemCount { get; set; }
|
public required int ItemCount { get; set; }
|
||||||
|
|
||||||
private Item ItemData => Services.DataManager.GetExcelSheet<Item>().GetRow(Item.ItemId);
|
private static ExcelSheet<Item>? s_itemSheet;
|
||||||
|
private static ExcelSheet<Item> ItemSheet => s_itemSheet ??= Services.DataManager.GetExcelSheet<Item>();
|
||||||
|
|
||||||
public Vector4 RarityColor => ItemData.RarityColor;
|
private bool _rowLoaded;
|
||||||
|
private Item _row;
|
||||||
|
|
||||||
public uint IconId => ItemData.Icon;
|
private string? _name;
|
||||||
|
private string? _description;
|
||||||
|
|
||||||
public string Name => ItemData.Name.ToString();
|
private ref readonly Item Row
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!_rowLoaded)
|
||||||
|
{
|
||||||
|
_row = ItemSheet.GetRow(Item.ItemId);
|
||||||
|
_rowLoaded = true;
|
||||||
|
}
|
||||||
|
return ref _row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int Level => ItemData.LevelEquip;
|
public Vector4 RarityColor => Row.RarityColor;
|
||||||
|
public uint IconId => Row.Icon;
|
||||||
|
|
||||||
public int ItemLevel => (int) ItemData.LevelItem.RowId;
|
public string Name => _name ??= Row.Name.ToString();
|
||||||
|
|
||||||
public int Rarity => ItemData.Rarity;
|
public int Level => Row.LevelEquip;
|
||||||
|
public int ItemLevel => (int)Row.LevelItem.RowId;
|
||||||
|
public int Rarity => Row.Rarity;
|
||||||
|
|
||||||
public RowRef<ItemUICategory> UiCategory => ItemData.ItemUICategory;
|
public RowRef<ItemUICategory> UiCategory => Row.ItemUICategory;
|
||||||
|
|
||||||
private string Description => ItemData.Description.ToString();
|
private string Description => _description ??= Row.Description.ToString();
|
||||||
|
|
||||||
public bool IsRegexMatch(string searchTerms) {
|
public bool IsRegexMatch(string searchTerms)
|
||||||
const RegexOptions regexOptions = RegexOptions.CultureInvariant | RegexOptions.IgnoreCase;
|
{
|
||||||
|
if (string.IsNullOrEmpty(searchTerms))
|
||||||
|
return true;
|
||||||
|
|
||||||
if (Regex.IsMatch(Name, searchTerms, regexOptions)) return true;
|
var re = new Regex(searchTerms, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||||
if (Regex.IsMatch(Description, searchTerms, regexOptions)) return true;
|
|
||||||
if (Regex.IsMatch(Level.ToString(), searchTerms, regexOptions)) return true;
|
if (re.IsMatch(Name)) return true;
|
||||||
if (Regex.IsMatch(ItemLevel.ToString(), searchTerms, regexOptions)) return true;
|
|
||||||
|
if (re.IsMatch(Description)) return true;
|
||||||
|
|
||||||
|
if (re.IsMatch(Level.ToString())) return true;
|
||||||
|
if (re.IsMatch(ItemLevel.ToString())) return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals(ItemInfo? other) {
|
public bool IsRegexMatch(Regex re)
|
||||||
if (other is null) return false;
|
{
|
||||||
if (ReferenceEquals(this, other)) return true;
|
if (re.IsMatch(Name)) return true;
|
||||||
return Item.ItemId.Equals(other.Item.ItemId) && ItemCount == other.ItemCount;
|
if (re.IsMatch(Description)) return true;
|
||||||
|
|
||||||
|
if (re.IsMatch(Level.ToString())) return true;
|
||||||
|
if (re.IsMatch(ItemLevel.ToString())) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Equals(object? obj) {
|
public bool Equals(ItemInfo? other)
|
||||||
if (obj is null) return false;
|
=> other is not null && Item.ItemId == other.Item.ItemId && ItemCount == other.ItemCount;
|
||||||
if (ReferenceEquals(this, obj)) return true;
|
|
||||||
if (obj.GetType() != GetType()) return false;
|
public override bool Equals(object? obj)
|
||||||
return Equals((ItemInfo) obj);
|
=> obj is ItemInfo other && Equals(other);
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
// ReSharper disable NonReadonlyMemberInGetHashCode
|
|
||||||
=> HashCode.Combine(Item.ItemId, ItemCount);
|
=> HashCode.Combine(Item.ItemId, ItemCount);
|
||||||
// ReSharper restore NonReadonlyMemberInGetHashCode
|
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,13 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
|
|||||||
|
|
||||||
private readonly IReadOnlyList<IReadOnlyList<NodeBase>> _rowsView;
|
private readonly IReadOnlyList<IReadOnlyList<NodeBase>> _rowsView;
|
||||||
|
|
||||||
|
private float _lastAvailableWidth = float.NaN;
|
||||||
|
private float _lastStartX = float.NaN;
|
||||||
|
private float _lastHSpace = float.NaN;
|
||||||
|
private float _lastVSpace = float.NaN;
|
||||||
|
private float _lastTopPadding = float.NaN;
|
||||||
|
private float _lastBottomPadding = float.NaN;
|
||||||
|
|
||||||
public WrappingGridNode()
|
public WrappingGridNode()
|
||||||
{
|
{
|
||||||
_rowsView = new RowsReadOnlyView(_rows);
|
_rowsView = new RowsReadOnlyView(_rows);
|
||||||
@@ -37,17 +44,254 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
|
|||||||
|
|
||||||
protected override void InternalRecalculateLayout()
|
protected override void InternalRecalculateLayout()
|
||||||
{
|
{
|
||||||
RecycleAllRows();
|
|
||||||
_rowIndex.Clear();
|
|
||||||
|
|
||||||
int count = NodeList.Count;
|
int count = NodeList.Count;
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
{
|
{
|
||||||
|
RecycleAllRows();
|
||||||
|
_rowIndex.Clear();
|
||||||
_requiredHeight = 0f;
|
_requiredHeight = 0f;
|
||||||
_requiredHeightDirty = false;
|
_requiredHeightDirty = false;
|
||||||
|
RememberLayoutParams();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_rows.Count != 0 && TryUpdateLayoutWithoutReflowOrTailReflow(count))
|
||||||
|
{
|
||||||
|
_requiredHeightDirty = true;
|
||||||
|
RememberLayoutParams();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FullReflow(count);
|
||||||
|
_requiredHeightDirty = true;
|
||||||
|
RememberLayoutParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryUpdateLayoutWithoutReflowOrTailReflow(int count)
|
||||||
|
{
|
||||||
|
if (!LayoutParamsMatchLast())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int mismatchRow = FindFirstMismatchRow(count, out int mismatchNodeIndex);
|
||||||
|
|
||||||
|
if (mismatchRow < 0)
|
||||||
|
{
|
||||||
|
RepositionExistingRows();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TailReflowFrom(mismatchRow, mismatchNodeIndex, count);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int FindFirstMismatchRow(int count, out int mismatchNodeIndex)
|
||||||
|
{
|
||||||
|
float availableWidth = Width;
|
||||||
|
float hSpace = HorizontalSpacing;
|
||||||
|
float startX = FirstItemSpacing;
|
||||||
|
|
||||||
|
int rowIdx = 0;
|
||||||
|
int nodeIdx = 0;
|
||||||
|
|
||||||
|
while (nodeIdx < count)
|
||||||
|
{
|
||||||
|
if (rowIdx >= _rows.Count)
|
||||||
|
{
|
||||||
|
mismatchNodeIndex = nodeIdx;
|
||||||
|
return rowIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<NodeBase> existingRow = _rows[rowIdx];
|
||||||
|
int existingRowCount = existingRow.Count;
|
||||||
|
|
||||||
|
if (existingRowCount == 0)
|
||||||
|
{
|
||||||
|
mismatchNodeIndex = nodeIdx;
|
||||||
|
return rowIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
int predictedCount = 0;
|
||||||
|
float currentX = startX;
|
||||||
|
|
||||||
|
while (nodeIdx + predictedCount < count)
|
||||||
|
{
|
||||||
|
NodeBase node = NodeList[nodeIdx + predictedCount];
|
||||||
|
float w = node.Width;
|
||||||
|
|
||||||
|
if (predictedCount != 0 && (currentX + w) > availableWidth)
|
||||||
|
break;
|
||||||
|
|
||||||
|
predictedCount++;
|
||||||
|
currentX += w + hSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (predictedCount != existingRowCount)
|
||||||
|
{
|
||||||
|
mismatchNodeIndex = nodeIdx;
|
||||||
|
return rowIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < existingRowCount; j++)
|
||||||
|
{
|
||||||
|
if (!ReferenceEquals(existingRow[j], NodeList[nodeIdx + j]))
|
||||||
|
{
|
||||||
|
mismatchNodeIndex = nodeIdx;
|
||||||
|
return rowIdx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeIdx += existingRowCount;
|
||||||
|
rowIdx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rowIdx < _rows.Count)
|
||||||
|
{
|
||||||
|
mismatchNodeIndex = nodeIdx;
|
||||||
|
return rowIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
mismatchNodeIndex = -1;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RepositionExistingRows()
|
||||||
|
{
|
||||||
|
_rowIndex.Clear();
|
||||||
|
_rowIndex.EnsureCapacity(NodeList.Count);
|
||||||
|
|
||||||
|
float hSpace = HorizontalSpacing;
|
||||||
|
float vSpace = VerticalSpacing;
|
||||||
|
float startX = FirstItemSpacing;
|
||||||
|
|
||||||
|
float y = TopPadding;
|
||||||
|
|
||||||
|
for (int rowIdx = 0; rowIdx < _rows.Count; rowIdx++)
|
||||||
|
{
|
||||||
|
List<NodeBase> row = _rows[rowIdx];
|
||||||
|
float x = startX;
|
||||||
|
float rowHeight = 0f;
|
||||||
|
|
||||||
|
for (int j = 0; j < row.Count; j++)
|
||||||
|
{
|
||||||
|
NodeBase node = row[j];
|
||||||
|
|
||||||
|
node.X = x;
|
||||||
|
node.Y = y;
|
||||||
|
|
||||||
|
AdjustNode(node);
|
||||||
|
|
||||||
|
float h = node.Height;
|
||||||
|
if (h > rowHeight) rowHeight = h;
|
||||||
|
|
||||||
|
_rowIndex[node] = rowIdx;
|
||||||
|
|
||||||
|
x += node.Width + hSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
y += rowHeight + vSpace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TailReflowFrom(int startRowIndex, int startNodeIndex, int count)
|
||||||
|
{
|
||||||
|
_rowIndex.Clear();
|
||||||
|
_rowIndex.EnsureCapacity(count);
|
||||||
|
|
||||||
|
float availableWidth = Width;
|
||||||
|
float hSpace = HorizontalSpacing;
|
||||||
|
float vSpace = VerticalSpacing;
|
||||||
|
float startX = FirstItemSpacing;
|
||||||
|
|
||||||
|
float y = TopPadding;
|
||||||
|
|
||||||
|
if ((uint)startRowIndex > (uint)_rows.Count)
|
||||||
|
startRowIndex = _rows.Count;
|
||||||
|
|
||||||
|
for (int rowIdx = 0; rowIdx < startRowIndex; rowIdx++)
|
||||||
|
{
|
||||||
|
List<NodeBase> row = _rows[rowIdx];
|
||||||
|
float x = startX;
|
||||||
|
float rowHeight = 0f;
|
||||||
|
|
||||||
|
for (int j = 0; j < row.Count; j++)
|
||||||
|
{
|
||||||
|
NodeBase node = row[j];
|
||||||
|
|
||||||
|
node.X = x;
|
||||||
|
node.Y = y;
|
||||||
|
|
||||||
|
AdjustNode(node);
|
||||||
|
|
||||||
|
float h = node.Height;
|
||||||
|
if (h > rowHeight) rowHeight = h;
|
||||||
|
|
||||||
|
_rowIndex[node] = rowIdx;
|
||||||
|
|
||||||
|
x += node.Width + hSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
y += rowHeight + vSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = _rows.Count - 1; i >= startRowIndex; i--)
|
||||||
|
{
|
||||||
|
List<NodeBase> row = _rows[i];
|
||||||
|
row.Clear();
|
||||||
|
_rowPool.Push(row);
|
||||||
|
_rows.RemoveAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
int currentRowIndex = startRowIndex;
|
||||||
|
float xCursor = startX;
|
||||||
|
float rowHeightTail = 0f;
|
||||||
|
|
||||||
|
List<NodeBase> currentRow = RentRowList(capacityHint: 8);
|
||||||
|
|
||||||
|
for (int i = startNodeIndex; i < count; i++)
|
||||||
|
{
|
||||||
|
NodeBase node = NodeList[i];
|
||||||
|
float w = node.Width;
|
||||||
|
|
||||||
|
if (currentRow.Count != 0 && (xCursor + w) > availableWidth)
|
||||||
|
{
|
||||||
|
_rows.Add(currentRow);
|
||||||
|
currentRowIndex++;
|
||||||
|
|
||||||
|
y += rowHeightTail + vSpace;
|
||||||
|
xCursor = startX;
|
||||||
|
rowHeightTail = 0f;
|
||||||
|
|
||||||
|
currentRow = RentRowList(capacityHint: 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FullReflow(int count)
|
||||||
|
{
|
||||||
|
RecycleAllRows();
|
||||||
|
_rowIndex.Clear();
|
||||||
_rowIndex.EnsureCapacity(count);
|
_rowIndex.EnsureCapacity(count);
|
||||||
|
|
||||||
float availableWidth = Width;
|
float availableWidth = Width;
|
||||||
@@ -65,7 +309,6 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
|
|||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
NodeBase node = NodeList[i];
|
NodeBase node = NodeList[i];
|
||||||
|
|
||||||
float nodeWidth = node.Width;
|
float nodeWidth = node.Width;
|
||||||
|
|
||||||
if (currentRow.Count != 0 && (currentX + nodeWidth) > availableWidth)
|
if (currentRow.Count != 0 && (currentX + nodeWidth) > availableWidth)
|
||||||
@@ -102,8 +345,6 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
|
|||||||
{
|
{
|
||||||
RecycleRow(currentRow);
|
RecycleRow(currentRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
_requiredHeightDirty = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -121,7 +362,6 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
|
|||||||
if (bottom > maxBottom) maxBottom = bottom;
|
if (bottom > maxBottom) maxBottom = bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
maxBottom += BottomPadding;
|
maxBottom += BottomPadding;
|
||||||
|
|
||||||
_requiredHeight = maxBottom;
|
_requiredHeight = maxBottom;
|
||||||
@@ -160,6 +400,27 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
|
|||||||
_rowPool.Push(row);
|
_rowPool.Push(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool LayoutParamsMatchLast()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
_lastAvailableWidth == Width &&
|
||||||
|
_lastStartX == FirstItemSpacing &&
|
||||||
|
_lastHSpace == HorizontalSpacing &&
|
||||||
|
_lastVSpace == VerticalSpacing &&
|
||||||
|
_lastTopPadding == TopPadding &&
|
||||||
|
_lastBottomPadding == BottomPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RememberLayoutParams()
|
||||||
|
{
|
||||||
|
_lastAvailableWidth = Width;
|
||||||
|
_lastStartX = FirstItemSpacing;
|
||||||
|
_lastHSpace = HorizontalSpacing;
|
||||||
|
_lastVSpace = VerticalSpacing;
|
||||||
|
_lastTopPadding = TopPadding;
|
||||||
|
_lastBottomPadding = BottomPadding;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -174,10 +435,7 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
|
|||||||
yield return _rows[i];
|
yield return _rows[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
{
|
|
||||||
return GetEnumerator();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class ReferenceEqualityComparer<TRef> : IEqualityComparer<TRef> where TRef : class
|
private sealed class ReferenceEqualityComparer<TRef> : IEqualityComparer<TRef> where TRef : class
|
||||||
|
|||||||
Reference in New Issue
Block a user