Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
commonMain.io.nacular.doodle.animation.impl.AnimatorImpl.kt Maven / Gradle / Ivy
package io.nacular.doodle.animation.impl
import io.nacular.doodle.animation.Animation
import io.nacular.doodle.animation.Animator
import io.nacular.doodle.animation.Animator.Listener
import io.nacular.doodle.animation.Animator.MeasureTransitionBuilder
import io.nacular.doodle.animation.Animator.TransitionBuilder
import io.nacular.doodle.animation.Moment
import io.nacular.doodle.animation.NoneUnit
import io.nacular.doodle.animation.noneUnits
import io.nacular.doodle.animation.transition.Transition
import io.nacular.doodle.scheduler.AnimationScheduler
import io.nacular.doodle.scheduler.Task
import io.nacular.doodle.time.Timer
import io.nacular.doodle.utils.Completable
import io.nacular.doodle.utils.CompletableImpl
import io.nacular.doodle.utils.CompletableImpl.State.Active
import io.nacular.doodle.utils.CompletableImpl.State.Canceled
import io.nacular.doodle.utils.ObservableSet
import io.nacular.doodle.utils.Pool
import io.nacular.doodle.utils.SetPool
import io.nacular.measured.units.Measure
import io.nacular.measured.units.Time
import io.nacular.measured.units.Time.Companion.milliseconds
import io.nacular.measured.units.Units
import io.nacular.measured.units.div
import io.nacular.measured.units.times
/**
* Created by Nicholas Eddy on 1/12/20.
*/
public class AnimatorImpl(private val timer: Timer, private val animationScheduler: AnimationScheduler): Animator {
private class KeyFrame {
var time = 0 * milliseconds
}
private class InternalProperty(initialValue: Measure) {
val transitions = mutableListOf>()
var activeTransition = null as TransitionNode?
private set
var value = Moment(initialValue, 0 * initialValue / (1 * milliseconds))
fun add(transition: Transition) {
val start = if (transitions.isEmpty()) KeyFrame() else transitions.last().endTime
val end = KeyFrame()
add(transition, start, end)
}
fun add(transition: Transition, start: KeyFrame, end: KeyFrame) {
transitions += TransitionNode(transition, start, end)
}
fun nextTransition(initialValue: Moment, elapsedTime: Measure): TransitionNode? = transitions.firstOrNull()?.let {
when (it.shouldStart(elapsedTime)) {
true -> {
it.calculateEndTime(initialValue)
transitions -= it
activeTransition = it
it
}
else -> null
}
}
}
private class TransitionNode(val transition: Transition, val startTime: KeyFrame, var endTime: KeyFrame) {
fun shouldStart(elapsedTime: Measure) = startTime.time <= elapsedTime
fun calculateEndTime(initialState: Moment) {
endTime.time = startTime.time + transition.duration(initialState)
}
}
private class Result(val active: Boolean, val old: Measure, val new: Measure)
private inner class AnimationImpl(
private val property: InternalProperty, private val block: (Measure) -> Unit): Animation, CompletableImpl() {
private lateinit var startTime: Measure
private var previousPosition = property.value.position
val isCanceled get() = state == Canceled
fun run(currentTime: Measure): Result {
if (!::startTime.isInitialized) {
startTime = currentTime
}
val totalElapsedTime = currentTime - startTime
var momentValue = property.value
var activeTransition = property.activeTransition ?: property.nextTransition(momentValue, totalElapsedTime)
// Skip over out-dated Transitions, making sure to take their end-state into account
while (activeTransition != null && activeTransition.endTime.time < totalElapsedTime) {
momentValue = activeTransition.transition.endState(momentValue)
activeTransition = property.nextTransition(momentValue, totalElapsedTime)
property.value = momentValue
}
if (activeTransition != null) {
momentValue = activeTransition.transition.value(momentValue, totalElapsedTime - activeTransition.startTime.time)
}
return Result(activeTransition != null, previousPosition, momentValue.position).apply {
if (new != old || totalElapsedTime == 0 * milliseconds) {
block(new)
}
if (!active) {
completed()
}
previousPosition = momentValue.position
}
}
override fun cancel() {
cancel(broadcast = true)
}
fun cancel(broadcast: Boolean = true) {
if (state != Active) {
return
}
super.cancel()
if (broadcast) {
(listeners as? SetPool)?.forEach { it.canceled(this@AnimatorImpl, setOf(this)) }
}
}
}
private inner class TransitionPairBuilderImpl(
start : T,
end : T,
transition: (start: T, end: T) -> Transition): TransitionBuilder {
private val property = InternalProperty(start * noneUnits).apply { add(transition(start, end)) }
override fun then(transition: Transition) = this.also { property.add(transition) }
override fun invoke(block: (T) -> Unit): Animation = AnimationImpl(property) { block(it.amount as T) }.also { addAnimation(it) }
}
private inner class TransitionBuilderImpl(
value : T,
transition: (value: T) -> Transition): TransitionBuilder {
private val property = InternalProperty(value * noneUnits).apply { add(transition(value)) }
override fun then(transition: Transition) = this.also { property.add(transition) }
override fun invoke(block: (T) -> Unit): Animation = AnimationImpl(property) { block(it.amount as T) }.also { addAnimation(it) }
}
private inner class MeasurePairTransitionBuilderImpl(
start : Measure,
end : Measure,
transition: (start: Measure, end: Measure) -> Transition): MeasureTransitionBuilder {
private val property = InternalProperty(start).apply { add(transition(start, end)) }
override fun then(transition: Transition) = this.also { property.add(transition) }
override fun invoke(block: (Measure) -> Unit): Animation {
return AnimationImpl(property) { block(it) }.also { addAnimation(it) }
}
}
private inner class MeasureTransitionBuilderImpl(
value : Measure,
transition: (value: Measure) -> Transition): MeasureTransitionBuilder {
private val property = InternalProperty(value).apply { add(transition(value)) }
override fun then(transition: Transition) = this.also { property.add(transition) }
override fun invoke(block: (Measure) -> Unit): Animation {
return AnimationImpl(property) { block(it) }.also { addAnimation(it) }
}
}
private var task = null as Task?
private val animations = ObservableSet>().apply {
changed += { _,_,_ ->
when {
isNotEmpty() -> if (task?.completed != false) startAnimation()
else -> task?.cancel()
}
}
}
private var inAnimation = false
private var concurrentlyModifiedAnimations: ObservableSet>? = null
override fun Pair.using(transition: (start: T, end: T) -> Transition): TransitionBuilder = TransitionPairBuilderImpl(first, second, transition)
override fun T.using(transition: (value: T) -> Transition): TransitionBuilder = TransitionBuilderImpl(this, transition)
override fun Pair, Measure>.using(transition: (start: Measure, end: Measure) -> Transition): MeasureTransitionBuilder = MeasurePairTransitionBuilderImpl(first, second, transition)
override fun Measure.using(transition: (value: Measure) -> Transition): MeasureTransitionBuilder = MeasureTransitionBuilderImpl(this, transition)
override fun invoke(block: Animator.() -> Unit): Completable {
val newAnimations = mutableSetOf>()
val listener: (ObservableSet>, Set>, Set>) -> Unit = { _,_,new ->
newAnimations += new
}
animations.changed += listener
apply(block)
animations.changed -= listener
return object: CompletableImpl() {
private var numCompleted = 0
init {
newAnimations.forEach {
it.completed += {
if (++numCompleted == newAnimations.size) {
completed()
}
}
}
}
override fun cancel() {
newAnimations.forEach { it.cancel(broadcast = false) }
(listeners as? SetPool)?.forEach { it.canceled(this@AnimatorImpl, newAnimations) }
super.cancel()
}
}
}
override val listeners: Pool = SetPool()
private fun startAnimation() {
task = animationScheduler.onNextFrame {
onAnimate()
}
}
private fun onAnimate() {
val changed = mutableSetOf()
val completed = mutableSetOf()
val iterator = animations.iterator()
inAnimation = true
while (iterator.hasNext()) {
val it = iterator.next()
when {
it.isCanceled -> iterator.remove()
else -> {
val result = it.run(timer.now).also { result ->
if (!result.active) {
completed += it
iterator.remove()
}
}
if (result.new != result.old) {
changed += it
}
}
}
}
inAnimation = false
concurrentlyModifiedAnimations?.let {
animations.addAll(it)
}
if (changed.isNotEmpty()) {
(listeners as? SetPool)?.forEach { it.changed(this, changed) }
}
when {
animations.isNotEmpty() -> task = animationScheduler.onNextFrame {
onAnimate()
}
completed.isNotEmpty() -> (listeners as? SetPool)?.forEach { it.completed(this, completed) }
}
}
private fun addAnimation(animation: AnimatorImpl.AnimationImpl<*>) {
when {
inAnimation -> {
if (concurrentlyModifiedAnimations == null) {
concurrentlyModifiedAnimations = ObservableSet(animations)
}
concurrentlyModifiedAnimations?.apply { this += animation }
}
else -> animations += animation
}
}
}