forked from Ottermandias/OtterGui
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathImGuiClip.cs
196 lines (166 loc) · 7.88 KB
/
ImGuiClip.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
using ImGuiNET;
using OtterGui.Raii;
using OtterGui.Text;
namespace OtterGui;
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;
}
// Get the number of skipped items of a given height necessary for the current scroll bar,
// but subtracting the current cursor position,
// and apply the dummy of the appropriate height, removing one item spacing.
// The height has to contain the spacing.
public static int GetNecessarySkipsAtPos(float height, float cursorPosY)
{
var curY = ImGui.GetScrollY() - cursorPosY;
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)
{
using var clipper = ImUtf8.ListClipper(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]);
}
}
}
// 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)
{
using var clipper = ImUtf8.ListClipper(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);
}
}
}
// 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())
{
using var id = ImRaii.PushId(idx);
draw(it.Current);
}
// Just checking IsItemVisible caused some issues when not the entire width of the window was visible.
if (!ImGui.IsRectVisible(ImGui.GetItemRectMin(), ImGui.GetItemRectMin() with { Y = ImGui.GetItemRectMax().Y }))
{
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 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 ClippedTableDraw<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 (ImRaii.PushId(idx))
{
draw(it.Current);
}
// Just checking IsItemVisible caused some issues when not the entire width of the window was visible.
if (!ImGui.IsRectVisible(ImGui.GetItemRectMin(), ImGui.GetItemRectMin() with { Y = ImGui.GetItemRectMax().Y }))
{
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);
}