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