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