chore: add raii, tables code from OtterGui into new Dalamud.Interface assembly

This commit is contained in:
goat 2023-03-06 20:52:21 +01:00
parent e0d4e60aad
commit 6bf1376515
No known key found for this signature in database
GPG key ID: 49E2AA8C6A76498B
22 changed files with 1356 additions and 0 deletions

View file

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

View file

@ -0,0 +1,239 @@
using System.Numerics;
using ImGuiNET;
namespace Dalamud.Interface.Raii;
// Most ImGui widgets with IDisposable interface that automatically destroys them
// when created with using variables.
public static partial class ImRaii
{
private static int _disabledCount = 0;
public static IEndObject Child(string strId)
=> new EndUnconditionally(ImGui.EndChild, ImGui.BeginChild(strId));
public static IEndObject Child(string strId, Vector2 size)
=> new EndUnconditionally(ImGui.EndChild, ImGui.BeginChild(strId, size));
public static IEndObject Child(string strId, Vector2 size, bool border)
=> new EndUnconditionally(ImGui.EndChild, ImGui.BeginChild(strId, size, border));
public static IEndObject Child(string strId, Vector2 size, bool border, ImGuiWindowFlags flags)
=> new EndUnconditionally(ImGui.EndChild, ImGui.BeginChild(strId, size, border, flags));
public static IEndObject DragDropTarget()
=> new EndConditionally(ImGui.EndDragDropTarget, ImGui.BeginDragDropTarget());
public static IEndObject DragDropSource()
=> new EndConditionally(ImGui.EndDragDropSource, ImGui.BeginDragDropSource());
public static IEndObject DragDropSource(ImGuiDragDropFlags flags)
=> new EndConditionally(ImGui.EndDragDropSource, ImGui.BeginDragDropSource(flags));
public static IEndObject Popup(string id)
=> new EndConditionally(ImGui.EndPopup, ImGui.BeginPopup(id));
public static IEndObject Popup(string id, ImGuiWindowFlags flags)
=> new EndConditionally(ImGui.EndPopup, ImGui.BeginPopup(id, flags));
public static IEndObject ContextPopup(string id)
=> new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupContextWindow(id));
public static IEndObject ContextPopup(string id, ImGuiPopupFlags flags)
=> new EndConditionally(ImGui.EndPopup, ImGui.BeginPopupContextWindow(id, flags));
public static IEndObject Combo(string label, string previewValue)
=> new EndConditionally(ImGui.EndCombo, ImGui.BeginCombo(label, previewValue));
public static IEndObject Combo(string label, string previewValue, ImGuiComboFlags flags)
=> new EndConditionally(ImGui.EndCombo, ImGui.BeginCombo(label, previewValue, flags));
public static IEndObject Group()
{
ImGui.BeginGroup();
return new EndUnconditionally(ImGui.EndGroup, true);
}
public static IEndObject Tooltip()
{
ImGui.BeginTooltip();
return new EndUnconditionally(ImGui.EndTooltip, true);
}
public static IEndObject ListBox(string label)
=> new EndConditionally(ImGui.EndListBox, ImGui.BeginListBox(label));
public static IEndObject ListBox(string label, Vector2 size)
=> new EndConditionally(ImGui.EndListBox, ImGui.BeginListBox(label, size));
public static IEndObject Table(string table, int numColumns)
=> new EndConditionally(ImGui.EndTable, ImGui.BeginTable(table, numColumns));
public static IEndObject Table(string table, int numColumns, ImGuiTableFlags flags)
=> new EndConditionally(ImGui.EndTable, ImGui.BeginTable(table, numColumns, flags));
public static IEndObject Table(string table, int numColumns, ImGuiTableFlags flags, Vector2 outerSize)
=> new EndConditionally(ImGui.EndTable, ImGui.BeginTable(table, numColumns, flags, outerSize));
public static IEndObject Table(string table, int numColumns, ImGuiTableFlags flags, Vector2 outerSize, float innerWidth)
=> new EndConditionally(ImGui.EndTable, ImGui.BeginTable(table, numColumns, flags, outerSize, innerWidth));
public static IEndObject TabBar(string label)
=> new EndConditionally(ImGui.EndTabBar, ImGui.BeginTabBar(label));
public static IEndObject TabBar(string label, ImGuiTabBarFlags flags)
=> new EndConditionally(ImGui.EndTabBar, ImGui.BeginTabBar(label, flags));
public static IEndObject TabItem(string label)
=> new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label));
public static unsafe IEndObject TabItem(byte* label, ImGuiTabItemFlags flags)
=> new EndConditionally(ImGuiNative.igEndTabItem, ImGuiNative.igBeginTabItem(label, null, flags) != 0);
public static IEndObject TabItem(string label, ref bool open)
=> new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label, ref open));
public static IEndObject TabItem(string label, ref bool open, ImGuiTabItemFlags flags)
=> new EndConditionally(ImGui.EndTabItem, ImGui.BeginTabItem(label, ref open, flags));
public static IEndObject TreeNode(string label)
=> new EndConditionally(ImGui.TreePop, ImGui.TreeNodeEx(label));
public static IEndObject TreeNode(string label, ImGuiTreeNodeFlags flags)
=> new EndConditionally(flags.HasFlag(ImGuiTreeNodeFlags.NoTreePushOnOpen) ? Nop : ImGui.TreePop, ImGui.TreeNodeEx(label, flags));
public static IEndObject Disabled()
{
ImGui.BeginDisabled();
++_disabledCount;
return DisabledEnd();
}
public static IEndObject Disabled(bool disabled)
{
if (!disabled)
return new EndConditionally(Nop, false);
ImGui.BeginDisabled();
++_disabledCount;
return DisabledEnd();
}
public static IEndObject Enabled()
{
var oldCount = _disabledCount;
if (oldCount == 0)
return new EndConditionally(Nop, false);
void Restore()
{
_disabledCount += oldCount;
while (--oldCount >= 0)
ImGui.BeginDisabled();
}
for (; _disabledCount > 0; --_disabledCount)
ImGui.EndDisabled();
return new EndUnconditionally(Restore, true);
}
private static IEndObject DisabledEnd()
=> new EndUnconditionally(() =>
{
--_disabledCount;
ImGui.EndDisabled();
}, true);
/* Only in OtterGui for now
public static IEndObject FramedGroup(string label)
{
Widget.BeginFramedGroup(label, Vector2.Zero);
return new EndUnconditionally(Widget.EndFramedGroup, true);
}
public static IEndObject FramedGroup(string label, Vector2 minSize, string description = "")
{
Widget.BeginFramedGroup(label, minSize, description);
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;
// Empty end object.
public static readonly IEndObject Empty = new EndConditionally(Nop, false);
}
// Use end-function regardless of success.
// Used by Child, Group and Tooltip.
private struct EndUnconditionally : IEndObject
{
private Action EndAction { get; }
public bool Success { get; }
public bool Disposed { get; private set; }
public EndUnconditionally(Action endAction, bool success)
{
this.EndAction = endAction;
this.Success = success;
this.Disposed = false;
}
public void Dispose()
{
if (this.Disposed)
return;
this.EndAction();
this.Disposed = true;
}
}
// Use end-function only on success.
private struct EndConditionally : IEndObject
{
private Action EndAction { get; }
public bool Success { get; }
public bool Disposed { get; private set; }
public EndConditionally(Action endAction, bool success)
{
this.EndAction = endAction;
this.Success = success;
this.Disposed = false;
}
public void Dispose()
{
if (this.Disposed)
return;
if (this.Success)
this.EndAction();
this.Disposed = true;
}
}
// Used to avoid tree pops when flag for no push is set.
private static void Nop()
{ }
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,147 @@
using System.Numerics;
using ImGuiNET;
namespace Dalamud.Interface.Raii;
// Push an arbitrary amount of styles into an object that are all popped when it is disposed.
// If condition is false, no style is pushed.
// In debug mode, checks that the type of the value given for the style is valid.
public static partial class ImRaii
{
public static Style PushStyle(ImGuiStyleVar idx, float value, bool condition = true)
=> new Style().Push(idx, value, condition);
public static Style PushStyle(ImGuiStyleVar idx, Vector2 value, bool condition = true)
=> new Style().Push(idx, value, condition);
// Push styles that revert all current style changes made temporarily.
public static Style DefaultStyle()
{
var ret = new Style();
var reverseStack = Style.Stack.GroupBy(p => p.Item1).Select(p => (p.Key, p.First().Item2)).ToArray();
foreach (var (idx, val) in reverseStack)
{
if (float.IsNaN(val.Y))
ret.Push(idx, val.X);
else
ret.Push(idx, val);
}
return ret;
}
public sealed class Style : IDisposable
{
internal static readonly List<(ImGuiStyleVar, Vector2)> Stack = new();
private int _count;
[System.Diagnostics.Conditional("DEBUG")]
private static void CheckStyleIdx(ImGuiStyleVar idx, Type type)
{
var shouldThrow = idx switch
{
ImGuiStyleVar.Alpha => type != typeof(float),
ImGuiStyleVar.WindowPadding => type != typeof(Vector2),
ImGuiStyleVar.WindowRounding => type != typeof(float),
ImGuiStyleVar.WindowBorderSize => type != typeof(float),
ImGuiStyleVar.WindowMinSize => type != typeof(Vector2),
ImGuiStyleVar.WindowTitleAlign => type != typeof(Vector2),
ImGuiStyleVar.ChildRounding => type != typeof(float),
ImGuiStyleVar.ChildBorderSize => type != typeof(float),
ImGuiStyleVar.PopupRounding => type != typeof(float),
ImGuiStyleVar.PopupBorderSize => type != typeof(float),
ImGuiStyleVar.FramePadding => type != typeof(Vector2),
ImGuiStyleVar.FrameRounding => type != typeof(float),
ImGuiStyleVar.FrameBorderSize => type != typeof(float),
ImGuiStyleVar.ItemSpacing => type != typeof(Vector2),
ImGuiStyleVar.ItemInnerSpacing => type != typeof(Vector2),
ImGuiStyleVar.IndentSpacing => type != typeof(float),
ImGuiStyleVar.CellPadding => type != typeof(Vector2),
ImGuiStyleVar.ScrollbarSize => type != typeof(float),
ImGuiStyleVar.ScrollbarRounding => type != typeof(float),
ImGuiStyleVar.GrabMinSize => type != typeof(float),
ImGuiStyleVar.GrabRounding => type != typeof(float),
ImGuiStyleVar.TabRounding => type != typeof(float),
ImGuiStyleVar.ButtonTextAlign => type != typeof(Vector2),
ImGuiStyleVar.SelectableTextAlign => type != typeof(Vector2),
ImGuiStyleVar.DisabledAlpha => type != typeof(float),
_ => throw new ArgumentOutOfRangeException(nameof(idx), idx, null),
};
if (shouldThrow)
throw new ArgumentException($"Unable to push {type} to {idx}.");
}
public static Vector2 GetStyle(ImGuiStyleVar idx)
{
var style = ImGui.GetStyle();
return idx switch
{
ImGuiStyleVar.Alpha => new Vector2(style.Alpha, float.NaN),
ImGuiStyleVar.WindowPadding => style.WindowPadding,
ImGuiStyleVar.WindowRounding => new Vector2(style.WindowRounding, float.NaN),
ImGuiStyleVar.WindowBorderSize => new Vector2(style.WindowBorderSize, float.NaN),
ImGuiStyleVar.WindowMinSize => style.WindowMinSize,
ImGuiStyleVar.WindowTitleAlign => style.WindowTitleAlign,
ImGuiStyleVar.ChildRounding => new Vector2(style.ChildRounding, float.NaN),
ImGuiStyleVar.ChildBorderSize => new Vector2(style.ChildBorderSize, float.NaN),
ImGuiStyleVar.PopupRounding => new Vector2(style.PopupRounding, float.NaN),
ImGuiStyleVar.PopupBorderSize => new Vector2(style.PopupBorderSize, float.NaN),
ImGuiStyleVar.FramePadding => style.FramePadding,
ImGuiStyleVar.FrameRounding => new Vector2(style.FrameRounding, float.NaN),
ImGuiStyleVar.FrameBorderSize => new Vector2(style.FrameBorderSize, float.NaN),
ImGuiStyleVar.ItemSpacing => style.ItemSpacing,
ImGuiStyleVar.ItemInnerSpacing => style.ItemInnerSpacing,
ImGuiStyleVar.IndentSpacing => new Vector2(style.IndentSpacing, float.NaN),
ImGuiStyleVar.CellPadding => style.CellPadding,
ImGuiStyleVar.ScrollbarSize => new Vector2(style.ScrollbarSize, float.NaN),
ImGuiStyleVar.ScrollbarRounding => new Vector2(style.ScrollbarRounding, float.NaN),
ImGuiStyleVar.GrabMinSize => new Vector2(style.GrabMinSize, float.NaN),
ImGuiStyleVar.GrabRounding => new Vector2(style.GrabRounding, float.NaN),
ImGuiStyleVar.TabRounding => new Vector2(style.TabRounding, float.NaN),
ImGuiStyleVar.ButtonTextAlign => style.ButtonTextAlign,
ImGuiStyleVar.SelectableTextAlign => style.SelectableTextAlign,
ImGuiStyleVar.DisabledAlpha => new Vector2(style.DisabledAlpha, float.NaN),
_ => throw new ArgumentOutOfRangeException(nameof(idx), idx, null),
};
}
public Style Push(ImGuiStyleVar idx, float value, bool condition = true)
{
if (!condition)
return this;
CheckStyleIdx(idx, typeof(float));
Stack.Add((idx, GetStyle(idx)));
ImGui.PushStyleVar(idx, value);
++this._count;
return this;
}
public Style Push(ImGuiStyleVar idx, Vector2 value, bool condition = true)
{
if (!condition)
return this;
CheckStyleIdx(idx, typeof(Vector2));
Stack.Add((idx, GetStyle(idx)));
ImGui.PushStyleVar(idx, value);
++this._count;
return this;
}
public void Pop(int num = 1)
{
num = Math.Min(num, this._count);
this._count -= num;
ImGui.PopStyleVar(num);
Stack.RemoveRange(Stack.Count - num, num);
}
public void Dispose()
=> this.Pop(this._count);
}
}