1 /* 2 * 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 package com.android.internal.widget.remotecompose.core.operations.utilities.easing; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 21 import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; 22 import com.android.internal.widget.remotecompose.core.serialize.Serializable; 23 24 /** Support Animation of the FloatExpression */ 25 public class FloatAnimation extends Easing implements Serializable { 26 float[] mSpec; 27 // mSpec[0] = duration 28 // int(mSpec[1]) = num_of_param << 16 | type 29 // mSpec[2..1+num_of_param] params 30 // mSpec[2+num_of_param] starting Value 31 Easing mEasingCurve; 32 33 private float mDuration = 1; 34 private float mWrap = Float.NaN; 35 private float mInitialValue = Float.NaN; 36 private float mTargetValue = Float.NaN; 37 private int mDirectionalSnap = 0; 38 // private float mScale = 1; 39 float mOffset = 0; 40 private boolean mPropagate = false; 41 42 @NonNull 43 @Override toString()44 public String toString() { 45 46 String str = "type " + mType; 47 if (!Float.isNaN(mInitialValue)) { 48 str += " " + mInitialValue; 49 } 50 if (!Float.isNaN(mTargetValue)) { 51 str += " -> " + mTargetValue; 52 } 53 if (!Float.isNaN(mWrap)) { 54 str += " % " + mWrap; 55 } 56 57 return str; 58 } 59 60 /** 61 * Create an animation based on a float encoding of the animation 62 * 63 * @param description the float encoding of the animation 64 */ FloatAnimation(@onNull float... description)65 public FloatAnimation(@NonNull float... description) { 66 mType = CUBIC_STANDARD; 67 setAnimationDescription(description); 68 } 69 70 /** 71 * Create an animation based on the parameters 72 * 73 * @param type The type of animation 74 * @param duration The duration of the animation 75 * @param description The float parameters describing the animation 76 * @param initialValue The initial value of the float (NaN if none) 77 * @param wrap The wrap value of the animation NaN if it does not wrap 78 */ FloatAnimation( int type, float duration, @Nullable float[] description, float initialValue, float wrap)79 public FloatAnimation( 80 int type, 81 float duration, 82 @Nullable float[] description, 83 float initialValue, 84 float wrap) { 85 mType = CUBIC_STANDARD; 86 setAnimationDescription(packToFloatArray(duration, type, description, initialValue, wrap)); 87 } 88 89 /** 90 * packs spec into a float array 91 * 92 * @param duration 93 * @param type 94 * @param spec 95 * @param initialValue 96 * @return 97 */ packToFloatArray( float duration, int type, @Nullable float[] spec, float initialValue, float wrap)98 public static @NonNull float[] packToFloatArray( 99 float duration, int type, @Nullable float[] spec, float initialValue, float wrap) { 100 int count = 0; 101 102 if (!Float.isNaN(initialValue)) { 103 count++; 104 } 105 if (spec != null) { 106 107 count++; 108 } 109 if (spec != null || type != CUBIC_STANDARD) { 110 count++; 111 count += (spec == null) ? 0 : spec.length; 112 } 113 114 if (!Float.isNaN(initialValue)) { 115 count++; 116 } 117 if (!Float.isNaN(wrap)) { 118 count++; 119 } 120 if (duration != 1 || count > 0) { 121 count++; 122 } 123 if (!Float.isNaN(wrap) || !Float.isNaN(initialValue)) { 124 count++; 125 } 126 float[] ret = new float[count]; 127 int pos = 0; 128 int specLen = (spec == null) ? 0 : spec.length; 129 130 if (ret.length > 0) { 131 ret[pos++] = duration; 132 } 133 if (ret.length > 1) { 134 int wrapBit = Float.isNaN(wrap) ? 0 : 1; 135 int initBit = Float.isNaN(initialValue) ? 0 : 2; 136 int bits = type | ((wrapBit | initBit) << 8); 137 ret[pos++] = Float.intBitsToFloat(specLen << 16 | bits); 138 } 139 140 if (specLen > 0) { 141 System.arraycopy(spec, 0, ret, pos, spec.length); 142 pos += spec.length; 143 } 144 if (!Float.isNaN(initialValue)) { 145 ret[pos++] = initialValue; 146 } 147 if (!Float.isNaN(wrap)) { 148 ret[pos] = wrap; 149 } 150 return ret; 151 } 152 153 /** 154 * Useful to debug the packed form of an animation string 155 * 156 * @param description the float encoding of the animation 157 * @return a string describing the animation 158 */ unpackAnimationToString(float[] description)159 public static String unpackAnimationToString(float[] description) { 160 float[] mSpec = description; 161 float mDuration = (mSpec.length == 0) ? 1 : mSpec[0]; 162 int len = 0; 163 int type = 0; 164 float wrapValue = Float.NaN; 165 float initialValue = Float.NaN; 166 int directionalSnap = 0; 167 boolean propagate = false; 168 if (mSpec.length > 1) { 169 int num_type = Float.floatToRawIntBits(mSpec[1]); 170 type = num_type & 0xFF; 171 boolean wrap = ((num_type >> 8) & 0x1) > 0; 172 boolean init = ((num_type >> 8) & 0x2) > 0; 173 directionalSnap = (num_type >> 10) & 0x3; 174 propagate = ((num_type >> 12) & 0x1) > 0; 175 len = (num_type >> 16) & 0xFFFF; 176 int off = 2 + len; 177 if (init) { 178 initialValue = mSpec[off++]; 179 } 180 if (wrap) { 181 wrapValue = mSpec[off]; 182 } 183 } 184 float[] params = description; 185 int offset = 2; 186 187 String typeStr = ""; 188 switch (type) { 189 case CUBIC_STANDARD: 190 typeStr = "CUBIC_STANDARD"; 191 break; 192 case CUBIC_ACCELERATE: 193 typeStr = "CUBIC_ACCELERATE"; 194 break; 195 case CUBIC_DECELERATE: 196 typeStr = "CUBIC_DECELERATE"; 197 break; 198 case CUBIC_LINEAR: 199 typeStr = "CUBIC_LINEAR"; 200 break; 201 case CUBIC_ANTICIPATE: 202 typeStr = "CUBIC_ANTICIPATE"; 203 break; 204 case CUBIC_OVERSHOOT: 205 typeStr = "CUBIC_OVERSHOOT"; 206 207 break; 208 case CUBIC_CUSTOM: 209 typeStr = "CUBIC_CUSTOM ("; 210 typeStr += params[offset + 0] + " "; 211 typeStr += params[offset + 1] + " "; 212 typeStr += params[offset + 2] + " "; 213 typeStr += params[offset + 3] + " )"; 214 break; 215 case EASE_OUT_BOUNCE: 216 typeStr = "EASE_OUT_BOUNCE"; 217 218 break; 219 case EASE_OUT_ELASTIC: 220 typeStr = "EASE_OUT_ELASTIC"; 221 break; 222 case SPLINE_CUSTOM: 223 typeStr = "SPLINE_CUSTOM ("; 224 for (int i = offset; i < offset + len; i++) { 225 typeStr += params[i] + " "; 226 } 227 typeStr += ")"; 228 break; 229 } 230 231 String str = mDuration + " " + typeStr; 232 if (!Float.isNaN(initialValue)) { 233 str += " init =" + initialValue; 234 } 235 if (!Float.isNaN(wrapValue)) { 236 str += " wrap =" + wrapValue; 237 } 238 if (directionalSnap != 0) { 239 str += " directionalSnap=" + directionalSnap; 240 } 241 if (propagate) { 242 str += " propagate"; 243 } 244 return str; 245 } 246 247 /** 248 * Create an animation based on a float encoding of the animation 249 * 250 * @param description the float encoding of the animation 251 */ setAnimationDescription(@onNull float[] description)252 public void setAnimationDescription(@NonNull float[] description) { 253 mSpec = description; 254 mDuration = (mSpec.length == 0) ? 1 : mSpec[0]; 255 int len = 0; 256 if (mSpec.length > 1) { 257 int num_type = Float.floatToRawIntBits(mSpec[1]); 258 mType = num_type & 0xFF; 259 boolean wrap = ((num_type >> 8) & 0x1) > 0; 260 boolean init = ((num_type >> 8) & 0x2) > 0; 261 int directional = (num_type >> 10) & 0x3; 262 boolean propagate = ((num_type >> 12) & 0x1) > 0; 263 len = (num_type >> 16) & 0xFFFF; 264 int off = 2 + len; 265 if (init) { 266 mInitialValue = mSpec[off++]; 267 } 268 if (wrap) { 269 mWrap = mSpec[off]; 270 } 271 mDirectionalSnap = directional; 272 mPropagate = propagate; 273 } 274 create(mType, description, 2, len); 275 } 276 create(int type, @Nullable float[] params, int offset, int len)277 private void create(int type, @Nullable float[] params, int offset, int len) { 278 switch (type) { 279 case CUBIC_STANDARD: 280 case CUBIC_ACCELERATE: 281 case CUBIC_DECELERATE: 282 case CUBIC_LINEAR: 283 case CUBIC_ANTICIPATE: 284 case CUBIC_OVERSHOOT: 285 mEasingCurve = new CubicEasing(type); 286 break; 287 case CUBIC_CUSTOM: 288 mEasingCurve = 289 new CubicEasing( 290 params[offset + 0], 291 params[offset + 1], 292 params[offset + 2], 293 params[offset + 3]); 294 break; 295 case EASE_OUT_BOUNCE: 296 mEasingCurve = new BounceCurve(type); 297 break; 298 case EASE_OUT_ELASTIC: 299 mEasingCurve = new ElasticOutCurve(); 300 break; 301 case SPLINE_CUSTOM: 302 mEasingCurve = new StepCurve(params, offset, len); 303 break; 304 } 305 } 306 307 /** 308 * Get the duration the interpolate is to take 309 * 310 * @return duration in seconds 311 */ getDuration()312 public float getDuration() { 313 return mDuration; 314 } 315 316 /** 317 * Set the initial Value 318 * 319 * @param value the value to set 320 */ setInitialValue(float value)321 public void setInitialValue(float value) { 322 323 if (Float.isNaN(mWrap)) { 324 mInitialValue = value; 325 } else { 326 mInitialValue = value % mWrap; 327 } 328 setScaleOffset(); 329 } 330 wrap(float wrap, float value)331 private static float wrap(float wrap, float value) { 332 value = value % wrap; 333 if (value < 0) { 334 value += wrap; 335 } 336 return value; 337 } 338 wrapDistance(float wrap, float from, float to)339 float wrapDistance(float wrap, float from, float to) { 340 float delta = (to - from) % 360; 341 if (delta < -wrap / 2) { 342 delta += wrap; 343 } else if (delta > wrap / 2) { 344 delta -= wrap; 345 } 346 return delta; 347 } 348 349 /** 350 * Set the target value to interpolate to 351 * 352 * @param value the value to set 353 */ setTargetValue(float value)354 public void setTargetValue(float value) { 355 mTargetValue = value; 356 if (!Float.isNaN(mWrap)) { 357 mInitialValue = wrap(mWrap, mInitialValue); 358 mTargetValue = wrap(mWrap, mTargetValue); 359 if (Float.isNaN(mInitialValue)) { 360 mInitialValue = mTargetValue; 361 } 362 363 float dist = wrapDistance(mWrap, mInitialValue, mTargetValue); 364 if ((dist > 0) && (mTargetValue < mInitialValue)) { 365 mTargetValue += mWrap; 366 } else if ((dist < 0) && mDirectionalSnap != 0) { 367 if (mDirectionalSnap == 1 && mTargetValue > mInitialValue) { 368 mInitialValue = mTargetValue; 369 } 370 if (mDirectionalSnap == 2 && mTargetValue < mInitialValue) { 371 mInitialValue = mTargetValue; 372 } 373 mTargetValue -= mWrap; 374 } 375 } 376 setScaleOffset(); 377 } 378 379 /** 380 * Get the target value 381 * 382 * @return the target value 383 */ getTargetValue()384 public float getTargetValue() { 385 return mTargetValue; 386 } 387 setScaleOffset()388 private void setScaleOffset() { 389 if (!Float.isNaN(mInitialValue) && !Float.isNaN(mTargetValue)) { 390 // mScale = (mTargetValue - mInitialValue); // TODO: commented out because 391 // unused. 392 mOffset = mInitialValue; 393 } else { 394 // mScale = 1; // TODO: commented out because its unused 395 mOffset = 0; 396 } 397 } 398 399 /** get the value at time t in seconds since start */ 400 @Override get(float t)401 public float get(float t) { 402 if (mDirectionalSnap == 1 && mTargetValue < mInitialValue) { 403 mInitialValue = mTargetValue; 404 return mTargetValue; 405 } 406 if (mDirectionalSnap == 2 && mTargetValue > mInitialValue) { 407 mInitialValue = mTargetValue; 408 return mTargetValue; 409 } 410 return mEasingCurve.get(t / mDuration) * (mTargetValue - mInitialValue) + mInitialValue; 411 } 412 413 /** get the slope of the easing function at at x */ 414 @Override getDiff(float t)415 public float getDiff(float t) { 416 return mEasingCurve.getDiff(t / mDuration) * (mTargetValue - mInitialValue); 417 } 418 419 /** 420 * @return if you should propagate the animation 421 */ isPropagate()422 public boolean isPropagate() { 423 return mPropagate; 424 } 425 426 /** 427 * Get the initial value 428 * 429 * @return the initial value 430 */ getInitialValue()431 public float getInitialValue() { 432 return mInitialValue; 433 } 434 435 @Override serialize(MapSerializer serializer)436 public void serialize(MapSerializer serializer) { 437 serializer 438 .addType("FloatAnimation") 439 .add("initialValue", mInitialValue) 440 .add("targetValue", mTargetValue) 441 .add("duration", mDuration) 442 .add("easing", Easing.getString(mEasingCurve.getType())); 443 } 444 } 445