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