ToolingGlue.kt

/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.compose.animation.core

@RequiresOptIn(message = "This API is internal to library.")
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
annotation class InternalAnimationApi

/**
 * Seekable animation class provides utilities to create an animation using a state pair, and
 * supports querying animation values based on a specific time. This class is designed to be
 * entirely stateless in terms of animation lifecycle. This design makes it easy for higher level
 * stateful construct to be built on top of it.
 *
 * This API is intended for tools' use only. Hence the @InternalAnimationApi.
 */
/** @suppress */
@InternalAnimationApi
class SeekableAnimation<T>(
    val def: TransitionDefinition<T>,
    val fromState: T,
    val toState: T
) {
    private val currentValues: MutableMap<PropKey<Any, AnimationVector>, Any> = mutableMapOf()
    private val currentAnimWrappers: MutableMap<PropKey<Any, AnimationVector>,
        Animation<Any, AnimationVector>> = mutableMapOf()

    init {
        val to = def.states[toState]!!
        val from = def.states[fromState]!!
        currentValues.putAll(from.props)
        val transSpec: TransitionSpec<T> = def.getSpec(fromState, toState)
        // Initialize currentAnimWrappers
        for ((prop, _) in to.props) {
            currentAnimWrappers[prop] = prop.createAnimationWrapper(
                transSpec.getAnimationForProp(prop), from[prop], null, to[prop]
            )
        }
    }

    /**
     * Duration of the animation. When there are multiple properties being animated in a
     * transition, this will be the duration of the longest running animation.
     */
    val duration: Long =
        currentAnimWrappers.asSequence().map { it.value.durationMillis }.maxOrNull()!!

    /**
     * Max duration for a single iteration (including delay) of all animations in the list.
     * Non-repeatable animations are factored in as animations with one iteration.
     */
    val maxDurationPerIteration: Long =
        currentAnimWrappers.asSequence().map {
            ((it.value as? TargetBasedAnimation)?.animationSpec as? VectorizedRepeatableSpec)
                ?.duration?.toLong()
                ?: it.value.durationMillis
        }.maxOrNull()!!

    /**
     * Returns the animation values at the given playtime. This time could be any time between 0
     * and duration, where 0 means the beginning of the animation.
     *
     * @param playTime animation play time in [0, duration]
     */
    fun getAnimValuesAt(playTime: Long): Map<PropKey<Any, AnimationVector>, Any> {
        if (playTime <= 0) {
            currentValues.putAll(def.states[fromState]!!.props)
        } else if (playTime >= duration) {
            currentValues.putAll(def.states[toState]!!.props)
        } else {
            for ((prop, animation) in currentAnimWrappers) {
                currentValues[prop] = animation.getValue(playTime)
            }
        }
        return currentValues
    }
}

/**
 * Creates a [SeekableAnimation] using the same [TransitionDefinition] that the
 * [TransitionAnimation] is created from.
 *
 * Note: This API is intended for tools' use only. Hence the @InternalAnimationApi.
 *
 * @param fromState The state that a [SeekableAnimation] will start from.
 * @param toState The state that a [SeekableAnimation] will end in.
 * @suppress
 */
@InternalAnimationApi
fun <T> TransitionAnimation<T>.createSeekableAnimation(fromState: T, toState: T) =
    SeekableAnimation<T>(def, fromState, toState)

/**
 * Returns the all states available in a [TransitionDefinition].
 *
 * This API is intended for tools' use only. Hence the @InternalAnimationApi.
 * @suppress
 */
@InternalAnimationApi
fun <T> TransitionAnimation<T>.getStates(): Set<T> = def.states.keys