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

Commit 11fc2857 authored by Mike Schneider's avatar Mike Schneider
Browse files

Refactor MotionSpecBuilder and Effects to allow directionality

Changes the Effect API to require specifying the placements they
support. During applying the effects, access to the desired placement
is provided again.

Rewrote the placement code for simplicity.

Bug: 401500734
Test: Unit tests
Flag: EXEMPT not yet used in production
Change-Id: Ib17c9f0527206c2af1b8128e128cebf23875b2bc
parent 7947810e
Loading
Loading
Loading
Loading
+13 −14
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.mechanics.effects

import com.android.mechanics.spec.BreakpointKey
import com.android.mechanics.spec.Mapping
import com.android.mechanics.spec.builder.Effect
import com.android.mechanics.spec.builder.EffectApplyScope
@@ -32,9 +33,18 @@ val MotionSpecBuilderScope.one: FixedValue
    get() = FixedValue.One

/** Produces a fixed [value]. */
open class FixedValue(val value: Float) : Effect {

    override fun EffectApplyScope.createSpec() {
class FixedValue(val value: Float) :
    Effect.PlaceableAfter, Effect.PlaceableBefore, Effect.PlaceableBetween {

    override fun MotionBuilderContext.intrinsicSize(): Float = Float.NaN

    override fun EffectApplyScope.createSpec(
        minLimit: Float,
        minLimitKey: BreakpointKey,
        maxLimit: Float,
        maxLimitKey: BreakpointKey,
        placement: EffectPlacement,
    ) {
        return unidirectional(Mapping.Fixed(value))
    }

@@ -43,14 +53,3 @@ open class FixedValue(val value: Float) : Effect {
        val One = FixedValue(1f)
    }
}

/** Produces a fixed [value], for a predefined [extent]. */
class FixedValueWithExtent(value: Float, private val extent: Float) : FixedValue(value) {
    init {
        require(extent > 0)
    }

    override fun MotionBuilderContext.measure(effectPlacement: EffectPlacement): Float {
        return extent * effectPlacement.directionSign
    }
}
+24 −23
Original line number Diff line number Diff line
@@ -54,7 +54,7 @@ class MagneticDetach(
    private val attachScale: Float = Defaults.AttachDetachScale * (attachPosition / detachPosition),
    private val detachSpring: SpringParameters = Defaults.Spring,
    private val attachSpring: SpringParameters = Defaults.Spring,
) : Effect {
) : Effect.PlaceableAfter, Effect.PlaceableBefore {

    init {
        require(attachPosition <= detachPosition)
@@ -65,16 +65,20 @@ class MagneticDetach(
        Detached,
    }

    override fun MotionBuilderContext.measure(effectPlacement: EffectPlacement): Float {
        return detachPosition.toPx() * effectPlacement.directionSign
    override fun MotionBuilderContext.intrinsicSize(): Float {
        return detachPosition.toPx()
    }

    override fun EffectApplyScope.createSpec() {
        val startPos = minLimit
        val reattachPos = startPos + attachPosition.toPx()
        val detachPos = maxLimit
        val startValue = baseValue(startPos)
        val detachValue = baseValue(detachPos)
    override fun EffectApplyScope.createSpec(
        minLimit: Float,
        minLimitKey: BreakpointKey,
        maxLimit: Float,
        maxLimitKey: BreakpointKey,
        placement: EffectPlacement,
    ) {
        val reattachPos = minLimit + attachPosition.toPx()
        val startValue = baseValue(minLimit)
        val detachValue = baseValue(maxLimit)
        val reattachValue = baseValue(reattachPos)

        val scaledDetachValue = startValue + (detachValue - startValue) * detachScale
@@ -83,15 +87,14 @@ class MagneticDetach(
        val attachKey = BreakpointKey("attach")

        forward(
            initialMapping = Mapping.Linear(startPos, startValue, detachPos, scaledDetachValue),
            initialMapping = Mapping.Linear(minLimit, startValue, maxLimit, scaledDetachValue),
            semantics = listOf(semanticState with State.Attached),
        ) {
            maxLimitSpring = detachSpring
            maxLimitSemantics = listOf(semanticState with State.Detached)
            after(spring = detachSpring, semantics = listOf(semanticState with State.Detached))
        }

        backward(
            initialMapping = Mapping.Linear(startPos, startValue, reattachPos, scaledReattachValue),
            initialMapping = Mapping.Linear(minLimit, startValue, reattachPos, scaledReattachValue),
            semantics = listOf(semanticState with State.Attached),
        ) {
            mapping(
@@ -125,10 +128,10 @@ class MagneticDetach(

                val tweakedMapping = Mapping { input ->
                    if (input <= pivotPos) {
                        val t = (input - startPos) / (pivotPos - startPos)
                        val t = (input - minLimit) / (pivotPos - minLimit)
                        lerp(startValue, pivotValue, t)
                    } else {
                        val t = (input - pivotPos) / (detachPos - pivotPos)
                        val t = (input - pivotPos) / (maxLimit - pivotPos)
                        lerp(pivotValue, scaledDetachValue, t)
                    }
                }
@@ -139,7 +142,6 @@ class MagneticDetach(
        }
    }

    companion object {
    object Defaults {
        val AttachDetachState = SemanticKey<State>()
        val AttachDetachScale = .3f
@@ -148,4 +150,3 @@ class MagneticDetach(
        val Spring = SpringParameters(stiffness = 800f, dampingRatio = 0.95f)
    }
}
}
+0 −13
Original line number Diff line number Diff line
@@ -136,19 +136,6 @@ fun interface Mapping {
        }
    }

    data class Tanh(val scaling: Float, val tilt: Float, val offset: Float = 0f) : Mapping {

        init {
            require(scaling.isFinite())
            require(tilt.isFinite())
            require(offset.isFinite())
        }

        override fun map(input: Float): Float {
            return scaling * kotlin.math.tanh((input + offset) / (scaling * tilt))
        }
    }

    companion object {
        val Zero = Fixed(0f)
        val One = Fixed(1f)
+2 −2
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ import com.android.mechanics.spring.SpringParameters
 *
 * Clients must use [directionalMotionSpec] instead.
 */
internal class DirectionalBuilderImpl(
internal open class DirectionalBuilderImpl(
    override val defaultSpring: SpringParameters,
    baseSemantics: List<SemanticValue<*>>,
) : DirectionalBuilderScope {
@@ -58,7 +58,7 @@ internal class DirectionalBuilderImpl(
        initialSemantics.forEach { semantic ->
            val existingBuilder = semantics.firstOrNull { it.key == semantic.key }
            if (existingBuilder != null) {
                existingBuilder.backfill(mappings.size)
                existingBuilder.backfill(mappings.size - 1)
                existingBuilder.append(semantic.value)
            } else {
                SegmentSemanticValuesBuilder(semantic).also { semantics.add(it) }
+28 −21
Original line number Diff line number Diff line
@@ -16,41 +16,48 @@

package com.android.mechanics.spec.builder

import com.android.mechanics.spec.BreakpointKey

/**
 * Blueprint for a reusable behavior in a [MotionSpec].
 *
 * [Effect] instances are reusable for building multiple
 */
interface Effect {
    /**
     * Defines the intrinsic length of the effect.
     *
     * The return value takes precedent over the [effectPlacement]. By default, the result is
     * derived from the [effectPlacement].
     *
     * Returning `Float.POSITIVE/NEGATIVE_INFINITY` will cause the effect to extend to the start of
     * the next effect, or the boundary of the effect.
     */
    fun MotionBuilderContext.measure(effectPlacement: EffectPlacement): Float {
        return if (effectPlacement.end.isFinite()) {
            effectPlacement.end - effectPlacement.start
        } else {
            effectPlacement.end
        }
    }
sealed interface Effect {

    /**
     * Applies the effect to the motion spec.
     *
     * The boundaries of the effect are defined by the [EffectApplyScope.minLimit] and
     * [EffectApplyScope.maxLimit] properties, and extend in both, the min and max direction by the
     * same amount.
     * The boundaries of the effect are defined by the [minLimit] and [maxLimit] properties, and
     * extend in both, the min and max direction by the same amount.
     *
     * Implementations must invoke either [EffectApplyScope.unidirectional] or both,
     * [EffectApplyScope.forward] and [EffectApplyScope.backward]. The motion spec builder will
     * throw if neither is called.
     */
    fun EffectApplyScope.createSpec()
    fun EffectApplyScope.createSpec(
        minLimit: Float,
        minLimitKey: BreakpointKey,
        maxLimit: Float,
        maxLimitKey: BreakpointKey,
        placement: EffectPlacement,
    )

    interface PlaceableAfter : Effect {
        fun MotionBuilderContext.intrinsicSize(): Float
    }

    interface PlaceableBefore : Effect {
        fun MotionBuilderContext.intrinsicSize(): Float
    }

    interface PlaceableBetween : Effect

    interface PlaceableAt : Effect {
        fun MotionBuilderContext.minExtent(): Float

        fun MotionBuilderContext.maxExtent(): Float
    }
}

/**
Loading