1 /*
<lambda>null2  * Copyright (C) 2024 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.ink.brush
18 
19 import androidx.annotation.FloatRange
20 import androidx.annotation.RestrictTo
21 import androidx.ink.geometry.ImmutableVec
22 import androidx.ink.nativeloader.NativeLoader
23 import androidx.ink.nativeloader.UsedByNative
24 import java.util.Collections.unmodifiableList
25 import kotlin.jvm.JvmField
26 
27 /**
28  * An easing function always passes through the (x, y) points (0, 0) and (1, 1). It typically acts
29  * to map x values in the [0, 1] interval to y values in [0, 1] by either one of the predefined or
30  * one of the parameterized curve types below. Depending on the type of curve, input and output
31  * values outside [0, 1] are possible.
32  */
33 @ExperimentalInkCustomBrushApi
34 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
35 
36 // NotCloseable: Finalize is only used to free the native peer.
37 @Suppress("NotCloseable")
38 public abstract class EasingFunction private constructor(internal val nativePointer: Long) {
39 
40     public fun finalize() {
41         EasingFunctionNative.free(nativePointer)
42     }
43 
44     public companion object {
45         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
46         public fun wrapNative(unownedNativePointer: Long): EasingFunction =
47             when (EasingFunctionNative.getParametersType(unownedNativePointer)) {
48                 0 -> Predefined(unownedNativePointer)
49                 1 -> CubicBezier(unownedNativePointer)
50                 2 -> Linear(unownedNativePointer)
51                 3 -> Steps(unownedNativePointer)
52                 else -> throw IllegalArgumentException("Invalid easing function type")
53             }
54     }
55 
56     public class Predefined internal constructor(nativePointer: Long) :
57         EasingFunction(nativePointer) {
58 
59         private constructor(value: Int) : this(EasingFunctionNative.createPredefined(value))
60 
61         internal val value: Int
62             get() = EasingFunctionNative.getPredefinedValueInt(nativePointer)
63 
64         internal fun toSimpleString(): String =
65             when (value) {
66                 0 -> "LINEAR"
67                 1 -> "EASE"
68                 2 -> "EASE_IN"
69                 3 -> "EASE_OUT"
70                 4 -> "EASE_IN_OUT"
71                 5 -> "STEP_START"
72                 6 -> "STEP_END"
73                 else -> "INVALID"
74             }
75 
76         override fun toString(): String = PREFIX + toSimpleString()
77 
78         override fun equals(other: Any?): Boolean {
79             if (other == null || other !is Predefined) return false
80             return value == other.value
81         }
82 
83         override fun hashCode(): Int = value.hashCode()
84 
85         public companion object {
86             /** The linear identity function: accepts and returns values outside [0, 1]. */
87             @JvmField public val LINEAR: Predefined = Predefined(0)
88 
89             /**
90              * Predefined cubic Bezier function. See
91              * [ease](https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions)
92              * and @see [CubicBezier] about input values outside [0, 1])
93              */
94             @JvmField public val EASE: Predefined = Predefined(1)
95 
96             /**
97              * Predefined cubic Bezier function. See
98              * [ease-in](https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions)
99              * and @see [CubicBezier] about input values outside [0, 1])
100              */
101             @JvmField public val EASE_IN: Predefined = Predefined(2)
102 
103             /**
104              * Predefined cubic Bezier function. See
105              * [ease-out](https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions)
106              * and @see [CubicBezier] about input values outside [0, 1])
107              */
108             @JvmField public val EASE_OUT: Predefined = Predefined(3)
109 
110             /**
111              * Predefined cubic Bezier function. See
112              * [ease-in-out](https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions)
113              * and @see [CubicBezier] about input values outside [0, 1])
114              */
115             @JvmField public val EASE_IN_OUT: Predefined = Predefined(4)
116 
117             /**
118              * Predefined step function with a jump-start at input progress value of 0. See
119              * [step start](https://www.w3.org/TR/css-easing-1/#step-easing-functions)
120              */
121             @JvmField public val STEP_START: Predefined = Predefined(5)
122 
123             /**
124              * Predefined step function with a jump-end at input progress value of 1. See
125              * [step end](https://www.w3.org/TR/css-easing-1/#step-easing-functions)
126              */
127             @JvmField public val STEP_END: Predefined = Predefined(6)
128 
129             private const val PREFIX = "EasingFunction.Predefined."
130         }
131     }
132 
133     /**
134      * Parameters for a custom cubic Bezier easing function.
135      *
136      * A cubic Bezier is generally defined by four points, P0 - P3. In the case of the easing
137      * function, P0 is defined to be the point (0, 0), and P3 is defined to be the point (1, 1). The
138      * values of [x1] and [x2] are required to be in the range [0, 1]. This guarantees that the
139      * resulting curve is a function with respect to x and follows the
140      * [CSS specification](https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions)
141      *
142      * Valid parameters must have all finite values, and [x1] and [x2] must be in the interval
143      * [0, 1].
144      *
145      * Input x values that are outside the interval [0, 1] will be clamped, but output values will
146      * not. This is somewhat different from the w3c defined cubic Bezier that allows extrapolated
147      * values outside x in [0, 1] by following end-point tangents.
148      */
149     public class CubicBezier internal constructor(nativePointer: Long) :
150         EasingFunction(nativePointer) {
151 
152         /**
153          * Creates a new [CubicBezier] easing function.
154          *
155          * @param x1 The x-coordinate of the first control point. Must be in the range [0, 1].
156          * @param y1 The y-coordinate of the first control point.
157          * @param x2 The x-coordinate of the second control point. Must be in the range [0, 1].
158          * @param y2 The y-coordinate of the second control point.
159          */
160         public constructor(
161             @FloatRange(from = 0.0, to = 1.0) x1: Float,
162             y1: Float,
163             @FloatRange(from = 0.0, to = 1.0) x2: Float,
164             y2: Float,
165         ) : this(EasingFunctionNative.createCubicBezier(x1, y1, x2, y2))
166 
167         /** The x-coordinate of the first control point. Must be in the range [0, 1]. */
168         @get:FloatRange(from = 0.0, to = 1.0)
169         public val x1: Float
170             get() = EasingFunctionNative.getCubicBezierX1(nativePointer)
171 
172         /** The y-coordinate of the first control point. */
173         public val y1: Float
174             get() = EasingFunctionNative.getCubicBezierY1(nativePointer)
175 
176         /** The x-coordinate of the second control point. Must be in the range [0, 1]. */
177         @get:FloatRange(from = 0.0, to = 1.0)
178         public val x2: Float
179             get() = EasingFunctionNative.getCubicBezierX2(nativePointer)
180 
181         /** The y-coordinate of the second control point. */
182         public val y2: Float
183             get() = EasingFunctionNative.getCubicBezierY2(nativePointer)
184 
185         override fun equals(other: Any?): Boolean {
186             if (other == null || other !is CubicBezier) {
187                 return false
188             }
189             return x1 == other.x1 && x2 == other.x2 && y1 == other.y1 && y2 == other.y2
190         }
191 
192         override fun hashCode(): Int {
193             var result = x1.hashCode()
194             result = 31 * result + x2.hashCode()
195             result = 31 * result + y1.hashCode()
196             result = 31 * result + y2.hashCode()
197             return result
198         }
199 
200         override fun toString(): String =
201             "EasingFunction.CubicBezier(x1=$x1, y1=$y1, x2=$x2, y2=$y2)"
202 
203         // Declared to make extension functions available.
204         public companion object
205     }
206 
207     /**
208      * Parameters for a custom piecewise-linear easing function.
209      *
210      * A piecewise-linear function is defined by a sequence of points; the value of the function at
211      * an x-position equal to one of those points is equal to the y-position of that point, and the
212      * value of the function at an x-position between two points is equal to the linear
213      * interpolation between those points' y-positions. This easing function implicitly includes the
214      * points (0, 0) and (1, 1), so the `points` field below need only include any points between
215      * those. If [points] is empty, then this function is equivalent to the [Predefined.LINEAR]
216      * identity function.
217      *
218      * To be valid, all y-positions must be finite, and all x-positions must be in the range [0, 1]
219      * and must be monotonically non-decreasing. It is valid for multiple points to have the same
220      * x-position, in order to create a discontinuity in the function; in that case, the value of
221      * the function at exactly that x-position is equal to the y-position of the last of these
222      * points.
223      *
224      * If the input x-value is outside the interval [0, 1], the output will be extrapolated from the
225      * first/last line segment.
226      */
227     public class Linear internal constructor(nativePointer: Long) : EasingFunction(nativePointer) {
228 
229         /**
230          * Creates a new [Linear] easing function.
231          *
232          * @param points The points that define the piecewise-linear function.
233          */
234         public constructor(
235             points: List<ImmutableVec>
236         ) : this(
237             EasingFunctionNative.createLinear(
238                 FloatArray(points.size * 2) { index ->
239                     if (index % 2 == 0) {
240                         points[index / 2].x
241                     } else {
242                         points[index / 2].y
243                     }
244                 }
245             )
246         )
247 
248         /** The points that define the piecewise-linear function. */
249         public val points: List<ImmutableVec> =
250             unmodifiableList(
251                 List<ImmutableVec>(EasingFunctionNative.getLinearNumPoints(nativePointer)) { index
252                     ->
253                     ImmutableVec(
254                         EasingFunctionNative.getLinearPointX(nativePointer, index),
255                         EasingFunctionNative.getLinearPointY(nativePointer, index),
256                     )
257                 }
258             )
259 
260         override fun equals(other: Any?): Boolean {
261             if (other == null || other !is Linear) {
262                 return false
263             }
264             return points == other.points
265         }
266 
267         override fun hashCode(): Int {
268             return points.hashCode()
269         }
270 
271         override fun toString(): String = "EasingFunction.Linear(${points})"
272 
273         // Declared to make extension functions available.
274         public companion object
275     }
276 
277     /**
278      * Parameters for a custom step easing function.
279      *
280      * A step function is defined by the number of equal-sized steps into which the
281      * [0, 1) interval of input-x is split and the behavior at the extremes. When x < 0, the output will always be 0. When x >= 1, the output will always be 1. The output of the first and last steps is governed by the [StepPosition].
282      *
283      * The behavior and naming follows the CSS steps() specification at
284      * [CSS Easing Functions](https://www.w3.org/TR/css-easing-1/#step-easing-functions)
285      */
286     public class Steps internal constructor(nativePointer: Long) : EasingFunction(nativePointer) {
287 
288         /**
289          * Creates a new [Steps] easing function.
290          *
291          * @param stepCount The number of steps. Must always be greater than 0, and must be greater
292          *   than 1 if [stepPosition] is [StepPosition.JUMP_NONE].
293          * @param stepPosition The behavior of the first and last steps.
294          */
295         public constructor(
296             stepCount: Int,
297             stepPosition: StepPosition,
298         ) : this(EasingFunctionNative.createSteps(stepCount, stepPosition.value))
299 
300         /**
301          * The number of steps. Must always be greater than 0, and must be greater than 1 if
302          * [stepPosition] is [StepPosition.JUMP_NONE].
303          */
304         public val stepCount: Int
305             get() = EasingFunctionNative.getStepsCount(nativePointer)
306 
307         /** The behavior of the first and last steps. */
308         public val stepPosition: StepPosition
309             get() = EasingFunctionNative.getStepsPosition(nativePointer)
310 
311         override fun equals(other: Any?): Boolean {
312             if (other == null || other !is Steps) {
313                 return false
314             }
315             return stepCount == other.stepCount && stepPosition == other.stepPosition
316         }
317 
318         override fun hashCode(): Int {
319             var result = stepCount.hashCode()
320             result = 31 * result + stepPosition.hashCode()
321             return result
322         }
323 
324         override fun toString(): String =
325             "EasingFunction.Steps(stepCount=$stepCount, stepPosition=$stepPosition)"
326 
327         // Declared to make extension functions available.
328         public companion object
329     }
330 
331     /**
332      * Setting to determine the desired output value of the first and last step of
333      * [0, 1) for [EasingFunction.Steps].
334      */
335     public class StepPosition internal constructor(@JvmField internal val value: Int) {
336 
337         internal fun toSimpleString(): String =
338             when (value) {
339                 0 -> "JUMP_END"
340                 1 -> "JUMP_START"
341                 2 -> "JUMP_BOTH"
342                 3 -> "JUMP_NONE"
343                 else -> "INVALID"
344             }
345 
346         override fun toString(): String = PREFIX + toSimpleString()
347 
348         override fun equals(other: Any?): Boolean {
349             if (other == null || other !is StepPosition) return false
350             return value == other.value
351         }
352 
353         override fun hashCode(): Int = value.hashCode()
354 
355         public companion object {
356             /**
357              * The step function "jumps" at the end of [0, 1): For x in [0, 1/step_count) => y = 0.
358              * For x in [1 - 1/step_count, 1) => y = 1 - 1/step_count.
359              */
360             @JvmField public val JUMP_END: StepPosition = StepPosition(0)
361             /**
362              * The step function "jumps" at the start of [0, 1): For x in [0, 1/step_count) => y =
363              * 1/step_count. For x in [1 - 1/step_count, 1) => y = 1.
364              */
365             @JvmField public val JUMP_START: StepPosition = StepPosition(1)
366             /**
367              * The step function "jumps" at both the start and the end: For x in [0, 1/step_count)
368              * => y = 1/(step_count + 1). For x in [1 - 1/step_count, 1) => y = 1 - 1/(step_count +
369              * 1).
370              */
371             @JvmField public val JUMP_BOTH: StepPosition = StepPosition(2)
372 
373             /**
374              * The step function does not "jump" at either boundary: For x in [0, 1/step_count) => y
375              * = 0. For x in [1 - 1/step_count, 1) => y = 1.
376              */
377             @JvmField public val JUMP_NONE: StepPosition = StepPosition(3)
378             private const val PREFIX = "EasingFunction.StepPosition."
379         }
380     }
381 }
382 
383 @OptIn(ExperimentalInkCustomBrushApi::class)
384 @UsedByNative
385 private object EasingFunctionNative {
386     init {
387         NativeLoader.load()
388     }
389 
createPredefinednull390     @UsedByNative public external fun createPredefined(value: Int): Long
391 
392     @UsedByNative
393     public external fun createCubicBezier(x1: Float, y1: Float, x2: Float, y2: Float): Long
394 
395     @UsedByNative public external fun createLinear(points: FloatArray): Long
396 
397     @UsedByNative public external fun createSteps(stepCount: Int, stepPosition: Int): Long
398 
399     @UsedByNative public external fun free(nativePointer: Long)
400 
401     @UsedByNative public external fun getParametersType(nativePointer: Long): Int
402 
403     // Predefined easing function accessors:
404 
405     @UsedByNative public external fun getPredefinedValueInt(nativePointer: Long): Int
406 
407     // Cubic Bezier easing function accessors:
408 
409     @UsedByNative public external fun getCubicBezierX1(nativePointer: Long): Float
410 
411     @UsedByNative public external fun getCubicBezierY1(nativePointer: Long): Float
412 
413     @UsedByNative public external fun getCubicBezierX2(nativePointer: Long): Float
414 
415     @UsedByNative public external fun getCubicBezierY2(nativePointer: Long): Float
416 
417     // Linear easing function accessors:
418 
419     @UsedByNative public external fun getLinearNumPoints(nativePointer: Long): Int
420 
421     @UsedByNative public external fun getLinearPointX(nativePointer: Long, index: Int): Float
422 
423     @UsedByNative public external fun getLinearPointY(nativePointer: Long, index: Int): Float
424 
425     // Steps easing function accessors:
426 
427     @UsedByNative public external fun getStepsCount(nativePointer: Long): Int
428 
429     public fun getStepsPosition(nativePointer: Long): EasingFunction.StepPosition =
430         EasingFunction.StepPosition(getStepsPositionInt(nativePointer))
431 
432     @UsedByNative private external fun getStepsPositionInt(nativePointer: Long): Int
433 }
434