1 /*
<lambda>null2  * Copyright 2024 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.graphics.GraphicsLayerScope
21 import androidx.compose.ui.graphics.layer.GraphicsLayer
22 import androidx.compose.ui.internal.checkPrecondition
23 import androidx.compose.ui.internal.requirePrecondition
24 import androidx.compose.ui.layout.AlignmentLine
25 import androidx.compose.ui.layout.Measurable
26 import androidx.compose.ui.layout.Placeable
27 import androidx.compose.ui.node.LayoutNode.Companion.NotPlacedPlaceOrder
28 import androidx.compose.ui.node.LayoutNode.LayoutState
29 import androidx.compose.ui.unit.Constraints
30 import androidx.compose.ui.unit.IntOffset
31 import androidx.compose.ui.unit.IntSize
32 import androidx.compose.ui.util.fastForEach
33 
34 /**
35  * [LookaheadPassDelegate] manages the measure/layout and alignmentLine related queries for the
36  * lookahead pass.
37  */
38 internal class LookaheadPassDelegate(
39     private val layoutNodeLayoutDelegate: LayoutNodeLayoutDelegate
40 ) : Placeable(), Measurable, AlignmentLinesOwner, MotionReferencePlacementDelegate {
41 
42     private enum class PlacedState {
43         IsPlacedInLookahead,
44         IsPlacedInApproach,
45         IsNotPlaced
46     }
47 
48     /**
49      * Tracks whether another lookahead measure pass is needed for the LayoutNodeLayoutDelegate.
50      * Mutation to [measurePending] is confined to LookaheadPassDelegate. It can only be set true
51      * from outside of this class via [markMeasurePending]. It is cleared (i.e. set false) during
52      * the lookahead measure pass (i.e. in [performMeasure]).
53      */
54     // TODO: Make LookaheadPassDelegate#measure/layoutPending as the source of truth rather than
55     // the other way around (i.e. relying on [LayoutNodeLayoutDelegate]) once b/384579646 is fixed.
56     private var measurePending: Boolean
57         private set(value) {
58             layoutNodeLayoutDelegate.lookaheadMeasurePending = value
59         }
60         get() = layoutNodeLayoutDelegate.lookaheadMeasurePending
61 
62     /**
63      * Tracks whether another lookahead layout pass is needed for the LayoutNodeLayoutDelegate.
64      * Mutation to [layoutPending] is confined to this class. It can only be set true from outside
65      * of this class via [markLayoutPending]. It is cleared (i.e. set false) during the layout pass
66      * (i.e. in [LookaheadPassDelegate.layoutChildren]).
67      */
68     private var layoutPending: Boolean
69         private set(value) {
70             layoutNodeLayoutDelegate.lookaheadLayoutPending = value
71         }
72         get() = layoutNodeLayoutDelegate.lookaheadLayoutPending
73 
74     /**
75      * Tracks whether another lookahead layout pass is needed for the LayoutNodeLayoutDelegate for
76      * the purposes of calculating alignment lines. After calculating alignment lines, if the
77      * [Placeable.PlacementScope.coordinates] have been accessed, there is no need to rerun layout
78      * for further alignment lines checks, but [layoutPending] will indicate that the normal
79      * placement still needs to be run.
80      */
81     private var layoutPendingForAlignment: Boolean
82         private set(value) {
83             layoutNodeLayoutDelegate.lookaheadLayoutPendingForAlignment = value
84         }
85         get() = layoutNodeLayoutDelegate.lookaheadLayoutPendingForAlignment
86 
87     private val layoutNode: LayoutNode
88         get() = layoutNodeLayoutDelegate.layoutNode
89 
90     internal fun markLayoutPending() {
91         layoutPending = true
92         layoutPendingForAlignment = true
93     }
94 
95     /** Marks the layoutNode dirty for another lookahead measure pass. */
96     internal fun markMeasurePending() {
97         measurePending = true
98     }
99 
100     /**
101      * Is true during [replace] invocation. Helps to differentiate between the cases when our parent
102      * is measuring us during the measure block, and when we are remeasured individually because of
103      * some change. This could be useful to know if we need to record the placing order.
104      */
105     private var relayoutWithoutParentInProgress: Boolean = false
106 
107     /**
108      * The value [placeOrder] had during the previous parent `layoutChildren`. Helps us to
109      * understand if the order did change.
110      */
111     private var previousPlaceOrder: Int = NotPlacedPlaceOrder
112 
113     /**
114      * The order in which this node was placed by its parent during the previous `layoutChildren`.
115      * Before the placement the order is set to [NotPlacedPlaceOrder] to all the children. Then
116      * every placed node assigns this variable to parent's LayoutNodeLayoutDelegate's
117      * nextChildLookaheadPlaceOrder and increments this counter. Not placed items will still have
118      * [NotPlacedPlaceOrder] set.
119      */
120     internal var placeOrder: Int = NotPlacedPlaceOrder
121 
122     internal var measuredByParent = LayoutNode.UsageByParent.NotUsed
123     internal val measurePassDelegate: MeasurePassDelegate
124         get() = layoutNodeLayoutDelegate.measurePassDelegate
125 
126     private val outerCoordinator
127         get() = layoutNodeLayoutDelegate.outerCoordinator
128 
129     private var layoutState
130         set(value) {
131             layoutNodeLayoutDelegate.layoutState = value
132         }
133         get() = layoutNodeLayoutDelegate.layoutState
134 
135     private var duringAlignmentLinesQuery: Boolean = false
136     internal var placedOnce: Boolean = false
137     private var measuredOnce: Boolean = false
138     val lastConstraints: Constraints?
139         get() = lookaheadConstraints
140 
141     private var lookaheadConstraints: Constraints? = null
142     private var lastPosition: IntOffset = IntOffset.Zero
143 
144     private var lastZIndex: Float = 0f
145 
146     private var lastLayerBlock: (GraphicsLayerScope.() -> Unit)? = null
147 
148     private var lastExplicitLayer: GraphicsLayer? = null
149 
150     override val isPlaced: Boolean
151         get() = _placedState != PlacedState.IsNotPlaced
152 
153     private var _placedState: PlacedState = PlacedState.IsNotPlaced
154 
155     override val innerCoordinator: NodeCoordinator
156         get() = layoutNode.innerCoordinator
157 
158     override val alignmentLines: AlignmentLines = LookaheadAlignmentLines(this)
159 
160     private val _childDelegates = MutableVector<LookaheadPassDelegate>()
161 
162     /**
163      * This property indicates whether the lookahead pass delegate needs to be placed in the
164      * approach pass. In the vast majority cases LookaheadPassDelegate is placed in the lookahead
165      * pass. The two scenarios that leads to it being placed in approach are:
166      * 1) The layout node is a lookahead root. The lookahead pass therefore has to be initiated by
167      *    the node itself (rather than participating in parent's lookahead pass).
168      * 2) When the parent skips the child in its lookahead placement. This can happen when the
169      *    parent's placement logic intentionally skips placing the child during lookahead after
170      *    measuring it.
171      */
172     val needsToBePlacedInApproach: Boolean
173         get() =
174             if (layoutNode.isOutMostLookaheadRoot) {
175                 true
176             } else {
177                 if (
178                     _placedState == PlacedState.IsNotPlaced &&
179                         !layoutNodeLayoutDelegate.detachedFromParentLookaheadPass
180                 ) {
181                     // Never placed in lookahead. Since this node is not completely detached
182                     // from
183                     // parent's lookahead pass (i.e. properly measured during parent's
184                     // lookahead),
185                     // mark only placement detached from lookahead.
186                     layoutNodeLayoutDelegate.detachedFromParentLookaheadPlacement = true
187                 }
188                 detachedFromParentLookaheadPlacement
189             }
190 
191     internal var childDelegatesDirty: Boolean = true
192 
193     /** [Measurable]s provided to layout during lookahead pass. */
194     internal val childDelegates: List<LookaheadPassDelegate>
195         get() {
196             layoutNode.children.let {
197                 // Invoke children to get children updated before checking dirty
198                 if (!childDelegatesDirty) return _childDelegates.asMutableList()
199             }
200             layoutNode.updateChildMeasurables(_childDelegates) {
201                 it.layoutDelegate.lookaheadPassDelegate!!
202             }
203             childDelegatesDirty = false
204             return _childDelegates.asMutableList()
205         }
206 
207     var layingOutChildren = false
208         private set
209 
210     private inline fun forEachChildDelegate(block: (LookaheadPassDelegate) -> Unit) =
211         layoutNode.forEachChild { block(it.layoutDelegate.lookaheadPassDelegate!!) }
212 
213     override fun layoutChildren() {
214         layingOutChildren = true
215         alignmentLines.recalculateQueryOwner()
216 
217         if (layoutPending) {
218             onBeforeLayoutChildren()
219         }
220         val lookaheadDelegate = innerCoordinator.lookaheadDelegate!!
221         // as a result of the previous operation we can figure out a child has been resized
222         // and we need to be remeasured, not relaid out
223         if (
224             layoutPendingForAlignment ||
225                 (!duringAlignmentLinesQuery &&
226                     !lookaheadDelegate.isPlacingForAlignment &&
227                     layoutPending)
228         ) {
229             layoutPending = false
230             val oldLayoutState = layoutState
231             layoutState = LayoutState.LookaheadLayingOut
232             val owner = layoutNode.requireOwner()
233             layoutNodeLayoutDelegate.lookaheadCoordinatesAccessedDuringPlacement = false
234             owner.snapshotObserver.observeLayoutSnapshotReads(layoutNode) {
235                 clearPlaceOrder()
236                 forEachChildAlignmentLinesOwner { child ->
237                     child.alignmentLines.usedDuringParentLayout = false
238                 }
239                 innerCoordinator.lookaheadDelegate?.isPlacingForAlignment?.let { forAlignment ->
240                     layoutNode.children.fastForEach {
241                         it.outerCoordinator.lookaheadDelegate?.isPlacingForAlignment = forAlignment
242                     }
243                 }
244                 lookaheadDelegate.measureResult.placeChildren()
245                 innerCoordinator.lookaheadDelegate?.isPlacingForAlignment?.let { _ ->
246                     layoutNode.children.fastForEach {
247                         it.outerCoordinator.lookaheadDelegate?.isPlacingForAlignment = false
248                     }
249                 }
250                 checkChildrenPlaceOrderForUpdates()
251                 forEachChildAlignmentLinesOwner { child ->
252                     child.alignmentLines.previousUsedDuringParentLayout =
253                         child.alignmentLines.usedDuringParentLayout
254                 }
255             }
256             layoutState = oldLayoutState
257             if (
258                 layoutNodeLayoutDelegate.lookaheadCoordinatesAccessedDuringPlacement &&
259                     lookaheadDelegate.isPlacingForAlignment
260             ) {
261                 requestLayout()
262             }
263             layoutPendingForAlignment = false
264         }
265         if (alignmentLines.usedDuringParentLayout) {
266             alignmentLines.previousUsedDuringParentLayout = true
267         }
268         if (alignmentLines.dirty && alignmentLines.required) alignmentLines.recalculate()
269 
270         layingOutChildren = false
271     }
272 
273     private val detachedFromParentLookaheadPlacement
274         get() = layoutNodeLayoutDelegate.detachedFromParentLookaheadPlacement
275 
276     private fun checkChildrenPlaceOrderForUpdates() {
277         forEachChildDelegate { child ->
278             // we set `placeOrder` to NotPlacedPlaceOrder for all the children, then
279             // during the placeChildren() invocation the real order will be assigned for
280             // all the placed children.
281             if (child.previousPlaceOrder != child.placeOrder) {
282                 if (child.placeOrder == NotPlacedPlaceOrder) {
283                     child.markNodeAndSubtreeAsNotPlaced(inLookahead = true)
284                 }
285             }
286         }
287     }
288 
289     /**
290      * Marks the subtree as not placed. When hitting a node that is detached from parent's lookahead
291      * placement, both the node and subtree is skipped in the recursion. Their placedState will be
292      * updated during approach.
293      *
294      * Note: This is not perfectly symmetric to markNodeAndSubtreeAsPlaced, because to consider a
295      * node placed, we only need one confirmation of placing the node. In contrast, to say a node is
296      * not placed, we need both passes to not place it.
297      */
298     internal fun markNodeAndSubtreeAsNotPlaced(inLookahead: Boolean) {
299         if (
300             (inLookahead && detachedFromParentLookaheadPlacement) ||
301                 (!inLookahead && !detachedFromParentLookaheadPlacement)
302         ) {
303             // Not in the right pass. No-op
304             return
305         }
306 
307         _placedState = PlacedState.IsNotPlaced
308 
309         // If the node is detached from parent's lookahead placement, it means it gets placed
310         // during approach. We therefore don't take placement signals from parent in
311         // lookahead pass, but defer that until approach pass.
312         // Note: when we propagate this call downward in the tree, we mark `inLookahead` as
313         // true, because from the children's perspective this call does indeed come from
314         // their parent's lookahead, even though parent's lookahead is triggered by its
315         // parent's approach.
316         forEachChildDelegate { it.markNodeAndSubtreeAsNotPlaced(true) }
317     }
318 
319     override fun calculateAlignmentLines(): Map<AlignmentLine, Int> {
320         if (!duringAlignmentLinesQuery) {
321             if (layoutState == LayoutState.LookaheadMeasuring) {
322                 // Mark alignments used by modifier
323                 alignmentLines.usedByModifierMeasurement = true
324                 // We quickly transition to layoutPending as we need the alignment lines now.
325                 // Later we will see that we also laid out as part of measurement and will skip
326                 // layout.
327                 if (alignmentLines.dirty) layoutNodeLayoutDelegate.markLookaheadLayoutPending()
328             } else {
329                 // Note this can also happen for onGloballyPositioned queries.
330                 alignmentLines.usedByModifierLayout = true
331             }
332         }
333         innerCoordinator.lookaheadDelegate?.isPlacingForAlignment = true
334         layoutChildren()
335         innerCoordinator.lookaheadDelegate?.isPlacingForAlignment = false
336         return alignmentLines.getLastCalculation()
337     }
338 
339     override val parentAlignmentLinesOwner: AlignmentLinesOwner?
340         get() = layoutNode.parent?.layoutDelegate?.lookaheadAlignmentLinesOwner
341 
342     override fun forEachChildAlignmentLinesOwner(block: (AlignmentLinesOwner) -> Unit) {
343         layoutNode.forEachChild { block(it.layoutDelegate.lookaheadAlignmentLinesOwner!!) }
344     }
345 
346     override fun requestLayout() {
347         layoutNode.requestLookaheadRelayout()
348     }
349 
350     override fun requestMeasure() {
351         layoutNode.requestLookaheadRemeasure()
352     }
353 
354     /**
355      * This is called any time a placement has done that changes the position during the lookahead
356      * layout pass. If any child is looking at their own coordinates to know how to place children,
357      * it will be invalided.
358      *
359      * Note that this is called for every changed position. While not many layouts look at their
360      * coordinates, if there is one, it will cause all position changes from an ancestor to call
361      * down the hierarchy. If this becomes expensive (e.g. many parents change their position on the
362      * same frame), it might be worth using a flag so that this call becomes cheap after the first
363      * one.
364      */
365     fun notifyChildrenUsingLookaheadCoordinatesWhilePlacing() {
366         if (layoutNodeLayoutDelegate.childrenAccessingLookaheadCoordinatesDuringPlacement > 0) {
367             layoutNode.forEachChild { child ->
368                 val childLayoutDelegate = child.layoutDelegate
369                 val accessed =
370                     childLayoutDelegate.lookaheadCoordinatesAccessedDuringPlacement ||
371                         childLayoutDelegate.lookaheadCoordinatesAccessedDuringModifierPlacement
372                 if (accessed && !childLayoutDelegate.lookaheadLayoutPending) {
373                     child.requestLookaheadRelayout()
374                 }
375                 childLayoutDelegate.lookaheadPassDelegate
376                     ?.notifyChildrenUsingLookaheadCoordinatesWhilePlacing()
377             }
378         }
379     }
380 
381     override fun measure(constraints: Constraints): Placeable {
382         if (
383             layoutNode.parent?.layoutState == LayoutState.LookaheadMeasuring ||
384                 layoutNode.parent?.layoutState == LayoutState.LookaheadLayingOut
385         ) {
386             layoutNodeLayoutDelegate.detachedFromParentLookaheadPass = false
387         }
388         trackLookaheadMeasurementByParent(layoutNode)
389         if (layoutNode.intrinsicsUsageByParent == LayoutNode.UsageByParent.NotUsed) {
390             // This LayoutNode may have asked children for intrinsics. If so, we should
391             // clear the intrinsics usage for everything that was requested previously.
392             layoutNode.clearSubtreeIntrinsicsUsage()
393         }
394         // Since this a measure request coming from the parent. We'd be starting lookahead
395         // only if the current layoutNode is the top-level lookahead root.
396         // This is an optimization to avoid redundant Snapshot.enter when creating new snapshots
397         // for lookahead, in order to reduce the size of the call stack.
398         remeasure(constraints)
399         return this
400     }
401 
402     // Track lookahead measurement
403     private fun trackLookaheadMeasurementByParent(node: LayoutNode) {
404         // when we measure the root it is like the virtual parent is currently laying out
405         val parent = node.parent
406         if (parent != null) {
407             checkPrecondition(
408                 measuredByParent == LayoutNode.UsageByParent.NotUsed ||
409                     @Suppress("DEPRECATION") node.canMultiMeasure
410             ) {
411                 MeasuredTwiceErrorMessage
412             }
413             measuredByParent =
414                 when (parent.layoutState) {
415                     LayoutState.LookaheadMeasuring,
416                     LayoutState.Measuring -> LayoutNode.UsageByParent.InMeasureBlock
417                     LayoutState.LayingOut,
418                     LayoutState.LookaheadLayingOut -> LayoutNode.UsageByParent.InLayoutBlock
419                     else ->
420                         throw IllegalStateException(
421                             "Measurable could be only measured from the parent's measure or layout" +
422                                 " block. Parents state is ${parent.layoutState}"
423                         )
424                 }
425         } else {
426             measuredByParent = LayoutNode.UsageByParent.NotUsed
427         }
428     }
429 
430     private var parentDataDirty: Boolean = true
431     override var parentData: Any? = measurePassDelegate.parentData
432         private set
433 
434     internal fun performMeasure(constraints: Constraints) {
435         layoutState = LayoutState.LookaheadMeasuring
436         measurePending = false
437         layoutNode.requireOwner().snapshotObserver.observeMeasureSnapshotReads(layoutNode) {
438             outerCoordinator.lookaheadDelegate!!.measure(constraints)
439         }
440         markLayoutPending()
441         if (layoutNode.isOutMostLookaheadRoot) {
442             // If layoutNode is the root of the lookahead, measure is redirected to lookahead
443             // measure, and layout pass will begin lookahead placement, measure & layout.
444             measurePassDelegate.markLayoutPending()
445         } else {
446             // If layoutNode is not the root of the lookahead, measure needs to follow the
447             // lookahead measure.
448             measurePassDelegate.markMeasurePending()
449         }
450         layoutState = LayoutState.Idle
451     }
452 
453     // Lookahead remeasurement with the given constraints.
454     fun remeasure(constraints: Constraints): Boolean {
455         withComposeStackTrace(layoutNode) {
456             requirePrecondition(!layoutNode.isDeactivated) {
457                 "measure is called on a deactivated node"
458             }
459             val parent = layoutNode.parent
460             @Suppress("Deprecation")
461             layoutNode.canMultiMeasure =
462                 layoutNode.canMultiMeasure || (parent != null && parent.canMultiMeasure)
463             if (layoutNode.lookaheadMeasurePending || lookaheadConstraints != constraints) {
464                 lookaheadConstraints = constraints
465                 measurementConstraints = constraints
466                 alignmentLines.usedByModifierMeasurement = false
467                 forEachChildAlignmentLinesOwner {
468                     it.alignmentLines.usedDuringParentMeasurement = false
469                 }
470                 // Copy out the previous size before performing lookahead measure. If never
471                 // measured, set the last size to negative instead of Zero in anticipation for zero
472                 // being a valid lookahead size.
473                 val lastLookaheadSize =
474                     if (measuredOnce) measuredSize else IntSize(Int.MIN_VALUE, Int.MIN_VALUE)
475                 measuredOnce = true
476                 val lookaheadDelegate = outerCoordinator.lookaheadDelegate
477                 checkPrecondition(lookaheadDelegate != null) {
478                     "Lookahead result from lookaheadRemeasure cannot be null"
479                 }
480 
481                 layoutNodeLayoutDelegate.performLookaheadMeasure(constraints)
482                 measuredSize = IntSize(lookaheadDelegate.width, lookaheadDelegate.height)
483                 val sizeChanged =
484                     lastLookaheadSize.width != lookaheadDelegate.width ||
485                         lastLookaheadSize.height != lookaheadDelegate.height
486                 return sizeChanged
487             } else {
488                 // this node doesn't require being remeasured. however in order to make sure we have
489                 // the final size we need to also make sure the whole subtree is remeasured as it
490                 // can trigger extra remeasure request on our node. we do it now in order to report
491                 // the final measured size to our parent without doing extra pass later.
492                 layoutNode.owner?.forceMeasureTheSubtree(layoutNode, affectsLookahead = true)
493 
494                 // Restore the intrinsics usage for the sub-tree
495                 layoutNode.resetSubtreeIntrinsicsUsage()
496             }
497             return false
498         }
499     }
500 
501     override fun placeAt(
502         position: IntOffset,
503         zIndex: Float,
504         layerBlock: (GraphicsLayerScope.() -> Unit)?
505     ) {
506         placeSelf(position, zIndex, layerBlock, null)
507     }
508 
509     override fun placeAt(position: IntOffset, zIndex: Float, layer: GraphicsLayer) {
510         placeSelf(position, zIndex, null, layer)
511     }
512 
513     override var isPlacedUnderMotionFrameOfReference: Boolean = false
514 
515     override fun updatePlacedUnderMotionFrameOfReference(newMFR: Boolean) {
516         // Delegated to outerCoordinator
517         val old = outerCoordinator.lookaheadDelegate?.isPlacedUnderMotionFrameOfReference
518         if (newMFR != old) {
519             outerCoordinator.lookaheadDelegate?.isPlacedUnderMotionFrameOfReference = newMFR
520         }
521         isPlacedUnderMotionFrameOfReference = newMFR
522     }
523 
524     private fun placeSelf(
525         position: IntOffset,
526         zIndex: Float,
527         layerBlock: (GraphicsLayerScope.() -> Unit)?,
528         layer: GraphicsLayer?
529     ) {
530         withComposeStackTrace(layoutNode) {
531             if (layoutNode.parent?.layoutState == LayoutState.LookaheadLayingOut) {
532                 // This placement call comes from parent
533                 layoutNodeLayoutDelegate.detachedFromParentLookaheadPlacement = false
534             }
535             requirePrecondition(!layoutNode.isDeactivated) {
536                 "place is called on a deactivated node"
537             }
538             layoutState = LayoutState.LookaheadLayingOut
539             placedOnce = true
540             onNodePlacedCalled = false
541             if (position != lastPosition) {
542                 if (
543                     layoutNodeLayoutDelegate.lookaheadCoordinatesAccessedDuringModifierPlacement ||
544                         layoutNodeLayoutDelegate.lookaheadCoordinatesAccessedDuringPlacement
545                 ) {
546                     layoutPending = true
547                 }
548                 notifyChildrenUsingLookaheadCoordinatesWhilePlacing()
549             }
550             val owner = layoutNode.requireOwner()
551 
552             if (!layoutPending && isPlaced) {
553                 outerCoordinator.lookaheadDelegate!!.placeSelfApparentToRealOffset(position)
554                 onNodePlaced()
555             } else {
556                 layoutNodeLayoutDelegate.lookaheadCoordinatesAccessedDuringModifierPlacement = false
557                 alignmentLines.usedByModifierLayout = false
558                 owner.snapshotObserver.observeLayoutModifierSnapshotReads(layoutNode) {
559                     val expectsLookaheadPlacementFromParent =
560                         !layoutNode.isOutMostLookaheadRoot &&
561                             !layoutNodeLayoutDelegate.detachedFromParentLookaheadPlacement
562 
563                     val scope =
564                         if (expectsLookaheadPlacementFromParent) {
565                             outerCoordinator.wrappedBy?.lookaheadDelegate?.placementScope
566                         } else {
567                             // Uses the approach pass placement scope intentionally here when
568                             // the
569                             // lookahead placement is detached from parent. This way we will
570                             // be able to pick up the correct `withMotionFrameOfReference` flag
571                             // from the placement scope.
572                             outerCoordinator.wrappedBy?.placementScope
573                         } ?: owner.placementScope
574                     with(scope) { outerCoordinator.lookaheadDelegate!!.place(position) }
575                 }
576             }
577             lastPosition = position
578             lastZIndex = zIndex
579             lastLayerBlock = layerBlock
580             lastExplicitLayer = layer
581             layoutState = LayoutState.Idle
582         }
583     }
584 
585     // We are setting our measuredSize to match the coerced outerCoordinator size, to prevent
586     // double offseting for layout cooperation. However, this means that here we need
587     // to override these getters to make the measured values correct in Measured.
588     // TODO(popam): clean this up
589     override val measuredWidth: Int
590         get() = outerCoordinator.lookaheadDelegate!!.measuredWidth
591 
592     override val measuredHeight: Int
593         get() = outerCoordinator.lookaheadDelegate!!.measuredHeight
594 
595     override fun get(alignmentLine: AlignmentLine): Int {
596         if (layoutNode.parent?.layoutState == LayoutState.LookaheadMeasuring) {
597             alignmentLines.usedDuringParentMeasurement = true
598         } else if (layoutNode.parent?.layoutState == LayoutState.LookaheadLayingOut) {
599             alignmentLines.usedDuringParentLayout = true
600         }
601         duringAlignmentLinesQuery = true
602         val result = outerCoordinator.lookaheadDelegate!![alignmentLine]
603         duringAlignmentLinesQuery = false
604         return result
605     }
606 
607     override fun minIntrinsicWidth(height: Int): Int {
608         onIntrinsicsQueried()
609         return outerCoordinator.lookaheadDelegate!!.minIntrinsicWidth(height)
610     }
611 
612     override fun maxIntrinsicWidth(height: Int): Int {
613         onIntrinsicsQueried()
614         return outerCoordinator.lookaheadDelegate!!.maxIntrinsicWidth(height)
615     }
616 
617     override fun minIntrinsicHeight(width: Int): Int {
618         onIntrinsicsQueried()
619         return outerCoordinator.lookaheadDelegate!!.minIntrinsicHeight(width)
620     }
621 
622     override fun maxIntrinsicHeight(width: Int): Int {
623         onIntrinsicsQueried()
624         return outerCoordinator.lookaheadDelegate!!.maxIntrinsicHeight(width)
625     }
626 
627     private fun onIntrinsicsQueried() {
628         // How intrinsics work when specific / custom intrinsics are not provided to the custom
629         // layout is we essentially run the measure block of a child with not-final constraints
630         // and fake measurables. It is possible that some measure blocks are not pure and have
631         // side effects, like save some state calculated during the measurement.
632         // In order to make it possible we always have to rerun the measure block with the real
633         // final constraints after the intrinsics run. Sometimes it will cause unnecessary
634         // remeasurements, but it makes sure such component states are using the correct final
635         // constraints/sizes.
636         layoutNode.requestLookaheadRemeasure()
637 
638         // Mark the intrinsics size has been used by the parent if it hasn't already been
639         // marked.
640         val parent = layoutNode.parent
641         if (
642             parent != null && layoutNode.intrinsicsUsageByParent == LayoutNode.UsageByParent.NotUsed
643         ) {
644             layoutNode.intrinsicsUsageByParent =
645                 when (parent.layoutState) {
646                     LayoutState.Measuring -> LayoutNode.UsageByParent.InMeasureBlock
647                     LayoutState.LayingOut -> LayoutNode.UsageByParent.InLayoutBlock
648                     // Called from parent's intrinsic measurement
649                     else -> parent.intrinsicsUsageByParent
650                 }
651         }
652     }
653 
654     /**
655      * If this was used in an intrinsics measurement, find the parent that used it and invalidate
656      * either the measure block or layout block.
657      */
658     fun invalidateIntrinsicsParent(forceRequest: Boolean) {
659         val parent = layoutNode.parent
660         val intrinsicsUsageByParent = layoutNode.intrinsicsUsageByParent
661         if (parent != null && intrinsicsUsageByParent != LayoutNode.UsageByParent.NotUsed) {
662             // find measuring parent
663             var intrinsicsUsingParent: LayoutNode = parent
664             while (intrinsicsUsingParent.intrinsicsUsageByParent == intrinsicsUsageByParent) {
665                 intrinsicsUsingParent = intrinsicsUsingParent.parent ?: break
666             }
667             when (intrinsicsUsageByParent) {
668                 LayoutNode.UsageByParent.InMeasureBlock ->
669                     if (intrinsicsUsingParent.lookaheadRoot != null) {
670                         intrinsicsUsingParent.requestLookaheadRemeasure(forceRequest)
671                     } else {
672                         intrinsicsUsingParent.requestRemeasure(forceRequest)
673                     }
674                 LayoutNode.UsageByParent.InLayoutBlock ->
675                     if (intrinsicsUsingParent.lookaheadRoot != null) {
676                         intrinsicsUsingParent.requestLookaheadRelayout(forceRequest)
677                     } else {
678                         intrinsicsUsingParent.requestRelayout(forceRequest)
679                     }
680                 else -> error("Intrinsics isn't used by the parent")
681             }
682         }
683     }
684 
685     fun invalidateParentData() {
686         parentDataDirty = true
687     }
688 
689     fun updateParentData(): Boolean {
690         if (parentData == null && outerCoordinator.lookaheadDelegate!!.parentData == null) {
691             return false
692         }
693         if (!parentDataDirty) return false
694         parentDataDirty = false
695         parentData = outerCoordinator.lookaheadDelegate!!.parentData
696         return true
697     }
698 
699     private var onNodePlacedCalled = false
700 
701     internal fun onNodePlaced() {
702         onNodePlacedCalled = true
703         val parent = layoutNode.parent
704         if (
705             (_placedState != PlacedState.IsPlacedInLookahead &&
706                 !detachedFromParentLookaheadPlacement) ||
707                 (_placedState != PlacedState.IsPlacedInApproach &&
708                     detachedFromParentLookaheadPlacement)
709         ) {
710             // Needs to update _placedState
711             markNodeAndSubtreeAsPlaced()
712             if (relayoutWithoutParentInProgress) {
713                 // this node wasn't placed previously and the parent thinks this node is not
714                 // visible, so we need to relayout the parent to get the `placeOrder`.
715                 parent?.requestLookaheadRelayout()
716             }
717         }
718         if (parent != null) {
719             if (
720                 !relayoutWithoutParentInProgress &&
721                     (parent.layoutState == LayoutState.LayingOut ||
722                         parent.layoutState == LayoutState.LookaheadLayingOut)
723             ) {
724                 // the parent is currently placing its children
725                 checkPrecondition(placeOrder == NotPlacedPlaceOrder) {
726                     "Place was called on a node which was placed already"
727                 }
728                 placeOrder = parent.layoutDelegate.nextChildLookaheadPlaceOrder
729                 parent.layoutDelegate.nextChildLookaheadPlaceOrder++
730             }
731             // if relayoutWithoutParentInProgress is true we were asked to be relaid out without
732             // affecting the parent. this means our placeOrder didn't change since the last time
733             // parent placed us.
734         } else {
735             // parent is null for the root node
736             placeOrder = 0
737         }
738         layoutChildren()
739     }
740 
741     private fun clearPlaceOrder() {
742         // reset the place order counter which will be used by the children
743         layoutNodeLayoutDelegate.nextChildLookaheadPlaceOrder = 0
744         forEachChildDelegate { child ->
745             // and reset the place order for all the children before placing them
746             child.previousPlaceOrder = child.placeOrder
747             child.placeOrder = NotPlacedPlaceOrder
748             // before rerunning the user's layout block reset previous measuredByParent
749             // for children which we measured in the layout block during the last run.
750             if (child.measuredByParent == LayoutNode.UsageByParent.InLayoutBlock) {
751                 child.measuredByParent = LayoutNode.UsageByParent.NotUsed
752             }
753         }
754     }
755 
756     // This gets called after the node has been placed. The call comes from:
757     // 1) InnerCoordinator, after all the modifiers in the chain has been placed.
758     // or 2) placeSelf, if the modifier chain doesn't need to be replaced.
759     // Note: This method only marks subtree nodes that have a `placeOrder` i.e. nodes that
760     // are placed in lookahead pass.
761     private fun markNodeAndSubtreeAsPlaced() {
762         val prevPlacedState = _placedState
763         // Update the _placedState based on whether the placement is a part of approach or
764         // lookahead.
765         if (detachedFromParentLookaheadPlacement) {
766             _placedState = PlacedState.IsPlacedInApproach
767         } else {
768             _placedState = PlacedState.IsPlacedInLookahead
769         }
770         if (prevPlacedState != PlacedState.IsPlacedInLookahead) {
771             if (layoutNodeLayoutDelegate.lookaheadMeasurePending) {
772                 // if the node was not placed previous remeasure request could have been ignored
773                 layoutNode.requestLookaheadRemeasure(forceRequest = true)
774             }
775         }
776 
777         layoutNode.forEachChild {
778             // this child was placed during the previous parent's layoutChildren(). this means
779             // that
780             // before the parent became not placed this child was placed. we need to restore
781             // that
782             val childDelegate =
783                 requireNotNull(it.lookaheadPassDelegate) {
784                     "Error: Child node's lookahead pass delegate cannot be null " +
785                         "when in a lookahead scope."
786                 }
787             if (childDelegate.placeOrder != NotPlacedPlaceOrder) {
788                 childDelegate.markNodeAndSubtreeAsPlaced()
789                 it.rescheduleRemeasureOrRelayout(it)
790             }
791         }
792     }
793 
794     /**
795      * The callback to be executed before running layoutChildren.
796      *
797      * There are possible cases when we run layoutChildren() on the parent node, but some of its
798      * children are not yet measured even if they are supposed to be measured in the measure block
799      * of our parent.
800      *
801      * Example: val child = Layout(...) Layout(child) { measurable, constraints -> val placeable =
802      * measurable.first().measure(constraints) layout(placeable.width, placeable.height) {
803      * placeable.place(0, 0) } } And now some set of changes scheduled remeasure for child and
804      * relayout for parent.
805      *
806      * During the [MeasureAndLayoutDelegate.measureAndLayout] we will start with the parent as it
807      * has lower depth. Inside the layout block we will call placeable.width which is currently
808      * dirty as the child was scheduled to remeasure. This callback will ensure it never happens and
809      * pre-remeasure everything required for this layoutChildren().
810      */
811     private fun onBeforeLayoutChildren() {
812         layoutNode.forEachChild {
813             if (
814                 it.lookaheadMeasurePending &&
815                     it.measuredByParentInLookahead == LayoutNode.UsageByParent.InMeasureBlock
816             ) {
817                 if (
818                     it.layoutDelegate.lookaheadPassDelegate!!.remeasure(
819                         it.layoutDelegate.lastLookaheadConstraints!!
820                     )
821                 ) {
822                     layoutNode.requestLookaheadRemeasure()
823                 }
824             }
825         }
826     }
827 
828     fun replace() {
829         try {
830             relayoutWithoutParentInProgress = true
831             checkPrecondition(placedOnce) { "replace() called on item that was not placed" }
832 
833             onNodePlacedCalled = false
834             val wasPlacedBefore = isPlaced
835             placeSelf(lastPosition, 0f, lastLayerBlock, lastExplicitLayer)
836             if (wasPlacedBefore && !onNodePlacedCalled) {
837                 // parent should be notified that this node is not placed anymore so the
838                 // children `placeOrder`s are updated.
839                 layoutNode.parent?.requestLookaheadRelayout()
840             }
841         } finally {
842             relayoutWithoutParentInProgress = false
843         }
844     }
845 
846     fun onNodeDetached() {
847         placeOrder = NotPlacedPlaceOrder
848         previousPlaceOrder = NotPlacedPlaceOrder
849         _placedState = PlacedState.IsNotPlaced
850     }
851 
852     fun onAttachedToNullParent() {
853         // it is a root node and attached root nodes are always placed (as there is no parent
854         // to place them explicitly)
855         _placedState = PlacedState.IsPlacedInLookahead
856     }
857 }
858