idlize

Форк
0
275 строк · 9.2 Кб
1
/*
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
6
 *
7
 * http://www.apache.org/licenses/LICENSE-2.0
8
 *
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.
14
 */
15

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"
22

23

24
/**
25
 * This interface represents a pauseable animated state,
26
 * which value is changed according to the given animation.
27
 */
28
export interface AnimatedState<Value> extends State<Value> {
29
    animation: TimeAnimation<Value>
30
    readonly running: boolean
31
    paused: boolean
32
}
33

34
/**
35
 * Creates animated state that could later be used as data source for animations.
36
 *
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
41
 */
42
export function animatedState<V>(animation: TimeAnimation<V>, startNow: boolean = false, timeProvider?: () => uint64): AnimatedState<V> {
43
    return new AnimatedStateImpl(animation, startNow, timeProvider)
44
}
45

46

47
/**
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.
52
 */
53
export interface MutableAnimatedState<Value> extends MutableState<Value> {
54
    animation: TimeAnimation<Value>
55
    readonly running: boolean
56
}
57

58
/**
59
 * Creates and remembers a mutable state that automatically animates the value as it changes.
60
 *
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
64
 */
65
export function mutableAnimatedState<Value>(initial: Value, animationProvider: ImplicitAnimationProvider<Value>): MutableAnimatedState<Value> {
66
    return new MutableAnimatedStateImpl(initial, animationProvider)
67
}
68

69
/**
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
74
 */
75
export type ImplicitAnimationProvider<Value> = (from: Value, to: Value) => TimeAnimation<Value>
76

77

78
/**
79
 * This interface represents a value provider with ability to choose value animation by parameter.
80
 */
81
export interface StateAnimator<Parameter, Value> {
82
    parameter: Parameter
83
    readonly value: Value
84
    onValueChange(action: (newValue: Value) => void): void
85
}
86

87
/**
88
 * Creates state animator with controllable parameter.
89
 * When parameter is changed, the animation is recreated.
90
 *
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
94
 */
95
export function stateAnimator<P, V>(parameter: P, animationProvider: ParametrizedAnimationProvider<P, V>): StateAnimator<P, V> {
96
    return new StateAnimatorImpl(parameter, animationProvider)
97
}
98

99
/**
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
104
 */
105
export type ParametrizedAnimationProvider<P, V> = (parameter: P, value: V | undefined) => TimeAnimation<V>
106

107

108
// IMPLEMENTATION DETAILS: DO NOT USE IT DIRECTLY
109

110

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>
117

118
    dispose() {
119
        this.runningState.dispose()
120
        this.pausedState.dispose()
121
        this.valueState.dispose()
122
    }
123

124
    get disposed(): boolean {
125
        return this.valueState.disposed
126
    }
127

128
    get modified(): boolean {
129
        return this.valueState.modified
130
    }
131

132
    get value() {
133
        return this.valueState.value
134
    }
135

136
    get running() {
137
        return this.runningState.value
138
    }
139

140
    get paused() {
141
        return this.pausedState.value
142
    }
143

144
    set paused(paused: boolean) {
145
        this.pausedState.value = paused
146
    }
147

148
    get animation() {
149
        return this.myAnimation
150
    }
151

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())
156
    }
157

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")
164
            return 0
165
        }
166
        this.myAnimation = myAnimation
167
        this.runningState = manager.mutableState(startNow)
168
        this.pausedState = manager.mutableState(!startNow)
169
        this.valueState = manager.computableState((): Value => {
170
            try {
171
                const time = this.timeProvider()
172
                const paused = this.pausedState.value
173
                if (this.pausedState.modified) {
174
                    if (paused) {
175
                        this.animation.onPause(time)
176
                    } else {
177
                        this.animation.onStart(time)
178
                    }
179
                }
180
                // compute value from the time provided
181
                let newValue = this.animation.getValue(time)
182
                this.action?.(newValue)
183
                return newValue
184
            } finally {
185
                // update running state if needed
186
                this.runningState.value = this.animation.running
187
            }
188
        })
189
        if (startNow) {
190
            this.animation.onStart(this.timeProvider())
191
        }
192
    }
193

194
    onValueChange(action: (newValue: Value) => void): void {
195
        this.action = action
196
    }
197

198
    action: ((newValue: Value) => void) | undefined = undefined
199
}
200

201

202
class MutableAnimatedStateImpl<Value> implements MutableAnimatedState<Value> {
203
    private animatedState: AnimatedStateImpl<Value>
204
    private readonly animationProvider: ImplicitAnimationProvider<Value>
205

206
    dispose(): void {
207
        this.animatedState.dispose()
208
    }
209

210
    constructor(initial: Value, animationProvider: ImplicitAnimationProvider<Value>) {
211
        this.animatedState = new AnimatedStateImpl(constAnimation(initial), true)
212
        this.animationProvider = animationProvider
213
    }
214

215
    get disposed(): boolean {
216
        return this.animatedState.disposed
217
    }
218

219
    get modified(): boolean {
220
        return this.animatedState.modified
221
    }
222

223
    get value(): Value {
224
        return this.animatedState.value
225
    }
226

227
    set value(value: Value) {
228
        this.animatedState.animation = this.animationProvider(this.value, value)
229
    }
230

231
    get running() {
232
        return this.animatedState.running
233
    }
234

235
    get animation() {
236
        return this.animatedState.animation
237
    }
238

239
    set animation(animation: TimeAnimation<Value>) {
240
        this.animatedState.animation = animation
241
    }
242
}
243

244

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>
249

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
254
    }
255

256
    get parameter(): P {
257
        return this.parameterState.value
258
    }
259

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)
264
    }
265

266
    get value(): V {
267
        return this.animatedState.value
268
    }
269

270
    onValueChange(action: (newValue: V) => void): void {
271
        // TODO: rethink how we'd better subscribe appropriate scope on value change.
272
        this.value
273
        this.animatedState.onValueChange(action)
274
    }
275
}
276

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.