Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,73 @@ public Size CellMeasured(int columnIndex, int rowIndex, Size size)
return (-1, -1);
}

public (int index, double position) GetOrEstimateColumnAt(double viewportStartU, double viewportEndU, int itemCount, double startU, int firstIndex, ref double estimatedElementSizeU)
{
// We have no elements, nothing to do here.
if (itemCount <= 0)
return (-1, 0);

// If we're at 0 then display the first item.
if (MathUtilities.IsZero(viewportStartU))
return (0, 0);

var u = startU;

for (var i = 0; i < Count; ++i)
{
var size = this[i].ActualWidth;

if (double.IsNaN(size))
break;

var endU = u + size;

if (endU > viewportStartU && u < viewportEndU)
return (firstIndex + i, u);

u = endU;
}

// We don't have any realized elements in the requested viewport, or can't rely on
// StartU being valid. Estimate the index using only the estimated size. First,
// estimate the element size, using defaultElementSizeU if we don't have any realized
// elements.
var estimatedSize = EstimateElementSizeU() switch
{
-1 => estimatedElementSizeU,
var v => v,
};

// Store the estimated size for the next layout pass.
estimatedElementSizeU = estimatedSize;

// Estimate the element at the start of the viewport.
var index = Math.Min((int)(viewportStartU / estimatedSize), itemCount - 1);
return (index, index * estimatedSize);
}

public double EstimateElementSizeU()
{
var total = 0.0;
var divisor = 0.0;

// Average the size of the realized elements.
foreach (var column in this)
{
var size = column.ActualWidth;
if (double.IsNaN(size))
continue;
total += size;
++divisor;
}

// We don't have any elements on which to base our estimate.
if (divisor == 0 || total == 0)
return -1;

return total / divisor;
}

public double GetEstimatedWidth(double constraint)
{
var hasStar = false;
Expand Down Expand Up @@ -102,7 +169,7 @@ public void SetColumnWidth(int columnIndex, GridLength width)

public void ViewportChanged(Rect viewport)
{
if (_viewportWidth != viewport.Width)
if (!MathUtilities.AreClose(_viewportWidth, viewport.Width))
{
_viewportWidth = viewport.Width;
if (_initialized)
Expand Down Expand Up @@ -138,7 +205,6 @@ private void UpdateColumnSizes()
{
// Size the star columns.
var starWidthWasConstrained = false;
var used = 0.0;

availableSpace = Math.Max(0, availableSpace);

Expand All @@ -150,7 +216,6 @@ private void UpdateColumnSizes()
if (column.Width.IsStar)
{
column.CalculateStarWidth(availableSpace, totalStars);
used += NotNaN(column.ActualWidth);
starWidthWasConstrained |= column.StarWidthWasConstrained;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ public interface IColumns : IReadOnlyList<IColumn>, INotifyCollectionChanged
/// </returns>
(int index, double x) GetColumnAt(double x);

public (int index, double position) GetOrEstimateColumnAt(double viewportStartU, double viewportEndU, int itemCount, double startU, int firstIndex, ref double estimatedElementSizeU);

public double EstimateElementSizeU();

/// <summary>
/// Gets the estimated total width of all columns.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ namespace Avalonia.Controls.Primitives
/// </remarks>
public abstract class TreeDataGridColumnarPresenterBase<TItem> : TreeDataGridPresenterBase<TItem>
{
private double _lastEstimatedElementSizeU = 25;

protected IColumns? Columns => Items as IColumns;

protected sealed override Size GetInitialConstraint(Control element, int index, Size availableSize)
Expand All @@ -28,11 +30,27 @@ protected override (int index, double position) GetOrEstimateAnchorElementForVie
double viewportEnd,
int itemCount)
{
if (Columns?.GetColumnAt(viewportStart) is var (index, position) && index >= 0)
if (Columns?.GetColumnAt(viewportStart) is (var index and >= 0, var position))
return (index, position);

if (Columns?.GetOrEstimateColumnAt(viewportStart, viewportEnd, itemCount, StartU, FirstIndex, ref _lastEstimatedElementSizeU) is { index: >= 0 } res)
return res;

return base.GetOrEstimateAnchorElementForViewport(viewportStart, viewportEnd, itemCount);
}

protected override double EstimateElementSizeU()
{
if (Columns is null)
return _lastEstimatedElementSizeU;

var result = Columns.EstimateElementSizeU();
if (result >= 0)
_lastEstimatedElementSizeU = result;

return _lastEstimatedElementSizeU;
}

protected sealed override bool NeedsFinalMeasurePass(int firstIndex, IReadOnlyList<Control?> elements)
{
var columns = Columns!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ public IReadOnlyList<TItem>? Items

protected abstract Orientation Orientation { get; }
protected Rect Viewport { get; private set; } = s_invalidViewport;

/// <summary>
/// Gets the position of the first realized element on the primary axis.
/// </summary>
protected double StartU => _realizedElements?.StartU ?? 0;

protected int FirstIndex => _realizedElements?.FirstIndex ?? 0;

public Control? BringIntoView(int index, Rect? rect = null)
{
Expand Down Expand Up @@ -641,7 +648,7 @@ private Control GetRecycledOrCreateElement(IReadOnlyList<TItem> items, int index
return e;
}

private double EstimateElementSizeU()
protected virtual double EstimateElementSizeU()
{
if (_realizedElements is null)
return _lastEstimatedElementSizeU;
Expand Down