Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Animator to start automatically by default, without waiting for the next frame, and allowing to configure that behaviour in root and children nodes #1100

Merged
merged 2 commits into from
Nov 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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