- 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); 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 System.Numerics;
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
@ -7,293 +5,92 @@ using Dalamud.Bindings.ImPlot;
namespace Dalamud.Interface.Utility.Raii; namespace Dalamud.Interface.Utility.Raii;
// TODO Convert to new syntax
// All previous files, but only for ImPlot specific functions. // All previous files, but only for ImPlot specific functions.
public static partial class ImRaii public static partial class ImRaii
{ {
#region EndObjects #region EndObjects
public static EndConditionally 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 EndConditionally 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 EndConditionally 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 EndConditionally 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 EndConditionally 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 EndConditionally 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 EndConditionally 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 EndConditionally 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 EndConditionally 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 EndConditionally 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 EndConditionally 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 EndConditionally 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 EndConditionally 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 EndConditionally 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 EndConditionally DragDropTargetAxis(ImAxis axis) public static PlotDragDropTargetDisposable DragDropTargetAxis(ImAxis axis)
=> new EndConditionally(ImPlot.EndDragDropTarget, ImPlot.BeginDragDropTargetAxis(axis)); => PlotDragDropTargetDisposable.AxisPlot(axis);
public static EndConditionally DragDropTargetLegend() public static PlotDragDropTargetDisposable DragDropTargetLegend()
=> new EndConditionally(ImPlot.EndDragDropTarget, ImPlot.BeginDragDropTargetLegend()); => PlotDragDropTargetDisposable.LegendPlot();
public static EndConditionally 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
} }