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