Initial commit: AetherBags + KamiToolKit for FC Gitea
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace KamiToolKit.Premade.Nodes;
|
||||
|
||||
public unsafe class AlphaBarNode : SimpleComponentNode {
|
||||
public readonly ImGuiImageNode AlphaBarBackgroundNode;
|
||||
public readonly ImGuiImageNode AlphaBarGradientNode;
|
||||
public readonly ImGuiImageNode AlphaBarSelectorNode;
|
||||
|
||||
private readonly ViewportEventListener alphaEventListener;
|
||||
private bool isAlphaDragging;
|
||||
|
||||
public AlphaBarNode() {
|
||||
alphaEventListener = new ViewportEventListener(AlphaSliderEvent);
|
||||
|
||||
AlphaBarBackgroundNode = new AlphaImageNode();
|
||||
AlphaBarBackgroundNode.AttachNode(this);
|
||||
|
||||
AlphaBarGradientNode = new ImGuiImageNode {
|
||||
TexturePath = DalamudInterface.Instance.GetAssetPath("VerticalGradient_WhiteToAlpha.png"),
|
||||
FitTexture = true,
|
||||
};
|
||||
AlphaBarGradientNode.AttachNode(this);
|
||||
AlphaBarGradientNode.AddEvent(AtkEventType.MouseDown, OnAlphaBarMouseDown);
|
||||
|
||||
AlphaBarSelectorNode = new ImGuiImageNode {
|
||||
TexturePath = DalamudInterface.Instance.GetAssetPath("alpha_selector.png"),
|
||||
FitTexture = true,
|
||||
};
|
||||
AlphaBarSelectorNode.AttachNode(this);
|
||||
AlphaBarSelectorNode.AddEvent(AtkEventType.MouseDown, OnAlphaBarMouseDown);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing, bool isNativeDestructor) {
|
||||
if (disposing) {
|
||||
base.Dispose(disposing, isNativeDestructor);
|
||||
|
||||
alphaEventListener.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
AlphaBarBackgroundNode.Size = Size;
|
||||
AlphaBarGradientNode.Size = Size;
|
||||
|
||||
AlphaBarSelectorNode.Size = new Vector2(Width + 4.0f, 10.0f);
|
||||
AlphaBarSelectorNode.Position = new Vector2(-2.0f, 0.0f);
|
||||
}
|
||||
|
||||
private void OnAlphaBarMouseDown() {
|
||||
if (!isAlphaDragging) {
|
||||
alphaEventListener.AddEvent(AtkEventType.MouseMove, AlphaBarGradientNode);
|
||||
alphaEventListener.AddEvent(AtkEventType.MouseUp, AlphaBarGradientNode);
|
||||
isAlphaDragging = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void AlphaSliderEvent(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
|
||||
switch (eventType) {
|
||||
case AtkEventType.MouseUp:
|
||||
alphaEventListener.RemoveEvent(AtkEventType.MouseMove);
|
||||
alphaEventListener.RemoveEvent(AtkEventType.MouseUp);
|
||||
isAlphaDragging = false;
|
||||
break;
|
||||
|
||||
case AtkEventType.MouseMove: {
|
||||
var mousePosition = new Vector2(atkEventData->MouseData.PosX, atkEventData->MouseData.PosY);
|
||||
var scale = ParentAddon is not null ? ParentAddon->Scale : 1.0f;
|
||||
var scaledHeight = AlphaBarGradientNode.Height * scale;
|
||||
var minY = AlphaBarGradientNode.ScreenY;
|
||||
var maxY = AlphaBarGradientNode.ScreenY + scaledHeight;
|
||||
|
||||
if (mousePosition.Y >= minY && mousePosition.Y <= maxY) {
|
||||
var alphaRatio = 1.0f - (mousePosition.Y - AlphaBarGradientNode.ScreenY) / scaledHeight;
|
||||
|
||||
AlphaBarSelectorNode.Y = Height - Height * alphaRatio - 5.0f;
|
||||
OnAlphaChanged?.Invoke(alphaRatio);
|
||||
}
|
||||
else if (mousePosition.Y < minY) {
|
||||
AlphaBarSelectorNode.Y = -4.0f;
|
||||
OnAlphaChanged?.Invoke(1.0f);
|
||||
}
|
||||
else if (mousePosition.Y > maxY) {
|
||||
AlphaBarSelectorNode.Y = Height - 4.0f;
|
||||
OnAlphaChanged?.Invoke(0.0f);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Action<float>? OnAlphaChanged { get; init; }
|
||||
|
||||
public override Vector4 Color {
|
||||
get => AlphaBarGradientNode.Color;
|
||||
set {
|
||||
AlphaBarGradientNode.MultiplyColor = value.AsVector3();
|
||||
AlphaBarSelectorNode.Y = Height - Height * value.W - 5.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public override ColorHelpers.HsvaColor ColorHsva {
|
||||
get => AlphaBarGradientNode.MultiplyColorHsva;
|
||||
set {
|
||||
AlphaBarGradientNode.MultiplyColorHsva = value with { A = 1.0f };
|
||||
AlphaBarSelectorNode.Y = Height - Height * value.A - 5.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace KamiToolKit.Premade.Nodes;
|
||||
|
||||
public abstract class ConfigNode<T> : SimpleComponentNode {
|
||||
public T? ConfigurationOption {
|
||||
get;
|
||||
set {
|
||||
field = value;
|
||||
OptionChanged(value);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void OptionChanged(T? option);
|
||||
|
||||
public Action<T>? OnConfigChanged { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Utility;
|
||||
using KamiToolKit.Nodes;
|
||||
using KamiToolKit.Premade.Widgets;
|
||||
|
||||
namespace KamiToolKit.Premade.Nodes;
|
||||
|
||||
/// <summary>
|
||||
/// A non-owning list node that supports searching, and various callbacks for easily editing a list.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Data type to display the data for.</typeparam>
|
||||
/// <typeparam name="TU">ListItemNode derived type, for defining the result view.</typeparam>
|
||||
public class ModifyListNode<T, TU> : SimpleComponentNode where TU : ListItemNode<T>, new() {
|
||||
private readonly SearchWidget searchWidget;
|
||||
private readonly ListNode<T, TU> listNode;
|
||||
|
||||
private readonly TextButtonNode addButton;
|
||||
private readonly TextButtonNode editButton;
|
||||
private readonly TextButtonNode removeButton;
|
||||
|
||||
public ModifyListNode() {
|
||||
searchWidget = new SearchWidget {
|
||||
OnSortOrderChanged = OnSortOrderChanged,
|
||||
OnSearchUpdated = OnSearchUpdated,
|
||||
};
|
||||
searchWidget.AttachNode(this);
|
||||
|
||||
listNode = new ListNode<T, TU> {
|
||||
OptionsList = [],
|
||||
OnItemSelected = OnListItemSelected,
|
||||
};
|
||||
listNode.AttachNode(this);
|
||||
|
||||
addButton = new TextButtonNode {
|
||||
String = "Add",
|
||||
OnClick = OnAddClicked,
|
||||
IsEnabled = false,
|
||||
};
|
||||
addButton.AttachNode(this);
|
||||
|
||||
editButton = new TextButtonNode {
|
||||
String = "Edit",
|
||||
OnClick = OnEditClicked,
|
||||
IsEnabled = false,
|
||||
};
|
||||
editButton.AttachNode(this);
|
||||
|
||||
removeButton = new TextButtonNode {
|
||||
String = "Remove",
|
||||
OnClick = OnRemoveClicked,
|
||||
IsEnabled = false,
|
||||
};
|
||||
removeButton.AttachNode(this);
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
searchWidget.Size = new Vector2(Width, 65.0f);
|
||||
searchWidget.Position = Vector2.Zero;
|
||||
|
||||
listNode.Size = new Vector2(Width, Height - searchWidget.Height - 40.0f);
|
||||
listNode.Position = new Vector2(0.0f, searchWidget.Y + searchWidget.Height + 8.0f);
|
||||
|
||||
const float buttonPadding = 5.0f;
|
||||
var buttonWidth = (Width - buttonPadding * 2.0f) / 3.0f;
|
||||
|
||||
addButton.Size = new Vector2(buttonWidth, 24.0f);
|
||||
addButton.Position = new Vector2(0.0f, Height - 24.0f);
|
||||
|
||||
editButton.Size = new Vector2(buttonWidth, 24.0f);
|
||||
editButton.Position = new Vector2(buttonWidth + buttonPadding, Height - 24.0f);
|
||||
|
||||
removeButton.Size = new Vector2(buttonWidth, 24.0f);
|
||||
removeButton.Position = new Vector2(buttonWidth * 2.0f + buttonPadding * 2.0f, Height - 24.0f);
|
||||
}
|
||||
|
||||
public List<T> Options {
|
||||
get;
|
||||
set {
|
||||
field = value;
|
||||
listNode.OptionsList = value;
|
||||
}
|
||||
} = [];
|
||||
|
||||
public List<string>? SortOptions {
|
||||
get => searchWidget.SortingOptions;
|
||||
set {
|
||||
searchWidget.SortingOptions = value ?? [];
|
||||
OnSizeChanged();
|
||||
|
||||
if (value is not null && value.Count > 0) {
|
||||
OnSortOrderChanged(value.First(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Action<T?>? SelectionChanged { get; init; }
|
||||
|
||||
public Action? AddNewEntry {
|
||||
get;
|
||||
set {
|
||||
field = value;
|
||||
addButton.IsEnabled = value is not null;
|
||||
}
|
||||
}
|
||||
|
||||
public Action<T>? RemoveEntry {
|
||||
get;
|
||||
set {
|
||||
field = value;
|
||||
removeButton.IsEnabled = value is not null && SelectedOption is not null;
|
||||
}
|
||||
}
|
||||
|
||||
public Action<T>? EditEntry {
|
||||
get;
|
||||
set {
|
||||
field = value;
|
||||
editButton.IsEnabled = value is not null && SelectedOption is not null;
|
||||
}
|
||||
}
|
||||
|
||||
public delegate int ItemCompareDelegate(T left, T right, string sortingMode);
|
||||
public ItemCompareDelegate? ItemComparer { get; set; }
|
||||
|
||||
public delegate bool IsSearchMatchDelegate(T obj, string searchString);
|
||||
public IsSearchMatchDelegate? IsSearchMatch { get; set; }
|
||||
|
||||
public T? SelectedOption { get; private set; }
|
||||
|
||||
public float ItemSpacing {
|
||||
get => listNode.ItemSpacing;
|
||||
set {
|
||||
listNode.ItemSpacing = value;
|
||||
OnSizeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSortOrderChanged(string sortingString, bool reversed) {
|
||||
if (ItemComparer is null) return;
|
||||
|
||||
var listCopy = Options.ToList();
|
||||
listCopy.Sort((left, right) => ItemComparer.Invoke(left, right, sortingString) * (reversed ? -1 : 1));
|
||||
listNode.OptionsList = listCopy;
|
||||
UpdateButtonStates();
|
||||
}
|
||||
|
||||
private void OnSearchUpdated(string searchString) {
|
||||
if (IsSearchMatch is null) return;
|
||||
|
||||
if (searchString.IsNullOrEmpty()) {
|
||||
listNode.OptionsList = Options;
|
||||
}
|
||||
else {
|
||||
listNode.OptionsList = Options.Where(item => IsSearchMatch(item, searchString)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnListItemSelected(T? obj) {
|
||||
SelectedOption = obj;
|
||||
SelectionChanged?.Invoke(SelectedOption);
|
||||
|
||||
UpdateButtonStates();
|
||||
}
|
||||
|
||||
private void OnAddClicked() {
|
||||
AddNewEntry?.Invoke();
|
||||
RefreshList();
|
||||
}
|
||||
|
||||
private void OnEditClicked() {
|
||||
if (SelectedOption is null) return;
|
||||
|
||||
EditEntry?.Invoke(SelectedOption);
|
||||
RefreshList();
|
||||
}
|
||||
|
||||
private void OnRemoveClicked() {
|
||||
if (SelectedOption is null) return;
|
||||
|
||||
RemoveEntry?.Invoke(SelectedOption);
|
||||
RefreshList();
|
||||
}
|
||||
|
||||
private void UpdateButtonStates() {
|
||||
editButton.IsEnabled = SelectedOption is not null && EditEntry is not null;
|
||||
removeButton.IsEnabled = SelectedOption is not null && RemoveEntry is not null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the displayed list data.
|
||||
/// This resets scroll position, so don't spam it.
|
||||
/// </summary>
|
||||
public void RefreshList() {
|
||||
OnSortOrderChanged(searchWidget.SortMode, searchWidget.IsReversed);
|
||||
OnSearchUpdated(searchWidget.SearchText);
|
||||
listNode.FullRebuild();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace KamiToolKit.Premade.Nodes;
|
||||
|
||||
/// <summary>
|
||||
/// A TextButton that has a configurable set of states
|
||||
/// </summary>
|
||||
public class MultiStateButtonNode<T> : TextButtonNode where T : notnull {
|
||||
public Action<T>? OnStateChanged { get; set; }
|
||||
|
||||
public MultiStateButtonNode()
|
||||
=> OnClick = CycleState;
|
||||
|
||||
public required List<T> States {
|
||||
get;
|
||||
set {
|
||||
field = value;
|
||||
UpdateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private int SelectedIndex {
|
||||
get;
|
||||
set {
|
||||
field = value;
|
||||
UpdateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
public T SelectedState {
|
||||
get => States[SelectedIndex];
|
||||
set => SelectedIndex = States.IndexOf(value);
|
||||
}
|
||||
|
||||
private void CycleState() {
|
||||
if (States.Count is 0) return;
|
||||
|
||||
SelectedIndex = (SelectedIndex + 1) % States.Count;
|
||||
OnStateChanged?.Invoke(SelectedState);
|
||||
}
|
||||
|
||||
private void UpdateDisplay() {
|
||||
if (SelectedIndex < 0) return;
|
||||
if (SelectedIndex > States.Count - 1) return;
|
||||
|
||||
String = GetStateText(States[SelectedIndex]);
|
||||
}
|
||||
|
||||
protected virtual string GetStateText(T state) {
|
||||
if (state is Enum enumState) {
|
||||
return enumState.Description;
|
||||
}
|
||||
|
||||
return state.ToString() ?? "Unable to Parse Type";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Numerics;
|
||||
using KamiToolKit.Nodes;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace KamiToolKit.Premade.Nodes;
|
||||
|
||||
public class UnderlinedTextNode : SimpleComponentNode {
|
||||
|
||||
public readonly CategoryTextNode LabelTextNode;
|
||||
public readonly HorizontalLineNode LineNode;
|
||||
|
||||
public UnderlinedTextNode() {
|
||||
LabelTextNode = new CategoryTextNode();
|
||||
LabelTextNode.AttachNode(this);
|
||||
|
||||
LineNode = new HorizontalLineNode {
|
||||
Height = 4.0f,
|
||||
};
|
||||
LineNode.AttachNode(this);
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
LabelTextNode.Size = new Vector2(Width, Height - 4.0f);
|
||||
LabelTextNode.Position = new Vector2(0.0f, 0.0f);
|
||||
|
||||
LineNode.Position = new Vector2(0.0f, LabelTextNode.Bounds.Bottom - 4.0f);
|
||||
RecalculateLineSize();
|
||||
}
|
||||
|
||||
public ReadOnlySeString String {
|
||||
get => LabelTextNode.String;
|
||||
set {
|
||||
LabelTextNode.String = value;
|
||||
RecalculateLineSize();
|
||||
}
|
||||
}
|
||||
|
||||
private void RecalculateLineSize() {
|
||||
var textSize = LabelTextNode.GetTextDrawSize();
|
||||
LineNode.Width = textSize.X + 32.0f;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user