Initial commit: AetherBags + KamiToolKit for FC Gitea
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace KamiToolKit.Nodes;
|
||||
|
||||
public abstract class LayoutListNode : SimpleComponentNode {
|
||||
|
||||
protected readonly List<NodeBase> NodeList = [];
|
||||
private bool suppressRecalculateLayout;
|
||||
|
||||
public IEnumerable<T> GetNodes<T>() where T : NodeBase => NodeList.OfType<T>();
|
||||
|
||||
public IReadOnlyList<NodeBase> Nodes => NodeList;
|
||||
|
||||
public bool ClipListContents {
|
||||
get => NodeFlags.HasFlag(NodeFlags.Clip);
|
||||
set {
|
||||
if (value) {
|
||||
AddNodeFlags(NodeFlags.Clip);
|
||||
}
|
||||
else {
|
||||
RemoveNodeFlags(NodeFlags.Clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float ItemSpacing { get; set; }
|
||||
|
||||
public float FirstItemSpacing { get; set; }
|
||||
|
||||
public void RecalculateLayout() {
|
||||
if (suppressRecalculateLayout) return;
|
||||
|
||||
OnRecalculateLayout();
|
||||
|
||||
foreach (var node in NodeList) {
|
||||
if (node is LayoutListNode subNode) {
|
||||
subNode.RecalculateLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void OnRecalculateLayout();
|
||||
|
||||
protected virtual void AdjustNode(NodeBase node) { }
|
||||
|
||||
public ICollection<NodeBase> InitialNodes {
|
||||
init => AddNode(value);
|
||||
}
|
||||
|
||||
public void AddNode(IEnumerable<NodeBase> nodes)
|
||||
{
|
||||
suppressRecalculateLayout = true;
|
||||
try
|
||||
{
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
AddNode(node);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
suppressRecalculateLayout = false;
|
||||
}
|
||||
RecalculateLayout();
|
||||
}
|
||||
|
||||
public virtual void AddNode(NodeBase? node) {
|
||||
if (node is null) return;
|
||||
|
||||
NodeList.Add(node);
|
||||
|
||||
node.AttachNode(this);
|
||||
|
||||
RecalculateLayout();
|
||||
}
|
||||
|
||||
public void RemoveNode(params NodeBase[] items)
|
||||
{
|
||||
suppressRecalculateLayout = true;
|
||||
try
|
||||
{
|
||||
foreach (var node in items)
|
||||
{
|
||||
RemoveNode(node);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
suppressRecalculateLayout = false;
|
||||
}
|
||||
RecalculateLayout();
|
||||
}
|
||||
|
||||
public virtual void RemoveNode(NodeBase node) {
|
||||
if (!NodeList.Contains(node)) return;
|
||||
|
||||
NodeList.Remove(node);
|
||||
node.Dispose();
|
||||
|
||||
RecalculateLayout();
|
||||
}
|
||||
|
||||
public void AddDummy(float size = 0.0f) {
|
||||
var dummyNode = new ResNode {
|
||||
Size = new Vector2(size, size),
|
||||
};
|
||||
|
||||
AddNode(dummyNode);
|
||||
}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
suppressRecalculateLayout = true;
|
||||
try
|
||||
{
|
||||
foreach (var node in NodeList.ToList())
|
||||
{
|
||||
RemoveNode(node);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
suppressRecalculateLayout = false;
|
||||
}
|
||||
RecalculateLayout();
|
||||
}
|
||||
|
||||
public delegate TU CreateNewNode<in T, out TU>(T data) where TU : NodeBase;
|
||||
|
||||
public delegate T GetDataFromNode<out T, in TU>(TU node) where TU : NodeBase;
|
||||
|
||||
public bool SyncWithListData<T, TU>(IEnumerable<T> dataList, GetDataFromNode<T?, TU> getDataFromNode, CreateNewNode<T, TU> createNodeMethod) where TU : NodeBase
|
||||
{
|
||||
suppressRecalculateLayout = true;
|
||||
var anythingChanged = false;
|
||||
try
|
||||
{
|
||||
var nodesOfType = GetNodes<TU>().ToList();
|
||||
var dataSet = dataList.ToHashSet(EqualityComparer<T>.Default);
|
||||
var represented = new HashSet<T>(EqualityComparer<T>.Default);
|
||||
|
||||
foreach (var node in nodesOfType)
|
||||
{
|
||||
var nodeData = getDataFromNode(node);
|
||||
|
||||
if (nodeData is null || !dataSet.Contains(nodeData))
|
||||
{
|
||||
RemoveNode(node);
|
||||
anythingChanged = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
represented.Add(nodeData);
|
||||
}
|
||||
|
||||
foreach (var data in dataSet)
|
||||
{
|
||||
if (represented.Contains(data))
|
||||
continue;
|
||||
|
||||
var newNode = createNodeMethod(data);
|
||||
AddNode(newNode);
|
||||
anythingChanged = true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
suppressRecalculateLayout = false;
|
||||
}
|
||||
RecalculateLayout();
|
||||
|
||||
return anythingChanged;
|
||||
}
|
||||
|
||||
public bool SyncWithListDataByKey<T, TU, TKey>(
|
||||
IReadOnlyList<T> dataList,
|
||||
Func<T, TKey> getKeyFromData,
|
||||
Func<TU, TKey> getKeyFromNode,
|
||||
Action<TU, T> updateNode,
|
||||
CreateNewNode<T, TU> createNodeMethod,
|
||||
IEqualityComparer<TKey>? keyComparer = null) where TU : NodeBase where TKey : notnull
|
||||
{
|
||||
suppressRecalculateLayout = true;
|
||||
var anythingChanged = false;
|
||||
try
|
||||
{
|
||||
keyComparer ??= EqualityComparer<TKey>.Default;
|
||||
|
||||
var existing = new List<TU>(capacity: NodeList.Count);
|
||||
foreach (var t in NodeList)
|
||||
{
|
||||
if (t is TU tu)
|
||||
existing.Add(tu);
|
||||
}
|
||||
|
||||
var byKey = new Dictionary<TKey, TU>(existing.Count, keyComparer);
|
||||
List<TU>? duplicates = null;
|
||||
|
||||
foreach (var node in existing)
|
||||
{
|
||||
var key = getKeyFromNode(node);
|
||||
|
||||
if (!byKey.TryAdd(key, node))
|
||||
(duplicates ??= new List<TU>(4)).Add(node);
|
||||
}
|
||||
|
||||
var desired = new List<TU>(dataList.Count);
|
||||
|
||||
foreach (var data in dataList)
|
||||
{
|
||||
var key = getKeyFromData(data);
|
||||
|
||||
if (byKey.TryGetValue(key, out var existingNode))
|
||||
{
|
||||
updateNode(existingNode, data);
|
||||
desired.Add(existingNode);
|
||||
byKey.Remove(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
var newNode = createNodeMethod(data);
|
||||
AddNode(newNode);
|
||||
updateNode(newNode, data);
|
||||
|
||||
desired.Add(newNode);
|
||||
anythingChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (byKey.Count != 0)
|
||||
{
|
||||
foreach (var kv in byKey)
|
||||
{
|
||||
RemoveNode(kv.Value);
|
||||
anythingChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (duplicates is not null)
|
||||
{
|
||||
for (var i = 0; i < duplicates.Count; i++)
|
||||
{
|
||||
RemoveNode(duplicates[i]);
|
||||
anythingChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
var desiredCount = desired.Count;
|
||||
var j = 0;
|
||||
var mismatch = false;
|
||||
|
||||
for (var i = 0; i < NodeList.Count; i++)
|
||||
{
|
||||
if (NodeList[i] is TU)
|
||||
{
|
||||
if (j >= desiredCount)
|
||||
{
|
||||
mismatch = true;
|
||||
break;
|
||||
}
|
||||
|
||||
NodeBase desiredNode = desired[j++];
|
||||
if (!ReferenceEquals(NodeList[i], desiredNode))
|
||||
{
|
||||
NodeList[i] = desiredNode;
|
||||
anythingChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mismatch && j != desiredCount)
|
||||
mismatch = true;
|
||||
|
||||
if (mismatch)
|
||||
{
|
||||
var firstTuIndex = -1;
|
||||
|
||||
for (var i = 0; i < NodeList.Count; i++)
|
||||
{
|
||||
if (NodeList[i] is TU)
|
||||
{
|
||||
firstTuIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstTuIndex < 0)
|
||||
firstTuIndex = NodeList.Count;
|
||||
|
||||
for (var i = NodeList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (NodeList[i] is TU)
|
||||
NodeList.RemoveAt(i);
|
||||
}
|
||||
|
||||
NodeList.InsertRange(firstTuIndex, desired);
|
||||
anythingChanged = true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
suppressRecalculateLayout = false;
|
||||
}
|
||||
RecalculateLayout();
|
||||
|
||||
return anythingChanged;
|
||||
}
|
||||
|
||||
public void ReorderNodes(Comparison<NodeBase> comparison) {
|
||||
NodeList.Sort(comparison);
|
||||
RecalculateLayout();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user