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

Commit 301fa079 authored by Mike Schneider's avatar Mike Schneider
Browse files

Allow specifying semantics on a MotionSpec level

Test: Unit tests
Bug: 391553479
Flag: EXEMPT Not yet used
Change-Id: I493386c8403aada7c9349aa6cb1dd81c75ada027
parent 7389cfa1
Loading
Loading
Loading
Loading
+15 −3
Original line number Diff line number Diff line
@@ -31,12 +31,14 @@ import com.android.mechanics.spring.SpringParameters
 *   caused by setting this new spec.
 * @param segmentHandlers allow for custom segment-change logic, when the `MotionValue` runtime
 *   would leave the [SegmentKey].
 * @param semantics semantics applied to the complete [MotionSpec]
 */
data class MotionSpec(
    val maxDirection: DirectionalMotionSpec,
    val minDirection: DirectionalMotionSpec = maxDirection,
    val resetSpring: SpringParameters = DefaultResetSpring,
    val segmentHandlers: Map<SegmentKey, OnChangeSegmentHandler> = emptyMap(),
    val semantics: List<SemanticValue<*>> = emptyList(),
) {

    /** The [DirectionalMotionSpec] for the specified [direction]. */
@@ -52,6 +54,16 @@ data class MotionSpec(
        return get(segmentKey.direction).findSegmentIndex(segmentKey) != -1
    }

    /**
     * The semantic state for [key], as defined for the [MotionSpec].
     *
     * Returns `null` if no semantic value with [key] is defined.
     */
    fun <T> semanticState(key: SemanticKey<T>): T? {
        @Suppress("UNCHECKED_CAST")
        return semantics.fastFirstOrNull { it.key == key }?.value as T?
    }

    /**
     * The semantic state for [key] at segment with [segmentKey].
     *
@@ -60,7 +72,8 @@ data class MotionSpec(
     */
    fun <T> semanticState(key: SemanticKey<T>, segmentKey: SegmentKey): T? {
        with(get(segmentKey.direction)) {
            val semanticValues = semantics.fastFirstOrNull { it.key == key } ?: return null
            val semanticValues =
                semantics.fastFirstOrNull { it.key == key } ?: return semanticState(key)
            val segmentIndex = findSegmentIndex(segmentKey)
            if (segmentIndex < 0) throw NoSuchElementException()

@@ -154,8 +167,7 @@ data class MotionSpec(
 *   element, and [Breakpoint.maxLimit] as the last element.
 * @param mappings All mappings in between the breakpoints, thus must always contain
 *   `breakpoints.size - 1` elements.
 * @param semantics semantics provided by this spec, must only reference to breakpoint keys included
 *   in [breakpoints].
 * @param semantics Semantics that apply to the [MotionSpec].
 */
data class DirectionalMotionSpec(
    val breakpoints: List<Breakpoint>,
+9 −8
Original line number Diff line number Diff line
@@ -33,9 +33,9 @@ fun MotionBuilderContext.spatialMotionSpec(
    baseMapping: Mapping = Mapping.Identity,
    defaultSpring: SpringParameters = this.spatial.default,
    resetSpring: SpringParameters = defaultSpring,
    baseSemantics: List<SemanticValue<*>> = emptyList(),
    semantics: List<SemanticValue<*>> = emptyList(),
    init: MotionSpecBuilderScope.() -> Unit,
) = motionSpec(baseMapping, defaultSpring, resetSpring, baseSemantics, init)
) = motionSpec(baseMapping, defaultSpring, resetSpring, semantics, init)

/**
 * Creates a [MotionSpec] for an effects value.
@@ -49,9 +49,9 @@ fun MotionBuilderContext.effectsMotionSpec(
    baseMapping: Mapping = Mapping.Zero,
    defaultSpring: SpringParameters = this.effects.default,
    resetSpring: SpringParameters = defaultSpring,
    baseSemantics: List<SemanticValue<*>> = emptyList(),
    semantics: List<SemanticValue<*>> = emptyList(),
    init: MotionSpecBuilderScope.() -> Unit,
) = motionSpec(baseMapping, defaultSpring, resetSpring, baseSemantics, init)
) = motionSpec(baseMapping, defaultSpring, resetSpring, semantics, init)

/**
 * Creates a [MotionSpec], based on reusable effects.
@@ -61,21 +61,21 @@ fun MotionBuilderContext.effectsMotionSpec(
 *   unless otherwise specified.
 * @param resetSpring spring parameters to animate a difference in output, if the difference is
 *   caused by setting this new spec.
 * @param baseSemantics initial semantics that apply before of effects override them.
 * @param semantics initial semantics that apply before of effects override them.
 * @param init
 */
fun MotionBuilderContext.motionSpec(
    baseMapping: Mapping,
    defaultSpring: SpringParameters,
    resetSpring: SpringParameters = defaultSpring,
    baseSemantics: List<SemanticValue<*>> = emptyList(),
    semantics: List<SemanticValue<*>> = emptyList(),
    init: MotionSpecBuilderScope.() -> Unit,
): MotionSpec {
    return MotionSpecBuilderImpl(
            baseMapping,
            defaultSpring,
            resetSpring,
            baseSemantics,
            semantics,
            motionBuilderContext = this,
        )
        .apply(init)
@@ -121,8 +121,9 @@ fun MotionBuilderContext.fixedValueSpec(
    semantics: List<SemanticValue<*>> = emptyList(),
): MotionSpec {
    return MotionSpec(
        directionalMotionSpec(Mapping.Fixed(value), semantics),
        directionalMotionSpec(Mapping.Fixed(value)),
        resetSpring = resetSpring,
        semantics = semantics,
    )
}

+11 −7
Original line number Diff line number Diff line
@@ -56,13 +56,17 @@ internal class MotionSpecBuilderImpl(

    fun build(): MotionSpec {
        if (placedEffects.isEmpty()) {
            return MotionSpec(directionalMotionSpec(baseMapping), resetSpring = resetSpring)
            return MotionSpec(
                directionalMotionSpec(baseMapping),
                resetSpring = resetSpring,
                semantics = baseSemantics,
            )
        }

        builders =
            mutableObjectListOf(
                DirectionalEffectBuilderScopeImpl(defaultSpring, baseSemantics),
                DirectionalEffectBuilderScopeImpl(defaultSpring, baseSemantics),
                DirectionalEffectBuilderScopeImpl(defaultSpring),
                DirectionalEffectBuilderScopeImpl(defaultSpring),
            )
        segmentHandlers = mutableMapOf()

@@ -104,6 +108,7 @@ internal class MotionSpecBuilderImpl(
            builders[1].build(),
            resetSpring,
            segmentHandlers.toMap(),
            semantics = baseSemantics,
        )
    }

@@ -452,10 +457,9 @@ internal class MotionSpecBuilderImpl(
    }
}

private class DirectionalEffectBuilderScopeImpl(
    defaultSpring: SpringParameters,
    baseSemantics: List<SemanticValue<*>>,
) : DirectionalBuilderImpl(defaultSpring, baseSemantics), DirectionalEffectBuilderScope {
private class DirectionalEffectBuilderScopeImpl(defaultSpring: SpringParameters) :
    DirectionalBuilderImpl(defaultSpring, baseSemantics = emptyList()),
    DirectionalEffectBuilderScope {

    var beforeGuarantee: Guarantee? = null
    var beforeSpring: SpringParameters? = null
+30 −0
Original line number Diff line number Diff line
@@ -307,6 +307,36 @@ class MotionSpecTest {
        assertFailsWith<NoSuchElementException> { underTest.semantics(unknownSegment) }
    }

    @Test
    fun semantics_atSpecLevel_canBeAssociatedWithSpec() {
        val underTest = MotionSpec(DirectionalMotionSpec.Empty, semantics = listOf(S1 with "One"))

        assertThat(underTest.semanticState(S1)).isEqualTo("One")
    }

    @Test
    fun semantics_atSpecLevel_canBeQueriedViaSegment() {
        val underTest = MotionSpec(DirectionalMotionSpec.Empty, semantics = listOf(S1 with "One"))

        val maxDirectionSegment = SegmentKey(BMin, BMax, InputDirection.Max)
        assertThat(underTest.semanticState(S1, maxDirectionSegment)).isEqualTo("One")
    }

    @Test
    fun semantics_atSpecLevel_segmentLevelTakesPrecedence() {
        val underTest =
            MotionSpec(
                maxDirection = directionalMotionSpec(semantics = listOf(S1 with "Two")),
                minDirection = DirectionalMotionSpec.Empty,
                semantics = listOf(S1 with "One"),
            )

        assertThat(underTest.semanticState(S1, SegmentKey(BMin, BMax, InputDirection.Max)))
            .isEqualTo("Two")
        assertThat(underTest.semanticState(S1, SegmentKey(BMin, BMax, InputDirection.Min)))
            .isEqualTo("One")
    }

    companion object {
        val BMin = Breakpoint.minLimit.key
        val B1 = BreakpointKey("one")
+16 −0
Original line number Diff line number Diff line
@@ -48,6 +48,22 @@ class MotionSpecBuilderTest : MotionBuilderContext by FakeMotionSpecBuilderConte
        assertThat(result).bothDirections().breakpoints().isEmpty()
    }

    @Test
    fun motionSpec_semantics_appliedToSpec() {
        val result = spatialMotionSpec(semantics = listOf(TestSemantics with "One")) {}

        assertThat(result.semanticState(TestSemantics)).isEqualTo("One")
        assertThat(result).bothDirections().semantics().withKey(TestSemantics).isNull()
    }

    @Test
    fun fixedMotionSpec_semantics_appliedToSpec() {
        val result = fixedSpatialValueSpec(0f, semantics = listOf(TestSemantics with "One"))

        assertThat(result.semanticState(TestSemantics)).isEqualTo("One")
        assertThat(result).bothDirections().semantics().withKey(TestSemantics).isNull()
    }

    @Test
    fun placement_absoluteAfter_createsTwoSegments() {
        val result =