All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.androidx.compose.ui.layout.LookaheadLayoutCoordinates.kt Maven / Gradle / Ivy

/*
 * 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.internal.checkPrecondition
import androidx.compose.ui.node.LookaheadDelegate
import androidx.compose.ui.node.NodeCoordinator
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.round
import androidx.compose.ui.unit.toOffset

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

    override val size: IntSize
        get() = lookaheadDelegate.let { IntSize(it.width, it.height) }
    override val providedAlignmentLines: Set
        get() = coordinator.providedAlignmentLines

    override val parentLayoutCoordinates: LayoutCoordinates?
        get() {
            checkPrecondition(isAttached) { NodeCoordinator.ExpectAttachedLayoutCoordinates }
            return coordinator.layoutNode.outerCoordinator.wrappedBy?.let {
                it.lookaheadDelegate?.coordinates
            }
        }
    override val parentCoordinates: LayoutCoordinates?
        get() {
            checkPrecondition(isAttached) { NodeCoordinator.ExpectAttachedLayoutCoordinates }
            return coordinator.wrappedBy?.lookaheadDelegate?.coordinates
        }

    override val isAttached: Boolean
        get() = coordinator.isAttached

    override val introducesMotionFrameOfReference: Boolean
        get() = lookaheadDelegate.isPlacedUnderMotionFrameOfReference

    private val lookaheadOffset: Offset
        get() = lookaheadDelegate.rootLookaheadDelegate.let {
            localPositionOf(it.coordinates, Offset.Zero) -
                coordinator.localPositionOf(it.coordinator, Offset.Zero)
        }

    override fun screenToLocal(relativeToScreen: Offset): Offset =
        coordinator.screenToLocal(relativeToScreen) + lookaheadOffset

    override fun localToScreen(relativeToLocal: Offset): Offset =
        coordinator.localToScreen(relativeToLocal + lookaheadOffset)

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

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

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

    override fun localPositionOf(
        sourceCoordinates: LayoutCoordinates,
        relativeToSource: Offset
    ): Offset = localPositionOf(
        sourceCoordinates = sourceCoordinates,
        relativeToSource = relativeToSource,
        includeMotionFrameOfReference = true
    )

    override fun localPositionOf(
        sourceCoordinates: LayoutCoordinates,
        relativeToSource: Offset,
        includeMotionFrameOfReference: Boolean
    ): Offset {
        if (sourceCoordinates is LookaheadLayoutCoordinates) {
            val source = sourceCoordinates.lookaheadDelegate
            source.coordinator.onCoordinatesUsed()
            val commonAncestor = coordinator.findCommonAncestor(source.coordinator)

            return commonAncestor.lookaheadDelegate?.let { ancestor ->
                // Common ancestor is in lookahead
                val sourceInCommonAncestor = source.positionIn(
                    ancestor = ancestor,
                    excludingAgnosticOffset = !includeMotionFrameOfReference
                ) + relativeToSource.round()

                val lookaheadPosInAncestor = lookaheadDelegate.positionIn(
                    ancestor = ancestor,
                    excludingAgnosticOffset = !includeMotionFrameOfReference
                )

                (sourceInCommonAncestor - lookaheadPosInAncestor).toOffset()
            } ?: commonAncestor.let {
                // The two coordinates are in two separate LookaheadLayouts
                val sourceRoot = source.rootLookaheadDelegate

                val sourcePosition = source.positionIn(
                    ancestor = sourceRoot,
                    excludingAgnosticOffset = !includeMotionFrameOfReference
                ) + sourceRoot.position + relativeToSource.round()

                val rootDelegate = lookaheadDelegate.rootLookaheadDelegate
                val lookaheadPosition = lookaheadDelegate.positionIn(
                    ancestor = rootDelegate,
                    excludingAgnosticOffset = !includeMotionFrameOfReference
                ) + rootDelegate.position

                val relativePosition = (sourcePosition - lookaheadPosition).toOffset()

                rootDelegate.coordinator.wrappedBy!!.localPositionOf(
                    sourceCoordinates = sourceRoot.coordinator.wrappedBy!!,
                    relativeToSource = relativePosition,
                    includeMotionFrameOfReference = includeMotionFrameOfReference
                )
            }
        } else {
            val rootDelegate = lookaheadDelegate.rootLookaheadDelegate
            // This is a case of mixed coordinates where `this` is lookahead coords, and
            // `sourceCoordinates` isn't. Therefore we'll break this into two parts:
            // local position in lookahead coords space && local position in regular layout coords
            // space.
            val localLookaheadPos = localPositionOf(
                sourceCoordinates = rootDelegate.lookaheadLayoutCoordinates,
                relativeToSource = relativeToSource,
                includeMotionFrameOfReference = includeMotionFrameOfReference
            )

            val localPos = rootDelegate.coordinator.coordinates.localPositionOf(
                sourceCoordinates = sourceCoordinates,
                relativeToSource = Offset.Zero,
                includeMotionFrameOfReference = includeMotionFrameOfReference
            )
            return localLookaheadPos + localPos
        }
    }

    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 transformToScreen(matrix: Matrix) {
        coordinator.transformToScreen(matrix)
    }

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

internal val LookaheadDelegate.rootLookaheadDelegate: LookaheadDelegate
    get() {
        var root = layoutNode
        while (root.parent?.lookaheadRoot != null) {
            val lookaheadRoot = root.parent?.lookaheadRoot!!
            if (lookaheadRoot.isVirtualLookaheadRoot) {
                root = root.parent!!
            } else {
                root = root.parent!!.lookaheadRoot!!
            }
        }
        return root.outerCoordinator.lookaheadDelegate!!
    }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy