AnimationState.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

import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.Uptime

/**
 * [AnimationState] contains the necessary information to indicate the state of an animation.
 * Once an [AnimationState] is constructed, it can only be updated/mutated by animations. If
 * there's a need to mutate some of the fields of an [AnimationState], consider using [copy]
 * functions.
 *
 * @param typeConverter [TwoWayConverter] to convert type [T] from and to [AnimationVector]
 * @param initialValue initial value of the [AnimationState]
 * @param initialVelocityVector initial velocity of the [AnimationState], null (i.e. no velocity)
 *                              by default.
 * @param lastFrameTime last frame time of the animation, [Uptime.Unspecified] by default
 * @param finishedTime the time that the animation finished successfully, [Uptime.Unspecified]
 *                     until then
 *
 * @param isRunning whether the [AnimationState] is currently being updated by an animation.
 *                  False by default
 */
class AnimationState<T, V : AnimationVector>(
    val typeConverter: TwoWayConverter<T, V>,
    initialValue: T,
    initialVelocityVector: V? = null,
    lastFrameTime: Uptime = Uptime.Unspecified,
    finishedTime: Uptime = Uptime.Unspecified,
    isRunning: Boolean = false
) : State<T> {
    /**
     * Current value of the [AnimationState].
     */
    override var value: T by mutableStateOf(initialValue)
        internal set

    /**
     * Current velocity vector of the [AnimationState].
     */
    var velocityVector: V =
        initialVelocityVector?.copy() ?: typeConverter.createZeroVectorFrom(initialValue)
        internal set

    /**
     * Last frame time of the animation.
     *
     * If the animation has never started, this will be [Uptime.Unspecified], unless specified
     * otherwise in the [AnimationState] constructor. [lastFrameTime] is updated every frame
     * during an animation. It is also used for starting a sequential animation in
     * [AnimationState.animateTo]. This allows the sequential animation to set its start time
     * to when the previous animation is interrupted or finished.
     */
    var lastFrameTime: Uptime = lastFrameTime
        internal set

    /**
     * The time when the animation finished successfully. If the animation has never finished
     * (i.e. currently running, interrupted, or never started), this will be [Uptime.Unspecified],
     * unless specified otherwise in [AnimationState] constructor.
     */
    var finishedTime: Uptime = finishedTime
        internal set

    /**
     * Indicates whether the animation is currently running.
     */
    var isRunning: Boolean = isRunning
        internal set

    /**
     * Velocity of type [T], converted from [velocityVector].
     */
    val velocity: T
        get() = typeConverter.convertFromVector(velocityVector)
}

/**
 * Indicates whether the given [AnimationState] is for an animation that has finished, indicated by
 * [AnimationState.finishedTime] having a specified value.
 */
val AnimationState<*, *>.isFinished
    get() = finishedTime != Uptime.Unspecified

/**
 * [AnimationScope] provides all the animation related info specific to an animation run. An
 * [AnimationScope] will be accessible during an animation.
 *
 * @see [AnimationState.animateTo]
 */
class AnimationScope<T, V : AnimationVector> internal constructor(
    initialValue: T,
    /**
     * [TwoWayConverter] to convert type [T] from and to [AnimationVector].
     */
    val typeConverter: TwoWayConverter<T, V>,
    initialVelocityVector: V,
    lastFrameTime: Uptime,
    /**
     * Target value of the animation.
     */
    val targetValue: T,
    /**
     * Start time of the animation.
     */
    val startTime: Uptime,
    isRunning: Boolean,
    private val onCancel: () -> Unit
) {
    // Externally immutable fields
    var value: T by mutableStateOf(initialValue)
        internal set
    var velocityVector: V = initialVelocityVector.copy()
        internal set
    var lastFrameTime: Uptime = lastFrameTime
        internal set
    var finishedTime: Uptime = Uptime.Unspecified
        internal set
    var isRunning: Boolean by mutableStateOf(isRunning)
        internal set

    /**
     * Velocity of type [T], converted from [velocityVector].
     */
    val velocity
        get() = typeConverter.convertFromVector(velocityVector)

    /**
     * Cancels the animation that this [AnimationScope] corresponds to. The scope will not be
     * updated any more after [cancelAnimation] is called.
     */
    fun cancelAnimation() {
        isRunning = false
        onCancel()
    }

    /**
     * Creates an [AnimationState] that populates all the fields in [AnimationState] from
     * [AnimationScope].
     */
    fun toAnimationState() = AnimationState(
        typeConverter, value, velocityVector, lastFrameTime, finishedTime, isRunning
    )
}

/**
 * Creates a new [AnimationState] from a given [AnimationState]. This function allows some of the
 * fields to be different in the new [AnimationState].
 *
 * @param value value of the [AnimationState], using the value of the given [AnimationState] by
 *              default
 * @param velocityVector velocity of the [AnimationState], using the velocity of the given
 *                 [AnimationState] by default.
 * @param lastFrameTime last frame time of the animation, same as the given [AnimationState] by
 *                      default
 * @param finishedTime the time that the animation finished successfully, [Uptime.Unspecified] until
 *                     then. Default value is the same as the given [AnimationState].
 * @param isRunning whether the [AnimationState] is currently being updated by an animation.
 *                  Same as the given [AnimationState] by default
 *
 * @return A new [AnimationState] instance copied from the given instance, with some fields
 *         optionally altered
 */
fun <T, V : AnimationVector> AnimationState<T, V>.copy(
    value: T = this.value,
    velocityVector: V? = this.velocityVector.copy(),
    lastFrameTime: Uptime = this.lastFrameTime,
    finishedTime: Uptime = this.finishedTime,
    isRunning: Boolean = this.isRunning
): AnimationState<T, V> =
    AnimationState(
        this.typeConverter, value, velocityVector, lastFrameTime, finishedTime, isRunning
    )

/**
 * Creates a new [AnimationState] of Float [value] type from a given [AnimationState] of the same
 * type. This function allows some of the fields to be different in the new [AnimationState].
 *
 * @param value value of the [AnimationState], using the value of the given [AnimationState] by
 *              default
 * @param velocity velocity of the [AnimationState], using the velocity of the given
 *                 [AnimationState] by default.
 * @param lastFrameTime last frame time of the animation, same as the given [AnimationState] by
 *                      default
 * @param finishedTime the time that the animation finished successfully, same as the given
 *                     [AnimationState] by default.
 * @param isRunning whether the [AnimationState] is currently being updated by an animation.
 *                  Same as the given [AnimationState] by default
 *
 * @return A new [AnimationState] instance copied from the given instance, with some fields
 *         optionally altered
 */
fun AnimationState<Float, AnimationVector1D>.copy(
    value: Float = this.value,
    velocity: Float = this.velocityVector.value,
    lastFrameTime: Uptime = this.lastFrameTime,
    finishedTime: Uptime = this.finishedTime,
    isRunning: Boolean = this.isRunning
): AnimationState<Float, AnimationVector1D> =
    AnimationState(
        this.typeConverter, value, AnimationVector(velocity), lastFrameTime, finishedTime, isRunning
    )

/**
 * Factory method for creating an [AnimationState] for Float [initialValue].
 *
 * @param initialValue initial value of the [AnimationState]
 * @param initialVelocity initial velocity of the [AnimationState], 0 (i.e. no velocity) by default
 * @param lastFrameTime last frame time of the animation, [Uptime.Unspecified] by default
 * @param finishedTime the time that the animation finished successfully, [Uptime.Unspecified] by
 *                     default.
 * @param isRunning whether the [AnimationState] is currently being updated by an animation.
 *                  False by default
 *
 * @return A new [AnimationState] instance
 */
fun AnimationState(
    initialValue: Float,
    initialVelocity: Float = 0f,
    lastFrameTime: Uptime = Uptime.Unspecified,
    finishedTime: Uptime = Uptime.Unspecified,
    isRunning: Boolean = false
): AnimationState<Float, AnimationVector1D> {
    return AnimationState(
        Float.VectorConverter,
        initialValue,
        AnimationVector(initialVelocity),
        lastFrameTime,
        finishedTime,
        isRunning
    )
}

/**
 * Factory method for creating an [AnimationState] with an [initialValue] and an [initialVelocity].
 *
 * @param typeConverter [TwoWayConverter] to convert type [T] from and to [AnimationVector]
 * @param initialValue initial value of the [AnimationState]
 * @param initialVelocity initial velocity of the [AnimationState]
 * @param lastFrameTime last frame time of the animation, [Uptime.Unspecified] by default
 * @param finishedTime the time that the animation finished successfully, [Uptime.Unspecified] by
 *                     default.
 * @param isRunning whether the [AnimationState] is currently being updated by an animation.
 *                  False by default
 *
 * @return A new [AnimationState] instance
 */
fun <T, V : AnimationVector> AnimationState(
    typeConverter: TwoWayConverter<T, V>,
    initialValue: T,
    initialVelocity: T,
    lastFrameTime: Uptime = Uptime.Unspecified,
    finishedTime: Uptime = Uptime.Unspecified,
    isRunning: Boolean = false
): AnimationState<T, V> {
    return AnimationState(
        typeConverter,
        initialValue,
        typeConverter.convertToVector(initialVelocity),
        lastFrameTime,
        finishedTime,
        isRunning
    )
}

/**
 * Creates an AnimationVector with all the values set to 0 using the provided [TwoWayConverter]
 * and the [value].
 *
 * @return a new AnimationVector instance of type [V].
 */
fun <T, V : AnimationVector> TwoWayConverter<T, V>.createZeroVectorFrom(value: T) =
    convertToVector(value).newInstance()