• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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