1+ import { ConsecutiveNumbers } from "../helpers/ConsecutiveNumbers" ;
2+
13import {
24 LayoutParams ,
35 RVDimension ,
46 RVLayout ,
57 RVLayoutInfo ,
68 RVLayoutManager ,
79} from "./LayoutManager" ;
8- import { ConsecutiveNumbers } from "../helpers/ConsecutiveNumbers" ;
910
1011/**
1112 * MasonryLayoutManager implementation that arranges items in a masonry/pinterest-style layout.
@@ -19,6 +20,8 @@ export class RVMasonryLayoutManagerImpl extends RVLayoutManager {
1920 private columnHeights : number [ ] ;
2021 /** Current column index for sequential placement */
2122 private currentColumn = 0 ;
23+ /** Per-column sorted lists of item indices for efficient visibility detection */
24+ private columnItems : number [ ] [ ] = [ ] ;
2225
2326 /** If there's a span change for masonry layout, we need to recompute all the widths */
2427 private fullRelayoutRequired = false ;
@@ -156,6 +159,8 @@ export class RVMasonryLayoutManagerImpl extends RVLayoutManager {
156159 this . placeItemSequentially ( layout , span ) ;
157160 }
158161 }
162+ // Rebuild per-column item mappings for efficient visibility detection
163+ this . rebuildColumnItems ( ) ;
159164 }
160165
161166 /**
@@ -323,31 +328,86 @@ export class RVMasonryLayoutManagerImpl extends RVLayoutManager {
323328 }
324329
325330 /**
326- * Override getVisibleLayouts to avoid using binary search on the full
327- * layouts array. In masonry layout, item y-positions are NOT sorted by
328- * index (items are placed in columns), so binary search produces wrong
329- * results causing items to disappear. Instead, we scan each column
330- * independently where y-positions ARE sorted, and combine the results.
331+ * Rebuilds per-column item index arrays from layout positions.
332+ * Within each column, items are sorted by y-position (guaranteed by
333+ * placement order since column heights only increase).
334+ */
335+ private rebuildColumnItems ( ) : void {
336+ const columnWidth = this . boundedSize / this . maxColumns ;
337+ this . columnItems = Array . from ( { length : this . maxColumns } , ( ) => [ ] ) ;
338+ for ( let i = 0 ; i < this . layouts . length ; i ++ ) {
339+ const layout = this . layouts [ i ] ;
340+ const col = Math . min (
341+ Math . round ( layout . x / columnWidth ) ,
342+ this . maxColumns - 1
343+ ) ;
344+ this . columnItems [ col ] . push ( i ) ;
345+ }
346+ }
347+
348+ /**
349+ * Override getVisibleLayouts to use per-column binary search instead of
350+ * a single binary search on the full layouts array. In masonry layout,
351+ * y-positions are NOT sorted by index (items are placed in columns), but
352+ * they ARE sorted within each column. Binary searching each column gives
353+ * O(numColumns * log(n/numColumns)) performance.
331354 */
332355 getVisibleLayouts (
333356 unboundDimensionStart : number ,
334357 unboundDimensionEnd : number
335358 ) : ConsecutiveNumbers {
359+ if ( this . layouts . length === 0 ) {
360+ return ConsecutiveNumbers . EMPTY ;
361+ }
362+
336363 let firstVisibleIndex = - 1 ;
337364 let lastVisibleIndex = - 1 ;
338- const layoutCount = this . layouts . length ;
339-
340- for ( let i = 0 ; i < layoutCount ; i ++ ) {
341- const layout = this . layouts [ i ] ;
342- const position = layout . y ;
343- const size = layout . height ;
344365
345- // Item is visible if it overlaps with the viewport
346- if ( position + size > unboundDimensionStart && position < unboundDimensionEnd ) {
347- if ( firstVisibleIndex === - 1 ) {
348- firstVisibleIndex = i ;
366+ for ( const items of this . columnItems ) {
367+ if ( items . length === 0 ) continue ;
368+
369+ // Binary search for first visible item in this column:
370+ // find first item where y + height > unboundDimensionStart
371+ let lo = 0 ;
372+ let hi = items . length - 1 ;
373+ let colFirst = - 1 ;
374+ while ( lo <= hi ) {
375+ const mid = ( lo + hi ) >>> 1 ;
376+ const layout = this . layouts [ items [ mid ] ] ;
377+ if ( layout . y + layout . height > unboundDimensionStart ) {
378+ colFirst = mid ;
379+ hi = mid - 1 ;
380+ } else {
381+ lo = mid + 1 ;
382+ }
383+ }
384+ if ( colFirst === - 1 ) continue ;
385+
386+ // Binary search for last visible item in this column:
387+ // find last item where y < unboundDimensionEnd
388+ lo = colFirst ;
389+ hi = items . length - 1 ;
390+ let colLast = - 1 ;
391+ while ( lo <= hi ) {
392+ const mid = ( lo + hi ) >>> 1 ;
393+ const layout = this . layouts [ items [ mid ] ] ;
394+ if ( layout . y < unboundDimensionEnd ) {
395+ colLast = mid ;
396+ lo = mid + 1 ;
397+ } else {
398+ hi = mid - 1 ;
349399 }
350- lastVisibleIndex = i ;
400+ }
401+ if ( colLast === - 1 ) continue ;
402+
403+ // Update global min/max with item indices from this column
404+ const colFirstIndex = items [ colFirst ] ;
405+ const colLastIndex = items [ colLast ] ;
406+ if ( firstVisibleIndex === - 1 || colFirstIndex < firstVisibleIndex ) {
407+ firstVisibleIndex = colFirstIndex ;
408+ }
409+ if ( lastVisibleIndex === - 1 || colLastIndex > lastVisibleIndex ) {
410+ lastVisibleIndex = colLastIndex ;
351411 }
352412 }
353413
0 commit comments