44import java .util .Optional ;
55import java .util .function .Function ;
66
7+ import javafx .beans .property .SimpleBooleanProperty ;
78import javafx .beans .value .ObservableObjectValue ;
89import javafx .geometry .Bounds ;
910import javafx .scene .control .IndexRange ;
1011
12+ import org .reactfx .EventStream ;
1113import org .reactfx .EventStreams ;
1214import org .reactfx .Subscription ;
1315import org .reactfx .collection .LiveList ;
1416import org .reactfx .collection .MemoizationList ;
17+ import org .reactfx .util .Tuple3 ;
1518import org .reactfx .value .Val ;
1619import org .reactfx .value .ValBase ;
1720
@@ -56,9 +59,14 @@ public SizeTracker(
5659 this .viewportBounds = viewportBounds ;
5760 this .cells = lazyCells ;
5861 this .breadths = lazyCells .map (orientation ::minBreadth ).memoize ();
59- this .maxKnownMinBreadth = breadths .memoizedItems ()
60- .reduce (Math ::max )
61- .orElseConst (0.0 );
62+ LiveList <Double > knownBreadths = this .breadths .memoizedItems ();
63+
64+ this .maxKnownMinBreadth = Val .create (
65+ () -> knownBreadths .stream ().mapToDouble ( Double ::doubleValue ).max ().orElse (0.0 ),
66+ knownBreadths .changes ().filter ( c -> knownBreadths .size () > 0 )
67+ // skip zero cell events to prevent scrollbar flicker
68+ );
69+
6270 this .breadthForCells = Val .combine (
6371 maxKnownMinBreadth ,
6472 viewportBounds ,
@@ -71,8 +79,6 @@ public SizeTracker(
7179 this .lengths = cells .mapDynamic (lengthFn ).memoize ();
7280
7381 LiveList <Double > knownLengths = this .lengths .memoizedItems ();
74- Val <Double > sumOfKnownLengths = knownLengths .reduce ((a , b ) -> a + b ).orElseConst (0.0 );
75- Val <Integer > knownLengthCount = knownLengths .sizeProperty ();
7682
7783 this .averageLengthEstimate = Val .create (
7884 () -> {
@@ -82,25 +88,24 @@ public SizeTracker(
8288 lengths .force (j , j + 1 );
8389 }
8490
85- int count = knownLengthCount .getValue ();
86- return count == 0
87- ? null
88- : sumOfKnownLengths .getValue () / count ;
91+ return knownLengths .stream ()
92+ .mapToDouble ( Double ::doubleValue )
93+ .average ().orElse (0.0 );
8994 },
90- sumOfKnownLengths , knownLengthCount );
95+ knownLengths .changes ().filter ( c -> knownLengths .size () > 0 )
96+ // skip zero cell events to prevent scrollbar flicker
97+ );
9198
9299 this .totalLengthEstimate = Val .combine (
93- averageLengthEstimate , cells .sizeProperty (),
100+ averageLengthEstimate ,
101+ cells .sizeProperty ().filter ( s -> s > 0 ),
94102 (avg , n ) -> n * avg );
95103
96104 Val <Integer > firstVisibleIndex = Val .create (
97105 () -> cells .getMemoizedCount () == 0 ? null : cells .indexOfMemoizedItem (0 ),
98106 cells , cells .memoizedItems ()); // need to observe cells.memoizedItems()
99107 // as well, because they may change without a change in cells.
100108
101- Val <? extends Cell <?, ?>> firstVisibleCell = cells .memoizedItems ()
102- .collapse (visCells -> visCells .isEmpty () ? null : visCells .get (0 ));
103-
104109 Val <Integer > knownLengthCountBeforeFirstVisibleCell = Val .create (() -> {
105110 return firstVisibleIndex .getOpt ()
106111 .map (i -> lengths .getMemoizedCountBefore (Math .min (i , lengths .size ())))
@@ -117,17 +122,23 @@ public SizeTracker(
117122 averageLengthEstimate ,
118123 (firstIdx , knownCnt , avgLen ) -> (firstIdx - knownCnt ) * avgLen );
119124
120- Val <Double > firstCellMinY = firstVisibleCell .flatMap (orientation ::minYProperty );
125+ Val <Double > firstCellMinY = cells .memoizedItems ()
126+ .collapse (visCells -> visCells .isEmpty () ? null : visCells .get (0 ))
127+ .flatMap (orientation ::minYProperty );
121128
122- lengthOffsetEstimate = Val . wrap ( EventStreams .combine (
129+ EventStream < Tuple3 < Double , Double , Double >> lengthOffsetStream = EventStreams .combine (
123130 totalKnownLengthBeforeFirstVisibleCell .values (),
124131 unknownLengthEstimateBeforeFirstVisibleCell .values (),
125132 firstCellMinY .values ()
126- )
127- .filter ( t3 -> t3 .test ( (a ,b ,minY ) -> a != null && b != null && minY != null ) )
128- .thenRetainLatestFor ( Duration .ofMillis ( 1 ) )
129- .map ( t3 -> t3 .map ( (a ,b ,minY ) -> Double .valueOf ( a + b - minY ) ) )
130- .toBinding ( 0.0 ) );
133+ );
134+
135+ lengthOffsetEstimate = Val .wrap (
136+ // skip spurious events resulting from cell replacement (delete then add again), except
137+ // when immediateUpdate is true: activated via updateNextLengthOffsetEstimateImmediately()
138+ new PausableSuccessionStream <>( lengthOffsetStream , Duration .ofMillis (15 ), immediateUpdate )
139+ .filter ( t3 -> t3 .test ( (a ,b ,minY ) -> a != null && b != null && minY != null ) )
140+ .map ( t3 -> t3 .map ( (a ,b ,minY ) -> Double .valueOf ( Math .round ( a + b - minY ) ) ) )
141+ .toBinding ( 0.0 ) );
131142
132143 // pinning totalLengthEstimate and lengthOffsetEstimate
133144 // binds it all together and enables memoization
@@ -136,6 +147,9 @@ public SizeTracker(
136147 lengthOffsetEstimate .pin ());
137148 }
138149
150+ private SimpleBooleanProperty immediateUpdate = new SimpleBooleanProperty ();
151+ void updateNextLengthOffsetEstimateImmediately () { immediateUpdate .set ( true ); }
152+
139153 private static <T > Val <T > avoidFalseInvalidations (Val <T > src ) {
140154 return new ValBase <T >() {
141155 @ Override
0 commit comments