Merge branch 'dev/pie-lover'

This commit is contained in:
Shawrkie Williams
2026-01-11 11:58:32 -05:00
12 changed files with 236 additions and 72 deletions
+23 -6
View File
@@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using AetherBags.AddonLifecycles;
using AetherBags.Configuration; using AetherBags.Configuration;
using AetherBags.Helpers; using AetherBags.Helpers;
using AetherBags.Inventory; using AetherBags.Inventory;
@@ -43,8 +42,8 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected virtual float MaxWindowHeight => 1000; protected virtual float MaxWindowHeight => 1000;
protected const float CategorySpacing = 12; protected const float CategorySpacing = 12;
protected const float ItemSize = 40; protected const float ItemSize = 42;
protected const float ItemPadding = 4; protected const float ItemPadding = 5;
protected const float FooterHeight = 28f; protected const float FooterHeight = 28f;
protected const float FooterTopSpacing = 4f; protected const float FooterTopSpacing = 4f;
protected const float SettingsButtonOffset = 48f; protected const float SettingsButtonOffset = 48f;
@@ -172,7 +171,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
string dataFilter = config.SearchMode == SearchMode.Filter ? searchText : string.Empty; string dataFilter = config.SearchMode == SearchMode.Filter ? searchText : string.Empty;
var categories = InventoryState.GetCategories(dataFilter); var categories = InventoryState.GetCategories(dataFilter);
float maxContentWidth = MaxWindowWidth - (ContentStartPosition.X * 2); float maxContentWidth = CategoriesNode.Width > 0 ? CategoriesNode.Width : ContentSize.X;
int maxItemsPerLine = CalculateOptimalItemsPerLine(maxContentWidth); int maxItemsPerLine = CalculateOptimalItemsPerLine(maxContentWidth);
CategoriesNode.SyncWithListDataByKey<CategorizedInventory, InventoryCategoryNode, uint>( CategoriesNode.SyncWithListDataByKey<CategorizedInventory, InventoryCategoryNode, uint>(
@@ -181,10 +180,11 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
getKeyFromNode: node => node.CategorizedInventory.Key, getKeyFromNode: node => node.CategorizedInventory.Key,
updateNode: (node, data) => updateNode: (node, data) =>
{ {
node.MaxWidth = maxContentWidth;
node.SetCategoryData(data, Math.Min(data.Items.Count, maxItemsPerLine)); node.SetCategoryData(data, Math.Min(data.Items.Count, maxItemsPerLine));
node.RefreshNodeVisuals(); node.RefreshNodeVisuals();
}, },
createNodeMethod: _ => CreateCategoryNode()); createNodeMethod: _ => CreateCategoryNode(maxContentWidth));
if (HasPinning) if (HasPinning)
{ {
@@ -254,11 +254,12 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
BackgroundDropTarget.AttachNode(this); BackgroundDropTarget.AttachNode(this);
} }
protected virtual InventoryCategoryNode CreateCategoryNode() protected virtual InventoryCategoryNode CreateCategoryNode(float? maxWidth = null)
{ {
return new InventoryCategoryNode return new InventoryCategoryNode
{ {
Size = ContentSize with { Y = 120 }, Size = ContentSize with { Y = 120 },
MaxWidth = maxWidth,
OnRefreshRequested = ManualRefresh, OnRefreshRequested = ManualRefresh,
OnDragEnd = () => InventoryOrchestrator.RefreshAll(updateMaps: true), OnDragEnd = () => InventoryOrchestrator.RefreshAll(updateMaps: true),
}; };
@@ -340,6 +341,20 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
CategoriesNode.Position = contentPos; CategoriesNode.Position = contentPos;
CategoriesNode.Size = new Vector2(contentSize.X, gridH); CategoriesNode.Size = new Vector2(contentSize.X, gridH);
UpdateCategoryMaxWidths(contentSize.X);
}
private void UpdateCategoryMaxWidths(float maxWidth)
{
foreach (var node in CategoriesNode.Nodes)
{
if (node is InventoryCategoryNode categoryNode && categoryNode.MaxWidth != maxWidth)
{
categoryNode.MaxWidth = maxWidth;
categoryNode.RecalculateSize();
}
}
} }
protected virtual void AutoSizeWindow() protected virtual void AutoSizeWindow()
@@ -376,6 +391,8 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
CategoriesNode.Position = ContentStartPosition; CategoriesNode.Position = ContentStartPosition;
CategoriesNode.Size = new Vector2(contentWidth, gridBudget); CategoriesNode.Size = new Vector2(contentWidth, gridBudget);
UpdateCategoryMaxWidths(contentWidth);
CategoriesNode.RecalculateLayout(); CategoriesNode.RecalculateLayout();
float requiredGridHeight = CategoriesNode.GetRequiredHeight(); float requiredGridHeight = CategoriesNode.GetRequiredHeight();
+47
View File
@@ -0,0 +1,47 @@
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
namespace AetherBags.Helpers;
/// <summary>
/// Thread-safe cache for compiled Regex objects to avoid repeated compilation overhead.
/// </summary>
internal static class RegexCache
{
private const int MaxCacheSize = 128;
private static readonly ConcurrentDictionary<string, Regex> Cache = new();
/// <summary>
/// Gets or creates a compiled Regex for the given pattern with case-insensitive matching.
/// Returns null if the pattern is invalid.
/// </summary>
public static Regex? GetOrCreate(string pattern)
{
if (string.IsNullOrEmpty(pattern))
return null;
if (Cache.TryGetValue(pattern, out var cached))
return cached;
try
{
var regex = new Regex(pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled);
if (Cache.Count < MaxCacheSize)
{
Cache.TryAdd(pattern, regex);
}
return regex;
}
catch
{
return null;
}
}
/// <summary>
/// Clears the regex cache. Call when configuration changes significantly.
/// </summary>
public static void Clear() => Cache.Clear();
}
@@ -10,6 +10,7 @@ public sealed class CategoryBucket
public List<ItemInfo> Items = null!; public List<ItemInfo> Items = null!;
public List<ItemInfo> FilteredItems = null!; public List<ItemInfo> FilteredItems = null!;
public bool Used; public bool Used;
public bool NeedsSorting = true;
} }
public sealed class ItemCountDescComparer : IComparer<ItemInfo> public sealed class ItemCountDescComparer : IComparer<ItemInfo>
@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using AetherBags.Configuration; using AetherBags.Configuration;
using AetherBags.Inventory.Items; using AetherBags.Inventory.Items;
using KamiToolKit.Classes; using KamiToolKit.Classes;
@@ -49,6 +48,7 @@ public static class CategoryBucketManager
bucket.Used = false; bucket.Used = false;
bucket.Items.Clear(); bucket.Items.Clear();
bucket.FilteredItems.Clear(); bucket.FilteredItems.Clear();
bucket.NeedsSorting = true;
} }
} }
@@ -302,8 +302,9 @@ public static class CategoryBucketManager
CategoryInfo miscInfo; CategoryInfo miscInfo;
if (itemInfoByKey.Count > 0) if (itemInfoByKey.Count > 0)
{ {
var sample = itemInfoByKey.Values.First(); using var enumerator = itemInfoByKey.Values.GetEnumerator();
miscInfo = GetCategoryInfoCached(0u, sample); enumerator.MoveNext();
miscInfo = GetCategoryInfoCached(0u, enumerator.Current);
} }
else else
{ {
@@ -353,7 +354,12 @@ public static class CategoryBucketManager
continue; continue;
// TODO: Make configurable // TODO: Make configurable
bucket.Items.Sort(ItemCountDescComparer.Instance); // Only sort if items changed
if (bucket.NeedsSorting)
{
bucket.Items.Sort(ItemCountDescComparer.Instance);
bucket.NeedsSorting = false;
}
sortedCategoryKeys.Add(bucket.Key); sortedCategoryKeys.Add(bucket.Key);
} }
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using AetherBags.Helpers;
using AetherBags.Inventory.Items; using AetherBags.Inventory.Items;
namespace AetherBags.Inventory.Categories; namespace AetherBags.Inventory.Categories;
@@ -17,17 +18,8 @@ public static class InventoryFilter
if (string.IsNullOrEmpty(filterString)) if (string.IsNullOrEmpty(filterString))
return allCategories; return allCategories;
Regex? re = null; Regex? re = RegexCache.GetOrCreate(filterString);
bool regexValid = true; bool regexValid = re != null;
try
{
re = new Regex(filterString, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
}
catch
{
regexValid = false;
}
filteredCategories.Clear(); filteredCategories.Clear();
@@ -1,6 +1,6 @@
using System; using System;
using System.Text.RegularExpressions;
using AetherBags.Configuration; using AetherBags.Configuration;
using AetherBags.Helpers;
using AetherBags.Inventory.Items; using AetherBags.Inventory.Items;
namespace AetherBags.Inventory.Categories; namespace AetherBags.Inventory.Categories;
@@ -30,17 +30,11 @@ internal static class UserCategoryMatcher
if (string.IsNullOrWhiteSpace(pattern)) if (string.IsNullOrWhiteSpace(pattern))
continue; continue;
try var regex = RegexCache.GetOrCreate(pattern);
if (regex != null && regex.IsMatch(item.Name))
{ {
if (Regex.IsMatch(item.Name, pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)) matchesAnyIdentification = true;
{ break;
matchesAnyIdentification = true;
break;
}
}
catch
{
// Invalid regex: ignore it.
} }
} }
} }
+56 -7
View File
@@ -18,13 +18,27 @@ public static class HighlightState
private static readonly Dictionary<HighlightSource, (HashSet<uint> ids, Vector3 color)> Labels = new(); private static readonly Dictionary<HighlightSource, (HashSet<uint> ids, Vector3 color)> Labels = new();
private static readonly Dictionary<HighlightSource, Dictionary<uint, HighlightEntry>> PerItemLabels = new(); private static readonly Dictionary<HighlightSource, Dictionary<uint, HighlightEntry>> PerItemLabels = new();
// Flat cache for O(1) lookups
private static readonly Dictionary<uint, HighlightEntry> CachedEntries = new(capacity: 512);
private static bool _cacheValid;
private static int _version;
/// <summary>
/// Version counter that increments when highlight state changes.
/// Used by ItemInfo to detect when cached visual state is stale.
/// </summary>
public static int Version => _version;
public static string? SelectedAllaganToolsFilterKey { get; set; } = string.Empty; public static string? SelectedAllaganToolsFilterKey { get; set; } = string.Empty;
public static string? SelectedBisBuddyFilterKey { get; set; } = string.Empty; public static string? SelectedBisBuddyFilterKey { get; set; } = string.Empty;
public static bool IsFilterActive => Filters.Count > 0; public static bool IsFilterActive => Filters.Count > 0;
public static void SetFilter(HighlightSource source, IEnumerable<uint> ids) public static void SetFilter(HighlightSource source, IEnumerable<uint> ids)
=> Filters[source] = new HashSet<uint>(ids); {
Filters[source] = new HashSet<uint>(ids);
_version++;
}
public static bool IsInActiveFilters(uint itemId) public static bool IsInActiveFilters(uint itemId)
{ {
@@ -36,19 +50,42 @@ public static class HighlightState
public static HighlightEntry? GetHighlightEntry(uint itemId) public static HighlightEntry? GetHighlightEntry(uint itemId)
{ {
EnsureCacheValid();
return CachedEntries.TryGetValue(itemId, out var entry) ? entry : null;
}
private static void EnsureCacheValid()
{
if (_cacheValid) return;
CachedEntries.Clear();
// PerItemLabels have priority - add them first
foreach (var perItemLabel in PerItemLabels.Values) foreach (var perItemLabel in PerItemLabels.Values)
{ {
if (perItemLabel.TryGetValue(itemId, out var entry)) foreach (var (id, entry) in perItemLabel)
return entry; {
CachedEntries.TryAdd(id, entry);
}
} }
// Labels are fallback - only add if not already present
foreach (var label in Labels.Values) foreach (var label in Labels.Values)
{ {
if (label.ids.Contains(itemId)) var color = label.color;
return new HighlightEntry(itemId, label.color); foreach (var id in label.ids)
{
CachedEntries.TryAdd(id, new HighlightEntry(id, color));
}
} }
return null; _cacheValid = true;
}
private static void InvalidateCache()
{
_cacheValid = false;
_version++;
} }
public static Vector3? GetLabelColor(uint itemId) public static Vector3? GetLabelColor(uint itemId)
@@ -58,6 +95,7 @@ public static class HighlightState
{ {
PerItemLabels.Remove(source); PerItemLabels.Remove(source);
Labels[source] = (new HashSet<uint>(ids), color); Labels[source] = (new HashSet<uint>(ids), color);
InvalidateCache();
} }
public static void SetLabelWithColors(HighlightSource source, Dictionary<uint, Vector4> itemColors) public static void SetLabelWithColors(HighlightSource source, Dictionary<uint, Vector4> itemColors)
@@ -76,6 +114,7 @@ public static class HighlightState
} }
PerItemLabels[source] = entries; PerItemLabels[source] = entries;
InvalidateCache();
} }
public static void SetLabelWithColors(HighlightSource source, IEnumerable<HighlightEntry> entries) public static void SetLabelWithColors(HighlightSource source, IEnumerable<HighlightEntry> entries)
@@ -89,6 +128,7 @@ public static class HighlightState
} }
PerItemLabels[source] = dict; PerItemLabels[source] = dict;
InvalidateCache();
} }
public static void SetLabelWithColors(HighlightSource source, Dictionary<uint, Vector3> itemColors) public static void SetLabelWithColors(HighlightSource source, Dictionary<uint, Vector3> itemColors)
@@ -102,6 +142,7 @@ public static class HighlightState
} }
PerItemLabels[source] = entries; PerItemLabels[source] = entries;
InvalidateCache();
} }
public static void ClearAll() public static void ClearAll()
@@ -109,14 +150,22 @@ public static class HighlightState
Filters.Clear(); Filters.Clear();
Labels.Clear(); Labels.Clear();
PerItemLabels.Clear(); PerItemLabels.Clear();
CachedEntries.Clear();
_cacheValid = true; // Empty cache is valid
_version++;
SelectedAllaganToolsFilterKey = string.Empty; SelectedAllaganToolsFilterKey = string.Empty;
} }
public static void ClearFilter(HighlightSource source) => Filters.Remove(source); public static void ClearFilter(HighlightSource source)
{
Filters.Remove(source);
_version++;
}
public static void ClearLabel(HighlightSource source) public static void ClearLabel(HighlightSource source)
{ {
Labels.Remove(source); Labels.Remove(source);
PerItemLabels.Remove(source); PerItemLabels.Remove(source);
InvalidateCache();
} }
} }
+39 -10
View File
@@ -1,6 +1,7 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using AetherBags.Helpers;
using AetherBags.Inventory.Context; using AetherBags.Inventory.Context;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using Lumina.Excel; using Lumina.Excel;
@@ -23,6 +24,12 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
private string? _name; private string? _name;
private string? _description; private string? _description;
private string? _levelString;
private string? _itemLevelString;
private int _cachedHighlightVersion = -1;
private float _cachedVisualAlpha;
private Vector3 _cachedHighlightColor;
private ref readonly Item Row private ref readonly Item Row
{ {
@@ -44,6 +51,8 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
public int Level => Row.LevelEquip; public int Level => Row.LevelEquip;
public int ItemLevel => (int)Row.LevelItem.RowId; public int ItemLevel => (int)Row.LevelItem.RowId;
private string LevelString => _levelString ??= Level.ToString();
private string ItemLevelString => _itemLevelString ??= ItemLevel.ToString();
public int Rarity => Row.Rarity; public int Rarity => Row.Rarity;
public uint VendorPrice => Row.PriceLow; public uint VendorPrice => Row.PriceLow;
public uint StackSize => Row.StackSize; public uint StackSize => Row.StackSize;
@@ -90,19 +99,37 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
} }
} }
public float VisualAlpha => IsEligibleForContext ? 1.0f : 0.4f; public float VisualAlpha
{
get
{
EnsureVisualStateCached();
return _cachedVisualAlpha;
}
}
public Vector3 HighlightOverlayColor public Vector3 HighlightOverlayColor
{ {
get get
{ {
if (!System.Config.Categories.BisBuddyEnabled) EnsureVisualStateCached();
return Vector3.Zero; return _cachedHighlightColor;
return HighlightState.GetLabelColor(Item.ItemId) ?? Vector3.Zero;
} }
} }
private void EnsureVisualStateCached()
{
int currentVersion = HighlightState.Version;
if (_cachedHighlightVersion == currentVersion)
return;
_cachedVisualAlpha = IsEligibleForContext ? 1.0f : 0.4f;
_cachedHighlightColor = System.Config.Categories.BisBuddyEnabled
? HighlightState.GetLabelColor(Item.ItemId) ?? Vector3.Zero
: Vector3.Zero;
_cachedHighlightVersion = currentVersion;
}
private bool CheckNativeContextEligibility() private bool CheckNativeContextEligibility()
{ {
uint contextId = InventoryContextState.ActiveContextId; uint contextId = InventoryContextState.ActiveContextId;
@@ -138,14 +165,16 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
if (string.IsNullOrEmpty(searchTerms)) if (string.IsNullOrEmpty(searchTerms))
return true; return true;
var re = new Regex(searchTerms, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); var re = RegexCache.GetOrCreate(searchTerms);
if (re == null)
return false;
if (re.IsMatch(Name)) return true; if (re.IsMatch(Name)) return true;
if (re.IsMatch(Description)) return true; if (re.IsMatch(Description)) return true;
if (re.IsMatch(Level.ToString())) return true; if (re.IsMatch(LevelString)) return true;
if (re.IsMatch(ItemLevel.ToString())) return true; if (re.IsMatch(ItemLevelString)) return true;
return false; return false;
} }
@@ -155,8 +184,8 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
if (re.IsMatch(Name)) return true; if (re.IsMatch(Name)) return true;
if (re.IsMatch(Description)) return true; if (re.IsMatch(Description)) return true;
if (re.IsMatch(Level.ToString())) return true; if (re.IsMatch(LevelString)) return true;
if (re.IsMatch(ItemLevel.ToString())) return true; if (re.IsMatch(ItemLevelString)) return true;
return false; return false;
} }
@@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using AetherBags.Configuration; using AetherBags.Configuration;
using AetherBags.Currency; using AetherBags.Currency;
using AetherBags.Inventory.Categories; using AetherBags.Inventory.Categories;
@@ -109,7 +108,7 @@ public abstract class InventoryStateBase
} }
else else
{ {
UpdateAllaganHighlight(HighlightState.SelectedBisBuddyFilterKey); UpdateBisBuddyHighlight(HighlightState.SelectedBisBuddyFilterKey);
} }
} }
else else
@@ -156,10 +155,10 @@ public abstract class InventoryStateBase
return; return;
} }
var filterItems = System.IPC.AllaganTools.GetFilterItems(filterKey); var bisItems = System.IPC.BisBuddy.ItemLookup;
if (filterItems != null) if (bisItems.Count > 0)
{ {
HighlightState.SetFilter(HighlightSource.BiSBuddy, filterItems.Keys); HighlightState.SetFilter(HighlightSource.BiSBuddy, bisItems.Keys);
} }
else else
{ {
@@ -30,6 +30,7 @@ public class InventoryCategoryNode : InventoryCategoryNodeBase
private const float MinWidth = 40; private const float MinWidth = 40;
private float? _fixedWidth; private float? _fixedWidth;
private float? _maxWidth;
private int _hoverRefs; private int _hoverRefs;
private bool _headerSuppressed; private bool _headerSuppressed;
private bool _headerExpanded; private bool _headerExpanded;
@@ -40,6 +41,7 @@ public class InventoryCategoryNode : InventoryCategoryNodeBase
private int _lastItemCount; private int _lastItemCount;
private ulong _lastItemsHash; private ulong _lastItemsHash;
private int _lastItemsPerLine; private int _lastItemsPerLine;
private float? _lastMaxWidth;
public event Action<InventoryCategoryNode, bool>? HeaderHoverChanged; public event Action<InventoryCategoryNode, bool>? HeaderHoverChanged;
public Action? OnRefreshRequested { get; set; } public Action? OnRefreshRequested { get; set; }
@@ -88,6 +90,7 @@ public class InventoryCategoryNode : InventoryCategoryNodeBase
{ {
bool categoryChanged = data.Key != _lastCategoryKey; bool categoryChanged = data.Key != _lastCategoryKey;
bool itemsPerLineChanged = itemsPerLine != _lastItemsPerLine; bool itemsPerLineChanged = itemsPerLine != _lastItemsPerLine;
bool maxWidthChanged = _maxWidth != _lastMaxWidth;
ulong itemsHash = ComputeItemsHash(CollectionsMarshal.AsSpan(data.Items)); ulong itemsHash = ComputeItemsHash(CollectionsMarshal.AsSpan(data.Items));
bool itemsChanged = data.Items.Count != _lastItemCount || itemsHash != _lastItemsHash; bool itemsChanged = data.Items.Count != _lastItemCount || itemsHash != _lastItemsHash;
@@ -96,6 +99,7 @@ public class InventoryCategoryNode : InventoryCategoryNodeBase
_lastItemCount = data.Items.Count; _lastItemCount = data.Items.Count;
_lastItemsHash = itemsHash; _lastItemsHash = itemsHash;
_lastItemsPerLine = itemsPerLine; _lastItemsPerLine = itemsPerLine;
_lastMaxWidth = _maxWidth;
_categorizedInventory = data; _categorizedInventory = data;
@@ -120,7 +124,7 @@ public class InventoryCategoryNode : InventoryCategoryNodeBase
_itemGridNode.ItemsPerLine = itemsPerLine; _itemGridNode.ItemsPerLine = itemsPerLine;
} }
if (categoryChanged || itemsChanged || itemsPerLineChanged) if (categoryChanged || itemsChanged || itemsPerLineChanged || maxWidthChanged)
{ {
RecalculateSize(); RecalculateSize();
} }
@@ -160,6 +164,12 @@ public class InventoryCategoryNode : InventoryCategoryNodeBase
} }
} }
public float? MaxWidth
{
get => _maxWidth;
set => _maxWidth = value;
}
public override bool IsPinnedInConfig => CategorizedInventory.Category?.IsPinned ?? false; public override bool IsPinnedInConfig => CategorizedInventory.Category?.IsPinned ?? false;
public void BeginHeaderHover() public void BeginHeaderHover()
@@ -224,13 +234,19 @@ public class InventoryCategoryNode : InventoryCategoryNodeBase
} }
} }
private void RecalculateSize() public void RecalculateSize()
{ {
int itemCount = CategorizedInventory.Items.Count; int itemCount = CategorizedInventory.Items.Count;
float cellW = _itemGridNode.Nodes.Count > 0 ? _itemGridNode.Nodes[0].Width : FallbackItemSize;
float cellH = _itemGridNode.Nodes.Count > 0 ? _itemGridNode.Nodes[0].Height : FallbackItemSize;
float hPad = _itemGridNode.HorizontalPadding;
float vPad = _itemGridNode.VerticalPadding;
if (itemCount == 0) if (itemCount == 0)
{ {
float width = _fixedWidth ?? MinWidth; float width = _fixedWidth ?? MinWidth;
if (_maxWidth.HasValue) width = Math.Min(width, _maxWidth.Value);
Size = new Vector2(width, HeaderHeight); Size = new Vector2(width, HeaderHeight);
_baseHeaderWidth = width; _baseHeaderWidth = width;
_itemGridNode.Position = new Vector2(0, HeaderHeight); _itemGridNode.Position = new Vector2(0, HeaderHeight);
@@ -240,21 +256,36 @@ public class InventoryCategoryNode : InventoryCategoryNodeBase
} }
int itemsPerLine = Math.Max(1, _itemGridNode.ItemsPerLine); int itemsPerLine = Math.Max(1, _itemGridNode.ItemsPerLine);
float minUsableWidth = cellW;
if (_maxWidth.HasValue && _fixedWidth is null && _maxWidth.Value >= minUsableWidth)
{
int maxColumns = (int)MathF.Floor((_maxWidth.Value + hPad) / (cellW + hPad));
maxColumns = Math.Max(1, maxColumns);
float widthNeeded = maxColumns * cellW + (maxColumns - 1) * hPad;
if (widthNeeded > _maxWidth.Value && maxColumns > 1)
maxColumns--;
itemsPerLine = Math.Min(itemsPerLine, maxColumns);
}
int rows = (itemCount + itemsPerLine - 1) / itemsPerLine; int rows = (itemCount + itemsPerLine - 1) / itemsPerLine;
int actualColumns = Math.Min(itemCount, itemsPerLine); int actualColumns = Math.Min(itemCount, itemsPerLine);
float cellW = _itemGridNode.Nodes.Count > 0 ? _itemGridNode.Nodes[0].Width : FallbackItemSize;
float cellH = _itemGridNode.Nodes.Count > 0 ? _itemGridNode.Nodes[0].Height : FallbackItemSize;
float hPad = _itemGridNode.HorizontalPadding;
float vPad = _itemGridNode.VerticalPadding;
float calculatedWidth = _fixedWidth ?? Math.Max(MinWidth, actualColumns * cellW + (actualColumns - 1) * hPad); float calculatedWidth = _fixedWidth ?? Math.Max(MinWidth, actualColumns * cellW + (actualColumns - 1) * hPad);
if (_maxWidth.HasValue && _fixedWidth is null && _maxWidth.Value >= minUsableWidth)
calculatedWidth = Math.Min(calculatedWidth, _maxWidth.Value);
float height = HeaderHeight + rows * cellH + (rows - 1) * vPad; float height = HeaderHeight + rows * cellH + (rows - 1) * vPad;
Size = new Vector2(calculatedWidth, height); Size = new Vector2(calculatedWidth, height);
_itemGridNode.Position = new Vector2(0, HeaderHeight); _itemGridNode.Position = new Vector2(0, HeaderHeight);
_itemGridNode.Size = new Vector2(calculatedWidth, height - HeaderHeight); _itemGridNode.Size = new Vector2(calculatedWidth, height - HeaderHeight);
if (_itemGridNode.ItemsPerLine != itemsPerLine)
_itemGridNode.ItemsPerLine = itemsPerLine;
_baseHeaderWidth = calculatedWidth; _baseHeaderWidth = calculatedWidth;
ApplyHeaderVisualStateAndSize(); ApplyHeaderVisualStateAndSize();
@@ -78,7 +78,6 @@ public abstract class DeferrableLayoutListNode : SimpleComponentNode
} }
} }
[Obsolete]
protected virtual void AdjustNode(NodeBase node) { } protected virtual void AdjustNode(NodeBase node) { }
protected abstract void InternalRecalculateLayout(); protected abstract void InternalRecalculateLayout();
+9 -9
View File
@@ -30,9 +30,9 @@ public sealed class WrappingGridNode<T> : DeferrableLayoutListNode where T : Nod
private float _lastVSpace = float.NaN; private float _lastVSpace = float.NaN;
private float _lastTopPadding = float.NaN; private float _lastTopPadding = float.NaN;
private float _lastBottomPadding = float.NaN; private float _lastBottomPadding = float.NaN;
private bool _lastuseCompactPacking; private bool _lastUseCompactPacking;
private bool _lastpreferLargestFit; private bool _lastPreferLargestFit;
private bool _lastuseStableInsert; private bool _lastUseStableInsert;
private int _lastCompactLookahead; private int _lastCompactLookahead;
private int[] _orderScratch = Array.Empty<int>(); private int[] _orderScratch = Array.Empty<int>();
@@ -977,9 +977,9 @@ public sealed class WrappingGridNode<T> : DeferrableLayoutListNode where T : Nod
NearlyEqual(_lastVSpace, VerticalSpacing) && NearlyEqual(_lastVSpace, VerticalSpacing) &&
NearlyEqual(_lastTopPadding, TopPadding) && NearlyEqual(_lastTopPadding, TopPadding) &&
NearlyEqual(_lastBottomPadding, BottomPadding) && NearlyEqual(_lastBottomPadding, BottomPadding) &&
_lastuseCompactPacking == System.Config.General.CompactPackingEnabled && _lastUseCompactPacking == System.Config.General.CompactPackingEnabled &&
_lastpreferLargestFit == System.Config.General.CompactPreferLargestFit && _lastPreferLargestFit == System.Config.General.CompactPreferLargestFit &&
_lastuseStableInsert == System.Config.General.CompactStableInsert && _lastUseStableInsert == System.Config.General.CompactStableInsert &&
_lastCompactLookahead == System.Config.General.CompactLookahead; _lastCompactLookahead == System.Config.General.CompactLookahead;
} }
@@ -992,9 +992,9 @@ public sealed class WrappingGridNode<T> : DeferrableLayoutListNode where T : Nod
_lastTopPadding = TopPadding; _lastTopPadding = TopPadding;
_lastBottomPadding = BottomPadding; _lastBottomPadding = BottomPadding;
_lastuseCompactPacking = System.Config.General.CompactPackingEnabled; _lastUseCompactPacking = System.Config.General.CompactPackingEnabled;
_lastpreferLargestFit = System.Config.General.CompactPreferLargestFit; _lastPreferLargestFit = System.Config.General.CompactPreferLargestFit;
_lastuseStableInsert = System.Config.General.CompactStableInsert; _lastUseStableInsert = System.Config.General.CompactStableInsert;
_lastCompactLookahead = System.Config.General.CompactLookahead; _lastCompactLookahead = System.Config.General.CompactLookahead;
} }