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