1 /*
<lambda>null2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.animation.core
18 
19 import androidx.collection.IntList
20 import androidx.collection.IntObjectMap
21 import androidx.collection.MutableIntList
22 import androidx.collection.MutableIntObjectMap
23 import androidx.compose.animation.core.AnimationConstants.DefaultDurationMillis
24 import androidx.compose.animation.core.internal.JvmDefaultWithCompatibility
25 import androidx.compose.ui.util.fastCoerceIn
26 import kotlin.jvm.JvmInline
27 import kotlin.math.min
28 
29 /**
30  * [VectorizedAnimationSpec]s are stateless vector based animation specifications. They do not
31  * assume any starting/ending conditions. Nor do they manage a lifecycle. All it stores is the
32  * configuration that is particular to the type of the animation. easing and duration for
33  * [VectorizedTweenSpec]s, or spring constants for [VectorizedSpringSpec]s. Its stateless nature
34  * allows the same [VectorizedAnimationSpec] to be reused by a few different running animations with
35  * different starting and ending values. More importantly, it allows the system to reuse the same
36  * animation spec when the animation target changes in-flight.
37  *
38  * Since [VectorizedAnimationSpec]s are stateless, it requires starting value/velocity and ending
39  * value to be passed in, along with playtime, to calculate the value or velocity at that time. Play
40  * time here is the progress of the animation in terms of milliseconds, where 0 means the start of
41  * the animation and [getDurationNanos] returns the play time for the end of the animation.
42  *
43  * __Note__: For use cases where the starting values/velocity and ending values aren't expected to
44  * change, it is recommended to use [Animation] that caches these static values and hence does not
45  * require them to be supplied in the value/velocity calculation.
46  *
47  * @see Animation
48  */
49 @JvmDefaultWithCompatibility
50 public interface VectorizedAnimationSpec<V : AnimationVector> {
51     /**
52      * Whether or not the [VectorizedAnimationSpec] specifies an infinite animation. That is, one
53      * that will not finish by itself, one that needs an external action to stop. For examples, an
54      * indeterminate progress bar, which will only stop when it is removed from the composition.
55      */
56     public val isInfinite: Boolean
57 
58     /**
59      * Calculates the value of the animation at given the playtime, with the provided start/end
60      * values, and start velocity.
61      *
62      * @param playTimeNanos time since the start of the animation
63      * @param initialValue start value of the animation
64      * @param targetValue end value of the animation
65      * @param initialVelocity start velocity of the animation
66      */
67     public fun getValueFromNanos(
68         playTimeNanos: Long,
69         initialValue: V,
70         targetValue: V,
71         initialVelocity: V
72     ): V
73 
74     /**
75      * Calculates the velocity of the animation at given the playtime, with the provided start/end
76      * values, and start velocity.
77      *
78      * @param playTimeNanos time since the start of the animation
79      * @param initialValue start value of the animation
80      * @param targetValue end value of the animation
81      * @param initialVelocity start velocity of the animation
82      */
83     public fun getVelocityFromNanos(
84         playTimeNanos: Long,
85         initialValue: V,
86         targetValue: V,
87         initialVelocity: V
88     ): V
89 
90     /**
91      * Calculates the duration of an animation. For duration-based animations, this will return the
92      * pre-defined duration. For physics-based animations, the duration will be estimated based on
93      * the physics configuration (such as spring stiffness, damping ratio, visibility threshold) as
94      * well as the [initialValue], [targetValue] values, and [initialVelocity].
95      *
96      * @param initialValue start value of the animation
97      * @param targetValue end value of the animation
98      * @param initialVelocity start velocity of the animation
99      */
100     @Suppress("MethodNameUnits")
101     public fun getDurationNanos(initialValue: V, targetValue: V, initialVelocity: V): Long
102 
103     /**
104      * Calculates the end velocity of the animation with the provided start/end values, and start
105      * velocity. For duration-based animations, end velocity will be the velocity of the animation
106      * at the duration time. This is also the default assumption. However, for physics-based
107      * animations, end velocity is an [AnimationVector] of 0s.
108      *
109      * @param initialValue start value of the animation
110      * @param targetValue end value of the animation
111      * @param initialVelocity start velocity of the animation
112      */
113     public fun getEndVelocity(initialValue: V, targetValue: V, initialVelocity: V): V =
114         getVelocityFromNanos(
115             getDurationNanos(initialValue, targetValue, initialVelocity),
116             initialValue,
117             targetValue,
118             initialVelocity
119         )
120 }
121 
122 /**
123  * Calculates the duration of an animation. For duration-based animations, this will return the
124  * pre-defined duration. For physics-based animations, the duration will be estimated based on the
125  * physics configuration (such as spring stiffness, damping ratio, visibility threshold) as well as
126  * the [initialValue], [targetValue] values, and [initialVelocity].
127  *
128  * @param initialValue start value of the animation
129  * @param targetValue end value of the animation
130  * @param initialVelocity start velocity of the animation
131  */
getDurationMillisnull132 internal fun <V : AnimationVector> VectorizedAnimationSpec<V>.getDurationMillis(
133     initialValue: V,
134     targetValue: V,
135     initialVelocity: V
136 ): Long = getDurationNanos(initialValue, targetValue, initialVelocity) / MillisToNanos
137 
138 /**
139  * Calculates the value of the animation at given the playtime, with the provided start/end values,
140  * and start velocity.
141  *
142  * @param playTimeMillis time since the start of the animation
143  * @param start start value of the animation
144  * @param end end value of the animation
145  * @param startVelocity start velocity of the animation
146  */
147 // TODO: Move tests off this API
148 internal fun <V : AnimationVector> VectorizedAnimationSpec<V>.getValueFromMillis(
149     playTimeMillis: Long,
150     start: V,
151     end: V,
152     startVelocity: V
153 ): V = getValueFromNanos(playTimeMillis * MillisToNanos, start, end, startVelocity)
154 
155 /**
156  * All the finite [VectorizedAnimationSpec]s implement this interface, including:
157  * [VectorizedKeyframesSpec], [VectorizedTweenSpec], [VectorizedRepeatableSpec],
158  * [VectorizedSnapSpec], [VectorizedSpringSpec], etc. The [VectorizedAnimationSpec] that does
159  * __not__ implement this is: [InfiniteRepeatableSpec].
160  */
161 @JvmDefaultWithCompatibility
162 public interface VectorizedFiniteAnimationSpec<V : AnimationVector> : VectorizedAnimationSpec<V> {
163     override val isInfinite: Boolean
164         get() = false
165 }
166 
167 /** Base class for [VectorizedAnimationSpec]s that are based on a fixed [durationMillis]. */
168 @JvmDefaultWithCompatibility
169 public interface VectorizedDurationBasedAnimationSpec<V : AnimationVector> :
170     VectorizedFiniteAnimationSpec<V> {
171     /** duration is the amount of time while animation is not yet finished. */
172     public val durationMillis: Int
173 
174     /** delay defines the amount of time that animation can be delayed. */
175     public val delayMillis: Int
176 
177     @Suppress("MethodNameUnits")
getDurationNanosnull178     override fun getDurationNanos(initialValue: V, targetValue: V, initialVelocity: V): Long =
179         (delayMillis + durationMillis) * MillisToNanos
180 }
181 
182 /**
183  * Clamps the input [playTime] to the duration range of the given
184  * [VectorizedDurationBasedAnimationSpec].
185  */
186 internal fun VectorizedDurationBasedAnimationSpec<*>.clampPlayTime(playTime: Long): Long {
187     return (playTime - delayMillis).fastCoerceIn(0, durationMillis.toLong())
188 }
189 
190 /**
191  * [VectorizedKeyframesSpec] class manages the animation based on the values defined at different
192  * timestamps in the duration of the animation (i.e. different keyframes). Each keyframe can be
193  * provided via [keyframes] parameter. [VectorizedKeyframesSpec] allows very specific animation
194  * definitions with a precision to millisecond.
195  *
196  * Here's an example of creating a [VectorizedKeyframesSpec] animation: ([keyframes] and
197  * [KeyframesSpec.KeyframesSpecConfig] could make defining key frames much more readable.)
198  *
199  *     val delay = 120
200  *     val startValue = AnimationVector3D(100f, 200f, 300f)
201  *     val endValue = AnimationVector3D(200f, 100f, 0f)
202  *     val keyframes = VectorizedKeyframesSpec<AnimationVector3D>(
203  *          keyframes = mutableMapOf (
204  *               0 to (startValue to LinearEasing),
205  *               100 to (startValue to FastOutLinearInEasing)
206  *          ),
207  *          durationMillis = 200,
208  *          delayMillis = delay
209  *     )
210  *
211  * The interpolation between each value is dictated by [VectorizedKeyframeSpecElementInfo.arcMode]
212  * on each keyframe. If no keyframe information is provided, [initialArcMode] is used.
213  *
214  * @see [KeyframesSpec]
215  */
216 public class VectorizedKeyframesSpec<V : AnimationVector>
217 internal constructor(
218     // List of all timestamps. Must include start (time = 0), end (time = durationMillis) and all
219     // other timestamps found in [keyframes].
220     private val timestamps: IntList,
221     private val keyframes: IntObjectMap<VectorizedKeyframeSpecElementInfo<V>>,
222     override val durationMillis: Int,
223     override val delayMillis: Int,
224     // Easing used for any segment of time not covered by [keyframes].
225     private val defaultEasing: Easing,
226     // The [ArcMode] used from time `0` until the first keyframe. So, it applies
227     // for the entire duration if [keyframes] is empty.
228     private val initialArcMode: ArcMode
229 ) : VectorizedDurationBasedAnimationSpec<V> {
230     /**
231      * @param keyframes a map from time to a value/easing function pair. The value in each entry
232      *   defines the animation value at that time, and the easing curve is used in the interval
233      *   starting from that time.
234      * @param durationMillis total duration of the animation
235      * @param delayMillis the amount of the time the animation should wait before it starts.
236      *   Defaults to 0.
237      */
238     public constructor(
239         keyframes: Map<Int, Pair<V, Easing>>,
240         durationMillis: Int,
241         delayMillis: Int = 0
242     ) : this(
243         timestamps =
<lambda>null244             kotlin.run {
245                 val times = MutableIntList(keyframes.size + 2)
246                 keyframes.forEach { (t, _) -> times.add(t) }
247                 if (!keyframes.containsKey(0)) {
248                     times.add(0, 0)
249                 }
250                 if (!keyframes.containsKey(durationMillis)) {
251                     times.add(durationMillis)
252                 }
253                 times.sort()
254                 return@run times
255             },
256         keyframes =
<lambda>null257             kotlin.run {
258                 val timeToInfoMap = MutableIntObjectMap<VectorizedKeyframeSpecElementInfo<V>>()
259                 keyframes.forEach { (time, valueEasing) ->
260                     timeToInfoMap[time] =
261                         VectorizedKeyframeSpecElementInfo(
262                             vectorValue = valueEasing.first,
263                             easing = valueEasing.second,
264                             arcMode = ArcMode.ArcLinear
265                         )
266                 }
267 
268                 return@run timeToInfoMap
269             },
270         durationMillis = durationMillis,
271         delayMillis = delayMillis,
272         defaultEasing = LinearEasing,
273         initialArcMode = ArcMode.ArcLinear
274     )
275 
276     /**
277      * List of time range for the given keyframes.
278      *
279      * This will be used to do a faster lookup for the corresponding Easing curves.
280      */
281     private var modes: IntArray = EmptyIntArray
282     private var times: FloatArray = EmptyFloatArray
283     private var valueVector: V? = null
284     private var velocityVector: V? = null
285 
286     // Objects for ArcSpline
287     private var lastInitialValue: V? = null
288     private var lastTargetValue: V? = null
289     private var posArray: FloatArray = EmptyFloatArray
290     private var slopeArray: FloatArray = EmptyFloatArray
291     private var arcSpline: ArcSpline = EmptyArcSpline
292 
initnull293     private fun init(initialValue: V, targetValue: V, initialVelocity: V) {
294         var requiresArcSpline = arcSpline !== EmptyArcSpline
295 
296         // Only need to initialize once
297         if (valueVector == null) {
298             valueVector = initialValue.newInstance()
299             velocityVector = initialVelocity.newInstance()
300 
301             times = FloatArray(timestamps.size) { timestamps[it].toFloat() / SecondsToMillis }
302 
303             modes =
304                 IntArray(timestamps.size) {
305                     val mode = (keyframes[timestamps[it]]?.arcMode ?: initialArcMode)
306                     if (mode != ArcMode.ArcLinear) {
307                         requiresArcSpline = true
308                     }
309 
310                     mode.value
311                 }
312         }
313 
314         if (!requiresArcSpline) {
315             return
316         }
317 
318         // Initialize variables dependent on initial and/or target value
319         if (
320             arcSpline === EmptyArcSpline ||
321                 lastInitialValue != initialValue ||
322                 lastTargetValue != targetValue
323         ) {
324             lastInitialValue = initialValue
325             lastTargetValue = targetValue
326 
327             // Force to the next even dimension
328             val dimensionCount = initialValue.size % 2 + initialValue.size
329             posArray = FloatArray(dimensionCount)
330             slopeArray = FloatArray(dimensionCount)
331 
332             // TODO(b/299477780): Re-use objects, after the first pass, only the initial and target
333             //  may change, and only if the keyframes does not overwrite it
334             val values =
335                 Array(timestamps.size) {
336                     val timestamp = timestamps[it]
337                     val info = keyframes[timestamp]
338                     // Start (zero) and end (durationMillis) may not have been declared in
339                     // keyframes
340                     if (timestamp == 0 && info == null) {
341                         FloatArray(dimensionCount) { i -> initialValue[i] }
342                     } else if (timestamp == durationMillis && info == null) {
343                         FloatArray(dimensionCount) { i -> targetValue[i] }
344                     } else {
345                         // All other values are guaranteed to exist
346                         val vectorValue = info!!.vectorValue
347                         FloatArray(dimensionCount) { i -> vectorValue[i] }
348                     }
349                 }
350             arcSpline = ArcSpline(arcModes = modes, timePoints = times, y = values)
351         }
352     }
353 
354     /**
355      * @Throws IllegalStateException When the initial or final value to animate within a keyframe is
356      *   missing.
357      */
getValueFromNanosnull358     override fun getValueFromNanos(
359         playTimeNanos: Long,
360         initialValue: V,
361         targetValue: V,
362         initialVelocity: V
363     ): V {
364         val playTimeMillis = playTimeNanos / MillisToNanos
365         val clampedPlayTime = clampPlayTime(playTimeMillis).toInt()
366 
367         // If there is a key frame defined with the given time stamp, return that value
368         val keyframe = keyframes[clampedPlayTime]
369         if (keyframe != null) {
370             return keyframe.vectorValue
371         }
372 
373         if (clampedPlayTime >= durationMillis) {
374             return targetValue
375         } else if (clampedPlayTime <= 0) {
376             return initialValue
377         }
378 
379         init(initialValue, targetValue, initialVelocity)
380 
381         // Cannot be null after calling init()
382         val valueVector = valueVector!!
383 
384         // ArcSpline is only initialized when necessary
385         if (arcSpline !== EmptyArcSpline) {
386             // ArcSpline requires eased play time in seconds
387             val easedTime = getEasedTime(clampedPlayTime)
388 
389             val posArray = posArray
390             arcSpline.getPos(time = easedTime, v = posArray)
391             for (i in posArray.indices) {
392                 valueVector[i] = posArray[i]
393             }
394             return valueVector
395         }
396 
397         // If ArcSpline is not required we do a simple linear interpolation
398         val index = findEntryForTimeMillis(clampedPlayTime)
399 
400         // For the `lerp` method we need the eased time as a fraction
401         val easedTime = getEasedTimeFromIndex(index, clampedPlayTime, true)
402 
403         val timestampStart = timestamps[index]
404         val startKeyframe = keyframes[timestampStart]
405         // Use initial value if it wasn't overwritten by the user
406         // This is always the correct fallback assuming timestamps and keyframes were populated
407         // as expected
408         val startValue: V = startKeyframe?.vectorValue ?: initialValue
409 
410         val timestampEnd = timestamps[index + 1]
411         val endKeyframe = keyframes[timestampEnd]
412         // Use target value if it wasn't overwritten by the user
413         // This is always the correct fallback assuming timestamps and keyframes were populated
414         // as expected
415         val endValue: V = endKeyframe?.vectorValue ?: targetValue
416 
417         for (i in 0 until valueVector.size) {
418             valueVector[i] = lerp(startValue[i], endValue[i], easedTime)
419         }
420         return valueVector
421     }
422 
getVelocityFromNanosnull423     override fun getVelocityFromNanos(
424         playTimeNanos: Long,
425         initialValue: V,
426         targetValue: V,
427         initialVelocity: V
428     ): V {
429         val playTimeMillis = playTimeNanos / MillisToNanos
430         val clampedPlayTime = clampPlayTime(playTimeMillis)
431         if (clampedPlayTime < 0L) {
432             return initialVelocity
433         }
434 
435         init(initialValue, targetValue, initialVelocity)
436 
437         // Cannot be null after calling init()
438         val velocityVector = velocityVector!!
439 
440         // ArcSpline is only initialized when necessary
441         if (arcSpline !== EmptyArcSpline) {
442             val easedTime = getEasedTime(clampedPlayTime.toInt())
443             val slopeArray = slopeArray
444             arcSpline.getSlope(time = easedTime, v = slopeArray)
445             for (i in slopeArray.indices) {
446                 velocityVector[i] = slopeArray[i]
447             }
448             return velocityVector
449         }
450 
451         // Velocity calculation when ArcSpline is not used
452         val startNum =
453             getValueFromMillis(clampedPlayTime - 1, initialValue, targetValue, initialVelocity)
454         val endNum = getValueFromMillis(clampedPlayTime, initialValue, targetValue, initialVelocity)
455         for (i in 0 until startNum.size) {
456             velocityVector[i] = (startNum[i] - endNum[i]) * 1000f
457         }
458         return velocityVector
459     }
460 
getEasedTimenull461     private fun getEasedTime(timeMillis: Int): Float {
462         // There's no promise on the nature of the given time, so we need to search for the correct
463         // time range at every call
464         val index = findEntryForTimeMillis(timeMillis)
465         return getEasedTimeFromIndex(index, timeMillis, false)
466     }
467 
getEasedTimeFromIndexnull468     private fun getEasedTimeFromIndex(index: Int, timeMillis: Int, asFraction: Boolean): Float {
469         if (index >= timestamps.lastIndex) {
470             // Return the same value. This may only happen at the end of the animation.
471             return timeMillis.toFloat() / SecondsToMillis
472         }
473         val timeMin = timestamps[index]
474         val timeMax = timestamps[index + 1]
475 
476         if (timeMillis == timeMin) {
477             return timeMin.toFloat() / SecondsToMillis
478         }
479 
480         val timeRange = timeMax - timeMin
481         val easing = keyframes[timeMin]?.easing ?: defaultEasing
482         val rawFraction = (timeMillis - timeMin).toFloat() / timeRange
483         val easedFraction = easing.transform(rawFraction)
484 
485         if (asFraction) {
486             return easedFraction
487         }
488         return (timeRange * easedFraction + timeMin) / SecondsToMillis
489     }
490 
491     /**
492      * Returns the entry index such that:
493      *
494      * [timeMillis] >= Entry(i).key && [timeMillis] < Entry(i+1).key
495      */
findEntryForTimeMillisnull496     private fun findEntryForTimeMillis(timeMillis: Int): Int {
497         val index = timestamps.binarySearch(timeMillis)
498         return if (index < -1) -(index + 2) else index
499     }
500 }
501 
502 internal data class VectorizedKeyframeSpecElementInfo<V : AnimationVector>(
503     val vectorValue: V,
504     val easing: Easing,
505     val arcMode: ArcMode
506 )
507 
508 /**
509  * Interpolation mode for Arc-based animation spec.
510  *
511  * @see ArcAbove
512  * @see ArcBelow
513  * @see ArcLinear
514  * @see ArcAnimationSpec
515  */
516 @JvmInline
517 public value class ArcMode internal constructor(internal val value: Int) {
518 
519     public companion object {
520         /**
521          * Interpolates using a quarter of an Ellipse where the curve is "above" the center of the
522          * Ellipse.
523          */
524         public val ArcAbove: ArcMode = ArcMode(ArcSplineArcAbove)
525 
526         /**
527          * Interpolates using a quarter of an Ellipse where the curve is "below" the center of the
528          * Ellipse.
529          */
530         public val ArcBelow: ArcMode = ArcMode(ArcSplineArcBelow)
531 
532         /**
533          * An [ArcMode] that forces linear interpolation.
534          *
535          * You'll likely only use this mode within a keyframe.
536          */
537         public val ArcLinear: ArcMode = ArcMode(ArcSplineArcStartLinear)
538     }
539 }
540 
541 /**
542  * [VectorizedSnapSpec] immediately snaps the animating value to the end value.
543  *
544  * @param delayMillis the amount of time (in milliseconds) that the animation should wait before it
545  *   starts. Defaults to 0.
546  */
547 public class VectorizedSnapSpec<V : AnimationVector>(override val delayMillis: Int = 0) :
548     VectorizedDurationBasedAnimationSpec<V> {
549 
getValueFromNanosnull550     override fun getValueFromNanos(
551         playTimeNanos: Long,
552         initialValue: V,
553         targetValue: V,
554         initialVelocity: V
555     ): V {
556         return if (playTimeNanos < delayMillis * MillisToNanos) {
557             initialValue
558         } else {
559             targetValue
560         }
561     }
562 
getVelocityFromNanosnull563     override fun getVelocityFromNanos(
564         playTimeNanos: Long,
565         initialValue: V,
566         targetValue: V,
567         initialVelocity: V
568     ): V {
569         return initialVelocity
570     }
571 
572     override val durationMillis: Int
573         get() = 0
574 }
575 
576 /**
577  * This animation takes another [VectorizedDurationBasedAnimationSpec] and plays it __infinite__
578  * times.
579  *
580  * initialStartOffset can be used to either delay the start of the animation or to fast forward the
581  * animation to a given play time. This start offset will **not** be repeated, whereas the delay in
582  * the [animation] (if any) will be repeated. By default, the amount of offset is 0.
583  *
584  * @param animation the [VectorizedAnimationSpec] describing each repetition iteration.
585  * @param repeatMode whether animation should repeat by starting from the beginning (i.e.
586  *   [RepeatMode.Restart]) or from the end (i.e. [RepeatMode.Reverse])
587  * @param initialStartOffset offsets the start of the animation
588  */
589 public class VectorizedInfiniteRepeatableSpec<V : AnimationVector>(
590     private val animation: VectorizedDurationBasedAnimationSpec<V>,
591     private val repeatMode: RepeatMode = RepeatMode.Restart,
592     initialStartOffset: StartOffset = StartOffset(0)
593 ) : VectorizedAnimationSpec<V> {
594     @Deprecated(
595         level = DeprecationLevel.HIDDEN,
596         message =
597             "This method has been deprecated in favor of the constructor that" +
598                 " accepts start offset."
599     )
600     public constructor(
601         animation: VectorizedDurationBasedAnimationSpec<V>,
602         repeatMode: RepeatMode = RepeatMode.Restart
603     ) : this(animation, repeatMode, StartOffset(0))
604 
605     override val isInfinite: Boolean
606         get() = true
607 
608     /** Single iteration duration */
609     internal val durationNanos: Long =
610         (animation.delayMillis + animation.durationMillis) * MillisToNanos
611 
612     private val initialOffsetNanos = initialStartOffset.value * MillisToNanos
613 
repetitionPlayTimeNanosnull614     private fun repetitionPlayTimeNanos(playTimeNanos: Long): Long {
615         if (playTimeNanos + initialOffsetNanos <= 0) {
616             return 0
617         } else {
618             val postOffsetPlayTimeNanos = playTimeNanos + initialOffsetNanos
619             val repeatsCount = postOffsetPlayTimeNanos / durationNanos
620             if (repeatMode == RepeatMode.Restart || repeatsCount % 2 == 0L) {
621                 return postOffsetPlayTimeNanos - repeatsCount * durationNanos
622             } else {
623                 return (repeatsCount + 1) * durationNanos - postOffsetPlayTimeNanos
624             }
625         }
626     }
627 
repetitionStartVelocitynull628     private fun repetitionStartVelocity(
629         playTimeNanos: Long,
630         start: V,
631         startVelocity: V,
632         end: V
633     ): V =
634         if (playTimeNanos + initialOffsetNanos > durationNanos) {
635             // Start velocity of the 2nd and subsequent iteration will be the velocity at the end
636             // of the first iteration, instead of the initial velocity.
637             animation.getVelocityFromNanos(
638                 playTimeNanos = durationNanos - initialOffsetNanos,
639                 initialValue = start,
640                 targetValue = end,
641                 initialVelocity = startVelocity
642             )
643         } else {
644             startVelocity
645         }
646 
getValueFromNanosnull647     override fun getValueFromNanos(
648         playTimeNanos: Long,
649         initialValue: V,
650         targetValue: V,
651         initialVelocity: V
652     ): V {
653         return animation.getValueFromNanos(
654             repetitionPlayTimeNanos(playTimeNanos),
655             initialValue,
656             targetValue,
657             repetitionStartVelocity(playTimeNanos, initialValue, initialVelocity, targetValue)
658         )
659     }
660 
getVelocityFromNanosnull661     override fun getVelocityFromNanos(
662         playTimeNanos: Long,
663         initialValue: V,
664         targetValue: V,
665         initialVelocity: V
666     ): V {
667         return animation.getVelocityFromNanos(
668             repetitionPlayTimeNanos(playTimeNanos),
669             initialValue,
670             targetValue,
671             repetitionStartVelocity(playTimeNanos, initialValue, initialVelocity, targetValue)
672         )
673     }
674 
675     @Suppress("MethodNameUnits")
getDurationNanosnull676     override fun getDurationNanos(initialValue: V, targetValue: V, initialVelocity: V): Long =
677         Long.MAX_VALUE
678 }
679 
680 /**
681  * This animation takes another [VectorizedDurationBasedAnimationSpec] and plays it [iterations]
682  * times. For infinitely repeating animation spec, [VectorizedInfiniteRepeatableSpec] is
683  * recommended.
684  *
685  * __Note__: When repeating in the [RepeatMode.Reverse] mode, it's highly recommended to have an
686  * __odd__ number of iterations. Otherwise, the animation may jump to the end value when it finishes
687  * the last iteration.
688  *
689  * initialStartOffset can be used to either delay the start of the animation or to fast forward the
690  * animation to a given play time. This start offset will **not** be repeated, whereas the delay in
691  * the [animation] (if any) will be repeated. By default, the amount of offset is 0.
692  *
693  * @param iterations the count of iterations. Should be at least 1.
694  * @param animation the [VectorizedAnimationSpec] describing each repetition iteration.
695  * @param repeatMode whether animation should repeat by starting from the beginning (i.e.
696  *   [RepeatMode.Restart]) or from the end (i.e. [RepeatMode.Reverse])
697  * @param initialStartOffset offsets the start of the animation
698  */
699 public class VectorizedRepeatableSpec<V : AnimationVector>(
700     private val iterations: Int,
701     private val animation: VectorizedDurationBasedAnimationSpec<V>,
702     private val repeatMode: RepeatMode = RepeatMode.Restart,
703     initialStartOffset: StartOffset = StartOffset(0)
704 ) : VectorizedFiniteAnimationSpec<V> {
705     @Deprecated(
706         level = DeprecationLevel.HIDDEN,
707         message =
708             "This method has been deprecated in favor of the constructor that accepts" +
709                 " start offset."
710     )
711     public constructor(
712         iterations: Int,
713         animation: VectorizedDurationBasedAnimationSpec<V>,
714         repeatMode: RepeatMode = RepeatMode.Restart
715     ) : this(iterations, animation, repeatMode, StartOffset(0))
716 
717     init {
718         if (iterations < 1) {
719             throw IllegalArgumentException("Iterations count can't be less than 1")
720         }
721     }
722 
723     // Per-iteration duration
724     internal val durationNanos: Long =
725         (animation.delayMillis + animation.durationMillis) * MillisToNanos
726 
727     // Fast forward amount. Delay type => negative offset
728     private val initialOffsetNanos = initialStartOffset.value * MillisToNanos
729 
730     private fun repetitionPlayTimeNanos(playTimeNanos: Long): Long {
731         if (playTimeNanos + initialOffsetNanos <= 0) {
732             return 0
733         } else {
734             val postOffsetPlayTimeNanos = playTimeNanos + initialOffsetNanos
735             val repeatsCount = min(postOffsetPlayTimeNanos / durationNanos, iterations - 1L)
736             return if (repeatMode == RepeatMode.Restart || repeatsCount % 2 == 0L) {
737                 postOffsetPlayTimeNanos - repeatsCount * durationNanos
738             } else {
739                 (repeatsCount + 1) * durationNanos - postOffsetPlayTimeNanos
740             }
741         }
742     }
743 
744     private fun repetitionStartVelocity(
745         playTimeNanos: Long,
746         start: V,
747         startVelocity: V,
748         end: V
749     ): V =
750         if (playTimeNanos + initialOffsetNanos > durationNanos) {
751             // Start velocity of the 2nd and subsequent iteration will be the velocity at the end
752             // of the first iteration, instead of the initial velocity.
753             getVelocityFromNanos(durationNanos - initialOffsetNanos, start, startVelocity, end)
754         } else startVelocity
755 
756     override fun getValueFromNanos(
757         playTimeNanos: Long,
758         initialValue: V,
759         targetValue: V,
760         initialVelocity: V
761     ): V {
762         return animation.getValueFromNanos(
763             repetitionPlayTimeNanos(playTimeNanos),
764             initialValue,
765             targetValue,
766             repetitionStartVelocity(playTimeNanos, initialValue, initialVelocity, targetValue)
767         )
768     }
769 
770     override fun getVelocityFromNanos(
771         playTimeNanos: Long,
772         initialValue: V,
773         targetValue: V,
774         initialVelocity: V
775     ): V {
776         return animation.getVelocityFromNanos(
777             repetitionPlayTimeNanos(playTimeNanos),
778             initialValue,
779             targetValue,
780             repetitionStartVelocity(playTimeNanos, initialValue, initialVelocity, targetValue)
781         )
782     }
783 
784     @Suppress("MethodNameUnits")
785     override fun getDurationNanos(initialValue: V, targetValue: V, initialVelocity: V): Long {
786         return iterations * durationNanos - initialOffsetNanos
787     }
788 }
789 
790 /** Physics class contains a number of recommended configurations for physics animations. */
791 public object Spring {
792     /** Stiffness constant for extremely stiff spring */
793     public const val StiffnessHigh: Float = 10_000f
794 
795     /**
796      * Stiffness constant for medium stiff spring. This is the default stiffness for spring force.
797      */
798     public const val StiffnessMedium: Float = 1500f
799 
800     /**
801      * Stiffness constant for medium-low stiff spring. This is the default stiffness for springs
802      * used in enter/exit transitions.
803      */
804     public const val StiffnessMediumLow: Float = 400f
805 
806     /** Stiffness constant for a spring with low stiffness. */
807     public const val StiffnessLow: Float = 200f
808 
809     /** Stiffness constant for a spring with very low stiffness. */
810     public const val StiffnessVeryLow: Float = 50f
811 
812     /**
813      * Damping ratio for a very bouncy spring. Note for under-damped springs (i.e. damping ratio <
814      * 1), the lower the damping ratio, the more bouncy the spring.
815      */
816     public const val DampingRatioHighBouncy: Float = 0.2f
817 
818     /**
819      * Damping ratio for a medium bouncy spring. This is also the default damping ratio for spring
820      * force. Note for under-damped springs (i.e. damping ratio < 1), the lower the damping ratio,
821      * the more bouncy the spring.
822      */
823     public const val DampingRatioMediumBouncy: Float = 0.5f
824 
825     /**
826      * Damping ratio for a spring with low bounciness. Note for under-damped springs (i.e. damping
827      * ratio < 1), the lower the damping ratio, the higher the bounciness.
828      */
829     public const val DampingRatioLowBouncy: Float = 0.75f
830 
831     /**
832      * Damping ratio for a spring with no bounciness. This damping ratio will create a critically
833      * damped spring that returns to equilibrium within the shortest amount of time without
834      * oscillating.
835      */
836     public const val DampingRatioNoBouncy: Float = 1f
837 
838     /** Default cutoff for rounding off physics based animations */
839     public const val DefaultDisplacementThreshold: Float = 0.01f
840 }
841 
842 /** Internal data structure for storing different FloatAnimations for different dimensions. */
843 internal interface Animations {
getnull844     operator fun get(index: Int): FloatAnimationSpec
845 }
846 
847 /**
848  * [VectorizedSpringSpec] uses spring animations to animate (each dimension of) [AnimationVector]s.
849  */
850 public class VectorizedSpringSpec<V : AnimationVector>
851 private constructor(
852     public val dampingRatio: Float,
853     public val stiffness: Float,
854     anims: Animations
855 ) : VectorizedFiniteAnimationSpec<V> by VectorizedFloatAnimationSpec(anims) {
856 
857     /**
858      * Creates a [VectorizedSpringSpec] that uses the same spring constants (i.e. [dampingRatio] and
859      * [stiffness] on all dimensions. The optional [visibilityThreshold] defines when the animation
860      * should be considered to be visually close enough to target to stop. By default,
861      * [Spring.DefaultDisplacementThreshold] is used on all dimensions of the [AnimationVector].
862      *
863      * @param dampingRatio damping ratio of the spring. [Spring.DampingRatioNoBouncy] by default.
864      * @param stiffness stiffness of the spring. [Spring.StiffnessMedium] by default.
865      * @param visibilityThreshold specifies the visibility threshold for each dimension.
866      */
867     public constructor(
868         dampingRatio: Float = Spring.DampingRatioNoBouncy,
869         stiffness: Float = Spring.StiffnessMedium,
870         visibilityThreshold: V? = null
871     ) : this(
872         dampingRatio,
873         stiffness,
874         createSpringAnimations(visibilityThreshold, dampingRatio, stiffness)
875     )
876 }
877 
createSpringAnimationsnull878 private fun <V : AnimationVector> createSpringAnimations(
879     visibilityThreshold: V?,
880     dampingRatio: Float,
881     stiffness: Float
882 ): Animations {
883     return if (visibilityThreshold != null) {
884         object : Animations {
885             private val anims =
886                 Array(visibilityThreshold.size) { index ->
887                     FloatSpringSpec(dampingRatio, stiffness, visibilityThreshold[index])
888                 }
889 
890             override fun get(index: Int): FloatSpringSpec = anims[index]
891         }
892     } else {
893         object : Animations {
894             private val anim = FloatSpringSpec(dampingRatio, stiffness)
895 
896             override fun get(index: Int): FloatSpringSpec = anim
897         }
898     }
899 }
900 
901 /**
902  * [VectorizedTweenSpec] animates a [AnimationVector] value by interpolating the start and end
903  * value, in the given [durationMillis] using the given [easing] curve.
904  *
905  * @param durationMillis duration of the [VectorizedTweenSpec] animation. Defaults to
906  *   [DefaultDurationMillis].
907  * @param delayMillis the amount of time the animation should wait before it starts running, 0 by
908  *   default.
909  * @param easing the easing curve used by the animation. [FastOutSlowInEasing] by default.
910  */
911 // TODO: Support different tween on different dimens
912 public class VectorizedTweenSpec<V : AnimationVector>(
913     override val durationMillis: Int = DefaultDurationMillis,
914     override val delayMillis: Int = 0,
915     public val easing: Easing = FastOutSlowInEasing
916 ) : VectorizedDurationBasedAnimationSpec<V> {
917 
918     private val anim =
919         VectorizedFloatAnimationSpec<V>(FloatTweenSpec(durationMillis, delayMillis, easing))
920 
getValueFromNanosnull921     override fun getValueFromNanos(
922         playTimeNanos: Long,
923         initialValue: V,
924         targetValue: V,
925         initialVelocity: V
926     ): V {
927         return anim.getValueFromNanos(playTimeNanos, initialValue, targetValue, initialVelocity)
928     }
929 
getVelocityFromNanosnull930     override fun getVelocityFromNanos(
931         playTimeNanos: Long,
932         initialValue: V,
933         targetValue: V,
934         initialVelocity: V
935     ): V {
936         return anim.getVelocityFromNanos(playTimeNanos, initialValue, targetValue, initialVelocity)
937     }
938 }
939 
940 /**
941  * A convenient implementation of [VectorizedFloatAnimationSpec] that turns a [FloatAnimationSpec]
942  * into a multi-dimensional [VectorizedFloatAnimationSpec], by using the same [FloatAnimationSpec]
943  * on each dimension of the [AnimationVector] that is being animated.
944  */
945 public class VectorizedFloatAnimationSpec<V : AnimationVector>
946 internal constructor(private val anims: Animations) : VectorizedFiniteAnimationSpec<V> {
947     private lateinit var valueVector: V
948     private lateinit var velocityVector: V
949     private lateinit var endVelocityVector: V
950 
951     /**
952      * Creates a [VectorizedAnimationSpec] from a [FloatAnimationSpec]. The given
953      * [FloatAnimationSpec] will be used to animate every dimension of the [AnimationVector].
954      *
955      * @param anim the animation spec for animating each dimension of the [AnimationVector]
956      */
957     public constructor(
958         anim: FloatAnimationSpec
959     ) : this(
960         object : Animations {
getnull961             override fun get(index: Int): FloatAnimationSpec {
962                 return anim
963             }
964         }
965     )
966 
getValueFromNanosnull967     override fun getValueFromNanos(
968         playTimeNanos: Long,
969         initialValue: V,
970         targetValue: V,
971         initialVelocity: V
972     ): V {
973         if (!::valueVector.isInitialized) {
974             valueVector = initialValue.newInstance()
975         }
976         for (i in 0 until valueVector.size) {
977             valueVector[i] =
978                 anims[i].getValueFromNanos(
979                     playTimeNanos,
980                     initialValue[i],
981                     targetValue[i],
982                     initialVelocity[i]
983                 )
984         }
985         return valueVector
986     }
987 
getVelocityFromNanosnull988     override fun getVelocityFromNanos(
989         playTimeNanos: Long,
990         initialValue: V,
991         targetValue: V,
992         initialVelocity: V
993     ): V {
994         if (!::velocityVector.isInitialized) {
995             velocityVector = initialVelocity.newInstance()
996         }
997         for (i in 0 until velocityVector.size) {
998             velocityVector[i] =
999                 anims[i].getVelocityFromNanos(
1000                     playTimeNanos,
1001                     initialValue[i],
1002                     targetValue[i],
1003                     initialVelocity[i]
1004                 )
1005         }
1006         return velocityVector
1007     }
1008 
getEndVelocitynull1009     override fun getEndVelocity(initialValue: V, targetValue: V, initialVelocity: V): V {
1010         if (!::endVelocityVector.isInitialized) {
1011             endVelocityVector = initialVelocity.newInstance()
1012         }
1013         for (i in 0 until endVelocityVector.size) {
1014             endVelocityVector[i] =
1015                 anims[i].getEndVelocity(initialValue[i], targetValue[i], initialVelocity[i])
1016         }
1017         return endVelocityVector
1018     }
1019 
1020     @Suppress("MethodNameUnits")
getDurationNanosnull1021     override fun getDurationNanos(initialValue: V, targetValue: V, initialVelocity: V): Long {
1022         var maxDuration = 0L
1023         for (i in 0 until initialValue.size) {
1024             maxDuration =
1025                 maxOf(
1026                     maxDuration,
1027                     anims[i].getDurationNanos(initialValue[i], targetValue[i], initialVelocity[i])
1028                 )
1029         }
1030         return maxDuration
1031     }
1032 }
1033 
1034 private val EmptyIntArray: IntArray = IntArray(0)
1035 private val EmptyFloatArray: FloatArray = FloatArray(0)
1036 private val EmptyArcSpline =
1037     ArcSpline(IntArray(2), FloatArray(2), arrayOf(FloatArray(2), FloatArray(2)))
1038