Initial commit: AetherBags + KamiToolKit for FC Gitea
Debug Build and Test / Build against Latest Dalamud (push) Has been cancelled
Debug Build and Test / Build against Staging Dalamud (push) Has been cancelled

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-08 14:46:31 -05:00
commit 8db4ce6094
375 changed files with 34124 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
using KamiToolKit.Classes;
using KamiToolKit.Enums;
namespace KamiToolKit.Nodes;
public sealed class AlphaImageNode : ImGuiImageNode {
public AlphaImageNode() {
TexturePath = DalamudInterface.Instance.GetAssetPath("alpha_background.png");
WrapMode = WrapMode.Tile;
}
}
@@ -0,0 +1,47 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Enums;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public class AlternateCooldownNode : ResNode {
public readonly ImageNode CooldownImage;
public AlternateCooldownNode() {
CooldownImage = new ImageNode {
NodeId = 15,
Size = new Vector2(44.0f, 46.0f),
Position = new Vector2(0.0f, 2.0f),
Origin = new Vector2(22.0f, 23.0f),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
WrapMode = WrapMode.Tile,
PartId = 80,
};
IconNodeTextureHelper.LoadIconARecast2Texture(CooldownImage);
CooldownImage.AttachNode(this);
BuildTimeline();
}
private void BuildTimeline() {
CooldownImage.AddTimeline(new TimelineBuilder()
.BeginFrameSet(11, 92)
.AddFrame(11, alpha: 255, scale: new Vector2(1.0f), multiplyColor: new Vector3(100.0f), addColor: new Vector3(0.0f), partId: 1)
.AddFrame(92, alpha: 255, scale: new Vector2(1.0f), multiplyColor: new Vector3(100.0f), addColor: new Vector3(0.0f), partId: 79)
.EndFrameSet()
.BeginFrameSet(93, 174)
.AddFrame(93, alpha: 255, scale: new Vector2(1.0f), multiplyColor: new Vector3(100.0f), addColor: new Vector3(0.0f), partId: 82)
.AddFrame(174, alpha: 255, scale: new Vector2(1.0f), multiplyColor: new Vector3(100.0f), addColor: new Vector3(0.0f), partId: 160)
.EndFrameSet()
.BeginFrameSet(175, 205)
.AddFrame(175, alpha: 255, scale: new Vector2(1.0f), multiplyColor: new Vector3(100.0f), addColor: new Vector3(0.0f), partId: 80)
.AddFrame(191, alpha: 255, scale: new Vector2(1.2f), multiplyColor: new Vector3(100.0f), addColor: new Vector3(200.0f), partId: 80)
.AddFrame(205, alpha: 0, scale: new Vector2(1.25f), multiplyColor: new Vector3(100.0f), addColor: new Vector3(200.0f), partId: 80)
.EndFrameSet()
.Build());
}
}
+36
View File
@@ -0,0 +1,36 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Enums;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public class AntsNode : ResNode {
public readonly ImageNode AntsImageNode;
public AntsNode() {
AntsImageNode = new ImageNode {
NodeId = 13,
Size = new Vector2(48, 48),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
WrapMode = WrapMode.Tile,
PartId = 13,
};
IconNodeTextureHelper.LoadIconAFrameTexture(AntsImageNode);
AntsImageNode.AttachNode(this);
BuildTimeline();
}
private void BuildTimeline() {
AntsImageNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(2, 9)
.AddFrame(2, partId: 6)
.AddFrame(9, partId: 13)
.EndFrameSet()
.Build());
}
}
@@ -0,0 +1,26 @@
using System.Numerics;
using Dalamud.Interface;
namespace KamiToolKit.Nodes;
/// <summary>
/// A simple image node that makes it easy to display a single color.
/// </summary>
public unsafe class BackgroundImageNode : SimpleImageNode {
public BackgroundImageNode() {
FitTexture = true;
}
public new Vector4 Color {
get => new(AddColor.X, AddColor.Y, AddColor.Z, ResNode->Color.A / 255.0f);
set {
ResNode->Color = new Vector4(0.0f, 0.0f, 0.0f, value.W).ToByteColor();
AddColor = value.AsVector3Color();
}
}
public new ColorHelpers.HsvaColor ColorHsva {
get => ColorHelpers.RgbaToHsv(Color);
set => Color = ColorHelpers.HsvToRgb(value);
}
}
@@ -0,0 +1,24 @@
using System.Numerics;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
/// <summary>
/// A node that shows a border loaded from the party list textures
/// </summary>
public unsafe class BorderNineGridNode : NineGridNode {
public BorderNineGridNode() {
PartsList.Add(new Part {
TextureCoordinates = new Vector2(0.0f, 0.0f),
Size = new Vector2(64.0f, 64.0f),
Id = 0,
TexturePath = "ui/uld/PartyListTargetBase.tex",
});
TopOffset = 20;
LeftOffset = 20;
RightOffset = 20;
BottomOffset = 20;
PartsRenderType = 108;
}
}
@@ -0,0 +1,23 @@
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
// Simple helper class for making basic text label, node will auto-resize to fit label
public sealed class CategoryTextNode : TextNode {
public CategoryTextNode() {
Height = 16.0f;
TextFlags = TextFlags.AutoAdjustNodeSize;
TextColor = ColorHelper.GetColor(2);
TextOutlineColor = ColorHelper.GetColor(7);
FontType = FontType.Axis;
FontSize = 14;
LineSpacing = 24;
AlignmentType = AlignmentType.Left;
}
public override float Height {
get => base.Height;
set => base.Height = value + 8.0f; // Add extra height for padding
}
}
+230
View File
@@ -0,0 +1,230 @@
using System;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Enums;
using KamiToolKit.Timelines;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
public unsafe class CheckboxNode : ComponentNode<AtkComponentCheckBox, AtkUldComponentDataCheckBox> {
public readonly ImageNode BoxBackground;
public readonly ImageNode BoxForeground;
public readonly TextNode Label;
public CheckboxNode() {
SetInternalComponentType(ComponentType.CheckBox);
BoxBackground = new SimpleImageNode {
TexturePath = "ui/uld/CheckBoxA.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
TextureSize = new Vector2(16.0f, 16.0f),
Size = new Vector2(16.0f, 16.0f),
Position = new Vector2(0.0f, 2.0f),
WrapMode = WrapMode.Stretch,
};
BoxBackground.AttachNode(this);
BoxForeground = new SimpleImageNode {
TexturePath = "ui/uld/CheckBoxA.tex",
TextureCoordinates = new Vector2(16.0f, 0.0f),
TextureSize = new Vector2(16.0f, 16.0f),
Size = new Vector2(16.0f, 16.0f),
Position = new Vector2(0.0f, 2.0f),
WrapMode = WrapMode.Stretch,
};
BoxForeground.AttachNode(this);
Label = new TextNode {
Size = new Vector2(0.0f, 20.0f),
Position = new Vector2(20.0f, 0.0f),
FontType = FontType.Axis,
AlignmentType = AlignmentType.Left,
FontSize = 14,
LineSpacing = 14,
TextColor = ColorHelper.GetColor(8),
TextOutlineColor = ColorHelper.GetColor(7),
TextFlags = TextFlags.AutoAdjustNodeSize,
};
Label.AttachNode(this);
Component->Flags = 606464;
Data->Nodes[0] = Label.NodeId;
Data->Nodes[1] = BoxBackground.NodeId;
Data->Nodes[2] = 0;
LoadTimelines();
AddEvent(AtkEventType.ButtonClick, ClickHandler);
AddEvent(AtkEventType.InputReceived, ClickHandler);
InitializeComponentEvents();
Component->Left = 20;
Component->Right = 20;
Component->Top = 0;
Component->Bottom = 0;
BoxForeground.IsVisible = Component->IsChecked;
BoxForeground.DrawFlags = 0;
}
public Action<bool>? OnClick { get; set; }
public ReadOnlySeString String {
get => Label.String;
set {
Label.String = value;
Width = Height + Label.Width + 4.0f;
}
}
public bool IsChecked {
get => Component->IsChecked;
set => Component->SetChecked(value);
}
private void ClickHandler() {
OnClick?.Invoke(Component->IsChecked);
}
public bool DisableAutoResize {
get => Label.TextFlags.HasFlag(TextFlags.AutoAdjustNodeSize);
set {
if (value) {
Label.TextFlags &= ~TextFlags.AutoAdjustNodeSize;
Label.TextFlags |= TextFlags.Ellipsis;
}
else {
Label.TextFlags |= TextFlags.AutoAdjustNodeSize;
Label.TextFlags &= ~TextFlags.Ellipsis;
}
}
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
BoxBackground.Size = new Vector2(Height, Height) - new Vector2(4.0f, 4.0f);
BoxForeground.Size = new Vector2(Height, Height) - new Vector2(4.0f, 4.0f);
Label.Height = Height;
Label.X = Height;
if (DisableAutoResize) {
Label.Width = Width - Height;
}
}
private void LoadTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 155)
.AddLabelPair(1, 10, 1)
.AddLabelPair(11, 20, 2)
.AddLabelPair(21, 30, 3)
.AddLabelPair(31, 40, 7)
.AddLabelPair(41, 50, 6)
.AddLabelPair(51, 60, 4)
.AddLabelPair(61, 70, 8)
.AddLabelPair(71, 80, 9)
.AddLabelPair(81, 90, 10)
.AddLabelPair(91, 100, 14)
.AddLabelPair(101, 110, 13)
.AddLabelPair(111, 115, 11)
.AddLabelPair(116, 125, 12)
.AddLabelPair(126, 135, 5)
.AddLabelPair(136, 145, 15)
.AddLabelPair(146, 155, 16)
.EndFrameSet()
.Build());
CollisionNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 155)
.AddEmptyFrame(1)
.EndFrameSet()
.Build());
BoxBackground.AddTimeline(new TimelineBuilder()
.AddFrameSetWithFrame(1, 10, 1, new Vector2(0.0f, 2.0f), 255, multiplyColor: new Vector3(100.0f))
.BeginFrameSet(11, 20)
.AddFrame(11, new Vector2(0.0f, 2.0f), 255, multiplyColor: new Vector3(100.0f))
.AddFrame(13, new Vector2(0.0f, 2.0f), 255, new Vector3(16.0f), new Vector3(100.0f))
.EndFrameSet()
.AddFrameSetWithFrame(21, 30, 21, new Vector2(0.0f, 2.0f), 255, new Vector3(16.0f), new Vector3(100.0f))
.AddFrameSetWithFrame(31, 40, 31, new Vector2(0.0f, 2.0f), 102, multiplyColor: new Vector3(80.0f))
.AddFrameSetWithFrame(41, 50, 41, new Vector2(0.0f, 2.0f), 255, new Vector3(16.0f), new Vector3(100.0f))
.BeginFrameSet(51, 60)
.AddFrame(51, new Vector2(0.0f, 2.0f), 255, new Vector3(16.0f), new Vector3(100.0f))
.AddFrame(60, new Vector2(0.0f, 2.0f), 255, multiplyColor: new Vector3(100.0f))
.EndFrameSet()
.AddFrameSetWithFrame(61, 70, 61, new Vector2(0.0f, 2.0f), 255, multiplyColor: new Vector3(100.0f))
.BeginFrameSet(71, 80)
.AddFrame(71, new Vector2(0.0f, 2.0f), 255, multiplyColor: new Vector3(100.0f))
.AddFrame(73, new Vector2(0.0f, 2.0f), 255, new Vector3(16.0f), new Vector3(100.0f))
.EndFrameSet()
.AddFrameSetWithFrame(81, 90, 81, new Vector2(0.0f, 2.0f), 255, new Vector3(16.0f), new Vector3(100.0f))
.AddFrameSetWithFrame(91, 100, 91, new Vector2(0.0f, 2.0f), 102, multiplyColor: new Vector3(80.0f))
.AddFrameSetWithFrame(101, 110, 101, new Vector2(0.0f, 2.0f), 255, new Vector3(16.0f), new Vector3(100.0f))
.BeginFrameSet(111, 115)
.AddFrame(111, new Vector2(0.0f, 2.0f), 255, new Vector3(16.0f), new Vector3(100.0f))
.AddFrame(115, new Vector2(0.0f, 2.0f), 255, multiplyColor: new Vector3(100.0f))
.EndFrameSet()
.AddFrameSetWithFrame(116, 125, 116, new Vector2(0.0f, 2.0f), addColor: new Vector3(16.0f), multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(126, 135, 126, new Vector2(0.0f, 2.0f), 255, new Vector3(16.0f), new Vector3(100.0f))
.AddFrameSetWithFrame(136, 145, 126, new Vector2(0.0f, 2.0f), 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(146, 155, 146, new Vector2(0.0f, 2.0f), 255, multiplyColor: new Vector3(100.0f))
.Build());
BoxForeground.AddTimeline(new TimelineBuilder()
.AddFrameSetWithFrame(61, 70, 61, alpha: 255, multiplyColor: new Vector3(100.0f))
.BeginFrameSet(71, 80)
.AddFrame(71, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrame(73, alpha: 255, multiplyColor: new Vector3(100.0f), addColor: new Vector3(16.0f))
.EndFrameSet()
.AddFrameSetWithFrame(81, 90, 81, alpha: 255, addColor: new Vector3(16.0f), multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(91, 100, 91, alpha: 102, multiplyColor: new Vector3(80.0f))
.AddFrameSetWithFrame(101, 110, 101, alpha: 255, addColor: new Vector3(16.0f), multiplyColor: new Vector3(100.0f))
.BeginFrameSet(111, 115)
.AddFrame(111, alpha: 255, addColor: new Vector3(16.0f), multiplyColor: new Vector3(100.0f))
.AddFrame(115, alpha: 255, multiplyColor: new Vector3(100.0f))
.EndFrameSet()
.BeginFrameSet(116, 125)
.AddFrame(116, alpha: 0, addColor: new Vector3(16.0f), multiplyColor: new Vector3(100.0f))
.AddFrame(119, alpha: 255, addColor: new Vector3(16.0f), multiplyColor: new Vector3(100.0f))
.EndFrameSet()
.BeginFrameSet(126, 135)
.AddFrame(126, alpha: 255, addColor: new Vector3(16.0f), multiplyColor: new Vector3(100.0f))
.AddFrame(129, alpha: 0, addColor: new Vector3(16.0f), multiplyColor: new Vector3(100.0f))
.EndFrameSet()
.BeginFrameSet(136, 145)
.AddFrame(136, alpha: 0, multiplyColor: new Vector3(100.0f))
.AddFrame(140, alpha: 255, multiplyColor: new Vector3(100.0f))
.EndFrameSet()
.BeginFrameSet(146, 255)
.AddFrame(146, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrame(150, alpha: 0, multiplyColor: new Vector3(100.0f))
.EndFrameSet()
.Build());
Label.AddTimeline(new TimelineBuilder()
.AddFrameSetWithFrame(1, 10, 1, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(11, 20, 11, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(21, 30, 21, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(31, 40, 31, alpha: 102, multiplyColor: new Vector3(80.0f))
.AddFrameSetWithFrame(41, 50, 41, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(51, 60, 51, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(61, 70, 61, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(71, 80, 71, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(81, 90, 81, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(91, 100, 91, alpha: 102, multiplyColor: new Vector3(80.0f))
.AddFrameSetWithFrame(101, 110, 101, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(111, 115, 111, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(116, 135, 116, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(126, 135, 126, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(136, 145, 136, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(146, 155, 146, alpha: 255, multiplyColor: new Vector3(100.0f))
.Build());
}
}
@@ -0,0 +1,36 @@
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
public unsafe class ClippingMaskNode : NodeBase<AtkClippingMaskNode> {
public readonly PartsList PartsList;
public ClippingMaskNode() : base(NodeType.ClippingMask) {
PartsList = new PartsList();
Node->PartsList = PartsList.InternalPartsList;
}
protected override void Dispose(bool disposing, bool isNativeDestructor) {
if (disposing) {
if (!isNativeDestructor) {
PartsList.Dispose();
Node->PartsList = null;
}
base.Dispose(disposing, isNativeDestructor);
}
}
public ushort PartId {
get => Node->PartId;
set => Node->PartId = value;
}
public AtkUldPart* AddPart(Part part)
=> PartsList.Add(part);
public void AddPart(params Part[] parts)
=> PartsList.Add(parts);
}
+20
View File
@@ -0,0 +1,20 @@
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Nodes;
public unsafe class CollisionNode() : NodeBase<AtkCollisionNode>(NodeType.Collision) {
public virtual CollisionType CollisionType {
get => Node->CollisionType;
set => Node->CollisionType = value;
}
public virtual uint Uses {
get => Node->Uses;
set => Node->Uses = (ushort)value;
}
public virtual AtkComponentBase* LinkedComponent {
get => Node->LinkedComponent;
set => Node->LinkedComponent = value;
}
}
+63
View File
@@ -0,0 +1,63 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Enums;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public class CooldownNode : ResNode {
public readonly ImageNode CooldownImage;
public readonly ImageNode GlossyImageFrame;
public CooldownNode() {
GlossyImageFrame = new ImageNode {
NodeId = 18,
Size = new Vector2(48.0f, 48.0f),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
WrapMode = WrapMode.Tile,
};
IconNodeTextureHelper.LoadIconAFrameTexture(GlossyImageFrame);
GlossyImageFrame.AttachNode(this);
CooldownImage = new ImageNode {
NodeId = 17,
Size = new Vector2(44.0f, 46.0f),
Position = new Vector2(2.0f, 2.0f),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
WrapMode = WrapMode.Tile,
PartId = 80,
};
IconNodeTextureHelper.LoadIconARecastTexture(CooldownImage);
CooldownImage.AttachNode(this);
BuildTimelines();
}
private void BuildTimelines() {
GlossyImageFrame.AddTimeline(new TimelineBuilder()
.AddFrameSetWithFrame(1, 10, 1, partId: 0)
.AddFrameSetWithFrame(11, 20, 11, partId: 1)
.AddFrameSetWithFrame(21, 30, 21, partId: 2)
.AddFrameSetWithFrame(31, 40, 31, partId: 3)
.AddFrameSetWithFrame(41, 50, 41, partId: 18)
.AddFrameSetWithFrame(51, 60, 51, partId: 19)
.AddFrameSetWithFrame(143, 165, 143, partId: 0)
.Build());
CooldownImage.AddTimeline(new TimelineBuilder()
.BeginFrameSet(61, 142)
.AddFrame(61, alpha: 255, partId: 1)
.AddFrame(142, alpha: 255, partId: 79)
.EndFrameSet()
.BeginFrameSet(143, 165)
.AddFrame(143, alpha: 255, partId: 80)
.AddFrame(165, alpha: 0, partId: 79)
.EndFrameSet()
.Build());
}
}
+155
View File
@@ -0,0 +1,155 @@
using System.Numerics;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Enums;
using Lumina.Text.Payloads;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
/// <summary>
/// A counter node for displaying numbers
/// </summary>
public unsafe class CounterNode : NodeBase<AtkCounterNode> {
public readonly PartsList PartsList;
public CounterNode() : base(NodeType.Counter) {
PartsList = new PartsList();
PartsList.Add(new Part());
Node->PartsList = PartsList.InternalPartsList;
NumberWidth = 10;
CommaWidth = 8;
SpaceWidth = 6;
TextAlignment = AlignmentType.Right;
CounterWidth = 32;
Font = CounterFont.MoneyFont;
}
protected override void Dispose(bool disposing, bool isNativeDestructor) {
if (disposing) {
if (!isNativeDestructor) {
PartsList.Dispose();
Node->PartsList = null;
}
base.Dispose(disposing, isNativeDestructor);
}
}
protected string TexturePath {
get => PartsList[0]->LoadedPath;
set => PartsList[0]->LoadTexture(value);
}
protected Vector2 TextureCoordinates {
get => new(PartsList[0]->U, PartsList[0]->V);
set {
PartsList[0]->U = (ushort) value.X;
PartsList[0]->V = (ushort) value.X;
}
}
protected Vector2 TextureSize {
get => new(PartsList[0]->Width, PartsList[0]->Height);
set {
PartsList[0]->Width = (ushort) value.X;
PartsList[0]->Height = (ushort) value.X;
}
}
public uint NumberWidth {
get => Node->NumberWidth;
set => Node->NumberWidth = (byte)value;
}
public uint CommaWidth {
get => Node->CommaWidth;
set => Node->CommaWidth = (byte)value;
}
public uint SpaceWidth {
get => Node->SpaceWidth;
set => Node->SpaceWidth = (byte)value;
}
public AlignmentType TextAlignment {
get => (AlignmentType) Node->TextAlign;
set => Node->TextAlign = (ushort) value;
}
public float CounterWidth {
get => Node->CounterWidth;
set => Node->CounterWidth = value;
}
public int Number {
get => int.Parse(Node->NodeText.ToString());
set => Node->SetText(ParseNumber(value));
}
public ReadOnlySeString String {
get => Node->NodeText.AsSpan();
set => Node->SetText(ParseString(value));
}
public CounterFont Font {
get;
set {
field = value;
var fontPath = string.Empty;
var partSize = Vector2.Zero;
switch (value) {
case CounterFont.MoneyFont:
fontPath = "ui/uld/Money_Number.tex";
partSize = new Vector2(22.0f, 22.0f);
break;
case CounterFont.ChocoboRace:
fontPath = "ui/uld/RaceChocoboNum.tex";
partSize = new Vector2(30.0f, 60.0f);
break;
}
if (fontPath != string.Empty && partSize != Vector2.Zero) {
PartsList[0]->Width = (ushort)partSize.X;
PartsList[0]->Height = (ushort)partSize.Y;
PartsList[0]->LoadTexture(fontPath);
}
}
}
private static ReadOnlySeString ParseString(ReadOnlySeString value) {
using var builder = new RentedSeStringBuilder();
return builder.Builder.Append(value).GetViewAsSpan();
}
private static ReadOnlySeString ParseNumber(int value) {
using var rentedBuilder = new RentedSeStringBuilder();
// <kilo(lnum1,\,)>
var evaluatedString = DalamudInterface.Instance.SeStringEvaluator.EvaluateFromAddon(18, [ value ]);
foreach (var payload in evaluatedString) {
switch (payload.Type) {
// Fix for French thousands separators.
// The game calls FormatAddonText2 that does this.
case ReadOnlySePayloadType.Macro when payload.MacroCode is MacroCode.NonBreakingSpace:
rentedBuilder.Builder.Append(' ');
break;
default:
rentedBuilder.Builder.Append(payload);
break;
}
}
return rentedBuilder.Builder.GetViewAsSpan();
}
}
+30
View File
@@ -0,0 +1,30 @@
using System.Numerics;
using KamiToolKit.Enums;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public class CursorNode : ResNode {
public readonly SimpleImageNode CursorImageNode;
public CursorNode() {
CursorImageNode = new SimpleImageNode {
NodeId = 3,
TexturePath = "ui/uld/TextInputA.tex",
Size = new Vector2(4.0f, 24.0f),
TextureCoordinates = new Vector2(68.0f, 0.0f),
TextureSize = new Vector2(4.0f, 24.0f),
WrapMode = WrapMode.Tile,
};
CursorImageNode.AttachNode(this);
CursorImageNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 8)
.AddEmptyFrame(1)
.EndFrameSet()
.Build());
Timeline?.PlayAnimation(101);
}
}
+271
View File
@@ -0,0 +1,271 @@
using System;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Client.Enums;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Enums;
using KamiToolKit.Timelines;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
public unsafe class DragDropNode : ComponentNode<AtkComponentDragDrop, AtkUldComponentDataDragDrop> {
public readonly ImageNode DragDropBackgroundNode;
public readonly IconNode IconNode;
public DragDropNode() {
SetInternalComponentType(ComponentType.DragDrop);
DragDropBackgroundNode = new SimpleImageNode {
NodeId = 3,
Size = new Vector2(44.0f, 44.0f),
TexturePath = "ui/uld/DragTargetA.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
TextureSize = new Vector2(44.0f, 44.0f),
WrapMode = WrapMode.Tile,
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
};
DragDropBackgroundNode.AttachNode(this);
IconNode = new IconNode {
NodeId = 2,
Size = new Vector2(44.0f, 48.0f),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
};
IconNode.AttachNode(this);
LoadTimelines();
Data->Nodes[0] = IconNode.NodeId;
AcceptedType = DragDropType.Everything;
Payload = new DragDropPayload();
Component->AtkDragDropInterface.DragDropType = DragDropType.Everything;
Component->AtkDragDropInterface.DragDropReferenceIndex = 0;
InitializeComponentEvents();
AddEvent(AtkEventType.DragDropBegin, DragDropBeginHandler);
AddEvent(AtkEventType.DragDropInsert, DragDropInsertHandler);
AddEvent(AtkEventType.DragDropDiscard, DragDropDiscardHandler);
AddEvent(AtkEventType.DragDropClick, DragDropClickHandler);
AddEvent(AtkEventType.DragDropRollOver, DragDropRollOverHandler);
AddEvent(AtkEventType.DragDropRollOut, DragDropRollOutHandler);
}
private bool IsDragDropEndRegistered { get; set; }
/// <summary>
/// Event that is triggered when a DragDrop is beginning
/// </summary>
public Action<DragDropNode>? OnBegin { get; set; }
/// <summary>
/// Event that is triggered when a DragDrop has finished
/// </summary>
public Action<DragDropNode>? OnEnd { get; set; }
/// <summary>
/// Event that is triggered when a compatible DragDrop is dropped onto this node
/// </summary>
public Action<DragDropNode, DragDropPayload>? OnPayloadAccepted { get; set; }
/// <summary>
/// Event that is triggered when the item in this drag drop is being dropped onto the world
/// </summary>
public Action<DragDropNode>? OnDiscard { get; set; }
/// <summary>
/// Event that is triggered when the item is clicked
/// </summary>
public Action<DragDropNode>? OnClicked { get; set; }
/// <summary>
/// Event that is triggered when the item is being moused over
/// </summary>
public Action<DragDropNode>? OnRollOver { get; set; }
/// <summary>
/// Event that is triggered when the item is no longer being moused over
/// </summary>
public Action<DragDropNode>? OnRollOut { get; set; }
public DragDropPayload Payload { get; set; }
public uint IconId {
get => IconNode.IconId;
set {
IconNode.IconId = value;
IconNode.IsVisible = value != 0;
}
}
public bool IsIconDisabled {
get => IconNode.IsIconDisabled;
set => IconNode.IsIconDisabled = value;
}
public int Quantity {
get => int.Parse(Component->GetQuantityText().ToString());
set => Component->SetQuantity(value);
}
public string QuantityString {
get => Component->GetQuantityText().ToString();
set => Component->SetQuantityText(value);
}
public DragDropType AcceptedType {
get => Component->AcceptedType;
set => Component->AcceptedType = value;
}
public AtkDragDropInterface.SoundEffectSuppression SoundEffectSuppression {
get => Component->AtkDragDropInterface.DragDropSoundEffectSuppression;
set => Component->AtkDragDropInterface.DragDropSoundEffectSuppression = value;
}
public bool IsDraggable {
get => !Component->Flags.HasFlag(DragDropFlag.Locked);
set {
if (value) {
Component->Flags &= ~DragDropFlag.Locked;
}
else {
Component->Flags |= DragDropFlag.Locked;
}
}
}
/// <summary>
/// When true, allows left-clicking the item to trigger OnClicked
/// </summary>
public bool IsClickable {
get => Component->Flags.HasFlag(DragDropFlag.Clickable);
set {
if (value) {
Component->Flags |= DragDropFlag.Clickable;
}
else {
Component->Flags &= ~DragDropFlag.Clickable;
}
}
}
private void DragDropBeginHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
atkEvent->SetEventIsHandled();
Payload.ToDragDropInterface(atkEventData->DragDropData.DragDropInterface);
OnBegin?.Invoke(this);
if (!IsDragDropEndRegistered) {
AddEvent(AtkEventType.DragDropEnd, DragDropEndHandler);
IsDragDropEndRegistered = true;
}
}
public override ReadOnlySeString TextTooltip {
get;
set {
field = value;
switch (value) {
case { IsEmpty: false } when !TooltipRegistered:
AddEvent(AtkEventType.DragDropRollOver, ShowTooltip);
AddEvent(AtkEventType.DragDropRollOut, HideTooltip);
TooltipRegistered = true;
break;
}
}
}
private void DragDropInsertHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
atkEvent->SetEventIsHandled();
atkEvent->State.StateFlags |= AtkEventStateFlags.HasReturnFlags;
atkEvent->State.ReturnFlags = 1;
var payload = DragDropPayload.FromDragDropInterface(atkEventData->DragDropData.DragDropInterface);
Payload.Clear();
IconId = 0;
OnPayloadAccepted?.Invoke(this, payload);
}
private void DragDropDiscardHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
atkEvent->SetEventIsHandled();
atkEvent->State.StateFlags |= AtkEventStateFlags.HasReturnFlags;
atkEvent->State.ReturnFlags = 1;
OnDiscard?.Invoke(this);
}
private void DragDropEndHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
atkEvent->SetEventIsHandled();
atkEventData->DragDropData.DragDropInterface->GetPayloadContainer()->Clear();
OnEnd?.Invoke(this);
if (IsDragDropEndRegistered) {
RemoveEvent(AtkEventType.DragDropEnd, DragDropEndHandler);
IsDragDropEndRegistered = false;
}
}
private void DragDropClickHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
atkEvent->SetEventIsHandled();
atkEvent->State.StateFlags |= AtkEventStateFlags.HasReturnFlags;
atkEvent->State.ReturnFlags = 1;
OnClicked?.Invoke(this);
}
private void DragDropRollOverHandler()
=> OnRollOver?.Invoke(this);
private void DragDropRollOutHandler()
=> OnRollOut?.Invoke(this);
/// Clear the payload data and set iconId to zero
public void Clear() {
Payload.Clear();
IconId = 0;
}
// Show fancy tooltip for the currently stored data
public void ShowTooltip(AtkTooltipManager.AtkTooltipType type, ActionKind actionKind) {
if (AtkStage.Instance()->DragDropManager.IsDragging) return;
var addon = RaptureAtkUnitManager.Instance()->GetAddonByNode(ResNode);
if (addon is null) return;
var tooltipArgs = new AtkTooltipManager.AtkTooltipArgs();
tooltipArgs.Ctor();
tooltipArgs.ActionArgs.Id = Payload.Int2;
tooltipArgs.ActionArgs.Kind = (DetailKind)actionKind;
AtkStage.Instance()->TooltipManager.ShowTooltip(
AtkTooltipManager.AtkTooltipType.Action,
addon->Id,
ResNode,
&tooltipArgs);
}
private void LoadTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 59)
.AddLabelPair(1, 10, 1)
.AddLabelPair(11, 19, 2)
.AddLabelPair(20, 29, 3)
.AddLabelPair(30, 39, 7)
.AddLabelPair(40, 49, 6)
.AddLabelPair(50, 59, 4)
.EndFrameSet()
.Build());
}
}
+120
View File
@@ -0,0 +1,120 @@
using System;
using System.IO;
using System.Numerics;
using System.Threading.Tasks;
using Dalamud.Interface.Textures;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Timelines;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
namespace KamiToolKit.Nodes;
public class GifImageNode : ResNode {
public ImageNode ImageNode;
public GifImageNode() {
ImageNode = new ImageNode();
ImageNode.AttachNode(this);
}
public required string FilePath {
set {
Task.Run(() => LoadFrames(value));
}
}
public override float Width {
get => base.Width;
set {
ImageNode.Width = value;
base.Width = value;
}
}
public override float Height {
get => base.Height;
set {
ImageNode.Height = value;
base.Height = value;
}
}
public Vector2 GifFrameSize { get; private set; }
public bool FitNodeToGif { get; set; }
public Action? OnGifLoaded { get; set; }
private async void LoadFrames(string filepath) {
try {
var image = await LoadAsync(filepath);
if (image.Length <= 0) return;
using var memoryStream = new MemoryStream(image);
using var processedImage = Image.Load<Rgba32>(memoryStream);
if (processedImage.Frames.Count is 0) return;
uint currentPartId = 0;
var frameDelay = processedImage.Frames.RootFrame.Metadata.GetGifMetadata().FrameDelay / 3.33333333f;
var frameCount = (int)(processedImage.Frames.Count * frameDelay);
GifFrameSize = new Vector2(processedImage.Width, processedImage.Height);
if (FitNodeToGif) {
Size = GifFrameSize;
}
foreach (var frame in processedImage.Frames) {
var buffer = new byte[8 * frame.Width * frame.Height];
frame.CopyPixelDataTo(buffer);
var texture = await DalamudInterface.Instance.TextureProvider.CreateFromRawAsync(RawImageSpecification.Rgba32(frame.Width, frame.Height), buffer);
unsafe {
var newPart = ImageNode.AddPart(new Part {
Size = texture.Size,
Id = currentPartId++,
});
newPart->LoadTexture(texture);
}
}
ImageNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, frameCount)
.AddFrame(0, partId: 0)
.AddFrame(frameCount, partId: currentPartId)
.EndFrameSet()
.Build());
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, frameCount)
.AddLabel(1, 200, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(frameCount, 0, AtkTimelineJumpBehavior.LoopForever, 200)
.EndFrameSet()
.Build());
Timeline?.PlayAnimation( AtkTimelineJumpBehavior.LoopForever, 200);
await DalamudInterface.Instance.Framework.RunOnFrameworkThread(() => {
OnGifLoaded?.Invoke();
});
}
catch (Exception e) {
Log.Exception(e);
}
}
private static async Task<byte[]> LoadAsync(string path) {
byte[] data = [];
if (File.Exists(path)) {
data = await File.ReadAllBytesAsync(path);
}
return data;
}
}
@@ -0,0 +1,63 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Enums;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public class HoldButtonProgressNode : ResNode {
public readonly ImageNode ImageNode;
public HoldButtonProgressNode() {
ImageNode = new SimpleImageNode {
NodeId = 4,
TexturePath = "ui/uld/LongPressButtonA.tex",
TextureCoordinates = new Vector2(0.0f, 36.0f),
TextureSize = new Vector2(100.0f, 36.0f),
Size = new Vector2(0.0f, 36.0f),
WrapMode = WrapMode.Tile,
};
ImageNode.AttachNode(this);
BuildTimelines();
}
private void BuildTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 83)
.AddLabel(1, 29, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(60, 30, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(61, 31, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(73, 32, AtkTimelineJumpBehavior.PlayOnce, 31)
.AddLabel(74, 33, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(83, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.EndFrameSet()
.BeginFrameSet(18, 26)
.AddEmptyFrame(18)
.EndFrameSet()
.BeginFrameSet(37, 53)
.AddEmptyFrame(37)
.EndFrameSet()
.BeginFrameSet(54, 71)
.AddEmptyFrame(54)
.EndFrameSet()
.Build()
);
ImageNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 60)
.AddFrame(1, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(61, 73)
.AddFrame(61, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(74, 83)
.AddFrame(74, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(76, addColor: new Vector3(150, 150, 100), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(83, addColor: new Vector3(20, 20, 20), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.Build()
);
}
}
@@ -0,0 +1,13 @@
using System.Numerics;
namespace KamiToolKit.Nodes;
public class HorizontalLineNode : SimpleNineGridNode {
public HorizontalLineNode() {
TexturePath = "ui/uld/WindowA_Line.tex";
TextureCoordinates = Vector2.Zero;
TextureSize = new Vector2(32.0f, 4.0f);
LeftOffset = 12.0f;
RightOffset = 12.0f;
}
}
+216
View File
@@ -0,0 +1,216 @@
using System.Linq;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Enums;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public unsafe class IconExtras : ResNode {
public readonly AlternateCooldownNode AlternateCooldownNode;
public readonly AntsNode AntsNode;
public readonly ImageNode ChargeCountImageNode;
public readonly ImageNode ClickFlashImageNode;
public readonly CooldownNode CooldownNode;
public readonly ImageNode HoveredBorderImageNode;
public readonly TextNode QuantityTextNode;
public readonly TextNode ResourceCostTextNode;
public readonly ImageNode TimelineImageNode;
public IconExtras() {
TimelineImageNode = new SimpleImageNode {
NodeId = 19,
Size = new Vector2(40.0f, 40.0f),
Position = new Vector2(4.0f, 4.0f),
NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents,
WrapMode = WrapMode.Tile,
ImageNodeFlags = ImageNodeFlags.AutoFit,
};
TimelineImageNode.AttachNode(this);
CooldownNode = new CooldownNode {
NodeId = 16,
Size = new Vector2(48.0f, 48.0f),
NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents,
};
CooldownNode.AttachNode(this);
AlternateCooldownNode = new AlternateCooldownNode {
NodeId = 14,
Size = new Vector2(44.0f, 48.0f),
Position = new Vector2(2.0f, 0.0f),
NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents,
};
AlternateCooldownNode.AttachNode(this);
AntsNode = new AntsNode {
NodeId = 12,
Size = new Vector2(48.0f, 48.0f),
NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents,
};
AntsNode.AttachNode(this);
HoveredBorderImageNode = new ImageNode {
NodeId = 11,
Size = new Vector2(72.0f, 72.0f),
Position = new Vector2(-12.0f, -12.0f),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
PartId = 16,
WrapMode = WrapMode.Tile,
};
IconNodeTextureHelper.LoadIconAFrameTexture(HoveredBorderImageNode);
HoveredBorderImageNode.AttachNode(this);
ChargeCountImageNode = new ImageNode {
NodeId = 10,
Size = new Vector2(20.0f, 20.0f),
Position = new Vector2(28.0f, 28.0f),
NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents,
WrapMode = WrapMode.Tile,
};
foreach (var yIndex in Enumerable.Range(0, 2))
foreach (var xIndex in Enumerable.Range(0, 5)) {
var coordinate = new Vector2(xIndex * 20.0f, yIndex * 20.0f);
ChargeCountImageNode.AddPart(new Part {
TexturePath = "ui/uld/IconA_ChargeIcon.tex",
TextureCoordinates = coordinate,
Size = new Vector2(20.0f, 20.0f),
Id = (uint)(xIndex + yIndex),
});
}
ChargeCountImageNode.AttachNode(this);
QuantityTextNode = new TextNode {
NodeId = 9,
Size = new Vector2(40.0f, 12.0f),
Position = new Vector2(4.0f, 34.0f),
NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents,
Color = ColorHelper.GetColor(50),
TextOutlineColor = ColorHelper.GetColor(51),
AlignmentType = AlignmentType.Right,
};
QuantityTextNode.AttachNode(this);
// Also cooldown time text for non-globals
ResourceCostTextNode = new TextNode {
NodeId = 8,
Size = new Vector2(48.0f, 12.0f),
Position = new Vector2(3.0f, 37.0f),
NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents,
Color = ColorHelper.GetColor(50),
TextOutlineColor = ColorHelper.GetColor(51),
AlignmentType = AlignmentType.Left,
};
ResourceCostTextNode.AttachNode(this);
ClickFlashImageNode = new ImageNode {
NodeId = 7,
Size = new Vector2(64, 64),
Position = new Vector2(-8.0f, -8.0f),
Origin = new Vector2(32.0f, 32.0f),
NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents,
WrapMode = WrapMode.Tile,
PartId = 17,
};
IconNodeTextureHelper.LoadIconAFrameTexture(ClickFlashImageNode);
ClickFlashImageNode.AttachNode(this);
BuildTimelines();
}
private void BuildTimelines() {
TimelineImageNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(10, 19)
.AddFrame(10, alpha: 0, multiplyColor: new Vector3(100.0f), addColor: new Vector3(255.0f))
.AddFrame(12, alpha: 63, multiplyColor: new Vector3(100.0f), addColor: new Vector3(255.0f))
.EndFrameSet()
.BeginFrameSet(20, 29)
.AddFrame(20, alpha: 63, multiplyColor: new Vector3(100.0f), addColor: new Vector3(255.0f))
.EndFrameSet()
.BeginFrameSet(40, 49)
.AddFrame(40, alpha: 63, multiplyColor: new Vector3(100.0f), addColor: new Vector3(255.0f))
.EndFrameSet()
.BeginFrameSet(50, 59)
.AddFrame(50, alpha: 63, multiplyColor: new Vector3(100.0f), addColor: new Vector3(255.0f))
.AddFrame(52, alpha: 0, multiplyColor: new Vector3(100.0f), addColor: new Vector3(255.0f))
.EndFrameSet()
.Build());
CooldownNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 165)
.AddLabel(1, 19, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(11, 20, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(21, 21, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(31, 22, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(41, 101, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(51, 102, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabelPair(61, 142, 24)
.AddLabelPair(143, 165, 25)
.EndFrameSet()
.AddFrameSetWithFrame(1, 9, 1, multiplyColor: new Vector3(100.0f), addColor: new Vector3(0.0f))
.BeginFrameSet(10, 19)
.AddFrame(10, multiplyColor: new Vector3(100.0f), addColor: new Vector3(0.0f))
.AddFrame(12, multiplyColor: new Vector3(100.0f), addColor: new Vector3(16.0f))
.EndFrameSet()
.AddFrameSetWithFrame(20, 29, 20, multiplyColor: new Vector3(100.0f), addColor: new Vector3(16.0f))
.AddFrameSetWithFrame(30, 39, 30, multiplyColor: new Vector3(100.0f), addColor: new Vector3(0.0f))
.AddFrameSetWithFrame(40, 49, 40, multiplyColor: new Vector3(100.0f), addColor: new Vector3(16.0f))
.BeginFrameSet(50, 59)
.AddFrame(50, multiplyColor: new Vector3(100.0f), addColor: new Vector3(16.0f))
.AddFrame(52, multiplyColor: new Vector3(100.0f), addColor: new Vector3(0.0f))
.EndFrameSet()
.Build());
AlternateCooldownNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 205)
.AddLabel(1, 17, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(11, 101, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(92, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(93, 102, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(174, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(175, 103, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(205, 0, AtkTimelineJumpBehavior.LoopForever, 103)
.EndFrameSet()
.Build());
AntsNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 9)
.AddLabel(1, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(2, 26, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(9, 0, AtkTimelineJumpBehavior.LoopForever, 26)
.EndFrameSet()
.Build());
HoveredBorderImageNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(10, 19)
.AddFrame(10, alpha: 0, multiplyColor: new Vector3(100.0f), addColor: new Vector3(0.0f))
.AddFrame(12, alpha: 255, multiplyColor: new Vector3(100.0f), addColor: new Vector3(0.0f))
.EndFrameSet()
.BeginFrameSet(20, 29)
.AddFrame(20, alpha: 255, multiplyColor: new Vector3(100.0f), addColor: new Vector3(0.0f))
.EndFrameSet()
.BeginFrameSet(40, 49)
.AddFrame(40, alpha: 255, multiplyColor: new Vector3(100.0f), addColor: new Vector3(0.0f))
.EndFrameSet()
.BeginFrameSet(50, 59)
.AddFrame(50, alpha: 255, multiplyColor: new Vector3(100.0f), addColor: new Vector3(0.0f))
.AddFrame(52, alpha: 0, multiplyColor: new Vector3(100.0f), addColor: new Vector3(0.0f))
.EndFrameSet()
.Build());
ClickFlashImageNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(20, 29)
.AddFrame(20, alpha: 255, scale: new Vector2(0.1f))
.AddFrame(29, alpha: 0, scale: new Vector2(1.0f))
.EndFrameSet()
.Build());
}
}
+28
View File
@@ -0,0 +1,28 @@
using System.Numerics;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
/// <summary>
/// A simple image node for use with displaying game icons.
/// </summary>
/// <remarks>This node is not intended to be used with multiple <see cref="Part" />'s.</remarks>
public unsafe class IconImageNode : SimpleImageNode {
public IconImageNode() {
TextureSize = new Vector2(32.0f, 32.0f);
}
public uint IconId {
get;
set {
if (value != field) {
field = value;
PartsList[0]->LoadIcon(value);
}
}
}
public bool IsTextureReady => PartsList[0]->IsTextureReady;
public uint? LoadedIconId => Node->IconId;
}
+44
View File
@@ -0,0 +1,44 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Enums;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public class IconIndicator : ResNode {
public readonly ImageNode IconNode;
public IconIndicator(uint innerNodeId) {
IconNode = new ImageNode {
NodeId = innerNodeId,
Size = new Vector2(18, 18),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
WrapMode = WrapMode.Stretch,
PartId = (uint)(innerNodeId == 5 ? 25 : 30),
};
IconNodeTextureHelper.LoadIconAFrameTexture(IconNode);
IconNode.AttachNode(this);
BuildTimeline();
}
private void BuildTimeline() {
IconNode.AddTimeline(new TimelineBuilder()
.AddFrameSetWithFrame(11, 20, 11, partId: 14)
.AddFrameSetWithFrame(21, 30, 21, partId: 15)
.AddFrameSetWithFrame(31, 40, 31, partId: 21)
.AddFrameSetWithFrame(41, 50, 41, partId: 22)
.AddFrameSetWithFrame(51, 60, 51, partId: 23)
.AddFrameSetWithFrame(61, 70, 61, partId: 24)
.AddFrameSetWithFrame(71, 79, 71, partId: 29)
.AddFrameSetWithFrame(80, 89, 80, partId: 30)
.AddFrameSetWithFrame(90, 99, 90, partId: 25)
.AddFrameSetWithFrame(100, 109, 100, partId: 26)
.AddFrameSetWithFrame(110, 119, 110, partId: 27)
.AddFrameSetWithFrame(120, 129, 120, partId: 28)
.Build());
}
}
@@ -0,0 +1,78 @@
using System.Linq;
using System.Numerics;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
public static unsafe class IconNodeTextureHelper {
public static void LoadIconAFrameTexture(ImageNode image) {
image.AddPart(new Part { Id = 0, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f) });
image.AddPart(new Part { Id = 1, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f), TextureCoordinates = new Vector2(48.0f, 0.0f) });
image.AddPart(new Part { Id = 2, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f), TextureCoordinates = new Vector2(0.0f, 48.0f) });
image.AddPart(new Part { Id = 3, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f), TextureCoordinates = new Vector2(48.0f, 48.0f) });
image.AddPart(new Part { Id = 4, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f), TextureCoordinates = new Vector2(0.0f, 96.0f) });
image.AddPart(new Part { Id = 5, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f), TextureCoordinates = new Vector2(48.0f, 96.0f) });
image.AddPart(new Part { Id = 6, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f), TextureCoordinates = new Vector2(96.0f, 0.0f) });
image.AddPart(new Part { Id = 7, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f), TextureCoordinates = new Vector2(144.0f, 0.0f) });
image.AddPart(new Part { Id = 8, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f), TextureCoordinates = new Vector2(192.0f, 0.0f) });
image.AddPart(new Part { Id = 9, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f), TextureCoordinates = new Vector2(96.0f, 48.0f) });
image.AddPart(new Part { Id = 10, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f), TextureCoordinates = new Vector2(144.0f, 48.0f) });
image.AddPart(new Part { Id = 11, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f), TextureCoordinates = new Vector2(192.0f, 48.0f) });
image.AddPart(new Part { Id = 12, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f), TextureCoordinates = new Vector2(96.0f, 96.0f) });
image.AddPart(new Part { Id = 13, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f), TextureCoordinates = new Vector2(144.0f, 96.0f) });
image.AddPart(new Part { Id = 14, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(18.0f, 18.0f), TextureCoordinates = new Vector2(192.0f, 96.0f) });
image.AddPart(new Part { Id = 15, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(18.0f, 18.0f), TextureCoordinates = new Vector2(192.0f, 114.0f) });
image.AddPart(new Part { Id = 16, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(72.0f, 72.0f), TextureCoordinates = new Vector2(240.0f, 0.0f) });
image.AddPart(new Part { Id = 17, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(64.0f, 64.0f), TextureCoordinates = new Vector2(240.0f, 72.0f) });
image.AddPart(new Part { Id = 18, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f), TextureCoordinates = new Vector2(312.0f, 0.0f) });
image.AddPart(new Part { Id = 19, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f), TextureCoordinates = new Vector2(312.0f, 48.0f) });
image.AddPart(new Part { Id = 20, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(48.0f, 48.0f), TextureCoordinates = new Vector2(312.0f, 96.0f) });
image.AddPart(new Part { Id = 21, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(18.0f, 18.0f), TextureCoordinates = new Vector2(210.0f, 114.0f) });
image.AddPart(new Part { Id = 22, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(18.0f, 18.0f), TextureCoordinates = new Vector2(360.0f, 96.0f) });
image.AddPart(new Part { Id = 23, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(18.0f, 18.0f), TextureCoordinates = new Vector2(378.0f, 96.0f) });
image.AddPart(new Part { Id = 24, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(18.0f, 18.0f), TextureCoordinates = new Vector2(360.0f, 114.0f) });
image.AddPart(new Part { Id = 25, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(18.0f, 18.0f), TextureCoordinates = new Vector2(210.0f, 96.0f) });
image.AddPart(new Part { Id = 26, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(18.0f, 18.0f), TextureCoordinates = new Vector2(408.0f, 0.0f) });
image.AddPart(new Part { Id = 27, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(18.0f, 18.0f), TextureCoordinates = new Vector2(408.0f, 18.0f) });
image.AddPart(new Part { Id = 28, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(18.0f, 18.0f), TextureCoordinates = new Vector2(408.0f, 36.0f) });
image.AddPart(new Part { Id = 29, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(18.0f, 18.0f), TextureCoordinates = new Vector2(396.0f, 96.0f) });
image.AddPart(new Part { Id = 30, TexturePath = "ui/uld/IconA_Frame.tex", Size = new Vector2(18.0f, 18.0f), TextureCoordinates = new Vector2(396.0f, 114.0f) });
}
public static void LoadIconARecast2Texture(ImageNode imageNode) {
foreach (var yIndex in Enumerable.Range(0, 9))
foreach (var xIndex in Enumerable.Range(0, 9)) {
var coordinate = new Vector2(xIndex * 44.0f, yIndex * 48.0f);
imageNode.AddPart(new Part {
TexturePath = "ui/uld/IconA_Recast2.tex",
TextureCoordinates = coordinate,
Size = new Vector2(44.0f, 46.0f),
Id = (uint)(xIndex + yIndex),
});
}
foreach (var yIndex in Enumerable.Range(9, 9))
foreach (var xIndex in Enumerable.Range(9, 9)) {
var coordinate = new Vector2(xIndex * 44.0f, (yIndex - 9) * 48.0f);
imageNode.AddPart(new Part {
TexturePath = "ui/uld/IconA_Recast2.tex",
TextureCoordinates = coordinate,
Size = new Vector2(44.0f, 46.0f),
Id = (uint)(xIndex + yIndex),
});
}
}
public static void LoadIconARecastTexture(ImageNode imageNode) {
foreach (var yIndex in Enumerable.Range(0, 9))
foreach (var xIndex in Enumerable.Range(0, 9)) {
var coordinate = new Vector2(xIndex * 44.0f, yIndex * 48.0f);
imageNode.AddPart(new Part {
TexturePath = "ui/uld/IconA_Recast.tex",
TextureCoordinates = coordinate,
Size = new Vector2(44.0f, 46.0f),
Id = (uint)(xIndex + yIndex),
});
}
}
}
+64
View File
@@ -0,0 +1,64 @@
using System.IO;
using Dalamud.Interface.Textures.TextureWraps;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
/// <summary>
/// A simple image node that allows you to load an IDalamudTextureWrap texture into a native image node.
/// This node creates a single <see cref="Part" />
/// </summary>
/// <remarks>This node is not intended to be used with multiple <see cref="Part" />'s.</remarks>
public class ImGuiImageNode : SimpleImageNode {
public IDalamudTextureWrap? LoadedTexture;
public override unsafe string TexturePath {
get => base.TexturePath;
set {
if (Path.IsPathRooted(value)) {
LoadTextureFromFile(value);
}
else if (DalamudInterface.Instance.DataManager.FileExists(value)) {
PartsList[0]->LoadTexture(value);
}
}
}
/// <summary>
/// Takes ownership of passed in IDalamudTextureWrap, disposes texture when node is disposed.
/// </summary>
public unsafe void LoadTexture(IDalamudTextureWrap texture) {
var previouslyLoadedTexture = LoadedTexture;
PartsList[0]->LoadTexture(texture);
// Delay unloading texture until new texture is loaded.
previouslyLoadedTexture?.Dispose();
LoadedTexture = texture;
}
public void LoadTextureFromFile(string fileSystemPath) {
DalamudInterface.Instance.Framework.RunOnTick(async () => {
Alpha = 0.0f;
var newTexture = await DalamudInterface.Instance.TextureProvider.GetFromFile(fileSystemPath).RentAsync();
LoadTexture(newTexture);
TextureSize = newTexture.Size;
Alpha = 1.0f;
MarkDirty();
});
}
// Note, disposes loaded IDalamudTextureWrap if either native or managed code frees this node.
protected override void Dispose(bool disposing, bool isNativeDestructor) {
if (disposing) {
base.Dispose(disposing, isNativeDestructor);
LoadedTexture?.Dispose();
LoadedTexture = null;
}
}
}
+61
View File
@@ -0,0 +1,61 @@
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Enums;
namespace KamiToolKit.Nodes;
public unsafe class ImageNode : NodeBase<AtkImageNode> {
public readonly PartsList PartsList;
public ImageNode() : base(NodeType.Image) {
PartsList = new PartsList();
Node->PartsList = PartsList.InternalPartsList;
}
protected override void Dispose(bool disposing, bool isNativeDestructor) {
if (disposing) {
if (!isNativeDestructor) {
PartsList.Dispose();
Node->PartsList = null;
}
base.Dispose(disposing, isNativeDestructor);
}
}
public uint PartId {
get => Node->PartId;
set => Node->PartId = (ushort) value;
}
public WrapMode WrapMode {
get => (WrapMode) Node->WrapMode;
set => Node->WrapMode = (byte) value;
}
public ImageNodeFlags ImageNodeFlags {
get => Node->Flags;
set => Node->Flags = value;
}
/// <summary>
/// When set to true, will cause the loaded texture to
/// fit itself to the size of the node
/// </summary>
public bool FitTexture {
set {
if (value) {
ImageNodeFlags = ImageNodeFlags.AutoFit;
WrapMode = WrapMode.Stretch;
}
}
}
public AtkUldPart* AddPart(Part part)
=> PartsList.Add(part);
public void AddPart(params Part[] parts)
=> PartsList.Add(parts);
}
+15
View File
@@ -0,0 +1,15 @@
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
public sealed class LabelTextNode : TextNode {
public LabelTextNode() {
TextColor = ColorHelper.GetColor(8);
TextOutlineColor = ColorHelper.GetColor(7);
FontType = FontType.Axis;
FontSize = 14;
LineSpacing = 24;
AlignmentType = AlignmentType.Left;
}
}
+78
View File
@@ -0,0 +1,78 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
public unsafe class NineGridNode : NodeBase<AtkNineGridNode> {
public readonly PartsList PartsList;
public NineGridNode() : base(NodeType.NineGrid) {
PartsList = new PartsList();
Node->PartsList = PartsList.InternalPartsList;
}
protected override void Dispose(bool disposing, bool isNativeDestructor) {
if (disposing) {
if (!isNativeDestructor) {
PartsList.Dispose();
Node->PartsList = null;
}
base.Dispose(disposing, isNativeDestructor);
}
}
public uint PartId {
get => Node->PartId;
set => Node->PartId = value;
}
public Vector4 Offsets {
get => new(Node->TopOffset, Node->BottomOffset, Node->LeftOffset, Node->RightOffset);
set {
Node->TopOffset = (short)value.X;
Node->BottomOffset = (short)value.Y;
Node->LeftOffset = (short)value.Z;
Node->RightOffset = (short)value.W;
}
}
public float TopOffset {
get => Node->TopOffset;
set => Node->TopOffset = (short)value;
}
public float BottomOffset {
get => Node->BottomOffset;
set => Node->BottomOffset = (short)value;
}
public float LeftOffset {
get => Node->LeftOffset;
set => Node->LeftOffset = (short)value;
}
public float RightOffset {
get => Node->RightOffset;
set => Node->RightOffset = (short)value;
}
public uint BlendMode {
get => Node->BlendMode;
set => Node->BlendMode = value;
}
public byte PartsRenderType {
get => Node->PartsTypeRenderType;
set => Node->PartsTypeRenderType = value;
}
public AtkUldPart* AddPart(Part part)
=> PartsList.Add(part);
public void AddPart(params Part[] parts)
=> PartsList.Add(parts);
}
@@ -0,0 +1,153 @@
using System.Numerics;
using Dalamud.Game.Addon.Events;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Enums;
namespace KamiToolKit.Nodes;
internal unsafe class NodeEditOverlayNode : SimpleComponentNode {
private readonly NineGridNode backgroundNode;
private readonly ResizeNineGridNode bottomEditNode;
private readonly ResizeButtonNode leftCornerEditNode;
private readonly ResizeNineGridNode leftEditNode;
private readonly ResizeButtonNode rightCornerEditNode;
private readonly ResizeNineGridNode rightEditNode;
private readonly ResizeNineGridNode topEditNode;
public NodeEditOverlayNode() {
backgroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/HUDLayout.tex",
TextureSize = new Vector2(44.0f, 32.0f),
TextureCoordinates = new Vector2(0.0f, 0.0f),
TopOffset = 20,
BottomOffset = 8,
LeftOffset = 21,
RightOffset = 21,
Alpha = 0.75f,
};
backgroundNode.AttachNode(this);
rightEditNode = new ResizeNineGridNode();
rightEditNode.AttachNode(this);
bottomEditNode = new ResizeNineGridNode();
bottomEditNode.AttachNode(this);
leftEditNode = new ResizeNineGridNode();
leftEditNode.AttachNode(this);
topEditNode = new ResizeNineGridNode();
topEditNode.AttachNode(this);
rightCornerEditNode = new ResizeButtonNode(ResizeDirection.BottomRight);
rightCornerEditNode.AttachNode(this);
leftCornerEditNode = new ResizeButtonNode(ResizeDirection.BottomLeft);
leftCornerEditNode.AttachNode(this);
}
public bool ShowParts {
get;
set {
field = value;
rightEditNode.IsVisible = value;
bottomEditNode.IsVisible = value;
leftEditNode.IsVisible = value;
topEditNode.IsVisible = value;
rightCornerEditNode.IsVisible = value;
leftCornerEditNode.IsVisible = value;
}
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
backgroundNode.Size = Size - new Vector2(24.0f, 24.0f);
backgroundNode.Position = new Vector2(12.0f, 12.0f);
const float lineThickness = 4.0f;
leftEditNode.Size = new Vector2(Height - 32.0f, lineThickness);
leftEditNode.Position = new Vector2(16.0f + leftEditNode.Height / 2.0f, 16.0f);
leftEditNode.RotationDegrees = 90.0f;
rightEditNode.Size = new Vector2(Height - 32.0f, lineThickness);
rightEditNode.Position = new Vector2(Width - 16.0f + rightEditNode.Height / 2.0f, 16.0f);
rightEditNode.RotationDegrees = 90.0f;
topEditNode.Size = new Vector2(Width - 32.0f, lineThickness);
topEditNode.Position = new Vector2(16.0f, 16.0f - lineThickness / 2.0f);
bottomEditNode.Size = new Vector2(Width - 32.0f, lineThickness);
bottomEditNode.Position = new Vector2(16.0f, Height - 16.0f - lineThickness / 2.0f);
leftCornerEditNode.Size = new Vector2(24.0f, 24.0f);
leftCornerEditNode.Position = new Vector2(16.0f - lineThickness / 4.0f, Height - 16.0f - leftCornerEditNode.Height);
rightCornerEditNode.Size = new Vector2(24.0f, 24.0f);
rightCornerEditNode.Position = new Vector2(Width - 16.0f - rightCornerEditNode.Width + lineThickness / 4.0f, Height - 16.0f - rightCornerEditNode.Height);
}
public Vector2 GetSizeDelta(Vector2 mouseDelta) {
if (leftEditNode.IsHovered) return new Vector2(-mouseDelta.X, 0.0f);
if (rightEditNode.IsHovered) return new Vector2(mouseDelta.X, 0.0f);
if (topEditNode.IsHovered) return new Vector2(0.0f, -mouseDelta.Y);
if (bottomEditNode.IsHovered) return new Vector2(0.0f, mouseDelta.Y);
if (rightCornerEditNode.IsHovered) return mouseDelta;
if (leftCornerEditNode.IsHovered) return new Vector2(-mouseDelta.X, mouseDelta.Y);
return Vector2.Zero;
}
public Vector2 GetPositionDelta(Vector2 mouseDelta) {
if (leftEditNode.IsHovered) return new Vector2(mouseDelta.X, 0.0f);
if (topEditNode.IsHovered) return new Vector2(0.0f, mouseDelta.Y);
if (leftCornerEditNode.IsHovered) return new Vector2(mouseDelta.X, 0.0f);
return Vector2.Zero;
}
public void UpdateHover(AtkEventData* eventData) {
rightEditNode.IsHovered = rightEditNode.CheckCollision(eventData);
bottomEditNode.IsHovered = bottomEditNode.CheckCollision(eventData);
leftEditNode.IsHovered = leftEditNode.CheckCollision(eventData);
topEditNode.IsHovered = topEditNode.CheckCollision(eventData);
rightCornerEditNode.IsHovered = rightCornerEditNode.CheckCollision(eventData);
leftCornerEditNode.IsHovered = leftCornerEditNode.CheckCollision(eventData);
if (rightCornerEditNode.IsHovered) {
bottomEditNode.IsHovered = false;
rightEditNode.IsHovered = false;
}
if (leftCornerEditNode.IsHovered) {
leftEditNode.IsHovered = false;
bottomEditNode.IsHovered = false;
}
}
public bool AnyHovered() {
if (rightEditNode.IsHovered) return true;
if (bottomEditNode.IsHovered) return true;
if (leftEditNode.IsHovered) return true;
if (topEditNode.IsHovered) return true;
if (rightCornerEditNode.IsHovered) return true;
if (leftCornerEditNode.IsHovered) return true;
return false;
}
public void SetCursor() {
if (rightEditNode.IsHovered) SetCursor(AddonCursorType.ResizeWE);
if (bottomEditNode.IsHovered) SetCursor(AddonCursorType.ResizeNS);
if (leftEditNode.IsHovered) SetCursor(AddonCursorType.ResizeWE);
if (topEditNode.IsHovered) SetCursor(AddonCursorType.ResizeNS);
if (rightCornerEditNode.IsHovered) SetCursor(AddonCursorType.ResizeNWSR);
if (leftCornerEditNode.IsHovered) SetCursor(AddonCursorType.ResizeNESW);
}
private static void SetCursor(AddonCursorType cursor)
=> DalamudInterface.Instance.AddonEventManager.SetCursor(cursor);
}
+190
View File
@@ -0,0 +1,190 @@
using System;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public unsafe class NumericInputNode : ComponentNode<AtkComponentNumericInput, AtkUldComponentDataNumericInput> {
public readonly ButtonBase AddButton;
public readonly NineGridNode BackgroundNode;
public readonly CursorNode CursorNode;
public readonly NineGridNode FocusBorderNode;
public readonly ButtonBase SubtractButton;
public readonly TextNode ValueTextNode;
public NumericInputNode() {
SetInternalComponentType(ComponentType.NumericInput);
BackgroundNode = new SimpleNineGridNode {
NodeId = 8,
Position = new Vector2(0.0f, 3.0f),
TexturePath = "ui/uld/NumericStepperB.tex",
TextureCoordinates = new Vector2(56.0f, 0.0f),
TextureSize = new Vector2(24.0f, 24.0f),
Height = 24.0f,
Offsets = new Vector4(10.0f),
};
BackgroundNode.AttachNode(this);
AddButton = new TextureButtonNode {
NodeId = 7,
TexturePath = "ui/uld/NumericStepperB.tex",
TextureCoordinates = new Vector2(28.0f, 0.0f),
TextureSize = new Vector2(28.0f, 28.0f),
Size = new Vector2(28.0f, 28.0f),
};
AddButton.AttachNode(this);
SubtractButton = new TextureButtonNode {
NodeId = 6,
TexturePath = "ui/uld/NumericStepperB.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
TextureSize = new Vector2(28.0f, 28.0f),
Size = new Vector2(28.0f, 28.0f),
};
SubtractButton.AttachNode(this);
ValueTextNode = new TextNode {
NodeId = 5,
Position = new Vector2(6.0f, 6.0f),
FontType = FontType.Axis,
TextColor = ColorHelper.GetColor(1),
FontSize = 12,
AlignmentType = AlignmentType.Top,
String = "999",
};
ValueTextNode.AttachNode(this);
FocusBorderNode = new SimpleNineGridNode {
NodeId = 4,
TexturePath = "ui/uld/TextInputA.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
TextureSize = new Vector2(24.0f, 24.0f),
Position = new Vector2(-3.0f, -2.0f),
Offsets = new Vector4(10.0f),
IsVisible = false,
};
FocusBorderNode.AttachNode(this);
CursorNode = new CursorNode {
NodeId = 2,
Size = new Vector2(4.0f, 24.0f),
OriginY = 4.0f,
};
CursorNode.AttachNode(this);
BuildTimelines();
Data->Nodes[0] = ValueTextNode.NodeId;
Data->Nodes[1] = 0;
Data->Nodes[2] = CursorNode.NodeId;
Data->Nodes[3] = AddButton.NodeId;
Data->Nodes[4] = SubtractButton.NodeId;
Data->Max = int.MaxValue;
InitializeComponentEvents();
AddEvent(AtkEventType.ValueUpdate, ValueUpdateHandler);
}
public int Value {
get => Component->Value;
set => Component->InnerSetValue(value, true, false);
}
public int Min {
get => Component->Data.Min;
set => Component->Data.Min = value;
}
public int Max {
get => Component->Data.Max;
set => Component->Data.Max = value;
}
public int Step {
get => Component->Data.Add;
set => Component->Data.Add = value;
}
public Action<int>? OnValueUpdate { get; set; }
protected override void OnSizeChanged() {
base.OnSizeChanged();
ValueTextNode.Size = new Vector2(Width - 58.0f, Height / 2.0f);
FocusBorderNode.Size = new Vector2(Width - 40.0f, Height + 4.0f);
BackgroundNode.Width = Width - 46.0f;
AddButton.X = Width - 50.0f;
SubtractButton.X = Width - 28.0f;
}
private void ValueUpdateHandler() {
OnValueUpdate?.Invoke(Value);
}
private void BuildTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 29)
.AddLabel(1, 17, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(9, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(10, 18, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(19, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(20, 7, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(29, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.EndFrameSet()
.Build()
);
BackgroundNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 9)
.AddFrame(1, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(10, 19)
.AddFrame(10, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(12, addColor: new Vector3(20, 20, 20), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(20, 29)
.AddFrame(20, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.Build()
);
ValueTextNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 19)
.AddFrame(1, alpha: 255)
.AddFrame(1, textColor: new Vector3(255.0f, 255.0f, 255.0f) * 255.0f)
.EndFrameSet()
.BeginFrameSet(20, 29)
.AddFrame(20, alpha: 127)
.AddFrame(20, textColor: new Vector3(255.0f, 255.0f, 255.0f) * 255.0f)
.EndFrameSet()
.Build()
);
FocusBorderNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(10, 19)
.AddFrame(10, alpha: 0)
.AddFrame(12, alpha: 255)
.EndFrameSet()
.Build()
);
CursorNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 15)
.AddLabel(1, 101, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(15, 0, AtkTimelineJumpBehavior.LoopForever, 101)
.EndFrameSet()
.BeginFrameSet(1, 19)
.AddEmptyFrame(1)
.EndFrameSet()
.Build()
);
}
}
+8
View File
@@ -0,0 +1,8 @@
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Nodes;
/// <summary>
/// A generic basic resource node.
/// </summary>
public class ResNode() : NodeBase<AtkResNode>(NodeType.Res);
@@ -0,0 +1,40 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Nodes;
public class ResizeNineGridNode : SimpleComponentNode {
public readonly NineGridNode BorderNode;
public ResizeNineGridNode() {
BorderNode = new SimpleNineGridNode {
TexturePath = "ui/uld/WindowA_line.tex",
TextureCoordinates = new Vector2(2.0f, 1.0f),
TextureSize = new Vector2(28.0f, 3.0f),
LeftOffset = 12,
RightOffset = 12,
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
};
BorderNode.AttachNode(this);
}
public bool IsHovered {
get;
set {
field = value;
if (value) {
BorderNode.AddColor = new Vector3(100.0f, 100.0f, 100.0f) / 255.0f;
}
else {
BorderNode.AddColor = Vector3.Zero;
}
}
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
BorderNode.Size = Size;
}
}
@@ -0,0 +1,59 @@
using System.Numerics;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
public unsafe class SimpleClippingMaskNode : ClippingMaskNode {
public SimpleClippingMaskNode() {
PartsList.Add(new Part());
}
public float U {
get => PartsList[0]->U;
set => PartsList[0]->U = (ushort)value;
}
public float V {
get => PartsList[0]->V;
set => PartsList[0]->V = (ushort)value;
}
public Vector2 TextureCoordinates {
get => new(U, V);
set {
U = value.X;
V = value.Y;
}
}
public float TextureHeight {
get => PartsList[0]->Height;
set => PartsList[0]->Height = (ushort)value;
}
public float TextureWidth {
get => PartsList[0]->Width;
set => PartsList[0]->Width = (ushort)value;
}
public Vector2 TextureSize {
get => new(TextureWidth, TextureHeight);
set {
TextureWidth = value.X;
TextureHeight = value.Y;
}
}
public virtual string TexturePath {
get => PartsList[0]->LoadedPath;
set => PartsList[0]->LoadTexture(value);
}
public Vector2 ActualTextureSize => PartsList[0]->LoadedTextureSize;
public void LoadTexture(string path)
=> PartsList[0]->LoadTexture(path);
public void LoadIcon(uint iconId)
=> PartsList[0]->LoadIcon(iconId);
}
@@ -0,0 +1,22 @@
using System;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
public class SimpleComponentNode : ComponentNode<AtkComponentBase, AtkUldComponentDataBase> {
public override ReadOnlySeString TextTooltip {
get => CollisionNode.TextTooltip;
set => CollisionNode.TextTooltip = value;
}
public bool DisableCollisionNode {
set {
if (!value) {
throw new Exception("Clearing DisableCollisionNode is not supported.");
}
CollisionNode.NodeFlags = 0;
}
}
}
@@ -0,0 +1,14 @@
using System.Numerics;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
public unsafe class SimpleCounterNode : CounterNode {
public SimpleCounterNode() {
PartsList.Add(new Part {
TexturePath = "ui/uld/Money_Number.tex",
TextureCoordinates = Vector2.Zero,
Size = new Vector2(22.0f, 22.0f),
});
}
}
@@ -0,0 +1,64 @@
using System.Numerics;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
/// <summary>
/// A simple image node that automatically creates a single <see cref="Part" />, and exposes helpers to modify that
/// part.
/// </summary>
/// <remarks>This node is not intended to be used with multiple <see cref="Part" />'s.</remarks>
public unsafe class SimpleImageNode : ImageNode {
public SimpleImageNode() {
PartsList.Add(new Part());
}
public float U {
get => PartsList[0]->U;
set => PartsList[0]->U = (ushort)value;
}
public float V {
get => PartsList[0]->V;
set => PartsList[0]->V = (ushort)value;
}
public Vector2 TextureCoordinates {
get => new(U, V);
set {
U = value.X;
V = value.Y;
}
}
public float TextureHeight {
get => PartsList[0]->Height;
set => PartsList[0]->Height = (ushort)value;
}
public float TextureWidth {
get => PartsList[0]->Width;
set => PartsList[0]->Width = (ushort)value;
}
public Vector2 TextureSize {
get => new(TextureWidth, TextureHeight);
set {
TextureWidth = value.X;
TextureHeight = value.Y;
}
}
public virtual string TexturePath {
get => PartsList[0]->LoadedPath;
set => PartsList[0]->LoadTexture(value);
}
public Vector2 ActualTextureSize => PartsList[0]->LoadedTextureSize;
public void LoadTexture(string path, bool resolveTheme = true)
=> PartsList[0]->LoadTexture(path, resolveTheme);
public void LoadIcon(uint iconId)
=> PartsList[0]->LoadIcon(iconId);
}
@@ -0,0 +1,51 @@
using System.Numerics;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
public unsafe class SimpleNineGridNode : NineGridNode {
public SimpleNineGridNode() {
PartsList.Add(new Part());
}
public float U {
get => PartsList[0]->U;
set => PartsList[0]->U = (ushort)value;
}
public float V {
get => PartsList[0]->V;
set => PartsList[0]->V = (ushort)value;
}
public Vector2 TextureCoordinates {
get => new(U, V);
set {
U = value.X;
V = value.Y;
}
}
public float TextureWidth {
get => PartsList[0]->Width;
set => PartsList[0]->Width = (ushort)value;
}
public float TextureHeight {
get => PartsList[0]->Height;
set => PartsList[0]->Height = (ushort)value;
}
public Vector2 TextureSize {
get => new(TextureWidth, TextureHeight);
set {
TextureWidth = value.X;
TextureHeight = value.Y;
}
}
public string TexturePath {
get => PartsList[0]->LoadedPath;
set => PartsList[0]->LoadTexture(value);
}
}
@@ -0,0 +1,6 @@
namespace KamiToolKit.Nodes;
public class SimpleOverlayNode : SimpleComponentNode {
public SimpleOverlayNode()
=> DisableCollisionNode = true;
}
@@ -0,0 +1,49 @@
using System.Linq;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Nodes;
public class TextInputSelectionListNode : ResNode {
public readonly NineGridNode BackgroundNode;
public readonly TextInputButtonNode[] Buttons = new TextInputButtonNode[9];
public readonly TextNode LabelNode;
public TextInputSelectionListNode() {
BackgroundNode = new SimpleNineGridNode {
NodeId = 15,
Size = new Vector2(186.0f, 208.0f),
TexturePath = "ui/uld/TextInputA.tex",
TextureCoordinates = new Vector2(48.0f, 0.0f),
TextureSize = new Vector2(20.0f, 20.0f),
TopOffset = 8.0f,
BottomOffset = 8.0f,
LeftOffset = 9.0f,
RightOffset = 9.0f,
PartsRenderType = 4,
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.Fill | NodeFlags.EmitsEvents,
};
BackgroundNode.AttachNode(this);
LabelNode = new TextNode {
NodeId = 14,
Position = new Vector2(13.0f, 182.0f),
Size = new Vector2(160.0f, 21.0f),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
AlignmentType = (AlignmentType)21,
FontType = FontType.MiedingerMed,
};
LabelNode.AttachNode(this);
foreach (var index in Enumerable.Range(0, 9)) {
Buttons[index] = new TextInputButtonNode {
NodeId = (uint)(13 - index),
Position = new Vector2(13.0f, 164.0f - 20.0f * index),
Size = new Vector2(160.0f, 24.0f),
};
Buttons[index].AttachNode(this);
}
}
}
@@ -0,0 +1,93 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
public unsafe class TextNineGridNode : ComponentNode<AtkComponentTextNineGrid, AtkUldComponentDataTextNineGrid> {
public readonly NineGridNode BackgroundNineGrid;
public readonly TextNode TextNode;
public TextNineGridNode() {
SetInternalComponentType(ComponentType.TextNineGrid);
BackgroundNineGrid = new SimpleNineGridNode {
TexturePath = "ui/uld/ToolTipS.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
TextureSize = new Vector2(32.0f, 24.0f),
TopOffset = 10,
BottomOffset = 10,
LeftOffset = 15,
RightOffset = 15,
};
BackgroundNineGrid.AttachNode(this);
TextNode = new TextNode {
TextOutlineColor = ColorHelper.GetColor(55),
Position = new Vector2(4.0f, 1.0f),
FontSize = 23,
AlignmentType = AlignmentType.Right,
FontType = FontType.TrumpGothic,
TextFlags = TextFlags.Edge,
};
TextNode.AttachNode(this);
Data->Nodes[0] = TextNode.NodeId;
Data->Nodes[1] = 0;
InitializeComponentEvents();
// Disable ParentNode else SetText
// causes this node to resize itself incorrectly.
Component->ParentNode = null;
}
public ReadOnlySeString String {
get => TextNode.String;
set => Component->SetText(value);
}
public int Number {
get => int.Parse(TextNode.String);
set => TextNode.String = value.ToString();
}
public int FontSize {
get => (int)TextNode.FontSize;
set => TextNode.FontSize = (uint)value;
}
public FontType FontType {
get => TextNode.FontType;
set => TextNode.FontType = value;
}
public Vector4 TextOutlineColor {
get => TextNode.TextOutlineColor;
set => TextNode.TextOutlineColor = value;
}
public Vector4 TextColor {
get => TextNode.TextColor;
set => TextNode.TextColor = value;
}
public TextFlags TextFlags {
get => TextNode.TextFlags;
set => TextNode.TextFlags = value;
}
public AlignmentType AlignmentType {
get => TextNode.AlignmentType;
set => TextNode.AlignmentType = value;
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
BackgroundNineGrid.Size = Size;
TextNode.Size = Size - new Vector2(8.0f, 2.0f);
}
}
+154
View File
@@ -0,0 +1,154 @@
using System.Numerics;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
public unsafe class TextNode : NodeBase<AtkTextNode> {
public TextNode() : base(NodeType.Text) {
TextColor = ColorHelper.GetColor(8);
TextOutlineColor = ColorHelper.GetColor(7);
FontSize = 12;
FontType = FontType.Axis;
LineSpacing = 12;
AlignmentType = AlignmentType.Left;
}
public Vector4 TextColor {
get => Node->TextColor.ToVector4();
set => Node->TextColor = value.ToByteColor();
}
public Vector4 TextOutlineColor {
get => Node->EdgeColor.ToVector4();
set => Node->EdgeColor = value.ToByteColor();
}
public Vector4 BackgroundColor {
get => Node->BackgroundColor.ToVector4();
set => Node->BackgroundColor = value.ToByteColor();
}
public uint SelectStart {
get => Node->SelectStart;
set => Node->SelectStart = value;
}
public uint SelectEnd {
get => Node->SelectEnd;
set => Node->SelectEnd = value;
}
public AlignmentType AlignmentType {
get => Node->AlignmentType;
set {
Node->SetAlignment(value);
UpdateText();
}
}
public FontType FontType {
get => Node->FontType;
set {
Node->SetFont(value);
UpdateText();
}
}
public TextFlags TextFlags {
get => Node->TextFlags;
set {
Node->TextFlags = value;
UpdateText();
}
}
public void AddTextFlags(params TextFlags[] flags) {
foreach (var flag in flags) {
TextFlags |= flag;
}
}
public void RemoveTextFlags(params TextFlags[] flags) {
foreach (var flag in flags) {
TextFlags &= ~flag;
}
}
public uint FontSize {
get => Node->FontSize;
set {
Node->FontSize = (byte)value;
UpdateText();
}
}
public uint LineSpacing {
get => Node->LineSpacing;
set {
Node->LineSpacing = (byte)value;
UpdateText();
}
}
public uint CharSpacing {
get => Node->CharSpacing;
set {
Node->CharSpacing = (byte)value;
UpdateText();
}
}
public uint TextId {
get => Node->TextId;
set => Node->TextId = value;
}
public ReadOnlySeString String {
get => new(Node->GetText().AsSpan());
set {
using var builder = new RentedSeStringBuilder();
Node->SetText(builder.Builder.Append(value).GetViewAsSpan());
}
}
public override Vector2 Size {
get => base.Size;
set {
base.Size = value;
UpdateText();
}
}
public void SetNumber(int number, bool showCommas = false, bool showPlusSign = false, int digits = 0, bool zeroPad = false)
=> Node->SetNumber(number, showCommas, showPlusSign, (byte)digits, zeroPad);
public Vector2 GetTextDrawSize(ReadOnlySeString text, bool considerScale = true) {
using var builder = new RentedSeStringBuilder();
ushort sizeX = 0;
ushort sizeY = 0;
fixed (byte* ptr = builder.Builder.Append(text).GetViewAsSpan())
Node->GetTextDrawSize(&sizeX, &sizeY, ptr, considerScale: considerScale);
return new Vector2(sizeX, sizeY);
}
public Vector2 GetTextDrawSize(bool considerScale = true) {
ushort sizeX = 0;
ushort sizeY = 0;
Node->GetTextDrawSize(&sizeX, &sizeY, considerScale: considerScale);
return new Vector2(sizeX, sizeY);
}
private void UpdateText() {
using var builder = new RentedSeStringBuilder();
Node->SetText(builder.Builder.Append(String).GetViewAsSpan());
}
}
@@ -0,0 +1,26 @@
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Nodes;
/// <summary>
/// WARNING: This is a non-owning texture image node.
/// This node is meant to reference a texture that is owned elsewhere.
/// </summary>
public unsafe class TextureImageNode : SimpleImageNode {
public void SetTexture(Texture* texture) {
var asset = PartsList[0]->UldAsset;
asset->AtkTexture.KernelTexture = texture;
asset->AtkTexture.TextureType = TextureType.KernelTexture;
}
protected override void Dispose(bool disposing, bool isNativeDestructor) {
if (disposing) {
var asset = PartsList[0]->UldAsset;
asset->AtkTexture.KernelTexture = null;
asset->AtkTexture.TextureType = 0;
base.Dispose(disposing, isNativeDestructor);
}
}
}
@@ -0,0 +1,401 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Timelines;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
public unsafe class TreeListCategoryNode : ResNode {
public readonly NineGridNode BackgroundNode;
public readonly SimpleComponentNode ChildContainer;
public readonly ImageNode CollapseArrowNode;
public readonly CollisionNode CollisionNode;
public readonly TextNode LabelNode;
private readonly List<NodeBase> children = [];
public IReadOnlyCollection<TreeListHeaderNode> HeaderNodes => children.OfType<TreeListHeaderNode>().ToList();
public IReadOnlyCollection<NodeBase> Children => children.AsReadOnly();
public IEnumerable<T> GetNodes<T>() where T : NodeBase => children.OfType<T>();
public Action<bool>? OnToggle;
public TreeListCategoryNode() {
CollisionNode = new CollisionNode {
Height = 28.0f,
};
CollisionNode.AttachNode(this);
BackgroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/ListItemB.tex",
TextureSize = new Vector2(48.0f, 28.0f),
TextureCoordinates = new Vector2(0.0f, 24.0f),
Height = 28.0f,
TopOffset = 10.0f,
LeftOffset = 12.0f,
RightOffset = 12.0f,
BottomOffset = 12.0f,
};
BackgroundNode.AttachNode(this);
CollapseArrowNode = new ImageNode {
Position = new Vector2(0.0f, 1.0f),
Size = new Vector2(24.0f, 24.0f),
PartId = 1,
};
CollapseArrowNode.AddPart(new Part {
TexturePath = "ui/uld/ListItemB.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
Size = new Vector2(24.0f, 24.0f),
Id = 0,
});
CollapseArrowNode.AddPart(new Part {
TexturePath = "ui/uld/ListItemB.tex",
TextureCoordinates = new Vector2(24.0f, 0.0f),
Size = new Vector2(24.0f, 24.0f),
Id = 1,
});
CollapseArrowNode.AttachNode(this);
LabelNode = new TextNode {
Position = new Vector2(23.0f, 0.0f),
FontType = FontType.Axis,
FontSize = 14,
Height = 28.0f,
AlignmentType = AlignmentType.Left,
TextColor = ColorHelper.GetColor(50),
TextOutlineColor = ColorHelper.GetColor(7),
};
LabelNode.AttachNode(this);
ChildContainer = new SimpleComponentNode {
Position = new Vector2(0.0f, 24.0f + VerticalPadding),
};
ChildContainer.AttachNode(this);
BuildTimelines();
CollisionNode.ShowClickableCursor = true;
CollisionNode.AddEvent(AtkEventType.MouseOver, () => Timeline?.PlayAnimation(IsCollapsed ? 2 : 9));
CollisionNode.AddEvent(AtkEventType.MouseOut, () => Timeline?.PlayAnimation(IsCollapsed ? 1 : 8));
CollisionNode.AddEvent(AtkEventType.MouseClick, () => {
IsCollapsed = !IsCollapsed;
UpdateCollapsed();
OnToggle?.Invoke(!IsCollapsed);
});
}
public TreeListNode? ParentTreeListNode { get; set; }
private bool InternalIsCollapsed { get; set; }
public bool IsCollapsed {
get => InternalIsCollapsed;
set {
InternalIsCollapsed = value;
UpdateCollapsed();
Timeline?.PlayAnimation(IsCollapsed ? 1 : 8);
}
}
public float VerticalPadding { get; set; } = 4.0f;
public ReadOnlySeString String {
get => LabelNode.String;
set => LabelNode.String = value;
}
private void UpdateCollapsed() {
Timeline?.PlayAnimation(IsCollapsed ? 1 : 8);
ChildContainer.IsVisible = !IsCollapsed;
Height = IsCollapsed ? BackgroundNode.Height : ChildContainer.Height + BackgroundNode.Height;
ParentTreeListNode?.RefreshLayout();
}
public void RecalculateLayout() {
ChildContainer.Height = 0.0f;
foreach (var child in children) {
if (!child.IsVisible) continue;
child.Y = ChildContainer.Height;
child.Width = ChildContainer.Width;
ChildContainer.Height += child.Height + VerticalPadding;
Height = ChildContainer.Height + BackgroundNode.Height;
}
UpdateCollapsed();
}
public void AddHeader(ReadOnlySeString label) {
var newHeaderNode = new TreeListHeaderNode {
Size = new Vector2(Width, 24.0f),
String = label,
};
AddNode(newHeaderNode);
}
public void AddNode(NodeBase node) {
node.Y = ChildContainer.Height;
node.Width = ChildContainer.Width;
node.NodeId = (uint)children.Count + 2;
ChildContainer.Height += node.Height + VerticalPadding;
Height = ChildContainer.Height + BackgroundNode.Height;
children.Add(node);
node.AttachNode(ChildContainer);
UpdateCollapsed();
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
BackgroundNode.Width = Width;
CollapseArrowNode.Width = 24.0f;
LabelNode.Width = Width - 23.0f;
ChildContainer.Width = Width;
CollisionNode.Width = Width;
foreach (var node in children) {
node.Width = Width;
}
}
public void UpdateChildrenNodeId() {
CollisionNode.NodeId = NodeId * 10000 + 1;
BackgroundNode.NodeId = NodeId * 10000 + 2;
CollapseArrowNode.NodeId = NodeId * 10000 + 3;
LabelNode.NodeId = NodeId * 10000 + 4;
ChildContainer.NodeId = NodeId * 10000 + 5;
}
private void BuildTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 119)
.AddLabel(1, 1, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(9, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(10, 2, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(19, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(20, 3, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(29, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(30, 7, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(39, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(40, 6, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(49, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(50, 4, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(59, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(60, 8, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(69, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(70, 9, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(79, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(80, 10, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(89, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(90, 14, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(99, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(100, 13, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(109, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(110, 11, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(119, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.EndFrameSet()
.Build()
);
CollapseArrowNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 9)
.AddFrame(1, alpha: 255)
.AddFrame(1, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(1, partId: 0)
.EndFrameSet()
.BeginFrameSet(10, 19)
.AddFrame(10, alpha: 255)
.AddFrame(12, alpha: 255)
.AddFrame(10, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(12, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(10, partId: 0)
.AddFrame(12, partId: 0)
.EndFrameSet()
.BeginFrameSet(20, 29)
.AddFrame(20, alpha: 255)
.AddFrame(20, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(20, partId: 0)
.EndFrameSet()
.BeginFrameSet(30, 39)
.AddFrame(30, alpha: 178)
.AddFrame(30, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(50, 50, 50))
.AddFrame(30, partId: 0)
.EndFrameSet()
.BeginFrameSet(40, 49)
.AddFrame(40, alpha: 255)
.AddFrame(40, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(40, partId: 0)
.EndFrameSet()
.BeginFrameSet(50, 59)
.AddFrame(50, alpha: 255)
.AddFrame(52, alpha: 255)
.AddFrame(50, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(52, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(50, partId: 0)
.AddFrame(52, partId: 0)
.EndFrameSet()
.BeginFrameSet(60, 69)
.AddFrame(60, alpha: 255)
.AddFrame(60, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(60, partId: 1)
.EndFrameSet()
.BeginFrameSet(70, 79)
.AddFrame(70, alpha: 255)
.AddFrame(72, alpha: 255)
.AddFrame(70, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(72, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(70, partId: 1)
.AddFrame(72, partId: 1)
.EndFrameSet()
.BeginFrameSet(80, 89)
.AddFrame(80, alpha: 255)
.AddFrame(80, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(80, partId: 0)
.EndFrameSet()
.BeginFrameSet(90, 99)
.AddFrame(90, alpha: 178)
.AddFrame(90, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(50, 50, 50))
.AddFrame(90, partId: 1)
.EndFrameSet()
.BeginFrameSet(100, 109)
.AddFrame(100, alpha: 255)
.AddFrame(100, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(100, partId: 1)
.EndFrameSet()
.BeginFrameSet(110, 119)
.AddFrame(110, alpha: 255)
.AddFrame(112, alpha: 255)
.AddFrame(110, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(112, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(110, partId: 1)
.AddFrame(112, partId: 1)
.EndFrameSet()
.Build()
);
LabelNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 9)
.AddFrame(1, alpha: 229)
.AddFrame(1, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(10, 19)
.AddFrame(10, alpha: 229)
.AddFrame(10, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(20, 29)
.AddFrame(20, alpha: 229)
.AddFrame(20, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(30, 39)
.AddFrame(30, alpha: 153)
.AddFrame(30, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(80, 80, 80))
.EndFrameSet()
.BeginFrameSet(40, 49)
.AddFrame(40, alpha: 229)
.AddFrame(40, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(50, 59)
.AddFrame(50, alpha: 229)
.AddFrame(50, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(60, 69)
.AddFrame(60, alpha: 229)
.AddFrame(60, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(70, 79)
.AddFrame(70, alpha: 229)
.AddFrame(70, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(80, 89)
.AddFrame(80, alpha: 229)
.AddFrame(80, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(90, 99)
.AddFrame(90, alpha: 153)
.AddFrame(90, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(80, 80, 80))
.EndFrameSet()
.BeginFrameSet(100, 109)
.AddFrame(100, alpha: 229)
.AddFrame(100, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(110, 119)
.AddFrame(110, alpha: 229)
.AddFrame(110, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.Build()
);
BackgroundNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 9)
.AddFrame(1, alpha: 255)
.AddFrame(1, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(10, 19)
.AddFrame(10, alpha: 255)
.AddFrame(12, alpha: 255)
.AddFrame(10, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(12, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(20, 29)
.AddFrame(20, alpha: 255)
.AddFrame(20, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(30, 39)
.AddFrame(30, alpha: 178)
.AddFrame(30, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(50, 50, 50))
.EndFrameSet()
.BeginFrameSet(40, 49)
.AddFrame(40, alpha: 255)
.AddFrame(40, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(50, 59)
.AddFrame(50, alpha: 255)
.AddFrame(52, alpha: 255)
.AddFrame(50, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(52, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(60, 69)
.AddFrame(60, alpha: 255)
.AddFrame(60, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(70, 79)
.AddFrame(70, alpha: 255)
.AddFrame(72, alpha: 255)
.AddFrame(70, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(72, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(80, 89)
.AddFrame(80, alpha: 255)
.AddFrame(80, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(90, 99)
.AddFrame(90, alpha: 178)
.AddFrame(90, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(50, 50, 50))
.EndFrameSet()
.BeginFrameSet(100, 109)
.AddFrame(100, alpha: 255)
.AddFrame(100, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(110, 119)
.AddFrame(110, alpha: 255)
.AddFrame(112, alpha: 255)
.AddFrame(110, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(112, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.Build()
);
}
}
@@ -0,0 +1,45 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
public class TreeListHeaderNode : ResNode {
public readonly NineGridNode DecorationNode;
public readonly TextNode LabelNode;
public TreeListHeaderNode() {
DecorationNode = new SimpleNineGridNode {
TexturePath = "ui/uld/journal_Separator.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
TextureSize = new Vector2(424.0f, 24.0f),
Size = new Vector2(24.0f, 24.0f),
LeftOffset = 25.0f,
RightOffset = 20.0f,
};
DecorationNode.AttachNode(this);
LabelNode = new TextNode {
Position = new Vector2(22.0f, 1.0f),
TextColor = ColorHelper.GetColor(7),
AlignmentType = AlignmentType.Left,
FontSize = 12,
FontType = FontType.Axis,
};
LabelNode.AttachNode(this);
}
public ReadOnlySeString String {
get => LabelNode.String;
set => LabelNode.String = value;
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
DecorationNode.Size = Size;
LabelNode.Size = new Vector2(Width - 22.0f, Height);
}
}
@@ -0,0 +1,17 @@
namespace KamiToolKit.Nodes;
public sealed unsafe class VerticalLineNode : HorizontalLineNode {
public VerticalLineNode() {
RotationDegrees = 90.0f;
}
public override float Height {
get => ResNode->GetWidth();
set => ResNode->SetWidth((ushort) value);
}
public override float Width {
get => ResNode->GetHeight();
set => ResNode->SetHeight((ushort) value);
}
}
@@ -0,0 +1,22 @@
using System.Numerics;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
public class WindowBackgroundNode : NineGridNode {
public WindowBackgroundNode(bool selectedPath, string path = "ui/uld/WindowA_Bg") {
var basePath = $"{path}{(selectedPath ? "Selected" : "Normal")}";
PartsList.Add(
new Part { TextureCoordinates = new Vector2(0.0f, 0.0f), Size = new Vector2(16.0f, 64.0f), Id = 0, TexturePath = $"{basePath}_Corner.tex" },
new Part { TextureCoordinates = new Vector2(0.0f, 0.0f), Size = new Vector2(32.0f, 64.0f), Id = 1, TexturePath = $"{basePath}_H.tex" },
new Part { TextureCoordinates = new Vector2(16.0f, 0.0f), Size = new Vector2(16.0f, 64.0f), Id = 2, TexturePath = $"{basePath}_Corner.tex" },
new Part { TextureCoordinates = new Vector2(0.0f, 0.0f), Size = new Vector2(16.0f, 32.0f), Id = 3, TexturePath = $"{basePath}_V.tex" },
new Part { TextureCoordinates = new Vector2(0.0f, 0.0f), Size = new Vector2(32.0f, 32.0f), Id = 4, TexturePath = $"{basePath}_HV.tex" },
new Part { TextureCoordinates = new Vector2(16.0f, 0.0f), Size = new Vector2(16.0f, 32.0f), Id = 5, TexturePath = $"{basePath}_V.tex" },
new Part { TextureCoordinates = new Vector2(0.0f, 64.0f), Size = new Vector2(16.0f, 32.0f), Id = 6, TexturePath = $"{basePath}_Corner.tex" },
new Part { TextureCoordinates = new Vector2(0.0f, 64.0f), Size = new Vector2(32.0f, 32.0f), Id = 7, TexturePath = $"{basePath}_H.tex" },
new Part { TextureCoordinates = new Vector2(16.0f, 64.0f), Size = new Vector2(16.0f, 32.0f), Id = 8, TexturePath = $"{basePath}_Corner.tex" }
);
}
}