/
RealizedElementList.cs
288 lines (254 loc) · 10.5 KB
/
RealizedElementList.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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
using System;
using System.Collections.Generic;
using Avalonia.Controls.Models.TreeDataGrid;
namespace Avalonia.Controls.Primitives
{
/// <summary>
/// Stores the realized element state for a <see cref="TreeDataGridPresenterBase{TItem}"/>.
/// </summary>
internal struct RealizedElementList
{
private int _firstIndex;
private List<IControl?>? _elements;
private List<double>? _sizes;
private double _startU;
/// <summary>
/// Gets the number of realized elements.
/// </summary>
public int Count => _elements?.Count ?? 0;
/// <summary>
/// Gets the model index of the first realized element, or -1 if no elements are realized.
/// </summary>
public int FirstModelIndex => _elements?.Count > 0 ? _firstIndex : -1;
/// <summary>
/// Gets the model index of the last realized element, or -1 if no elements are realized.
/// </summary>
public int LastModelIndex => _elements?.Count > 0 ? _firstIndex + _elements.Count - 1 : -1;
/// <summary>
/// Gets the elements.
/// </summary>
public IReadOnlyList<IControl?> Elements => _elements ??= new List<IControl?>();
/// <summary>
/// Gets the sizes of the elements on the primary axis.
/// </summary>
public IReadOnlyList<double> SizeU => _sizes ??= new List<double>();
/// <summary>
/// Gets the position of the first element on the primary axis.
/// </summary>
public double StartU => _startU;
/// <summary>
/// Adds a newly realized element to the collection.
/// </summary>
/// <param name="modelIndex">The model index of the element.</param>
/// <param name="element">The element.</param>
/// <param name="u">The position of the elemnt on the primary axis.</param>
/// <param name="sizeU">The size of the element on the primary axis.</param>
public void Add(int modelIndex, IControl element, double u, double sizeU)
{
if (modelIndex < 0)
throw new ArgumentOutOfRangeException(nameof(modelIndex));
_elements ??= new List<IControl?>();
_sizes ??= new List<double>();
if (Count == 0)
{
_elements.Add(element);
_sizes.Add(sizeU);
_startU = u;
_firstIndex = modelIndex;
}
else if (modelIndex == LastModelIndex + 1)
{
_elements.Add(element);
_sizes.Add(sizeU);
}
else if (modelIndex == FirstModelIndex - 1)
{
--_firstIndex;
_elements.Insert(0, element);
_sizes.Insert(0, sizeU);
_startU = u;
}
else
{
throw new NotSupportedException("Can only add items to the beginning or end of realized elements.");
}
}
/// <summary>
/// Gets the element at the specified model index, if realized.
/// </summary>
/// <param name="modelIndex">The index in the source collection of the element to get.</param>
/// <returns>The element if realized; otherwise null.</returns>
public IControl? GetElement(int modelIndex)
{
var index = modelIndex - FirstModelIndex;
if (index >= 0 && index < _elements?.Count)
return _elements[index];
return null;
}
/// <summary>
/// Updates the elements in response to items being inserted into the source collection.
/// </summary>
/// <param name="modelIndex">The index in the source collection of the insert.</param>
/// <param name="count">The number of items inserted.</param>
/// <param name="updateElementIndex">A method used to update the element indexes.</param>
public void ItemsInserted(int modelIndex, int count, Action<IControl, int> updateElementIndex)
{
if (modelIndex < 0)
throw new ArgumentOutOfRangeException(nameof(modelIndex));
if (_elements is null || _elements.Count == 0)
return;
// Get the index within the realized _elements collection.
var first = FirstModelIndex;
var index = modelIndex - first;
if (index < Count)
{
// The insertion point affects the realized elements. Update the index of the
// elements after the insertion point.
var elementCount = _elements.Count;
var start = Math.Max(index, 0);
for (var i = start; i < elementCount; ++i)
{
if (_elements[i] is IControl element)
updateElementIndex(element, first + i + count);
}
if (index <= 0)
{
// The insertion point was before the first element, update the first index.
_firstIndex += count;
}
else
{
// The insertion point was within the realized elements, insert an empty space
// in _elements and _sizes.
_elements!.InsertMany(index, null, count);
_sizes!.InsertMany(index, 0.0, count);
}
}
}
/// <summary>
/// Updates the elements in response to items being removed from the source collection.
/// </summary>
/// <param name="modelIndex">The index in the source collection of the remove.</param>
/// <param name="count">The number of items removed.</param>
/// <param name="updateElementIndex">A method used to update the element indexes.</param>
/// <param name="recycleElement">A method used to recycle elements.</param>
public void ItemsRemoved(
int modelIndex,
int count,
Action<IControl, int> updateElementIndex,
Action<IControl> recycleElement)
{
if (modelIndex < 0)
throw new ArgumentOutOfRangeException(nameof(modelIndex));
if (_elements is null || _elements.Count == 0)
return;
// Get the removal start and end index within the realized _elements collection.
var first = FirstModelIndex;
var startIndex = modelIndex - first;
var endIndex = (modelIndex + count) - first;
if (endIndex < 0)
{
// The removed range was before the realized elements. Update the first index.
_firstIndex -= count;
}
else
{
// Recycle and remove the affected elements.
var start = Math.Max(startIndex, 0);
var end = Math.Min(endIndex, _elements.Count);
for (var i = start; i < end; ++i)
{
if (_elements[i] is IControl element)
recycleElement(element);
}
_elements.RemoveRange(start, end - start);
_sizes!.RemoveRange(start, end - start);
// Update the indexes of the elements after the removed range.
end = _elements.Count;
for (var i = start; i < end; ++i)
{
if (_elements[i] is IControl element)
updateElementIndex(element, first + i);
}
}
}
/// <summary>
/// Recycles elements before a specific index.
/// </summary>
/// <param name="modelIndex">The index in the source collection of new first element.</param>
/// <param name="recycleElement">A method used to recycle elements.</param>
public void RecycleElementsBefore(int modelIndex, Action<IControl> recycleElement)
{
if (modelIndex <= FirstModelIndex || _elements is null || _elements.Count == 0)
return;
if (modelIndex > LastModelIndex)
{
RecycleAllElements(recycleElement);
}
else
{
var endIndex = modelIndex - FirstModelIndex;
for (var i = 0; i < endIndex; ++i)
{
if (_elements[i] is IControl e)
recycleElement(e);
}
_elements.RemoveRange(0, endIndex);
_sizes!.RemoveRange(0, endIndex);
_firstIndex = modelIndex;
}
}
/// <summary>
/// Recycles elements after a specific index.
/// </summary>
/// <param name="modelIndex">The index in the source collection of new last element.</param>
/// <param name="recycleElement">A method used to recycle elements.</param>
public void RecycleElementsAfter(int modelIndex, Action<IControl> recycleElement)
{
if (modelIndex >= LastModelIndex || _elements is null || _elements.Count == 0)
return;
if (modelIndex < FirstModelIndex)
{
RecycleAllElements(recycleElement);
}
else
{
var startIndex = (modelIndex + 1) - FirstModelIndex;
var count = _elements.Count;
for (var i = startIndex; i < count; ++i)
{
if (_elements[i] is IControl e)
recycleElement(e);
}
_elements.RemoveRange(startIndex, _elements.Count - startIndex);
_sizes!.RemoveRange(startIndex, _sizes.Count - startIndex);
}
}
/// <summary>
/// Recycles all realized elements.
/// </summary>
/// <param name="recycleElement">A method used to recycle elements.</param>
public void RecycleAllElements(Action<IControl> recycleElement)
{
if (_elements is null || _elements.Count == 0)
return;
foreach (var e in _elements)
{
if (e is object)
recycleElement(e);
}
_startU = _firstIndex = 0;
_elements?.Clear();
_sizes?.Clear();
}
/// <summary>
/// Resets the element list and prepares it for reuse.
/// </summary>
public void ResetForReuse()
{
_startU = _firstIndex = 0;
_elements?.Clear();
_sizes?.Clear();
}
}
}