OuterMeasurablePlaceable.kt

/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.compose.ui.node

import androidx.compose.ui.graphics.GraphicsLayerScope
import androidx.compose.ui.layout.AlignmentLine
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.node.LayoutNode.LayoutState
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize

internal class OuterMeasurablePlaceable(
    private val layoutNode: LayoutNode,
    var outerWrapper: LayoutNodeWrapper
) : Measurable, Placeable() {

    private var measuredOnce = false
    val lastConstraints: Constraints? get() = if (measuredOnce) measurementConstraints else null
    var lastPosition: IntOffset? = null
        private set
    private var lastLayerBlock: (GraphicsLayerScope.() -> Unit)? = null
    private val lastProvidedAlignmentLines = mutableMapOf<AlignmentLine, Int>()
    private var lastZIndex: Float = 0f

    /**
     * A local version of [Owner.measureIteration] to ensure that [MeasureBlocks.measure]
     * is not called multiple times within a measure pass.
     */
    var measureIteration = -1L
        private set

    override var parentData: Any? = null
        private set

    /**
     * The function to be executed when the parent layout measures its children.
     */
    override fun measure(constraints: Constraints): Placeable {
        // when we measure the root it is like the virtual parent is currently laying out
        val parentState = layoutNode.parent?.layoutState ?: LayoutState.LayingOut
        layoutNode.measuredByParent = when (parentState) {
            LayoutState.Measuring -> LayoutNode.UsageByParent.InMeasureBlock
            LayoutState.LayingOut -> LayoutNode.UsageByParent.InLayoutBlock
            else -> throw IllegalStateException(
                "Measurable could be only measured from the parent's measure or layout block." +
                    "Parents state is $parentState"
            )
        }
        remeasure(constraints)
        return this
    }

    /**
     * Return true if the measured size has been changed
     */
    fun remeasure(constraints: Constraints): Boolean {
        val owner = layoutNode.requireOwner()
        val iteration = owner.measureIteration
        val parent = layoutNode.parent
        @Suppress("Deprecation")
        layoutNode.canMultiMeasure = layoutNode.canMultiMeasure ||
            (parent != null && parent.canMultiMeasure)
        @Suppress("Deprecation")
        check(measureIteration != iteration || layoutNode.canMultiMeasure) {
            "measure() may not be called multiple times on the same Measurable"
        }
        measureIteration = owner.measureIteration
        if (layoutNode.layoutState == LayoutState.NeedsRemeasure ||
            measurementConstraints != constraints
        ) {
            measuredOnce = true
            layoutNode.layoutState = LayoutState.Measuring
            measurementConstraints = constraints
            lastProvidedAlignmentLines.clear()
            lastProvidedAlignmentLines.putAll(layoutNode.providedAlignmentLines)
            owner.snapshotObserver.observeMeasureSnapshotReads(layoutNode) {
                outerWrapper.measure(constraints)
            }
            layoutNode.layoutState = LayoutState.NeedsRelayout
            if (layoutNode.providedAlignmentLines != lastProvidedAlignmentLines) {
                layoutNode.onAlignmentsChanged()
            }
            val previousSize = measuredSize
            val newWidth = outerWrapper.width
            val newHeight = outerWrapper.height
            if (newWidth != previousSize.width ||
                newHeight != previousSize.height
            ) {
                measuredSize = IntSize(newWidth, newHeight)
                return true
            }
        }
        return false
    }

    override fun get(line: AlignmentLine): Int = outerWrapper[line]

    override fun placeAt(
        position: IntOffset,
        zIndex: Float,
        layerBlock: (GraphicsLayerScope.() -> Unit)?
    ) {
        lastPosition = position
        lastZIndex = zIndex
        lastLayerBlock = layerBlock
        with(PlacementScope) {
            if (layerBlock == null) {
                outerWrapper.place(position, lastZIndex)
            } else {
                outerWrapper.placeWithLayer(position, lastZIndex, layerBlock)
            }
        }
    }

    /**
     * Calls [placeAt] with the same position used during the last [placeAt] call
     */
    fun replace() {
        placeAt(checkNotNull(lastPosition), lastZIndex, lastLayerBlock)
    }

    override fun minIntrinsicWidth(height: Int): Int = outerWrapper.minIntrinsicWidth(height)

    override fun maxIntrinsicWidth(height: Int): Int = outerWrapper.maxIntrinsicWidth(height)

    override fun minIntrinsicHeight(width: Int): Int = outerWrapper.minIntrinsicHeight(width)

    override fun maxIntrinsicHeight(width: Int): Int = outerWrapper.maxIntrinsicHeight(width)

    /**
     * Recalculates the parent data.
     */
    fun recalculateParentData() {
        parentData = outerWrapper.parentData
    }
}