This commit is contained in:
Infi 2026-02-14 19:58:55 +00:00 committed by GitHub
commit 2abffd911b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 2946 additions and 870 deletions

View file

@ -45,28 +45,10 @@ public static partial class ImGuiComponents
/// <returns>Indicator if button is clicked.</returns> /// <returns>Indicator if button is clicked.</returns>
public static bool DisabledButton(string labelWithId, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) public static bool DisabledButton(string labelWithId, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f)
{ {
using var col = new ImRaii.Color(); using var col = ImRaii.PushColor(ImGuiCol.Button, defaultColor)
.Push(ImGuiCol.ButtonActive, activeColor)
if (defaultColor.HasValue) .Push(ImGuiCol.ButtonHovered, hoveredColor);
{ using var style = ImRaii.PushStyle(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * alphaMult);
col.Push(ImGuiCol.Button, defaultColor.Value); return ImGui.Button(labelWithId);
}
if (activeColor.HasValue)
{
col.Push(ImGuiCol.ButtonActive, activeColor.Value);
}
if (hoveredColor.HasValue)
{
col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value);
}
var style = ImGui.GetStyle();
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, style.Alpha * alphaMult))
{
return ImGui.Button(labelWithId);
}
} }
} }

View file

@ -1,8 +1,8 @@
using System.Numerics;
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using FFXIVClientStructs.FFXIV.Common.Math;
namespace Dalamud.Interface.Components; namespace Dalamud.Interface.Components;
/// <summary> /// <summary>
@ -24,12 +24,7 @@ public static partial class ImGuiComponents
/// <param name="color">The color of the icon.</param> /// <param name="color">The color of the icon.</param>
public static void HelpMarker(string helpText, FontAwesomeIcon icon, Vector4? color = null) public static void HelpMarker(string helpText, FontAwesomeIcon icon, Vector4? color = null)
{ {
using var col = new ImRaii.Color(); using var col = ImRaii.PushColor(ImGuiCol.TextDisabled, color);
if (color.HasValue)
{
col.Push(ImGuiCol.TextDisabled, color.Value);
}
ImGui.SameLine(); ImGui.SameLine();

View file

@ -123,22 +123,9 @@ public static partial class ImGuiComponents
/// <returns>Indicator if button is clicked.</returns> /// <returns>Indicator if button is clicked.</returns>
public static bool IconButton(string iconText, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) public static bool IconButton(string iconText, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null)
{ {
using var col = new ImRaii.Color(); using var col = ImRaii.PushColor(ImGuiCol.Button, defaultColor)
.Push(ImGuiCol.ButtonActive, activeColor)
if (defaultColor.HasValue) .Push(ImGuiCol.ButtonHovered, hoveredColor);
{
col.Push(ImGuiCol.Button, defaultColor.Value);
}
if (activeColor.HasValue)
{
col.Push(ImGuiCol.ButtonActive, activeColor.Value);
}
if (hoveredColor.HasValue)
{
col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value);
}
if (size.HasValue) if (size.HasValue)
{ {
@ -203,22 +190,9 @@ public static partial class ImGuiComponents
/// <returns>Indicator if button is clicked.</returns> /// <returns>Indicator if button is clicked.</returns>
public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null)
{ {
using var col = new ImRaii.Color(); using var col = ImRaii.PushColor(ImGuiCol.Button, defaultColor)
.Push(ImGuiCol.ButtonActive, activeColor)
if (defaultColor.HasValue) .Push(ImGuiCol.ButtonHovered, hoveredColor);
{
col.Push(ImGuiCol.Button, defaultColor.Value);
}
if (activeColor.HasValue)
{
col.Push(ImGuiCol.ButtonActive, activeColor.Value);
}
if (hoveredColor.HasValue)
{
col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value);
}
if (size.HasValue) if (size.HasValue)
{ {

View file

@ -55,12 +55,9 @@ internal unsafe partial class UiDebug
private void DrawSidebar() private void DrawSidebar()
{ {
using var gr = ImRaii.Group(); using var gr = ImRaii.Group();
if (gr.Success) this.DrawNameSearch();
{ this.DrawAddonSelectionList();
this.DrawNameSearch(); this.elementSelector.DrawInterface();
this.DrawAddonSelectionList();
this.elementSelector.DrawInterface();
}
} }
private void DrawNameSearch() private void DrawNameSearch()

View file

@ -893,8 +893,8 @@ internal class SeStringCreatorWidget : IDataWindowWidget
return; return;
} }
using var node = asTreeNode ? this.SeStringTreeNode(id, rosss) : null; using var node = asTreeNode ? this.SeStringTreeNode(id, rosss) : default;
if (asTreeNode && !node!) return; if (asTreeNode && !node) return;
if (!asTreeNode && renderSeString) if (!asTreeNode && renderSeString)
{ {
@ -1237,7 +1237,7 @@ internal class SeStringCreatorWidget : IDataWindowWidget
return parameters.OrderBy(x => x.Key).Select(x => x.Value).ToArray(); return parameters.OrderBy(x => x.Key).Select(x => x.Value).ToArray();
} }
private ImRaii.IEndObject SeStringTreeNode(string id, ReadOnlySeStringSpan previewText, uint color = 0xFF00FFFF, ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.None) private ImRaii.TreeNodeDisposable SeStringTreeNode(string id, ReadOnlySeStringSpan previewText, uint color = 0xFF00FFFF, ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.None)
{ {
using var titleColor = ImRaii.PushColor(ImGuiCol.Text, color); using var titleColor = ImRaii.PushColor(ImGuiCol.Text, color);
var node = ImRaii.TreeNode("##" + id, flags); var node = ImRaii.TreeNode("##" + id, flags);

View file

@ -172,13 +172,8 @@ public static partial class ImGuiHelpers
/// <param name="color">The color of the text.</param> /// <param name="color">The color of the text.</param>
public static void ClickToCopyText(ImU8String text, ImU8String textCopy = default, Vector4? color = null) public static void ClickToCopyText(ImU8String text, ImU8String textCopy = default, Vector4? color = null)
{ {
using (var col = new ImRaii.Color()) using (ImRaii.PushColor(ImGuiCol.Text, color))
{ {
if (color.HasValue)
{
col.Push(ImGuiCol.Text, color.Value);
}
ImGui.Text(text.Span); ImGui.Text(text.Span);
} }

View file

@ -1,69 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
// Push an arbitrary amount of colors into an object that are all popped when it is disposed.
// If condition is false, no color is pushed.
public static partial class ImRaii
{
public static Color PushColor(ImGuiCol idx, uint color, bool condition = true)
=> new Color().Push(idx, color, condition);
public static Color PushColor(ImGuiCol idx, Vector4 color, bool condition = true)
=> new Color().Push(idx, color, condition);
// Push colors that revert all current color changes made temporarily.
public static Color DefaultColors()
{
var ret = new Color();
var reverseStack = Color.Stack.GroupBy(p => p.Item1).Select(p => (p.Key, p.First().Item2)).ToArray();
foreach (var (idx, val) in reverseStack)
ret.Push(idx, val);
return ret;
}
public sealed class Color : IDisposable
{
internal static readonly List<(ImGuiCol, uint)> Stack = [];
private int count;
public Color Push(ImGuiCol idx, uint color, bool condition = true)
{
if (condition)
{
Stack.Add((idx, ImGui.GetColorU32(idx)));
ImGui.PushStyleColor(idx, color);
++this.count;
}
return this;
}
public Color Push(ImGuiCol idx, Vector4 color, bool condition = true)
{
if (condition)
{
Stack.Add((idx, ImGui.GetColorU32(idx)));
ImGui.PushStyleColor(idx, color);
++this.count;
}
return this;
}
public void Pop(int num = 1)
{
num = Math.Min(num, this.count);
this.count -= num;
ImGui.PopStyleColor(num);
Stack.RemoveRange(Stack.Count - num, num);
}
public void Dispose()
=> this.Pop(this.count);
}
}

View file

@ -0,0 +1,99 @@
using System.Numerics;
using Dalamud.Bindings.ImGui;
// ReSharper disable once CheckNamespace
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui child windows. </summary>
public ref struct ChildDisposable : IDisposable
{
/// <summary> Whether creating the child window succeeded and it is at least partly visible. </summary>
public readonly bool Success;
/// <summary> Gets a value indicating whether the child window is already ended. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="ChildDisposable"/> struct. </summary>
/// <param name="strId"> The ID of the child as text. </param>
/// <returns> A disposable object that evaluates to true if any part of the begun child is currently visible. Use with using. </returns>
internal ChildDisposable(ImU8String strId)
{
this.Success = ImGui.BeginChild(strId);
this.Alive = true;
}
/// <summary>Initializes a new instance of the <see cref="ChildDisposable"/> struct. </summary>
/// <param name="strId"> The ID of the child as text. </param>
/// <param name="size"> The desired size of the child. </param>
/// <returns> A disposable object that evaluates to true if any part of the begun child is currently visible. Use with using. </returns>
internal ChildDisposable(ImU8String strId, Vector2 size)
{
this.Success = ImGui.BeginChild(strId, size);
this.Alive = true;
}
/// <summary>Initializes a new instance of the <see cref="ChildDisposable"/> struct. </summary>
/// <param name="strId"> The ID of the child as text. </param>
/// <param name="size"> The desired size of the child. </param>
/// <param name="border"> Whether the child should be framed by a border. </param>
/// <returns> A disposable object that evaluates to true if any part of the begun child is currently visible. Use with using. </returns>
internal ChildDisposable(ImU8String strId, Vector2 size, bool border)
{
this.Success = ImGui.BeginChild(strId, size, border);
this.Alive = true;
}
/// <summary>Initializes a new instance of the <see cref="ChildDisposable"/> struct. </summary>
/// <param name="strId"> The ID of the child as text. </param>
/// <param name="size"> The desired size of the child. </param>
/// <param name="border"> Whether the child should be framed by a border. </param>
/// <param name="flags"> Additional flags for the child. </param>
/// <returns> A disposable object that evaluates to true if any part of the begun child is currently visible. Use with using. </returns>
internal ChildDisposable(ImU8String strId, Vector2 size, bool border, ImGuiWindowFlags flags)
{
this.Success = ImGui.BeginChild(strId, size, border, flags);
this.Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(ChildDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(ChildDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(ChildDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(ChildDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(ChildDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(ChildDisposable i, bool value)
=> i.Success || value;
/// <summary> End the child window on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
ImGui.EndChild();
this.Alive = false;
}
/// <summary> End a child window without using an IDisposable.</summary>
public static void EndUnsafe()
=> ImGui.EndChild();
}
}

View file

@ -0,0 +1,118 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui;
// ReSharper disable once CheckNamespace
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
public sealed class ColorDisposable : IDisposable
{
internal static readonly List<(ImGuiCol Type, uint BackupColor)> Stack = [];
/// <summary> Gets the number of colors currently pushed using this disposable. </summary>
public int Count { get; private set; }
/// <summary> Push a color to the color stack. </summary>
/// <param name="type"> The type of color to change. </param>
/// <param name="color"> The color to change it to. </param>
/// <param name="condition"> If this is false, the color is not pushed. </param>
/// <returns> A disposable object that can be used to push further colors and pops those colors after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep colors pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public ColorDisposable Push(ImGuiCol type, uint color, bool condition = true)
=> condition ? this.InternalPush(type, color) : this;
/// <summary> Push a color to the color stack. </summary>
/// <param name="type"> The type of color to change. </param>
/// <param name="color"> The color to change it to. </param>
/// /// <param name="condition"> If this is false, the color is not pushed. </param>
/// <returns> A disposable object that can be used to push further colors and pops those colors after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep colors pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public ColorDisposable Push(ImGuiCol type, Vector4 color, bool condition = true)
=> condition ? this.InternalPush(type, color) : this;
/// <summary> Push a color to the color stack. </summary>
/// <param name="type"> The type of color to change. </param>
/// <param name="color"> The color to change it to. If this is null, no color will be set. </param>
/// /// <param name="condition"> If this is false, the color is not pushed. </param>
/// <returns> A disposable object that can be used to push further colors and pops those colors after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep colors pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public ColorDisposable Push(ImGuiCol type, Vector4? color, bool condition = true)
{
if (!color.HasValue)
return this;
return condition ? this.InternalPush(type, color.Value) : this;
}
private ColorDisposable InternalPush(ImGuiCol type, uint color)
{
Stack.Add((type, ImGui.GetColorU32(type)));
ImGui.PushStyleColor(type, color);
++this.Count;
return this;
}
private ColorDisposable InternalPush(ImGuiCol type, Vector4 color)
{
Stack.Add((type, ImGui.GetColorU32(type)));
ImGui.PushStyleColor(type, color);
++this.Count;
return this;
}
/// <summary> Reverts all pushed colors to their previous values temporarily. </summary>
/// <returns> A disposable object that can be used to push further colors and pops those colors after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep colors pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public static ColorDisposable DefaultColors()
{
var ret = new ColorDisposable();
var reverseStack = Stack.GroupBy(p => p.Type).Select(p => (p.Key, p.First().BackupColor)).ToArray();
foreach (var (idx, val) in reverseStack)
ret.Push(idx, val);
return ret;
}
/// <summary> Push the default value, i.e. the value as if nothing was ever pushed to this, of a color to the color stack. </summary>
/// <param name="type"> The type of color to return to its default value. </param>
/// <returns> A disposable object that can be used to push further colors and pops those colors after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep colors pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public ColorDisposable PushDefault(ImGuiCol type)
{
foreach (var styleMod in Stack.Where(m => m.Type == type))
return this.Push(type, styleMod.BackupColor);
return this;
}
/// <summary> Pop a number of colors. </summary>
/// <param name="num"> The number of colors to pop. This is clamped to the number of colors pushed by this object. </param>
public ColorDisposable Pop(int num = 1)
{
num = Math.Min(num, this.Count);
if (num > 0)
{
this.Count -= num;
ImGui.PopStyleColor(num);
Stack.RemoveRange(Stack.Count - num, num);
}
return this;
}
/// <summary> Pop all pushed colors. </summary>
public void Dispose()
{
this.Pop(this.Count);
this.Count = 0;
}
/// <summary> Pop a number of colors. </summary>
/// <param name="num"> The number of colors to pop. The number is not checked against the color stack. </param>
/// <remarks> Avoid using this function, and colors across scopes, as much as possible. </remarks>
public static void PopUnsafe(int num = 1)
=> ImGui.PopStyleColor(num);
}
}

View file

@ -0,0 +1,72 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around creating pre-table style column separation. </summary>
public ref struct ColumnsDisposable : IDisposable
{
/// <summary> The columns before pushing this to revert to. </summary>
public readonly int LastColumns;
/// <summary> Get the current number of columns. </summary>
public int Count
=> ImGui.GetColumnsCount();
/// <summary> Get the index of the current column. </summary>
public int Current
=> ImGui.GetColumnIndex();
/// <summary> Move to the next column. </summary>
public void Next()
=> ImGui.NextColumn();
/// <summary> Get or set the offset of the current column. </summary>
public float Offset
{
get => ImGui.GetColumnOffset(Current);
set => ImGui.SetColumnOffset(Current, value);
}
/// <summary> Get the offset of a column by index. </summary>
public float GetOffset(int index)
=> ImGui.GetColumnOffset(index);
/// <summary> Set the offset of a column by index. </summary>
public void SetOffset(int index, float value)
=> ImGui.SetColumnOffset(index, value);
/// <summary> Get or set the width of the current column. </summary>
public float Width
{
get => ImGui.GetColumnWidth(Current);
set => ImGui.SetColumnWidth(Current, value);
}
/// <summary> Get the width of a column by index. </summary>
public float GetWidth(int index)
=> ImGui.GetColumnWidth(index);
/// <summary> Set the width of a column by index. </summary>
public void SetWidth(int index, float width)
=> ImGui.SetColumnWidth(index, width);
/// <summary> Create a new column separation. </summary>
/// <param name="count"> The number of columns to separate. </param>
/// <param name="id"> An ID for the separation. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <param name="border"> Whether the columns should be separated by borders. </param>
/// <remarks> The columns system is outdated. Prefer to use <see cref="Table"/> instead. </remarks>
public ColumnsDisposable(int count, ImU8String id, bool border = false)
{
this.LastColumns = this.Count;
ImGui.Columns(count, id, border);
}
/// <summary> Revert to the prior number of columns. </summary>
public void Dispose()
=> ImGui.Columns(Math.Max(this.LastColumns, 1));
}
}

View file

@ -0,0 +1,90 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around creating pre-table style column separation. </summary>
public ref struct ComboDisposable : IDisposable
{
/// <summary> Whether creating the combo box succeeded and it is expanded. </summary>
public readonly bool Success;
/// <summary> Gets a value indicating whether the combo box is already ended. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="ComboDisposable"/> struct. </summary>
/// <param name="label"> The combo label as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <param name="previewValue"> The currently displayed string in the combo field as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <returns> A disposable object that evaluates to true if the begun combo popup is currently open. Use with using. </returns>
internal ComboDisposable(ImU8String label, ImU8String previewValue)
{
this.Success = ImGui.BeginCombo(label, previewValue);
this.Alive = true;
}
/// <summary>Initializes a new instance of the <see cref="ComboDisposable"/> struct. </summary>
/// <param name="label"> The combo label as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <param name="previewValue"> The currently displayed string in the combo field as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <param name="flags"> Additional flags to control the combo's behaviour. </param>
/// <returns> A disposable object that evaluates to true if the begun combo popup is currently open. Use with using. </returns>
internal ComboDisposable(ImU8String label, ImU8String previewValue, ImGuiComboFlags flags)
{
this.Success = ImGui.BeginCombo(label, previewValue, flags);
this.Alive = true;
}
// /// <summary> Begin a combo popup and end it on leaving scope. </summary>
// /// <param name="id"> The combos popup ID as provided by <see cref="Im.Combo.DrawPreview"/>. </param>
// /// <param name="boundingBox"> The combo preview's bounding box as provided by <see cref="Im.Combo.DrawPreview"/>. </param>
// /// <param name="flags"> Additional flags to control the combo's behaviour. </param>
// /// <returns> A disposable object that evaluates to true if the begun combo popup is currently open. Use with using. </returns>
// internal ComboDisposable(ImGuiId id, in Rectangle boundingBox, ImGuiComboFlags flags)
// {
// Success = ImGui.Combo(id, boundingBox, flags);
// Alive = true;
// }
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(ComboDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(ComboDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(ComboDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(ComboDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(ComboDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(ComboDisposable i, bool value)
=> i.Success || value;
/// <summary> End the combo box on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImGui.EndCombo();
this.Alive = false;
}
/// <summary> End a combo box without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImGui.EndCombo();
}
}

View file

@ -0,0 +1,58 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around disabled state. </summary>
public sealed class DisabledDisposable : IDisposable
{
/// <summary> The global count of disabled pushes to reenable. </summary>
public static int GlobalCount;
/// <summary> Gets the number of disabled states currently pushed using this disposable. </summary>
public int Count { get; private set; }
/// <summary> Push a disabled state onto the stack. </summary>
/// <param name="condition"> Whether to actually push a disabled state. </param>
/// <returns> A disposable object that can be used to push further disabled states for whatever reason and pop them on leaving scope. Use with using.</returns>
/// <remarks> If you need to keep a disabled state pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public DisabledDisposable Push(bool condition)
=> condition ? this.Push() : this;
/// <inheritdoc cref="Push(bool)"/>
public DisabledDisposable Push()
{
ImGui.BeginDisabled(true);
++this.Count;
++GlobalCount;
return this;
}
/// <summary> Pop a number of disabled states. </summary>
/// <param name="num"> The number of disabled states to pop. This is clamped to the number of disabled states pushed by this object. </param>
public void Pop(int num = 1)
{
num = Math.Min(num, this.Count);
this.Count -= num;
GlobalCount -= num;
while (num-- > 0)
ImGui.EndDisabled();
}
/// <summary> Pop all disabled states. </summary>
public void Dispose()
=> this.Pop(this.Count);
/// <summary> Pop a number of disabled states. </summary>
/// <param name="num"> The number of disabled states to pop. The number is not checked against the disabled stack. </param>
/// <remarks> Avoid using this function, and disabled states across scopes, as much as possible. </remarks>
public static void PopUnsafe(int num = 1)
{
while (num-- > 0)
ImGui.EndDisabled();
}
}
}

View file

@ -0,0 +1,74 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around a ImGui Drag and Drop Source. </summary>
public ref struct DragDropSourceDisposable : IDisposable
{
/// <summary> Whether creating the drag and drop source succeeded. </summary>
public readonly bool Success;
/// <summary> Whether the drag and drop source is already ended. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="DragDropSourceDisposable"/> struct. </summary>
/// <returns> A disposable object that indicates whether the source is active. Use with using. </returns>
public DragDropSourceDisposable()
{
this.Success = ImGui.BeginDragDropSource();
this.Alive = true;
}
/// <summary>Initializes a new instance of the <see cref="DragDropSourceDisposable"/> struct. </summary>
/// <param name="flags"> Additional flags to control the drag and drop behaviour. </param>
/// <returns> A disposable object that indicates whether the source is active. Use with using. </returns>
internal DragDropSourceDisposable(ImGuiDragDropFlags flags)
{
this.Success = ImGui.BeginDragDropSource(flags);
this.Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(DragDropSourceDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(DragDropSourceDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(DragDropSourceDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(DragDropSourceDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(DragDropSourceDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(DragDropSourceDisposable i, bool value)
=> i.Success || value;
/// <summary> End the drag and drop source on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImGui.EndDragDropSource();
this.Alive = false;
}
/// <summary> End a drag and drop source without using an IDisposable.</summary>
public static void EndUnsafe()
=> ImGui.EndDragDropSource();
}
}

View file

@ -0,0 +1,65 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around a ImGui Drag and Drop Target. </summary>
public ref struct DragDropTargetDisposable : IDisposable
{
/// <summary> Whether creating the drag and drop target succeeded. </summary>
public readonly bool Success;
/// <summary> Gets a value indicating whether the drag and drop target is already ended. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="DragDropTargetDisposable"/> struct. </summary>
/// <returns> A disposable object that indicates whether the target is active. Use with using. </returns>
public DragDropTargetDisposable()
{
this.Success = ImGui.BeginDragDropTarget();
this.Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(DragDropTargetDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(DragDropTargetDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(DragDropTargetDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(DragDropTargetDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(DragDropTargetDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(DragDropTargetDisposable i, bool value)
=> i.Success || value;
/// <summary> End the drag and drop target on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImGui.EndDragDropTarget();
this.Alive = false;
}
/// <summary> End a drag and drop target without using an IDisposable.</summary>
public static void EndUnsafe()
=> ImGui.EndDragDropTarget();
}
}

View file

@ -0,0 +1,46 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around re-enabling the state. </summary>
public sealed class EnabledDisposable : IDisposable
{
/// <summary> The stored number of ended disposables as a workaround. </summary>
public int Count { get; private set; }
/// <summary> Enforce an enabled state by popping the global number of disabled states. </summary>
/// <param name="condition"> Whether to force the enabled state. </param>
/// <returns> A disposable object that will push the prior number of enabled states after leaving scope. Use with using. </returns>
/// <remarks> This is a workaround for the problem that you can not force the state to be enabled without knowing the disabled stack's size. </remarks>
public EnabledDisposable(bool condition)
{
if (!condition)
return;
this.Count = DisabledDisposable.GlobalCount;
var count = this.Count;
while (count-- > 0)
ImGui.EndDisabled();
}
/// <inheritdoc cref="EnabledDisposable(bool)"/>
public EnabledDisposable()
{
this.Count = DisabledDisposable.GlobalCount;
var count = this.Count;
while (count-- > 0)
ImGui.EndDisabled();
}
/// <summary> Return to the prior disabled state. </summary>
public void Dispose()
{
while (this.Count-- > 0)
ImGui.BeginDisabled(true);
}
}
}

View file

@ -0,0 +1,66 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around pushing fonts. </summary>
public sealed class FontDisposable : IDisposable
{
internal static int FontPushCounter;
internal static ImFontPtr DefaultPushed;
/// <summary> The number of fonts currently pushed using this disposable. </summary>
public int Count { get; private set; }
/// <summary> Push a font to the font stack. </summary>
/// <param name="font"> The font to push. </param>
/// <param name="condition"> If this is false, the font is not pushed. </param>
/// <returns> A disposable object that can be used to push further fonts and pops those fonts after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep fonts pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public FontDisposable Push(ImFontPtr font, bool condition = true)
=> condition ? this.InternalPush(font) : this;
// Push the default font if any other font is currently pushed.
/// <summary> Push the default font if any other font is currently pushed. </summary>
public static FontDisposable DefaultFont()
=> new FontDisposable().Push(DefaultPushed, FontPushCounter > 0);
/// <inheritdoc cref="Push(ImSharp.Im.Font,bool)"/>
private FontDisposable InternalPush(ImFontPtr font)
{
if (FontPushCounter++ == 0)
DefaultPushed = ImGui.GetFont();
ImGui.PushFont(font);
++this.Count;
return this;
}
/// <summary> Pop a number of fonts. </summary>
/// <param name="num"> The number of fonts to pop. This is clamped to the number of fonts pushed by this object. </param>
public void Pop(int num = 1)
{
num = Math.Min(num, this.Count);
this.Count -= num;
FontPushCounter -= num;
while (num-- > 0)
ImGui.PopFont();
}
/// <summary> Pop all pushed fonts. </summary>
public void Dispose()
=> this.Pop(this.Count);
/// <summary> Pop a number of fonts. </summary>
/// <param name="num"> The number of fonts to pop. The number is not checked against the font stack. </param>
/// <remarks> Avoid using this function, and fonts across scopes, as much as possible. </remarks>
public static void PopUnsafe(int num = 1)
{
while (num-- > 0)
ImGui.PopFont();
}
}
}

View file

@ -0,0 +1,38 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui groups. </summary>
public ref struct GroupDisposable : IDisposable
{
/// <summary> Whether the group is still open. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="GroupDisposable"/> struct. </summary>
/// <returns> A disposable object. Use with using. </returns>
/// <remarks> Groups can be used to group multiple items together and treat them as a single item. </remarks>
public GroupDisposable()
{
this.Alive = true;
ImGui.BeginGroup();
}
/// <summary> End the group on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
ImGui.EndGroup();
this.Alive = false;
}
/// <summary> End a Group without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImGui.EndGroup();
}
}

View file

@ -0,0 +1,77 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui collapsing headers that also push an ID. </summary>
public ref struct HeaderDisposable : IDisposable
{
/// <summary> Whether the collapsing header is currently opened. </summary>
public readonly bool Success;
/// <summary> Whether the ID node is already popped. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="HeaderDisposable"/> struct. </summary>
/// <param name="label"> The header label and ID as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <param name="flags"> Additional flags to control the header's behaviour. </param>
/// <returns> A disposable object that evaluates to true if the collapsing header is currently open. Use with using. </returns>
internal HeaderDisposable(ImU8String label, ImGuiTreeNodeFlags flags)
{
this.Success = ImGui.CollapsingHeader(label, flags);
if (this.Success)
ImGui.PushID(label);
this.Alive = true;
}
/// <summary>Initializes a new instance of the <see cref="HeaderDisposable"/> struct. </summary>
/// <param name="label"> The header label and ID as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <param name="visible"> If true, displays a small close button on the upper right of the header, which will set this to false when clicked. If false, do not display the header at all. </param>
/// <param name="flags"> Additional flags to control the header's behaviour. </param>
/// <returns> A disposable object that evaluates to true if the collapsing header is currently open. Use with using. </returns>
internal HeaderDisposable(ImU8String label, ref bool visible, ImGuiTreeNodeFlags flags)
{
this.Success = ImGui.CollapsingHeader(label, ref visible, flags);
if (this.Success)
ImGui.PushID(label);
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(HeaderDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(HeaderDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(HeaderDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(HeaderDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(HeaderDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(HeaderDisposable i, bool value)
=> i.Success || value;
/// <summary> Pop the tree node on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImGui.PopID();
this.Alive = false;
}
}
}

View file

@ -0,0 +1,76 @@
using System.Runtime.CompilerServices;
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ID pushing. </summary>
public sealed class IdDisposable : IDisposable
{
/// <summary> The number of IDs currently pushed using this disposable. </summary>
public int Count { get; private set; }
/// <summary> Push a numerical ID to the ID stack and pop it on leaving scope. </summary>
/// <param name="id"> The ID. </param>
/// <returns> A disposable object that counts the number of pushes and can be used to push further IDs. Use with using. </returns>
/// <remarks> If you need to keep IDs pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
[OverloadResolutionPriority(100)]
public IdDisposable Push(ImU8String id, bool enabled = true)
{
if (!enabled)
return this;
++this.Count;
ImGui.PushID(id);
return this;
}
/// <inheritdoc cref="Push(ImU8String)"/>
public IdDisposable Push(int id, bool enabled = true)
{
if (!enabled)
return this;
++this.Count;
ImGui.PushID(id);
return this;
}
/// <inheritdoc cref="Push(ImU8String)"/>
public unsafe IdDisposable Push(nint id, bool enabled = true)
{
if (!enabled)
return this;
++this.Count;
ImGui.PushID(id.ToPointer());
return this;
}
/// <summary> Pop a number of IDs from the ID stack. </summary>
/// <param name="count"> The number of IDs to pop. This is clamped to the number of IDs pushed by this object. </param>
public void Pop(int count = 1)
{
if (count > this.Count)
count = this.Count;
this.Count -= count;
while (count-- > 0)
ImGui.PopID();
}
/// <summary> Pop all pushed IDs. </summary>
public void Dispose()
=> this.Pop(this.Count);
/// <summary> Pop a number of IDs from the ID stack without using an IDisposable. </summary>
/// <remarks> Avoid using this function, and IDs across scopes, as much as possible. </remarks>
public static void PopUnsafe(int num = 1)
{
while (num-- > 0)
ImGui.PopID();
}
}
}

View file

@ -0,0 +1,79 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around indentation. </summary>
public sealed class IndentDisposable : IDisposable
{
/// <summary> The current indentation pushed by this object. </summary>
public float CurrentIndent { get; private set; }
/// <summary> Add to the current indentation. </summary>
/// <param name="indent"> The value to change the indentation by. </param>
/// <param name="condition"> If this is false, the current indent is not changed. </param>
/// <returns> A disposable object that can be used to change the indentation more and reverts to the prior indentation after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep indentation for longer than the current scope, use without using. </remarks>
public IndentDisposable Indent(float indent, bool scaled = true, bool condition = true)
{
if (condition && indent is not 0)
{
if (scaled)
indent *= ImGuiHelpers.GlobalScale;
if (indent < 0)
ImGui.Unindent(-indent);
else
ImGui.Indent(indent);
this.CurrentIndent += indent;
}
return this;
}
/// <summary> Add to the current indentation. </summary>
/// <param name="indent"> The value to change the indentation by. </param>
/// <param name="condition"> If this is false, the current indent is not changed. </param>
/// <returns> A disposable object that can be used to change the indentation more and reverts to the prior indentation after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep indentation for longer than the current scope, use without using. </remarks>
public IndentDisposable Indent(int indent = 1, bool condition = true)
{
if (condition && indent is not 0)
{
var spacing = indent * ImGui.GetStyle().IndentSpacing;
if (indent < 0)
ImGui.Unindent(-spacing);
else
ImGui.Indent(spacing);
this.CurrentIndent += spacing;
}
return this;
}
/// <summary> Subtract from the current indentation. </summary>
/// <param name="indent"> The value to change the indentation by. </param>
/// <param name="condition"> If this is false, the current indent is not changed. </param>
/// <returns> A disposable object that can be used to change the indentation more and reverts to the prior indentation after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep indentation for longer than the current scope, use without using. </remarks>
public IndentDisposable Unindent(float indent, bool condition = true)
{
if (condition && indent is not 0)
{
if (indent < 0)
ImGui.Indent(-indent);
else
ImGui.Unindent(indent);
this.CurrentIndent -= indent;
}
return this;
}
/// <summary> Revert all indentation applied by this object. </summary>
public void Dispose()
=> this.Unindent(this.CurrentIndent);
}
}

View file

@ -0,0 +1,53 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around pushing item widths. </summary>
public sealed class ItemWidthDisposable : IDisposable
{
/// <summary> The number of item widths currently pushed using this disposable. </summary>
public int Count { get; private set; }
/// <summary> Push an item width to the item width stack. </summary>
/// <param name="width"> The item width to push in pixels. </param>
/// <param name="condition"> If this is false, the item width is not pushed. </param>
/// <returns> A disposable object that can be used to push further item widths and pops those item widths after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep item widths pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public ItemWidthDisposable Push(float width, bool condition)
=> condition ? this.Push(width) : this;
/// <inheritdoc cref="Push(float,bool)"/>
public ItemWidthDisposable Push(float width)
{
ImGui.PushItemWidth(width);
++this.Count;
return this;
}
/// <summary> Pop a number of item widths. </summary>
/// <param name="num"> The number of item widths to pop. This is clamped to the number of item widths pushed by this object. </param>
public void Pop(int num = 1)
{
num = Math.Min(num, this.Count);
this.Count -= num;
while (num-- > 0)
ImGui.PopItemWidth();
}
/// <summary> Pop all pushed item widths. </summary>
public void Dispose()
=> this.Pop(this.Count);
/// <summary> Pop a number of item widths. </summary>
/// <param name="num"> The number of item widths to pop. The number is not checked against the item width stack. </param>
/// <remarks> Avoid using this function, and item widths across scopes, as much as possible. </remarks>
public static void PopUnsafe(int num = 1)
{
while (num-- > 0)
ImGui.PopItemWidth();
}
}
}

View file

@ -0,0 +1,83 @@
// ReSharper disable once CheckNamespace
using System.Numerics;
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui list boxes. </summary>
public ref struct ListBoxDisposable : IDisposable
{
/// <summary> Whether creating the list box succeeded and it is expanded. </summary>
public readonly bool Success;
/// <summary> Whether the list box is already ended. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="ListBoxDisposable"/> struct. </summary>
/// <param name="label"> The list box label as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <returns> A disposable object that evaluates to true if the begun list box is currently visible. Use with using. </returns>
internal ListBoxDisposable(ImU8String label)
{
this.Success = ImGui.BeginListBox(label);
this.Alive = true;
}
/// <summary>Initializes a new instance of the <see cref="ListBoxDisposable"/> struct. </summary>
/// <param name="label"> The list box label as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <param name="size">
/// The size of the box. If these values are greater than 0, use them as pixel counts.
/// If .X == 0, use the current item width.
/// If .y == 0, use an arbitrary default height of about 7 items.
/// If they are less than 0, align to the right or bottom respectively.
/// </param>
/// <returns> A disposable object that evaluates to true if the begun list box is currently visible. Use with using. </returns>
internal ListBoxDisposable(ImU8String label, Vector2 size)
{
this.Success = ImGui.BeginListBox(label, size);
this.Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(ListBoxDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(ListBoxDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(ListBoxDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(ListBoxDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(ListBoxDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(ListBoxDisposable i, bool value)
=> i.Success || value;
/// <summary> End the list box on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImGui.EndListBox();
this.Alive = false;
}
/// <summary> End a list box without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImGui.EndListBox();
}
}

View file

@ -0,0 +1,66 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui main menu bars. </summary>
public ref struct MainMenuBarDisposable : IDisposable
{
/// <summary> Whether creating the main menu bar succeeded. </summary>
public readonly bool Success;
/// <summary> Whether the main menu bar is already ended. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="MainMenuBarDisposable"/> struct. </summary>
/// <returns> A disposable object that evaluates to true if the main menu bar was created. Use with using. </returns>
/// <remarks> Can create or append to a full screen menu bar at the top of the screen. </remarks>
public MainMenuBarDisposable()
{
this.Success = ImGui.BeginMainMenuBar();
this.Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(MainMenuBarDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(MainMenuBarDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(MainMenuBarDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(MainMenuBarDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(MainMenuBarDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(MainMenuBarDisposable i, bool value)
=> i.Success || value;
/// <summary> End the main menu bar on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImGui.EndMainMenuBar();
this.Alive = false;
}
/// <summary> End a main menu bar without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImGui.EndMainMenuBar();
}
}

View file

@ -0,0 +1,66 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui menu bars. </summary>
public ref struct MenuBarDisposable : IDisposable
{
/// <summary> Whether creating the menu bar succeeded. </summary>
public readonly bool Success;
/// <summary> Whether the menu bar is already ended. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="MenuBarDisposable"/> struct. </summary>
/// <returns> A disposable object that evaluates to true if the main menu bar was created. Use with using. </returns>
/// <remarks> Can create or append to a window that has <seealso cref="WindowFlags.MenuBar"/> set. </remarks>
public MenuBarDisposable()
{
Success = ImGui.BeginMenuBar();
Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(MenuBarDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(MenuBarDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(MenuBarDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(MenuBarDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(MenuBarDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(MenuBarDisposable i, bool value)
=> i.Success || value;
/// <summary> End the menu bar on leaving scope. </summary>
public void Dispose()
{
if (!Alive)
return;
if (Success)
ImGui.EndMenuBar();
Alive = false;
}
/// <summary> End a menu bar without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImGui.EndMenuBar();
}
}

View file

@ -0,0 +1,78 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui menus. </summary>
public ref struct MenuDisposable : IDisposable
{
/// <summary> Whether creating the menu bar succeeded. </summary>
public readonly bool Success;
/// <summary> Whether the menu is already ended. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="MenuDisposable"/> struct. </summary>
/// <param name="label"> The label of the menu as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <returns> A disposable object that evaluates to true if the menu was created. Use with using. </returns>
/// <remarks> Can create or append to a menu that already exists in a menu bar. </remarks>
internal MenuDisposable(ImU8String label)
{
this.Success = ImGui.BeginMenu(label);
this.Alive = true;
}
/// <summary>Initializes a new instance of the <see cref="MenuDisposable"/> struct. </summary>
/// <param name="label"> The label of the menu as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <param name="enabled"> Whether the menu is enabled or not. </param>
/// <returns> A disposable object that evaluates to true if the menu was created. Use with using. </returns>
/// <remarks> Can create or append to a menu that already exists in a menu bar. </remarks>
internal MenuDisposable(ImU8String label, bool enabled)
{
this.Success = ImGui.BeginMenu(label, enabled);
this.Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(MenuDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(MenuDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(MenuDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(MenuDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(MenuDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(MenuDisposable i, bool value)
=> i.Success || value;
/// <summary> End the menu on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImGui.EndMenu();
this.Alive = false;
}
/// <summary> End a menu without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImGui.EndMenu();
}
}

View file

@ -0,0 +1,73 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImPlot;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui tables. </summary>
public ref struct PlotAlignedDisposable : IDisposable
{
/// <summary> Whether creating the table succeeded. This needs to be checked before calling any of the member methods. </summary>
public readonly bool Success;
/// <summary> Whether the table is already ended. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="PlotAlignedDisposable"/> struct. </summary>
/// <param name="groupId"> The ID of the plot as text. </param>
/// <param name="vertical"> Whether the plot should be vertical. </param>
internal PlotAlignedDisposable(string groupId, bool vertical = true)
{
this.Success = ImPlot.BeginAlignedPlots(groupId, vertical);
this.Alive = true;
}
/// <inheritdoc cref="PlotAlignedDisposable(string,bool)"/>
internal PlotAlignedDisposable(ReadOnlySpan<byte> groupId, bool vertical = true)
{
this.Success = ImPlot.BeginAlignedPlots(groupId, vertical);
this.Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(PlotAlignedDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(PlotAlignedDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(PlotAlignedDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(PlotAlignedDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(PlotAlignedDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(PlotAlignedDisposable i, bool value)
=> i.Success || value;
/// <summary> End the Table on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImPlot.EndAlignedPlots();
this.Alive = false;
}
/// <summary> End a Table without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImPlot.EndAlignedPlots();
}
}

View file

@ -0,0 +1,124 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Bindings.ImPlot;
// ReSharper disable once CheckNamespace
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
public sealed class PlotColorDisposable : IDisposable
{
internal static readonly List<(ImPlotCol Type, uint BackupColor)> Stack = [];
/// <summary> Gets the number of colors currently pushed using this disposable. </summary>
public int Count { get; private set; }
/// <summary> Push a color to the color stack. </summary>
/// <param name="type"> The type of color to change. </param>
/// <param name="color"> The color to change it to. </param>
/// <param name="condition"> If this is false, the color is not pushed. </param>
/// <returns> A disposable object that can be used to push further colors and pops those colors after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep colors pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public PlotColorDisposable Push(ImPlotCol type, uint color, bool condition = true)
=> condition ? this.InternalPush(type, color) : this;
/// <summary> Push a color to the color stack. </summary>
/// <param name="type"> The type of color to change. </param>
/// <param name="color"> The color to change it to. </param>
/// /// <param name="condition"> If this is false, the color is not pushed. </param>
/// <returns> A disposable object that can be used to push further colors and pops those colors after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep colors pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public PlotColorDisposable Push(ImPlotCol type, Vector4 color, bool condition = true)
=> condition ? this.InternalPush(type, color) : this;
/// <summary> Push a color to the color stack. </summary>
/// <param name="type"> The type of color to change. </param>
/// <param name="color"> The color to change it to. If this is null, no color will be set. </param>
/// /// <param name="condition"> If this is false, the color is not pushed. </param>
/// <returns> A disposable object that can be used to push further colors and pops those colors after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep colors pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public PlotColorDisposable Push(ImPlotCol type, Vector4? color, bool condition = true)
{
if (!color.HasValue)
return this;
return condition ? this.InternalPush(type, color.Value) : this;
}
private PlotColorDisposable InternalPush(ImPlotCol type, uint color)
{
Stack.Add((type, GetColorU32(type)));
ImPlot.PushStyleColor(type, color);
++this.Count;
return this;
}
private PlotColorDisposable InternalPush(ImPlotCol type, Vector4 color)
{
Stack.Add((type, GetColorU32(type)));
ImPlot.PushStyleColor(type, color);
++this.Count;
return this;
}
/// <summary> Reverts all pushed colors to their previous values temporarily. </summary>
/// <returns> A disposable object that can be used to push further colors and pops those colors after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep colors pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public static PlotColorDisposable PlotDefaultColors()
{
var ret = new PlotColorDisposable();
var reverseStack = Stack.GroupBy(p => p.Type).Select(p => (p.Key, p.First().BackupColor)).ToArray();
foreach (var (idx, val) in reverseStack)
ret.Push(idx, val);
return ret;
}
/// <summary> Push the default value, i.e. the value as if nothing was ever pushed to this, of a color to the color stack. </summary>
/// <param name="type"> The type of color to return to its default value. </param>
/// <returns> A disposable object that can be used to push further colors and pops those colors after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep colors pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public PlotColorDisposable PlotPushDefault(ImPlotCol type)
{
foreach (var styleMod in Stack.Where(m => m.Type == type))
return this.Push(type, styleMod.BackupColor);
return this;
}
/// <summary> Pop a number of colors. </summary>
/// <param name="num"> The number of colors to pop. This is clamped to the number of colors pushed by this object. </param>
public PlotColorDisposable Pop(int num = 1)
{
num = Math.Min(num, this.Count);
if (num > 0)
{
this.Count -= num;
ImPlot.PopStyleColor(num);
Stack.RemoveRange(Stack.Count - num, num);
}
return this;
}
/// <summary> Pop all pushed colors. </summary>
public void Dispose()
{
this.Pop(this.Count);
this.Count = 0;
}
/// <summary> Pop a number of colors. </summary>
/// <param name="num"> The number of colors to pop. The number is not checked against the color stack. </param>
/// <remarks> Avoid using this function, and colors across scopes, as much as possible. </remarks>
public static void PopUnsafe(int num = 1)
=> ImPlot.PopStyleColor(num);
// Reimplementation of https://github.com/ocornut/imgui/blob/868facff9ded2d61425c67deeba354eb24275bd1/imgui.cpp#L3035
// for ImPlot
private static uint GetColorU32(ImPlotCol idx)
=> ImGui.GetColorU32(ImPlot.GetStyle().Colors[(int)idx]);
}
}

View file

@ -0,0 +1,75 @@
// ReSharper disable once CheckNamespace
using System.Numerics;
using Dalamud.Bindings.ImPlot;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui tables. </summary>
public ref struct PlotDisposable : IDisposable
{
/// <summary> Whether creating the table succeeded. This needs to be checked before calling any of the member methods. </summary>
public readonly bool Success;
/// <summary> Whether the table is already ended. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="PlotDisposable"/> struct. </summary>
/// <param name="titleId"> The ID of the plot as text. </param>
/// <param name="size"> The desired size of the plot. </param>
/// <param name="flags"> Additional flags for the plot. </param>
internal PlotDisposable(string titleId, Vector2 size, ImPlotFlags flags)
{
this.Success = ImPlot.BeginPlot(titleId, size, flags);
this.Alive = true;
}
/// <inheritdoc cref="PlotDisposable(string,Vector2,ImPlotFlags)"/>
internal PlotDisposable(ReadOnlySpan<byte> titleId, Vector2 size, ImPlotFlags flags)
{
this.Success = ImPlot.BeginPlot(titleId, size, flags);
this.Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(PlotDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(PlotDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(PlotDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(PlotDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(PlotDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(PlotDisposable i, bool value)
=> i.Success || value;
/// <summary> End the Table on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImPlot.EndPlot();
this.Alive = false;
}
/// <summary> End a Table without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImPlot.EndPlot();
}
}

View file

@ -0,0 +1,93 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
using Dalamud.Bindings.ImPlot;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui tables. </summary>
public ref struct PlotDragDropSourceDisposable : IDisposable
{
/// <summary> Whether creating the table succeeded. This needs to be checked before calling any of the member methods. </summary>
public readonly bool Success;
/// <summary> Whether the table is already ended. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="PlotDragDropSourceDisposable"/> struct. </summary>
/// <param name="labelId"> The ID of the plot as text. </param>
/// <param name="flags"> Additional flags to control the drag and drop behaviour. </param>
internal PlotDragDropSourceDisposable(string labelId, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None)
{
this.Success = ImPlot.BeginDragDropSourceItem(labelId, flags);
this.Alive = true;
}
/// <inheritdoc cref="PlotDragDropSourceDisposable(string,ImGuiDragDropFlags)"/>
internal PlotDragDropSourceDisposable(ReadOnlySpan<byte> labelId, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None)
{
this.Success = ImPlot.BeginDragDropSourceItem(labelId, flags);
this.Alive = true;
}
/// <summary> Initialize a new instance of the <see cref="PlotDragDropSourceDisposable"/> struct with SourceAxis. </summary>
/// <param name="axis"> The axis to drag. </param>
/// <param name="flags"> Additional flags to control the drag and drop behaviour. </param>
/// <returns> A disposable object that indicates whether the source is active. Use with using. </returns>
internal static PlotDragDropSourceDisposable AxisPlot(ImAxis axis, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None)
=> new(ImPlot.BeginDragDropSourceAxis(axis, flags));
/// <summary> Initialize a new instance of the <see cref="PlotDragDropSourceDisposable"/> struct with SourcePlot. </summary>
/// <param name="flags"> Additional flags to control the drag and drop behaviour. </param>
/// <returns> A disposable object that indicates whether the source is active. Use with using. </returns>
internal static PlotDragDropSourceDisposable SourcePlot(ImGuiDragDropFlags flags = ImGuiDragDropFlags.None)
=> new(ImPlot.BeginDragDropSourcePlot(flags));
private PlotDragDropSourceDisposable(bool success)
{
this.Success = success;
this.Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(PlotDragDropSourceDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(PlotDragDropSourceDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(PlotDragDropSourceDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(PlotDragDropSourceDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(PlotDragDropSourceDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(PlotDragDropSourceDisposable i, bool value)
=> i.Success || value;
/// <summary> End the Table on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImPlot.EndDragDropSource();
this.Alive = false;
}
/// <summary> End a Table without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImPlot.EndDragDropSource();
}
}

View file

@ -0,0 +1,83 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
using Dalamud.Bindings.ImPlot;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui tables. </summary>
public ref struct PlotDragDropTargetDisposable : IDisposable
{
/// <summary> Whether creating the table succeeded. This needs to be checked before calling any of the member methods. </summary>
public readonly bool Success;
/// <summary> Whether the table is already ended. </summary>
public bool Alive { get; private set; }
/// <summary> Initialize a new instance of the <see cref="PlotDragDropTargetDisposable"/> struct with SourceAxis. </summary>
/// <param name="axis"> The axis to drag. </param>
/// <param name="flags"> Additional flags to control the drag and drop behaviour. </param>
/// <returns> A disposable object that indicates whether the source is active. Use with using. </returns>
internal static PlotDragDropTargetDisposable AxisPlot(ImAxis axis)
=> new(ImPlot.BeginDragDropTargetAxis(axis));
/// <summary> Initialize a new instance of the <see cref="PlotDragDropTargetDisposable"/> struct with SourcePlot. </summary>
/// <param name="flags"> Additional flags to control the drag and drop behaviour. </param>
/// <returns> A disposable object that indicates whether the source is active. Use with using. </returns>
internal static PlotDragDropTargetDisposable LegendPlot()
=> new(ImPlot.BeginDragDropTargetLegend());
/// <summary> Initialize a new instance of the <see cref="PlotDragDropTargetDisposable"/> struct with SourcePlot. </summary>
/// <param name="flags"> Additional flags to control the drag and drop behaviour. </param>
/// <returns> A disposable object that indicates whether the source is active. Use with using. </returns>
internal static PlotDragDropTargetDisposable SourcePlot()
=> new(ImPlot.BeginDragDropTargetPlot());
private PlotDragDropTargetDisposable(bool success)
{
this.Success = success;
this.Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(PlotDragDropTargetDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(PlotDragDropTargetDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(PlotDragDropTargetDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(PlotDragDropTargetDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(PlotDragDropTargetDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(PlotDragDropTargetDisposable i, bool value)
=> i.Success || value;
/// <summary> End the Table on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImPlot.EndDragDropTarget();
this.Alive = false;
}
/// <summary> End a Table without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImPlot.EndDragDropTarget();
}
}

View file

@ -0,0 +1,74 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
using Dalamud.Bindings.ImPlot;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui tables. </summary>
public ref struct PlotLegendDisposable : IDisposable
{
/// <summary> Whether creating the table succeeded. This needs to be checked before calling any of the member methods. </summary>
public readonly bool Success;
/// <summary> Whether the table is already ended. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="PlotLegendDisposable"/> struct. </summary>
/// <param name="labelId"> The ID of the plot as text. </param>
/// <param name="mouseButton"> The mouse button to use for opening the legend popup. </param>
internal PlotLegendDisposable(string labelId, ImGuiMouseButton mouseButton = ImGuiMouseButton.Right)
{
this.Success = ImPlot.BeginLegendPopup(labelId, mouseButton);
this.Alive = true;
}
/// <inheritdoc cref="PlotLegendDisposable(string,ImGuiMouseButton)"/>
internal PlotLegendDisposable(ReadOnlySpan<byte> labelId, ImGuiMouseButton mouseButton = ImGuiMouseButton.Right)
{
this.Success = ImPlot.BeginLegendPopup(labelId, mouseButton);
this.Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(PlotLegendDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(PlotLegendDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(PlotLegendDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(PlotLegendDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(PlotLegendDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(PlotLegendDisposable i, bool value)
=> i.Success || value;
/// <summary> End the Table on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImPlot.EndLegendPopup();
this.Alive = false;
}
/// <summary> End a Table without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImPlot.EndLegendPopup();
}
}

View file

@ -0,0 +1,188 @@
// ReSharper disable once CheckNamespace
using System.Numerics;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Bindings.ImGui;
using Dalamud.Bindings.ImPlot;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around style pushing. </summary>
public sealed class PlotStyleDisposable : IDisposable
{
internal static readonly List<(ImPlotStyleVar Type, Vector2 Value)> Stack = [];
/// <summary> The number of styles currently pushed using this disposable. </summary>
public int Count { get; private set; }
/// <summary> Push a style variable to the style stack. </summary>
/// <param name="type"> The type of style variable to change. </param>
/// <param name="value"> The value to change it to. </param>
/// <param name="condition"> If this is false, the style is not pushed. </param>
/// <returns> A disposable object that can be used to push further style variables and pops those style variables after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep styles pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public PlotStyleDisposable Push(ImPlotStyleVar type, float value, bool condition)
=> condition ? this.Push(type, value) : this;
/// <inheritdoc cref="Push(ImGuiStyleVar,float,bool)"/>
public PlotStyleDisposable Push(ImPlotStyleVar type, Vector2 value, bool condition)
=> condition ? this.Push(type, value) : this;
public PlotStyleDisposable Push(ImPlotStyleVar type, float value)
{
CheckStyleIdx(type, typeof(float));
Stack.Add((type, GetStyle(type)));
ImPlot.PushStyleVar(type, value);
++this.Count;
return this;
}
/// <inheritdoc cref="Push(ImGuiStyleVar,float,bool)"/>
public PlotStyleDisposable Push(ImPlotStyleVar type, Vector2 value)
{
CheckStyleIdx(type, typeof(Vector2));
Stack.Add((type, GetStyle(type)));
ImPlot.PushStyleVar(type, value);
++this.Count;
return this;
}
/// <summary> Push styles that revert all current style changes made temporarily. </summary>
public static PlotStyleDisposable PlotDefaultStyle()
{
var ret = new PlotStyleDisposable();
var reverseStack = Stack.GroupBy(p => p.Type).Select(p => (p.Key, p.First().Value)).ToArray();
foreach (var (idx, val) in reverseStack)
{
if (idx == ImPlotStyleVar.Marker)
ret.Push(idx, (int)val.X);
else if (float.IsNaN(val.Y))
ret.Push(idx, val.X);
else
ret.Push(idx, val);
}
return ret;
}
/// <summary> Push the default value, i.e. the value as if nothing was ever pushed to this, of a style variable to the style stack. </summary>
/// <param name="type"> The type of style variable to return to its default value. </param>
/// <returns> A disposable object that can be used to push further style variables and pops those style variables after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep styles pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public PlotStyleDisposable PlotPushDefault(ImPlotStyleVar type)
{
foreach (var styleMod in Stack.Where(m => m.Type == type))
return Push(type, styleMod.Value);
return this;
}
/// <summary> Pop a number of style variables. </summary>
/// <param name="num"> The number of style variables to pop. This is clamped to the number of style variables pushed by this object. </param>
public PlotStyleDisposable Pop(int num = 1)
{
num = Math.Min(num, this.Count);
if (num > 0)
{
this.Count -= num;
ImPlot.PopStyleVar(num);
Stack.RemoveRange(Stack.Count - num, num);
}
return this;
}
/// <summary> Pop all pushed styles. </summary>
public void Dispose()
{
ImPlot.PopStyleVar(this.Count);
this.Count = 0;
}
/// <summary> Pop a number of style variables. </summary>
/// <param name="num"> The number of style variables to pop. The number is not checked against the style stack. </param>
/// <remarks> Avoid using this function, and styles across scopes, as much as possible. </remarks>
public static void PopUnsafe(int num = 1)
=> ImPlot.PopStyleVar(num);
private static void CheckStyleIdx(ImPlotStyleVar idx, Type type)
{
var shouldThrow = idx switch
{
ImPlotStyleVar.LineWeight => type != typeof(float),
ImPlotStyleVar.Marker => type != typeof(int),
ImPlotStyleVar.MarkerSize => type != typeof(float),
ImPlotStyleVar.MarkerWeight => type != typeof(float),
ImPlotStyleVar.FillAlpha => type != typeof(float),
ImPlotStyleVar.ErrorBarSize => type != typeof(float),
ImPlotStyleVar.ErrorBarWeight => type != typeof(float),
// ImPlotStyleVar.DigitalBitHeight => type != typeof(float),
// ImPlotStyleVar.DigitalBitGap => type != typeof(float),
// ImPlotStyleVar.PlotBorderSize => type != typeof(float),
ImPlotStyleVar.MinorAlpha => type != typeof(float),
ImPlotStyleVar.MajorTickLen => type != typeof(Vector2),
ImPlotStyleVar.MinorTickLen => type != typeof(Vector2),
ImPlotStyleVar.MajorTickSize => type != typeof(Vector2),
ImPlotStyleVar.MinorTickSize => type != typeof(Vector2),
ImPlotStyleVar.MajorGridSize => type != typeof(Vector2),
ImPlotStyleVar.MinorGridSize => type != typeof(Vector2),
// ImPlotStyleVar.PlotPadding => type != typeof(Vector2),
ImPlotStyleVar.LabelPadding => type != typeof(Vector2),
ImPlotStyleVar.LegendPadding => type != typeof(Vector2),
ImPlotStyleVar.LegendInnerPadding => type != typeof(Vector2),
ImPlotStyleVar.LegendSpacing => type != typeof(Vector2),
ImPlotStyleVar.MousePosPadding => type != typeof(Vector2),
ImPlotStyleVar.AnnotationPadding => type != typeof(Vector2),
ImPlotStyleVar.FitPadding => type != typeof(Vector2),
// ImPlotStyleVar.PlotDefaultSize => type != typeof(Vector2),
// ImPlotStyleVar.PlotMinSize => type != typeof(Vector2),
_ => throw new ArgumentOutOfRangeException(nameof(idx), idx, null),
};
if (shouldThrow)
throw new ArgumentException($"Unable to push {type} to {idx}.");
}
public static Vector2 GetStyle(ImPlotStyleVar idx)
{
var style = ImPlot.GetStyle();
return idx switch
{
ImPlotStyleVar.LineWeight => new Vector2(style.LineWeight, float.NaN),
ImPlotStyleVar.Marker => new Vector2(style.Marker, float.NaN),
ImPlotStyleVar.MarkerSize => new Vector2(style.MarkerSize, float.NaN),
ImPlotStyleVar.MarkerWeight => new Vector2(style.MarkerWeight, float.NaN),
ImPlotStyleVar.FillAlpha => new Vector2(style.FillAlpha, float.NaN),
ImPlotStyleVar.ErrorBarSize => new Vector2(style.ErrorBarSize, float.NaN),
ImPlotStyleVar.ErrorBarWeight => new Vector2(style.ErrorBarWeight, float.NaN),
// ImPlotStyleVar.DigitalBitHeight => new Vector2(style.DigitalBitHeight, float.NaN),
// ImPlotStyleVar.DigitalBitGap => new Vector2(style.DigitalBitGap, float.NaN),
// ImPlotStyleVar.PlotBorderSize => new Vector2(style.PlotBorderSize, float.NaN),
ImPlotStyleVar.MinorAlpha => new Vector2(style.MinorAlpha, float.NaN),
ImPlotStyleVar.MajorTickLen => style.MajorTickLen,
ImPlotStyleVar.MinorTickLen => style.MinorTickLen,
ImPlotStyleVar.MajorTickSize => style.MajorTickSize,
ImPlotStyleVar.MinorTickSize => style.MinorTickSize,
ImPlotStyleVar.MajorGridSize => style.MajorGridSize,
ImPlotStyleVar.MinorGridSize => style.MinorGridSize,
// ImPlotStyleVar.PlotPadding => style.PlotPadding,
ImPlotStyleVar.LabelPadding => style.LabelPadding,
ImPlotStyleVar.LegendPadding => style.LegendPadding,
ImPlotStyleVar.LegendInnerPadding => style.LegendInnerPadding,
ImPlotStyleVar.LegendSpacing => style.LegendSpacing,
ImPlotStyleVar.MousePosPadding => style.MousePosPadding,
ImPlotStyleVar.AnnotationPadding => style.AnnotationPadding,
ImPlotStyleVar.FitPadding => style.FitPadding,
// ImPlotStyleVar.PlotDefaultSize => style.PlotDefaultSize,
// ImPlotStyleVar.PlotMinSize => style.PlotMinSize,
_ => throw new ArgumentOutOfRangeException(nameof(idx), idx, null),
};
}
}
}

View file

@ -0,0 +1,99 @@
// ReSharper disable once CheckNamespace
using System.Numerics;
using Dalamud.Bindings.ImPlot;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui tables. </summary>
public ref struct PlotSubDisposable : IDisposable
{
/// <summary> Whether creating the table succeeded. This needs to be checked before calling any of the member methods. </summary>
public readonly bool Success;
/// <summary> Whether the table is already ended. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="PlotSubDisposable"/> struct. </summary>
/// <param name="titleId"> The ID of the plot as text. </param>
/// <param name="rows"> The number of rows in the plot. </param>
/// <param name="cols"> The number of columns in the plot. </param>
/// <param name="size"> The desired size of the plot. </param>
/// <param name="flags"> Additional flags for the plot. </param>
internal PlotSubDisposable(string titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags = ImPlotSubplotFlags.None)
{
this.Success = ImPlot.BeginSubplots(titleId, rows, cols, size, flags);
this.Alive = true;
}
/// <inheritdoc cref="PlotSubDisposable(string,int,int,Vector2,ImPlotSubplotFlags)"/>
internal PlotSubDisposable(ReadOnlySpan<byte> titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags = ImPlotSubplotFlags.None)
{
this.Success = ImPlot.BeginSubplots(titleId, rows, cols, size, flags);
this.Alive = true;
}
/// <summary>Initializes a new instance of the <see cref="PlotSubDisposable"/> struct. </summary>
/// <param name="titleId"> The ID of the plot as text. </param>
/// <param name="rows"> The number of rows in the plot. </param>
/// <param name="cols"> The number of columns in the plot. </param>
/// <param name="size"> The desired size of the plot. </param>
/// <param name="flags"> Additional flags for the plot. </param>
/// <param name="rowRatios"> The row ratios for the plot. </param>
/// <param name="colRatios"> The column ratios for the plot. </param>
internal PlotSubDisposable(string titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags, ref float rowRatios, ref float colRatios)
{
this.Success = ImPlot.BeginSubplots(titleId, rows, cols, size, flags, ref rowRatios, ref colRatios);
this.Alive = true;
}
/// <inheritdoc cref="PlotSubDisposable(string,int,int,Vector2,ImPlotSubplotFlags,ref float,ref float)"/>
internal PlotSubDisposable(ReadOnlySpan<byte> titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags, ref float rowRatios, ref float colRatios)
{
this.Success = ImPlot.BeginSubplots(titleId, rows, cols, size, flags, ref rowRatios, ref colRatios);
this.Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(PlotSubDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(PlotSubDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(PlotSubDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(PlotSubDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(PlotSubDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(PlotSubDisposable i, bool value)
=> i.Success || value;
/// <summary> End the Table on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImPlot.EndSubplots();
this.Alive = false;
}
/// <summary> End a Table without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImPlot.EndSubplots();
}
}

View file

@ -0,0 +1,101 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui popups. </summary>
public ref struct PopupDisposable : IDisposable
{
/// <summary> Whether creating the popup succeeded and it is open. </summary>
public readonly bool Success;
/// <summary> Whether the popup is already ended. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="PopupDisposable"/> struct. </summary>
/// <param name="id"> The ID of the popup as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <param name="flags"> Flags to forward to the popup window creation. </param>
/// <returns> A disposable object that evaluates to true if the begun popup is currently open. Use with using. </returns>
internal PopupDisposable(ImU8String id, ImGuiWindowFlags flags = ImGuiWindowFlags.None)
{
this.Success = ImGui.BeginPopup(id, flags);
this.Alive = true;
}
/// <summary> Open a popup when clicking on the last drawn item and begin it. </summary>
/// <param name="id"> The ID of the popup. </param>
/// <param name="flags"> Additional flags to control the popups behavior and the button to click to open it. </param>
/// <returns> A disposable object that evaluates to true if the begun popup is currently open. Use with using. </returns>
internal static PopupDisposable ContextItem(ImU8String id, ImGuiPopupFlags flags = ImGuiPopupFlags.MouseButtonRight)
=> new(ImGui.BeginPopupContextItem(id, flags));
/// <summary> Open a popup when clicking on empty space in the current window and begin it. </summary>
/// <param name="id"> The ID of the popup. </param>
/// <param name="flags"> Additional flags to control the popups behavior and the button to click to open it. </param>
/// <returns> A disposable object that evaluates to true if the begun popup is currently open. Use with using. </returns>
internal static PopupDisposable ContextWindow(ImU8String id, ImGuiPopupFlags flags = ImGuiPopupFlags.MouseButtonRight)
=> new(ImGui.BeginPopupContextWindow(id, flags));
/// <summary> Begin a modal popup and end it on leaving scope. </summary>
/// <param name="title"> The title of the popup as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <param name="open"> Whether the modal should be kept open or closed. </param>
/// <param name="flags"> Flags to pass to the popup window creation. </param>
/// <returns> A disposable object that evaluates to true if the begun popup is currently open. Use with using. </returns>
/// <remarks> Modal popups block interactions behind the popup and can not be closed by the user, add a dimming background and have a title bar. </remarks>
internal static PopupDisposable Modal(ImU8String title, ref bool open, ImGuiWindowFlags flags = ImGuiWindowFlags.None)
=> new(ImGui.BeginPopupModal(title, ref open, flags));
/// <inheritdoc cref="Modal(ImU8String,ref bool,ImGuiWindowFlags)"/>
internal static PopupDisposable Modal(ImU8String title, ImGuiWindowFlags flags = ImGuiWindowFlags.None)
=> new(ImGui.BeginPopupModal(title, flags));
private PopupDisposable(bool success)
{
this.Success = success;
this.Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(PopupDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(PopupDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(PopupDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(PopupDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(PopupDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(PopupDisposable i, bool value)
=> i.Success || value;
/// <summary> End the popup on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImGui.EndPopup();
this.Alive = false;
}
/// <summary> End a popup without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImGui.EndPopup();
}
}

View file

@ -1,43 +1,112 @@
// ReSharper disable once CheckNamespace
using System.Numerics;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii; namespace Dalamud.Interface.Utility.Raii;
// Push an arbitrary amount of styles into an object that are all popped when it is disposed.
// If condition is false, no style is pushed.
// In debug mode, checks that the type of the value given for the style is valid.
public static partial class ImRaii public static partial class ImRaii
{ {
public static Style PushStyle(ImGuiStyleVar idx, float value, bool condition = true) /// <summary> A wrapper around style pushing. </summary>
=> new Style().Push(idx, value, condition); public sealed class StyleDisposable : IDisposable
public static Style PushStyle(ImGuiStyleVar idx, Vector2 value, bool condition = true)
=> new Style().Push(idx, value, condition);
// Push styles that revert all current style changes made temporarily.
public static Style DefaultStyle()
{ {
var ret = new Style(); internal static readonly List<(ImGuiStyleVar Type, Vector2 Value)> Stack = [];
var reverseStack = Style.Stack.GroupBy(p => p.Item1).Select(p => (p.Key, p.First().Item2)).ToArray();
foreach (var (idx, val) in reverseStack) /// <summary> The number of styles currently pushed using this disposable. </summary>
public int Count { get; private set; }
/// <summary> Push a style variable to the style stack. </summary>
/// <param name="type"> The type of style variable to change. </param>
/// <param name="value"> The value to change it to. </param>
/// <param name="condition"> If this is false, the style is not pushed. </param>
/// <returns> A disposable object that can be used to push further style variables and pops those style variables after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep styles pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public StyleDisposable Push(ImGuiStyleVar type, float value, bool condition)
=> condition ? this.Push(type, value) : this;
/// <inheritdoc cref="Push(ImGuiStyleVar,float,bool)"/>
public StyleDisposable Push(ImGuiStyleVar type, Vector2 value, bool condition)
=> condition ? this.Push(type, value) : this;
public StyleDisposable Push(ImGuiStyleVar type, float value)
{ {
if (float.IsNaN(val.Y)) CheckStyleIdx(type, typeof(float));
ret.Push(idx, val.X); Stack.Add((type, GetStyle(type)));
else
ret.Push(idx, val); ImGui.PushStyleVar(type, value);
++this.Count;
return this;
} }
return ret; /// <inheritdoc cref="Push(ImGuiStyleVar,float,bool)"/>
} public StyleDisposable Push(ImGuiStyleVar type, Vector2 value)
{
CheckStyleIdx(type, typeof(Vector2));
Stack.Add((type, GetStyle(type)));
public sealed class Style : IDisposable ImGui.PushStyleVar(type, value);
{ ++this.Count;
internal static readonly List<(ImGuiStyleVar, Vector2)> Stack = []; return this;
}
private int count; /// <summary> Push styles that revert all current style changes made temporarily. </summary>
public static StyleDisposable DefaultStyle()
{
var ret = new StyleDisposable();
var reverseStack = Stack.GroupBy(p => p.Type).Select(p => (p.Key, p.First().Value)).ToArray();
foreach (var (idx, val) in reverseStack)
{
if (float.IsNaN(val.Y))
ret.Push(idx, val.X);
else
ret.Push(idx, val);
}
return ret;
}
/// <summary> Push the default value, i.e. the value as if nothing was ever pushed to this, of a style variable to the style stack. </summary>
/// <param name="type"> The type of style variable to return to its default value. </param>
/// <returns> A disposable object that can be used to push further style variables and pops those style variables after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep styles pushed longer than the current scope, use without using and use <seealso cref="PopUnsafe"/>. </remarks>
public StyleDisposable PushDefault(ImGuiStyleVar type)
{
foreach (var styleMod in Stack.Where(m => m.Type == type))
return Push(type, styleMod.Value);
return this;
}
/// <summary> Pop a number of style variables. </summary>
/// <param name="num"> The number of style variables to pop. This is clamped to the number of style variables pushed by this object. </param>
public StyleDisposable Pop(int num = 1)
{
num = Math.Min(num, this.Count);
if (num > 0)
{
this.Count -= num;
ImGui.PopStyleVar(num);
Stack.RemoveRange(Stack.Count - num, num);
}
return this;
}
/// <summary> Pop all pushed styles. </summary>
public void Dispose()
{
ImGui.PopStyleVar(this.Count);
this.Count = 0;
}
/// <summary> Pop a number of style variables. </summary>
/// <param name="num"> The number of style variables to pop. The number is not checked against the style stack. </param>
/// <remarks> Avoid using this function, and styles across scopes, as much as possible. </remarks>
public static void PopUnsafe(int num = 1)
=> ImGui.PopStyleVar(num);
private static void CheckStyleIdx(ImGuiStyleVar idx, Type type) private static void CheckStyleIdx(ImGuiStyleVar idx, Type type)
{ {
@ -108,42 +177,5 @@ public static partial class ImRaii
_ => throw new ArgumentOutOfRangeException(nameof(idx), idx, null), _ => throw new ArgumentOutOfRangeException(nameof(idx), idx, null),
}; };
} }
public Style Push(ImGuiStyleVar idx, float value, bool condition = true)
{
if (!condition)
return this;
CheckStyleIdx(idx, typeof(float));
Stack.Add((idx, GetStyle(idx)));
ImGui.PushStyleVar(idx, value);
++this.count;
return this;
}
public Style Push(ImGuiStyleVar idx, Vector2 value, bool condition = true)
{
if (!condition)
return this;
CheckStyleIdx(idx, typeof(Vector2));
Stack.Add((idx, GetStyle(idx)));
ImGui.PushStyleVar(idx, value);
++this.count;
return this;
}
public void Pop(int num = 1)
{
num = Math.Min(num, this.count);
this.count -= num;
ImGui.PopStyleVar(num);
Stack.RemoveRange(Stack.Count - num, num);
}
public void Dispose()
=> this.Pop(this.count);
} }
} }

View file

@ -0,0 +1,76 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui tab bars. </summary>
public ref struct TabBarDisposable : IDisposable
{
/// <summary> Whether creating the tab bar succeeded. </summary>
public readonly bool Success;
/// <summary> Whether the tab bar is already ended. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="TabBarDisposable"/> struct. </summary>
/// <param name="label"> The tab bar label as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <returns> A disposable object that evaluates to true if any part of the begun tab bar is currently visible. Use with using. </returns>
internal TabBarDisposable(ImU8String label)
{
this.Success = ImGui.BeginTabBar(label);
this.Alive = true;
}
/// <summary>Initializes a new instance of the <see cref="TabBarDisposable"/> struct. </summary>
/// <param name="label"> The tab bar label as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <param name="flags"> Additional flags to control the tab bar's behaviour. </param>
/// <returns> A disposable object that evaluates to true if any part of the begun tab bar is currently visible. Use with using. </returns>
internal TabBarDisposable(ImU8String label, ImGuiTabBarFlags flags)
{
this.Success = ImGui.BeginTabBar(label, flags);
this.Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(TabBarDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(TabBarDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(TabBarDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(TabBarDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(TabBarDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(TabBarDisposable i, bool value)
=> i.Success || value;
/// <summary> End the tab bar on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImGui.EndTabBar();
this.Alive = false;
}
/// <summary> End a tab bar without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImGui.EndTabBar();
}
}

View file

@ -0,0 +1,89 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui tab items. </summary>
public unsafe ref struct TabItemDisposable : IDisposable
{
/// <summary> Whether creating the tab item succeeded, and it is currently selected. </summary>
public readonly bool Success;
/// <summary> Whether the tab item is already ended. </summary>
public bool Alive { get; private set; }
/// <inheritdoc cref="TabItemDisposable(ImU8String,ref bool,ImGuiTabItemFlags)"/>
internal TabItemDisposable(ImU8String label)
{
this.Success = ImGui.BeginTabItem(label);
this.Alive = true;
}
/// <inheritdoc cref="TabItemDisposable(ImU8String,ref bool,ImGuiTabItemFlags)"/>
internal TabItemDisposable(ImU8String label, ImGuiTabItemFlags flags)
{
this.Success = ImGui.BeginTabItem(label, flags);
this.Alive = true;
}
/// <inheritdoc cref="TabItemDisposable(ImU8String,ref bool,ImGuiTabItemFlags)"/>
internal TabItemDisposable(ImU8String label, ref bool open)
{
this.Success = ImGui.BeginTabItem(label, ref open);
this.Alive = true;
}
/// <summary>Initializes a new instance of the <see cref="TabItemDisposable"/> struct. </summary>
/// <param name="label"> The tab item label as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <param name="open"> Whether the tab item is currently open. If this is provided, the tab item will render a close button that controls this value. </param>
/// <param name="flags"> Additional flags to control the tab item's behaviour. </param>
/// <returns> A disposable object that evaluates to true if the tab item is currently opened. Use with using. </returns>
internal TabItemDisposable(ImU8String label, scoped ref bool open, ImGuiTabItemFlags flags)
{
this.Success = ImGui.BeginTabItem(label, ref open, flags);
this.Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(TabItemDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(TabItemDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(TabItemDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(TabItemDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(TabItemDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(TabItemDisposable i, bool value)
=> i.Success || value;
/// <summary> End the tab item on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImGui.EndTabItem();
this.Alive = false;
}
/// <summary> End a tab item without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImGui.EndTabItem();
}
}

View file

@ -0,0 +1,100 @@
// ReSharper disable once CheckNamespace
using System.Numerics;
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui tables. </summary>
public ref struct TableDisposable : IDisposable
{
/// <summary> Whether creating the table succeeded. This needs to be checked before calling any of the member methods. </summary>
public readonly bool Success;
/// <summary> Whether the table is already ended. </summary>
public bool Alive { get; private set; }
/// <inheritdoc cref="TableDisposable(ImU8String,int,ImGuiTableFlags,Vector2,float)"/>
internal TableDisposable(ImU8String id, int columns)
{
this.Success = ImGui.BeginTable(id, columns);
this.Alive = true;
}
/// <inheritdoc cref="TableDisposable(ImU8String,int,ImGuiTableFlags,Vector2,float)"/>
internal TableDisposable(ImU8String id, int columns, ImGuiTableFlags flags)
{
this.Success = ImGui.BeginTable(id, columns, flags);
this.Alive = true;
}
/// <inheritdoc cref="TableDisposable(ImU8String,int,ImGuiTableFlags,Vector2,float)"/>
internal TableDisposable(ImU8String id, int columns, ImGuiTableFlags flags, Vector2 outerSize)
{
this.Success = ImGui.BeginTable(id, columns, flags, outerSize);
this.Alive = true;
}
/// <summary>Initializes a new instance of the <see cref="TableDisposable"/> struct. </summary>
/// <param name="id"> The table ID as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <param name="columns"> The number of columns in the table. </param>
/// <param name="flags"> Additional flags to control the table's behaviour. </param>
/// <param name="outerSize">
/// The size the table is fixed to.
/// <list type="bullet">
/// <item> If this is non-positive in X, right-align from the available region. (0 means full available width). </item>
/// <item> If this is positive in X, set a fixed width. </item>
/// <item> If both scroll-bars are disabled and this is negative in Y, right-align from the available region. (0 means full available width). </item>
/// </list>
/// The behaviour in Y is dependent on the existence of scroll-bars and other <paramref name="flags"/> (see <see href="https://github.com/ocornut/imgui/blob/master/imgui_tables.cpp" >imgui_tables.cpp</see>).
/// </param>
/// <param name="innerWidth"> The inner width in case the horizontal scroll-bar is enabled. If 0, fits into <paramref name="outerSize"/>.X, otherwise overrides the scrolling width. Negative values make no sense. </param>
/// <returns> A disposable object that evaluates to true if any part of the begun table is currently visible and should be checked before using table functionality. Use with using. </returns>
internal TableDisposable(ImU8String id, int columns, ImGuiTableFlags flags, Vector2 outerSize, float innerWidth)
{
this.Success = ImGui.BeginTable(id, columns, flags, outerSize, innerWidth);
this.Alive = true;
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(TableDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(TableDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(TableDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(TableDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(TableDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(TableDisposable i, bool value)
=> i.Success || value;
/// <summary> End the Table on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImGui.EndTable();
this.Alive = false;
}
/// <summary> End a Table without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImGui.EndTable();
}
}

View file

@ -0,0 +1,45 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around pushing text wrap positions. </summary>
public sealed class TextWrapDisposable : IDisposable
{
/// <summary> The number of text wrap positions currently pushed using this disposable. </summary>
public int Count { get; private set; }
/// <summary> Push a text wrap position to the text wrap stack. </summary>
/// <param name="localX"> The window-local X coordinate at which to wrap text. If this is negative, no wrapping, if it is 0, wrap from here to the end of the available content region, and if it is positive, wrap from here. </param>
/// <param name="condition"> If this is false, the position is not pushed. </param>
/// <returns> A disposable object that can be used to push further text wrap positions and pops those positions after leaving scope. Use with using. </returns>
/// <remarks> If you need to keep text wrap positions pushed longer than the current scope, use without using and use <seealso cref="Im.PopTextWrapPositionUnsafe"/>. </remarks>
public TextWrapDisposable Push(float localX, bool condition)
=> condition ? this.Push(localX) : this;
/// <inheritdoc cref="Push(float,bool)"/>
public TextWrapDisposable Push(float localX)
{
ImGui.PushTextWrapPos(localX);
++this.Count;
return this;
}
/// <summary> Pop a number of text wrap positions. </summary>
/// <param name="num"> The number of text wrap positions to pop. This is clamped to the number of positions pushed by this object. </param>
public void Pop(int num = 1)
{
num = Math.Min(num, this.Count);
this.Count -= num;
while (num-- > 0)
ImGui.PopTextWrapPos();
}
/// <summary> Pop all pushed text wrap positions. </summary>
public void Dispose()
=> this.Pop(this.Count);
}
}

View file

@ -0,0 +1,38 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui tooltips. </summary>
public ref struct TooltipDisposable : IDisposable
{
/// <summary> Whether the tooltip is still open. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="TooltipDisposable"/> struct. </summary>
/// <returns> A disposable object. Use with using. </returns>
/// <remarks> Anything drawn while a tooltip is active will be drawn in a little popup window on your cursor. </remarks>
public TooltipDisposable()
{
this.Alive = true;
ImGui.BeginTooltip();
}
/// <summary> End the tooltip on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
ImGui.EndTooltip();
this.Alive = false;
}
/// <summary> End a Tooltip without using an IDisposable. </summary>
public static void EndUnsafe()
=> ImGui.EndTooltip();
}
}

View file

@ -0,0 +1,85 @@
// ReSharper disable once CheckNamespace
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
/// <summary> A wrapper around ImGui tree nodes. </summary>
public ref struct TreeNodeDisposable : IDisposable
{
/// <summary> Whether creating the tree node succeeded and it is open. </summary>
public readonly bool Success;
/// <summary> Whether the tree node is already popped. </summary>
public bool Alive { get; private set; }
/// <summary>Initializes a new instance of the <see cref="TreeNodeDisposable"/> struct. </summary>
/// <param name="label"> The node label as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <param name="flags"> Additional flags to control the tree's behaviour. </param>
/// <returns> A disposable object that evaluates to true if the begun tree node is currently expanded. Use with using. </returns>
internal TreeNodeDisposable(ImU8String label)
{
this.Success = ImGui.TreeNodeEx(label);
this.Alive = true;
}
/// <summary>Initializes a new instance of the <see cref="TreeNodeDisposable"/> struct. </summary>
/// <param name="label"> The node label as text. If this is a UTF8 string, it HAS to be null-terminated. </param>
/// <param name="flags"> Additional flags to control the tree's behaviour. </param>
/// <returns> A disposable object that evaluates to true if the begun tree node is currently expanded. Use with using. </returns>
internal TreeNodeDisposable(ImU8String label, ImGuiTreeNodeFlags flags)
{
this.Success = ImGui.TreeNodeEx(label, flags);
this.Alive = !flags.HasFlag(ImGuiTreeNodeFlags.NoTreePushOnOpen);
}
/// <summary> Conversion to bool. </summary>
public static implicit operator bool(TreeNodeDisposable value)
=> value.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator true(TreeNodeDisposable i)
=> i.Success;
/// <summary> Conversion to bool. </summary>
public static bool operator false(TreeNodeDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on NOT operators. </summary>
public static bool operator !(TreeNodeDisposable i)
=> !i.Success;
/// <summary> Conversion to bool on AND operators. </summary>
public static bool operator &(TreeNodeDisposable i, bool value)
=> i.Success && value;
/// <summary> Conversion to bool on OR operators. </summary>
public static bool operator |(TreeNodeDisposable i, bool value)
=> i.Success || value;
/// <summary> Pop the tree node on leaving scope. </summary>
public void Dispose()
{
if (!this.Alive)
return;
if (this.Success)
ImGui.TreePop();
this.Alive = false;
}
/// <summary> Pop a tree node without using an IDisposable. </summary>
public static void PopUnsafe()
=> ImGui.TreePop();
}
}

View file

@ -8,197 +8,191 @@ namespace Dalamud.Interface.Utility.Raii;
// when created with using variables. // when created with using variables.
public static partial class ImRaii public static partial class ImRaii
{ {
private static int disabledCount = 0; public static ChildDisposable Child(ImU8String strId)
=> new(strId);
public static IEndObject Child(ImU8String strId) public static ChildDisposable Child(ImU8String strId, Vector2 size)
=> new EndUnconditionally(ImGui.EndChild, ImGui.BeginChild(strId)); => new(strId, size);
public static IEndObject Child(ImU8String strId, Vector2 size) public static ChildDisposable Child(ImU8String strId, Vector2 size, bool border)
=> new EndUnconditionally(ImGui.EndChild, ImGui.BeginChild(strId, size)); => new(strId, size, border);
public static IEndObject Child(ImU8String strId, Vector2 size, bool border) public static ChildDisposable Child(ImU8String strId, Vector2 size, bool border, ImGuiWindowFlags flags)
=> new EndUnconditionally(ImGui.EndChild, ImGui.BeginChild(strId, size, border)); => new(strId, size, border, flags);
public static IEndObject Child(ImU8String strId, Vector2 size, bool border, ImGuiWindowFlags flags) public static ColorDisposable PushColor(ImGuiCol idx, uint color, bool condition = true)
=> new EndUnconditionally(ImGui.EndChild, ImGui.BeginChild(strId, size, border, flags)); => new ColorDisposable().Push(idx, color, condition);
public static IEndObject DragDropTarget() public static ColorDisposable PushColor(ImGuiCol idx, Vector4 color, bool condition = true)
=> new EndConditionally(ImGui.EndDragDropTarget, ImGui.BeginDragDropTarget()); => new ColorDisposable().Push(idx, color, condition);
public static IEndObject DragDropSource() public static ColorDisposable PushColor(ImGuiCol idx, Vector4? color, bool condition = true)
=> new EndConditionally(ImGui.EndDragDropSource, ImGui.BeginDragDropSource()); => new ColorDisposable().Push(idx, color, condition);
public static IEndObject DragDropSource(ImGuiDragDropFlags flags) public static ColorDisposable DefaultColors()
=> new EndConditionally(ImGui.EndDragDropSource, ImGui.BeginDragDropSource(flags)); => ColorDisposable.DefaultColors();
public static IEndObject Popup(ImU8String id) public static StyleDisposable PushStyle(ImGuiStyleVar idx, float value, bool condition = true)
=> new EndConditionally(ImGui.EndPopup, ImGui.BeginPopup(id)); => new StyleDisposable().Push(idx, value, condition);
public static IEndObject Popup(ImU8String id, ImGuiWindowFlags flags) public static StyleDisposable PushStyle(ImGuiStyleVar idx, Vector2 value, bool condition = true)
=> new EndConditionally(ImGui.EndPopup, ImGui.BeginPopup(id, flags)); => new StyleDisposable().Push(idx, value, condition);
public static IEndObject PopupModal(ImU8String id) public static StyleDisposable DefaultStyle()
=> new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupModal(id)); => StyleDisposable.DefaultStyle();
public static IEndObject PopupModal(ImU8String id, ImGuiWindowFlags flags) public static FontDisposable PushFont(ImFontPtr font, bool condition = true)
=> new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupModal(id, flags)); => new FontDisposable().Push(font, condition);
public static IEndObject PopupModal(ImU8String id, ref bool open) public static IdDisposable PushId(ImU8String id, bool enabled = true)
=> new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupModal(id, ref open)); => new IdDisposable().Push(id, enabled);
public static IEndObject PopupModal(ImU8String id, ref bool open, ImGuiWindowFlags flags) public static IdDisposable PushId(int id, bool enabled = true)
=> new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupModal(id, ref open, flags)); => new IdDisposable().Push(id, enabled);
public static IEndObject ContextPopup(ImU8String id) public static IdDisposable PushId(nint id, bool enabled = true)
=> new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupContextWindow(id)); => new IdDisposable().Push(id, enabled);
public static IEndObject ContextPopup(ImU8String id, ImGuiPopupFlags flags) public static IndentDisposable PushIndent(float f, bool scaled = true, bool condition = true)
=> new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupContextWindow(id, flags)); => new IndentDisposable().Indent(f, scaled, condition);
public static IEndObject ContextPopupItem(ImU8String id) public static IndentDisposable PushIndent(int i = 1, bool condition = true)
=> new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupContextItem(id)); => new IndentDisposable().Indent(i, condition);
public static IEndObject ContextPopupItem(ImU8String id, ImGuiPopupFlags flags) public static DragDropTargetDisposable DragDropTarget()
=> new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupContextItem(id, flags)); => new();
public static IEndObject Combo(ImU8String label, ImU8String previewValue) public static DragDropSourceDisposable DragDropSource()
=> new EndConditionally(ImGui.EndCombo, ImGui.BeginCombo(label, previewValue)); => new();
public static IEndObject Combo(ImU8String label, ImU8String previewValue, ImGuiComboFlags flags) public static DragDropSourceDisposable DragDropSource(ImGuiDragDropFlags flags)
=> new EndConditionally(ImGui.EndCombo, ImGui.BeginCombo(label, previewValue, flags)); => new(flags);
public static IEndObject Menu(ImU8String label) public static HeaderDisposable Header(ImU8String label, ImGuiTreeNodeFlags flags)
=> new EndConditionally(ImGui.EndMenu, ImGui.BeginMenu(label)); => new(label, flags);
public static IEndObject MenuBar() public static HeaderDisposable Header(ImU8String label, ref bool visible, ImGuiTreeNodeFlags flags)
=> new EndConditionally(ImGui.EndMenuBar, ImGui.BeginMenuBar()); => new(label, ref visible, flags);
public static IEndObject MainMenuBar() public static PopupDisposable Popup(ImU8String id)
=> new EndConditionally(ImGui.EndMainMenuBar, ImGui.BeginMainMenuBar()); => new(id);
public static IEndObject Group() public static PopupDisposable Popup(ImU8String id, ImGuiWindowFlags flags)
{ => new(id, flags);
ImGui.BeginGroup();
return new EndUnconditionally(ImGui.EndGroup, true);
}
public static IEndObject Tooltip() public static PopupDisposable PopupModal(ImU8String id)
{ => PopupDisposable.Modal(id);
ImGui.BeginTooltip();
return new EndUnconditionally(ImGui.EndTooltip, true);
}
/// <summary> public static PopupDisposable PopupModal(ImU8String id, ImGuiWindowFlags flags)
/// Pushes the item width for the next widget and returns an <c>IDisposable</c> that pops => PopupDisposable.Modal(id, flags);
/// the width when done.
/// </summary>
/// <param name="width">The width to set the next widget to.</param>
/// <returns>An <see cref="IDisposable"/> for use in a <c>using</c> statement.</returns>
public static IEndObject ItemWidth(float width)
{
ImGui.PushItemWidth(width);
return new EndUnconditionally(ImGui.PopItemWidth, true);
}
/// <summary> public static PopupDisposable PopupModal(ImU8String id, ref bool open)
/// Pushes the item wrapping width for the next string written and returns an <c>IDisposable</c> => PopupDisposable.Modal(id, ref open);
/// that pops the wrap width when done.
/// </summary>
/// <param name="pos">The wrap width to set the next text written to.</param>
/// <returns>An <see cref="IDisposable"/> for use in a <c>using</c> statement.</returns>
public static IEndObject TextWrapPos(float pos)
{
ImGui.PushTextWrapPos(pos);
return new EndUnconditionally(ImGui.PopTextWrapPos, true);
}
public static IEndObject ListBox(ImU8String label) public static PopupDisposable PopupModal(ImU8String id, ref bool open, ImGuiWindowFlags flags)
=> new EndConditionally(ImGui.EndListBox, ImGui.BeginListBox(label)); => PopupDisposable.Modal(id, ref open, flags);
public static IEndObject ListBox(ImU8String label, Vector2 size) public static PopupDisposable ContextPopup(ImU8String id)
=> new EndConditionally(ImGui.EndListBox, ImGui.BeginListBox(label, size)); => PopupDisposable.ContextWindow(id);
public static IEndObject Table(ImU8String table, int numColumns) public static PopupDisposable ContextPopup(ImU8String id, ImGuiPopupFlags flags)
=> new EndConditionally(ImGui.EndTable, ImGui.BeginTable(table, numColumns)); => PopupDisposable.ContextWindow(id, flags);
public static IEndObject Table(ImU8String table, int numColumns, ImGuiTableFlags flags) public static PopupDisposable ContextPopupItem(ImU8String id)
=> new EndConditionally(ImGui.EndTable, ImGui.BeginTable(table, numColumns, flags)); => PopupDisposable.ContextItem(id);
public static IEndObject Table(ImU8String table, int numColumns, ImGuiTableFlags flags, Vector2 outerSize) public static PopupDisposable ContextPopupItem(ImU8String id, ImGuiPopupFlags flags)
=> new EndConditionally(ImGui.EndTable, ImGui.BeginTable(table, numColumns, flags, outerSize)); => PopupDisposable.ContextItem(id, flags);
public static IEndObject Table(ImU8String table, int numColumns, ImGuiTableFlags flags, Vector2 outerSize, float innerWidth) public static ComboDisposable Combo(ImU8String label, ImU8String previewValue)
=> new EndConditionally(ImGui.EndTable, ImGui.BeginTable(table, numColumns, flags, outerSize, innerWidth)); => new(label, previewValue);
public static IEndObject TabBar(ImU8String label) public static ComboDisposable Combo(ImU8String label, ImU8String previewValue, ImGuiComboFlags flags)
=> new EndConditionally(ImGui.EndTabBar, ImGui.BeginTabBar(label)); => new(label, previewValue, flags);
public static IEndObject TabBar(ImU8String label, ImGuiTabBarFlags flags) public static MenuDisposable Menu(ImU8String label)
=> new EndConditionally(ImGui.EndTabBar, ImGui.BeginTabBar(label, flags)); => new(label);
public static IEndObject TabItem(ImU8String label) public static MenuDisposable Menu(ImU8String label, bool enabled)
=> new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label)); => new(label, enabled);
public static unsafe IEndObject TabItem(byte* label, ImGuiTabItemFlags flags) public static MenuBarDisposable MenuBar()
=> new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label, flags)); => new();
public static unsafe IEndObject TabItem(ImU8String label, ImGuiTabItemFlags flags) public static MainMenuBarDisposable MainMenuBar()
=> new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label, flags)); => new();
public static IEndObject TabItem(ImU8String label, ref bool open) public static GroupDisposable Group()
=> new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label, ref open)); => new();
public static IEndObject TabItem(ImU8String label, ref bool open, ImGuiTabItemFlags flags) public static TooltipDisposable Tooltip()
=> new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label, ref open, flags)); => new();
public static IEndObject TreeNode(ImU8String label) public static ItemWidthDisposable ItemWidth(float width)
=> new EndConditionally(ImGui.TreePop, ImGui.TreeNodeEx(label)); => new ItemWidthDisposable().Push(width);
public static IEndObject TreeNode(ImU8String label, ImGuiTreeNodeFlags flags) public static ItemWidthDisposable ItemWidth(float width, bool condition)
=> new EndConditionally(flags.HasFlag(ImGuiTreeNodeFlags.NoTreePushOnOpen) ? Nop : ImGui.TreePop, ImGui.TreeNodeEx(label, flags)); => new ItemWidthDisposable().Push(width, condition);
public static IEndObject Disabled() public static TextWrapDisposable TextWrapPos(float pos)
{ => new TextWrapDisposable().Push(pos, true);
ImGui.BeginDisabled();
++disabledCount;
return DisabledEnd();
}
public static IEndObject Disabled(bool disabled) public static TextWrapDisposable TextWrapPos(float pos, bool condition)
{ => new TextWrapDisposable().Push(pos, condition);
if (!disabled)
return new EndConditionally(Nop, false);
ImGui.BeginDisabled(); public static ListBoxDisposable ListBox(ImU8String label)
++disabledCount; => new(label);
return DisabledEnd();
}
public static IEndObject Enabled() public static ListBoxDisposable ListBox(ImU8String label, Vector2 size)
{ => new(label, size);
var oldCount = disabledCount;
if (oldCount == 0)
return new EndConditionally(Nop, false);
void Restore() public static TableDisposable Table(ImU8String table, int numColumns)
{ => new(table, numColumns);
disabledCount += oldCount;
while (--oldCount >= 0)
ImGui.BeginDisabled();
}
for (; disabledCount > 0; --disabledCount) public static TableDisposable Table(ImU8String table, int numColumns, ImGuiTableFlags flags)
ImGui.EndDisabled(); => new(table, numColumns, flags);
return new EndUnconditionally(Restore, true); public static TableDisposable Table(ImU8String table, int numColumns, ImGuiTableFlags flags, Vector2 outerSize)
} => new(table, numColumns, flags, outerSize);
private static EndUnconditionally DisabledEnd() public static TableDisposable Table(ImU8String table, int numColumns, ImGuiTableFlags flags, Vector2 outerSize, float innerWidth)
=> new(() => => new(table, numColumns, flags, outerSize, innerWidth);
{
--disabledCount; public static TabBarDisposable TabBar(ImU8String label)
ImGui.EndDisabled(); => new(label);
}, true);
public static TabBarDisposable TabBar(ImU8String label, ImGuiTabBarFlags flags)
=> new(label, flags);
public static TabItemDisposable TabItem(ImU8String label)
=> new(label);
public static unsafe TabItemDisposable TabItem(byte* label, ImGuiTabItemFlags flags)
=> new(label, flags);
public static TabItemDisposable TabItem(ImU8String label, ImGuiTabItemFlags flags)
=> new(label, flags);
public static TabItemDisposable TabItem(ImU8String label, ref bool open)
=> new(label, ref open);
public static TabItemDisposable TabItem(ImU8String label, ref bool open, ImGuiTabItemFlags flags)
=> new(label, ref open, flags);
public static TreeNodeDisposable TreeNode(ImU8String label)
=> new(label);
public static TreeNodeDisposable TreeNode(ImU8String label, ImGuiTreeNodeFlags flags)
=> new(label, flags);
public static DisabledDisposable Disabled()
=> new DisabledDisposable().Push();
public static DisabledDisposable Disabled(bool disabled)
=> new DisabledDisposable().Push(disabled);
public static EnabledDisposable Enabled()
=> new(DisabledDisposable.GlobalCount != 0);
/* Only in OtterGui for now /* Only in OtterGui for now
public static IEndObject FramedGroup(string label) public static IEndObject FramedGroup(string label)
@ -213,87 +207,4 @@ public static partial class ImRaii
return new EndUnconditionally(Widget.EndFramedGroup, true); return new EndUnconditionally(Widget.EndFramedGroup, true);
} }
*/ */
// Used to avoid tree pops when flag for no push is set.
private static void Nop()
{
}
// Exported interface for RAII.
public interface IEndObject : IDisposable
{
public bool Success { get; }
public static bool operator true(IEndObject i)
=> i.Success;
public static bool operator false(IEndObject i)
=> !i.Success;
public static bool operator !(IEndObject i)
=> !i.Success;
public static bool operator &(IEndObject i, bool value)
=> i.Success && value;
public static bool operator |(IEndObject i, bool value)
=> i.Success || value;
// Empty end object.
public static readonly IEndObject Empty = new EndConditionally(Nop, false);
}
// Use end-function regardless of success.
// Used by Child, Group and Tooltip.
public struct EndUnconditionally : IEndObject
{
private Action EndAction { get; }
public bool Success { get; }
public bool Disposed { get; private set; }
public EndUnconditionally(Action endAction, bool success)
{
this.EndAction = endAction;
this.Success = success;
this.Disposed = false;
}
public void Dispose()
{
if (this.Disposed)
return;
this.EndAction();
this.Disposed = true;
}
}
// Use end-function only on success.
public struct EndConditionally : IEndObject
{
public EndConditionally(Action endAction, bool success)
{
this.EndAction = endAction;
this.Success = success;
this.Disposed = false;
}
public bool Success { get; }
public bool Disposed { get; private set; }
private Action EndAction { get; }
public void Dispose()
{
if (this.Disposed)
return;
if (this.Success)
this.EndAction();
this.Disposed = true;
}
}
} }

View file

@ -1,51 +0,0 @@
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
// Push an arbitrary amount of fonts into an object that are all popped when it is disposed.
// If condition is false, no font is pushed.
public static partial class ImRaii
{
public static Font PushFont(ImFontPtr font, bool condition = true)
=> condition ? new Font().Push(font) : new Font();
// Push the default font if any other font is currently pushed.
public static Font DefaultFont()
=> new Font().Push(Font.DefaultPushed, Font.FontPushCounter > 0);
public sealed class Font : IDisposable
{
internal static int FontPushCounter = 0;
internal static ImFontPtr DefaultPushed;
private int count;
public Font()
=> this.count = 0;
public Font Push(ImFontPtr font, bool condition = true)
{
if (condition)
{
if (FontPushCounter++ == 0)
DefaultPushed = ImGui.GetFont();
ImGui.PushFont(font);
++this.count;
}
return this;
}
public void Pop(int num = 1)
{
num = Math.Min(num, this.count);
this.count -= num;
FontPushCounter -= num;
while (num-- > 0)
ImGui.PopFont();
}
public void Dispose()
=> this.Pop(this.count);
}
}

View file

@ -1,66 +0,0 @@
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
// Push an arbitrary amount of ids into an object that are all popped when it is disposed.
// If condition is false, no id is pushed.
public static partial class ImRaii
{
public static Id PushId(ImU8String 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();
public static Id PushId(IntPtr id, bool enabled = true)
=> enabled ? new Id().Push(id) : new Id();
public sealed class Id : IDisposable
{
private int count;
public Id Push(ImU8String id, bool condition = true)
{
if (condition)
{
ImGui.PushID(id);
++this.count;
}
return this;
}
public Id Push(int id, bool condition = true)
{
if (condition)
{
ImGui.PushID(id);
++this.count;
}
return this;
}
public unsafe Id Push(IntPtr id, bool condition = true)
{
if (condition)
{
ImGui.PushID(id.ToPointer());
++this.count;
}
return this;
}
public void Pop(int num = 1)
{
num = Math.Min(num, this.count);
this.count -= num;
while (num-- > 0)
ImGui.PopID();
}
public void Dispose()
=> this.Pop(this.count);
}
}

View file

@ -1,70 +0,0 @@
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Utility.Raii;
public static partial class ImRaii
{
public static Indent PushIndent(float f, bool scaled = true, bool condition = true)
=> new Indent().Push(f, scaled, condition);
public static Indent PushIndent(int i = 1, bool condition = true)
=> new Indent().Push(i, condition);
public sealed class Indent : IDisposable
{
public float Indentation { get; private set; }
public Indent Push(float indent, bool scaled = true, bool condition = true)
{
if (condition)
{
if (scaled)
indent *= ImGuiHelpers.GlobalScale;
IndentInternal(indent);
this.Indentation += indent;
}
return this;
}
public Indent Push(int i = 1, bool condition = true)
{
if (condition)
{
var spacing = i * ImGui.GetStyle().IndentSpacing;
IndentInternal(spacing);
this.Indentation += spacing;
}
return this;
}
public void Pop(float indent, bool scaled = true)
{
if (scaled)
indent *= ImGuiHelpers.GlobalScale;
IndentInternal(-indent);
this.Indentation -= indent;
}
public void Pop(int i)
{
var spacing = i * ImGui.GetStyle().IndentSpacing;
IndentInternal(-spacing);
this.Indentation -= spacing;
}
private static void IndentInternal(float indent)
{
if (indent < 0)
ImGui.Unindent(-indent);
else if (indent > 0)
ImGui.Indent(indent);
}
public void Dispose()
=> this.Pop(this.Indentation, false);
}
}

View file

@ -1,5 +1,3 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics; using System.Numerics;
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
@ -12,286 +10,87 @@ public static partial class ImRaii
{ {
#region EndObjects #region EndObjects
public static IEndObject Plot(string titleId, Vector2 size, ImPlotFlags flags) public static PlotDisposable Plot(string titleId, Vector2 size, ImPlotFlags flags)
=> new EndConditionally(ImPlot.EndPlot, ImPlot.BeginPlot(titleId, size, flags)); => new(titleId, size, flags);
public static IEndObject Plot(ReadOnlySpan<byte> titleId, Vector2 size, ImPlotFlags flags) public static PlotDisposable Plot(ReadOnlySpan<byte> titleId, Vector2 size, ImPlotFlags flags)
=> new EndConditionally(ImPlot.EndPlot, ImPlot.BeginPlot(titleId, size, flags)); => new(titleId, size, flags);
public static IEndObject AlignedPlots(string groupId, bool vertical = true) public static PlotAlignedDisposable AlignedPlots(string groupId, bool vertical = true)
=> new EndConditionally(ImPlot.EndAlignedPlots, ImPlot.BeginAlignedPlots(groupId, vertical)); => new(groupId, vertical);
public static IEndObject AlignedPlots(ReadOnlySpan<byte> groupId, bool vertical = true) public static PlotAlignedDisposable AlignedPlots(ReadOnlySpan<byte> groupId, bool vertical = true)
=> new EndConditionally(ImPlot.EndAlignedPlots, ImPlot.BeginAlignedPlots(groupId, vertical)); => new(groupId, vertical);
public static IEndObject LegendPopup(string labelId, ImGuiMouseButton mouseButton = ImGuiMouseButton.Right) public static PlotLegendDisposable LegendPopup(string labelId, ImGuiMouseButton mouseButton = ImGuiMouseButton.Right)
=> new EndConditionally(ImPlot.EndLegendPopup, ImPlot.BeginLegendPopup(labelId, mouseButton)); => new(labelId, mouseButton);
public static IEndObject LegendPopup(ReadOnlySpan<byte> labelId, ImGuiMouseButton mouseButton = ImGuiMouseButton.Right) public static PlotLegendDisposable LegendPopup(ReadOnlySpan<byte> labelId, ImGuiMouseButton mouseButton = ImGuiMouseButton.Right)
=> new EndConditionally(ImPlot.EndLegendPopup, ImPlot.BeginLegendPopup(labelId, mouseButton)); => new(labelId, mouseButton);
public static IEndObject Subplots(string titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags = ImPlotSubplotFlags.None) public static PlotSubDisposable Subplots(string titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags = ImPlotSubplotFlags.None)
=> new EndConditionally(ImPlot.EndSubplots, ImPlot.BeginSubplots(titleId, rows, cols, size, flags)); => new(titleId, rows, cols, size, flags);
public static IEndObject Subplots(ReadOnlySpan<byte> titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags = ImPlotSubplotFlags.None) public static PlotSubDisposable Subplots(ReadOnlySpan<byte> titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags = ImPlotSubplotFlags.None)
=> new EndConditionally(ImPlot.EndSubplots, ImPlot.BeginSubplots(titleId, rows, cols, size, flags)); => new(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) public static PlotSubDisposable 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)); => new(titleId, rows, cols, size, flags, ref rowRatios, ref colRatios);
public static IEndObject Subplots(ReadOnlySpan<byte> titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags, ref float rowRatios, ref float colRatios) public static PlotSubDisposable Subplots(ReadOnlySpan<byte> 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)); => new(titleId, rows, cols, size, flags, ref rowRatios, ref colRatios);
public static IEndObject DragDropSourceAxis(ImAxis axis, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None) public static PlotDragDropSourceDisposable DragDropSourceItem(string labelId, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None)
=> new EndConditionally(ImPlot.EndDragDropSource, ImPlot.BeginDragDropSourceAxis(axis, flags)); => new(labelId, flags);
public static IEndObject DragDropSourceItem(string labelId, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None) public static PlotDragDropSourceDisposable DragDropSourceItem(ReadOnlySpan<byte> labelId, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None)
=> new EndConditionally(ImPlot.EndDragDropSource, ImPlot.BeginDragDropSourceItem(labelId, flags)); => new(labelId, flags);
public static IEndObject DragDropSourceItem(ReadOnlySpan<byte> labelId, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None) public static PlotDragDropSourceDisposable DragDropSourceAxis(ImAxis axis, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None)
=> new EndConditionally(ImPlot.EndDragDropSource, ImPlot.BeginDragDropSourceItem(labelId, flags)); => PlotDragDropSourceDisposable.AxisPlot(axis, flags);
public static IEndObject DragDropSourcePlot(ImGuiDragDropFlags flags = ImGuiDragDropFlags.None) public static PlotDragDropSourceDisposable DragDropSourcePlot(ImGuiDragDropFlags flags = ImGuiDragDropFlags.None)
=> new EndConditionally(ImPlot.EndDragDropSource, ImPlot.BeginDragDropSourcePlot(flags)); => PlotDragDropSourceDisposable.SourcePlot(flags);
public static IEndObject DragDropTargetAxis(ImAxis axis) public static PlotDragDropTargetDisposable DragDropTargetAxis(ImAxis axis)
=> new EndConditionally(ImPlot.EndDragDropTarget, ImPlot.BeginDragDropTargetAxis(axis)); => PlotDragDropTargetDisposable.AxisPlot(axis);
public static IEndObject DragDropTargetLegend() public static PlotDragDropTargetDisposable DragDropTargetLegend()
=> new EndConditionally(ImPlot.EndDragDropTarget, ImPlot.BeginDragDropTargetLegend()); => PlotDragDropTargetDisposable.LegendPlot();
public static IEndObject DragDropTargetPlot() public static PlotDragDropTargetDisposable DragDropTargetPlot()
=> new EndConditionally(ImPlot.EndDragDropTarget, ImPlot.BeginDragDropTargetPlot()); => PlotDragDropTargetDisposable.SourcePlot();
#endregion EndObjects #endregion EndObjects
#region Style #region Style
public static PlotStyle PushStyle(ImPlotStyleVar idx, int value, bool condition = true) public static PlotStyleDisposable PushStyle(ImPlotStyleVar idx, int value, bool condition = true)
=> new PlotStyle().Push(idx, value, condition); => new PlotStyleDisposable().Push(idx, value, condition);
public static PlotStyle PushStyle(ImPlotStyleVar idx, float value, bool condition = true) public static PlotStyleDisposable PushStyle(ImPlotStyleVar idx, float value, bool condition = true)
=> new PlotStyle().Push(idx, value, condition); => new PlotStyleDisposable().Push(idx, value, condition);
public static PlotStyle PushStyle(ImPlotStyleVar idx, Vector2 value, bool condition = true) public static PlotStyleDisposable PushStyle(ImPlotStyleVar idx, Vector2 value, bool condition = true)
=> new PlotStyle().Push(idx, value, condition); => new PlotStyleDisposable().Push(idx, value, condition);
// Push styles that revert all current plot style changes made temporarily. // Push styles that revert all current plot style changes made temporarily.
public static PlotStyle DefaultPlotStyle() public static PlotStyleDisposable DefaultPlotStyle()
{ => PlotStyleDisposable.PlotDefaultStyle();
var ret = new PlotStyle();
var reverseStack = PlotStyle.Stack.GroupBy(p => p.Item1).Select(p => (p.Key, p.First().Item2)).ToArray();
foreach (var (idx, val) in reverseStack)
{
if (idx == ImPlotStyleVar.Marker)
ret.Push(idx, (int)val.X);
else if (float.IsNaN(val.Y))
ret.Push(idx, val.X);
else
ret.Push(idx, val);
}
return ret;
}
public sealed class PlotStyle : IDisposable
{
internal static readonly List<(ImPlotStyleVar, Vector2)> Stack = [];
private int count;
private static void CheckStyleIdx(ImPlotStyleVar idx, Type type)
{
var shouldThrow = idx switch
{
ImPlotStyleVar.LineWeight => type != typeof(float),
ImPlotStyleVar.Marker => type != typeof(int),
ImPlotStyleVar.MarkerSize => type != typeof(float),
ImPlotStyleVar.MarkerWeight => type != typeof(float),
ImPlotStyleVar.FillAlpha => type != typeof(float),
ImPlotStyleVar.ErrorBarSize => type != typeof(float),
ImPlotStyleVar.ErrorBarWeight => type != typeof(float),
// ImPlotStyleVar.DigitalBitHeight => type != typeof(float),
// ImPlotStyleVar.DigitalBitGap => type != typeof(float),
// ImPlotStyleVar.PlotBorderSize => type != typeof(float),
ImPlotStyleVar.MinorAlpha => type != typeof(float),
ImPlotStyleVar.MajorTickLen => type != typeof(Vector2),
ImPlotStyleVar.MinorTickLen => type != typeof(Vector2),
ImPlotStyleVar.MajorTickSize => type != typeof(Vector2),
ImPlotStyleVar.MinorTickSize => type != typeof(Vector2),
ImPlotStyleVar.MajorGridSize => type != typeof(Vector2),
ImPlotStyleVar.MinorGridSize => type != typeof(Vector2),
// ImPlotStyleVar.PlotPadding => type != typeof(Vector2),
ImPlotStyleVar.LabelPadding => type != typeof(Vector2),
ImPlotStyleVar.LegendPadding => type != typeof(Vector2),
ImPlotStyleVar.LegendInnerPadding => type != typeof(Vector2),
ImPlotStyleVar.LegendSpacing => type != typeof(Vector2),
ImPlotStyleVar.MousePosPadding => type != typeof(Vector2),
ImPlotStyleVar.AnnotationPadding => type != typeof(Vector2),
ImPlotStyleVar.FitPadding => type != typeof(Vector2),
// ImPlotStyleVar.PlotDefaultSize => type != typeof(Vector2),
// ImPlotStyleVar.PlotMinSize => type != typeof(Vector2),
_ => throw new ArgumentOutOfRangeException(nameof(idx), idx, null),
};
if (shouldThrow)
throw new ArgumentException($"Unable to push {type} to {idx}.");
}
public static Vector2 GetStyle(ImPlotStyleVar idx)
{
var style = ImPlot.GetStyle();
return idx switch
{
ImPlotStyleVar.LineWeight => new Vector2(style.LineWeight, float.NaN),
ImPlotStyleVar.Marker => new Vector2(style.Marker, float.NaN),
ImPlotStyleVar.MarkerSize => new Vector2(style.MarkerSize, float.NaN),
ImPlotStyleVar.MarkerWeight => new Vector2(style.MarkerWeight, float.NaN),
ImPlotStyleVar.FillAlpha => new Vector2(style.FillAlpha, float.NaN),
ImPlotStyleVar.ErrorBarSize => new Vector2(style.ErrorBarSize, float.NaN),
ImPlotStyleVar.ErrorBarWeight => new Vector2(style.ErrorBarWeight, float.NaN),
// ImPlotStyleVar.DigitalBitHeight => new Vector2(style.DigitalBitHeight, float.NaN),
// ImPlotStyleVar.DigitalBitGap => new Vector2(style.DigitalBitGap, float.NaN),
// ImPlotStyleVar.PlotBorderSize => new Vector2(style.PlotBorderSize, float.NaN),
ImPlotStyleVar.MinorAlpha => new Vector2(style.MinorAlpha, float.NaN),
ImPlotStyleVar.MajorTickLen => style.MajorTickLen,
ImPlotStyleVar.MinorTickLen => style.MinorTickLen,
ImPlotStyleVar.MajorTickSize => style.MajorTickSize,
ImPlotStyleVar.MinorTickSize => style.MinorTickSize,
ImPlotStyleVar.MajorGridSize => style.MajorGridSize,
ImPlotStyleVar.MinorGridSize => style.MinorGridSize,
// ImPlotStyleVar.PlotPadding => style.PlotPadding,
ImPlotStyleVar.LabelPadding => style.LabelPadding,
ImPlotStyleVar.LegendPadding => style.LegendPadding,
ImPlotStyleVar.LegendInnerPadding => style.LegendInnerPadding,
ImPlotStyleVar.LegendSpacing => style.LegendSpacing,
ImPlotStyleVar.MousePosPadding => style.MousePosPadding,
ImPlotStyleVar.AnnotationPadding => style.AnnotationPadding,
ImPlotStyleVar.FitPadding => style.FitPadding,
// ImPlotStyleVar.PlotDefaultSize => style.PlotDefaultSize,
// ImPlotStyleVar.PlotMinSize => style.PlotMinSize,
_ => throw new ArgumentOutOfRangeException(nameof(idx), idx, null),
};
}
public PlotStyle Push(ImPlotStyleVar idx, int value, bool condition = true)
{
if (!condition)
return this;
// Should be accurate for +/- 2^24 markers, which is fine, because the only valid range
// for markers is [-1, 9].
CheckStyleIdx(idx, typeof(int));
Stack.Add((idx, GetStyle(idx)));
ImPlot.PushStyleVar(idx, value);
++this.count;
return this;
}
public PlotStyle Push(ImPlotStyleVar idx, float value, bool condition = true)
{
if (!condition)
return this;
CheckStyleIdx(idx, typeof(float));
Stack.Add((idx, GetStyle(idx)));
ImPlot.PushStyleVar(idx, value);
++this.count;
return this;
}
public PlotStyle Push(ImPlotStyleVar idx, Vector2 value, bool condition = true)
{
if (!condition)
return this;
CheckStyleIdx(idx, typeof(Vector2));
Stack.Add((idx, GetStyle(idx)));
ImPlot.PushStyleVar(idx, value);
++this.count;
return this;
}
public void Pop(int num = 1)
{
num = Math.Min(num, this.count);
this.count -= num;
ImPlot.PopStyleVar(num);
Stack.RemoveRange(Stack.Count - num, num);
}
public void Dispose()
=> this.Pop(this.count);
}
#endregion Style #endregion Style
#region Color #region Color
public static PlotColor PushColor(ImPlotCol idx, uint color, bool condition = true) public static PlotColorDisposable PushColor(ImPlotCol idx, uint color, bool condition = true)
=> new PlotColor().Push(idx, color, condition); => new PlotColorDisposable().Push(idx, color, condition);
public static PlotColor PushColor(ImPlotCol idx, Vector4 color, bool condition = true) public static PlotColorDisposable PushColor(ImPlotCol idx, Vector4 color, bool condition = true)
=> new PlotColor().Push(idx, color, condition); => new PlotColorDisposable().Push(idx, color, condition);
// Push colors that revert all current color changes made temporarily. // Push colors that revert all current color changes made temporarily.
public static PlotColor DefaultPlotColors() public static PlotColorDisposable DefaultPlotColors()
{ => PlotColorDisposable.PlotDefaultColors();
var ret = new PlotColor();
var reverseStack = PlotColor.Stack.GroupBy(p => p.Item1).Select(p => (p.Key, p.First().Item2)).ToArray();
foreach (var (idx, val) in reverseStack)
ret.Push(idx, val);
return ret;
}
public sealed class PlotColor : IDisposable
{
internal static readonly List<(ImPlotCol, uint)> Stack = [];
private int count;
// Reimplementation of https://github.com/ocornut/imgui/blob/868facff9ded2d61425c67deeba354eb24275bd1/imgui.cpp#L3035
// for ImPlot
private static uint GetColorU32(ImPlotCol idx)
=> ImGui.GetColorU32(ImPlot.GetStyle().Colors[(int)idx]);
public PlotColor Push(ImPlotCol idx, uint color, bool condition = true)
{
if (condition)
{
Stack.Add((idx, GetColorU32(idx)));
ImPlot.PushStyleColor(idx, color);
++this.count;
}
return this;
}
public PlotColor Push(ImPlotCol idx, Vector4 color, bool condition = true)
{
if (condition)
{
Stack.Add((idx, GetColorU32(idx)));
ImPlot.PushStyleColor(idx, color);
++this.count;
}
return this;
}
public void Pop(int num = 1)
{
num = Math.Min(num, this.count);
this.count -= num;
ImPlot.PopStyleColor(num);
Stack.RemoveRange(Stack.Count - num, num);
}
public void Dispose()
=> this.Pop(this.count);
}
#endregion Color #endregion Color
} }