Merge branch 'dev/pie-lover'
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
@@ -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
|
||||||
|
// Only sort if items changed
|
||||||
|
if (bucket.NeedsSorting)
|
||||||
|
{
|
||||||
bucket.Items.Sort(ItemCountDescComparer.Instance);
|
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,19 +30,13 @@ 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;
|
matchesAnyIdentification = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Invalid regex: ignore it.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!matchesAnyIdentification)
|
if (!matchesAnyIdentification)
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user