From a6d22e34f315d261779c4664db45f24f3410953a Mon Sep 17 00:00:00 2001 From: Infi Date: Sat, 14 Feb 2026 20:58:47 +0100 Subject: [PATCH] - Convert ImPlot to the new ref style --- .../Raii/Disposables/PlotAlignedDisposable.cs | 73 +++++ .../Raii/Disposables/PlotColorDisposable.cs | 124 ++++++++ .../Raii/Disposables/PlotDisposable.cs | 75 +++++ .../PlotDragDropSourceDisposable.cs | 93 ++++++ .../PlotDragDropTargetDisposable.cs | 83 +++++ .../Raii/Disposables/PlotLegendDisposable.cs | 74 +++++ .../Raii/Disposables/PlotStyleDisposable.cs | 188 +++++++++++ .../Raii/Disposables/PlotSubDisposable.cs | 99 ++++++ Dalamud/Interface/Utility/Raii/EndObjects.cs | 105 ------ Dalamud/Interface/Utility/Raii/Plot.cs | 299 +++--------------- 10 files changed, 857 insertions(+), 356 deletions(-) create mode 100644 Dalamud/Interface/Utility/Raii/Disposables/PlotAlignedDisposable.cs create mode 100644 Dalamud/Interface/Utility/Raii/Disposables/PlotColorDisposable.cs create mode 100644 Dalamud/Interface/Utility/Raii/Disposables/PlotDisposable.cs create mode 100644 Dalamud/Interface/Utility/Raii/Disposables/PlotDragDropSourceDisposable.cs create mode 100644 Dalamud/Interface/Utility/Raii/Disposables/PlotDragDropTargetDisposable.cs create mode 100644 Dalamud/Interface/Utility/Raii/Disposables/PlotLegendDisposable.cs create mode 100644 Dalamud/Interface/Utility/Raii/Disposables/PlotStyleDisposable.cs create mode 100644 Dalamud/Interface/Utility/Raii/Disposables/PlotSubDisposable.cs diff --git a/Dalamud/Interface/Utility/Raii/Disposables/PlotAlignedDisposable.cs b/Dalamud/Interface/Utility/Raii/Disposables/PlotAlignedDisposable.cs new file mode 100644 index 000000000..760b0ff05 --- /dev/null +++ b/Dalamud/Interface/Utility/Raii/Disposables/PlotAlignedDisposable.cs @@ -0,0 +1,73 @@ +// ReSharper disable once CheckNamespace + +using Dalamud.Bindings.ImPlot; + +namespace Dalamud.Interface.Utility.Raii; + +public static partial class ImRaii +{ + /// A wrapper around ImGui tables. + public ref struct PlotAlignedDisposable : IDisposable + { + /// Whether creating the table succeeded. This needs to be checked before calling any of the member methods. + public readonly bool Success; + + /// Whether the table is already ended. + public bool Alive { get; private set; } + + /// Initializes a new instance of the struct. + /// The ID of the plot as text. + /// Whether the plot should be vertical. + internal PlotAlignedDisposable(string groupId, bool vertical = true) + { + this.Success = ImPlot.BeginAlignedPlots(groupId, vertical); + this.Alive = true; + } + + /// + internal PlotAlignedDisposable(ReadOnlySpan groupId, bool vertical = true) + { + this.Success = ImPlot.BeginAlignedPlots(groupId, vertical); + this.Alive = true; + } + + /// Conversion to bool. + public static implicit operator bool(PlotAlignedDisposable value) + => value.Success; + + /// Conversion to bool. + public static bool operator true(PlotAlignedDisposable i) + => i.Success; + + /// Conversion to bool. + public static bool operator false(PlotAlignedDisposable i) + => !i.Success; + + /// Conversion to bool on NOT operators. + public static bool operator !(PlotAlignedDisposable i) + => !i.Success; + + /// Conversion to bool on AND operators. + public static bool operator &(PlotAlignedDisposable i, bool value) + => i.Success && value; + + /// Conversion to bool on OR operators. + public static bool operator |(PlotAlignedDisposable i, bool value) + => i.Success || value; + + /// End the Table on leaving scope. + public void Dispose() + { + if (!this.Alive) + return; + + if (this.Success) + ImPlot.EndAlignedPlots(); + this.Alive = false; + } + + /// End a Table without using an IDisposable. + public static void EndUnsafe() + => ImPlot.EndAlignedPlots(); + } +} diff --git a/Dalamud/Interface/Utility/Raii/Disposables/PlotColorDisposable.cs b/Dalamud/Interface/Utility/Raii/Disposables/PlotColorDisposable.cs new file mode 100644 index 000000000..da7a7e789 --- /dev/null +++ b/Dalamud/Interface/Utility/Raii/Disposables/PlotColorDisposable.cs @@ -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 = []; + + /// Gets the number of colors currently pushed using this disposable. + public int Count { get; private set; } + + /// Push a color to the color stack. + /// The type of color to change. + /// The color to change it to. + /// If this is false, the color is not pushed. + /// A disposable object that can be used to push further colors and pops those colors after leaving scope. Use with using. + /// If you need to keep colors pushed longer than the current scope, use without using and use . + public PlotColorDisposable Push(ImPlotCol type, uint color, bool condition = true) + => condition ? this.InternalPush(type, color) : this; + + /// Push a color to the color stack. + /// The type of color to change. + /// The color to change it to. + /// /// If this is false, the color is not pushed. + /// A disposable object that can be used to push further colors and pops those colors after leaving scope. Use with using. + /// If you need to keep colors pushed longer than the current scope, use without using and use . + public PlotColorDisposable Push(ImPlotCol type, Vector4 color, bool condition = true) + => condition ? this.InternalPush(type, color) : this; + + /// Push a color to the color stack. + /// The type of color to change. + /// The color to change it to. If this is null, no color will be set. + /// /// If this is false, the color is not pushed. + /// A disposable object that can be used to push further colors and pops those colors after leaving scope. Use with using. + /// If you need to keep colors pushed longer than the current scope, use without using and use . + 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; + } + + /// Reverts all pushed colors to their previous values temporarily. + /// A disposable object that can be used to push further colors and pops those colors after leaving scope. Use with using. + /// If you need to keep colors pushed longer than the current scope, use without using and use . + 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; + } + + /// Push the default value, i.e. the value as if nothing was ever pushed to this, of a color to the color stack. + /// The type of color to return to its default value. + /// A disposable object that can be used to push further colors and pops those colors after leaving scope. Use with using. + /// If you need to keep colors pushed longer than the current scope, use without using and use . + public PlotColorDisposable PlotPushDefault(ImPlotCol type) + { + foreach (var styleMod in Stack.Where(m => m.Type == type)) + return this.Push(type, styleMod.BackupColor); + + return this; + } + + /// Pop a number of colors. + /// The number of colors to pop. This is clamped to the number of colors pushed by this object. + 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; + } + + /// Pop all pushed colors. + public void Dispose() + { + this.Pop(this.Count); + this.Count = 0; + } + + /// Pop a number of colors. + /// The number of colors to pop. The number is not checked against the color stack. + /// Avoid using this function, and colors across scopes, as much as possible. + 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]); + } +} diff --git a/Dalamud/Interface/Utility/Raii/Disposables/PlotDisposable.cs b/Dalamud/Interface/Utility/Raii/Disposables/PlotDisposable.cs new file mode 100644 index 000000000..a2917ca80 --- /dev/null +++ b/Dalamud/Interface/Utility/Raii/Disposables/PlotDisposable.cs @@ -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 +{ + /// A wrapper around ImGui tables. + public ref struct PlotDisposable : IDisposable + { + /// Whether creating the table succeeded. This needs to be checked before calling any of the member methods. + public readonly bool Success; + + /// Whether the table is already ended. + public bool Alive { get; private set; } + + /// Initializes a new instance of the struct. + /// The ID of the plot as text. + /// The desired size of the plot. + /// Additional flags for the plot. + internal PlotDisposable(string titleId, Vector2 size, ImPlotFlags flags) + { + this.Success = ImPlot.BeginPlot(titleId, size, flags); + this.Alive = true; + } + + /// + internal PlotDisposable(ReadOnlySpan titleId, Vector2 size, ImPlotFlags flags) + { + this.Success = ImPlot.BeginPlot(titleId, size, flags); + this.Alive = true; + } + + /// Conversion to bool. + public static implicit operator bool(PlotDisposable value) + => value.Success; + + /// Conversion to bool. + public static bool operator true(PlotDisposable i) + => i.Success; + + /// Conversion to bool. + public static bool operator false(PlotDisposable i) + => !i.Success; + + /// Conversion to bool on NOT operators. + public static bool operator !(PlotDisposable i) + => !i.Success; + + /// Conversion to bool on AND operators. + public static bool operator &(PlotDisposable i, bool value) + => i.Success && value; + + /// Conversion to bool on OR operators. + public static bool operator |(PlotDisposable i, bool value) + => i.Success || value; + + /// End the Table on leaving scope. + public void Dispose() + { + if (!this.Alive) + return; + + if (this.Success) + ImPlot.EndPlot(); + this.Alive = false; + } + + /// End a Table without using an IDisposable. + public static void EndUnsafe() + => ImPlot.EndPlot(); + } +} diff --git a/Dalamud/Interface/Utility/Raii/Disposables/PlotDragDropSourceDisposable.cs b/Dalamud/Interface/Utility/Raii/Disposables/PlotDragDropSourceDisposable.cs new file mode 100644 index 000000000..cb0be3dea --- /dev/null +++ b/Dalamud/Interface/Utility/Raii/Disposables/PlotDragDropSourceDisposable.cs @@ -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 +{ + /// A wrapper around ImGui tables. + public ref struct PlotDragDropSourceDisposable : IDisposable + { + /// Whether creating the table succeeded. This needs to be checked before calling any of the member methods. + public readonly bool Success; + + /// Whether the table is already ended. + public bool Alive { get; private set; } + + /// Initializes a new instance of the struct. + /// The ID of the plot as text. + /// Additional flags to control the drag and drop behaviour. + internal PlotDragDropSourceDisposable(string labelId, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None) + { + this.Success = ImPlot.BeginDragDropSourceItem(labelId, flags); + this.Alive = true; + } + + /// + internal PlotDragDropSourceDisposable(ReadOnlySpan labelId, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None) + { + this.Success = ImPlot.BeginDragDropSourceItem(labelId, flags); + this.Alive = true; + } + + /// Initialize a new instance of the struct with SourceAxis. + /// The axis to drag. + /// Additional flags to control the drag and drop behaviour. + /// A disposable object that indicates whether the source is active. Use with using. + internal static PlotDragDropSourceDisposable AxisPlot(ImAxis axis, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None) + => new(ImPlot.BeginDragDropSourceAxis(axis, flags)); + + /// Initialize a new instance of the struct with SourcePlot. + /// Additional flags to control the drag and drop behaviour. + /// A disposable object that indicates whether the source is active. Use with using. + internal static PlotDragDropSourceDisposable SourcePlot(ImGuiDragDropFlags flags = ImGuiDragDropFlags.None) + => new(ImPlot.BeginDragDropSourcePlot(flags)); + + private PlotDragDropSourceDisposable(bool success) + { + this.Success = success; + this.Alive = true; + } + + /// Conversion to bool. + public static implicit operator bool(PlotDragDropSourceDisposable value) + => value.Success; + + /// Conversion to bool. + public static bool operator true(PlotDragDropSourceDisposable i) + => i.Success; + + /// Conversion to bool. + public static bool operator false(PlotDragDropSourceDisposable i) + => !i.Success; + + /// Conversion to bool on NOT operators. + public static bool operator !(PlotDragDropSourceDisposable i) + => !i.Success; + + /// Conversion to bool on AND operators. + public static bool operator &(PlotDragDropSourceDisposable i, bool value) + => i.Success && value; + + /// Conversion to bool on OR operators. + public static bool operator |(PlotDragDropSourceDisposable i, bool value) + => i.Success || value; + + /// End the Table on leaving scope. + public void Dispose() + { + if (!this.Alive) + return; + + if (this.Success) + ImPlot.EndDragDropSource(); + this.Alive = false; + } + + /// End a Table without using an IDisposable. + public static void EndUnsafe() + => ImPlot.EndDragDropSource(); + } +} diff --git a/Dalamud/Interface/Utility/Raii/Disposables/PlotDragDropTargetDisposable.cs b/Dalamud/Interface/Utility/Raii/Disposables/PlotDragDropTargetDisposable.cs new file mode 100644 index 000000000..361c847f7 --- /dev/null +++ b/Dalamud/Interface/Utility/Raii/Disposables/PlotDragDropTargetDisposable.cs @@ -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 +{ + /// A wrapper around ImGui tables. + public ref struct PlotDragDropTargetDisposable : IDisposable + { + /// Whether creating the table succeeded. This needs to be checked before calling any of the member methods. + public readonly bool Success; + + /// Whether the table is already ended. + public bool Alive { get; private set; } + + /// Initialize a new instance of the struct with SourceAxis. + /// The axis to drag. + /// Additional flags to control the drag and drop behaviour. + /// A disposable object that indicates whether the source is active. Use with using. + internal static PlotDragDropTargetDisposable AxisPlot(ImAxis axis) + => new(ImPlot.BeginDragDropTargetAxis(axis)); + + /// Initialize a new instance of the struct with SourcePlot. + /// Additional flags to control the drag and drop behaviour. + /// A disposable object that indicates whether the source is active. Use with using. + internal static PlotDragDropTargetDisposable LegendPlot() + => new(ImPlot.BeginDragDropTargetLegend()); + + /// Initialize a new instance of the struct with SourcePlot. + /// Additional flags to control the drag and drop behaviour. + /// A disposable object that indicates whether the source is active. Use with using. + internal static PlotDragDropTargetDisposable SourcePlot() + => new(ImPlot.BeginDragDropTargetPlot()); + + private PlotDragDropTargetDisposable(bool success) + { + this.Success = success; + this.Alive = true; + } + + /// Conversion to bool. + public static implicit operator bool(PlotDragDropTargetDisposable value) + => value.Success; + + /// Conversion to bool. + public static bool operator true(PlotDragDropTargetDisposable i) + => i.Success; + + /// Conversion to bool. + public static bool operator false(PlotDragDropTargetDisposable i) + => !i.Success; + + /// Conversion to bool on NOT operators. + public static bool operator !(PlotDragDropTargetDisposable i) + => !i.Success; + + /// Conversion to bool on AND operators. + public static bool operator &(PlotDragDropTargetDisposable i, bool value) + => i.Success && value; + + /// Conversion to bool on OR operators. + public static bool operator |(PlotDragDropTargetDisposable i, bool value) + => i.Success || value; + + /// End the Table on leaving scope. + public void Dispose() + { + if (!this.Alive) + return; + + if (this.Success) + ImPlot.EndDragDropTarget(); + this.Alive = false; + } + + /// End a Table without using an IDisposable. + public static void EndUnsafe() + => ImPlot.EndDragDropTarget(); + } +} diff --git a/Dalamud/Interface/Utility/Raii/Disposables/PlotLegendDisposable.cs b/Dalamud/Interface/Utility/Raii/Disposables/PlotLegendDisposable.cs new file mode 100644 index 000000000..65eba1c26 --- /dev/null +++ b/Dalamud/Interface/Utility/Raii/Disposables/PlotLegendDisposable.cs @@ -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 +{ + /// A wrapper around ImGui tables. + public ref struct PlotLegendDisposable : IDisposable + { + /// Whether creating the table succeeded. This needs to be checked before calling any of the member methods. + public readonly bool Success; + + /// Whether the table is already ended. + public bool Alive { get; private set; } + + /// Initializes a new instance of the struct. + /// The ID of the plot as text. + /// The mouse button to use for opening the legend popup. + internal PlotLegendDisposable(string labelId, ImGuiMouseButton mouseButton = ImGuiMouseButton.Right) + { + this.Success = ImPlot.BeginLegendPopup(labelId, mouseButton); + this.Alive = true; + } + + /// + internal PlotLegendDisposable(ReadOnlySpan labelId, ImGuiMouseButton mouseButton = ImGuiMouseButton.Right) + { + this.Success = ImPlot.BeginLegendPopup(labelId, mouseButton); + this.Alive = true; + } + + /// Conversion to bool. + public static implicit operator bool(PlotLegendDisposable value) + => value.Success; + + /// Conversion to bool. + public static bool operator true(PlotLegendDisposable i) + => i.Success; + + /// Conversion to bool. + public static bool operator false(PlotLegendDisposable i) + => !i.Success; + + /// Conversion to bool on NOT operators. + public static bool operator !(PlotLegendDisposable i) + => !i.Success; + + /// Conversion to bool on AND operators. + public static bool operator &(PlotLegendDisposable i, bool value) + => i.Success && value; + + /// Conversion to bool on OR operators. + public static bool operator |(PlotLegendDisposable i, bool value) + => i.Success || value; + + /// End the Table on leaving scope. + public void Dispose() + { + if (!this.Alive) + return; + + if (this.Success) + ImPlot.EndLegendPopup(); + this.Alive = false; + } + + /// End a Table without using an IDisposable. + public static void EndUnsafe() + => ImPlot.EndLegendPopup(); + } +} diff --git a/Dalamud/Interface/Utility/Raii/Disposables/PlotStyleDisposable.cs b/Dalamud/Interface/Utility/Raii/Disposables/PlotStyleDisposable.cs new file mode 100644 index 000000000..c2f9d6960 --- /dev/null +++ b/Dalamud/Interface/Utility/Raii/Disposables/PlotStyleDisposable.cs @@ -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 +{ + /// A wrapper around style pushing. + public sealed class PlotStyleDisposable : IDisposable + { + internal static readonly List<(ImPlotStyleVar Type, Vector2 Value)> Stack = []; + + /// The number of styles currently pushed using this disposable. + public int Count { get; private set; } + + /// Push a style variable to the style stack. + /// The type of style variable to change. + /// The value to change it to. + /// If this is false, the style is not pushed. + /// A disposable object that can be used to push further style variables and pops those style variables after leaving scope. Use with using. + /// If you need to keep styles pushed longer than the current scope, use without using and use . + public PlotStyleDisposable Push(ImPlotStyleVar type, float value, bool condition) + => condition ? this.Push(type, value) : this; + + /// + 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; + } + + /// + public PlotStyleDisposable Push(ImPlotStyleVar type, Vector2 value) + { + CheckStyleIdx(type, typeof(Vector2)); + Stack.Add((type, GetStyle(type))); + + ImPlot.PushStyleVar(type, value); + ++this.Count; + return this; + } + + /// Push styles that revert all current style changes made temporarily. + 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; + } + + /// Push the default value, i.e. the value as if nothing was ever pushed to this, of a style variable to the style stack. + /// The type of style variable to return to its default value. + /// A disposable object that can be used to push further style variables and pops those style variables after leaving scope. Use with using. + /// If you need to keep styles pushed longer than the current scope, use without using and use . + public PlotStyleDisposable PlotPushDefault(ImPlotStyleVar type) + { + foreach (var styleMod in Stack.Where(m => m.Type == type)) + return Push(type, styleMod.Value); + + return this; + } + + /// Pop a number of style variables. + /// The number of style variables to pop. This is clamped to the number of style variables pushed by this object. + 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; + } + + /// Pop all pushed styles. + public void Dispose() + { + ImPlot.PopStyleVar(this.Count); + this.Count = 0; + } + + /// Pop a number of style variables. + /// The number of style variables to pop. The number is not checked against the style stack. + /// Avoid using this function, and styles across scopes, as much as possible. + 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), + }; + } + } +} diff --git a/Dalamud/Interface/Utility/Raii/Disposables/PlotSubDisposable.cs b/Dalamud/Interface/Utility/Raii/Disposables/PlotSubDisposable.cs new file mode 100644 index 000000000..5232e625e --- /dev/null +++ b/Dalamud/Interface/Utility/Raii/Disposables/PlotSubDisposable.cs @@ -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 +{ + /// A wrapper around ImGui tables. + public ref struct PlotSubDisposable : IDisposable + { + /// Whether creating the table succeeded. This needs to be checked before calling any of the member methods. + public readonly bool Success; + + /// Whether the table is already ended. + public bool Alive { get; private set; } + + /// Initializes a new instance of the struct. + /// The ID of the plot as text. + /// The number of rows in the plot. + /// The number of columns in the plot. + /// The desired size of the plot. + /// Additional flags for the plot. + 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; + } + + /// + internal PlotSubDisposable(ReadOnlySpan titleId, int rows, int cols, Vector2 size, ImPlotSubplotFlags flags = ImPlotSubplotFlags.None) + { + this.Success = ImPlot.BeginSubplots(titleId, rows, cols, size, flags); + this.Alive = true; + } + + /// Initializes a new instance of the struct. + /// The ID of the plot as text. + /// The number of rows in the plot. + /// The number of columns in the plot. + /// The desired size of the plot. + /// Additional flags for the plot. + /// The row ratios for the plot. + /// The column ratios for the plot. + 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; + } + + /// + internal PlotSubDisposable(ReadOnlySpan 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; + } + + /// Conversion to bool. + public static implicit operator bool(PlotSubDisposable value) + => value.Success; + + /// Conversion to bool. + public static bool operator true(PlotSubDisposable i) + => i.Success; + + /// Conversion to bool. + public static bool operator false(PlotSubDisposable i) + => !i.Success; + + /// Conversion to bool on NOT operators. + public static bool operator !(PlotSubDisposable i) + => !i.Success; + + /// Conversion to bool on AND operators. + public static bool operator &(PlotSubDisposable i, bool value) + => i.Success && value; + + /// Conversion to bool on OR operators. + public static bool operator |(PlotSubDisposable i, bool value) + => i.Success || value; + + /// End the Table on leaving scope. + public void Dispose() + { + if (!this.Alive) + return; + + if (this.Success) + ImPlot.EndSubplots(); + this.Alive = false; + } + + /// End a Table without using an IDisposable. + public static void EndUnsafe() + => ImPlot.EndSubplots(); + } +} diff --git a/Dalamud/Interface/Utility/Raii/EndObjects.cs b/Dalamud/Interface/Utility/Raii/EndObjects.cs index 1d3ce1dad..bce02ed51 100644 --- a/Dalamud/Interface/Utility/Raii/EndObjects.cs +++ b/Dalamud/Interface/Utility/Raii/EndObjects.cs @@ -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; - } } diff --git a/Dalamud/Interface/Utility/Raii/Plot.cs b/Dalamud/Interface/Utility/Raii/Plot.cs index 807b2c38d..b5c7e6a03 100644 --- a/Dalamud/Interface/Utility/Raii/Plot.cs +++ b/Dalamud/Interface/Utility/Raii/Plot.cs @@ -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 titleId, Vector2 size, ImPlotFlags flags) - => new EndConditionally(ImPlot.EndPlot, ImPlot.BeginPlot(titleId, size, flags)); + public static PlotDisposable Plot(ReadOnlySpan 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 groupId, bool vertical = true) - => new EndConditionally(ImPlot.EndAlignedPlots, ImPlot.BeginAlignedPlots(groupId, vertical)); + public static PlotAlignedDisposable AlignedPlots(ReadOnlySpan 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 labelId, ImGuiMouseButton mouseButton = ImGuiMouseButton.Right) - => new EndConditionally(ImPlot.EndLegendPopup, ImPlot.BeginLegendPopup(labelId, mouseButton)); + public static PlotLegendDisposable LegendPopup(ReadOnlySpan 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 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 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 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 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 labelId, ImGuiDragDropFlags flags = ImGuiDragDropFlags.None) + => new(labelId, flags); - public static EndConditionally DragDropSourceItem(ReadOnlySpan 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 }