mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
chore: add raii, tables code from OtterGui into new Dalamud.Interface assembly
This commit is contained in:
parent
e0d4e60aad
commit
6bf1376515
22 changed files with 1356 additions and 0 deletions
85
Dalamud.Interface/ArrayExtensions.cs
Normal file
85
Dalamud.Interface/ArrayExtensions.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Dalamud.Interface;
|
||||
|
||||
internal static class ArrayExtensions
|
||||
{
|
||||
/// <summary> Iterate over enumerables with additional index. </summary>
|
||||
public static IEnumerable<(T Value, int Index)> WithIndex<T>(this IEnumerable<T> list)
|
||||
=> list.Select((x, i) => (x, i));
|
||||
|
||||
/// <summary> Remove an added index from an indexed enumerable. </summary>
|
||||
public static IEnumerable<T> WithoutIndex<T>(this IEnumerable<(T Value, int Index)> list)
|
||||
=> list.Select(x => x.Value);
|
||||
|
||||
/// <summary> Remove the value and only keep the index from an indexed enumerable. </summary>
|
||||
public static IEnumerable<int> WithoutValue<T>(this IEnumerable<(T Value, int Index)> list)
|
||||
=> list.Select(x => x.Index);
|
||||
|
||||
|
||||
// Find the index of the first object fulfilling predicate's criteria in the given list.
|
||||
// Returns -1 if no such object is found.
|
||||
public static int IndexOf<T>(this IEnumerable<T> array, Predicate<T> predicate)
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var obj in array)
|
||||
{
|
||||
if (predicate(obj))
|
||||
return i;
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Find the index of the first occurrence of needle in the given list.
|
||||
// Returns -1 if needle is not contained in the list.
|
||||
public static int IndexOf<T>(this IEnumerable<T> array, T needle) where T : notnull
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var obj in array)
|
||||
{
|
||||
if (needle.Equals(obj))
|
||||
return i;
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Find the first object fulfilling predicate's criteria in the given list, if one exists.
|
||||
// Returns true if an object is found, false otherwise.
|
||||
public static bool FindFirst<T>(this IEnumerable<T> array, Predicate<T> predicate, [NotNullWhen(true)] out T? result)
|
||||
{
|
||||
foreach (var obj in array)
|
||||
{
|
||||
if (predicate(obj))
|
||||
{
|
||||
result = obj!;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the first occurrence of needle in the given list and return the value contained in the list in result.
|
||||
// Returns true if an object is found, false otherwise.
|
||||
public static bool FindFirst<T>(this IEnumerable<T> array, T needle, [NotNullWhen(true)] out T? result) where T : notnull
|
||||
{
|
||||
foreach (var obj in array)
|
||||
{
|
||||
if (obj.Equals(needle))
|
||||
{
|
||||
result = obj;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
17
Dalamud.Interface/Dalamud.Interface.csproj
Normal file
17
Dalamud.Interface/Dalamud.Interface.csproj
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Platforms>x64;AnyCPU</Platforms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<RootNamespace>Dalamud.Interface</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
144
Dalamud.Interface/ImGuiClip.cs
Normal file
144
Dalamud.Interface/ImGuiClip.cs
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
using System.Numerics;
|
||||
using Dalamud.Interface.Raii;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface;
|
||||
|
||||
public static class ImGuiClip
|
||||
{
|
||||
// Get the number of skipped items of a given height necessary for the current scroll bar,
|
||||
// and apply the dummy of the appropriate height, removing one item spacing.
|
||||
// The height has to contain the spacing.
|
||||
public static int GetNecessarySkips(float height)
|
||||
{
|
||||
var curY = ImGui.GetScrollY();
|
||||
var skips = (int)(curY / height);
|
||||
if (skips > 0)
|
||||
ImGui.Dummy(new Vector2(1, skips * height - ImGui.GetStyle().ItemSpacing.Y));
|
||||
|
||||
return skips;
|
||||
}
|
||||
|
||||
// Draw the dummy for the remaining items computed by ClippedDraw,
|
||||
// removing one item spacing.
|
||||
public static void DrawEndDummy(int remainder, float height)
|
||||
{
|
||||
if (remainder > 0)
|
||||
ImGui.Dummy(new Vector2(1, remainder * height - ImGui.GetStyle().ItemSpacing.Y));
|
||||
}
|
||||
|
||||
// Draw a clipped random-access collection of consistent height lineHeight.
|
||||
// Uses ImGuiListClipper and thus handles start- and end-dummies itself.
|
||||
public static void ClippedDraw<T>(IReadOnlyList<T> data, Action<T> draw, float lineHeight)
|
||||
{
|
||||
ImGuiListClipperPtr clipper;
|
||||
unsafe
|
||||
{
|
||||
clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
|
||||
}
|
||||
|
||||
clipper.Begin(data.Count, lineHeight);
|
||||
while (clipper.Step())
|
||||
{
|
||||
for (var actualRow = clipper.DisplayStart; actualRow < clipper.DisplayEnd; actualRow++)
|
||||
{
|
||||
if (actualRow >= data.Count)
|
||||
return;
|
||||
|
||||
if (actualRow < 0)
|
||||
continue;
|
||||
|
||||
draw(data[actualRow]);
|
||||
}
|
||||
}
|
||||
|
||||
clipper.End();
|
||||
clipper.Destroy();
|
||||
}
|
||||
|
||||
// Draw a clipped random-access collection of consistent height lineHeight.
|
||||
// Uses ImGuiListClipper and thus handles start- and end-dummies itself, but acts on type and index.
|
||||
public static void ClippedDraw<T>(IReadOnlyList<T> data, Action<T, int> draw, float lineHeight)
|
||||
{
|
||||
ImGuiListClipperPtr clipper;
|
||||
unsafe
|
||||
{
|
||||
clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
|
||||
}
|
||||
|
||||
clipper.Begin(data.Count, lineHeight);
|
||||
while (clipper.Step())
|
||||
{
|
||||
for (var actualRow = clipper.DisplayStart; actualRow < clipper.DisplayEnd; actualRow++)
|
||||
{
|
||||
if (actualRow >= data.Count)
|
||||
return;
|
||||
|
||||
if (actualRow < 0)
|
||||
continue;
|
||||
|
||||
draw(data[actualRow], actualRow);
|
||||
}
|
||||
}
|
||||
|
||||
clipper.End();
|
||||
clipper.Destroy();
|
||||
}
|
||||
|
||||
// Draw non-random-access data without storing state.
|
||||
// Use GetNecessarySkips first and use its return value for skips.
|
||||
// startIndex can be set if using multiple separate chunks of data with different filter or draw functions (of the same height).
|
||||
// Returns either the non-negative remaining objects in data that could not be drawn due to being out of the visible area,
|
||||
// if count was given this will be subtracted instead of counted,
|
||||
// or the bitwise-inverse of the next startIndex for subsequent collections, if there is still room for more visible objects.
|
||||
public static int ClippedDraw<T>(IEnumerable<T> data, int skips, Action<T> draw, int? count = null, int startIndex = 0)
|
||||
{
|
||||
if (count != null && count.Value + startIndex <= skips)
|
||||
return ~(count.Value + startIndex);
|
||||
|
||||
using var it = data.GetEnumerator();
|
||||
var visible = false;
|
||||
var idx = startIndex;
|
||||
while (it.MoveNext())
|
||||
{
|
||||
if (idx >= skips)
|
||||
{
|
||||
using var group = ImRaii.Group();
|
||||
draw(it.Current);
|
||||
group.Dispose();
|
||||
if (!ImGui.IsItemVisible())
|
||||
{
|
||||
if (visible)
|
||||
{
|
||||
if (count != null)
|
||||
return Math.Max(0, count.Value - idx + startIndex - 1);
|
||||
|
||||
var remainder = 0;
|
||||
while (it.MoveNext())
|
||||
++remainder;
|
||||
|
||||
return remainder;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
++idx;
|
||||
}
|
||||
|
||||
return ~idx;
|
||||
}
|
||||
|
||||
|
||||
// Draw non-random-access data that gets filtered without storing state.
|
||||
// Use GetNecessarySkips first and use its return value for skips.
|
||||
// checkFilter should return true for items that should be displayed and false for those that should be skipped.
|
||||
// startIndex can be set if using multiple separate chunks of data with different filter or draw functions (of the same height).
|
||||
// Returns either the non-negative remaining objects in data that could not be drawn due to being out of the visible area,
|
||||
// or the bitwise-inverse of the next startIndex for subsequent collections, if there is still room for more visible objects.
|
||||
public static int FilteredClippedDraw<T>(IEnumerable<T> data, int skips, Func<T, bool> checkFilter, Action<T> draw, int startIndex = 0)
|
||||
=> ClippedDraw(data.Where(checkFilter), skips, draw, null, startIndex);
|
||||
}
|
||||
41
Dalamud.Interface/ImGuiTable.cs
Normal file
41
Dalamud.Interface/ImGuiTable.cs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
using Dalamud.Interface.Raii;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface;
|
||||
|
||||
public static class ImGuiTable
|
||||
{
|
||||
// Draw a simple table with the given data using the drawRow action.
|
||||
// Headers and thus columns and column count are defined by columnTitles.
|
||||
public static void DrawTable<T>(string label, IEnumerable<T> data, Action<T> drawRow, ImGuiTableFlags flags = ImGuiTableFlags.None,
|
||||
params string[] columnTitles)
|
||||
{
|
||||
if (columnTitles.Length == 0)
|
||||
return;
|
||||
|
||||
using var table = ImRaii.Table(label, columnTitles.Length, flags);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
foreach (var title in columnTitles)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableHeader(title);
|
||||
}
|
||||
|
||||
foreach (var datum in data)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
drawRow(datum);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a simple table with the given data using the drawRow action inside a collapsing header.
|
||||
// Headers and thus columns and column count are defined by columnTitles.
|
||||
public static void DrawTabbedTable<T>(string label, IEnumerable<T> data, Action<T> drawRow, ImGuiTableFlags flags = ImGuiTableFlags.None,
|
||||
params string[] columnTitles)
|
||||
{
|
||||
if (ImGui.CollapsingHeader(label))
|
||||
DrawTable($"{label}##Table", data, drawRow, flags, columnTitles);
|
||||
}
|
||||
}
|
||||
6
Dalamud.Interface/InterfaceHelpers.cs
Normal file
6
Dalamud.Interface/InterfaceHelpers.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
namespace Dalamud.Interface;
|
||||
|
||||
public static class InterfaceHelpers
|
||||
{
|
||||
public static float GlobalScale = 1.0f;
|
||||
}
|
||||
66
Dalamud.Interface/Raii/Color.cs
Normal file
66
Dalamud.Interface/Raii/Color.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
239
Dalamud.Interface/Raii/EndObjects.cs
Normal file
239
Dalamud.Interface/Raii/EndObjects.cs
Normal 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()
|
||||
{ }
|
||||
}
|
||||
51
Dalamud.Interface/Raii/Font.cs
Normal file
51
Dalamud.Interface/Raii/Font.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
66
Dalamud.Interface/Raii/Id.cs
Normal file
66
Dalamud.Interface/Raii/Id.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
70
Dalamud.Interface/Raii/Indent.cs
Normal file
70
Dalamud.Interface/Raii/Indent.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
147
Dalamud.Interface/Raii/Style.cs
Normal file
147
Dalamud.Interface/Raii/Style.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
29
Dalamud.Interface/StableInsertionSortExtension.cs
Normal file
29
Dalamud.Interface/StableInsertionSortExtension.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Dalamud.Interface;
|
||||
|
||||
internal static class StableInsertionSortExtension
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static void StableSort<T, TKey>(this IList<T> list, Func<T, TKey> selector)
|
||||
{
|
||||
var tmpList = new List<T>(list.Count);
|
||||
tmpList.AddRange(list.OrderBy(selector));
|
||||
for (var i = 0; i < tmpList.Count; ++i)
|
||||
list[i] = tmpList[i];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static void StableSort<T>(this IList<T> list, Comparison<T> comparer)
|
||||
{
|
||||
var tmpList = new List<(T, int)>(list.Count);
|
||||
tmpList.AddRange(list.WithIndex());
|
||||
tmpList.Sort((a, b) =>
|
||||
{
|
||||
var ret = comparer(a.Item1, b.Item1);
|
||||
return ret != 0 ? ret : a.Item2.CompareTo(b.Item2);
|
||||
});
|
||||
for (var i = 0; i < tmpList.Count; ++i)
|
||||
list[i] = tmpList[i].Item1;
|
||||
}
|
||||
}
|
||||
34
Dalamud.Interface/Table/Column.cs
Normal file
34
Dalamud.Interface/Table/Column.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Table;
|
||||
|
||||
public class Column<TItem>
|
||||
{
|
||||
public string Label = string.Empty;
|
||||
public ImGuiTableColumnFlags Flags = ImGuiTableColumnFlags.NoResize;
|
||||
|
||||
public virtual float Width
|
||||
=> -1f;
|
||||
|
||||
public string FilterLabel
|
||||
=> $"##{this.Label}Filter";
|
||||
|
||||
public virtual bool DrawFilter()
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted(this.Label);
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool FilterFunc(TItem item)
|
||||
=> true;
|
||||
|
||||
public virtual int Compare(TItem lhs, TItem rhs)
|
||||
=> 0;
|
||||
|
||||
public virtual void DrawColumn(TItem item, int idx)
|
||||
{ }
|
||||
|
||||
public int CompareInv(TItem lhs, TItem rhs)
|
||||
=> this.Compare(rhs, lhs);
|
||||
}
|
||||
65
Dalamud.Interface/Table/ColumnFlags.cs
Normal file
65
Dalamud.Interface/Table/ColumnFlags.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
using ImGuiNET;
|
||||
using ImRaii = Dalamud.Interface.Raii.ImRaii;
|
||||
|
||||
namespace Dalamud.Interface.Table;
|
||||
|
||||
public class ColumnFlags<T, TItem> : Column<TItem> where T : struct, Enum
|
||||
{
|
||||
public T AllFlags = default;
|
||||
|
||||
protected virtual IReadOnlyList<T> Values
|
||||
=> Enum.GetValues<T>();
|
||||
|
||||
protected virtual string[] Names
|
||||
=> Enum.GetNames<T>();
|
||||
|
||||
public virtual T FilterValue
|
||||
=> default;
|
||||
|
||||
protected virtual void SetValue(T value, bool enable)
|
||||
{ }
|
||||
|
||||
public override bool DrawFilter()
|
||||
{
|
||||
using var id = ImRaii.PushId(this.FilterLabel);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 0);
|
||||
ImGui.SetNextItemWidth(-Table.ArrowWidth * InterfaceHelpers.GlobalScale);
|
||||
var all = this.FilterValue.HasFlag(this.AllFlags);
|
||||
using var color = ImRaii.PushColor(ImGuiCol.FrameBg, 0x803030A0, !all);
|
||||
using var combo = ImRaii.Combo(string.Empty, this.Label, ImGuiComboFlags.NoArrowButton);
|
||||
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||
{
|
||||
this.SetValue(this.AllFlags, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!all && ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip("Right-click to clear filters.");
|
||||
|
||||
if (!combo)
|
||||
return false;
|
||||
|
||||
color.Pop();
|
||||
|
||||
var ret = false;
|
||||
if (ImGui.Checkbox("Enable All", ref all))
|
||||
{
|
||||
this.SetValue(this.AllFlags, all);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
using var indent = ImRaii.PushIndent(10f);
|
||||
for (var i = 0; i < this.Names.Length; ++i)
|
||||
{
|
||||
var tmp = this.FilterValue.HasFlag(this.Values[i]);
|
||||
if (!ImGui.Checkbox(this.Names[i], ref tmp))
|
||||
continue;
|
||||
|
||||
this.SetValue(this.Values[i], tmp);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
47
Dalamud.Interface/Table/ColumnSelect.cs
Normal file
47
Dalamud.Interface/Table/ColumnSelect.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using ImGuiNET;
|
||||
using ImRaii = Dalamud.Interface.Raii.ImRaii;
|
||||
|
||||
namespace Dalamud.Interface.Table;
|
||||
|
||||
public class ColumnSelect<T, TItem> : Column<TItem> where T : struct, Enum, IEquatable<T>
|
||||
{
|
||||
public ColumnSelect(T initialValue)
|
||||
=> this.FilterValue = initialValue;
|
||||
|
||||
protected virtual IReadOnlyList<T> Values
|
||||
=> Enum.GetValues<T>();
|
||||
|
||||
protected virtual string[] Names
|
||||
=> Enum.GetNames<T>();
|
||||
|
||||
protected virtual void SetValue(T value)
|
||||
=> this.FilterValue = value;
|
||||
|
||||
public T FilterValue;
|
||||
protected int Idx = -1;
|
||||
|
||||
public override bool DrawFilter()
|
||||
{
|
||||
using var id = ImRaii.PushId(this.FilterLabel);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 0);
|
||||
ImGui.SetNextItemWidth(-Table.ArrowWidth * InterfaceHelpers.GlobalScale);
|
||||
using var combo = ImRaii.Combo(string.Empty, this.Idx < 0 ? this.Label : this.Names[this.Idx]);
|
||||
if(!combo)
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
for (var i = 0; i < this.Names.Length; ++i)
|
||||
{
|
||||
if (this.FilterValue.Equals(this.Values[i]))
|
||||
this.Idx = i;
|
||||
if (!ImGui.Selectable(this.Names[i], this.Idx == i) || this.Idx == i)
|
||||
continue;
|
||||
|
||||
this.Idx = i;
|
||||
this.SetValue(this.Values[i]);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
56
Dalamud.Interface/Table/ColumnString.cs
Normal file
56
Dalamud.Interface/Table/ColumnString.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Interface.Raii;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Table;
|
||||
|
||||
public class ColumnString<TItem> : Column<TItem>
|
||||
{
|
||||
public ColumnString()
|
||||
=> this.Flags &= ~ImGuiTableColumnFlags.NoResize;
|
||||
|
||||
public string FilterValue = string.Empty;
|
||||
protected Regex? FilterRegex;
|
||||
|
||||
public virtual string ToName(TItem item)
|
||||
=> item!.ToString() ?? string.Empty;
|
||||
|
||||
public override int Compare(TItem lhs, TItem rhs)
|
||||
=> string.Compare(this.ToName(lhs), this.ToName(rhs), StringComparison.InvariantCulture);
|
||||
|
||||
public override bool DrawFilter()
|
||||
{
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 0);
|
||||
|
||||
ImGui.SetNextItemWidth(-Table.ArrowWidth * InterfaceHelpers.GlobalScale);
|
||||
var tmp = this.FilterValue;
|
||||
if (!ImGui.InputTextWithHint(this.FilterLabel, this.Label, ref tmp, 256) || tmp == this.FilterValue)
|
||||
return false;
|
||||
|
||||
this.FilterValue = tmp;
|
||||
try
|
||||
{
|
||||
this.FilterRegex = new Regex(this.FilterValue, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.FilterRegex = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool FilterFunc(TItem item)
|
||||
{
|
||||
var name = this.ToName(item);
|
||||
if (this.FilterValue.Length == 0)
|
||||
return true;
|
||||
|
||||
return this.FilterRegex?.IsMatch(name) ?? name.Contains(this.FilterValue, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public override void DrawColumn(TItem item, int _)
|
||||
{
|
||||
ImGui.TextUnformatted(this.ToName(item));
|
||||
}
|
||||
}
|
||||
172
Dalamud.Interface/Table/Table.cs
Normal file
172
Dalamud.Interface/Table/Table.cs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
using System.Numerics;
|
||||
using ImGuiNET;
|
||||
using ImRaii = Dalamud.Interface.Raii.ImRaii;
|
||||
|
||||
namespace Dalamud.Interface.Table;
|
||||
|
||||
public static class Table
|
||||
{
|
||||
public const float ArrowWidth = 10;
|
||||
}
|
||||
|
||||
public class Table<T>
|
||||
{
|
||||
protected bool FilterDirty = true;
|
||||
protected bool SortDirty = true;
|
||||
protected readonly ICollection<T> Items;
|
||||
internal readonly List<(T, int)> FilteredItems;
|
||||
|
||||
protected readonly string Label;
|
||||
protected readonly Column<T>[] Headers;
|
||||
|
||||
protected float ItemHeight { get; set; }
|
||||
public float ExtraHeight { get; set; } = 0;
|
||||
|
||||
private int _currentIdx = 0;
|
||||
|
||||
protected bool Sortable
|
||||
{
|
||||
get => this.Flags.HasFlag(ImGuiTableFlags.Sortable);
|
||||
set => this.Flags = value ? this.Flags | ImGuiTableFlags.Sortable : this.Flags & ~ImGuiTableFlags.Sortable;
|
||||
}
|
||||
|
||||
protected int SortIdx = -1;
|
||||
|
||||
public ImGuiTableFlags Flags = ImGuiTableFlags.RowBg
|
||||
| ImGuiTableFlags.Sortable
|
||||
| ImGuiTableFlags.BordersOuter
|
||||
| ImGuiTableFlags.ScrollY
|
||||
| ImGuiTableFlags.ScrollX
|
||||
| ImGuiTableFlags.PreciseWidths
|
||||
| ImGuiTableFlags.BordersInnerV
|
||||
| ImGuiTableFlags.NoBordersInBodyUntilResize;
|
||||
|
||||
public int TotalItems
|
||||
=> this.Items.Count;
|
||||
|
||||
public int CurrentItems
|
||||
=> this.FilteredItems.Count;
|
||||
|
||||
public int TotalColumns
|
||||
=> this.Headers.Length;
|
||||
|
||||
public int VisibleColumns { get; private set; }
|
||||
|
||||
public Table(string label, ICollection<T> items, params Column<T>[] headers)
|
||||
{
|
||||
this.Label = label;
|
||||
this.Items = items;
|
||||
this.Headers = headers;
|
||||
this.FilteredItems = new List<(T, int)>(this.Items.Count);
|
||||
this.VisibleColumns = this.Headers.Length;
|
||||
}
|
||||
|
||||
public void Draw(float itemHeight)
|
||||
{
|
||||
this.ItemHeight = itemHeight;
|
||||
using var id = ImRaii.PushId(this.Label);
|
||||
this.UpdateFilter();
|
||||
this.DrawTableInternal();
|
||||
}
|
||||
|
||||
protected virtual void DrawFilters()
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
protected virtual void PreDraw()
|
||||
{ }
|
||||
|
||||
private void SortInternal()
|
||||
{
|
||||
if (!this.Sortable)
|
||||
return;
|
||||
|
||||
var sortSpecs = ImGui.TableGetSortSpecs();
|
||||
this.SortDirty |= sortSpecs.SpecsDirty;
|
||||
|
||||
if (!this.SortDirty)
|
||||
return;
|
||||
|
||||
this.SortIdx = sortSpecs.Specs.ColumnIndex;
|
||||
|
||||
if (this.Headers.Length <= this.SortIdx)
|
||||
this.SortIdx = 0;
|
||||
|
||||
if (sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
|
||||
this.FilteredItems.StableSort((a, b) => this.Headers[this.SortIdx].Compare(a.Item1, b.Item1));
|
||||
else if (sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
|
||||
this.FilteredItems.StableSort((a, b) => this.Headers[this.SortIdx].CompareInv(a.Item1, b.Item1));
|
||||
else
|
||||
this.SortIdx = -1;
|
||||
this.SortDirty = false;
|
||||
sortSpecs.SpecsDirty = false;
|
||||
}
|
||||
|
||||
private void UpdateFilter()
|
||||
{
|
||||
if (!this.FilterDirty)
|
||||
return;
|
||||
|
||||
this.FilteredItems.Clear();
|
||||
var idx = 0;
|
||||
foreach (var item in this.Items)
|
||||
{
|
||||
if (this.Headers.All(header => header.FilterFunc(item)))
|
||||
this.FilteredItems.Add((item, idx));
|
||||
idx++;
|
||||
}
|
||||
|
||||
this.FilterDirty = false;
|
||||
this.SortDirty = true;
|
||||
}
|
||||
|
||||
private void DrawItem((T, int) pair)
|
||||
{
|
||||
var column = 0;
|
||||
using var id = ImRaii.PushId(this._currentIdx);
|
||||
this._currentIdx = pair.Item2;
|
||||
foreach (var header in this.Headers)
|
||||
{
|
||||
id.Push(column++);
|
||||
if (ImGui.TableNextColumn())
|
||||
header.DrawColumn(pair.Item1, pair.Item2);
|
||||
id.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTableInternal()
|
||||
{
|
||||
using var table = ImRaii.Table("Table", this.Headers.Length, this.Flags,
|
||||
ImGui.GetContentRegionAvail() - this.ExtraHeight * Vector2.UnitY * InterfaceHelpers.GlobalScale);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
this.PreDraw();
|
||||
ImGui.TableSetupScrollFreeze(1, 1);
|
||||
|
||||
foreach (var header in this.Headers)
|
||||
ImGui.TableSetupColumn(header.Label, header.Flags, header.Width);
|
||||
|
||||
ImGui.TableNextRow(ImGuiTableRowFlags.Headers);
|
||||
var i = 0;
|
||||
this.VisibleColumns = 0;
|
||||
foreach (var header in this.Headers)
|
||||
{
|
||||
using var id = ImRaii.PushId(i);
|
||||
if (ImGui.TableGetColumnFlags(i).HasFlag(ImGuiTableColumnFlags.IsEnabled))
|
||||
++this.VisibleColumns;
|
||||
if (!ImGui.TableSetColumnIndex(i++))
|
||||
continue;
|
||||
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||
ImGui.TableHeader(string.Empty);
|
||||
ImGui.SameLine();
|
||||
style.Pop();
|
||||
if (header.DrawFilter())
|
||||
this.FilterDirty = true;
|
||||
}
|
||||
|
||||
this.SortInternal();
|
||||
this._currentIdx = 0;
|
||||
ImGuiClip.ClippedDraw(this.FilteredItems, this.DrawItem, this.ItemHeight);
|
||||
}
|
||||
}
|
||||
14
Dalamud.sln
14
Dalamud.sln
|
|
@ -38,6 +38,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs.InteropS
|
|||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DalamudCrashHandler", "DalamudCrashHandler\DalamudCrashHandler.vcxproj", "{317A264C-920B-44A1-8A34-F3A6827B0705}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.Interface", "Dalamud.Interface\Dalamud.Interface.csproj", "{757C997D-AA58-4241-8299-243C56514917}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -202,6 +204,18 @@ Global
|
|||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.Build.0 = Release|x64
|
||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.ActiveCfg = Release|x64
|
||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.Build.0 = Release|x64
|
||||
{757C997D-AA58-4241-8299-243C56514917}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{757C997D-AA58-4241-8299-243C56514917}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{757C997D-AA58-4241-8299-243C56514917}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{757C997D-AA58-4241-8299-243C56514917}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{757C997D-AA58-4241-8299-243C56514917}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{757C997D-AA58-4241-8299-243C56514917}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{757C997D-AA58-4241-8299-243C56514917}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{757C997D-AA58-4241-8299-243C56514917}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{757C997D-AA58-4241-8299-243C56514917}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{757C997D-AA58-4241-8299-243C56514917}.Release|x64.Build.0 = Release|Any CPU
|
||||
{757C997D-AA58-4241-8299-243C56514917}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{757C997D-AA58-4241-8299-243C56514917}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@
|
|||
<PackageReference Include="System.Resources.Extensions" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Dalamud.Interface\Dalamud.Interface.csproj" />
|
||||
<ProjectReference Include="..\lib\FFXIVClientStructs\FFXIVClientStructs\FFXIVClientStructs.csproj" />
|
||||
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj" />
|
||||
<ProjectReference Include="..\lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj" />
|
||||
|
|
|
|||
|
|
@ -13,4 +13,5 @@ using System.Diagnostics.CodeAnalysis;
|
|||
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1512:Single-line comments should not be followed by blank line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1515:Single-line comment should be preceded by blank line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1127:Generic type constraints should be on their own line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1028:Code should not contain trailing whitespace", Justification = "I don't care anymore", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "We don't do those yet")]
|
||||
|
|
|
|||
|
|
@ -298,6 +298,7 @@ public static class ImGuiHelpers
|
|||
internal static void NewFrame()
|
||||
{
|
||||
GlobalScale = ImGui.GetIO().FontGlobalScale;
|
||||
InterfaceHelpers.GlobalScale = GlobalScale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@
|
|||
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Dalamud.Interface">
|
||||
<HintPath>$(DalamudLibPath)Dalamud.Interface.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImGui.NET">
|
||||
<HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue