@@ -56,7 +56,7 @@ public class VirtualizingStackPanel : VirtualizingPanel, IScrollSnapPointsInfo
5656 /// Defines the <see cref="CacheLength"/> property.
5757 /// </summary>
5858 public static readonly StyledProperty < double > CacheLengthProperty =
59- AvaloniaProperty . Register < VirtualizingStackPanel , double > ( nameof ( CacheLength ) , 0.0 ,
59+ AvaloniaProperty . Register < VirtualizingStackPanel , double > ( nameof ( CacheLength ) , 0.0 ,
6060 validate : v => v is >= 0 and <= 2 ) ;
6161
6262 private static readonly AttachedProperty < object ? > RecycleKeyProperty =
@@ -80,8 +80,8 @@ public class VirtualizingStackPanel : VirtualizingPanel, IScrollSnapPointsInfo
8080 private int _focusedIndex = - 1 ;
8181 private Control ? _realizingElement ;
8282 private int _realizingIndex = - 1 ;
83- private double _bufferFactor ;
84-
83+ private double _bufferFactor ;
84+
8585 private bool _hasReachedStart = false ;
8686 private bool _hasReachedEnd = false ;
8787 private Rect _lastMeasuredExtendedViewport ;
@@ -270,7 +270,7 @@ protected override Size ArrangeOverride(Size finalSize)
270270 new Rect ( 0 , u , finalSize . Width , sizeU ) ;
271271
272272 e . Arrange ( rect ) ;
273-
273+
274274 if ( e . IsVisible && _viewport . Intersects ( rect ) )
275275 {
276276 try
@@ -285,7 +285,7 @@ protected override Size ArrangeOverride(Size finalSize)
285285 e , ex . Message ) ;
286286 }
287287 }
288-
288+
289289 u += orientation == Orientation . Horizontal ? rect . Width : rect . Height ;
290290 }
291291 }
@@ -543,7 +543,7 @@ protected override void OnItemsControlChanged(ItemsControl? oldValue)
543543 var count = Items . Count ;
544544 var fromControl = from as Control ;
545545
546- if ( count == 0 ||
546+ if ( count == 0 ||
547547 ( fromControl is null && direction is not NavigationDirection . First and not NavigationDirection . Last ) )
548548 return null ;
549549
@@ -644,7 +644,7 @@ protected internal override int IndexFromContainer(Control container)
644644 element . BringIntoView ( ) ;
645645 return element ;
646646 }
647- else if ( this . GetLayoutRoot ( ) is { } root )
647+ else if ( this . GetLayoutRoot ( ) is { } root )
648648 {
649649 // Create and measure the element to be brought into view. Store it in a field so that
650650 // it can be re-used in the layout pass.
@@ -746,7 +746,7 @@ private MeasureViewport CalculateMeasureViewport(Orientation orientation, IReadO
746746 }
747747
748748 // Check if the anchor element is not within the currently realized elements.
749- var disjunct = anchorIndex < _realizedElements . FirstIndex ||
749+ var disjunct = anchorIndex < _realizedElements . FirstIndex ||
750750 anchorIndex > _realizedElements . LastIndex ;
751751
752752 return new MeasureViewport
@@ -780,12 +780,12 @@ private Size EstimateDesiredSize(Orientation orientation, int itemCount)
780780 // We have an element to scroll to, so we can estimate the desired size based on the
781781 // element's position and the remaining elements.
782782 var remaining = itemCount - _scrollToIndex - 1 ;
783- var u = orientation == Orientation . Horizontal ?
783+ var u = orientation == Orientation . Horizontal ?
784784 _scrollToElement . Bounds . Right :
785785 _scrollToElement . Bounds . Bottom ;
786786 var sizeU = u + ( remaining * _lastEstimatedElementSizeU ) ;
787- return orientation == Orientation . Horizontal ?
788- new ( sizeU , DesiredSize . Height ) :
787+ return orientation == Orientation . Horizontal ?
788+ new ( sizeU , DesiredSize . Height ) :
789789 new ( DesiredSize . Width , sizeU ) ;
790790 }
791791
@@ -901,23 +901,26 @@ private double GetOrEstimateElementU(int index)
901901 {
902902 var first = realized . FirstIndex ;
903903 var last = realized . LastIndex ;
904-
904+
905905 if ( index < first )
906906 {
907- return realized . StartU - ( ( first - index ) * estimatedSize ) ;
907+ // Interpolate between the known panel origin and the first realized item.
908+ // Using the realized items' average size to extrapolate backwards can place
909+ // the target before the panel origin.
910+ return realized . StartU * index / first ;
908911 }
909-
912+
910913 if ( index > last )
911914 {
912915 var sizes = realized . SizeU ;
913916 var realizedSpan = 0.0 ;
914-
917+
915918 for ( var i = 0 ; i < sizes . Count ; ++ i )
916919 {
917920 var sizeU = sizes [ i ] ;
918921 realizedSpan += double . IsNaN ( sizeU ) ? estimatedSize : sizeU ;
919922 }
920-
923+
921924 return realized . StartU + realizedSpan + ( ( index - last - 1 ) * estimatedSize ) ;
922925 }
923926 }
@@ -940,7 +943,7 @@ private void RealizeElements(
940943 var viewportEnd = horizontal ? _viewport . Right : _viewport . Bottom ;
941944 var anchorAtEnd = ! _hasReachedEnd && index == items . Count - 1 &&
942945 MathUtilities . GreaterThanOrClose ( viewportEnd , horizontal ? Bounds . Width : Bounds . Height ) ;
943-
946+
944947 // Reset boundary flags
945948 _hasReachedStart = false ;
946949 _hasReachedEnd = false ;
@@ -956,9 +959,9 @@ private void RealizeElements(
956959 _realizingIndex = index ;
957960 var e = GetOrCreateElement ( items , index ) ;
958961 _realizingElement = e ;
959-
962+
960963 e . Measure ( availableSize ) ;
961-
964+
962965 var sizeU = horizontal ? e . DesiredSize . Width : e . DesiredSize . Height ;
963966 var sizeV = horizontal ? e . DesiredSize . Height : e . DesiredSize . Width ;
964967
@@ -976,10 +979,10 @@ private void RealizeElements(
976979 _realizingIndex = - 1 ;
977980 _realizingElement = null ;
978981 } while ( u < viewport . viewportUEnd && index < items . Count ) ;
979-
982+
980983 // Check if we reached the end of the collection
981984 _hasReachedEnd = index >= items . Count ;
982-
985+
983986 // Store the last index and end U position for the desired size calculation.
984987 viewport . lastIndex = index - 1 ;
985988 viewport . realizedEndU = u ;
@@ -994,7 +997,7 @@ private void RealizeElements(
994997 while ( u > viewport . viewportUStart && index >= 0 )
995998 {
996999 var e = GetOrCreateElement ( items , index ) ;
997-
1000+
9981001 e . Measure ( availableSize ) ;
9991002 var sizeU = horizontal ? e . DesiredSize . Width : e . DesiredSize . Height ;
10001003 var sizeV = horizontal ? e . DesiredSize . Height : e . DesiredSize . Width ;
@@ -1004,7 +1007,7 @@ private void RealizeElements(
10041007 viewport . measuredV = Math . Max ( viewport . measuredV , sizeV ) ;
10051008 -- index ;
10061009 }
1007-
1010+
10081011 // Check if we reached the start of the collection
10091012 _hasReachedStart = index < 0 ;
10101013
@@ -1039,7 +1042,7 @@ private Control GetOrCreateElement(IReadOnlyList<object?> items, int index)
10391042 {
10401043 return _realizedElements ? . GetElement ( index ) ;
10411044 }
1042-
1045+
10431046 private static Control ? GetRealizedElement (
10441047 int index ,
10451048 ref int specialIndex ,
@@ -1118,7 +1121,7 @@ private void RecycleElement(Control element, int index)
11181121 {
11191122 Debug . Assert ( ItemsControl is not null ) ;
11201123 Debug . Assert ( ItemContainerGenerator is not null ) ;
1121-
1124+
11221125 _scrollAnchorProvider ? . UnregisterAnchorCandidate ( element ) ;
11231126
11241127 var recycleKey = element . GetValue ( RecycleKeyProperty ) ;
@@ -1153,7 +1156,7 @@ private void RecycleElementOnItemRemoved(Control element)
11531156 _scrollAnchorProvider ? . UnregisterAnchorCandidate ( element ) ;
11541157
11551158 var recycleKey = element . GetValue ( RecycleKeyProperty ) ;
1156-
1159+
11571160 if ( recycleKey is null )
11581161 {
11591162 ItemContainerGenerator ! . ClearItemContainer ( element ) ;
@@ -1181,7 +1184,7 @@ private void RecycleFocusedElement()
11811184 _focusedElement = null ;
11821185 _focusedIndex = - 1 ;
11831186 }
1184-
1187+
11851188 private void RecycleScrollToElement ( )
11861189 {
11871190 if ( _scrollToElement != null )
@@ -1191,7 +1194,7 @@ private void RecycleScrollToElement()
11911194 _scrollToElement = null ;
11921195 _scrollToIndex = - 1 ;
11931196 }
1194-
1197+
11951198 private void PushToRecyclePool ( object recycleKey , Control element )
11961199 {
11971200 _recyclePool ??= new ( ) ;
@@ -1211,7 +1214,7 @@ private void UpdateElementIndex(Control element, int oldIndex, int newIndex)
12111214
12121215 ItemContainerGenerator . ItemContainerIndexChanged ( element , oldIndex , newIndex ) ;
12131216 }
1214-
1217+
12151218 private Rect CalculateExtendedViewport ( bool vertical , double viewportSize , double bufferSize )
12161219 {
12171220
@@ -1378,7 +1381,7 @@ private void OnEffectiveViewportChanged(object? sender, EffectiveViewportChanged
13781381 private void OnItemsControlPropertyChanged ( object ? sender , AvaloniaPropertyChangedEventArgs e )
13791382 {
13801383 if ( _focusedElement is not null &&
1381- e . Property == KeyboardNavigation . TabOnceActiveElementProperty &&
1384+ e . Property == KeyboardNavigation . TabOnceActiveElementProperty &&
13821385 e . GetOldValue < IInputElement ? > ( ) == _focusedElement )
13831386 {
13841387 // TabOnceActiveElement has moved away from _focusedElement so we can recycle it.
@@ -1392,15 +1395,15 @@ private void OnCacheLengthChanged(AvaloniaPropertyChangedEventArgs e)
13921395 {
13931396 var newValue = e . GetNewValue < double > ( ) ;
13941397 _bufferFactor = newValue ;
1395-
1398+
13961399 // Force a recalculation of the extended viewport on the next layout pass
13971400 InvalidateMeasure ( ) ;
13981401 }
1399-
1402+
14001403 /// <inheritdoc/>
14011404 public IReadOnlyList < double > GetIrregularSnapPoints ( Orientation orientation , SnapPointsAlignment snapPointsAlignment )
14021405 {
1403- if ( _realizedElements == null )
1406+ if ( _realizedElements == null )
14041407 return new List < double > ( ) ;
14051408
14061409 return new VirtualizingSnapPointsList ( _realizedElements , ItemsControl ? . ItemsSource ? . Count ( ) ?? 0 , orientation , Orientation , snapPointsAlignment , EstimateElementSizeU ( ) ) ;
0 commit comments