{
+ parameter: Parameter
+ readonly value: Value
+ onValueChange(action: (newValue: Value) => void): void
+}
+
+/**
+ * Creates state animator with controllable parameter.
+ * When parameter is changed, the animation is recreated.
+ *
+ * @param parameter - initial value for controlling parameter
+ * @param animationProvider - factory producing a new animation when parameter changed
+ * @returns state animator with the animation created for given parameter
+ */
+export function stateAnimator(parameter: P, animationProvider: ParametrizedAnimationProvider
): StateAnimator
{
+ return new StateAnimatorImpl
(parameter, animationProvider)
+}
+
+/**
+ * A factory producing a new animation when parameter changed.
+ * @param parameter - controlling parameter
+ * @param value - previous value of the animator if exists
+ * @returns a new animation when parameter changed
+ */
+export type ParametrizedAnimationProvider
= (parameter: P, value: V | undefined) => TimeAnimation
+
+
+// IMPLEMENTATION DETAILS: DO NOT USE IT DIRECTLY
+
+
+class AnimatedStateImpl implements Disposable, AnimatedState {
+ private readonly timeProvider: () => int64
+ private runningState: MutableState
+ private pausedState: MutableState
+ private valueState: ComputableState
+ private myAnimation: TimeAnimation
+
+ dispose() {
+ this.runningState.dispose()
+ this.pausedState.dispose()
+ this.valueState.dispose()
+ }
+
+ get disposed(): boolean {
+ return this.valueState.disposed
+ }
+
+ get modified(): boolean {
+ return this.valueState.modified
+ }
+
+ get value(): Value {
+ return this.valueState.value
+ }
+
+ get running(): boolean {
+ return this.runningState.value
+ }
+
+ get paused(): boolean {
+ return this.pausedState.value
+ }
+
+ set paused(paused: boolean) {
+ this.pausedState.value = paused
+ }
+
+ get animation(): TimeAnimation {
+ return this.myAnimation
+ }
+
+ set animation(animation: TimeAnimation) {
+ if (this.myAnimation === animation) return // nothing to change
+ this.myAnimation = animation
+ if (!this.paused) animation.onStart(this.timeProvider())
+ }
+
+ constructor(myAnimation: TimeAnimation, startNow: boolean, timeProvider?: () => int64) {
+ const manager = GlobalStateManager.instance
+ if (timeProvider) {
+ this.timeProvider = timeProvider
+ } else {
+ this.timeProvider = (): int64 => {
+ const timer = getAnimationTimer(manager)
+ if (timer) return timer.value
+ console.log("global animation timer is not specified yet")
+ return 0
+ }
+ }
+ this.myAnimation = myAnimation
+ this.runningState = manager.mutableState(startNow)
+ this.pausedState = manager.mutableState(!startNow)
+ this.valueState = manager.computableState((_context: StateContext): Value => {
+ try {
+ const time = this.timeProvider()
+ const paused = this.pausedState.value
+ if (this.pausedState.modified) {
+ if (paused) {
+ this.animation.onPause(time)
+ } else {
+ this.animation.onStart(time)
+ }
+ }
+ // compute value from the time provided
+ let newValue = this.animation.getValue(time)
+ this.action?.(newValue)
+ return newValue
+ } finally {
+ // update running state if needed
+ this.runningState.value = this.animation.running
+ }
+ })
+ if (startNow) {
+ this.animation.onStart(this.timeProvider())
+ }
+ }
+
+ onValueChange(action: (newValue: Value) => void): void {
+ this.action = action
+ }
+
+ action: ((newValue: Value) => void) | undefined = undefined
+}
+
+
+class MutableAnimatedStateImpl implements MutableAnimatedState {
+ private animatedState: AnimatedStateImpl
+ private readonly animationProvider: ImplicitAnimationProvider
+
+ dispose(): void {
+ this.animatedState.dispose()
+ }
+
+ constructor(initial: Value, animationProvider: ImplicitAnimationProvider) {
+ this.animatedState = new AnimatedStateImpl(constAnimation(initial), true)
+ this.animationProvider = animationProvider
+ }
+
+ get disposed(): boolean {
+ return this.animatedState.disposed
+ }
+
+ get modified(): boolean {
+ return this.animatedState.modified
+ }
+
+ get value(): Value {
+ return this.animatedState.value
+ }
+
+ set value(value: Value) {
+ this.animatedState.animation = this.animationProvider(this.value, value)
+ }
+
+ get running(): boolean {
+ return this.animatedState.running
+ }
+
+ get animation(): TimeAnimation {
+ return this.animatedState.animation
+ }
+
+ set animation(animation: TimeAnimation) {
+ this.animatedState.animation = animation
+ }
+}
+
+
+class StateAnimatorImpl implements StateAnimator
{
+ private parameterState: MutableState
+ private animatedState: AnimatedStateImpl
+ private readonly animationProvider: ParametrizedAnimationProvider
+
+ constructor(parameter: P, animationProvider: ParametrizedAnimationProvider
) {
+ this.parameterState = GlobalStateManager.instance.mutableState
(parameter)
+ this.animatedState = new AnimatedStateImpl