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

net.peanuuutz.fork.ui.animation.Transition.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.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameMillis
import net.peanuuutz.fork.ui.animation.TimeConstants.UnspecifiedTimeMillis
import net.peanuuutz.fork.ui.animation.spec.target.composite.AnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.composite.FiniteAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.composite.SpringSpec
import net.peanuuutz.fork.ui.animation.vector.AnimationVector
import net.peanuuutz.fork.ui.animation.vector.VectorConvertor
import net.peanuuutz.fork.ui.animation.vector.newVector
import kotlin.math.max

@Composable
fun  rememberUpdatedTransition(
    targetValue: T
): Transition {
    val transition = remember {
        Transition(TransitionState(targetValue))
    }.apply {
        animateTo(targetValue)
    }

    DisposableEffect(Unit) {
        onDispose {
            transition.onTransitionEnd()
        }
    }

    return transition
}

@Composable
fun  rememberUpdatedTransition(
    transitionState: TransitionState
): Transition {
    val transition = remember {
        Transition(transitionState)
    }.apply {
        animateTo(transitionState.targetValue)
    }

    DisposableEffect(Unit) {
        onDispose {
            transition.onTransitionEnd()
        }
    }

    return transition
}

@Stable
class Transition(transitionState: TransitionState) {
    val state: T
        get() = _transitionState.value

    val targetState: T
        get() = _transitionState.targetValue

    val segment: Segment
        get() = _segment

    inner class Segment(
        val initialState: T,
        val targetState: T
    ) {
        infix fun T.to(targetState: T): Boolean {
            return this == initialState && targetState == [email protected]
        }

        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other !is Transition<*>.Segment) return false
            if (initialState != other.initialState) return false
            if (targetState != other.targetState) return false
            return true
        }

        override fun hashCode(): Int {
            var result = initialState?.hashCode() ?: 0
            result = 31 * result + (targetState?.hashCode() ?: 0)
            return result
        }

        override fun toString(): String {
            return "Transition.Segment(initialState=$initialState, targetState=$targetState)"
        }
    }

    val animationStates: List>
        get() = _animationStates

    @Stable
    abstract inner class AnimationState> : State {
        abstract val convertor: VectorConvertor

        abstract override val value: S

        val valueVector: V
            get() = convertor.convertToVector(value)

        abstract val velocityVector: V

        val velocity: S
            get() = convertor.convertFromVector(velocityVector)

        abstract val isRunning: Boolean

        abstract val animationSpec: AnimationSpec

        abstract val animation: TargetBasedAnimation
    }

    val children: List>
        get() = _children

    val totalDurationMillis: Int by derivedStateOf {
        var durationMillis = 0
        animationStates.forEach { animationState ->
            durationMillis = max(durationMillis, animationState.animation.durationMillis)
        }
        children.forEach { transition ->
            durationMillis = max(durationMillis, transition.totalDurationMillis)
        }
        durationMillis
    }

    val isRunning: Boolean
        get() = _transitionState.isRunning

    abstract inner class DeferredAnimation> {
        abstract val convertor: VectorConvertor

        abstract fun animate(
            transitionSpec: Segment.() -> FiniteAnimationSpec = { SpringSpec() },
            targetValueByState: (state: T) -> S
        ): State
    }

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

    private val _transitionState: TransitionStateImpl = transitionState as TransitionStateImpl

    private var _segment: Segment by mutableStateOf(Segment(state, state))

    private val _animationStates: MutableList> = mutableStateListOf()

    private val _children: MutableList> = mutableStateListOf()

    private var shouldUpdate: Boolean by mutableStateOf(true)

    private var startTimeMillis: Int by mutableStateOf(UnspecifiedTimeMillis)

    private var playTimeMillis: Int by mutableStateOf(0)

    internal fun addAnimationState(animationState: AnimationStateImpl<*, *>) {
        _animationStates.add(animationState)
    }

    internal fun removeAnimationState(animationState: AnimationStateImpl<*, *>) {
        _animationStates.remove(animationState)
    }

    internal fun addChild(transition: Transition<*>) {
        _children.add(transition)
    }

    internal fun removeChild(transition: Transition<*>) {
        _children.remove(transition)
    }

    @Composable
    internal fun animateTo(targetState: T) {
        updateTargetState(targetState)
        if (state != targetState || isRunning || shouldUpdate) {
            LaunchedEffect(this) {
                do {
                    withFrameMillis { frameTimeMillisLong ->
                        val frameTimeMillis = frameTimeMillisLong.toInt()
                        update(frameTimeMillis)
                    }
                } while (isRunning)
            }
        }
    }

    internal fun updateTargetState(targetState: T) {
        if (this.targetState != targetState) {
            _segment = Segment(this.targetState, targetState)
            _transitionState.value = this.targetState
            _transitionState.targetValue = targetState
            if (!isRunning) {
                shouldUpdate = true
            }
            _animationStates.forEach { animation ->
                animation.shouldReset = true
            }
        }
    }

    private fun update(frameTimeMillis: Int) {
        if (startTimeMillis == UnspecifiedTimeMillis) {
            onTransitionStart(frameTimeMillis)
        }
        shouldUpdate = false
        playTimeMillis = frameTimeMillis - startTimeMillis
        var allFinished = true
        _animationStates.forEach { state ->
            if (state.isRunning) {
                state.update(playTimeMillis)
            }
            if (state.isRunning) {
                allFinished = false
            }
        }
        _children.forEach { transition ->
            if (transition.state != transition.targetState) {
                transition.update(playTimeMillis)
            }
            if (transition.state != transition.targetState) {
                allFinished = false
            }
        }
        if (allFinished) {
            onTransitionEnd()
        }
    }

    private fun onTransitionStart(frameTimeMillis: Int) {
        startTimeMillis = frameTimeMillis
        _transitionState.isRunning = true
    }

    internal fun onTransitionEnd() {
        _transitionState.isRunning = false
        startTimeMillis = UnspecifiedTimeMillis
        _transitionState.value = targetState
        playTimeMillis = 0
    }

    @Stable
    internal inner class AnimationStateImpl>(
        override val convertor: VectorConvertor,
        initialValue: S,
        initialVelocityVector: V
    ) : AnimationState() {
        override var value: S by mutableStateOf(initialValue)

        private var targetValue: S by mutableStateOf(initialValue)

        override var velocityVector: V by mutableStateOf(initialVelocityVector)

        override var animationSpec: FiniteAnimationSpec by mutableStateOf(SpringSpec())

        override var animation: TargetBasedAnimation by mutableStateOf(
            value = TargetBasedAnimation(
                vectorizedAnimationSpec = animationSpec.vectorize(convertor),
                convertor = convertor,
                initialValue = initialValue,
                targetValue = targetValue,
                initialVelocityVector = initialVelocityVector
            )
        )

        override var isRunning: Boolean by mutableStateOf(false)

        var shouldReset: Boolean by mutableStateOf(false)

        private var offsetFrameTimeMillis: Int by mutableStateOf(0)

        fun updateAnimation(
            animationSpec: FiniteAnimationSpec,
            targetValue: S
        ) {
            if (this.targetValue != targetValue || shouldReset) {
                this.targetValue = targetValue
                this.animationSpec = animationSpec
                animation = TargetBasedAnimation(
                    vectorizedAnimationSpec = animationSpec.vectorize(convertor),
                    convertor = convertor,
                    initialValue = value,
                    targetValue = targetValue,
                    initialVelocityVector = velocityVector
                )
                isRunning = true
                shouldReset = false
                offsetFrameTimeMillis = playTimeMillis
                shouldUpdate = true
            }
        }

        fun update(playTimeMillis: Int) {
            val actualPlayTimeMillis = playTimeMillis - offsetFrameTimeMillis
            value = animation.getValueFromMillis(actualPlayTimeMillis)
            velocityVector = animation.getVelocityVectorFromMillis(actualPlayTimeMillis)
            if (actualPlayTimeMillis >= animation.durationMillis) {
                isRunning = false
                offsetFrameTimeMillis = 0
            }
        }
    }

    internal inner class DeferredAnimationImpl>(
        override val convertor: VectorConvertor
    ) : DeferredAnimation() {
        var deferredState: DeferredState? = null

        internal inner class DeferredState>(
            val animationState: AnimationStateImpl
        ) : State {
            lateinit var transitionSpec: Segment.() -> FiniteAnimationSpec

            lateinit var targetValueByState: (T) -> S

            override val value: S
                get() {
                    update()
                    return animationState.value
                }

            private fun update() {
                val segment = segment
                val transitionSpec = segment.transitionSpec()
                val targetValue = targetValueByState(segment.targetState)
                animationState.updateAnimation(transitionSpec, targetValue)
            }
        }

        override fun animate(
            transitionSpec: Segment.() -> FiniteAnimationSpec,
            targetValueByState: (state: T) -> S
        ): State {
            val deferredState = deferredState ?: kotlin.run {
                val initialValue = targetValueByState(state)
                val animationState = AnimationStateImpl(
                    convertor = convertor,
                    initialValue = initialValue,
                    initialVelocityVector = convertor.newVector(initialValue)
                )
                addAnimationState(animationState)
                DeferredState(animationState).also { deferredState = it }
            }
            return deferredState.apply {
                this.transitionSpec = transitionSpec
                this.targetValueByState = targetValueByState
            }
        }
    }
}

@Composable
inline fun  Transition.derive(
    crossinline stateTransformer: @Composable (parentState: T) -> S
): Transition {
    // This will only be used once (to create child transition)
    val initialParentState = remember(this) { state }
    return derive(
        initialState = stateTransformer(initialParentState),
        targetState = stateTransformer(targetState)
    )
}

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

@PublishedApi
@Composable
internal fun  Transition.derive(
    initialState: S,
    targetState: S
): Transition {
    val child = remember(this) {
        Transition(TransitionState(initialState))
    }.apply {
        updateTargetState(targetState)
    }

    DisposableEffect(this) {
        addChild(child)
        onDispose {
            removeChild(child)
        }
    }

    return child
}