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

Commit d2581dbb authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Always use the transition scene(s) when computing element values

This CL ensures that we always use the current transition of an element
when computing its values (size, offset, etc), even for scenes not
currently in the transition. Before this CL, it could happen that we
would use the wrong scene when computing the size of an element in a
scene during complex interruption scenarios.

Bug: 290930950
Test: ElementTest
Flag: com.android.systemui.scene_container
Change-Id: I860ee7049c0bfe989dd095899c745f787c207f2a
parent 0ce8047f
Loading
Loading
Loading
Loading
+35 −34
Original line number Original line Diff line number Diff line
@@ -302,7 +302,7 @@ internal class ElementNode(
        }
        }


        val placeable =
        val placeable =
            measure(layoutImpl, scene, element, transition, sceneState, measurable, constraints)
            measure(layoutImpl, element, transition, sceneState, measurable, constraints)
        sceneState.lastSize = placeable.size()
        sceneState.lastSize = placeable.size()
        return layout(placeable.width, placeable.height) { place(transition, placeable) }
        return layout(placeable.width, placeable.height) { place(transition, placeable) }
    }
    }
@@ -317,7 +317,6 @@ internal class ElementNode(
            // scene when idle.
            // scene when idle.
            val coords =
            val coords =
                coordinates ?: error("Element ${element.key} does not have any coordinates")
                coordinates ?: error("Element ${element.key} does not have any coordinates")
            val targetOffsetInScene = lookaheadScopeCoordinates.localLookaheadPositionOf(coords)


            // No need to place the element in this scene if we don't want to draw it anyways.
            // No need to place the element in this scene if we don't want to draw it anyways.
            if (!shouldPlaceElement(layoutImpl, scene.key, element, transition)) {
            if (!shouldPlaceElement(layoutImpl, scene.key, element, transition)) {
@@ -329,12 +328,11 @@ internal class ElementNode(
            val targetOffset =
            val targetOffset =
                computeValue(
                computeValue(
                    layoutImpl,
                    layoutImpl,
                    scene,
                    sceneState,
                    element,
                    element,
                    transition,
                    transition,
                    sceneValue = { it.targetOffset },
                    sceneValue = { it.targetOffset },
                    transformation = { it.offset },
                    transformation = { it.offset },
                    idleValue = targetOffsetInScene,
                    currentValue = { currentOffset },
                    currentValue = { currentOffset },
                    isSpecified = { it != Offset.Unspecified },
                    isSpecified = { it != Offset.Unspecified },
                    ::lerp,
                    ::lerp,
@@ -395,7 +393,7 @@ internal class ElementNode(
                        return@placeWithLayer
                        return@placeWithLayer
                    }
                    }


                    alpha = elementAlpha(layoutImpl, scene, element, transition, sceneState)
                    alpha = elementAlpha(layoutImpl, element, transition, sceneState)
                    compositingStrategy = CompositingStrategy.ModulateAlpha
                    compositingStrategy = CompositingStrategy.ModulateAlpha
                }
                }
            }
            }
@@ -425,7 +423,7 @@ internal class ElementNode(
        element.wasDrawnInAnyScene = true
        element.wasDrawnInAnyScene = true


        val transition = elementTransition(layoutImpl, element, currentTransitions)
        val transition = elementTransition(layoutImpl, element, currentTransitions)
        val drawScale = getDrawScale(layoutImpl, scene, element, transition, sceneState)
        val drawScale = getDrawScale(layoutImpl, element, transition, sceneState)
        if (drawScale == Scale.Default) {
        if (drawScale == Scale.Default) {
            drawContent()
            drawContent()
        } else {
        } else {
@@ -801,7 +799,6 @@ private fun isElementOpaque(
 */
 */
private fun elementAlpha(
private fun elementAlpha(
    layoutImpl: SceneTransitionLayoutImpl,
    layoutImpl: SceneTransitionLayoutImpl,
    scene: Scene,
    element: Element,
    element: Element,
    transition: TransitionState.Transition?,
    transition: TransitionState.Transition?,
    sceneState: Element.SceneState,
    sceneState: Element.SceneState,
@@ -809,12 +806,11 @@ private fun elementAlpha(
    val alpha =
    val alpha =
        computeValue(
        computeValue(
                layoutImpl,
                layoutImpl,
                scene,
                sceneState,
                element,
                element,
                transition,
                transition,
                sceneValue = { 1f },
                sceneValue = { 1f },
                transformation = { it.alpha },
                transformation = { it.alpha },
                idleValue = 1f,
                currentValue = { 1f },
                currentValue = { 1f },
                isSpecified = { true },
                isSpecified = { true },
                ::lerp,
                ::lerp,
@@ -862,9 +858,8 @@ private fun interruptedAlpha(
    )
    )
}
}


private fun ApproachMeasureScope.measure(
private fun measure(
    layoutImpl: SceneTransitionLayoutImpl,
    layoutImpl: SceneTransitionLayoutImpl,
    scene: Scene,
    element: Element,
    element: Element,
    transition: TransitionState.Transition?,
    transition: TransitionState.Transition?,
    sceneState: Element.SceneState,
    sceneState: Element.SceneState,
@@ -879,12 +874,11 @@ private fun ApproachMeasureScope.measure(
    val targetSize =
    val targetSize =
        computeValue(
        computeValue(
            layoutImpl,
            layoutImpl,
            scene,
            sceneState,
            element,
            element,
            transition,
            transition,
            sceneValue = { it.targetSize },
            sceneValue = { it.targetSize },
            transformation = { it.size },
            transformation = { it.size },
            idleValue = lookaheadSize,
            currentValue = { measurable.measure(constraints).also { maybePlaceable = it }.size() },
            currentValue = { measurable.measure(constraints).also { maybePlaceable = it }.size() },
            isSpecified = { it != Element.SizeUnspecified },
            isSpecified = { it != Element.SizeUnspecified },
            ::lerp,
            ::lerp,
@@ -930,7 +924,6 @@ private fun Placeable.size(): IntSize = IntSize(width, height)


private fun ContentDrawScope.getDrawScale(
private fun ContentDrawScope.getDrawScale(
    layoutImpl: SceneTransitionLayoutImpl,
    layoutImpl: SceneTransitionLayoutImpl,
    scene: Scene,
    element: Element,
    element: Element,
    transition: TransitionState.Transition?,
    transition: TransitionState.Transition?,
    sceneState: Element.SceneState,
    sceneState: Element.SceneState,
@@ -938,12 +931,11 @@ private fun ContentDrawScope.getDrawScale(
    val scale =
    val scale =
        computeValue(
        computeValue(
            layoutImpl,
            layoutImpl,
            scene,
            sceneState,
            element,
            element,
            transition,
            transition,
            sceneValue = { Scale.Default },
            sceneValue = { Scale.Default },
            transformation = { it.drawScale },
            transformation = { it.drawScale },
            idleValue = Scale.Default,
            currentValue = { Scale.Default },
            currentValue = { Scale.Default },
            isSpecified = { true },
            isSpecified = { true },
            ::lerp,
            ::lerp,
@@ -1010,11 +1002,12 @@ private fun ContentDrawScope.getDrawScale(
 * Measurable.
 * Measurable.
 *
 *
 * @param layoutImpl the [SceneTransitionLayoutImpl] associated to [element].
 * @param layoutImpl the [SceneTransitionLayoutImpl] associated to [element].
 * @param scene the scene containing [element].
 * @param currentSceneState the scene state of the scene for which we are computing the value. Note
 *   that during interruptions, this could be the state of a scene that is neither
 *   [transition.toScene] nor [transition.fromScene].
 * @param element the element being animated.
 * @param element the element being animated.
 * @param sceneValue the value being animated.
 * @param sceneValue the value being animated.
 * @param transformation the transformation associated to the value being animated.
 * @param transformation the transformation associated to the value being animated.
 * @param idleValue the value when idle, i.e. when there is no transition happening.
 * @param currentValue the value that would be used if it is not transformed. Note that this is
 * @param currentValue the value that would be used if it is not transformed. Note that this is
 *   different than [idleValue] even if the value is not transformed directly because it could be
 *   different than [idleValue] even if the value is not transformed directly because it could be
 *   impacted by the transformations on other elements, like a parent that is being translated or
 *   impacted by the transformations on other elements, like a parent that is being translated or
@@ -1024,12 +1017,11 @@ private fun ContentDrawScope.getDrawScale(
 */
 */
private inline fun <T> computeValue(
private inline fun <T> computeValue(
    layoutImpl: SceneTransitionLayoutImpl,
    layoutImpl: SceneTransitionLayoutImpl,
    scene: Scene,
    currentSceneState: Element.SceneState,
    element: Element,
    element: Element,
    transition: TransitionState.Transition?,
    transition: TransitionState.Transition?,
    sceneValue: (Element.SceneState) -> T,
    sceneValue: (Element.SceneState) -> T,
    transformation: (ElementTransformations) -> PropertyTransformation<T>?,
    transformation: (ElementTransformations) -> PropertyTransformation<T>?,
    idleValue: T,
    currentValue: () -> T,
    currentValue: () -> T,
    isSpecified: (T) -> Boolean,
    isSpecified: (T) -> Boolean,
    lerp: (T, T, Float) -> T,
    lerp: (T, T, Float) -> T,
@@ -1051,19 +1043,22 @@ private inline fun <T> computeValue(
    if (fromState == null && toState == null) {
    if (fromState == null && toState == null) {
        // TODO(b/311600838): Throw an exception instead once layers of disposed elements are not
        // TODO(b/311600838): Throw an exception instead once layers of disposed elements are not
        // run anymore.
        // run anymore.
        return idleValue
        return sceneValue(currentSceneState)
    }
    }


    val currentScene = currentSceneState.scene
    if (transition is TransitionState.HasOverscrollProperties) {
    if (transition is TransitionState.HasOverscrollProperties) {
        val overscroll = transition.currentOverscrollSpec
        val overscroll = transition.currentOverscrollSpec
        if (overscroll?.scene == scene.key) {
        if (overscroll?.scene == currentScene) {
            val elementSpec = overscroll.transformationSpec.transformations(element.key, scene.key)
            val elementSpec =
                overscroll.transformationSpec.transformations(element.key, currentScene)
            val propertySpec = transformation(elementSpec) ?: return currentValue()
            val propertySpec = transformation(elementSpec) ?: return currentValue()
            val overscrollState = checkNotNull(if (scene.key == toScene) toState else fromState)
            val overscrollState = checkNotNull(if (currentScene == toScene) toState else fromState)
            val idleValue = sceneValue(overscrollState)
            val targetValue =
            val targetValue =
                propertySpec.transform(
                propertySpec.transform(
                    layoutImpl,
                    layoutImpl,
                    scene,
                    currentScene,
                    element,
                    element,
                    overscrollState,
                    overscrollState,
                    transition,
                    transition,
@@ -1107,24 +1102,30 @@ private inline fun <T> computeValue(
        return if (start == end) start else lerp(start, end, transition.progress)
        return if (start == end) start else lerp(start, end, transition.progress)
    }
    }


    val transformation =
        transformation(transition.transformationSpec.transformations(element.key, scene.key))
            // 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
            // pass).
            ?: return currentValue()

    // Get the transformed value, i.e. the target value at the beginning (for entering elements) or
    // Get the transformed value, i.e. the target value at the beginning (for entering elements) or
    // end (for leaving elements) of the transition.
    // end (for leaving elements) of the transition.
    val sceneState =
    val sceneState =
        checkNotNull(
        checkNotNull(
            when {
            when {
                isSharedElement && scene.key == fromScene -> fromState
                isSharedElement && currentScene == fromScene -> fromState
                isSharedElement -> toState
                isSharedElement -> toState
                else -> fromState ?: toState
                else -> fromState ?: toState
            }
            }
        )
        )


    // The scene for which we compute the transformation. Note that this is not necessarily
    // [currentScene] because [currentScene] could be a different scene than the transition
    // fromScene or toScene during interruptions.
    val scene = sceneState.scene

    val transformation =
        transformation(transition.transformationSpec.transformations(element.key, scene))
            // 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
            // pass).
            ?: return currentValue()

    val idleValue = sceneValue(sceneState)
    val targetValue =
    val targetValue =
        transformation.transform(
        transformation.transform(
            layoutImpl,
            layoutImpl,
@@ -1146,7 +1147,7 @@ private inline fun <T> computeValue(
    val rangeProgress = transformation.range?.progress(progress) ?: progress
    val rangeProgress = transformation.range?.progress(progress) ?: progress


    // Interpolate between the value at rest and the value before entering/after leaving.
    // Interpolate between the value at rest and the value before entering/after leaving.
    val isEntering = scene.key == toScene
    val isEntering = scene == toScene
    return if (isEntering) {
    return if (isEntering) {
        lerp(targetValue, idleValue, rangeProgress)
        lerp(targetValue, idleValue, rangeProgress)
    } else {
    } else {
+2 −3
Original line number Original line Diff line number Diff line
@@ -20,7 +20,6 @@ import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.Scene
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.TransitionState
@@ -34,7 +33,7 @@ internal class AnchoredSize(
) : PropertyTransformation<IntSize> {
) : PropertyTransformation<IntSize> {
    override fun transform(
    override fun transform(
        layoutImpl: SceneTransitionLayoutImpl,
        layoutImpl: SceneTransitionLayoutImpl,
        scene: Scene,
        scene: SceneKey,
        element: Element,
        element: Element,
        sceneState: Element.SceneState,
        sceneState: Element.SceneState,
        transition: TransitionState.Transition,
        transition: TransitionState.Transition,
@@ -60,7 +59,7 @@ internal class AnchoredSize(
        // This simple implementation assumes that the size of [element] is the same as the size of
        // This simple implementation assumes that the size of [element] is the same as the size of
        // the [anchor] in [scene], so simply transform to the size of the anchor in the other
        // the [anchor] in [scene], so simply transform to the size of the anchor in the other
        // scene.
        // scene.
        return if (scene.key == transition.fromScene) {
        return if (scene == transition.fromScene) {
            anchorSizeIn(transition.toScene)
            anchorSizeIn(transition.toScene)
        } else {
        } else {
            anchorSizeIn(transition.fromScene)
            anchorSizeIn(transition.fromScene)
+2 −3
Original line number Original line Diff line number Diff line
@@ -21,7 +21,6 @@ import androidx.compose.ui.geometry.isSpecified
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.Scene
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.TransitionState
@@ -33,7 +32,7 @@ internal class AnchoredTranslate(
) : PropertyTransformation<Offset> {
) : PropertyTransformation<Offset> {
    override fun transform(
    override fun transform(
        layoutImpl: SceneTransitionLayoutImpl,
        layoutImpl: SceneTransitionLayoutImpl,
        scene: Scene,
        scene: SceneKey,
        element: Element,
        element: Element,
        sceneState: Element.SceneState,
        sceneState: Element.SceneState,
        transition: TransitionState.Transition,
        transition: TransitionState.Transition,
@@ -61,7 +60,7 @@ internal class AnchoredTranslate(
            anchorOffsetIn(transition.toScene) ?: throwException(transition.toScene)
            anchorOffsetIn(transition.toScene) ?: throwException(transition.toScene)
        val offset = anchorToOffset - anchorFromOffset
        val offset = anchorToOffset - anchorFromOffset


        return if (scene.key == transition.toScene) {
        return if (scene == transition.toScene) {
            Offset(
            Offset(
                value.x - offset.x,
                value.x - offset.x,
                value.y - offset.y,
                value.y - offset.y,
+2 −2
Original line number Original line Diff line number Diff line
@@ -20,7 +20,7 @@ import androidx.compose.ui.geometry.Offset
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.Scale
import com.android.compose.animation.scene.Scale
import com.android.compose.animation.scene.Scene
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.TransitionState


@@ -37,7 +37,7 @@ internal class DrawScale(


    override fun transform(
    override fun transform(
        layoutImpl: SceneTransitionLayoutImpl,
        layoutImpl: SceneTransitionLayoutImpl,
        scene: Scene,
        scene: SceneKey,
        element: Element,
        element: Element,
        sceneState: Element.SceneState,
        sceneState: Element.SceneState,
        transition: TransitionState.Transition,
        transition: TransitionState.Transition,
+3 −3
Original line number Original line Diff line number Diff line
@@ -20,7 +20,7 @@ import androidx.compose.ui.geometry.Offset
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.Scene
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.TransitionState


@@ -32,13 +32,13 @@ internal class EdgeTranslate(
) : PropertyTransformation<Offset> {
) : PropertyTransformation<Offset> {
    override fun transform(
    override fun transform(
        layoutImpl: SceneTransitionLayoutImpl,
        layoutImpl: SceneTransitionLayoutImpl,
        scene: Scene,
        scene: SceneKey,
        element: Element,
        element: Element,
        sceneState: Element.SceneState,
        sceneState: Element.SceneState,
        transition: TransitionState.Transition,
        transition: TransitionState.Transition,
        value: Offset
        value: Offset
    ): Offset {
    ): Offset {
        val sceneSize = scene.targetSize
        val sceneSize = layoutImpl.scene(scene).targetSize
        val elementSize = sceneState.targetSize
        val elementSize = sceneState.targetSize
        if (elementSize == Element.SizeUnspecified) {
        if (elementSize == Element.SizeUnspecified) {
            return value
            return value
Loading