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" }
);
}
}
+93
View File
@@ -0,0 +1,93 @@
using System;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public abstract unsafe class ButtonBase : ComponentNode<AtkComponentButton, AtkUldComponentDataButton> {
protected ButtonBase() {
SetInternalComponentType(ComponentType.Button);
AddEvent(AtkEventType.ButtonClick, ClickHandler);
}
public Action? OnClick { get; set; }
public bool IsChecked {
get => Component->IsChecked;
set => Component->SetChecked(value);
}
private void ClickHandler() {
OnClick?.Invoke();
}
protected static void LoadTwoPartTimelines(NodeBase parent, NodeBase foreground) {
parent.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 59)
.AddLabelPair(1, 9, 1)
.AddLabelPair(10, 19, 2)
.AddLabelPair(20, 29, 3)
.AddLabelPair(30, 39, 7)
.AddLabelPair(40, 49, 6)
.AddLabelPair(50, 59, 4)
.EndFrameSet()
.Build());
foreground.AddTimeline(new TimelineBuilder()
.AddFrameSetWithFrame(1, 9, 1, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f))
.BeginFrameSet(10, 19)
.AddFrame(10, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f))
.AddFrame(12, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f), addColor: new Vector3(16.0f))
.EndFrameSet()
.AddFrameSetWithFrame(20, 29, 20, new Vector2(0.0f, 1.0f), 255, multiplyColor: new Vector3(100.0f), addColor: new Vector3(16.0f))
.AddFrameSetWithFrame(30, 39, 30, Vector2.Zero, 178, multiplyColor: new Vector3(50.0f))
.AddFrameSetWithFrame(40, 49, 40, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f), addColor: new Vector3(16.0f))
.BeginFrameSet(50, 59)
.AddFrame(50, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f), addColor: new Vector3(16.0f))
.AddFrame(52, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f))
.EndFrameSet()
.AddFrameSetWithFrame(130, 139, 130, Vector2.Zero, 255, new Vector3(16.0f), new Vector3(100.0f))
.AddFrameSetWithFrame(140, 149, 140, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(150, 159, 150, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f))
.Build());
}
protected static void LoadThreePartTimelines(NodeBase parent, NodeBase background, NodeBase foreground, Vector2 foregroundPositionOffset) {
parent.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 53)
.AddLabelPair(1, 10, 1)
.AddLabelPair(11, 17, 2)
.AddLabelPair(18, 26, 3)
.AddLabelPair(27, 36, 7)
.AddLabelPair(37, 46, 6)
.AddLabelPair(47, 53, 4)
.EndFrameSet()
.Build());
background.AddTimeline(new TimelineBuilder()
.AddFrameSetWithFrame(1, 10, 1, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f))
.BeginFrameSet(11, 17)
.AddFrame(11, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f))
.AddFrame(13, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f), addColor: new Vector3(16.0f))
.EndFrameSet()
.AddFrameSetWithFrame(18, 26, 18, new Vector2(0.0f, 1.0f), 255, new Vector3(16.0f))
.AddFrameSetWithFrame(27, 36, 27, Vector2.Zero, 178, multiplyColor: new Vector3(50.0f))
.AddFrameSetWithFrame(37, 46, 37, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f), addColor: new Vector3(16.0f))
.BeginFrameSet(47, 53)
.AddFrame(47, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f), addColor: new Vector3(16.0f))
.AddFrame(53, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f))
.EndFrameSet()
.Build());
foreground.AddTimeline(new TimelineBuilder()
.AddFrameSetWithFrame(1, 10, 1, foregroundPositionOffset, 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(11, 17, 11, foregroundPositionOffset, 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(18, 26, 18, foregroundPositionOffset + new Vector2(0.0f, 1.0f), 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(27, 36, 27, foregroundPositionOffset, 153, multiplyColor: new Vector3(80.0f))
.AddFrameSetWithFrame(37, 46, 37, foregroundPositionOffset, 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(47, 53, 47, foregroundPositionOffset, 255, multiplyColor: new Vector3(100.0f))
.Build());
}
}
@@ -0,0 +1,261 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Enums;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public abstract class ListNode : SimpleComponentNode;
/// Note, automatically inserts buttons to fill the set height, please ensure option count is greater than button count.
public abstract unsafe class ButtonListNode<T> : ListNode {
public readonly NineGridNode BackgroundNode;
public readonly ResNode ContainerNode;
public readonly ScrollBarNode ScrollBarNode;
public List<ListButtonNode> Nodes = [];
protected ButtonListNode() {
SetInternalComponentType(ComponentType.Base);
BackgroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/ListB.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
TextureSize = new Vector2(32.0f, 32.0f),
TopOffset = 10,
BottomOffset = 12,
LeftOffset = 10,
RightOffset = 10,
};
BackgroundNode.AttachNode(this);
ContainerNode = new ResNode {
NodeFlags = NodeFlags.Visible | NodeFlags.Clip,
};
ContainerNode.AttachNode(this);
ScrollBarNode = new ScrollBarNode {
Position = new Vector2(0.0f, 9.0f),
Size = new Vector2(8.0f, 0.0f),
OnValueChanged = OnScrollUpdate,
HideWhenDisabled = true,
};
ScrollBarNode.AttachNode(this);
BuildTimelines();
ContainerNode.AddEvent(AtkEventType.MouseWheel, OnMouseWheel);
}
protected override void Dispose(bool disposing, bool isNativeDestructor) {
if (disposing) {
if (isFocusSet && !isNativeDestructor) {
if (ParentAddon is not null) {
ClearFocusable(ParentAddon);
}
}
base.Dispose(disposing, isNativeDestructor);
}
}
public T? SelectedOption {
get;
set {
field = value;
UpdateSelected();
}
}
public List<T>? Options {
get;
set {
field = value;
RebuildNodeList();
}
}
protected float NodeHeight { get; set; } = 22.0f;
private int ButtonCount { get; set; }
public int MaxButtons {
get;
set {
field = value;
RebuildNodeList();
}
} = 5;
public int CurrentStartIndex { get; set; }
public Action<T>? OnOptionSelected { get; set; }
protected override void OnSizeChanged() {
base.OnSizeChanged();
BackgroundNode.Size = Size;
ContainerNode.Size = new Vector2(Width - 25.0f, Height);
foreach (var buttonNode in Nodes) {
buttonNode.Width = Width - 25.0f;
}
ScrollBarNode.X = Width - 17.0f;
}
private void OnScrollUpdate(int scrollPosition) {
var index = scrollPosition / 22.0f;
CurrentStartIndex = (int)index;
UpdateNodes();
}
private void OnMouseWheel(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
CurrentStartIndex -= atkEventData->MouseData.WheelDirection;
UpdateNodes();
ScrollBarNode.ScrollPosition = (int)(CurrentStartIndex * NodeHeight + 9.0f);
atkEvent->SetEventIsHandled();
}
private void RebuildNodeList() {
foreach (var button in Nodes) {
button.DetachNode();
button.Dispose();
}
Nodes.Clear();
ButtonCount = Math.Min(MaxButtons, Options?.Count ?? 0);
var height = ButtonCount * NodeHeight + 24.0f;
Height = height;
BackgroundNode.Height = height;
ContainerNode.Height = height;
ScrollBarNode.Height = height - 23.0f;
foreach (var index in Enumerable.Range(0, ButtonCount)) {
var newButton = new ListButtonNode {
NodeId = (uint)index,
Size = new Vector2(Width - 25.0f, NodeHeight),
Position = new Vector2(8.0f, NodeHeight * index + 9.0f),
String = $"Button {index}",
OnClick = () => OnOptionClick(index),
};
Nodes.Add(newButton);
newButton.AttachNode(ContainerNode);
}
RecalculateScrollParams();
UpdateNodes();
}
public void RecalculateScrollParams() {
if (Options is not null) {
ScrollBarNode.UpdateScrollParams((int)ScrollBarNode.Height, (int)(Options.Count * NodeHeight));
}
}
protected virtual void OnOptionClick(int nodeId) {
if (Options is null) return;
SelectedOption = Options[nodeId + CurrentStartIndex];
OnOptionSelected?.Invoke(Options[nodeId + CurrentStartIndex]);
UpdateSelected();
}
private void UpdateSelected() {
if (Options is null) return;
foreach (var index in Enumerable.Range(0, ButtonCount)) {
var option = Options[index + CurrentStartIndex];
Nodes[index].Selected = SelectedOption?.Equals(option) ?? false;
Nodes[index].String = GetLabelForOption(option);
}
}
protected abstract string GetLabelForOption(T option);
protected void UpdateNodes() {
if (Options is null) return;
var maxStartIndex = Options.Count - Nodes.Count;
var max = Math.Max(0, maxStartIndex);
CurrentStartIndex = Math.Clamp(CurrentStartIndex, 0, max);
UpdateSelected();
}
public void SelectDefaultOption() {
if (Options is not null && Options.Count > 0) {
SelectedOption = Options.First();
}
}
public void Show() {
IsVisible = true;
AddDrawFlags(DrawFlags.RenderOnTop);
if (ParentAddon is not null) {
SetFocusable(ParentAddon);
}
}
public void Hide() {
IsVisible = false;
RemoveDrawFlags(DrawFlags.RenderOnTop);
if (ParentAddon is not null) {
ClearFocusable(ParentAddon);
}
}
public void Toggle(bool newState) {
if (newState) {
Show();
}
else {
Hide();
}
}
private bool isFocusSet;
public void SetFocusable(AtkUnitBase* addon) {
foreach (ref var focusableNode in addon->AdditionalFocusableNodes) {
if (focusableNode.Value is null) {
focusableNode = ResNode;
isFocusSet = true;
}
}
}
public void ClearFocusable(AtkUnitBase* addon) {
foreach (ref var focusableNode in addon->AdditionalFocusableNodes) {
if (focusableNode.Value == ResNode) {
focusableNode = null;
isFocusSet = false;
}
}
}
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()
);
}
}
@@ -0,0 +1,150 @@
using System.Numerics;
using KamiToolKit.Enums;
namespace KamiToolKit.Nodes;
public class CircleButtonNode : ButtonBase {
public readonly SimpleImageNode ImageNode;
public CircleButtonNode() {
ImageNode = new SimpleImageNode {
TexturePath = "ui/uld/CircleButtons.tex",
TextureSize = new Vector2(24.0f, 24.0f),
TextureCoordinates = new Vector2(0.0f, 112.0f),
WrapMode = WrapMode.Stretch,
};
ImageNode.AttachNode(this);
LoadTimelines();
InitializeComponentEvents();
}
public ButtonIcon Icon {
get;
set {
field = value;
var uldInfo = GetTextureCoordinateForIcon(value);
ImageNode.TextureCoordinates = uldInfo.TextureCoordinates;
ImageNode.TextureSize = uldInfo.TextureSize;
}
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
ImageNode.Size = Size;
}
private static UldTextureInfo GetTextureCoordinateForIcon(ButtonIcon icon) => icon switch {
ButtonIcon.GearCog => new UldTextureInfo(0.0f, 0.0f, 28.0f, 28.0f),
ButtonIcon.Filter => new UldTextureInfo(28.0f, 0.0f, 28.0f, 28.0f),
ButtonIcon.Sort => new UldTextureInfo(56.0f, 0.0f, 28.0f, 28.0f),
ButtonIcon.QuestionMark => new UldTextureInfo(84.0f, 0.0f, 28.0f, 28.0f),
ButtonIcon.Refresh => new UldTextureInfo(112.0f, 0.0f, 28.0f, 28.0f),
ButtonIcon.ChatBubble => new UldTextureInfo(140.0f, 0.0f, 28.0f, 28.0f),
ButtonIcon.LeftArrow => new UldTextureInfo(168.0f, 0.0f, 28.0f, 28.0f),
ButtonIcon.UpArrow => new UldTextureInfo(196.0f, 0.0f, 28.0f, 28.0f),
ButtonIcon.Chest => new UldTextureInfo(224.0f, 0.0f, 28.0f, 28.0f),
ButtonIcon.Document => new UldTextureInfo(0.0f, 28.0f, 28.0f, 28.0f),
ButtonIcon.Edit => new UldTextureInfo(28.0f, 28.0f, 28.0f, 28.0f),
ButtonIcon.Add => new UldTextureInfo(56.0f, 28.0f, 28.0f, 28.0f),
ButtonIcon.RightArrow => new UldTextureInfo(84.0f, 28.0f, 28.0f, 28.0f),
ButtonIcon.MusicNote => new UldTextureInfo(112.0f, 28.0f, 28.0f, 28.0f),
ButtonIcon.Sprout => new UldTextureInfo(140.0f, 28.0f, 28.0f, 28.0f),
ButtonIcon.Dice => new UldTextureInfo(168.0f, 28.0f, 28.0f, 28.0f),
ButtonIcon.ArrowDown => new UldTextureInfo(196.0f, 28.0f, 28.0f, 28.0f),
ButtonIcon.Eye => new UldTextureInfo(0.0f, 56.0f, 28.0f, 28.0f),
ButtonIcon.Envelope => new UldTextureInfo(28.0f, 56.0f, 28.0f, 28.0f),
ButtonIcon.Volume => new UldTextureInfo(56.0f, 56.0f, 28.0f, 28.0f),
ButtonIcon.Mute => new UldTextureInfo(84.0f, 56.0f, 28.0f, 28.0f),
ButtonIcon.WavePulse => new UldTextureInfo(112.0f, 56.0f, 28.0f, 28.0f),
ButtonIcon.CheckedBox => new UldTextureInfo(140.0f, 56.0f, 28.0f, 28.0f),
ButtonIcon.Cross => new UldTextureInfo(168.0f, 56.0f, 28.0f, 28.0f),
ButtonIcon.Globe => new UldTextureInfo(196.0f, 56.0f, 28.0f, 28.0f),
ButtonIcon.ActiveGearCog => new UldTextureInfo(0.0f, 84.0f, 28.0f, 28.0f),
ButtonIcon.ActiveFilter => new UldTextureInfo(28.0f, 84.0f, 28.0f, 28.0f),
ButtonIcon.Update => new UldTextureInfo(56.0f, 84.0f, 28.0f, 28.0f),
ButtonIcon.ActiveRing => new UldTextureInfo(84.0f, 84.0f, 28.0f, 28.0f),
ButtonIcon.Exclamation => new UldTextureInfo(112.0f, 84.0f, 28.0f, 28.0f),
ButtonIcon.InsetDocument => new UldTextureInfo(140.0f, 84.0f, 28.0f, 28.0f),
ButtonIcon.GearCogWithChatBubble => new UldTextureInfo(168.0f, 84.0f, 28.0f, 28.0f),
ButtonIcon.FlatbedCartBoxes => new UldTextureInfo(196.0f, 84.0f, 28.0f, 28.0f),
ButtonIcon.MagnifyingGlass => new UldTextureInfo(0.0f, 112.0f, 24.0f, 24.0f),
ButtonIcon.EditSmall => new UldTextureInfo(24.0f, 112.0f, 24.0f, 24.0f),
ButtonIcon.WeaponDraw => new UldTextureInfo(48.0f, 112.0f, 24.0f, 24.0f),
ButtonIcon.Headgear => new UldTextureInfo(72.0f, 112.0f, 24.0f, 24.0f),
ButtonIcon.Sword => new UldTextureInfo(96.0f, 112.0f, 24.0f, 24.0f),
ButtonIcon.Emotes => new UldTextureInfo(120.0f, 112.0f, 24.0f, 24.0f),
ButtonIcon.PersonStanding => new UldTextureInfo(144.0f, 112.0f, 24.0f, 24.0f),
ButtonIcon.PaintBucket => new UldTextureInfo(0.0f, 136.0f, 24.0f, 24.0f),
ButtonIcon.EyeSmall => new UldTextureInfo(24.0f, 136.0f, 24.0f, 24.0f),
ButtonIcon.Undo => new UldTextureInfo(48.0f, 136.0f, 24.0f, 24.0f),
ButtonIcon.PinPaper => new UldTextureInfo(72.0f, 136.0f, 24.0f, 24.0f),
ButtonIcon.CrossSmall => new UldTextureInfo(96.0f, 136.0f, 24.0f, 24.0f),
_ => new UldTextureInfo(0.0f, 0.0f, 28.0f, 28.0f),
};
private void LoadTimelines()
=> LoadTwoPartTimelines(this, ImageNode);
}
public enum ButtonIcon {
GearCog,
Filter,
Sort,
QuestionMark,
Refresh,
ChatBubble,
LeftArrow,
UpArrow,
Chest,
Document,
Edit,
Add,
RightArrow,
MusicNote,
Sprout,
Dice,
ArrowDown,
Eye,
Envelope,
Volume,
Mute,
WavePulse,
CheckedBox,
Cross,
Globe,
ActiveGearCog,
ActiveFilter,
Update,
ActiveRing,
Exclamation,
InsetDocument,
GearCogWithChatBubble,
FlatbedCartBoxes,
MagnifyingGlass,
EditSmall,
WeaponDraw,
Headgear,
Sword,
Emotes,
PersonStanding,
PaintBucket,
EyeSmall,
Undo,
PinPaper,
CrossSmall,
}
internal record UldTextureInfo(float PositionX = 0.0f, float PositionY = 0.0f, float Width = 0.0f, float Height = 0.0f) {
public Vector2 TextureCoordinates => new(PositionX, PositionY);
public Vector2 TextureSize => new(Width, Height);
}
@@ -0,0 +1,116 @@
using System.Numerics;
using Dalamud.Interface;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Premade.Color;
using KamiToolKit.Timelines;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
public unsafe class ColorOptionTextButtonNode : ButtonBase {
public readonly NineGridNode BackgroundNode;
public readonly TextNode LabelNode;
public readonly ColorPreviewNode ColorNode;
public ColorOptionTextButtonNode() {
BackgroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/ButtonA.tex",
TextureSize = new Vector2(100.0f, 28.0f),
LeftOffset = 16.0f,
RightOffset = 16.0f,
};
BackgroundNode.AttachNode(this);
ColorNode = new ColorPreviewNode {
DisableCollisionNode = true,
};
ColorNode.AttachNode(this);
LabelNode = new TextNode {
AlignmentType = AlignmentType.Center,
Position = new Vector2(16.0f, 3.0f),
};
LabelNode.AttachNode(this);
LoadTimelines();
Data->Nodes[0] = LabelNode.NodeId;
Data->Nodes[1] = BackgroundNode.NodeId;
InitializeComponentEvents();
}
public ReadOnlySeString String {
get => LabelNode.String;
set => LabelNode.String = value;
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
LabelNode.Size = new Vector2(Width - 32.0f, Height - 8.0f);
BackgroundNode.Size = Size;
ColorNode.Size = new Vector2(17.0f, 17.0f);
}
public ColorHelpers.HsvaColor? DefaultHsvaColor {
get => ColorNode.ColorHsva;
set => ColorNode.ColorHsva = value ?? default;
}
public Vector4? DefaultColor {
get => ColorNode.Color;
set => ColorNode.Color = value ?? default;
}
private void LoadTimelines() {
var foregroundPositionOffset = new Vector2(24.0f, 3.0f);
var colorElementPositionOffset = new Vector2(16.0f, 2.0f);
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 53)
.AddLabelPair(1, 10, 1)
.AddLabelPair(11, 17, 2)
.AddLabelPair(18, 26, 3)
.AddLabelPair(27, 36, 7)
.AddLabelPair(37, 46, 6)
.AddLabelPair(47, 53, 4)
.EndFrameSet()
.Build());
BackgroundNode.AddTimeline(new TimelineBuilder()
.AddFrameSetWithFrame(1, 10, 1, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f))
.BeginFrameSet(11, 17)
.AddFrame(11, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f))
.AddFrame(13, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f), addColor: new Vector3(16.0f))
.EndFrameSet()
.AddFrameSetWithFrame(18, 26, 18, new Vector2(0.0f, 1.0f), 255, new Vector3(16.0f))
.AddFrameSetWithFrame(27, 36, 27, Vector2.Zero, 178, multiplyColor: new Vector3(50.0f))
.AddFrameSetWithFrame(37, 46, 37, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f), addColor: new Vector3(16.0f))
.BeginFrameSet(47, 53)
.AddFrame(47, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f), addColor: new Vector3(16.0f))
.AddFrame(53, Vector2.Zero, 255, multiplyColor: new Vector3(100.0f))
.EndFrameSet()
.Build());
ColorNode.AddTimeline(new TimelineBuilder()
.AddFrameSetWithFrame(1, 10, 1, colorElementPositionOffset, 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(11, 17, 11, colorElementPositionOffset, 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(18, 26, 18, colorElementPositionOffset + new Vector2(0.0f, 1.0f), 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(27, 36, 27, colorElementPositionOffset, 153, multiplyColor: new Vector3(80.0f))
.AddFrameSetWithFrame(37, 46, 37, colorElementPositionOffset, 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(47, 53, 47, colorElementPositionOffset, 255, multiplyColor: new Vector3(100.0f))
.Build());
LabelNode.AddTimeline(new TimelineBuilder()
.AddFrameSetWithFrame(1, 10, 1, foregroundPositionOffset, 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(11, 17, 11, foregroundPositionOffset, 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(18, 26, 18, foregroundPositionOffset + new Vector2(0.0f, 1.0f), 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(27, 36, 27, foregroundPositionOffset, 153, multiplyColor: new Vector3(80.0f))
.AddFrameSetWithFrame(37, 46, 37, foregroundPositionOffset, 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(47, 53, 47, foregroundPositionOffset, 255, multiplyColor: new Vector3(100.0f))
.Build());
}
}
@@ -0,0 +1,112 @@
using System;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
public abstract unsafe class ComponentNode(NodeType nodeType) : NodeBase<AtkComponentNode>(nodeType) {
public abstract CollisionNode CollisionNode { get; }
public abstract AtkComponentBase* ComponentBase { get; }
public abstract AtkUldComponentDataBase* DataBase { get; }
}
public abstract unsafe class ComponentNode<T, TU> : ComponentNode where T : unmanaged, ICreatable where TU : unmanaged {
public sealed override CollisionNode CollisionNode { get; }
public sealed override AtkComponentBase* ComponentBase => Node->Component;
public sealed override AtkUldComponentDataBase* DataBase => Node->Component->UldManager.ComponentData;
protected ComponentNode() : base(NodeType.Component) {
Node->Component = (AtkComponentBase*) NativeMemoryHelper.Create<T>();
Node->Component->UldManager.ComponentData = (AtkUldComponentDataBase*)NativeMemoryHelper.UiAlloc<TU>();
ComponentBase->Initialize();
CollisionNode = new CollisionNode {
NodeId = 1,
LinkedComponent = ComponentBase,
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.HasCollision |
NodeFlags.RespondToMouse | NodeFlags.Focusable | NodeFlags.EmitsEvents | NodeFlags.Fill,
};
CollisionNode.ResNode->ParentNode = ResNode;
CollisionNode.ParentUldManager = &((AtkComponentBase*)Component)->UldManager;
ChildNodes.Add(CollisionNode);
ComponentBase->OwnerNode = Node;
ComponentBase->ComponentFlags = 1;
ref var uldManager = ref ComponentBase->UldManager;
uldManager.Objects = (AtkUldObjectInfo*)NativeMemoryHelper.UiAlloc<AtkUldComponentInfo>();
ref var objects = ref uldManager.Objects;
uldManager.ObjectCount = 1;
SetInternalComponentType(ComponentType.Base);
objects->NodeList = (AtkResNode**)NativeMemoryHelper.Malloc(8);
objects->NodeList[0] = CollisionNode;
objects->NodeCount = 1;
objects->Id = 1000;
uldManager.InitializeResourceRendererManager();
uldManager.RootNode = CollisionNode;
uldManager.UpdateDrawNodeList();
uldManager.ResourceFlags = AtkUldManagerResourceFlag.Initialized | AtkUldManagerResourceFlag.ArraysAllocated;
uldManager.LoadedState = AtkLoadState.Loaded;
}
protected override void Dispose(bool disposing, bool isNativeDestructor) {
if (disposing) {
try {
if (!isNativeDestructor) {
Node->Component->Deinitialize();
Node->Component->Dtor(1);
Node->Component = null;
}
}
catch (Exception e) {
Log.Exception(e);
} finally {
base.Dispose(disposing, isNativeDestructor);
}
}
}
public static implicit operator AtkEventListener*(ComponentNode<T, TU> node) => &node.ComponentBase->AtkEventListener;
public static implicit operator T*(ComponentNode<T, TU> node) => node.Component;
public static implicit operator TU*(ComponentNode<T, TU> node) => node.Data;
protected void SetInternalComponentType(ComponentType type) {
var componentInfo = (AtkUldComponentInfo*)ComponentBase->UldManager.Objects;
componentInfo->ComponentType = type;
}
protected void InitializeComponentEvents() {
ComponentBase->InitializeFromComponentData(DataBase);
ComponentBase->Setup();
ComponentBase->SetEnabledState(true);
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
CollisionNode.Size = Size;
ComponentBase->UldManager.RootNodeHeight = (ushort)Height;
ComponentBase->UldManager.RootNodeWidth = (ushort)Width;
}
public virtual bool IsEnabled {
get => NodeFlags.HasFlag(NodeFlags.Enabled);
set => ComponentBase->SetEnabledState(value);
}
public override int ChildCount => ComponentBase->UldManager.NodeListCount;
public T* Component => (T*)ComponentBase;
public TU* Data => (TU*)DataBase;
}
+461
View File
@@ -0,0 +1,461 @@
using System;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Enums;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public abstract unsafe class DropDownNode<T, TU> : SimpleComponentNode where T : ButtonListNode<TU>, new() {
public readonly NineGridNode BackgroundNode;
public readonly ImageNode CollapseArrowNode;
public readonly CollisionNode DropDownFocusCollisionNode;
public readonly TextNode LabelNode;
public readonly T OptionListNode;
protected DropDownNode() {
BackgroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/DropDownA.tex",
TextureSize = new Vector2(44.0f, 23.0f),
TextureCoordinates = new Vector2(0.0f, 0.0f),
Size = new Vector2(250.0f, 24.0f),
Height = 23.0f,
LeftOffset = 16.0f,
RightOffset = 16.0f,
};
BackgroundNode.AttachNode(this);
CollapseArrowNode = new SimpleImageNode {
TexturePath = "ui/uld/DropDownA.tex",
TextureCoordinates = new Vector2(44.0f, 0.0f),
TextureSize = new Vector2(12.0f, 12.0f),
Position = new Vector2(6.0f, 17.0f),
Size = new Vector2(12.0f, 12.0f),
WrapMode = WrapMode.Stretch,
};
CollapseArrowNode.AttachNode(this);
LabelNode = new TextNode {
Position = new Vector2(20.0f, 0.0f),
Size = new Vector2(218.0f, 21.0f),
FontType = FontType.Axis,
FontSize = 12,
AlignmentType = AlignmentType.Left,
TextColor = ColorHelper.GetColor(50),
TextOutlineColor = ColorHelper.GetColor(7),
String = "Demo",
};
LabelNode.AttachNode(this);
OptionListNode = new T {
NodeId = NodeIdBase,
Position = new Vector2(4.0f, 21.0f),
Size = new Vector2(242.0f, 243.0f),
IsVisible = false,
};
OptionListNode.AttachNode(this);
DropDownFocusCollisionNode = new CollisionNode();
DropDownFocusCollisionNode.AttachNode(OptionListNode.CollisionNode, NodePosition.AfterTarget);
DropDownFocusCollisionNode.AddEvent(AtkEventType.MouseDown, Toggle);
DropDownFocusCollisionNode.AddEvent(AtkEventType.MouseWheel, Toggle);
BuildTimelines();
Timeline?.PlayAnimation(4);
CollisionNode.ShowClickableCursor = true;
CollisionNode.AddEvent(AtkEventType.MouseOver, () => Timeline?.PlayAnimation(IsCollapsed ? 2 : 9));
CollisionNode.AddEvent(AtkEventType.MouseOut, () => Timeline?.PlayAnimation(IsCollapsed ? 4 : 11));
CollisionNode.AddEvent(AtkEventType.MouseClick, Toggle);
Component->SoundEffectId = 1;
Component->SetEnabledState(true);
}
public bool IsCollapsed { get; set; } = true;
public int MaxListOptions {
get => OptionListNode.MaxButtons;
set => OptionListNode.MaxButtons = value;
}
public TU? SelectedOption {
get => OptionListNode.SelectedOption;
set {
OptionListNode.SelectedOption = value;
UpdateLabel(value);
}
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
CollisionNode.Size = Size;
BackgroundNode.Size = new Vector2(Width, Height - 1.0f);
LabelNode.Size = new Vector2(Width - 32.0f, Height - 3.0f);
OptionListNode.Width = Width - 8.0f;
OptionListNode.Position = new Vector2(4.0f, Height - 3.0f);
}
public Action<bool>? OnCollapseToggled { get; set; }
public Action? OnUncollapsed { get; set; }
public Action? OnCollapsed { get; set; }
public void Collapse(bool playSoundEffect = true) {
if (!IsEnabled) return;
if (IsCollapsed) return;
IsCollapsed = true;
Timeline?.PlayAnimation(4);
OptionListNode.Toggle(false);
// TODO: replace this (and in Uncollapse) with just a check for playSoundEffect and a call to Component->PlaySoundEffect();
// when https://github.com/aers/FFXIVClientStructs/commit/e5b6fc51 landed in Dalamud
if (playSoundEffect && Component->SoundEffectId is not -1)
UIGlobals.PlaySoundEffect((uint)Component->SoundEffectId);
OptionListNode.ReattachNode(this);
// Need to reset position after reattaching, so screen position is recalculated correctly
OptionListNode.Position = Size with { X = 0.0f } + new Vector2(4.0f, -4.0f);
OnCollapsed?.Invoke();
}
public void Uncollapse(bool playSoundEffect = true) {
if (!IsEnabled) return;
if (!IsCollapsed) return;
IsCollapsed = false;
Timeline?.PlayAnimation(11);
OptionListNode.Toggle(true);
if (playSoundEffect && Component->SoundEffectId is not -1)
UIGlobals.PlaySoundEffect((uint)Component->SoundEffectId);
if (ParentAddon is not null) {
OptionListNode.Position = (ScreenPosition - ParentAddon->Position) / ParentAddon->Scale + Size with { X = 0.0f } + new Vector2(4.0f, -4.0f);
MoveListOnScreen();
DropDownFocusCollisionNode.Position = -OptionListNode.Position;
DropDownFocusCollisionNode.Size = ParentAddon->RootSize;
OptionListNode.ReattachNode(ParentAddon->RootNode);
}
OnUncollapsed?.Invoke();
}
public void Toggle() {
Toggle(true);
}
public void Toggle(bool playSoundEffect) {
if (!IsEnabled) return;
if (IsCollapsed) {
Uncollapse(playSoundEffect);
}
else {
Collapse(playSoundEffect);
}
OnCollapseToggled?.Invoke(IsCollapsed);
}
public void RecalculateScrollParams()
=> OptionListNode.RecalculateScrollParams();
private void MoveListOnScreen() {
var screenSize = AtkStage.Instance()->ScreenSize;
var parentAddon = RaptureAtkUnitManager.Instance()->GetAddonByNode(ResNode);
if (parentAddon == null) {
return;
}
var scale = parentAddon->Scale;
var scaledListSize = OptionListNode.Size * scale;
if (ScreenPosition.X + scaledListSize.X > screenSize.Width) {
OptionListNode.X += (screenSize.Width - OptionListNode.ScreenPosition.X - scaledListSize.X - 4f) / scale;
}
else if (ScreenPosition.X < 0) {
OptionListNode.X -= OptionListNode.ScreenPosition.X / scale;
}
if (OptionListNode.ScreenPosition.Y + scaledListSize.Y > screenSize.Height) {
OptionListNode.Y += (screenSize.Height - OptionListNode.ScreenPosition.Y - scaledListSize.Y) / scale;
}
}
protected abstract void UpdateLabel(TU? option);
private void BuildTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 120)
.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(120, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.EndFrameSet()
.Build()
);
CollapseArrowNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 9)
.AddFrame(1, new Vector2(6, 17))
.AddFrame(1, rotation: 4.712389f)
.AddFrame(1, alpha: 255)
.AddFrame(1, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(10, 19)
.AddFrame(10, new Vector2(6, 17))
.AddFrame(12, new Vector2(6, 17))
.AddFrame(10, rotation: 4.712389f)
.AddFrame(12, rotation: 4.712389f)
.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, new Vector2(6, 18))
.AddFrame(20, rotation: 4.712389f)
.AddFrame(20, alpha: 255)
.AddFrame(20, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(30, 39)
.AddFrame(30, new Vector2(6, 17))
.AddFrame(30, rotation: 4.712389f)
.AddFrame(30, alpha: 178)
.AddFrame(30, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(50, 50, 50))
.EndFrameSet()
.BeginFrameSet(40, 49)
.AddFrame(40, new Vector2(6, 17))
.AddFrame(40, rotation: 4.712389f)
.AddFrame(40, alpha: 255)
.AddFrame(40, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(50, 59)
.AddFrame(50, new Vector2(6, 17))
.AddFrame(52, new Vector2(6, 17))
.AddFrame(50, rotation: 4.712389f)
.AddFrame(52, rotation: 4.712389f)
.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, new Vector2(6, 6))
.AddFrame(60, rotation: 0)
.AddFrame(60, alpha: 255)
.AddFrame(60, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(70, 79)
.AddFrame(70, new Vector2(6, 6))
.AddFrame(72, new Vector2(6, 6))
.AddFrame(70, rotation: 0)
.AddFrame(72, rotation: 0)
.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, new Vector2(6, 7))
.AddFrame(80, rotation: 0)
.AddFrame(80, alpha: 255)
.AddFrame(80, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(90, 99)
.AddFrame(90, new Vector2(6, 6))
.AddFrame(90, rotation: 0)
.AddFrame(90, alpha: 178)
.AddFrame(90, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(50, 50, 50))
.EndFrameSet()
.BeginFrameSet(100, 109)
.AddFrame(100, new Vector2(6, 6))
.AddFrame(100, rotation: 0)
.AddFrame(100, alpha: 255)
.AddFrame(100, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(110, 120)
.AddFrame(110, new Vector2(6, 6))
.AddFrame(112, new Vector2(6, 6))
.AddFrame(110, rotation: 0)
.AddFrame(112, rotation: 0)
.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()
);
LabelNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 9)
.AddFrame(1, new Vector2(20, 0))
.AddFrame(1, alpha: 255)
.AddFrame(1, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(10, 19)
.AddFrame(10, new Vector2(20, 0))
.AddFrame(10, alpha: 255)
.AddFrame(10, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(20, 29)
.AddFrame(20, new Vector2(20, 1))
.AddFrame(20, alpha: 255)
.AddFrame(20, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(30, 39)
.AddFrame(30, new Vector2(20, 0))
.AddFrame(30, alpha: 153)
.AddFrame(30, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(80, 80, 80))
.EndFrameSet()
.BeginFrameSet(40, 49)
.AddFrame(40, new Vector2(20, 0))
.AddFrame(40, alpha: 255)
.AddFrame(40, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(50, 59)
.AddFrame(50, new Vector2(20, 0))
.AddFrame(50, alpha: 255)
.AddFrame(50, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(60, 69)
.AddFrame(60, new Vector2(20, 0))
.AddFrame(60, alpha: 255)
.AddFrame(60, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(70, 79)
.AddFrame(70, new Vector2(20, 0))
.AddFrame(70, alpha: 255)
.AddFrame(70, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(80, 89)
.AddFrame(80, new Vector2(20, 1))
.AddFrame(80, alpha: 255)
.AddFrame(80, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(90, 99)
.AddFrame(90, new Vector2(20, 0))
.AddFrame(90, alpha: 153)
.AddFrame(90, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(80, 80, 80))
.EndFrameSet()
.BeginFrameSet(100, 109)
.AddFrame(100, new Vector2(20, 0))
.AddFrame(100, alpha: 255)
.AddFrame(100, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(110, 120)
.AddFrame(110, new Vector2(20, 0))
.AddFrame(110, alpha: 255)
.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, new Vector2(0, 0))
.AddFrame(1, alpha: 255)
.AddFrame(1, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(10, 19)
.AddFrame(10, new Vector2(0, 0))
.AddFrame(12, new Vector2(0, 0))
.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, new Vector2(0, 1))
.AddFrame(20, alpha: 255)
.AddFrame(20, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(30, 39)
.AddFrame(30, new Vector2(0, 0))
.AddFrame(30, alpha: 178)
.AddFrame(30, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(50, 50, 50))
.EndFrameSet()
.BeginFrameSet(40, 49)
.AddFrame(40, new Vector2(0, 0))
.AddFrame(40, alpha: 255)
.AddFrame(40, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(50, 59)
.AddFrame(50, new Vector2(0, 0))
.AddFrame(52, new Vector2(0, 0))
.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, new Vector2(0, 0))
.AddFrame(60, alpha: 255)
.AddFrame(60, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(70, 79)
.AddFrame(70, new Vector2(0, 0))
.AddFrame(72, new Vector2(0, 0))
.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, new Vector2(0, 1))
.AddFrame(80, alpha: 255)
.AddFrame(80, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(90, 99)
.AddFrame(90, new Vector2(0, 0))
.AddFrame(90, alpha: 178)
.AddFrame(90, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(50, 50, 50))
.EndFrameSet()
.BeginFrameSet(100, 109)
.AddFrame(100, new Vector2(0, 0))
.AddFrame(100, alpha: 255)
.AddFrame(100, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(110, 120)
.AddFrame(110, new Vector2(0, 0))
.AddFrame(112, new Vector2(0, 0))
.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,9 @@
using System;
namespace KamiToolKit.Nodes;
public class EnumButtonListNode<T> : ButtonListNode<T> where T : Enum {
protected override string GetLabelForOption(T option)
=> option.Description;
}
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
namespace KamiToolKit.Nodes;
public class EnumDropDownNode<T> : DropDownNode<EnumButtonListNode<T>, T> where T : Enum{
public EnumDropDownNode() {
OptionListNode.OnOptionSelected += OptionSelectedHandler;
}
public Action<T>? OnOptionSelected { get; set; }
public required List<T>? Options {
get => OptionListNode.Options;
set {
OptionListNode.Options = value;
OptionListNode.SelectDefaultOption();
UpdateLabel(OptionListNode.SelectedOption);
}
}
private void OptionSelectedHandler(T option) {
OnOptionSelected?.Invoke(option);
UpdateLabel(option);
Toggle(false);
}
protected override void UpdateLabel(T? option) {
LabelNode.String = option?.Description;
}
}
@@ -0,0 +1,270 @@
using System;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Timelines;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
public unsafe class HoldButtonNode : ComponentNode<AtkComponentHoldButton, AtkUldComponentDataHoldButton> {
public readonly NineGridNode BackgroundNode;
public readonly NineGridNode FrameNode;
public readonly HoldButtonProgressNode ProgressNode;
public readonly TextNode TextNode;
public HoldButtonNode() {
SetInternalComponentType(ComponentType.HoldButton);
BackgroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/LongPressButtonA.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
TextureSize = new Vector2(100.0f, 36.0f),
Size = new Vector2(100.0f, 36.0f),
LeftOffset = 16,
RightOffset = 16,
};
BackgroundNode.AttachNode(this);
ProgressNode = new HoldButtonProgressNode {
Size = new Vector2(100.0f, 36.0f),
};
ProgressNode.AttachNode(this);
FrameNode = new SimpleNineGridNode {
TexturePath = "ui/uld/LongPressButtonA.tex",
TextureCoordinates = new Vector2(0.0f, 72.0f),
TextureSize = new Vector2(100.0f, 36.0f),
Size = new Vector2(100.0f, 36.0f),
};
FrameNode.AttachNode(this);
TextNode = new TextNode {
Position = new Vector2(16.0f, 8.0f),
Size = new Vector2(68.0f, 20.0f),
AlignmentType = AlignmentType.Center,
String = "OK",
};
TextNode.AttachNode(this);
Data->Nodes[0] = TextNode.NodeId;
Data->Nodes[1] = BackgroundNode.NodeId;
Data->Nodes[2] = ProgressNode.NodeId;
Data->Nodes[3] = ProgressNode.ImageNode.NodeId;
InitializeComponentEvents();
AddEvent(AtkEventType.ButtonClick, ClickHandler);
BuildTimelines();
}
public bool UnlockAfterClick { get; set; }
public Action? OnClick { get; set; }
public ReadOnlySeString String {
get => TextNode.String;
set => TextNode.String = value;
}
private void ClickHandler() {
OnClick?.Invoke();
if (UnlockAfterClick) {
Reset();
}
}
public void Reset() {
Component->IsTargetReached = false;
Component->IsEventFired = false;
Component->Progress.StartValue = 0;
Component->Progress.TargetValue = 0;
Component->Progress.CurrentValue = 0;
Component->Progress.EndValue = 0;
}
private void BuildTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 20)
.AddLabel(1, 17, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(10, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(11, 101, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(20, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.EndFrameSet()
.Build()
);
BackgroundNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 10)
.AddFrame(1, new Vector2(0, 0))
.AddFrame(1, alpha: 255)
.AddFrame(1, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(11, 17)
.AddFrame(11, new Vector2(0, 0))
.AddFrame(13, new Vector2(0, 0))
.AddFrame(11, alpha: 255)
.AddFrame(13, alpha: 255)
.AddFrame(11, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(13, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(18, 26)
.AddFrame(18, new Vector2(0, 1))
.AddFrame(18, alpha: 255)
.AddFrame(18, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(27, 36)
.AddFrame(27, new Vector2(0, 0))
.AddFrame(27, alpha: 178)
.AddFrame(27, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(50, 50, 50))
.EndFrameSet()
.BeginFrameSet(37, 46)
.AddFrame(37, new Vector2(0, 0))
.AddFrame(37, alpha: 255)
.AddFrame(37, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(47, 53)
.AddFrame(47, new Vector2(0, 0))
.AddFrame(53, new Vector2(0, 0))
.AddFrame(47, alpha: 255)
.AddFrame(53, alpha: 255)
.AddFrame(47, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(53, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(54, 64)
.AddFrame(54, new Vector2(0, 0))
.AddFrame(54, alpha: 255)
.AddFrame(54, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(65, 71)
.AddFrame(65, new Vector2(0, 0))
.AddFrame(71, new Vector2(0, 0))
.AddFrame(65, alpha: 255)
.AddFrame(71, alpha: 255)
.AddFrame(65, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(71, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.Build()
);
ProgressNode.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()
);
FrameNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 10)
.AddFrame(1, new Vector2(0, 0))
.AddFrame(1, alpha: 255)
.AddFrame(1, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(11, 17)
.AddFrame(11, new Vector2(0, 0))
.AddFrame(13, new Vector2(0, 0))
.AddFrame(11, alpha: 255)
.AddFrame(13, alpha: 255)
.AddFrame(11, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(13, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(95, 95, 95))
.EndFrameSet()
.BeginFrameSet(18, 26)
.AddFrame(18, new Vector2(0, 0))
.AddFrame(18, alpha: 255)
.AddFrame(18, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(95, 95, 95))
.EndFrameSet()
.BeginFrameSet(27, 36)
.AddFrame(27, new Vector2(0, 0))
.AddFrame(27, alpha: 178)
.AddFrame(27, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(50, 50, 50))
.EndFrameSet()
.BeginFrameSet(37, 46)
.AddFrame(37, new Vector2(0, 0))
.AddFrame(37, alpha: 255)
.AddFrame(37, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(95, 95, 95))
.EndFrameSet()
.BeginFrameSet(47, 53)
.AddFrame(47, new Vector2(0, 0))
.AddFrame(53, new Vector2(0, 0))
.AddFrame(47, alpha: 255)
.AddFrame(53, alpha: 255)
.AddFrame(47, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(95, 95, 95))
.AddFrame(53, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(54, 64)
.AddFrame(54, new Vector2(0, 0))
.AddFrame(54, alpha: 255)
.AddFrame(54, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(95, 95, 95))
.EndFrameSet()
.BeginFrameSet(65, 71)
.AddFrame(65, new Vector2(0, 0))
.AddFrame(71, new Vector2(0, 0))
.AddFrame(65, alpha: 255)
.AddFrame(71, alpha: 255)
.AddFrame(65, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(95, 95, 95))
.AddFrame(71, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.Build()
);
TextNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 10)
.AddFrame(1, new Vector2(16, 8))
.AddFrame(1, alpha: 255)
.AddFrame(1, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(11, 17)
.AddFrame(11, new Vector2(16, 8))
.AddFrame(11, alpha: 255)
.AddFrame(11, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(18, 26)
.AddFrame(18, new Vector2(16, 9))
.AddFrame(18, alpha: 255)
.AddFrame(18, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(27, 36)
.AddFrame(27, new Vector2(16, 8))
.AddFrame(27, alpha: 153)
.AddFrame(27, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(80, 80, 80))
.EndFrameSet()
.BeginFrameSet(37, 46)
.AddFrame(37, new Vector2(16, 8))
.AddFrame(37, alpha: 255)
.AddFrame(37, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(47, 53)
.AddFrame(47, new Vector2(16, 8))
.AddFrame(47, alpha: 255)
.AddFrame(47, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(54, 64)
.AddFrame(54, new Vector2(16, 8))
.AddFrame(54, alpha: 255)
.AddFrame(54, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(65, 71)
.AddFrame(65, new Vector2(16, 8))
.AddFrame(65, alpha: 255)
.AddFrame(65, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.Build()
);
}
}
@@ -0,0 +1,51 @@
using System.Numerics;
namespace KamiToolKit.Nodes;
/// <summary>
/// Uses a GameIconId to display that icon as the decorator for the button.
/// </summary>
public class IconButtonNode : ButtonBase {
public readonly NineGridNode BackgroundNode;
public readonly IconImageNode ImageNode;
public IconButtonNode() {
BackgroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/BgParts.tex",
TextureSize = new Vector2(32.0f, 32.0f),
TextureCoordinates = new Vector2(33.0f, 65.0f),
TopOffset = 8.0f,
LeftOffset = 8.0f,
RightOffset = 8.0f,
BottomOffset = 8.0f,
};
BackgroundNode.AttachNode(this);
ImageNode = new IconImageNode {
TextureSize = new Vector2(32.0f, 32.0f),
FitTexture = true,
};
ImageNode.AttachNode(this);
LoadTimelines();
InitializeComponentEvents();
}
public uint IconId {
get => ImageNode.IconId;
set => ImageNode.IconId = value;
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
ImageNode.Size = Size - new Vector2(16.0f, 16.0f);
ImageNode.Position = BackgroundNode.Position + new Vector2(BackgroundNode.LeftOffset, BackgroundNode.TopOffset);
BackgroundNode.Size = Size;
}
private void LoadTimelines()
=> LoadThreePartTimelines(this, BackgroundNode, ImageNode, new Vector2(8.0f, 8.0f));
}
+133
View File
@@ -0,0 +1,133 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Enums;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public unsafe class IconNode : ComponentNode<AtkComponentIcon, AtkUldComponentDataIcon> {
public readonly IconExtras IconExtras;
public readonly IconImageNode IconImage;
public readonly IconIndicator IconIndicator1;
public readonly IconIndicator IconIndicator2;
public IconNode() {
SetInternalComponentType(ComponentType.Icon);
IconImage = new IconImageNode {
NodeId = 20,
Size = new Vector2(40.0f, 40.0f),
Position = new Vector2(2.0f, 3.0f),
WrapMode = WrapMode.Tile,
ImageNodeFlags = ImageNodeFlags.AutoFit,
};
IconImage.AttachNode(this);
IconExtras = new IconExtras {
NodeId = 6,
Size = new Vector2(60, 60),
Position = new Vector2(-2.0f, 0.0f),
};
IconExtras.AttachNode(this);
IconIndicator1 = new IconIndicator(5) {
NodeId = 4,
Size = new Vector2(18.0f, 18.0f),
Position = new Vector2(27.0f, 11.0f),
};
IconIndicator1.AttachNode(this);
IconIndicator2 = new IconIndicator(3) {
NodeId = 2,
Size = new Vector2(18.0f, 18.0f),
Position = new Vector2(27.0f, -2.0f),
};
IconIndicator2.AttachNode(this);
BuildTimeline();
Data->Nodes[0] = IconImage.NodeId;
Data->Nodes[1] = IconExtras.CooldownNode.NodeId;
Data->Nodes[2] = IconExtras.NodeId;
Data->Nodes[3] = IconExtras.ResourceCostTextNode.NodeId;
Data->Nodes[4] = IconExtras.QuantityTextNode.NodeId;
Data->Nodes[5] = IconExtras.AntsNode.NodeId;
Data->Nodes[6] = IconIndicator1.IconNode.NodeId;
Data->Nodes[7] = IconIndicator2.IconNode.NodeId;
InitializeComponentEvents();
}
public uint IconId {
get => Component->IconId;
set => Component->LoadIcon(value);
}
public bool IsIconLoading
=> Component->Flags.HasFlag(IconComponentFlags.IsIconLoading);
public bool IsIconDisabled {
get => Component->Flags.HasFlag(IconComponentFlags.IsDisabled);
set => Component->SetIconImageDisableState(value);
}
public byte ComboLevel {
get {
if (Component->Flags.HasFlag(IconComponentFlags.ComboLevel3))
return 3;
if (Component->Flags.HasFlag(IconComponentFlags.ComboLevel2))
return 2;
if (Component->Flags.HasFlag(IconComponentFlags.ComboLevel1))
return 1;
return 0;
}
set => Component->SetComboLevel(value is >= 1 and <= 3, (byte)(value - 1));
}
public bool IsMacro {
get => Component->Flags.HasFlag(IconComponentFlags.IsMacro);
set => Component->SetIsMacro(value);
}
public bool IsRecipe {
get => Component->Flags.HasFlag(IconComponentFlags.IsRecipe);
set => Component->SetIsRecipe(value);
}
public bool IsBeingDragged
=> Component->Flags.HasFlag(IconComponentFlags.IsBeingDragged);
private void BuildTimeline() {
IconExtras.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 59)
.AddLabelPair(1, 9, 1)
.AddLabelPair(10, 19, 2)
.AddLabelPair(20, 29, 3)
.AddLabelPair(30, 39, 7)
.AddLabelPair(40, 49, 6)
.AddLabelPair(50, 59, 4)
.EndFrameSet()
.Build());
var iconIndicatorTimeline = new TimelineBuilder()
.BeginFrameSet(1, 129)
.AddLabel(1, 17, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(11, 101, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(21, 102, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(31, 103, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(41, 104, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(51, 105, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(61, 106, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(71, 107, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(80, 108, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(90, 109, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(100, 110, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(110, 111, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(120, 112, AtkTimelineJumpBehavior.PlayOnce, 0)
.EndFrameSet();
IconIndicator1.AddTimeline(iconIndicatorTimeline.Build());
IconIndicator2.AddTimeline(iconIndicatorTimeline.Build());
}
}
@@ -0,0 +1,81 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Nodes;
public class IconToggleNode : SimpleComponentNode {
private readonly IconImageNode iconNode;
private readonly ClippingMaskNode clipNode;
private readonly SimpleImageNode highlightNode; // For selected
private readonly SimpleImageNode lowlightNode; // For unselected
public IconToggleNode() {
iconNode = new IconImageNode {
TextureSize = new Vector2(36.0f, 36.0f),
FitTexture = true,
};
iconNode.AttachNode(this);
clipNode = new SimpleClippingMaskNode {
TextureCoordinates = Vector2.Zero,
TextureSize = new Vector2(32.0f, 32.0f),
TexturePath = "ui/uld/BgPartsMask.tex",
Size = new Vector2(32.0f, 32.0f),
};
clipNode.AttachNode(this);
highlightNode = new SimpleImageNode {
Size = new Vector2(36.0f, 36.0f),
IsVisible = false,
TextureCoordinates = new Vector2(69.0f, 1.0f),
TextureSize = new Vector2(36.0f, 36.0f),
TexturePath = "ui/uld/BgParts.tex",
};
highlightNode.AttachNode(this);
lowlightNode = new SimpleImageNode {
Size = new Vector2(36.0f, 36.0f),
IsVisible = false,
TextureCoordinates = new Vector2(141.0f, 1.0f),
TextureSize = new Vector2(36.0f, 36.0f),
TexturePath = "ui/uld/BgParts.tex",
};
lowlightNode.AttachNode(this);
CollisionNode.AddEvent(AtkEventType.MouseClick, () => UIGlobals.PlaySoundEffect(1));
}
public uint IconId {
get => iconNode.IconId;
set => iconNode.IconId = value;
}
public bool IsToggled {
get;
set {
field = value;
highlightNode.IsVisible = value;
lowlightNode.IsVisible = !value;
}
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
// Icon is 32x32 centered within the 36x36 node
var iconSize = Size - new Vector2(4.0f, 4.0f);
var iconOffset = new Vector2(2.0f, 2.0f);
iconNode.Size = iconSize;
iconNode.Position = iconOffset;
clipNode.Size = iconSize;
clipNode.Position = iconOffset;
highlightNode.Size = Size;
highlightNode.Position = Vector2.Zero;
lowlightNode.Size = Size;
lowlightNode.Position = Vector2.Zero;
}
}
@@ -0,0 +1,59 @@
using System.Numerics;
using Dalamud.Interface.Textures.TextureWraps;
namespace KamiToolKit.Nodes;
public class ImGuiIconButtonNode : ButtonBase {
public readonly NineGridNode BackgroundNode;
public readonly ImGuiImageNode ImageNode;
public ImGuiIconButtonNode() {
BackgroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/BgParts.tex",
TextureSize = new Vector2(32.0f, 32.0f),
TextureCoordinates = new Vector2(33.0f, 65.0f),
TopOffset = 8.0f,
LeftOffset = 8.0f,
RightOffset = 8.0f,
BottomOffset = 8.0f,
};
BackgroundNode.AttachNode(this);
ImageNode = new ImGuiImageNode {
FitTexture = true,
};
ImageNode.AttachNode(this);
LoadTimelines();
InitializeComponentEvents();
}
public bool ShowBackground {
get => BackgroundNode.IsVisible;
set => BackgroundNode.IsVisible = value;
}
public string TexturePath {
get => ImageNode.TexturePath;
set => ImageNode.TexturePath = value;
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
ImageNode.Size = Size - new Vector2(16.0f, 16.0f);
ImageNode.Position = BackgroundNode.Position + new Vector2(BackgroundNode.LeftOffset, BackgroundNode.TopOffset);
BackgroundNode.Size = Size;
}
public void LoadTexture(IDalamudTextureWrap texture)
=> ImageNode.LoadTexture(texture);
public void LoadTextureFromFile(string path)
=> ImageNode.LoadTextureFromFile(path);
private void LoadTimelines()
=> LoadThreePartTimelines(this, BackgroundNode, ImageNode, new Vector2(8.0f, 8.0f));
}
@@ -0,0 +1,192 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Timelines;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
public unsafe class ListButtonNode : ButtonBase {
public readonly NineGridNode HoverBackgroundNode;
public readonly TextNode LabelNode;
public readonly NineGridNode SelectedBackgroundNode;
public ListButtonNode() {
HoverBackgroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/ListItemA.tex",
TextureCoordinates = new Vector2(0.0f, 22.0f),
TextureSize = new Vector2(64.0f, 22.0f),
LeftOffset = 16,
RightOffset = 1,
};
HoverBackgroundNode.AttachNode(this);
SelectedBackgroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/ListItemA.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
TextureSize = new Vector2(64.0f, 22.0f),
LeftOffset = 16,
RightOffset = 1,
};
SelectedBackgroundNode.AttachNode(this);
LabelNode = new TextNode {
Position = new Vector2(10.0f, 1.0f),
TextColor = ColorHelper.GetColor(8),
TextOutlineColor = ColorHelper.GetColor(7),
FontType = FontType.Axis,
FontSize = 14,
AlignmentType = AlignmentType.Left,
String = "Label Not Set",
};
LabelNode.AttachNode(this);
LoadTimelines();
InitializeComponentEvents();
}
public bool Selected {
get => Component->IsChecked;
set => Component->SetChecked(value);
}
public ReadOnlySeString String {
get => LabelNode.String;
set => LabelNode.String = value;
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
HoverBackgroundNode.Size = Size;
SelectedBackgroundNode.Size = Size;
LabelNode.Size = new Vector2(Width - 10.0f, Height - 1.0f);
}
private void LoadTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 120)
.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(120, 0, AtkTimelineJumpBehavior.PlayOnce, 0)
.EndFrameSet()
.Build()
);
HoverBackgroundNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 9)
.AddFrame(1, alpha: 0)
.EndFrameSet()
.BeginFrameSet(10, 19)
.AddFrame(10, alpha: 0)
.AddFrame(13, alpha: 255)
.EndFrameSet()
.BeginFrameSet(20, 29)
.AddFrame(20, alpha: 255)
.EndFrameSet()
.BeginFrameSet(40, 49)
.AddFrame(40, alpha: 255)
.EndFrameSet()
.BeginFrameSet(50, 59)
.AddFrame(50, alpha: 255)
.AddFrame(52, alpha: 0)
.EndFrameSet()
.Build()
);
SelectedBackgroundNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(60, 69)
.AddFrame(60, alpha: 214)
.AddFrame(60, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(70, 79)
.AddFrame(70, alpha: 214)
.AddFrame(72, alpha: 255)
.AddFrame(70, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(72, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(80, 89)
.AddFrame(80, alpha: 255)
.AddFrame(80, addColor: new Vector3(0, 0, 0), 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(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(110, 120)
.AddFrame(110, alpha: 255)
.AddFrame(112, alpha: 214)
.AddFrame(110, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(112, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.Build()
);
LabelNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 9)
.AddFrame(1, alpha: 255)
.EndFrameSet()
.BeginFrameSet(10, 19)
.AddFrame(10, alpha: 255)
.EndFrameSet()
.BeginFrameSet(20, 29)
.AddFrame(20, alpha: 255)
.EndFrameSet()
.BeginFrameSet(30, 39)
.AddFrame(30, alpha: 127)
.EndFrameSet()
.BeginFrameSet(40, 49)
.AddFrame(40, alpha: 255)
.EndFrameSet()
.BeginFrameSet(50, 59)
.AddFrame(50, alpha: 255)
.EndFrameSet()
.BeginFrameSet(60, 69)
.AddFrame(60, alpha: 255)
.EndFrameSet()
.BeginFrameSet(70, 79)
.AddFrame(70, alpha: 255)
.EndFrameSet()
.BeginFrameSet(80, 89)
.AddFrame(80, alpha: 255)
.EndFrameSet()
.BeginFrameSet(90, 99)
.AddFrame(90, alpha: 127)
.EndFrameSet()
.BeginFrameSet(100, 109)
.AddFrame(100, alpha: 255)
.EndFrameSet()
.BeginFrameSet(110, 120)
.AddFrame(110, alpha: 255)
.EndFrameSet()
.Build()
);
}
}
@@ -0,0 +1,40 @@
using System.Linq;
using KamiToolKit.Classes;
using Lumina.Excel;
namespace KamiToolKit.Nodes;
public class LuminaButtonListNode<T> : ButtonListNode<T> where T : struct, IExcelRow<T> {
public delegate string GetLabel(T excelRow);
public delegate bool ShouldShow(T excelRow);
public GetLabel? LabelFunction {
get;
set {
field = value;
ResolveOptions();
}
}
public ShouldShow? FilterFunction {
get;
set {
field = value;
ResolveOptions();
}
}
private void ResolveOptions() {
if (LabelFunction is null) return;
if (FilterFunction is null) return;
Options = DalamudInterface.Instance.DataManager.GetExcelSheet<T>()
.Where(row => FilterFunction(row))
.ToList();
}
protected override string GetLabelForOption(T option)
=> LabelFunction?.Invoke(option) ?? "ERROR: Label Function Not Found";
}
@@ -0,0 +1,47 @@
using System;
using Lumina.Excel;
namespace KamiToolKit.Nodes;
public class LuminaDropDownNode<T> : DropDownNode<LuminaButtonListNode<T>, T> where T : struct, IExcelRow<T> {
public LuminaDropDownNode() {
OptionListNode.OnOptionSelected += OptionSelectedHandler;
}
public Action<T>? OnOptionSelected { get; set; }
public LuminaButtonListNode<T>.GetLabel? LabelFunction {
get => OptionListNode.LabelFunction;
set {
OptionListNode.LabelFunction = value;
ResolveOptions();
}
}
public LuminaButtonListNode<T>.ShouldShow? FilterFunction {
get => OptionListNode.FilterFunction;
set {
OptionListNode.FilterFunction = value;
ResolveOptions();
}
}
private void OptionSelectedHandler(T option) {
OnOptionSelected?.Invoke(option);
UpdateLabel(option);
Toggle(false);
}
private void ResolveOptions() {
if (LabelFunction is null) return;
if (FilterFunction is null) return;
OptionListNode.SelectDefaultOption();
LabelNode.String = LabelFunction.Invoke(OptionListNode.SelectedOption);
}
protected override void UpdateLabel(T option) {
LabelNode.String = LabelFunction?.Invoke(option) ?? "ERROR: Label Function Not Set";
}
}
@@ -0,0 +1,95 @@
using System.Drawing;
using System.Numerics;
using Dalamud.Interface;
namespace KamiToolKit.Nodes;
public unsafe class ProgressBarCastNode : ProgressNode {
public readonly NineGridNode BackgroundImageNode;
public readonly NineGridNode ProgressNode;
public readonly NineGridNode BorderImageNode;
public ProgressBarCastNode() {
BackgroundImageNode = new SimpleNineGridNode {
TexturePath = "ui/uld/Parameter_Gauge.tex",
TextureSize = new Vector2(160.0f, 20.0f),
TextureCoordinates = new Vector2(0.0f, 100.0f),
LeftOffset = 20,
RightOffset = 20,
};
BackgroundImageNode.AttachNode(this);
ProgressNode = new SimpleNineGridNode {
TexturePath = "ui/uld/Parameter_Gauge.tex",
TextureSize = new Vector2(160.0f, 20.0f),
TextureCoordinates = new Vector2(0.0f, 40.0f),
MultiplyColor = new Vector3(90.0f, 75.0f, 75.0f) / 255.0f,
AddColor = KnownColor.Yellow.Vector().AsVector3Color() / 255.0f,
LeftOffset = 10,
RightOffset = 10,
};
ProgressNode.AttachNode(this);
BorderImageNode = new SimpleNineGridNode {
TexturePath = "ui/uld/Parameter_Gauge.tex",
TextureSize = new Vector2(160.0f, 20.0f),
TextureCoordinates = new Vector2(0.0f, 0.0f),
LeftOffset = 20,
RightOffset = 20,
};
BorderImageNode.AttachNode(this);
}
public override float Progress {
get => ProgressNode.Width / Width;
set => ProgressNode.Width = Width * value;
}
public override Vector4 BackgroundColor {
get => new(BackgroundImageNode.AddColor.X, BackgroundImageNode.AddColor.Y, BackgroundImageNode.AddColor.Z, BackgroundImageNode.ResNode->Color.A / 255.0f);
set {
BackgroundImageNode.ResNode->Color = new Vector4(1.0f, 1.0f, 1.0f, value.W).ToByteColor();
BackgroundImageNode.AddColor = value.AsVector3Color();
}
}
public Vector4 BorderColor {
get => new(BorderImageNode.AddColor.X, BorderImageNode.AddColor.Y, BorderImageNode.AddColor.Z, BorderImageNode.ResNode->Color.A / 255.0f);
set {
BorderImageNode.ResNode->Color = new Vector4(1.0f, 1.0f, 1.0f, value.W).ToByteColor();
BorderImageNode.AddColor = value.AsVector3Color();
}
}
public override Vector4 BarColor {
get => new(ProgressNode.AddColor.X, ProgressNode.AddColor.Y, ProgressNode.AddColor.Z, ProgressNode.ResNode->Color.A / 255.0f);
set {
ProgressNode.ResNode->Color = new Vector4(1.0f, 1.0f, 1.0f, value.W).ToByteColor();
ProgressNode.AddColor = value.AsVector3Color();
}
}
public override Vector3 MultiplyColor {
get => base.MultiplyColor;
set {
base.MultiplyColor = value;
BackgroundImageNode.MultiplyColor = value;
ProgressNode.MultiplyColor = value;
BorderImageNode.MultiplyColor = value;
}
}
public bool BorderVisible {
get => BorderImageNode.IsVisible;
set => BorderImageNode.IsVisible = value;
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
BackgroundImageNode.Size = Size;
ProgressNode.Size = Size;
BorderImageNode.Size = Size;
}
}
@@ -0,0 +1,57 @@
using System.Numerics;
namespace KamiToolKit.Nodes;
public unsafe class ProgressBarEnemyCastNode : ProgressNode {
public readonly NineGridNode BackgroundImageNode;
public readonly NineGridNode ProgressNode;
public ProgressBarEnemyCastNode() {
BackgroundImageNode = new SimpleNineGridNode {
TexturePath = "ui/uld/PartyList_GaugeCast.tex",
TextureSize = new Vector2(204.0f, 20.0f),
TextureCoordinates = new Vector2(0.0f, 12.0f),
LeftOffset = 20,
RightOffset = 20,
};
BackgroundImageNode.AttachNode(this);
ProgressNode = new SimpleNineGridNode {
TexturePath = "ui/uld/PartyList_GaugeCast.tex",
TextureSize = new Vector2(188.0f, 7.0f),
TextureCoordinates = new Vector2(8.0f, 3.0f),
LeftOffset = 10,
RightOffset = 10,
};
ProgressNode.AttachNode(this);
}
public override float Progress {
get => ProgressNode.Width / Width;
set => ProgressNode.Width = Width * value;
}
public override Vector4 BackgroundColor {
get => new(BackgroundImageNode.AddColor.X, BackgroundImageNode.AddColor.Y, BackgroundImageNode.AddColor.Z, BackgroundImageNode.ResNode->Color.A / 255.0f);
set {
BackgroundImageNode.ResNode->Color = new Vector4(1.0f, 1.0f, 1.0f, value.W).ToByteColor();
BackgroundImageNode.AddColor = value.AsVector3Color();
}
}
public override Vector4 BarColor {
get => new(ProgressNode.AddColor.X, ProgressNode.AddColor.Y, ProgressNode.AddColor.Z, ProgressNode.ResNode->Color.A / 255.0f);
set {
ProgressNode.ResNode->Color = new Vector4(1.0f, 1.0f, 1.0f, value.W).ToByteColor();
ProgressNode.AddColor = value.AsVector3Color();
}
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
BackgroundImageNode.Size = Size;
ProgressNode.Size = Size;
}
}
@@ -0,0 +1,51 @@
using System.Numerics;
namespace KamiToolKit.Nodes;
public class ProgressBarNode : ProgressNode {
public readonly NineGridNode BackgroundNode;
public readonly NineGridNode ForegroundNode;
public ProgressBarNode() {
BackgroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/ToDoList.tex",
TextureCoordinates = new Vector2(108.0f, 8.0f),
TextureSize = new Vector2(44.0f, 12.0f),
LeftOffset = 6,
RightOffset = 6,
};
BackgroundNode.AttachNode(this);
ForegroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/ToDoList.tex",
TextureCoordinates = new Vector2(112.0f, 0.0f),
TextureSize = new Vector2(40.0f, 8.0f),
LeftOffset = 4,
RightOffset = 4,
};
ForegroundNode.AttachNode(this);
}
public override Vector4 BackgroundColor {
get => BackgroundNode.Color;
set => BackgroundNode.Color = value;
}
public override Vector4 BarColor {
get => ForegroundNode.Color;
set => ForegroundNode.Color = value;
}
public override float Progress {
get => ForegroundNode.Width / Width;
set => ForegroundNode.Width = Width * value;
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
BackgroundNode.Size = Size;
ForegroundNode.Size = Size;
}
}
@@ -0,0 +1,9 @@
using System.Numerics;
namespace KamiToolKit.Nodes;
public abstract class ProgressNode : SimpleComponentNode {
public abstract float Progress { get; set; }
public abstract Vector4 BarColor { get; set; }
public abstract Vector4 BackgroundColor { get; set; }
}
@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Timelines;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
public class RadioButtonGroupNode : SimpleComponentNode {
private readonly List<RadioButtonNode> radioButtons = [];
public RadioButtonGroupNode() {
BuildTimelines();
}
public ReadOnlySeString? SelectedOption {
get => radioButtons.FirstOrDefault(button => button.IsSelected)?.String;
set {
if (value == null)
return;
foreach (var radioButton in radioButtons) {
radioButton.IsChecked = radioButton.String == value;
radioButton.IsSelected = radioButton.String == value;
}
RecalculateLayout();
}
}
public float VerticalPadding { get; set; } = 2.0f;
public void AddButton(ReadOnlySeString label, Action callback) {
var newRadioButton = new RadioButtonNode {
Height = 16.0f,
String = label,
Callback = callback,
};
newRadioButton.AddEvent(AtkEventType.ButtonClick, () => ClickHandler(newRadioButton));
radioButtons.Add(newRadioButton);
newRadioButton.AttachNode(this);
if (radioButtons.Count is 1) {
newRadioButton.IsChecked = true;
newRadioButton.IsSelected = true;
}
RecalculateLayout();
}
public void RemoveButton(ReadOnlySeString label) {
var button = radioButtons.FirstOrDefault(button => button.String == label);
if (button is null) return;
button.Dispose();
radioButtons.Remove(button);
RecalculateLayout();
}
public void Clear() {
foreach (var node in radioButtons) {
node.Dispose();
}
radioButtons.Clear();
}
private void RecalculateLayout() {
var yPosition = 0.0f;
foreach (var index in Enumerable.Range(0, radioButtons.Count)) {
var button = radioButtons[index];
button.Y = yPosition;
yPosition += button.Height + VerticalPadding;
}
Height = yPosition;
}
private void ClickHandler(RadioButtonNode selectedButton) {
foreach (var radioButton in radioButtons) {
radioButton.IsChecked = false;
radioButton.IsSelected = false;
}
selectedButton.IsChecked = true;
selectedButton.IsSelected = true;
}
private void BuildTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 19)
.AddLabel(1, 101, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(10, 102, AtkTimelineJumpBehavior.PlayOnce, 0)
.EndFrameSet()
.Build()
);
}
}
@@ -0,0 +1,309 @@
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;
internal unsafe class RadioButtonNode : ComponentNode<AtkComponentRadioButton, AtkUldComponentDataRadioButton> {
public readonly TextNode LabelNode;
public readonly ImageNode SelectedImageNode;
public readonly ImageNode UnselectedImageNode;
public RadioButtonNode() {
SetInternalComponentType(ComponentType.RadioButton);
UnselectedImageNode = new SimpleImageNode {
NodeId = 4,
TexturePath = "ui/uld/RadioButtonA.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
TextureSize = new Vector2(16.0f, 16.0f),
Size = new Vector2(16.0f, 16.0f),
WrapMode = WrapMode.Tile,
};
UnselectedImageNode.AttachNode(this);
SelectedImageNode = new SimpleImageNode {
NodeId = 3,
TexturePath = "ui/uld/RadioButtonA.tex",
TextureCoordinates = new Vector2(16.0f, 0.0f),
TextureSize = new Vector2(16.0f, 16.0f),
Size = new Vector2(16.0f, 16.0f),
IsVisible = false,
WrapMode = WrapMode.Tile,
};
SelectedImageNode.AttachNode(this);
LabelNode = new TextNode {
NodeId = 2,
Position = new Vector2(20.0f, 0.0f),
Size = new Vector2(98.0f, 16.0f),
FontSize = 14,
TextColor = ColorHelper.GetColor(8),
TextOutlineColor = ColorHelper.GetColor(7),
AlignmentType = AlignmentType.Left,
};
LabelNode.AttachNode(this);
BuildTimelines();
Data->Nodes[0] = LabelNode.NodeId;
Data->Nodes[1] = UnselectedImageNode.NodeId;
Data->Nodes[2] = 0;
Data->Nodes[3] = 0;
AddEvent(AtkEventType.ButtonClick, ClickHandler);
InitializeComponentEvents();
}
public Action? Callback { get; set; }
public ReadOnlySeString String {
get => LabelNode.String;
set {
LabelNode.String = value;
Width = LabelNode.Width + LabelNode.Position.X;
}
}
public bool IsChecked {
get => Component->IsChecked;
set => Component->SetChecked(value);
}
public bool IsSelected {
get => Component->IsSelected;
set {
Component->IsSelected = value;
SelectedImageNode.IsVisible = value;
}
}
private void ClickHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
Callback?.Invoke();
}
private void BuildTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 9)
.AddFrame(1, new Vector2(24, 62))
.EndFrameSet()
.BeginFrameSet(10, 19)
.AddFrame(10, new Vector2(24, 44))
.EndFrameSet()
.Build()
);
CollisionNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 159)
.AddEmptyFrame(1)
.EndFrameSet()
.Build()
);
UnselectedImageNode.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: 102)
.AddFrame(30, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(80, 80, 80))
.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: 102)
.AddFrame(90, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(80, 80, 80))
.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()
.BeginFrameSet(120, 129)
.AddFrame(120, alpha: 255)
.AddFrame(120, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(130, 139)
.AddFrame(130, alpha: 255)
.AddFrame(130, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(140, 149)
.AddFrame(140, alpha: 255)
.AddFrame(140, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(150, 159)
.AddFrame(150, alpha: 255)
.AddFrame(150, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.Build()
);
SelectedImageNode.AddTimeline(new TimelineBuilder()
.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: 102)
.AddFrame(90, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(80, 80, 80))
.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()
.BeginFrameSet(120, 129)
.AddFrame(120, alpha: 0)
.AddFrame(122, alpha: 255)
.AddFrame(120, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(122, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(130, 139)
.AddFrame(130, alpha: 255)
.AddFrame(132, alpha: 0)
.AddFrame(130, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(132, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(140, 149)
.AddFrame(140, alpha: 0)
.AddFrame(142, alpha: 255)
.AddFrame(140, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(142, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(150, 159)
.AddFrame(150, alpha: 255)
.AddFrame(152, alpha: 0)
.AddFrame(150, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(152, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.Build()
);
LabelNode.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(10, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(20, 29)
.AddFrame(20, alpha: 255)
.AddFrame(20, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(30, 39)
.AddFrame(30, alpha: 102)
.AddFrame(30, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(80, 80, 80))
.EndFrameSet()
.BeginFrameSet(40, 49)
.AddFrame(40, alpha: 255)
.AddFrame(40, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(50, 59)
.AddFrame(50, alpha: 255)
.AddFrame(50, 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(70, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(80, 89)
.AddFrame(80, alpha: 255)
.AddFrame(80, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(90, 99)
.AddFrame(90, alpha: 102)
.AddFrame(90, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(80, 80, 80))
.EndFrameSet()
.BeginFrameSet(100, 109)
.AddFrame(100, alpha: 255)
.AddFrame(100, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(110, 119)
.AddFrame(110, alpha: 255)
.AddFrame(110, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(120, 129)
.AddFrame(120, alpha: 255)
.AddFrame(120, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(130, 139)
.AddFrame(130, alpha: 255)
.AddFrame(130, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(140, 149)
.AddFrame(140, alpha: 255)
.AddFrame(140, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(150, 159)
.AddFrame(150, alpha: 255)
.AddFrame(150, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.Build()
);
}
}
@@ -0,0 +1,58 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Enums;
namespace KamiToolKit.Nodes;
// Not intended for public use, this is specialized for KamiToolKit.NodeBase.Resize
internal class ResizeButtonNode : SimpleComponentNode {
public readonly ImageNode SelectedImageNode;
public readonly ImageNode UnselectedImageNode;
public ResizeButtonNode(ResizeDirection direction) {
UnselectedImageNode = new SimpleImageNode {
TexturePath = "ui/uld/ChatLog.tex",
TextureCoordinates = new Vector2(32.0f, 34.0f),
TextureSize = new Vector2(18.0f, 18.0f),
Size = new Vector2(16.0f, 16.0f),
Origin = new Vector2(8.0f, 8.0f),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
WrapMode = WrapMode.Tile,
ImageNodeFlags = direction is ResizeDirection.BottomRight ? ImageNodeFlags.FlipV : ImageNodeFlags.FlipH | ImageNodeFlags.FlipV,
};
UnselectedImageNode.AttachNode(this);
SelectedImageNode = new SimpleImageNode {
TexturePath = "ui/uld/ChatLog.tex",
TextureCoordinates = new Vector2(4.0f, 34.0f),
TextureSize = new Vector2(18.0f, 18.0f),
Size = new Vector2(16.0f, 16.0f),
Origin = new Vector2(8.0f, 8.0f),
NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents,
WrapMode = WrapMode.Tile,
ImageNodeFlags = direction is ResizeDirection.BottomRight ? ImageNodeFlags.FlipV : ImageNodeFlags.FlipH | ImageNodeFlags.FlipV,
};
SelectedImageNode.AttachNode(this);
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
UnselectedImageNode.Size = Size - new Vector2(4.0f, 4.0f);
UnselectedImageNode.Position = new Vector2(2.0f, 2.0f);
SelectedImageNode.Size = Size - new Vector2(4.0f, 4.0f);
SelectedImageNode.Position = new Vector2(2.0f, 2.0f);
}
public bool IsHovered {
get;
set {
field = value;
UnselectedImageNode.IsVisible = !value;
SelectedImageNode.IsVisible = value;
}
}
}
@@ -0,0 +1,16 @@
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Nodes;
public unsafe class ScrollBarBackgroundButtonNode : ComponentNode<AtkComponentButton, AtkUldComponentDataButton> {
public ScrollBarBackgroundButtonNode() {
SetInternalComponentType(ComponentType.Button);
Component->ButtonBGNode = CollisionNode;
Data->Nodes[0] = 0;
Data->Nodes[1] = CollisionNode.NodeId;
InitializeComponentEvents();
}
}
@@ -0,0 +1,88 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public unsafe class ScrollBarForegroundButtonNode : ComponentNode<AtkComponentButton, AtkUldComponentDataButton> {
public readonly NineGridNode ButtonTexture;
public ScrollBarForegroundButtonNode() {
SetInternalComponentType(ComponentType.Button);
ButtonTexture = new SimpleNineGridNode {
TexturePath = "ui/uld/ScrollBarA.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
TextureSize = new Vector2(8.0f, 16.0f),
TopOffset = 4,
BottomOffset = 4,
};
ButtonTexture.AttachNode(this);
Data->Nodes[0] = 0;
Data->Nodes[1] = ButtonTexture.NodeId;
BuildTimelines();
InitializeComponentEvents();
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
ButtonTexture.Size = Size;
}
private void BuildTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 59)
.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)
.EndFrameSet()
.Build()
);
ButtonTexture.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()
.Build()
);
}
}
@@ -0,0 +1,128 @@
using System;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Nodes;
public unsafe class ScrollBarNode : ComponentNode<AtkComponentScrollBar, AtkUldComponentDataScrollBar> {
public readonly ScrollBarBackgroundButtonNode BackgroundButtonNode;
public readonly ScrollBarForegroundButtonNode ForegroundButtonNode;
public ScrollBarNode() {
SetInternalComponentType(ComponentType.ScrollBar);
BackgroundButtonNode = new ScrollBarBackgroundButtonNode {
Size = new Vector2(8.0f, 306.0f),
};
BackgroundButtonNode.AttachNode(this);
ForegroundButtonNode = new ScrollBarForegroundButtonNode {
Size = new Vector2(8.0f, 306.0f),
};
ForegroundButtonNode.AttachNode(this);
Data->Nodes[0] = ForegroundButtonNode.NodeId;
Data->Nodes[1] = 0; // Arrow Up Button
Data->Nodes[2] = 0; // Arrow Down Button
Data->Nodes[3] = BackgroundButtonNode.NodeId;
Data->Vertical = 1;
Data->Margin = 0;
InitializeComponentEvents();
Component->MouseDownScreenPos = 0;
Component->MouseWheelSpeed = 24;
AddEvent(AtkEventType.ValueUpdate, UpdateHandler);
}
public Action<int>? OnValueChanged { get; set; }
public NodeBase? ContentNode {
get;
set {
field = value;
if (value is not null) {
Component->ContentNode = value;
UpdateScrollParams();
}
}
}
public CollisionNode? ContentCollisionNode {
get;
set {
field = value;
Component->ContentCollisionNode = value is null ? null : value.Node;
UpdateScrollParams();
}
}
public int ScrollPosition {
get => Component->ScrollPosition;
set => Component->SetScrollPosition(value);
}
public int ScrollSpeed {
get => Component->MouseWheelSpeed;
set => Component->MouseWheelSpeed = (short)value;
}
public bool HideWhenDisabled { get; set; }
private void UpdateHandler() {
OnValueChanged?.Invoke(Component->PendingScrollPosition);
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
BackgroundButtonNode.Size = Size;
ForegroundButtonNode.Size = Size;
}
/// <summary>
/// Updates from attached Content and Collision nodes
/// </summary>
public void UpdateScrollParams() {
if (Component->ContentNode is null) return;
if (Component->ContentCollisionNode is null) return;
var content = Component->ContentNode;
var collision = Component->ContentCollisionNode;
UpdateScrollParams(collision->Height, content->Height);
}
public void UpdateScrollParams(int barHeight, int offScreenHeight) {
var distance = offScreenHeight - barHeight;
Component->ScrollbarLength = (short)barHeight;
Component->ScrollMaxPosition = Math.Max(distance, 0);
Component->ContentNodeOffScreenLength = Math.Max((short)distance, (short)0);
Component->EmptyLength = Math.Max(barHeight - (int)((float)barHeight / offScreenHeight * barHeight), 0);
ForegroundButtonNode.Height = barHeight - Component->EmptyLength;
if (Component->ScrollPosition > Component->ScrollMaxPosition) {
Component->SetScrollPosition(Component->ScrollMaxPosition);
}
if (Component->EmptyLength is 0) {
ForegroundButtonNode.Y = 0.0f;
ContentNode?.Y = 0;
}
var enabledState = Component->EmptyLength is not 0;
Component->SetEnabledState(enabledState);
if (HideWhenDisabled) {
BackgroundButtonNode.IsVisible = enabledState;
ForegroundButtonNode.IsVisible = enabledState;
}
}
}
@@ -0,0 +1,100 @@
using System.Linq;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Nodes;
public unsafe class ScrollingAreaNode<T> : SimpleComponentNode where T : NodeBase, new() {
public readonly SimpleComponentNode ContentAreaClipNode;
public readonly T ContentAreaNode;
public readonly ScrollBarNode ScrollBarNode;
public readonly CollisionNode ScrollingCollisionNode;
public ScrollingAreaNode() {
ScrollingCollisionNode = new CollisionNode();
ScrollingCollisionNode.AttachNode(this);
ContentAreaClipNode = new SimpleComponentNode {
NodeFlags = NodeFlags.Clip | NodeFlags.EmitsEvents | NodeFlags.Visible,
};
ContentAreaClipNode.AttachNode(this);
ContentAreaNode = new T();
ContentAreaNode.AttachNode(ContentAreaClipNode);
ScrollBarNode = new ScrollBarNode {
ContentNode = ContentAreaNode,
ContentCollisionNode = ScrollingCollisionNode,
HideWhenDisabled = true,
};
ScrollBarNode.AttachNode(this);
ContentAreaClipNode.ResNode->AtkEventManager.RegisterEvent(
AtkEventType.MouseWheel,
5,
null,
ScrollingCollisionNode,
ScrollBarNode,
false);
ScrollingCollisionNode.ResNode->AtkEventManager.RegisterEvent(
AtkEventType.MouseWheel,
5,
null,
ScrollingCollisionNode,
ScrollBarNode,
false);
ContentAreaNode.ResNode->AtkEventManager.RegisterEvent(
AtkEventType.MouseWheel,
5,
null,
ScrollingCollisionNode,
ScrollBarNode,
false);
}
public virtual T ContentNode => ContentAreaNode;
public int ScrollPosition {
get => ScrollBarNode.ScrollPosition;
set => ScrollBarNode.ScrollPosition = value;
}
public int ScrollSpeed {
get => ScrollBarNode.ScrollSpeed;
set => ScrollBarNode.ScrollSpeed = value;
}
public required float ContentHeight {
get => ContentAreaNode.Height;
set {
ContentAreaNode.Height = value;
ScrollBarNode.UpdateScrollParams();
}
}
public bool AutoHideScrollBar {
get => ScrollBarNode.HideWhenDisabled;
set => ScrollBarNode.HideWhenDisabled = value;
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
ContentAreaNode.Width = Width - 16.0f;
ScrollingCollisionNode.Size = new Vector2(Width - 16.0f, Height);
ContentAreaClipNode.Size = new Vector2(Width - 16.0f, Height);
ScrollBarNode.Size = new Vector2(8.0f, Height);
ScrollBarNode.UpdateScrollParams();
ScrollBarNode.X = Width - 8.0f;
}
public void FitToContentHeight() {
if (ContentNode is LayoutListNode layoutNode) {
ContentHeight = layoutNode.Nodes.Sum(node => node.IsVisible ? node.Height + layoutNode.ItemSpacing : 0.0f) + layoutNode.FirstItemSpacing;
}
}
}
@@ -0,0 +1,95 @@
using System;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Nodes;
public class SelectableNode : SimpleComponentNode {
private readonly NineGridNode hoveredBackgroundNode;
private readonly NineGridNode selectedBackgroundNode;
public SelectableNode() {
hoveredBackgroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/ListItemA.tex",
TextureCoordinates = new Vector2(0.0f, 22.0f),
TextureSize = new Vector2(64.0f, 22.0f),
TopOffset = 6,
BottomOffset = 6,
LeftOffset = 16,
RightOffset = 1,
IsVisible = false,
};
hoveredBackgroundNode.AttachNode(this);
selectedBackgroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/ListItemA.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
TextureSize = new Vector2(64.0f, 22.0f),
TopOffset = 6,
BottomOffset = 6,
LeftOffset = 16,
RightOffset = 1,
IsVisible = false,
};
selectedBackgroundNode.AttachNode(this);
CollisionNode.AddEvent(AtkEventType.MouseOver, () => {
if (!IsSelected && EnableHighlight) {
IsHovered = true;
}
});
CollisionNode.AddEvent(AtkEventType.MouseDown, () => {
if (EnableSelection) {
IsSelected = true;
OnClick?.Invoke(this);
}
});
CollisionNode.AddEvent(AtkEventType.MouseOut, () => {
IsHovered = false;
});
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
hoveredBackgroundNode.Size = Size + new Vector2(6.0f, 6.0f);
hoveredBackgroundNode.Position = new Vector2(-3.0f, -3.0f);
selectedBackgroundNode.Size = Size + new Vector2(6.0f, 6.0f);
selectedBackgroundNode.Position = new Vector2(-3.0f, -3.0f);
}
public Action<SelectableNode>? OnClick {
get;
set {
field = value;
CollisionNode.ShowClickableCursor = value is not null && EnableSelection;
}
}
public bool EnableSelection {
get;
set {
field = value;
CollisionNode.ShowClickableCursor = value;
}
} = true;
public bool EnableHighlight { get; set; } = true;
public bool IsHovered {
get => hoveredBackgroundNode.IsVisible;
set => hoveredBackgroundNode.IsVisible = value;
}
public bool IsSelected {
get => selectedBackgroundNode.IsVisible;
set {
selectedBackgroundNode.IsVisible = value;
if (value) {
hoveredBackgroundNode.IsVisible = false;
}
}
}
}
@@ -0,0 +1,83 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public unsafe class SliderBackgroundButtonNode : ComponentNode<AtkComponentButton, AtkUldComponentDataButton> {
public readonly NineGridNode BackgroundTexture;
public SliderBackgroundButtonNode() {
SetInternalComponentType(ComponentType.Button);
BackgroundTexture = new SimpleNineGridNode {
TexturePath = "ui/uld/SliderGaugeHorizontalA.tex",
TextureCoordinates = new Vector2(16.0f, 0.0f),
TextureSize = new Vector2(40.0f, 8.0f),
LeftOffset = 8,
RightOffset = 8,
};
BackgroundTexture.AttachNode(this);
Component->ButtonBGNode = BackgroundTexture;
Data->Nodes[0] = 0;
Data->Nodes[1] = BackgroundTexture.NodeId;
BuildTimelines();
InitializeComponentEvents();
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
BackgroundTexture.Size = new Vector2(Width, Height / 2.0f);
BackgroundTexture.Y = Height / 4.0f;
}
private void BuildTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 20)
.AddFrame(1, alpha: 255)
.EndFrameSet()
.BeginFrameSet(21, 30)
.AddFrame(21, alpha: 127)
.EndFrameSet()
.Build()
);
BackgroundTexture.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()
.Build()
);
}
}
@@ -0,0 +1,78 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Enums;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public class SliderForegroundButtonNode : ComponentNode<AtkComponentButton, AtkUldComponentDataButton> {
public readonly ImageNode HandleNode;
public SliderForegroundButtonNode() {
SetInternalComponentType(ComponentType.Button);
HandleNode = new SimpleImageNode {
TexturePath = "ui/uld/SliderGaugeHorizontalA.tex",
TextureCoordinates = new Vector2(1.0f, 1.0f),
TextureSize = new Vector2(14.0f, 15.0f),
Size = new Vector2(14.0f, 15.0f),
WrapMode = WrapMode.Stretch,
};
HandleNode.AttachNode(this);
BuildTimelines();
InitializeComponentEvents();
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
HandleNode.Size = Size;
}
private void BuildTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 20)
.AddFrame(1, alpha: 255)
.EndFrameSet()
.BeginFrameSet(21, 30)
.AddFrame(21, alpha: 178)
.EndFrameSet()
.Build()
);
HandleNode.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: 255)
.AddFrame(30, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(70, 70, 70))
.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()
.Build()
);
}
}
+181
View File
@@ -0,0 +1,181 @@
using System;
using System.Globalization;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public unsafe class SliderNode : ComponentNode<AtkComponentSlider, AtkUldComponentDataSlider> {
public readonly NineGridNode ProgressTextureNode;
public readonly SliderBackgroundButtonNode SliderBackgroundButtonNode;
public readonly SliderForegroundButtonNode SliderForegroundButtonNode;
public readonly TextNode ValueNode;
public readonly TextNode FloatValueNode;
public SliderNode() {
SetInternalComponentType(ComponentType.Slider);
SliderBackgroundButtonNode = new SliderBackgroundButtonNode();
SliderBackgroundButtonNode.AttachNode(this);
ProgressTextureNode = new SimpleNineGridNode {
TexturePath = "ui/uld/SliderGaugeHorizontalA.tex",
TextureCoordinates = new Vector2(16.0f, 8.0f),
TextureSize = new Vector2(40.0f, 7.0f),
Height = 7.0f,
Y = 4.0f,
LeftOffset = 8,
RightOffset = 8,
};
ProgressTextureNode.AttachNode(this);
SliderForegroundButtonNode = new SliderForegroundButtonNode {
Size = new Vector2(16.0f, 16.0f),
};
SliderForegroundButtonNode.AttachNode(this);
ValueNode = new TextNode {
Size = new Vector2(24.0f, 16.0f),
FontType = FontType.Axis,
FontSize = 12,
AlignmentType = AlignmentType.TopLeft,
TextFlags = TextFlags.AutoAdjustNodeSize,
};
ValueNode.AttachNode(this);
FloatValueNode = new TextNode {
Size = new Vector2(24.0f, 16.0f),
IsVisible = false,
FontType = FontType.Axis,
FontSize = 12,
AlignmentType = AlignmentType.TopLeft,
TextFlags = TextFlags.AutoAdjustNodeSize,
};
FloatValueNode.AttachNode(this);
Data->Step = 1;
Data->Min = 0;
Data->Max = 100;
Data->OfffsetL = 4;
Data->OffsetR = 50;
Data->Nodes[0] = ProgressTextureNode.NodeId;
Data->Nodes[1] = SliderForegroundButtonNode.NodeId;
Data->Nodes[2] = ValueNode.NodeId;
Data->Nodes[3] = SliderBackgroundButtonNode.NodeId;
BuildTimelines();
InitializeComponentEvents();
Component->SliderSize = 220;
Component->OffsetR = 50;
Component->OffsetL = 4;
AddEvent(AtkEventType.SliderValueUpdate, ValueChangedHandler);
}
public Action<int>? OnValueChanged { get; set; }
public required Range Range {
get => Data->Min .. Data->Max;
set {
Component->SetMaxValue(value.End.Value);
Component->SetMinValue(value.Start.Value);
Value = Math.Clamp(Value, value.Start.Value, value.End.Value);
}
}
public int Step {
get => Component->Steps;
set => Component->Steps = value;
}
public int Value {
get => Component->Value;
set {
Component->SetValue(value);
UpdateFormattedText();
}
}
public int DecimalPlaces {
get;
set {
field = value;
UpdateFormattedText();
}
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
SliderBackgroundButtonNode.Size = new Vector2(Width - 18.0f - 25.0f, Height / 2.0f);
SliderBackgroundButtonNode.Position = new Vector2(0.0f, 4.0f);
ProgressTextureNode.Size = new Vector2(0.0f, Height / 2.0f - 1.0f);
ProgressTextureNode.Position = new Vector2(0.0f, 4.0f);
SliderForegroundButtonNode.Size = new Vector2(Height - 4.0f, Height - 4.0f);
SliderForegroundButtonNode.Position = new Vector2(0.0f, 0.0f);
ValueNode.Size = new Vector2(0.0f, Height);
ValueNode.Position = new Vector2(Width - 18.0f - 20.0f, 0.0f);
FloatValueNode.Size = new Vector2(0.0f, Height);
FloatValueNode.Position = new Vector2(Width - 18.0f - 20.0f, 0.0f);
Component->SliderSize = (short)Width;
}
private void ValueChangedHandler() {
OnValueChanged?.Invoke(Value);
UpdateFormattedText();
}
private void UpdateFormattedText() {
if (DecimalPlaces is not 0) {
var formatInfo = new NumberFormatInfo {
NumberDecimalDigits = DecimalPlaces,
};
FloatValueNode.IsVisible = true;
FloatValueNode.String = string.Format(formatInfo, "{0:F}", Value / MathF.Pow(10, DecimalPlaces));
ValueNode.FontSize = 0;
}
}
private void BuildTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 30)
.AddLabel(1, 17, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(11, 18, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(21, 7, AtkTimelineJumpBehavior.PlayOnce, 0)
.EndFrameSet()
.Build()
);
ProgressTextureNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 20)
.AddFrame(1, alpha: 255)
.EndFrameSet()
.BeginFrameSet(21, 30)
.AddFrame(21, alpha: 127)
.EndFrameSet()
.Build()
);
ValueNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 20)
.AddFrame(1, alpha: 255)
.EndFrameSet()
.BeginFrameSet(21, 30)
.AddFrame(21, alpha: 153)
.EndFrameSet()
.Build()
);
}
}
+130
View File
@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Timelines;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
public class TabBarNode : SimpleComponentNode {
private readonly List<TabBarRadioButtonNode> radioButtons = [];
public TabBarNode() {
BuildTimelines();
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
RecalculateLayout();
}
public void AddTab(ReadOnlySeString label, Action callback, bool isEnabled = true) {
var newButton = new TabBarRadioButtonNode {
Height = Height,
String = label,
OnClick = callback,
IsEnabled = isEnabled,
MultiplyColor = isEnabled ? Vector3.One : new Vector3(0.6f, 0.6f, 0.6f),
};
newButton.AddEvent(AtkEventType.ButtonClick, () => ClickHandler(newButton));
radioButtons.Add(newButton);
newButton.AttachNode(this);
if (radioButtons.Count is 1) {
newButton.IsSelected = true;
}
RecalculateLayout();
}
private void ClickHandler(TabBarRadioButtonNode button) {
foreach (var radioButton in radioButtons) {
radioButton.IsChecked = false;
radioButton.IsSelected = false;
}
button.IsChecked = true;
button.IsSelected = true;
}
public void SelectTab(ReadOnlySeString label) {
var button = radioButtons.FirstOrDefault(button => button.String == label);
if (button is null) return;
ClickHandler(button);
}
public void DisableTab(ReadOnlySeString label) {
var button = radioButtons.FirstOrDefault(button => button.String == label);
if (button is null) return;
button.IsEnabled = false;
button.MultiplyColor = new Vector3(0.6f, 0.6f, 0.6f);
}
public void EnableTab(ReadOnlySeString label) {
var button = radioButtons.FirstOrDefault(button => button.String == label);
if (button is null) return;
button.IsEnabled = true;
button.MultiplyColor = Vector3.One;
}
public void ToggleTab(ReadOnlySeString label) {
var button = radioButtons.FirstOrDefault(button => button.String == label);
if (button is null) return;
button.IsEnabled = !button.IsEnabled;
if (button.IsEnabled) {
button.MultiplyColor = Vector3.One;
}
else {
button.MultiplyColor = new Vector3(0.6f, 0.6f, 0.6f);
}
}
public void RemoveTab(ReadOnlySeString label) {
var button = radioButtons.FirstOrDefault(button => button.String == label);
if (button is null) return;
button.Dispose();
radioButtons.Remove(button);
RecalculateLayout();
}
public void Clear() {
foreach (var node in radioButtons) {
node.Dispose();
}
radioButtons.Clear();
}
private void RecalculateLayout() {
var step = Width / radioButtons.Count;
foreach (var index in Enumerable.Range(0, radioButtons.Count)) {
var button = radioButtons[index];
button.Width = step + 5.0f;
button.X = step * index - 5.0f;
button.Height = Height;
}
}
private void BuildTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 20)
.AddLabel(1, 101, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(11, 102, AtkTimelineJumpBehavior.PlayOnce, 0)
.EndFrameSet()
.Build()
);
}
}
@@ -0,0 +1,287 @@
using System;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Timelines;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
public unsafe class TabBarRadioButtonNode : ComponentNode<AtkComponentRadioButton, AtkUldComponentDataRadioButton> {
public readonly TextNode LabelNode;
public readonly NineGridNode SelectedNineGridNode;
public readonly NineGridNode UnselectedNineGridNode;
public TabBarRadioButtonNode() {
SetInternalComponentType(ComponentType.RadioButton);
UnselectedNineGridNode = new SimpleNineGridNode {
Position = new Vector2(-2.0f, -1.0f),
TexturePath = "ui/uld/TabButtonA.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
TextureSize = new Vector2(88.0f, 26.0f),
LeftOffset = 16,
RightOffset = 16,
};
UnselectedNineGridNode.AttachNode(this);
SelectedNineGridNode = new SimpleNineGridNode {
Position = new Vector2(-2.0f, -1.0f),
TexturePath = "ui/uld/TabButtonA.tex",
TextureCoordinates = new Vector2(0.0f, 26.0f),
TextureSize = new Vector2(88.0f, 26.0f),
LeftOffset = 16,
RightOffset = 16,
IsVisible = false,
};
SelectedNineGridNode.AttachNode(this);
LabelNode = new TextNode {
Position = new Vector2(13.0f, 2.0f),
AlignmentType = AlignmentType.Center,
TextColor = ColorHelper.GetColor(50),
};
LabelNode.AttachNode(this);
BuildTimelines();
Data->Nodes[0] = LabelNode.NodeId;
Data->Nodes[1] = UnselectedNineGridNode.NodeId;
Data->Nodes[2] = 0;
Data->Nodes[3] = 0;
AddEvent(AtkEventType.ButtonClick, ClickHandler);
InitializeComponentEvents();
}
public Action? OnClick { get; set; }
public ReadOnlySeString String {
get => LabelNode.String;
set => Component->SetText(value);
}
public bool IsSelected {
get => Component->IsSelected;
set {
Component->IsSelected = value;
if (value) {
SelectedNineGridNode.IsVisible = true;
UnselectedNineGridNode.IsVisible = false;
}
else {
SelectedNineGridNode.IsVisible = false;
UnselectedNineGridNode.IsVisible = true;
}
}
}
public bool IsChecked {
get => Component->IsChecked;
set => Component->SetChecked(value);
}
private void ClickHandler() {
OnClick?.Invoke();
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
CollisionNode.Size = Size;
UnselectedNineGridNode.Size = new Vector2(Width + 4.0f, Height + 2.0f);
SelectedNineGridNode.Size = new Vector2(Width + 4.0f, Height + 2.0f);
LabelNode.Size = new Vector2(Width - 25.0f, Height - 4.0f);
}
private void BuildTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(11, 20)
.AddFrame(11, new Vector2(525, 0))
.EndFrameSet()
.Build()
);
UnselectedNineGridNode.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(120, 129)
.AddFrame(120, alpha: 255)
.AddFrame(122, alpha: 0)
.AddFrame(120, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(122, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(130, 139)
.AddFrame(130, alpha: 0)
.AddFrame(132, alpha: 255)
.AddFrame(130, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(132, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(140, 149)
.AddFrame(140, alpha: 255)
.AddFrame(142, alpha: 0)
.AddFrame(140, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(142, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(150, 159)
.AddFrame(150, alpha: 0)
.AddFrame(152, alpha: 255)
.AddFrame(150, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(152, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.Build()
);
SelectedNineGridNode.AddTimeline(new TimelineBuilder()
.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()
.BeginFrameSet(120, 129)
.AddFrame(120, alpha: 0)
.AddFrame(122, alpha: 255)
.AddFrame(120, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(122, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(130, 139)
.AddFrame(130, alpha: 255)
.AddFrame(132, alpha: 0)
.AddFrame(130, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(132, addColor: new Vector3(16, 16, 16), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(140, 149)
.AddFrame(140, alpha: 0)
.AddFrame(142, alpha: 255)
.AddFrame(140, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(142, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(150, 159)
.AddFrame(150, alpha: 255)
.AddFrame(152, alpha: 0)
.AddFrame(150, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.AddFrame(152, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.Build()
);
LabelNode.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(10, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(20, 29)
.AddFrame(20, alpha: 255)
.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: 255)
.AddFrame(40, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(50, 59)
.AddFrame(50, alpha: 255)
.AddFrame(50, 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(70, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(80, 89)
.AddFrame(80, alpha: 255)
.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: 255)
.AddFrame(100, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(110, 119)
.AddFrame(110, alpha: 255)
.AddFrame(110, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(120, 129)
.AddFrame(120, alpha: 255)
.AddFrame(120, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(130, 139)
.AddFrame(130, alpha: 255)
.AddFrame(130, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(140, 149)
.AddFrame(140, alpha: 255)
.AddFrame(140, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.BeginFrameSet(150, 159)
.AddFrame(150, alpha: 255)
.AddFrame(150, addColor: new Vector3(0, 0, 0), multiplyColor: new Vector3(100, 100, 100))
.EndFrameSet()
.Build()
);
}
}
@@ -0,0 +1,5 @@
namespace KamiToolKit.Nodes;
public class TextButtonListNode : ButtonListNode<string> {
protected override string GetLabelForOption(string option) => option;
}
@@ -0,0 +1,51 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
public unsafe class TextButtonNode : ButtonBase {
public readonly NineGridNode BackgroundNode;
public readonly TextNode LabelNode;
public TextButtonNode() {
BackgroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/ButtonA.tex",
TextureSize = new Vector2(100.0f, 28.0f),
LeftOffset = 16.0f,
RightOffset = 16.0f,
};
BackgroundNode.AttachNode(this);
LabelNode = new TextNode {
AlignmentType = AlignmentType.Center,
Position = new Vector2(16.0f, 3.0f),
TextColor = ColorHelper.GetColor(50),
};
LabelNode.AttachNode(this);
LoadTimelines();
Data->Nodes[0] = LabelNode.NodeId;
Data->Nodes[1] = BackgroundNode.NodeId;
InitializeComponentEvents();
}
public ReadOnlySeString String {
get => LabelNode.String;
set => LabelNode.String = value;
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
LabelNode.Size = new Vector2(Width - 32.0f, Height - 8.0f);
BackgroundNode.Size = Size;
}
private void LoadTimelines()
=> LoadThreePartTimelines(this, BackgroundNode, LabelNode, new Vector2(16.0f, 3.0f));
}
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
namespace KamiToolKit.Nodes;
public class TextDropDownNode : DropDownNode<TextButtonListNode, string> {
public TextDropDownNode() {
OptionListNode.OnOptionSelected += OptionSelectedHandler;
}
public Action<string>? OnOptionSelected { get; set; }
public required List<string>? Options {
get => OptionListNode.Options;
set {
OptionListNode.Options = value;
OptionListNode.SelectDefaultOption();
UpdateLabel(OptionListNode.SelectedOption);
}
}
private void OptionSelectedHandler(string option) {
OnOptionSelected?.Invoke(option);
UpdateLabel(option);
Toggle(false);
}
protected override void UpdateLabel(string? option) {
LabelNode.String = option ?? "ERROR: Invalid Default Option";
}
}
@@ -0,0 +1,77 @@
using System.Drawing;
using System.Numerics;
using Dalamud.Interface;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Timelines;
namespace KamiToolKit.Nodes;
public unsafe class TextInputButtonNode : ButtonBase {
public readonly NineGridNode BackgroundNode;
public readonly TextNode LabelNode;
public TextInputButtonNode() {
BackgroundNode = new SimpleNineGridNode {
Size = new Vector2(160.0f, 24.0f),
LeftOffset = 16.0f,
RightOffset = 1.0f,
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.Fill | NodeFlags.EmitsEvents,
TexturePath = "ui/uld/ListItemA.tex",
TextureCoordinates = new Vector2(0.0f, 22.0f),
TextureSize = new Vector2(63.0f, 22.0f),
};
BackgroundNode.AttachNode(this);
LabelNode = new TextNode {
Position = new Vector2(12.0f, 2.0f),
Size = new Vector2(140.0f, 18.0f),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
AlignmentType = AlignmentType.Left,
TextFlags = TextFlags.AutoAdjustNodeSize,
TextColor = KnownColor.White.Vector(),
TextOutlineColor = KnownColor.White.Vector(),
BackgroundColor = KnownColor.Black.Vector(),
};
LabelNode.AttachNode(this);
Data->Nodes[0] = LabelNode.NodeId;
Data->Nodes[1] = BackgroundNode.NodeId;
LoadTimeline();
InitializeComponentEvents();
}
private void LoadTimeline() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 59)
.AddLabelPair(1, 9, 1)
.AddLabelPair(10, 19, 2)
.AddLabelPair(20, 29, 3)
.AddLabelPair(30, 39, 7)
.AddLabelPair(40, 49, 6)
.AddLabelPair(50, 59, 4)
.EndFrameSet()
.Build());
BackgroundNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(10, 19)
.AddFrame(10, alpha: 0)
.AddFrame(13, alpha: 255)
.EndFrameSet()
.AddFrameSetWithFrame(20, 29, 20, alpha: 255)
.AddFrameSetWithFrame(40, 49, 40, alpha: 255)
.BeginFrameSet(50, 59)
.AddFrame(50, alpha: 255)
.AddFrame(52, alpha: 0)
.EndFrameSet()
.Build());
LabelNode.AddTimeline(new TimelineBuilder()
.AddFrameSetWithFrame(1, 29, 1, alpha: 255, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(30, 39, 30, alpha: 153, multiplyColor: new Vector3(80.0f))
.AddFrameSetWithFrame(40, 59, 40, alpha: 255, multiplyColor: new Vector3(100.0f))
.Build());
}
}
@@ -0,0 +1,318 @@
using System;
using System.Drawing;
using System.Numerics;
using System.Runtime.InteropServices;
using Dalamud.Interface;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Graphics;
using FFXIVClientStructs.FFXIV.Client.System.Input;
using FFXIVClientStructs.FFXIV.Component.GUI;
using InteropGenerator.Runtime;
using KamiToolKit.Classes;
using KamiToolKit.Enums;
using KamiToolKit.Timelines;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
public unsafe class TextInputNode : ComponentNode<AtkComponentTextInput, AtkUldComponentDataTextInput> {
public readonly NineGridNode BackgroundNode;
public readonly TextNode CurrentTextNode;
public readonly CursorNode CursorNode;
public readonly NineGridNode FocusNode;
public readonly TextInputSelectionListNode SelectionListNode;
public readonly TextNode TextLimitsNode;
public readonly TextNode PlaceholderTextNode;
private AtkComponentInputBase.CallbackDelegate? pinnedCallbackFunction;
public TextInputNode() {
SetInternalComponentType(ComponentType.TextInput);
BackgroundNode = new SimpleNineGridNode {
NodeId = 19,
TexturePath = "ui/uld/TextInputA.tex",
TextureCoordinates = new Vector2(24.0f, 0.0f),
TextureSize = new Vector2(24.0f, 24.0f),
Offsets = new Vector4(10.0f),
Size = new Vector2(152.0f, 28.0f),
};
BackgroundNode.AttachNode(this);
FocusNode = new SimpleNineGridNode {
NodeId = 18,
TexturePath = "ui/uld/TextInputA.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
TextureSize = new Vector2(24.0f, 24.0f),
Offsets = new Vector4(10.0f),
Size = new Vector2(152.0f, 28.0f),
};
FocusNode.AttachNode(this);
TextLimitsNode = new TextNode {
NodeId = 17,
Position = new Vector2(-24.0f, 6.0f),
Size = new Vector2(170.0f, 19.0f),
FontType = FontType.MiedingerMed,
FontSize = 14,
AlignmentType = (AlignmentType)21,
};
TextLimitsNode.AttachNode(this);
CurrentTextNode = new TextNode {
NodeId = 16,
Position = new Vector2(10.0f, 6.0f),
Size = new Vector2(132.0f, 18.0f),
AlignmentType = AlignmentType.TopLeft,
TextFlags = TextFlags.AutoAdjustNodeSize,
TextColor = ColorHelper.GetColor(1),
};
CurrentTextNode.AttachNode(this);
SelectionListNode = new TextInputSelectionListNode {
NodeId = 4,
Position = new Vector2(0.0f, 22.0f),
Size = new Vector2(186.0f, 208.0f),
};
SelectionListNode.AttachNode(this);
CursorNode = new CursorNode {
NodeId = 2,
Position = new Vector2(10.0f, 2.0f),
Size = new Vector2(4.0f, 24.0f),
OriginY = 4.0f,
};
CursorNode.AttachNode(this);
PlaceholderTextNode = new TextNode {
Position = new Vector2(8.0f, 0.0f),
TextColor = ColorHelper.GetColor(3),
};
PlaceholderTextNode.AttachNode(this);
Data->Nodes[0] = CurrentTextNode.NodeId;
Data->Nodes[1] = BackgroundNode.NodeId;
Data->Nodes[2] = CursorNode.NodeId;
Data->Nodes[3] = SelectionListNode.NodeId;
Data->Nodes[4] = SelectionListNode.Buttons[8].NodeId;
Data->Nodes[5] = SelectionListNode.Buttons[7].NodeId;
Data->Nodes[6] = SelectionListNode.Buttons[6].NodeId;
Data->Nodes[7] = SelectionListNode.Buttons[5].NodeId;
Data->Nodes[8] = SelectionListNode.Buttons[4].NodeId;
Data->Nodes[9] = SelectionListNode.Buttons[3].NodeId;
Data->Nodes[10] = SelectionListNode.Buttons[2].NodeId;
Data->Nodes[11] = SelectionListNode.Buttons[1].NodeId;
Data->Nodes[12] = SelectionListNode.Buttons[0].NodeId;
Data->Nodes[13] = SelectionListNode.LabelNode.NodeId;
Data->Nodes[14] = SelectionListNode.BackgroundNode.NodeId;
Data->Nodes[15] = TextLimitsNode.NodeId;
Data->CandidateColor = new ByteColor { R = 66 };
Data->IMEColor = new ByteColor { R = 67 };
Data->FocusColor = KnownColor.Black.Vector().ToByteColor();
Flags = TextInputFlags.EnableIme | TextInputFlags.AllowUpperCase | TextInputFlags.AllowLowerCase |
TextInputFlags.EnableDictionary | TextInputFlags.AllowNumberInput | TextInputFlags.AllowSymbolInput;
EnableCompletion = false;
Component->EnableTabCallback = true;
LoadTimelines();
pinnedCallbackFunction = OnCallback;
Component->Callback = (delegate* unmanaged<AtkUnitBase*, InputCallbackType, CStringPointer, CStringPointer, int, InputCallbackResult>) Marshal.GetFunctionPointerForDelegate(pinnedCallbackFunction);
InitializeComponentEvents();
CollisionNode.AddEvent(AtkEventType.FocusStart, () => {
PlaceholderTextNode.IsVisible = false;
OnFocused?.Invoke();
if (AutoSelectAll && Component->EvaluatedString.Length > 0) {
DalamudInterface.Instance.Framework.RunOnTick(() => {
var keyModifiers = new AtkTextInput.KeyModifiers {
IsControlDown = true,
};
AtkStage.Instance()->AtkInputManager->TextInput->ProcessKeyShortcut(SeVirtualKey.A, &keyModifiers);
}, delayTicks: 1);
}
});
CollisionNode.AddEvent(AtkEventType.FocusStop, () => {
OnUnfocused?.Invoke();
if (!PlaceholderString.IsNullOrEmpty() && String.IsEmpty) {
PlaceholderTextNode.IsVisible = true;
PlaceholderTextNode.String = PlaceholderString;
}
});
}
protected override void Dispose(bool disposing, bool isNativeDestructor) {
if (disposing) {
base.Dispose(disposing, isNativeDestructor);
pinnedCallbackFunction = null;
}
}
public bool IsFocused
=> AtkStage.Instance()->AtkInputManager->FocusedNode == CollisionNode.Node;
public int MaxCharacters {
get => (int)Component->ComponentTextData.MaxChar;
set => Component->ComponentTextData.MaxChar = (uint)value;
}
public bool ShowLimitText {
get => TextLimitsNode.IsVisible;
set => TextLimitsNode.IsVisible = value;
}
public TextInputFlags Flags {
get => (TextInputFlags) ((byte)Data->Flags1 | (byte)Data->Flags2 << 8);
set {
Data->Flags1 = (TextInputFlags1)((ushort)value & 0xFF);
Data->Flags2 = (TextInputFlags2)((ushort)value >> 8);
}
}
public bool EnableCompletion {
get => Component->EnableCompletion;
set => Component->EnableCompletion = value;
}
public bool EnableFocusSounds {
get => Component->EnableFocusSounds;
set => Component->EnableFocusSounds = value;
}
public virtual ReadOnlySeString String {
get => Component->EvaluatedString.AsSpan();
set {
Component->SetText(value);
UpdatePlaceholderVisibility();
}
}
public string? PlaceholderString {
get;
set {
field = value;
UpdatePlaceholderVisibility();
}
}
public bool IsError {
get => FocusNode.MultiplyColor == new Vector3(1.0f, 0.6f, 0.6f);
set => FocusNode.MultiplyColor = value ? new Vector3(1.0f, 0.6f, 0.6f) : Vector3.One;
}
public bool AutoSelectAll { get; set; } = true;
public void ClearFocus() {
if (IsFocused) {
AtkStage.Instance()->AtkInputManager->SetFocus(null, ParentAddon, 0);
}
}
public virtual Action<ReadOnlySeString>? OnInputReceived { get; set; }
public virtual Action<ReadOnlySeString>? OnInputComplete { get; set; }
public Action? OnFocusLost { get; set; }
public Action? OnEscapeEntered { get; set; }
public Action? OnTabEntered { get; set; }
public Action? OnFocused { get; set; }
public Action? OnUnfocused { get; set; }
private InputCallbackResult OnCallback(AtkUnitBase* addon, InputCallbackType type, CStringPointer rawString, CStringPointer evaluatedString, int eventKind) {
try {
switch (type) {
case InputCallbackType.Enter:
if (this is TextMultiLineInputNode) break;
OnInputComplete?.Invoke(Component->EvaluatedString.AsSpan());
ClearFocus();
break;
case InputCallbackType.TextChanged:
OnInputReceived?.Invoke(Component->EvaluatedString.AsSpan());
break;
case InputCallbackType.Escape:
OnEscapeEntered?.Invoke();
break;
case InputCallbackType.FocusLost:
OnFocusLost?.Invoke();
break;
case InputCallbackType.Tab:
OnTabEntered?.Invoke();
break;
}
return InputCallbackResult.None;
}
catch (Exception e) {
Log.Exception(e);
return InputCallbackResult.None;
}
}
private void UpdatePlaceholderVisibility() {
PlaceholderTextNode.String = PlaceholderString ?? string.Empty;
PlaceholderTextNode.IsVisible = String.IsEmpty && !PlaceholderString.IsNullOrEmpty();
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
BackgroundNode.Size = Size;
FocusNode.Size = Size;
PlaceholderTextNode.Size = Size;
TextLimitsNode.Size = new Vector2(Width + 18.0f, Height - 9.0f);
CurrentTextNode.Size = new Vector2(Width - 20.0f, Height - 10.0f);
}
private void LoadTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 29)
.AddLabelPair(1, 9, 17)
.AddLabelPair(10, 19, 18)
.AddLabelPair(20, 29, 7)
.EndFrameSet()
.Build());
BackgroundNode.AddTimeline(new TimelineBuilder()
.AddFrameSetWithFrame(1, 9, 1, alpha: 255)
.BeginFrameSet(10, 19)
.AddFrame(10, alpha: 255)
.AddFrame(12, alpha: 255)
.EndFrameSet()
.AddFrameSetWithFrame(20, 29, 20, alpha: 127)
.Build());
FocusNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(10, 19)
.AddFrame(10, alpha: 0)
.AddFrame(12, alpha: 255)
.EndFrameSet()
.Build());
TextLimitsNode.AddTimeline(new TimelineBuilder()
.AddFrameSetWithFrame(1, 9, 1, alpha: 102)
.BeginFrameSet(10, 19)
.AddFrame(10, alpha: 102)
.AddFrame(12, alpha: 127)
.EndFrameSet()
.AddFrameSetWithFrame(20, 29, 20, alpha: 76)
.Build());
CursorNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 15)
.AddLabel(1, 101, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(15, 0, AtkTimelineJumpBehavior.LoopForever, 101)
.EndFrameSet()
.Build());
}
}
@@ -0,0 +1,97 @@
using System;
using FFXIVClientStructs.FFXIV.Client.System.Input;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Enums;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
public unsafe class TextMultiLineInputNode : TextInputNode {
public TextMultiLineInputNode() {
TextLimitsNode.AlignmentType = AlignmentType.BottomRight;
CurrentTextNode.TextFlags |= TextFlags.MultiLine;
CurrentTextNode.LineSpacing = 14;
Flags |= TextInputFlags.MultiLine;
CollisionNode.AddEvent(AtkEventType.InputReceived, InputComplete);
Component->InputSanitizationFlags = AllowedEntities.UppercaseLetters | AllowedEntities.LowercaseLetters | AllowedEntities.Numbers |
AllowedEntities.SpecialCharacters | AllowedEntities.CharacterList | AllowedEntities.OtherCharacters |
AllowedEntities.Payloads | AllowedEntities.Unknown9;
Component->ComponentTextData.Flags2 = TextInputFlags2.MultiLine | TextInputFlags2.AllowSymbolInput | TextInputFlags2.AllowNumberInput;
Component->ComponentTextData.MaxLine = byte.MaxValue;
Component->ComponentTextData.MaxByte = ushort.MaxValue;
}
public uint MaxLines {
get => Component->ComponentTextData.MaxLine;
set => Component->ComponentTextData.MaxLine = value;
}
public uint MaxBytes {
get => Component->ComponentTextData.MaxByte;
set => Component->ComponentTextData.MaxByte = value;
}
public override ReadOnlySeString String {
get => base.String;
set {
base.String = value;
PlaceholderTextNode.IsVisible = PlaceholderString is not null && value.IsEmpty;
UpdateHeightForContent();
}
}
public override Action<ReadOnlySeString>? OnInputReceived {
get => base.OnInputReceived;
set {
base.OnInputReceived = _ => UpdateHeightForContent();
base.OnInputReceived += value;
}
}
public bool AutoUpdateHeight { get; set; }
public Action<float>? HeightChanged { get; set; }
private void UpdateHeightForContent() {
if (!AutoUpdateHeight) return;
var text = String;
var lineCount = Math.Max(1, text.ToString().Split('\r', '\n').Length);
var lineHeight = CurrentTextNode.LineSpacing;
var contentHeight = Math.Max(Height, lineCount * lineHeight + 20);
var oldHeight = Height;
Height = contentHeight;
if (Math.Abs(contentHeight - oldHeight) > 0.1f) {
HeightChanged?.Invoke(Height);
}
}
private void InputComplete() {
if (UIInputData.Instance()->IsKeyPressed(SeVirtualKey.RETURN)) {
var textInputComponent = Node->GetAsAtkComponentTextInput();
var cursorPos = textInputComponent->CursorPos;
using (var utf8String = new Utf8String()) {
utf8String.SetString("\r");
textInputComponent->WriteString(&utf8String);
}
textInputComponent->CursorPos = cursorPos + 1;
textInputComponent->SelectionStart = cursorPos + 1;
textInputComponent->SelectionEnd = cursorPos + 1;
}
OnInputComplete?.Invoke(Component->EvaluatedString.AsSpan());
}
}
@@ -0,0 +1,184 @@
using System;
using System.Linq;
using FFXIVClientStructs.FFXIV.Client.System.Input;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Enums;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Nodes;
/// <summary>
/// Needs More Work.
/// </summary>
internal unsafe class TextMultiLineInputNodeScrollable : TextInputNode {
private int startLineIndex;
private bool isProgrammaticTextSet;
private ReadOnlySeString fullText;
private ReadOnlySeString lastDisplayedText;
public TextMultiLineInputNodeScrollable() {
TextLimitsNode.AlignmentType = AlignmentType.BottomRight;
CurrentTextNode.TextFlags |= TextFlags.MultiLine;
CurrentTextNode.LineSpacing = 14;
Flags |= TextInputFlags.MultiLine;
CollisionNode.AddEvent(AtkEventType.InputReceived, InputComplete);
CollisionNode.AddEvent(AtkEventType.MouseWheel, OnMouseScrolled);
Component->InputSanitizationFlags = AllowedEntities.UppercaseLetters | AllowedEntities.LowercaseLetters | AllowedEntities.Numbers |
AllowedEntities.SpecialCharacters | AllowedEntities.CharacterList | AllowedEntities.OtherCharacters |
AllowedEntities.Payloads | AllowedEntities.Unknown9;
Component->ComponentTextData.Flags2 = TextInputFlags2.MultiLine | TextInputFlags2.AllowSymbolInput | TextInputFlags2.AllowNumberInput;
Component->ComponentTextData.MaxLine = byte.MaxValue;
Component->ComponentTextData.MaxByte = ushort.MaxValue;
}
public uint MaxLines {
get => Component->ComponentTextData.MaxLine;
set => Component->ComponentTextData.MaxLine = value;
}
public uint MaxBytes {
get => Component->ComponentTextData.MaxByte;
set => Component->ComponentTextData.MaxByte = value;
}
public override ReadOnlySeString String {
get => fullText;
set {
isProgrammaticTextSet = true;
fullText = value;
UpdateCurrentTextDisplay();
isProgrammaticTextSet = false;
}
}
public override Action<ReadOnlySeString>? OnInputReceived {
get => base.OnInputReceived;
set {
base.OnInputReceived = currentComponentText => {
if (isProgrammaticTextSet) return;
ApplyDisplayChangesToFullText(currentComponentText.ToString());
lastDisplayedText = currentComponentText;
UpdateLineCountDisplay();
};
base.OnInputReceived += value;
}
}
private void OnMouseScrolled(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
var lines = fullText.ToString().Split(['\r', '\n'], StringSplitOptions.None);
var lineHeight = CurrentTextNode.LineSpacing;
var maxVisibleLines = (int)(Height / lineHeight);
var oldStartLineIndex = startLineIndex;
if (atkEventData->IsScrollUp)
startLineIndex = Math.Max(0, startLineIndex - 1);
else if (atkEventData->IsScrollDown)
startLineIndex = Math.Min(Math.Max(0, lines.Length - maxVisibleLines), startLineIndex + 1);
if (oldStartLineIndex != startLineIndex) {
UpdateCurrentTextDisplay();
}
atkEvent->SetEventIsHandled();
}
private void ApplyDisplayChangesToFullText(string newDisplayedText) {
var lines = fullText.ToString().Split(['\r', '\n'], StringSplitOptions.None).ToList();
var oldDisplayLines = lastDisplayedText.ToString().Split(['\r', '\n'], StringSplitOptions.None);
var newDisplayLines = newDisplayedText.Split(['\r', '\n'], StringSplitOptions.None);
if (startLineIndex < lines.Count) {
var removeCount = Math.Min(oldDisplayLines.Length, lines.Count - startLineIndex);
lines.RemoveRange(startLineIndex, removeCount);
lines.InsertRange(startLineIndex, newDisplayLines);
}
else {
lines.AddRange(newDisplayLines);
}
for (var i = lines.Count - 1; i >= 0; i--) {
if (string.IsNullOrEmpty(lines[i]))
lines.RemoveAt(i);
else
break;
}
if (lines.Count == 0)
lines.Add(string.Empty);
fullText = string.Join("\r", lines);
lastDisplayedText = newDisplayedText;
}
private void UpdateLineCountDisplay() {
var lines = fullText.ToString().Split(['\r', '\n'], StringSplitOptions.None);
var lineHeight = CurrentTextNode.LineSpacing;
var totalLines = lines.Length;
var maxVisibleLines = (int)(Height / lineHeight);
if (maxVisibleLines <= 0) return;
startLineIndex = Math.Clamp(startLineIndex, 0, Math.Max(0, totalLines - maxVisibleLines));
var currentEndLine = Math.Min(startLineIndex + maxVisibleLines, totalLines);
var limitText = $"{startLineIndex + 1}-{currentEndLine}/{totalLines}";
TextLimitsNode.String = limitText;
}
private void UpdateCurrentTextDisplay() {
var lines = fullText.ToString().Split(['\r', '\n'], StringSplitOptions.None);
var lineHeight = CurrentTextNode.LineSpacing;
var maxVisibleLines = (int)(Height / lineHeight);
if (maxVisibleLines <= 0) return;
startLineIndex = Math.Clamp(startLineIndex, 0, Math.Max(0, lines.Length - maxVisibleLines));
var displayText = startLineIndex > 0 && startLineIndex < lines.Length
? string.Join("\r", lines.Skip(startLineIndex).Take(maxVisibleLines))
: fullText.ToString();
lastDisplayedText = displayText;
var capturedProgrammaticFlag = isProgrammaticTextSet;
isProgrammaticTextSet = capturedProgrammaticFlag;
Component->SetText(displayText);
UpdateLineCountDisplay();
}
private void InputComplete() {
if (UIInputData.Instance()->IsKeyPressed(SeVirtualKey.RETURN)) {
var textInputComponent = Node->GetAsAtkComponentTextInput();
var cursorPos = textInputComponent->CursorPos;
using (var utf8String = new Utf8String()) {
utf8String.SetString("\r");
textInputComponent->WriteString(&utf8String);
}
textInputComponent->CursorPos = cursorPos + 1;
textInputComponent->SelectionStart = cursorPos + 1;
textInputComponent->SelectionEnd = cursorPos + 1;
}
OnInputComplete?.Invoke(Component->EvaluatedString.AsSpan());
}
}
@@ -0,0 +1,44 @@
using System.Numerics;
using KamiToolKit.Enums;
namespace KamiToolKit.Nodes;
public class TextureButtonNode : ButtonBase {
public readonly SimpleImageNode ImageNode;
public TextureButtonNode() {
ImageNode = new ImGuiImageNode {
WrapMode = WrapMode.Stretch,
};
ImageNode.AttachNode(this);
LoadTimelines();
InitializeComponentEvents();
}
public string TexturePath {
get => ImageNode.TexturePath;
set => ImageNode.TexturePath = value;
}
public Vector2 TextureCoordinates {
get => ImageNode.TextureCoordinates;
set => ImageNode.TextureCoordinates = value;
}
public Vector2 TextureSize {
get => ImageNode.TextureSize;
set => ImageNode.TextureSize = value;
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
ImageNode.Size = Size;
}
private void LoadTimelines()
=> LoadTwoPartTimelines(this, ImageNode);
}
@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace KamiToolKit.Nodes;
public class TreeListNode : SimpleComponentNode {
private readonly SimpleComponentNode childContainer;
private readonly List<TreeListCategoryNode> children = [];
public ReadOnlyCollection<TreeListCategoryNode> CategoryNodes => children.AsReadOnly();
public TreeListNode() {
childContainer = new SimpleComponentNode();
childContainer.AttachNode(this);
}
public float CategoryVerticalSpacing { get; set; } = 4.0f;
public Action<float>? OnLayoutUpdate { get; set; }
protected override void OnSizeChanged() {
base.OnSizeChanged();
childContainer.Width = Width;
}
public void AddCategoryNode(TreeListCategoryNode node) {
RefreshLayout();
children.Add(node);
node.NodeId = (uint)children.Count + 1;
node.Width = childContainer.Width;
node.Y = childContainer.Height;
node.AttachNode(childContainer);
node.ParentTreeListNode = this;
childContainer.Height += node.Height + CategoryVerticalSpacing;
}
public void RefreshLayout() {
childContainer.Height = 0.0f;
foreach (var child in children) {
if (!child.IsVisible) continue;
child.Y = childContainer.Height;
childContainer.Height += child.Height + CategoryVerticalSpacing;
child.UpdateChildrenNodeId();
}
OnLayoutUpdate?.Invoke(childContainer.Height);
}
}
+265
View File
@@ -0,0 +1,265 @@
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 WindowNode : WindowNodeBase {
public readonly ImageNode BackgroundImageNode;
public readonly WindowBackgroundNode BackgroundNode;
public readonly WindowBackgroundNode BorderNode;
public readonly TextureButtonNode CloseButtonNode;
public readonly TextureButtonNode ConfigurationButtonNode;
public readonly SimpleNineGridNode DividingLineNode;
public readonly CollisionNode HeaderCollisionNode;
public readonly ResNode HeaderContainerNode;
public readonly TextureButtonNode InformationButtonNode;
public readonly TextNode SubtitleNode;
public readonly TextNode TitleNode;
public WindowNode() {
CollisionNode.NodeId = 13;
Component->ShowFlags = 1;
HeaderCollisionNode = new CollisionNode {
Uses = 2,
NodeId = 12,
Height = 28.0f,
Position = new Vector2(8.0f, 8.0f),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.HasCollision | NodeFlags.RespondToMouse | NodeFlags.EmitsEvents | NodeFlags.Focusable,
};
HeaderCollisionNode.AttachNode(this);
BackgroundNode = new WindowBackgroundNode(false) {
NodeId = 11,
Position = Vector2.Zero,
Offsets = new Vector4(64.0f, 32.0f, 32.0f, 32.0f),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.Fill | NodeFlags.EmitsEvents,
PartsRenderType = 19,
};
BackgroundNode.AttachNode(this);
BorderNode = new WindowBackgroundNode(true) {
NodeId = 10,
Position = Vector2.Zero,
Offsets = new Vector4(64.0f, 32.0f, 32.0f, 32.0f),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.Fill | NodeFlags.EmitsEvents,
PartsRenderType = 7,
};
BorderNode.AttachNode(this);
BackgroundImageNode = new SimpleImageNode {
NodeId = 9,
WrapMode = WrapMode.Stretch,
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
TexturePath = "ui/uld/WindowA_Gradation.tex",
TextureCoordinates = new Vector2(6.0f, 2.0f),
TextureSize = new Vector2(24.0f, 24.0f),
};
BackgroundImageNode.AttachNode(this);
HeaderContainerNode = new ResNode {
NodeId = 2,
Size = new Vector2(477.0f, 38.0f),
};
HeaderContainerNode.AttachNode(this);
DividingLineNode = new SimpleNineGridNode {
NodeId = 8,
TexturePath = "ui/uld/WindowA_Line.tex",
TextureCoordinates = Vector2.Zero,
TextureSize = new Vector2(32.0f, 4.0f),
Size = new Vector2(650.0f, 4.0f),
LeftOffset = 12.0f,
RightOffset = 12.0f,
Position = new Vector2(10.0f, 33.0f),
};
DividingLineNode.AttachNode(HeaderContainerNode);
CloseButtonNode = new TextureButtonNode {
NodeId = 7,
Size = new Vector2(28.0f, 28.0f),
Position = new Vector2(449.0f, 6.0f),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
TexturePath = "ui/uld/WindowA_Button.tex",
TextureCoordinates = new Vector2(0.0f, 0.0f),
TextureSize = new Vector2(28.0f, 28.0f),
};
CloseButtonNode.AttachNode(HeaderContainerNode);
ConfigurationButtonNode = new TextureButtonNode {
NodeId = 6,
Size = new Vector2(16.0f, 16.0f),
Position = new Vector2(435.0f, 8.0f),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
TexturePath = "ui/uld/WindowA_Button.tex",
TextureCoordinates = new Vector2(44.0f, 0.0f),
TextureSize = new Vector2(16.0f, 16.0f),
};
ConfigurationButtonNode.AttachNode(HeaderContainerNode);
InformationButtonNode = new TextureButtonNode {
NodeId = 5,
Size = new Vector2(16.0f, 16.0f),
Position = new Vector2(421.0f, 8.0f),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
TexturePath = "ui/uld/WindowA_Button.tex",
TextureCoordinates = new Vector2(28.0f, 0.0f),
TextureSize = new Vector2(16.0f, 16.0f),
};
InformationButtonNode.AttachNode(HeaderContainerNode);
SubtitleNode = new TextNode {
NodeId = 4,
LineSpacing = 12,
AlignmentType = AlignmentType.Left,
FontSize = 12,
FontType = FontType.Axis,
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
TextColor = ColorHelper.GetColor(3),
TextOutlineColor = ColorHelper.GetColor(6),
BackgroundColor = Vector4.Zero,
Size = new Vector2(46.0f, 20.0f),
Position = new Vector2(83.0f, 17.0f),
};
SubtitleNode.AttachNode(HeaderContainerNode);
TitleNode = new TextNode {
NodeId = 3,
LineSpacing = 23,
AlignmentType = AlignmentType.Left,
FontSize = 23,
FontType = FontType.TrumpGothic,
TextFlags = TextFlags.AutoAdjustNodeSize,
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents,
TextColor = ColorHelper.GetColor(2),
TextOutlineColor = ColorHelper.GetColor(7),
BackgroundColor = Vector4.Zero,
Size = new Vector2(86.0f, 31.0f),
Position = new Vector2(12.0f, 7.0f),
};
TitleNode.AttachNode(HeaderContainerNode);
Data->ShowCloseButton = 1;
Data->ShowConfigButton = 0;
Data->ShowHelpButton = 0;
Data->ShowHeader = 1;
Data->Nodes[0] = TitleNode.NodeId;
Data->Nodes[1] = SubtitleNode.NodeId;
Data->Nodes[2] = CloseButtonNode.NodeId;
Data->Nodes[3] = ConfigurationButtonNode.NodeId;
Data->Nodes[4] = InformationButtonNode.NodeId;
Data->Nodes[5] = 0;
Data->Nodes[6] = HeaderContainerNode.NodeId;
Data->Nodes[7] = 0;
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents;
LoadTimelines();
InitializeComponentEvents();
}
public AtkUnitBase* OwnerAddon {
get => Component->OwnerUnitBase;
set => Component->OwnerUnitBase = value;
}
public ReadOnlySeString Title {
get => TitleNode.String;
set {
TitleNode.String = value;
TitleNode.IsVisible = true;
}
}
public ReadOnlySeString Subtitle {
get => SubtitleNode.String;
set {
SubtitleNode.String = value;
SubtitleNode.IsVisible = true;
SubtitleNode.X = TitleNode.X + TitleNode.Width + 2.0f;
}
}
public override void SetTitle(string title, string? subtitle = null) {
base.SetTitle(title, subtitle);
SubtitleNode.Position = new Vector2(TitleNode.Bounds.Right + 4.0f, SubtitleNode.Y);
}
public bool ShowCloseButton {
get => CloseButtonNode.IsVisible;
set => CloseButtonNode.IsVisible = value;
}
public bool ShowConfigButton {
get => ConfigurationButtonNode.IsVisible;
set => ConfigurationButtonNode.IsVisible = value;
}
public bool ShowHelpButton {
get => InformationButtonNode.IsVisible;
set => InformationButtonNode.IsVisible = value;
}
public bool ShowHeader {
get => InformationButtonNode.IsVisible;
set => InformationButtonNode.IsVisible = value;
}
public bool Focused {
get => BorderNode.IsVisible;
set => BorderNode.IsVisible = value;
}
public override float HeaderHeight => HeaderContainerNode.Height;
public override Vector2 ContentSize => new(BackgroundImageNode.Width, BackgroundImageNode.Height - HeaderHeight);
public override Vector2 ContentStartPosition => new(BackgroundImageNode.X, BackgroundImageNode.Y + HeaderHeight);
public override ResNode WindowHeaderFocusNode => HeaderContainerNode;
protected override void OnSizeChanged() {
base.OnSizeChanged();
HeaderContainerNode.Width = Width;
HeaderCollisionNode.Width = Width - 14.0f;
BackgroundNode.Size = Size;
BorderNode.Size = Size;
BackgroundImageNode.Size = new Vector2(Width - 8.0f, Height - 16.0f);
BackgroundImageNode.Position = new Vector2(4.0f, 4.0f);
CloseButtonNode.X = Width - 33.0f;
ConfigurationButtonNode.X = Width - 47.0f;
InformationButtonNode.X = Width - 61.0f;
DividingLineNode.Width = Width - 20.0f;
}
private void LoadTimelines() {
AddTimeline(new TimelineBuilder()
.BeginFrameSet(1, 29)
.AddLabelPair(1, 9, 17)
.AddLabelPair(10, 19, 18)
.AddLabelPair(20, 29, 7)
.EndFrameSet()
.Build());
BackgroundNode.AddTimeline(new TimelineBuilder()
.AddFrameSetWithFrame(1, 9, 1, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(10, 19, 10, multiplyColor: new Vector3(100.0f))
.AddFrameSetWithFrame(20, 29, 20, multiplyColor: new Vector3(50.0f))
.Build());
BorderNode.AddTimeline(new TimelineBuilder()
.BeginFrameSet(10, 19)
.AddFrame(10, alpha: 0)
.AddFrame(12, alpha: 255)
.EndFrameSet()
.Build());
}
}
@@ -0,0 +1,19 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Nodes;
public abstract unsafe class WindowNodeBase : ComponentNode<AtkComponentWindow, AtkUldComponentDataWindow> {
protected WindowNodeBase() {
SetInternalComponentType(ComponentType.Window);
}
public abstract Vector2 ContentSize { get; }
public abstract Vector2 ContentStartPosition { get; }
public abstract float HeaderHeight { get; }
public abstract ResNode WindowHeaderFocusNode { get; }
public virtual void SetTitle(string title, string? subtitle = null)
=> Component->SetTitle(title, subtitle ?? string.Empty);
}
@@ -0,0 +1,7 @@
namespace KamiToolKit.Nodes;
public class AlignedHorizontalListNode : HorizontalListNode {
protected override void AdjustNode(NodeBase node) {
node.Y = 0.0f;
}
}
@@ -0,0 +1,7 @@
namespace KamiToolKit.Nodes;
public abstract class AlignedVerticalListNode : VerticalListNode {
protected override void AdjustNode(NodeBase node) {
node.X = 0.0f;
}
}
+70
View File
@@ -0,0 +1,70 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace KamiToolKit.Nodes;
public record GridSize(int Columns, int Rows);
public class GridNode : SimpleComponentNode {
private readonly List<SimpleComponentNode> gridNodes = [];
public SimpleComponentNode this[int x, int y] {
get => gridNodes[x + y * GridSize.Columns];
set => gridNodes[x + y * GridSize.Columns] = value;
}
public SimpleComponentNode this[int index] {
get => gridNodes[index];
set => gridNodes[index] = value;
}
/// <summary>
/// Warning: Changing this value will dispose any existing layout nodes.
/// </summary>
public required GridSize GridSize {
get;
set {
field = value;
ReallocateArray();
}
} = new(0, 0);
private void ReallocateArray() {
foreach (var node in gridNodes) {
node.Dispose();
}
gridNodes.Clear();
foreach (var _ in Enumerable.Range(0, GridSize.Rows * GridSize.Columns)) {
gridNodes.Add(new SimpleComponentNode());
}
foreach (var row in Enumerable.Range(0, GridSize.Rows)) {
foreach (var column in Enumerable.Range(0, GridSize.Columns)) {
this[column, row].AttachNode(this);
this[column, row].IsVisible = true;
}
}
RecalculateLayout();
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
RecalculateLayout();
}
public void RecalculateLayout() {
var gridWidth = Width / GridSize.Columns;
var gridHeight = Height / GridSize.Rows;
foreach (var row in Enumerable.Range(0, GridSize.Rows)) {
foreach (var column in Enumerable.Range(0, GridSize.Columns)) {
this[column, row].Size = new Vector2(gridWidth, gridHeight);
this[column, row].Position = new Vector2(column * gridWidth, row * gridHeight);
}
}
}
}
@@ -0,0 +1,49 @@
using System.Linq;
using KamiToolKit.Enums;
namespace KamiToolKit.Nodes;
public class HorizontalFlexNode : LayoutListNode {
public FlexFlags AlignmentFlags { get; set; } = FlexFlags.FitContentHeight;
public float FitPadding { get; set; } = 4.0f;
public override float Width {
get => base.Width;
set {
base.Width = value;
RecalculateLayout();
}
}
protected override void OnRecalculateLayout() {
var step = Width / NodeList.Count;
if (NodeList.Count != 0 && AlignmentFlags.HasFlag(FlexFlags.FitContentHeight)) {
Height = NodeList.Max(node => node.Height);
}
foreach (var index in Enumerable.Range(0, NodeList.Count)) {
if (AlignmentFlags.HasFlag(FlexFlags.CenterHorizontally)) {
NodeList[index].X = step * index + step / 2.0f - NodeList[index].Width / 2.0f;
}
else {
NodeList[index].X = step * index;
}
if (AlignmentFlags.HasFlag(FlexFlags.FitHeight)) {
NodeList[index].Height = Height;
}
if (AlignmentFlags.HasFlag(FlexFlags.CenterVertically)) {
NodeList[index].Y = Height / 2 - NodeList[index].Height / 2;
}
if (AlignmentFlags.HasFlag(FlexFlags.FitWidth)) {
NodeList[index].Width = step - FitPadding;
}
}
}
}
@@ -0,0 +1,66 @@
using System.Linq;
using KamiToolKit.Enums;
namespace KamiToolKit.Nodes;
public class HorizontalListNode : LayoutListNode {
public HorizontalListAnchor Alignment {
get;
set {
field = value;
RecalculateLayout();
}
}
public override float Width {
get => base.Width;
set {
base.Width = value;
RecalculateLayout();
}
}
/// <summary>
/// Adjusts contained nodes heights to match this nodes height
/// </summary>
public bool FitHeight { get; set; }
/// <summary>
/// Resizes the horizontal list node to fit all contents
/// </summary>
public bool FitToContentHeight { get; set; }
protected override void OnRecalculateLayout() {
var startX = Alignment switch {
HorizontalListAnchor.Left => 0.0f + FirstItemSpacing,
HorizontalListAnchor.Right => Width - FirstItemSpacing,
_ => 0.0f,
};
foreach (var node in NodeList) {
if (!node.IsVisible) continue;
if (Alignment is HorizontalListAnchor.Right) {
startX -= node.Width + ItemSpacing;
}
node.X = startX;
AdjustNode(node);
if (Alignment is HorizontalListAnchor.Left) {
startX += node.Width + ItemSpacing;
}
if (FitHeight) {
node.Height = Height;
}
}
if (FitToContentHeight) {
Height = NodeList.Max(node => node.Height);
}
}
public float AreaRemaining => Width - NodeList.Sum(node => node.Width + ItemSpacing) - ItemSpacing;
}
@@ -0,0 +1,29 @@
using System.Linq;
using System.Numerics;
namespace KamiToolKit.Nodes;
public class LabelLayoutNode : LayoutListNode {
public bool FillWidth { get; set; }
protected override void OnRecalculateLayout() {
if (Nodes.Count is 0) return;
var labelNode = Nodes[0];
var labelNodeWidth = labelNode.Width;
labelNode.Position = new Vector2(0.0f, 0.0f);
var position = labelNodeWidth + FirstItemSpacing;
foreach (var node in Nodes.Skip(1)) {
node.X = position;
if (FillWidth) {
node.Width = (Width - labelNodeWidth - FirstItemSpacing) / (Nodes.Count - 1);
}
position += node.Width + ItemSpacing;
}
}
}
+317
View File
@@ -0,0 +1,317 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Nodes;
public abstract class LayoutListNode : SimpleComponentNode {
protected readonly List<NodeBase> NodeList = [];
private bool suppressRecalculateLayout;
public IEnumerable<T> GetNodes<T>() where T : NodeBase => NodeList.OfType<T>();
public IReadOnlyList<NodeBase> Nodes => NodeList;
public bool ClipListContents {
get => NodeFlags.HasFlag(NodeFlags.Clip);
set {
if (value) {
AddNodeFlags(NodeFlags.Clip);
}
else {
RemoveNodeFlags(NodeFlags.Clip);
}
}
}
public float ItemSpacing { get; set; }
public float FirstItemSpacing { get; set; }
public void RecalculateLayout() {
if (suppressRecalculateLayout) return;
OnRecalculateLayout();
foreach (var node in NodeList) {
if (node is LayoutListNode subNode) {
subNode.RecalculateLayout();
}
}
}
protected abstract void OnRecalculateLayout();
protected virtual void AdjustNode(NodeBase node) { }
public ICollection<NodeBase> InitialNodes {
init => AddNode(value);
}
public void AddNode(IEnumerable<NodeBase> nodes)
{
suppressRecalculateLayout = true;
try
{
foreach (var node in nodes)
{
AddNode(node);
}
}
finally
{
suppressRecalculateLayout = false;
}
RecalculateLayout();
}
public virtual void AddNode(NodeBase? node) {
if (node is null) return;
NodeList.Add(node);
node.AttachNode(this);
RecalculateLayout();
}
public void RemoveNode(params NodeBase[] items)
{
suppressRecalculateLayout = true;
try
{
foreach (var node in items)
{
RemoveNode(node);
}
}
finally
{
suppressRecalculateLayout = false;
}
RecalculateLayout();
}
public virtual void RemoveNode(NodeBase node) {
if (!NodeList.Contains(node)) return;
NodeList.Remove(node);
node.Dispose();
RecalculateLayout();
}
public void AddDummy(float size = 0.0f) {
var dummyNode = new ResNode {
Size = new Vector2(size, size),
};
AddNode(dummyNode);
}
public virtual void Clear()
{
suppressRecalculateLayout = true;
try
{
foreach (var node in NodeList.ToList())
{
RemoveNode(node);
}
}
finally
{
suppressRecalculateLayout = false;
}
RecalculateLayout();
}
public delegate TU CreateNewNode<in T, out TU>(T data) where TU : NodeBase;
public delegate T GetDataFromNode<out T, in TU>(TU node) where TU : NodeBase;
public bool SyncWithListData<T, TU>(IEnumerable<T> dataList, GetDataFromNode<T?, TU> getDataFromNode, CreateNewNode<T, TU> createNodeMethod) where TU : NodeBase
{
suppressRecalculateLayout = true;
var anythingChanged = false;
try
{
var nodesOfType = GetNodes<TU>().ToList();
var dataSet = dataList.ToHashSet(EqualityComparer<T>.Default);
var represented = new HashSet<T>(EqualityComparer<T>.Default);
foreach (var node in nodesOfType)
{
var nodeData = getDataFromNode(node);
if (nodeData is null || !dataSet.Contains(nodeData))
{
RemoveNode(node);
anythingChanged = true;
continue;
}
represented.Add(nodeData);
}
foreach (var data in dataSet)
{
if (represented.Contains(data))
continue;
var newNode = createNodeMethod(data);
AddNode(newNode);
anythingChanged = true;
}
}
finally
{
suppressRecalculateLayout = false;
}
RecalculateLayout();
return anythingChanged;
}
public bool SyncWithListDataByKey<T, TU, TKey>(
IReadOnlyList<T> dataList,
Func<T, TKey> getKeyFromData,
Func<TU, TKey> getKeyFromNode,
Action<TU, T> updateNode,
CreateNewNode<T, TU> createNodeMethod,
IEqualityComparer<TKey>? keyComparer = null) where TU : NodeBase where TKey : notnull
{
suppressRecalculateLayout = true;
var anythingChanged = false;
try
{
keyComparer ??= EqualityComparer<TKey>.Default;
var existing = new List<TU>(capacity: NodeList.Count);
foreach (var t in NodeList)
{
if (t is TU tu)
existing.Add(tu);
}
var byKey = new Dictionary<TKey, TU>(existing.Count, keyComparer);
List<TU>? duplicates = null;
foreach (var node in existing)
{
var key = getKeyFromNode(node);
if (!byKey.TryAdd(key, node))
(duplicates ??= new List<TU>(4)).Add(node);
}
var desired = new List<TU>(dataList.Count);
foreach (var data in dataList)
{
var key = getKeyFromData(data);
if (byKey.TryGetValue(key, out var existingNode))
{
updateNode(existingNode, data);
desired.Add(existingNode);
byKey.Remove(key);
}
else
{
var newNode = createNodeMethod(data);
AddNode(newNode);
updateNode(newNode, data);
desired.Add(newNode);
anythingChanged = true;
}
}
if (byKey.Count != 0)
{
foreach (var kv in byKey)
{
RemoveNode(kv.Value);
anythingChanged = true;
}
}
if (duplicates is not null)
{
for (var i = 0; i < duplicates.Count; i++)
{
RemoveNode(duplicates[i]);
anythingChanged = true;
}
}
var desiredCount = desired.Count;
var j = 0;
var mismatch = false;
for (var i = 0; i < NodeList.Count; i++)
{
if (NodeList[i] is TU)
{
if (j >= desiredCount)
{
mismatch = true;
break;
}
NodeBase desiredNode = desired[j++];
if (!ReferenceEquals(NodeList[i], desiredNode))
{
NodeList[i] = desiredNode;
anythingChanged = true;
}
}
}
if (!mismatch && j != desiredCount)
mismatch = true;
if (mismatch)
{
var firstTuIndex = -1;
for (var i = 0; i < NodeList.Count; i++)
{
if (NodeList[i] is TU)
{
firstTuIndex = i;
break;
}
}
if (firstTuIndex < 0)
firstTuIndex = NodeList.Count;
for (var i = NodeList.Count - 1; i >= 0; i--)
{
if (NodeList[i] is TU)
NodeList.RemoveAt(i);
}
NodeList.InsertRange(firstTuIndex, desired);
anythingChanged = true;
}
}
finally
{
suppressRecalculateLayout = false;
}
RecalculateLayout();
return anythingChanged;
}
public void ReorderNodes(Comparison<NodeBase> comparison) {
NodeList.Sort(comparison);
RecalculateLayout();
}
}
+200
View File
@@ -0,0 +1,200 @@
using System;
using System.Linq;
using System.Numerics;
using KamiToolKit.Enums;
namespace KamiToolKit.Nodes;
/// Node that manages the layout of other nodes
public class ListBoxNode : LayoutListNode {
public readonly BackgroundImageNode Background;
public readonly BorderNineGridNode Border;
public ListBoxNode() {
Background = new BackgroundImageNode {
IsVisible = false,
};
Background.AttachNode(this);
Border = new BorderNineGridNode {
IsVisible = false,
};
Border.AttachNode(this);
}
public LayoutAnchor LayoutAnchor {
get;
set {
field = value;
RecalculateLayout();
}
}
public bool FitContents {
get;
set {
field = value;
RecalculateLayout();
Size = GetMinimumSize();
}
}
public LayoutOrientation LayoutOrientation {
get;
set {
field = value;
RecalculateLayout();
}
}
public Vector4 BackgroundColor {
get => Background.Color;
set => Background.Color = value;
}
public bool ShowBackground {
get => Background.IsVisible;
set => Background.IsVisible = value;
}
public bool ShowBorder {
get => Border.IsVisible;
set => Border.IsVisible = value;
}
public override float Height {
get => base.Height;
set => base.Height = FitContents ? GetMinimumSize().Y : value;
}
public override float Width {
get => base.Width;
set => base.Width = FitContents ? GetMinimumSize().X : value;
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
Background.Size = Size;
Border.Size = Size + new Vector2(30.0f, 30.0f);
Border.Position = -new Vector2(15.0f, 15.0f);
RecalculateLayout();
}
protected override void OnRecalculateLayout() {
var runningPosition = LayoutOrientation switch {
LayoutOrientation.Vertical when LayoutAnchor is LayoutAnchor.TopLeft or LayoutAnchor.TopRight
=> GetLayoutStartPosition() + new Vector2(0.0f, FirstItemSpacing),
LayoutOrientation.Vertical when LayoutAnchor is LayoutAnchor.BottomLeft or LayoutAnchor.BottomRight
=> GetLayoutStartPosition() - new Vector2(0.0f, FirstItemSpacing),
LayoutOrientation.Horizontal when LayoutAnchor is LayoutAnchor.BottomLeft or LayoutAnchor.TopLeft
=> GetLayoutStartPosition() + new Vector2(FirstItemSpacing, 0.0f),
LayoutOrientation.Horizontal when LayoutAnchor is LayoutAnchor.BottomRight or LayoutAnchor.TopRight
=> GetLayoutStartPosition() - new Vector2(FirstItemSpacing, 0.0f),
_ => Vector2.Zero,
};
foreach (var node in NodeList.Where(node => node.IsVisible)) {
if (LayoutOrientation is LayoutOrientation.Vertical) {
switch (LayoutAnchor) {
case LayoutAnchor.TopLeft:
node.Position = runningPosition;
runningPosition.Y += node.Height * node.Scale.Y + ItemSpacing;
break;
case LayoutAnchor.TopRight:
node.Position = runningPosition - new Vector2(node.Width * node.Scale.X, 0.0f);
runningPosition.Y += node.Height * node.Scale.Y + ItemSpacing;
break;
case LayoutAnchor.BottomLeft:
node.Position = runningPosition - new Vector2(0.0f, node.Height * node.Scale.Y);
runningPosition.Y -= node.Height * node.Scale.Y + ItemSpacing;
break;
case LayoutAnchor.BottomRight:
node.Position = runningPosition - new Vector2(node.Width * node.Scale.X, node.Height * node.Scale.Y);
runningPosition.Y -= node.Height * node.Scale.Y + ItemSpacing;
break;
}
}
else if (LayoutOrientation is LayoutOrientation.Horizontal) {
switch (LayoutAnchor) {
case LayoutAnchor.TopLeft:
node.Position = runningPosition;
runningPosition.X += node.Width * node.Scale.X + ItemSpacing;
break;
case LayoutAnchor.TopRight:
node.Position = runningPosition - new Vector2(node.Width * node.Scale.X, 0.0f);
runningPosition.X -= node.Width * node.Scale.X + ItemSpacing;
break;
case LayoutAnchor.BottomLeft:
node.Position = runningPosition - new Vector2(0.0f, node.Height * node.Scale.Y);
runningPosition.X += node.Width * node.Scale.X + ItemSpacing;
break;
case LayoutAnchor.BottomRight:
node.Position = runningPosition - new Vector2(node.Width * node.Scale.X, node.Height * node.Scale.Y);
runningPosition.X -= node.Width * node.Scale.X + ItemSpacing;
break;
}
}
}
}
public override void AddNode(NodeBase? node) {
base.AddNode(node);
Size = GetMinimumSize();
}
public override void RemoveNode(NodeBase node) {
base.RemoveNode(node);
Size = GetMinimumSize();
}
/// <summary>
/// Get the current minimum size that would contain all the nodes including their margins.
/// </summary>
public Vector2 GetMinimumSize() {
var size = LayoutOrientation switch {
LayoutOrientation.Vertical => new Vector2(0.0f, FirstItemSpacing),
LayoutOrientation.Horizontal => new Vector2(FirstItemSpacing, 0.0f),
_ => Vector2.Zero,
};
foreach (var node in NodeList.Where(node => node.IsVisible)) {
switch (LayoutOrientation) {
// Horizontal we take max height, and add widths
case LayoutOrientation.Horizontal:
size.Y = MathF.Max(size.Y, node.Height);
size.X += node.Width + ItemSpacing;
break;
// Vertical we take max width, and add heights
case LayoutOrientation.Vertical:
size.X = MathF.Max(size.X, node.Width);
size.Y += node.Height + ItemSpacing;
break;
}
}
return size;
}
private Vector2 GetLayoutStartPosition() => LayoutAnchor switch {
LayoutAnchor.TopLeft => Vector2.Zero,
LayoutAnchor.TopRight => new Vector2(Width, 0.0f),
LayoutAnchor.BottomLeft => new Vector2(0.0f, Height),
LayoutAnchor.BottomRight => new Vector2(Width, Height),
_ => throw new ArgumentOutOfRangeException(),
};
}
+40
View File
@@ -0,0 +1,40 @@
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
public abstract class ListItemNode<T> : SelectableNode {
public abstract float ItemHeight { get; }
public T? ItemData {
get;
set {
if (value is not null) {
if (!GenericUtil.AreEqual(field, value)) {
IsSettingNodeData = true;
SetNodeData(value);
IsSettingNodeData = false;
}
}
field = value;
IsVisible = value is not null;
}
}
/// <summary>
/// Bool that indicates if SetNodeDate when different is being called.
/// Used to prevent things like checkboxes from trigger a file save due to the value being changed.
/// </summary>
protected bool IsSettingNodeData { get; private set; }
protected abstract void SetNodeData(T itemData);
public virtual void Update() { }
protected void DisableInteractions() {
EnableSelection = false;
EnableHighlight = false;
DisableCollisionNode = true;
}
}
+181
View File
@@ -0,0 +1,181 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
public unsafe class ListNode<T, TU> : SimpleComponentNode where TU : ListItemNode<T>, new() {
public readonly ScrollBarNode ScrollBarNode;
public ListNode() {
using (var displayNode = new TU()) {
itemHeight = displayNode.ItemHeight;
}
ScrollBarNode = new ScrollBarNode {
OnValueChanged = OnScrollUpdate,
ScrollSpeed = (int) itemHeight,
HideWhenDisabled = true,
};
ScrollBarNode.AttachNode(this);
AddEvent(AtkEventType.MouseWheel, OnMouseWheel);
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
ScrollBarNode.Size = new Vector2(8.0f, Height);
ScrollBarNode.Position = new Vector2(Width - 8.0f, 0.0f);
var newNodeCount = (int)(Height / (itemHeight + ItemSpacing));
if (newNodeCount != nodeCount) {
FullRebuild();
}
foreach (var node in nodeList) {
node.Width = ScrollBarNode.Bounds.Left - 8.0f;
}
RecalculateScroll();
}
public Action<T?>? OnItemSelected { get; set; }
public float ItemSpacing {
get;
set {
field = value;
FullRebuild();
}
}
public required List<T> OptionsList {
get;
set {
field = value;
var newNodeCount = (int)(Height / (itemHeight + ItemSpacing));
if (newNodeCount != nodeCount) {
FullRebuild();
}
else {
PopulateNodes();
RecalculateScroll();
}
}
} = [];
private readonly List<TU> nodeList = [];
private readonly float itemHeight;
private T? selectedItem;
private int scrollPosition;
private int nodeCount;
/// <summary>
/// Resets and rebuilds list
/// </summary>
public void FullRebuild() {
foreach (var node in nodeList) {
node.Dispose();
}
nodeList.Clear();
scrollPosition = Math.Clamp(scrollPosition, 0, Math.Max(OptionsList.Count - nodeCount, 0));
selectedItem = default;
RebuildNodeList();
PopulateNodes();
RecalculateScroll();
}
public void Update() {
PopulateNodes();
foreach (var node in nodeList) {
if (node.IsVisible) {
node.Update();
}
}
}
private void RebuildNodeList() {
nodeCount = (int)(Height / (itemHeight + ItemSpacing));
if (nodeCount < 1) return;
foreach (var index in Enumerable.Range(0, nodeCount)) {
var node = new TU {
Size = new Vector2(ScrollBarNode.Bounds.Left - 8.0f, itemHeight),
Position = new Vector2(0.0f, index * (itemHeight + ItemSpacing)),
NodeId = (uint)index + 2,
OnClick = clickedNode => {
SelectItem(((TU)clickedNode).ItemData);
OnItemSelected?.Invoke(selectedItem);
},
IsVisible = false,
};
node.AttachNode(this);
nodeList.Add(node);
}
}
private void PopulateNodes() {
foreach (var (nodeIndex, node) in nodeList.Index()) {
var dataIndex = scrollPosition + nodeIndex;
if (dataIndex < OptionsList.Count) {
var item = OptionsList[dataIndex];
node.ItemData = item;
node.IsVisible = true;
node.IsSelected = GenericUtil.AreEqual(item, selectedItem);
}
else {
node.IsVisible = false;
}
}
}
private void SelectItem(T? item) {
if (item is null) return;
selectedItem = item;
foreach (var node in nodeList) {
if (node.ItemData is null) {
node.IsSelected = false;
}
else {
node.IsSelected = GenericUtil.AreEqual(node.ItemData, selectedItem);
}
}
}
private void RecalculateScroll() {
if (OptionsList.Count < nodeCount) {
ScrollBarNode.ScrollPosition = 0;
ScrollBarNode.IsEnabled = false;
}
var totalHeight = (int)( OptionsList.Count * (itemHeight + ItemSpacing) + ItemSpacing);
ScrollBarNode.UpdateScrollParams((int) (nodeList.Count * (itemHeight + ItemSpacing)), totalHeight);
ScrollBarNode.ScrollPosition = (int)( scrollPosition * (itemHeight + ItemSpacing) );
}
private void OnScrollUpdate(int newPosition) {
scrollPosition = (int)( newPosition / ( itemHeight + ItemSpacing ) );
PopulateNodes();
}
private void OnMouseWheel(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
scrollPosition += atkEventData->IsScrollUp ? -1 : 1;
scrollPosition = Math.Clamp(scrollPosition, 0, Math.Max(0, OptionsList.Count - nodeCount));
ScrollBarNode.ScrollPosition = (int)( scrollPosition * (itemHeight + ItemSpacing) );
PopulateNodes();
atkEvent->SetEventIsHandled();
}
}
@@ -0,0 +1,46 @@
using System;
using System.Linq;
using KamiToolKit.Enums;
namespace KamiToolKit.Nodes;
public class OrderedVerticalListNode<T, TU> : VerticalListNode where T : NodeBase {
public Func<T, TU>? OrderSelector { get; set; }
protected override void OnRecalculateLayout() {
var typedList = NodeList.OfType<T>();
if (OrderSelector is null) {
RecalculateLayout();
return;
}
var orderedList = typedList.OrderBy(OrderSelector).ToList();
var startY = Anchor switch {
VerticalListAnchor.Top => 0.0f + FirstItemSpacing,
VerticalListAnchor.Bottom => Height,
_ => 0.0f,
};
foreach (var node in orderedList) {
if (!node.IsVisible) continue;
if (Anchor is VerticalListAnchor.Bottom) {
startY -= node.Height + ItemSpacing;
}
node.Y = startY;
AdjustNode(node);
if (Anchor is VerticalListAnchor.Top) {
startY += node.Height + ItemSpacing;
}
}
if (FitContents) {
Height = orderedList.Sum(node => node.IsVisible ? node.Height + ItemSpacing : 0.0f) + FirstItemSpacing;
}
}
}
@@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using KamiToolKit.Enums;
namespace KamiToolKit.Nodes;
/// <summary>
/// This is a combination of a ScrollingAreaNode and a VerticalListNode for easy layout
/// </summary>
public class ScrollingListNode : SimpleComponentNode {
private readonly ScrollingAreaNode<VerticalListNode> listNode;
public ScrollingListNode() {
listNode = new ScrollingAreaNode<VerticalListNode> {
ContentHeight = 100.0f,
};
listNode.AttachNode(this);
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
listNode.Size = Size;
listNode.ContentNode.RecalculateLayout();
listNode.FitToContentHeight();
}
public bool FitContents {
get => listNode.ContentNode.FitContents;
set => listNode.ContentNode.FitContents = value;
}
public bool FitWidth {
get => listNode.ContentNode.FitWidth;
set => listNode.ContentNode.FitWidth = value;
}
public VerticalListAnchor Anchor {
get => listNode.ContentNode.Anchor;
set => listNode.ContentNode.Anchor = value;
}
public VerticalListAlignment Alignment {
get => listNode.ContentNode.Alignment;
set => listNode.ContentNode.Alignment = value;
}
public bool ClipListContents {
get => listNode.ContentNode.ClipListContents;
set => listNode.ContentNode.ClipListContents = value;
}
public float ItemSpacing {
get => listNode.ContentNode.ItemSpacing;
set => listNode.ContentNode.ItemSpacing = value;
}
public float FirstItemSpacing {
get => listNode.ContentNode.FirstItemSpacing;
set => listNode.ContentNode.FirstItemSpacing = value;
}
public ICollection<NodeBase> InitialNodes {
init => listNode.ContentNode.AddNode(value);
}
public bool AutoHideScrollBar {
get => listNode.AutoHideScrollBar;
set => listNode.AutoHideScrollBar = value;
}
public int ScrollSpeed {
get => listNode.ScrollSpeed;
set => listNode.ScrollSpeed = value;
}
public int ScrollPosition {
get => listNode.ScrollPosition;
set => listNode.ScrollPosition = value;
}
public float ContentWidth => listNode.ContentNode.Width;
public IReadOnlyList<NodeBase> Nodes => listNode.ContentNode.Nodes;
public IEnumerable<T> GetNodes<T>() where T : NodeBase => listNode.ContentNode.GetNodes<T>();
public void RecalculateLayout() {
listNode.ContentNode.RecalculateLayout();
listNode.FitToContentHeight();
}
public void FitToContentHeight() => listNode.FitToContentHeight();
public void AddNode(IEnumerable<NodeBase> nodes) => listNode.ContentNode.AddNode(nodes);
public void AddNode(NodeBase? node) => listNode.ContentNode.AddNode(node);
public void RemoveNode(params NodeBase[] nodes) => listNode.ContentNode.RemoveNode(nodes);
public void RemoveNode(NodeBase node) => listNode.ContentNode.RemoveNode(node);
public void AddDummy(float size = 0.0f) => listNode.ContentNode.AddDummy(size);
public void Clear() => listNode.ContentNode.Clear();
public void ReorderNodes(Comparison<NodeBase> comparison) => listNode.ContentNode.ReorderNodes(comparison);
public VerticalListNode VerticalListNode => listNode.ContentNode;
}
@@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Linq;
namespace KamiToolKit.Nodes;
/// <summary>
/// This is a combination of a ScrollingAreaNode and a TreeListNode for easy layout
/// </summary>
public class ScrollingTreeNode : SimpleComponentNode {
private readonly ScrollingAreaNode<TreeListNode> listNode;
public ScrollingTreeNode() {
listNode = new ScrollingAreaNode<TreeListNode> {
ContentHeight = 100.0f,
};
listNode.AttachNode(this);
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
listNode.Size = Size;
RecalculateLayout();
}
public float CategoryVerticalSpacing {
get => listNode.ContentNode.CategoryVerticalSpacing;
set => listNode.ContentNode.CategoryVerticalSpacing = value;
}
public bool AutoHideScrollBar {
get => listNode.AutoHideScrollBar;
set => listNode.AutoHideScrollBar = value;
}
public int ScrollSpeed {
get => listNode.ScrollSpeed;
set => listNode.ScrollSpeed = value;
}
public IReadOnlyList<TreeListCategoryNode> CategoryNodes => listNode.ContentNode.CategoryNodes;
public void RecalculateLayout() {
listNode.ContentNode.RefreshLayout();
listNode.ContentHeight = CategoryNodes.Sum(node => node.IsVisible ? node.Height + CategoryVerticalSpacing : 0.0f);
}
public void AddCategoryNode(TreeListCategoryNode node) => listNode.ContentNode.AddCategoryNode(node);
public TreeListNode TreeListNode => listNode.ContentNode;
}
@@ -0,0 +1,99 @@
using System.Collections.Generic;
using System.Linq;
using KamiToolKit.Classes;
namespace KamiToolKit.Nodes;
public class TabbedVerticalListNode : SimpleComponentNode {
private readonly List<TabbedNodeEntry<NodeBase>> nodeList = [];
public float TabSize { get; set; } = 18.0f;
public float ItemVerticalSpacing { get; set; }
public bool FitWidth { get; set; }
public int TabStep { get; set; }
// Adds tab amount to any following nodes being added
public void AddTab(int tabAmount) {
TabStep += tabAmount;
}
// Removes tab amount from any following nodes being added
public void SubtractTab(int tabAmount) {
TabStep -= tabAmount;
}
public void AddNode(NodeBase node) {
AddNode(0, node);
}
public void AddNode(IEnumerable<NodeBase> nodes) {
AddNode(0, nodes);
}
public void AddNode(int tabIndex, IEnumerable<NodeBase> nodes) {
foreach (var node in nodes) {
AddNode(tabIndex, node);
}
}
public void AddNode(int tabIndex, NodeBase node) {
nodeList.Add(new TabbedNodeEntry<NodeBase>(node, tabIndex + TabStep));
node.AttachNode(this);
node.NodeId = (uint)nodeList.Count + 1;
RecalculateLayout();
}
public void RemoveNode(params NodeBase[] nodes) {
foreach (var node in nodes) {
RemoveNode(node);
}
}
public void RemoveNode(NodeBase node) {
var target = nodeList.FirstOrDefault(item => item.Node == node);
if (target is null) return;
target.Node.DetachNode();
nodeList.Remove(target);
RecalculateLayout();
}
public void Clear() {
foreach (var nodeEntry in nodeList) {
nodeEntry.Node.DetachNode();
}
nodeList.Clear();
RecalculateLayout();
}
public void RecalculateLayout() {
var startY = 0.0f;
foreach (var (node, tab) in nodeList) {
if (!node.IsVisible) continue;
node.Y = startY;
node.X = tab * TabSize;
if (FitWidth) {
node.Width = Width - node.X - ItemVerticalSpacing;
// Also update layout of any contained nodes
if (node is LayoutListNode layoutNode) {
layoutNode.RecalculateLayout();
}
}
startY += node.Height + ItemVerticalSpacing;
}
Height = startY + ItemVerticalSpacing;
}
}

Some files were not shown because too many files have changed in this diff Show More