Merge pull request #9 from Seeker1437/feature/wrapping-grid-node-layout-rework
Layout engine changes first pass
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>>
|
||||
|
||||
+1
-1
Submodule KamiToolKit updated: 9101fc8a8e...4edae5d1dc
Reference in New Issue
Block a user