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.key;
17 
18 import androidx.constraintlayout.core.motion.MotionWidget;
19 import androidx.constraintlayout.core.motion.utils.FloatRect;
20 import androidx.constraintlayout.core.motion.utils.SplineSet;
21 import androidx.constraintlayout.core.motion.utils.TypedValues;
22 
23 import java.util.HashMap;
24 import java.util.HashSet;
25 
26 public class MotionKeyPosition extends MotionKey {
27     static final String NAME = "KeyPosition";
28     protected static final float SELECTION_SLOPE = 20;
29     public int mCurveFit = UNSET;
30     public String mTransitionEasing = null;
31     public int mPathMotionArc = UNSET; // -1 means not set
32     public int mDrawPath = 0;
33     public float mPercentWidth = Float.NaN;
34     public float mPercentHeight = Float.NaN;
35     public float mPercentX = Float.NaN;
36     public float mPercentY = Float.NaN;
37     public float mAltPercentX = Float.NaN;
38     public float mAltPercentY = Float.NaN;
39     public static final int TYPE_SCREEN = 2;
40     public static final int TYPE_PATH = 1;
41     public static final int TYPE_CARTESIAN = 0;
42     public int mPositionType = TYPE_CARTESIAN;
43 
44     private float mCalculatedPositionX = Float.NaN;
45     private float mCalculatedPositionY = Float.NaN;
46     static final int KEY_TYPE = 2;
47 
48     {
49         mType = KEY_TYPE;
50     }
51 
52     // TODO this needs the views dimensions to be accurate
calcScreenPosition(int layoutWidth, int layoutHeight)53     private void calcScreenPosition(int layoutWidth, int layoutHeight) {
54         int viewWidth = 0;
55         int viewHeight = 0;
56         mCalculatedPositionX = (layoutWidth - viewWidth) * mPercentX + viewWidth / 2;
57         mCalculatedPositionY = (layoutHeight - viewHeight) * mPercentX + viewHeight / 2;
58     }
59 
calcPathPosition(float startX, float startY, float endX, float endY)60     private void calcPathPosition(float startX, float startY,
61             float endX, float endY) {
62         float pathVectorX = endX - startX;
63         float pathVectorY = endY - startY;
64         float perpendicularX = -pathVectorY;
65         float perpendicularY = pathVectorX;
66         mCalculatedPositionX = startX + pathVectorX * mPercentX + perpendicularX * mPercentY;
67         mCalculatedPositionY = startY + pathVectorY * mPercentX + perpendicularY * mPercentY;
68     }
69 
calcCartesianPosition(float startX, float startY, float endX, float endY)70     private void calcCartesianPosition(float startX, float startY,
71             float endX, float endY) {
72         float pathVectorX = endX - startX;
73         float pathVectorY = endY - startY;
74         float dxdx = Float.isNaN(mPercentX) ? 0 : mPercentX;
75         float dydx = Float.isNaN(mAltPercentY) ? 0 : mAltPercentY;
76         float dydy = Float.isNaN(mPercentY) ? 0 : mPercentY;
77         float dxdy = Float.isNaN(mAltPercentX) ? 0 : mAltPercentX;
78         mCalculatedPositionX = (int) (startX + pathVectorX * dxdx + pathVectorY * dxdy);
79         mCalculatedPositionY = (int) (startY + pathVectorX * dydx + pathVectorY * dydy);
80     }
81 
getPositionX()82     float getPositionX() {
83         return mCalculatedPositionX;
84     }
85 
getPositionY()86     float getPositionY() {
87         return mCalculatedPositionY;
88     }
89 
90     // @TODO: add description
positionAttributes(MotionWidget view, FloatRect start, FloatRect end, float x, float y, String[] attribute, float[] value)91     public void positionAttributes(MotionWidget view,
92             FloatRect start,
93             FloatRect end,
94             float x,
95             float y,
96             String[] attribute,
97             float[] value) {
98         switch (mPositionType) {
99 
100             case TYPE_PATH:
101                 positionPathAttributes(start, end, x, y, attribute, value);
102                 return;
103             case TYPE_SCREEN:
104                 positionScreenAttributes(view, start, end, x, y, attribute, value);
105                 return;
106             case TYPE_CARTESIAN:
107             default:
108                 positionCartAttributes(start, end, x, y, attribute, value);
109                 return;
110 
111         }
112     }
113 
positionPathAttributes(FloatRect start, FloatRect end, float x, float y, String[] attribute, float[] value)114     void positionPathAttributes(FloatRect start,
115             FloatRect end,
116             float x,
117             float y,
118             String[] attribute,
119             float[] value) {
120         float startCenterX = start.centerX();
121         float startCenterY = start.centerY();
122         float endCenterX = end.centerX();
123         float endCenterY = end.centerY();
124         float pathVectorX = endCenterX - startCenterX;
125         float pathVectorY = endCenterY - startCenterY;
126         float distance = (float) Math.hypot(pathVectorX, pathVectorY);
127         if (distance < 0.0001) {
128             System.out.println("distance ~ 0");
129             value[0] = 0;
130             value[1] = 0;
131             return;
132         }
133 
134         float dx = pathVectorX / distance;
135         float dy = pathVectorY / distance;
136         float perpendicular = (dx * (y - startCenterY) - (x - startCenterX) * dy) / distance;
137         float dist = (dx * (x - startCenterX) + dy * (y - startCenterY)) / distance;
138         if (attribute[0] != null) {
139             if (PositionType.S_PERCENT_X.equals(attribute[0])) {
140                 value[0] = dist;
141                 value[1] = perpendicular;
142             }
143         } else {
144             attribute[0] = PositionType.S_PERCENT_X;
145             attribute[1] = PositionType.S_PERCENT_Y;
146             value[0] = dist;
147             value[1] = perpendicular;
148         }
149     }
150 
positionScreenAttributes(MotionWidget view, FloatRect start, FloatRect end, float x, float y, String[] attribute, float[] value)151     void positionScreenAttributes(MotionWidget view,
152             FloatRect start,
153             FloatRect end,
154             float x,
155             float y,
156             String[] attribute,
157             float[] value) {
158         float startCenterX = start.centerX();
159         float startCenterY = start.centerY();
160         float endCenterX = end.centerX();
161         float endCenterY = end.centerY();
162         @SuppressWarnings("unused") float pathVectorX = endCenterX - startCenterX;
163         @SuppressWarnings("unused") float pathVectorY = endCenterY - startCenterY;
164         MotionWidget viewGroup = ((MotionWidget) view.getParent());
165         int width = viewGroup.getWidth();
166         int height = viewGroup.getHeight();
167 
168         if (attribute[0] != null) { // they are saying what to use
169             if (PositionType.S_PERCENT_X.equals(attribute[0])) {
170                 value[0] = x / width;
171                 value[1] = y / height;
172             } else {
173                 value[1] = x / width;
174                 value[0] = y / height;
175             }
176         } else { // we will use what we want to
177             attribute[0] = PositionType.S_PERCENT_X;
178             value[0] = x / width;
179             attribute[1] = PositionType.S_PERCENT_Y;
180             value[1] = y / height;
181         }
182     }
183 
positionCartAttributes(FloatRect start, FloatRect end, float x, float y, String[] attribute, float[] value)184     void positionCartAttributes(FloatRect start,
185             FloatRect end,
186             float x,
187             float y,
188             String[] attribute,
189             float[] value) {
190         float startCenterX = start.centerX();
191         float startCenterY = start.centerY();
192         float endCenterX = end.centerX();
193         float endCenterY = end.centerY();
194         float pathVectorX = endCenterX - startCenterX;
195         float pathVectorY = endCenterY - startCenterY;
196         if (attribute[0] != null) { // they are saying what to use
197             if (PositionType.S_PERCENT_X.equals(attribute[0])) {
198                 value[0] = (x - startCenterX) / pathVectorX;
199                 value[1] = (y - startCenterY) / pathVectorY;
200             } else {
201                 value[1] = (x - startCenterX) / pathVectorX;
202                 value[0] = (y - startCenterY) / pathVectorY;
203             }
204         } else { // we will use what we want to
205             attribute[0] = PositionType.S_PERCENT_X;
206             value[0] = (x - startCenterX) / pathVectorX;
207             attribute[1] = PositionType.S_PERCENT_Y;
208             value[1] = (y - startCenterY) / pathVectorY;
209         }
210     }
211 
212     // @TODO: add description
intersects(int layoutWidth, int layoutHeight, FloatRect start, FloatRect end, float x, float y)213     public boolean intersects(int layoutWidth,
214             int layoutHeight,
215             FloatRect start,
216             FloatRect end,
217             float x,
218             float y) {
219         calcPosition(layoutWidth, layoutHeight, start.centerX(),
220                 start.centerY(), end.centerX(), end.centerY());
221         if ((Math.abs(x - mCalculatedPositionX) < SELECTION_SLOPE)
222                 && (Math.abs(y - mCalculatedPositionY) < SELECTION_SLOPE)) {
223             return true;
224         }
225         return false;
226     }
227 
228     // @TODO: add description
229     @Override
copy(MotionKey src)230     public MotionKey copy(MotionKey src) {
231         super.copy(src);
232         MotionKeyPosition k = (MotionKeyPosition) src;
233         mTransitionEasing = k.mTransitionEasing;
234         mPathMotionArc = k.mPathMotionArc;
235         mDrawPath = k.mDrawPath;
236         mPercentWidth = k.mPercentWidth;
237         mPercentHeight = Float.NaN;
238         mPercentX = k.mPercentX;
239         mPercentY = k.mPercentY;
240         mAltPercentX = k.mAltPercentX;
241         mAltPercentY = k.mAltPercentY;
242         mCalculatedPositionX = k.mCalculatedPositionX;
243         mCalculatedPositionY = k.mCalculatedPositionY;
244         return this;
245     }
246 
247     // @TODO: add description
248     @Override
clone()249     public MotionKey clone() {
250         return new MotionKeyPosition().copy(this);
251     }
252 
calcPosition(int layoutWidth, int layoutHeight, float startX, float startY, float endX, float endY)253     void calcPosition(int layoutWidth,
254             int layoutHeight,
255             float startX,
256             float startY,
257             float endX,
258             float endY) {
259         switch (mPositionType) {
260             case TYPE_SCREEN:
261                 calcScreenPosition(layoutWidth, layoutHeight);
262                 return;
263 
264             case TYPE_PATH:
265                 calcPathPosition(startX, startY, endX, endY);
266                 return;
267             case TYPE_CARTESIAN:
268             default:
269                 calcCartesianPosition(startX, startY, endX, endY);
270                 return;
271         }
272     }
273 
274     @Override
getAttributeNames(HashSet<String> attributes)275     public void getAttributeNames(HashSet<String> attributes) {
276 
277     }
278 
279     // @TODO: add description
280 
281     /**
282      * @param splines splines to write values to
283      */
284     @Override
addValues(HashMap<String, SplineSet> splines)285     public void addValues(HashMap<String, SplineSet> splines) {
286     }
287 
288     @Override
setValue(int type, int value)289     public boolean setValue(int type, int value) {
290         switch (type) {
291             case PositionType.TYPE_POSITION_TYPE:
292                 mPositionType = value;
293                 break;
294             case TypedValues.TYPE_FRAME_POSITION:
295                 mFramePosition = value;
296                 break;
297             case PositionType.TYPE_CURVE_FIT:
298                 mCurveFit = value;
299                 break;
300 
301             default:
302                 return super.setValue(type, value);
303         }
304         return true;
305 
306     }
307 
308     @Override
setValue(int type, float value)309     public boolean setValue(int type, float value) {
310         switch (type) {
311             case PositionType.TYPE_PERCENT_WIDTH:
312                 mPercentWidth = value;
313                 break;
314             case PositionType.TYPE_PERCENT_HEIGHT:
315                 mPercentHeight = value;
316                 break;
317             case PositionType.TYPE_SIZE_PERCENT:
318                 mPercentHeight = mPercentWidth = value;
319                 break;
320             case PositionType.TYPE_PERCENT_X:
321                 mPercentX = value;
322                 break;
323             case PositionType.TYPE_PERCENT_Y:
324                 mPercentY = value;
325                 break;
326             default:
327                 return super.setValue(type, value);
328         }
329         return true;
330     }
331 
332     @Override
setValue(int type, String value)333     public boolean setValue(int type, String value) {
334         switch (type) {
335             case PositionType.TYPE_TRANSITION_EASING:
336                 mTransitionEasing = value.toString();
337                 break;
338             default:
339                 return super.setValue(type, value);
340         }
341         return true;
342     }
343 
344     @Override
getId(String name)345     public int getId(String name) {
346         return PositionType.getId(name);
347     }
348 
349 }
350