From a812b41187d1077ec8787caeef5f334a38bfdc24 Mon Sep 17 00:00:00 2001 From: Wah <74884113+Brappp@users.noreply.github.com> Date: Mon, 2 Sep 2024 10:08:03 -0400 Subject: [PATCH] imgui hell --- BossMod/Config/ConfigAboutTab.cs | 126 +++++++++++++++----- BossMod/Config/ConfigUI.cs | 198 ++++++++++++++++++++++--------- 2 files changed, 238 insertions(+), 86 deletions(-) diff --git a/BossMod/Config/ConfigAboutTab.cs b/BossMod/Config/ConfigAboutTab.cs index 0076390e53..65fd4f26af 100644 --- a/BossMod/Config/ConfigAboutTab.cs +++ b/BossMod/Config/ConfigAboutTab.cs @@ -1,52 +1,118 @@ -using Dalamud.Interface.Utility.Raii; +using Dalamud.Interface; +using Dalamud.Interface.Utility; using ImGuiNET; using System.Diagnostics; +using System.Numerics; namespace BossMod; public sealed class ConfigAboutTab { - // Colors - private readonly Color DiscordColor = Color.FromComponents(88, 101, 242); + private static readonly Vector4 titleColor = new Vector4(1.0f, 0.65f, 0.0f, 1.0f); + private static readonly Vector4 textColor = ImGui.GetStyle().Colors[(int)ImGuiCol.Text]; + private static readonly Vector4 sectionBgColor = new Vector4(0.15f, 0.15f, 0.15f, 1.0f); + private static readonly Vector4 borderColor = new Vector4(0.7f, 0.7f, 0.7f, 0.8f); + private static readonly Vector4 discordButtonColor = new Vector4(0.34f, 0.44f, 0.88f, 1.0f); + private static readonly Vector4 buttonColor = new Vector4(0.2f, 0.5f, 0.8f, 1.0f); + private static readonly ImFontPtr titleFont = ImGui.GetIO().Fonts.AddFontFromFileTTF("your-font-path.ttf", 28.0f); - public void Draw() + public static void Draw() { - using var wrap = ImRaii.TextWrapPos(0); - Section("", "Boss Mod (vbm) provides boss fight radar, auto-rotation, cooldown planning, and AI. All of the its modules can be toggled individually. Support for it can be found in the Discord server linked at the bottom of this tab."); - ImGui.NewLine(); - Section("Radar", "The radar provides an on-screen window that contains area mini-map that shows player positions, boss position(s), various imminent AoEs, and other mechanics. This is useful because you don't have to remember what ability names mean and you can see exactly whether you're getting clipped by incoming AoE or not. It is only enabled for supported duties, which are visible in the \"Supported duties\" tab."); - ImGui.NewLine(); - Section("Autorotation", "Autorotation will execute fully optimal rotations to the best of its ability. When you go to the \"Autorotation Presets\" tab to create a preset, the maturity of each rotation module is present in a tooltip. A guide for using this feature can be found on the project's GitHub wiki, which is linked at the bottom of this tab."); - ImGui.NewLine(); - Section("CD Planner", "As long as a boss has a module supporting it, a CD plan can be created for it. This feature replaces autorotations in specific fights and allows you to time specific abilities to cast at specific times. A guide for using this feature can be found on the project's GitHub wiki, which is linked at the bottom of this tab."); - ImGui.NewLine(); - Section("AI", "The AI module was created to automate movement during boss fights. It can be hooked by other plugins to automate entire duties, or be used on its own to make a fight easier. It will automatically move your character based on safe zones determined by a boss's module, which are visible on the radar."); - - ImGui.NewLine(); - using (ImRaii.PushColor(ImGuiCol.Button, DiscordColor.ABGR)) - LinkButton("Puni.sh Discord", "https://discord.gg/punishxiv"); + ImGui.TextWrapped("Boss Mod (vbm) provides boss fight radar, auto-rotation, cooldown planning, and AI. All of its modules can be toggled individually. Support for it can be found in the Discord server linked at the bottom of this tab."); + ImGui.Spacing(); - ImGui.SameLine(); - LinkButton("Boss Mod Repository", "https://github.com/awgil/ffxiv_bossmod"); + DrawSection("Radar", new[] + { + "Provides an on-screen window that contains an area mini-map showing player positions, boss position(s), various imminent AoEs, and other mechanics.", + "Useful because you don't have to remember what ability names mean.", + "See exactly whether you're getting clipped by incoming AoE or not.", + "Enabled for supported duties, visible in the \"Supported Duties\" tab." + }); + + DrawSection("Autorotation", new[] + { + "Executes fully optimal rotations to the best of its ability.", + "Go to the \"Autorotation Presets\" tab to create a preset.", + "Maturity of each rotation module is present in a tooltip.", + "Guide for using this feature can be found on the project's GitHub wiki." + }); + + DrawSection("CD Planner", new[] + { + "Creates a CD plan for bosses with supporting modules.", + "Replaces autorotations in specific fights.", + "Allows you to time specific abilities to cast at specific times.", + "Guide for using this feature can be found on the project's GitHub wiki." + }); + DrawSection("AI", new[] + { + "Automates movement during boss fights.", + "Can be hooked by other plugins to automate entire duties.", + "Can be used on its own to make a fight easier.", + "Automatically moves your character based on safe zones determined by a boss's module, visible on the radar." + }); + + ImGui.Spacing(); + + float buttonWidth = 180f; + DrawDiscordButton("Puni.sh Discord", "https://discord.gg/punishxiv", buttonWidth); ImGui.SameLine(); - LinkButton("Boss Mod Wiki Tutorials", "https://github.com/awgil/ffxiv_bossmod/wiki"); + DrawButton("Boss Mod Repository", "https://github.com/awgil/ffxiv_bossmod", buttonWidth); + ImGui.SameLine(); + DrawButton("Boss Mod Wiki Tutorials", "https://github.com/awgil/ffxiv_bossmod/wiki", buttonWidth); + } + + private static void DrawSection(string title, string[] bulletPoints) + { + ImGui.PushStyleColor(ImGuiCol.ChildBg, sectionBgColor); + ImGui.PushStyleColor(ImGuiCol.Border, borderColor); + ImGui.BeginChild(title, new Vector2(0, 150), false, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.AlwaysUseWindowPadding); + + ImGui.PushFont(titleFont); + ImGui.PushStyleColor(ImGuiCol.Text, titleColor); + ImGui.TextUnformatted(title); + ImGui.PopStyleColor(); + ImGui.PopFont(); + + ImGui.Separator(); + + ImGui.PushStyleColor(ImGuiCol.Text, textColor); + foreach (var point in bulletPoints) + { + ImGui.Bullet(); + ImGui.SameLine(); + ImGui.TextWrapped(point); + } + ImGui.PopStyleColor(); + + ImGui.EndChild(); + ImGui.PopStyleColor(2); + + ImGui.Spacing(); } - private void Section(string title, string text) + private static void DrawButton(string label, string url, float width) { - if (title.Length > 0) + ImGui.PushStyleColor(ImGuiCol.Button, buttonColor); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Lerp(buttonColor, Vector4.One, 0.2f)); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Lerp(buttonColor, Vector4.One, 0.4f)); + if (ImGui.Button(label, new Vector2(width, 0))) { - // TODO: Make a separate and larger font for the headings - using var headingFont = ImRaii.PushFont(ImGui.GetFont()); - ImGui.TextUnformatted(title); + Process.Start("explorer.exe", url); } - ImGui.TextUnformatted(text); + ImGui.PopStyleColor(3); } - private void LinkButton(string label, string link) + private static void DrawDiscordButton(string label, string url, float width) { - if (ImGui.Button(label)) - Process.Start("explorer.exe", link); + ImGui.PushStyleColor(ImGuiCol.Button, discordButtonColor); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Lerp(discordButtonColor, Vector4.One, 0.2f)); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Lerp(discordButtonColor, Vector4.One, 0.4f)); + if (ImGui.Button(label, new Vector2(width, 0))) + { + Process.Start("explorer.exe", url); + } + ImGui.PopStyleColor(3); } } diff --git a/BossMod/Config/ConfigUI.cs b/BossMod/Config/ConfigUI.cs index 1d7b04dcb6..78f97aaa89 100644 --- a/BossMod/Config/ConfigUI.cs +++ b/BossMod/Config/ConfigUI.cs @@ -2,24 +2,30 @@ using Dalamud.Interface.Utility.Raii; using ImGuiNET; using System.Reflection; +using System.Numerics; namespace BossMod; public sealed class ConfigUI : IDisposable { - private class UINode(ConfigNode node) + private class UINode { - public ConfigNode Node = node; - public string Name = ""; + public ConfigNode Node; + public string Name; public int Order; public UINode? Parent; - public List Children = []; + public List Children = new(); + + public UINode(ConfigNode node) + { + Node = node; + Name = string.Empty; + } } - private readonly List _roots = []; + private readonly List _roots = new(); private readonly UITree _tree = new(); private readonly UITabs _tabs = new(); - private readonly ConfigAboutTab _about = new(); private readonly ModuleViewer _mv; private readonly ConfigRoot _root; private readonly WorldState _ws; @@ -31,12 +37,13 @@ public ConfigUI(ConfigRoot config, WorldState ws, RotationDatabase? rotationDB) _ws = ws; _mv = new(rotationDB?.Plans, ws); _presets = rotationDB != null ? new(rotationDB.Presets) : null; - _tabs.Add("About", _about.Draw); - _tabs.Add("Settings", () => DrawNodes(_roots)); - _tabs.Add("Supported Bosses", () => _mv.Draw(_tree, _ws)); + + _tabs.Add("About", ConfigAboutTab.Draw); + _tabs.Add("Settings", DrawSettings); + _tabs.Add("Supported Duties", () => _mv.Draw(_tree, _ws)); _tabs.Add("Autorotation Presets", () => _presets?.Draw()); - Dictionary nodes = []; + var nodes = new Dictionary(); foreach (var n in config.Nodes) { nodes[n.GetType()] = new(n); @@ -47,13 +54,16 @@ public ConfigUI(ConfigRoot config, WorldState ws, RotationDatabase? rotationDB) var props = t.GetCustomAttribute(); n.Name = props?.Name ?? GenerateNodeName(t); n.Order = props?.Order ?? 0; - if (props?.Parent != null) - n.Parent = nodes.GetValueOrDefault(props.Parent); + n.Parent = props?.Parent != null ? nodes.GetValueOrDefault(props.Parent) : null; if (n.Parent != null) + { n.Parent.Children.Add(n); + } else + { _roots.Add(n); + } } SortByOrder(_roots); @@ -68,36 +78,76 @@ public void Dispose() public void Draw() { + var style = ImGui.GetStyle(); + var originalStyle = new ImGuiStyleSnapshot(style); + + ApplyCustomStyling(); + _tabs.Draw(); + + originalStyle.Restore(style); + } + + private void DrawSettings() + { + if (ImGui.BeginChild("SettingsWindow", new Vector2(0, 0), true)) + { + foreach (var node in _tree.Nodes(_roots, n => new(n.Name))) + { + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 5); + DrawNode(node.Node, _root, _tree, _ws); + DrawNodes(node.Children); + } + ImGui.EndChild(); + } } public static void DrawNode(ConfigNode node, ConfigRoot root, UITree tree, WorldState ws) { - // draw standard properties foreach (var field in node.GetType().GetFields()) { var props = field.GetCustomAttribute(); if (props == null) continue; + var cursor = ImGui.GetCursorPosY(); + ImGui.SetCursorPosY(cursor + ImGui.GetStyle().FramePadding.Y); + if (!string.IsNullOrEmpty(props.Tooltip)) + { + UIMisc.HelpMarker(props.Tooltip); + } + else + { + using var invisible = ImRaii.PushColor(ImGuiCol.Text, 0x00000000); + UIMisc.IconText(Dalamud.Interface.FontAwesomeIcon.InfoCircle, "(?)"); + } + ImGui.SameLine(); + ImGui.SetCursorPosY(cursor); + var value = field.GetValue(node); - if (DrawProperty(props.Label, props.Tooltip, node, field, value, root, tree, ws)) + if (DrawProperty(props.Label, node, field, value, root, tree, ws)) { node.Modified.Fire(); } } - // draw custom stuff node.DrawCustom(tree, ws); } - private static string GenerateNodeName(Type t) => t.Name.EndsWith("Config", StringComparison.Ordinal) ? t.Name.Remove(t.Name.Length - "Config".Length) : t.Name; + private static string GenerateNodeName(Type t) + { + return t.Name.EndsWith("Config", StringComparison.Ordinal) + ? t.Name[..^"Config".Length] + : t.Name; + } private void SortByOrder(List nodes) { - nodes.SortBy(e => e.Order); + nodes.Sort((a, b) => a.Order.CompareTo(b.Order)); foreach (var n in nodes) + { SortByOrder(n.Children); + } } private void DrawNodes(List nodes) @@ -109,39 +159,20 @@ private void DrawNodes(List nodes) } } - private static void DrawHelp(string tooltip) + private static bool DrawProperty(string label, ConfigNode node, FieldInfo member, object? value, ConfigRoot root, UITree tree, WorldState ws) => value switch { - // draw tooltip marker with proper alignment - var cursor = ImGui.GetCursorPosY(); - ImGui.SetCursorPosY(cursor + ImGui.GetStyle().FramePadding.Y); - if (tooltip.Length > 0) - { - UIMisc.HelpMarker(tooltip); - } - else - { - using var invisible = ImRaii.PushColor(ImGuiCol.Text, 0x00000000); - UIMisc.IconText(Dalamud.Interface.FontAwesomeIcon.InfoCircle, "(?)"); - } - ImGui.SameLine(); - ImGui.SetCursorPosY(cursor); - } - - private static bool DrawProperty(string label, string tooltip, ConfigNode node, FieldInfo member, object? value, ConfigRoot root, UITree tree, WorldState ws) => value switch - { - bool v => DrawProperty(label, tooltip, node, member, v), - Enum v => DrawProperty(label, tooltip, node, member, v), - float v => DrawProperty(label, tooltip, node, member, v), - int v => DrawProperty(label, tooltip, node, member, v), - Color v => DrawProperty(label, tooltip, node, member, v), - Color[] v => DrawProperty(label, tooltip, node, member, v), - GroupAssignment v => DrawProperty(label, tooltip, node, member, v, root, tree, ws), + bool v => DrawProperty(label, node, member, v), + Enum v => DrawProperty(label, node, member, v), + float v => DrawProperty(label, node, member, v), + int v => DrawProperty(label, node, member, v), + Color v => DrawProperty(label, node, member, v), + Color[] v => DrawProperty(label, node, member, v), + GroupAssignment v => DrawProperty(label, node, member, v, root, tree, ws), _ => false }; - private static bool DrawProperty(string label, string tooltip, ConfigNode node, FieldInfo member, bool v) + private static bool DrawProperty(string label, ConfigNode node, FieldInfo member, bool v) { - DrawHelp(tooltip); var combo = member.GetCustomAttribute(); if (combo != null) { @@ -162,9 +193,8 @@ private static bool DrawProperty(string label, string tooltip, ConfigNode node, return false; } - private static bool DrawProperty(string label, string tooltip, ConfigNode node, FieldInfo member, Enum v) + private static bool DrawProperty(string label, ConfigNode node, FieldInfo member, Enum v) { - DrawHelp(tooltip); if (UICombo.Enum(label, ref v)) { member.SetValue(node, v); @@ -173,9 +203,8 @@ private static bool DrawProperty(string label, string tooltip, ConfigNode node, return false; } - private static bool DrawProperty(string label, string tooltip, ConfigNode node, FieldInfo member, float v) + private static bool DrawProperty(string label, ConfigNode node, FieldInfo member, float v) { - DrawHelp(tooltip); var slider = member.GetCustomAttribute(); if (slider != null) { @@ -199,9 +228,8 @@ private static bool DrawProperty(string label, string tooltip, ConfigNode node, return false; } - private static bool DrawProperty(string label, string tooltip, ConfigNode node, FieldInfo member, int v) + private static bool DrawProperty(string label, ConfigNode node, FieldInfo member, int v) { - DrawHelp(tooltip); var slider = member.GetCustomAttribute(); if (slider != null) { @@ -225,9 +253,8 @@ private static bool DrawProperty(string label, string tooltip, ConfigNode node, return false; } - private static bool DrawProperty(string label, string tooltip, ConfigNode node, FieldInfo member, Color v) + private static bool DrawProperty(string label, ConfigNode node, FieldInfo member, Color v) { - DrawHelp(tooltip); var col = v.ToFloat4(); if (ImGui.ColorEdit4(label, ref col, ImGuiColorEditFlags.PickerHueWheel)) { @@ -237,12 +264,11 @@ private static bool DrawProperty(string label, string tooltip, ConfigNode node, return false; } - private static bool DrawProperty(string label, string tooltip, ConfigNode node, FieldInfo member, Color[] v) + private static bool DrawProperty(string label, ConfigNode node, FieldInfo member, Color[] v) { var modified = false; for (int i = 0; i < v.Length; ++i) { - DrawHelp(tooltip); var col = v[i].ToFloat4(); if (ImGui.ColorEdit4($"{label} {i}", ref col, ImGuiColorEditFlags.PickerHueWheel)) { @@ -254,7 +280,7 @@ private static bool DrawProperty(string label, string tooltip, ConfigNode node, return modified; } - private static bool DrawProperty(string label, string tooltip, ConfigNode node, FieldInfo member, GroupAssignment v, ConfigRoot root, UITree tree, WorldState ws) + private static bool DrawProperty(string label, ConfigNode node, FieldInfo member, GroupAssignment v, ConfigRoot root, UITree tree, WorldState ws) { var group = member.GetCustomAttribute(); if (group == null) @@ -315,4 +341,64 @@ private static void DrawPropertyContextMenu(ConfigNode node, FieldInfo member, G } } } + + private void ApplyCustomStyling() + { + ImGuiStylePtr style = ImGui.GetStyle(); + style.FramePadding = new Vector2(8, 4); + style.ItemSpacing = new Vector2(10, 6); + style.WindowPadding = new Vector2(10, 10); + style.FrameRounding = 4.0f; + style.GrabRounding = 4.0f; + + style.Colors[(int)ImGuiCol.WindowBg] = new Vector4(0.15f, 0.15f, 0.15f, 1.0f); + style.Colors[(int)ImGuiCol.FrameBg] = new Vector4(0.20f, 0.20f, 0.20f, 1.0f); + style.Colors[(int)ImGuiCol.TitleBg] = new Vector4(0.09f, 0.30f, 0.50f, 1.0f); + style.Colors[(int)ImGuiCol.TitleBgActive] = new Vector4(0.12f, 0.40f, 0.60f, 1.0f); + style.Colors[(int)ImGuiCol.Button] = new Vector4(0.0f, 0.48f, 0.8f, 0.6f); + style.Colors[(int)ImGuiCol.ButtonHovered] = new Vector4(0.0f, 0.60f, 0.9f, 0.8f); + style.Colors[(int)ImGuiCol.ButtonActive] = new Vector4(0.0f, 0.72f, 1.0f, 1.0f); + style.Colors[(int)ImGuiCol.Header] = new Vector4(0.0f, 0.48f, 0.8f, 0.7f); + style.Colors[(int)ImGuiCol.HeaderHovered] = new Vector4(0.0f, 0.60f, 0.9f, 0.8f); + style.Colors[(int)ImGuiCol.HeaderActive] = new Vector4(0.0f, 0.72f, 1.0f, 0.9f); + } +} + +public class ImGuiStyleSnapshot +{ + public Vector2 FramePadding { get; } + public Vector2 ItemSpacing { get; } + public Vector2 WindowPadding { get; } + public float FrameRounding { get; } + public float GrabRounding { get; } + public Vector4[] Colors { get; } + + public ImGuiStyleSnapshot(ImGuiStylePtr style) + { + FramePadding = style.FramePadding; + ItemSpacing = style.ItemSpacing; + WindowPadding = style.WindowPadding; + FrameRounding = style.FrameRounding; + GrabRounding = style.GrabRounding; + + Colors = new Vector4[style.Colors.Count]; + for (int i = 0; i < style.Colors.Count; i++) + { + Colors[i] = style.Colors[i]; + } + } + + public void Restore(ImGuiStylePtr style) + { + style.FramePadding = FramePadding; + style.ItemSpacing = ItemSpacing; + style.WindowPadding = WindowPadding; + style.FrameRounding = FrameRounding; + style.GrabRounding = GrabRounding; + + for (int i = 0; i < Colors.Length; i++) + { + style.Colors[i] = Colors[i]; + } + } }