1 /*
2  * Copyright 2025 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.foundation.lazy.layout
18 
19 import androidx.annotation.FloatRange
20 import androidx.compose.foundation.ExperimentalFoundationApi
21 import androidx.compose.runtime.Stable
22 import androidx.compose.ui.unit.Density
23 import androidx.compose.ui.unit.Dp
24 import androidx.compose.ui.unit.dp
25 import kotlin.math.roundToInt
26 
27 /**
28  * Represents an out of viewport area of a Lazy Layout where items should be cached. Items will be
29  * prepared in the Cache Window area in advance to improve scroll performance.
30  */
31 @ExperimentalFoundationApi
32 @Stable
33 interface LazyLayoutCacheWindow {
34 
35     /**
36      * Calculates the prefetch window area in pixels for prefetching on the scroll direction, "ahead
37      * window". The prefetch window strategy will prepare items in the ahead area in advance s they
38      * are ready to be used when they become visible.
39      *
40      * @param viewport The size of the viewport in this Lazy Layout in pixels.
41      */
Densitynull42     fun Density.calculateAheadWindow(viewport: Int): Int = 0
43 
44     /**
45      * Calculates the window area in pixels for keeping items in the scroll counter direction,
46      * "behind window". Items in the behind window will not be disposed and can be accessed more
47      * quickly if they become visible again.
48      *
49      * @param viewport The size of the viewport in this Lazy Layout in pixels.
50      */
51     fun Density.calculateBehindWindow(viewport: Int): Int = 0
52 }
53 
54 /**
55  * A Dp based [LazyLayoutCacheWindow].
56  *
57  * @param ahead The size of the ahead window to be used as per
58  *   [LazyLayoutCacheWindow.calculateAheadWindow].
59  * @param behind The size of the behind window to be used as per
60  *   [LazyLayoutCacheWindow.calculateBehindWindow].
61  */
62 @ExperimentalFoundationApi
63 fun LazyLayoutCacheWindow(ahead: Dp = 0.dp, behind: Dp = 0.dp): LazyLayoutCacheWindow {
64     return DpLazyLayoutCacheWindow(ahead, behind)
65 }
66 
67 @OptIn(ExperimentalFoundationApi::class)
68 private class DpLazyLayoutCacheWindow(val ahead: Dp, val behind: Dp) : LazyLayoutCacheWindow {
calculateAheadWindownull69     override fun Density.calculateAheadWindow(viewport: Int): Int = ahead.roundToPx()
70 
71     override fun Density.calculateBehindWindow(viewport: Int): Int = behind.roundToPx()
72 
73     override fun hashCode(): Int {
74         return 31 * ahead.hashCode() + behind.hashCode()
75     }
76 
equalsnull77     override fun equals(other: Any?): Boolean {
78         return if (other is DpLazyLayoutCacheWindow) {
79             other.ahead == this.ahead && other.behind == this.behind
80         } else {
81             false
82         }
83     }
84 }
85 
86 /**
87  * Creates a [LazyLayoutCacheWindow] based off a fraction of the viewport.
88  *
89  * @param aheadFraction The fraction of the viewport to be used for the ahead window.
90  * @param behindFraction The fraction of the viewport to be used for the behind window.
91  */
92 @ExperimentalFoundationApi
LazyLayoutCacheWindownull93 fun LazyLayoutCacheWindow(
94     @FloatRange(from = 0.0) aheadFraction: Float = 0.0f,
95     @FloatRange(from = 0.0) behindFraction: Float = 0.0f
96 ): LazyLayoutCacheWindow = FractionLazyLayoutCacheWindow(aheadFraction, behindFraction)
97 
98 @OptIn(ExperimentalFoundationApi::class)
99 private class FractionLazyLayoutCacheWindow(val aheadFraction: Float, val behindFraction: Float) :
100     LazyLayoutCacheWindow {
101     override fun Density.calculateAheadWindow(viewport: Int): Int =
102         (viewport * aheadFraction).roundToInt()
103 
104     override fun Density.calculateBehindWindow(viewport: Int): Int =
105         (viewport * behindFraction).roundToInt()
106 
107     override fun hashCode(): Int {
108         return 31 * aheadFraction.hashCode() + behindFraction.hashCode()
109     }
110 
111     override fun equals(other: Any?): Boolean {
112         return if (other is FractionLazyLayoutCacheWindow) {
113             other.aheadFraction == this.aheadFraction && other.behindFraction == this.behindFraction
114         } else {
115             false
116         }
117     }
118 }
119