1 /*
2  * Copyright (C) 2021 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 androidx.constraintlayout.core.motion;
17 
18 import static androidx.constraintlayout.core.motion.MotionWidget.UNSET;
19 
20 import androidx.constraintlayout.core.motion.key.MotionConstraintSet;
21 import androidx.constraintlayout.core.motion.key.MotionKey;
22 import androidx.constraintlayout.core.motion.key.MotionKeyAttributes;
23 import androidx.constraintlayout.core.motion.key.MotionKeyCycle;
24 import androidx.constraintlayout.core.motion.key.MotionKeyPosition;
25 import androidx.constraintlayout.core.motion.key.MotionKeyTimeCycle;
26 import androidx.constraintlayout.core.motion.key.MotionKeyTrigger;
27 import androidx.constraintlayout.core.motion.utils.CurveFit;
28 import androidx.constraintlayout.core.motion.utils.DifferentialInterpolator;
29 import androidx.constraintlayout.core.motion.utils.Easing;
30 import androidx.constraintlayout.core.motion.utils.FloatRect;
31 import androidx.constraintlayout.core.motion.utils.KeyCache;
32 import androidx.constraintlayout.core.motion.utils.KeyCycleOscillator;
33 import androidx.constraintlayout.core.motion.utils.KeyFrameArray;
34 import androidx.constraintlayout.core.motion.utils.Rect;
35 import androidx.constraintlayout.core.motion.utils.SplineSet;
36 import androidx.constraintlayout.core.motion.utils.TimeCycleSplineSet;
37 import androidx.constraintlayout.core.motion.utils.TypedBundle;
38 import androidx.constraintlayout.core.motion.utils.TypedValues;
39 import androidx.constraintlayout.core.motion.utils.Utils;
40 import androidx.constraintlayout.core.motion.utils.VelocityMatrix;
41 import androidx.constraintlayout.core.motion.utils.ViewState;
42 
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.Collections;
46 import java.util.HashMap;
47 import java.util.HashSet;
48 
49 /**
50  * Contains the picture of a view through a transition and is used to interpolate it.
51  * During a transition every view has a MotionController which drives its position.
52  * <p>
53  * All parameter which affect a views motion are added to MotionController and then setup()
54  * builds out the splines that control the view.
55  *
56  *
57  */
58 public class Motion implements TypedValues {
59     public static final int PATH_PERCENT = 0;
60     public static final int PATH_PERPENDICULAR = 1;
61     public static final int HORIZONTAL_PATH_X = 2;
62     public static final int HORIZONTAL_PATH_Y = 3;
63     public static final int VERTICAL_PATH_X = 4;
64     public static final int VERTICAL_PATH_Y = 5;
65     public static final int DRAW_PATH_NONE = 0;
66     public static final int DRAW_PATH_BASIC = 1;
67     public static final int DRAW_PATH_RELATIVE = 2;
68     public static final int DRAW_PATH_CARTESIAN = 3;
69     public static final int DRAW_PATH_AS_CONFIGURED = 4;
70     public static final int DRAW_PATH_RECTANGLE = 5;
71     public static final int DRAW_PATH_SCREEN = 6;
72 
73     public static final int ROTATION_RIGHT = 1;
74     public static final int ROTATION_LEFT = 2;
75     Rect mTempRect = new Rect(); // for efficiency
76 
77     private static final String TAG = "MotionController";
78     private static final boolean DEBUG = false;
79     private static final boolean FAVOR_FIXED_SIZE_VIEWS = false;
80     MotionWidget mView;
81     public String mId;
82     String mConstraintTag;
83     private int mCurveFitType = CurveFit.SPLINE;
84     private MotionPaths mStartMotionPath = new MotionPaths();
85     private MotionPaths mEndMotionPath = new MotionPaths();
86 
87     private MotionConstrainedPoint mStartPoint = new MotionConstrainedPoint();
88     private MotionConstrainedPoint mEndPoint = new MotionConstrainedPoint();
89 
90     // spline 0 is the generic one that process all the standard attributes
91     private CurveFit[] mSpline;
92     private CurveFit mArcSpline;
93     float mMotionStagger = Float.NaN;
94     float mStaggerOffset = 0;
95     float mStaggerScale = 1.0f;
96     float mCurrentCenterX, mCurrentCenterY;
97     private int[] mInterpolateVariables;
98     private double[] mInterpolateData; // scratch data created during setup
99     private double[] mInterpolateVelocity; // scratch data created during setup
100 
101     private String[] mAttributeNames;  // the names of the custom attributes
102     private int[] mAttributeInterpolatorCount; // how many interpolators for each custom attribute
103     private int mMaxDimension = 4;
104     private float[] mValuesBuff = new float[mMaxDimension];
105     private ArrayList<MotionPaths> mMotionPaths = new ArrayList<>();
106     private float[] mVelocity = new float[1]; // used as a temp buffer to return values
107 
108     private ArrayList<MotionKey> mKeyList = new ArrayList<>(); // List of key frame items
109 
110     // splines to calculate for use TimeCycles
111     private HashMap<String, TimeCycleSplineSet> mTimeCycleAttributesMap;
112     private HashMap<String, SplineSet> mAttributesMap; // splines to calculate values of attributes
113 
114     // splines to calculate values of attributes
115     private HashMap<String, KeyCycleOscillator> mCycleMap;
116     private MotionKeyTrigger[] mKeyTriggers; // splines to calculate values of attributes
117     private int mPathMotionArc = UNSET;
118 
119     // if set, pivot point is maintained as the other object
120     private int mTransformPivotTarget = UNSET;
121 
122     // if set, pivot point is maintained as the other object
123     private MotionWidget mTransformPivotView = null;
124     private int mQuantizeMotionSteps = UNSET;
125     private float mQuantizeMotionPhase = Float.NaN;
126     private DifferentialInterpolator mQuantizeMotionInterpolator = null;
127     private boolean mNoMovement = false;
128     Motion mRelativeMotion;
129     /**
130      * Get the view to pivot around
131      *
132      * @return id of view or UNSET if not set
133      */
getTransformPivotTarget()134     public int getTransformPivotTarget() {
135         return mTransformPivotTarget;
136     }
137 
138     /**
139      * Set a view to pivot around
140      *
141      * @param transformPivotTarget id of view
142      */
setTransformPivotTarget(int transformPivotTarget)143     public void setTransformPivotTarget(int transformPivotTarget) {
144         mTransformPivotTarget = transformPivotTarget;
145         mTransformPivotView = null;
146     }
147 
148     /**
149      * provides access to MotionPath objects
150      */
getKeyFrame(int i)151     public MotionPaths getKeyFrame(int i) {
152         return mMotionPaths.get(i);
153     }
154 
Motion(MotionWidget view)155     public Motion(MotionWidget view) {
156         setView(view);
157     }
158 
159     /**
160      * get the left most position of the widget at the start of the movement.
161      *
162      * @return the left most position
163      */
getStartX()164     public float getStartX() {
165         return mStartMotionPath.mX;
166     }
167 
168     /**
169      * get the top most position of the widget at the start of the movement.
170      * Positive is down.
171      *
172      * @return the top most position
173      */
getStartY()174     public float getStartY() {
175         return mStartMotionPath.mY;
176     }
177 
178     /**
179      * get the left most position of the widget at the end of the movement.
180      *
181      * @return the left most position
182      */
getFinalX()183     public float getFinalX() {
184         return mEndMotionPath.mX;
185     }
186 
187     /**
188      * get the top most position of the widget at the end of the movement.
189      * Positive is down.
190      *
191      * @return the top most position
192      */
getFinalY()193     public float getFinalY() {
194         return mEndMotionPath.mY;
195     }
196 
197     /**
198      * get the width of the widget at the start of the movement.
199      *
200      * @return the width at the start
201      */
getStartWidth()202     public float getStartWidth() {
203         return mStartMotionPath.mWidth;
204     }
205 
206     /**
207      * get the width of the widget at the start of the movement.
208      *
209      * @return the height at the start
210      */
getStartHeight()211     public float getStartHeight() {
212         return mStartMotionPath.mHeight;
213     }
214 
215     /**
216      * get the width of the widget at the end of the movement.
217      *
218      * @return the width at the end
219      */
getFinalWidth()220     public float getFinalWidth() {
221         return mEndMotionPath.mWidth;
222     }
223 
224     /**
225      * get the width of the widget at the end of the movement.
226      *
227      * @return the height at the end
228      */
getFinalHeight()229     public float getFinalHeight() {
230         return mEndMotionPath.mHeight;
231     }
232 
233     /**
234      * Returns the id of the view to move relative to.
235      * The position at the start and then end will be viewed relative to this view
236      * -1 is the return value if NOT in polar mode
237      *
238      * @return the view id of the view this is in polar mode to or -1 if not in polar
239      */
getAnimateRelativeTo()240     public String getAnimateRelativeTo() {
241         return mStartMotionPath.mAnimateRelativeTo;
242     }
243 
244     /**
245      * set up the motion to be relative to this other motionController
246      */
setupRelative(Motion motionController)247     public void setupRelative(Motion motionController) {
248         mRelativeMotion = motionController;
249     }
250 
setupRelative()251     private void setupRelative() {
252         if (mRelativeMotion == null) {
253             return;
254         }
255         mStartMotionPath.setupRelative(mRelativeMotion, mRelativeMotion.mStartMotionPath);
256         mEndMotionPath.setupRelative(mRelativeMotion, mRelativeMotion.mEndMotionPath);
257     }
258 
getCenterX()259     public float getCenterX() {
260         return mCurrentCenterX;
261     }
262 
getCenterY()263     public float getCenterY() {
264         return mCurrentCenterY;
265     }
266 
267     // @TODO: add description
getCenter(double p, float[] pos, float[] vel)268     public void getCenter(double p, float[] pos, float[] vel) {
269         double[] position = new double[4];
270         double[] velocity = new double[4];
271         mSpline[0].getPos(p, position);
272         mSpline[0].getSlope(p, velocity);
273         Arrays.fill(vel, 0);
274         mStartMotionPath.getCenter(p, mInterpolateVariables, position, pos, velocity, vel);
275     }
276 
277     /**
278      * Fills the array point with the center coordinates.
279      * point[0] is filled with the
280      * x coordinate of "time" 0.0 mPoints[point.length-1] is filled with the y coordinate of "time"
281      * 1.0
282      *
283      * @param points     array to fill (should be 2x pointCount)
284      * @param pointCount truncate mPoints to this length. Must be > 1 and <= mPoints.length
285      */
buildPath(float[] points, int pointCount)286     public void buildPath(float[] points, int pointCount) {
287         float mils = 1.0f / (pointCount - 1);
288         SplineSet trans_x =
289                 (mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.TRANSLATION_X);
290         SplineSet trans_y =
291                 (mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.TRANSLATION_Y);
292         KeyCycleOscillator osc_x =
293                 (mCycleMap == null) ? null : mCycleMap.get(MotionKey.TRANSLATION_X);
294         KeyCycleOscillator osc_y =
295                 (mCycleMap == null) ? null : mCycleMap.get(MotionKey.TRANSLATION_Y);
296 
297         for (int i = 0; i < pointCount; i++) {
298             float position = i * mils;
299             if (mStaggerScale != 1.0f) {
300                 if (position < mStaggerOffset) {
301                     position = 0;
302                 }
303                 if (position > mStaggerOffset && position < 1.0) {
304                     position -= mStaggerOffset;
305                     position *= mStaggerScale;
306                     position = Math.min(position, 1.0f);
307                 }
308             }
309             double p = position;
310 
311             Easing easing = mStartMotionPath.mKeyFrameEasing;
312             float start = 0;
313             float end = Float.NaN;
314             for (MotionPaths frame : mMotionPaths) {
315                 if (frame.mKeyFrameEasing != null) { // this frame has an easing
316                     if (frame.mTime < position) {  // frame with easing is before the current pos
317                         easing = frame.mKeyFrameEasing; // this is the candidate
318                         start = frame.mTime; // this is also the starting time
319                     } else { // frame with easing is past the pos
320                         if (Float.isNaN(end)) { // we never ended the time line
321                             end = frame.mTime;
322                         }
323                     }
324                 }
325             }
326 
327             if (easing != null) {
328                 if (Float.isNaN(end)) {
329                     end = 1.0f;
330                 }
331                 float offset = (position - start) / (end - start);
332                 offset = (float) easing.get(offset);
333                 p = offset * (end - start) + start;
334 
335             }
336 
337             mSpline[0].getPos(p, mInterpolateData);
338             if (mArcSpline != null) {
339                 if (mInterpolateData.length > 0) {
340                     mArcSpline.getPos(p, mInterpolateData);
341                 }
342             }
343             mStartMotionPath.getCenter(p, mInterpolateVariables, mInterpolateData, points, i * 2);
344 
345             if (osc_x != null) {
346                 points[i * 2] += osc_x.get(position);
347             } else if (trans_x != null) {
348                 points[i * 2] += trans_x.get(position);
349             }
350             if (osc_y != null) {
351                 points[i * 2 + 1] += osc_y.get(position);
352             } else if (trans_y != null) {
353                 points[i * 2 + 1] += trans_y.get(position);
354             }
355         }
356     }
357 
getPos(double position)358     double[] getPos(double position) {
359         mSpline[0].getPos(position, mInterpolateData);
360         if (mArcSpline != null) {
361             if (mInterpolateData.length > 0) {
362                 mArcSpline.getPos(position, mInterpolateData);
363             }
364         }
365         return mInterpolateData;
366     }
367 
368     /**
369      * Fills the array point with the center coordinates.
370      * point[0] is filled with the
371      * x coordinate of "time" 0.0 mPoints[point.length-1] is filled with the y coordinate of "time"
372      * 1.0
373      *
374      * @param bounds array to fill (should be 2x the number of mPoints
375      * @return number of key frames
376      */
buildBounds(float[] bounds, int pointCount)377     void buildBounds(float[] bounds, int pointCount) {
378         float mils = 1.0f / (pointCount - 1);
379         @SuppressWarnings("unused") SplineSet trans_x =
380                 (mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.TRANSLATION_X);
381         @SuppressWarnings("unused") SplineSet trans_y =
382                 (mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.TRANSLATION_Y);
383         @SuppressWarnings("unused") KeyCycleOscillator osc_x =
384                 (mCycleMap == null) ? null : mCycleMap.get(MotionKey.TRANSLATION_X);
385         @SuppressWarnings("unused") KeyCycleOscillator osc_y =
386                 (mCycleMap == null) ? null : mCycleMap.get(MotionKey.TRANSLATION_Y);
387 
388         for (int i = 0; i < pointCount; i++) {
389             float position = i * mils;
390             if (mStaggerScale != 1.0f) {
391                 if (position < mStaggerOffset) {
392                     position = 0;
393                 }
394                 if (position > mStaggerOffset && position < 1.0) {
395                     position -= mStaggerOffset;
396                     position *= mStaggerScale;
397                     position = Math.min(position, 1.0f);
398                 }
399             }
400             double p = position;
401 
402             Easing easing = mStartMotionPath.mKeyFrameEasing;
403             float start = 0;
404             float end = Float.NaN;
405             for (MotionPaths frame : mMotionPaths) {
406                 if (frame.mKeyFrameEasing != null) { // this frame has an easing
407                     if (frame.mTime < position) {  // frame with easing is before the current pos
408                         easing = frame.mKeyFrameEasing; // this is the candidate
409                         start = frame.mTime; // this is also the starting time
410                     } else { // frame with easing is past the pos
411                         if (Float.isNaN(end)) { // we never ended the time line
412                             end = frame.mTime;
413                         }
414                     }
415                 }
416             }
417 
418             if (easing != null) {
419                 if (Float.isNaN(end)) {
420                     end = 1.0f;
421                 }
422                 float offset = (position - start) / (end - start);
423                 offset = (float) easing.get(offset);
424                 p = offset * (end - start) + start;
425 
426             }
427 
428             mSpline[0].getPos(p, mInterpolateData);
429             if (mArcSpline != null) {
430                 if (mInterpolateData.length > 0) {
431                     mArcSpline.getPos(p, mInterpolateData);
432                 }
433             }
434             mStartMotionPath.getBounds(mInterpolateVariables, mInterpolateData, bounds, i * 2);
435         }
436     }
437 
getPreCycleDistance()438     private float getPreCycleDistance() {
439         int pointCount = 100;
440         float[] points = new float[2];
441         float sum = 0;
442         float mils = 1.0f / (pointCount - 1);
443         double x = 0, y = 0;
444         for (int i = 0; i < pointCount; i++) {
445             float position = i * mils;
446 
447             double p = position;
448 
449             Easing easing = mStartMotionPath.mKeyFrameEasing;
450             float start = 0;
451             float end = Float.NaN;
452             for (MotionPaths frame : mMotionPaths) {
453                 if (frame.mKeyFrameEasing != null) { // this frame has an easing
454                     if (frame.mTime < position) {  // frame with easing is before the current pos
455                         easing = frame.mKeyFrameEasing; // this is the candidate
456                         start = frame.mTime; // this is also the starting time
457                     } else { // frame with easing is past the pos
458                         if (Float.isNaN(end)) { // we never ended the time line
459                             end = frame.mTime;
460                         }
461                     }
462                 }
463             }
464 
465             if (easing != null) {
466                 if (Float.isNaN(end)) {
467                     end = 1.0f;
468                 }
469                 float offset = (position - start) / (end - start);
470                 offset = (float) easing.get(offset);
471                 p = offset * (end - start) + start;
472 
473             }
474 
475             mSpline[0].getPos(p, mInterpolateData);
476             mStartMotionPath.getCenter(p, mInterpolateVariables, mInterpolateData, points, 0);
477             if (i > 0) {
478                 sum += (float) Math.hypot(y - points[1], x - points[0]);
479             }
480             x = points[0];
481             y = points[1];
482         }
483         return sum;
484     }
485 
getPositionKeyframe(int layoutWidth, int layoutHeight, float x, float y)486     MotionKeyPosition getPositionKeyframe(int layoutWidth, int layoutHeight, float x, float y) {
487         FloatRect start = new FloatRect();
488         start.left = mStartMotionPath.mX;
489         start.top = mStartMotionPath.mY;
490         start.right = start.left + mStartMotionPath.mWidth;
491         start.bottom = start.top + mStartMotionPath.mHeight;
492         FloatRect end = new FloatRect();
493         end.left = mEndMotionPath.mX;
494         end.top = mEndMotionPath.mY;
495         end.right = end.left + mEndMotionPath.mWidth;
496         end.bottom = end.top + mEndMotionPath.mHeight;
497         for (MotionKey key : mKeyList) {
498             if (key instanceof MotionKeyPosition) {
499                 if (((MotionKeyPosition) key).intersects(layoutWidth,
500                         layoutHeight, start, end, x, y)) {
501                     return (MotionKeyPosition) key;
502                 }
503             }
504         }
505         return null;
506     }
507 
508     // @TODO: add description
buildKeyFrames(float[] keyFrames, int[] mode, int[] pos)509     public int buildKeyFrames(float[] keyFrames, int[] mode, int[] pos) {
510         if (keyFrames != null) {
511             int count = 0;
512             double[] time = mSpline[0].getTimePoints();
513             if (mode != null) {
514                 for (MotionPaths keyFrame : mMotionPaths) {
515                     mode[count++] = keyFrame.mMode;
516                 }
517                 count = 0;
518             }
519             if (pos != null) {
520                 for (MotionPaths keyFrame : mMotionPaths) {
521                     pos[count++] = (int) (100 * keyFrame.mPosition);
522                 }
523                 count = 0;
524             }
525             for (int i = 0; i < time.length; i++) {
526                 mSpline[0].getPos(time[i], mInterpolateData);
527                 mStartMotionPath.getCenter(time[i],
528                         mInterpolateVariables, mInterpolateData, keyFrames, count);
529                 count += 2;
530             }
531             return count / 2;
532         }
533         return 0;
534     }
535 
buildKeyBounds(float[] keyBounds, int[] mode)536     int buildKeyBounds(float[] keyBounds, int[] mode) {
537         if (keyBounds != null) {
538             int count = 0;
539             double[] time = mSpline[0].getTimePoints();
540             if (mode != null) {
541                 for (MotionPaths keyFrame : mMotionPaths) {
542                     mode[count++] = keyFrame.mMode;
543                 }
544                 count = 0;
545             }
546 
547             for (int i = 0; i < time.length; i++) {
548                 mSpline[0].getPos(time[i], mInterpolateData);
549                 mStartMotionPath.getBounds(mInterpolateVariables,
550                         mInterpolateData, keyBounds, count);
551                 count += 2;
552             }
553             return count / 2;
554         }
555         return 0;
556     }
557 
558     String[] mAttributeTable;
559 
getAttributeValues(String attributeType, float[] points, int pointCount)560     int getAttributeValues(String attributeType, float[] points, int pointCount) {
561         @SuppressWarnings("unused") float mils = 1.0f / (pointCount - 1);
562         SplineSet spline = mAttributesMap.get(attributeType);
563         if (spline == null) {
564             return -1;
565         }
566         for (int j = 0; j < points.length; j++) {
567             points[j] = spline.get(j / (points.length - 1));
568         }
569         return points.length;
570     }
571 
572     // @TODO: add description
buildRect(float p, float[] path, int offset)573     public void buildRect(float p, float[] path, int offset) {
574         p = getAdjustedPosition(p, null);
575         mSpline[0].getPos(p, mInterpolateData);
576         mStartMotionPath.getRect(mInterpolateVariables, mInterpolateData, path, offset);
577     }
578 
buildRectangles(float[] path, int pointCount)579     void buildRectangles(float[] path, int pointCount) {
580         float mils = 1.0f / (pointCount - 1);
581         for (int i = 0; i < pointCount; i++) {
582             float position = i * mils;
583             position = getAdjustedPosition(position, null);
584             mSpline[0].getPos(position, mInterpolateData);
585             mStartMotionPath.getRect(mInterpolateVariables, mInterpolateData, path, i * 8);
586         }
587     }
588 
getKeyFrameParameter(int type, float x, float y)589     float getKeyFrameParameter(int type, float x, float y) {
590 
591         float dx = mEndMotionPath.mX - mStartMotionPath.mX;
592         float dy = mEndMotionPath.mY - mStartMotionPath.mY;
593         float startCenterX = mStartMotionPath.mX + mStartMotionPath.mWidth / 2;
594         float startCenterY = mStartMotionPath.mY + mStartMotionPath.mHeight / 2;
595         float hypotenuse = (float) Math.hypot(dx, dy);
596         if (hypotenuse < 0.0000001) {
597             return Float.NaN;
598         }
599 
600         float vx = x - startCenterX;
601         float vy = y - startCenterY;
602         float distFromStart = (float) Math.hypot(vx, vy);
603         if (distFromStart == 0) {
604             return 0;
605         }
606         float pathDistance = (vx * dx + vy * dy);
607 
608         switch (type) {
609             case PATH_PERCENT:
610                 return pathDistance / hypotenuse;
611             case PATH_PERPENDICULAR:
612                 return (float) Math.sqrt(hypotenuse * hypotenuse - pathDistance * pathDistance);
613             case HORIZONTAL_PATH_X:
614                 return vx / dx;
615             case HORIZONTAL_PATH_Y:
616                 return vy / dx;
617             case VERTICAL_PATH_X:
618                 return vx / dy;
619             case VERTICAL_PATH_Y:
620                 return vy / dy;
621         }
622         return 0;
623     }
624 
insertKey(MotionPaths point)625     private void insertKey(MotionPaths point) {
626         MotionPaths redundant = null;
627         for (MotionPaths p : mMotionPaths) {
628             if (point.mPosition == p.mPosition) {
629                 redundant = p;
630             }
631         }
632         if (redundant != null) {
633             mMotionPaths.remove(redundant);
634         }
635         int pos = Collections.binarySearch(mMotionPaths, point);
636         if (pos == 0) {
637             Utils.loge(TAG, " KeyPath position \"" + point.mPosition + "\" outside of range");
638         }
639         mMotionPaths.add(-pos - 1, point);
640     }
641 
addKeys(ArrayList<MotionKey> list)642     void addKeys(ArrayList<MotionKey> list) {
643         mKeyList.addAll(list);
644         if (DEBUG) {
645             for (MotionKey key : mKeyList) {
646                 Utils.log(TAG, " ################ set = " + key.getClass().getSimpleName());
647             }
648         }
649     }
650 
651     // @TODO: add description
addKey(MotionKey key)652     public void addKey(MotionKey key) {
653         mKeyList.add(key);
654         if (DEBUG) {
655             Utils.log(TAG, " ################ addKey = " + key.getClass().getSimpleName());
656         }
657     }
658 
setPathMotionArc(int arc)659     public void setPathMotionArc(int arc) {
660         mPathMotionArc = arc;
661     }
662 
663     /**
664      * Called after all TimePoints & Cycles have been added;
665      * Spines are evaluated
666      */
setup(int parentWidth, int parentHeight, float transitionDuration, long currentTime)667     public void setup(int parentWidth,
668             int parentHeight,
669             float transitionDuration,
670             long currentTime) {
671         @SuppressWarnings({"unused", "ModifiedButNotUsed"})
672         HashSet<String> springAttributes = new HashSet<>();
673         // attributes we need to interpolate
674         HashSet<String> timeCycleAttributes = new HashSet<>(); // attributes we need to interpolate
675         HashSet<String> splineAttributes = new HashSet<>(); // attributes we need to interpolate
676         HashSet<String> cycleAttributes = new HashSet<>(); // attributes we need to oscillate
677         HashMap<String, Integer> interpolation = new HashMap<>();
678         ArrayList<MotionKeyTrigger> triggerList = null;
679 
680         setupRelative();
681         if (DEBUG) {
682             if (mKeyList == null) {
683                 Utils.log(TAG, ">>>>>>>>>>>>>>> mKeyList==null");
684 
685             } else {
686                 Utils.log(TAG, ">>>>>>>>>>>>>>> mKeyList for " + mView.getName());
687 
688             }
689         }
690 
691         if (mPathMotionArc != UNSET && mStartMotionPath.mPathMotionArc == UNSET) {
692             mStartMotionPath.mPathMotionArc = mPathMotionArc;
693         }
694 
695         mStartPoint.different(mEndPoint, splineAttributes);
696         if (DEBUG) {
697             HashSet<String> attr = new HashSet<>();
698             mStartPoint.different(mEndPoint, attr);
699             Utils.log(TAG, ">>>>>>>>>>>>>>> MotionConstrainedPoint found "
700                     + Arrays.toString(attr.toArray()));
701         }
702         if (mKeyList != null) {
703             for (MotionKey key : mKeyList) {
704                 if (key instanceof MotionKeyPosition) {
705                     MotionKeyPosition keyPath = (MotionKeyPosition) key;
706                     insertKey(new MotionPaths(parentWidth, parentHeight,
707                             keyPath, mStartMotionPath, mEndMotionPath));
708                     if (keyPath.mCurveFit != UNSET) {
709                         mCurveFitType = keyPath.mCurveFit;
710                     }
711                 } else if (key instanceof MotionKeyCycle) {
712                     key.getAttributeNames(cycleAttributes);
713                 } else if (key instanceof MotionKeyTimeCycle) {
714                     key.getAttributeNames(timeCycleAttributes);
715                 } else if (key instanceof MotionKeyTrigger) {
716                     if (triggerList == null) {
717                         triggerList = new ArrayList<>();
718                     }
719                     triggerList.add((MotionKeyTrigger) key);
720                 } else {
721                     key.setInterpolation(interpolation);
722                     key.getAttributeNames(splineAttributes);
723                 }
724             }
725         }
726 
727         //--------------------------- trigger support --------------------
728 
729         if (triggerList != null) {
730             mKeyTriggers = triggerList.toArray(new MotionKeyTrigger[0]);
731         }
732 
733         //--------------------------- splines support --------------------
734         if (!splineAttributes.isEmpty()) {
735             mAttributesMap = new HashMap<>();
736             for (String attribute : splineAttributes) {
737                 SplineSet splineSets;
738                 if (attribute.startsWith("CUSTOM,")) {
739                     KeyFrameArray.CustomVar attrList = new KeyFrameArray.CustomVar();
740                     String customAttributeName = attribute.split(",")[1];
741                     for (MotionKey key : mKeyList) {
742                         if (key.mCustom == null) {
743                             continue;
744                         }
745                         CustomVariable customAttribute = key.mCustom.get(customAttributeName);
746                         if (customAttribute != null) {
747                             attrList.append(key.mFramePosition, customAttribute);
748                         }
749                     }
750                     splineSets = SplineSet.makeCustomSplineSet(attribute, attrList);
751                 } else {
752                     splineSets = SplineSet.makeSpline(attribute, currentTime);
753                 }
754                 if (splineSets == null) {
755                     continue;
756                 }
757                 splineSets.setType(attribute);
758                 mAttributesMap.put(attribute, splineSets);
759             }
760             if (mKeyList != null) {
761                 for (MotionKey key : mKeyList) {
762                     if ((key instanceof MotionKeyAttributes)) {
763                         key.addValues(mAttributesMap);
764                     }
765                 }
766             }
767             mStartPoint.addValues(mAttributesMap, 0);
768             mEndPoint.addValues(mAttributesMap, 100);
769 
770             for (String spline : mAttributesMap.keySet()) {
771                 int curve = CurveFit.SPLINE; // default is SPLINE
772                 if (interpolation.containsKey(spline)) {
773                     Integer boxedCurve = interpolation.get(spline);
774                     if (boxedCurve != null) {
775                         curve = boxedCurve;
776                     }
777                 }
778                 SplineSet splineSet = mAttributesMap.get(spline);
779                 if (splineSet != null) {
780                     splineSet.setup(curve);
781                 }
782             }
783         }
784 
785         //--------------------------- timeCycle support --------------------
786         if (!timeCycleAttributes.isEmpty()) {
787             if (mTimeCycleAttributesMap == null) {
788                 mTimeCycleAttributesMap = new HashMap<>();
789             }
790             for (String attribute : timeCycleAttributes) {
791                 if (mTimeCycleAttributesMap.containsKey(attribute)) {
792                     continue;
793                 }
794 
795                 SplineSet splineSets = null;
796                 if (attribute.startsWith("CUSTOM,")) {
797                     KeyFrameArray.CustomVar attrList = new KeyFrameArray.CustomVar();
798                     String customAttributeName = attribute.split(",")[1];
799                     for (MotionKey key : mKeyList) {
800                         if (key.mCustom == null) {
801                             continue;
802                         }
803                         CustomVariable customAttribute = key.mCustom.get(customAttributeName);
804                         if (customAttribute != null) {
805                             attrList.append(key.mFramePosition, customAttribute);
806                         }
807                     }
808                     splineSets = SplineSet.makeCustomSplineSet(attribute, attrList);
809                 } else {
810                     splineSets = SplineSet.makeSpline(attribute, currentTime);
811                 }
812                 if (splineSets == null) {
813                     continue;
814                 }
815                 splineSets.setType(attribute);
816 //                mTimeCycleAttributesMap.put(attribute, splineSets);
817             }
818 
819             if (mKeyList != null) {
820                 for (MotionKey key : mKeyList) {
821                     if (key instanceof MotionKeyTimeCycle) {
822                         ((MotionKeyTimeCycle) key).addTimeValues(mTimeCycleAttributesMap);
823                     }
824                 }
825             }
826 
827             for (String spline : mTimeCycleAttributesMap.keySet()) {
828                 int curve = CurveFit.SPLINE; // default is SPLINE
829                 if (interpolation.containsKey(spline)) {
830                     curve = interpolation.get(spline);
831                 }
832                 mTimeCycleAttributesMap.get(spline).setup(curve);
833             }
834         }
835 
836         //--------------------------------- end new key frame 2
837 
838         MotionPaths[] points = new MotionPaths[2 + mMotionPaths.size()];
839         int count = 1;
840         points[0] = mStartMotionPath;
841         points[points.length - 1] = mEndMotionPath;
842         if (mMotionPaths.size() > 0 && mCurveFitType == MotionKey.UNSET) {
843             mCurveFitType = CurveFit.SPLINE;
844         }
845         for (MotionPaths point : mMotionPaths) {
846             points[count++] = point;
847         }
848 
849         // -----  setup custom attributes which must be in the start and end constraint sets
850         int variables = 18;
851         HashSet<String> attributeNameSet = new HashSet<>();
852         for (String s : mEndMotionPath.mCustomAttributes.keySet()) {
853             if (mStartMotionPath.mCustomAttributes.containsKey(s)) {
854                 if (!splineAttributes.contains("CUSTOM," + s)) {
855                     attributeNameSet.add(s);
856                 }
857             }
858         }
859 
860         mAttributeNames = attributeNameSet.toArray(new String[0]);
861         mAttributeInterpolatorCount = new int[mAttributeNames.length];
862         for (int i = 0; i < mAttributeNames.length; i++) {
863             String attributeName = mAttributeNames[i];
864             mAttributeInterpolatorCount[i] = 0;
865             for (int j = 0; j < points.length; j++) {
866                 if (points[j].mCustomAttributes.containsKey(attributeName)) {
867                     CustomVariable attribute = points[j].mCustomAttributes.get(attributeName);
868                     if (attribute != null) {
869                         mAttributeInterpolatorCount[i] += attribute.numberOfInterpolatedValues();
870                         break;
871                     }
872                 }
873             }
874         }
875         boolean arcMode = points[0].mPathMotionArc != UNSET;
876         boolean[] mask = new boolean[variables + mAttributeNames.length]; // defaults to false
877         for (int i = 1; i < points.length; i++) {
878             points[i].different(points[i - 1], mask, mAttributeNames, arcMode);
879         }
880 
881         count = 0;
882         for (int i = 1; i < mask.length; i++) {
883             if (mask[i]) {
884                 count++;
885             }
886         }
887 
888         mInterpolateVariables = new int[count];
889         int varLen = Math.max(2, count);
890         mInterpolateData = new double[varLen];
891         mInterpolateVelocity = new double[varLen];
892 
893         count = 0;
894         for (int i = 1; i < mask.length; i++) {
895             if (mask[i]) {
896                 mInterpolateVariables[count++] = i;
897             }
898         }
899 
900         double[][] splineData = new double[points.length][mInterpolateVariables.length];
901         double[] timePoint = new double[points.length];
902 
903         for (int i = 0; i < points.length; i++) {
904             points[i].fillStandard(splineData[i], mInterpolateVariables);
905             timePoint[i] = points[i].mTime;
906         }
907 
908         for (int j = 0; j < mInterpolateVariables.length; j++) {
909             int interpolateVariable = mInterpolateVariables[j];
910             if (interpolateVariable < MotionPaths.sNames.length) {
911                 @SuppressWarnings("unused") String s =
912                         MotionPaths.sNames[mInterpolateVariables[j]] + " [";
913                 for (int i = 0; i < points.length; i++) {
914                     s += splineData[i][j];
915                 }
916             }
917         }
918         mSpline = new CurveFit[1 + mAttributeNames.length];
919 
920         for (int i = 0; i < mAttributeNames.length; i++) {
921             int pointCount = 0;
922             double[][] splinePoints = null;
923             double[] timePoints = null;
924             String name = mAttributeNames[i];
925 
926             for (int j = 0; j < points.length; j++) {
927                 if (points[j].hasCustomData(name)) {
928                     if (splinePoints == null) {
929                         timePoints = new double[points.length];
930                         splinePoints =
931                                 new double[points.length][points[j].getCustomDataCount(name)];
932                     }
933                     timePoints[pointCount] = points[j].mTime;
934                     points[j].getCustomData(name, splinePoints[pointCount], 0);
935                     pointCount++;
936                 }
937             }
938             timePoints = Arrays.copyOf(timePoints, pointCount);
939             splinePoints = Arrays.copyOf(splinePoints, pointCount);
940             mSpline[i + 1] = CurveFit.get(mCurveFitType, timePoints, splinePoints);
941         }
942 
943         // Spline for positions
944         mSpline[0] = CurveFit.get(mCurveFitType, timePoint, splineData);
945         // --------------------------- SUPPORT ARC MODE --------------
946         if (points[0].mPathMotionArc != UNSET) {
947             int size = points.length;
948             int[] mode = new int[size];
949             double[] time = new double[size];
950             double[][] values = new double[size][2];
951             for (int i = 0; i < size; i++) {
952                 mode[i] = points[i].mPathMotionArc;
953                 time[i] = points[i].mTime;
954                 values[i][0] = points[i].mX;
955                 values[i][1] = points[i].mY;
956             }
957 
958             mArcSpline = CurveFit.getArc(mode, time, values);
959         }
960 
961         //--------------------------- Cycle support --------------------
962         float distance = Float.NaN;
963         mCycleMap = new HashMap<>();
964         if (mKeyList != null) {
965             for (String attribute : cycleAttributes) {
966                 KeyCycleOscillator cycle = KeyCycleOscillator.makeWidgetCycle(attribute);
967                 if (cycle == null) {
968                     continue;
969                 }
970 
971                 if (cycle.variesByPath()) {
972                     if (Float.isNaN(distance)) {
973                         distance = getPreCycleDistance();
974                     }
975                 }
976                 cycle.setType(attribute);
977                 mCycleMap.put(attribute, cycle);
978             }
979             for (MotionKey key : mKeyList) {
980                 if (key instanceof MotionKeyCycle) {
981                     ((MotionKeyCycle) key).addCycleValues(mCycleMap);
982                 }
983             }
984             for (KeyCycleOscillator cycle : mCycleMap.values()) {
985                 cycle.setup(distance);
986             }
987         }
988 
989         if (DEBUG) {
990             Utils.log(TAG, "Animation of splineAttributes "
991                     + Arrays.toString(splineAttributes.toArray()));
992             Utils.log(TAG, "Animation of cycle " + Arrays.toString(mCycleMap.keySet().toArray()));
993             if (mAttributesMap != null) {
994                 Utils.log(TAG, " splines = " + Arrays.toString(mAttributesMap.keySet().toArray()));
995                 for (String s : mAttributesMap.keySet()) {
996                     Utils.log(TAG, s + " = " + mAttributesMap.get(s));
997                 }
998             }
999             Utils.log(TAG, " ---------------------------------------- ");
1000         }
1001 
1002         //--------------------------- end cycle support ----------------
1003     }
1004 
1005     /**
1006      * Debug string
1007      */
1008     @Override
toString()1009     public String toString() {
1010         return " start: x: " + mStartMotionPath.mX + " y: " + mStartMotionPath.mY
1011                 + " end: x: " + mEndMotionPath.mX + " y: " + mEndMotionPath.mY;
1012     }
1013 
readView(MotionPaths motionPaths)1014     private void readView(MotionPaths motionPaths) {
1015         motionPaths.setBounds((int) mView.getX(), (int) mView.getY(),
1016                 mView.getWidth(), mView.getHeight());
1017     }
1018 
setView(MotionWidget view)1019     public void setView(MotionWidget view) {
1020         mView = view;
1021     }
1022 
getView()1023     public MotionWidget getView() {
1024         return mView;
1025     }
1026 
1027     // @TODO: add description
setStart(MotionWidget mw)1028     public void setStart(MotionWidget mw) {
1029         mStartMotionPath.mTime = 0;
1030         mStartMotionPath.mPosition = 0;
1031         mStartMotionPath.setBounds(mw.getX(), mw.getY(), mw.getWidth(), mw.getHeight());
1032         mStartMotionPath.applyParameters(mw);
1033         mStartPoint.setState(mw);
1034         TypedBundle p = mw.getWidgetFrame().getMotionProperties();
1035         if (p != null) {
1036             p.applyDelta(this);
1037         }
1038     }
1039 
1040     // @TODO: add description
setEnd(MotionWidget mw)1041     public void setEnd(MotionWidget mw) {
1042         mEndMotionPath.mTime = 1;
1043         mEndMotionPath.mPosition = 1;
1044         readView(mEndMotionPath);
1045         mEndMotionPath.setBounds(mw.getLeft(), mw.getTop(), mw.getWidth(), mw.getHeight());
1046         mEndMotionPath.applyParameters(mw);
1047         mEndPoint.setState(mw);
1048     }
1049 
1050     // @TODO: add description
setStartState(ViewState rect, MotionWidget v, int rotation, int preWidth, int preHeight)1051     public void setStartState(ViewState rect,
1052             MotionWidget v,
1053             int rotation,
1054             int preWidth,
1055             int preHeight) {
1056         mStartMotionPath.mTime = 0;
1057         mStartMotionPath.mPosition = 0;
1058         int cx, cy;
1059         Rect r = new Rect();
1060         switch (rotation) {
1061             case 2:
1062                 cx = rect.left + rect.right;
1063                 cy = rect.top + rect.bottom;
1064                 r.left = preHeight - (cy + rect.width()) / 2;
1065                 r.top = (cx - rect.height()) / 2;
1066                 r.right = r.left + rect.width();
1067                 r.bottom = r.top + rect.height();
1068                 break;
1069             case 1:
1070                 cx = rect.left + rect.right;
1071                 cy = rect.top + rect.bottom;
1072                 r.left = (cy - rect.width()) / 2;
1073                 r.top = preWidth - (cx + rect.height()) / 2;
1074                 r.right = r.left + rect.width();
1075                 r.bottom = r.top + rect.height();
1076                 break;
1077         }
1078         mStartMotionPath.setBounds(r.left, r.top, r.width(), r.height());
1079         mStartPoint.setState(r, v, rotation, rect.rotation);
1080     }
1081 
rotate(Rect rect, Rect out, int rotation, int preHeight, int preWidth)1082     void rotate(Rect rect, Rect out, int rotation, int preHeight, int preWidth) {
1083         int cx, cy;
1084         switch (rotation) {
1085 
1086             case MotionConstraintSet.ROTATE_PORTRATE_OF_LEFT:
1087                 cx = rect.left + rect.right;
1088                 cy = rect.top + rect.bottom;
1089                 out.left = preHeight - (cy + rect.width()) / 2;
1090                 out.top = (cx - rect.height()) / 2;
1091                 out.right = out.left + rect.width();
1092                 out.bottom = out.top + rect.height();
1093                 break;
1094             case MotionConstraintSet.ROTATE_PORTRATE_OF_RIGHT:
1095                 cx = rect.left + rect.right;
1096                 cy = rect.top + rect.bottom;
1097                 out.left = (cy - rect.width()) / 2;
1098                 out.top = preWidth - (cx + rect.height()) / 2;
1099                 out.right = out.left + rect.width();
1100                 out.bottom = out.top + rect.height();
1101                 break;
1102             case MotionConstraintSet.ROTATE_LEFT_OF_PORTRATE:
1103                 cx = rect.left + rect.right;
1104                 cy = rect.bottom + rect.top;
1105                 out.left = preHeight - (cy + rect.width()) / 2;
1106                 out.top = (cx - rect.height()) / 2;
1107                 out.right = out.left + rect.width();
1108                 out.bottom = out.top + rect.height();
1109                 break;
1110             case MotionConstraintSet.ROTATE_RIGHT_OF_PORTRATE:
1111                 cx = rect.left + rect.right;
1112                 cy = rect.top + rect.bottom;
1113                 out.left = rect.height() / 2 + rect.top - cx / 2;
1114                 out.top = preWidth - (cx + rect.height()) / 2;
1115                 out.right = out.left + rect.width();
1116                 out.bottom = out.top + rect.height();
1117                 break;
1118         }
1119     }
1120 
1121     // Todo : Implement  QuantizeMotion scene rotate
1122     //    void setStartState(Rect cw, ConstraintSet constraintSet,
1123     //                      int parentWidth, int parentHeight) {
1124     //        int rotate = constraintSet.mRotate; // for rotated frames
1125     //        if (rotate != 0) {
1126     //            rotate(cw, mTempRect, rotate, parentWidth, parentHeight);
1127     //        }
1128     //        mStartMotionPath.time = 0;
1129     //        mStartMotionPath.position = 0;
1130     //        readView(mStartMotionPath);
1131     //        mStartMotionPath.setBounds(cw.left, cw.top, cw.width(), cw.height());
1132     //        ConstraintSet.Constraint constraint = constraintSet.getParameters(mId);
1133     //        mStartMotionPath.applyParameters(constraint);
1134     //        mMotionStagger = constraint.motion.mMotionStagger;
1135     //        mStartPoint.setState(cw, constraintSet, rotate, mId);
1136     //        mTransformPivotTarget = constraint.transform.transformPivotTarget;
1137     //        mQuantizeMotionSteps = constraint.motion.mQuantizeMotionSteps;
1138     //        mQuantizeMotionPhase = constraint.motion.mQuantizeMotionPhase;
1139     //        mQuantizeMotionInterpolator = getInterpolator(mView.getContext(),
1140     //                constraint.motion.mQuantizeInterpolatorType,
1141     //                constraint.motion.mQuantizeInterpolatorString,
1142     //                constraint.motion.mQuantizeInterpolatorID
1143     //        );
1144     //    }
1145 
1146     static final int EASE_IN_OUT = 0;
1147     static final int EASE_IN = 1;
1148     static final int EASE_OUT = 2;
1149     static final int LINEAR = 3;
1150     static final int BOUNCE = 4;
1151     static final int OVERSHOOT = 5;
1152     private static final int SPLINE_STRING = -1;
1153     @SuppressWarnings("unused") private static final int INTERPOLATOR_REFERENCE_ID = -2;
1154     @SuppressWarnings("unused") private static final int INTERPOLATOR_UNDEFINED = -3;
1155 
getInterpolator(int type, String interpolatorString, @SuppressWarnings("unused") int id)1156     private static DifferentialInterpolator getInterpolator(int type,
1157             String interpolatorString,
1158             @SuppressWarnings("unused") int id) {
1159         switch (type) {
1160             case SPLINE_STRING:
1161                 final Easing easing = Easing.getInterpolator(interpolatorString);
1162                 return new DifferentialInterpolator() {
1163                     float mX;
1164 
1165                     @Override
1166                     public float getInterpolation(float x) {
1167                         mX = x;
1168                         return (float) easing.get(x);
1169                     }
1170 
1171                     @Override
1172                     public float getVelocity() {
1173                         return (float) easing.getDiff(mX);
1174                     }
1175                 };
1176 
1177         }
1178         return null;
1179     }
1180 
1181 //    void setEndState(Rect cw, ConstraintSet constraintSet, int parentWidth, int parentHeight) {
1182 //        int rotate = constraintSet.mRotate; // for rotated frames
1183 //        if (rotate != 0) {
1184 //            rotate(cw, mTempRect, rotate, parentWidth, parentHeight);
1185 //            cw = mTempRect;
1186 //        }
1187 //        mEndMotionPath.time = 1;
1188 //        mEndMotionPath.position = 1;
1189 //        readView(mEndMotionPath);
1190 //        mEndMotionPath.setBounds(cw.left, cw.top, cw.width(), cw.height());
1191 //        mEndMotionPath.applyParameters(constraintSet.getParameters(mId));
1192 //        mEndPoint.setState(cw, constraintSet, rotate, mId);
1193 //    }
1194 
setBothStates(MotionWidget v)1195     void setBothStates(MotionWidget v) {
1196         mStartMotionPath.mTime = 0;
1197         mStartMotionPath.mPosition = 0;
1198         mNoMovement = true;
1199         mStartMotionPath.setBounds(v.getX(), v.getY(), v.getWidth(), v.getHeight());
1200         mEndMotionPath.setBounds(v.getX(), v.getY(), v.getWidth(), v.getHeight());
1201         mStartPoint.setState(v);
1202         mEndPoint.setState(v);
1203     }
1204 
1205     /**
1206      * Calculates the adjusted position (and optional velocity).
1207      * Note if requesting velocity staggering is not considered
1208      *
1209      * @param position position pre stagger
1210      * @param velocity return velocity
1211      * @return actual position accounting for easing and staggering
1212      */
getAdjustedPosition(float position, float[] velocity)1213     private float getAdjustedPosition(float position, float[] velocity) {
1214         if (velocity != null) {
1215             velocity[0] = 1;
1216         } else if (mStaggerScale != 1.0) {
1217             if (position < mStaggerOffset) {
1218                 position = 0;
1219             }
1220             if (position > mStaggerOffset && position < 1.0) {
1221                 position -= mStaggerOffset;
1222                 position *= mStaggerScale;
1223                 position = Math.min(position, 1.0f);
1224             }
1225         }
1226 
1227         // adjust the position based on the easing curve
1228         float adjusted = position;
1229         Easing easing = mStartMotionPath.mKeyFrameEasing;
1230         float start = 0;
1231         float end = Float.NaN;
1232         for (MotionPaths frame : mMotionPaths) {
1233             if (frame.mKeyFrameEasing != null) { // this frame has an easing
1234                 if (frame.mTime < position) {  // frame with easing is before the current pos
1235                     easing = frame.mKeyFrameEasing; // this is the candidate
1236                     start = frame.mTime; // this is also the starting time
1237                 } else { // frame with easing is past the pos
1238                     if (Float.isNaN(end)) { // we never ended the time line
1239                         end = frame.mTime;
1240                     }
1241                 }
1242             }
1243         }
1244 
1245         if (easing != null) {
1246             if (Float.isNaN(end)) {
1247                 end = 1.0f;
1248             }
1249             float offset = (position - start) / (end - start);
1250             float new_offset = (float) easing.get(offset);
1251             adjusted = new_offset * (end - start) + start;
1252             if (velocity != null) {
1253                 velocity[0] = (float) easing.getDiff(offset);
1254             }
1255         }
1256         return adjusted;
1257     }
1258 
endTrigger(boolean start)1259     void endTrigger(boolean start) {
1260 //        if ("button".equals(Debug.getName(mView)))
1261 //            if (mKeyTriggers != null) {
1262 //                for (int i = 0; i < mKeyTriggers.length; i++) {
1263 //                    mKeyTriggers[i].conditionallyFire(start ? -100 : 100, mView);
1264 //                }
1265 //            }
1266     }
1267     //##############################################################################################
1268     //$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%
1269     //$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%
1270     //$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%
1271     //$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%
1272     //##############################################################################################
1273 
1274     /**
1275      * The main driver of interpolation
1276      *
1277      * @return do you need to keep animating
1278      */
interpolate(MotionWidget child, float globalPosition, long time, KeyCache keyCache)1279     public boolean interpolate(MotionWidget child,
1280             float globalPosition,
1281             long time,
1282             KeyCache keyCache) {
1283         @SuppressWarnings("unused")  boolean timeAnimation = false;
1284         float position = getAdjustedPosition(globalPosition, null);
1285         // This quantize the position into steps e.g. 4 steps = 0-0.25,0.25-0.50 etc
1286         if (mQuantizeMotionSteps != UNSET) {
1287             @SuppressWarnings("unused") float pin = position;
1288             float steps = 1.0f / mQuantizeMotionSteps; // the length of a step
1289             float jump = (float) Math.floor(position / steps) * steps; // step jumps
1290             float section = (position % steps) / steps; // float from 0 to 1 in a step
1291 
1292             if (!Float.isNaN(mQuantizeMotionPhase)) {
1293                 section = (section + mQuantizeMotionPhase) % 1;
1294             }
1295             if (mQuantizeMotionInterpolator != null) {
1296                 section = mQuantizeMotionInterpolator.getInterpolation(section);
1297             } else {
1298                 section = section > 0.5 ? 1 : 0;
1299             }
1300             position = section * steps + jump;
1301         }
1302         // MotionKeyTimeCycle.PathRotate timePathRotate = null;
1303         if (mAttributesMap != null) {
1304             for (SplineSet aSpline : mAttributesMap.values()) {
1305                 aSpline.setProperty(child, position);
1306             }
1307         }
1308 
1309         //       TODO add KeyTimeCycle
1310         //        if (mTimeCycleAttributesMap != null) {
1311         //            for (ViewTimeCycle aSpline : mTimeCycleAttributesMap.values()) {
1312         //                if (aSpline instanceof ViewTimeCycle.PathRotate) {
1313         //                    timePathRotate = (ViewTimeCycle.PathRotate) aSpline;
1314         //                    continue;
1315         //                }
1316         //                timeAnimation |= aSpline.setProperty(child, position, time, keyCache);
1317         //            }
1318         //        }
1319 
1320         if (mSpline != null) {
1321             mSpline[0].getPos(position, mInterpolateData);
1322             mSpline[0].getSlope(position, mInterpolateVelocity);
1323             if (mArcSpline != null) {
1324                 if (mInterpolateData.length > 0) {
1325                     mArcSpline.getPos(position, mInterpolateData);
1326                     mArcSpline.getSlope(position, mInterpolateVelocity);
1327                 }
1328             }
1329 
1330             if (!mNoMovement) {
1331                 mStartMotionPath.setView(position, child,
1332                         mInterpolateVariables, mInterpolateData, mInterpolateVelocity, null);
1333             }
1334             if (mTransformPivotTarget != UNSET) {
1335                 if (mTransformPivotView == null) {
1336                     MotionWidget layout = (MotionWidget) child.getParent();
1337                     mTransformPivotView = layout.findViewById(mTransformPivotTarget);
1338                 }
1339                 if (mTransformPivotView != null) {
1340                     float cy =
1341                             (mTransformPivotView.getTop() + mTransformPivotView.getBottom()) / 2.0f;
1342                     float cx =
1343                             (mTransformPivotView.getLeft() + mTransformPivotView.getRight()) / 2.0f;
1344                     if (child.getRight() - child.getLeft() > 0
1345                             && child.getBottom() - child.getTop() > 0) {
1346                         float px = (cx - child.getLeft());
1347                         float py = (cy - child.getTop());
1348                         child.setPivotX(px);
1349                         child.setPivotY(py);
1350                     }
1351                 }
1352             }
1353 
1354             //       TODO add support for path rotate
1355             //            if (mAttributesMap != null) {
1356             //                for (SplineSet aSpline : mAttributesMap.values()) {
1357             //                    if (aSpline instanceof ViewSpline.PathRotate
1358             //                          && mInterpolateVelocity.length > 1)
1359             //                        ((ViewSpline.PathRotate) aSpline).setPathRotate(child,
1360             //                        position, mInterpolateVelocity[0], mInterpolateVelocity[1]);
1361             //                }
1362             //
1363             //            }
1364             //            if (timePathRotate != null) {
1365             //                timeAnimation |= timePathRotate.setPathRotate(child, keyCache,
1366             //                  position, time, mInterpolateVelocity[0], mInterpolateVelocity[1]);
1367             //            }
1368 
1369             for (int i = 1; i < mSpline.length; i++) {
1370                 CurveFit spline = mSpline[i];
1371                 spline.getPos(position, mValuesBuff);
1372                 //interpolated here
1373                 mStartMotionPath.mCustomAttributes
1374                         .get(mAttributeNames[i - 1])
1375                         .setInterpolatedValue(child, mValuesBuff);
1376             }
1377             if (mStartPoint.mVisibilityMode == MotionWidget.VISIBILITY_MODE_NORMAL) {
1378                 if (position <= 0.0f) {
1379                     child.setVisibility(mStartPoint.mVisibility);
1380                 } else if (position >= 1.0f) {
1381                     child.setVisibility(mEndPoint.mVisibility);
1382                 } else if (mEndPoint.mVisibility != mStartPoint.mVisibility) {
1383                     child.setVisibility(MotionWidget.VISIBLE);
1384                 }
1385             }
1386 
1387             if (mKeyTriggers != null) {
1388                 for (int i = 0; i < mKeyTriggers.length; i++) {
1389                     mKeyTriggers[i].conditionallyFire(position, child);
1390                 }
1391             }
1392         } else {
1393             // do the interpolation
1394 
1395             float float_l =
1396                     (mStartMotionPath.mX + (mEndMotionPath.mX - mStartMotionPath.mX) * position);
1397             float float_t =
1398                     (mStartMotionPath.mY + (mEndMotionPath.mY - mStartMotionPath.mY) * position);
1399             float float_width = (mStartMotionPath.mWidth
1400                     + (mEndMotionPath.mWidth - mStartMotionPath.mWidth) * position);
1401             float float_height = (mStartMotionPath.mHeight
1402                     + (mEndMotionPath.mHeight - mStartMotionPath.mHeight) * position);
1403             int l = (int) (0.5f + float_l);
1404             int t = (int) (0.5f + float_t);
1405             int r = (int) (0.5f + float_l + float_width);
1406             int b = (int) (0.5f + float_t + float_height);
1407             int width = r - l;
1408             int height = b - t;
1409 
1410             if (FAVOR_FIXED_SIZE_VIEWS) {
1411                 l = (int) (mStartMotionPath.mX
1412                         + (mEndMotionPath.mX - mStartMotionPath.mX) * position);
1413                 t = (int) (mStartMotionPath.mY
1414                         + (mEndMotionPath.mY - mStartMotionPath.mY) * position);
1415                 width = (int) (mStartMotionPath.mWidth
1416                         + (mEndMotionPath.mWidth - mStartMotionPath.mWidth) * position);
1417                 height = (int) (mStartMotionPath.mHeight
1418                         + (mEndMotionPath.mHeight - mStartMotionPath.mHeight) * position);
1419                 r = l + width;
1420                 b = t + height;
1421             }
1422             // widget is responsible to call measure
1423             child.layout(l, t, r, b);
1424         }
1425 
1426         // TODO add pathRotate KeyCycles
1427         if (mCycleMap != null) {
1428             for (KeyCycleOscillator osc : mCycleMap.values()) {
1429                 if (osc instanceof KeyCycleOscillator.PathRotateSet) {
1430                     ((KeyCycleOscillator.PathRotateSet) osc).setPathRotate(child, position,
1431                             mInterpolateVelocity[0], mInterpolateVelocity[1]);
1432                 } else {
1433                     osc.setProperty(child, position);
1434                 }
1435             }
1436         }
1437         //   When we support TimeCycle return true if repaint is needed
1438         //        return timeAnimation;
1439         return false;
1440     }
1441 
1442     /**
1443      * This returns the differential with respect to the animation layout position (Progress)
1444      * of a point on the view (post layout effects are not computed)
1445      *
1446      * @param position    position in time
1447      * @param locationX   the x location on the view (0 = left edge, 1 = right edge)
1448      * @param locationY   the y location on the view (0 = top, 1 = bottom)
1449      * @param mAnchorDpDt returns the differential of the motion with respect to the position
1450      */
getDpDt(float position, float locationX, float locationY, float[] mAnchorDpDt)1451     public void getDpDt(float position, float locationX, float locationY, float[] mAnchorDpDt) {
1452         position = getAdjustedPosition(position, mVelocity);
1453 
1454         if (mSpline != null) {
1455             mSpline[0].getSlope(position, mInterpolateVelocity);
1456             mSpline[0].getPos(position, mInterpolateData);
1457             float v = mVelocity[0];
1458             for (int i = 0; i < mInterpolateVelocity.length; i++) {
1459                 mInterpolateVelocity[i] *= v;
1460             }
1461 
1462             if (mArcSpline != null) {
1463                 if (mInterpolateData.length > 0) {
1464                     mArcSpline.getPos(position, mInterpolateData);
1465                     mArcSpline.getSlope(position, mInterpolateVelocity);
1466                     mStartMotionPath.setDpDt(locationX, locationY, mAnchorDpDt,
1467                             mInterpolateVariables, mInterpolateVelocity, mInterpolateData);
1468                 }
1469                 return;
1470             }
1471             mStartMotionPath.setDpDt(locationX, locationY, mAnchorDpDt,
1472                     mInterpolateVariables, mInterpolateVelocity, mInterpolateData);
1473             return;
1474         }
1475         // do the interpolation
1476         float dleft = (mEndMotionPath.mX - mStartMotionPath.mX);
1477         float dTop = (mEndMotionPath.mY - mStartMotionPath.mY);
1478         float dWidth = (mEndMotionPath.mWidth - mStartMotionPath.mWidth);
1479         float dHeight = (mEndMotionPath.mHeight - mStartMotionPath.mHeight);
1480         float dRight = dleft + dWidth;
1481         float dBottom = dTop + dHeight;
1482         mAnchorDpDt[0] = dleft * (1 - locationX) + dRight * locationX;
1483         mAnchorDpDt[1] = dTop * (1 - locationY) + dBottom * locationY;
1484     }
1485 
1486     /**
1487      * This returns the differential with respect to the animation post layout transform
1488      * of a point on the view
1489      *
1490      * @param position    position in time
1491      * @param width       width of the view
1492      * @param height      height of the view
1493      * @param locationX   the x location on the view (0 = left edge, 1 = right edge)
1494      * @param locationY   the y location on the view (0 = top, 1 = bottom)
1495      * @param mAnchorDpDt returns the differential of the motion with respect to the position
1496      */
getPostLayoutDvDp(float position, int width, int height, float locationX, float locationY, float[] mAnchorDpDt)1497     void getPostLayoutDvDp(float position,
1498             int width,
1499             int height,
1500             float locationX,
1501             float locationY,
1502             float[] mAnchorDpDt) {
1503         if (DEBUG) {
1504             Utils.log(TAG, " position= " + position
1505                     + " location= " + locationX + " , " + locationY);
1506         }
1507         position = getAdjustedPosition(position, mVelocity);
1508 
1509         SplineSet trans_x =
1510                 (mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.TRANSLATION_X);
1511         SplineSet trans_y =
1512                 (mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.TRANSLATION_Y);
1513         SplineSet rotation =
1514                 (mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.ROTATION);
1515         SplineSet scale_x = (mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.SCALE_X);
1516         SplineSet scale_y = (mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.SCALE_Y);
1517 
1518         KeyCycleOscillator osc_x =
1519                 (mCycleMap == null) ? null : mCycleMap.get(MotionKey.TRANSLATION_X);
1520         KeyCycleOscillator osc_y =
1521                 (mCycleMap == null) ? null : mCycleMap.get(MotionKey.TRANSLATION_Y);
1522         KeyCycleOscillator osc_r = (mCycleMap == null) ? null : mCycleMap.get(MotionKey.ROTATION);
1523         KeyCycleOscillator osc_sx = (mCycleMap == null) ? null : mCycleMap.get(MotionKey.SCALE_X);
1524         KeyCycleOscillator osc_sy = (mCycleMap == null) ? null : mCycleMap.get(MotionKey.SCALE_Y);
1525 
1526         VelocityMatrix vmat = new VelocityMatrix();
1527         vmat.clear();
1528         vmat.setRotationVelocity(rotation, position);
1529         vmat.setTranslationVelocity(trans_x, trans_y, position);
1530         vmat.setScaleVelocity(scale_x, scale_y, position);
1531         vmat.setRotationVelocity(osc_r, position);
1532         vmat.setTranslationVelocity(osc_x, osc_y, position);
1533         vmat.setScaleVelocity(osc_sx, osc_sy, position);
1534         if (mArcSpline != null) {
1535             if (mInterpolateData.length > 0) {
1536                 mArcSpline.getPos(position, mInterpolateData);
1537                 mArcSpline.getSlope(position, mInterpolateVelocity);
1538                 mStartMotionPath.setDpDt(locationX, locationY, mAnchorDpDt,
1539                         mInterpolateVariables, mInterpolateVelocity, mInterpolateData);
1540             }
1541             vmat.applyTransform(locationX, locationY, width, height, mAnchorDpDt);
1542             return;
1543         }
1544         if (mSpline != null) {
1545             position = getAdjustedPosition(position, mVelocity);
1546             mSpline[0].getSlope(position, mInterpolateVelocity);
1547             mSpline[0].getPos(position, mInterpolateData);
1548             float v = mVelocity[0];
1549             for (int i = 0; i < mInterpolateVelocity.length; i++) {
1550                 mInterpolateVelocity[i] *= v;
1551             }
1552             mStartMotionPath.setDpDt(locationX, locationY, mAnchorDpDt,
1553                     mInterpolateVariables, mInterpolateVelocity, mInterpolateData);
1554             vmat.applyTransform(locationX, locationY, width, height, mAnchorDpDt);
1555             return;
1556         }
1557 
1558         // do the interpolation
1559         float dleft = (mEndMotionPath.mX - mStartMotionPath.mX);
1560         float dTop = (mEndMotionPath.mY - mStartMotionPath.mY);
1561         float dWidth = (mEndMotionPath.mWidth - mStartMotionPath.mWidth);
1562         float dHeight = (mEndMotionPath.mHeight - mStartMotionPath.mHeight);
1563         float dRight = dleft + dWidth;
1564         float dBottom = dTop + dHeight;
1565         mAnchorDpDt[0] = dleft * (1 - locationX) + dRight * locationX;
1566         mAnchorDpDt[1] = dTop * (1 - locationY) + dBottom * locationY;
1567 
1568         vmat.clear();
1569         vmat.setRotationVelocity(rotation, position);
1570         vmat.setTranslationVelocity(trans_x, trans_y, position);
1571         vmat.setScaleVelocity(scale_x, scale_y, position);
1572         vmat.setRotationVelocity(osc_r, position);
1573         vmat.setTranslationVelocity(osc_x, osc_y, position);
1574         vmat.setScaleVelocity(osc_sx, osc_sy, position);
1575         vmat.applyTransform(locationX, locationY, width, height, mAnchorDpDt);
1576     }
1577 
1578     // @TODO: add description
getDrawPath()1579     public int getDrawPath() {
1580         int mode = mStartMotionPath.mDrawPath;
1581         for (MotionPaths keyFrame : mMotionPaths) {
1582             mode = Math.max(mode, keyFrame.mDrawPath);
1583         }
1584         mode = Math.max(mode, mEndMotionPath.mDrawPath);
1585         return mode;
1586     }
1587 
setDrawPath(int debugMode)1588     public void setDrawPath(int debugMode) {
1589         mStartMotionPath.mDrawPath = debugMode;
1590     }
1591 
name()1592     String name() {
1593 
1594         return mView.getName();
1595     }
1596 
positionKeyframe(MotionWidget view, MotionKeyPosition key, float x, float y, String[] attribute, float[] value)1597     void positionKeyframe(MotionWidget view,
1598             MotionKeyPosition key,
1599             float x,
1600             float y,
1601             String[] attribute,
1602             float[] value) {
1603         FloatRect start = new FloatRect();
1604         start.left = mStartMotionPath.mX;
1605         start.top = mStartMotionPath.mY;
1606         start.right = start.left + mStartMotionPath.mWidth;
1607         start.bottom = start.top + mStartMotionPath.mHeight;
1608         FloatRect end = new FloatRect();
1609         end.left = mEndMotionPath.mX;
1610         end.top = mEndMotionPath.mY;
1611         end.right = end.left + mEndMotionPath.mWidth;
1612         end.bottom = end.top + mEndMotionPath.mHeight;
1613         key.positionAttributes(view, start, end, x, y, attribute, value);
1614     }
1615 
1616     /**
1617      * Get the keyFrames for the view controlled by this MotionController
1618      *
1619      * @param type is position(0-100) + 1000
1620      *             * mType(1=Attributes, 2=Position, 3=TimeCycle 4=Cycle 5=Trigger
1621      * @param pos  the x&y position of the keyFrame along the path
1622      * @return Number of keyFrames found
1623      */
getKeyFramePositions(int[] type, float[] pos)1624     public int getKeyFramePositions(int[] type, float[] pos) {
1625         int i = 0;
1626         int count = 0;
1627         for (MotionKey key : mKeyList) {
1628             type[i++] = key.mFramePosition + 1000 * key.mType;
1629             float time = key.mFramePosition / 100.0f;
1630             mSpline[0].getPos(time, mInterpolateData);
1631             mStartMotionPath.getCenter(time, mInterpolateVariables, mInterpolateData, pos, count);
1632             count += 2;
1633         }
1634 
1635         return i;
1636     }
1637 
1638     /**
1639      * Gets the keyFrames for the view controlled by this MotionController.
1640      * The info data structure is of the form
1641      * 0 length if your are at index i the [i+len+1] is the next entry
1642      * 1 type  1=Attributes, 2=Position, 3=TimeCycle 4=Cycle 5=Trigger
1643      * 2 position
1644      * 3 x location
1645      * 4 y location
1646      * 5
1647      * ...
1648      * length
1649      *
1650      * @param type if type is -1, skip all keyframes with type != -1
1651      * @param info is a data structure array of int that holds info on each keyframe
1652      * @return Number of keyFrames found
1653      */
getKeyFrameInfo(int type, int[] info)1654     public int getKeyFrameInfo(int type, int[] info) {
1655         int count = 0;
1656         int cursor = 0;
1657         float[] pos = new float[2];
1658         int len;
1659         for (MotionKey key : mKeyList) {
1660             if (key.mType != type && type == -1) {
1661                 continue;
1662             }
1663             len = cursor;
1664             info[cursor] = 0;
1665 
1666             info[++cursor] = key.mType;
1667             info[++cursor] = key.mFramePosition;
1668 
1669             float time = key.mFramePosition / 100.0f;
1670             mSpline[0].getPos(time, mInterpolateData);
1671             mStartMotionPath.getCenter(time, mInterpolateVariables, mInterpolateData, pos, 0);
1672             info[++cursor] = Float.floatToIntBits(pos[0]);
1673             info[++cursor] = Float.floatToIntBits(pos[1]);
1674             if (key instanceof MotionKeyPosition) {
1675                 MotionKeyPosition kp = (MotionKeyPosition) key;
1676                 info[++cursor] = kp.mPositionType;
1677 
1678                 info[++cursor] = Float.floatToIntBits(kp.mPercentX);
1679                 info[++cursor] = Float.floatToIntBits(kp.mPercentY);
1680             }
1681             cursor++;
1682             info[len] = cursor - len;
1683             count++;
1684         }
1685 
1686         return count;
1687     }
1688 
1689     @Override
setValue(int id, int value)1690     public boolean setValue(int id, int value) {
1691         switch (id) {
1692             case TypedValues.PositionType.TYPE_PATH_MOTION_ARC:
1693                 setPathMotionArc(value);
1694                 return true;
1695             case TypedValues.MotionType.TYPE_QUANTIZE_MOTIONSTEPS:
1696                 mQuantizeMotionSteps = value;
1697                 return true;
1698             case TypedValues.TransitionType.TYPE_AUTO_TRANSITION:
1699                 // TODO add support for auto transitions mAutoTransition = value;
1700                 return true;
1701         }
1702         return false;
1703     }
1704 
1705     @Override
setValue(int id, float value)1706     public boolean setValue(int id, float value) {
1707         if (MotionType.TYPE_QUANTIZE_MOTION_PHASE == id) {
1708             mQuantizeMotionPhase = value;
1709             return true;
1710         }
1711         if (MotionType.TYPE_STAGGER == id) {
1712             mMotionStagger = value;
1713             return true;
1714         }
1715         return false;
1716     }
1717 
1718     @Override
setValue(int id, String value)1719     public boolean setValue(int id, String value) {
1720         if (TransitionType.TYPE_INTERPOLATOR == id
1721                 || MotionType.TYPE_QUANTIZE_INTERPOLATOR_TYPE == id) {
1722             mQuantizeMotionInterpolator = getInterpolator(SPLINE_STRING, value, 0);
1723             return true;
1724         }
1725         if (MotionType.TYPE_ANIMATE_RELATIVE_TO == id) {
1726             mStartMotionPath.mAnimateRelativeTo = value;
1727             return true;
1728         }
1729         return false;
1730     }
1731 
1732     @Override
setValue(int id, boolean value)1733     public boolean setValue(int id, boolean value) {
1734         return false;
1735     }
1736 
1737     @Override
getId(String name)1738     public int getId(String name) {
1739         return 0;
1740     }
1741 
1742     /**
1743      * Set stagger scale
1744      */
setStaggerScale(float staggerScale)1745     public void setStaggerScale(float staggerScale) {
1746         mStaggerScale = staggerScale;
1747     }
1748 
1749     /**
1750      * set the offset used in calculating stagger launches
1751      *
1752      * @param staggerOffset fraction of progress before this controller runs
1753      */
setStaggerOffset(float staggerOffset)1754     public void setStaggerOffset(float staggerOffset) {
1755         mStaggerOffset = staggerOffset;
1756     }
1757 
1758     /**
1759      * The values set in
1760      * motion: {
1761      * stagger: '2'
1762      * }
1763      *
1764      * @return value from motion: { stagger: ? } or NaN if not set
1765      */
getMotionStagger()1766     public float getMotionStagger() {
1767         return mMotionStagger;
1768     }
1769 
setIdString(String stringId)1770     public void setIdString(String stringId) {
1771         mId = stringId;
1772         mStartMotionPath.mId = mId;
1773     }
1774 }
1775