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

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

Introduce TransformationMatcher and Transformation.Factory

This CL introduces TransformationMatcher and Transformation.Factory. The
goal of this change is twofold:

1. It lifts the ElementMatcher out of the Transformation interface.
2. It ensures that we use different transformation objects when multiple
   elements match the same transformation. This will be important once
   transformation become stateful (e.g. with Motion Mechanics or custom
   transformations), as we don't want different elements to implicitly
   share the same transformation state.

Bug: 376438969
Test: atest PlatformComposeSceneTransitionLayoutTests
Flag: com.android.systemui.scene_container

Change-Id: I91409aa399cf77960bf8aa8cbd540eb529f313b3
parent a97625ec
Loading
Loading
Loading
Loading
+16 −14
Original line number Diff line number Diff line
@@ -267,11 +267,12 @@ internal class MutableSceneTransitionLayoutStateImpl(
        private set

    /**
     * The flattened list of [SharedElementTransformation] within all the transitions in
     * The flattened list of [SharedElementTransformation.Factory] within all the transitions in
     * [transitionStates].
     */
    private val transformationsWithElevation: List<SharedElementTransformation> by derivedStateOf {
        transformationsWithElevation(transitionStates)
    private val transformationFactoriesWithElevation:
        List<SharedElementTransformation.Factory> by derivedStateOf {
        transformationFactoriesWithElevation(transitionStates)
    }

    override val currentScene: SceneKey
@@ -692,22 +693,23 @@ internal class MutableSceneTransitionLayoutStateImpl(
        animate()
    }

    private fun transformationsWithElevation(
    private fun transformationFactoriesWithElevation(
        transitionStates: List<TransitionState>
    ): List<SharedElementTransformation> {
    ): List<SharedElementTransformation.Factory> {
        return buildList {
            transitionStates.fastForEach { state ->
                if (state !is TransitionState.Transition) {
                    return@fastForEach
                }

                state.transformationSpec.transformations.fastForEach { transformationWithRange ->
                    val transformation = transformationWithRange.transformation
                state.transformationSpec.transformationMatchers.fastForEach { transformationMatcher
                    ->
                    val factory = transformationMatcher.factory
                    if (
                        transformation is SharedElementTransformation &&
                            transformation.elevateInContent != null
                        factory is SharedElementTransformation.Factory &&
                            factory.elevateInContent != null
                    ) {
                        add(transformation)
                        add(factory)
                    }
                }
            }
@@ -722,10 +724,10 @@ internal class MutableSceneTransitionLayoutStateImpl(
     * necessary, for performance.
     */
    internal fun isElevationPossible(content: ContentKey, element: ElementKey?): Boolean {
        if (transformationsWithElevation.isEmpty()) return false
        return transformationsWithElevation.fastAny { transformation ->
            transformation.elevateInContent == content &&
                (element == null || transformation.matcher.matches(element, content))
        if (transformationFactoriesWithElevation.isEmpty()) return false
        return transformationFactoriesWithElevation.fastAny { factory ->
            factory.elevateInContent == content &&
                (element == null || factory.matcher.matches(element, content))
        }
    }
}
+34 −22
Original line number Diff line number Diff line
@@ -36,7 +36,7 @@ import com.android.compose.animation.scene.transformation.InterpolatedScaleTrans
import com.android.compose.animation.scene.transformation.InterpolatedSizeTransformation
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.SharedElementTransformation
import com.android.compose.animation.scene.transformation.Transformation
import com.android.compose.animation.scene.transformation.TransformationMatcher
import com.android.compose.animation.scene.transformation.TransformationWithRange

/** The transitions configuration of a [SceneTransitionLayout]. */
@@ -232,8 +232,8 @@ interface TransformationSpec {
     */
    val distance: UserActionDistance?

    /** The list of [Transformation] applied to elements during this transition. */
    val transformations: List<TransformationWithRange<*>>
    /** The list of [TransformationMatcher] applied to elements during this transformation. */
    val transformationMatchers: List<TransformationMatcher>

    companion object {
        internal val Empty =
@@ -241,7 +241,7 @@ interface TransformationSpec {
                progressSpec = snap(),
                swipeSpec = null,
                distance = null,
                transformations = emptyList(),
                transformationMatchers = emptyList(),
            )
        internal val EmptyProvider = { _: TransitionState.Transition -> Empty }
    }
@@ -272,7 +272,14 @@ internal class TransitionSpecImpl(
                    progressSpec = reverse.progressSpec,
                    swipeSpec = reverse.swipeSpec,
                    distance = reverse.distance,
                    transformations = reverse.transformations.map { it.reversed() },
                    transformationMatchers =
                        reverse.transformationMatchers.map {
                            TransformationMatcher(
                                matcher = it.matcher,
                                factory = it.factory,
                                range = it.range?.reversed(),
                            )
                        },
                )
            },
        )
@@ -325,7 +332,7 @@ internal class TransformationSpecImpl(
    override val progressSpec: AnimationSpec<Float>,
    override val swipeSpec: SpringSpec<Float>?,
    override val distance: UserActionDistance?,
    override val transformations: List<TransformationWithRange<*>>,
    override val transformationMatchers: List<TransformationMatcher>,
) : TransformationSpec {
    private val cache = mutableMapOf<ElementKey, MutableMap<ContentKey, ElementTransformations>>()

@@ -335,7 +342,7 @@ internal class TransformationSpecImpl(
            .getOrPut(content) { computeTransformations(element, content) }
    }

    /** Filter [transformations] to compute the [ElementTransformations] of [element]. */
    /** Filter [transformationMatchers] to compute the [ElementTransformations] of [element]. */
    private fun computeTransformations(
        element: ElementKey,
        content: ContentKey,
@@ -346,46 +353,51 @@ internal class TransformationSpecImpl(
        var drawScale: TransformationWithRange<PropertyTransformation<Scale>>? = null
        var alpha: TransformationWithRange<PropertyTransformation<Float>>? = null

        transformations.fastForEach { transformationWithRange ->
            val transformation = transformationWithRange.transformation
            if (!transformation.matcher.matches(element, content)) {
        transformationMatchers.fastForEach { transformationMatcher ->
            if (!transformationMatcher.matcher.matches(element, content)) {
                return@fastForEach
            }

            when (transformation) {
            when (val transformation = transformationMatcher.factory.create()) {
                is SharedElementTransformation -> {
                    throwIfNotNull(shared, element, name = "shared")
                    shared =
                        transformationWithRange
                            as TransformationWithRange<SharedElementTransformation>
                    shared = TransformationWithRange(transformation, transformationMatcher.range)
                }
                is InterpolatedOffsetTransformation,
                is CustomOffsetTransformation -> {
                    throwIfNotNull(offset, element, name = "offset")
                    offset =
                        transformationWithRange
                            as TransformationWithRange<PropertyTransformation<Offset>>
                        TransformationWithRange(
                            transformation as PropertyTransformation<Offset>,
                            transformationMatcher.range,
                        )
                }
                is InterpolatedSizeTransformation,
                is CustomSizeTransformation -> {
                    throwIfNotNull(size, element, name = "size")
                    size =
                        transformationWithRange
                            as TransformationWithRange<PropertyTransformation<IntSize>>
                        TransformationWithRange(
                            transformation as PropertyTransformation<IntSize>,
                            transformationMatcher.range,
                        )
                }
                is InterpolatedScaleTransformation,
                is CustomScaleTransformation -> {
                    throwIfNotNull(drawScale, element, name = "drawScale")
                    drawScale =
                        transformationWithRange
                            as TransformationWithRange<PropertyTransformation<Scale>>
                        TransformationWithRange(
                            transformation as PropertyTransformation<Scale>,
                            transformationMatcher.range,
                        )
                }
                is InterpolatedAlphaTransformation,
                is CustomAlphaTransformation -> {
                    throwIfNotNull(alpha, element, name = "alpha")
                    alpha =
                        transformationWithRange
                            as TransformationWithRange<PropertyTransformation<Float>>
                        TransformationWithRange(
                            transformation as PropertyTransformation<Float>,
                            transformationMatcher.range,
                        )
                }
            }
        }
+3 −10
Original line number Diff line number Diff line
@@ -26,7 +26,7 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
import com.android.compose.animation.scene.transformation.Transformation
import kotlin.math.tanh

/** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */
@@ -529,15 +529,8 @@ interface PropertyTransformationBuilder {
        anchorHeight: Boolean = true,
    )

    /**
     * Apply a [CustomPropertyTransformation] to one or more elements.
     *
     * @see com.android.compose.animation.scene.transformation.CustomSizeTransformation
     * @see com.android.compose.animation.scene.transformation.CustomOffsetTransformation
     * @see com.android.compose.animation.scene.transformation.CustomAlphaTransformation
     * @see com.android.compose.animation.scene.transformation.CustomScaleTransformation
     */
    fun transformation(transformation: CustomPropertyTransformation<*>)
    /** Apply a [transformation] to the element(s) matching [matcher]. */
    fun transformation(matcher: ElementMatcher, transformation: Transformation.Factory)
}

/** This converter lets you change a linear progress into a function of your choice. */
+35 −25
Original line number Diff line number Diff line
@@ -30,7 +30,6 @@ import androidx.compose.ui.unit.Dp
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.transformation.AnchoredSize
import com.android.compose.animation.scene.transformation.AnchoredTranslate
import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
import com.android.compose.animation.scene.transformation.DrawScale
import com.android.compose.animation.scene.transformation.EdgeTranslate
import com.android.compose.animation.scene.transformation.Fade
@@ -38,8 +37,8 @@ import com.android.compose.animation.scene.transformation.OverscrollTranslate
import com.android.compose.animation.scene.transformation.ScaleSize
import com.android.compose.animation.scene.transformation.SharedElementTransformation
import com.android.compose.animation.scene.transformation.Transformation
import com.android.compose.animation.scene.transformation.TransformationMatcher
import com.android.compose.animation.scene.transformation.TransformationRange
import com.android.compose.animation.scene.transformation.TransformationWithRange
import com.android.compose.animation.scene.transformation.Translate

internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
@@ -88,7 +87,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
        builder: OverscrollBuilder.() -> Unit,
    ): OverscrollSpec {
        val impl = OverscrollBuilderImpl().apply(builder)
        check(impl.transformations.isNotEmpty()) {
        check(impl.transformationMatchers.isNotEmpty()) {
            "This method does not allow empty transformations. " +
                "Use overscrollDisabled($content, $orientation) instead."
        }
@@ -113,7 +112,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
                        progressSpec = snap(),
                        swipeSpec = null,
                        distance = impl.distance,
                        transformations = impl.transformations,
                        transformationMatchers = impl.transformationMatchers,
                    ),
                progressConverter = impl.progressConverter,
            )
@@ -138,7 +137,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
                progressSpec = impl.spec,
                swipeSpec = impl.swipeSpec,
                distance = impl.distance,
                transformations = impl.transformations,
                transformationMatchers = impl.transformationMatchers,
            )
        }

@@ -158,7 +157,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
}

internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
    val transformations = mutableListOf<TransformationWithRange<*>>()
    val transformationMatchers = mutableListOf<TransformationMatcher>()
    private var range: TransformationRange? = null
    protected var reversed = false
    override var distance: UserActionDistance? = null
@@ -174,23 +173,31 @@ internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
        range = null
    }

    protected fun addTransformation(transformation: Transformation) {
        val transformationWithRange = TransformationWithRange(transformation, range)
        transformations.add(
    protected fun addTransformation(
        matcher: ElementMatcher,
        transformation: Transformation.Factory,
    ) {
        transformationMatchers.add(
            TransformationMatcher(
                matcher,
                transformation,
                range?.let { range ->
                    if (reversed) {
                transformationWithRange.reversed()
                        range.reversed()
                    } else {
                transformationWithRange
                        range
                    }
                },
            )
        )
    }

    override fun fade(matcher: ElementMatcher) {
        addTransformation(Fade(matcher))
        addTransformation(matcher, Fade.Factory)
    }

    override fun translate(matcher: ElementMatcher, x: Dp, y: Dp) {
        addTransformation(Translate(matcher, x, y))
        addTransformation(matcher, Translate.Factory(x, y))
    }

    override fun translate(
@@ -198,19 +205,19 @@ internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
        edge: Edge,
        startsOutsideLayoutBounds: Boolean,
    ) {
        addTransformation(EdgeTranslate(matcher, edge, startsOutsideLayoutBounds))
        addTransformation(matcher, EdgeTranslate.Factory(edge, startsOutsideLayoutBounds))
    }

    override fun anchoredTranslate(matcher: ElementMatcher, anchor: ElementKey) {
        addTransformation(AnchoredTranslate(matcher, anchor))
        addTransformation(matcher, AnchoredTranslate.Factory(anchor))
    }

    override fun scaleSize(matcher: ElementMatcher, width: Float, height: Float) {
        addTransformation(ScaleSize(matcher, width, height))
        addTransformation(matcher, ScaleSize.Factory(width, height))
    }

    override fun scaleDraw(matcher: ElementMatcher, scaleX: Float, scaleY: Float, pivot: Offset) {
        addTransformation(DrawScale(matcher, scaleX, scaleY, pivot))
        addTransformation(matcher, DrawScale.Factory(scaleX, scaleY, pivot))
    }

    override fun anchoredSize(
@@ -219,12 +226,12 @@ internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
        anchorWidth: Boolean,
        anchorHeight: Boolean,
    ) {
        addTransformation(AnchoredSize(matcher, anchor, anchorWidth, anchorHeight))
        addTransformation(matcher, AnchoredSize.Factory(anchor, anchorWidth, anchorHeight))
    }

    override fun transformation(transformation: CustomPropertyTransformation<*>) {
    override fun transformation(matcher: ElementMatcher, transformation: Transformation.Factory) {
        check(range == null) { "Custom transformations can not be applied inside a range" }
        addTransformation(transformation)
        addTransformation(matcher, transformation)
    }
}

@@ -263,7 +270,10 @@ internal class TransitionBuilderImpl(override val transition: TransitionState.Tr
                "(${transition.toContent.debugName})"
        }

        addTransformation(SharedElementTransformation(matcher, enabled, elevateInContent))
        addTransformation(
            matcher,
            SharedElementTransformation.Factory(matcher, enabled, elevateInContent),
        )
    }

    override fun timestampRange(
@@ -294,6 +304,6 @@ internal open class OverscrollBuilderImpl : BaseTransitionBuilderImpl(), Overscr
        x: OverscrollScope.() -> Float,
        y: OverscrollScope.() -> Float,
    ) {
        addTransformation(OverscrollTranslate(matcher, x, y))
        addTransformation(matcher, OverscrollTranslate.Factory(x, y))
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -385,7 +385,7 @@ sealed interface TransitionState {
                    else -> null
                } ?: return true

            return specForCurrentScene.transformationSpec.transformations.isNotEmpty()
            return specForCurrentScene.transformationSpec.transformationMatchers.isNotEmpty()
        }

        internal open fun interruptionProgress(layoutImpl: SceneTransitionLayoutImpl): Float {
Loading