All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.peanuuutz.fork.ui.animation.Animation.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2020 The Android Open Source Project
 * Modifications Copyright 2022 Peanuuutz
 *
 * 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 net.peanuuutz.fork.ui.animation

import androidx.compose.runtime.withFrameMillis
import kotlinx.coroutines.CancellationException
import net.peanuuutz.fork.ui.animation.TimeConstants.UnspecifiedTimeMillis
import net.peanuuutz.fork.ui.animation.spec.decay.DefaultFloatDecayAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.decay.composite.DecayAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.decay.vectorized.VectorizedDecayAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.DefaultFloatAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.composite.AnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.vectorized.VectorizedAnimationSpec
import net.peanuuutz.fork.ui.animation.vector.AnimationVector
import net.peanuuutz.fork.ui.animation.vector.AnimationVector1D
import net.peanuuutz.fork.ui.animation.vector.VectorConvertor
import net.peanuuutz.fork.ui.animation.vector.copy
import net.peanuuutz.fork.ui.animation.vector.newVector

interface Animation> {
    val isInfinite: Boolean

    val convertor: VectorConvertor

    val initialValue: S

    val targetValue: S

    val initialVelocityVector: V

    val endVelocityVector: V

    fun getValueFromMillis(playTimeMillis: Int): S

    fun getVelocityVectorFromMillis(playTimeMillis: Int): V

    val durationMillis: Int
}

fun > Animation.getValueVectorFromMillis(
    playTimeMillis: Int
): V {
    val value = getValueFromMillis(playTimeMillis)
    return convertor.convertToVector(value)
}

fun > Animation.getVelocityFromMillis(
    playTimeMillis: Int
): S {
    val velocityVector = getVelocityVectorFromMillis(playTimeMillis)
    return convertor.convertFromVector(velocityVector)
}

class TargetBasedAnimation>(
    val vectorizedAnimationSpec: VectorizedAnimationSpec,
    override val convertor: VectorConvertor,
    override val initialValue: S,
    override val targetValue: S,
    initialVelocityVector: V? = null
) : Animation {
    override val isInfinite: Boolean
        get() = vectorizedAnimationSpec.isInfinite

    private val initialValueVector: V = convertor.convertToVector(initialValue)

    private val targetValueVector: V = convertor.convertToVector(targetValue)

    override val initialVelocityVector: V = initialVelocityVector?.copy() ?: convertor.newVector(initialValue)

    override val endVelocityVector: V = vectorizedAnimationSpec.getEndVelocity(
        initialValue = initialValueVector,
        targetValue = targetValueVector,
        initialVelocity = this.initialVelocityVector
    )

    override fun getValueFromMillis(playTimeMillis: Int): S {
        return if (playTimeMillis < durationMillis) {
            convertor.convertFromVector(
                vector = vectorizedAnimationSpec.getValueFromMillis(
                    playTimeMillis = playTimeMillis,
                    initialValue = initialValueVector,
                    targetValue = targetValueVector,
                    initialVelocity = initialVelocityVector
                )
            )
        } else {
            targetValue
        }
    }

    override fun getVelocityVectorFromMillis(playTimeMillis: Int): V {
        return if (playTimeMillis < durationMillis) {
            vectorizedAnimationSpec.getVelocityFromMillis(
                playTimeMillis = playTimeMillis,
                initialValue = initialValueVector,
                targetValue = targetValueVector,
                initialVelocity = initialVelocityVector
            )
        } else {
            endVelocityVector
        }
    }

    override val durationMillis: Int = vectorizedAnimationSpec.getDurationMillis(
        initialValue = initialValueVector,
        targetValue = targetValueVector,
        initialVelocity = this.initialVelocityVector
    )
}

fun TargetBasedAnimation(
    animationSpec: AnimationSpec = DefaultFloatAnimationSpec,
    initialValue: Float,
    targetValue: Float,
    initialVelocity: Float = 0.0f
): TargetBasedAnimation {
    return TargetBasedAnimation(
        vectorizedAnimationSpec = animationSpec.vectorize(Float.VectorConvertor),
        convertor = Float.VectorConvertor,
        initialValue = initialValue,
        targetValue = targetValue,
        initialVelocityVector = Float.VectorConvertor.convertToVector(initialVelocity)
    )
}

fun > AnimationSpec.createAnimation(
    convertor: VectorConvertor,
    initialValue: S,
    targetValue: S,
    initialVelocityVector: V? = null
): TargetBasedAnimation {
    return TargetBasedAnimation(
        vectorizedAnimationSpec = vectorize(convertor),
        convertor = convertor,
        initialValue = initialValue,
        targetValue = targetValue,
        initialVelocityVector = initialVelocityVector
    )
}

class DecayAnimation>(
    val vectorizedDecayAnimationSpec: VectorizedDecayAnimationSpec,
    override val convertor: VectorConvertor,
    override val initialValue: S,
    initialVelocityVector: V? = null
) : Animation {
    override val isInfinite: Boolean
        get() = false

    private val initialValueVector: V = convertor.convertToVector(initialValue)

    override val initialVelocityVector: V = initialVelocityVector?.copy() ?: convertor.newVector(initialValue)

    override val targetValue: S = convertor.convertFromVector(
        vector = vectorizedDecayAnimationSpec.getTargetValue(initialValueVector, this.initialVelocityVector)
    )

    override val endVelocityVector: V = this.initialVelocityVector.new()

    override fun getValueFromMillis(playTimeMillis: Int): S {
        return if (playTimeMillis < durationMillis) {
            convertor.convertFromVector(
                vector = vectorizedDecayAnimationSpec.getValueFromMillis(
                    playTimeMillis = playTimeMillis,
                    initialValue = initialValueVector,
                    initialVelocity = initialVelocityVector
                )
            )
        } else {
            targetValue
        }
    }

    override fun getVelocityVectorFromMillis(playTimeMillis: Int): V {
        return if (playTimeMillis < durationMillis) {
            vectorizedDecayAnimationSpec.getVelocityFromMillis(
                playTimeMillis = playTimeMillis,
                initialValue = initialValueVector,
                initialVelocity = initialVelocityVector
            )
        } else {
            endVelocityVector
        }
    }

    override val durationMillis: Int = vectorizedDecayAnimationSpec.getDurationMillis(
        initialValue = initialValueVector,
        initialVelocity = this.initialVelocityVector
    )
}

fun DecayAnimation(
    decayAnimationSpec: DecayAnimationSpec = DefaultFloatDecayAnimationSpec,
    initialValue: Float,
    initialVelocity: Float = 0.0f
): DecayAnimation {
    return DecayAnimation(
        vectorizedDecayAnimationSpec = decayAnimationSpec.vectorize(Float.VectorConvertor),
        convertor = Float.VectorConvertor,
        initialValue = initialValue,
        initialVelocityVector = Float.VectorConvertor.convertToVector(initialVelocity)
    )
}

fun > DecayAnimationSpec.createAnimation(
    convertor: VectorConvertor,
    initialValue: S,
    initialVelocityVector: V? = null
): DecayAnimation {
    return DecayAnimation(
        vectorizedDecayAnimationSpec = vectorize(convertor),
        convertor = convertor,
        initialValue = initialValue,
        initialVelocityVector = initialVelocityVector
    )
}

// ======== Internal ========

internal suspend fun > Animation.run(
    startTimeMillis: Int,
    executionState: AnimationStateImpl,
    callback: ((executionState: AnimationStateImpl) -> Unit)? = null
) {
    executionState.isRunning = true
    var resolvedStartTimeMillis = startTimeMillis
    if (startTimeMillis == UnspecifiedTimeMillis) {
        callWithFrameMillis { frameTimeMillis ->
            resolvedStartTimeMillis = frameTimeMillis
            update(frameTimeMillis, frameTimeMillis, executionState, callback)
        }
    } else {
        update(startTimeMillis, startTimeMillis, executionState, callback)
    }
    while (executionState.isRunning) {
        callWithFrameMillis { frameTimeMillis ->
            update(frameTimeMillis, resolvedStartTimeMillis, executionState, callback)
        }
    }
}

private suspend fun > Animation.callWithFrameMillis(
    onFrame: (frameTimeMillis: Int) -> R
): R {
    return if (!isInfinite) {
        withFrameMillis { frameTimeMillisLong ->
            val frameTimeMillis = frameTimeMillisLong.toInt()
            onFrame(frameTimeMillis)
        }
    } else {
        withInfiniteAnimationFrameMillis { frameTimeMillisLong ->
            val frameTimeMillis = frameTimeMillisLong.toInt()
            onFrame(frameTimeMillis)
        }
    }
}

private fun > Animation.update(
    frameTimeMillis: Int,
    startTimeMillis: Int,
    executionState: AnimationStateImpl,
    callback: ((executionState: AnimationStateImpl) -> Unit)? = null
) {
    with(executionState) {
        try {
            lastFrameTimeMillis = frameTimeMillis
            val playTimeMillis = frameTimeMillis - startTimeMillis
            value = getValueFromMillis(playTimeMillis)
            velocityVector = getVelocityVectorFromMillis(playTimeMillis)
            if (playTimeMillis >= durationMillis) {
                finishedTimeMillis = lastFrameTimeMillis
                isRunning = false
            }
            callback?.invoke(this)
        } catch (e: CancellationException) {
            isRunning = false
            throw e
        }
    }
}