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 package androidx.compose.ui.node
18
19 import androidx.compose.runtime.collection.MutableVector
20 import androidx.compose.ui.layout.AlignmentLine
21 import androidx.compose.ui.layout.Measurable
22 import androidx.compose.ui.layout.Placeable
23 import androidx.compose.ui.node.LayoutNode.LayoutState
24 import androidx.compose.ui.unit.Constraints
25
26 /**
27 * This class works as a layout delegate for [LayoutNode]. It delegates all the measure/layout
28 * requests to its [measurePassDelegate] and [lookaheadPassDelegate] depending on whether the
29 * request is specific to lookahead.
30 */
31 internal class LayoutNodeLayoutDelegate(
32 internal val layoutNode: LayoutNode,
33 ) {
34 val outerCoordinator: NodeCoordinator
35 get() = layoutNode.nodes.outerCoordinator
36
37 val lastConstraints: Constraints?
38 get() = measurePassDelegate.lastConstraints
39
40 val lastLookaheadConstraints: Constraints?
41 get() = lookaheadPassDelegate?.lastConstraints
42
43 internal val height: Int
44 get() = measurePassDelegate.height
45
46 internal val width: Int
47 get() = measurePassDelegate.width
48
49 /**
50 * Marks that a LayoutNode will not participate in its parent's lookahead pass. More
51 * specifically, when the parent does lookahead measurement and placement, the child that is
52 * marked [detachedFromParentLookaheadPass] will be skipped. The lookahead measurement and
53 * placement will instead happen during approach, as if the node was the lookahead root. This is
54 * needed in SubcomposeLayout where some content is only composed in approach pass, therefore
55 * they have already missed their parents' lookahead pass. They will instead do a make-up
56 * lookahead measurement/placement in approach.
57 *
58 * This gets set to true via [MeasurePassDelegate.markDetachedFromParentLookaheadPass] and
59 * automatically gets unset in `measure` when the measure call comes from parent with
60 * layoutState being LookaheadMeasuring or LookaheadLayingOut.
61 *
62 * Note: This is different than [detachedFromParentLookaheadPlacement] in that the node is
63 * detached from both parent's lookahead measurement and placement.
64 */
65 internal var detachedFromParentLookaheadPass: Boolean = false
66
67 /**
68 * This is a flag indicating the node is not going to be placed in the parent's lookahead, but
69 * only placed during parent's approach. This is needed because in SubcomposeLayout, a custom
70 * measure policy may consider placement unnecessary after measuring the content in lookahead.
71 * However, the custom measure policy may require placement of the same content during approach,
72 * due to different measured size in approach vs. lookahead. As such, we are marking the nodes
73 * that were skipped by its parent's lookahead placement, so that we can do a make-up lookahead
74 * placement later in approach.
75 *
76 * This gets set to `true` when the node is SubComposeLayout's direct children (i.e. folded
77 * children of the SubComposeLayout's root) && not placed in lookahead but needs to be placed in
78 * approach. [detachedFromParentLookaheadPlacement] gets reset to false when there is a
79 * lookahead placement call coming from parent.
80 *
81 * Note: When [detachedFromParentLookaheadPlacement] is true, it is implied that the lookahead
82 * measurement is still triggered by its parent's lookahead measure. In contrast,
83 * [detachedFromParentLookaheadPass] indicates that both measurement and placement are detached
84 * from parent's lookahead.
85 */
86 internal var detachedFromParentLookaheadPlacement: Boolean = false
87
88 /**
89 * The layout state the node is currently in.
90 *
91 * The mutation of [layoutState] is confined to [LayoutNodeLayoutDelegate], and is therefore
92 * read-only outside this class. This makes the state machine easier to reason about.
93 */
94 internal var layoutState = LayoutState.Idle
95
96 /**
97 * Tracks whether another measure pass is needed for the LayoutNodeLayoutDelegate. Mutation to
98 * [measurePending] is confined to LayoutNodeLayoutDelegate. It can only be set true from
99 * outside of this class via [markMeasurePending]. It is cleared (i.e. set false) during the
100 * measure pass (i.e. in [measurePassDelegate.performMeasure]).
101 */
102 internal val measurePending: Boolean
103 get() = measurePassDelegate.measurePending
104
105 /**
106 * Tracks whether another layout pass is needed for the LayoutNodeLayoutDelegate. Mutation to
107 * [layoutPending] is confined to this class. It can only be set true from outside of this class
108 * via [markLayoutPending]. It is cleared (i.e. set false) during the layout pass (i.e. in
109 * [MeasurePassDelegate.layoutChildren]).
110 */
111 internal val layoutPending: Boolean
112 get() = measurePassDelegate.layoutPending
113
114 /**
115 * Tracks whether another lookahead measure pass is needed for the LayoutNodeLayoutDelegate.
116 * Mutation to [lookaheadMeasurePending] is confined to LayoutNodeLayoutDelegate. It can only be
117 * set true from outside of this class via [markLookaheadMeasurePending]. It is cleared (i.e.
118 * set false) during the lookahead measure pass (i.e. in [performLookaheadMeasure]).
119 */
120 internal var lookaheadMeasurePending: Boolean = false
121
122 /**
123 * Tracks whether another lookahead layout pass is needed for the LayoutNodeLayoutDelegate.
124 * Mutation to [lookaheadLayoutPending] is confined to this class. It can only be set true from
125 * outside of this class via [markLookaheadLayoutPending]. It is cleared (i.e. set false) during
126 * the layout pass (i.e. in [LookaheadPassDelegate.layoutChildren]).
127 */
128 internal var lookaheadLayoutPending: Boolean = false
129
130 /**
131 * Tracks whether another lookahead layout pass is needed for the LayoutNodeLayoutDelegate for
132 * the purposes of calculating alignment lines. After calculating alignment lines, if the
133 * [Placeable.PlacementScope.coordinates] have been accessed, there is no need to rerun layout
134 * for further alignment lines checks, but [lookaheadLayoutPending] will indicate that the
135 * normal placement still needs to be run.
136 */
137 internal var lookaheadLayoutPendingForAlignment = false
138
139 /**
140 * The counter on a parent node which is used by its children to understand the order in which
141 * they were placed in the lookahead pass.
142 */
143 internal var nextChildLookaheadPlaceOrder: Int = 0
144
145 /**
146 * The counter on a parent node which is used by its children to understand the order in which
147 * they were placed in the main pass.
148 */
149 internal var nextChildPlaceOrder: Int = 0
150
151 /** Marks the layoutNode dirty for another layout pass. */
152 internal fun markLayoutPending() {
153 measurePassDelegate.markLayoutPending()
154 }
155
156 /** Marks the layoutNode dirty for another measure pass. */
157 internal fun markMeasurePending() {
158 measurePassDelegate.markMeasurePending()
159 }
160
161 /** Marks the layoutNode dirty for another lookahead layout pass. */
162 internal fun markLookaheadLayoutPending() {
163 lookaheadLayoutPending = true
164 lookaheadLayoutPendingForAlignment = true
165 }
166
167 /** Marks the layoutNode dirty for another lookahead measure pass. */
168 internal fun markLookaheadMeasurePending() {
169 lookaheadMeasurePending = true
170 }
171
172 internal val alignmentLinesOwner: AlignmentLinesOwner
173 get() = measurePassDelegate
174
175 internal val lookaheadAlignmentLinesOwner: AlignmentLinesOwner?
176 get() = lookaheadPassDelegate
177
178 /**
179 * This is used to track when the [Placeable.PlacementScope.coordinates] have been accessed
180 * while placement is run. When the coordinates are accessed during an alignment line query, it
181 * indicates that the placement is not final and must be run again so that the correct
182 * positioning is done. If the coordinates are not accessed during an alignment lines query (and
183 * it isn't just a [LookaheadCapablePlaceable.isShallowPlacing]), then the placement can be
184 * considered final and doesn't have to be run again.
185 *
186 * Also, if coordinates are accessed during placement, then a change in parent coordinates
187 * requires placement to be run again.
188 */
189 var coordinatesAccessedDuringPlacement = false
190 set(value) {
191 val oldValue = field
192 if (oldValue != value) {
193 field = value
194 if (value && !coordinatesAccessedDuringModifierPlacement) {
195 // if first out of both flags changes to true increment
196 childrenAccessingCoordinatesDuringPlacement++
197 } else if (!value && !coordinatesAccessedDuringModifierPlacement) {
198 // if both flags changes to false decrement
199 childrenAccessingCoordinatesDuringPlacement--
200 }
201 }
202 }
203
204 /**
205 * Similar to [coordinatesAccessedDuringPlacement], but tracks the coordinates read happening
206 * during the modifier layout blocks run.
207 */
208 var coordinatesAccessedDuringModifierPlacement = false
209 set(value) {
210 val oldValue = field
211 if (oldValue != value) {
212 field = value
213 if (value && !coordinatesAccessedDuringPlacement) {
214 // if first out of both flags changes to true increment
215 childrenAccessingCoordinatesDuringPlacement++
216 } else if (!value && !coordinatesAccessedDuringPlacement) {
217 // if both flags changes to false decrement
218 childrenAccessingCoordinatesDuringPlacement--
219 }
220 }
221 }
222
223 /**
224 * The number of children with [coordinatesAccessedDuringPlacement] or have descendants with
225 * [coordinatesAccessedDuringPlacement]. This also includes this, if
226 * [coordinatesAccessedDuringPlacement] is `true`.
227 */
228 var childrenAccessingCoordinatesDuringPlacement = 0
229 set(value) {
230 val oldValue = field
231 field = value
232 if ((oldValue == 0) != (value == 0)) {
233 // A child is either newly listening for coordinates or stopped listening
234 val parentLayoutDelegate = layoutNode.parent?.layoutDelegate
235 if (parentLayoutDelegate != null) {
236 if (value == 0) {
237 parentLayoutDelegate.childrenAccessingCoordinatesDuringPlacement--
238 } else {
239 parentLayoutDelegate.childrenAccessingCoordinatesDuringPlacement++
240 }
241 }
242 }
243 }
244
245 /** Equivalent flag of [coordinatesAccessedDuringPlacement] but for [lookaheadPassDelegate]. */
246 var lookaheadCoordinatesAccessedDuringPlacement = false
247 set(value) {
248 val oldValue = field
249 if (oldValue != value) {
250 field = value
251 if (value && !lookaheadCoordinatesAccessedDuringModifierPlacement) {
252 // if first out of both flags changes to true increment
253 childrenAccessingLookaheadCoordinatesDuringPlacement++
254 } else if (!value && !lookaheadCoordinatesAccessedDuringModifierPlacement) {
255 // if both flags changes to false decrement
256 childrenAccessingLookaheadCoordinatesDuringPlacement--
257 }
258 }
259 }
260
261 /**
262 * Equivalent flag of [coordinatesAccessedDuringModifierPlacement] but for
263 * [lookaheadPassDelegate].
264 */
265 var lookaheadCoordinatesAccessedDuringModifierPlacement = false
266 set(value) {
267 val oldValue = field
268 if (oldValue != value) {
269 field = value
270 if (value && !lookaheadCoordinatesAccessedDuringPlacement) {
271 // if first out of both flags changes to true increment
272 childrenAccessingLookaheadCoordinatesDuringPlacement++
273 } else if (!value && !lookaheadCoordinatesAccessedDuringPlacement) {
274 // if both flags changes to false decrement
275 childrenAccessingLookaheadCoordinatesDuringPlacement--
276 }
277 }
278 }
279
280 /**
281 * Equivalent flag of [childrenAccessingCoordinatesDuringPlacement] but for
282 * [lookaheadPassDelegate].
283 *
284 * Naturally, this flag should only be affected by the lookahead coordinates access flags.
285 */
286 var childrenAccessingLookaheadCoordinatesDuringPlacement = 0
287 set(value) {
288 val oldValue = field
289 field = value
290 if ((oldValue == 0) != (value == 0)) {
291 // A child is either newly listening for coordinates or stopped listening
292 val parentLayoutDelegate = layoutNode.parent?.layoutDelegate
293 if (parentLayoutDelegate != null) {
294 if (value == 0) {
295 parentLayoutDelegate.childrenAccessingLookaheadCoordinatesDuringPlacement--
296 } else {
297 parentLayoutDelegate.childrenAccessingLookaheadCoordinatesDuringPlacement++
298 }
299 }
300 }
301 }
302
303 /**
304 * measurePassDelegate manages the measure/layout and alignmentLine related queries for the
305 * actual measure/layout pass.
306 */
307 internal val measurePassDelegate = MeasurePassDelegate(this)
308
309 /**
310 * lookaheadPassDelegate manages the measure/layout and alignmentLine related queries for the
311 * lookahead pass.
312 */
313 internal var lookaheadPassDelegate: LookaheadPassDelegate? = null
314 private set
315
316 fun onCoordinatesUsed() {
317 val state = layoutNode.layoutState
318 if (state == LayoutState.LayingOut || state == LayoutState.LookaheadLayingOut) {
319 if (measurePassDelegate.layingOutChildren) {
320 coordinatesAccessedDuringPlacement = true
321 } else {
322 coordinatesAccessedDuringModifierPlacement = true
323 }
324 }
325 if (state == LayoutState.LookaheadLayingOut) {
326 if (lookaheadPassDelegate?.layingOutChildren == true) {
327 lookaheadCoordinatesAccessedDuringPlacement = true
328 } else {
329 lookaheadCoordinatesAccessedDuringModifierPlacement = true
330 }
331 }
332 }
333
334 internal fun performLookaheadMeasure(constraints: Constraints) {
335 lookaheadPassDelegate?.performMeasure(constraints)
336 }
337
338 internal fun ensureLookaheadDelegateCreated() {
339 if (lookaheadPassDelegate == null) {
340 lookaheadPassDelegate = LookaheadPassDelegate(this)
341 }
342 }
343
344 fun updateParentData() {
345 if (measurePassDelegate.updateParentData()) {
346 layoutNode.parent?.requestRemeasure()
347 }
348 if (lookaheadPassDelegate?.updateParentData() == true) {
349 if (layoutNode.isOutMostLookaheadRoot) {
350 layoutNode.parent?.requestRemeasure()
351 } else {
352 layoutNode.parent?.requestLookaheadRemeasure()
353 }
354 }
355 }
356
357 fun invalidateParentData() {
358 measurePassDelegate.invalidateParentData()
359 lookaheadPassDelegate?.invalidateParentData()
360 }
361
362 fun resetAlignmentLines() {
363 measurePassDelegate.alignmentLines.reset()
364 lookaheadPassDelegate?.alignmentLines?.reset()
365 }
366
367 fun markChildrenDirty() {
368 measurePassDelegate.childDelegatesDirty = true
369 lookaheadPassDelegate?.let { it.childDelegatesDirty = true }
370 }
371
372 fun onRemovedFromLookaheadScope() {
373 lookaheadPassDelegate = null
374 // Clear lookahead invalidations when a LayoutNode is moved out of LookaheadScope.
375 lookaheadLayoutPending = false
376 lookaheadMeasurePending = false
377 }
378 }
379
380 /**
381 * Returns if the we are at the lookahead root of the tree, by checking if the parent is has a
382 * lookahead root.
383 */
384 internal val LayoutNode.isOutMostLookaheadRoot: Boolean
385 get() =
386 lookaheadRoot != null &&
387 (parent?.lookaheadRoot == null || layoutDelegate.detachedFromParentLookaheadPass)
388
updateChildMeasurablesnull389 internal inline fun <T : Measurable> LayoutNode.updateChildMeasurables(
390 destination: MutableVector<T>,
391 transform: (LayoutNode) -> T
392 ) {
393 forEachChildIndexed { i, layoutNode ->
394 if (destination.size <= i) {
395 destination.add(transform(layoutNode))
396 } else {
397 destination[i] = transform(layoutNode)
398 }
399 }
400 destination.removeRange(children.size, destination.size)
401 }
402
403 internal const val MeasuredTwiceErrorMessage: String =
404 "measure() may not be called multiple times on the same Measurable. If you want to " +
405 "get the content size of the Measurable before calculating the final constraints, " +
406 "please use methods like minIntrinsicWidth()/maxIntrinsicWidth() and " +
407 "minIntrinsicHeight()/maxIntrinsicHeight()"
408
409 /**
410 * AlignmentLinesOwner defines APIs that are needed to respond to alignment line changes, and to
411 * query alignment line related info.
412 *
413 * [LookaheadPassDelegate] and [MeasurePassDelegate] both implement this interface, and they
414 * encapsulate the difference in alignment lines handling for lookahead pass vs. actual
415 * measure/layout pass.
416 */
417 internal interface AlignmentLinesOwner : Measurable {
418 /** Whether the AlignmentLinesOwner has been placed. */
419 val isPlaced: Boolean
420
421 /** InnerNodeCoordinator of the LayoutNode that the AlignmentLinesOwner operates on. */
422 val innerCoordinator: NodeCoordinator
423
424 /**
425 * Alignment lines for either lookahead pass or post-lookahead pass, depending on the
426 * AlignmentLineOwner.
427 */
428 val alignmentLines: AlignmentLines
429
430 /**
431 * The implementation for laying out children. Different types of AlignmentLinesOwner will
432 * layout children for either the lookahead pass, or the layout pass post-lookahead.
433 */
layoutChildrennull434 fun layoutChildren()
435
436 /** Recalculate the alignment lines if dirty, and layout children as needed. */
437 fun calculateAlignmentLines(): Map<AlignmentLine, Int>
438
439 /**
440 * Parent [AlignmentLinesOwner]. This will be the AlignmentLinesOwner for the same pass but for
441 * the parent [LayoutNode].
442 */
443 val parentAlignmentLinesOwner: AlignmentLinesOwner?
444
445 /**
446 * This allows iterating all the AlignmentOwners for the same pass for each of the child
447 * LayoutNodes
448 */
449 fun forEachChildAlignmentLinesOwner(block: (AlignmentLinesOwner) -> Unit)
450
451 /**
452 * Depending on which pass the [AlignmentLinesOwner] is created for, this could mean
453 * requestLookaheadLayout() for the lookahead pass, or requestLayout() for post- lookahead pass.
454 */
455 fun requestLayout()
456
457 /**
458 * Depending on which pass the [AlignmentLinesOwner] is created for, this could mean
459 * requestLookaheadMeasure() for the lookahead pass, or requestMeasure() for post- lookahead
460 * pass.
461 */
462 fun requestMeasure()
463 }
464
465 /**
466 * Interface for layout delegates, so that they can set the
467 * [LookaheadCapablePlaceable.isPlacedUnderMotionFrameOfReference] to the proper placeable.
468 */
469 internal interface MotionReferencePlacementDelegate {
470 /**
471 * Called when a layout is about to be placed.
472 *
473 * This updates the corresponding [LookaheadCapablePlaceable]'s
474 * [LookaheadCapablePlaceable.isPlacedUnderMotionFrameOfReference] flag IF AND ONLY IF the
475 * placement call comes from parent [LookaheadCapablePlaceable]. More specifically, for
476 * [LookaheadCapablePlaceable] that are the head of the modifier chain (e.g. outerCoordinator),
477 * the placement call doesn't always come from the parent, as the node can be independently
478 * replaced. For these [LookaheadCapablePlaceable], we maintain the old
479 * [isPlacedUnderMotionFrameOfReference] until the next placement call comes from the parent.
480 * This reason is that only placement from parent runs the placement lambda where
481 * [Placeable.PlacementScope.withMotionFrameOfReferencePlacement] is invoked. Also note, for
482 * [LookaheadCapablePlaceable] that are not the head of the modifier chain, the placement call
483 * always comes from the parent.
484 *
485 * The placeable should be tagged such that its corresponding coordinates reflect the flag in
486 * [androidx.compose.ui.layout.LayoutCoordinates.introducesMotionFrameOfReference]. Note that
487 * when it's placed on the current frame of reference, it means it doesn't introduce a new frame
488 * of reference.
489 */
490 fun updatePlacedUnderMotionFrameOfReference(newMFR: Boolean)
491
492 /**
493 * Flag to indicate whether the [MotionReferencePlacementDelegate] is being placed under motion
494 * frame of reference. This is also reflected in
495 * [androidx.compose.ui.layout.LayoutCoordinates.introducesMotionFrameOfReference], which is
496 * used for local position calculation between coordinates.
497 */
498 val isPlacedUnderMotionFrameOfReference: Boolean
499 }
500