Initial commit: AetherBags + KamiToolKit for FC Gitea
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user