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