1 /*
<lambda>null2 * Copyright 2022 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.ui.layout
18
19 import androidx.compose.runtime.Stable
20 import androidx.compose.ui.node.getChildrenOfVirtualChildren
21 import androidx.compose.ui.unit.Constraints
22 import androidx.compose.ui.util.fastMap
23
24 /**
25 * Defines the measure and layout behavior of a [Layout] overload which accepts a list of multiple
26 * composable content lambdas.
27 *
28 * This interface is identical to [MeasurePolicy], but provides you with a list of lists of
29 * [Measurable]s which allows to threat children put into different content lambdas differently.
30 * Such list has the same size as the list of contents passed into [Layout] and contains the list of
31 * [Measurable]s of the corresponding content lambda in the same order.
32 *
33 * Intrinsic measurement methods define the intrinsic size of the layout. These can be queried by
34 * the layout's parent in order to obtain, in specific cases, more information about the size of the
35 * layout in the absence of specific constraints:
36 * - [minIntrinsicWidth] defines the minimum width this layout can take, given a specific height,
37 * such that the content of the layout will be painted correctly
38 * - [minIntrinsicHeight] defines the minimum height this layout can take, given a specific width,
39 * such that the content of the layout will be painted correctly
40 * - [maxIntrinsicWidth] defines the minimum width such that increasing it further will not decrease
41 * the minimum intrinsic height
42 * - [maxIntrinsicHeight] defines the minimum height such that increasing it further will not
43 * decrease the minimum intrinsic width Most layout scenarios do not require querying intrinsic
44 * measurements. Therefore, when writing a custom layout, it is common to only define the actual
45 * measurement, as most of the times the intrinsic measurements of the layout will not be queried.
46 * Moreover, intrinsic measurement methods have default implementations that make a best effort
47 * attempt to calculate the intrinsic measurements by reusing the [measure] method. Note this will
48 * not be correct for all layouts, but can be a convenient approximation. Intrinsic measurements
49 * can be useful when the layout system enforcement of no more than one measurement per child is
50 * limiting. Layouts that use them are the `preferredWidth(IntrinsicSize)` and
51 * `preferredHeight(IntrinsicSize)` modifiers. See their samples for when they can be useful.
52 *
53 * @see Layout
54 * @see MeasurePolicy
55 */
56 @Stable
57 fun interface MultiContentMeasurePolicy {
58 /**
59 * The function that defines the measurement and layout. Each [Measurable] in the [measurables]
60 * lists corresponds to a layout child of the layout, and children can be measured using the
61 * [Measurable.measure] method. This method takes the [Constraints] which the child should
62 * respect; different children can be measured with different constraints. Measuring a child
63 * returns a [Placeable], which reveals the size chosen by the child as a result of its own
64 * measurement. According to the children sizes, the parent defines the position of the
65 * children, by [placing][Placeable.PlacementScope.place] the [Placeable]s in the
66 * [MeasureResult.placeChildren] of the returned [MeasureResult]. Therefore the parent needs to
67 * measure its children with appropriate [Constraints], such that whatever valid sizes children
68 * choose, they can be laid out correctly according to the parent's layout algorithm. This is
69 * because there is no measurement negotiation between the parent and children: once a child
70 * chooses its size, the parent needs to handle it correctly.
71 *
72 * It is identical to [MeasurePolicy.measure], but provides you with a list of lists of
73 * [Measurable]s which allows to threat children put into different content lambdas differently.
74 * Such list has the same size as the list of contents passed into [Layout] and contains the
75 * list of [Measurable]s of the corresponding content lambda in the same order.
76 *
77 * Note that a child is allowed to choose a size that does not satisfy its constraints. However,
78 * when this happens, the placeable's [width][Placeable.width] and [height][Placeable.height]
79 * will not represent the real size of the child, but rather the size coerced in the child's
80 * constraints. Therefore, it is common for parents to assume in their layout algorithm that its
81 * children will always respect the constraints. When this does not happen in reality, the
82 * position assigned to the child will be automatically offset to be centered on the space
83 * assigned by the parent under the assumption that constraints were respected. Rarely, when a
84 * parent really needs to know the true size of the child, they can read this from the
85 * placeable's [Placeable.measuredWidth] and [Placeable.measuredHeight].
86 *
87 * [MeasureResult] objects are usually created using the [MeasureScope.layout] factory, which
88 * takes the calculated size of this layout, its alignment lines, and a block defining the
89 * positioning of the children layouts.
90 */
91 fun MeasureScope.measure(
92 measurables: List<List<Measurable>>,
93 constraints: Constraints
94 ): MeasureResult
95
96 /**
97 * The function used to calculate [IntrinsicMeasurable.minIntrinsicWidth]. It represents the
98 * minimum width this layout can take, given a specific height, such that the content of the
99 * layout can be painted correctly.
100 *
101 * It is identical to [MeasurePolicy.minIntrinsicWidth], but provides you with a list of lists
102 * of [Measurable]s which allows to threat children put into different content lambdas
103 * differently. Such list has the same size as the list of contents passed into [Layout] and
104 * contains the list of [Measurable]s of the corresponding content lambda in the same order.
105 */
106 fun IntrinsicMeasureScope.minIntrinsicWidth(
107 measurables: List<List<IntrinsicMeasurable>>,
108 height: Int
109 ): Int {
110 val mapped =
111 measurables.fastMap { list ->
112 list.fastMap {
113 DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Min, IntrinsicWidthHeight.Width)
114 }
115 }
116 val constraints = Constraints(maxHeight = height)
117 val layoutReceiver = IntrinsicsMeasureScope(this, layoutDirection)
118 val layoutResult = layoutReceiver.measure(mapped, constraints)
119 return layoutResult.width
120 }
121
122 /**
123 * The function used to calculate [IntrinsicMeasurable.minIntrinsicHeight]. It represents the
124 * minimum height this layout can take, given a specific width, such that the content of the
125 * layout will be painted correctly.
126 *
127 * It is identical to [MeasurePolicy.minIntrinsicHeight], but provides you with a list of lists
128 * of [Measurable]s which allows to threat children put into different content lambdas
129 * differently. Such list has the same size as the list of contents passed into [Layout] and
130 * contains the list of [Measurable]s of the corresponding content lambda in the same order.
131 */
132 fun IntrinsicMeasureScope.minIntrinsicHeight(
133 measurables: List<List<IntrinsicMeasurable>>,
134 width: Int
135 ): Int {
136 val mapped =
137 measurables.fastMap { list ->
138 list.fastMap {
139 DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Min, IntrinsicWidthHeight.Height)
140 }
141 }
142 val constraints = Constraints(maxWidth = width)
143 val layoutReceiver = IntrinsicsMeasureScope(this, layoutDirection)
144 val layoutResult = layoutReceiver.measure(mapped, constraints)
145 return layoutResult.height
146 }
147
148 /**
149 * The function used to calculate [IntrinsicMeasurable.maxIntrinsicWidth]. It represents the
150 * minimum width such that increasing it further will not decrease the minimum intrinsic height.
151 *
152 * It is identical to [MeasurePolicy.maxIntrinsicWidth], but provides you with a list of lists
153 * of [Measurable]s which allows to threat children put into different content lambdas
154 * differently. Such list has the same size as the list of contents passed into [Layout] and
155 * contains the list of [Measurable]s of the corresponding content lambda in the same order.
156 */
157 fun IntrinsicMeasureScope.maxIntrinsicWidth(
158 measurables: List<List<IntrinsicMeasurable>>,
159 height: Int
160 ): Int {
161 val mapped =
162 measurables.fastMap { list ->
163 list.fastMap {
164 DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Max, IntrinsicWidthHeight.Width)
165 }
166 }
167 val constraints = Constraints(maxHeight = height)
168 val layoutReceiver = IntrinsicsMeasureScope(this, layoutDirection)
169 val layoutResult = layoutReceiver.measure(mapped, constraints)
170 return layoutResult.width
171 }
172
173 /**
174 * The function used to calculate [IntrinsicMeasurable.maxIntrinsicHeight]. It represents the
175 * minimum height such that increasing it further will not decrease the minimum intrinsic width.
176 *
177 * It is identical to [MeasurePolicy.maxIntrinsicHeight], but provides you with a list of lists
178 * of [Measurable]s which allows to threat children put into different content lambdas
179 * differently. Such list has the same size as the list of contents passed into [Layout] and
180 * contains the list of [Measurable]s of the corresponding content lambda in the same order.
181 */
182 fun IntrinsicMeasureScope.maxIntrinsicHeight(
183 measurables: List<List<IntrinsicMeasurable>>,
184 width: Int
185 ): Int {
186 val mapped =
187 measurables.fastMap { list ->
188 list.fastMap {
189 DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Max, IntrinsicWidthHeight.Height)
190 }
191 }
192 val constraints = Constraints(maxWidth = width)
193 val layoutReceiver = IntrinsicsMeasureScope(this, layoutDirection)
194 val layoutResult = layoutReceiver.measure(mapped, constraints)
195 return layoutResult.height
196 }
197 }
198
199 @PublishedApi
createMeasurePolicynull200 internal fun createMeasurePolicy(measurePolicy: MultiContentMeasurePolicy): MeasurePolicy =
201 MultiContentMeasurePolicyImpl(measurePolicy)
202
203 internal data class MultiContentMeasurePolicyImpl(val measurePolicy: MultiContentMeasurePolicy) :
204 MeasurePolicy {
205 override fun MeasureScope.measure(measurables: List<Measurable>, constraints: Constraints) =
206 with(measurePolicy) { measure(getChildrenOfVirtualChildren(this@measure), constraints) }
207
208 override fun IntrinsicMeasureScope.minIntrinsicWidth(
209 measurables: List<IntrinsicMeasurable>,
210 height: Int
211 ) =
212 with(measurePolicy) {
213 minIntrinsicWidth(getChildrenOfVirtualChildren(this@minIntrinsicWidth), height)
214 }
215
216 override fun IntrinsicMeasureScope.minIntrinsicHeight(
217 measurables: List<IntrinsicMeasurable>,
218 width: Int
219 ) =
220 with(measurePolicy) {
221 minIntrinsicHeight(getChildrenOfVirtualChildren(this@minIntrinsicHeight), width)
222 }
223
224 override fun IntrinsicMeasureScope.maxIntrinsicWidth(
225 measurables: List<IntrinsicMeasurable>,
226 height: Int
227 ) =
228 with(measurePolicy) {
229 maxIntrinsicWidth(getChildrenOfVirtualChildren(this@maxIntrinsicWidth), height)
230 }
231
232 override fun IntrinsicMeasureScope.maxIntrinsicHeight(
233 measurables: List<IntrinsicMeasurable>,
234 width: Int
235 ) =
236 with(measurePolicy) {
237 maxIntrinsicHeight(getChildrenOfVirtualChildren(this@maxIntrinsicHeight), width)
238 }
239 }
240