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,15 @@ 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+ EventStreams .changesOf ( knownBreadths ).filter ( c -> c .getList ().size () > 0 )
67+ // skips spurious events resulting from cell replacement (delete then add again)
68+ .successionEnds ( Duration .ofMillis ( 15 ) )
69+ );
70+
6271 this .breadthForCells = Val .combine (
6372 maxKnownMinBreadth ,
6473 viewportBounds ,
@@ -71,8 +80,6 @@ public SizeTracker(
7180 this .lengths = cells .mapDynamic (lengthFn ).memoize ();
7281
7382 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 ();
7683
7784 this .averageLengthEstimate = Val .create (
7885 () -> {
@@ -82,12 +89,14 @@ public SizeTracker(
8289 lengths .force (j , j + 1 );
8390 }
8491
85- int count = knownLengthCount .getValue ();
86- return count == 0
87- ? null
88- : sumOfKnownLengths .getValue () / count ;
92+ return knownLengths .stream ()
93+ .mapToDouble ( Double ::doubleValue )
94+ .average ().orElse (0.0 );
8995 },
90- sumOfKnownLengths , knownLengthCount );
96+ EventStreams .changesOf ( knownLengths ).filter ( c -> c .getList ().size () > 0 )
97+ // skips spurious events resulting from cell replacement (delete then add again)
98+ .successionEnds ( Duration .ofMillis ( 15 ) )
99+ );
91100
92101 this .totalLengthEstimate = Val .combine (
93102 averageLengthEstimate , cells .sizeProperty (),
@@ -98,9 +107,6 @@ public SizeTracker(
98107 cells , cells .memoizedItems ()); // need to observe cells.memoizedItems()
99108 // as well, because they may change without a change in cells.
100109
101- Val <? extends Cell <?, ?>> firstVisibleCell = cells .memoizedItems ()
102- .collapse (visCells -> visCells .isEmpty () ? null : visCells .get (0 ));
103-
104110 Val <Integer > knownLengthCountBeforeFirstVisibleCell = Val .create (() -> {
105111 return firstVisibleIndex .getOpt ()
106112 .map (i -> lengths .getMemoizedCountBefore (Math .min (i , lengths .size ())))
@@ -117,17 +123,23 @@ public SizeTracker(
117123 averageLengthEstimate ,
118124 (firstIdx , knownCnt , avgLen ) -> (firstIdx - knownCnt ) * avgLen );
119125
120- Val <Double > firstCellMinY = firstVisibleCell .flatMap (orientation ::minYProperty );
126+ Val <Double > firstCellMinY = cells .memoizedItems ()
127+ .collapse (visCells -> visCells .isEmpty () ? null : visCells .get (0 ))
128+ .flatMap (orientation ::minYProperty );
121129
122- lengthOffsetEstimate = Val . wrap ( EventStreams .combine (
130+ EventStream < Tuple3 < Double , Double , Double >> lengthOffsetStream = EventStreams .combine (
123131 totalKnownLengthBeforeFirstVisibleCell .values (),
124132 unknownLengthEstimateBeforeFirstVisibleCell .values (),
125133 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 ) );
134+ );
135+
136+ lengthOffsetEstimate = Val .wrap (
137+ // skip spurious events resulting from cell replacement (delete then add again), except
138+ // when immediateUpdate is true: activated via updateNextLengthOffsetEstimateImmediately()
139+ new PausableSuccessionStream <>( lengthOffsetStream , Duration .ofMillis (15 ), immediateUpdate )
140+ .filter ( t3 -> t3 .test ( (a ,b ,minY ) -> a != null && b != null && minY != null ) )
141+ .map ( t3 -> t3 .map ( (a ,b ,minY ) -> Double .valueOf ( Math .round ( a + b - minY ) ) ) )
142+ .toBinding ( 0.0 ) );
131143
132144 // pinning totalLengthEstimate and lengthOffsetEstimate
133145 // binds it all together and enables memoization
@@ -136,6 +148,9 @@ public SizeTracker(
136148 lengthOffsetEstimate .pin ());
137149 }
138150
151+ private SimpleBooleanProperty immediateUpdate = new SimpleBooleanProperty ();
152+ void updateNextLengthOffsetEstimateImmediately () { immediateUpdate .set ( true ); }
153+
139154 private static <T > Val <T > avoidFalseInvalidations (Val <T > src ) {
140155 return new ValBase <T >() {
141156 @ Override
0 commit comments