- Convert ImPlot to the new ref style

This commit is contained in:
Infi 2026-02-14 20:58:47 +01:00
parent 8d55dccd75
commit a6d22e34f3
10 changed files with 857 additions and 356 deletions

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

@ -207,109 +207,4 @@ public static partial class ImRaii
return new EndUnconditionally(Widget.EndFramedGroup, true);
}
*/
// 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;
}
// Use end-function regardless of success.
// Used by Child, Group and Tooltip.
public ref 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;
}
public static bool operator true(EndUnconditionally i)
=> i.Success;
public static bool operator false(EndUnconditionally i)
=> !i.Success;
public static bool operator !(EndUnconditionally i)
=> !i.Success;
public static bool operator &(EndUnconditionally i, bool value)
=> i.Success && value;
public static bool operator |(EndUnconditionally i, bool value)
=> i.Success || value;
}
// Use end-function only on success.
public ref 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;
}
public static bool operator true(EndConditionally i)
=> i.Success;
public static bool operator false(EndConditionally i)
=> !i.Success;
public static bool operator !(EndConditionally i)
=> !i.Success;
public static bool operator &(EndConditionally i, bool value)
=> i.Success && value;
public static bool operator |(EndConditionally i, bool value)
=> i.Success || value;
}
}

View file

@ -1,5 +1,3 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui;
@ -7,293 +5,92 @@ using Dalamud.Bindings.ImPlot;
namespace Dalamud.Interface.Utility.Raii;
// TODO Convert to new syntax
// All previous files, but only for ImPlot specific functions.
public static partial class ImRaii
{
#region EndObjects
public static EndConditionally Plot(string titleId, Vector2 size, ImPlotFlags flags)
=> new EndConditionally(ImPlot.EndPlot, ImPlot.BeginPlot(titleId, size, flags));
public static PlotDisposable Plot(string titleId, Vector2 size, ImPlotFlags flags)
=> new(titleId, size, flags);
public static EndConditionally Plot(ReadOnlySpan<byte> titleId, Vector2 size, ImPlotFlags flags)
=> new EndConditionally(ImPlot.EndPlot, ImPlot.BeginPlot(titleId, size, flags));
public static PlotDisposable Plot(ReadOnlySpan<byte> titleId, Vector2 size, ImPlotFlags flags)
=> new(titleId, size, flags);
public static EndConditionally AlignedPlots(string groupId, bool vertical = true)
=> new EndConditionally(ImPlot.EndAlignedPlots, ImPlot.BeginAlignedPlots(groupId, vertical));
public static PlotAlignedDisposable AlignedPlots(string groupId, bool vertical = true)
=> new(groupId, vertical);
public static EndConditionally AlignedPlots(ReadOnlySpan<byte> groupId, bool vertical = true)
=> new EndConditionally(ImPlot.EndAlignedPlots, ImPlot.BeginAlignedPlots(groupId, vertical));
public static PlotAlignedDisposable AlignedPlots(ReadOnlySpan<byte> groupId, bool vertical = true)
=> new(groupId, vertical);
public static EndConditionally LegendPopup(string labelId, ImGuiMouseButton mouseButton = ImGuiMouseButton.Right)
=> new EndConditionally(ImPlot.EndLegendPopup, ImPlot.BeginLegendPopup(labelId, mouseButton));
public static PlotLegendDisposable LegendPopup(string labelId, ImGuiMouseButton mouseButton = ImGuiMouseButton.Right)
=> new(labelId, mouseButton);
public static EndConditionally LegendPopup(ReadOnlySpan<byte> labelId, ImGuiMouseButton mouseButton = ImGuiMouseButton.Right)
=> new EndConditionally(ImPlot.EndLegendPopup, ImPlot.BeginLegendPopup(labelId, mouseButton));
public static PlotLegendDisposable LegendPopup(ReadOnlySpan<byte> labelId, ImGuiMouseButton mouseButton = ImGuiMouseButton.Right)
=> new(labelId, mouseButton);
public static EndConditionally Subplots(string titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags = ImPlotSubplotFlags.None)
=> new EndConditionally(ImPlot.EndSubplots, ImPlot.BeginSubplots(titleId, rows, cols, size, flags));
public static PlotSubDisposable Subplots(string titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags = ImPlotSubplotFlags.None)
=> new(titleId, rows, cols, size, flags);
public static EndConditionally 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));
public static PlotSubDisposable Subplots(ReadOnlySpan<byte> titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags = ImPlotSubplotFlags.None)
=> new(titleId, rows, cols, size, flags);
public static EndConditionally Subplots(string titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags, ref float rowRatios, ref float colRatios)
=> new EndConditionally(ImPlot.EndSubplots, ImPlot.BeginSubplots(titleId, rows, cols, size, flags, ref rowRatios, ref colRatios));
public static PlotSubDisposable Subplots(string titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags, ref float rowRatios, ref float colRatios)
=> new(titleId, rows, cols, size, flags, ref rowRatios, ref colRatios);
public static EndConditionally 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));
public static PlotSubDisposable Subplots(ReadOnlySpan<byte> titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags, ref float rowRatios, ref float colRatios)
=> new(titleId, rows, cols, size, flags, ref rowRatios, ref colRatios);
public static EndConditionally DragDropSourceAxis(ImAxis axis, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None)
=> new EndConditionally(ImPlot.EndDragDropSource, ImPlot.BeginDragDropSourceAxis(axis, flags));
public static PlotDragDropSourceDisposable DragDropSourceItem(string labelId, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None)
=> new(labelId, flags);
public static EndConditionally DragDropSourceItem(string labelId, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None)
=> new EndConditionally(ImPlot.EndDragDropSource, ImPlot.BeginDragDropSourceItem(labelId, flags));
public static PlotDragDropSourceDisposable DragDropSourceItem(ReadOnlySpan<byte> labelId, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None)
=> new(labelId, flags);
public static EndConditionally DragDropSourceItem(ReadOnlySpan<byte> labelId, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None)
=> new EndConditionally(ImPlot.EndDragDropSource, ImPlot.BeginDragDropSourceItem(labelId, flags));
public static PlotDragDropSourceDisposable DragDropSourceAxis(ImAxis axis, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None)
=> PlotDragDropSourceDisposable.AxisPlot(axis, flags);
public static EndConditionally DragDropSourcePlot(ImGuiDragDropFlags flags = ImGuiDragDropFlags.None)
=> new EndConditionally(ImPlot.EndDragDropSource, ImPlot.BeginDragDropSourcePlot(flags));
public static PlotDragDropSourceDisposable DragDropSourcePlot(ImGuiDragDropFlags flags = ImGuiDragDropFlags.None)
=> PlotDragDropSourceDisposable.SourcePlot(flags);
public static EndConditionally DragDropTargetAxis(ImAxis axis)
=> new EndConditionally(ImPlot.EndDragDropTarget, ImPlot.BeginDragDropTargetAxis(axis));
public static PlotDragDropTargetDisposable DragDropTargetAxis(ImAxis axis)
=> PlotDragDropTargetDisposable.AxisPlot(axis);
public static EndConditionally DragDropTargetLegend()
=> new EndConditionally(ImPlot.EndDragDropTarget, ImPlot.BeginDragDropTargetLegend());
public static PlotDragDropTargetDisposable DragDropTargetLegend()
=> PlotDragDropTargetDisposable.LegendPlot();
public static EndConditionally DragDropTargetPlot()
=> new EndConditionally(ImPlot.EndDragDropTarget, ImPlot.BeginDragDropTargetPlot());
public static PlotDragDropTargetDisposable DragDropTargetPlot()
=> PlotDragDropTargetDisposable.SourcePlot();
#endregion EndObjects
#region Style
public static PlotStyle PushStyle(ImPlotStyleVar idx, int value, bool condition = true)
=> new PlotStyle().Push(idx, value, condition);
public static PlotStyleDisposable PushStyle(ImPlotStyleVar idx, int value, bool condition = true)
=> new PlotStyleDisposable().Push(idx, value, condition);
public static PlotStyle PushStyle(ImPlotStyleVar idx, float value, bool condition = true)
=> new PlotStyle().Push(idx, value, condition);
public static PlotStyleDisposable PushStyle(ImPlotStyleVar idx, float value, bool condition = true)
=> new PlotStyleDisposable().Push(idx, value, condition);
public static PlotStyle PushStyle(ImPlotStyleVar idx, Vector2 value, bool condition = true)
=> new PlotStyle().Push(idx, value, condition);
public static PlotStyleDisposable PushStyle(ImPlotStyleVar idx, Vector2 value, bool condition = true)
=> new PlotStyleDisposable().Push(idx, value, condition);
// Push styles that revert all current plot style changes made temporarily.
public static PlotStyle DefaultPlotStyle()
{
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);
}
public static PlotStyleDisposable DefaultPlotStyle()
=> PlotStyleDisposable.PlotDefaultStyle();
#endregion Style
#region Color
public static PlotColor PushColor(ImPlotCol idx, uint color, bool condition = true)
=> new PlotColor().Push(idx, color, condition);
public static PlotColorDisposable PushColor(ImPlotCol idx, uint color, bool condition = true)
=> new PlotColorDisposable().Push(idx, color, condition);
public static PlotColor PushColor(ImPlotCol idx, Vector4 color, bool condition = true)
=> new PlotColor().Push(idx, color, condition);
public static PlotColorDisposable PushColor(ImPlotCol idx, Vector4 color, bool condition = true)
=> new PlotColorDisposable().Push(idx, color, condition);
// Push colors that revert all current color changes made temporarily.
public static PlotColor DefaultPlotColors()
{
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);
}
public static PlotColorDisposable DefaultPlotColors()
=> PlotColorDisposable.PlotDefaultColors();
#endregion Color
}