Initial commit: AetherBags + KamiToolKit for FC Gitea
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
using KamiToolKit.Premade.Nodes;
|
||||
|
||||
namespace KamiToolKit.Premade.Addons;
|
||||
|
||||
public class ListConfigAddon<T, TU, TV> : NativeAddon where T: class where TV : ConfigNode<T>, new() where TU : ListItemNode<T>, new() {
|
||||
|
||||
private ModifyListNode<T, TU>? selectionListNode;
|
||||
private VerticalLineNode? separatorLine;
|
||||
private TV? configNode;
|
||||
private TextNode? nothingSelectedTextNode;
|
||||
|
||||
protected override unsafe void OnSetup(AtkUnitBase* addon) {
|
||||
selectionListNode = new ModifyListNode<T, TU> {
|
||||
Position = ContentStartPosition,
|
||||
Size = new Vector2(250.0f, ContentSize.Y),
|
||||
SortOptions = SortOptions,
|
||||
Options = Options,
|
||||
SelectionChanged = SelectionChanged,
|
||||
AddNewEntry = OnAddClicked,
|
||||
RemoveEntry = OnRemoveClicked,
|
||||
ItemComparer = ItemComparer,
|
||||
IsSearchMatch = OnSearchUpdated,
|
||||
ItemSpacing = ItemSpacing,
|
||||
};
|
||||
selectionListNode.AttachNode(this);
|
||||
|
||||
separatorLine = new VerticalLineNode {
|
||||
Position = ContentStartPosition + new Vector2(250.0f + 8.0f, 0.0f),
|
||||
Size = new Vector2(4.0f, ContentSize.Y),
|
||||
};
|
||||
separatorLine.AttachNode(this);
|
||||
|
||||
nothingSelectedTextNode = new TextNode {
|
||||
Position = ContentStartPosition + new Vector2(250.0f + 16.0f, 0.0f),
|
||||
Size = ContentSize - new Vector2(250.0f + 16.0f, 0.0f),
|
||||
AlignmentType = AlignmentType.Center,
|
||||
TextFlags = TextFlags.WordWrap | TextFlags.MultiLine,
|
||||
FontSize = 14,
|
||||
LineSpacing = 22,
|
||||
FontType = FontType.Axis,
|
||||
String = "Please select an option on the left",
|
||||
TextColor = ColorHelper.GetColor(1),
|
||||
};
|
||||
nothingSelectedTextNode.AttachNode(this);
|
||||
|
||||
configNode = new TV {
|
||||
Position = ContentStartPosition + new Vector2(250.0f + 16.0f, 0.0f),
|
||||
Size = ContentSize - new Vector2(250.0f + 16.0f, 0.0f),
|
||||
OnConfigChanged = option => EditCompleted?.Invoke(option),
|
||||
IsVisible = false,
|
||||
};
|
||||
configNode.AttachNode(this);
|
||||
}
|
||||
|
||||
public required ModifyListNode<T, TU>.ItemCompareDelegate? ItemComparer {
|
||||
get;
|
||||
init {
|
||||
field = value;
|
||||
selectionListNode?.ItemComparer = value;
|
||||
}
|
||||
}
|
||||
|
||||
public required ModifyListNode<T, TU>.IsSearchMatchDelegate? IsSearchMatch {
|
||||
get;
|
||||
init {
|
||||
field = value;
|
||||
selectionListNode?.IsSearchMatch = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddClicked() {
|
||||
AddClicked?.Invoke(this);
|
||||
selectionListNode?.RefreshList();
|
||||
}
|
||||
|
||||
private void OnRemoveClicked(T listItem) {
|
||||
RemoveClicked?.Invoke(this, listItem);
|
||||
SelectionChanged(null);
|
||||
selectionListNode?.RefreshList();
|
||||
}
|
||||
|
||||
private void SelectionChanged(T? listItem) {
|
||||
SetConfigNodeItem(listItem);
|
||||
}
|
||||
|
||||
private bool OnSearchUpdated(T obj, string searchString) {
|
||||
SelectItem(null);
|
||||
return IsSearchMatch?.Invoke(obj, searchString) ?? false;
|
||||
}
|
||||
|
||||
private void SetConfigNodeItem(T? configItem) {
|
||||
if (configNode is null) return;
|
||||
if (nothingSelectedTextNode is null) return;
|
||||
|
||||
configNode.ConfigurationOption = configItem;
|
||||
|
||||
configNode.IsVisible = configNode.ConfigurationOption is not null;
|
||||
nothingSelectedTextNode.IsVisible = configNode.ConfigurationOption is null;
|
||||
}
|
||||
|
||||
public void RefreshList()
|
||||
=> selectionListNode?.RefreshList();
|
||||
|
||||
public void SelectItem(T? listItem)
|
||||
=> SelectionChanged(listItem);
|
||||
|
||||
public List<string>? SortOptions {
|
||||
get;
|
||||
set {
|
||||
field = value;
|
||||
selectionListNode?.SortOptions = value;
|
||||
}
|
||||
} = ["Alphabetical", "Id"];
|
||||
|
||||
public required List<T> Options { get;
|
||||
set {
|
||||
field = value;
|
||||
selectionListNode?.Options = value;
|
||||
}
|
||||
} = [];
|
||||
|
||||
public float ItemSpacing {
|
||||
get;
|
||||
set {
|
||||
field = value;
|
||||
selectionListNode?.ItemSpacing = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Action<ListConfigAddon<T, TU, TV>>? AddClicked {
|
||||
get;
|
||||
set {
|
||||
field = value;
|
||||
selectionListNode?.AddNewEntry = () => {
|
||||
value?.Invoke(this);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public Action<ListConfigAddon<T, TU, TV>, T>? RemoveClicked {
|
||||
get;
|
||||
set {
|
||||
field = value;
|
||||
selectionListNode?.RemoveEntry = entry => {
|
||||
value?.Invoke(this, entry);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public Action<T>? EditCompleted { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Nodes;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace KamiToolKit.Premade.Color;
|
||||
|
||||
public class ColorEditNode : SimpleOverlayNode {
|
||||
|
||||
private readonly ColorPreviewNode previewNode;
|
||||
private readonly TextNode labelNode;
|
||||
|
||||
private ColorPickerAddon? colorPicker = new() {
|
||||
InternalName = "ColorPicker",
|
||||
Title = "Color Picker",
|
||||
};
|
||||
|
||||
public ColorEditNode() {
|
||||
DisableCollisionNode = true;
|
||||
|
||||
previewNode = new ColorPreviewNode();
|
||||
previewNode.AttachNode(this);
|
||||
|
||||
labelNode = new TextNode {
|
||||
AlignmentType = AlignmentType.Left,
|
||||
};
|
||||
labelNode.AttachNode(this);
|
||||
|
||||
previewNode.CollisionNode.ShowClickableCursor = true;
|
||||
previewNode.CollisionNode.AddEvent(AtkEventType.MouseClick, OnClicked);
|
||||
|
||||
labelNode.ShowClickableCursor = true;
|
||||
labelNode.AddEvent(AtkEventType.MouseClick, OnClicked);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing, bool isNativeDestructor) {
|
||||
base.Dispose(disposing, isNativeDestructor);
|
||||
|
||||
colorPicker?.Dispose();
|
||||
colorPicker = null;
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
previewNode.Size = new Vector2(Height - 6.0f, Height - 6.0f);
|
||||
previewNode.Position = new Vector2(3.0f, 3.0f);
|
||||
|
||||
labelNode.Size = new Vector2(Width - Height - 12.0f, Height);
|
||||
labelNode.Position = new Vector2(previewNode.Bounds.Right + 12.0f, 0.0f);
|
||||
}
|
||||
|
||||
private void OnClicked() {
|
||||
var originalColor = CurrentColor;
|
||||
colorPicker?.DefaultColor = DefaultColor;
|
||||
colorPicker?.InitialColor = CurrentColor;
|
||||
|
||||
colorPicker?.OnColorPreviewed = color => {
|
||||
previewNode.Color = color;
|
||||
CurrentColor = color;
|
||||
OnColorPreviewed?.Invoke(color);
|
||||
};
|
||||
|
||||
colorPicker?.OnColorCancelled = () => {
|
||||
CurrentColor = originalColor;
|
||||
OnColorCancelled?.Invoke();
|
||||
};
|
||||
|
||||
colorPicker?.OnColorConfirmed = color => {
|
||||
CurrentColor = color;
|
||||
OnColorConfirmed?.Invoke(color);
|
||||
};
|
||||
|
||||
colorPicker?.Toggle();
|
||||
}
|
||||
|
||||
public Vector4 CurrentColor {
|
||||
get => previewNode.Color;
|
||||
set => previewNode.Color = value;
|
||||
}
|
||||
|
||||
public ReadOnlySeString String {
|
||||
get => labelNode.String;
|
||||
set => labelNode.String = value;
|
||||
}
|
||||
|
||||
public Vector4? DefaultColor { get; set; }
|
||||
|
||||
public Action? OnColorCancelled { get; set; }
|
||||
public Action<Vector4>? OnColorPreviewed { get; set; }
|
||||
public Action<Vector4>? OnColorConfirmed { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace KamiToolKit.Premade.Color;
|
||||
|
||||
public class ColorPickerAddon : NativeAddon {
|
||||
private ColorPickerWidget? colorPicker;
|
||||
private HorizontalLineNode? horizontalLine;
|
||||
private TextButtonNode? confirmButton;
|
||||
private ColorOptionTextButtonNode? defaultColorPreview;
|
||||
private TextButtonNode? cancelButton;
|
||||
|
||||
private bool isCancelClicked;
|
||||
|
||||
private Vector4 initialRgba;
|
||||
private ColorHelpers.HsvaColor initialHsva;
|
||||
|
||||
protected override unsafe void OnSetup(AtkUnitBase* addon) {
|
||||
SetWindowSize(new Vector2(400.0f, 425.0f));
|
||||
|
||||
initialHsva = InitialHsvaColor;
|
||||
initialRgba = ColorHelpers.HsvToRgb(initialHsva);
|
||||
|
||||
colorPicker = new ColorPickerWidget {
|
||||
Position = ContentStartPosition,
|
||||
Size = ContentSize,
|
||||
};
|
||||
colorPicker.AttachNode(this);
|
||||
|
||||
colorPicker.ColorPreviewed += hsva => OnHsvaColorPreviewed?.Invoke(hsva);
|
||||
colorPicker.RgbaColorPreviewed += rgba => OnColorPreviewed?.Invoke(rgba);
|
||||
|
||||
colorPicker.SetColor(initialRgba);
|
||||
|
||||
horizontalLine = new HorizontalLineNode {
|
||||
Position = ContentStartPosition + new Vector2(2.0f, ContentSize.Y - 40.0f),
|
||||
Size = new Vector2(ContentSize.X - 4.0f, 2.0f),
|
||||
};
|
||||
horizontalLine.AttachNode(this);
|
||||
|
||||
confirmButton = new TextButtonNode {
|
||||
Position = ContentStartPosition + new Vector2(0.0f, ContentSize.Y - 24.0f),
|
||||
Size = new Vector2(100.0f, 24.0f),
|
||||
String = "Confirm",
|
||||
OnClick = OnConfirmClicked,
|
||||
};
|
||||
confirmButton.AttachNode(this);
|
||||
|
||||
if (DefaultHsvaColor is { } defaultColor) {
|
||||
defaultColorPreview = new ColorOptionTextButtonNode {
|
||||
Size = new Vector2(100.0f, 24.0f),
|
||||
Position = ContentStartPosition + new Vector2(ContentSize.X / 2.0f - 50.0f, ContentSize.Y - 24.0f),
|
||||
String = "Default",
|
||||
OnClick = OnDefaultClicked,
|
||||
DefaultHsvaColor = defaultColor,
|
||||
};
|
||||
defaultColorPreview.AttachNode(this);
|
||||
}
|
||||
|
||||
cancelButton = new TextButtonNode {
|
||||
Position = ContentStartPosition + new Vector2(ContentSize.X - 100.0f, ContentSize.Y - 24.0f),
|
||||
Size = new Vector2(100.0f, 24.0f),
|
||||
String = "Cancel",
|
||||
OnClick = OnCancelClicked,
|
||||
};
|
||||
cancelButton.AttachNode(this);
|
||||
}
|
||||
|
||||
protected override unsafe void OnHide(AtkUnitBase* addon) {
|
||||
if (!isCancelClicked) {
|
||||
OnHsvaColorPreviewed?.Invoke(initialHsva);
|
||||
OnColorPreviewed?.Invoke(initialRgba);
|
||||
|
||||
OnColorCancelled?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConfirmClicked() {
|
||||
if (colorPicker is null) return;
|
||||
|
||||
var rgba = ColorHelpers.HsvToRgb(colorPicker.CurrentColor);
|
||||
OnColorConfirmed?.Invoke(rgba);
|
||||
OnHsvaColorConfirmed?.Invoke(colorPicker.CurrentColor);
|
||||
|
||||
isCancelClicked = true;
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnDefaultClicked() {
|
||||
if (colorPicker is null) return;
|
||||
|
||||
if (DefaultHsvaColor is { } defaultColor) {
|
||||
colorPicker.SetColor(defaultColor);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCancelClicked() {
|
||||
isCancelClicked = true;
|
||||
|
||||
OnHsvaColorPreviewed?.Invoke(initialHsva);
|
||||
OnColorPreviewed?.Invoke(initialRgba);
|
||||
|
||||
OnColorCancelled?.Invoke();
|
||||
Close();
|
||||
}
|
||||
|
||||
public Action<Vector4>? OnColorPreviewed { get; set; }
|
||||
public Action<ColorHelpers.HsvaColor>? OnHsvaColorPreviewed { get; set; }
|
||||
|
||||
public Action<Vector4>? OnColorConfirmed { get; set; }
|
||||
public Action<ColorHelpers.HsvaColor>? OnHsvaColorConfirmed { get; set; }
|
||||
public Action? OnColorCancelled { get; set; }
|
||||
|
||||
public ColorHelpers.HsvaColor? DefaultHsvaColor { get; set; }
|
||||
|
||||
public Vector4? DefaultColor {
|
||||
get;
|
||||
set {
|
||||
field = value;
|
||||
DefaultHsvaColor = value is null ? null : ColorHelpers.RgbaToHsv(value.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector4 InitialColor {
|
||||
get => ColorHelpers.HsvToRgb(InitialHsvaColor);
|
||||
set => InitialHsvaColor = ColorHelpers.RgbaToHsv(value);
|
||||
}
|
||||
|
||||
public ColorHelpers.HsvaColor InitialHsvaColor { get; set; } =
|
||||
ColorHelpers.RgbaToHsv(new Vector4(1.0f, 0.0f, 0.0f, 1.0f));
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
using KamiToolKit.Premade.Nodes;
|
||||
|
||||
namespace KamiToolKit.Premade.Color;
|
||||
|
||||
public class ColorPickerWidget : SimpleComponentNode {
|
||||
public readonly ColorRingWithSquareNode ColorPickerNode;
|
||||
public readonly AlphaBarNode AlphaBarNode;
|
||||
public readonly ColorPreviewWithInput ColorPreviewWithInput;
|
||||
|
||||
public ColorHelpers.HsvaColor CurrentColor { get; private set; }
|
||||
|
||||
public Action<ColorHelpers.HsvaColor>? ColorPreviewed;
|
||||
public Action<Vector4>? RgbaColorPreviewed;
|
||||
|
||||
private int batchDepth;
|
||||
private bool previewDirty;
|
||||
|
||||
public ColorPickerWidget() {
|
||||
ColorPickerNode = new ColorRingWithSquareNode {
|
||||
OnHueChanged = SetHue,
|
||||
OnSaturationChanged = SetSaturation,
|
||||
OnValueChanged = SetValue,
|
||||
};
|
||||
ColorPickerNode.AttachNode(this);
|
||||
|
||||
AlphaBarNode = new AlphaBarNode {
|
||||
OnAlphaChanged = SetAlpha,
|
||||
};
|
||||
AlphaBarNode.AttachNode(this);
|
||||
|
||||
ColorPreviewWithInput = new ColorPreviewWithInput {
|
||||
OnHsvaColorChanged = newColor => {
|
||||
using (BeginBatchUpdate()) {
|
||||
SetHue(newColor.H);
|
||||
SetSaturation(newColor.S);
|
||||
SetValue(newColor.V);
|
||||
SetAlpha(newColor.A);
|
||||
}
|
||||
},
|
||||
};
|
||||
ColorPreviewWithInput.AttachNode(this);
|
||||
|
||||
CurrentColor = ColorHelpers.RgbaToHsv(new Vector4(1.0f, 0.0f, 0.0f, 1.0f));
|
||||
|
||||
using (BeginBatchUpdate()) {
|
||||
SetHue(CurrentColor.H);
|
||||
SetSaturation(CurrentColor.S);
|
||||
SetValue(CurrentColor.V);
|
||||
SetAlpha(CurrentColor.A);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
var mainWidgetWidth = Width * 3.0f / 4.0f;
|
||||
|
||||
ColorPickerNode.Size = new Vector2(mainWidgetWidth, mainWidgetWidth);
|
||||
|
||||
AlphaBarNode.Size = new Vector2(Width / 16.0f, mainWidgetWidth - 60.0f);
|
||||
AlphaBarNode.Position = new Vector2(mainWidgetWidth + (Width - mainWidgetWidth) / 3.0f - AlphaBarNode.Width / 2.0f, 30.0f);
|
||||
|
||||
ColorPreviewWithInput.Size = new Vector2(150.0f, 32.0f);
|
||||
ColorPreviewWithInput.Position = new Vector2(Width / 2.0f - 75.0f, ColorPickerNode.Y + ColorPickerNode.Height - 1.0f);
|
||||
}
|
||||
|
||||
private IDisposable BeginBatchUpdate() {
|
||||
batchDepth++;
|
||||
return new BatchToken(this);
|
||||
}
|
||||
|
||||
internal void EndBatchUpdate() {
|
||||
batchDepth--;
|
||||
if (batchDepth <= 0) {
|
||||
batchDepth = 0;
|
||||
|
||||
if (previewDirty) {
|
||||
previewDirty = false;
|
||||
RaisePreview();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RaisePreviewMaybe() {
|
||||
if (batchDepth > 0) {
|
||||
previewDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
RaisePreview();
|
||||
}
|
||||
|
||||
private void RaisePreview() {
|
||||
var hsva = CurrentColor;
|
||||
ColorPreviewed?.Invoke(hsva);
|
||||
RgbaColorPreviewed?.Invoke(ColorHelpers.HsvToRgb(hsva));
|
||||
}
|
||||
|
||||
public void SetAlpha(float alpha) {
|
||||
CurrentColor = CurrentColor with { A = alpha };
|
||||
|
||||
ColorPreviewWithInput.ColorHsva = CurrentColor;
|
||||
AlphaBarNode.ColorHsva = CurrentColor;
|
||||
|
||||
RaisePreviewMaybe();
|
||||
}
|
||||
|
||||
public void SetHue(float hue) {
|
||||
CurrentColor = CurrentColor with { H = hue };
|
||||
|
||||
ColorPickerNode.RotationDegrees = hue * 360.0f;
|
||||
ColorPickerNode.SelectorColor = CurrentColor;
|
||||
ColorPickerNode.SquareColor = CurrentColor with { S = 1.0f, V = 1.0f };
|
||||
|
||||
ColorPreviewWithInput.ColorHsva = CurrentColor;
|
||||
AlphaBarNode.ColorHsva = CurrentColor;
|
||||
|
||||
RaisePreviewMaybe();
|
||||
}
|
||||
|
||||
public void SetSaturation(float saturation) {
|
||||
CurrentColor = CurrentColor with { S = saturation };
|
||||
|
||||
ColorPreviewWithInput.ColorHsva = CurrentColor;
|
||||
ColorPickerNode.SelectorColor = CurrentColor;
|
||||
|
||||
ColorPickerNode.SquareColor = CurrentColor;
|
||||
ColorPickerNode.SquareSaturationValue = CurrentColor;
|
||||
|
||||
AlphaBarNode.ColorHsva = CurrentColor;
|
||||
|
||||
RaisePreviewMaybe();
|
||||
}
|
||||
|
||||
public void SetValue(float value) {
|
||||
CurrentColor = CurrentColor with { V = value };
|
||||
|
||||
ColorPreviewWithInput.ColorHsva = CurrentColor;
|
||||
ColorPickerNode.SelectorColor = CurrentColor;
|
||||
|
||||
ColorPickerNode.SquareColor = CurrentColor;
|
||||
ColorPickerNode.SquareSaturationValue = CurrentColor;
|
||||
|
||||
AlphaBarNode.ColorHsva = CurrentColor;
|
||||
|
||||
RaisePreviewMaybe();
|
||||
}
|
||||
|
||||
public void SetColor(Vector4 color) {
|
||||
var converted = ColorHelpers.RgbaToHsv(color);
|
||||
|
||||
using (BeginBatchUpdate()) {
|
||||
SetHue(converted.H);
|
||||
SetSaturation(converted.S);
|
||||
SetValue(converted.V);
|
||||
SetAlpha(converted.A);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetColor(ColorHelpers.HsvaColor color) {
|
||||
using (BeginBatchUpdate()) {
|
||||
SetHue(color.H);
|
||||
SetSaturation(color.S);
|
||||
SetValue(color.V);
|
||||
SetAlpha(color.A);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Enums;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace KamiToolKit.Premade.Color;
|
||||
|
||||
public class ColorPreviewNode : SimpleComponentNode {
|
||||
public readonly BackgroundImageNode SelectedColorPreviewNode;
|
||||
public readonly ImGuiImageNode AlphaLayerPreviewNode;
|
||||
public readonly BackgroundImageNode SelectedColorPreviewBorderNode;
|
||||
|
||||
public ColorPreviewNode() {
|
||||
SelectedColorPreviewBorderNode = new BackgroundImageNode {
|
||||
Color = KnownColor.White.Vector(),
|
||||
};
|
||||
SelectedColorPreviewBorderNode.AttachNode(this);
|
||||
|
||||
AlphaLayerPreviewNode = new ImGuiImageNode {
|
||||
TexturePath = DalamudInterface.Instance.GetAssetPath("alpha_background.png"),
|
||||
WrapMode = WrapMode.Tile,
|
||||
};
|
||||
AlphaLayerPreviewNode.AttachNode(this);
|
||||
|
||||
SelectedColorPreviewNode = new BackgroundImageNode {
|
||||
Color = new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
|
||||
};
|
||||
SelectedColorPreviewNode.AttachNode(this);
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
SelectedColorPreviewBorderNode.Size = new Vector2(Height - 4.0f, Width - 4.0f);
|
||||
SelectedColorPreviewBorderNode.Position = new Vector2(2.0f, 2.0f);
|
||||
|
||||
AlphaLayerPreviewNode.Size = new Vector2(Height - 6.0f, Width - 6.0f);
|
||||
AlphaLayerPreviewNode.Position = new Vector2(3.0f, 3.0f);
|
||||
|
||||
SelectedColorPreviewNode.Size = new Vector2(Height - 6.0f, Width - 6.0f);
|
||||
SelectedColorPreviewNode.Position = new Vector2(3.0f, 3.0f);
|
||||
}
|
||||
|
||||
public override Vector4 Color {
|
||||
get => SelectedColorPreviewNode.Color;
|
||||
set => SelectedColorPreviewNode.Color = value;
|
||||
}
|
||||
|
||||
public override ColorHelpers.HsvaColor ColorHsva {
|
||||
get => SelectedColorPreviewNode.ColorHsva;
|
||||
set => SelectedColorPreviewNode.ColorHsva = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using KamiToolKit.Nodes;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace KamiToolKit.Premade.Color;
|
||||
|
||||
public class ColorPreviewWithInput : SimpleComponentNode {
|
||||
public readonly ColorPreviewNode ColorPreviewNode;
|
||||
public readonly TextInputNode ColorInputNode;
|
||||
|
||||
public ColorPreviewWithInput() {
|
||||
ColorPreviewNode = new ColorPreviewNode();
|
||||
ColorPreviewNode.AttachNode(this);
|
||||
|
||||
ColorInputNode = new TextInputNode {
|
||||
AutoSelectAll = true,
|
||||
OnInputComplete = OnTextInputComplete,
|
||||
};
|
||||
ColorInputNode.AttachNode(this);
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
ColorPreviewNode.Size = new Vector2(Height, Height);
|
||||
ColorPreviewNode.Position = Vector2.Zero;
|
||||
|
||||
ColorInputNode.Size = new Vector2(Width - Height - 8.0f, Height - 2.0f);
|
||||
ColorInputNode.Position = new Vector2(Height + 8.0f, 1.0f);
|
||||
}
|
||||
|
||||
public Action<ColorHelpers.HsvaColor>? OnHsvaColorChanged { get; set; }
|
||||
public Action<Vector4>? OnColorChanged { get; set; }
|
||||
|
||||
public ReadOnlySeString String {
|
||||
get => ColorInputNode.String;
|
||||
set => ColorInputNode.String = value;
|
||||
}
|
||||
|
||||
public override Vector4 Color {
|
||||
get => ColorPreviewNode.Color;
|
||||
set {
|
||||
ColorPreviewNode.Color = value;
|
||||
UpdateColorText();
|
||||
}
|
||||
}
|
||||
|
||||
public override ColorHelpers.HsvaColor ColorHsva {
|
||||
get => ColorPreviewNode.ColorHsva;
|
||||
set {
|
||||
ColorPreviewNode.ColorHsva = value;
|
||||
UpdateColorText();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTextInputComplete(ReadOnlySeString obj) {
|
||||
var str = obj.ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(str) || !str.StartsWith('#')) return;
|
||||
|
||||
var hexString = str.TrimStart('#');
|
||||
|
||||
// Allow #RRGGBB and #RRGGBBAA only
|
||||
if (hexString.Length != 6 && hexString.Length != 8) return;
|
||||
|
||||
const NumberStyles style = NumberStyles.HexNumber;
|
||||
var culture = CultureInfo.InvariantCulture;
|
||||
|
||||
if (!byte.TryParse(hexString[0..2], style, culture, out var r)) return;
|
||||
if (!byte.TryParse(hexString[2..4], style, culture, out var g)) return;
|
||||
if (!byte.TryParse(hexString[4..6], style, culture, out var b)) return;
|
||||
|
||||
byte a = 255;
|
||||
if (hexString.Length == 8 && !byte.TryParse(hexString[6..8], style, culture, out a)) return;
|
||||
|
||||
var newColor = new Vector4(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
|
||||
|
||||
Color = newColor;
|
||||
OnColorChanged?.Invoke(newColor);
|
||||
OnHsvaColorChanged?.Invoke(ColorHelpers.RgbaToHsv(newColor));
|
||||
}
|
||||
|
||||
private void UpdateColorText()
|
||||
=> ColorInputNode.String = $"#{(int)(Color.X * 255):X2}{(int)(Color.Y * 255):X2}{(int)(Color.Z * 255):X2}{(int)(Color.W * 255):X2}";
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Enums;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace KamiToolKit.Premade.Color;
|
||||
|
||||
public unsafe class ColorRingWithSquareNode : SimpleComponentNode {
|
||||
public readonly ColorSquareNode ColorSquareNode;
|
||||
public readonly ImGuiImageNode ColorRingNode;
|
||||
public readonly ImGuiImageNode ColorRingSelectorNode;
|
||||
|
||||
private bool isRingDrag;
|
||||
private bool isSquareDrag;
|
||||
|
||||
private readonly ViewportEventListener eventListener;
|
||||
|
||||
public ColorRingWithSquareNode() {
|
||||
eventListener = new ViewportEventListener(SquareEventHandler);
|
||||
|
||||
ColorSquareNode = new ColorSquareNode {
|
||||
DrawFlags = DrawFlags.UseTransformedCollision,
|
||||
};
|
||||
ColorSquareNode.AttachNode(this);
|
||||
|
||||
ColorRingNode = new ImGuiImageNode {
|
||||
TexturePath = DalamudInterface.Instance.GetAssetPath("color_ring.png"),
|
||||
FitTexture = true,
|
||||
ImageNodeFlags = ImageNodeFlags.FlipV,
|
||||
};
|
||||
ColorRingNode.AttachNode(this);
|
||||
|
||||
ColorRingSelectorNode = new ImGuiImageNode {
|
||||
TexturePath = DalamudInterface.Instance.GetAssetPath("color_ring_selector.png"),
|
||||
FitTexture = true,
|
||||
MultiplyColor = new Vector3(1.0f, 0.0f, 0.0f),
|
||||
};
|
||||
ColorRingSelectorNode.AttachNode(this);
|
||||
|
||||
AddEvent(AtkEventType.MouseDown, OnMouseDown);
|
||||
AddEvent(AtkEventType.MouseUp, OnMouseUp);
|
||||
AddEvent(AtkEventType.MouseMove, OnMouseMove);
|
||||
AddEvent(AtkEventType.MouseOut, OnMouseOut);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing, bool isNativeDestructor) {
|
||||
if (disposing) {
|
||||
base.Dispose(disposing, isNativeDestructor);
|
||||
eventListener.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
ColorSquareNode.Size = Size / 2.0f - new Vector2(24.0f, 24.0f);
|
||||
ColorSquareNode.Position = Size / 4.0f + new Vector2(12.0f, 12.0f);
|
||||
ColorSquareNode.RotationDegrees = 45.0f;
|
||||
|
||||
ColorRingNode.Size = Size;
|
||||
|
||||
ColorRingSelectorNode.Size = Size;
|
||||
ColorRingSelectorNode.Origin = Size / 2.0f;
|
||||
}
|
||||
|
||||
private bool IsRingClicked(AtkEventData* data) {
|
||||
var clickPosition = data->MousePosition;
|
||||
var scale = ParentAddon is not null ? ParentAddon->Scale : 1.0f;
|
||||
var center = ColorRingNode.ScreenPosition + ColorRingNode.Size * scale / 2.0f;
|
||||
var distance = Vector2.Distance(clickPosition, center);
|
||||
var scaledDistance = distance / (Width * scale / 256.0f);
|
||||
|
||||
return scaledDistance is >= 82.0f and <= 99.0f;
|
||||
}
|
||||
|
||||
private float GetRingClickAngle(AtkEventData* data) {
|
||||
var clickPosition = data->MousePosition;
|
||||
var scale = ParentAddon is not null ? ParentAddon->Scale : 1.0f;
|
||||
var center = ColorRingNode.ScreenPosition + ColorRingNode.Size * scale / 2.0f;
|
||||
var relativePosition = clickPosition - center;
|
||||
var calculatedAngle = MathF.Atan2(relativePosition.Y, relativePosition.X) * 180.0f / MathF.PI;
|
||||
|
||||
return calculatedAngle;
|
||||
}
|
||||
|
||||
private void OnMouseDown(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
|
||||
if (ColorSquareNode.CheckCollision(atkEventData)) {
|
||||
UpdateSquareColor(atkEventData->MousePosition);
|
||||
|
||||
if (!isSquareDrag) {
|
||||
isSquareDrag = true;
|
||||
eventListener.AddEvent(AtkEventType.MouseMove, ColorSquareNode);
|
||||
eventListener.AddEvent(AtkEventType.MouseUp, ColorSquareNode);
|
||||
}
|
||||
}
|
||||
|
||||
if (IsRingClicked(atkEventData)) {
|
||||
isRingDrag = true;
|
||||
UpdateRingColor(atkEventData);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseMove(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
|
||||
if (isRingDrag && !isSquareDrag) {
|
||||
UpdateRingColor(atkEventData);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseUp() {
|
||||
isRingDrag = false;
|
||||
isSquareDrag = false;
|
||||
}
|
||||
|
||||
private void OnMouseOut() {
|
||||
isRingDrag = false;
|
||||
isSquareDrag = false;
|
||||
}
|
||||
|
||||
private void UpdateRingColor(AtkEventData* data) {
|
||||
var angle = GetRingClickAngle(data);
|
||||
|
||||
if (angle < 0) {
|
||||
angle += 360.0f;
|
||||
}
|
||||
|
||||
OnHueChanged?.Invoke(angle / 360.0f);
|
||||
}
|
||||
|
||||
private void UpdateSquareColor(Vector2 clickPosition) {
|
||||
// Note: ColorSquareNode.ScreenPosition changes as the node rotates
|
||||
// However, Position does not change
|
||||
var scale = ParentAddon is not null ? ParentAddon->Scale : 1.0f;
|
||||
var center = ScreenPosition + (ColorSquareNode.Position + ColorSquareNode.Origin) * scale;
|
||||
|
||||
var relativePosition = clickPosition - center;
|
||||
var rotatedPoint = RotatePoint(relativePosition / scale, Vector2.Zero, -ColorSquareNode.RotationDegrees) / ColorSquareNode.Scale;
|
||||
|
||||
var xClamped = Math.Clamp(rotatedPoint.X, -ColorSquareNode.Width / 2, ColorSquareNode.Width / 2);
|
||||
var yClamped = Math.Clamp(rotatedPoint.Y, -ColorSquareNode.Height / 2, ColorSquareNode.Height / 2);
|
||||
|
||||
ColorSquareNode.ColorDotPosition = new Vector2(xClamped, yClamped) + ColorSquareNode.Origin;
|
||||
|
||||
var saturation = ColorSquareNode.ColorDotPosition.X / ColorSquareNode.Width;
|
||||
var lightness = 1 - ColorSquareNode.ColorDotPosition.Y / ColorSquareNode.Height;
|
||||
|
||||
OnSaturationChanged?.Invoke(saturation);
|
||||
OnValueChanged?.Invoke(lightness);
|
||||
}
|
||||
|
||||
private void SquareEventHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
|
||||
if (eventType is AtkEventType.MouseMove && isSquareDrag && !isRingDrag) {
|
||||
UpdateSquareColor(new Vector2(atkEventData->MouseData.PosX, atkEventData->MouseData.PosY));
|
||||
}
|
||||
|
||||
if (eventType is AtkEventType.MouseUp) {
|
||||
isSquareDrag = false;
|
||||
eventListener.RemoveEvent(AtkEventType.MouseMove);
|
||||
eventListener.RemoveEvent(AtkEventType.MouseUp);
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector2 RotatePoint(Vector2 pointToRotate, Vector2 centerPoint, float angleInDegrees) {
|
||||
var angleInRadians = angleInDegrees * (MathF.PI / 180);
|
||||
var cosTheta = MathF.Cos(angleInRadians);
|
||||
var sinTheta = MathF.Sin(angleInRadians);
|
||||
return new Vector2 {
|
||||
X = cosTheta * (pointToRotate.X - centerPoint.X) - sinTheta * (pointToRotate.Y - centerPoint.Y) + centerPoint.X,
|
||||
Y = sinTheta * (pointToRotate.X - centerPoint.X) + cosTheta * (pointToRotate.Y - centerPoint.Y) + centerPoint.Y,
|
||||
};
|
||||
}
|
||||
|
||||
public Action<float>? OnHueChanged { get; init; }
|
||||
public Action<float>? OnSaturationChanged { get; init; }
|
||||
public Action<float>? OnValueChanged { get; init; }
|
||||
|
||||
public override float RotationDegrees {
|
||||
get => ColorSquareNode.RotationDegrees;
|
||||
set {
|
||||
ColorSquareNode.RotationDegrees = value + 45.0f;
|
||||
ColorRingSelectorNode.RotationDegrees = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ColorHelpers.HsvaColor SelectorColor {
|
||||
get => ColorRingSelectorNode.MultiplyColorHsva;
|
||||
set => ColorRingSelectorNode.MultiplyColorHsva = value;
|
||||
}
|
||||
|
||||
public ColorHelpers.HsvaColor SquareColor {
|
||||
get => ColorSquareNode.MultiplyColorHsva;
|
||||
set => ColorSquareNode.MultiplyColorHsva = value with { S = 1.0f, V = 1.0f };
|
||||
}
|
||||
|
||||
public ColorHelpers.HsvaColor SquareSaturationValue {
|
||||
get => ColorSquareNode.MultiplyColorHsva;
|
||||
set => ColorSquareNode.ColorDotPosition = new Vector2(ColorSquareNode.Width * value.S, ColorSquareNode.Height - ColorSquareNode.Height * value.V);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace KamiToolKit.Premade.Color;
|
||||
|
||||
public class ColorSquareNode : SimpleComponentNode {
|
||||
public readonly ImGuiImageNode WhiteGradientNode;
|
||||
public readonly ImGuiImageNode ColorGradientNode;
|
||||
public readonly ImGuiImageNode BlackGradientNode;
|
||||
public readonly ImGuiImageNode ColorDotNode;
|
||||
|
||||
public ColorSquareNode() {
|
||||
WhiteGradientNode = new ImGuiImageNode {
|
||||
TexturePath = DalamudInterface.Instance.GetAssetPath("HorizontalGradient_WhiteToAlpha.png"),
|
||||
FitTexture = true,
|
||||
};
|
||||
WhiteGradientNode.AttachNode(this);
|
||||
|
||||
ColorGradientNode = new ImGuiImageNode {
|
||||
TexturePath = DalamudInterface.Instance.GetAssetPath("HorizontalGradient_WhiteToAlpha.png"),
|
||||
FitTexture = true,
|
||||
ImageNodeFlags = ImageNodeFlags.FlipH,
|
||||
};
|
||||
ColorGradientNode.AttachNode(this);
|
||||
|
||||
BlackGradientNode = new ImGuiImageNode {
|
||||
TexturePath = DalamudInterface.Instance.GetAssetPath("VerticalGradient_AlphaToBlack.png"),
|
||||
FitTexture = true,
|
||||
};
|
||||
BlackGradientNode.AttachNode(this);
|
||||
|
||||
ColorDotNode = new ImGuiImageNode {
|
||||
TexturePath = DalamudInterface.Instance.GetAssetPath("color_select_dot.png"),
|
||||
FitTexture = true,
|
||||
};
|
||||
ColorDotNode.AttachNode(this);
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
WhiteGradientNode.Size = Size;
|
||||
ColorGradientNode.Size = Size;
|
||||
BlackGradientNode.Size = Size;
|
||||
|
||||
Origin = Size / 2.0f;
|
||||
|
||||
ColorDotNode.Size = new Vector2(16.0f, 16.0f);
|
||||
ColorDotNode.Origin = ColorDotNode.Size / 2.0f;
|
||||
ColorDotNode.Position = new Vector2(Width, 0.0f) - ColorDotNode.Origin;
|
||||
}
|
||||
|
||||
public override ColorHelpers.HsvaColor MultiplyColorHsva {
|
||||
get => ColorGradientNode.MultiplyColorHsva;
|
||||
set => ColorGradientNode.MultiplyColorHsva = value;
|
||||
}
|
||||
|
||||
public Vector2 ColorDotPosition {
|
||||
get => ColorDotNode.Position + ColorDotNode.Origin;
|
||||
set => ColorDotNode.Position = value - ColorDotNode.Origin;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace KamiToolKit.Premade.GenericListItemNodes;
|
||||
|
||||
public abstract class GenericListItemNode<T> : ListItemNode<T> {
|
||||
public override float ItemHeight => 48.0f;
|
||||
|
||||
protected readonly IconImageNode IconNode;
|
||||
protected readonly TextNode LabelTextNode;
|
||||
protected readonly TextNode SubLabelTextNode;
|
||||
protected readonly TextNode IdTextNode;
|
||||
|
||||
protected GenericListItemNode() {
|
||||
IconNode = new IconImageNode {
|
||||
FitTexture = true,
|
||||
IconId = 60072,
|
||||
};
|
||||
IconNode.AttachNode(this);
|
||||
|
||||
LabelTextNode = new TextNode {
|
||||
TextFlags = TextFlags.Ellipsis | TextFlags.Emboss,
|
||||
FontSize = 14,
|
||||
LineSpacing = 14,
|
||||
AlignmentType = AlignmentType.BottomLeft,
|
||||
TextColor = ColorHelper.GetColor(8),
|
||||
TextOutlineColor = ColorHelper.GetColor(7),
|
||||
};
|
||||
LabelTextNode.AttachNode(this);
|
||||
|
||||
SubLabelTextNode = new TextNode {
|
||||
TextFlags = TextFlags.Ellipsis | TextFlags.Emboss,
|
||||
FontSize = 12,
|
||||
LineSpacing = 12,
|
||||
AlignmentType = AlignmentType.TopLeft,
|
||||
TextColor = ColorHelper.GetColor(3),
|
||||
TextOutlineColor = ColorHelper.GetColor(7),
|
||||
};
|
||||
SubLabelTextNode.AttachNode(this);
|
||||
|
||||
IdTextNode = new TextNode {
|
||||
TextFlags = TextFlags.Emboss,
|
||||
FontSize = 10,
|
||||
AlignmentType = AlignmentType.BottomRight,
|
||||
TextColor = ColorHelper.GetColor(3),
|
||||
};
|
||||
IdTextNode.AttachNode(this);
|
||||
|
||||
CollisionNode.ShowClickableCursor = true;
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
IconNode.Size = new Vector2(Height - 4.0f, Height - 4.0f);
|
||||
IconNode.Position = new Vector2(2.0f, 2.0f);
|
||||
|
||||
LabelTextNode.Size = new Vector2(Width - Height - 2.0f - 30.0f, Height / 2.0f);
|
||||
LabelTextNode.Position = new Vector2(Height + 2.0f, 0.0f);
|
||||
|
||||
SubLabelTextNode.Size = new Vector2(Width - Height - 2.0f - 10.0f, Height / 2.0f);
|
||||
SubLabelTextNode.Position = new Vector2(Height + 2.0f + 10.0f, Height / 2.0f);
|
||||
|
||||
IdTextNode.Size = new Vector2(30.0f, Height / 2.0f);
|
||||
IdTextNode.Position = new Vector2(Width - 30.0f, 0.0f);
|
||||
}
|
||||
|
||||
protected override void SetNodeData(T itemData) {
|
||||
IconNode.IconId = GetIconId(itemData);
|
||||
LabelTextNode.String = GetLabelText(itemData);
|
||||
SubLabelTextNode.String = GetSubLabelText(itemData);
|
||||
IdTextNode.String = GetId(itemData).ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
protected abstract uint GetIconId(T data);
|
||||
protected abstract string GetLabelText(T data);
|
||||
protected abstract string GetSubLabelText(T data);
|
||||
protected abstract uint? GetId(T data);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace KamiToolKit.Premade.GenericListItemNodes;
|
||||
|
||||
public abstract class GenericSimpleListItemNode<T> : ListItemNode<T> {
|
||||
public override float ItemHeight => 48.0f;
|
||||
|
||||
protected readonly IconImageNode IconNode;
|
||||
protected readonly TextNode LabelTextNode;
|
||||
|
||||
protected GenericSimpleListItemNode() {
|
||||
IconNode = new IconImageNode {
|
||||
FitTexture = true,
|
||||
IconId = 60072,
|
||||
};
|
||||
IconNode.AttachNode(this);
|
||||
|
||||
LabelTextNode = new TextNode {
|
||||
TextFlags = TextFlags.Ellipsis | TextFlags.Emboss,
|
||||
FontSize = 14,
|
||||
LineSpacing = 14,
|
||||
AlignmentType = AlignmentType.Left,
|
||||
TextColor = ColorHelper.GetColor(8),
|
||||
TextOutlineColor = ColorHelper.GetColor(7),
|
||||
};
|
||||
LabelTextNode.AttachNode(this);
|
||||
|
||||
CollisionNode.ShowClickableCursor = true;
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
IconNode.Size = new Vector2(Height - 4.0f, Height - 4.0f);
|
||||
IconNode.Position = new Vector2(2.0f, 2.0f);
|
||||
|
||||
LabelTextNode.Size = new Vector2(Width - IconNode.Width - 6.0f, Height);
|
||||
LabelTextNode.Position = new Vector2(IconNode.Bounds.Right + 6.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace KamiToolKit.Premade.GenericListItemNodes;
|
||||
|
||||
public abstract class GenericStringListItemNode<T> : ListItemNode<T> {
|
||||
public override float ItemHeight => 24.0f;
|
||||
|
||||
protected readonly TextNode StringNode;
|
||||
|
||||
protected GenericStringListItemNode() {
|
||||
StringNode = new TextNode {
|
||||
TextFlags = TextFlags.Ellipsis | TextFlags.Emboss,
|
||||
FontSize = 14,
|
||||
LineSpacing = 14,
|
||||
AlignmentType = AlignmentType.Left,
|
||||
TextColor = ColorHelper.GetColor(8),
|
||||
TextOutlineColor = ColorHelper.GetColor(7),
|
||||
};
|
||||
StringNode.AttachNode(this);
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
StringNode.Size = new Vector2(Width - 20.0f, Height);
|
||||
StringNode.Position = new Vector2(10.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using FFXIVClientStructs.Interop;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace KamiToolKit.Premade.ListItemNodes;
|
||||
|
||||
public unsafe class AddonListItemNode : ListItemNode<Pointer<AtkUnitBase>> {
|
||||
public override float ItemHeight => 48.0f;
|
||||
|
||||
protected readonly IconImageNode IconNode;
|
||||
protected readonly TextNode LabelTextNode;
|
||||
|
||||
public AddonListItemNode() {
|
||||
IconNode = new IconImageNode {
|
||||
FitTexture = true,
|
||||
IconId = 60072,
|
||||
};
|
||||
IconNode.AttachNode(this);
|
||||
|
||||
LabelTextNode = new TextNode {
|
||||
TextFlags = TextFlags.Ellipsis | TextFlags.Emboss,
|
||||
FontSize = 14,
|
||||
LineSpacing = 14,
|
||||
AlignmentType = AlignmentType.Left,
|
||||
TextColor = ColorHelper.GetColor(8),
|
||||
TextOutlineColor = ColorHelper.GetColor(7),
|
||||
};
|
||||
LabelTextNode.AttachNode(this);
|
||||
|
||||
CollisionNode.ShowClickableCursor = true;
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
IconNode.Size = new Vector2(Height - 4.0f, Height - 4.0f);
|
||||
IconNode.Position = new Vector2(2.0f, 2.0f);
|
||||
|
||||
LabelTextNode.Size = new Vector2(Width - IconNode.Width - 6.0f, Height);
|
||||
LabelTextNode.Position = new Vector2(IconNode.Bounds.Right + 6.0f, 0.0f);
|
||||
}
|
||||
|
||||
protected override void SetNodeData(Pointer<AtkUnitBase> itemData) {
|
||||
if (itemData.Value is null) return;
|
||||
|
||||
IconNode.IconId = itemData.Value->IsVisible ? (uint) 60071 : 60072;
|
||||
LabelTextNode.String = itemData.Value->NameString;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace KamiToolKit.Premade.ListItemNodes;
|
||||
|
||||
public class CurrencyListItemNode : ListItemNode<Item> {
|
||||
public override float ItemHeight => 48.0f;
|
||||
|
||||
protected readonly IconImageNode IconNode;
|
||||
protected readonly TextNode LabelTextNode;
|
||||
|
||||
public CurrencyListItemNode() {
|
||||
IconNode = new IconImageNode {
|
||||
FitTexture = true,
|
||||
IconId = 60072,
|
||||
};
|
||||
IconNode.AttachNode(this);
|
||||
|
||||
LabelTextNode = new TextNode {
|
||||
TextFlags = TextFlags.Ellipsis | TextFlags.Emboss,
|
||||
FontSize = 14,
|
||||
LineSpacing = 14,
|
||||
AlignmentType = AlignmentType.Left,
|
||||
TextColor = ColorHelper.GetColor(8),
|
||||
TextOutlineColor = ColorHelper.GetColor(7),
|
||||
};
|
||||
LabelTextNode.AttachNode(this);
|
||||
|
||||
CollisionNode.ShowClickableCursor = true;
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
IconNode.Size = new Vector2(Height - 4.0f, Height - 4.0f);
|
||||
IconNode.Position = new Vector2(2.0f, 2.0f);
|
||||
|
||||
LabelTextNode.Size = new Vector2(Width - IconNode.Width - 6.0f, Height);
|
||||
LabelTextNode.Position = new Vector2(IconNode.Bounds.Right + 6.0f, 0.0f);
|
||||
}
|
||||
|
||||
protected override void SetNodeData(Item itemData) {
|
||||
if (itemData.RowId is 0) return;
|
||||
|
||||
IconNode.IconId = itemData.Icon;
|
||||
LabelTextNode.String = itemData.Name.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace KamiToolKit.Premade.ListItemNodes;
|
||||
|
||||
public class ItemListItemNode : ListItemNode<Item> {
|
||||
public override float ItemHeight => 32.0f;
|
||||
|
||||
protected readonly IconImageNode IconNode;
|
||||
protected readonly TextNode LabelTextNode;
|
||||
protected readonly TextNode SubLabelTextNode;
|
||||
protected readonly TextNode IdTextNode;
|
||||
|
||||
public ItemListItemNode() {
|
||||
IconNode = new IconImageNode {
|
||||
FitTexture = true,
|
||||
IconId = 60072,
|
||||
};
|
||||
IconNode.AttachNode(this);
|
||||
|
||||
LabelTextNode = new TextNode {
|
||||
TextFlags = TextFlags.Ellipsis | TextFlags.Emboss,
|
||||
FontSize = 14,
|
||||
LineSpacing = 14,
|
||||
AlignmentType = AlignmentType.BottomLeft,
|
||||
TextColor = ColorHelper.GetColor(8),
|
||||
TextOutlineColor = ColorHelper.GetColor(7),
|
||||
};
|
||||
LabelTextNode.AttachNode(this);
|
||||
|
||||
SubLabelTextNode = new TextNode {
|
||||
TextFlags = TextFlags.Ellipsis | TextFlags.Emboss,
|
||||
FontSize = 12,
|
||||
LineSpacing = 12,
|
||||
AlignmentType = AlignmentType.TopLeft,
|
||||
TextColor = ColorHelper.GetColor(3),
|
||||
TextOutlineColor = ColorHelper.GetColor(7),
|
||||
};
|
||||
SubLabelTextNode.AttachNode(this);
|
||||
|
||||
IdTextNode = new TextNode {
|
||||
TextFlags = TextFlags.Emboss,
|
||||
FontSize = 10,
|
||||
AlignmentType = AlignmentType.BottomRight,
|
||||
TextColor = ColorHelper.GetColor(3),
|
||||
};
|
||||
IdTextNode.AttachNode(this);
|
||||
|
||||
CollisionNode.ShowClickableCursor = true;
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
IconNode.Size = new Vector2(Height - 4.0f, Height - 4.0f);
|
||||
IconNode.Position = new Vector2(2.0f, 2.0f);
|
||||
|
||||
LabelTextNode.Size = new Vector2(Width - Height - 2.0f - 30.0f, Height / 2.0f);
|
||||
LabelTextNode.Position = new Vector2(Height + 2.0f, 0.0f);
|
||||
|
||||
SubLabelTextNode.Size = new Vector2(Width - Height - 2.0f - 10.0f, Height / 2.0f);
|
||||
SubLabelTextNode.Position = new Vector2(Height + 2.0f + 10.0f, Height / 2.0f);
|
||||
|
||||
IdTextNode.Size = new Vector2(30.0f, Height / 2.0f);
|
||||
IdTextNode.Position = new Vector2(Width - 30.0f, 0.0f);
|
||||
}
|
||||
|
||||
protected override void SetNodeData(Item itemData) {
|
||||
if (itemData.RowId is 0) return;
|
||||
|
||||
IconNode.IconId = itemData.Icon;
|
||||
LabelTextNode.String = itemData.Name.ToString();
|
||||
SubLabelTextNode.String = itemData.ItemSearchCategory.ValueNullable?.Name.ToString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace KamiToolKit.Premade.ListItemNodes;
|
||||
|
||||
public class StatusListItemNode : ListItemNode<Status> {
|
||||
public override float ItemHeight => 48.0f;
|
||||
|
||||
protected readonly IconImageNode IconImageNode;
|
||||
protected readonly TextNode StatusLabelNode;
|
||||
|
||||
public StatusListItemNode() {
|
||||
IconImageNode = new IconImageNode {
|
||||
FitTexture = true,
|
||||
IconId = 60072,
|
||||
};
|
||||
IconImageNode.AttachNode(this);
|
||||
|
||||
StatusLabelNode = new TextNode {
|
||||
TextFlags = TextFlags.Ellipsis | TextFlags.Emboss,
|
||||
FontSize = 14,
|
||||
LineSpacing = 14,
|
||||
AlignmentType = AlignmentType.Left,
|
||||
TextColor = ColorHelper.GetColor(8),
|
||||
TextOutlineColor = ColorHelper.GetColor(7),
|
||||
};
|
||||
StatusLabelNode.AttachNode(this);
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
IconImageNode.Size = new Vector2((Height - 4.0f) * 0.75f , Height - 4.0f);
|
||||
IconImageNode.Position = new Vector2(2.0f, 2.0f);
|
||||
|
||||
StatusLabelNode.Size = new Vector2(Width - IconImageNode.Width - 6.0f, Height);
|
||||
StatusLabelNode.Position = new Vector2(IconImageNode.Bounds.Right + 6.0f, 0.0f);
|
||||
}
|
||||
|
||||
protected override void SetNodeData(Status itemData) {
|
||||
IconImageNode.IconId = itemData.Icon;
|
||||
StatusLabelNode.String = itemData.Name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using KamiToolKit.Premade.GenericListItemNodes;
|
||||
|
||||
namespace KamiToolKit.Premade.ListItemNodes;
|
||||
|
||||
public class StringListItemNode : GenericStringListItemNode<string> {
|
||||
protected override void SetNodeData(string itemData)
|
||||
=> StringNode.String = itemData;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace KamiToolKit.Premade.ListItemNodes;
|
||||
|
||||
public class TerritoryTypeListItemNode : ListItemNode<TerritoryType> {
|
||||
public override float ItemHeight => 64.0f;
|
||||
|
||||
private readonly SimpleImageNode territoryImageNode;
|
||||
private readonly SimpleImageNode placeholderImageNode;
|
||||
private readonly TextNode territoryTitleNode;
|
||||
private readonly TextNode territoryDescriptionNode;
|
||||
private readonly TextNode territoryIdNode;
|
||||
|
||||
public TerritoryTypeListItemNode() {
|
||||
territoryImageNode = new SimpleImageNode {
|
||||
FitTexture = true,
|
||||
IsVisible = false,
|
||||
};
|
||||
territoryImageNode.AttachNode(this);
|
||||
|
||||
placeholderImageNode = new IconImageNode {
|
||||
FitTexture = true,
|
||||
IconId = 60072,
|
||||
};
|
||||
placeholderImageNode.AttachNode(this);
|
||||
|
||||
territoryTitleNode = new TextNode {
|
||||
TextFlags = TextFlags.Ellipsis,
|
||||
AlignmentType = AlignmentType.BottomLeft,
|
||||
String = "None Selected",
|
||||
};
|
||||
territoryTitleNode.AttachNode(this);
|
||||
|
||||
territoryDescriptionNode = new TextNode {
|
||||
TextFlags = TextFlags.Ellipsis,
|
||||
AlignmentType = AlignmentType.TopLeft,
|
||||
TextColor = ColorHelper.GetColor(2),
|
||||
};
|
||||
territoryDescriptionNode.AttachNode(this);
|
||||
|
||||
territoryIdNode = new TextNode {
|
||||
AlignmentType = AlignmentType.Right,
|
||||
TextColor = ColorHelper.GetColor(3),
|
||||
};
|
||||
territoryIdNode.AttachNode(this);
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
territoryImageNode.Size = new Vector2((Height - 4.0f) * 1.777f, Height - 4.0f);
|
||||
territoryImageNode.Position = new Vector2(2.0f, 2.0f);
|
||||
|
||||
territoryIdNode.Size = new Vector2(30.0f, 30.0f);
|
||||
territoryIdNode.Position = new Vector2(Width - territoryIdNode.Width, 0.0f);
|
||||
|
||||
placeholderImageNode.Size = new Vector2(Height - 4.0f, Height - 4.0f);
|
||||
placeholderImageNode.Position = new Vector2(2.0f, 2.0f);
|
||||
|
||||
territoryTitleNode.Size = new Vector2(Width - territoryImageNode.Width - 10.0f - territoryIdNode.Width - 4.0f, Height / 2.0f);
|
||||
territoryTitleNode.Position = new Vector2(territoryImageNode.Bounds.Right + 8.0f, 0.0f);
|
||||
|
||||
territoryDescriptionNode.Size = territoryTitleNode.Size;
|
||||
territoryDescriptionNode.Position = new Vector2(territoryTitleNode.Bounds.Left, Height / 2.0f);
|
||||
}
|
||||
|
||||
protected override void SetNodeData(TerritoryType territory) {
|
||||
if (territory.RowId is 0) return;
|
||||
|
||||
territoryIdNode.String = territory.RowId.ToString();
|
||||
|
||||
if (territory.LoadingImage.ValueNullable?.FileName is { IsEmpty: false } filePath) {
|
||||
territoryImageNode.LoadTexture($"ui/loadingimage/{filePath}_hr1.tex");
|
||||
territoryImageNode.IsVisible = true;
|
||||
}
|
||||
else {
|
||||
territoryImageNode.IsVisible = false;
|
||||
}
|
||||
|
||||
placeholderImageNode.IsVisible = !territoryImageNode.IsVisible;
|
||||
|
||||
territoryTitleNode.String = territory.PlaceName.ValueNullable?.Name.ToString() ?? string.Empty;
|
||||
territoryDescriptionNode.String = territory.ContentFinderCondition.RowId is 0 ? string.Empty : territory.ContentFinderCondition.Value.Name.ToString();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using FFXIVClientStructs.Interop;
|
||||
using KamiToolKit.Premade.ListItemNodes;
|
||||
|
||||
namespace KamiToolKit.Premade.SearchAddons;
|
||||
|
||||
public unsafe class AddonSearchAddon : BaseSearchAddon<Pointer<AtkUnitBase>, AddonListItemNode> {
|
||||
|
||||
public AddonSearchAddon() {
|
||||
SearchOptions = GetAllAddons();
|
||||
SortingOptions = [ "Visibility", "Alphabetical" ];
|
||||
ItemSpacing = 3.0f;
|
||||
}
|
||||
|
||||
protected override int Comparer(Pointer<AtkUnitBase> left, Pointer<AtkUnitBase> right, string sortingString, bool reversed) {
|
||||
if (left.Value is null || right.Value is null) return 0;
|
||||
|
||||
switch (sortingString) {
|
||||
case "Alphabetical":
|
||||
return string.CompareOrdinal(left.Value->NameString, right.Value->NameString) * (reversed ? -1 : 1);
|
||||
|
||||
case "Visibility":
|
||||
var visibilityComparison = right.Value->IsVisible.CompareTo(left.Value->IsVisible);
|
||||
if (visibilityComparison is 0) {
|
||||
visibilityComparison = string.CompareOrdinal(left.Value->NameString, right.Value->NameString);
|
||||
}
|
||||
|
||||
return visibilityComparison * (reversed ? -1 : 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected override bool IsMatch(Pointer<AtkUnitBase> item, string searchString) {
|
||||
var regex = new Regex(searchString,RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
|
||||
return regex.IsMatch(item.Value->NameString);
|
||||
}
|
||||
|
||||
private static List<Pointer<AtkUnitBase>> GetAllAddons() {
|
||||
List<Pointer<AtkUnitBase>> addons = [];
|
||||
|
||||
foreach (var entry in RaptureAtkUnitManager.Instance()->AllLoadedUnitsList.Entries) {
|
||||
if (entry.Value is null) continue;
|
||||
addons.Add(entry);
|
||||
}
|
||||
|
||||
return addons;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Nodes;
|
||||
using KamiToolKit.Premade.Widgets;
|
||||
|
||||
namespace KamiToolKit.Premade.SearchAddons;
|
||||
|
||||
public abstract class BaseSearchAddon<T, TU> : NativeAddon where TU : ListItemNode<T>, new() {
|
||||
|
||||
private SearchWidget? searchWidget;
|
||||
private ListNode<T, TU>? listNode;
|
||||
|
||||
private TextButtonNode? cancelButton;
|
||||
private TextButtonNode? confirmButton;
|
||||
|
||||
private T? selectedOption;
|
||||
|
||||
protected override unsafe void OnSetup(AtkUnitBase* addon) {
|
||||
searchWidget = new SearchWidget {
|
||||
Size = ContentSize,
|
||||
Position = ContentStartPosition,
|
||||
SortingOptions = SortingOptions,
|
||||
OnSortOrderChanged = OnSortOrderUpdated,
|
||||
OnSearchUpdated = OnSearchUpdated,
|
||||
};
|
||||
searchWidget.AttachNode(this);
|
||||
|
||||
listNode = new ListNode<T, TU> {
|
||||
Position = new Vector2(ContentStartPosition.X, searchWidget.Y + searchWidget.Height + 8.0f),
|
||||
Size = new Vector2(ContentSize.X, ContentSize.Y - searchWidget.Height - 16.0f - 24.0f - 8.0f),
|
||||
ItemSpacing = ItemSpacing,
|
||||
OptionsList = SearchOptions,
|
||||
OnItemSelected = item => {
|
||||
selectedOption = item;
|
||||
confirmButton?.IsEnabled = true;
|
||||
},
|
||||
};
|
||||
listNode.AttachNode(this);
|
||||
|
||||
const float buttonPadding = 20.0f;
|
||||
var contentWidth = ContentSize.X - buttonPadding * 2;
|
||||
var buttonWidth = contentWidth / 3.0f;
|
||||
|
||||
cancelButton = new TextButtonNode {
|
||||
Size = new Vector2(buttonWidth, 24.0f),
|
||||
Position = new Vector2(ContentStartPosition.X, ContentStartPosition.Y + ContentSize.Y - 24.0f - 8.0f),
|
||||
String = "Cancel",
|
||||
OnClick = OnCancelClicked,
|
||||
};
|
||||
cancelButton.AttachNode(this);
|
||||
|
||||
confirmButton = new TextButtonNode {
|
||||
Size = new Vector2(buttonWidth, 24.0f),
|
||||
Position = new Vector2(ContentStartPosition.X + buttonWidth * 2 + buttonPadding * 2, ContentStartPosition.Y + ContentSize.Y - 24.0f - 8.0f),
|
||||
IsEnabled = false,
|
||||
String = "Confirm",
|
||||
OnClick = OnConfirmClicked,
|
||||
};
|
||||
confirmButton.AttachNode(this);
|
||||
|
||||
if (SortingOptions.Count > 0) {
|
||||
OnSortOrderUpdated(SortingOptions.First(), false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCancelClicked() {
|
||||
selectedOption = default;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnConfirmClicked() {
|
||||
if (selectedOption is not null) {
|
||||
SelectionResult?.Invoke(selectedOption);
|
||||
}
|
||||
|
||||
selectedOption = default;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnSortOrderUpdated(string sortingString, bool reversed) {
|
||||
var resortedList = SearchOptions.ToList();
|
||||
resortedList.Sort((left, right) => Comparer(left, right, sortingString, reversed));
|
||||
|
||||
listNode?.OptionsList = resortedList;
|
||||
}
|
||||
|
||||
private void OnSearchUpdated(string searchString) {
|
||||
listNode?.OptionsList = SearchOptions.Where(item => IsMatch(item, searchString)).ToList();
|
||||
}
|
||||
|
||||
protected abstract int Comparer(T left, T right, string sortingString, bool reversed);
|
||||
protected abstract bool IsMatch(T item, string searchString);
|
||||
|
||||
public List<string> SortingOptions { get; init; } = [ "Alphabetical", "Id" ];
|
||||
|
||||
public List<T> SearchOptions {
|
||||
get;
|
||||
set {
|
||||
field = value;
|
||||
listNode?.OptionsList = value;
|
||||
}
|
||||
} = [];
|
||||
|
||||
public float ItemSpacing {
|
||||
get;
|
||||
set {
|
||||
field = value;
|
||||
listNode?.ItemSpacing = value;
|
||||
}
|
||||
} = 6.0f;
|
||||
|
||||
public Action<T>? SelectionResult { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Premade.ListItemNodes;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace KamiToolKit.Premade.SearchAddons;
|
||||
|
||||
public class CurrencySearchAddon : ItemSearchAddonBase<CurrencyListItemNode> {
|
||||
public CurrencySearchAddon()
|
||||
=> SearchOptions = GetCurrencyItems().ToList();
|
||||
|
||||
private static IEnumerable<Item> GetCurrencyItems() {
|
||||
var dataManager = DalamudInterface.Instance.DataManager;
|
||||
|
||||
var obsoleteTomes = dataManager.GetExcelSheet<TomestonesItem>()
|
||||
.Where(item => item.Tomestones.RowId is 0)
|
||||
.Select(item => item.Item.Value)
|
||||
.ToHashSet(EqualityComparer<Item>.Create(
|
||||
(x, y) => x.RowId == y.RowId,
|
||||
obj => obj.RowId.GetHashCode()
|
||||
));
|
||||
|
||||
return dataManager.GetExcelSheet<Item>()
|
||||
.Where(item => item is { Name.IsEmpty: false, ItemUICategory.RowId: 100 } or { RowId: >= 1 and < 100, Name.IsEmpty: false })
|
||||
.Where(item => !obsoleteTomes.Contains(item));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
using KamiToolKit.Premade.ListItemNodes;
|
||||
|
||||
namespace KamiToolKit.Premade.SearchAddons;
|
||||
|
||||
public class ItemSearchAddon : ItemSearchAddonBase<ItemListItemNode>;
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using KamiToolKit.Nodes;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace KamiToolKit.Premade.SearchAddons;
|
||||
|
||||
public class ItemSearchAddonBase<T> : BaseSearchAddon<Item, T> where T : ListItemNode<Item>, new() {
|
||||
protected override int Comparer(Item left, Item right, string sortingString, bool reversed) {
|
||||
var result = sortingString switch {
|
||||
"Alphabetical" => string.CompareOrdinal(left.Name.ToString(), right.Name.ToString()),
|
||||
"Id" => left.RowId.CompareTo(right.RowId),
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
return reversed ? -result : result;
|
||||
}
|
||||
|
||||
protected override bool IsMatch(Item item, string searchString) {
|
||||
var isDescriptionSearch = searchString.StartsWith('$');
|
||||
|
||||
if (isDescriptionSearch) {
|
||||
searchString = searchString[1..];
|
||||
}
|
||||
|
||||
var regex = new Regex(searchString,RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
|
||||
if (regex.IsMatch(item.RowId.ToString())) return true;
|
||||
if (regex.IsMatch(item.Name.ToString())) return true;
|
||||
if (regex.IsMatch(item.Description.ToString()) && isDescriptionSearch) return true;
|
||||
if (regex.IsMatch(item.LevelEquip.ToString())) return true;
|
||||
if (regex.IsMatch(item.LevelItem.RowId.ToString())) return true;
|
||||
if (regex.IsMatch(item.ClassJobCategory.Value.Name.ToString())) return true;
|
||||
if (regex.IsMatch(item.ItemUICategory.Value.Name.ToString())) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Premade.ListItemNodes;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace KamiToolKit.Premade.SearchAddons;
|
||||
|
||||
public class StatusSearchAddon : BaseSearchAddon<Status, StatusListItemNode> {
|
||||
public StatusSearchAddon() {
|
||||
SearchOptions = DalamudInterface.Instance.DataManager.GetExcelSheet<Status>()
|
||||
.Where(territory => territory.RowId is not 0)
|
||||
.Where(territory => !territory.Name.IsEmpty)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
protected override int Comparer(Status left, Status right, string sortingString, bool reversed){
|
||||
var result = sortingString switch {
|
||||
"Alphabetical" => string.CompareOrdinal(left.Name.ToString(), right.Name.ToString()),
|
||||
"Id" => left.RowId.CompareTo(right.RowId),
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
return reversed ? -result : result;
|
||||
}
|
||||
|
||||
protected override bool IsMatch(Status item, string searchString) {
|
||||
var regex = new Regex(searchString,RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
|
||||
if (regex.IsMatch(item.RowId.ToString())) return true;
|
||||
if (regex.IsMatch(item.Name.ToString())) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Utility;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Premade.ListItemNodes;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace KamiToolKit.Premade.SearchAddons;
|
||||
|
||||
public class TerritorySearchAddon : BaseSearchAddon<TerritoryType, TerritoryTypeListItemNode> {
|
||||
public TerritorySearchAddon() {
|
||||
SearchOptions = DalamudInterface.Instance.DataManager.GetExcelSheet<TerritoryType>()
|
||||
.Where(territory => territory.RowId is not 0)
|
||||
.Where(territory => territory.LoadingImage.RowId is not 0)
|
||||
.Where(territory => !territory.PlaceName.ValueNullable?.Name.ToString().IsNullOrEmpty() ?? false)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
protected override int Comparer(TerritoryType left, TerritoryType right, string sortingString, bool reversed) {
|
||||
var result = sortingString switch {
|
||||
"Alphabetical" => string.CompareOrdinal(left.Name.ToString(), right.Name.ToString()),
|
||||
"Id" => left.RowId.CompareTo(right.RowId),
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
return reversed ? -result : result;
|
||||
}
|
||||
|
||||
protected override bool IsMatch(TerritoryType item, string searchString) {
|
||||
var regex = new Regex(searchString,RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
|
||||
if (regex.IsMatch(item.RowId.ToString())) return true;
|
||||
if (regex.IsMatch(item.PlaceName.ValueNullable?.Name.ToString() ?? string.Empty)) return true;
|
||||
if (regex.IsMatch(item.ContentFinderCondition.ValueNullable?.Name.ToString() ?? string.Empty)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using KamiToolKit.Nodes;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace KamiToolKit.Premade.Widgets;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a search element that has a searchbar, and a dropdown for reordering elements.
|
||||
/// </summary>
|
||||
public unsafe class SearchWidget : SimpleComponentNode {
|
||||
public readonly TextInputNode InputNode;
|
||||
public readonly TextDropDownNode SortOrderDropDown;
|
||||
public readonly CircleButtonNode ReverseButtonNode;
|
||||
|
||||
public bool IsReversed { get; private set; }
|
||||
public string SearchText { get; private set; } = string.Empty;
|
||||
public string SortMode { get; private set; } = string.Empty;
|
||||
|
||||
public delegate void SearchUpdated(string searchString);
|
||||
public delegate void SortUpdated(string sortingString, bool reversed);
|
||||
|
||||
public SearchWidget() {
|
||||
InputNode = new TextInputNode {
|
||||
PlaceholderString = "Search . . .",
|
||||
String = SearchText,
|
||||
OnInputReceived = SearchTextChanged,
|
||||
};
|
||||
InputNode.AttachNode(this);
|
||||
|
||||
SortOrderDropDown = new TextDropDownNode {
|
||||
MaxListOptions = 0,
|
||||
Options = [],
|
||||
IsVisible = false,
|
||||
SelectedOption = SortMode == string.Empty ? null : SortMode,
|
||||
OnOptionSelected = DropDownChanged,
|
||||
};
|
||||
SortOrderDropDown.AttachNode(this);
|
||||
|
||||
ReverseButtonNode = new CircleButtonNode {
|
||||
Icon = ButtonIcon.Sort,
|
||||
OnClick = OnReverseButtonClicked,
|
||||
TextTooltip = "Reverse Sort Direction",
|
||||
IsVisible = false,
|
||||
};
|
||||
ReverseButtonNode.AttachNode(this);
|
||||
|
||||
ResNode->SetHeight(38);
|
||||
}
|
||||
|
||||
public required SortUpdated OnSortOrderChanged { get; set; }
|
||||
|
||||
private void OnReverseButtonClicked() {
|
||||
IsReversed = !IsReversed;
|
||||
OnSortOrderChanged(SortMode, IsReversed);
|
||||
}
|
||||
|
||||
private void DropDownChanged(string newOption) {
|
||||
SortMode = newOption;
|
||||
OnSortOrderChanged(SortMode, IsReversed);
|
||||
}
|
||||
|
||||
public required SearchUpdated OnSearchUpdated { get; set; }
|
||||
|
||||
private void SearchTextChanged(ReadOnlySeString newSearchString) {
|
||||
SearchText = newSearchString.ToString();
|
||||
OnSearchUpdated(SearchText);
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
InputNode.Size = new Vector2(Width - 10.0f, 28.0f);
|
||||
InputNode.Position = new Vector2(5.0f, 5.0f);
|
||||
|
||||
ReverseButtonNode.Size = new Vector2(28.0f, 28.0f);
|
||||
ReverseButtonNode.Position = new Vector2(Width - 5.0f - ReverseButtonNode.Width, InputNode.Height + 8.0f);
|
||||
|
||||
SortOrderDropDown.Size = new Vector2(Width - 5.0f - ReverseButtonNode.Width - 5.0f - 5.0f, 28.0f);
|
||||
SortOrderDropDown.Position = new Vector2(5.0f, InputNode.Height + 8.0f);
|
||||
}
|
||||
|
||||
// Disallow modifying the height of this element.
|
||||
public override float Height { get => base.Height; set { } }
|
||||
|
||||
public int MaxDropdownOptions {
|
||||
get => SortOrderDropDown.MaxListOptions;
|
||||
set => SortOrderDropDown.MaxListOptions = value;
|
||||
}
|
||||
|
||||
public List<string> SortingOptions {
|
||||
get => SortOrderDropDown.Options ?? [];
|
||||
set {
|
||||
SortOrderDropDown.Options = value;
|
||||
SortOrderDropDown.MaxListOptions = value.Count / 2 + 1;
|
||||
SortOrderDropDown.IsVisible = value.Count > 0;
|
||||
ReverseButtonNode.IsVisible = value.Count > 0;
|
||||
|
||||
ResNode->SetHeight((ushort)(value.Count > 0 ? 69 : 38));
|
||||
|
||||
if (SortingOptions.Count > 0) {
|
||||
SortMode = value.First();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string? SelectedOption => SortOrderDropDown.SelectedOption;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace KamiToolKit.Premade.Widgets;
|
||||
|
||||
public class Vector2EditWidget : SimpleComponentNode {
|
||||
public readonly GridNode GridNode;
|
||||
public readonly TextNode WidthTextNode;
|
||||
public readonly TextNode HeightTextNode;
|
||||
public readonly NumericInputNode WidthInputNode;
|
||||
public readonly NumericInputNode HeightInputNode;
|
||||
|
||||
public Vector2EditWidget() {
|
||||
GridNode = new GridNode {
|
||||
GridSize = new GridSize(2, 2),
|
||||
};
|
||||
GridNode.AttachNode(this);
|
||||
|
||||
WidthTextNode = new TextNode {
|
||||
AlignmentType = AlignmentType.Bottom,
|
||||
FontType = FontType.Axis,
|
||||
FontSize = 14,
|
||||
LineSpacing = 14,
|
||||
TextColor = ColorHelper.GetColor(8),
|
||||
TextOutlineColor = ColorHelper.GetColor(7),
|
||||
TextFlags = TextFlags.Edge | TextFlags.AutoAdjustNodeSize,
|
||||
String = XLabel ?? "Width",
|
||||
};
|
||||
WidthTextNode.AttachNode(GridNode[0, 0]);
|
||||
|
||||
HeightTextNode = new TextNode {
|
||||
AlignmentType = AlignmentType.Bottom,
|
||||
FontType = FontType.Axis,
|
||||
FontSize = 14,
|
||||
LineSpacing = 14,
|
||||
TextColor = ColorHelper.GetColor(8),
|
||||
TextOutlineColor = ColorHelper.GetColor(7),
|
||||
TextFlags = TextFlags.Edge | TextFlags.AutoAdjustNodeSize,
|
||||
String = YLabel ?? "Height",
|
||||
};
|
||||
HeightTextNode.AttachNode(GridNode[1, 0]);
|
||||
|
||||
WidthInputNode = new NumericInputNode {
|
||||
Position = new Vector2(2.0f, 2.0f),
|
||||
OnValueUpdate = OnXValueUpdated,
|
||||
};
|
||||
WidthInputNode.AttachNode(GridNode[0, 1]);
|
||||
|
||||
HeightInputNode = new NumericInputNode {
|
||||
Position = new Vector2(2.0f, 2.0f),
|
||||
OnValueUpdate = OnYValueUpdated,
|
||||
};
|
||||
HeightInputNode.AttachNode(GridNode[1, 1]);
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
GridNode.Size = Size;
|
||||
|
||||
WidthTextNode.Size = GridNode[0, 0].Size;
|
||||
HeightTextNode.Size = GridNode[1, 0].Size;
|
||||
|
||||
WidthInputNode.Size = GridNode[0, 1].Size;
|
||||
HeightInputNode.Size = GridNode[1, 1].Size;
|
||||
}
|
||||
|
||||
private void OnXValueUpdated(int newValue) {
|
||||
Value = Value with { X = newValue };
|
||||
OnValueChanged?.Invoke(Value);
|
||||
}
|
||||
|
||||
private void OnYValueUpdated(int newValue) {
|
||||
Value = Value with { Y = newValue };
|
||||
OnValueChanged?.Invoke(Value);
|
||||
}
|
||||
|
||||
public Vector2 Value {
|
||||
get;
|
||||
set {
|
||||
field = value;
|
||||
WidthInputNode.Value = (int) value.X;
|
||||
HeightInputNode.Value = (int) value.Y;
|
||||
}
|
||||
}
|
||||
|
||||
public Action<Vector2>? OnValueChanged { get; set; }
|
||||
|
||||
public string? XLabel { get; set; }
|
||||
public string? YLabel { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user