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 { fun getCurrentValue(): T } @OptIn(ExperimentalTime::class) class PercentageDV(initialStaticValue: Percentage = 0.percent): DynamicValue { 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, 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, 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 { 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) } }