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