141 lines
No EOL
5.1 KiB
Kotlin
141 lines
No EOL
5.1 KiB
Kotlin
package de.moritzruth.theaterdsl
|
|
|
|
import de.moritzruth.theaterdsl.value.*
|
|
import kotlinx.collections.immutable.ImmutableList
|
|
import kotlinx.collections.immutable.persistentListOf
|
|
import kotlinx.collections.immutable.toImmutableList
|
|
import kotlin.math.PI
|
|
import kotlin.math.asin
|
|
import kotlin.math.min
|
|
import kotlin.math.sin
|
|
import kotlin.time.Duration
|
|
import kotlin.time.ExperimentalTime
|
|
import kotlin.time.TimeSource
|
|
|
|
interface DynamicValue<T> {
|
|
fun getCurrentValue(): T
|
|
}
|
|
|
|
@OptIn(ExperimentalTime::class)
|
|
class PercentageDV(initialStaticValue: Percentage = 0.percent): DynamicValue<Percentage> {
|
|
private sealed interface State {
|
|
data class Static(val value: Percentage): State
|
|
|
|
data class Fade(val start: Percentage, val end: Percentage, val duration: Duration): State {
|
|
val delta = end.value - start.value
|
|
}
|
|
|
|
data class Sine(
|
|
val offset: Double,
|
|
val minimum: Percentage,
|
|
val maximum: Percentage,
|
|
val period: Duration
|
|
): State {
|
|
companion object {
|
|
fun calculateA(minimum: Percentage, maximum: Percentage) = (maximum.value - minimum.value) * 0.5
|
|
fun calculateX(progress: Double, offset: Double) = 2 * PI * progress - offset
|
|
fun calculateD(minimum: Percentage) = 0.5 + minimum.value
|
|
}
|
|
}
|
|
|
|
data class Step(val steps: ImmutableList<Percentage>, val interval: Duration, val startIndex: Int): State
|
|
}
|
|
|
|
private var stateChangeMark = TimeSource.Monotonic.markNow()
|
|
private var state: State = State.Static(initialStaticValue)
|
|
set(value) {
|
|
field = value
|
|
stateChangeMark = TimeSource.Monotonic.markNow()
|
|
}
|
|
|
|
override fun getCurrentValue(): Percentage {
|
|
val elapsedTime = stateChangeMark.elapsedNow()
|
|
|
|
return when (val s = state) {
|
|
is State.Static -> s.value
|
|
is State.Fade -> (min(elapsedTime / s.duration, 1.0) * s.delta + s.start.value).toFloat().asPercentage()
|
|
is State.Sine -> {
|
|
val a = State.Sine.calculateA(s.minimum, s.maximum)
|
|
val x = State.Sine.calculateX(elapsedTime / s.period, s.offset)
|
|
val d = State.Sine.calculateD(s.minimum)
|
|
|
|
(a * sin(x) + d).toFloat().asPercentage()
|
|
}
|
|
is State.Step -> {
|
|
val index = (elapsedTime / s.interval).toInt() + s.startIndex
|
|
s.steps[index.mod(s.steps.size)]
|
|
}
|
|
}
|
|
}
|
|
|
|
fun static(value: Percentage) {
|
|
state = State.Static(value)
|
|
}
|
|
|
|
fun fade(end: Percentage, duration: Duration, start: Percentage = getCurrentValue()) {
|
|
state = State.Fade(start, end, duration)
|
|
}
|
|
|
|
fun sine(minimum: Percentage, maximum: Percentage, period: Duration, start: Percentage = getCurrentValue()) {
|
|
val offset = asin((start.value - State.Sine.calculateD(minimum)) / State.Sine.calculateA(minimum, maximum))
|
|
state = State.Sine(offset, minimum, maximum, period)
|
|
}
|
|
|
|
fun steps(steps: List<Percentage>, interval: Duration, startIndex: Int = 0) {
|
|
state = State.Step(steps.toImmutableList(), interval, startIndex)
|
|
}
|
|
|
|
fun switch(a: Percentage, b: Percentage, interval: Duration, startAtCurrent: Boolean = true) {
|
|
val startIndex = startAtCurrent
|
|
.takeIf { it }
|
|
?.let { getCurrentValue() }
|
|
?.takeIf { it == b }
|
|
?.let { 1 } ?: 0
|
|
|
|
steps(persistentListOf(a, b), interval, startIndex)
|
|
}
|
|
}
|
|
|
|
@OptIn(ExperimentalTime::class)
|
|
class ColorDV(initialStaticValue: Color = Color.WHITE): DynamicValue<Color> {
|
|
private sealed interface State {
|
|
data class Static(val value: Color): State
|
|
data class Fade(val start: Color, val end: Color, val duration: Duration): State {
|
|
val deltaHue = ((end.hue.degree - start.hue.degree).mod(360f) + 540f).mod(360f) - 180f
|
|
val deltaSaturation = end.saturation.value - start.saturation.value
|
|
val deltaBrightness = end.brightness.value - start.brightness.value
|
|
}
|
|
}
|
|
|
|
private var stateChangeMark = TimeSource.Monotonic.markNow()
|
|
private var state: State = State.Static(initialStaticValue)
|
|
set(value) {
|
|
field = value
|
|
stateChangeMark = TimeSource.Monotonic.markNow()
|
|
}
|
|
|
|
override fun getCurrentValue(): Color {
|
|
val elapsedTime = stateChangeMark.elapsedNow()
|
|
|
|
return when(val s = state) {
|
|
is State.Static -> s.value
|
|
is State.Fade -> {
|
|
val progress = min(elapsedTime / s.duration, 1.0)
|
|
|
|
Color(
|
|
hue = Angle((s.start.hue.degree + s.deltaHue * progress).toFloat()),
|
|
saturation = Percentage((s.start.saturation.value + s.deltaSaturation * progress).toFloat()),
|
|
brightness = Percentage((s.start.brightness.value + s.deltaBrightness * progress).toFloat())
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun static(value: Color) {
|
|
state = State.Static(value)
|
|
}
|
|
|
|
fun fade(end: Color, duration: Duration, start: Color = getCurrentValue()) {
|
|
state = State.Fade(start, end, duration)
|
|
}
|
|
} |