From ee63f6087796c69db922b849008169f561d10e20 Mon Sep 17 00:00:00 2001 From: ItsBexy <103910869+ItsBexy@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:36:27 -0700 Subject: [PATCH] Update UIDebug2, ImGuiComponents, ImGuiHelpers (#2081) * Update ImGuiComponents & ImGuiHelpers Took some helper functions created for `UiDebug2`, and incorporated them into `ImGuiComponents` / `ImGuiHelpers` instead - `IconButton()` (and its various overloads) now includes an optional size parameter - `IconButtonSelect()` has been added, allowing a row or grid of IconButtons to serve as a radio-like input - `HelpMarker()` now includes an optional color parameter - `ClickToCopyText()` now includes an optional color parameter, and includes the `FontAwesome.Copy` icon in the tooltip. - Implemented ImRaii in these files These changes are intended not to break any existing calls in plugins or within Dalamud itself. * Fix ambiguous overloads * UiDebug2 Updates - Fixed XY coordinate display - Added AtkValue table to AddonTree display - Restored old behaviour wherein the Addon display initially only shows the Root node and a collapsed node list - The above should also fix the Element Selector / Search behaviour, allowing it to scroll correctly to the searched node - Now displays field offsets for any node/component whose pointer exists in the addon struct - Tidied up node tree headers by removing memory addresses (they're still readable in the opened tree, of course) --- .../ImGuiComponents.ColorPickerWithPalette.cs | 8 +- .../ImGuiComponents.DisabledButton.cs | 54 ++-- .../Components/ImGuiComponents.HelpMarker.cs | 39 ++- .../Components/ImGuiComponents.IconButton.cs | 284 +++++++++++------- .../ImGuiComponents.IconButtonSelect.cs | 87 ++++++ .../ImGuiComponents.TextWithLabel.cs | 10 +- .../ImGuiComponents.ToggleSwitch.cs | 7 +- .../UiDebug2/Browsing/AddonTree.AtkValues.cs | 124 ++++++++ .../UiDebug2/Browsing/AddonTree.FieldNames.cs | 12 + .../Internal/UiDebug2/Browsing/AddonTree.cs | 53 ++-- .../Internal/UiDebug2/Browsing/Events.cs | 8 +- .../UiDebug2/Browsing/NodeTree.Component.cs | 38 ++- .../UiDebug2/Browsing/NodeTree.Editor.cs | 5 +- .../UiDebug2/Browsing/NodeTree.Res.cs | 55 +++- .../Internal/UiDebug2/ElementSelector.cs | 2 +- .../Internal/UiDebug2/Utility/Gui.cs | 99 ++---- .../Internal/UiDebug2/Utility/NodeBounds.cs | 10 +- Dalamud/Interface/Utility/ImGuiHelpers.cs | 32 +- 18 files changed, 649 insertions(+), 278 deletions(-) create mode 100644 Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs diff --git a/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs b/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs index aa707aecb..e2f68eab2 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs @@ -1,6 +1,8 @@ using System.Numerics; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + using ImGuiNET; namespace Dalamud.Interface.Components; @@ -41,7 +43,9 @@ public static partial class ImGuiComponents ImGui.OpenPopup($"###ColorPickerPopup{id}"); } - if (ImGui.BeginPopup($"###ColorPickerPopup{id}")) + using var popup = ImRaii.Popup($"###ColorPickerPopup{id}"); + + if (popup) { if (ImGui.ColorPicker4($"###ColorPicker{id}", ref existingColor, flags)) { @@ -61,8 +65,6 @@ public static partial class ImGuiComponents ImGui.SameLine(); } } - - ImGui.EndPopup(); } return selectedColor; diff --git a/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs b/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs index 907ad0aeb..ab2ed4724 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs @@ -1,5 +1,7 @@ using System.Numerics; +using Dalamud.Interface.Utility.Raii; + using ImGuiNET; namespace Dalamud.Interface.Components; @@ -21,17 +23,16 @@ public static partial class ImGuiComponents /// Indicator if button is clicked. public static bool DisabledButton(FontAwesomeIcon icon, int? id = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) { - ImGui.PushFont(UiBuilder.IconFont); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + var text = icon.ToIconString(); + if (id.HasValue) + { + text = $"{text}##{id}"; + } - var text = icon.ToIconString(); - if (id.HasValue) - text = $"{text}##{id}"; - - var button = DisabledButton(text, defaultColor, activeColor, hoveredColor, alphaMult); - - ImGui.PopFont(); - - return button; + return DisabledButton(text, defaultColor, activeColor, hoveredColor, alphaMult); + } } /// @@ -45,31 +46,28 @@ public static partial class ImGuiComponents /// Indicator if button is clicked. public static bool DisabledButton(string labelWithId, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) { + using var col = new ImRaii.Color(); + if (defaultColor.HasValue) - ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); + { + col.Push(ImGuiCol.Button, defaultColor.Value); + } if (activeColor.HasValue) - ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); + { + col.Push(ImGuiCol.ButtonActive, activeColor.Value); + } if (hoveredColor.HasValue) - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); + { + col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value); + } var style = ImGui.GetStyle(); - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, style.Alpha * alphaMult); - var button = ImGui.Button(labelWithId); - - ImGui.PopStyleVar(); - - if (defaultColor.HasValue) - ImGui.PopStyleColor(); - - if (activeColor.HasValue) - ImGui.PopStyleColor(); - - if (hoveredColor.HasValue) - ImGui.PopStyleColor(); - - return button; + using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, style.Alpha * alphaMult)) + { + return ImGui.Button(labelWithId); + } } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs b/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs index f0ecf2f2f..3392136d1 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs @@ -1,3 +1,7 @@ +using Dalamud.Interface.Utility.Raii; + +using FFXIVClientStructs.FFXIV.Common.Math; + using ImGuiNET; namespace Dalamud.Interface.Components; @@ -18,17 +22,32 @@ public static partial class ImGuiComponents /// /// The text to display on hover. /// The icon to use. - public static void HelpMarker(string helpText, FontAwesomeIcon icon) + /// The color of the icon. + public static void HelpMarker(string helpText, FontAwesomeIcon icon, Vector4? color = null) { + using var col = new ImRaii.Color(); + + if (color.HasValue) + { + col.Push(ImGuiCol.TextDisabled, color.Value); + } + ImGui.SameLine(); - ImGui.PushFont(UiBuilder.IconFont); - ImGui.TextDisabled(icon.ToIconString()); - ImGui.PopFont(); - if (!ImGui.IsItemHovered()) return; - ImGui.BeginTooltip(); - ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f); - ImGui.TextUnformatted(helpText); - ImGui.PopTextWrapPos(); - ImGui.EndTooltip(); + + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + ImGui.TextDisabled(icon.ToIconString()); + } + + if (ImGui.IsItemHovered()) + { + using (ImRaii.Tooltip()) + { + using (ImRaii.TextWrapPos(ImGui.GetFontSize() * 35.0f)) + { + ImGui.TextUnformatted(helpText); + } + } + } } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs index dc2c99608..5e64fe463 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs @@ -1,6 +1,8 @@ using System.Numerics; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + using ImGuiNET; namespace Dalamud.Interface.Components; @@ -15,8 +17,26 @@ public static partial class ImGuiComponents /// /// The icon for the button. /// Indicator if button is clicked. - public static bool IconButton(FontAwesomeIcon icon) - => IconButton(icon, null, null, null); + public static bool IconButton(FontAwesomeIcon icon) => IconButton(icon, null); + + /// + /// IconButton component to use an icon as a button. + /// + /// The icon for the button. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(FontAwesomeIcon icon, Vector2 size) => IconButton(icon, null, null, null, size); + + /// + /// IconButton component to use an icon as a button. + /// + /// The icon for the button. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(FontAwesomeIcon icon, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) => IconButton($"{icon.ToIconString()}", defaultColor, activeColor, hoveredColor, size); /// /// IconButton component to use an icon as a button. @@ -24,8 +44,28 @@ public static partial class ImGuiComponents /// The ID of the button. /// The icon for the button. /// Indicator if button is clicked. - public static bool IconButton(int id, FontAwesomeIcon icon) - => IconButton(id, icon, null, null, null); + public static bool IconButton(int id, FontAwesomeIcon icon) => IconButton(id, icon, null); + + /// + /// IconButton component to use an icon as a button. + /// + /// The ID of the button. + /// The icon for the button. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(int id, FontAwesomeIcon icon, Vector2 size) => IconButton(id, icon, null, null, null, size); + + /// + /// IconButton component to use an icon as a button with color options. + /// + /// The ID of the button. + /// The icon for the button. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(int id, FontAwesomeIcon icon, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) => IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor, size); /// /// IconButton component to use an icon as a button. @@ -33,51 +73,45 @@ public static partial class ImGuiComponents /// The ID of the button. /// The icon for the button. /// Indicator if button is clicked. - public static bool IconButton(string id, FontAwesomeIcon icon) - => IconButton(id, icon, null, null, null); + public static bool IconButton(string id, FontAwesomeIcon icon) => IconButton(id, icon, null); + + /// + /// IconButton component to use an icon as a button. + /// + /// The ID of the button. + /// The icon for the button. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(string id, FontAwesomeIcon icon, Vector2 size) + => IconButton(id, icon, null, null, null, size); + + /// + /// IconButton component to use an icon as a button with color options. + /// + /// The ID of the button. + /// The icon for the button. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(string id, FontAwesomeIcon icon, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) + => IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor, size); /// /// IconButton component to use an icon as a button. /// /// Text already containing the icon string. /// Indicator if button is clicked. - public static bool IconButton(string iconText) - => IconButton(iconText, null, null, null); + public static bool IconButton(string iconText) => IconButton(iconText, null); /// /// IconButton component to use an icon as a button. /// - /// The icon for the button. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. + /// Text already containing the icon string. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. /// Indicator if button is clicked. - public static bool IconButton(FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) - => IconButton($"{icon.ToIconString()}", defaultColor, activeColor, hoveredColor); - - /// - /// IconButton component to use an icon as a button with color options. - /// - /// The ID of the button. - /// The icon for the button. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. - /// Indicator if button is clicked. - public static bool IconButton(int id, FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) - => IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor); - - /// - /// IconButton component to use an icon as a button with color options. - /// - /// The ID of the button. - /// The icon for the button. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. - /// Indicator if button is clicked. - public static bool IconButton(string id, FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) - => IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor); + public static bool IconButton(string iconText, Vector2 size) => IconButton(iconText, null, null, null, size); /// /// IconButton component to use an icon as a button with color options. @@ -86,62 +120,72 @@ public static partial class ImGuiComponents /// The default color of the button. /// The color of the button when active. /// The color of the button when hovered. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. /// Indicator if button is clicked. - public static bool IconButton(string iconText, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + public static bool IconButton(string iconText, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) { - var numColors = 0; + using var col = new ImRaii.Color(); if (defaultColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); - numColors++; + col.Push(ImGuiCol.Button, defaultColor.Value); } if (activeColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); - numColors++; + col.Push(ImGuiCol.ButtonActive, activeColor.Value); } if (hoveredColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); - numColors++; + col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value); + } + + if (size.HasValue) + { + size *= ImGuiHelpers.GlobalScale; } var icon = iconText; - if (icon.Contains("#")) - icon = icon[..icon.IndexOf("#", StringComparison.Ordinal)]; + if (icon.Contains('#')) + { + icon = icon[..icon.IndexOf('#', StringComparison.Ordinal)]; + } - ImGui.PushID(iconText); + bool button; - ImGui.PushFont(UiBuilder.IconFont); - var iconSize = ImGui.CalcTextSize(icon); - ImGui.PopFont(); - - var dl = ImGui.GetWindowDrawList(); - var cursor = ImGui.GetCursorScreenPos(); - - // Draw an ImGui button with the icon and text - var buttonWidth = iconSize.X + (ImGui.GetStyle().FramePadding.X * 2); - var buttonHeight = ImGui.GetFrameHeight(); - var button = ImGui.Button(string.Empty, new Vector2(buttonWidth, buttonHeight)); - - // Draw the icon on the window drawlist - var iconPos = new Vector2(cursor.X + ImGui.GetStyle().FramePadding.X, cursor.Y + ImGui.GetStyle().FramePadding.Y); - - ImGui.PushFont(UiBuilder.IconFont); - dl.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon); - ImGui.PopFont(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + var iconSize = ImGui.CalcTextSize(icon); + var cursor = ImGui.GetCursorScreenPos(); - ImGui.PopID(); + var width = size is { X: not 0 } ? size.Value.X : iconSize.X + (ImGui.GetStyle().FramePadding.X * 2); + var height = size is { Y: not 0 } ? size.Value.Y : ImGui.GetFrameHeight(); - if (numColors > 0) - ImGui.PopStyleColor(numColors); + var buttonSize = new Vector2(width, height); + + using (ImRaii.PushId(iconText)) + { + button = ImGui.Button(string.Empty, buttonSize); + } + + var iconPos = cursor + ((buttonSize - iconSize) / 2f); + + ImGui.GetWindowDrawList().AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon); + } return button; } + /// + /// IconButton component to use an icon as a button with color options. + /// + /// Icon to show. + /// Text to show. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon & text. + /// Indicator if button is clicked. + public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector2 size) => IconButtonWithText(icon, text, null, null, null, size); + /// /// IconButton component to use an icon as a button with color options. /// @@ -150,61 +194,72 @@ public static partial class ImGuiComponents /// The default color of the button. /// The color of the button when active. /// The color of the button when hovered. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon & text. /// Indicator if button is clicked. - public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) { - var numColors = 0; + using var col = new ImRaii.Color(); if (defaultColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); - numColors++; + col.Push(ImGuiCol.Button, defaultColor.Value); } if (activeColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); - numColors++; + col.Push(ImGuiCol.ButtonActive, activeColor.Value); } if (hoveredColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); - numColors++; + col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value); } - ImGui.PushID(text); + if (size.HasValue) + { + size *= ImGuiHelpers.GlobalScale; + } + + bool button; + + Vector2 iconSize; + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + iconSize = ImGui.CalcTextSize(icon.ToIconString()); + } + + var textStr = text; + if (textStr.Contains('#')) + { + textStr = textStr[..textStr.IndexOf('#', StringComparison.Ordinal)]; + } + + var framePadding = ImGui.GetStyle().FramePadding; + var iconPadding = 3 * ImGuiHelpers.GlobalScale; - ImGui.PushFont(UiBuilder.IconFont); - var iconSize = ImGui.CalcTextSize(icon.ToIconString()); - ImGui.PopFont(); - - var textSize = ImGui.CalcTextSize(text); - var dl = ImGui.GetWindowDrawList(); var cursor = ImGui.GetCursorScreenPos(); - var iconPadding = 3 * ImGuiHelpers.GlobalScale; - - // Draw an ImGui button with the icon and text - var buttonWidth = iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding; - var buttonHeight = ImGui.GetFrameHeight(); - var button = ImGui.Button(string.Empty, new Vector2(buttonWidth, buttonHeight)); - - // Draw the icon on the window drawlist - var iconPos = new Vector2(cursor.X + ImGui.GetStyle().FramePadding.X, cursor.Y + ImGui.GetStyle().FramePadding.Y); - - ImGui.PushFont(UiBuilder.IconFont); - dl.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString()); - ImGui.PopFont(); - - // Draw the text on the window drawlist - var textPos = new Vector2(iconPos.X + iconSize.X + iconPadding, cursor.Y + ImGui.GetStyle().FramePadding.Y); - dl.AddText(textPos, ImGui.GetColorU32(ImGuiCol.Text), text); + using (ImRaii.PushId(text)) + { + var textSize = ImGui.CalcTextSize(textStr); - ImGui.PopID(); + var width = size is { X: not 0 } ? size.Value.X : iconSize.X + textSize.X + (framePadding.X * 2) + iconPadding; + var height = size is { Y: not 0 } ? size.Value.Y : ImGui.GetFrameHeight(); - if (numColors > 0) - ImGui.PopStyleColor(numColors); + button = ImGui.Button(string.Empty, new Vector2(width, height)); + } + + var iconPos = cursor + framePadding; + var textPos = new Vector2(iconPos.X + iconSize.X + iconPadding, cursor.Y + framePadding.Y); + + var dl = ImGui.GetWindowDrawList(); + + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + dl.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString()); + } + + dl.AddText(textPos, ImGui.GetColorU32(ImGuiCol.Text), textStr); return button; } @@ -217,16 +272,15 @@ public static partial class ImGuiComponents /// Width. internal static float GetIconButtonWithTextWidth(FontAwesomeIcon icon, string text) { - ImGui.PushFont(UiBuilder.IconFont); - var iconSize = ImGui.CalcTextSize(icon.ToIconString()); - ImGui.PopFont(); - - var textSize = ImGui.CalcTextSize(text); - var dl = ImGui.GetWindowDrawList(); - var cursor = ImGui.GetCursorScreenPos(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + var iconSize = ImGui.CalcTextSize(icon.ToIconString()); - var iconPadding = 3 * ImGuiHelpers.GlobalScale; - - return iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding; + var textSize = ImGui.CalcTextSize(text); + + var iconPadding = 3 * ImGuiHelpers.GlobalScale; + + return iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding; + } } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs new file mode 100644 index 000000000..3f9c469bb --- /dev/null +++ b/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +using Dalamud.Interface.Utility; + +using ImGuiNET; + +namespace Dalamud.Interface.Components; + +public static partial class ImGuiComponents +{ + /// + /// A radio-like input that uses icon buttons. + /// + /// The type of the value being set. + /// Text that will be used to generate individual labels for the buttons. + /// The value to set. + /// The icons that will be displayed on each button. + /// The options that each button will apply. + /// Arranges the buttons in a grid with the given number of columns. 0 = ignored (all buttons drawn in one row). + /// Sets the size of all buttons. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// The default color of the button range. + /// The color of the actively-selected button. + /// The color of the buttons when hovered. + /// True if any button is clicked. + internal static bool IconButtonSelect(string label, ref T val, IEnumerable optionIcons, IEnumerable optionValues, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + { + var options = optionIcons.Zip(optionValues, static (icon, value) => new KeyValuePair(icon, value)); + return IconButtonSelect(label, ref val, options, columns, buttonSize, defaultColor, activeColor, hoveredColor); + } + + /// + /// A radio-like input that uses icon buttons. + /// + /// The type of the value being set. + /// Text that will be used to generate individual labels for the buttons. + /// The value to set. + /// A list of all icon/option pairs. + /// Arranges the buttons in a grid with the given number of columns. 0 = ignored (all buttons drawn in one row). + /// Sets the size of all buttons. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// The default color of the button range. + /// The color of the actively-selected button. + /// The color of the buttons when hovered. + /// True if any button is clicked. + internal static unsafe bool IconButtonSelect(string label, ref T val, IEnumerable> options, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + { + defaultColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.Button); + activeColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.ButtonActive); + hoveredColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.ButtonHovered); + + var result = false; + + var innerSpacing = ImGui.GetStyle().ItemInnerSpacing; + var y = ImGui.GetCursorPosY(); + + var optArr = options.ToArray(); + for (var i = 0; i < optArr.Length; i++) + { + if (i > 0) + { + if (columns == 0 || i % columns != 0) + { + ImGui.SameLine(0, innerSpacing.X); + } + else + { + y += (buttonSize is { Y: not 0 } ? buttonSize.Value.Y * ImGuiHelpers.GlobalScale : ImGui.GetFrameHeight()) + innerSpacing.Y; + + ImGui.SetCursorPosY(y); + } + } + + optArr[i].Deconstruct(out var icon, out var option); + + var selected = val is not null && val.Equals(option); + + if (IconButton($"{label}{option}{i}", icon, selected ? activeColor : defaultColor, activeColor, hoveredColor, buttonSize)) + { + val = option; + result = true; + } + } + + return result; + } +} diff --git a/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs b/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs index 597b472c6..43b54fc93 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs @@ -1,3 +1,5 @@ +using Dalamud.Interface.Utility.Raii; + using ImGuiNET; namespace Dalamud.Interface.Components; @@ -24,7 +26,13 @@ public static partial class ImGuiComponents else { ImGui.Text(value + "*"); - if (ImGui.IsItemHovered()) ImGui.SetTooltip(hint); + if (ImGui.IsItemHovered()) + { + using (ImRaii.Tooltip()) + { + ImGui.TextUnformatted(hint); + } + } } } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs b/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs index 64f3d01eb..6d6e0f6c3 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs @@ -36,9 +36,14 @@ public static partial class ImGuiComponents } if (ImGui.IsItemHovered()) + { drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.ButtonActive] : new Vector4(0.78f, 0.78f, 0.78f, 1.0f)), height * 0.5f); + } else + { drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.Button] * 0.6f : new Vector4(0.35f, 0.35f, 0.35f, 1.0f)), height * 0.50f); + } + drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1))); return changed; @@ -62,7 +67,7 @@ public static partial class ImGuiComponents // TODO: animate ImGui.InvisibleButton(id, new Vector2(width, height)); - var dimFactor = 0.5f; + const float dimFactor = 0.5f; drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(v ? colors[(int)ImGuiCol.Button] * dimFactor : new Vector4(0.55f, 0.55f, 0.55f, 1.0f) * dimFactor), height * 0.50f); drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1) * dimFactor)); diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs new file mode 100644 index 000000000..4b7a531c0 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs @@ -0,0 +1,124 @@ +using Dalamud.Interface.Internal.UiDebug2.Utility; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Memory; +using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Component.GUI; + +using ImGuiNET; + +using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +public unsafe partial class AddonTree +{ + /// + /// Prints a table of AtkValues associated with a given addon. + /// + /// The addon to look up. + internal static void PrintAtkValues(AtkUnitBase* addon) + { + var atkValue = addon->AtkValues; + if (addon->AtkValuesCount > 0 && atkValue != null) + { + using var tree = ImRaii.TreeNode($"Atk Values [{addon->AtkValuesCount}]###atkValues_{addon->NameString}"); + if (tree) + { + using (ImRaii.Table( + "atkUnitBase_atkValueTable", + 3, + ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg)) + { + ImGui.TableSetupColumn("Index"); + ImGui.TableSetupColumn("Type"); + ImGui.TableSetupColumn("Value"); + ImGui.TableHeadersRow(); + + try + { + for (var i = 0; i < addon->AtkValuesCount; i++) + { + ImGui.TableNextColumn(); + if (atkValue->Type == 0) + { + ImGui.TextDisabled($"#{i}"); + } + else + { + ImGui.Text($"#{i}"); + } + + ImGui.TableNextColumn(); + if (atkValue->Type == 0) + { + ImGui.TextDisabled("Not Set"); + } + else + { + ImGui.Text($"{atkValue->Type}"); + } + + ImGui.TableNextColumn(); + + switch (atkValue->Type) + { + case 0: + break; + case ValueType.Int: + case ValueType.UInt: + { + ImGui.TextUnformatted($"{atkValue->Int}"); + break; + } + + case ValueType.ManagedString: + case ValueType.String8: + case ValueType.String: + { + if (atkValue->String == null) + { + ImGui.TextDisabled("null"); + } + else + { + var str = MemoryHelper.ReadSeStringNullTerminated(new nint(atkValue->String)); + Util.ShowStruct(str, (ulong)atkValue); + } + + break; + } + + case ValueType.Bool: + { + ImGui.TextUnformatted($"{atkValue->Byte != 0}"); + break; + } + + case ValueType.Pointer: + ImGui.TextUnformatted($"{(nint)atkValue->Pointer}"); + break; + + default: + { + ImGui.TextDisabled("Unhandled Type"); + ImGui.SameLine(); + Util.ShowStruct(atkValue); + break; + } + } + + atkValue++; + } + } + catch (Exception ex) + { + ImGui.TextColored(new(1, 0, 0, 1), $"{ex}"); + } + } + } + + Gui.PaddedSeparator(); + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs index 8affa1eac..0b1dcb66c 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs @@ -23,6 +23,11 @@ public unsafe partial class AddonTree /// internal Dictionary> FieldNames { get; set; } = []; + /// + /// Gets or sets the size of the addon according to its Attributes in FFXIVClientStructs. + /// + internal int AddonSize { get; set; } + private object? GetAddonObj(AtkUnitBase* addon) { if (addon == null) @@ -42,6 +47,13 @@ public unsafe partial class AddonTree select t) { AddonTypeDict[this.AddonName] = t; + + var size = t.StructLayoutAttribute?.Size; + if (size != null) + { + this.AddonSize = size.Value; + } + break; } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs index 2823a2058..9d6575a55 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Numerics; using Dalamud.Interface.Components; + using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; @@ -19,14 +20,12 @@ namespace Dalamud.Interface.Internal.UiDebug2.Browsing; /// public unsafe partial class AddonTree : IDisposable { - private readonly nint initialPtr; - private AddonPopoutWindow? window; private AddonTree(string name, nint ptr) { this.AddonName = name; - this.initialPtr = ptr; + this.InitialPtr = ptr; this.PopulateFieldNames(ptr); } @@ -35,6 +34,11 @@ public unsafe partial class AddonTree : IDisposable /// internal string AddonName { get; init; } + /// + /// Gets the addon's pointer at the time this was created. + /// + internal nint InitialPtr { get; init; } + /// /// Gets or sets a collection of trees representing nodes within this addon. /// @@ -81,7 +85,7 @@ public unsafe partial class AddonTree : IDisposable { if (AddonTrees.TryGetValue(name, out var tree)) { - if (tree.initialPtr == ptr) + if (tree.InitialPtr == ptr) { return tree; } @@ -143,34 +147,47 @@ public unsafe partial class AddonTree : IDisposable ImGui.SetTooltip("Toggle Popout Window"); } - ImGui.Separator(); - - PrintFieldValuePair("Address", $"{(nint)addon:X}"); + PaddedSeparator(1); var uldManager = addon->UldManager; + + PrintFieldValuePair("Address", $"{(nint)addon:X}"); + PrintFieldValuePair("Agent", $"{GameGui.FindAgentInterface(addon):X}"); + PrintFieldValuePairs( ("X", $"{addon->X}"), - ("Y", $"{addon->X}"), + ("Y", $"{addon->Y}"), ("Scale", $"{addon->Scale}"), ("Widget Count", $"{uldManager.ObjectCount}")); - ImGui.Separator(); - var addonObj = this.GetAddonObj(addon); if (addonObj != null) { + PaddedSeparator(); ShowStruct(addonObj, (ulong)addon); } - ImGui.Dummy(new(25 * ImGui.GetIO().FontGlobalScale)); - ImGui.Separator(); + PaddedSeparator(); - ResNodeTree.PrintNodeList(uldManager.NodeList, uldManager.NodeListCount, this); + PrintAtkValues(addon); - ImGui.Dummy(new(25 * ImGui.GetIO().FontGlobalScale)); - ImGui.Separator(); + if (addon->RootNode != null) + { + ResNodeTree.GetOrCreate(addon->RootNode, this).Print(0); + PaddedSeparator(); + } - ResNodeTree.PrintNodeListAsTree(addon->CollisionNodeList, (int)addon->CollisionNodeListCount, "Collision List", this, new(0.5F, 0.7F, 1F, 1F)); + if (uldManager.NodeList != null) + { + var count = uldManager.NodeListCount; + ResNodeTree.PrintNodeListAsTree(uldManager.NodeList, count, $"Node List [{count}]:", this, new(0, 0.85F, 1, 1)); + PaddedSeparator(); + } + + if (addon->CollisionNodeList != null) + { + ResNodeTree.PrintNodeListAsTree(addon->CollisionNodeList, (int)addon->CollisionNodeListCount, "Collision List", this, new(0.5F, 0.7F, 1F, 1F)); + } if (SearchResults.Length > 0 && Countdown <= 0) { @@ -218,7 +235,7 @@ public unsafe partial class AddonTree : IDisposable private bool ValidateAddon(out AtkUnitBase* addon) { addon = (AtkUnitBase*)GameGui.GetAddonByName(this.AddonName); - if (addon == null || (nint)addon != this.initialPtr) + if (addon == null || (nint)addon != this.InitialPtr) { this.Dispose(); return false; @@ -231,7 +248,7 @@ public unsafe partial class AddonTree : IDisposable { if (this.window == null) { - this.window = new AddonPopoutWindow(this, $"{this.AddonName}###addonPopout{this.initialPtr}"); + this.window = new AddonPopoutWindow(this, $"{this.AddonName}###addonPopout{this.InitialPtr}"); PopoutWindows.AddWindow(this.window); } else diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs index 0c3a947dd..45a2d90eb 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs @@ -1,4 +1,6 @@ -using Dalamud.Interface.Internal.UiDebug2.Utility; +using System.Numerics; + +using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using FFXIVClientStructs.FFXIV.Component.GUI; @@ -56,9 +58,9 @@ public static class Events ImGui.TableNextColumn(); ImGui.TextUnformatted($"{evt->State.UnkFlags1}"); ImGui.TableNextColumn(); - Gui.ClickToCopyText($"{(nint)evt->Target:X}"); + ImGuiHelpers.ClickToCopyText($"{(nint)evt->Target:X}", null, new Vector4(0.6f, 0.6f, 0.6f, 1)); ImGui.TableNextColumn(); - Gui.ClickToCopyText($"{(nint)evt->Listener:X}"); + ImGuiHelpers.ClickToCopyText($"{(nint)evt->Listener:X}", null, new Vector4(0.6f, 0.6f, 0.6f, 1)); evt = evt->NextEvent; } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs index d9fcf52cc..4a1989441 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs @@ -34,11 +34,13 @@ internal unsafe class ComponentNodeTree : ResNodeTree private AtkUldManager* UldManager => &this.Component->UldManager; + private int? ComponentFieldOffset { get; set; } + /// private protected override string GetHeaderText() { var childCount = (int)this.UldManager->NodeListCount; - return $"{this.componentType} Component Node{(childCount > 0 ? $" [+{childCount}]" : string.Empty)} (Node: {(nint)this.Node:X} / Comp: {(nint)this.Component:X})"; + return $"{this.componentType} Component Node{(childCount > 0 ? $" [+{childCount}]" : string.Empty)}"; } /// @@ -62,10 +64,10 @@ internal unsafe class ComponentNodeTree : ResNodeTree } /// - private protected override void PrintFieldNames() + private protected override void PrintFieldLabels() { - this.PrintFieldName((nint)this.Node, new(0, 0.85F, 1, 1)); - this.PrintFieldName((nint)this.Component, new(0f, 0.5f, 0.8f, 1f)); + this.PrintFieldLabel((nint)this.Node, new(0, 0.85F, 1, 1), this.NodeFieldOffset); + this.PrintFieldLabel((nint)this.Component, new(0f, 0.5f, 0.8f, 1f), this.ComponentFieldOffset); } /// @@ -108,6 +110,34 @@ internal unsafe class ComponentNodeTree : ResNodeTree } } + /// + private protected override void GetFieldOffset() + { + var nodeFound = false; + var componentFound = false; + for (var i = 0; i < this.AddonTree.AddonSize; i += 0x8) + { + var readPtr = Marshal.ReadIntPtr(this.AddonTree.InitialPtr + i); + + if (readPtr == (nint)this.Node) + { + this.NodeFieldOffset = i; + nodeFound = true; + } + + if (readPtr == (nint)this.Component) + { + this.ComponentFieldOffset = i; + componentFound = true; + } + + if (nodeFound && componentFound) + { + break; + } + } + } + private void PrintComponentObject() { PrintFieldValuePair("Component", $"{(nint)this.Component:X}"); diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs index 6cb178bd7..6b6522bb4 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Numerics; +using Dalamud.Interface.Components; using Dalamud.Interface.Internal.UiDebug2.Utility; using Dalamud.Interface.Utility.Raii; @@ -370,8 +371,8 @@ internal unsafe partial class TextNodeTree var hAlign = (int)alignment % 3; var vAlign = ((int)alignment - hAlign) / 3; - var hAlignInput = IconButtonSelect($"{label}H", ref hAlign, [0, 1, 2], [AlignLeft, AlignCenter, AlignRight]); - var vAlignInput = IconButtonSelect($"{label}V", ref vAlign, [0, 1, 2], [ArrowsUpToLine, GripLines, ArrowsDownToLine]); + var hAlignInput = ImGuiComponents.IconButtonSelect($"{label}H", ref hAlign, [AlignLeft, AlignCenter, AlignRight], [0, 1, 2], 3u, new(25, 0)); + var vAlignInput = ImGuiComponents.IconButtonSelect($"{label}V", ref vAlign, [ArrowsUpToLine, GripLines, ArrowsDownToLine], [0, 1, 2], 3u, new(25, 0)); if (hAlignInput || vAlignInput) { diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs index a333940c1..2edf4e570 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Numerics; +using System.Runtime.InteropServices; using Dalamud.Interface.Components; using Dalamud.Interface.Internal.UiDebug2.Utility; @@ -60,6 +61,11 @@ internal unsafe partial class ResNodeTree : IDisposable /// private protected NodeType NodeType { get; init; } + /// + /// Gets or sets the offset of this node within its parent Addon. + /// + private protected int? NodeFieldOffset { get; set; } + /// /// Clears this NodeTree's popout window, if it has one. /// @@ -164,19 +170,26 @@ internal unsafe partial class ResNodeTree : IDisposable internal void WriteTreeHeading() { ImGui.TextUnformatted(this.GetHeaderText()); - this.PrintFieldNames(); + this.PrintFieldLabels(); } /// - /// If the given pointer has been identified as a field within the addon struct, this method prints that field's name. + /// If the given pointer is referenced with the addon struct, the offset within the addon will be printed. If the given pointer has been identified as a field within the addon struct, this method also prints that field's name. /// /// The pointer to check. /// The text color to use. - private protected void PrintFieldName(nint ptr, Vector4 color) + /// The field offset of the pointer, if it was found in the addon. + private protected void PrintFieldLabel(nint ptr, Vector4 color, int? fieldOffset) { + if (fieldOffset != null) + { + ImGui.SameLine(0, -1); + ImGui.TextColored(color * 0.85f, $"[0x{fieldOffset:X}]"); + } + if (this.AddonTree.FieldNames.TryGetValue(ptr, out var result)) { - ImGui.SameLine(); + ImGui.SameLine(0, -1); ImGui.TextColored(color, string.Join(".", result)); } } @@ -188,7 +201,15 @@ internal unsafe partial class ResNodeTree : IDisposable private protected virtual string GetHeaderText() { var count = this.GetDirectChildCount(); - return $"{this.NodeType} Node{(count > 0 ? $" [+{count}]" : string.Empty)} ({(nint)this.Node:X})"; + return $"{this.NodeType} Node{(count > 0 ? $" [+{count}]" : string.Empty)}"; + } + + /// + /// Prints any field names for the node. + /// + private protected virtual void PrintFieldLabels() + { + this.PrintFieldLabel((nint)this.Node, new(0, 0.85F, 1, 1), this.NodeFieldOffset); } /// @@ -201,11 +222,6 @@ internal unsafe partial class ResNodeTree : IDisposable ImGui.NewLine(); } - /// - /// Prints any field names for the node. - /// - private protected virtual void PrintFieldNames() => this.PrintFieldName((nint)this.Node, new(0, 0.85F, 1, 1)); - /// /// Prints all direct children of this node. /// @@ -227,6 +243,21 @@ internal unsafe partial class ResNodeTree : IDisposable { } + /// + /// Attempts to retrieve the field offset of the given pointer within the parent addon. + /// + private protected virtual void GetFieldOffset() + { + for (var i = 0; i < this.AddonTree.AddonSize; i += 0x8) + { + if (Marshal.ReadIntPtr(this.AddonTree.InitialPtr + i) == (nint)this.Node) + { + this.NodeFieldOffset = i; + break; + } + } + } + private int GetDirectChildCount() { var count = 0; @@ -273,6 +304,8 @@ internal unsafe partial class ResNodeTree : IDisposable ImGui.SetNextItemOpen(true, ImGuiCond.Always); } + this.GetFieldOffset(); + using var col = ImRaii.PushColor(Text, displayColor); using var tree = ImRaii.TreeNode(label, SpanFullWidth); @@ -281,7 +314,7 @@ internal unsafe partial class ResNodeTree : IDisposable new NodeBounds(this.Node).Draw(visible ? new(0.1f, 1f, 0.1f, 1f) : new(1f, 0f, 0.2f, 1f)); } - ImGui.SameLine(); + ImGui.SameLine(0, -1); this.WriteTreeHeading(); col.Pop(); diff --git a/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs index 7f603fdac..6693c3fb0 100644 --- a/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs +++ b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs @@ -79,7 +79,7 @@ internal unsafe class ElementSelector : IDisposable /// internal void DrawInterface() { - using (ImRaii.Child("###sidebar_elementSelector", new(250, 0), true)) + using (ImRaii.Child("###sidebar_elementSelector", new(250, -1), true)) { using (ImRaii.PushFont(IconFont)) { diff --git a/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs index 954e5cb72..cc73b79c6 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs @@ -1,7 +1,6 @@ -using System.Collections.Generic; using System.Numerics; -using Dalamud.Interface.Components; +using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using FFXIVClientStructs.FFXIV.Client.Graphics; @@ -17,40 +16,6 @@ namespace Dalamud.Interface.Internal.UiDebug2.Utility; /// internal static class Gui { - /// - /// A radio-button-esque input that uses Fontawesome icon buttons. - /// - /// The type of value being set. - /// The label for the inputs. - /// The value being set. - /// A list of all options. - /// A list of icons corresponding to the options. - /// true if a button is clicked. - internal static unsafe bool IconButtonSelect(string label, ref T val, List options, List icons) - { - var ret = false; - - for (var i = 0; i < options.Count; i++) - { - if (i > 0) - { - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); - } - - var option = options[i]; - var icon = icons.Count > i ? icons[i] : FontAwesomeIcon.Question; - var color = *ImGui.GetStyleColorVec4(val is not null && val.Equals(option) ? ButtonActive : Button); - - if (ImGuiComponents.IconButton($"{label}{option}{i}", icon, color)) - { - val = option; - ret = true; - } - } - - return ret; - } - /// /// Prints field name and its value. /// @@ -61,13 +26,14 @@ internal static class Gui { ImGui.TextUnformatted($"{fieldName}:"); ImGui.SameLine(); + var grey60 = new Vector4(0.6f, 0.6f, 0.6f, 1); if (copy) { - ClickToCopyText(value); + ImGuiHelpers.ClickToCopyText(value, null, grey60); } else { - ImGui.TextColored(new(0.6f, 0.6f, 0.6f, 1), value); + ImGui.TextColored(grey60, value); } } @@ -102,7 +68,10 @@ internal static class Gui /// Colors the text itself either white or black, depending on the luminosity of the background color. internal static void PrintColor(Vector4 color, string fmt) { - using (new ImRaii.Color().Push(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1)).Push(Button, color).Push(ButtonActive, color).Push(ButtonHovered, color)) + using (new ImRaii.Color().Push(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1)) + .Push(Button, color) + .Push(ButtonActive, color) + .Push(ButtonHovered, color)) { ImGui.SmallButton(fmt); } @@ -117,39 +86,6 @@ internal static class Gui 0.5f) * vector4.W; } - /// - /// Print out text that can be copied when clicked. - /// - /// The text to show. - /// The text to copy when clicked. - internal static void ClickToCopyText(string text, string? textCopy = null) - { - using (ImRaii.PushColor(Text, new Vector4(0.6f, 0.6f, 0.6f, 1))) - { - textCopy ??= text; - ImGui.TextUnformatted($"{text}"); - } - - if (ImGui.IsItemHovered()) - { - using (ImRaii.Tooltip()) - { - using (ImRaii.PushFont(UiBuilder.IconFont)) - { - ImGui.TextUnformatted(FontAwesomeIcon.Copy.ToIconString()); - } - - ImGui.SameLine(); - ImGui.TextUnformatted($"{textCopy}"); - } - } - - if (ImGui.IsItemClicked()) - { - ImGui.SetClipboardText($"{textCopy}"); - } - } - /// /// Draws a tooltip that changes based on the cursor's x-position within the hovered item. /// @@ -176,4 +112,23 @@ internal static class Gui return true; } + + /// + /// Draws a separator with some padding above and below. + /// + /// Governs whether to pad above, below, or both. + /// The amount of padding. + internal static void PaddedSeparator(uint mask = 0b11, float padding = 5f) + { + if ((mask & 0b10) > 0) + { + ImGui.Dummy(new(padding * ImGui.GetIO().FontGlobalScale)); + } + + ImGui.Separator(); + if ((mask & 0b01) > 0) + { + ImGui.Dummy(new(padding * ImGui.GetIO().FontGlobalScale)); + } + } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs b/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs index cffd676f7..3d28cb836 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs @@ -6,7 +6,7 @@ using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; -using static System.Math; +using static System.MathF; using static Dalamud.Interface.ColorHelpers; namespace Dalamud.Interface.Internal.UiDebug2.Utility; @@ -133,7 +133,7 @@ public unsafe struct NodeBounds if (p.Y > Min(p1.Y, p2.Y) && p.Y <= Max(p1.Y, p2.Y) && p.X <= Max(p1.X, p2.X) && - (p1.X.Equals(p2.X) || p.X <= ((p.Y - p1.Y) * (p2.X - p1.X) / (p2.Y - p1.Y)) + p1.X)) + (p1.X.Equals(p2.X) || p.X <= (((p.Y - p1.Y) * (p2.X - p1.X)) / (p2.Y - p1.Y)) + p1.X)) { inside = !inside; } @@ -144,12 +144,12 @@ public unsafe struct NodeBounds private static Vector2 TransformPoint(Vector2 p, Vector2 o, float r, Vector2 s) { - var cosR = (float)Cos(r); - var sinR = (float)Sin(r); + var cosR = Cos(r); + var sinR = Sin(r); var d = (p - o) * s; return new( - o.X + (d.X * cosR) - (d.Y * sinR), + (o.X + (d.X * cosR)) - (d.Y * sinR), o.Y + (d.X * sinR) + (d.Y * cosR)); } diff --git a/Dalamud/Interface/Utility/ImGuiHelpers.cs b/Dalamud/Interface/Utility/ImGuiHelpers.cs index 8ce7a48d7..8dedae5cd 100644 --- a/Dalamud/Interface/Utility/ImGuiHelpers.cs +++ b/Dalamud/Interface/Utility/ImGuiHelpers.cs @@ -167,17 +167,41 @@ public static class ImGuiHelpers /// /// The text to show. /// The text to copy when clicked. - public static void ClickToCopyText(string text, string? textCopy = null) + /// The color of the text. + public static void ClickToCopyText(string text, string? textCopy = null, Vector4? color = null) { textCopy ??= text; - ImGui.Text($"{text}"); + + using (var col = new ImRaii.Color()) + { + if (color.HasValue) + { + col.Push(ImGuiCol.Text, color.Value); + } + + ImGui.TextUnformatted($"{text}"); + } + if (ImGui.IsItemHovered()) { ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); - if (textCopy != text) ImGui.SetTooltip(textCopy); + + using (ImRaii.Tooltip()) + { + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + ImGui.TextUnformatted(FontAwesomeIcon.Copy.ToIconString()); + } + + ImGui.SameLine(); + ImGui.TextUnformatted(textCopy); + } } - if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}"); + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText(textCopy); + } } /// Draws a SeString.