From 7c2c74418f2ba97eab06e399545aeeebbe4f13ed Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Thu, 17 Jul 2025 01:56:38 +0200 Subject: [PATCH] [imgui-bindings] Add ReadOnlySpan ImRaii/ImGuiHelpers overloads (#2314) * Add ReadOnlySpan ImRaii overloads * Add ReadOnlySpan ImGuiHelpers overloads --- Dalamud/Interface/Utility/ImGuiHelpers.cs | 88 ++++++++++++- Dalamud/Interface/Utility/Raii/EndObjects.cs | 127 +++++++++++++++---- Dalamud/Interface/Utility/Raii/Id.cs | 14 ++ Dalamud/Interface/Utility/Raii/Plot.cs | 18 +++ 4 files changed, 217 insertions(+), 30 deletions(-) diff --git a/Dalamud/Interface/Utility/ImGuiHelpers.cs b/Dalamud/Interface/Utility/ImGuiHelpers.cs index 3285c8daf..376539bec 100644 --- a/Dalamud/Interface/Utility/ImGuiHelpers.cs +++ b/Dalamud/Interface/Utility/ImGuiHelpers.cs @@ -139,6 +139,10 @@ public static partial class ImGuiHelpers public static void SetWindowPosRelativeMainViewport(string name, Vector2 position, ImGuiCond condition = ImGuiCond.None) => ImGui.SetWindowPos(name, position + MainViewport.Pos, condition); + /// + public static void SetWindowPosRelativeMainViewport(ReadOnlySpan name, Vector2 position, ImGuiCond condition = ImGuiCond.None) + => ImGui.SetWindowPos(name, position + MainViewport.Pos, condition); + /// /// Creates default color palette for use with color pickers. /// @@ -162,7 +166,12 @@ public static partial class ImGuiHelpers /// /// Text in the button. /// with the size of the button. - public static Vector2 GetButtonSize(string text) => ImGui.CalcTextSize(text) + (ImGui.GetStyle().FramePadding * 2); + public static Vector2 GetButtonSize(string text) + => ImGui.CalcTextSize(text) + (ImGui.GetStyle().FramePadding * 2); + + /// + public static Vector2 GetButtonSize(ReadOnlySpan text) + => ImGui.CalcTextSize(text) + (ImGui.GetStyle().FramePadding * 2); /// /// Print out text that can be copied when clicked. @@ -172,6 +181,7 @@ public static partial class ImGuiHelpers /// The color of the text. public static void ClickToCopyText(string text, string? textCopy = null, Vector4? color = null) { + text ??= string.Empty; textCopy ??= text; using (var col = new ImRaii.Color()) @@ -181,7 +191,45 @@ public static partial class ImGuiHelpers col.Push(ImGuiCol.Text, color.Value); } - ImGui.TextUnformatted($"{text}"); + ImGui.TextUnformatted(text); + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + + using (ImRaii.Tooltip()) + { + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + ImGui.TextUnformatted(FontAwesomeIcon.Copy.ToIconString()); + } + + ImGui.SameLine(); + ImGui.TextUnformatted(textCopy); + } + } + + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText(textCopy); + } + } + + /// + public static void ClickToCopyText(ReadOnlySpan text, ReadOnlySpan textCopy = default, Vector4? color = null) + { + if (textCopy.IsEmpty) + textCopy = text; + + using (var col = new ImRaii.Color()) + { + if (color.HasValue) + { + col.Push(ImGuiCol.Text, color.Value); + } + + ImGui.TextUnformatted(text); } if (ImGui.IsItemHovered()) @@ -239,6 +287,15 @@ public static partial class ImGuiHelpers /// The text to write. public static void SafeTextWrapped(string text) => ImGui.TextWrapped(text.Replace("%", "%%")); + /// + public static void SafeTextWrapped(ReadOnlySpan text) + { + if (text.Contains((byte)'%')) + ImGui.TextWrapped(Encoding.UTF8.GetString(text).Replace("%", "%%")); + else + ImGui.TextWrapped(text); + } + /// /// Write unformatted text wrapped. /// @@ -248,7 +305,16 @@ public static partial class ImGuiHelpers { using (ImRaii.PushColor(ImGuiCol.Text, color)) { - ImGui.TextWrapped(text.Replace("%", "%%")); + SafeTextWrapped(text); + } + } + + /// + public static void SafeTextColoredWrapped(Vector4 color, ReadOnlySpan text) + { + using (ImRaii.PushColor(ImGuiCol.Text, color)) + { + SafeTextWrapped(text); } } @@ -450,11 +516,23 @@ public static partial class ImGuiHelpers ImGui.TextUnformatted(text); } + /// + public static void CenteredText(ReadOnlySpan text) + { + CenterCursorForText(text); + ImGui.TextUnformatted(text); + } + /// /// Center the ImGui cursor for a certain text. - /// + /// /// The text to center for. - public static void CenterCursorForText(string text) => CenterCursorFor(ImGui.CalcTextSize(text).X); + public static void CenterCursorForText(string text) + => CenterCursorFor(ImGui.CalcTextSize(text).X); + + /// + public static void CenterCursorForText(ReadOnlySpan text) + => CenterCursorFor(ImGui.CalcTextSize(text).X); /// /// Center the ImGui cursor for an item with a certain width. diff --git a/Dalamud/Interface/Utility/Raii/EndObjects.cs b/Dalamud/Interface/Utility/Raii/EndObjects.cs index 2ed87b5a4..5b33b3273 100644 --- a/Dalamud/Interface/Utility/Raii/EndObjects.cs +++ b/Dalamud/Interface/Utility/Raii/EndObjects.cs @@ -14,15 +14,27 @@ public static partial class ImRaii public static IEndObject Child(string strId) => new EndUnconditionally(ImGui.EndChild, ImGui.BeginChild(strId)); + public static IEndObject Child(ReadOnlySpan strId) + => new EndUnconditionally(ImGui.EndChild, ImGui.BeginChild(strId)); + public static IEndObject Child(string strId, Vector2 size) => new EndUnconditionally(ImGui.EndChild, ImGui.BeginChild(strId, size)); + public static IEndObject Child(ReadOnlySpan strId, Vector2 size) + => new EndUnconditionally(ImGui.EndChild, ImGui.BeginChild(strId, size)); + public static IEndObject Child(string strId, Vector2 size, bool border) => new EndUnconditionally(ImGui.EndChild, ImGui.BeginChild(strId, size, border)); + public static IEndObject Child(ReadOnlySpan strId, Vector2 size, bool border) + => new EndUnconditionally(ImGui.EndChild, ImGui.BeginChild(strId, size, border)); + public static IEndObject Child(string strId, Vector2 size, bool border, ImGuiWindowFlags flags) => new EndUnconditionally(ImGui.EndChild, ImGui.BeginChild(strId, size, border, flags)); + public static IEndObject Child(ReadOnlySpan strId, Vector2 size, bool border, ImGuiWindowFlags flags) + => new EndUnconditionally(ImGui.EndChild, ImGui.BeginChild(strId, size, border, flags)); + public static IEndObject DragDropTarget() => new EndConditionally(ImGui.EndDragDropTarget, ImGui.BeginDragDropTarget()); @@ -35,39 +47,87 @@ public static partial class ImRaii public static IEndObject Popup(string id) => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopup(id)); + public static IEndObject Popup(ReadOnlySpan id) + => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopup(id)); + public static IEndObject Popup(string id, ImGuiWindowFlags flags) => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopup(id, flags)); + public static IEndObject Popup(ReadOnlySpan id, ImGuiWindowFlags flags) + => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopup(id, flags)); + public static IEndObject PopupModal(string id) => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupModal(id)); + public static IEndObject PopupModal(ReadOnlySpan id) + => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupModal(id)); + public static IEndObject PopupModal(string id, ref bool open) => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupModal(id, ref open)); + public static IEndObject PopupModal(ReadOnlySpan id, ref bool open) + => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupModal(id, ref open)); + public static IEndObject PopupModal(string id, ref bool open, ImGuiWindowFlags flags) => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupModal(id, ref open, flags)); + public static IEndObject PopupModal(ReadOnlySpan id, ref bool open, ImGuiWindowFlags flags) + => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupModal(id, ref open, flags)); + public static IEndObject ContextPopup(string id) => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupContextWindow(id)); + public static IEndObject ContextPopup(ReadOnlySpan id) + => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupContextWindow(id)); + public static IEndObject ContextPopup(string id, ImGuiPopupFlags flags) => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupContextWindow(id, flags)); + public static IEndObject ContextPopup(ReadOnlySpan id, ImGuiPopupFlags flags) + => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupContextWindow(id, flags)); + public static IEndObject ContextPopupItem(string id) => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupContextItem(id)); + public static IEndObject ContextPopupItem(ReadOnlySpan id) + => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupContextItem(id)); + public static IEndObject ContextPopupItem(string id, ImGuiPopupFlags flags) => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupContextItem(id, flags)); + public static IEndObject ContextPopupItem(ReadOnlySpan id, ImGuiPopupFlags flags) + => new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupContextItem(id, flags)); + public static IEndObject Combo(string label, string previewValue) => new EndConditionally(ImGui.EndCombo, ImGui.BeginCombo(label, previewValue)); + public static IEndObject Combo(ReadOnlySpan label, string previewValue) + => new EndConditionally(ImGui.EndCombo, ImGui.BeginCombo(label, previewValue)); + + public static IEndObject Combo(string label, ReadOnlySpan previewValue) + => new EndConditionally(ImGui.EndCombo, ImGui.BeginCombo(label, previewValue)); + + public static IEndObject Combo(ReadOnlySpan label, ReadOnlySpan previewValue) + => new EndConditionally(ImGui.EndCombo, ImGui.BeginCombo(label, previewValue)); + public static IEndObject Combo(string label, string previewValue, ImGuiComboFlags flags) => new EndConditionally(ImGui.EndCombo, ImGui.BeginCombo(label, previewValue, flags)); + public static IEndObject Combo(ReadOnlySpan label, string previewValue, ImGuiComboFlags flags) + => new EndConditionally(ImGui.EndCombo, ImGui.BeginCombo(label, previewValue, flags)); + + public static IEndObject Combo(string label, ReadOnlySpan previewValue, ImGuiComboFlags flags) + => new EndConditionally(ImGui.EndCombo, ImGui.BeginCombo(label, previewValue, flags)); + + public static IEndObject Combo(ReadOnlySpan label, ReadOnlySpan previewValue, ImGuiComboFlags flags) + => new EndConditionally(ImGui.EndCombo, ImGui.BeginCombo(label, previewValue, flags)); + public static IEndObject Menu(string label) => new EndConditionally(ImGui.EndMenu, ImGui.BeginMenu(label)); + public static IEndObject Menu(ReadOnlySpan label) + => new EndConditionally(ImGui.EndMenu, ImGui.BeginMenu(label)); + public static IEndObject MenuBar() => new EndConditionally(ImGui.EndMenuBar, ImGui.BeginMenuBar()); @@ -113,73 +173,90 @@ public static partial class ImRaii public static IEndObject ListBox(string label) => new EndConditionally(ImGui.EndListBox, ImGui.BeginListBox(label)); + public static IEndObject ListBox(ReadOnlySpan label) + => new EndConditionally(ImGui.EndListBox, ImGui.BeginListBox(label)); + public static IEndObject ListBox(string label, Vector2 size) => new EndConditionally(ImGui.EndListBox, ImGui.BeginListBox(label, size)); + public static IEndObject ListBox(ReadOnlySpan label, Vector2 size) + => new EndConditionally(ImGui.EndListBox, ImGui.BeginListBox(label, size)); + public static IEndObject Table(string table, int numColumns) => new EndConditionally(ImGui.EndTable, ImGui.BeginTable(table, numColumns)); + public static IEndObject Table(ReadOnlySpan table, int numColumns) + => new EndConditionally(ImGui.EndTable, ImGui.BeginTable(table, numColumns)); + public static IEndObject Table(string table, int numColumns, ImGuiTableFlags flags) => new EndConditionally(ImGui.EndTable, ImGui.BeginTable(table, numColumns, flags)); + public static IEndObject Table(ReadOnlySpan table, int numColumns, ImGuiTableFlags flags) + => new EndConditionally(ImGui.EndTable, ImGui.BeginTable(table, numColumns, flags)); + public static IEndObject Table(string table, int numColumns, ImGuiTableFlags flags, Vector2 outerSize) => new EndConditionally(ImGui.EndTable, ImGui.BeginTable(table, numColumns, flags, outerSize)); + public static IEndObject Table(ReadOnlySpan table, int numColumns, ImGuiTableFlags flags, Vector2 outerSize) + => new EndConditionally(ImGui.EndTable, ImGui.BeginTable(table, numColumns, flags, outerSize)); + public static IEndObject Table(string table, int numColumns, ImGuiTableFlags flags, Vector2 outerSize, float innerWidth) => new EndConditionally(ImGui.EndTable, ImGui.BeginTable(table, numColumns, flags, outerSize, innerWidth)); + public static IEndObject Table(ReadOnlySpan table, int numColumns, ImGuiTableFlags flags, Vector2 outerSize, float innerWidth) + => new EndConditionally(ImGui.EndTable, ImGui.BeginTable(table, numColumns, flags, outerSize, innerWidth)); + public static IEndObject TabBar(string label) => new EndConditionally(ImGui.EndTabBar, ImGui.BeginTabBar(label)); + public static IEndObject TabBar(ReadOnlySpan label) + => new EndConditionally(ImGui.EndTabBar, ImGui.BeginTabBar(label)); + public static IEndObject TabBar(string label, ImGuiTabBarFlags flags) => new EndConditionally(ImGui.EndTabBar, ImGui.BeginTabBar(label, flags)); + public static IEndObject TabBar(ReadOnlySpan label, ImGuiTabBarFlags flags) + => new EndConditionally(ImGui.EndTabBar, ImGui.BeginTabBar(label, flags)); + public static IEndObject TabItem(string label) => new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label)); + public static IEndObject TabItem(ReadOnlySpan label) + => new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label)); + public static unsafe IEndObject TabItem(byte* label, ImGuiTabItemFlags flags) => new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label, null, flags)); public static unsafe IEndObject TabItem(string label, ImGuiTabItemFlags flags) - { - ArgumentNullException.ThrowIfNull(label); + => new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label, null, flags)); - // One-off for now, we should make this into a generic solution if we need it more often - const int labelMaxAlloc = 2048; - - var labelByteCount = Encoding.UTF8.GetByteCount(label); - - if (labelByteCount > labelMaxAlloc) - { - throw new ArgumentOutOfRangeException(nameof(label), $"Label is too long. (Longer than {labelMaxAlloc} bytes)"); - } - - var nativeLabelStackBytes = stackalloc byte[labelByteCount + 1]; - - int nativeLabelOffset; - fixed (char* utf16Ptr = label) - { - nativeLabelOffset = Encoding.UTF8.GetBytes(utf16Ptr, label.Length, nativeLabelStackBytes, labelByteCount); - } - - nativeLabelStackBytes[nativeLabelOffset] = 0; - - var ret = ImGui.BeginTabItem(nativeLabelStackBytes, null, flags); - return new EndConditionally(ImGui.EndTabItem, ret); - } + public static unsafe IEndObject TabItem(ReadOnlySpan label, ImGuiTabItemFlags flags) + => new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label, null, flags)); public static IEndObject TabItem(string label, ref bool open) => new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label, ref open)); + public static IEndObject TabItem(ReadOnlySpan label, ref bool open) + => new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label, ref open)); + public static IEndObject TabItem(string label, ref bool open, ImGuiTabItemFlags flags) => new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label, ref open, flags)); + public static IEndObject TabItem(ReadOnlySpan label, ref bool open, ImGuiTabItemFlags flags) + => new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label, ref open, flags)); + public static IEndObject TreeNode(string label) => new EndConditionally(ImGui.TreePop, ImGui.TreeNodeEx(label)); + public static IEndObject TreeNode(ReadOnlySpan label) + => new EndConditionally(ImGui.TreePop, ImGui.TreeNodeEx(label)); + public static IEndObject TreeNode(string label, ImGuiTreeNodeFlags flags) => new EndConditionally(flags.HasFlag(ImGuiTreeNodeFlags.NoTreePushOnOpen) ? Nop : ImGui.TreePop, ImGui.TreeNodeEx(label, flags)); + public static IEndObject TreeNode(ReadOnlySpan label, ImGuiTreeNodeFlags flags) + => new EndConditionally(flags.HasFlag(ImGuiTreeNodeFlags.NoTreePushOnOpen) ? Nop : ImGui.TreePop, ImGui.TreeNodeEx(label, flags)); + public static IEndObject Disabled() { ImGui.BeginDisabled(); diff --git a/Dalamud/Interface/Utility/Raii/Id.cs b/Dalamud/Interface/Utility/Raii/Id.cs index e3ab0bfcd..0cde4639a 100644 --- a/Dalamud/Interface/Utility/Raii/Id.cs +++ b/Dalamud/Interface/Utility/Raii/Id.cs @@ -9,6 +9,9 @@ public static partial class ImRaii public static Id PushId(string id, bool enabled = true) => enabled ? new Id().Push(id) : new Id(); + public static Id PushId(ReadOnlySpan id, bool enabled = true) + => enabled ? new Id().Push(id) : new Id(); + public static Id PushId(int id, bool enabled = true) => enabled ? new Id().Push(id) : new Id(); @@ -30,6 +33,17 @@ public static partial class ImRaii return this; } + public Id Push(ReadOnlySpan id, bool condition = true) + { + if (condition) + { + ImGui.PushID(id); + ++this.count; + } + + return this; + } + public Id Push(int id, bool condition = true) { if (condition) diff --git a/Dalamud/Interface/Utility/Raii/Plot.cs b/Dalamud/Interface/Utility/Raii/Plot.cs index a811282ea..d2ff38299 100644 --- a/Dalamud/Interface/Utility/Raii/Plot.cs +++ b/Dalamud/Interface/Utility/Raii/Plot.cs @@ -15,24 +15,42 @@ public static partial class ImRaii public static IEndObject Plot(string titleId, Vector2 size, ImPlotFlags flags) => new EndConditionally(ImPlot.EndPlot, ImPlot.BeginPlot(titleId, size, flags)); + public static IEndObject Plot(ReadOnlySpan titleId, Vector2 size, ImPlotFlags flags) + => new EndConditionally(ImPlot.EndPlot, ImPlot.BeginPlot(titleId, size, flags)); + public static IEndObject AlignedPlots(string groupId, bool vertical = true) => new EndConditionally(ImPlot.EndAlignedPlots, ImPlot.BeginAlignedPlots(groupId, vertical)); + public static IEndObject AlignedPlots(ReadOnlySpan groupId, bool vertical = true) + => new EndConditionally(ImPlot.EndAlignedPlots, ImPlot.BeginAlignedPlots(groupId, vertical)); + public static IEndObject LegendPopup(string labelId, ImGuiMouseButton mouseButton = ImGuiMouseButton.Right) => new EndConditionally(ImPlot.EndLegendPopup, ImPlot.BeginLegendPopup(labelId, mouseButton)); + public static IEndObject LegendPopup(ReadOnlySpan labelId, ImGuiMouseButton mouseButton = ImGuiMouseButton.Right) + => new EndConditionally(ImPlot.EndLegendPopup, ImPlot.BeginLegendPopup(labelId, mouseButton)); + public static IEndObject Subplots(string titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags = ImPlotSubplotFlags.None) => new EndConditionally(ImPlot.EndSubplots, ImPlot.BeginSubplots(titleId, rows, cols, size, flags)); + public static IEndObject Subplots(ReadOnlySpan titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags = ImPlotSubplotFlags.None) + => new EndConditionally(ImPlot.EndSubplots, ImPlot.BeginSubplots(titleId, rows, cols, size, flags)); + public static IEndObject Subplots(string titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags, ref float rowRatios, ref float colRatios) => new EndConditionally(ImPlot.EndSubplots, ImPlot.BeginSubplots(titleId, rows, cols, size, flags, ref rowRatios, ref colRatios)); + public static IEndObject Subplots(ReadOnlySpan titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags, ref float rowRatios, ref float colRatios) + => new EndConditionally(ImPlot.EndSubplots, ImPlot.BeginSubplots(titleId, rows, cols, size, flags, ref rowRatios, ref colRatios)); + public static IEndObject DragDropSourceAxis(ImAxis axis, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None) => new EndConditionally(ImPlot.EndDragDropSource, ImPlot.BeginDragDropSourceAxis(axis, flags)); public static IEndObject DragDropSourceItem(string labelId, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None) => new EndConditionally(ImPlot.EndDragDropSource, ImPlot.BeginDragDropSourceItem(labelId, flags)); + public static IEndObject DragDropSourceItem(ReadOnlySpan labelId, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None) + => new EndConditionally(ImPlot.EndDragDropSource, ImPlot.BeginDragDropSourceItem(labelId, flags)); + public static IEndObject DragDropSourcePlot(ImGuiDragDropFlags flags = ImGuiDragDropFlags.None) => new EndConditionally(ImPlot.EndDragDropSource, ImPlot.BeginDragDropSourcePlot(flags));