diff --git a/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/ColumnList.cs b/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/ColumnList.cs index 6393108c..73d8b5be 100644 --- a/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/ColumnList.cs +++ b/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/ColumnList.cs @@ -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; @@ -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) @@ -138,7 +205,6 @@ private void UpdateColumnSizes() { // Size the star columns. var starWidthWasConstrained = false; - var used = 0.0; availableSpace = Math.Max(0, availableSpace); @@ -150,7 +216,6 @@ private void UpdateColumnSizes() if (column.Width.IsStar) { column.CalculateStarWidth(availableSpace, totalStars); - used += NotNaN(column.ActualWidth); starWidthWasConstrained |= column.StarWidthWasConstrained; } } diff --git a/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/IColumns.cs b/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/IColumns.cs index b54d2bd0..e3963abd 100644 --- a/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/IColumns.cs +++ b/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/IColumns.cs @@ -39,6 +39,10 @@ public interface IColumns : IReadOnlyList, INotifyCollectionChanged /// (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(); + /// /// Gets the estimated total width of all columns. /// diff --git a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridColumnarPresenterBase.cs b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridColumnarPresenterBase.cs index 6e1129e5..c8d11c00 100644 --- a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridColumnarPresenterBase.cs +++ b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridColumnarPresenterBase.cs @@ -15,6 +15,8 @@ namespace Avalonia.Controls.Primitives /// public abstract class TreeDataGridColumnarPresenterBase : TreeDataGridPresenterBase { + private double _lastEstimatedElementSizeU = 25; + protected IColumns? Columns => Items as IColumns; protected sealed override Size GetInitialConstraint(Control element, int index, Size availableSize) @@ -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 elements) { var columns = Columns!; diff --git a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs index b95dd152..16455c2d 100644 --- a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs +++ b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs @@ -89,6 +89,13 @@ public IReadOnlyList? Items protected abstract Orientation Orientation { get; } protected Rect Viewport { get; private set; } = s_invalidViewport; + + /// + /// Gets the position of the first realized element on the primary axis. + /// + protected double StartU => _realizedElements?.StartU ?? 0; + + protected int FirstIndex => _realizedElements?.FirstIndex ?? 0; public Control? BringIntoView(int index, Rect? rect = null) { @@ -641,7 +648,7 @@ private Control GetRecycledOrCreateElement(IReadOnlyList items, int index return e; } - private double EstimateElementSizeU() + protected virtual double EstimateElementSizeU() { if (_realizedElements is null) return _lastEstimatedElementSizeU;