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

Commit 435abf43 authored by Johannes Gallmann's avatar Johannes Gallmann Committed by Android (Google) Code Review
Browse files

Merge "Add support for two-stage transitions in STL" into main

parents 718b4328 e4c655c5
Loading
Loading
Loading
Loading
+92 −4
Original line number Diff line number Diff line
@@ -1133,10 +1133,98 @@ private inline fun <T> computeValue(

    val transformation =
        transformation(transition.transformationSpec.transformations(element.key, scene))

    val previewTransformation =
        transition.previewTransformationSpec?.let {
            transformation(it.transformations(element.key, scene))
        }
    if (previewTransformation != null) {
        val isInPreviewStage = transition.isInPreviewStage

        val idleValue = sceneValue(sceneState)
        val isEntering = scene == toScene
        val previewTargetValue =
            previewTransformation.transform(
                layoutImpl,
                scene,
                element,
                sceneState,
                transition,
                idleValue,
            )

        val targetValueOrNull =
            transformation?.transform(
                layoutImpl,
                scene,
                element,
                sceneState,
                transition,
                idleValue,
            )

        // Make sure we don't read progress if values are the same and we don't need to interpolate,
        // so we don't invalidate the phase where this is read.
        when {
            isInPreviewStage && isEntering && previewTargetValue == targetValueOrNull ->
                return previewTargetValue
            isInPreviewStage && !isEntering && idleValue == previewTargetValue -> return idleValue
            previewTargetValue == targetValueOrNull && idleValue == previewTargetValue ->
                return idleValue
            else -> {}
        }

        val previewProgress = transition.previewProgress
        // progress is not needed for all cases of the below when block, therefore read it lazily
        // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range
        val previewRangeProgress =
            previewTransformation.range?.progress(previewProgress) ?: previewProgress

        if (isInPreviewStage) {
            // if we're in the preview stage of the transition, interpolate between start state and
            // preview target state:
            return if (isEntering) {
                // i.e. in the entering case between previewTargetValue and targetValue (or
                // idleValue if no transformation is defined in the second stage transition)...
                lerp(previewTargetValue, targetValueOrNull ?: idleValue, previewRangeProgress)
            } else {
                // ...and in the exiting case between the idleValue and the previewTargetValue.
                lerp(idleValue, previewTargetValue, previewRangeProgress)
            }
        }

        // if we're in the second stage of the transition, interpolate between the state the
        // element was left at the end of the preview-phase and the target state:
        return if (isEntering) {
            // i.e. in the entering case between preview-end-state and the idleValue...
            lerp(
                lerp(previewTargetValue, targetValueOrNull ?: idleValue, previewRangeProgress),
                idleValue,
                transformation?.range?.progress(transition.progress) ?: transition.progress
            )
        } else {
            if (targetValueOrNull == null) {
                // ... and in the exiting case, the element should remain in the preview-end-state
                // if no further transformation is defined in the second-stage transition...
                lerp(idleValue, previewTargetValue, previewRangeProgress)
            } else {
                // ...and otherwise it should be interpolated between preview-end-state and
                // targetValue
                lerp(
                    lerp(idleValue, previewTargetValue, previewRangeProgress),
                    targetValueOrNull,
                    transformation.range?.progress(transition.progress) ?: transition.progress
                )
            }
        }
    }

    if (transformation == null) {
        // 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()
        return currentValue()
    }

    val idleValue = sceneValue(sceneState)
    val targetValue =
+12 −3
Original line number Diff line number Diff line
@@ -77,8 +77,17 @@ private class PredictiveBackTransition(
    private var progressAnimatable by mutableStateOf<Animatable<Float, AnimationVector1D>?>(null)
    var dragProgress: Float by mutableFloatStateOf(0f)

    override val previewProgress: Float
        get() = dragProgress

    override val previewProgressVelocity: Float
        get() = 0f // Currently, velocity is not exposed by predictive back API

    override val isInPreviewStage: Boolean
        get() = progressAnimatable == null && previewTransformationSpec != null

    override val progress: Float
        get() = progressAnimatable?.value ?: dragProgress
        get() = progressAnimatable?.value ?: previewTransformationSpec?.let { 0f } ?: dragProgress

    override val progressVelocity: Float
        get() = progressAnimatable?.velocity ?: 0f
@@ -109,8 +118,8 @@ private class PredictiveBackTransition(
                toScene -> 1f
                else -> error("scene $currentScene should be either $fromScene or $toScene")
            }

        val animatable = Animatable(dragProgress).also { progressAnimatable = it }
        val startProgress = if (previewTransformationSpec != null) 0f else dragProgress
        val animatable = Animatable(startProgress).also { progressAnimatable = it }

        // Important: We start atomically to make sure that we start the coroutine even if it is
        // cancelled right after it is launched, so that finishTransition() is correctly called.
+18 −0
Original line number Diff line number Diff line
@@ -189,6 +189,19 @@ sealed interface TransitionState {
        /** The current velocity of [progress], in progress units. */
        abstract val progressVelocity: Float

        /**
         * The progress of the preview transition. This is usually in the `[0; 1]` range, but it can
         * also be less than `0` or greater than `1` when using transitions with a spring
         * AnimationSpec or when flinging quickly during a swipe gesture.
         */
        open val previewProgress: Float = 0f

        /** The current velocity of [previewProgress], in progress units. */
        open val previewProgressVelocity: Float = 0f

        /** Whether the transition is currently in the preview stage */
        open val isInPreviewStage: Boolean = false

        /** Whether the transition was triggered by user input rather than being programmatic. */
        abstract val isInitiatedByUserInput: Boolean

@@ -203,6 +216,7 @@ sealed interface TransitionState {
         * [started][MutableSceneTransitionLayoutStateImpl.startTransition].
         */
        internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
        internal var previewTransformationSpec: TransformationSpecImpl? = null
        private var fromOverscrollSpec: OverscrollSpecImpl? = null
        private var toOverscrollSpec: OverscrollSpecImpl? = null

@@ -437,6 +451,10 @@ internal class MutableSceneTransitionLayoutStateImpl(
            transitions
                .transitionSpec(fromScene, toScene, key = transition.key)
                .transformationSpec()
        transition.previewTransformationSpec =
            transitions
                .transitionSpec(fromScene, toScene, key = transition.key)
                .previewTransformationSpec()
        if (orientation != null) {
            transition.updateOverscrollSpecs(
                fromSpec = transitions.overscrollSpec(fromScene, orientation),
+18 −3
Original line number Diff line number Diff line
@@ -110,7 +110,7 @@ internal constructor(
    }

    private fun defaultTransition(from: SceneKey, to: SceneKey) =
        TransitionSpecImpl(key = null, from, to, TransformationSpec.EmptyProvider)
        TransitionSpecImpl(key = null, from, to, null, null, TransformationSpec.EmptyProvider)

    internal fun overscrollSpec(scene: SceneKey, orientation: Orientation): OverscrollSpecImpl? =
        overscrollCache
@@ -177,10 +177,18 @@ interface TransitionSpec {
    /**
     * The [TransformationSpec] associated to this [TransitionSpec].
     *
     * Note that this is called once every a transition associated to this [TransitionSpec] is
     * Note that this is called once whenever a transition associated to this [TransitionSpec] is
     * started.
     */
    fun transformationSpec(): TransformationSpec

    /**
     * The preview [TransformationSpec] associated to this [TransitionSpec].
     *
     * Note that this is called once whenever a transition associated to this [TransitionSpec] is
     * started.
     */
    fun previewTransformationSpec(): TransformationSpec?
}

interface TransformationSpec {
@@ -225,13 +233,17 @@ internal class TransitionSpecImpl(
    override val key: TransitionKey?,
    override val from: SceneKey?,
    override val to: SceneKey?,
    private val transformationSpec: () -> TransformationSpecImpl,
    private val previewTransformationSpec: (() -> TransformationSpecImpl)? = null,
    private val reversePreviewTransformationSpec: (() -> TransformationSpecImpl)? = null,
    private val transformationSpec: () -> TransformationSpecImpl
) : TransitionSpec {
    override fun reversed(): TransitionSpecImpl {
        return TransitionSpecImpl(
            key = key,
            from = to,
            to = from,
            previewTransformationSpec = reversePreviewTransformationSpec,
            reversePreviewTransformationSpec = previewTransformationSpec,
            transformationSpec = {
                val reverse = transformationSpec.invoke()
                TransformationSpecImpl(
@@ -245,6 +257,9 @@ internal class TransitionSpecImpl(
    }

    override fun transformationSpec(): TransformationSpecImpl = this.transformationSpec.invoke()

    override fun previewTransformationSpec(): TransformationSpecImpl? =
        previewTransformationSpec?.invoke()
}

/** The definition of the overscroll behavior of the [scene]. */
+12 −0
Original line number Diff line number Diff line
@@ -54,11 +54,17 @@ interface SceneTransitionsBuilder {
     * If [key] is not `null`, then this transition will only be used if the same key is specified
     * when triggering the transition.
     *
     * Optionally, define a [preview] animation which will be played during the first stage of the
     * transition, e.g. during the predictive back gesture. In case your transition should be
     * reversible with the reverse animation having a preview as well, define a [reversePreview].
     *
     * @see from
     */
    fun to(
        to: SceneKey,
        key: TransitionKey? = null,
        preview: (TransitionBuilder.() -> Unit)? = null,
        reversePreview: (TransitionBuilder.() -> Unit)? = null,
        builder: TransitionBuilder.() -> Unit = {},
    ): TransitionSpec

@@ -74,11 +80,17 @@ interface SceneTransitionsBuilder {
     * 2. to == A && from == B, which is then treated in reverse.
     * 3. (from == A && to == null) || (from == null && to == B)
     * 4. (from == B && to == null) || (from == null && to == A), which is then treated in reverse.
     *
     * Optionally, define a [preview] animation which will be played during the first stage of the
     * transition, e.g. during the predictive back gesture. In case your transition should be
     * reversible with the reverse animation having a preview as well, define a [reversePreview].
     */
    fun from(
        from: SceneKey,
        to: SceneKey? = null,
        key: TransitionKey? = null,
        preview: (TransitionBuilder.() -> Unit)? = null,
        reversePreview: (TransitionBuilder.() -> Unit)? = null,
        builder: TransitionBuilder.() -> Unit = {},
    ): TransitionSpec

Loading