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.node
18 
19 import androidx.compose.ui.Modifier
20 import androidx.compose.ui.graphics.GraphicsLayerScope
21 import androidx.compose.ui.layout.AlignmentLine
22 import androidx.compose.ui.layout.ApproachIntrinsicMeasureScope
23 import androidx.compose.ui.layout.ApproachIntrinsicsMeasureScope
24 import androidx.compose.ui.layout.ApproachMeasureScope
25 import androidx.compose.ui.layout.IntrinsicMeasurable
26 import androidx.compose.ui.layout.IntrinsicMeasureScope
27 import androidx.compose.ui.layout.IntrinsicsMeasureScope
28 import androidx.compose.ui.layout.LargeDimension
29 import androidx.compose.ui.layout.Measurable
30 import androidx.compose.ui.layout.MeasureResult
31 import androidx.compose.ui.layout.MeasureScope
32 import androidx.compose.ui.layout.Placeable
33 import androidx.compose.ui.unit.Constraints
34 import androidx.compose.ui.unit.IntOffset
35 import androidx.compose.ui.unit.IntSize
36 
37 /**
38  * A [Modifier.Node] that changes how its wrapped content is measured and laid out. It has the same
39  * measurement and layout functionality as the [androidx.compose.ui.layout.Layout] component, while
40  * wrapping exactly one layout due to it being a modifier. In contrast, the
41  * [androidx.compose.ui.layout.Layout] component is used to define the layout behavior of multiple
42  * children.
43  *
44  * This is the [androidx.compose.ui.Modifier.Node] equivalent of
45  * [androidx.compose.ui.layout.LayoutModifier]
46  *
47  * @sample androidx.compose.ui.samples.LayoutModifierNodeSample
48  * @see androidx.compose.ui.layout.Layout
49  */
50 interface LayoutModifierNode : DelegatableNode {
51     /**
52      * The function used to measure the modifier. The [measurable] corresponds to the wrapped
53      * content, and it can be measured with the desired constraints according to the logic of the
54      * [LayoutModifierNode]. The modifier needs to choose its own size, which can depend on the size
55      * chosen by the wrapped content (the obtained [Placeable]), if the wrapped content was
56      * measured. The size needs to be returned as part of a [MeasureResult], alongside the placement
57      * logic of the [Placeable], which defines how the wrapped content should be positioned inside
58      * the [LayoutModifierNode]. A convenient way to create the [MeasureResult] is to use the
59      * [MeasureScope.layout] factory function.
60      *
61      * A [LayoutModifierNode] uses the same measurement and layout concepts and principles as a
62      * [androidx.compose.ui.layout.Layout], the only difference is that they apply to exactly one
63      * child. For a more detailed explanation of measurement and layout, see
64      * [androidx.compose.ui.layout.MeasurePolicy].
65      */
66     fun MeasureScope.measure(measurable: Measurable, constraints: Constraints): MeasureResult
67 
68     /** The function used to calculate [IntrinsicMeasurable.minIntrinsicWidth]. */
69     fun IntrinsicMeasureScope.minIntrinsicWidth(measurable: IntrinsicMeasurable, height: Int): Int =
70         NodeMeasuringIntrinsics.minWidth(
71             { intrinsicMeasurable, constraints -> measure(intrinsicMeasurable, constraints) },
72             this,
73             measurable,
74             height
75         )
76 
77     /** The lambda used to calculate [IntrinsicMeasurable.minIntrinsicHeight]. */
78     fun IntrinsicMeasureScope.minIntrinsicHeight(measurable: IntrinsicMeasurable, width: Int): Int =
79         NodeMeasuringIntrinsics.minHeight(
80             { intrinsicMeasurable, constraints -> measure(intrinsicMeasurable, constraints) },
81             this,
82             measurable,
83             width
84         )
85 
86     /** The function used to calculate [IntrinsicMeasurable.maxIntrinsicWidth]. */
87     fun IntrinsicMeasureScope.maxIntrinsicWidth(measurable: IntrinsicMeasurable, height: Int): Int {
88         return NodeMeasuringIntrinsics.maxWidth(
89             { intrinsicMeasurable, constraints -> measure(intrinsicMeasurable, constraints) },
90             this,
91             measurable,
92             height
93         )
94     }
95 
96     /** The lambda used to calculate [IntrinsicMeasurable.maxIntrinsicHeight]. */
97     fun IntrinsicMeasureScope.maxIntrinsicHeight(measurable: IntrinsicMeasurable, width: Int): Int =
98         NodeMeasuringIntrinsics.maxHeight(
99             { intrinsicMeasurable, constraints -> measure(intrinsicMeasurable, constraints) },
100             this,
101             measurable,
102             width
103         )
104 }
105 
106 /**
107  * Performs the node remeasuring synchronously even if the node was not marked as needs remeasure
108  * before. Useful for cases like when during scrolling you need to re-execute the measure block to
109  * consume the scroll offset and remeasure your children in a blocking way.
110  */
remeasureSyncnull111 fun LayoutModifierNode.remeasureSync() = requireLayoutNode().forceRemeasure()
112 
113 /**
114  * This will invalidate the current node's layer, and ensure that the layer is redrawn for the next
115  * frame.
116  */
117 fun LayoutModifierNode.invalidateLayer() = requireCoordinator(Nodes.Layout).invalidateLayer()
118 
119 /**
120  * This will invalidate the current node's placement result, and ensure that relayout (the placement
121  * block rerun) of this node will happen for the next frame .
122  */
123 fun LayoutModifierNode.invalidatePlacement() = requireLayoutNode().requestRelayout()
124 
125 /**
126  * This invalidates the current node's measure result, and ensures that a re-measurement (the
127  * measurement block rerun) of this node will happen for the next frame.
128  */
129 fun LayoutModifierNode.invalidateMeasurement() = requireLayoutNode().invalidateMeasurements()
130 
131 internal fun LayoutModifierNode.requestRemeasure() = requireLayoutNode().requestRemeasure()
132 
133 internal object NodeMeasuringIntrinsics {
134     // Fun interface for measure block to avoid autoBoxing of Constraints
135     internal fun interface MeasureBlock {
136         fun MeasureScope.measure(measurable: Measurable, constraints: Constraints): MeasureResult
137     }
138 
139     internal fun interface ApproachMeasureBlock {
140         fun ApproachMeasureScope.measure(
141             measurable: Measurable,
142             constraints: Constraints
143         ): MeasureResult
144     }
145 
146     internal fun minWidth(
147         measureBlock: ApproachMeasureBlock,
148         intrinsicMeasureScope: ApproachIntrinsicMeasureScope,
149         intrinsicMeasurable: IntrinsicMeasurable,
150         h: Int
151     ): Int {
152         val measurable =
153             DefaultIntrinsicMeasurable(
154                 intrinsicMeasurable,
155                 IntrinsicMinMax.Min,
156                 IntrinsicWidthHeight.Width
157             )
158         val constraints = Constraints(maxHeight = h)
159         val layoutResult =
160             with(measureBlock) {
161                 ApproachIntrinsicsMeasureScope(
162                         intrinsicMeasureScope,
163                         intrinsicMeasureScope.layoutDirection
164                     )
165                     .measure(measurable, constraints)
166             }
167         return layoutResult.width
168     }
169 
170     internal fun minHeight(
171         measureBlock: ApproachMeasureBlock,
172         intrinsicMeasureScope: ApproachIntrinsicMeasureScope,
173         intrinsicMeasurable: IntrinsicMeasurable,
174         w: Int
175     ): Int {
176         val measurable =
177             DefaultIntrinsicMeasurable(
178                 intrinsicMeasurable,
179                 IntrinsicMinMax.Min,
180                 IntrinsicWidthHeight.Height
181             )
182         val constraints = Constraints(maxWidth = w)
183         val layoutResult =
184             with(measureBlock) {
185                 ApproachIntrinsicsMeasureScope(
186                         intrinsicMeasureScope,
187                         intrinsicMeasureScope.layoutDirection
188                     )
189                     .measure(measurable, constraints)
190             }
191         return layoutResult.height
192     }
193 
194     internal fun maxWidth(
195         measureBlock: ApproachMeasureBlock,
196         intrinsicMeasureScope: ApproachIntrinsicMeasureScope,
197         intrinsicMeasurable: IntrinsicMeasurable,
198         h: Int
199     ): Int {
200         val measurable =
201             DefaultIntrinsicMeasurable(
202                 intrinsicMeasurable,
203                 IntrinsicMinMax.Max,
204                 IntrinsicWidthHeight.Width
205             )
206         val constraints = Constraints(maxHeight = h)
207         val layoutResult =
208             with(measureBlock) {
209                 ApproachIntrinsicsMeasureScope(
210                         intrinsicMeasureScope,
211                         intrinsicMeasureScope.layoutDirection
212                     )
213                     .measure(measurable, constraints)
214             }
215         return layoutResult.width
216     }
217 
218     internal fun maxHeight(
219         measureBlock: ApproachMeasureBlock,
220         intrinsicMeasureScope: ApproachIntrinsicMeasureScope,
221         intrinsicMeasurable: IntrinsicMeasurable,
222         w: Int
223     ): Int {
224         val measurable =
225             DefaultIntrinsicMeasurable(
226                 intrinsicMeasurable,
227                 IntrinsicMinMax.Max,
228                 IntrinsicWidthHeight.Height
229             )
230         val constraints = Constraints(maxWidth = w)
231         val layoutResult =
232             with(measureBlock) {
233                 ApproachIntrinsicsMeasureScope(
234                         intrinsicMeasureScope,
235                         intrinsicMeasureScope.layoutDirection
236                     )
237                     .measure(measurable, constraints)
238             }
239         return layoutResult.height
240     }
241 
242     internal fun minWidth(
243         measureBlock: MeasureBlock,
244         intrinsicMeasureScope: IntrinsicMeasureScope,
245         intrinsicMeasurable: IntrinsicMeasurable,
246         h: Int
247     ): Int {
248         val measurable =
249             DefaultIntrinsicMeasurable(
250                 intrinsicMeasurable,
251                 IntrinsicMinMax.Min,
252                 IntrinsicWidthHeight.Width
253             )
254         val constraints = Constraints(maxHeight = h)
255         val layoutResult =
256             with(measureBlock) {
257                 IntrinsicsMeasureScope(intrinsicMeasureScope, intrinsicMeasureScope.layoutDirection)
258                     .measure(measurable, constraints)
259             }
260 
261         return layoutResult.width
262     }
263 
264     internal fun minHeight(
265         measureBlock: MeasureBlock,
266         intrinsicMeasureScope: IntrinsicMeasureScope,
267         intrinsicMeasurable: IntrinsicMeasurable,
268         w: Int
269     ): Int {
270         val measurable =
271             DefaultIntrinsicMeasurable(
272                 intrinsicMeasurable,
273                 IntrinsicMinMax.Min,
274                 IntrinsicWidthHeight.Height
275             )
276         val constraints = Constraints(maxWidth = w)
277         val layoutResult =
278             with(measureBlock) {
279                 IntrinsicsMeasureScope(intrinsicMeasureScope, intrinsicMeasureScope.layoutDirection)
280                     .measure(measurable, constraints)
281             }
282         return layoutResult.height
283     }
284 
285     internal fun maxWidth(
286         measureBlock: MeasureBlock,
287         intrinsicMeasureScope: IntrinsicMeasureScope,
288         intrinsicMeasurable: IntrinsicMeasurable,
289         h: Int
290     ): Int {
291         val measurable =
292             DefaultIntrinsicMeasurable(
293                 intrinsicMeasurable,
294                 IntrinsicMinMax.Max,
295                 IntrinsicWidthHeight.Width
296             )
297         val constraints = Constraints(maxHeight = h)
298         val layoutResult =
299             with(measureBlock) {
300                 IntrinsicsMeasureScope(intrinsicMeasureScope, intrinsicMeasureScope.layoutDirection)
301                     .measure(measurable, constraints)
302             }
303         return layoutResult.width
304     }
305 
306     internal fun maxHeight(
307         measureBlock: MeasureBlock,
308         intrinsicMeasureScope: IntrinsicMeasureScope,
309         intrinsicMeasurable: IntrinsicMeasurable,
310         w: Int
311     ): Int {
312         val measurable =
313             DefaultIntrinsicMeasurable(
314                 intrinsicMeasurable,
315                 IntrinsicMinMax.Max,
316                 IntrinsicWidthHeight.Height
317             )
318         val constraints = Constraints(maxWidth = w)
319         val layoutResult =
320             with(measureBlock) {
321                 IntrinsicsMeasureScope(intrinsicMeasureScope, intrinsicMeasureScope.layoutDirection)
322                     .measure(measurable, constraints)
323             }
324         return layoutResult.height
325     }
326 
327     private class DefaultIntrinsicMeasurable(
328         val measurable: IntrinsicMeasurable,
329         val minMax: IntrinsicMinMax,
330         val widthHeight: IntrinsicWidthHeight
331     ) : Measurable {
332         override val parentData: Any?
333             get() = measurable.parentData
334 
335         override fun measure(constraints: Constraints): Placeable {
336             if (widthHeight == IntrinsicWidthHeight.Width) {
337                 val width =
338                     if (minMax == IntrinsicMinMax.Max) {
339                         measurable.maxIntrinsicWidth(constraints.maxHeight)
340                     } else {
341                         measurable.minIntrinsicWidth(constraints.maxHeight)
342                     }
343                 val height =
344                     if (constraints.hasBoundedHeight) constraints.maxHeight else LargeDimension
345                 return EmptyPlaceable(width, height)
346             }
347             val height =
348                 if (minMax == IntrinsicMinMax.Max) {
349                     measurable.maxIntrinsicHeight(constraints.maxWidth)
350                 } else {
351                     measurable.minIntrinsicHeight(constraints.maxWidth)
352                 }
353             val width = if (constraints.hasBoundedWidth) constraints.maxWidth else LargeDimension
354             return EmptyPlaceable(width, height)
355         }
356 
357         override fun minIntrinsicWidth(height: Int): Int {
358             return measurable.minIntrinsicWidth(height)
359         }
360 
361         override fun maxIntrinsicWidth(height: Int): Int {
362             return measurable.maxIntrinsicWidth(height)
363         }
364 
365         override fun minIntrinsicHeight(width: Int): Int {
366             return measurable.minIntrinsicHeight(width)
367         }
368 
369         override fun maxIntrinsicHeight(width: Int): Int {
370             return measurable.maxIntrinsicHeight(width)
371         }
372     }
373 
374     private class EmptyPlaceable(width: Int, height: Int) : Placeable() {
375         init {
376             measuredSize = IntSize(width, height)
377         }
378 
379         override fun get(alignmentLine: AlignmentLine): Int = AlignmentLine.Unspecified
380 
381         override fun placeAt(
382             position: IntOffset,
383             zIndex: Float,
384             layerBlock: (GraphicsLayerScope.() -> Unit)?
385         ) {}
386     }
387 
388     private enum class IntrinsicMinMax {
389         Min,
390         Max
391     }
392 
393     private enum class IntrinsicWidthHeight {
394         Width,
395         Height
396     }
397 }
398