Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 68415bc2 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Improve ContentScope.layoutState for Nested STLs" into main

parents cfb6b8ae 88e55d16
Loading
Loading
Loading
Loading
+63 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 com.android.compose.animation.scene

import androidx.compose.ui.util.fastForEach

/**
 * An implementation of [SceneTransitionLayoutState] for nested STLs that takes ancestors into
 * account in its state check functions.
 */
internal class NestedSceneTransitionLayoutState(
    internal val ancestors: List<SceneTransitionLayoutState>,
    internal val delegate: SceneTransitionLayoutState,
) : SceneTransitionLayoutState by delegate {
    init {
        check(ancestors.isNotEmpty()) {
            "NestedSceneTransitionLayoutState should not be used for non nested STLs"
        }
    }

    override fun isIdle(content: ContentKey?): Boolean {
        var foundContent = content == null
        forEachState { state ->
            if (!state.isIdle()) return false
            foundContent = foundContent || state.isIdle(content)
        }
        return foundContent
    }

    override fun isTransitioning(from: ContentKey?, to: ContentKey?): Boolean {
        forEachState { state -> if (state.isTransitioning(from, to)) return true }
        return false
    }

    override fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean {
        forEachState { state -> if (state.isTransitioningBetween(content, other)) return true }
        return false
    }

    override fun isTransitioningFromOrTo(content: ContentKey): Boolean {
        forEachState { state -> if (state.isTransitioningFromOrTo(content)) return true }
        return false
    }

    private inline fun forEachState(action: (SceneTransitionLayoutState) -> Unit) {
        action(delegate)
        ancestors.fastForEach { action(it) }
    }
}
+12 −1
Original line number Diff line number Diff line
@@ -198,7 +198,18 @@ interface BaseContentScope : ElementStateScope {
    /** The key of this content. */
    val contentKey: ContentKey

    /** The state of the [SceneTransitionLayout] in which this content is contained. */
    /**
     * The state of the [SceneTransitionLayout] in which this content is contained.
     *
     * Important: Inside a [ContentScope.NestedSceneTransitionLayout], this will *not* be the state
     * passed to [ContentScope.NestedSceneTransitionLayout] but a new one that delegates to it
     * instead, so that checks on the current state also consider the ancestor STL states.
     *
     * @see SceneTransitionLayoutState.isIdle
     * @see SceneTransitionLayoutState.isTransitioning
     * @see SceneTransitionLayoutState.isTransitioningBetween
     * @see SceneTransitionLayoutState.isTransitioningFromOrTo
     */
    val layoutState: SceneTransitionLayoutState

    /** The [LookaheadScope] used by the [SceneTransitionLayout]. */
+35 −4
Original line number Diff line number Diff line
@@ -91,15 +91,42 @@ sealed interface SceneTransitionLayoutState {
    val transitions: SceneTransitions

    /**
     * Whether we are transitioning. If [from] or [to] is empty, we will also check that they match
     * the contents we are animating from and/or to.
     * Whether we are idle. If [content] isn't `null`, return `true` if idle and current content
     * contains [content]. If [content] is `null`, will return `true` if idle, regardless of current
     * content.
     *
     * If this is the state of a [ContentScope.NestedSceneTransitionLayout], then this will also
     * consider ancestors and return `true` iff *all* ancestor states are idle (and at least one of
     * them is idle at [content], if it is not null).
     */
    fun isIdle(content: ContentKey? = null): Boolean

    /**
     * Whether we are transitioning. If [from] or [to] are `null`, only the non-`null` one would be
     * checked; if both are `null`, will return `true` if any transition is ongoing.
     *
     * If this is the state of a [ContentScope.NestedSceneTransitionLayout], then this will also
     * consider ancestors and return `true` if *any* ancestor is transitioning (from [from] to [to],
     * if they are not null).
     */
    fun isTransitioning(from: ContentKey? = null, to: ContentKey? = null): Boolean

    /** Whether we are transitioning from [content] to [other], or from [other] to [content]. */
    /**
     * Whether we are transitioning from [content] to [other], or from [other] to [content].
     *
     * If this is the state of a [ContentScope.NestedSceneTransitionLayout], then this will also
     * consider ancestors and return `true` if *any* ancestor is transitioning (between [from] and
     * [to], if they are not null).
     */
    fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean

    /** Whether we are transitioning from or to [content]. */
    /**
     * Whether we are transitioning from or to [content].
     *
     * If this is the state of a [ContentScope.NestedSceneTransitionLayout], then this will also
     * consider ancestors and return `true` if *any* ancestor is transitioning (from or to
     * [content], if it is not null).
     */
    fun isTransitioningFromOrTo(content: ContentKey): Boolean
}

@@ -390,6 +417,10 @@ internal class MutableSceneTransitionLayoutStateImpl(
        }
    }

    override fun isIdle(content: ContentKey?): Boolean {
        return transitionState.isIdle(content)
    }

    override fun isTransitioning(from: ContentKey?, to: ContentKey?): Boolean {
        val transition = currentTransition ?: return false
        return transition.isTransitioning(from, to)
+11 −1
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastMap
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
@@ -65,6 +66,7 @@ import com.android.compose.animation.scene.InternalContentScope
import com.android.compose.animation.scene.MovableElement
import com.android.compose.animation.scene.MovableElementContentScope
import com.android.compose.animation.scene.MovableElementKey
import com.android.compose.animation.scene.NestedSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneTransitionLayoutForTesting
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.SceneTransitionLayoutScope
@@ -295,7 +297,15 @@ internal class ContentScopeImpl(
    override val contentKey: ContentKey
        get() = content.key

    override val layoutState: SceneTransitionLayoutState = layoutImpl.state
    override val layoutState: SceneTransitionLayoutState =
        if (layoutImpl.ancestors.isEmpty()) {
            layoutImpl.state
        } else {
            NestedSceneTransitionLayoutState(
                ancestors = layoutImpl.ancestors.fastMap { it.layoutImpl.state },
                delegate = layoutImpl.state,
            )
        }

    override val lookaheadScope: LookaheadScope
        get() = layoutImpl.lookaheadScope
+35 −10
Original line number Diff line number Diff line
@@ -63,11 +63,40 @@ sealed interface TransitionState {
     */
    val currentOverlays: Set<OverlayKey>

    /**
     * Whether we are idle. If [content] isn't `null`, return `true` if idle and current content
     * contains [content]. If [content] is `null`, will return `true` if idle, regardless of current
     * content.
     */
    fun isIdle(content: ContentKey? = null): Boolean

    /**
     * Whether we are transitioning. If [from] or [to] are `null`, only the non-`null` one would be
     * checked; if both are `null`, will return `true` if any transition is ongoing.
     */
    fun isTransitioning(from: ContentKey? = null, to: ContentKey? = null): Boolean

    /** Whether we are transitioning from [content] to [other], or from [other] to [content]. */
    fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean

    /** Whether we are transitioning from or to [content]. */
    fun isTransitioningFromOrTo(content: ContentKey): Boolean

    /** The scene [currentScene] is idle. */
    data class Idle(
        override val currentScene: SceneKey,
        override val currentOverlays: Set<OverlayKey> = emptySet(),
    ) : TransitionState
    ) : TransitionState {
        override fun isIdle(content: ContentKey?): Boolean {
            return content == null || content == currentScene || currentOverlays.contains(content)
        }

        override fun isTransitioning(from: ContentKey?, to: ContentKey?): Boolean = false

        override fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean = false

        override fun isTransitioningFromOrTo(content: ContentKey): Boolean = false
    }

    sealed class Transition(
        val fromContent: ContentKey,
@@ -337,22 +366,18 @@ sealed interface TransitionState {
            }
        }

        /**
         * Whether we are transitioning. If [from] or [to] is empty, we will also check that they
         * match the contents we are animating from and/or to.
         */
        fun isTransitioning(from: ContentKey? = null, to: ContentKey? = null): Boolean {
        override fun isIdle(content: ContentKey?): Boolean = false

        override fun isTransitioning(from: ContentKey?, to: ContentKey?): Boolean {
            return (from == null || fromContent == from) && (to == null || toContent == to)
        }

        /** Whether we are transitioning from [content] to [other], or from [other] to [content]. */
        fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean {
        override fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean {
            return isTransitioning(from = content, to = other) ||
                isTransitioning(from = other, to = content)
        }

        /** Whether we are transitioning from or to [content]. */
        fun isTransitioningFromOrTo(content: ContentKey): Boolean {
        override fun isTransitioningFromOrTo(content: ContentKey): Boolean {
            return fromContent == content || toContent == content
        }

Loading