Merge pull request #9 from Seeker1437/feature/wrapping-grid-node-layout-rework

Layout engine changes first pass
This commit is contained in:
Jeffrey Veenhuis
2025-12-23 05:25:39 +01:00
committed by GitHub
7 changed files with 362 additions and 11 deletions
@@ -7,6 +7,10 @@ public class GeneralSettings
{
public InventoryStackMode StackMode { get; set; } = InventoryStackMode.AggregateByItemId;
public bool DebugEnabled { get; set; } = false;
public bool CompactPackingEnabled { get; set; } = true;
public int CompactLookahead { get; set; } = 24;
public bool CompactPreferLargestFit { get; set; } = true;
public bool CompactStableInsert { get; set; } = true;
}
public enum InventoryStackMode : byte
@@ -0,0 +1,7 @@
using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Configuration;
internal class ConfigurationRoot : TabbedVerticalListNode
{
}
@@ -1,8 +1,9 @@
using AetherBags.Configuration;
using AetherBags.Nodes.Configuration.Layout;
using KamiToolKit.Nodes;
using System;
using System.Linq;
using System.Numerics;
using AetherBags.Configuration;
using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Configuration;
@@ -10,7 +11,8 @@ public sealed class GeneralScrollingAreaNode : ScrollingAreaNode<VerticalListNod
{
private readonly CheckboxNode _debugCheckboxNode = null!;
private readonly LabeledDropdownNode _stackDropDown = null!;
public GeneralScrollingAreaNode()
public unsafe GeneralScrollingAreaNode()
{
GeneralSettings config = System.Config.General;
@@ -31,6 +33,8 @@ public sealed class GeneralScrollingAreaNode : ScrollingAreaNode<VerticalListNod
};
ContentNode.AddNode(_stackDropDown);
ContentNode.AddNode(new LayoutConfigurationNode());
_debugCheckboxNode = new CheckboxNode
{
Size = new Vector2(300, 20),
@@ -40,7 +44,6 @@ public sealed class GeneralScrollingAreaNode : ScrollingAreaNode<VerticalListNod
OnClick = isChecked => { config.DebugEnabled = isChecked; }
};
ContentNode.AddNode(_debugCheckboxNode);
}
private void RefreshInventory() => System.AddonInventoryWindow.ManualRefresh();
@@ -0,0 +1,60 @@
using AetherBags.Configuration;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Classes.Timelines;
using KamiToolKit.Nodes;
using System.Numerics;
namespace AetherBags.Nodes.Configuration.Layout;
internal sealed class CompactLookaheadNode : SimpleComponentNode
{
public readonly LabelTextNode TitleNode;
public readonly NumericInputNode CompactLookahead = null!;
public unsafe CompactLookaheadNode()
{
GeneralSettings config = System.Config.General;
TitleNode = new LabelTextNode
{
Size = Size with { Y = 24 },
String = "Compact Lookahead",
};
TitleNode.AttachNode(this);
CompactLookahead = new NumericInputNode
{
Position = Position with { X = 240 },
Size = Size with { X = 88 },
IsVisible = true,
Value = config.CompactLookahead,
OnValueUpdate = value =>
{
config.CompactLookahead = value;
System.AddonInventoryWindow.ManualRefresh();
}
};
CompactLookahead.ComponentBase->SetEnabledState(config.CompactPackingEnabled);
CompactLookahead.AttachNode(this);
TitleNode.AddTimeline(new TimelineBuilder()
.AddFrameSetWithFrame(1, 10, 1, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(11, 20, 11, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(21, 30, 21, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(31, 40, 31, alpha: 102, multiplyColor: new Vector3(80.0f))
.AddFrameSetWithFrame(41, 50, 41, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(51, 60, 51, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(61, 70, 61, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(71, 80, 71, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(81, 90, 81, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(91, 100, 91, alpha: 102, multiplyColor: new Vector3(80.0f))
.AddFrameSetWithFrame(101, 110, 101, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(111, 115, 111, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(116, 135, 116, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(126, 135, 126, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(136, 145, 136, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(146, 155, 146, alpha: 255, multiplyColor: new Vector3(100.0f))
.Build());
}
}
@@ -0,0 +1,83 @@
using System.Numerics;
using AetherBags.Configuration;
using KamiToolKit.Nodes;
using KamiToolKit.Classes;
namespace AetherBags.Nodes.Configuration.Layout;
internal class LayoutConfigurationNode : TabbedVerticalListNode
{
private readonly CompactLookaheadNode _compactLookaheadNode = null!;
private readonly CheckboxNode _preferLargestFitCheckboxNode = null!;
private readonly CheckboxNode _useStableInsertCheckboxNode = null!;
public unsafe LayoutConfigurationNode()
{
GeneralSettings config = System.Config.General;
var titleNode = new LabelTextNode
{
Size = Size with { Y = 18 },
String = "Layout Configuration",
TextColor = ColorHelper.GetColor(2),
TextOutlineColor = ColorHelper.GetColor(0),
};
AddNode(titleNode);
AddTab(1);
var compactPackingCheckboxNode = new CheckboxNode
{
Size = Size with { Y = 18 },
IsVisible = true,
String = "Use Compact Packing",
IsChecked = config.CompactPackingEnabled,
OnClick = isChecked =>
{
config.CompactPackingEnabled = isChecked;
_preferLargestFitCheckboxNode.IsEnabled = isChecked;
_useStableInsertCheckboxNode.IsEnabled = isChecked;
_compactLookaheadNode.CompactLookahead.ComponentBase->SetEnabledState(isChecked);
System.AddonInventoryWindow.ManualRefresh();
}
};
AddNode(compactPackingCheckboxNode);
AddTab(1);
_preferLargestFitCheckboxNode = new CheckboxNode
{
Size = Size with { Y = 18 },
IsVisible = true,
String = "Prefer Largest Fit",
IsEnabled = config.CompactPackingEnabled,
IsChecked = config.CompactPreferLargestFit,
OnClick = isChecked =>
{
config.CompactPreferLargestFit = isChecked;
System.AddonInventoryWindow.ManualRefresh();
}
};
AddNode(_preferLargestFitCheckboxNode);
_useStableInsertCheckboxNode = new CheckboxNode
{
Size = Size with { Y = 18 },
IsVisible = true,
String = "Use Stable Insert",
IsEnabled = config.CompactPackingEnabled,
IsChecked = config.CompactStableInsert,
OnClick = isChecked =>
{
config.CompactStableInsert = isChecked;
System.AddonInventoryWindow.ManualRefresh();
}
};
AddNode(_useStableInsertCheckboxNode);
_compactLookaheadNode = new CompactLookaheadNode
{
Size = new Vector2(320, 20)
};
AddNode(_compactLookaheadNode);
}
}
+200 -6
View File
@@ -31,6 +31,12 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
private float _lastVSpace = float.NaN;
private float _lastTopPadding = float.NaN;
private float _lastBottomPadding = float.NaN;
private bool _lastuseCompactPacking;
private bool _lastpreferLargestFit;
private bool _lastuseStableInsert;
private int _lastCompactLookahead;
private int[] _orderScratch = Array.Empty<int>();
public WrappingGridNode()
{
@@ -55,6 +61,22 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
return;
}
if (System.Config.General.CompactPackingEnabled)
{
if (_rows.Count != 0 && LayoutParamsMatchLast() && NodeSetMatchesExistingLayout(count))
{
RepositionExistingRows();
_requiredHeightDirty = true;
RememberLayoutParams();
return;
}
FullReflowCompact(count);
_requiredHeightDirty = true;
RememberLayoutParams();
return;
}
if (_rows.Count != 0 && TryUpdateLayoutWithoutReflowOrTailReflow(count))
{
_requiredHeightDirty = true;
@@ -67,6 +89,20 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
RememberLayoutParams();
}
private bool NodeSetMatchesExistingLayout(int count)
{
if (_rowIndex.Count != count)
return false;
for (int i = 0; i < count; i++)
{
if (!_rowIndex.ContainsKey(NodeList[i]))
return false;
}
return true;
}
private bool TryUpdateLayoutWithoutReflowOrTailReflow(int count)
{
if (!LayoutParamsMatchLast())
@@ -347,6 +383,134 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
}
}
private void FullReflowCompact(int count)
{
RecycleAllRows();
_rowIndex.Clear();
_rowIndex.EnsureCapacity(count);
float availableWidth = Width;
float hSpace = HorizontalSpacing;
float vSpace = VerticalSpacing;
float startX = FirstItemSpacing;
float y = TopPadding;
EnsureOrderScratch(count);
for (int i = 0; i < count; i++)
_orderScratch[i] = i;
int lookahead = System.Config.General.CompactLookahead;
if (lookahead < 0) lookahead = 0;
int p = 0;
int rowIdx = 0;
while (p < count)
{
List<NodeBase> row = RentRowList(capacityHint: 8);
float x = startX;
float rowHeight = 0f;
while (p < count)
{
int idx = _orderScratch[p];
NodeBase node = NodeList[idx];
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 >= count) end = count - 1;
for (int s = p + 1; s <= end; s++)
{
int candIdx = _orderScratch[s];
NodeBase cand = NodeList[candIdx];
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 idx = _orderScratch[p];
NodeBase node = NodeList[idx];
float w = node.Width;
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;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float GetRequiredHeight()
{
@@ -400,15 +564,29 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
_rowPool.Push(row);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool NearlyEqual(float a, float b)
{
float diff = MathF.Abs(a - b);
if (diff <= 0.05f) return true;
float max = MathF.Max(MathF.Abs(a), MathF.Abs(b));
return diff <= max * 0.0005f;
}
private bool LayoutParamsMatchLast()
{
return
_lastAvailableWidth == Width &&
_lastStartX == FirstItemSpacing &&
_lastHSpace == HorizontalSpacing &&
_lastVSpace == VerticalSpacing &&
_lastTopPadding == TopPadding &&
_lastBottomPadding == BottomPadding;
NearlyEqual(_lastAvailableWidth, Width) &&
NearlyEqual(_lastStartX, FirstItemSpacing) &&
NearlyEqual(_lastHSpace, HorizontalSpacing) &&
NearlyEqual(_lastVSpace, VerticalSpacing) &&
NearlyEqual(_lastTopPadding, TopPadding) &&
NearlyEqual(_lastBottomPadding, BottomPadding) &&
_lastuseCompactPacking == System.Config.General.CompactPackingEnabled &&
_lastpreferLargestFit == System.Config.General.CompactPreferLargestFit &&
_lastuseStableInsert == System.Config.General.CompactStableInsert &&
_lastCompactLookahead == System.Config.General.CompactLookahead;
}
private void RememberLayoutParams()
@@ -419,6 +597,22 @@ public sealed class WrappingGridNode<T> : LayoutListNode where T : NodeBase
_lastVSpace = VerticalSpacing;
_lastTopPadding = TopPadding;
_lastBottomPadding = BottomPadding;
_lastuseCompactPacking = System.Config.General.CompactPackingEnabled;
_lastpreferLargestFit = System.Config.General.CompactPreferLargestFit;
_lastuseStableInsert = System.Config.General.CompactStableInsert;
_lastCompactLookahead = System.Config.General.CompactLookahead;
}
private void EnsureOrderScratch(int needed)
{
if (_orderScratch.Length >= needed)
return;
int newSize = _orderScratch.Length == 0 ? 64 : _orderScratch.Length;
while (newSize < needed) newSize *= 2;
_orderScratch = new int[newSize];
}
private sealed class RowsReadOnlyView : IReadOnlyList<IReadOnlyList<NodeBase>>