2
* Copyright (c) 2022-2024 Huawei Device Co., Ltd.
3
* Licensed under the Apache License, Version 2.0 (the "License");
4
* you may not use this file except in compliance with the License.
5
* You may obtain a copy of the License at
7
* http://www.apache.org/licenses/LICENSE-2.0
9
* Unless required by applicable law or agreed to in writing, software
10
* distributed under the License is distributed on an "AS IS" BASIS,
11
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
* See the License for the specific language governing permissions and
13
* limitations under the License.
16
import { refEqual, uint64 } from "@koalaui/compat"
17
import { getAnimationTimer } from "./GlobalTimer"
18
import { TimeAnimation, constAnimation } from "./TimeAnimation"
19
import { Disposable } from "../states/Disposable"
20
import { GlobalStateManager } from "../states/GlobalStateManager"
21
import { ComputableState, MutableState, State } from "../states/State"
25
* This interface represents a pauseable animated state,
26
* which value is changed according to the given animation.
28
export interface AnimatedState<Value> extends State<Value> {
29
animation: TimeAnimation<Value>
30
readonly running: boolean
35
* Creates animated state that could later be used as data source for animations.
37
* @param animation - animation definition
38
* @param startNow - if animation shall start immediately after creation
39
* @param timeProvider - custom time provider for testing purposes
40
* @returns animated state with the specified animation
42
export function animatedState<V>(animation: TimeAnimation<V>, startNow: boolean = false, timeProvider?: () => uint64): AnimatedState<V> {
43
return new AnimatedStateImpl(animation, startNow, timeProvider)
48
* This interface represents a mutable animated state,
49
* which value is changed according to the given animation.
50
* If its value is changed by user, a new animation is requested
51
* to change the current value to the target one.
53
export interface MutableAnimatedState<Value> extends MutableState<Value> {
54
animation: TimeAnimation<Value>
55
readonly running: boolean
59
* Creates and remembers a mutable state that automatically animates the value as it changes.
61
* @param initial - initial value
62
* @param animationProvider - factory producing a new animation from the current value and the target one
63
* @returns a mutable state that automatically animates the value as it changes
65
export function mutableAnimatedState<Value>(initial: Value, animationProvider: ImplicitAnimationProvider<Value>): MutableAnimatedState<Value> {
66
return new MutableAnimatedStateImpl(initial, animationProvider)
70
* A factory producing a new animation from the current value and the target one.
71
* @param from - start value for animation
72
* @param to - end value for animation
73
* @returns a new animation from the current value and the target one
75
export type ImplicitAnimationProvider<Value> = (from: Value, to: Value) => TimeAnimation<Value>
79
* This interface represents a value provider with ability to choose value animation by parameter.
81
export interface StateAnimator<Parameter, Value> {
84
onValueChange(action: (newValue: Value) => void): void
88
* Creates state animator with controllable parameter.
89
* When parameter is changed, the animation is recreated.
91
* @param parameter - initial value for controlling parameter
92
* @param animationProvider - factory producing a new animation when parameter changed
93
* @returns state animator with the animation created for given parameter
95
export function stateAnimator<P, V>(parameter: P, animationProvider: ParametrizedAnimationProvider<P, V>): StateAnimator<P, V> {
96
return new StateAnimatorImpl(parameter, animationProvider)
100
* A factory producing a new animation when parameter changed.
101
* @param parameter - controlling parameter
102
* @param value - previous value of the animator if exists
103
* @returns a new animation when parameter changed
105
export type ParametrizedAnimationProvider<P, V> = (parameter: P, value: V | undefined) => TimeAnimation<V>
108
// IMPLEMENTATION DETAILS: DO NOT USE IT DIRECTLY
111
class AnimatedStateImpl<Value> implements Disposable, AnimatedState<Value> {
112
private readonly timeProvider: () => uint64
113
private runningState: MutableState<boolean>
114
private pausedState: MutableState<boolean>
115
private valueState: ComputableState<Value>
116
private myAnimation: TimeAnimation<Value>
119
this.runningState.dispose()
120
this.pausedState.dispose()
121
this.valueState.dispose()
124
get disposed(): boolean {
125
return this.valueState.disposed
128
get modified(): boolean {
129
return this.valueState.modified
133
return this.valueState.value
137
return this.runningState.value
141
return this.pausedState.value
144
set paused(paused: boolean) {
145
this.pausedState.value = paused
149
return this.myAnimation
152
set animation(animation: TimeAnimation<Value>) {
153
if (this.myAnimation === animation) return // nothing to change
154
this.myAnimation = animation
155
if (!this.paused) animation.onStart(this.timeProvider())
158
constructor(myAnimation: TimeAnimation<Value>, startNow: boolean, timeProvider?: () => uint64) {
159
const manager = GlobalStateManager.instance
160
this.timeProvider = timeProvider !== undefined ? timeProvider : (): uint64 => {
161
const timer = getAnimationTimer(manager)
162
if (timer !== undefined) return timer.value
163
console.trace("global animation timer is not specified yet")
166
this.myAnimation = myAnimation
167
this.runningState = manager.mutableState(startNow)
168
this.pausedState = manager.mutableState(!startNow)
169
this.valueState = manager.computableState((): Value => {
171
const time = this.timeProvider()
172
const paused = this.pausedState.value
173
if (this.pausedState.modified) {
175
this.animation.onPause(time)
177
this.animation.onStart(time)
180
// compute value from the time provided
181
let newValue = this.animation.getValue(time)
182
this.action?.(newValue)
185
// update running state if needed
186
this.runningState.value = this.animation.running
190
this.animation.onStart(this.timeProvider())
194
onValueChange(action: (newValue: Value) => void): void {
198
action: ((newValue: Value) => void) | undefined = undefined
202
class MutableAnimatedStateImpl<Value> implements MutableAnimatedState<Value> {
203
private animatedState: AnimatedStateImpl<Value>
204
private readonly animationProvider: ImplicitAnimationProvider<Value>
207
this.animatedState.dispose()
210
constructor(initial: Value, animationProvider: ImplicitAnimationProvider<Value>) {
211
this.animatedState = new AnimatedStateImpl(constAnimation(initial), true)
212
this.animationProvider = animationProvider
215
get disposed(): boolean {
216
return this.animatedState.disposed
219
get modified(): boolean {
220
return this.animatedState.modified
224
return this.animatedState.value
227
set value(value: Value) {
228
this.animatedState.animation = this.animationProvider(this.value, value)
232
return this.animatedState.running
236
return this.animatedState.animation
239
set animation(animation: TimeAnimation<Value>) {
240
this.animatedState.animation = animation
245
class StateAnimatorImpl<P, V> implements StateAnimator<P, V> {
246
private parameterState: MutableState<P>
247
private animatedState: AnimatedStateImpl<V>
248
private readonly animationProvider: ParametrizedAnimationProvider<P, V>
250
constructor(parameter: P, animationProvider: ParametrizedAnimationProvider<P, V>) {
251
this.parameterState = GlobalStateManager.instance.mutableState(parameter)
252
this.animatedState = new AnimatedStateImpl(animationProvider(parameter, undefined), true)
253
this.animationProvider = animationProvider
257
return this.parameterState.value
260
set parameter(parameter: P) {
261
if (refEqual(this.parameterState.value, parameter)) return // nothing to change
262
this.parameterState.value = parameter
263
this.animatedState.animation = this.animationProvider(parameter, this.animatedState.value)
267
return this.animatedState.value
270
onValueChange(action: (newValue: V) => void): void {
271
// TODO: rethink how we'd better subscribe appropriate scope on value change.
273
this.animatedState.onValueChange(action)