Initial release: HSUI v1.0.0.0 - HUD replacement with configurable hotbars
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,682 @@
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using HSUI.Enums;
|
||||
using HSUI.Helpers;
|
||||
using HSUI.Interface.GeneralElements;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace HSUI.Config.Attributes
|
||||
{
|
||||
#region class attributes
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class ExportableAttribute : Attribute
|
||||
{
|
||||
public bool exportable;
|
||||
|
||||
public ExportableAttribute(bool exportable)
|
||||
{
|
||||
this.exportable = exportable;
|
||||
}
|
||||
}
|
||||
|
||||
public class ShareableAttribute : Attribute
|
||||
{
|
||||
public bool shareable;
|
||||
|
||||
public ShareableAttribute(bool shareable)
|
||||
{
|
||||
this.shareable = shareable;
|
||||
}
|
||||
}
|
||||
|
||||
public class ResettableAttribute : Attribute
|
||||
{
|
||||
public bool resettable;
|
||||
|
||||
public ResettableAttribute(bool resettable)
|
||||
{
|
||||
this.resettable = resettable;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DisableableAttribute : Attribute
|
||||
{
|
||||
public bool disableable;
|
||||
|
||||
public DisableableAttribute(bool disableable)
|
||||
{
|
||||
this.disableable = disableable;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DisableParentSettingsAttribute : Attribute
|
||||
{
|
||||
public readonly string[] DisabledFields;
|
||||
|
||||
public DisableParentSettingsAttribute(params string[] fields)
|
||||
{
|
||||
this.DisabledFields = fields;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region method attributes
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class ManualDrawAttribute : Attribute
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region field attributes
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public abstract class ConfigAttribute : Attribute
|
||||
{
|
||||
public string friendlyName;
|
||||
public bool isMonitored = false;
|
||||
public bool separator = false;
|
||||
public bool spacing = false;
|
||||
public string? help = null;
|
||||
|
||||
public ConfigAttribute(string friendlyName)
|
||||
{
|
||||
this.friendlyName = friendlyName;
|
||||
}
|
||||
|
||||
public bool Draw(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader = false)
|
||||
{
|
||||
bool result = DrawField(field, config, ID, collapsingHeader);
|
||||
|
||||
if (help != null && ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip(help);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public abstract bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader = false);
|
||||
|
||||
protected string IDText(string? ID) => ID != null ? " ##" + ID : "";
|
||||
|
||||
protected void TriggerChangeEvent<T>(PluginConfigObject config, string fieldName, object value, ChangeType type = ChangeType.None)
|
||||
{
|
||||
if (!isMonitored || config is not IOnChangeEventArgs eventObject)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
eventObject.OnValueChanged(new OnChangeEventArgs<T>(fieldName, (T)value, type));
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class CheckboxAttribute : ConfigAttribute
|
||||
{
|
||||
public CheckboxAttribute(string friendlyName) : base(friendlyName) { }
|
||||
|
||||
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||
{
|
||||
var disableable = config.Disableable;
|
||||
|
||||
if (!disableable && friendlyName == "Enabled")
|
||||
{
|
||||
if (ID != null)
|
||||
{
|
||||
ImGui.Text(ID);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool? fieldVal = (bool?)field.GetValue(config);
|
||||
bool boolVal = fieldVal.HasValue ? fieldVal.Value : false;
|
||||
|
||||
if (ImGui.Checkbox(ID != null && friendlyName == "Enabled" && !collapsingHeader ? ID : friendlyName + IDText(ID), ref boolVal))
|
||||
{
|
||||
field.SetValue(config, boolVal);
|
||||
|
||||
TriggerChangeEvent<bool>(config, field.Name, boolVal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
|
||||
public class RadioSelector : ConfigAttribute
|
||||
{
|
||||
private string[] _options;
|
||||
|
||||
public string? Label { get; set; }
|
||||
|
||||
public RadioSelector(params string[] options) : base(string.Join("_", options))
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public RadioSelector(Type enumType) : this(enumType.IsEnum ? Enum.GetNames(enumType) : Array.Empty<string>()) { }
|
||||
|
||||
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||
{
|
||||
bool changed = false;
|
||||
object? fieldVal = field.GetValue(config);
|
||||
|
||||
int intVal = 0;
|
||||
if (fieldVal != null)
|
||||
{
|
||||
intVal = (int)fieldVal;
|
||||
}
|
||||
|
||||
string? label = Label ?? friendlyName;
|
||||
if (!string.IsNullOrEmpty(label) && label != string.Join("_", _options))
|
||||
{
|
||||
ImGui.TextUnformatted(label);
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
for (int i = 0; i < _options.Length; i++)
|
||||
{
|
||||
changed |= ImGui.RadioButton(_options[i], ref intVal, i);
|
||||
if (i < _options.Length - 1)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
field.SetValue(config, intVal);
|
||||
TriggerChangeEvent<int>(config, field.Name, intVal);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class DragFloatAttribute : ConfigAttribute
|
||||
{
|
||||
public float min;
|
||||
public float max;
|
||||
public float velocity;
|
||||
|
||||
public DragFloatAttribute(string friendlyName) : base(friendlyName)
|
||||
{
|
||||
min = 1f;
|
||||
max = 1000f;
|
||||
velocity = 1f;
|
||||
}
|
||||
|
||||
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||
{
|
||||
float? fieldVal = (float?)field.GetValue(config);
|
||||
float floatVal = fieldVal.HasValue ? fieldVal.Value : 0;
|
||||
|
||||
if (ImGui.DragFloat(friendlyName + IDText(ID), ref floatVal, velocity, min, max))
|
||||
{
|
||||
field.SetValue(config, floatVal);
|
||||
|
||||
TriggerChangeEvent<float>(config, field.Name, floatVal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class DragIntAttribute : ConfigAttribute
|
||||
{
|
||||
public int min;
|
||||
public int max;
|
||||
public float velocity;
|
||||
|
||||
public DragIntAttribute(string friendlyName) : base(friendlyName)
|
||||
{
|
||||
min = 1;
|
||||
max = 1000;
|
||||
velocity = 1;
|
||||
}
|
||||
|
||||
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||
{
|
||||
int? fieldVal = (int?)field.GetValue(config);
|
||||
int intVal = fieldVal.HasValue ? fieldVal.Value : 0;
|
||||
|
||||
if (ImGui.DragInt(friendlyName + IDText(ID), ref intVal, velocity, min, max))
|
||||
{
|
||||
field.SetValue(config, intVal);
|
||||
|
||||
TriggerChangeEvent<int>(config, field.Name, intVal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class DragFloat2Attribute : ConfigAttribute
|
||||
{
|
||||
public float min;
|
||||
public float max;
|
||||
public float velocity;
|
||||
|
||||
public DragFloat2Attribute(string friendlyName) : base(friendlyName)
|
||||
{
|
||||
min = 1f;
|
||||
max = 1000f;
|
||||
velocity = 1f;
|
||||
}
|
||||
|
||||
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||
{
|
||||
Vector2? fieldVal = (Vector2?)field.GetValue(config);
|
||||
Vector2 vectorVal = fieldVal.HasValue ? fieldVal.Value : Vector2.Zero;
|
||||
|
||||
if (ImGui.DragFloat2(friendlyName + IDText(ID), ref vectorVal, velocity, min, max))
|
||||
{
|
||||
field.SetValue(config, vectorVal);
|
||||
|
||||
TriggerChangeEvent<Vector2>(config, field.Name, vectorVal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class DragInt2Attribute : ConfigAttribute
|
||||
{
|
||||
public int min;
|
||||
public int max;
|
||||
public int velocity;
|
||||
|
||||
public DragInt2Attribute(string friendlyName) : base(friendlyName)
|
||||
{
|
||||
min = 1;
|
||||
max = 1000;
|
||||
velocity = 1;
|
||||
}
|
||||
|
||||
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||
{
|
||||
Vector2? fieldVal = (Vector2?)field.GetValue(config);
|
||||
Vector2 vectorVal = fieldVal.HasValue ? fieldVal.Value : Vector2.Zero;
|
||||
|
||||
if (ImGui.DragFloat2(friendlyName + IDText(ID), ref vectorVal, velocity, min, max))
|
||||
{
|
||||
field.SetValue(config, vectorVal);
|
||||
|
||||
TriggerChangeEvent<Vector2>(config, field.Name, vectorVal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class InputTextAttribute : ConfigAttribute
|
||||
{
|
||||
public int maxLength;
|
||||
public bool formattable = true;
|
||||
|
||||
private string _searchText = "";
|
||||
|
||||
public InputTextAttribute(string friendlyName) : base(friendlyName)
|
||||
{
|
||||
this.friendlyName = friendlyName;
|
||||
maxLength = 999;
|
||||
}
|
||||
|
||||
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||
{
|
||||
string? fieldVal = (string?)field.GetValue(config);
|
||||
string stringVal = fieldVal ?? "";
|
||||
string? finalValue = null;
|
||||
|
||||
string popupId = ID != null ? "DelvUI_TextTagsList " + ID : "DelvUI_TextTagsList ##" + friendlyName;
|
||||
|
||||
if (!formattable)
|
||||
{
|
||||
if (ImGui.InputText(friendlyName + IDText(ID), ref stringVal))
|
||||
{
|
||||
finalValue = stringVal;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float scale = ImGuiHelpers.GlobalScale;
|
||||
float width = ImGui.CalcItemWidth();
|
||||
float height = Math.Max(24 * scale, ImGui.CalcTextSize(stringVal, false, width).Y + 6 * scale);
|
||||
Vector2 size = new Vector2(width, height);
|
||||
|
||||
if (ImGui.InputTextMultiline(friendlyName + IDText(ID), ref stringVal, maxLength, size, ImGuiInputTextFlags.AllowTabInput))
|
||||
{
|
||||
finalValue = stringVal;
|
||||
}
|
||||
|
||||
// text tags
|
||||
ImGui.SameLine();
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (ImGui.Button(FontAwesomeIcon.Pen.ToIconString() + "##" + ID))
|
||||
{
|
||||
ImGui.OpenPopup(popupId);
|
||||
}
|
||||
ImGui.PopFont();
|
||||
|
||||
ImGuiHelper.SetTooltip("Text Tags");
|
||||
}
|
||||
|
||||
var selectedTag = ImGuiHelper.DrawTextTagsList(popupId, ref _searchText);
|
||||
if (selectedTag != null)
|
||||
{
|
||||
finalValue = stringVal + selectedTag;
|
||||
}
|
||||
|
||||
if (finalValue != null)
|
||||
{
|
||||
field.SetValue(config, finalValue);
|
||||
TriggerChangeEvent<string>(config, field.Name, finalValue);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class ColorEdit4Attribute : ConfigAttribute
|
||||
{
|
||||
public ColorEdit4Attribute(string friendlyName) : base(friendlyName) { }
|
||||
|
||||
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||
{
|
||||
PluginConfigColor? colorVal = (PluginConfigColor?)field.GetValue(config);
|
||||
Vector4 vector = (colorVal != null ? colorVal.Vector : Vector4.Zero);
|
||||
|
||||
if (ImGui.ColorEdit4(friendlyName + IDText(ID), ref vector, ImGuiColorEditFlags.AlphaPreview | ImGuiColorEditFlags.AlphaBar))
|
||||
{
|
||||
if (colorVal is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
colorVal.Vector = vector;
|
||||
field.SetValue(config, colorVal);
|
||||
|
||||
TriggerChangeEvent<PluginConfigColor>(config, field.Name, colorVal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class ComboAttribute : ConfigAttribute
|
||||
{
|
||||
public string[] options;
|
||||
|
||||
public ComboAttribute(string friendlyName, params string[] options) : base(friendlyName)
|
||||
{
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||
{
|
||||
object? fieldVal = field.GetValue(config);
|
||||
|
||||
int intVal = 0;
|
||||
if (fieldVal != null)
|
||||
{
|
||||
intVal = (int)fieldVal;
|
||||
}
|
||||
|
||||
if (ImGui.Combo(friendlyName + IDText(ID), ref intVal, options, 4))
|
||||
{
|
||||
field.SetValue(config, intVal);
|
||||
|
||||
TriggerChangeEvent<int>(config, field.Name, intVal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class DragDropHorizontalAttribute : ConfigAttribute
|
||||
{
|
||||
public string[] names;
|
||||
|
||||
public DragDropHorizontalAttribute(string friendlyName, params string[] names) : base(friendlyName)
|
||||
{
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||
{
|
||||
ImGui.Text(friendlyName);
|
||||
int[]? fieldVal = (int[]?)field.GetValue(config);
|
||||
int[] order = fieldVal ?? Array.Empty<int>();
|
||||
|
||||
for (int i = 0; i < order.Length; i++)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.Button(names[order[i]], new Vector2(100, 25));
|
||||
|
||||
if (ImGui.IsItemActive())
|
||||
{
|
||||
float drag_dx = ImGui.GetMouseDragDelta(ImGuiMouseButton.Left).X;
|
||||
|
||||
if ((drag_dx > 80.0f && i < order.Length - 1))
|
||||
{
|
||||
var _curri = order[i];
|
||||
order[i] = order[i + 1];
|
||||
order[i + 1] = _curri;
|
||||
field.SetValue(config, order);
|
||||
ImGui.ResetMouseDragDelta();
|
||||
}
|
||||
else if ((drag_dx < -80.0f && i > 0))
|
||||
{
|
||||
var _curri = order[i];
|
||||
order[i] = order[i - 1];
|
||||
order[i - 1] = _curri;
|
||||
field.SetValue(config, order);
|
||||
ImGui.ResetMouseDragDelta();
|
||||
}
|
||||
|
||||
TriggerChangeEvent<int[]>(config, field.Name, order);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class FontAttribute : ConfigAttribute
|
||||
{
|
||||
public FontAttribute(string friendlyName = "Font and Size") : base(friendlyName) { }
|
||||
|
||||
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||
{
|
||||
var fontsConfig = ConfigurationManager.Instance.GetConfigObject<FontsConfig>();
|
||||
if (fontsConfig == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string? stringVal = (string?)field.GetValue(config);
|
||||
|
||||
int index = stringVal == null || stringVal.Length == 0 || !fontsConfig.Fonts.ContainsKey(stringVal) ? -1 :
|
||||
fontsConfig.Fonts.IndexOfKey(stringVal);
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
if (fontsConfig.Fonts.ContainsKey(FontsConfig.DefaultBigFontKey))
|
||||
{
|
||||
index = fontsConfig.Fonts.IndexOfKey(FontsConfig.DefaultBigFontKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var options = fontsConfig.Fonts.Values.Select(fontData => fontData.Name + " " + fontData.Size.ToString()).ToArray();
|
||||
|
||||
if (ImGui.Combo(friendlyName + IDText(ID), ref index, options, 4))
|
||||
{
|
||||
stringVal = fontsConfig.Fonts.Keys[index];
|
||||
field.SetValue(config, stringVal);
|
||||
|
||||
TriggerChangeEvent<string>(config, field.Name, stringVal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class BarTextureAttribute : ConfigAttribute
|
||||
{
|
||||
public BarTextureAttribute(string friendlyName = "Bar Texture") : base(friendlyName) { }
|
||||
|
||||
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||
{
|
||||
if (BarTexturesManager.Instance == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
List<string> textures = BarTexturesManager.Instance.BarTextureNames.ToList();
|
||||
string? stringVal = (string?)field.GetValue(config);
|
||||
|
||||
int index = 0;
|
||||
if (stringVal != null && stringVal.Length > 0 && textures.Contains(stringVal))
|
||||
{
|
||||
index = textures.IndexOf(stringVal);
|
||||
}
|
||||
|
||||
string[] options = textures.ToArray();
|
||||
|
||||
if (ImGui.Combo(friendlyName + IDText(ID), ref index, options, 10))
|
||||
{
|
||||
stringVal = options[index];
|
||||
field.SetValue(config, stringVal);
|
||||
|
||||
TriggerChangeEvent<string>(config, field.Name, stringVal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class AnchorAttribute : ComboAttribute
|
||||
{
|
||||
public AnchorAttribute(string friendlyName)
|
||||
: base(friendlyName, new string[] { "Center", "Left", "Right", "Top", "TopLeft", "TopRight", "Bottom", "BottomLeft", "BottomRight" })
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class BarTextureDrawModeAttribute : ComboAttribute
|
||||
{
|
||||
public BarTextureDrawModeAttribute(string friendlyName)
|
||||
: base(friendlyName, new string[] { "Stretch", "Repeat Horizontal", "Repeat Vertical", "Repeat" })
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class StrataLevelAttribute : ConfigAttribute
|
||||
{
|
||||
private string[] options = { "Lowest", "Low", "Mid-Low", "Mid", "Mid-High", "High", "Highest" };
|
||||
|
||||
public StrataLevelAttribute(string friendlyName) : base(friendlyName)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||
{
|
||||
object? fieldVal = field.GetValue(config);
|
||||
|
||||
int intVal = 0;
|
||||
if (fieldVal != null)
|
||||
{
|
||||
intVal = (int)fieldVal;
|
||||
}
|
||||
|
||||
if (ImGui.Combo(friendlyName + IDText(ID), ref intVal, options, 4))
|
||||
{
|
||||
field.SetValue(config, (StrataLevel?)intVal);
|
||||
|
||||
TriggerChangeEvent<int>(config, field.Name, intVal);
|
||||
ConfigurationManager.Instance?.OnStrataLevelChanged(config);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region field ordering attributes
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class OrderAttribute : Attribute
|
||||
{
|
||||
public int pos;
|
||||
public string? collapseWith = "Enabled";
|
||||
|
||||
public OrderAttribute(int pos)
|
||||
{
|
||||
this.pos = pos;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class NestedConfigAttribute : OrderAttribute
|
||||
{
|
||||
public string friendlyName;
|
||||
public bool separator = false;
|
||||
public bool spacing = true;
|
||||
public bool nest = true;
|
||||
public bool collapsingHeader = true;
|
||||
|
||||
public NestedConfigAttribute(string friendlyName, int pos) : base(pos)
|
||||
{
|
||||
this.friendlyName = friendlyName;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
|
||||
namespace HSUI.Config.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class SectionAttribute : Attribute
|
||||
{
|
||||
public string SectionName;
|
||||
public bool ForceAllowExport;
|
||||
|
||||
public SectionAttribute(string name, bool forceAllowExport = false)
|
||||
{
|
||||
SectionName = name;
|
||||
ForceAllowExport = forceAllowExport;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
public class SubSectionAttribute : Attribute
|
||||
{
|
||||
public string SubSectionName;
|
||||
public int Depth;
|
||||
|
||||
public SubSectionAttribute(string subSectionName, int depth)
|
||||
{
|
||||
SubSectionName = subSectionName;
|
||||
Depth = depth;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,703 @@
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Logging;
|
||||
using HSUI.Config.Profiles;
|
||||
using HSUI.Config.Tree;
|
||||
using HSUI.Config.Windows;
|
||||
using HSUI.Helpers;
|
||||
using HSUI.Interface;
|
||||
using HSUI.Interface.EnemyList;
|
||||
using HSUI.Interface.GeneralElements;
|
||||
using HSUI.Interface.Jobs;
|
||||
using HSUI.Interface.Party;
|
||||
using HSUI.Interface.PartyCooldowns;
|
||||
using HSUI.Interface.StatusEffects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace HSUI.Config
|
||||
{
|
||||
public delegate void ConfigurationManagerEventHandler(ConfigurationManager configurationManager);
|
||||
public delegate void StrataLevelsEventHandler(ConfigurationManager configurationManager, PluginConfigObject config);
|
||||
public delegate void GlobalVisibilityEventHandler(ConfigurationManager configurationManager, VisibilityConfig config);
|
||||
|
||||
public class ConfigurationManager : IDisposable
|
||||
{
|
||||
public static ConfigurationManager Instance { get; private set; } = null!;
|
||||
|
||||
private BaseNode _configBaseNode;
|
||||
private Dictionary<string, BaseNode> _configBaseNodeByProfile;
|
||||
public BaseNode ConfigBaseNode
|
||||
{
|
||||
get => _configBaseNode;
|
||||
set
|
||||
{
|
||||
_configBaseNode = value;
|
||||
_mainConfigWindow.node = value;
|
||||
}
|
||||
}
|
||||
|
||||
private WindowSystem _windowSystem;
|
||||
private MainConfigWindow _mainConfigWindow;
|
||||
private ChangelogWindow _changelogWindow;
|
||||
private GridWindow _gridWindow;
|
||||
|
||||
public bool IsConfigWindowOpened => _mainConfigWindow.IsOpen;
|
||||
public bool IsChangelogWindowOpened => _changelogWindow.IsOpen;
|
||||
public bool ShowingModalWindow = false;
|
||||
|
||||
public GradientDirection GradientDirection
|
||||
{
|
||||
get
|
||||
{
|
||||
var config = Instance.GetConfigObject<MiscColorConfig>();
|
||||
return config != null ? config.GradientDirection : GradientDirection.None;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OverrideDalamudStyle
|
||||
{
|
||||
get
|
||||
{
|
||||
HUDOptionsConfig config = Instance.GetConfigObject<HUDOptionsConfig>();
|
||||
return config != null ? config.OverrideDalamudStyle : true;
|
||||
}
|
||||
}
|
||||
|
||||
public CultureInfo ActiveCultreInfo
|
||||
{
|
||||
get {
|
||||
HUDOptionsConfig config = Instance.GetConfigObject<HUDOptionsConfig>();
|
||||
return config == null || config.UseRegionalNumberFormats ? CultureInfo.CurrentCulture : CultureInfo.InvariantCulture;
|
||||
}
|
||||
}
|
||||
|
||||
public string ConfigDirectory;
|
||||
|
||||
public string CurrentVersion => Plugin.Version;
|
||||
public string? PreviousVersion { get; private set; } = null;
|
||||
|
||||
private bool _needsProfileUpdate = false;
|
||||
private bool _lockHUD = true;
|
||||
|
||||
public bool LockHUD
|
||||
{
|
||||
get => _lockHUD;
|
||||
set
|
||||
{
|
||||
if (_lockHUD == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lockHUD = value;
|
||||
_mainConfigWindow.IsOpen = value;
|
||||
_gridWindow.IsOpen = !value;
|
||||
|
||||
LockEvent?.Invoke(this);
|
||||
|
||||
if (_lockHUD)
|
||||
{
|
||||
ConfigBaseNode.NeedsSave = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowHUD = true;
|
||||
|
||||
public event ConfigurationManagerEventHandler? ResetEvent;
|
||||
public event ConfigurationManagerEventHandler? LockEvent;
|
||||
public event ConfigurationManagerEventHandler? ConfigClosedEvent;
|
||||
public event StrataLevelsEventHandler? StrataLevelsChangedEvent;
|
||||
public event GlobalVisibilityEventHandler? GlobalVisibilityEvent;
|
||||
|
||||
public ConfigurationManager()
|
||||
{
|
||||
ConfigDirectory = Plugin.PluginInterface.GetPluginConfigDirectory();
|
||||
|
||||
_configBaseNodeByProfile = new Dictionary<string, BaseNode>();
|
||||
_configBaseNode = new BaseNode();
|
||||
InitializeBaseNode(_configBaseNode);
|
||||
_configBaseNode.ConfigObjectResetEvent += OnConfigObjectReset;
|
||||
|
||||
_mainConfigWindow = new MainConfigWindow("HSUI Settings");
|
||||
_mainConfigWindow.node = _configBaseNode;
|
||||
_mainConfigWindow.CloseAction = () =>
|
||||
{
|
||||
ConfigClosedEvent?.Invoke(this);
|
||||
|
||||
if (ConfigBaseNode.NeedsSave)
|
||||
{
|
||||
SaveConfigurations();
|
||||
}
|
||||
|
||||
if (_needsProfileUpdate)
|
||||
{
|
||||
UpdateCurrentProfile();
|
||||
_needsProfileUpdate = false;
|
||||
}
|
||||
};
|
||||
|
||||
string changelog = LoadChangelog();
|
||||
_changelogWindow = new ChangelogWindow("HSUI Changelog v" + Plugin.Version, changelog);
|
||||
_gridWindow = new GridWindow("Grid ##HSUI");
|
||||
|
||||
_windowSystem = new WindowSystem("HSUI_Windows");
|
||||
_windowSystem.AddWindow(_mainConfigWindow);
|
||||
_windowSystem.AddWindow(_changelogWindow);
|
||||
_windowSystem.AddWindow(_gridWindow);
|
||||
|
||||
CheckVersion();
|
||||
|
||||
Plugin.ClientState.Logout += OnLogout;
|
||||
Plugin.JobChangedEvent += OnJobChanged;
|
||||
|
||||
_configBaseNode.CreateNodesIfNeeded();
|
||||
}
|
||||
|
||||
~ConfigurationManager()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigBaseNode.ConfigObjectResetEvent -= OnConfigObjectReset;
|
||||
Plugin.ClientState.Logout -= OnLogout;
|
||||
Plugin.JobChangedEvent -= OnJobChanged;
|
||||
|
||||
Instance = null!;
|
||||
}
|
||||
|
||||
public static void Initialize() { Instance = new ConfigurationManager(); }
|
||||
|
||||
private void OnConfigObjectReset(BaseNode sender)
|
||||
{
|
||||
ResetEvent?.Invoke(this);
|
||||
}
|
||||
|
||||
private void OnLogout(int type, int code)
|
||||
{
|
||||
SaveConfigurations();
|
||||
ProfilesManager.Instance?.SaveCurrentProfile();
|
||||
}
|
||||
|
||||
private void OnJobChanged(uint jobId)
|
||||
{
|
||||
UpdateCurrentProfile();
|
||||
}
|
||||
|
||||
private string LoadChangelog()
|
||||
{
|
||||
string path = Path.Combine(Plugin.AssemblyLocation, "changelog.md");
|
||||
|
||||
try
|
||||
{
|
||||
string fullChangelog = File.ReadAllText(path);
|
||||
string versionChangelog = fullChangelog.Split("#", StringSplitOptions.RemoveEmptyEntries)[0];
|
||||
return versionChangelog.Replace(Plugin.Version, "");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.Logger.Error("Error loading changelog: " + e.Message);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private void CheckVersion()
|
||||
{
|
||||
string path = Path.Combine(ConfigDirectory, "version");
|
||||
|
||||
bool needsBackup = false;
|
||||
try
|
||||
{
|
||||
bool needsWrite = false;
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
needsWrite = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
PreviousVersion = File.ReadAllText(path);
|
||||
if (PreviousVersion != Plugin.Version)
|
||||
{
|
||||
needsWrite = true;
|
||||
needsBackup = true;
|
||||
}
|
||||
}
|
||||
|
||||
_changelogWindow.IsOpen = needsWrite;
|
||||
_changelogWindow.AutoClose = true;
|
||||
|
||||
if (needsWrite)
|
||||
{
|
||||
File.WriteAllText(path, Plugin.Version);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.Logger.Error("Error checking version: " + e.Message);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (needsBackup && PreviousVersion != null)
|
||||
{
|
||||
BackupFiles(PreviousVersion);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.Logger.Error("Error making backup: " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void BackupFiles(string version)
|
||||
{
|
||||
string backupsRoot = Path.Combine(ConfigDirectory, "Backups");
|
||||
if (!Directory.Exists(backupsRoot))
|
||||
{
|
||||
Directory.CreateDirectory(backupsRoot);
|
||||
}
|
||||
|
||||
string backupPath = Path.Combine(backupsRoot, version);
|
||||
|
||||
foreach (string folderPath in Directory.GetDirectories(ConfigDirectory, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (folderPath.Contains("Backups")) { continue; }
|
||||
|
||||
Directory.CreateDirectory(folderPath.Replace(ConfigDirectory, backupPath));
|
||||
}
|
||||
|
||||
foreach (string filePath in Directory.GetFiles(ConfigDirectory, "*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
File.Copy(filePath, filePath.Replace(ConfigDirectory, backupPath), true);
|
||||
}
|
||||
}
|
||||
|
||||
#region strata
|
||||
public void OnStrataLevelChanged(PluginConfigObject config)
|
||||
{
|
||||
StrataLevelsChangedEvent?.Invoke(this, config);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region visibility
|
||||
public void OnGlobalVisibilityChanged(VisibilityConfig config)
|
||||
{
|
||||
GlobalVisibilityEvent?.Invoke(this, config);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region windows
|
||||
public void ToggleConfigWindow()
|
||||
{
|
||||
_mainConfigWindow.Toggle();
|
||||
}
|
||||
|
||||
public void OpenConfigWindow()
|
||||
{
|
||||
_mainConfigWindow.IsOpen = true;
|
||||
}
|
||||
|
||||
public void CloseConfigWindow()
|
||||
{
|
||||
_mainConfigWindow.IsOpen = false;
|
||||
}
|
||||
|
||||
public void OpenChangelogWindow()
|
||||
{
|
||||
_changelogWindow.IsOpen = true;
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
_windowSystem.Draw();
|
||||
}
|
||||
|
||||
public void AddExtraSectionNode(SectionNode node)
|
||||
{
|
||||
ConfigBaseNode.AddExtraSectionNode(node);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region config getters and setters
|
||||
public PluginConfigObject GetConfigObjectForType(Type type)
|
||||
{
|
||||
MethodInfo? genericMethod = GetType().GetMethod("GetConfigObject");
|
||||
MethodInfo? method = genericMethod?.MakeGenericMethod(type);
|
||||
return (PluginConfigObject)method?.Invoke(this, null)!;
|
||||
}
|
||||
public T GetConfigObject<T>() where T : PluginConfigObject => ConfigBaseNode.GetConfigObject<T>()!;
|
||||
|
||||
public static PluginConfigObject GetDefaultConfigObjectForType(Type type)
|
||||
{
|
||||
MethodInfo? method = type.GetMethod("DefaultConfig", BindingFlags.Public | BindingFlags.Static);
|
||||
return (PluginConfigObject)method?.Invoke(null, null)!;
|
||||
}
|
||||
|
||||
public ConfigPageNode GetConfigPageNode<T>() where T : PluginConfigObject => ConfigBaseNode.GetConfigPageNode<T>()!;
|
||||
|
||||
public void SetConfigObject(PluginConfigObject configObject) => ConfigBaseNode.SetConfigObject(configObject);
|
||||
|
||||
public List<T> GetObjects<T>() => ConfigBaseNode.GetObjects<T>();
|
||||
#endregion
|
||||
|
||||
#region load / save / profiles
|
||||
public bool IsFreshInstall()
|
||||
{
|
||||
return Directory.GetDirectories(ConfigDirectory).Length == 0;
|
||||
}
|
||||
|
||||
public void LoadOrInitializeFiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsFreshInstall())
|
||||
{
|
||||
LoadConfigurations();
|
||||
|
||||
// gotta save after initial load store possible version update changes right away
|
||||
SaveConfigurations(true);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.Logger.Error("Error initializing configurations: " + e.Message);
|
||||
|
||||
if (e.StackTrace != null)
|
||||
{
|
||||
Plugin.Logger.Error(e.StackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ForceNeedsSave()
|
||||
{
|
||||
ConfigBaseNode.NeedsSave = true;
|
||||
}
|
||||
|
||||
public void LoadConfigurations()
|
||||
{
|
||||
PerformV2Migration();
|
||||
ConfigBaseNode.Load(ConfigDirectory);
|
||||
}
|
||||
|
||||
public void SaveConfigurations(bool forced = false)
|
||||
{
|
||||
if (!forced && !ConfigBaseNode.NeedsSave)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigBaseNode.Save(ConfigDirectory);
|
||||
|
||||
ProfilesManager.Instance?.SaveCurrentProfile();
|
||||
|
||||
ConfigBaseNode.NeedsSave = false;
|
||||
}
|
||||
|
||||
public void PerformV2Migration()
|
||||
{
|
||||
// create necessary folders
|
||||
string[] newFolders = new string[] { "Other Elements", "Customization" };
|
||||
foreach (string folder in newFolders)
|
||||
{
|
||||
string path = Path.Combine(ConfigDirectory, folder);
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
}
|
||||
|
||||
// move files
|
||||
Dictionary<string, string> files = new Dictionary<string, string>()
|
||||
{
|
||||
["Misc\\Experience Bar.json"] = "Other Elements\\Experience Bar.json",
|
||||
["Misc\\GCD Indicator.json"] = "Other Elements\\GCD Indicator.json",
|
||||
["Misc\\Limit Break.json"] = "Other Elements\\Limit Break.json",
|
||||
["Misc\\MP Ticker.json"] = "Other Elements\\MP Ticker.json",
|
||||
["Misc\\Pull Timer.json"] = "Other Elements\\Pull Timer.json",
|
||||
["Misc\\Fonts.json"] = "Customization\\Fonts.json"
|
||||
};
|
||||
|
||||
foreach (string key in files.Keys)
|
||||
{
|
||||
string v1Path = Path.Combine(ConfigDirectory, key);
|
||||
string v2Path = Path.Combine(ConfigDirectory, files[key]);
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(v1Path) && !File.Exists(v2Path))
|
||||
{
|
||||
File.Move(v1Path, v2Path);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.Logger.Error("Error migrating file \"" + v1Path + "\" to v2 config structure: " + e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateCurrentProfile()
|
||||
{
|
||||
// dont update the profile on job change when the config window is opened
|
||||
if (_mainConfigWindow.IsOpen)
|
||||
{
|
||||
_needsProfileUpdate = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ProfilesManager.Instance?.UpdateCurrentProfile();
|
||||
}
|
||||
|
||||
public string? ExportCurrentConfigs()
|
||||
{
|
||||
return ConfigBaseNode.GetBase64String();
|
||||
}
|
||||
|
||||
public void OnProfileDeleted(string profileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
_configBaseNodeByProfile.Remove(profileName);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public bool ImportProfile(string oldProfileName, string profileName, string rawString, bool forceLoad = false)
|
||||
{
|
||||
// cache old profile
|
||||
_configBaseNodeByProfile[oldProfileName] = ConfigBaseNode;
|
||||
|
||||
// load profile from cache or from rawString
|
||||
BaseNode? loadedNode = null;
|
||||
if (forceLoad || !_configBaseNodeByProfile.TryGetValue(profileName, out loadedNode))
|
||||
{
|
||||
ImportProfileNonCached(rawString, out loadedNode);
|
||||
}
|
||||
|
||||
if (loadedNode == null) { return false; }
|
||||
|
||||
if (IsConfigWindowOpened || string.IsNullOrEmpty(loadedNode.SelectedOptionName))
|
||||
{
|
||||
loadedNode.SelectedOptionName = ConfigBaseNode.SelectedOptionName;
|
||||
loadedNode.RefreshSelectedNode();
|
||||
}
|
||||
|
||||
ConfigBaseNode.ConfigObjectResetEvent -= OnConfigObjectReset;
|
||||
ConfigBaseNode = loadedNode;
|
||||
ConfigBaseNode.ConfigObjectResetEvent += OnConfigObjectReset;
|
||||
|
||||
PerformV2Migration();
|
||||
|
||||
ResetEvent?.Invoke(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ImportProfileNonCached(string rawString, out BaseNode? node)
|
||||
{
|
||||
List<string> importStrings = new List<string>(rawString.Trim().Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries));
|
||||
ImportData[] imports = importStrings.Select(str => new ImportData(str)).ToArray();
|
||||
|
||||
node = new BaseNode();
|
||||
InitializeBaseNode(node);
|
||||
|
||||
Dictionary<Type, PluginConfigObject> oldConfigObjects = new Dictionary<Type, PluginConfigObject>();
|
||||
|
||||
foreach (ImportData importData in imports)
|
||||
{
|
||||
PluginConfigObject? config = importData.GetObject();
|
||||
if (config == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!node.SetConfigObject(config))
|
||||
{
|
||||
oldConfigObjects.Add(config.GetType(), config);
|
||||
}
|
||||
}
|
||||
|
||||
if (ProfilesManager.Instance != null)
|
||||
{
|
||||
node.AddExtraSectionNode(ProfilesManager.Instance.ProfilesNode);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region initialization
|
||||
private static void InitializeBaseNode(BaseNode node)
|
||||
{
|
||||
// creates node tree in the right order...
|
||||
foreach (Type type in ConfigObjectTypes)
|
||||
{
|
||||
var genericMethod = node.GetType().GetMethod("GetConfigPageNode");
|
||||
var method = genericMethod?.MakeGenericMethod(type);
|
||||
method?.Invoke(node, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static Type[] ConfigObjectTypes = new Type[]
|
||||
{
|
||||
// Unit Frames
|
||||
typeof(PlayerUnitFrameConfig),
|
||||
typeof(TargetUnitFrameConfig),
|
||||
typeof(TargetOfTargetUnitFrameConfig),
|
||||
typeof(FocusTargetUnitFrameConfig),
|
||||
|
||||
// Mana Bars
|
||||
typeof(PlayerPrimaryResourceConfig),
|
||||
typeof(TargetPrimaryResourceConfig),
|
||||
typeof(TargetOfTargetPrimaryResourceConfig),
|
||||
typeof(FocusTargetPrimaryResourceConfig),
|
||||
|
||||
// Castbars
|
||||
typeof(PlayerCastbarConfig),
|
||||
typeof(TargetCastbarConfig),
|
||||
typeof(TargetOfTargetCastbarConfig),
|
||||
typeof(FocusTargetCastbarConfig),
|
||||
|
||||
// Buffs and Debuffs
|
||||
typeof(PlayerBuffsListConfig),
|
||||
typeof(PlayerDebuffsListConfig),
|
||||
typeof(TargetBuffsListConfig),
|
||||
typeof(TargetDebuffsListConfig),
|
||||
typeof(FocusTargetBuffsListConfig),
|
||||
typeof(FocusTargetDebuffsListConfig),
|
||||
typeof(CustomEffectsListConfig),
|
||||
|
||||
// Nameplates
|
||||
typeof(NameplatesGeneralConfig),
|
||||
typeof(PlayerNameplateConfig),
|
||||
typeof(EnemyNameplateConfig),
|
||||
typeof(PartyMembersNameplateConfig),
|
||||
typeof(AllianceMembersNameplateConfig),
|
||||
typeof(FriendPlayerNameplateConfig),
|
||||
typeof(OtherPlayerNameplateConfig),
|
||||
typeof(PetNameplateConfig),
|
||||
typeof(NPCNameplateConfig),
|
||||
typeof(MinionNPCNameplateConfig),
|
||||
typeof(ObjectsNameplateConfig),
|
||||
|
||||
// Party Frames
|
||||
typeof(PartyFramesConfig),
|
||||
typeof(PartyFramesHealthBarsConfig),
|
||||
typeof(PartyFramesManaBarConfig),
|
||||
typeof(PartyFramesCastbarConfig),
|
||||
typeof(PartyFramesIconsConfig),
|
||||
typeof(PartyFramesBuffsConfig),
|
||||
typeof(PartyFramesDebuffsConfig),
|
||||
typeof(PartyFramesTrackersConfig),
|
||||
typeof(PartyFramesCooldownListConfig),
|
||||
|
||||
// Party Cooldowns
|
||||
typeof(PartyCooldownsConfig),
|
||||
typeof(PartyCooldownsBarConfig),
|
||||
typeof(PartyCooldownsDataConfig),
|
||||
|
||||
// Enemy List
|
||||
typeof(EnemyListConfig),
|
||||
typeof(EnemyListHealthBarConfig),
|
||||
typeof(EnemyListEnmityIconConfig),
|
||||
typeof(EnemyListSignIconConfig),
|
||||
typeof(EnemyListCastbarConfig),
|
||||
typeof(EnemyListBuffsConfig),
|
||||
typeof(EnemyListDebuffsConfig),
|
||||
|
||||
// Job Specific Bars
|
||||
typeof(PaladinConfig),
|
||||
typeof(WarriorConfig),
|
||||
typeof(DarkKnightConfig),
|
||||
typeof(GunbreakerConfig),
|
||||
|
||||
typeof(WhiteMageConfig),
|
||||
typeof(ScholarConfig),
|
||||
typeof(AstrologianConfig),
|
||||
typeof(SageConfig),
|
||||
|
||||
typeof(MonkConfig),
|
||||
typeof(DragoonConfig),
|
||||
typeof(NinjaConfig),
|
||||
typeof(SamuraiConfig),
|
||||
typeof(ReaperConfig),
|
||||
typeof(ViperConfig),
|
||||
|
||||
typeof(BardConfig),
|
||||
typeof(MachinistConfig),
|
||||
typeof(DancerConfig),
|
||||
|
||||
typeof(BlackMageConfig),
|
||||
typeof(SummonerConfig),
|
||||
typeof(RedMageConfig),
|
||||
typeof(BlueMageConfig),
|
||||
typeof(PictomancerConfig),
|
||||
|
||||
// Other Elements
|
||||
typeof(ExperienceBarConfig),
|
||||
typeof(GCDIndicatorConfig),
|
||||
typeof(HotbarsConfig),
|
||||
typeof(Hotbar1BarConfig),
|
||||
typeof(Hotbar2BarConfig),
|
||||
typeof(Hotbar3BarConfig),
|
||||
typeof(Hotbar4BarConfig),
|
||||
typeof(Hotbar5BarConfig),
|
||||
typeof(Hotbar6BarConfig),
|
||||
typeof(Hotbar7BarConfig),
|
||||
typeof(Hotbar8BarConfig),
|
||||
typeof(Hotbar9BarConfig),
|
||||
typeof(Hotbar10BarConfig),
|
||||
typeof(PullTimerConfig),
|
||||
typeof(LimitBreakConfig),
|
||||
typeof(MPTickerConfig),
|
||||
|
||||
// Colors
|
||||
typeof(TanksColorConfig),
|
||||
typeof(HealersColorConfig),
|
||||
typeof(MeleeColorConfig),
|
||||
typeof(RangedColorConfig),
|
||||
typeof(CastersColorConfig),
|
||||
typeof(RolesColorConfig),
|
||||
typeof(MiscColorConfig),
|
||||
|
||||
// Customization
|
||||
typeof(FontsConfig),
|
||||
typeof(BarTexturesConfig),
|
||||
|
||||
// Visibility
|
||||
typeof(GlobalVisibilityConfig),
|
||||
typeof(HotbarsVisibilityConfig),
|
||||
|
||||
// Misc
|
||||
typeof(HUDOptionsConfig),
|
||||
typeof(WindowClippingConfig),
|
||||
typeof(TooltipsConfig),
|
||||
typeof(GridConfig),
|
||||
|
||||
// Import
|
||||
typeof(ImportConfig)
|
||||
};
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
using HSUI.Config;
|
||||
using HSUI.Config.Attributes;
|
||||
using HSUI.Config.Profiles;
|
||||
using HSUI.Helpers;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace HSUI.Interface
|
||||
{
|
||||
[Disableable(false)]
|
||||
[Exportable(false)]
|
||||
[Shareable(false)]
|
||||
[Resettable(false)]
|
||||
[Section("Import")]
|
||||
[SubSection("General", 0)]
|
||||
public class ImportConfig : PluginConfigObject
|
||||
{
|
||||
private string _importString = "";
|
||||
private bool _importing = false;
|
||||
private string? _errorMessage = null;
|
||||
|
||||
private List<ImportData>? _importDataList = null;
|
||||
private List<bool>? _importDataEnabled = null;
|
||||
|
||||
public new static ImportConfig DefaultConfig() { return new ImportConfig(); }
|
||||
|
||||
[ManualDraw]
|
||||
public bool Draw(ref bool changed)
|
||||
{
|
||||
ImGui.Text("Import string:");
|
||||
|
||||
ImGui.InputText("", ref _importString, 999999);
|
||||
|
||||
ImGui.Text("Here you can import specific parts of a profile.\nIf the string contains more than one part you will be able to select which parts you wish to import.");
|
||||
|
||||
if (ImGui.Button("Import", new Vector2(560, 24)))
|
||||
{
|
||||
_importing = _importString.Length > 0;
|
||||
}
|
||||
|
||||
ImGuiHelper.DrawSeparator(1, 1);
|
||||
ImGui.Text("To browse presets made by users of the HSUI community join our Discord and find the #profiles channel.");
|
||||
|
||||
if (ImGui.Button("HSUI Discord", new Vector2(560, 24)))
|
||||
{
|
||||
Utils.OpenUrl("https://discord.gg/xzde5qQayh");
|
||||
}
|
||||
|
||||
// error modal
|
||||
if (_errorMessage != null)
|
||||
{
|
||||
if (ImGuiHelper.DrawErrorModal(_errorMessage))
|
||||
{
|
||||
_importing = false;
|
||||
_errorMessage = null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// parse import string
|
||||
if (_importing && _importDataList == null)
|
||||
{
|
||||
_errorMessage = Parse();
|
||||
}
|
||||
|
||||
// confirmation modal
|
||||
if (_importDataList != null && _importDataList.Count > 0)
|
||||
{
|
||||
var (didConfirm, didClose) = DrawImportConfirmationModal();
|
||||
|
||||
if (didConfirm)
|
||||
{
|
||||
_errorMessage = Import();
|
||||
|
||||
if (_errorMessage == null)
|
||||
{
|
||||
_importString = "";
|
||||
}
|
||||
}
|
||||
|
||||
if (didConfirm || didClose)
|
||||
{
|
||||
_importing = false;
|
||||
_importDataList = null;
|
||||
_importDataEnabled = null;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return didConfirm && _errorMessage == null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string? Import()
|
||||
{
|
||||
if (_importDataList == null || _importDataEnabled == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<PluginConfigObject> configObjects = new List<PluginConfigObject>(_importDataList.Count);
|
||||
|
||||
for (int i = 0; i < _importDataList.Count; i++)
|
||||
{
|
||||
if (i >= _importDataEnabled.Count || _importDataEnabled[i] == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ImportData importData = _importDataList[i];
|
||||
PluginConfigObject? config = importData.GetObject();
|
||||
if (config == null)
|
||||
{
|
||||
return "Couldn't import \"" + importData.Name + "\"";
|
||||
}
|
||||
|
||||
configObjects.Add(config);
|
||||
}
|
||||
|
||||
foreach (PluginConfigObject config in configObjects)
|
||||
{
|
||||
ConfigurationManager.Instance.SetConfigObject(config);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? Parse()
|
||||
{
|
||||
string[] importStrings = _importString.Trim().Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (importStrings.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_importDataList = new List<ImportData>(importStrings.Length);
|
||||
_importDataEnabled = new List<bool>(importStrings.Length);
|
||||
|
||||
foreach (var str in importStrings)
|
||||
{
|
||||
try
|
||||
{
|
||||
ImportData importData = new ImportData(str);
|
||||
_importDataList.Add(importData);
|
||||
_importDataEnabled.Add(true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_importDataList = null;
|
||||
_importDataEnabled = null;
|
||||
|
||||
return e is ArgumentException ? e.Message : "Invalid import string!";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public (bool, bool) DrawImportConfirmationModal()
|
||||
{
|
||||
if (_importDataList == null || _importDataEnabled == null)
|
||||
{
|
||||
return (false, true);
|
||||
}
|
||||
|
||||
ConfigurationManager.Instance.ShowingModalWindow = true;
|
||||
|
||||
bool didConfirm = false;
|
||||
bool didClose = false;
|
||||
|
||||
ImGui.OpenPopup("Import ##HSUI");
|
||||
|
||||
Vector2 center = ImGui.GetMainViewport().GetCenter();
|
||||
ImGui.SetNextWindowPos(center, ImGuiCond.Appearing, new Vector2(0.5f, 0.5f));
|
||||
|
||||
bool p_open = true; // i've no idea what this is used for
|
||||
|
||||
if (ImGui.BeginPopupModal("Import ##HSUI", ref p_open, ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoMove))
|
||||
{
|
||||
float width = 300;
|
||||
|
||||
ImGui.Text("Select which parts to import:");
|
||||
|
||||
ImGui.NewLine();
|
||||
if (ImGui.Button("Select All", new Vector2(width / 2f - 5, 24)))
|
||||
{
|
||||
for (int i = 0; i < _importDataEnabled.Count; i++)
|
||||
{
|
||||
_importDataEnabled[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Deselect All", new Vector2(width / 2f - 5, 24)))
|
||||
{
|
||||
for (int i = 0; i < _importDataEnabled.Count; i++)
|
||||
{
|
||||
_importDataEnabled[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.NewLine();
|
||||
float height = Math.Min(30 * _importDataList.Count, 400);
|
||||
|
||||
ImGui.BeginChild("import checkboxes", new Vector2(width, height), false);
|
||||
|
||||
for (int i = 0; i < _importDataList.Count; i++)
|
||||
{
|
||||
bool value = _importDataEnabled[i];
|
||||
if (ImGui.Checkbox(_importDataList[i].Name, ref value))
|
||||
{
|
||||
_importDataEnabled[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
|
||||
ImGui.NewLine();
|
||||
if (ImGui.Button("OK", new Vector2(width / 2f - 5, 24)))
|
||||
{
|
||||
ImGui.CloseCurrentPopup();
|
||||
didConfirm = true;
|
||||
didClose = true;
|
||||
}
|
||||
|
||||
ImGui.SetItemDefaultFocus();
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Cancel", new Vector2(width / 2f - 5, 24)))
|
||||
{
|
||||
ImGui.CloseCurrentPopup();
|
||||
didClose = true;
|
||||
}
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
// close button on nav
|
||||
else
|
||||
{
|
||||
didClose = true;
|
||||
}
|
||||
|
||||
if (didClose)
|
||||
{
|
||||
ConfigurationManager.Instance.ShowingModalWindow = false;
|
||||
}
|
||||
|
||||
return (didConfirm, didClose);
|
||||
}
|
||||
}
|
||||
|
||||
public class ImportData
|
||||
{
|
||||
public readonly Type ConfigType;
|
||||
public readonly string Name;
|
||||
|
||||
public readonly string ImportString;
|
||||
public readonly string JsonString;
|
||||
|
||||
public ImportData(string base64String)
|
||||
{
|
||||
ImportString = base64String;
|
||||
JsonString = ImportExportHelper.Base64DecodeAndDecompress(base64String);
|
||||
|
||||
string? typeString = (string?)JObject.Parse(JsonString)["$type"];
|
||||
if (typeString == null)
|
||||
{
|
||||
throw new ArgumentException("Invalid type");
|
||||
}
|
||||
|
||||
// Migrate DelvUI type names to HSUI for profile/import compatibility
|
||||
if (typeString.Contains("DelvUI"))
|
||||
{
|
||||
typeString = typeString.Replace("DelvUI.", "HSUI.").Replace(", DelvUI", ", HSUI");
|
||||
}
|
||||
|
||||
Type? type = Type.GetType(typeString);
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentException("Invalid type: \"" + typeString + "\"");
|
||||
}
|
||||
|
||||
ConfigType = type;
|
||||
Name = Utils.UserFriendlyConfigName(type.Name);
|
||||
}
|
||||
|
||||
public PluginConfigObject? GetObject()
|
||||
{
|
||||
MethodInfo? methodInfo = typeof(PluginConfigObject).GetMethod("LoadFromJsonString", BindingFlags.Public | BindingFlags.Static);
|
||||
MethodInfo? function = methodInfo?.MakeGenericMethod(ConfigType);
|
||||
return (PluginConfigObject?)function?.Invoke(this, new object[] { JsonString })!;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
|
||||
namespace HSUI.Config
|
||||
{
|
||||
public delegate void ConfigValueChangeEventHandler(PluginConfigObject sender, OnChangeBaseArgs args);
|
||||
|
||||
public enum ChangeType
|
||||
{
|
||||
None = 0,
|
||||
ListAdd = 1,
|
||||
ListRemove = 2
|
||||
}
|
||||
|
||||
public class OnChangeBaseArgs : EventArgs
|
||||
{
|
||||
public string PropertyName { get; }
|
||||
public ChangeType ChangeType { get; private set; }
|
||||
|
||||
public OnChangeBaseArgs(string keyName, ChangeType type = ChangeType.None)
|
||||
{
|
||||
PropertyName = keyName;
|
||||
ChangeType = type;
|
||||
}
|
||||
}
|
||||
|
||||
public class OnChangeEventArgs<T> : OnChangeBaseArgs
|
||||
{
|
||||
public T Value { get; }
|
||||
|
||||
public OnChangeEventArgs(string keyName, T value, ChangeType type = ChangeType.None) : base(keyName, type)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IOnChangeEventArgs
|
||||
{
|
||||
public abstract event ConfigValueChangeEventHandler? ValueChangeEvent;
|
||||
|
||||
public abstract void OnValueChanged(OnChangeBaseArgs e);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
using HSUI.Config.Attributes;
|
||||
using HSUI.Enums;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace HSUI.Config
|
||||
{
|
||||
public abstract class PluginConfigObject : IOnChangeEventArgs
|
||||
{
|
||||
public string Version => Plugin.Version;
|
||||
|
||||
[Checkbox("Enabled")]
|
||||
[Order(0, collapseWith = null)]
|
||||
public bool Enabled = true;
|
||||
|
||||
#region convenience properties
|
||||
[JsonIgnore]
|
||||
public bool Exportable
|
||||
{
|
||||
get
|
||||
{
|
||||
ExportableAttribute? attribute = (ExportableAttribute?)GetType().GetCustomAttribute(typeof(ExportableAttribute), false);
|
||||
return attribute == null || attribute.exportable;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool Shareable
|
||||
{
|
||||
get
|
||||
{
|
||||
ShareableAttribute? attribute = (ShareableAttribute?)GetType().GetCustomAttribute(typeof(ShareableAttribute), false);
|
||||
return attribute == null || attribute.shareable;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool Resettable
|
||||
{
|
||||
get
|
||||
{
|
||||
ResettableAttribute? attribute = (ResettableAttribute?)GetType().GetCustomAttribute(typeof(ResettableAttribute), false);
|
||||
return attribute == null || attribute.resettable;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool Disableable
|
||||
{
|
||||
get
|
||||
{
|
||||
DisableableAttribute? attribute = (DisableableAttribute?)GetType().GetCustomAttribute(typeof(DisableableAttribute), false);
|
||||
return attribute == null || attribute.disableable;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string[]? DisableParentSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
DisableParentSettingsAttribute? attribute = (DisableParentSettingsAttribute?)GetType().GetCustomAttribute(typeof(DisableParentSettingsAttribute), true);
|
||||
return attribute?.DisabledFields;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected bool ColorEdit4(string label, ref PluginConfigColor color)
|
||||
{
|
||||
var vector = color.Vector;
|
||||
|
||||
if (ImGui.ColorEdit4(label, ref vector))
|
||||
{
|
||||
color.Vector = vector;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static PluginConfigObject DefaultConfig()
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
public List<T> GetObjects<T>()
|
||||
{
|
||||
List<T> list = new List<T>();
|
||||
|
||||
Type type = typeof(T);
|
||||
if (this is T obj)
|
||||
{
|
||||
list.Add(obj);
|
||||
}
|
||||
|
||||
// iterate properties
|
||||
PropertyInfo[] properties = GetType().GetProperties();
|
||||
foreach (PropertyInfo property in properties)
|
||||
{
|
||||
object? value = property.GetValue(this);
|
||||
|
||||
if (value is T o)
|
||||
{
|
||||
list.Add(o);
|
||||
}
|
||||
else if (value is PluginConfigObject p)
|
||||
{
|
||||
list.AddRange(p.GetObjects<T>());
|
||||
}
|
||||
}
|
||||
|
||||
// iterate fields
|
||||
FieldInfo[] fields = GetType().GetFields();
|
||||
foreach (FieldInfo field in fields)
|
||||
{
|
||||
object? value = field.GetValue(this);
|
||||
|
||||
if (value is T o)
|
||||
{
|
||||
list.Add(o);
|
||||
}
|
||||
else if (value is PluginConfigObject p)
|
||||
{
|
||||
list.AddRange(p.GetObjects<T>());
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public T? Load<T>(FileInfo fileInfo) where T : PluginConfigObject
|
||||
{
|
||||
return LoadFromJson<T>(fileInfo.FullName);
|
||||
}
|
||||
|
||||
public static T? LoadFromJson<T>(string path) where T : PluginConfigObject
|
||||
{
|
||||
if (!File.Exists(path)) { return null; }
|
||||
|
||||
return LoadFromJsonString<T>(File.ReadAllText(path));
|
||||
}
|
||||
|
||||
public static T? LoadFromJsonString<T>(string jsonString) where T : PluginConfigObject
|
||||
{
|
||||
JsonSerializerSettings settings = new JsonSerializerSettings();
|
||||
settings.ContractResolver = new PluginConfigObjectsContractResolver();
|
||||
|
||||
return JsonConvert.DeserializeObject<T>(jsonString, settings);
|
||||
}
|
||||
|
||||
#region IOnChangeEventArgs
|
||||
|
||||
// sending event outside of the config
|
||||
public event ConfigValueChangeEventHandler? ValueChangeEvent;
|
||||
|
||||
// received events from the node
|
||||
public void OnValueChanged(OnChangeBaseArgs e)
|
||||
{
|
||||
ValueChangeEvent?.Invoke(this, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public abstract class MovablePluginConfigObject : PluginConfigObject
|
||||
{
|
||||
[JsonIgnore]
|
||||
public string ID;
|
||||
|
||||
[StrataLevel("Strata Level")]
|
||||
[Order(2)]
|
||||
public StrataLevel? Strata;
|
||||
|
||||
public StrataLevel StrataLevel => Strata ?? StrataLevel.LOWEST;
|
||||
|
||||
[DragInt2("Position", min = -4000, max = 4000)]
|
||||
[Order(5)]
|
||||
public Vector2 Position = Vector2.Zero;
|
||||
|
||||
public MovablePluginConfigObject()
|
||||
{
|
||||
ID = $"DelvUI_{GetType().Name}_{Guid.NewGuid()}";
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AnchorablePluginConfigObject : MovablePluginConfigObject
|
||||
{
|
||||
[DragInt2("Size", min = 1, max = 4000, isMonitored = true)]
|
||||
[Order(10)]
|
||||
public Vector2 Size;
|
||||
|
||||
[Anchor("Anchor")]
|
||||
[Order(15)]
|
||||
public DrawAnchor Anchor = DrawAnchor.Center;
|
||||
}
|
||||
|
||||
public class PluginConfigColor
|
||||
{
|
||||
[JsonIgnore] private float[] _colorMapRatios = { -.8f, -.3f, .1f };
|
||||
|
||||
[JsonIgnore] private Vector4 _vector;
|
||||
|
||||
public PluginConfigColor(Vector4 vector, float[]? colorMapRatios = null)
|
||||
{
|
||||
_vector = vector;
|
||||
|
||||
if (colorMapRatios != null && colorMapRatios.Length >= 3)
|
||||
{
|
||||
_colorMapRatios = colorMapRatios;
|
||||
}
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
public static PluginConfigColor FromHex(uint hexColor)
|
||||
{
|
||||
// ARGB to ABGR
|
||||
uint r = (hexColor >> 16) & 0xFF;
|
||||
uint b = hexColor & 0xFF;
|
||||
hexColor = (hexColor & 0xFF00FF00) | (b << 16) | r;
|
||||
|
||||
return new PluginConfigColor(ImGui.ColorConvertU32ToFloat4(hexColor));
|
||||
}
|
||||
|
||||
public PluginConfigColor WithAlpha(float alpha)
|
||||
{
|
||||
if (alpha == Vector.W) { return this; }
|
||||
|
||||
return new PluginConfigColor(Vector.WithNewAlpha(alpha));
|
||||
}
|
||||
|
||||
public static PluginConfigColor Empty => new(Vector4.Zero);
|
||||
|
||||
public Vector4 Vector
|
||||
{
|
||||
get => _vector;
|
||||
set
|
||||
{
|
||||
if (_vector == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_vector = value;
|
||||
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore] public uint Base { get; private set; }
|
||||
|
||||
[JsonIgnore] public uint Background { get; private set; }
|
||||
|
||||
[JsonIgnore] public uint TopGradient { get; private set; }
|
||||
|
||||
[JsonIgnore] public uint BottomGradient { get; private set; }
|
||||
|
||||
private void Update()
|
||||
{
|
||||
Base = ImGui.ColorConvertFloat4ToU32(_vector);
|
||||
Background = ImGui.ColorConvertFloat4ToU32(_vector.AdjustColor(_colorMapRatios[0]));
|
||||
TopGradient = ImGui.ColorConvertFloat4ToU32(_vector.AdjustColor(_colorMapRatios[1]));
|
||||
BottomGradient = ImGui.ColorConvertFloat4ToU32(_vector.AdjustColor(_colorMapRatios[2]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
using Dalamud.Logging;
|
||||
using HSUI.Interface.EnemyList;
|
||||
using HSUI.Interface.GeneralElements;
|
||||
using HSUI.Interface.Party;
|
||||
using HSUI.Interface.StatusEffects;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace HSUI.Config
|
||||
{
|
||||
public abstract class PluginConfigObjectConverter : JsonConverter
|
||||
{
|
||||
protected Dictionary<string, PluginConfigObjectFieldConverter> FieldConvertersMap = new Dictionary<string, PluginConfigObjectFieldConverter>();
|
||||
|
||||
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var genericMethod = GetType().GetMethod("ConvertJson");
|
||||
var method = genericMethod?.MakeGenericMethod(objectType);
|
||||
return method?.Invoke(this, new object[] { reader, serializer });
|
||||
}
|
||||
|
||||
public T? ConvertJson<T>(JsonReader reader, JsonSerializer serializer) where T : PluginConfigObject
|
||||
{
|
||||
Type type = typeof(T);
|
||||
T? config = null;
|
||||
|
||||
try
|
||||
{
|
||||
ConstructorInfo? constructor = type.GetConstructor(new Type[] { });
|
||||
if (constructor != null)
|
||||
{
|
||||
config = (T?)Activator.CreateInstance<T>();
|
||||
}
|
||||
else
|
||||
{
|
||||
config = (T?)ConfigurationManager.GetDefaultConfigObjectForType(type);
|
||||
}
|
||||
|
||||
// last resource, hackily create an instance without calling the constructor
|
||||
if (config == null)
|
||||
{
|
||||
#pragma warning disable SYSLIB0050 // Type or member is obsolete
|
||||
config = (T)FormatterServices.GetUninitializedObject(type);
|
||||
#pragma warning restore SYSLIB0050 // Type or member is obsolete
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.Logger.Error($"Error creating a {type.Name}: " + e.Message);
|
||||
}
|
||||
|
||||
if (config == null) { return null; }
|
||||
|
||||
try
|
||||
{
|
||||
JObject? jsonObject = (JObject?)serializer.Deserialize(reader);
|
||||
if (jsonObject == null) { return null; }
|
||||
|
||||
Dictionary<string, object> ValuesMap = new Dictionary<string, object>();
|
||||
|
||||
// get values from json
|
||||
foreach (JProperty property in jsonObject.Properties())
|
||||
{
|
||||
string propertyName = property.Name;
|
||||
object? value = null;
|
||||
|
||||
// convert values if needed
|
||||
if (FieldConvertersMap.TryGetValue(propertyName, out PluginConfigObjectFieldConverter? fieldConverter) && fieldConverter != null)
|
||||
{
|
||||
(propertyName, value) = fieldConverter.Convert(property.Value);
|
||||
}
|
||||
// read value from json
|
||||
else
|
||||
{
|
||||
FieldInfo? field = type.GetField(propertyName);
|
||||
if (field != null)
|
||||
{
|
||||
value = property.Value.ToObject(field.FieldType);
|
||||
}
|
||||
}
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
ValuesMap.Add(propertyName, value);
|
||||
}
|
||||
}
|
||||
|
||||
// apply values
|
||||
foreach (string key in ValuesMap.Keys)
|
||||
{
|
||||
string[] fields = key.Split(".", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
object? currentObject = config;
|
||||
object value = ValuesMap[key];
|
||||
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
{
|
||||
FieldInfo? field = currentObject?.GetType().GetField(fields[i]);
|
||||
if (field == null) { break; }
|
||||
|
||||
if (i == fields.Length - 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
field.SetValue(currentObject, value);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
else
|
||||
{
|
||||
currentObject = field.GetValue(currentObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.Logger.Error($"Error deserializing {type.Name}: " + e.Message);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||
{
|
||||
if (value == null) { return; }
|
||||
|
||||
JObject jsonObject = new JObject();
|
||||
Type type = value.GetType();
|
||||
jsonObject.Add("$type", type.FullName + ", HSUI");
|
||||
|
||||
FieldInfo[] fields = type.GetFields();
|
||||
|
||||
foreach (FieldInfo field in fields)
|
||||
{
|
||||
if (field.GetCustomAttribute<JsonIgnoreAttribute>() != null) { continue; }
|
||||
|
||||
object? fieldValue = field.GetValue(value);
|
||||
if (fieldValue != null)
|
||||
{
|
||||
jsonObject.Add(field.Name, JToken.FromObject(fieldValue, serializer));
|
||||
}
|
||||
}
|
||||
|
||||
jsonObject.WriteTo(writer);
|
||||
}
|
||||
}
|
||||
|
||||
#region contract resolver
|
||||
public class PluginConfigObjectsContractResolver : DefaultContractResolver
|
||||
{
|
||||
private static Dictionary<Type, Type> ConvertersMap = new Dictionary<Type, Type>()
|
||||
{
|
||||
[typeof(UnitFrameConfig)] = typeof(ColorByHealthFieldsConverter),
|
||||
[typeof(PlayerUnitFrameConfig)] = typeof(ColorByHealthFieldsConverter),
|
||||
[typeof(TargetUnitFrameConfig)] = typeof(ColorByHealthFieldsConverter),
|
||||
[typeof(TargetOfTargetUnitFrameConfig)] = typeof(ColorByHealthFieldsConverter),
|
||||
[typeof(FocusTargetUnitFrameConfig)] = typeof(ColorByHealthFieldsConverter),
|
||||
|
||||
[typeof(PartyFramesColorsConfig)] = typeof(ColorByHealthFieldsConverter),
|
||||
[typeof(PartyFramesRoleIconConfig)] = typeof(PartyFramesIconsConverter),
|
||||
[typeof(PartyFramesLeaderIconConfig)] = typeof(PartyFramesIconsConverter),
|
||||
[typeof(PartyFramesRaiseTrackerConfig)] = typeof(PartyFramesTrackerConfigConverter),
|
||||
[typeof(PartyFramesInvulnTrackerConfig)] = typeof(PartyFramesTrackerConfigConverter),
|
||||
[typeof(PartyFramesManaBarConfig)] = typeof(PartyFramesManaBarConfigConverter),
|
||||
|
||||
[typeof(StatusEffectsBlacklistConfig)] = typeof(StatusEffectsBlacklistConfigConverter),
|
||||
|
||||
[typeof(HUDOptionsConfig)] = typeof(HUDOptionsConfigConverter),
|
||||
|
||||
[typeof(CastbarConfig)] = typeof(CastbarConfigConverter),
|
||||
[typeof(UnitFrameCastbarConfig)] = typeof(CastbarConfigConverter),
|
||||
[typeof(PlayerCastbarConfig)] = typeof(CastbarConfigConverter),
|
||||
[typeof(TargetCastbarConfig)] = typeof(CastbarConfigConverter),
|
||||
[typeof(TargetOfTargetCastbarConfig)] = typeof(CastbarConfigConverter),
|
||||
[typeof(FocusTargetCastbarConfig)] = typeof(CastbarConfigConverter),
|
||||
[typeof(PartyFramesCastbarConfig)] = typeof(CastbarConfigConverter),
|
||||
[typeof(EnemyListCastbarConfig)] = typeof(CastbarConfigConverter),
|
||||
};
|
||||
|
||||
protected override JsonObjectContract CreateObjectContract(Type objectType)
|
||||
{
|
||||
JsonObjectContract contract = base.CreateObjectContract(objectType);
|
||||
|
||||
if (ConvertersMap.TryGetValue(objectType, out Type? converterType) && converterType != null)
|
||||
{
|
||||
contract.Converter = (JsonConverter?)Activator.CreateInstance(converterType);
|
||||
}
|
||||
|
||||
return contract;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region field converters
|
||||
public abstract class PluginConfigObjectFieldConverter
|
||||
{
|
||||
public readonly string NewFieldPath;
|
||||
public PluginConfigObjectFieldConverter(string newFieldPath)
|
||||
{
|
||||
NewFieldPath = newFieldPath;
|
||||
}
|
||||
|
||||
public abstract (string, object) Convert(JToken token);
|
||||
}
|
||||
|
||||
public class NewTypeFieldConverter<TOld, TNew> : PluginConfigObjectFieldConverter
|
||||
where TOld : struct
|
||||
where TNew : struct
|
||||
{
|
||||
private TNew DefaultValue;
|
||||
private Func<TOld, TNew> Func;
|
||||
|
||||
public NewTypeFieldConverter(string newFieldPath, TNew defaultValue, Func<TOld, TNew> func) : base(newFieldPath)
|
||||
{
|
||||
DefaultValue = defaultValue;
|
||||
Func = func;
|
||||
}
|
||||
|
||||
public override (string, object) Convert(JToken token)
|
||||
{
|
||||
TNew result = DefaultValue;
|
||||
|
||||
TOld? oldValue = token.ToObject<TOld>();
|
||||
if (oldValue.HasValue)
|
||||
{
|
||||
result = Func(oldValue.Value);
|
||||
}
|
||||
|
||||
return (NewFieldPath, result);
|
||||
}
|
||||
}
|
||||
|
||||
public class SameTypeFieldConverter<T> : NewTypeFieldConverter<T, T> where T : struct
|
||||
{
|
||||
public SameTypeFieldConverter(string newFieldPath, T defaultValue)
|
||||
: base(newFieldPath, defaultValue, (oldValue) => { return oldValue; })
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class NewClassFieldConverter<TOld, TNew> : PluginConfigObjectFieldConverter
|
||||
where TOld : class
|
||||
where TNew : class
|
||||
{
|
||||
private TNew DefaultValue;
|
||||
private Func<TOld, TNew> Func;
|
||||
|
||||
public NewClassFieldConverter(string newFieldPath, TNew defaultValue, Func<TOld, TNew> func)
|
||||
: base(newFieldPath)
|
||||
{
|
||||
DefaultValue = defaultValue;
|
||||
Func = func;
|
||||
}
|
||||
|
||||
public override (string, object) Convert(JToken token)
|
||||
{
|
||||
TNew result = DefaultValue;
|
||||
|
||||
TOld? oldValue = token.ToObject<TOld>();
|
||||
if (oldValue != null)
|
||||
{
|
||||
result = Func(oldValue);
|
||||
}
|
||||
|
||||
return (NewFieldPath, result);
|
||||
}
|
||||
}
|
||||
|
||||
public class SameClassFieldConverter<T> : NewClassFieldConverter<T, T> where T : class
|
||||
{
|
||||
public SameClassFieldConverter(string newFieldPath, T defaultValue)
|
||||
: base(newFieldPath, defaultValue, (oldValue) => { return oldValue; })
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class TypeToClassFieldConverter<TOld, TNew> : PluginConfigObjectFieldConverter
|
||||
where TOld : struct
|
||||
where TNew : class
|
||||
{
|
||||
private TNew DefaultValue;
|
||||
private Func<TOld, TNew> Func;
|
||||
|
||||
public TypeToClassFieldConverter(string newFieldPath, TNew defaultValue, Func<TOld, TNew> func) : base(newFieldPath)
|
||||
{
|
||||
DefaultValue = defaultValue;
|
||||
Func = func;
|
||||
}
|
||||
|
||||
public override (string, object) Convert(JToken token)
|
||||
{
|
||||
TNew result = DefaultValue;
|
||||
|
||||
TOld? oldValue = token.ToObject<TOld>();
|
||||
if (oldValue.HasValue)
|
||||
{
|
||||
result = Func(oldValue.Value);
|
||||
}
|
||||
|
||||
return (NewFieldPath, result);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
|
||||
namespace HSUI.Config.Profiles
|
||||
{
|
||||
public static class ImportExportHelper
|
||||
{
|
||||
public static string CompressAndBase64Encode(string jsonString)
|
||||
{
|
||||
using MemoryStream output = new();
|
||||
|
||||
using (DeflateStream gzip = new(output, CompressionLevel.Optimal))
|
||||
{
|
||||
using StreamWriter writer = new(gzip, Encoding.UTF8);
|
||||
writer.Write(jsonString);
|
||||
}
|
||||
|
||||
return Convert.ToBase64String(output.ToArray());
|
||||
}
|
||||
|
||||
public static string Base64DecodeAndDecompress(string base64String)
|
||||
{
|
||||
var base64EncodedBytes = Convert.FromBase64String(base64String);
|
||||
|
||||
using MemoryStream inputStream = new(base64EncodedBytes);
|
||||
using DeflateStream gzip = new(inputStream, CompressionMode.Decompress);
|
||||
using StreamReader reader = new(gzip, Encoding.UTF8);
|
||||
var decodedString = reader.ReadToEnd();
|
||||
|
||||
return decodedString;
|
||||
}
|
||||
|
||||
public static string GenerateExportString(object obj)
|
||||
{
|
||||
JsonSerializerSettings settings = new JsonSerializerSettings
|
||||
{
|
||||
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||
TypeNameHandling = TypeNameHandling.Objects
|
||||
};
|
||||
|
||||
var jsonString = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
|
||||
return CompressAndBase64Encode(jsonString);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
using HSUI.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace HSUI.Config.Profiles
|
||||
{
|
||||
public class Profile
|
||||
{
|
||||
public string Name;
|
||||
|
||||
public bool AutoSwitchEnabled = false;
|
||||
public AutoSwitchData AutoSwitchData = new AutoSwitchData();
|
||||
public int HudLayout;
|
||||
public bool AttachHudEnabled = false;
|
||||
|
||||
public Profile(string name, bool autoSwitchEnabled = false, AutoSwitchData? autoSwitchData = null, bool attachHudEnabled = false, int hudLayout = 0)
|
||||
{
|
||||
Name = name;
|
||||
|
||||
AutoSwitchEnabled = autoSwitchEnabled;
|
||||
AutoSwitchData = autoSwitchData ?? AutoSwitchData;
|
||||
|
||||
AttachHudEnabled = attachHudEnabled;
|
||||
HudLayout = hudLayout;
|
||||
}
|
||||
}
|
||||
|
||||
public class AutoSwitchData
|
||||
{
|
||||
public Dictionary<JobRoles, List<bool>> Map;
|
||||
|
||||
public AutoSwitchData()
|
||||
{
|
||||
Map = new Dictionary<JobRoles, List<bool>>();
|
||||
|
||||
JobRoles[] roles = (JobRoles[])Enum.GetValues(typeof(JobRoles));
|
||||
|
||||
foreach (JobRoles role in roles)
|
||||
{
|
||||
int count = JobsHelper.JobsByRole[role].Count;
|
||||
List<bool> list = new List<bool>(count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
list.Add(false);
|
||||
}
|
||||
|
||||
Map.Add(role, list);
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetRoleEnabled(JobRoles role)
|
||||
{
|
||||
foreach (bool value in Map[role])
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetRoleEnabled(JobRoles role, bool value)
|
||||
{
|
||||
for (int i = 0; i < Map[role].Count; i++)
|
||||
{
|
||||
Map[role][i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled(JobRoles role, int index)
|
||||
{
|
||||
if (Map.TryGetValue(role, out List<bool>? list) && list != null)
|
||||
{
|
||||
if (index >= list.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return list[index];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ValidateRolesData()
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
JobRoles[] roles = (JobRoles[])Enum.GetValues(typeof(JobRoles));
|
||||
|
||||
foreach (JobRoles role in roles)
|
||||
{
|
||||
int count = JobsHelper.JobsByRole[role].Count;
|
||||
List<bool> list = Map[role];
|
||||
|
||||
if (list.Count < count)
|
||||
{
|
||||
for (int i = 0; i < count - list.Count; i++)
|
||||
{
|
||||
list.Add(false);
|
||||
}
|
||||
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,372 @@
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Textures.TextureWraps;
|
||||
using Dalamud.Interface.Utility;
|
||||
using HSUI.Config.Attributes;
|
||||
using HSUI.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace HSUI.Config.Tree
|
||||
{
|
||||
public delegate void ConfigObjectResetEventHandler(BaseNode sender);
|
||||
|
||||
public class BaseNode : Node
|
||||
{
|
||||
public event ConfigObjectResetEventHandler? ConfigObjectResetEvent;
|
||||
|
||||
private Dictionary<Type, ConfigPageNode> _configPageNodesMap;
|
||||
|
||||
public bool NeedsSave = false;
|
||||
public string? SelectedOptionName = null;
|
||||
|
||||
private List<Node> _extraNodes = new List<Node>();
|
||||
private List<Node> _nodes = new List<Node>();
|
||||
|
||||
public IReadOnlyCollection<Node> Sections => _nodes.AsReadOnly();
|
||||
|
||||
private float _scale => ImGuiHelpers.GlobalScale;
|
||||
|
||||
public BaseNode()
|
||||
{
|
||||
_configPageNodesMap = new Dictionary<Type, ConfigPageNode>();
|
||||
}
|
||||
|
||||
public void AddExtraSectionNode(SectionNode node)
|
||||
{
|
||||
_extraNodes.Add(node);
|
||||
|
||||
_nodes.Clear();
|
||||
CreateNodesIfNeeded();
|
||||
}
|
||||
|
||||
public T? GetConfigObject<T>() where T : PluginConfigObject
|
||||
{
|
||||
var pageNode = GetConfigPageNode<T>();
|
||||
|
||||
return pageNode != null ? (T)pageNode.ConfigObject : null;
|
||||
}
|
||||
|
||||
public void RemoveConfigObject<T>() where T : PluginConfigObject
|
||||
{
|
||||
if (_configPageNodesMap.ContainsKey(typeof(T)))
|
||||
{
|
||||
_configPageNodesMap.Remove(typeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
public ConfigPageNode? GetConfigPageNode<T>() where T : PluginConfigObject
|
||||
{
|
||||
if (_configPageNodesMap.TryGetValue(typeof(T), out var node))
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
var configPageNode = GetOrAddConfig<T>();
|
||||
|
||||
if (configPageNode != null && configPageNode.ConfigObject != null)
|
||||
{
|
||||
_configPageNodesMap.Add(typeof(T), configPageNode);
|
||||
|
||||
return configPageNode;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetConfigPageNode(ConfigPageNode configPageNode)
|
||||
{
|
||||
if (configPageNode.ConfigObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_configPageNodesMap[configPageNode.ConfigObject.GetType()] = configPageNode;
|
||||
}
|
||||
|
||||
public bool SetConfigObject(PluginConfigObject configObject)
|
||||
{
|
||||
if (_configPageNodesMap.TryGetValue(configObject.GetType(), out ConfigPageNode? configPageNode))
|
||||
{
|
||||
configPageNode.ConfigObject = configObject;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool PushStyles()
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.ScrollbarRounding, 1); //Scrollbar Radius
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.TabRounding, 1); //Tabs Radius Radius
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 1); //Intractable Elements Radius
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.GrabRounding, 1); //Gradable Elements Radius
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.PopupRounding, 1); //Popup Radius
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.ScrollbarSize, 10); //Popup Radius
|
||||
|
||||
if (ConfigurationManager.Instance.OverrideDalamudStyle)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(46f / 255f, 45f / 255f, 46f / 255f, 1f));
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .2f));
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .2f));
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.Separator, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .4f));
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.ScrollbarBg, new Vector4(20f / 255f, 21f / 255f, 20f / 255f, .7f));
|
||||
ImGui.PushStyleColor(ImGuiCol.ScrollbarGrab, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .7f));
|
||||
ImGui.PushStyleColor(ImGuiCol.ScrollbarGrabActive, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .7f));
|
||||
ImGui.PushStyleColor(ImGuiCol.ScrollbarGrabHovered, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .7f));
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.Tab, new Vector4(46f / 255f, 45f / 255f, 46f / 255f, 1f));
|
||||
ImGui.PushStyleColor(ImGuiCol.TabActive, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .7f));
|
||||
ImGui.PushStyleColor(ImGuiCol.TabHovered, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .2f));
|
||||
ImGui.PushStyleColor(ImGuiCol.TabUnfocused, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .2f));
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.Border, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, 1f));
|
||||
ImGui.PushStyleColor(ImGuiCol.CheckMark, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, 1f));
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.TableBorderStrong, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, 1f));
|
||||
ImGui.PushStyleColor(ImGuiCol.TableBorderLight, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .4f));
|
||||
ImGui.PushStyleColor(ImGuiCol.TableHeaderBg, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .2f));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void PopStyles(bool popColors)
|
||||
{
|
||||
if (popColors)
|
||||
{
|
||||
ImGui.PopStyleColor(17);
|
||||
}
|
||||
|
||||
ImGui.PopStyleVar(6);
|
||||
}
|
||||
|
||||
public void CreateNodesIfNeeded()
|
||||
{
|
||||
if (_nodes.Count > 0) { return; }
|
||||
|
||||
_nodes.AddRange(_children);
|
||||
_nodes.AddRange(_extraNodes);
|
||||
}
|
||||
|
||||
public void RefreshSelectedNode()
|
||||
{
|
||||
if (_nodes.Count == 0) { return; }
|
||||
|
||||
foreach (SectionNode node in _nodes.FindAll(x => x is SectionNode))
|
||||
{
|
||||
node.Selected = node.Name == SelectedOptionName;
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw(float alpha)
|
||||
{
|
||||
CreateNodesIfNeeded();
|
||||
if (_nodes.Count == 0) { return; }
|
||||
|
||||
bool changed = false;
|
||||
bool didReset = false;
|
||||
|
||||
bool popColors = PushStyles();
|
||||
|
||||
ImGui.BeginGroup(); // Middle section
|
||||
{
|
||||
ImGui.BeginGroup(); // Left
|
||||
{
|
||||
// banner
|
||||
IDalamudTextureWrap? delvUiBanner = Plugin.BannerTexture?.GetWrapOrDefault();
|
||||
if (delvUiBanner != null)
|
||||
{
|
||||
ImGui.SetCursorPos(new Vector2(15 + 150 * _scale / 2f - delvUiBanner.Width / 2f, 5));
|
||||
ImGui.Image(delvUiBanner.Handle, new Vector2(delvUiBanner.Width, delvUiBanner.Height));
|
||||
}
|
||||
|
||||
// version
|
||||
ImGui.SetCursorPos(new Vector2(60 * _scale, 35));
|
||||
ImGui.Text($"v{Plugin.Version}");
|
||||
|
||||
|
||||
// section list
|
||||
if (ImGui.BeginChild("left pane", new Vector2(150 * _scale, -10), true, ImGuiWindowFlags.NoScrollbar))
|
||||
{
|
||||
|
||||
// if no section is selected, select the first
|
||||
if (_nodes.Any() && _nodes.Find(o => o is SectionNode sectionNode && sectionNode.Selected) == null)
|
||||
{
|
||||
SectionNode? selectedSection = (SectionNode?)_nodes.Find(o => o is SectionNode sectionNode && sectionNode.Name == SelectedOptionName);
|
||||
if (selectedSection != null)
|
||||
{
|
||||
selectedSection.Selected = true;
|
||||
SelectedOptionName = selectedSection.Name;
|
||||
}
|
||||
else if (_nodes.Count > 0)
|
||||
{
|
||||
SectionNode node = (SectionNode)_nodes[0];
|
||||
node.Selected = true;
|
||||
SelectedOptionName = node.Name;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (SectionNode selectionNode in _nodes)
|
||||
{
|
||||
if (ImGui.Selectable(selectionNode.Name, selectionNode.Selected))
|
||||
{
|
||||
selectionNode.Selected = true;
|
||||
SelectedOptionName = selectionNode.Name;
|
||||
|
||||
foreach (SectionNode otherNode in _nodes.FindAll(x => x != selectionNode))
|
||||
{
|
||||
otherNode.Selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
DrawExportResetContextMenu(selectionNode, selectionNode.Name);
|
||||
}
|
||||
|
||||
// changelog button
|
||||
float y = ImGui.GetWindowHeight() - (30 * _scale);
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(255f / 255f, 255f / 255f, 255f / 255f, alpha));
|
||||
ImGui.SetCursorPos(new Vector2(19 * _scale, y));
|
||||
if (ImGui.Button(FontAwesomeIcon.List.ToIconString(), new Vector2(24 * _scale, 24 * _scale)))
|
||||
{
|
||||
ConfigurationManager.Instance.OpenChangelogWindow();
|
||||
}
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.PopFont();
|
||||
ImGuiHelper.SetTooltip("Changelog");
|
||||
|
||||
// discord button
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(114f / 255f, 137f / 255f, 218f / 255f, alpha));
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(114f / 255f, 137f / 255f, 218f / 255f, alpha * .85f));
|
||||
ImGui.SetCursorPos(new Vector2(48 * _scale, y));
|
||||
if (ImGui.Button(FontAwesomeIcon.Link.ToIconString(), new Vector2(24 * _scale, 24 * _scale)))
|
||||
{
|
||||
Utils.OpenUrl("https://discord.gg/xzde5qQayh");
|
||||
}
|
||||
ImGui.PopStyleColor(2);
|
||||
ImGui.PopFont();
|
||||
ImGuiHelper.SetTooltip("HSUI Discord");
|
||||
|
||||
// discord button
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(255f / 255f, 94f / 255f, 91f / 255f, alpha));
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(255f / 255f, 94f / 255f, 91f / 255f, alpha * .85f));
|
||||
ImGui.SetCursorPos(new Vector2(77 * _scale, y));
|
||||
if (ImGui.Button(FontAwesomeIcon.MugHot.ToIconString(), new Vector2(24 * _scale, 24 * _scale)))
|
||||
{
|
||||
Utils.OpenUrl("https://ko-fi.com/Tischel");
|
||||
}
|
||||
ImGui.PopStyleColor(2);
|
||||
ImGui.PopFont();
|
||||
ImGuiHelper.SetTooltip("Tip the developer at ko-fi.com");
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
ImGui.EndGroup(); // Left
|
||||
|
||||
|
||||
didReset |= DrawResetModal();
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.BeginGroup(); // Right
|
||||
{
|
||||
foreach (SectionNode selectionNode in _nodes)
|
||||
{
|
||||
didReset |= selectionNode.Draw(ref changed, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndGroup(); // Right
|
||||
}
|
||||
|
||||
ImGui.EndGroup(); // Middle section
|
||||
|
||||
// close button
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, alpha));
|
||||
ImGui.SetCursorPos(new Vector2(ImGui.GetWindowWidth() - 28 * _scale, 5 * _scale));
|
||||
if (ImGui.Button(FontAwesomeIcon.Times.ToIconString(), new Vector2(22 * _scale, 22 * _scale)))
|
||||
{
|
||||
ConfigurationManager.Instance.CloseConfigWindow();
|
||||
}
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.PopFont();
|
||||
ImGuiHelper.SetTooltip("Close");
|
||||
|
||||
// unlock button
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, alpha));
|
||||
ImGui.SetCursorPos(new Vector2(ImGui.GetWindowWidth() - 60 * _scale, 5 * _scale));
|
||||
string lockString = ConfigurationManager.Instance.LockHUD ? FontAwesomeIcon.Lock.ToIconString() : FontAwesomeIcon.LockOpen.ToIconString();
|
||||
if (ImGui.Button(lockString, new Vector2(22 * _scale, 22 * _scale)))
|
||||
{
|
||||
ConfigurationManager.Instance.LockHUD = !ConfigurationManager.Instance.LockHUD;
|
||||
}
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.PopFont();
|
||||
ImGuiHelper.SetTooltip("Unlock HUD");
|
||||
|
||||
// hide button
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, alpha));
|
||||
ImGui.SetCursorPos(new Vector2(ImGui.GetWindowWidth() - 88 * _scale, 5 * _scale));
|
||||
string hideString = ConfigurationManager.Instance.ShowHUD ? FontAwesomeIcon.Eye.ToIconString() : FontAwesomeIcon.EyeSlash.ToIconString();
|
||||
if (ImGui.Button(hideString, new Vector2(26 * _scale, 22 * _scale)))
|
||||
{
|
||||
ConfigurationManager.Instance.ShowHUD = !ConfigurationManager.Instance.ShowHUD;
|
||||
}
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.PopFont();
|
||||
ImGuiHelper.SetTooltip(ConfigurationManager.Instance.ShowHUD ? "Hide HUD" : "Show HUD");
|
||||
|
||||
PopStyles(popColors);
|
||||
|
||||
if (didReset)
|
||||
{
|
||||
ConfigObjectResetEvent?.Invoke(this);
|
||||
}
|
||||
|
||||
NeedsSave |= changed | didReset;
|
||||
}
|
||||
|
||||
public ConfigPageNode? GetOrAddConfig<T>() where T : PluginConfigObject
|
||||
{
|
||||
object[] attributes = typeof(T).GetCustomAttributes(true);
|
||||
|
||||
foreach (object attribute in attributes)
|
||||
{
|
||||
if (attribute is SectionAttribute sectionAttribute)
|
||||
{
|
||||
foreach (SectionNode sectionNode in _children)
|
||||
{
|
||||
if (sectionNode.Name == sectionAttribute.SectionName)
|
||||
{
|
||||
return sectionNode.GetOrAddConfig<T>();
|
||||
}
|
||||
}
|
||||
|
||||
SectionNode newNode = new();
|
||||
newNode.Name = sectionAttribute.SectionName;
|
||||
newNode.ForceAllowExport = sectionAttribute.ForceAllowExport;
|
||||
_children.Add(newNode);
|
||||
|
||||
return newNode.GetOrAddConfig<T>();
|
||||
}
|
||||
}
|
||||
|
||||
Type type = typeof(T);
|
||||
throw new ArgumentException("The provided configuration object does not specify a section: " + type.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
using Dalamud.Logging;
|
||||
using HSUI.Config.Attributes;
|
||||
using HSUI.Config.Profiles;
|
||||
using HSUI.Helpers;
|
||||
using HSUI.Interface;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace HSUI.Config.Tree
|
||||
{
|
||||
public class ConfigPageNode : SubSectionNode
|
||||
{
|
||||
private PluginConfigObject _configObject = null!;
|
||||
private List<ConfigNode>? _drawList = null;
|
||||
private Dictionary<string, ConfigPageNode> _nestedConfigPageNodes = null!;
|
||||
|
||||
public PluginConfigObject ConfigObject
|
||||
{
|
||||
get => _configObject;
|
||||
set
|
||||
{
|
||||
_configObject = value;
|
||||
GenerateNestedConfigPageNodes();
|
||||
_drawList = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override List<T> GetObjects<T>()
|
||||
{
|
||||
return _configObject.GetObjects<T>();
|
||||
}
|
||||
|
||||
private void GenerateNestedConfigPageNodes()
|
||||
{
|
||||
_nestedConfigPageNodes = new Dictionary<string, ConfigPageNode>();
|
||||
|
||||
FieldInfo[] fields = _configObject.GetType().GetFields();
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
foreach (var attribute in field.GetCustomAttributes(true))
|
||||
{
|
||||
if (attribute is not NestedConfigAttribute nestedConfigAttribute)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = field.GetValue(_configObject);
|
||||
|
||||
if (value is not PluginConfigObject nestedConfig)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ConfigPageNode configPageNode = new();
|
||||
configPageNode.ConfigObject = nestedConfig;
|
||||
configPageNode.Name = nestedConfigAttribute.friendlyName;
|
||||
|
||||
if (nestedConfig.Disableable)
|
||||
{
|
||||
configPageNode.Name += "##" + nestedConfig.GetHashCode();
|
||||
}
|
||||
|
||||
_nestedConfigPageNodes.Add(field.Name, configPageNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string? GetBase64String()
|
||||
{
|
||||
if (!AllowShare())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ImportExportHelper.GenerateExportString(ConfigObject);
|
||||
}
|
||||
|
||||
protected override bool AllowExport()
|
||||
{
|
||||
return ConfigObject.Exportable;
|
||||
}
|
||||
|
||||
protected override bool AllowShare()
|
||||
{
|
||||
return ConfigObject.Shareable;
|
||||
}
|
||||
|
||||
protected override bool AllowReset()
|
||||
{
|
||||
return ConfigObject.Resettable;
|
||||
}
|
||||
|
||||
public override bool Draw(ref bool changed) { return DrawWithID(ref changed); }
|
||||
|
||||
private bool DrawWithID(ref bool changed, string? ID = null)
|
||||
{
|
||||
bool didReset = false;
|
||||
|
||||
// Only do this stuff the first time the config page is loaded
|
||||
if (_drawList is null)
|
||||
{
|
||||
_drawList = GenerateDrawList();
|
||||
}
|
||||
|
||||
if (_drawList is not null)
|
||||
{
|
||||
foreach (var fieldNode in _drawList)
|
||||
{
|
||||
didReset |= fieldNode.Draw(ref changed);
|
||||
}
|
||||
}
|
||||
|
||||
didReset |= DrawPortableSection();
|
||||
|
||||
ImGui.NewLine(); // fixes some long pages getting cut off
|
||||
|
||||
return didReset;
|
||||
}
|
||||
|
||||
private List<ConfigNode> GenerateDrawList(string? ID = null)
|
||||
{
|
||||
Dictionary<string, ConfigNode> fieldMap = new Dictionary<string, ConfigNode>();
|
||||
|
||||
FieldInfo[] fields = ConfigObject.GetType().GetFields();
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (ConfigObject.DisableParentSettings != null && ConfigObject.DisableParentSettings.Contains(field.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (object attribute in field.GetCustomAttributes(true))
|
||||
{
|
||||
if (attribute is NestedConfigAttribute nestedConfigAttribute && _nestedConfigPageNodes.TryGetValue(field.Name, out ConfigPageNode? node))
|
||||
{
|
||||
var newNodes = node.GenerateDrawList(node.Name);
|
||||
foreach (var newNode in newNodes)
|
||||
{
|
||||
newNode.Position = nestedConfigAttribute.pos;
|
||||
newNode.Separator = nestedConfigAttribute.separator;
|
||||
newNode.Spacing = nestedConfigAttribute.spacing;
|
||||
newNode.ParentName = nestedConfigAttribute.collapseWith;
|
||||
newNode.Nest = nestedConfigAttribute.nest;
|
||||
newNode.CollapsingHeader = nestedConfigAttribute.collapsingHeader;
|
||||
fieldMap.Add($"{node.Name}_{newNode.Name}", newNode);
|
||||
}
|
||||
}
|
||||
else if (attribute is OrderAttribute orderAttribute)
|
||||
{
|
||||
var fieldNode = new FieldNode(field, ConfigObject, ID);
|
||||
fieldNode.Position = orderAttribute.pos;
|
||||
fieldNode.ParentName = orderAttribute.collapseWith;
|
||||
if (fieldMap.TryGetValue(field.Name, out var existing) && existing is FieldNode existingFn &&
|
||||
existingFn.Field.DeclaringType != null && field.DeclaringType != null &&
|
||||
existingFn.Field.DeclaringType.IsAssignableFrom(field.DeclaringType) &&
|
||||
existingFn.Field.DeclaringType != field.DeclaringType)
|
||||
{
|
||||
fieldMap[field.Name] = fieldNode;
|
||||
}
|
||||
else if (!fieldMap.ContainsKey(field.Name))
|
||||
{
|
||||
fieldMap.Add(field.Name, fieldNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var manualDrawMethods = ConfigObject.GetType().GetMethods().Where(m => Attribute.IsDefined(m, typeof(ManualDrawAttribute), false));
|
||||
foreach (var method in manualDrawMethods)
|
||||
{
|
||||
string id = $"ManualDraw##{method.GetHashCode()}";
|
||||
fieldMap.Add(id, new ManualDrawNode(method, ConfigObject, id));
|
||||
}
|
||||
|
||||
foreach (var configNode in fieldMap.Values)
|
||||
{
|
||||
if (configNode.ParentName is not null &&
|
||||
fieldMap.TryGetValue(configNode.ParentName, out ConfigNode? parentNode))
|
||||
{
|
||||
if (!ConfigObject.Disableable &&
|
||||
parentNode.Name.Equals("Enabled") &&
|
||||
parentNode.ID is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parentNode is FieldNode parentFieldNode)
|
||||
{
|
||||
parentFieldNode.CollapseControl = true;
|
||||
parentFieldNode.AddChild(configNode.Position, configNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var fieldNodes = fieldMap.Values.ToList();
|
||||
fieldNodes.RemoveAll(f => f.IsChild);
|
||||
fieldNodes.Sort((x, y) => x.Position - y.Position);
|
||||
return fieldNodes;
|
||||
}
|
||||
|
||||
private bool DrawPortableSection()
|
||||
{
|
||||
if (!AllowExport())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ImGuiHelper.DrawSeparator(2, 1);
|
||||
|
||||
const float buttonWidth = 120;
|
||||
|
||||
ImGui.BeginGroup();
|
||||
|
||||
float width = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X;
|
||||
ImGui.SetCursorPos(new Vector2(width / 2f - buttonWidth - 5, ImGui.GetCursorPosY()));
|
||||
|
||||
if (ImGui.Button("Export", new Vector2(120, 24)))
|
||||
{
|
||||
var exportString = ImportExportHelper.GenerateExportString(ConfigObject);
|
||||
ImGui.SetClipboardText(exportString);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Reset", new Vector2(120, 24)))
|
||||
{
|
||||
_nodeToReset = this;
|
||||
_nodeToResetName = Utils.UserFriendlyConfigName(ConfigObject.GetType().Name);
|
||||
}
|
||||
|
||||
ImGui.NewLine();
|
||||
ImGui.EndGroup();
|
||||
|
||||
return DrawResetModal();
|
||||
}
|
||||
|
||||
public override void Save(string path)
|
||||
{
|
||||
string[] splits = path.Split("\\", StringSplitOptions.RemoveEmptyEntries);
|
||||
string directory = path.Replace(splits.Last(), "");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
string finalPath = path + ".json";
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(
|
||||
finalPath,
|
||||
JsonConvert.SerializeObject(
|
||||
ConfigObject,
|
||||
Formatting.Indented,
|
||||
new JsonSerializerSettings { TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, TypeNameHandling = TypeNameHandling.Objects }
|
||||
)
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.Logger.Error("Error when saving config object: " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Load(string path)
|
||||
{
|
||||
if (ConfigObject is not PluginConfigObject) { return; }
|
||||
|
||||
FileInfo finalPath = new(path + ".json");
|
||||
|
||||
// Use reflection to call the LoadForType method, this allows us to specify a type at runtime.
|
||||
// While in general use this is important as the conversion from the superclass 'PluginConfigObject' to a specific subclass (e.g. 'BlackMageHudConfig') would
|
||||
// be handled by Json.NET, when the plugin is reloaded with a different assembly (as is the case when using LivePluginLoader, or updating the plugin in-game)
|
||||
// it fails. In order to fix this we need to specify the specific subclass, in order to do this during runtime we must use reflection to set the generic.
|
||||
MethodInfo? methodInfo = ConfigObject.GetType().GetMethod("Load");
|
||||
MethodInfo? function = methodInfo?.MakeGenericMethod(ConfigObject.GetType());
|
||||
|
||||
object?[] args = new object?[] { finalPath };
|
||||
PluginConfigObject? config = (PluginConfigObject?)function?.Invoke(ConfigObject, args);
|
||||
|
||||
ConfigObject = config ?? ConfigObject;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
Type type = ConfigObject.GetType();
|
||||
ImportData? importData = ProfilesManager.Instance?.DefaultImportData(type);
|
||||
|
||||
if (importData == null)
|
||||
{
|
||||
Plugin.Logger.Error("Error finding default import data for type " + type.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
PluginConfigObject? config = importData.GetObject();
|
||||
if (config == null)
|
||||
{
|
||||
Plugin.Logger.Error("Error importing default import data for type " + type.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigObject = config;
|
||||
}
|
||||
|
||||
public override ConfigPageNode? GetOrAddConfig<T>() => this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using HSUI.Config.Attributes;
|
||||
using HSUI.Helpers;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace HSUI.Config.Tree
|
||||
{
|
||||
public abstract class ConfigNode
|
||||
{
|
||||
public bool CollapseControl { get; set; }
|
||||
|
||||
public bool IsChild { get; set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public bool Nest { get; set; } = true;
|
||||
|
||||
public string? ParentName { get; set; }
|
||||
|
||||
public int Position { get; set; } = Int32.MaxValue;
|
||||
|
||||
public bool Separator { get; set; }
|
||||
|
||||
public bool Spacing { get; set; }
|
||||
|
||||
public bool CollapsingHeader { get; set; }
|
||||
|
||||
public string? ID { get; private set; }
|
||||
|
||||
protected PluginConfigObject ConfigObject { get; set; }
|
||||
|
||||
public ConfigNode(PluginConfigObject configObject, string? id, string name)
|
||||
{
|
||||
ConfigObject = configObject;
|
||||
ID = id;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public abstract bool Draw(ref bool changed, int depth = 0);
|
||||
|
||||
protected void DrawSeparatorOrSpacing()
|
||||
{
|
||||
if (Separator)
|
||||
{
|
||||
ImGuiHelper.DrawSeparator(1, 1);
|
||||
}
|
||||
|
||||
if (Spacing)
|
||||
{
|
||||
ImGuiHelper.DrawSpacing(1);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void DrawNestIndicator(int depth)
|
||||
{
|
||||
// This draws the L shaped symbols and padding to the left of config items collapsible under a checkbox.
|
||||
// Shift cursor to the right to pad for children with depth more than 1.
|
||||
// 26 is an arbitrary value I found to be around half the width of a checkbox
|
||||
ImGui.SetCursorPos(ImGui.GetCursorPos() + new Vector2(26, 0) * Math.Max((depth - 1), 0));
|
||||
|
||||
if (ConfigurationManager.Instance.OverrideDalamudStyle)
|
||||
{
|
||||
ImGui.TextColored(new Vector4(229f / 255f, 57f / 255f, 57f / 255f, 1f), "\u2514");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text("\u2514");
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
protected static ConfigAttribute? GetConfigAttribute(FieldInfo field)
|
||||
{
|
||||
return field.GetCustomAttributes(true).Where(a => a is ConfigAttribute).FirstOrDefault() as ConfigAttribute;
|
||||
}
|
||||
}
|
||||
|
||||
public class FieldNode : ConfigNode
|
||||
{
|
||||
private SortedDictionary<int, ConfigNode> _childNodes;
|
||||
private ConfigAttribute? _configAttribute;
|
||||
private FieldInfo _mainField;
|
||||
|
||||
public FieldInfo Field => _mainField;
|
||||
|
||||
public FieldNode(FieldInfo mainField, PluginConfigObject configObject, string? id) : base(configObject, id, mainField.Name)
|
||||
{
|
||||
_mainField = mainField;
|
||||
_childNodes = new SortedDictionary<int, ConfigNode>();
|
||||
|
||||
_configAttribute = GetConfigAttribute(mainField);
|
||||
if (_configAttribute is not null)
|
||||
{
|
||||
Separator = _configAttribute.separator;
|
||||
Spacing = _configAttribute.spacing;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddChild(int position, ConfigNode field)
|
||||
{
|
||||
field.IsChild = true;
|
||||
|
||||
while (_childNodes.ContainsKey(position))
|
||||
{
|
||||
position++;
|
||||
}
|
||||
|
||||
_childNodes.Add(position, field);
|
||||
}
|
||||
|
||||
public override bool Draw(ref bool changed, int depth = 0)
|
||||
{
|
||||
bool reset = false;
|
||||
DrawSeparatorOrSpacing();
|
||||
|
||||
if (!Nest)
|
||||
{
|
||||
depth = 0;
|
||||
}
|
||||
|
||||
if (depth > 0)
|
||||
{
|
||||
DrawNestIndicator(depth);
|
||||
}
|
||||
|
||||
bool collapsing = CollapsingHeader && ConfigObject.Disableable;
|
||||
|
||||
// Draw the ConfigAttribute
|
||||
if (!collapsing)
|
||||
{
|
||||
DrawConfigAttribute(ref changed, _mainField);
|
||||
}
|
||||
|
||||
bool enabled = _mainField.GetValue(ConfigObject) as bool? ?? false;
|
||||
|
||||
// Draw children
|
||||
if (CollapseControl && Attribute.IsDefined(_mainField, typeof(CheckboxAttribute)))
|
||||
{
|
||||
if (collapsing)
|
||||
{
|
||||
if (ImGui.CollapsingHeader(ID + "##CollapsingHeader"))
|
||||
{
|
||||
DrawNestIndicator(depth);
|
||||
DrawConfigAttribute(ref changed, _mainField);
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
reset |= DrawChildren(ref changed, depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!collapsing && enabled)
|
||||
{
|
||||
ImGui.BeginGroup();
|
||||
reset |= DrawChildren(ref changed, depth);
|
||||
ImGui.EndGroup();
|
||||
}
|
||||
}
|
||||
|
||||
return reset;
|
||||
}
|
||||
|
||||
private bool DrawChildren(ref bool changed, int depth)
|
||||
{
|
||||
bool reset = false;
|
||||
|
||||
int childDepth = depth + 1;
|
||||
foreach (ConfigNode child in _childNodes.Values)
|
||||
{
|
||||
if (child.Separator)
|
||||
{
|
||||
childDepth = 0;
|
||||
}
|
||||
|
||||
reset |= child.Draw(ref changed, childDepth);
|
||||
}
|
||||
|
||||
return reset;
|
||||
}
|
||||
|
||||
private void DrawConfigAttribute(ref bool changed, FieldInfo field)
|
||||
{
|
||||
if (_configAttribute is not null)
|
||||
{
|
||||
changed |= _configAttribute.Draw(field, ConfigObject, ID, CollapsingHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ManualDrawNode : ConfigNode
|
||||
{
|
||||
private MethodInfo _drawMethod;
|
||||
|
||||
public ManualDrawNode(MethodInfo method, PluginConfigObject configObject, string? id) : base(configObject, id, id ?? "")
|
||||
{
|
||||
_drawMethod = method;
|
||||
}
|
||||
|
||||
public override bool Draw(ref bool changed, int depth = 0)
|
||||
{
|
||||
object[] args = new object[] { false };
|
||||
bool? result = (bool?)_drawMethod.Invoke(ConfigObject, args);
|
||||
|
||||
bool arg = (bool)args[0];
|
||||
changed |= arg;
|
||||
return result ?? false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
using HSUI.Helpers;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace HSUI.Config.Tree
|
||||
{
|
||||
public abstract class Node
|
||||
{
|
||||
protected List<Node> _children = new List<Node>();
|
||||
public IReadOnlyList<Node> Children => _children.AsReadOnly();
|
||||
|
||||
public void Add(Node node)
|
||||
{
|
||||
_children.Add(node);
|
||||
}
|
||||
|
||||
#region reset
|
||||
protected Node? _nodeToReset = null;
|
||||
protected string? _nodeToResetName = null;
|
||||
|
||||
public virtual List<T> GetObjects<T>()
|
||||
{
|
||||
List<T> list = new List<T>();
|
||||
|
||||
foreach (Node node in _children)
|
||||
{
|
||||
list.AddRange(node.GetObjects<T>());
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
protected void DrawExportResetContextMenu(Node node, string name)
|
||||
{
|
||||
if (_nodeToReset != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool allowExport = node.AllowExport();
|
||||
bool allowReset = node.AllowReset();
|
||||
if (!allowExport && !allowReset)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_nodeToReset = ImGuiHelper.DrawExportResetContextMenu(node, allowExport, allowReset);
|
||||
_nodeToResetName = name;
|
||||
}
|
||||
|
||||
protected virtual bool AllowExport()
|
||||
{
|
||||
foreach (Node child in _children)
|
||||
{
|
||||
if (child.AllowExport())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual bool AllowShare()
|
||||
{
|
||||
foreach (Node child in _children)
|
||||
{
|
||||
if (child.AllowShare())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual bool AllowReset()
|
||||
{
|
||||
foreach (Node child in _children)
|
||||
{
|
||||
if (child.AllowReset())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected bool DrawResetModal()
|
||||
{
|
||||
if (_nodeToReset == null || _nodeToResetName == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string[] lines = new string[] { "Are you sure you want to reset \"" + _nodeToResetName + "\"?" };
|
||||
var (didReset, didClose) = ImGuiHelper.DrawConfirmationModal("Reset?", lines);
|
||||
|
||||
if (didReset)
|
||||
{
|
||||
_nodeToReset.Reset();
|
||||
_nodeToReset = null;
|
||||
}
|
||||
else if (didClose)
|
||||
{
|
||||
_nodeToReset = null;
|
||||
}
|
||||
|
||||
return didReset;
|
||||
}
|
||||
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
foreach (Node child in _children)
|
||||
{
|
||||
child.Reset();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region save and load
|
||||
public virtual void Save(string path)
|
||||
{
|
||||
foreach (Node child in _children)
|
||||
{
|
||||
child.Save(path);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Load(string path)
|
||||
{
|
||||
foreach (Node child in _children)
|
||||
{
|
||||
child.Load(path);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region export
|
||||
public virtual string? GetBase64String()
|
||||
{
|
||||
if (_children == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
string base64String = "";
|
||||
|
||||
foreach (Node child in _children)
|
||||
{
|
||||
string? childString = child.GetBase64String();
|
||||
|
||||
if (childString != null && childString.Length > 0)
|
||||
{
|
||||
base64String += "|" + childString;
|
||||
}
|
||||
}
|
||||
|
||||
return base64String;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
using HSUI.Config.Attributes;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
|
||||
namespace HSUI.Config.Tree
|
||||
{
|
||||
public class SectionNode : Node
|
||||
{
|
||||
public bool Selected;
|
||||
public string Name = null!;
|
||||
public bool ForceAllowExport = false;
|
||||
public string? ForceSelectedTabName = null;
|
||||
|
||||
public SectionNode() { }
|
||||
|
||||
protected override bool AllowExport()
|
||||
{
|
||||
if (ForceAllowExport) { return true; }
|
||||
|
||||
return base.AllowExport();
|
||||
}
|
||||
|
||||
public bool Draw(ref bool changed, float alpha)
|
||||
{
|
||||
if (!Selected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool didReset = false;
|
||||
|
||||
ImGui.NewLine();
|
||||
|
||||
if (ImGui.BeginChild(
|
||||
"DelvU_Settings_Tab",
|
||||
new Vector2(0, -10),
|
||||
false,
|
||||
ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse
|
||||
))
|
||||
{
|
||||
bool popColors = false;
|
||||
if (ConfigurationManager.Instance.OverrideDalamudStyle)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Tab, new Vector4(45f / 255f, 45f / 255f, 45f / 255f, alpha));
|
||||
ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(45f / 255f, 45f / 255f, 45f / 255f, alpha));
|
||||
popColors = true;
|
||||
}
|
||||
|
||||
if (ImGui.BeginTabBar("##Tabs", ImGuiTabBarFlags.None))
|
||||
{
|
||||
foreach (SubSectionNode subSectionNode in _children)
|
||||
{
|
||||
if (ForceSelectedTabName != null)
|
||||
{
|
||||
bool a = subSectionNode.Name == ForceSelectedTabName; // no idea how this works
|
||||
ImGuiTabItemFlags flag = subSectionNode.Name == ForceSelectedTabName ? ImGuiTabItemFlags.SetSelected : ImGuiTabItemFlags.None;
|
||||
|
||||
if (!ImGui.BeginTabItem(subSectionNode.Name, ref a, flag))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ImGui.BeginTabItem(subSectionNode.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
DrawExportResetContextMenu(subSectionNode, subSectionNode.Name);
|
||||
|
||||
ImGui.BeginChild("subconfig value", new Vector2(0, 0), true);
|
||||
didReset |= subSectionNode.Draw(ref changed);
|
||||
ImGui.EndChild();
|
||||
|
||||
ImGui.EndTabItem();
|
||||
}
|
||||
|
||||
ImGui.EndTabBar();
|
||||
ForceSelectedTabName = null;
|
||||
}
|
||||
|
||||
if (popColors)
|
||||
{
|
||||
ImGui.PopStyleColor(2);
|
||||
}
|
||||
|
||||
didReset |= DrawResetModal();
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
|
||||
return didReset;
|
||||
}
|
||||
|
||||
public override void Save(string path)
|
||||
{
|
||||
foreach (SubSectionNode child in _children)
|
||||
{
|
||||
child.Save(Path.Combine(path, Name));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Load(string path)
|
||||
{
|
||||
foreach (SubSectionNode child in _children)
|
||||
{
|
||||
child.Load(Path.Combine(path, Name));
|
||||
}
|
||||
}
|
||||
|
||||
public ConfigPageNode? GetOrAddConfig<T>() where T : PluginConfigObject
|
||||
{
|
||||
object[] attributes = typeof(T).GetCustomAttributes(true);
|
||||
|
||||
foreach (object attribute in attributes)
|
||||
{
|
||||
if (attribute is SubSectionAttribute subSectionAttribute)
|
||||
{
|
||||
foreach (SubSectionNode subSectionNode in _children)
|
||||
{
|
||||
if (subSectionNode.Name == subSectionAttribute.SubSectionName)
|
||||
{
|
||||
return subSectionNode.GetOrAddConfig<T>();
|
||||
}
|
||||
}
|
||||
|
||||
if (subSectionAttribute.Depth == 0)
|
||||
{
|
||||
NestedSubSectionNode newNode = new();
|
||||
newNode.Name = subSectionAttribute.SubSectionName;
|
||||
newNode.Depth = 0;
|
||||
_children.Add(newNode);
|
||||
|
||||
return newNode.GetOrAddConfig<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Type type = typeof(T);
|
||||
throw new ArgumentException("The provided configuration object does not specify a sub-section: " + type.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
using HSUI.Config.Attributes;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace HSUI.Config.Tree
|
||||
{
|
||||
public abstract class SubSectionNode : Node
|
||||
{
|
||||
public string Name = null!;
|
||||
public int Depth;
|
||||
public string? ForceSelectedTabName = null;
|
||||
|
||||
public abstract bool Draw(ref bool changed);
|
||||
|
||||
public abstract ConfigPageNode? GetOrAddConfig<T>() where T : PluginConfigObject;
|
||||
}
|
||||
|
||||
public class NestedSubSectionNode : SubSectionNode
|
||||
{
|
||||
public NestedSubSectionNode() { }
|
||||
|
||||
public override bool Draw(ref bool changed)
|
||||
{
|
||||
bool didReset = false;
|
||||
|
||||
if (_children.Count > 1)
|
||||
{
|
||||
ImGui.BeginChild(
|
||||
"DelvUI_Tabs_" + Depth,
|
||||
new Vector2(0, ImGui.GetWindowHeight() - 22),
|
||||
false,
|
||||
ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse
|
||||
); // Leave room for 1 line below us
|
||||
|
||||
if (ImGui.BeginTabBar("##tabs" + Depth, ImGuiTabBarFlags.None))
|
||||
{
|
||||
didReset |= DrawSubConfig(ref changed);
|
||||
}
|
||||
|
||||
ImGui.EndTabBar();
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.BeginChild("item" + Depth + " view", new Vector2(0, ImGui.GetWindowHeight() - 20)); // Leave room for 1 line below us
|
||||
|
||||
didReset |= DrawSubConfig(ref changed);
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
return didReset;
|
||||
}
|
||||
|
||||
public bool DrawSubConfig(ref bool changed)
|
||||
{
|
||||
bool didReset = false;
|
||||
|
||||
foreach (SubSectionNode subSectionNode in _children)
|
||||
{
|
||||
if (subSectionNode is NestedSubSectionNode)
|
||||
{
|
||||
if (ForceSelectedTabName != null)
|
||||
{
|
||||
bool a = subSectionNode.Name == ForceSelectedTabName; // no idea how this works
|
||||
ImGuiTabItemFlags flag = subSectionNode.Name == ForceSelectedTabName ? ImGuiTabItemFlags.SetSelected : ImGuiTabItemFlags.None;
|
||||
|
||||
if (!ImGui.BeginTabItem(subSectionNode.Name, ref a, flag))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ImGui.BeginTabItem(subSectionNode.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
DrawExportResetContextMenu(subSectionNode, subSectionNode.Name);
|
||||
|
||||
ImGui.BeginChild("subconfig" + Depth + " value", new Vector2(0, ImGui.GetWindowHeight()));
|
||||
didReset |= subSectionNode.Draw(ref changed);
|
||||
ImGui.EndChild();
|
||||
|
||||
ImGui.EndTabItem();
|
||||
}
|
||||
else
|
||||
{
|
||||
didReset |= subSectionNode.Draw(ref changed);
|
||||
}
|
||||
}
|
||||
|
||||
ForceSelectedTabName = null;
|
||||
|
||||
didReset |= DrawResetModal();
|
||||
|
||||
return didReset;
|
||||
}
|
||||
|
||||
public override void Save(string path)
|
||||
{
|
||||
foreach (SubSectionNode child in _children)
|
||||
{
|
||||
child.Save(Path.Combine(path, Name));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Load(string path)
|
||||
{
|
||||
foreach (SubSectionNode child in _children)
|
||||
{
|
||||
child.Load(Path.Combine(path, Name));
|
||||
}
|
||||
}
|
||||
|
||||
public override ConfigPageNode? GetOrAddConfig<T>()
|
||||
{
|
||||
var type = typeof(T);
|
||||
if (type == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
object[] attributes = type.GetCustomAttributes(true);
|
||||
|
||||
foreach (object attribute in attributes)
|
||||
{
|
||||
if (attribute is SubSectionAttribute subSectionAttribute)
|
||||
{
|
||||
if (subSectionAttribute.Depth != Depth + 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (SubSectionNode subSectionNode in _children)
|
||||
{
|
||||
if (subSectionNode.Name == subSectionAttribute.SubSectionName)
|
||||
{
|
||||
return subSectionNode.GetOrAddConfig<T>();
|
||||
}
|
||||
}
|
||||
|
||||
NestedSubSectionNode nestedSubSectionNode = new();
|
||||
nestedSubSectionNode.Name = subSectionAttribute.SubSectionName;
|
||||
nestedSubSectionNode.Depth = Depth + 1;
|
||||
_children.Add(nestedSubSectionNode);
|
||||
|
||||
return nestedSubSectionNode.GetOrAddConfig<T>();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (SubSectionNode subSectionNode in _children)
|
||||
{
|
||||
if (subSectionNode.Name == type.FullName && subSectionNode is ConfigPageNode node)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
ConfigPageNode configPageNode = new();
|
||||
configPageNode.ConfigObject = ConfigurationManager.GetDefaultConfigObjectForType(type);
|
||||
configPageNode.Name = type.FullName!;
|
||||
_children.Add(configPageNode);
|
||||
|
||||
return configPageNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using Dalamud.Interface.Windowing;
|
||||
using HSUI.Helpers;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace HSUI.Config.Windows
|
||||
{
|
||||
public class ChangelogWindow : Window
|
||||
{
|
||||
public string Changelog { get; set; }
|
||||
private bool _needsToSetSize = true;
|
||||
|
||||
public bool AutoClose = false;
|
||||
private double _openTime = -1;
|
||||
|
||||
private bool _popColors = false;
|
||||
|
||||
public ChangelogWindow(string name, string changelog) : base(name)
|
||||
{
|
||||
Changelog = changelog;
|
||||
}
|
||||
|
||||
public override void PreDraw()
|
||||
{
|
||||
if (ConfigurationManager.Instance.OverrideDalamudStyle)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.WindowBg, new Vector4(10f / 255f, 10f / 255f, 10f / 255f, 0.95f));
|
||||
_popColors = true;
|
||||
}
|
||||
|
||||
if (_needsToSetSize)
|
||||
{
|
||||
float height = ImGui.CalcTextSize(Changelog).Y + 100;
|
||||
ImGui.SetNextWindowSize(new Vector2(500, Math.Min(height, 500)), ImGuiCond.FirstUseEver);
|
||||
_needsToSetSize = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
Vector2 size = ImGui.GetWindowSize();
|
||||
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + size.X - 24);
|
||||
ImGui.TextWrapped(Changelog);
|
||||
|
||||
if (AutoClose &&
|
||||
_openTime > 0 &&
|
||||
ImGui.GetTime() - _openTime > 10)
|
||||
{
|
||||
IsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostDraw()
|
||||
{
|
||||
if (_popColors)
|
||||
{
|
||||
ImGui.PopStyleColor();
|
||||
_popColors = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnOpen()
|
||||
{
|
||||
_openTime = ImGui.GetTime();
|
||||
}
|
||||
|
||||
public override void OnClose()
|
||||
{
|
||||
if (AutoClose && InputsHelper.Instance != null)
|
||||
{
|
||||
AutoClose = false;
|
||||
Plugin.LoadTime = ImGui.GetTime() - InputsHelper.InitializationDelay;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Dalamud.Interface.Windowing;
|
||||
using HSUI.Interface.GeneralElements;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System.Numerics;
|
||||
|
||||
namespace HSUI.Config.Windows
|
||||
{
|
||||
public class GridWindow : Window
|
||||
{
|
||||
private bool _popColors = false;
|
||||
public GridWindow(string name) : base(name)
|
||||
{
|
||||
Flags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoScrollWithMouse;
|
||||
Size = new Vector2(300, 200);
|
||||
}
|
||||
|
||||
public override void OnClose()
|
||||
{
|
||||
ConfigurationManager.Instance.LockHUD = true;
|
||||
}
|
||||
|
||||
public override void PreDraw()
|
||||
{
|
||||
if (ConfigurationManager.Instance.OverrideDalamudStyle)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.WindowBg, new Vector4(10f / 255f, 10f / 255f, 10f / 255f, 0.95f));
|
||||
_popColors = true;
|
||||
}
|
||||
|
||||
ImGui.SetNextWindowFocus();
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
var configManager = ConfigurationManager.Instance;
|
||||
var node = configManager.GetConfigPageNode<GridConfig>();
|
||||
if (node == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.PushItemWidth(150);
|
||||
bool changed = false;
|
||||
node.Draw(ref changed);
|
||||
}
|
||||
|
||||
public override void PostDraw()
|
||||
{
|
||||
if (_popColors)
|
||||
{
|
||||
ImGui.PopStyleColor();
|
||||
_popColors = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Logging;
|
||||
using HSUI.Config.Tree;
|
||||
using HSUI.Interface.GeneralElements;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace HSUI.Config.Windows
|
||||
{
|
||||
public class MainConfigWindow : Window
|
||||
{
|
||||
public BaseNode? node { get; set; }
|
||||
public Action? CloseAction;
|
||||
|
||||
private float _alpha = 1f;
|
||||
private Vector2 _lastWindowPos = Vector2.Zero;
|
||||
private Vector2 _size = new Vector2(1050, 750);
|
||||
|
||||
private bool _popColors = false;
|
||||
|
||||
public MainConfigWindow(string name) : base(name)
|
||||
{
|
||||
Flags = ImGuiWindowFlags.NoTitleBar;
|
||||
Size = _size;
|
||||
SizeCondition = ImGuiCond.FirstUseEver;
|
||||
}
|
||||
|
||||
public override void OnClose()
|
||||
{
|
||||
CloseAction?.Invoke();
|
||||
}
|
||||
|
||||
private bool CheckWindowFocus()
|
||||
{
|
||||
Vector2 mousePos = ImGui.GetMousePos();
|
||||
Vector2 endPos = _lastWindowPos + _size;
|
||||
|
||||
return mousePos.X >= _lastWindowPos.X && mousePos.X <= endPos.X &&
|
||||
mousePos.Y >= _lastWindowPos.Y && mousePos.Y <= endPos.Y;
|
||||
}
|
||||
|
||||
public override void PreDraw()
|
||||
{
|
||||
_alpha = 1;
|
||||
|
||||
HUDOptionsConfig? config = ConfigurationManager.Instance.GetConfigObject<HUDOptionsConfig>();
|
||||
if (config?.DimConfigWindow == true)
|
||||
{
|
||||
_alpha = CheckWindowFocus() ? 1 : 0.5f;
|
||||
}
|
||||
|
||||
if (ConfigurationManager.Instance.OverrideDalamudStyle)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Border, new Vector4(0f / 255f, 0f / 255f, 0f / 255f, _alpha));
|
||||
ImGui.PushStyleColor(ImGuiCol.BorderShadow, new Vector4(0f / 255f, 0f / 255f, 0f / 255f, _alpha));
|
||||
ImGui.PushStyleColor(ImGuiCol.WindowBg, new Vector4(20f / 255f, 21f / 255f, 20f / 255f, _alpha));
|
||||
_popColors = true;
|
||||
}
|
||||
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 1);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 1);
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
_lastWindowPos = ImGui.GetWindowPos();
|
||||
|
||||
if (_popColors)
|
||||
{
|
||||
ImGui.PopStyleColor(3);
|
||||
_popColors = false;
|
||||
}
|
||||
|
||||
ImGui.PopStyleVar(2);
|
||||
|
||||
node?.Draw(_alpha);
|
||||
|
||||
_size = ImGui.GetWindowSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user