Initial commit: AetherBags + KamiToolKit for FC Gitea
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
internal class AddonConfig {
|
||||
public Vector2 Position = Vector2.Zero;
|
||||
public float Scale = 1.0f;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
using KamiToolKit.Premade.Color;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
internal readonly struct BatchToken(ColorPickerWidget owner) : IDisposable {
|
||||
public void Dispose() => owner.EndBatchUpdate();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
public class Bounds {
|
||||
public required Vector2 TopLeft { get; set; }
|
||||
public required Vector2 BottomRight { get; set; }
|
||||
|
||||
public float Top => TopLeft.Y;
|
||||
public float Left => TopLeft.X;
|
||||
public float Bottom => BottomRight.Y;
|
||||
public float Right => BottomRight.X;
|
||||
|
||||
public float Width => BottomRight.X - TopLeft.X;
|
||||
public float Height => BottomRight.Y - TopLeft.Y;
|
||||
public Vector2 Size => new(Width, Height);
|
||||
|
||||
public float CenterX => (TopLeft.X + BottomRight.X) / 2.0f;
|
||||
public float CenterY => (TopLeft.Y + BottomRight.Y) / 2.0f;
|
||||
public Vector2 Center => new(CenterX, CenterY);
|
||||
|
||||
public override string ToString() => $"{TopLeft}, {BottomRight}";
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
public static unsafe class ColorHelper {
|
||||
public static Vector4 GetColor(uint colorId)
|
||||
=> ConvertToVector4(AtkStage.Instance()->AtkUIColorHolder->GetColor(true, colorId));
|
||||
|
||||
private static Vector4 ConvertToVector4(uint color) {
|
||||
var a = (byte)(color >> 24);
|
||||
var b = (byte)(color >> 16);
|
||||
var g = (byte)(color >> 8);
|
||||
var r = (byte)color;
|
||||
|
||||
return new Vector4(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using static FFXIVClientStructs.FFXIV.Component.GUI.AtkModuleInterface;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
public unsafe class CustomEventInterface : IDisposable {
|
||||
|
||||
private readonly AtkEventInterface* eventInterface;
|
||||
|
||||
private AtkEventInterface.Delegates.ReceiveEvent? receiveEventDelegate;
|
||||
private AtkEventInterface.Delegates.ReceiveEventWithResult? receiveEventWithResultDelegate;
|
||||
|
||||
public CustomEventInterface(AtkEventInterface.Delegates.ReceiveEvent eventHandler, AtkEventInterface.Delegates.ReceiveEventWithResult? receiveEventWithResult = null) {
|
||||
receiveEventDelegate = eventHandler;
|
||||
receiveEventWithResultDelegate = receiveEventWithResult;
|
||||
|
||||
eventInterface = NativeMemoryHelper.UiAlloc<AtkEventInterface>();
|
||||
eventInterface->VirtualTable = (AtkEventInterface.AtkEventInterfaceVirtualTable*)NativeMemoryHelper.Malloc((ulong)sizeof(void*) * 2);
|
||||
eventInterface->VirtualTable->ReceiveEvent = (delegate* unmanaged<AtkEventInterface*, AtkValue*, AtkValue*, uint, ulong, AtkValue*>)Marshal.GetFunctionPointerForDelegate(receiveEventDelegate);
|
||||
|
||||
if (receiveEventWithResultDelegate is not null) {
|
||||
eventInterface->VirtualTable->ReceiveEventWithResult = (delegate* unmanaged<AtkEventInterface*, AtkValue*, AtkValue*, uint, ulong, AtkValue*>)Marshal.GetFunctionPointerForDelegate(receiveEventWithResultDelegate);
|
||||
}
|
||||
else {
|
||||
eventInterface->VirtualTable->ReceiveEventWithResult = (delegate* unmanaged<AtkEventInterface*, AtkValue*, AtkValue*, uint, ulong, AtkValue*>)(delegate* unmanaged<void>)&NullSub;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
if (eventInterface is null) return;
|
||||
|
||||
NativeMemoryHelper.Free(eventInterface->VirtualTable, (ulong)sizeof(void*) * 2);
|
||||
NativeMemoryHelper.UiFree(eventInterface);
|
||||
|
||||
receiveEventDelegate = null;
|
||||
receiveEventWithResultDelegate = null;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly] private static void NullSub() { }
|
||||
|
||||
public static implicit operator AtkEventInterface*(CustomEventInterface listener) => listener.eventInterface;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
public unsafe class CustomEventListener : IDisposable {
|
||||
|
||||
private readonly AtkEventListener* eventListener;
|
||||
|
||||
private AtkEventListener.Delegates.ReceiveEvent? receiveEventDelegate;
|
||||
|
||||
public CustomEventListener(AtkEventListener.Delegates.ReceiveEvent eventHandler) {
|
||||
receiveEventDelegate = eventHandler;
|
||||
|
||||
eventListener = NativeMemoryHelper.UiAlloc<AtkEventListener>();
|
||||
eventListener->VirtualTable = (AtkEventListener.AtkEventListenerVirtualTable*)NativeMemoryHelper.Malloc((ulong)sizeof(void*) * 3);
|
||||
eventListener->VirtualTable->Dtor = (delegate* unmanaged<AtkEventListener*, byte, AtkEventListener*>)(delegate* unmanaged<void>)&NullSub;
|
||||
eventListener->VirtualTable->ReceiveGlobalEvent = (delegate* unmanaged<AtkEventListener*, AtkEventType, int, AtkEvent*, AtkEventData*, void>)(delegate* unmanaged<void>)&NullSub;
|
||||
eventListener->VirtualTable->ReceiveEvent = (delegate* unmanaged<AtkEventListener*, AtkEventType, int, AtkEvent*, AtkEventData*, void>)Marshal.GetFunctionPointerForDelegate(receiveEventDelegate);
|
||||
}
|
||||
|
||||
public virtual void Dispose() {
|
||||
if (eventListener is null) return;
|
||||
|
||||
NativeMemoryHelper.Free(eventListener->VirtualTable, (ulong)sizeof(void*) * 3);
|
||||
NativeMemoryHelper.UiFree(eventListener);
|
||||
|
||||
receiveEventDelegate = null;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly] private static void NullSub() { }
|
||||
|
||||
public static implicit operator AtkEventListener*(CustomEventListener listener) => listener.eventListener;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Dalamud.Interface.Textures.TextureWraps;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
internal class DalamudInterface {
|
||||
|
||||
private static DalamudInterface? instance;
|
||||
public static DalamudInterface Instance => instance ??= new DalamudInterface();
|
||||
|
||||
[PluginService] public IPluginLog Log { get; set; } = null!;
|
||||
[PluginService] public IAddonLifecycle AddonLifecycle { get; set; } = null!;
|
||||
[PluginService] public IDataManager DataManager { get; set; } = null!;
|
||||
[PluginService] public ITextureProvider TextureProvider { get; set; } = null!;
|
||||
[PluginService] public IFramework Framework { get; set; } = null!;
|
||||
[PluginService] public IAddonEventManager AddonEventManager { get; set; } = null!;
|
||||
[PluginService] public IDalamudPluginInterface PluginInterface { get; set; } = null!;
|
||||
[PluginService] public IGameGui GameGui { get; set; } = null!;
|
||||
[PluginService] public IGameInteropProvider GameInteropProvider { get; set; } = null!;
|
||||
[PluginService] public ISeStringEvaluator SeStringEvaluator { get; set; } = null!;
|
||||
|
||||
private DalamudInterface() {
|
||||
if (!KamiToolKitLibrary.IsInitialized)
|
||||
throw new Exception("KamiToolKit not initialized! You must call KamiToolKitLibrary.Initialize() before using KamiToolKit.\n" +
|
||||
"Don't forget to call KamiToolKitLibrary.Dispose() in your plugins dispose to ensure all assets are freed and to trigger bad practice warnings.");
|
||||
}
|
||||
|
||||
public string GetAssetDirectoryPath()
|
||||
=> Path.Combine(PluginInterface.AssemblyLocation.DirectoryName ?? throw new Exception("Directory from Dalamud is Invalid Somehow"), "Assets");
|
||||
|
||||
public string GetAssetPath(string assetName)
|
||||
=> Path.Combine(GetAssetDirectoryPath(), assetName);
|
||||
|
||||
public IDalamudTextureWrap? LoadAsset(string assetName)
|
||||
=> TextureProvider.GetFromFile(GetAssetPath(assetName)).GetWrapOrDefault();
|
||||
}
|
||||
|
||||
internal static class Log {
|
||||
|
||||
private static readonly bool ExcessiveLogging = false;
|
||||
|
||||
internal static void Debug(string message) {
|
||||
DalamudInterface.Instance.Log.Debug($"[KamiToolKit] {message}");
|
||||
}
|
||||
|
||||
internal static void Fatal(string message) {
|
||||
DalamudInterface.Instance.Log.Fatal($"[KamiToolKit] {message}");
|
||||
}
|
||||
|
||||
internal static void Warning(string message) {
|
||||
DalamudInterface.Instance.Log.Warning($"[KamiToolKit] {message}");
|
||||
}
|
||||
|
||||
internal static void Verbose(string message) {
|
||||
DalamudInterface.Instance.Log.Verbose($"[KamiToolKit] {message}");
|
||||
}
|
||||
|
||||
internal static void Excessive(string message) {
|
||||
if (ExcessiveLogging) {
|
||||
Verbose($"[KamiToolKit] {message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Error(string message) {
|
||||
DalamudInterface.Instance.Log.Error($"[KamiToolKit] {message}");
|
||||
}
|
||||
|
||||
internal static void Exception(Exception exception, [CallerMemberName] string? callerName = null) {
|
||||
DalamudInterface.Instance.Log.Error(exception, $"Exception in {callerName}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using Lumina.Text;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
public unsafe class DragDropPayload {
|
||||
|
||||
public DragDropType Type { get; set; } = DragDropType.Nothing;
|
||||
|
||||
public short ReferenceIndex { get; set; }
|
||||
|
||||
/// <remarks> Index (like AtkDragDropInterface.ReferenceIndex), InventoryType, etc. </remarks>
|
||||
public int Int1 { get; set; }
|
||||
|
||||
/// <remarks> ActionId, ItemId, EmoteId, InventorySlotIndex, ListIndex, MacroIndex etc. </remarks>
|
||||
public int Int2 { get; set; } = -1;
|
||||
|
||||
// unknown usage
|
||||
// public ulong Unk8 { get; set; }
|
||||
|
||||
// unknown usage
|
||||
// public AtkValue* AtkValue { get; set; }
|
||||
|
||||
public ReadOnlySeString Text { get; set; }
|
||||
|
||||
// unknown usage
|
||||
// public uint Flags { get; set; }
|
||||
|
||||
public static DragDropPayload FromDragDropInterface(AtkDragDropInterface* dragDropInterface) {
|
||||
var payloadContainer = dragDropInterface->GetPayloadContainer();
|
||||
|
||||
return new DragDropPayload {
|
||||
Type = dragDropInterface->DragDropType,
|
||||
ReferenceIndex = dragDropInterface->DragDropReferenceIndex,
|
||||
Int1 = payloadContainer->Int1,
|
||||
Int2 = payloadContainer->Int2,
|
||||
Text = new ReadOnlySeString(payloadContainer->Text),
|
||||
};
|
||||
}
|
||||
|
||||
public void ToDragDropInterface(AtkDragDropInterface* dragDropInterface, bool writeToPayloadContainer = true) {
|
||||
dragDropInterface->DragDropType = Type;
|
||||
dragDropInterface->DragDropReferenceIndex = ReferenceIndex;
|
||||
|
||||
if (writeToPayloadContainer) {
|
||||
var payloadContainer = dragDropInterface->GetPayloadContainer();
|
||||
payloadContainer->Clear();
|
||||
payloadContainer->Int1 = Int1;
|
||||
payloadContainer->Int2 = Int2;
|
||||
|
||||
if (Text.IsEmpty) {
|
||||
payloadContainer->Text.Clear();
|
||||
}
|
||||
else {
|
||||
var stringBuilder = new SeStringBuilder().Append(Text);
|
||||
payloadContainer->Text.SetString(stringBuilder.GetViewAsSpan());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
Type = DragDropType.Nothing;
|
||||
ReferenceIndex = 0;
|
||||
Int1 = 0;
|
||||
Int2 = -1;
|
||||
Text = default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Diagnostics;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
/// WARNING: These features are potentially extremely volatile, use at your own risk.
|
||||
public unsafe class Experimental {
|
||||
private static Experimental? instance;
|
||||
public static Experimental Instance => instance ??= new Experimental();
|
||||
|
||||
public void EnableHooks() { }
|
||||
|
||||
public void DisposeHooks() {
|
||||
}
|
||||
|
||||
// WARNING: May result in undefined state or accidental network requests
|
||||
// Use at your own risk.
|
||||
[Conditional("DEBUG")]
|
||||
public static void ForceOpenAddon(AgentId agentId, int delayTicks = 0) {
|
||||
if (delayTicks is not 0) {
|
||||
DalamudInterface.Instance.Framework.RunOnTick(() => {
|
||||
AgentModule.Instance()->GetAgentByInternalId(agentId)->Show();
|
||||
}, delayTicks: delayTicks);
|
||||
}
|
||||
else {
|
||||
DalamudInterface.Instance.Framework.RunOnFrameworkThread(() => {
|
||||
AgentModule.Instance()->GetAgentByInternalId(agentId)->Show();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// WARNING: May result in undefined state or accidental network requests
|
||||
// Use at your own risk.
|
||||
[Conditional("DEBUG")]
|
||||
public static void ForceCloseAddon(AgentId agentId)
|
||||
=> DalamudInterface.Instance.Framework.RunOnFrameworkThread(() => {
|
||||
AgentModule.Instance()->GetAgentByInternalId(agentId)->Hide();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
public static class FlagHelper {
|
||||
public static bool ReadFlag<T>(ref T flagsField, int flag) where T : struct, IBinaryInteger<T>
|
||||
=> (flagsField & T.One << BitOperations.Log2((uint)flag)) != T.Zero;
|
||||
|
||||
public static void SetFlag<T>(ref T flagsField, int flag) where T : struct, IBinaryInteger<T>
|
||||
=> flagsField |= T.One << BitOperations.Log2((uint)flag);
|
||||
|
||||
public static void ClearFlag<T>(ref T flagsField, int flag) where T : struct, IBinaryInteger<T>
|
||||
=> flagsField &= ~(T.One << BitOperations.Log2((uint)flag));
|
||||
|
||||
public static void UpdateFlag<T>(ref T flagsField, int flag, bool enable) where T : struct, IBinaryInteger<T> {
|
||||
if (enable) {
|
||||
SetFlag(ref flagsField, flag);
|
||||
}
|
||||
else {
|
||||
ClearFlag(ref flagsField, flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
internal static class GenericUtil {
|
||||
public static bool AreEqual<T>(T? left, T? right) {
|
||||
if (default(T) == null) return ReferenceEquals(left, right);
|
||||
|
||||
if (left == null || right == null) return left == null && right == null;
|
||||
|
||||
var leftSpan = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, byte>(ref left), Unsafe.SizeOf<T>());
|
||||
var rightSpan = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, byte>(ref right), Unsafe.SizeOf<T>());
|
||||
|
||||
return leftSpan.SequenceEqual(rightSpan);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using ListItemInfo = FFXIVClientStructs.FFXIV.Component.GUI.AtkComponentListItemPopulator.ListItemInfo;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
public unsafe class ListPopulatorData {
|
||||
public AtkUnitBase* Addon { get; init; }
|
||||
public ListItemInfo* ItemInfo { get; init; }
|
||||
public AtkResNode** NodeList { get; init; }
|
||||
public uint Index { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
internal static class NativeMemoryHelper {
|
||||
public static unsafe T* UiAlloc<T>(int elementCount, ulong alignment = 8) where T : unmanaged
|
||||
=> UiAlloc<T>((uint)elementCount, alignment);
|
||||
|
||||
public static unsafe T* UiAlloc<T>(uint elementCount = 1, ulong alignment = 8) where T : unmanaged {
|
||||
var allocSize = (ulong)sizeof(T) * elementCount;
|
||||
var memory = (T*)IMemorySpace.GetUISpace()->Malloc(allocSize, alignment);
|
||||
|
||||
IMemorySpace.Memset(memory, 0, allocSize);
|
||||
|
||||
if (memory is null) {
|
||||
throw new Exception($"Unable to allocate memory for {typeof(T)}");
|
||||
}
|
||||
|
||||
return memory;
|
||||
}
|
||||
|
||||
public static unsafe void UiFree<T>(T* memory) where T : unmanaged
|
||||
=> IMemorySpace.Free(memory);
|
||||
|
||||
public static unsafe void UiFree<T>(T* memory, uint elementCount) where T : unmanaged
|
||||
=> IMemorySpace.Free(memory, (ulong)sizeof(T) * elementCount);
|
||||
|
||||
public static unsafe T* Create<T>() where T : unmanaged, ICreatable {
|
||||
var memory = IMemorySpace.GetUISpace()->Create<T>();
|
||||
|
||||
if (memory is null) {
|
||||
throw new Exception($"Unable to allocate memory for {typeof(T)}");
|
||||
}
|
||||
|
||||
return memory;
|
||||
}
|
||||
|
||||
public static unsafe nint Malloc(ulong size, ulong alignment = 8)
|
||||
=> (nint)IMemorySpace.GetUISpace()->Malloc(size, alignment);
|
||||
|
||||
public static unsafe void Free(void* memory, ulong size)
|
||||
=> IMemorySpace.Free(memory, size);
|
||||
|
||||
public static unsafe void ResizeArray<T>(ref T* array, int oldSize, uint newSize) where T : unmanaged
|
||||
=> ResizeArray(ref array, oldSize, (int)newSize);
|
||||
|
||||
public static unsafe void ResizeArray<T>(ref T* array, uint oldSize, uint newSize) where T : unmanaged
|
||||
=> ResizeArray(ref array, (int)oldSize, (int)newSize);
|
||||
|
||||
public static unsafe void ResizeArray<T>(ref T* array, uint oldSize, int newSize) where T : unmanaged
|
||||
=> ResizeArray(ref array, (int)oldSize, newSize);
|
||||
|
||||
public static unsafe void ResizeArray<T>(ref T* array, int oldSize, int newSize) where T : unmanaged {
|
||||
var newBuffer = UiAlloc<T>((uint)newSize);
|
||||
|
||||
Copy(array, newBuffer, oldSize);
|
||||
|
||||
if (array is not null) {
|
||||
UiFree(array, (uint)oldSize);
|
||||
}
|
||||
|
||||
array = newBuffer;
|
||||
}
|
||||
|
||||
public static unsafe void Copy<T>(T* oldBuffer, T* newBuffer, int count) where T : unmanaged
|
||||
=> Copy(oldBuffer, newBuffer, (uint)count);
|
||||
|
||||
public static unsafe void Copy<T>(T* oldBuffer, T* newBuffer, uint count) where T : unmanaged
|
||||
=> NativeMemory.Copy(oldBuffer, newBuffer, (nuint)(sizeof(T) * count));
|
||||
|
||||
public static unsafe void MemCopy<T>(T* oldBuffer, T* newBuffer, uint byteCount) where T : unmanaged
|
||||
=> NativeMemory.Copy(oldBuffer, newBuffer, byteCount);
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
using System;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
public enum NodePosition {
|
||||
BeforeTarget,
|
||||
AfterTarget,
|
||||
BeforeAllSiblings,
|
||||
AfterAllSiblings,
|
||||
AsLastChild,
|
||||
AsFirstChild,
|
||||
}
|
||||
|
||||
internal static unsafe class NodeLinker {
|
||||
internal static void AttachNode(AtkResNode* node, AtkResNode* attachTargetNode, NodePosition position) {
|
||||
switch (position) {
|
||||
case NodePosition.BeforeTarget:
|
||||
EmplaceBefore(node, attachTargetNode);
|
||||
break;
|
||||
|
||||
case NodePosition.AfterTarget:
|
||||
EmplaceAfter(node, attachTargetNode);
|
||||
break;
|
||||
|
||||
case NodePosition.BeforeAllSiblings:
|
||||
EmplaceBeforeSiblings(node, attachTargetNode);
|
||||
break;
|
||||
|
||||
case NodePosition.AfterAllSiblings:
|
||||
EmplaceAfterSiblings(node, attachTargetNode);
|
||||
break;
|
||||
|
||||
case NodePosition.AsLastChild:
|
||||
EmplaceAsLastChild(node, attachTargetNode);
|
||||
break;
|
||||
|
||||
case NodePosition.AsFirstChild:
|
||||
EmplaceAsFirstChild(node, attachTargetNode);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(position), position, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmplaceBefore(AtkResNode* node, AtkResNode* attachTargetNode) {
|
||||
node->ParentNode = attachTargetNode->ParentNode;
|
||||
|
||||
// Target node is the head of the nodelist, we will be the new head.
|
||||
if (attachTargetNode->NextSiblingNode is null) {
|
||||
attachTargetNode->ParentNode->ChildNode = node;
|
||||
}
|
||||
|
||||
// We have a node that will be before us
|
||||
if (attachTargetNode->NextSiblingNode is not null) {
|
||||
attachTargetNode->NextSiblingNode->PrevSiblingNode = node;
|
||||
node->NextSiblingNode = attachTargetNode->NextSiblingNode;
|
||||
}
|
||||
|
||||
attachTargetNode->NextSiblingNode = node;
|
||||
node->PrevSiblingNode = attachTargetNode;
|
||||
|
||||
if (attachTargetNode->ParentNode->GetNodeType() is not NodeType.Component) {
|
||||
attachTargetNode->ParentNode->ChildCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmplaceAfter(AtkResNode* node, AtkResNode* attachTargetNode) {
|
||||
node->ParentNode = attachTargetNode->ParentNode;
|
||||
|
||||
// We have a node that will be after us
|
||||
if (attachTargetNode->PrevSiblingNode is not null) {
|
||||
attachTargetNode->PrevSiblingNode->NextSiblingNode = node;
|
||||
node->PrevSiblingNode = attachTargetNode->PrevSiblingNode;
|
||||
}
|
||||
|
||||
attachTargetNode->PrevSiblingNode = node;
|
||||
node->NextSiblingNode = attachTargetNode;
|
||||
|
||||
if (attachTargetNode->ParentNode->GetNodeType() is not NodeType.Component) {
|
||||
attachTargetNode->ParentNode->ChildCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmplaceBeforeSiblings(AtkResNode* node, AtkResNode* attachTargetNode) {
|
||||
var current = attachTargetNode;
|
||||
var previous = current;
|
||||
|
||||
while (current is not null) {
|
||||
previous = current;
|
||||
current = current->NextSiblingNode;
|
||||
}
|
||||
|
||||
if (previous is not null) {
|
||||
EmplaceBefore(node, previous);
|
||||
}
|
||||
|
||||
if (attachTargetNode->ParentNode->GetNodeType() is not NodeType.Component) {
|
||||
attachTargetNode->ParentNode->ChildCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmplaceAfterSiblings(AtkResNode* node, AtkResNode* attachTargetNode) {
|
||||
var current = attachTargetNode;
|
||||
var previous = current;
|
||||
|
||||
while (current is not null) {
|
||||
previous = current;
|
||||
current = current->PrevSiblingNode;
|
||||
}
|
||||
|
||||
if (previous is not null) {
|
||||
EmplaceAfter(node, previous);
|
||||
}
|
||||
|
||||
if (attachTargetNode->ParentNode->GetNodeType() is not NodeType.Component) {
|
||||
attachTargetNode->ParentNode->ChildCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmplaceAsLastChild(AtkResNode* node, AtkResNode* attachTargetNode) {
|
||||
// If the child list is empty
|
||||
if (attachTargetNode->ChildNode is null && attachTargetNode->GetNodeType() is not NodeType.Component) {
|
||||
if (attachTargetNode->GetNodeType() is not NodeType.Component) {
|
||||
attachTargetNode->ChildNode = node;
|
||||
node->ParentNode = attachTargetNode;
|
||||
attachTargetNode->ChildCount++;
|
||||
}
|
||||
else {
|
||||
node->ParentNode = attachTargetNode;
|
||||
}
|
||||
}
|
||||
// Else Add to the List
|
||||
else {
|
||||
var currentNode = attachTargetNode->ChildNode;
|
||||
while (currentNode is not null && currentNode->PrevSiblingNode != null) {
|
||||
currentNode = currentNode->PrevSiblingNode;
|
||||
}
|
||||
|
||||
node->ParentNode = attachTargetNode;
|
||||
node->NextSiblingNode = currentNode;
|
||||
|
||||
if (currentNode is not null) {
|
||||
currentNode->PrevSiblingNode = node;
|
||||
}
|
||||
|
||||
if (attachTargetNode->GetNodeType() is not NodeType.Component) {
|
||||
attachTargetNode->ChildCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmplaceAsFirstChild(AtkResNode* node, AtkResNode* attachTargetNode) {
|
||||
// If the child list is empty
|
||||
if (attachTargetNode->ChildNode is null && attachTargetNode->ChildCount is 0) {
|
||||
if (attachTargetNode->GetNodeType() is not NodeType.Component) {
|
||||
attachTargetNode->ChildNode = node;
|
||||
node->ParentNode = attachTargetNode;
|
||||
attachTargetNode->ChildCount++;
|
||||
}
|
||||
else {
|
||||
node->ParentNode = attachTargetNode;
|
||||
}
|
||||
}
|
||||
// Else Add to the List as the First Child
|
||||
else {
|
||||
if (attachTargetNode->GetNodeType() is not NodeType.Component) {
|
||||
attachTargetNode->ChildNode->NextSiblingNode = node;
|
||||
node->PrevSiblingNode = attachTargetNode->ChildNode;
|
||||
attachTargetNode->ChildNode = node;
|
||||
node->ParentNode = attachTargetNode;
|
||||
attachTargetNode->ChildCount++;
|
||||
}
|
||||
else {
|
||||
node->PrevSiblingNode = attachTargetNode->ChildNode;
|
||||
node->ParentNode = attachTargetNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DetachNode(AtkResNode* node) {
|
||||
if (node is null) return;
|
||||
if (node->ParentNode is null) return;
|
||||
|
||||
if (node->ParentNode->ChildNode == node)
|
||||
node->ParentNode->ChildNode = node->PrevSiblingNode;
|
||||
|
||||
if (node->PrevSiblingNode != null)
|
||||
node->PrevSiblingNode->NextSiblingNode = node->NextSiblingNode;
|
||||
|
||||
if (node->NextSiblingNode != null)
|
||||
node->NextSiblingNode->PrevSiblingNode = node->PrevSiblingNode;
|
||||
|
||||
if (node->ParentNode->GetNodeType() is not NodeType.Component) {
|
||||
node->ParentNode->ChildCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
public class Part {
|
||||
|
||||
public float Width { get; set; }
|
||||
|
||||
public float Height { get; set; }
|
||||
|
||||
public Vector2 Size {
|
||||
get => new(Width, Height);
|
||||
set {
|
||||
Width = value.X;
|
||||
Height = value.Y;
|
||||
}
|
||||
}
|
||||
|
||||
public float U { get; set; }
|
||||
|
||||
public float V { get; set; }
|
||||
|
||||
public Vector2 TextureCoordinates {
|
||||
get => new(U, V);
|
||||
set {
|
||||
U = value.X;
|
||||
V = value.Y;
|
||||
}
|
||||
}
|
||||
|
||||
public uint Id { get; set; }
|
||||
|
||||
public string TexturePath { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper around a AtkUldPartsList, manages adding multiple parts more easily.
|
||||
/// </summary>
|
||||
public unsafe class PartsList : IDisposable {
|
||||
|
||||
internal AtkUldPartsList* InternalPartsList;
|
||||
|
||||
private bool isDisposed;
|
||||
|
||||
public PartsList() {
|
||||
InternalPartsList = NativeMemoryHelper.UiAlloc<AtkUldPartsList>();
|
||||
|
||||
InternalPartsList->Parts = null;
|
||||
InternalPartsList->PartCount = 0;
|
||||
InternalPartsList->Id = 0;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
if (!isDisposed) {
|
||||
foreach (var partIndex in Enumerable.Range(0, (int)PartCount)) {
|
||||
ref var part = ref InternalPartsList->Parts[partIndex];
|
||||
|
||||
if (part.UldAsset is not null && part.UldAsset->AtkTexture.IsTextureReady()) {
|
||||
part.UldAsset->AtkTexture.ReleaseTexture();
|
||||
part.UldAsset->AtkTexture.KernelTexture = null;
|
||||
part.UldAsset->AtkTexture.TextureType = 0;
|
||||
}
|
||||
|
||||
NativeMemoryHelper.UiFree(part.UldAsset);
|
||||
part.UldAsset = null;
|
||||
}
|
||||
|
||||
NativeMemoryHelper.UiFree(InternalPartsList);
|
||||
InternalPartsList = null;
|
||||
}
|
||||
|
||||
isDisposed = true;
|
||||
}
|
||||
|
||||
private uint PartCount {
|
||||
get => InternalPartsList->PartCount;
|
||||
set => InternalPartsList->PartCount = value;
|
||||
}
|
||||
|
||||
public void Add(params Part[] items) {
|
||||
foreach (var part in items) {
|
||||
Add(part);
|
||||
}
|
||||
}
|
||||
|
||||
public AtkUldPart* Add(Part item) {
|
||||
NativeMemoryHelper.ResizeArray(ref InternalPartsList->Parts, PartCount, PartCount + 1);
|
||||
|
||||
ref var newPart = ref InternalPartsList->Parts[PartCount];
|
||||
|
||||
newPart.Width = (ushort) item.Width;
|
||||
newPart.Height = (ushort) item.Height;
|
||||
newPart.U = (ushort) item.U;
|
||||
newPart.V = (ushort) item.V;
|
||||
|
||||
newPart.UldAsset = NativeMemoryHelper.UiAlloc<AtkUldAsset>();
|
||||
newPart.UldAsset->Id = item.Id;
|
||||
newPart.UldAsset->AtkTexture.Ctor();
|
||||
newPart.LoadTexture(item.TexturePath);
|
||||
|
||||
return &InternalPartsList->Parts[PartCount++];
|
||||
}
|
||||
|
||||
public AtkUldPart* this[int index] {
|
||||
get {
|
||||
if (InternalPartsList is null) return null;
|
||||
if (PartCount <= index) return null;
|
||||
|
||||
return &InternalPartsList->Parts[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
public record TabbedNodeEntry<T>(T Node, int Tab) where T : NodeBase;
|
||||
@@ -0,0 +1,26 @@
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace KamiToolKit.Classes;
|
||||
|
||||
public unsafe class ViewportEventListener(AtkEventListener.Delegates.ReceiveEvent eventHandler) : CustomEventListener(eventHandler) {
|
||||
public void AddEvent(AtkEventType eventType, AtkResNode* node) {
|
||||
DalamudInterface.Instance.Framework.RunOnFrameworkThread(() => {
|
||||
Log.Verbose($"Registering ViewportEvent: {eventType}");
|
||||
AtkStage.Instance()->ViewportEventManager.RegisterEvent(eventType, 0, node, &node->AtkEventTarget, this, false);
|
||||
});
|
||||
}
|
||||
|
||||
public void RemoveEvent(AtkEventType eventType) {
|
||||
DalamudInterface.Instance.Framework.RunOnFrameworkThread(() => {
|
||||
Log.Verbose($"Unregistering ViewportEvent: {eventType}");
|
||||
AtkStage.Instance()->ViewportEventManager.UnregisterEvent(eventType, 0, this, false);
|
||||
});
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
Log.Verbose("Disposing ViewportEventListener");
|
||||
|
||||
RemoveEvent(AtkEventType.UnregisterAll);
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user