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 NodeList = []; private bool suppressRecalculateLayout; public IEnumerable GetNodes() where T : NodeBase => NodeList.OfType(); public IReadOnlyList 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 InitialNodes { init => AddNode(value); } public void AddNode(IEnumerable 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(T data) where TU : NodeBase; public delegate T GetDataFromNode(TU node) where TU : NodeBase; public bool SyncWithListData(IEnumerable dataList, GetDataFromNode getDataFromNode, CreateNewNode createNodeMethod) where TU : NodeBase { suppressRecalculateLayout = true; var anythingChanged = false; try { var nodesOfType = GetNodes().ToList(); var dataSet = dataList.ToHashSet(EqualityComparer.Default); var represented = new HashSet(EqualityComparer.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( IReadOnlyList dataList, Func getKeyFromData, Func getKeyFromNode, Action updateNode, CreateNewNode createNodeMethod, IEqualityComparer? keyComparer = null) where TU : NodeBase where TKey : notnull { suppressRecalculateLayout = true; var anythingChanged = false; try { keyComparer ??= EqualityComparer.Default; var existing = new List(capacity: NodeList.Count); foreach (var t in NodeList) { if (t is TU tu) existing.Add(tu); } var byKey = new Dictionary(existing.Count, keyComparer); List? duplicates = null; foreach (var node in existing) { var key = getKeyFromNode(node); if (!byKey.TryAdd(key, node)) (duplicates ??= new List(4)).Add(node); } var desired = new List(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 comparison) { NodeList.Sort(comparison); RecalculateLayout(); } }