1 /*
<lambda>null2  * Copyright 2021 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.collection.mutableIntObjectMapOf
20 import androidx.compose.foundation.ExperimentalFoundationApi
21 import androidx.compose.foundation.internal.checkPrecondition
22 import androidx.compose.runtime.Stable
23 import androidx.compose.ui.geometry.Size
24 import androidx.compose.ui.geometry.isSpecified
25 import androidx.compose.ui.layout.MeasureResult
26 import androidx.compose.ui.layout.MeasureScope
27 import androidx.compose.ui.layout.Placeable
28 import androidx.compose.ui.layout.SubcomposeMeasureScope
29 import androidx.compose.ui.unit.Constraints
30 import androidx.compose.ui.unit.Dp
31 import androidx.compose.ui.unit.DpSize
32 import androidx.compose.ui.unit.TextUnit
33 import androidx.compose.ui.unit.TextUnitType
34 import androidx.compose.ui.unit.dp
35 import androidx.compose.ui.unit.isSpecified
36 import androidx.compose.ui.unit.sp
37 
38 /**
39  * The receiver scope of a [LazyLayout]'s measure lambda. The return value of the measure lambda is
40  * [MeasureResult], which should be returned by [layout].
41  *
42  * Main difference from the regular flow of writing any custom layout is that you have a new
43  * function [measure] which accepts item index and constraints, composes the item based and then
44  * measures all the layouts emitted in the item content block.
45  *
46  * Note: this interface is a part of [LazyLayout] harness that allows for building custom lazy
47  * layouts. LazyLayout and all corresponding APIs are still under development and are subject to
48  * change.
49  */
50 @Stable
51 @ExperimentalFoundationApi
52 sealed interface LazyLayoutMeasureScope : MeasureScope {
53     /**
54      * Subcompose and measure the item of lazy layout.
55      *
56      * @param index the item index. Should be no larger that [LazyLayoutItemProvider.itemCount].
57      * @param constraints [Constraints] to measure the children emitted into an item content
58      *   composable specified via [LazyLayoutItemProvider.Item].
59      * @return List of [Placeable]s. Note that if you emitted multiple children into the item
60      *   composable you will receive multiple placeables, each of them will be measured with the
61      *   passed [constraints].
62      */
63     fun measure(index: Int, constraints: Constraints): List<Placeable>
64 
65     // Below overrides added to work around https://youtrack.jetbrains.com/issue/KT-51672
66     // Must be kept in sync until resolved.
67 
68     @Stable
69     override fun TextUnit.toDp(): Dp {
70         checkPrecondition(type == TextUnitType.Sp) { "Only Sp can convert to Px" }
71         return Dp(value * fontScale)
72     }
73 
74     @Stable override fun Int.toDp(): Dp = (this / density).dp
75 
76     @Stable override fun Float.toDp(): Dp = (this / density).dp
77 
78     @Stable override fun Float.toSp(): TextUnit = (this / (fontScale * density)).sp
79 
80     @Stable override fun Int.toSp(): TextUnit = (this / (fontScale * density)).sp
81 
82     @Stable override fun Dp.toSp(): TextUnit = (value / fontScale).sp
83 
84     @Stable
85     override fun DpSize.toSize(): Size =
86         if (isSpecified) {
87             Size(width.toPx(), height.toPx())
88         } else {
89             Size.Unspecified
90         }
91 
92     @Stable
93     override fun Size.toDpSize(): DpSize =
94         if (isSpecified) {
95             DpSize(width.toDp(), height.toDp())
96         } else {
97             DpSize.Unspecified
98         }
99 }
100 
101 @OptIn(ExperimentalFoundationApi::class)
102 internal class LazyLayoutMeasureScopeImpl
103 internal constructor(
104     private val itemContentFactory: LazyLayoutItemContentFactory,
105     private val subcomposeMeasureScope: SubcomposeMeasureScope
<lambda>null106 ) : LazyLayoutMeasureScope, MeasureScope by subcomposeMeasureScope {
107 
108     private val itemProvider = itemContentFactory.itemProvider()
109 
110     /**
111      * A cache of the previously composed items. It allows us to support [get] re-executions with
112      * the same index during the same measure pass.
113      */
114     private val placeablesCache = mutableIntObjectMapOf<List<Placeable>>()
115 
116     override fun measure(index: Int, constraints: Constraints): List<Placeable> {
117         val cachedPlaceable = placeablesCache[index]
118         return if (cachedPlaceable != null) {
119             cachedPlaceable
120         } else {
121             val key = itemProvider.getKey(index)
122             val contentType = itemProvider.getContentType(index)
123             val itemContent = itemContentFactory.getContent(index, key, contentType)
124             val measurables = subcomposeMeasureScope.subcompose(key, itemContent)
125             List(measurables.size) { i -> measurables[i].measure(constraints) }
126                 .also { placeablesCache[index] = it }
127         }
128     }
129 
130     /** Below overrides added to work around https://youtrack.jetbrains.com/issue/KT-51672 */
131     override fun TextUnit.toDp(): Dp = with(subcomposeMeasureScope) { toDp() }
132 
133     override fun Int.toDp(): Dp = with(subcomposeMeasureScope) { toDp() }
134 
135     override fun Float.toDp(): Dp = with(subcomposeMeasureScope) { toDp() }
136 
137     override fun Float.toSp(): TextUnit = with(subcomposeMeasureScope) { toSp() }
138 
139     override fun Int.toSp(): TextUnit = with(subcomposeMeasureScope) { toSp() }
140 
141     override fun Dp.toSp(): TextUnit = with(subcomposeMeasureScope) { toSp() }
142 
143     override fun DpSize.toSize(): Size = with(subcomposeMeasureScope) { toSize() }
144 
145     override fun Size.toDpSize(): DpSize = with(subcomposeMeasureScope) { toDpSize() }
146 }
147