Initial commit: AetherBags + KamiToolKit for FC Gitea
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace KamiToolKit;
|
||||
|
||||
public abstract unsafe partial class NodeBase {
|
||||
|
||||
internal readonly List<NodeBase> ChildNodes = [];
|
||||
private NodeBase? parentNode;
|
||||
|
||||
internal AtkUldManager* ParentUldManager { get; set; }
|
||||
internal AtkUnitBase* ParentAddon { get; private set; }
|
||||
|
||||
[OverloadResolutionPriority(1)]
|
||||
public void AttachNode(NativeAddon? targetAddon, NodePosition targetPosition = NodePosition.AsLastChild)
|
||||
=> PerformManagedAttach(targetAddon, targetPosition);
|
||||
|
||||
public void AttachNode(AtkUnitBase* targetAddon, NodePosition targetPosition = NodePosition.AsLastChild)
|
||||
=> PerformNativeAttach(targetAddon is not null ? targetAddon->RootNode : null, targetPosition);
|
||||
|
||||
[OverloadResolutionPriority(1)]
|
||||
public void AttachNode(NodeBase? targetNode, NodePosition targetPosition = NodePosition.AsLastChild)
|
||||
=> PerformManagedAttach(targetNode, targetPosition);
|
||||
|
||||
public void AttachNode(AtkResNode* targetNode, NodePosition targetPosition = NodePosition.AsLastChild)
|
||||
=> PerformNativeAttach(targetNode, targetPosition);
|
||||
|
||||
public void AttachNode(AtkImageNode* targetNode, NodePosition targetPosition = NodePosition.AsLastChild)
|
||||
=> PerformNativeAttach((AtkResNode*)targetNode, targetPosition);
|
||||
|
||||
public void AttachNode(AtkTextNode* targetNode, NodePosition targetPosition = NodePosition.AsLastChild)
|
||||
=> PerformNativeAttach((AtkResNode*)targetNode, targetPosition);
|
||||
|
||||
public void AttachNode(AtkNineGridNode* targetNode, NodePosition targetPosition = NodePosition.AsLastChild)
|
||||
=> PerformNativeAttach((AtkResNode*)targetNode, targetPosition);
|
||||
|
||||
public void AttachNode(AtkCounterNode* targetNode, NodePosition targetPosition = NodePosition.AsLastChild)
|
||||
=> PerformNativeAttach((AtkResNode*)targetNode, targetPosition);
|
||||
|
||||
public void AttachNode(AtkCollisionNode* targetNode, NodePosition targetPosition = NodePosition.AsLastChild)
|
||||
=> PerformNativeAttach((AtkResNode*)targetNode, targetPosition);
|
||||
|
||||
public void AttachNode(AtkClippingMaskNode* targetNode, NodePosition targetPosition = NodePosition.AsLastChild)
|
||||
=> PerformNativeAttach((AtkResNode*)targetNode, targetPosition);
|
||||
|
||||
public void AttachNode(AtkComponentNode* targetNode, NodePosition targetPosition = NodePosition.AfterAllSiblings)
|
||||
=> PerformNativeAttach((AtkResNode*)targetNode, targetPosition);
|
||||
|
||||
private void PerformManagedAttach(NativeAddon? targetAddon, NodePosition targetPosition = NodePosition.AsLastChild) {
|
||||
if (MainThreadSafety.TryAssertMainThread()) return;
|
||||
if (targetAddon is null) return;
|
||||
|
||||
// Check the Addon's node list to find out what NodeId we should be, and set that before attaching
|
||||
if (NodeId > NodeIdBase) {
|
||||
NodeId = targetAddon.InternalAddon->UldManager.GetMaxNodeId() + 1;
|
||||
}
|
||||
|
||||
PerformNativeAttach(targetAddon.RootNode, targetPosition);
|
||||
|
||||
parentNode = targetAddon.RootNode;
|
||||
parentNode.ChildNodes.Add(this);
|
||||
}
|
||||
|
||||
private void PerformManagedAttach(NodeBase? targetNode, NodePosition targetPosition) {
|
||||
if (MainThreadSafety.TryAssertMainThread()) return;
|
||||
if (targetNode is null) return;
|
||||
|
||||
PerformNativeAttach(targetNode, targetPosition);
|
||||
|
||||
parentNode = targetNode;
|
||||
parentNode.ChildNodes.Add(this);
|
||||
}
|
||||
|
||||
private void PerformNativeAttach(AtkResNode* targetNode, NodePosition targetPosition) {
|
||||
if (MainThreadSafety.TryAssertMainThread()) return;
|
||||
if (targetNode is null) return;
|
||||
|
||||
if (targetNode->GetNodeType() is NodeType.Component) {
|
||||
|
||||
// If target is a ComponentNode,
|
||||
// then we don't ever wanna be a child of the ComponentNode itself,
|
||||
// we will want to be a sibling of the root node.
|
||||
// Therefore, redirect the target position to be siblings.
|
||||
targetPosition = targetPosition switch {
|
||||
NodePosition.AsLastChild => NodePosition.AfterAllSiblings,
|
||||
NodePosition.AsFirstChild => NodePosition.BeforeAllSiblings,
|
||||
_ => targetPosition,
|
||||
};
|
||||
|
||||
// If however, we are using BeforeTarget or AfterTarget,
|
||||
// then we do want to attach to the ComponentNode
|
||||
// else, attach to its root node.
|
||||
var componentNode = targetNode->GetAsAtkComponentNode();
|
||||
if (componentNode is not null) {
|
||||
targetNode = targetPosition switch {
|
||||
NodePosition.AfterTarget => targetNode,
|
||||
NodePosition.BeforeTarget => targetNode,
|
||||
NodePosition.AfterAllSiblings => componentNode->Component->UldManager.RootNode,
|
||||
NodePosition.BeforeAllSiblings => componentNode->Component->UldManager.RootNode,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(targetPosition), targetPosition, null),
|
||||
};
|
||||
|
||||
// We also need to check the components node list, to get a safely assigned nodeId
|
||||
if (NodeId > NodeIdBase) {
|
||||
NodeId = componentNode->Component->UldManager.GetMaxNodeId() + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NodeLinker.AttachNode(this, targetNode, targetPosition);
|
||||
UpdateParentAddon(targetNode);
|
||||
UpdateNative();
|
||||
}
|
||||
|
||||
internal void ReattachNode(AtkResNode* newTarget) {
|
||||
if (newTarget is null) return;
|
||||
|
||||
DetachNode();
|
||||
AttachNode(newTarget);
|
||||
}
|
||||
|
||||
public void DetachNode() {
|
||||
if (MainThreadSafety.TryAssertMainThread()) return;
|
||||
if (ResNode is null) return;
|
||||
|
||||
UnlinkFromNative();
|
||||
RemoveUldManagerObjectReferences();
|
||||
RemoveParentAddonReferences();
|
||||
RemoveParentNodeReferences();
|
||||
}
|
||||
|
||||
private void UnlinkFromNative() {
|
||||
NodeLinker.DetachNode(ResNode);
|
||||
ResNode->ParentNode = null;
|
||||
ResNode->NextSiblingNode = null;
|
||||
ResNode->PrevSiblingNode = null;
|
||||
}
|
||||
|
||||
private void RemoveUldManagerObjectReferences() {
|
||||
if (ParentUldManager is null) return;
|
||||
|
||||
ParentUldManager->RemoveNodeFromObjectList(this);
|
||||
ParentUldManager = null;
|
||||
}
|
||||
|
||||
private void RemoveParentAddonReferences() {
|
||||
if (ParentAddon is null) return;
|
||||
|
||||
ParentAddon->UldManager.UpdateDrawNodeList();
|
||||
ParentAddon->UpdateCollisionNodeList(false);
|
||||
|
||||
ParentAddon = null;
|
||||
|
||||
foreach (var child in GetAllChildren(this)) {
|
||||
child.ParentAddon = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveParentNodeReferences() {
|
||||
if (parentNode is null) return;
|
||||
|
||||
parentNode.ChildNodes.Remove(this);
|
||||
parentNode = null;
|
||||
}
|
||||
|
||||
private void UpdateNative() {
|
||||
if (ResNode is null) return;
|
||||
|
||||
MarkDirty();
|
||||
|
||||
if (ParentUldManager is null) {
|
||||
ParentUldManager = GetUldManagerForNode(ResNode);
|
||||
}
|
||||
|
||||
if (ParentUldManager is not null) {
|
||||
ParentUldManager->AddNodeToObjectList(this);
|
||||
}
|
||||
|
||||
if (ParentAddon is not null) {
|
||||
if (ParentAddon->NameString is "NamePlate") {
|
||||
Log.Warning("Warning, attaching to AddonNamePlate is not supported. Use OverlayController instead.");
|
||||
}
|
||||
|
||||
ParentAddon->UldManager.UpdateDrawNodeList();
|
||||
ParentAddon->UpdateCollisionNodeList(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateParentAddon(AtkResNode* node) {
|
||||
if (parentNode is not null && parentNode.ParentAddon is not null) {
|
||||
ParentAddon = parentNode.ParentAddon;
|
||||
}
|
||||
else if (ParentAddon is null) {
|
||||
var targetParentAddon = RaptureAtkUnitManager.Instance()->GetAddonByNode(node);
|
||||
if (targetParentAddon is not null) {
|
||||
ParentAddon = targetParentAddon;
|
||||
}
|
||||
}
|
||||
|
||||
if (ParentAddon is not null) {
|
||||
foreach (var child in GetAllChildren(this)) {
|
||||
child.ParentAddon = ParentAddon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AtkUldManager* GetUldManagerForNode(AtkResNode* node) {
|
||||
if (node is null) return null;
|
||||
|
||||
var targetNode = node;
|
||||
|
||||
if (targetNode->GetNodeType() is NodeType.Component) {
|
||||
targetNode = targetNode->ParentNode;
|
||||
}
|
||||
|
||||
// Try to get UldManager via the first parent that is a component
|
||||
while (targetNode is not null) {
|
||||
if (targetNode->GetNodeType() is NodeType.Component) {
|
||||
var componentNode = (AtkComponentNode*)targetNode;
|
||||
return &componentNode->Component->UldManager;
|
||||
}
|
||||
|
||||
targetNode = targetNode->ParentNode;
|
||||
}
|
||||
|
||||
// We failed to find a parent component, try to get a parent addon instead
|
||||
if (ParentAddon is not null) {
|
||||
return &ParentAddon->UldManager;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IEnumerable<NodeBase> GetAllChildren(NodeBase parent) {
|
||||
foreach (var child in parent.ChildNodes) {
|
||||
yield return child;
|
||||
foreach (var childNode in GetAllChildren(child)) {
|
||||
yield return childNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerable<NodeBase> GetLocalChildren(NodeBase parent) {
|
||||
if (parent is ComponentNode) yield break;
|
||||
|
||||
foreach (var child in parent.ChildNodes) {
|
||||
yield return child;
|
||||
|
||||
if (child is ComponentNode) continue;
|
||||
foreach (var childNode in GetLocalChildren(child)) {
|
||||
yield return childNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user