Skip to content

Commit

Permalink
Make Animator to start automatically by default, without waiting for …
Browse files Browse the repository at this point in the history
…the next frame, and allowing to configure that behaviour in root and children nodes (#1100)

* Make Animator to start automatically by default, without waiting for the next frame, and allowing to configure that behaviour

* Propagate Animator.startImmediately to children nodes
  • Loading branch information
soywiz authored Nov 10, 2022
1 parent 5943042 commit 6c07564
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 56 deletions.
64 changes: 38 additions & 26 deletions korge/src/commonMain/kotlin/com/soywiz/korge/animate/Animator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ fun View.animator(
defaultEasing: Easing = Animator.DEFAULT_EASING,
parallel: Boolean = false,
looped: Boolean = false,
startImmediately: Boolean = Animator.DEFAULT_START_IMMEDIATELY,
block: @AnimatorDslMarker Animator.() -> Unit = {}
): Animator = Animator(this, defaultTime, defaultSpeed, defaultEasing, parallel, looped, parent = null).apply(block)
): Animator = Animator(this, defaultTime, defaultSpeed, defaultEasing, parallel, looped, parent = null, startImmediately = startImmediately, level = 0).apply(block)

suspend fun View.animate(
defaultTime: TimeSpan = Animator.DEFAULT_TIME,
Expand All @@ -35,26 +36,30 @@ suspend fun View.animate(
parallel: Boolean = false,
looped: Boolean = false,
completeOnCancel: Boolean = false,
startImmediately: Boolean = Animator.DEFAULT_START_IMMEDIATELY,
block: @AnimatorDslMarker Animator.() -> Unit = {}
): Animator = Animator(this, defaultTime, defaultSpeed, defaultEasing, parallel, looped, parent = null).apply(block).also { it.await(completeOnCancel = completeOnCancel) }
): Animator = Animator(this, defaultTime, defaultSpeed, defaultEasing, parallel, looped, parent = null, startImmediately = startImmediately, level = 0).apply(block).also { it.await(completeOnCancel = completeOnCancel) }

open class Animator(
open class Animator @PublishedApi internal constructor(
@PublishedApi internal val root: View,
@PublishedApi internal val defaultTime: TimeSpan = DEFAULT_TIME,
@PublishedApi internal val defaultSpeed: Double = DEFAULT_SPEED,
@PublishedApi internal val defaultEasing: Easing = DEFAULT_EASING,
@PublishedApi internal val defaultTime: TimeSpan,
@PublishedApi internal val defaultSpeed: Double,
@PublishedApi internal val defaultEasing: Easing,
private val parallel: Boolean = false,
private val looped: Boolean = false,
private val parent: Animator?,
private var lazyInit: (Animator.() -> Unit)? = null,
@PublishedApi internal val level: Int = 0,
@PublishedApi internal val level: Int,
@PublishedApi internal val startImmediately: Boolean,
) : CloseableCancellable {
//private val indent: String get() = Indenter.INDENTS[level]

companion object {
val DEFAULT_TIME = 500.milliseconds
val DEFAULT_SPEED = 128.0 // Points per second
val DEFAULT_EASING = Easing.EASE
val DEFAULT_START_IMMEDIATELY = true
//val DEFAULT_START_IMMEDIATELY = false
}

val onComplete = Signal<Unit>()
Expand All @@ -74,23 +79,30 @@ open class Animator(
private fun ensure() {
if (parent != null) return parent.ensure()

val updateComponents = root.getComponentsOfType(UpdateComponent) ?: emptyList()
//println("updateComponents=${updateComponents.size}, updateComponents.contains(updater)=${updateComponents.contains(updater)}, updater=$updater : $updateComponents")
if (updateComponents.contains(updater)) return
if (updater != null && (root.getComponentsOfType(UpdateComponent) ?: emptyList()).contains(updater)) return
//if (updater != null) return

//println("!!!!!!!!!!!!! ADD NEW UPDATER : updater=$updater, this=$this, parent=$parent")
updater = root.addUpdater(first = false) {
if (autoInvalidateView) root.invalidateRender()
//println("****")
if (rootAnimationNode.update(it) >= 0.seconds) {
if (looped) {
onComplete()
} else {
cancel()

object : UpdateComponent {
override val view: View get() = this@Animator.root
override fun update(dt: TimeSpan) {
if (this@Animator.autoInvalidateView) this@Animator.root.invalidateRender()
//println("****")
if (this@Animator.rootAnimationNode.update(dt) >= TimeSpan.ZERO) {
if (this@Animator.looped) {
this@Animator.onComplete()
} else {
this@Animator.cancel()
}
}
}
} as Closeable
}.also {
updater = it
it.attach()
if (startImmediately) it.update(TimeSpan.ZERO)
}
}

private fun ensureInit() {
Expand Down Expand Up @@ -152,24 +164,24 @@ open class Animator(
}

inline fun parallel(
time: TimeSpan = this.defaultTime, speed: Double = this.defaultSpeed, easing: Easing = this.defaultEasing, looped: Boolean = false,
time: TimeSpan = this.defaultTime, speed: Double = this.defaultSpeed, easing: Easing = this.defaultEasing, looped: Boolean = false, startImmediately: Boolean = this.startImmediately,
callback: @AnimatorDslMarker Animator.() -> Unit
): Animator = Animator(root, time, speed, easing, true, looped, level = level + 1, parent = this).also { callback(it) }.also { addNode(it.rootAnimationNode) }
): Animator = Animator(root, time, speed, easing, true, looped, level = level + 1, parent = this, startImmediately = startImmediately).also { callback(it) }.also { addNode(it.rootAnimationNode) }

inline fun sequence(
defaultTime: TimeSpan = this.defaultTime, defaultSpeed: Double = this.defaultSpeed, easing: Easing = this.defaultEasing, looped: Boolean = false,
defaultTime: TimeSpan = this.defaultTime, defaultSpeed: Double = this.defaultSpeed, easing: Easing = this.defaultEasing, looped: Boolean = false, startImmediately: Boolean = this.startImmediately,
callback: @AnimatorDslMarker Animator.() -> Unit
): Animator = Animator(root, defaultTime, defaultSpeed, easing, false, looped, level = level + 1, parent = this).also { callback(it) }.also { addNode(it.rootAnimationNode) }
): Animator = Animator(root, defaultTime, defaultSpeed, easing, false, looped, level = level + 1, parent = this, startImmediately = startImmediately).also { callback(it) }.also { addNode(it.rootAnimationNode) }

fun parallelLazy(
time: TimeSpan = this.defaultTime, speed: Double = this.defaultSpeed, easing: Easing = this.defaultEasing, looped: Boolean = false,
time: TimeSpan = this.defaultTime, speed: Double = this.defaultSpeed, easing: Easing = this.defaultEasing, looped: Boolean = false, startImmediately: Boolean = this.startImmediately,
init: @AnimatorDslMarker Animator.() -> Unit
): Animator = Animator(root, time, speed, easing, true, looped, lazyInit = init, level = level + 1, parent = this).also { addNode(it.rootAnimationNode) }
): Animator = Animator(root, time, speed, easing, true, looped, lazyInit = init, level = level + 1, parent = this, startImmediately = startImmediately).also { addNode(it.rootAnimationNode) }

fun sequenceLazy(
time: TimeSpan = this.defaultTime, speed: Double = this.defaultSpeed, easing: Easing = this.defaultEasing, looped: Boolean = false,
time: TimeSpan = this.defaultTime, speed: Double = this.defaultSpeed, easing: Easing = this.defaultEasing, looped: Boolean = false, startImmediately: Boolean = this.startImmediately,
init: @AnimatorDslMarker Animator.() -> Unit
): Animator = Animator(root, time, speed, easing, false, looped, lazyInit = init, level = level + 1, parent = this).also { addNode(it.rootAnimationNode) }
): Animator = Animator(root, time, speed, easing, false, looped, lazyInit = init, level = level + 1, parent = this, startImmediately = startImmediately).also { addNode(it.rootAnimationNode) }

@PublishedApi internal fun __tween(vararg vs: V2<*>, lazyVs: Array<out () -> V2<*>>? = null, time: TimeSpan = this.defaultTime, lazyTime: (() -> TimeSpan)? = null, easing: Easing = this.defaultEasing, name: String?) {
//println("__tween=time=$time")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.soywiz.korge.animate

import com.soywiz.klock.*
import com.soywiz.korge.component.*
import com.soywiz.korge.view.*
import com.soywiz.korio.util.*
import com.soywiz.korma.geom.*
Expand All @@ -10,46 +11,72 @@ import kotlin.test.*
class NewAnimatorTest {
@Test
fun testBasic() {
val view = DummyView()
val animator = view.animator(defaultTime = 1.seconds, defaultEasing = Easing.LINEAR) {
moveTo(view, 10, 0)
}
val log = arrayListOf<String>()
animator.onComplete.add { log += "complete" }
fun generateLines(startImmediately: Boolean): List<String> {
val view = DummyView()
val lines = arrayListOf<String>()
val log = arrayListOf<String>()
var _animator: Animator? = null

val lines = arrayListOf<String>()
fun logLine() {
lines += "${view.pos.niceStr} : ${animator.nodes.size} : ${log.joinToString(",")}"
}
fun logLine() {
lines += "${view.pos.niceStr} : ${_animator?.nodes?.size} : ${view.getComponentsOfType(UpdateComponent)?.size} : ${log.joinToString(",")}"
}

view.updateSingleView(0.milliseconds); logLine()
view.updateSingleView(100.milliseconds); logLine()
view.updateSingleView(900.milliseconds); logLine()
logLine()
val animator = view.animator(defaultTime = 1.seconds, defaultEasing = Easing.LINEAR, startImmediately = startImmediately) {
moveTo(view, 10, 0)
}
_animator = animator
animator.onComplete.add { log += "complete" }

// Add a new node to the animator (even if completed)
animator.moveBy(view, 0, 10); logLine()
view.updateSingleView(100.milliseconds); logLine()
view.updateSingleView(100.milliseconds); logLine()
view.updateSingleView(800.milliseconds); logLine()
logLine()
view.updateSingleView(0.milliseconds); logLine()
view.updateSingleView(100.milliseconds); logLine()
view.updateSingleView(900.milliseconds); logLine()

// Add a new node to the animator (even if completed)
animator.moveBy(view, 0, 10); logLine()
view.updateSingleView(100.milliseconds); logLine()
view.updateSingleView(100.milliseconds); logLine()
view.updateSingleView(800.milliseconds); logLine()
return lines
}

assertEquals(
"""
(0, 0) : 0 :
(1, 0) : 0 :
(10, 0) : 0 : complete
(10, 0) : 1 : complete
(10, 1) : 0 : complete
(10, 2) : 0 : complete
(10, 10) : 0 : complete,complete
(0, 0) : null : null :
(0, 0) : 1 : 1 :
(0, 0) : 0 : 1 :
(1, 0) : 0 : 1 :
(10, 0) : 0 : 0 : complete
(10, 0) : 1 : 1 : complete
(10, 1) : 0 : 1 : complete
(10, 2) : 0 : 1 : complete
(10, 10) : 0 : 0 : complete,complete
---
(0, 0) : null : null :
(0, 0) : 0 : 1 :
(0, 0) : 0 : 1 :
(1, 0) : 0 : 1 :
(10, 0) : 0 : 0 : complete
(10, 0) : 0 : 1 : complete
(10, 1) : 0 : 1 : complete
(10, 2) : 0 : 1 : complete
(10, 10) : 0 : 0 : complete,complete
""".trimIndent(),
lines.joinToString("\n")
(generateLines(startImmediately = false) + "---" + generateLines(startImmediately = true)).joinToString("\n")
)

}

@Test
fun testSequences() {
val view = DummyView()
var log = ""
val lines = arrayListOf<String>()
fun logLine() {
lines += "${view.pos.niceStr}, ${view.alpha.niceStr(1)} : $log"
}
logLine()
val animator = view.animator(defaultTime = 1.seconds, defaultEasing = Easing.LINEAR) {
block(name = "0") { log += "0" }
block { log += "1" }
Expand All @@ -64,10 +91,6 @@ class NewAnimatorTest {
}
block { log += "3" }
}
val lines = arrayListOf<String>()
fun logLine() {
lines += "${view.pos.niceStr}, ${view.alpha.niceStr(1)} : $log"
}
logLine()
for (n in 0 until 34) {
view.updateSingleView(0.1.seconds)
Expand All @@ -76,6 +99,7 @@ class NewAnimatorTest {
assertEquals(
"""
(0, 0), 1 :
(0, 0), 1 : 01
(1, 0), 1 : 01
(2, 0), 1 : 01
(3, 0), 1 : 01
Expand Down

0 comments on commit 6c07564

Please sign in to comment.