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

Commit 6141b370 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Move transformationSpec and overscrollSpec to Transition

This CL moves transformationSpec and overscrollSpec from STLState to
Transition. This makes more sense given that those are properties of the
current transition, and this will be necessary for b/290930950 given
that multiples transitions will run in parallel in the future.

Bug: 290930950
Test: atest PlatformComposeSceneTransitionLayoutTests
Flag: N/A
Change-Id: I1d5eb40fb5ed079fbf66918c8bccb46af36faf27
parent 5ac36395
Loading
Loading
Loading
Loading
+3 −2
Original line number Original line Diff line number Diff line
@@ -138,8 +138,9 @@ private fun CoroutineScope.animate(
    // that will actually animate it.
    // that will actually animate it.
    layoutState.startTransition(transition, transitionKey)
    layoutState.startTransition(transition, transitionKey)


    // The transformation now contains the spec that we should use to instantiate the Animatable.
    // The transition now contains the transformation spec that we should use to instantiate the
    val animationSpec = layoutState.transformationSpec.progressSpec
    // Animatable.
    val animationSpec = transition.transformationSpec.progressSpec
    val visibilityThreshold =
    val visibilityThreshold =
        (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
        (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
    val animatable =
    val animatable =
+3 −26
Original line number Original line Diff line number Diff line
@@ -21,7 +21,6 @@ package com.android.compose.animation.scene
import android.util.Log
import android.util.Log
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.SpringSpec
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.getValue
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableFloatStateOf
@@ -269,19 +268,6 @@ private class DragControllerImpl(
    fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
    fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
        if (isDrivingTransition || force) {
        if (isDrivingTransition || force) {
            layoutState.startTransition(newTransition, newTransition.key)
            layoutState.startTransition(newTransition, newTransition.key)

            // Initialize SwipeTransition.transformationSpec and .swipeSpec. Note that this must be
            // called right after layoutState.startTransition() is called, because it computes the
            // current layoutState.transformationSpec().
            val transformationSpec = layoutState.transformationSpec
            newTransition.transformationSpec = transformationSpec
            newTransition.swipeSpec =
                transformationSpec.swipeSpec ?: layoutState.transitions.defaultSwipeSpec
        } else {
            // We were not driving the transition and we don't force the update, so the specs won't
            // be used and it doesn't matter which ones we set here.
            newTransition.transformationSpec = TransformationSpec.Empty
            newTransition.swipeSpec = SceneTransitions.DefaultSwipeSpec
        }
        }


        swipeTransition = newTransition
        swipeTransition = newTransition
@@ -616,18 +602,6 @@ private class SwipeTransition(
    override val isUserInputOngoing: Boolean
    override val isUserInputOngoing: Boolean
        get() = offsetAnimation == null
        get() = offsetAnimation == null


    /**
     * The [TransformationSpecImpl] associated to this transition.
     *
     * Note: This is lateinit because this [SwipeTransition] is needed by
     * [BaseSceneTransitionLayoutState] to compute the [TransitionSpec], and it will be set right
     * after [BaseSceneTransitionLayoutState.startTransition] is called with this transition.
     */
    lateinit var transformationSpec: TransformationSpecImpl

    /** The spec to use when animating this transition to either [fromScene] or [toScene]. */
    lateinit var swipeSpec: SpringSpec<Float>

    override val overscrollScope: OverscrollScope =
    override val overscrollScope: OverscrollScope =
        object : OverscrollScope {
        object : OverscrollScope {
            override val absoluteDistance: Float
            override val absoluteDistance: Float
@@ -701,6 +675,9 @@ private class SwipeTransition(
                coroutineScope
                coroutineScope
                    .launch {
                    .launch {
                        try {
                        try {
                            val swipeSpec =
                                transformationSpec.swipeSpec
                                    ?: layoutState.transitions.defaultSwipeSpec
                            animatable.animateTo(
                            animatable.animateTo(
                                targetValue = targetOffset,
                                targetValue = targetOffset,
                                animationSpec = swipeSpec,
                                animationSpec = swipeSpec,
+11 −14
Original line number Original line Diff line number Diff line
@@ -203,7 +203,7 @@ internal class ElementNode(
        measurable: Measurable,
        measurable: Measurable,
        constraints: Constraints,
        constraints: Constraints,
    ): MeasureResult {
    ): MeasureResult {
        val overscrollScene = layoutImpl.state.currentOverscrollSpec?.scene
        val overscrollScene = layoutImpl.state.currentTransition?.currentOverscrollSpec?.scene
        if (overscrollScene != null && overscrollScene != scene.key) {
        if (overscrollScene != null && overscrollScene != scene.key) {
            // There is an overscroll in progress on another scene
            // There is an overscroll in progress on another scene
            // By measuring composable elements, Compose can cache relevant information.
            // By measuring composable elements, Compose can cache relevant information.
@@ -269,13 +269,12 @@ private fun shouldDrawElement(
        transition == null ||
        transition == null ||
            transition.fromScene !in element.sceneStates ||
            transition.fromScene !in element.sceneStates ||
            transition.toScene !in element.sceneStates ||
            transition.toScene !in element.sceneStates ||
            layoutImpl.state.currentOverscrollSpec?.scene == scene.key
            transition.currentOverscrollSpec?.scene == scene.key
    ) {
    ) {
        return true
        return true
    }
    }


    val sharedTransformation =
    val sharedTransformation = sharedElementTransformation(transition, element.key)
        sharedElementTransformation(layoutImpl.state, transition, element.key)
    if (sharedTransformation?.enabled == false) {
    if (sharedTransformation?.enabled == false) {
        return true
        return true
    }
    }
@@ -305,23 +304,21 @@ internal fun shouldDrawOrComposeSharedElement(
            fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex,
            fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex,
            toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex,
            toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex,
        ) == scene
        ) == scene
    return chosenByPicker || layoutImpl.state.currentOverscrollSpec?.scene == scene
    return chosenByPicker || transition.currentOverscrollSpec?.scene == scene
}
}


private fun isSharedElementEnabled(
private fun isSharedElementEnabled(
    layoutState: BaseSceneTransitionLayoutState,
    transition: TransitionState.Transition,
    transition: TransitionState.Transition,
    element: ElementKey,
    element: ElementKey,
): Boolean {
): Boolean {
    return sharedElementTransformation(layoutState, transition, element)?.enabled ?: true
    return sharedElementTransformation(transition, element)?.enabled ?: true
}
}


internal fun sharedElementTransformation(
internal fun sharedElementTransformation(
    layoutState: BaseSceneTransitionLayoutState,
    transition: TransitionState.Transition,
    transition: TransitionState.Transition,
    element: ElementKey,
    element: ElementKey,
): SharedElementTransformation? {
): SharedElementTransformation? {
    val transformationSpec = layoutState.transformationSpec
    val transformationSpec = transition.transformationSpec
    val sharedInFromScene = transformationSpec.transformations(element, transition.fromScene).shared
    val sharedInFromScene = transformationSpec.transformations(element, transition.fromScene).shared
    val sharedInToScene = transformationSpec.transformations(element, transition.toScene).shared
    val sharedInToScene = transformationSpec.transformations(element, transition.toScene).shared


@@ -360,11 +357,11 @@ private fun isElementOpaque(
    }
    }


    val isSharedElement = fromState != null && toState != null
    val isSharedElement = fromState != null && toState != null
    if (isSharedElement && isSharedElementEnabled(layoutImpl.state, transition, element.key)) {
    if (isSharedElement && isSharedElementEnabled(transition, element.key)) {
        return true
        return true
    }
    }


    return layoutImpl.state.transformationSpec.transformations(element.key, scene.key).alpha == null
    return transition.transformationSpec.transformations(element.key, scene.key).alpha == null
}
}


/**
/**
@@ -559,7 +556,7 @@ private inline fun <T> computeValue(
    }
    }


    if (transition is TransitionState.HasOverscrollProperties) {
    if (transition is TransitionState.HasOverscrollProperties) {
        val overscroll = layoutImpl.state.currentOverscrollSpec
        val overscroll = transition.currentOverscrollSpec
        if (overscroll?.scene == scene.key) {
        if (overscroll?.scene == scene.key) {
            val elementSpec = overscroll.transformationSpec.transformations(element.key, scene.key)
            val elementSpec = overscroll.transformationSpec.transformations(element.key, scene.key)
            val propertySpec = transformation(elementSpec) ?: return currentValue()
            val propertySpec = transformation(elementSpec) ?: return currentValue()
@@ -597,7 +594,7 @@ private inline fun <T> computeValue(
    // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared
    // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared
    // elements follow the finger direction.
    // elements follow the finger direction.
    val isSharedElement = fromState != null && toState != null
    val isSharedElement = fromState != null && toState != null
    if (isSharedElement && isSharedElementEnabled(layoutImpl.state, transition, element.key)) {
    if (isSharedElement && isSharedElementEnabled(transition, element.key)) {
        val start = sceneValue(fromState!!)
        val start = sceneValue(fromState!!)
        val end = sceneValue(toState!!)
        val end = sceneValue(toState!!)


@@ -607,7 +604,7 @@ private inline fun <T> computeValue(
    }
    }


    val transformation =
    val transformation =
        transformation(layoutImpl.state.transformationSpec.transformations(element.key, scene.key))
        transformation(transition.transformationSpec.transformations(element.key, scene.key))
        // If there is no transformation explicitly associated to this element value, let's use
        // If there is no transformation explicitly associated to this element value, let's use
        // the value given by the system (like the current position and size given by the layout
        // the value given by the system (like the current position and size given by the layout
        // pass).
        // pass).
+47 −29
Original line number Original line Diff line number Diff line
@@ -204,6 +204,30 @@ sealed interface TransitionState {
        /** Whether user input is currently driving the transition. */
        /** Whether user input is currently driving the transition. */
        abstract val isUserInputOngoing: Boolean
        abstract val isUserInputOngoing: Boolean


        /**
         * The current [TransformationSpecImpl] and [OverscrollSpecImpl] associated to this
         * transition.
         *
         * Important: These will be set exactly once, when this transition is
         * [started][BaseSceneTransitionLayoutState.startTransition].
         */
        internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
        private var fromOverscrollSpec: OverscrollSpecImpl? = null
        private var toOverscrollSpec: OverscrollSpecImpl? = null

        /** The current [OverscrollSpecImpl], if this transition is currently overscrolling. */
        internal val currentOverscrollSpec: OverscrollSpecImpl?
            get() {
                if (this !is HasOverscrollProperties) return null
                val progress = progress
                val bouncingScene = bouncingScene
                return when {
                    progress < 0f || bouncingScene == fromScene -> fromOverscrollSpec
                    progress > 1f || bouncingScene == toScene -> toOverscrollSpec
                    else -> null
                }
            }

        init {
        init {
            check(fromScene != toScene)
            check(fromScene != toScene)
        }
        }
@@ -232,6 +256,14 @@ sealed interface TransitionState {
            return isTransitioning(from = scene, to = other) ||
            return isTransitioning(from = scene, to = other) ||
                isTransitioning(from = other, to = scene)
                isTransitioning(from = other, to = scene)
        }
        }

        internal fun updateOverscrollSpecs(
            fromSpec: OverscrollSpecImpl?,
            toSpec: OverscrollSpecImpl?,
        ) {
            fromOverscrollSpec = fromSpec
            toOverscrollSpec = toSpec
        }
    }
    }


    interface HasOverscrollProperties {
    interface HasOverscrollProperties {
@@ -275,32 +307,6 @@ internal abstract class BaseSceneTransitionLayoutState(
        mutableStateOf(TransitionState.Idle(initialScene))
        mutableStateOf(TransitionState.Idle(initialScene))
        protected set
        protected set


    /**
     * The current [transformationSpec] associated to [transitionState]. Accessing this value makes
     * sense only if [transitionState] is a [TransitionState.Transition].
     */
    internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty

    private var fromOverscrollSpec: OverscrollSpecImpl? = null
    private var toOverscrollSpec: OverscrollSpecImpl? = null

    /**
     * @return the overscroll [OverscrollSpecImpl] if it is defined for the current
     *   [transitionState] and we are currently over scrolling.
     */
    internal val currentOverscrollSpec: OverscrollSpecImpl?
        get() {
            val transition = currentTransition ?: return null
            if (transition !is TransitionState.HasOverscrollProperties) return null
            val progress = transition.progress
            val bouncingScene = transition.bouncingScene
            return when {
                progress < 0f || bouncingScene == transition.fromScene -> fromOverscrollSpec
                progress > 1f || bouncingScene == transition.toScene -> toOverscrollSpec
                else -> null
            }
        }

    private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()
    private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()


    /** Whether we can transition to the given [scene]. */
    /** Whether we can transition to the given [scene]. */
@@ -333,12 +339,24 @@ internal abstract class BaseSceneTransitionLayoutState(
        val fromScene = transition.fromScene
        val fromScene = transition.fromScene
        val toScene = transition.toScene
        val toScene = transition.toScene
        val orientation = (transition as? TransitionState.HasOverscrollProperties)?.orientation
        val orientation = (transition as? TransitionState.HasOverscrollProperties)?.orientation
        transformationSpec =

        // Update the transition specs.
        transition.transformationSpec =
            transitions.transitionSpec(fromScene, toScene, key = transitionKey).transformationSpec()
            transitions.transitionSpec(fromScene, toScene, key = transitionKey).transformationSpec()
        fromOverscrollSpec = orientation?.let { transitions.overscrollSpec(fromScene, it) }
        if (orientation != null) {
        toOverscrollSpec = orientation?.let { transitions.overscrollSpec(toScene, it) }
            transition.updateOverscrollSpecs(
                fromSpec = transitions.overscrollSpec(fromScene, orientation),
                toSpec = transitions.overscrollSpec(toScene, orientation),
            )
        } else {
            transition.updateOverscrollSpecs(fromSpec = null, toSpec = null)
        }

        // Handle transition links.
        cancelActiveTransitionLinks()
        cancelActiveTransitionLinks()
        setupTransitionLinks(transition)
        setupTransitionLinks(transition)

        // Set the current transition.
        transitionState = transition
        transitionState = transition
    }
    }


+3 −3
Original line number Original line Diff line number Diff line
@@ -984,14 +984,14 @@ class DraggableHandlerTest {
        val scene = layoutState.transitionState.currentScene
        val scene = layoutState.transitionState.currentScene
        // We should have overscroll spec for scene C
        // We should have overscroll spec for scene C
        assertThat(layoutState.transitions.overscrollSpec(scene, Orientation.Vertical)).isNotNull()
        assertThat(layoutState.transitions.overscrollSpec(scene, Orientation.Vertical)).isNotNull()
        assertThat(layoutState.currentOverscrollSpec).isNull()
        assertThat(layoutState.currentTransition?.currentOverscrollSpec).isNull()


        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
        nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
        nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))


        // We scrolled down, under scene C there is nothing, so we can use the overscroll spec
        // We scrolled down, under scene C there is nothing, so we can use the overscroll spec
        assertThat(layoutState.currentOverscrollSpec).isNotNull()
        assertThat(layoutState.currentTransition?.currentOverscrollSpec).isNotNull()
        assertThat(layoutState.currentOverscrollSpec?.scene).isEqualTo(SceneC)
        assertThat(layoutState.currentTransition?.currentOverscrollSpec?.scene).isEqualTo(SceneC)
        val transition = layoutState.currentTransition
        val transition = layoutState.currentTransition
        assertThat(transition).isNotNull()
        assertThat(transition).isNotNull()
        assertThat(transition!!.progress).isEqualTo(-0.1f)
        assertThat(transition!!.progress).isEqualTo(-0.1f)
Loading