LookaheadLayoutCoordinates.kt

/*
 * Copyright 2022 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.
 */

@file:OptIn(ExperimentalComposeUiApi::class)

package androidx.compose.ui.layout

import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.node.NodeCoordinator
import androidx.compose.ui.node.LookaheadDelegate
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.round
import androidx.compose.ui.unit.toOffset

/**
 * [LookaheadLayoutCoordinates] interface holds layout coordinates from both the lookahead
 * calculation and the post-lookahead layout pass.
 */
@ExperimentalComposeUiApi
sealed interface LookaheadLayoutCoordinates : LayoutCoordinates {
    /**
     * Converts an [relativeToSource] in [sourceCoordinates] space into local coordinates.
     * [sourceCoordinates] may be any [LookaheadLayoutCoordinates] that belong to the same
     * compose layout hierarchy. Unlike [localPositionOf], [localLookaheadPositionOf] uses
     * the lookahead positions for coordinates calculation.
     */
    fun localLookaheadPositionOf(
        sourceCoordinates: LookaheadLayoutCoordinates,
        relativeToSource: Offset = Offset.Zero
    ): Offset
}

internal class LookaheadLayoutCoordinatesImpl(val lookaheadDelegate: LookaheadDelegate) :
    LookaheadLayoutCoordinates {
    val coordinator: NodeCoordinator
        get() = lookaheadDelegate.coordinator

    override fun localLookaheadPositionOf(
        sourceCoordinates: LookaheadLayoutCoordinates,
        relativeToSource: Offset
    ): Offset {
        val source = (sourceCoordinates as LookaheadLayoutCoordinatesImpl).lookaheadDelegate
        val commonAncestor = coordinator.findCommonAncestor(source.coordinator)

        return commonAncestor.lookaheadDelegate?.let { ancestor ->
            // Common ancestor is in lookahead
            (source.positionIn(ancestor) + relativeToSource.round() -
                lookaheadDelegate.positionIn(ancestor)).toOffset()
        } ?: commonAncestor.let {
            // The two coordinates are in two separate LookaheadLayouts
            val sourceRoot = source.rootLookaheadDelegate
            val relativePosition = source.positionIn(sourceRoot) +
                sourceRoot.position + relativeToSource.round() -
                with(lookaheadDelegate) {
                    (positionIn(rootLookaheadDelegate) + rootLookaheadDelegate.position)
                }

            lookaheadDelegate.rootLookaheadDelegate.coordinator.wrappedBy!!.localPositionOf(
                sourceRoot.coordinator.wrappedBy!!, relativePosition.toOffset()
            )
        }
    }

    override val size: IntSize
        get() = coordinator.size
    override val providedAlignmentLines: Set<AlignmentLine>
        get() = coordinator.providedAlignmentLines

    override val parentLayoutCoordinates: LayoutCoordinates?
        get() = coordinator.parentLayoutCoordinates
    override val parentCoordinates: LayoutCoordinates?
        get() = coordinator.parentCoordinates
    override val isAttached: Boolean
        get() = coordinator.isAttached

    override fun windowToLocal(relativeToWindow: Offset): Offset =
        coordinator.windowToLocal(relativeToWindow)

    override fun localToWindow(relativeToLocal: Offset): Offset =
        coordinator.localToWindow(relativeToLocal)

    override fun localToRoot(relativeToLocal: Offset): Offset =
        coordinator.localToRoot(relativeToLocal)

    override fun localPositionOf(
        sourceCoordinates: LayoutCoordinates,
        relativeToSource: Offset
    ): Offset = coordinator.localPositionOf(sourceCoordinates, relativeToSource)

    override fun localBoundingBoxOf(
        sourceCoordinates: LayoutCoordinates,
        clipBounds: Boolean
    ): Rect = coordinator.localBoundingBoxOf(sourceCoordinates, clipBounds)

    override fun transformFrom(sourceCoordinates: LayoutCoordinates, matrix: Matrix) {
        coordinator.transformFrom(sourceCoordinates, matrix)
    }

    override fun get(alignmentLine: AlignmentLine): Int = coordinator.get(alignmentLine)
}

private val LookaheadDelegate.rootLookaheadDelegate: LookaheadDelegate
    get() = lookaheadScope.root.outerCoordinator.lookaheadDelegate!!