Files
AetherBags/KamiToolKit/Nodes/Layout/ListNode.cs
T
KnackAtNite 8db4ce6094
Debug Build and Test / Build against Latest Dalamud (push) Has been cancelled
Debug Build and Test / Build against Staging Dalamud (push) Has been cancelled
Initial commit: AetherBags + KamiToolKit for FC Gitea
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 14:46:31 -05:00

182 lines
5.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
public unsafe class ListNode<T, TU> : SimpleComponentNode where TU : ListItemNode<T>, new() {
public readonly ScrollBarNode ScrollBarNode;
public ListNode() {
using (var displayNode = new TU()) {
itemHeight = displayNode.ItemHeight;
}
ScrollBarNode = new ScrollBarNode {
OnValueChanged = OnScrollUpdate,
ScrollSpeed = (int) itemHeight,
HideWhenDisabled = true,
};
ScrollBarNode.AttachNode(this);
AddEvent(AtkEventType.MouseWheel, OnMouseWheel);
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
ScrollBarNode.Size = new Vector2(8.0f, Height);
ScrollBarNode.Position = new Vector2(Width - 8.0f, 0.0f);
var newNodeCount = (int)(Height / (itemHeight + ItemSpacing));
if (newNodeCount != nodeCount) {
FullRebuild();
}
foreach (var node in nodeList) {
node.Width = ScrollBarNode.Bounds.Left - 8.0f;
}
RecalculateScroll();
}
public Action<T?>? OnItemSelected { get; set; }
public float ItemSpacing {
get;
set {
field = value;
FullRebuild();
}
}
public required List<T> OptionsList {
get;
set {
field = value;
var newNodeCount = (int)(Height / (itemHeight + ItemSpacing));
if (newNodeCount != nodeCount) {
FullRebuild();
}
else {
PopulateNodes();
RecalculateScroll();
}
}
} = [];
private readonly List<TU> nodeList = [];
private readonly float itemHeight;
private T? selectedItem;
private int scrollPosition;
private int nodeCount;
/// <summary>
/// Resets and rebuilds list
/// </summary>
public void FullRebuild() {
foreach (var node in nodeList) {
node.Dispose();
}
nodeList.Clear();
scrollPosition = Math.Clamp(scrollPosition, 0, Math.Max(OptionsList.Count - nodeCount, 0));
selectedItem = default;
RebuildNodeList();
PopulateNodes();
RecalculateScroll();
}
public void Update() {
PopulateNodes();
foreach (var node in nodeList) {
if (node.IsVisible) {
node.Update();
}
}
}
private void RebuildNodeList() {
nodeCount = (int)(Height / (itemHeight + ItemSpacing));
if (nodeCount < 1) return;
foreach (var index in Enumerable.Range(0, nodeCount)) {
var node = new TU {
Size = new Vector2(ScrollBarNode.Bounds.Left - 8.0f, itemHeight),
Position = new Vector2(0.0f, index * (itemHeight + ItemSpacing)),
NodeId = (uint)index + 2,
OnClick = clickedNode => {
SelectItem(((TU)clickedNode).ItemData);
OnItemSelected?.Invoke(selectedItem);
},
IsVisible = false,
};
node.AttachNode(this);
nodeList.Add(node);
}
}
private void PopulateNodes() {
foreach (var (nodeIndex, node) in nodeList.Index()) {
var dataIndex = scrollPosition + nodeIndex;
if (dataIndex < OptionsList.Count) {
var item = OptionsList[dataIndex];
node.ItemData = item;
node.IsVisible = true;
node.IsSelected = GenericUtil.AreEqual(item, selectedItem);
}
else {
node.IsVisible = false;
}
}
}
private void SelectItem(T? item) {
if (item is null) return;
selectedItem = item;
foreach (var node in nodeList) {
if (node.ItemData is null) {
node.IsSelected = false;
}
else {
node.IsSelected = GenericUtil.AreEqual(node.ItemData, selectedItem);
}
}
}
private void RecalculateScroll() {
if (OptionsList.Count < nodeCount) {
ScrollBarNode.ScrollPosition = 0;
ScrollBarNode.IsEnabled = false;
}
var totalHeight = (int)( OptionsList.Count * (itemHeight + ItemSpacing) + ItemSpacing);
ScrollBarNode.UpdateScrollParams((int) (nodeList.Count * (itemHeight + ItemSpacing)), totalHeight);
ScrollBarNode.ScrollPosition = (int)( scrollPosition * (itemHeight + ItemSpacing) );
}
private void OnScrollUpdate(int newPosition) {
scrollPosition = (int)( newPosition / ( itemHeight + ItemSpacing ) );
PopulateNodes();
}
private void OnMouseWheel(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
scrollPosition += atkEventData->IsScrollUp ? -1 : 1;
scrollPosition = Math.Clamp(scrollPosition, 0, Math.Max(0, OptionsList.Count - nodeCount));
ScrollBarNode.ScrollPosition = (int)( scrollPosition * (itemHeight + ItemSpacing) );
PopulateNodes();
atkEvent->SetEventIsHandled();
}
}