Initial commit: AetherBags + KamiToolKit for FC Gitea
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user