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