From 7b764d802d9529d7dcc8a52c9db2f2627ce3b148 Mon Sep 17 00:00:00 2001 From: CallumBugajski <11320476+CallumBugajski@users.noreply.github.com> Date: Mon, 22 Jul 2024 10:48:39 +0100 Subject: [PATCH] StateV2: Implement animateTransitions Source-Commit: ee90cdfeae49312f87af0c5434ce5c76e6d349bf --- .../gg/essential/elementa/state/v2/animate.kt | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 unstable/statev2/src/main/kotlin/gg/essential/elementa/state/v2/animate.kt diff --git a/unstable/statev2/src/main/kotlin/gg/essential/elementa/state/v2/animate.kt b/unstable/statev2/src/main/kotlin/gg/essential/elementa/state/v2/animate.kt new file mode 100644 index 00000000..dc536ff1 --- /dev/null +++ b/unstable/statev2/src/main/kotlin/gg/essential/elementa/state/v2/animate.kt @@ -0,0 +1,88 @@ +package gg.essential.elementa.state.v2 + +import gg.essential.elementa.UIComponent +import gg.essential.elementa.components.Window +import gg.essential.elementa.constraints.animation.AnimationStrategy +import gg.essential.elementa.constraints.animation.Animations +import gg.essential.elementa.effects.Effect +import gg.essential.elementa.state.v2.ReferenceHolder +import java.lang.ref.WeakReference + +fun State.animateTransitions( + driverComponent: UIComponent, + duration: Float, + animationStrategy: AnimationStrategy = Animations.OUT_EXP, +): State { + if (duration <= 0f) { + return this + } + val resultState = mutableStateOf(this.getUntracked()) + driverComponent.enableEffect(AnimationDriver(this, WeakReference(resultState), duration, animationStrategy)) + return resultState +} + +private class AnimationDriver( + private val driver: State, + private val resultStateWeakReference: WeakReference>, + private val duration: Float, + private val animationStrategy: AnimationStrategy +): Effect() { + private val animationEventList = mutableListOf() + private lateinit var driverEffect: () -> Unit + private var durationFrames = 1 + + private var previousDriverStateValue = 0f + private var isDestroying = false + + override fun setup() { + previousDriverStateValue = driver.getUntracked() + durationFrames = (Window.of(boundComponent).animationFPS * duration).toInt().coerceAtLeast(1) + driverEffect = effect(ReferenceHolder.Weak) { + val input = driver() + animationEventList.add(AnimationEvent(previousDriverStateValue, input, durationFrames)) + previousDriverStateValue = input + } + } + + override fun animationFrame() { + val resultState = resultStateWeakReference.get() + if (resultState == null) { + destroy() + } else { + animationEventList.forEach { it.age++ } + animationEventList.removeIf { it.age >= durationFrames } + resultState.set(getAnimationValue()) + } + } + + private fun destroy() { + if (isDestroying) { + return + } + isDestroying = true + driverEffect() + Window.enqueueRenderOperation { + boundComponent.removeEffect(this) + } + } + + private fun getAnimationValue(): Float { + if (animationEventList.isEmpty()) { + return previousDriverStateValue + } + + return animationEventList.fold(animationEventList.first().startValue) { acc, event -> + val linearProgress = event.age.toFloat() / event.duration.toFloat() + val animatedProgress = animationStrategy.getValue(linearProgress) + acc + ((event.endValue - acc) * animatedProgress) + } + } + + private data class AnimationEvent( + val startValue: Float, + val endValue: Float, + val duration: Int, + var age: Int = 0, + ) + +} \ No newline at end of file