1 /*
<lambda>null2 * Copyright 2020 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 @file:Suppress("DEPRECATION")
18
19 package androidx.compose.ui.layout
20
21 import androidx.compose.runtime.Applier
22 import androidx.compose.runtime.Composable
23 import androidx.compose.runtime.ReusableComposeNode
24 import androidx.compose.runtime.SkippableUpdater
25 import androidx.compose.runtime.currentComposer
26 import androidx.compose.runtime.currentCompositeKeyHash
27 import androidx.compose.runtime.currentCompositeKeyHashCode
28 import androidx.compose.runtime.remember
29 import androidx.compose.ui.Modifier
30 import androidx.compose.ui.UiComposable
31 import androidx.compose.ui.graphics.GraphicsLayerScope
32 import androidx.compose.ui.materialize
33 import androidx.compose.ui.materializeWithCompositionLocalInjectionInternal
34 import androidx.compose.ui.node.ComposeUiNode
35 import androidx.compose.ui.node.ComposeUiNode.Companion.SetCompositeKeyHash
36 import androidx.compose.ui.node.ComposeUiNode.Companion.SetMeasurePolicy
37 import androidx.compose.ui.node.ComposeUiNode.Companion.SetModifier
38 import androidx.compose.ui.node.ComposeUiNode.Companion.SetResolvedCompositionLocals
39 import androidx.compose.ui.node.LayoutNode
40 import androidx.compose.ui.node.checkMeasuredSize
41 import androidx.compose.ui.unit.Constraints
42 import androidx.compose.ui.unit.IntOffset
43 import androidx.compose.ui.unit.IntSize
44 import androidx.compose.ui.unit.LayoutDirection
45 import androidx.compose.ui.util.fastCoerceAtLeast
46 import androidx.compose.ui.util.fastForEach
47 import kotlin.jvm.JvmName
48
49 /**
50 * [Layout] is the main core component for layout. It can be used to measure and position zero or
51 * more layout children.
52 *
53 * The measurement, layout and intrinsic measurement behaviours of this layout will be defined by
54 * the [measurePolicy] instance. See [MeasurePolicy] for more details.
55 *
56 * For a composable able to define its content according to the incoming constraints, see
57 * [androidx.compose.foundation.layout.BoxWithConstraints].
58 *
59 * Example usage:
60 *
61 * @sample androidx.compose.ui.samples.LayoutUsage
62 *
63 * Example usage with custom intrinsic measurements:
64 *
65 * @sample androidx.compose.ui.samples.LayoutWithProvidedIntrinsicsUsage
66 * @param content The children composable to be laid out.
67 * @param modifier Modifiers to be applied to the layout.
68 * @param measurePolicy The policy defining the measurement and positioning of the layout.
69 * @see Layout
70 * @see MeasurePolicy
71 * @see androidx.compose.foundation.layout.BoxWithConstraints
72 */
73 @Suppress("ComposableLambdaParameterPosition")
74 @UiComposable
75 @Composable
76 inline fun Layout(
77 content: @Composable @UiComposable () -> Unit,
78 modifier: Modifier = Modifier,
79 measurePolicy: MeasurePolicy
80 ) {
81 val compositeKeyHash = currentCompositeKeyHashCode.hashCode()
82 val localMap = currentComposer.currentCompositionLocalMap
83 val materialized = currentComposer.materialize(modifier)
84 ReusableComposeNode<ComposeUiNode, Applier<Any>>(
85 factory = ComposeUiNode.Constructor,
86 update = {
87 set(measurePolicy, SetMeasurePolicy)
88 set(localMap, SetResolvedCompositionLocals)
89 set(compositeKeyHash, SetCompositeKeyHash)
90 set(materialized, SetModifier)
91 },
92 content = content
93 )
94 }
95
96 /**
97 * [Layout] is the main core component for layout for "leaf" nodes. It can be used to measure and
98 * position zero children.
99 *
100 * The measurement, layout and intrinsic measurement behaviours of this layout will be defined by
101 * the [measurePolicy] instance. See [MeasurePolicy] for more details.
102 *
103 * For a composable able to define its content according to the incoming constraints, see
104 * [androidx.compose.foundation.layout.BoxWithConstraints].
105 *
106 * Example usage:
107 *
108 * @sample androidx.compose.ui.samples.LayoutUsage
109 *
110 * Example usage with custom intrinsic measurements:
111 *
112 * @sample androidx.compose.ui.samples.LayoutWithProvidedIntrinsicsUsage
113 * @param modifier Modifiers to be applied to the layout.
114 * @param measurePolicy The policy defining the measurement and positioning of the layout.
115 * @see Layout
116 * @see MeasurePolicy
117 * @see androidx.compose.foundation.layout.BoxWithConstraints
118 */
119 @Suppress("NOTHING_TO_INLINE")
120 @Composable
121 @UiComposable
Layoutnull122 inline fun Layout(modifier: Modifier = Modifier, measurePolicy: MeasurePolicy) {
123 val compositeKeyHash = currentCompositeKeyHashCode.hashCode()
124 val materialized = currentComposer.materialize(modifier)
125 val localMap = currentComposer.currentCompositionLocalMap
126 ReusableComposeNode<ComposeUiNode, Applier<Any>>(
127 factory = ComposeUiNode.Constructor,
128 update = {
129 set(measurePolicy, SetMeasurePolicy)
130 set(localMap, SetResolvedCompositionLocals)
131 set(materialized, SetModifier)
132 set(compositeKeyHash, SetCompositeKeyHash)
133 },
134 )
135 }
136
137 /**
138 * [Layout] is the main core component for layout. It can be used to measure and position zero or
139 * more layout children.
140 *
141 * This overload accepts a list of multiple composable content lambdas, which allows treating
142 * measurables put into different content lambdas differently - measure policy will provide a list
143 * of lists of Measurables, not just a single list. Such list has the same size as the list of
144 * contents passed into [Layout] and contains the list of measurables of the corresponding content
145 * lambda in the same order.
146 *
147 * Note that layouts emitted as part of all [contents] lambdas will be added as a direct children
148 * for this [Layout]. This means that if you set a custom z index on some children, the drawing
149 * order will be calculated as if they were all provided as part of one lambda.
150 *
151 * Example usage:
152 *
153 * @sample androidx.compose.ui.samples.LayoutWithMultipleContentsUsage
154 * @param contents The list of children composable contents to be laid out.
155 * @param modifier Modifiers to be applied to the layout.
156 * @param measurePolicy The policy defining the measurement and positioning of the layout.
157 * @see Layout for a simpler use case when you have only one content lambda.
158 */
159 @Suppress("ComposableLambdaParameterPosition", "NOTHING_TO_INLINE")
160 @UiComposable
161 @Composable
162 inline fun Layout(
163 contents: List<@Composable @UiComposable () -> Unit>,
164 modifier: Modifier = Modifier,
165 measurePolicy: MultiContentMeasurePolicy
166 ) {
167 Layout(
168 content = combineAsVirtualLayouts(contents),
169 modifier = modifier,
<lambda>null170 measurePolicy = remember(measurePolicy) { createMeasurePolicy(measurePolicy) }
171 )
172 }
173
174 @PublishedApi
175 internal fun combineAsVirtualLayouts(
176 contents: List<@Composable @UiComposable () -> Unit>
177 ): @Composable @UiComposable () -> Unit = {
contentnull178 contents.fastForEach { content ->
179 val compositeKeyHash = currentCompositeKeyHashCode.hashCode()
180 ReusableComposeNode<ComposeUiNode, Applier<Any>>(
181 factory = ComposeUiNode.VirtualConstructor,
182 update = { set(compositeKeyHash, SetCompositeKeyHash) },
183 content = content
184 )
185 }
186 }
187
188 /**
189 * This function uses a JVM-Name because the original name now has a different implementation for
190 * backwards compatibility [materializerOfWithCompositionLocalInjection]. More details can be found
191 * at https://issuetracker.google.com/275067189
192 */
193 @PublishedApi
194 @JvmName("modifierMaterializerOf")
materializerOfnull195 internal fun materializerOf(
196 modifier: Modifier
197 ): @Composable SkippableUpdater<ComposeUiNode>.() -> Unit = {
198 val compositeKeyHash = currentCompositeKeyHashCode.hashCode()
199 val materialized = currentComposer.materialize(modifier)
200 update {
201 set(materialized, SetModifier)
202 set(compositeKeyHash, SetCompositeKeyHash)
203 }
204 }
205
206 /**
207 * This function exists solely for solving a backwards-incompatibility with older compilations that
208 * used an older version of the `Layout` composable. New code paths should not call this. More
209 * details can be found at https://issuetracker.google.com/275067189
210 */
211 @JvmName("materializerOf")
212 @Deprecated(
213 "Needed only for backwards compatibility. Do not use.",
214 level = DeprecationLevel.WARNING
215 )
216 @PublishedApi
materializerOfWithCompositionLocalInjectionnull217 internal fun materializerOfWithCompositionLocalInjection(
218 modifier: Modifier
219 ): @Composable SkippableUpdater<ComposeUiNode>.() -> Unit = {
220 val compositeKeyHash = currentCompositeKeyHash.hashCode()
221 val materialized = currentComposer.materializeWithCompositionLocalInjectionInternal(modifier)
222 update {
223 set(materialized, SetModifier)
224 set(compositeKeyHash, SetCompositeKeyHash)
225 }
226 }
227
228 @Suppress("ComposableLambdaParameterPosition")
229 @Composable
230 @UiComposable
231 @Deprecated(
232 "This API is unsafe for UI performance at scale - using it incorrectly will lead " +
233 "to exponential performance issues. This API should be avoided whenever possible."
234 )
MultiMeasureLayoutnull235 fun MultiMeasureLayout(
236 modifier: Modifier = Modifier,
237 content: @Composable @UiComposable () -> Unit,
238 measurePolicy: MeasurePolicy
239 ) {
240 val compositeKeyHash = currentCompositeKeyHash.hashCode()
241 val materialized = currentComposer.materialize(modifier)
242 val localMap = currentComposer.currentCompositionLocalMap
243
244 ReusableComposeNode<LayoutNode, Applier<Any>>(
245 factory = LayoutNode.Constructor,
246 update = {
247 set(measurePolicy, SetMeasurePolicy)
248 set(localMap, SetResolvedCompositionLocals)
249 @Suppress("DEPRECATION") init { this.canMultiMeasure = true }
250 set(materialized, SetModifier)
251 set(compositeKeyHash, SetCompositeKeyHash)
252 },
253 content = content
254 )
255 }
256
257 /** Used to return a fixed sized item for intrinsics measurements in [Layout] */
258 private class FixedSizeIntrinsicsPlaceable(width: Int, height: Int) : Placeable() {
259 init {
260 measuredSize = IntSize(width, height)
261 }
262
getnull263 override fun get(alignmentLine: AlignmentLine): Int = AlignmentLine.Unspecified
264
265 override fun placeAt(
266 position: IntOffset,
267 zIndex: Float,
268 layerBlock: (GraphicsLayerScope.() -> Unit)?
269 ) {}
270 }
271
272 /** Identifies an [IntrinsicMeasurable] as a min or max intrinsic measurement. */
273 internal enum class IntrinsicMinMax {
274 Min,
275 Max
276 }
277
278 /** Identifies an [IntrinsicMeasurable] as a width or height intrinsic measurement. */
279 internal enum class IntrinsicWidthHeight {
280 Width,
281 Height
282 }
283
284 // A large value to use as a replacement for Infinity with DefaultIntrinisicMeasurable.
285 // A layout likely won't use this dimension as it is opposite from the one being measured in
286 // the max/min Intrinsic Width/Height, but it is possible. For example, if the direct child
287 // uses normal measurement/layout, we don't want to return Infinity sizes when its parent
288 // asks for intrinsic size. 15 bits can fit in a Constraints, so should be safe unless
289 // the parent adds to it and the other dimension is also very large (> 2^15).
290 internal const val LargeDimension = (1 shl 15) - 1
291
292 /**
293 * A wrapper around a [Measurable] for intrinsic measurements in [Layout]. Consumers of [Layout]
294 * don't identify intrinsic methods, but we can give a reasonable implementation by using their
295 * [measure], substituting the intrinsics gathering method for the [Measurable.measure] call.
296 */
297 internal class DefaultIntrinsicMeasurable(
298 val measurable: IntrinsicMeasurable,
299 private val minMax: IntrinsicMinMax,
300 private val widthHeight: IntrinsicWidthHeight
301 ) : Measurable {
302 override val parentData: Any?
303 get() = measurable.parentData
304
measurenull305 override fun measure(constraints: Constraints): Placeable {
306 if (widthHeight == IntrinsicWidthHeight.Width) {
307 val width =
308 if (minMax == IntrinsicMinMax.Max) {
309 measurable.maxIntrinsicWidth(constraints.maxHeight)
310 } else {
311 measurable.minIntrinsicWidth(constraints.maxHeight)
312 }
313 // Can't use infinity for height, so use a large number
314 val height = if (constraints.hasBoundedHeight) constraints.maxHeight else LargeDimension
315 return FixedSizeIntrinsicsPlaceable(width, height)
316 }
317 val height =
318 if (minMax == IntrinsicMinMax.Max) {
319 measurable.maxIntrinsicHeight(constraints.maxWidth)
320 } else {
321 measurable.minIntrinsicHeight(constraints.maxWidth)
322 }
323 // Can't use infinity for width, so use a large number
324 val width = if (constraints.hasBoundedWidth) constraints.maxWidth else LargeDimension
325 return FixedSizeIntrinsicsPlaceable(width, height)
326 }
327
minIntrinsicWidthnull328 override fun minIntrinsicWidth(height: Int): Int {
329 return measurable.minIntrinsicWidth(height)
330 }
331
maxIntrinsicWidthnull332 override fun maxIntrinsicWidth(height: Int): Int {
333 return measurable.maxIntrinsicWidth(height)
334 }
335
minIntrinsicHeightnull336 override fun minIntrinsicHeight(width: Int): Int {
337 return measurable.minIntrinsicHeight(width)
338 }
339
maxIntrinsicHeightnull340 override fun maxIntrinsicHeight(width: Int): Int {
341 return measurable.maxIntrinsicHeight(width)
342 }
343 }
344
345 /**
346 * Receiver scope for [Layout]'s and [LayoutModifier]'s layout lambda when used in an intrinsics
347 * call.
348 */
349 internal class IntrinsicsMeasureScope(
350 intrinsicMeasureScope: IntrinsicMeasureScope,
351 override val layoutDirection: LayoutDirection,
<lambda>null352 ) : MeasureScope, IntrinsicMeasureScope by intrinsicMeasureScope {
353 override fun layout(
354 width: Int,
355 height: Int,
356 alignmentLines: Map<AlignmentLine, Int>,
357 rulers: (RulerScope.() -> Unit)?,
358 placementBlock: Placeable.PlacementScope.() -> Unit
359 ): MeasureResult {
360 val w = width.fastCoerceAtLeast(0)
361 val h = height.fastCoerceAtLeast(0)
362 checkMeasuredSize(w, h)
363 return object : MeasureResult {
364 override val width: Int
365 get() = w
366
367 override val height: Int
368 get() = h
369
370 override val alignmentLines: Map<AlignmentLine, Int>
371 get() = alignmentLines
372
373 override val rulers: (RulerScope.() -> Unit)?
374 get() = rulers
375
376 override fun placeChildren() {
377 // Intrinsics should never be placed
378 }
379 }
380 }
381 }
382
383 internal class ApproachIntrinsicsMeasureScope(
384 intrinsicMeasureScope: ApproachIntrinsicMeasureScope,
385 override val layoutDirection: LayoutDirection,
<lambda>null386 ) : ApproachMeasureScope, ApproachIntrinsicMeasureScope by intrinsicMeasureScope {
387 override fun layout(
388 width: Int,
389 height: Int,
390 alignmentLines: Map<AlignmentLine, Int>,
391 rulers: (RulerScope.() -> Unit)?,
392 placementBlock: Placeable.PlacementScope.() -> Unit
393 ): MeasureResult {
394 val w = width.fastCoerceAtLeast(0)
395 val h = height.fastCoerceAtLeast(0)
396 checkMeasuredSize(w, h)
397 return object : MeasureResult {
398 override val width: Int
399 get() = w
400
401 override val height: Int
402 get() = h
403
404 override val alignmentLines: Map<AlignmentLine, Int>
405 get() = alignmentLines
406
407 override val rulers: (RulerScope.() -> Unit)?
408 get() = rulers
409
410 override fun placeChildren() {
411 // Intrinsics should never be placed
412 }
413 }
414 }
415 }
416