1 /* 2 * Copyright (C) 2019 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 17 package androidx.constraintlayout.core.widgets.analyzer; 18 19 import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL; 20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_PERCENT; 21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_RATIO; 22 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_SPREAD; 23 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_WRAP; 24 import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL; 25 26 import androidx.constraintlayout.core.widgets.ConstraintAnchor; 27 import androidx.constraintlayout.core.widgets.ConstraintWidget; 28 29 public abstract class WidgetRun implements Dependency { 30 public int matchConstraintsType; 31 ConstraintWidget mWidget; 32 RunGroup mRunGroup; 33 protected ConstraintWidget.DimensionBehaviour mDimensionBehavior; 34 DimensionDependency mDimension = new DimensionDependency(this); 35 36 public int orientation = HORIZONTAL; 37 boolean mResolved = false; 38 public DependencyNode start = new DependencyNode(this); 39 public DependencyNode end = new DependencyNode(this); 40 41 @SuppressWarnings("HiddenTypeParameter") 42 protected RunType mRunType = RunType.NONE; 43 WidgetRun(ConstraintWidget widget)44 public WidgetRun(ConstraintWidget widget) { 45 this.mWidget = widget; 46 } 47 48 @SuppressWarnings("HiddenAbstractMethod") clear()49 abstract void clear(); 50 51 @SuppressWarnings("HiddenAbstractMethod") apply()52 abstract void apply(); 53 54 @SuppressWarnings("HiddenAbstractMethod") applyToWidget()55 abstract void applyToWidget(); 56 57 @SuppressWarnings("HiddenAbstractMethod") reset()58 abstract void reset(); 59 60 @SuppressWarnings("HiddenAbstractMethod") supportsWrapComputation()61 abstract boolean supportsWrapComputation(); 62 isDimensionResolved()63 public boolean isDimensionResolved() { 64 return mDimension.resolved; 65 } 66 67 // @TODO: add description isCenterConnection()68 public boolean isCenterConnection() { 69 int connections = 0; 70 int count = start.mTargets.size(); 71 for (int i = 0; i < count; i++) { 72 DependencyNode dependency = start.mTargets.get(i); 73 if (dependency.mRun != this) { 74 connections++; 75 } 76 } 77 count = end.mTargets.size(); 78 for (int i = 0; i < count; i++) { 79 DependencyNode dependency = end.mTargets.get(i); 80 if (dependency.mRun != this) { 81 connections++; 82 } 83 } 84 return connections >= 2; 85 } 86 87 // @TODO: add description wrapSize(int direction)88 public long wrapSize(int direction) { 89 if (mDimension.resolved) { 90 long size = mDimension.value; 91 if (isCenterConnection()) { //start.targets.size() > 0 && end.targets.size() > 0) { 92 size += start.mMargin - end.mMargin; 93 } else { 94 if (direction == RunGroup.START) { 95 size += start.mMargin; 96 } else { 97 size -= end.mMargin; 98 } 99 } 100 return size; 101 } 102 return 0; 103 } 104 getTarget(ConstraintAnchor anchor)105 protected final DependencyNode getTarget(ConstraintAnchor anchor) { 106 if (anchor.mTarget == null) { 107 return null; 108 } 109 DependencyNode target = null; 110 ConstraintWidget targetWidget = anchor.mTarget.mOwner; 111 ConstraintAnchor.Type targetType = anchor.mTarget.mType; 112 switch (targetType) { 113 case LEFT: { 114 HorizontalWidgetRun run = targetWidget.mHorizontalRun; 115 target = run.start; 116 } 117 break; 118 case RIGHT: { 119 HorizontalWidgetRun run = targetWidget.mHorizontalRun; 120 target = run.end; 121 } 122 break; 123 case TOP: { 124 VerticalWidgetRun run = targetWidget.mVerticalRun; 125 target = run.start; 126 } 127 break; 128 case BASELINE: { 129 VerticalWidgetRun run = targetWidget.mVerticalRun; 130 target = run.baseline; 131 } 132 break; 133 case BOTTOM: { 134 VerticalWidgetRun run = targetWidget.mVerticalRun; 135 target = run.end; 136 } 137 break; 138 default: 139 break; 140 } 141 return target; 142 } 143 updateRunCenter(Dependency dependency, ConstraintAnchor startAnchor, ConstraintAnchor endAnchor, int orientation)144 protected void updateRunCenter(Dependency dependency, 145 ConstraintAnchor startAnchor, 146 ConstraintAnchor endAnchor, 147 int orientation) { 148 DependencyNode startTarget = getTarget(startAnchor); 149 DependencyNode endTarget = getTarget(endAnchor); 150 151 if (!(startTarget.resolved && endTarget.resolved)) { 152 return; 153 } 154 155 int startPos = startTarget.value + startAnchor.getMargin(); 156 int endPos = endTarget.value - endAnchor.getMargin(); 157 int distance = endPos - startPos; 158 159 if (!mDimension.resolved 160 && mDimensionBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) { 161 resolveDimension(orientation, distance); 162 } 163 164 if (!mDimension.resolved) { 165 return; 166 } 167 168 if (mDimension.value == distance) { 169 start.resolve(startPos); 170 end.resolve(endPos); 171 return; 172 } 173 174 // Otherwise, we have to center 175 float bias = orientation == HORIZONTAL ? mWidget.getHorizontalBiasPercent() 176 : mWidget.getVerticalBiasPercent(); 177 178 if (startTarget == endTarget) { 179 startPos = startTarget.value; 180 endPos = endTarget.value; 181 // TODO: taking advantage of bias here would be a nice feature to support, 182 // but for now let's stay compatible with 1.1 183 bias = 0.5f; 184 } 185 186 int availableDistance = (endPos - startPos - mDimension.value); 187 start.resolve((int) (0.5f + startPos + availableDistance * bias)); 188 end.resolve(start.value + mDimension.value); 189 } 190 resolveDimension(int orientation, int distance)191 private void resolveDimension(int orientation, int distance) { 192 switch (matchConstraintsType) { 193 case MATCH_CONSTRAINT_SPREAD: { 194 mDimension.resolve(getLimitedDimension(distance, orientation)); 195 } 196 break; 197 case MATCH_CONSTRAINT_PERCENT: { 198 ConstraintWidget parent = mWidget.getParent(); 199 if (parent != null) { 200 WidgetRun run = orientation == HORIZONTAL 201 ? parent.mHorizontalRun 202 : parent.mVerticalRun; 203 if (run.mDimension.resolved) { 204 float percent = orientation == HORIZONTAL 205 ? mWidget.mMatchConstraintPercentWidth 206 : mWidget.mMatchConstraintPercentHeight; 207 int targetDimensionValue = run.mDimension.value; 208 int size = (int) (0.5f + targetDimensionValue * percent); 209 mDimension.resolve(getLimitedDimension(size, orientation)); 210 } 211 } 212 } 213 break; 214 case MATCH_CONSTRAINT_WRAP: { 215 int wrapValue = getLimitedDimension(mDimension.wrapValue, orientation); 216 mDimension.resolve(Math.min(wrapValue, distance)); 217 } 218 break; 219 case MATCH_CONSTRAINT_RATIO: { 220 if (mWidget.mHorizontalRun.mDimensionBehavior 221 == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT 222 && mWidget.mHorizontalRun.matchConstraintsType == MATCH_CONSTRAINT_RATIO 223 && mWidget.mVerticalRun.mDimensionBehavior 224 == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT 225 && mWidget.mVerticalRun.matchConstraintsType == MATCH_CONSTRAINT_RATIO) { 226 // pof 227 } else { 228 WidgetRun run = (orientation == HORIZONTAL) 229 ? mWidget.mVerticalRun : mWidget.mHorizontalRun; 230 if (run.mDimension.resolved) { 231 float ratio = mWidget.getDimensionRatio(); 232 int value; 233 if (orientation == VERTICAL) { 234 value = (int) (0.5f + run.mDimension.value / ratio); 235 } else { 236 value = (int) (0.5f + ratio * run.mDimension.value); 237 } 238 mDimension.resolve(value); 239 } 240 } 241 } 242 break; 243 default: 244 break; 245 } 246 } 247 updateRunStart(Dependency dependency)248 protected void updateRunStart(Dependency dependency) { 249 250 } 251 updateRunEnd(Dependency dependency)252 protected void updateRunEnd(Dependency dependency) { 253 254 } 255 256 // @TODO: add description 257 @Override update(Dependency dependency)258 public void update(Dependency dependency) { 259 } 260 getLimitedDimension(int dimension, int orientation)261 protected final int getLimitedDimension(int dimension, int orientation) { 262 if (orientation == HORIZONTAL) { 263 int max = mWidget.mMatchConstraintMaxWidth; 264 int min = mWidget.mMatchConstraintMinWidth; 265 int value = Math.max(min, dimension); 266 if (max > 0) { 267 value = Math.min(max, dimension); 268 } 269 if (value != dimension) { 270 dimension = value; 271 } 272 } else { 273 int max = mWidget.mMatchConstraintMaxHeight; 274 int min = mWidget.mMatchConstraintMinHeight; 275 int value = Math.max(min, dimension); 276 if (max > 0) { 277 value = Math.min(max, dimension); 278 } 279 if (value != dimension) { 280 dimension = value; 281 } 282 } 283 return dimension; 284 } 285 getTarget(ConstraintAnchor anchor, int orientation)286 protected final DependencyNode getTarget(ConstraintAnchor anchor, int orientation) { 287 if (anchor.mTarget == null) { 288 return null; 289 } 290 DependencyNode target = null; 291 ConstraintWidget targetWidget = anchor.mTarget.mOwner; 292 WidgetRun run = (orientation == ConstraintWidget.HORIZONTAL) 293 ? targetWidget.mHorizontalRun : targetWidget.mVerticalRun; 294 ConstraintAnchor.Type targetType = anchor.mTarget.mType; 295 switch (targetType) { 296 case TOP: 297 case LEFT: { 298 target = run.start; 299 } 300 break; 301 case BOTTOM: 302 case RIGHT: { 303 target = run.end; 304 } 305 break; 306 default: 307 break; 308 } 309 return target; 310 } 311 addTarget(DependencyNode node, DependencyNode target, int margin)312 protected final void addTarget(DependencyNode node, 313 DependencyNode target, 314 int margin) { 315 node.mTargets.add(target); 316 node.mMargin = margin; 317 target.mDependencies.add(node); 318 } 319 addTarget(DependencyNode node, DependencyNode target, int marginFactor, @SuppressWarnings("HiddenTypeParameter") DimensionDependency dimensionDependency)320 protected final void addTarget(DependencyNode node, 321 DependencyNode target, 322 int marginFactor, 323 @SuppressWarnings("HiddenTypeParameter") DimensionDependency 324 dimensionDependency) { 325 node.mTargets.add(target); 326 node.mTargets.add(mDimension); 327 node.mMarginFactor = marginFactor; 328 node.mMarginDependency = dimensionDependency; 329 target.mDependencies.add(node); 330 dimensionDependency.mDependencies.add(node); 331 } 332 333 // @TODO: add description getWrapDimension()334 public long getWrapDimension() { 335 if (mDimension.resolved) { 336 return mDimension.value; 337 } 338 return 0; 339 } 340 isResolved()341 public boolean isResolved() { 342 return mResolved; 343 } 344 345 enum RunType {NONE, START, END, CENTER} 346 } 347